Pull from trunk

This commit is contained in:
Kovid Goyal 2009-04-12 12:16:16 -07:00
commit 0b59ff9c09
14 changed files with 284 additions and 104 deletions

View File

@ -262,6 +262,17 @@ class MOBIMetadataWriter(MetadataWriterPlugin):
def set_metadata(self, stream, mi, type): def set_metadata(self, stream, mi, type):
from calibre.ebooks.metadata.mobi import set_metadata from calibre.ebooks.metadata.mobi import set_metadata
set_metadata(stream, mi) set_metadata(stream, mi)
class PDFMetadataWriter(MetadataWriterPlugin):
name = 'Set PDF metadata'
file_types = set(['pdf'])
description = _('Set metadata in %s files') % 'PDF'
author = 'John Schember'
def set_metadata(self, stream, mi, type):
from calibre.ebooks.metadata.pdf import set_metadata
set_metadata(stream, mi)
from calibre.ebooks.epub.input import EPUBInput from calibre.ebooks.epub.input import EPUBInput

View File

@ -2,7 +2,7 @@ from __future__ import with_statement
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, shutil, traceback, functools, sys import os, shutil, traceback, functools, sys, re
from calibre.customize import Plugin, FileTypePlugin, MetadataReaderPlugin, \ from calibre.customize import Plugin, FileTypePlugin, MetadataReaderPlugin, \
MetadataWriterPlugin MetadataWriterPlugin
@ -55,7 +55,14 @@ def load_plugin(path_to_zip_file):
for name in zf.namelist(): for name in zf.namelist():
if name.lower().endswith('plugin.py'): if name.lower().endswith('plugin.py'):
locals = {} locals = {}
exec zf.read(name) in locals raw = zf.read(name)
match = re.search(r'coding[:=]\s*([-\w.]+)', raw[:300])
encoding = 'utf-8'
if match is not None:
encoding = match.group(1)
raw = raw.decode(encoding)
raw = re.sub('\r\n', '\n', raw)
exec raw in locals
for x in locals.values(): for x in locals.values():
if isinstance(x, type) and issubclass(x, Plugin): if isinstance(x, type) and issubclass(x, Plugin):
if x.minimum_calibre_version > version or \ if x.minimum_calibre_version > version or \

View File

@ -31,6 +31,11 @@ Run an embedded python interpreter.
parser.add_option('--migrate', action='store_true', default=False, parser.add_option('--migrate', action='store_true', default=False,
help='Migrate old database. Needs two arguments. Path ' help='Migrate old database. Needs two arguments. Path '
'to library1.db and path to new library folder.') 'to library1.db and path to new library folder.')
parser.add_option('--add-simple-plugin', default=None,
help='Add a simple plugin (i.e. a plugin that consists of only a '
'.py file), by specifying the path to the py file containing the '
'plugin code.')
return parser return parser
def update_zipfile(zipfile, mod, path): def update_zipfile(zipfile, mod, path):
@ -115,6 +120,22 @@ def debug_device_driver():
print 'Total space:', d.total_space() print 'Total space:', d.total_space()
break break
def add_simple_plugin(path_to_plugin):
import tempfile, zipfile, shutil
tdir = tempfile.mkdtemp()
open(os.path.join(tdir, 'custom_plugin.py'),
'wb').write(open(path_to_plugin, 'rb').read())
odir = os.getcwd()
os.chdir(tdir)
zf = zipfile.ZipFile('plugin.zip', 'w')
zf.write('custom_plugin.py')
zf.close()
from calibre.customize.ui import main
main(['calibre-customize', '-a', 'plugin.zip'])
os.chdir(odir)
shutil.rmtree(tdir)
def main(args=sys.argv): def main(args=sys.argv):
opts, args = option_parser().parse_args(args) opts, args = option_parser().parse_args(args)
@ -137,6 +158,8 @@ def main(args=sys.argv):
print 'You must specify the path to library1.db and the path to the new library folder' print 'You must specify the path to library1.db and the path to the new library folder'
return 1 return 1
migrate(args[1], args[2]) migrate(args[1], args[2])
elif opts.add_simple_plugin is not None:
add_simple_plugin(opts.add_simple_plugin)
else: else:
from IPython.Shell import IPShellEmbed from IPython.Shell import IPShellEmbed
ipshell = IPShellEmbed() ipshell = IPShellEmbed()

View File

@ -57,12 +57,17 @@ class USBMS(Device):
prefix = self._card_prefix if oncard else self._main_prefix prefix = self._card_prefix if oncard else self._main_prefix
ebook_dir = self.EBOOK_DIR_CARD if oncard else self.EBOOK_DIR_MAIN ebook_dir = self.EBOOK_DIR_CARD if oncard else self.EBOOK_DIR_MAIN
# Get all books in all directories under the root ebook_dir directory # Get all books in the ebook_dir directory
for path, dirs, files in os.walk(os.path.join(prefix, ebook_dir)): if self.SUPPORTS_SUB_DIRS:
# Filter out anything that isn't in the list of supported ebook for path, dirs, files in os.walk(os.path.join(prefix, ebook_dir)):
# types # Filter out anything that isn't in the list of supported ebook types
for book_type in self.FORMATS: for book_type in self.FORMATS:
for filename in fnmatch.filter(files, '*.%s' % (book_type)): for filename in fnmatch.filter(files, '*.%s' % (book_type)):
bl.append(self.__class__.book_from_path(os.path.join(path, filename)))
else:
path = os.path.join(prefix, ebook_dir)
for filename in os.listdir(path):
if path_to_ext(filename) in self.FORMATS:
bl.append(self.__class__.book_from_path(os.path.join(path, filename))) bl.append(self.__class__.book_from_path(os.path.join(path, filename)))
return bl return bl

View File

@ -1,11 +1,10 @@
'''Read meta information from PDF files'''
from __future__ import with_statement from __future__ import with_statement
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'''Read meta information from PDF files'''
import sys, os, re, StringIO import sys, os, StringIO
from calibre.ebooks.metadata import MetaInformation, authors_to_string from calibre.ebooks.metadata import MetaInformation, authors_to_string
from calibre.ptempfile import TemporaryDirectory from calibre.ptempfile import TemporaryDirectory
@ -31,7 +30,7 @@ def get_metadata(stream, extract_cover=True):
except: except:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
try: try:
info = PdfFileReader(stream).getDocumentInfo() info = PdfFileReader(stream).getDocumentInfo()
if info.title: if info.title:
@ -52,47 +51,56 @@ def get_metadata(stream, extract_cover=True):
def set_metadata(stream, mi): def set_metadata(stream, mi):
stream.seek(0) stream.seek(0)
raw = stream.read()
if mi.title: # Use a StringIO object for the pdf because we will want to over
tit = mi.title.encode('utf-8') if isinstance(mi.title, unicode) else mi.title # write it later and if we are working on the stream directly it
raw = re.compile(r'<<.*?/Title\((.+?)\)', re.DOTALL).sub(lambda m: m.group().replace(m.group(1), tit), raw) # could cause some issues.
if mi.authors: raw = StringIO.StringIO(stream.read())
au = authors_to_string(mi.authors) orig_pdf = PdfFileReader(raw)
if isinstance(au, unicode):
au = au.encode('utf-8') title = mi.title if mi.title else orig_pdf.documentInfo.title
raw = re.compile(r'<<.*?/Author\((.+?)\)', re.DOTALL).sub(lambda m: m.group().replace(m.group(1), au), raw) author = authors_to_string(mi.authors) if mi.authors else orig_pdf.documentInfo.author
out_pdf = PdfFileWriter(title=title, author=author)
for page in orig_pdf.pages:
out_pdf.addPage(page)
out_str = StringIO.StringIO()
out_pdf.write(out_str)
stream.seek(0) stream.seek(0)
stream.truncate() stream.truncate()
stream.write(raw) out_str.seek(0)
stream.write(out_str.read())
stream.seek(0) stream.seek(0)
def get_cover(stream): def get_cover(stream):
data = StringIO.StringIO() data = StringIO.StringIO()
try: try:
pdf = PdfFileReader(stream) pdf = PdfFileReader(stream)
output = PdfFileWriter() output = PdfFileWriter()
if len(pdf.pages) >= 1: if len(pdf.pages) >= 1:
output.addPage(pdf.getPage(0)) output.addPage(pdf.getPage(0))
with TemporaryDirectory('_pdfmeta') as tdir: with TemporaryDirectory('_pdfmeta') as tdir:
cover_path = os.path.join(tdir, 'cover.pdf') cover_path = os.path.join(tdir, 'cover.pdf')
outputStream = file(cover_path, "wb") outputStream = file(cover_path, "wb")
output.write(outputStream) output.write(outputStream)
outputStream.close() outputStream.close()
wand = NewMagickWand() wand = NewMagickWand()
MagickReadImage(wand, cover_path) MagickReadImage(wand, cover_path)
MagickSetImageFormat(wand, 'JPEG') MagickSetImageFormat(wand, 'JPEG')
MagickWriteImage(wand, '%s.jpg' % cover_path) MagickWriteImage(wand, '%s.jpg' % cover_path)
img = Image.open('%s.jpg' % cover_path) img = Image.open('%s.jpg' % cover_path)
img.save(data, 'JPEG') img.save(data, 'JPEG')
except: except:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return data.getvalue() return data.getvalue()

View File

@ -15,7 +15,8 @@ except ImportError:
from lxml import html, etree from lxml import html, etree
from calibre import entity_to_unicode from calibre import entity_to_unicode, sanitize_file_name
from calibre.ptempfile import TemporaryDirectory
from calibre.ebooks import DRMError from calibre.ebooks import DRMError
from calibre.ebooks.chardet import ENCODING_PATS from calibre.ebooks.chardet import ENCODING_PATS
from calibre.ebooks.mobi import MobiError from calibre.ebooks.mobi import MobiError
@ -25,7 +26,6 @@ from calibre.ebooks.mobi.langcodes import main_language, sub_language
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
from calibre.ebooks.metadata.opf2 import OPFCreator, OPF from calibre.ebooks.metadata.opf2 import OPFCreator, OPF
from calibre.ebooks.metadata.toc import TOC from calibre.ebooks.metadata.toc import TOC
from calibre import sanitize_file_name
class EXTHHeader(object): class EXTHHeader(object):
@ -157,6 +157,62 @@ class BookHeader(object):
self.exth.mi.language = self.language self.exth.mi.language = self.language
class MetadataHeader(BookHeader):
def __init__(self, stream):
self.stream = stream
self.ident = self.identity()
self.num_sections = self.section_count()
if self.num_sections >= 2:
header = self.header()
BookHeader.__init__(self, header, self.ident, None)
else:
self.exth = None
def identity(self):
self.stream.seek(60)
ident = self.stream.read(8).upper()
if ident not in ['BOOKMOBI', 'TEXTREAD']:
raise MobiError('Unknown book type: %s' % ident)
return ident
def section_count(self):
self.stream.seek(76)
return struct.unpack('>H', self.stream.read(2))[0]
def section_offset(self, number):
self.stream.seek(78+number*8)
return struct.unpack('>LBBBB', self.stream.read(8))[0]
def header(self):
section_headers = []
# First section with the metadata
section_headers.append(self.section_offset(0))
# Second section used to get the lengh of the first
section_headers.append(self.section_offset(1))
end_off = section_headers[1]
off = section_headers[0]
self.stream.seek(off)
return self.stream.read(end_off - off)
def section_data(self, number):
start = self.section_offset(number)
if number == self.num_sections -1:
end = os.stat(self.stream.name).st_size
else:
end = self.section_offset(number + 1)
self.stream.seek(start)
return self.stream.read(end - start)
class MobiReader(object): class MobiReader(object):
PAGE_BREAK_PAT = re.compile(r'(<[/]{0,1}mbp:pagebreak\s*[/]{0,1}>)+', re.IGNORECASE) PAGE_BREAK_PAT = re.compile(r'(<[/]{0,1}mbp:pagebreak\s*[/]{0,1}>)+', re.IGNORECASE)
IMAGE_ATTRS = ('lowrecindex', 'recindex', 'hirecindex') IMAGE_ATTRS = ('lowrecindex', 'recindex', 'hirecindex')
@ -593,27 +649,34 @@ class MobiReader(object):
im.convert('RGB').save(open(path, 'wb'), format='JPEG') im.convert('RGB').save(open(path, 'wb'), format='JPEG')
def get_metadata(stream): def get_metadata(stream):
from calibre.utils.logging import Log mi = MetaInformation(os.path.basename(stream.name), [_('Unknown')])
log = Log() try:
mr = MobiReader(stream, log) mh = MetadataHeader(stream)
if mr.book_header.exth is None:
mi = MetaInformation(mr.name, [_('Unknown')]) if mh.exth is not None:
else: if mh.exth.mi is not None:
mi = mr.create_opf('dummy.html')[0] mi = mh.exth.mi
try: else:
if hasattr(mr.book_header.exth, 'cover_offset'): with TemporaryDirectory('_mobi_meta_reader') as tdir:
cover_index = mr.book_header.first_image_index + \ mr = MobiReader(stream)
mr.book_header.exth.cover_offset mr.extract_content(tdir, {})
data = mr.sections[int(cover_index)][0] if mr.embedded_mi is not None:
else: mi = mr.embedded_mi
data = mr.sections[mr.book_header.first_image_index][0]
buf = cStringIO.StringIO(data) if hasattr(mh.exth, 'cover_offset'):
im = PILImage.open(buf) cover_index = mh.first_image_index + mh.exth.cover_offset
obuf = cStringIO.StringIO() data = mh.section_data(int(cover_index))
im.convert('RGBA').save(obuf, format='JPEG') else:
mi.cover_data = ('jpg', obuf.getvalue()) data = mh.section_data(mh.first_image_index)
except: buf = cStringIO.StringIO(data)
log.exception() im = PILImage.open(buf)
obuf = cStringIO.StringIO()
im.convert('RGBA').save(obuf, format='JPEG')
mi.cover_data = ('jpg', obuf.getvalue())
except:
import traceback
traceback.print_exc()
return mi return mi

View File

@ -67,6 +67,8 @@ def _config():
c.add_opt('default_send_to_device_action', default=None, c.add_opt('default_send_to_device_action', default=None,
help=_('Default action to perform when send to device button is ' help=_('Default action to perform when send to device button is '
'clicked')) 'clicked'))
c.add_opt('show_donate_button', default=True,
help='Show donation button')
return ConfigProxy(c) return ConfigProxy(c)
config = _config() config = _config()

View File

@ -658,7 +658,9 @@ class DeviceGUI(object):
bad = '\n'.join('<li>%s</li>'%(i,) for i in bad) bad = '\n'.join('<li>%s</li>'%(i,) for i in bad)
d = warning_dialog(self, _('No suitable formats'), d = warning_dialog(self, _('No suitable formats'),
_('Could not upload the following books to the device, ' _('Could not upload the following books to the device, '
'as no suitable formats were found:<br><ul>%s</ul>')%(bad,)) 'as no suitable formats were found. Try changing the output '
'format in the upper right corner next to the red heart and '
're-converting. <br><ul>%s</ul>')%(bad,))
d.exec_() d.exec_()
def upload_booklists(self): def upload_booklists(self):

View File

@ -176,19 +176,19 @@ class Config(ResizableDialog, Ui_Dialog):
def get_metadata(self): def get_metadata(self):
title, authors = self.get_title_and_authors() title, authors = self.get_title_and_authors()
mi = MetaInformation(title, authors) mi = MetaInformation(title, authors)
publisher = unicode(self.publisher.text()) publisher = unicode(self.publisher.text()).strip()
if publisher: if publisher:
mi.publisher = publisher mi.publisher = publisher
author_sort = unicode(self.author_sort.text()) author_sort = unicode(self.author_sort.text()).strip()
if author_sort: if author_sort:
mi.author_sort = author_sort mi.author_sort = author_sort
comments = unicode(self.comment.toPlainText()) comments = unicode(self.comment.toPlainText()).strip()
if comments: if comments:
mi.comments = comments mi.comments = comments
mi.series_index = int(self.series_index.value()) mi.series_index = int(self.series_index.value())
if self.series.currentIndex() > -1: if self.series.currentIndex() > -1:
mi.series = unicode(self.series.currentText()) mi.series = unicode(self.series.currentText()).strip()
tags = [t.strip() for t in unicode(self.tags.text()).split(',')] tags = [t.strip() for t in unicode(self.tags.text()).strip().split(',')]
if tags: if tags:
mi.tags = tags mi.tags = tags
@ -267,6 +267,7 @@ class Config(ResizableDialog, Ui_Dialog):
).exec_() ).exec_()
return return
mi = self.get_metadata() mi = self.get_metadata()
self.user_mi = mi
self.read_settings() self.read_settings()
self.cover_file = None self.cover_file = None
if self.row is not None: if self.row is not None:

View File

@ -108,6 +108,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.donate_action = self.system_tray_menu.addAction( self.donate_action = self.system_tray_menu.addAction(
QIcon(':/images/donate.svg'), _('&Donate to support calibre')) QIcon(':/images/donate.svg'), _('&Donate to support calibre'))
self.donate_button.setDefaultAction(self.donate_action) self.donate_button.setDefaultAction(self.donate_action)
if not config['show_donate_button']:
self.donate_button.setVisible(False)
self.addAction(self.quit_action) self.addAction(self.quit_action)
self.action_restart = QAction(_('&Restart'), self) self.action_restart = QAction(_('&Restart'), self)
self.addAction(self.action_restart) self.addAction(self.action_restart)

View File

@ -25,7 +25,7 @@ from calibre.ebooks.lrf.comic.convert_from import config as comicconfig
# Ordered list of source formats. Items closer to the beginning are # Ordered list of source formats. Items closer to the beginning are
# preferred for conversion over those toward the end. # preferred for conversion over those toward the end.
PREFERRED_SOURCE_FORMATS = ['epub', 'lit', 'mobi', 'prc', 'azw', 'fb2', 'odt', 'rtf', PREFERRED_SOURCE_FORMATS = ['epub', 'lit', 'mobi', 'prc', 'azw', 'fb2', 'odt', 'rtf',
'txt', 'pdf', 'oebzip', 'htm', 'html'] 'txt', 'pdf', 'oebzip', 'htm', 'html']
def get_dialog(fmt): def get_dialog(fmt):
@ -43,20 +43,20 @@ def get_config(fmt):
def auto_convert(fmt, parent, db, rows): def auto_convert(fmt, parent, db, rows):
changed = False changed = False
jobs = [] jobs = []
total = len(rows) total = len(rows)
if total == 0: if total == 0:
return None, None, None return None, None, None
parent.status_bar.showMessage(_('Starting auto conversion of %d books')%total, 2000) parent.status_bar.showMessage(_('Starting auto conversion of %d books')%total, 2000)
i = 0 i = 0
bad_rows = [] bad_rows = []
for i, row in enumerate(rows): for i, row in enumerate(rows):
row_id = db.id(row) row_id = db.id(row)
temp_files = [] temp_files = []
data = None data = None
in_formats = [f.lower() for f in db.formats(row).split(',')] in_formats = [f.lower() for f in db.formats(row).split(',')]
in_formats = list(set(in_formats).intersection(available_input_formats())) in_formats = list(set(in_formats).intersection(available_input_formats()))
@ -88,10 +88,10 @@ def auto_convert(fmt, parent, db, rows):
for row in bad_rows: for row in bad_rows:
title = db.title(row) title = db.title(row)
res.append('<li>%s</li>'%title) res.append('<li>%s</li>'%title)
msg = _('<p>Could not convert %d of %d books, because no suitable source format was found.<ul>%s</ul>')%(len(res), total, '\n'.join(res)) msg = _('<p>Could not convert %d of %d books, because no suitable source format was found.<ul>%s</ul>')%(len(res), total, '\n'.join(res))
warning_dialog(parent, _('Could not convert some books'), msg).exec_() warning_dialog(parent, _('Could not convert some books'), msg).exec_()
return jobs, changed, bad_rows return jobs, changed, bad_rows
def convert_single(fmt, parent, db, comics, others): def convert_single(fmt, parent, db, comics, others):
@ -120,10 +120,10 @@ def convert_single(fmt, parent, db, comics, others):
temp_files.append(d.cover_file) temp_files.append(d.cover_file)
opts.cover = d.cover_file.name opts.cover = d.cover_file.name
temp_files.extend([d.opf_file, pt, of]) temp_files.extend([d.opf_file, pt, of])
jobs.append(('any2'+fmt, args, _('Convert book: ')+d.mi.title, jobs.append(('any2'+fmt, args, _('Convert book: ')+d.mi.title,
fmt.upper(), row_id, temp_files)) fmt.upper(), row_id, temp_files))
changed = True changed = True
for row, row_id in zip(comics, comics_ids): for row, row_id in zip(comics, comics_ids):
mi = db.get_metadata(row) mi = db.get_metadata(row)
title = author = _('Unknown') title = author = _('Unknown')
@ -140,7 +140,7 @@ def convert_single(fmt, parent, db, comics, others):
try: try:
data = db.format(row, _fmt.upper()) data = db.format(row, _fmt.upper())
if data is not None: if data is not None:
break break
except: except:
continue continue
pt = PersistentTemporaryFile('.'+_fmt) pt = PersistentTemporaryFile('.'+_fmt)
@ -152,12 +152,12 @@ def convert_single(fmt, parent, db, comics, others):
opts.verbose = 2 opts.verbose = 2
args = [pt.name, opts] args = [pt.name, opts]
changed = True changed = True
jobs.append(('comic2'+fmt, args, _('Convert comic: ')+opts.title, jobs.append(('comic2'+fmt, args, _('Convert comic: ')+opts.title,
fmt.upper(), row_id, [pt, of])) fmt.upper(), row_id, [pt, of]))
return jobs, changed return jobs, changed
def convert_single_lrf(parent, db, comics, others): def convert_single_lrf(parent, db, comics, others):
changed = False changed = False
@ -182,10 +182,10 @@ def convert_single_lrf(parent, db, comics, others):
if d.cover_file: if d.cover_file:
temp_files.append(d.cover_file) temp_files.append(d.cover_file)
temp_files.extend([pt, of]) temp_files.extend([pt, of])
jobs.append(('any2lrf', [cmdline], _('Convert book: ')+d.title(), jobs.append(('any2lrf', [cmdline], _('Convert book: ')+d.title(),
'LRF', row_id, temp_files)) 'LRF', row_id, temp_files))
changed = True changed = True
for row, row_id in zip(comics, comics_ids): for row, row_id in zip(comics, comics_ids):
mi = db.get_metadata(row) mi = db.get_metadata(row)
title = author = _('Unknown') title = author = _('Unknown')
@ -202,7 +202,7 @@ def convert_single_lrf(parent, db, comics, others):
try: try:
data = db.format(row, fmt.upper()) data = db.format(row, fmt.upper())
if data is not None: if data is not None:
break break
except: except:
continue continue
if data is None: if data is None:
@ -216,19 +216,20 @@ def convert_single_lrf(parent, db, comics, others):
opts.verbose = 1 opts.verbose = 1
args = [pt.name, opts] args = [pt.name, opts]
changed = True changed = True
jobs.append(('comic2lrf', args, _('Convert comic: ')+opts.title, jobs.append(('comic2lrf', args, _('Convert comic: ')+opts.title,
'LRF', row_id, [pt, of])) 'LRF', row_id, [pt, of]))
return jobs, changed return jobs, changed
def convert_bulk(fmt, parent, db, comics, others): def convert_bulk(fmt, parent, db, comics, others):
if others: if others:
d = get_dialog(fmt)(parent, db) d = get_dialog(fmt)(parent, db)
if d.exec_() != QDialog.Accepted: if d.exec_() != QDialog.Accepted:
others = [] others, user_mi = [], None
else: else:
opts = d.opts opts = d.opts
opts.verbose = 2 opts.verbose = 2
user_mi = d.user_mi
if comics: if comics:
comic_opts = ComicConf.get_bulk_conversion_options(parent) comic_opts = ComicConf.get_bulk_conversion_options(parent)
if not comic_opts: if not comic_opts:
@ -239,7 +240,7 @@ def convert_bulk(fmt, parent, db, comics, others):
if total == 0: if total == 0:
return return
parent.status_bar.showMessage(_('Starting Bulk conversion of %d books')%total, 2000) parent.status_bar.showMessage(_('Starting Bulk conversion of %d books')%total, 2000)
for i, row in enumerate(others+comics): for i, row in enumerate(others+comics):
row_id = db.id(row) row_id = db.id(row)
if row in others: if row in others:
@ -256,6 +257,11 @@ def convert_bulk(fmt, parent, db, comics, others):
continue continue
options = opts.copy() options = opts.copy()
mi = db.get_metadata(row) mi = db.get_metadata(row)
if user_mi is not None:
if user_mi.series_index == 1:
user_mi.series_index = None
mi.smart_update(user_mi)
db.set_metadata(db.id(row), mi)
opf = OPFCreator(os.getcwdu(), mi) opf = OPFCreator(os.getcwdu(), mi)
opf_file = PersistentTemporaryFile('.opf') opf_file = PersistentTemporaryFile('.opf')
opf.render(opf_file) opf.render(opf_file)
@ -291,10 +297,10 @@ def convert_bulk(fmt, parent, db, comics, others):
try: try:
data = db.format(row, _fmt.upper()) data = db.format(row, _fmt.upper())
if data is not None: if data is not None:
break break
except: except:
continue continue
pt = PersistentTemporaryFile('.'+_fmt.lower()) pt = PersistentTemporaryFile('.'+_fmt.lower())
pt.write(data) pt.write(data)
pt.close() pt.close()
@ -304,17 +310,17 @@ def convert_bulk(fmt, parent, db, comics, others):
options.verbose = 1 options.verbose = 1
args = [pt.name, options] args = [pt.name, options]
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title)) desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
jobs.append(('comic2'+fmt, args, desc, fmt.upper(), row_id, [pt, of])) jobs.append(('comic2'+fmt, args, desc, fmt.upper(), row_id, [pt, of]))
if bad_rows: if bad_rows:
res = [] res = []
for row in bad_rows: for row in bad_rows:
title = db.title(row) title = db.title(row)
res.append('<li>%s</li>'%title) res.append('<li>%s</li>'%title)
msg = _('<p>Could not convert %d of %d books, because no suitable source format was found.<ul>%s</ul>')%(len(res), total, '\n'.join(res)) msg = _('<p>Could not convert %d of %d books, because no suitable source format was found.<ul>%s</ul>')%(len(res), total, '\n'.join(res))
warning_dialog(parent, _('Could not convert some books'), msg).exec_() warning_dialog(parent, _('Could not convert some books'), msg).exec_()
return jobs, False return jobs, False
@ -333,7 +339,7 @@ def convert_bulk_lrf(parent, db, comics, others):
if total == 0: if total == 0:
return return
parent.status_bar.showMessage(_('Starting Bulk conversion of %d books')%total, 2000) parent.status_bar.showMessage(_('Starting Bulk conversion of %d books')%total, 2000)
for i, row in enumerate(others+comics): for i, row in enumerate(others+comics):
row_id = db.id(row) row_id = db.id(row)
if row in others: if row in others:
@ -388,10 +394,10 @@ def convert_bulk_lrf(parent, db, comics, others):
try: try:
data = db.format(row, fmt.upper()) data = db.format(row, fmt.upper())
if data is not None: if data is not None:
break break
except: except:
continue continue
pt = PersistentTemporaryFile('.'+fmt.lower()) pt = PersistentTemporaryFile('.'+fmt.lower())
pt.write(data) pt.write(data)
pt.close() pt.close()
@ -401,17 +407,17 @@ def convert_bulk_lrf(parent, db, comics, others):
options.verbose = 1 options.verbose = 1
args = [pt.name, options] args = [pt.name, options]
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title)) desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
jobs.append(('comic2lrf', args, desc, 'LRF', row_id, [pt, of])) jobs.append(('comic2lrf', args, desc, 'LRF', row_id, [pt, of]))
if bad_rows: if bad_rows:
res = [] res = []
for row in bad_rows: for row in bad_rows:
title = db.title(row) title = db.title(row)
res.append('<li>%s</li>'%title) res.append('<li>%s</li>'%title)
msg = _('<p>Could not convert %d of %d books, because no suitable source format was found.<ul>%s</ul>')%(len(res), total, '\n'.join(res)) msg = _('<p>Could not convert %d of %d books, because no suitable source format was found.<ul>%s</ul>')%(len(res), total, '\n'.join(res))
warning_dialog(parent, _('Could not convert some books'), msg).exec_() warning_dialog(parent, _('Could not convert some books'), msg).exec_()
return jobs, False return jobs, False
def set_conversion_defaults_lrf(comic, parent, db): def set_conversion_defaults_lrf(comic, parent, db):
@ -438,7 +444,7 @@ def _fetch_news(data, fmt):
args.extend(['--password', data['password']]) args.extend(['--password', data['password']])
args.append(data['script'] if data['script'] else data['title']) args.append(data['script'] if data['script'] else data['title'])
return 'feeds2'+fmt.lower(), [args], _('Fetch news from ')+data['title'], fmt.upper(), [pt] return 'feeds2'+fmt.lower(), [args], _('Fetch news from ')+data['title'], fmt.upper(), [pt]
def fetch_scheduled_recipe(recipe, script): def fetch_scheduled_recipe(recipe, script):
from calibre.gui2.dialogs.scheduler import config from calibre.gui2.dialogs.scheduler import config
@ -453,7 +459,7 @@ def fetch_scheduled_recipe(recipe, script):
args.extend(['--username', x[0], '--password', x[1]]) args.extend(['--username', x[0], '--password', x[1]])
args.append(script) args.append(script)
return 'feeds2'+fmt, [args], _('Fetch news from ')+recipe.title, fmt.upper(), [pt] return 'feeds2'+fmt, [args], _('Fetch news from ')+recipe.title, fmt.upper(), [pt]
def auto_convert_ebook(*args): def auto_convert_ebook(*args):
return auto_convert(*args) return auto_convert(*args)
@ -463,14 +469,14 @@ def convert_single_ebook(*args):
return convert_single_lrf(*args) return convert_single_lrf(*args)
elif fmt in ('epub', 'mobi'): elif fmt in ('epub', 'mobi'):
return convert_single(fmt, *args) return convert_single(fmt, *args)
def convert_bulk_ebooks(*args): def convert_bulk_ebooks(*args):
fmt = prefs['output_format'].lower() fmt = prefs['output_format'].lower()
if fmt == 'lrf': if fmt == 'lrf':
return convert_bulk_lrf(*args) return convert_bulk_lrf(*args)
elif fmt in ('epub', 'mobi'): elif fmt in ('epub', 'mobi'):
return convert_bulk(fmt, *args) return convert_bulk(fmt, *args)
def set_conversion_defaults(comic, parent, db): def set_conversion_defaults(comic, parent, db):
fmt = prefs['output_format'].lower() fmt = prefs['output_format'].lower()
if fmt == 'lrf': if fmt == 'lrf':

View File

@ -7,19 +7,19 @@ from calibre.utils.config import Config, StringConfig
def server_config(defaults=None): def server_config(defaults=None):
desc=_('Settings to control the calibre content server') desc=_('Settings to control the calibre content server')
c = Config('server', desc) if defaults is None else StringConfig(defaults, desc) c = Config('server', desc) if defaults is None else StringConfig(defaults, desc)
c.add_opt('port', ['-p', '--port'], default=8080, c.add_opt('port', ['-p', '--port'], default=8080,
help=_('The port on which to listen. Default is %default')) help=_('The port on which to listen. Default is %default'))
c.add_opt('timeout', ['-t', '--timeout'], default=120, c.add_opt('timeout', ['-t', '--timeout'], default=120,
help=_('The server timeout in seconds. Default is %default')) help=_('The server timeout in seconds. Default is %default'))
c.add_opt('thread_pool', ['--thread-pool'], default=30, c.add_opt('thread_pool', ['--thread-pool'], default=30,
help=_('The max number of worker threads to use. Default is %default')) help=_('The max number of worker threads to use. Default is %default'))
c.add_opt('password', ['--password'], default=None, c.add_opt('password', ['--password'], default=None,
help=_('Set a password to restrict access. By default access is unrestricted.')) help=_('Set a password to restrict access. By default access is unrestricted.'))
c.add_opt('username', ['--username'], default='calibre', c.add_opt('username', ['--username'], default='calibre',
help=_('Username for access. By default, it is: %default')) help=_('Username for access. By default, it is: %default'))
c.add_opt('develop', ['--develop'], default=False, c.add_opt('develop', ['--develop'], default=False,
help='Development mode. Server automatically restarts on file changes and serves code files (html, css, js) from the file system instead of calibre\'s resource system.') help='Development mode. Server automatically restarts on file changes and serves code files (html, css, js) from the file system instead of calibre\'s resource system.')
c.add_opt('max_cover', ['--max-cover'], default='600x800', c.add_opt('max_cover', ['--max-cover'], default='600x800',
help=_('The maximum size for displayed covers. Default is %default.')) help=_('The maximum size for displayed covers. Default is %default.'))
return c return c

View File

@ -39,6 +39,7 @@ recipe_modules = ['recipe_' + r for r in (
'nacional_cro', '24sata', 'dnevni_avaz', 'glas_srpske', '24sata_rs', 'nacional_cro', '24sata', 'dnevni_avaz', 'glas_srpske', '24sata_rs',
'krstarica', 'krstarica_en', 'tanjug', 'laprensa_ni', 'azstarnet', 'krstarica', 'krstarica_en', 'tanjug', 'laprensa_ni', 'azstarnet',
'corriere_della_sera_it', 'corriere_della_sera_en', 'msdnmag_en', 'corriere_della_sera_it', 'corriere_della_sera_en', 'msdnmag_en',
'moneynews',
)] )]
import re, imp, inspect, time, os import re, imp, inspect, time, os

View File

@ -0,0 +1,49 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
moneynews.newsmax.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class MoneyNews(BasicNewsRecipe):
title = 'Moneynews.com'
__author__ = 'Darko Miletic'
description = 'Financial news worldwide'
publisher = 'moneynews.com'
category = 'news, finances, USA, business'
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'cp1252'
html2lrf_options = [
'--comment', description
, '--category', category
, '--publisher', publisher
, '--ignore-tables'
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\nlinearize_tables=True'
feeds = [
(u'Street Talk' , u'http://moneynews.newsmax.com/xml/streettalk.xml' )
,(u'Finance News' , u'http://moneynews.newsmax.com/xml/FinanceNews.xml' )
,(u'Economy' , u'http://moneynews.newsmax.com/xml/economy.xml' )
,(u'Companies' , u'http://moneynews.newsmax.com/xml/companies.xml' )
,(u'Markets' , u'http://moneynews.newsmax.com/xml/Markets.xml' )
,(u'Investing & Analysis' , u'http://moneynews.newsmax.com/xml/investing.xml' )
]
keep_only_tags = [dict(name='table', attrs={'class':'copy'})]
remove_tags = [
dict(name='td' , attrs={'id':'article_fontsize'})
,dict(name='table', attrs={'id':'toolbox' })
,dict(name='tr' , attrs={'id':'noprint3' })
]