diff --git a/src/calibre/constants.py b/src/calibre/constants.py index 2209f482f5..180433532d 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -2,7 +2,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' __appname__ = 'calibre' -__version__ = '0.4.126' +__version__ = '0.4.128' __author__ = "Kovid Goyal " ''' Various run time constants. diff --git a/src/calibre/debug.py b/src/calibre/debug.py index 274089baa1..81336953e1 100644 --- a/src/calibre/debug.py +++ b/src/calibre/debug.py @@ -43,7 +43,11 @@ def update_module(mod, path): zp = os.path.join(os.path.dirname(sys.executable), 'library.zip') elif isosx: zp = os.path.join(os.path.dirname(getattr(sys, 'frameworks_dir')), - 'Resources', 'lib', 'python2.5', 'site-packages.zip') + 'Resources', 'lib', + 'python'+'.'.join(map(str, sys.version_info[:2])), + 'site-packages.zip') + else: + zp = os.path.join(getattr(sys, 'frozen_path'), 'loader.zip') if zp is not None: update_zipfile(zp, mod, path) else: diff --git a/src/calibre/devices/cybookg3/driver.py b/src/calibre/devices/cybookg3/driver.py index cf9f2e5a41..6c026c8669 100644 --- a/src/calibre/devices/cybookg3/driver.py +++ b/src/calibre/devices/cybookg3/driver.py @@ -9,31 +9,26 @@ import os, fnmatch from calibre.devices.usbms.driver import USBMS class CYBOOKG3(USBMS): - MIME_MAP = { - 'mobi' : 'application/mobi', - 'prc' : 'application/prc', - 'html' : 'application/html', - 'pdf' : 'application/pdf', - 'rtf' : 'application/rtf', - 'txt' : 'text/plain', - } # Ordered list of supported formats - FORMATS = MIME_MAP.keys() + # Be sure these have an entry in calibre.devices.mime + FORMATS = ['mobi', 'prc', 'html', 'pdf', 'rtf', 'txt'] VENDOR_ID = 0x0bda PRODUCT_ID = 0x0703 BCD = [0x110, 0x132] VENDOR_NAME = 'BOOKEEN' - PRODUCT_NAME = 'CYBOOK_GEN3' + WINDOWS_MAIN_MEM = 'CYBOOK_GEN3__-FD' + WINDOWS_CARD_MEM = 'CYBOOK_GEN3__-SD' - OSX_NAME_MAIN_MEM = 'Bookeen Cybook Gen3 -FD Media' - OSX_NAME_CARD_MEM = 'Bookeen Cybook Gen3 -SD Media' + OSX_MAIN_MEM = 'Bookeen Cybook Gen3 -FD Media' + OSX_CARD_MEM = 'Bookeen Cybook Gen3 -SD Media' MAIN_MEMORY_VOLUME_LABEL = 'Cybook Gen 3 Main Memory' STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card' - EBOOK_DIR = "eBooks" + EBOOK_DIR_MAIN = "eBooks" + SUPPORTS_SUB_DIRS = True def delete_books(self, paths, end_session=True): for path in paths: @@ -52,3 +47,8 @@ class CYBOOKG3(USBMS): for filen in fnmatch.filter(files, filename + "*.t2b"): os.unlink(os.path.join(p, filen)) + try: + os.removedirs(os.path.dirname(path)) + except: + pass + diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index 85d25d82a4..6bbb658f2c 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -41,6 +41,20 @@ class Device(object): '''Return the FDI description of this device for HAL on linux.''' return '' + @classmethod + def can_handle(cls, device_info): + ''' + Optional method to perform further checks on a device to see if this driver + is capable of handling it. If it is not it should return False. This method + is only called after the vendor, product ids and the bcd have matched, so + it can do some relatively time intensive checks. The default implementation + returns True. + + :param device_info: On windows a device ID string. On Unix a tuple of + ``(vendor_id, product_id, bcd)``. + ''' + return True + def open(self): ''' Perform any device specific initialization. Called after the device is @@ -109,7 +123,8 @@ class Device(object): """ raise NotImplementedError() - def upload_books(self, files, names, on_card=False, end_session=True): + def upload_books(self, files, names, on_card=False, end_session=True, + metadata=None): ''' Upload a list of books to the device. If a file already exists on the device, it should be replaced. @@ -121,6 +136,10 @@ class Device(object): once uploaded to the device. len(names) == len(files) @return: A list of 3-element tuples. The list is meant to be passed to L{add_books_to_metadata}. + @param metadata: If not None, it is a list of dictionaries. Each dictionary + will have at least the key tags to allow the driver to choose book location + based on tags. len(metadata) == len(files). If your device does not support + hierarchical ebook folders, you can safely ignore this parameter. ''' raise NotImplementedError() diff --git a/src/calibre/devices/kindle/driver.py b/src/calibre/devices/kindle/driver.py index 06c3b1cf27..d5ef7008bc 100755 --- a/src/calibre/devices/kindle/driver.py +++ b/src/calibre/devices/kindle/driver.py @@ -9,24 +9,30 @@ import os, fnmatch from calibre.devices.usbms.driver import USBMS class KINDLE(USBMS): - MIME_MAP = { - 'azw' : 'application/azw', - 'mobi' : 'application/mobi', - 'prc' : 'application/prc', - 'txt' : 'text/plain', - } # Ordered list of supported formats - FORMATS = MIME_MAP.keys() + FORMATS = ['azw', 'mobi', 'prc', 'txt'] VENDOR_ID = 0x1949 PRODUCT_ID = 0x0001 - BCD = 0x399 + BCD = [0x399] VENDOR_NAME = 'AMAZON' - PRODUCT_NAME = 'KINDLE' + WINDOWS_MAIN_MEM = 'KINDLE' MAIN_MEMORY_VOLUME_LABEL = 'Kindle Main Memory' STORAGE_CARD_VOLUME_LABEL = 'Kindle Storage Card' - EBOOK_DIR = "documents" + EBOOK_DIR_MAIN = "documents" + + def delete_books(self, paths, end_session=True): + for path in paths: + if os.path.exists(path): + os.unlink(path) + + filepath, ext = os.path.splitext(path) + basepath, filename = os.path.split(filepath) + + # Delete the ebook auxiliary file + if os.path.exists(filepath + '.mbp'): + os.unlink(filepath + '.mbp') diff --git a/src/calibre/devices/mime.py b/src/calibre/devices/mime.py new file mode 100644 index 0000000000..0035a7b8d7 --- /dev/null +++ b/src/calibre/devices/mime.py @@ -0,0 +1,19 @@ +__license__ = 'GPL v3' +__copyright__ = '2009, John Schember ' +''' +Global Mime mapping of ebook types. +''' + +MIME_MAP = { + 'azw' : 'application/azw', + 'epub' : 'application/epub+zip', + 'html' : 'text/html', + 'lrf' : 'application/x-sony-bbeb', + 'lrx' : 'application/x-sony-bbeb', + 'mobi' : 'application/mobi', + 'pdf' : 'application/pdf', + 'prc' : 'application/prc', + 'rtf' : 'application/rtf', + 'txt' : 'text/plain', + } + diff --git a/src/calibre/devices/prs500/driver.py b/src/calibre/devices/prs500/driver.py index 232d2c758c..cca71376d4 100755 --- a/src/calibre/devices/prs500/driver.py +++ b/src/calibre/devices/prs500/driver.py @@ -841,7 +841,8 @@ class PRS500(Device): self.upload_book_list(booklists[1], end_session=False) @safe - def upload_books(self, files, names, on_card=False, end_session=True): + def upload_books(self, files, names, on_card=False, end_session=True, + metadata=None): card = self.card(end_session=False) prefix = card + '/' + self.CARD_PATH_PREFIX +'/' if on_card else '/Data/media/books/' if on_card and not self._exists(prefix)[0]: diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 2e8a6197a2..fa8f6845cc 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -407,7 +407,8 @@ class PRS505(Device): if not os.path.isdir(path): os.utime(path, None) - def upload_books(self, files, names, on_card=False, end_session=True): + def upload_books(self, files, names, on_card=False, end_session=True, + metadata=None): if on_card and not self._card_prefix: raise ValueError(_('The reader has no storage card connected.')) path = os.path.join(self._card_prefix, self.CARD_PATH_PREFIX) if on_card \ diff --git a/src/calibre/devices/scanner.py b/src/calibre/devices/scanner.py index ec937fc84d..f0cd9a66eb 100644 --- a/src/calibre/devices/scanner.py +++ b/src/calibre/devices/scanner.py @@ -60,15 +60,18 @@ class DeviceScanner(object): def is_device_connected(self, device): if iswindows: vid, pid = 'vid_%4.4x'%device.VENDOR_ID, 'pid_%4.4x'%device.PRODUCT_ID + vidd, pidd = 'vid_%i'%device.VENDOR_ID, 'pid_%i'%device.PRODUCT_ID for device_id in self.devices: - if vid in device_id and pid in device_id: + if (vid in device_id or vidd in device_id) and (pid in device_id or pidd in device_id): if self.test_bcd_windows(device_id, getattr(device, 'BCD', None)): - return True + if device.can_handle(device_id): + return True else: for vendor, product, bcdDevice in self.devices: if device.VENDOR_ID == vendor and device.PRODUCT_ID == product: if self.test_bcd(bcdDevice, getattr(device, 'BCD', None)): - return True + if device.can_handle((vendor, product, bcdDevice)): + return True return False diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index f6b0c6a0a8..c1dd56385a 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -6,7 +6,7 @@ intended to be subclassed with the relevant parts implemented for a particular device. This class handles devive detection. ''' -import os, time +import os, subprocess, time from calibre.devices.interface import Device as _Device from calibre.devices.errors import DeviceError @@ -23,11 +23,12 @@ class Device(_Device): PRODUCT_ID = 0x0 BCD = None - VENDOR_NAME = '' - PRODUCT_NAME = '' + VENDOR_NAME = None + WINDOWS_MAIN_MEM = None + WINDOWS_CARD_MEM = None - OSX_NAME_MAIN_MEM = '' - OSX_NAME_CARD_MEM = '' + OSX_MAIN_MEM = None + OSX_CARD_MEM = None MAIN_MEMORY_VOLUME_LABEL = '' STORAGE_CARD_VOLUME_LABEL = '' @@ -148,43 +149,47 @@ class Device(_Device): return (msz, 0, csz) - @classmethod - def windows_match_device(cls, device_id): - device_id = device_id.upper() - if 'VEN_'+cls.VENDOR_NAME in device_id and \ - 'PROD_'+cls.PRODUCT_NAME in device_id: - return True - vid, pid = hex(cls.VENDOR_ID)[2:], hex(cls.PRODUCT_ID)[2:] - while len(vid) < 4: vid = '0' + vid - while len(pid) < 4: pid = '0' + pid - if 'VID_'+vid in device_id and 'PID_'+pid in device_id: - return True + def windows_match_device(self, pnp_id, device_id): + pnp_id = pnp_id.upper() + + if device_id and pnp_id is not None: + device_id = device_id.upper() + + if 'VEN_' + self.VENDOR_NAME in pnp_id and 'PROD_' + device_id in pnp_id: + return True + return False - # This only supports Windows >= 2000 + def windows_get_drive_prefix(self, drive): + prefix = None + + try: + partition = drive.associators("Win32_DiskDriveToDiskPartition")[0] + logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0] + prefix = logical_disk.DeviceID + os.sep + except IndexError: + pass + + return prefix + def open_windows(self): - drives = [] + drives = {} wmi = __import__('wmi', globals(), locals(), [], -1) c = wmi.WMI() for drive in c.Win32_DiskDrive(): - if self.__class__.windows_match_device(str(drive.PNPDeviceID)): - if drive.Partitions == 0: - continue - try: - partition = drive.associators("Win32_DiskDriveToDiskPartition")[0] - logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0] - prefix = logical_disk.DeviceID+os.sep - drives.append((drive.Index, prefix)) - except IndexError: - continue + if self.windows_match_device(str(drive.PNPDeviceID), WINDOWS_MAIN_MEM): + drives['main'] = self.windows_get_drive_prefix(drive) + elif self.windows_match_device(str(drive.PNPDeviceID), WINDOWS_CARD_MEM): + drives['card'] = self.windows_get_drive_prefix(drive) + + if 'main' and 'card' in drives.keys(): + break if not drives: - raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__) - - drives.sort(cmp=lambda a, b: cmp(a[0], b[0])) - self._main_prefix = drives[0][1] - if len(drives) > 1: - self._card_prefix = drives[1][1] + raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.') % self.__class__.__name__) + + self._main_prefix = drives['main'] if 'main' in names.keys() else None + self._card_prefix = drives['card'] if 'card' in names.keys() else None @classmethod def get_osx_mountpoints(self, raw=None): @@ -207,9 +212,9 @@ class Device(_Device): break for i, line in enumerate(lines): - if line.strip().endswith('') and self.OSX_NAME_MAIN_MEM in line: + if self.OSX_MAIN_MEM is not None and line.strip().endswith('') and self.OSX_MAIN_MEM in line: get_dev_node(lines[i+1:], 'main') - if line.strip().endswith('') and self.OSX_NAME_CARD_MEM in line: + if self.OSX_CARD_MEM is not None and line.strip().endswith('') and self.OSX_CARD_MEM in line: get_dev_node(lines[i+1:], 'card') if len(names.keys()) == 2: break diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index 733ce76ae7..187a4c1a50 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -12,11 +12,13 @@ from itertools import cycle from calibre.devices.usbms.device import Device from calibre.devices.usbms.books import BookList, Book from calibre.devices.errors import FreeSpaceError +from calibre.devices.mime import MIME_MAP class USBMS(Device): - EBOOK_DIR = '' - MIME_MAP = {} FORMATS = [] + EBOOK_DIR_MAIN = '' + EBOOK_DIR_CARD = '' + SUPPORTS_SUB_DIRS = False def __init__(self, key='-1', log_packets=False, report_progress=None): pass @@ -35,29 +37,39 @@ class USBMS(Device): return bl prefix = self._card_prefix if oncard else self._main_prefix + 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 - for path, dirs, files in os.walk(os.path.join(prefix, self.EBOOK_DIR)): + # Get all books in all directories under the root ebook_dir directory + for path, dirs, files in os.walk(os.path.join(prefix, ebook_dir)): # Filter out anything that isn't in the list of supported ebook types - for book_type in self.MIME_MAP.keys(): + for book_type in self.FORMATS: for filename in fnmatch.filter(files, '*.%s' % (book_type)): title, author, mime = self.__class__.extract_book_metadata_by_filename(filename) bl.append(Book(os.path.join(path, filename), title, author, mime)) return bl - def upload_books(self, files, names, on_card=False, end_session=True): + def upload_books(self, files, names, on_card=False, end_session=True, + metadata=None): if on_card and not self._card_prefix: raise ValueError(_('The reader has no storage card connected.')) if not on_card: - path = os.path.join(self._main_prefix, self.EBOOK_DIR) + path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN) else: - path = os.path.join(self._card_prefix, self.EBOOK_DIR) - - sizes = map(os.path.getsize, files) + path = os.path.join(self._card_prefix, self.EBOOK_DIR_CARD) + + def get_size(obj): + if hasattr(obj, 'seek'): + obj.seek(0, os.SEEK_END) + size = obj.tell() + obj.seek(0) + return size + return os.path.getsize(obj) + + sizes = map(get_size, files) size = sum(sizes) - + if on_card and size > self.free_space()[2] - 1024*1024: raise FreeSpaceError(_("There is insufficient free space on the storage card")) if not on_card and size > self.free_space()[0] - 2*1024*1024: @@ -65,17 +77,42 @@ class USBMS(Device): paths = [] names = iter(names) + metadata = iter(metadata) for infile in files: - filepath = os.path.join(path, names.next()) + newpath = path + + if self.SUPPORTS_SUB_DIRS: + mdata = metadata.next() + + if 'tags' in mdata.keys(): + for tag in mdata['tags']: + if tag.startswith('/'): + newpath += tag + newpath = os.path.normpath(newpath) + break + + if not os.path.exists(newpath): + os.makedirs(newpath) + + filepath = os.path.join(newpath, names.next()) paths.append(filepath) - shutil.copy2(infile, filepath) + if hasattr(infile, 'read'): + infile.seek(0) + + dest = open(filepath, 'wb') + shutil.copyfileobj(infile, dest, 10*1024*1024) + + dest.flush() + dest.close() + else: + shutil.copy2(infile, filepath) return zip(paths, cycle([on_card])) @classmethod - def add_books_to_metadata(cls, locations, metadata, booklists): + def add_books_to_metadata(cls, locations, metadata, booklists): for location in locations: path = location[0] on_card = 1 if location[1] else 0 @@ -88,6 +125,10 @@ class USBMS(Device): if os.path.exists(path): # Delete the ebook os.unlink(path) + try: + os.removedirs(os.path.dirname(path)) + except: + pass @classmethod def remove_books_from_metadata(cls, paths, booklists): @@ -96,7 +137,6 @@ class USBMS(Device): for book in bl: if path.endswith(book.path): bl.remove(book) - break def sync_booklists(self, booklists, end_session=True): # There is no meta data on the device to update. The device is treated @@ -136,10 +176,11 @@ class USBMS(Device): else: book_title = os.path.splitext(filename)[0].replace('_', ' ') - fileext = os.path.splitext(filename)[1] - if fileext in cls.MIME_MAP.keys(): - book_mime = cls.MIME_MAP[fileext] - + fileext = os.path.splitext(filename)[1][1:] + + if fileext in cls.FORMATS: + book_mime = MIME_MAP[fileext] if fileext in MIME_MAP.keys() else 'Unknown' + return book_title, book_author, book_mime # ls, rm, cp, mkdir, touch, cat diff --git a/src/calibre/ebooks/epub/from_any.py b/src/calibre/ebooks/epub/from_any.py index e204b38b03..51fdcc4e6a 100644 --- a/src/calibre/ebooks/epub/from_any.py +++ b/src/calibre/ebooks/epub/from_any.py @@ -67,6 +67,7 @@ def txt2opf(path, tdir, opts): def pdf2opf(path, tdir, opts): from calibre.ebooks.lrf.pdf.convert_from import generate_html generate_html(path, tdir) + opts.dont_split_on_page_breaks = True return os.path.join(tdir, 'metadata.opf') def epub2opf(path, tdir, opts): diff --git a/src/calibre/ebooks/epub/from_html.py b/src/calibre/ebooks/epub/from_html.py index 9165e99313..413bea4801 100644 --- a/src/calibre/ebooks/epub/from_html.py +++ b/src/calibre/ebooks/epub/from_html.py @@ -77,6 +77,8 @@ def check_links(opf_path, pretty_print): html_files.append(os.path.abspath(content(f))) for path in html_files: + if not os.access(path, os.R_OK): + continue base = os.path.dirname(path) root = html.fromstring(open(content(path), 'rb').read(), parser=parser) for element, attribute, link, pos in list(root.iterlinks()): diff --git a/src/calibre/ebooks/html.py b/src/calibre/ebooks/html.py index 84af7527bd..634963e775 100644 --- a/src/calibre/ebooks/html.py +++ b/src/calibre/ebooks/html.py @@ -335,7 +335,7 @@ class PreProcessor(object): # Fix pdftohtml markup PDFTOHTML = [ # Remove
tags - (re.compile(r'', re.IGNORECASE), lambda match: ' '), + (re.compile(r'', re.IGNORECASE), lambda match: '
'), # Remove page numbers (re.compile(r'\d+
', re.IGNORECASE), lambda match: ''), # Remove
and replace

with

@@ -560,7 +560,7 @@ class Processor(Parser): hr = etree.Element('hr') if elem.getprevious() is None: elem.getparent()[:0] = [hr] - else: + elif elem.getparent() is not None: insert = None for i, c in enumerate(elem.getparent()): if c is elem: @@ -796,7 +796,19 @@ class Processor(Parser): setting = '' face = font.attrib.pop('face', None) if face is not None: - setting += 'font-face:%s;'%face + faces = [] + for face in face.split(','): + face = face.strip() + if ' ' in face and not (face[0] == face[-1] == '"'): + face = '"%s"' % face.replace('"', r'\"') + faces.append(face) + for generic in ('serif', 'sans-serif', 'monospace'): + if generic in faces: + break + else: + faces.append('serif') + family = ', '.join(faces) + setting += 'font-family: %s;' % family color = font.attrib.pop('color', None) if color is not None: setting += 'color:%s'%color diff --git a/src/calibre/ebooks/lit/reader.py b/src/calibre/ebooks/lit/reader.py index 0e7f9a1ccf..461c067382 100644 --- a/src/calibre/ebooks/lit/reader.py +++ b/src/calibre/ebooks/lit/reader.py @@ -7,24 +7,20 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' \ 'and Marshall T. Vandegrift ' -import sys, struct, os +import sys, struct, cStringIO, os import functools import re from urlparse import urldefrag -from cStringIO import StringIO -from urllib import unquote as urlunquote from lxml import etree from calibre.ebooks.lit import LitError from calibre.ebooks.lit.maps import OPF_MAP, HTML_MAP import calibre.ebooks.lit.mssha1 as mssha1 -from calibre.ebooks.oeb.base import XML_PARSER, urlnormalize +from calibre.ebooks.oeb.base import urlnormalize from calibre.ebooks import DRMError from calibre import plugins lzx, lxzerror = plugins['lzx'] msdes, msdeserror = plugins['msdes'] -__all__ = ["LitReader"] - XML_DECL = """ """ OPF_DECL = """ @@ -112,9 +108,6 @@ def consume_sized_utf8_string(bytes, zpad=False): pos += 1 return u''.join(result), bytes[pos:] -def encode(string): - return unicode(string).encode('ascii', 'xmlcharrefreplace') - class UnBinary(object): AMPERSAND_RE = re.compile( r'&(?!(?:#[0-9]+|#x[0-9a-fA-F]+|[a-zA-Z_:][a-zA-Z0-9.-_:]+);)') @@ -125,13 +118,13 @@ class UnBinary(object): def __init__(self, bin, path, manifest={}, map=HTML_MAP): self.manifest = manifest self.tag_map, self.attr_map, self.tag_to_attr_map = map - self.is_html = map is HTML_MAP + self.opf = map is OPF_MAP + self.bin = bin self.dir = os.path.dirname(path) - buf = StringIO() - self.binary_to_text(bin, buf) - self.raw = buf.getvalue().lstrip() + self.buf = cStringIO.StringIO() + self.binary_to_text() + self.raw = self.buf.getvalue().lstrip().decode('utf-8') self.escape_reserved() - self._tree = None def escape_reserved(self): raw = self.raw @@ -158,28 +151,18 @@ class UnBinary(object): return '/'.join(relpath) def __unicode__(self): - return self.raw.decode('utf-8') - - def __str__(self): return self.raw - - def tree(): - def fget(self): - if not self._tree: - self._tree = etree.fromstring(self.raw, parser=XML_PARSER) - return self._tree - return property(fget=fget) - tree = tree() - def binary_to_text(self, bin, buf, index=0, depth=0): + def binary_to_text(self, base=0, depth=0): tag_name = current_map = None dynamic_tag = errors = 0 in_censorship = is_goingdown = False state = 'text' + index = base flags = 0 - while index < len(bin): - c, index = read_utf8_char(bin, index) + while index < len(self.bin): + c, index = read_utf8_char(self.bin, index) oc = ord(c) if state == 'text': @@ -192,7 +175,7 @@ class UnBinary(object): c = '>>' elif c == '<': c = '<<' - buf.write(encode(c)) + self.buf.write(c.encode('ascii', 'xmlcharrefreplace')) elif state == 'get flags': if oc == 0: @@ -205,7 +188,7 @@ class UnBinary(object): state = 'text' if oc == 0 else 'get attr' if flags & FLAG_OPENING: tag = oc - buf.write('<') + self.buf.write('<') if not (flags & FLAG_CLOSING): is_goingdown = True if tag == 0x8000: @@ -222,7 +205,7 @@ class UnBinary(object): tag_name = '?'+unichr(tag)+'?' current_map = self.tag_to_attr_map[tag] print 'WARNING: tag %s unknown' % unichr(tag) - buf.write(encode(tag_name)) + self.buf.write(unicode(tag_name).encode('utf-8')) elif flags & FLAG_CLOSING: if depth == 0: raise LitError('Extra closing tag') @@ -234,14 +217,15 @@ class UnBinary(object): if not is_goingdown: tag_name = None dynamic_tag = 0 - buf.write(' />') + self.buf.write(' />') else: - buf.write('>') - index = self.binary_to_text(bin, buf, index, depth+1) + self.buf.write('>') + index = self.binary_to_text(base=index, depth=depth+1) is_goingdown = False if not tag_name: raise LitError('Tag ends before it begins.') - buf.write(encode(u''.join(('')))) + self.buf.write(u''.join( + ('')).encode('utf-8')) dynamic_tag = 0 tag_name = None state = 'text' @@ -261,7 +245,7 @@ class UnBinary(object): in_censorship = True state = 'get value length' continue - buf.write(' ' + encode(attr) + '=') + self.buf.write(' ' + unicode(attr).encode('utf-8') + '=') if attr in ['href', 'src']: state = 'get href length' else: @@ -269,39 +253,40 @@ class UnBinary(object): elif state == 'get value length': if not in_censorship: - buf.write('"') + self.buf.write('"') count = oc - 1 if count == 0: if not in_censorship: - buf.write('"') + self.buf.write('"') in_censorship = False state = 'get attr' continue state = 'get value' if oc == 0xffff: continue - if count < 0 or count > (len(bin) - index): + if count < 0 or count > (len(self.bin) - index): raise LitError('Invalid character count %d' % count) elif state == 'get value': if count == 0xfffe: if not in_censorship: - buf.write('%s"' % (oc - 1)) + self.buf.write('%s"' % (oc - 1)) in_censorship = False state = 'get attr' elif count > 0: if not in_censorship: - buf.write(encode(c)) + self.buf.write(c.encode( + 'ascii', 'xmlcharrefreplace')) count -= 1 if count == 0: if not in_censorship: - buf.write('"') + self.buf.write('"') in_censorship = False state = 'get attr' elif state == 'get custom length': count = oc - 1 - if count <= 0 or count > len(bin)-index: + if count <= 0 or count > len(self.bin)-index: raise LitError('Invalid character count %d' % count) dynamic_tag += 1 state = 'get custom' @@ -311,26 +296,26 @@ class UnBinary(object): tag_name += c count -= 1 if count == 0: - buf.write(encode(tag_name)) + self.buf.write(unicode(tag_name).encode('utf-8')) state = 'get attr' elif state == 'get attr length': count = oc - 1 - if count <= 0 or count > (len(bin) - index): + if count <= 0 or count > (len(self.bin) - index): raise LitError('Invalid character count %d' % count) - buf.write(' ') + self.buf.write(' ') state = 'get custom attr' elif state == 'get custom attr': - buf.write(encode(c)) + self.buf.write(unicode(c).encode('utf-8')) count -= 1 if count == 0: - buf.write('=') + self.buf.write('=') state = 'get value length' elif state == 'get href length': count = oc - 1 - if count <= 0 or count > (len(bin) - index): + if count <= 0 or count > (len(self.bin) - index): raise LitError('Invalid character count %d' % count) href = '' state = 'get href' @@ -344,11 +329,10 @@ class UnBinary(object): if frag: path = '#'.join((path, frag)) path = urlnormalize(path) - buf.write(encode(u'"%s"' % path)) + self.buf.write((u'"%s"' % path).encode('utf-8')) state = 'get attr' return index - class DirectoryEntry(object): def __init__(self, name, section, offset, size): self.name = name @@ -363,7 +347,6 @@ class DirectoryEntry(object): def __str__(self): return repr(self) - class ManifestItem(object): def __init__(self, original, internal, mime_type, offset, root, state): self.original = original @@ -391,87 +374,65 @@ class ManifestItem(object): % (self.internal, self.path, self.mime_type, self.offset, self.root, self.state) - def preserve(function): def wrapper(self, *args, **kwargs): - opos = self.stream.tell() + opos = self._stream.tell() try: return function(self, *args, **kwargs) finally: - self.stream.seek(opos) + self._stream.seek(opos) functools.update_wrapper(wrapper, function) return wrapper -class LitFile(object): +class LitReader(object): PIECE_SIZE = 16 - - def __init__(self, filename_or_stream): - if hasattr(filename_or_stream, 'read'): - self.stream = filename_or_stream - else: - self.stream = open(filename_or_stream, 'rb') - try: - self.opf_path = os.path.splitext( - os.path.basename(self.stream.name))[0] + '.opf' - except AttributeError: - self.opf_path = 'content.opf' - if self.magic != 'ITOLITLS': - raise LitError('Not a valid LIT file') - if self.version != 1: - raise LitError('Unknown LIT version %d' % (self.version,)) - self.read_secondary_header() - self.read_header_pieces() - self.read_section_names() - self.read_manifest() - self.read_drm() - - def warn(self, msg): - print "WARNING: %s" % (msg,) + XML_PARSER = etree.XMLParser( + recover=True, resolve_entities=False) def magic(): @preserve def fget(self): - self.stream.seek(0) - return self.stream.read(8) + self._stream.seek(0) + return self._stream.read(8) return property(fget=fget) magic = magic() def version(): def fget(self): - self.stream.seek(8) - return u32(self.stream.read(4)) + self._stream.seek(8) + return u32(self._stream.read(4)) return property(fget=fget) version = version() def hdr_len(): @preserve def fget(self): - self.stream.seek(12) - return int32(self.stream.read(4)) + self._stream.seek(12) + return int32(self._stream.read(4)) return property(fget=fget) hdr_len = hdr_len() def num_pieces(): @preserve def fget(self): - self.stream.seek(16) - return int32(self.stream.read(4)) + self._stream.seek(16) + return int32(self._stream.read(4)) return property(fget=fget) num_pieces = num_pieces() def sec_hdr_len(): @preserve def fget(self): - self.stream.seek(20) - return int32(self.stream.read(4)) + self._stream.seek(20) + return int32(self._stream.read(4)) return property(fget=fget) sec_hdr_len = sec_hdr_len() def guid(): @preserve def fget(self): - self.stream.seek(24) - return self.stream.read(16) + self._stream.seek(24) + return self._stream.read(16) return property(fget=fget) guid = guid() @@ -481,27 +442,44 @@ class LitFile(object): size = self.hdr_len \ + (self.num_pieces * self.PIECE_SIZE) \ + self.sec_hdr_len - self.stream.seek(0) - return self.stream.read(size) + self._stream.seek(0) + return self._stream.read(size) return property(fget=fget) header = header() + def __init__(self, filename_or_stream): + if hasattr(filename_or_stream, 'read'): + self._stream = filename_or_stream + else: + self._stream = open(filename_or_stream, 'rb') + if self.magic != 'ITOLITLS': + raise LitError('Not a valid LIT file') + if self.version != 1: + raise LitError('Unknown LIT version %d' % (self.version,)) + self.entries = {} + self._read_secondary_header() + self._read_header_pieces() + self._read_section_names() + self._read_manifest() + self._read_meta() + self._read_drm() + @preserve def __len__(self): - self.stream.seek(0, 2) - return self.stream.tell() + self._stream.seek(0, 2) + return self._stream.tell() @preserve - def read_raw(self, offset, size): - self.stream.seek(offset) - return self.stream.read(size) + def _read_raw(self, offset, size): + self._stream.seek(offset) + return self._stream.read(size) - def read_content(self, offset, size): - return self.read_raw(self.content_offset + offset, size) + def _read_content(self, offset, size): + return self._read_raw(self.content_offset + offset, size) - def read_secondary_header(self): + def _read_secondary_header(self): offset = self.hdr_len + (self.num_pieces * self.PIECE_SIZE) - bytes = self.read_raw(offset, self.sec_hdr_len) + bytes = self._read_raw(offset, self.sec_hdr_len) offset = int32(bytes[4:]) while offset < len(bytes): blocktype = bytes[offset:offset+4] @@ -529,21 +507,21 @@ class LitFile(object): if not hasattr(self, 'content_offset'): raise LitError('Could not figure out the content offset') - def read_header_pieces(self): + def _read_header_pieces(self): src = self.header[self.hdr_len:] for i in xrange(self.num_pieces): piece = src[i * self.PIECE_SIZE:(i + 1) * self.PIECE_SIZE] if u32(piece[4:]) != 0 or u32(piece[12:]) != 0: raise LitError('Piece %s has 64bit value' % repr(piece)) offset, size = u32(piece), int32(piece[8:]) - piece = self.read_raw(offset, size) + piece = self._read_raw(offset, size) if i == 0: continue # Dont need this piece elif i == 1: if u32(piece[8:]) != self.entry_chunklen or \ u32(piece[12:]) != self.entry_unknown: raise LitError('Secondary header does not match piece') - self.read_directory(piece) + self._read_directory(piece) elif i == 2: if u32(piece[8:]) != self.count_chunklen or \ u32(piece[12:]) != self.count_unknown: @@ -554,13 +532,12 @@ class LitFile(object): elif i == 4: self.piece4_guid = piece - def read_directory(self, piece): + def _read_directory(self, piece): if not piece.startswith('IFCM'): raise LitError('Header piece #1 is not main directory.') chunk_size, num_chunks = int32(piece[8:12]), int32(piece[24:28]) if (32 + (num_chunks * chunk_size)) != len(piece): - raise LitError('IFCM header has incorrect length') - self.entries = {} + raise LitError('IFCM HEADER has incorrect length') for i in xrange(num_chunks): offset = 32 + (i * chunk_size) chunk = piece[offset:offset + chunk_size] @@ -594,17 +571,17 @@ class LitFile(object): entry = DirectoryEntry(name, section, offset, size) self.entries[name] = entry - def read_section_names(self): + def _read_section_names(self): if '::DataSpace/NameList' not in self.entries: raise LitError('Lit file does not have a valid NameList') raw = self.get_file('::DataSpace/NameList') if len(raw) < 4: raise LitError('Invalid Namelist section') pos = 4 - num_sections = u16(raw[2:pos]) - self.section_names = [""] * num_sections - self.section_data = [None] * num_sections - for section in xrange(num_sections): + self.num_sections = u16(raw[2:pos]) + self.section_names = [""]*self.num_sections + self.section_data = [None]*self.num_sections + for section in xrange(self.num_sections): size = u16(raw[pos:pos+2]) pos += 2 size = size*2 + 2 @@ -614,12 +591,11 @@ class LitFile(object): raw[pos:pos+size].decode('utf-16-le').rstrip('\000') pos += size - def read_manifest(self): + def _read_manifest(self): if '/manifest' not in self.entries: raise LitError('Lit file does not have a valid manifest') raw = self.get_file('/manifest') self.manifest = {} - self.paths = {self.opf_path: None} while raw: slen, raw = ord(raw[0]), raw[1:] if slen == 0: break @@ -658,9 +634,28 @@ class LitFile(object): for item in mlist: if item.path[0] == '/': item.path = os.path.basename(item.path) - self.paths[item.path] = item - def read_drm(self): + def _pretty_print(self, xml): + f = cStringIO.StringIO(xml.encode('utf-8')) + doc = etree.parse(f, parser=self.XML_PARSER) + pretty = etree.tostring(doc, encoding='ascii', pretty_print=True) + return XML_DECL + unicode(pretty) + + def _read_meta(self): + path = 'content.opf' + raw = self.get_file('/meta') + xml = OPF_DECL + try: + xml += unicode(UnBinary(raw, path, self.manifest, OPF_MAP)) + except LitError: + if 'PENGUIN group' not in raw: raise + print "WARNING: attempting PENGUIN malformed OPF fix" + raw = raw.replace( + 'PENGUIN group', '\x00\x01\x18\x00PENGUIN group', 1) + xml += unicode(UnBinary(raw, path, self.manifest, OPF_MAP)) + self.meta = xml + + def _read_drm(self): self.drmlevel = 0 if '/DRMStorage/Licenses/EUL' in self.entries: self.drmlevel = 5 @@ -671,7 +666,7 @@ class LitFile(object): else: return if self.drmlevel < 5: - msdes.deskey(self.calculate_deskey(), msdes.DE1) + msdes.deskey(self._calculate_deskey(), msdes.DE1) bookkey = msdes.des(self.get_file('/DRMStorage/DRMSealed')) if bookkey[0] != '\000': raise LitError('Unable to decrypt title key!') @@ -679,7 +674,7 @@ class LitFile(object): else: raise DRMError("Cannot access DRM-protected book") - def calculate_deskey(self): + def _calculate_deskey(self): hashfiles = ['/meta', '/DRMStorage/DRMSource'] if self.drmlevel == 3: hashfiles.append('/DRMStorage/DRMBookplate') @@ -703,18 +698,18 @@ class LitFile(object): def get_file(self, name): entry = self.entries[name] if entry.section == 0: - return self.read_content(entry.offset, entry.size) + return self._read_content(entry.offset, entry.size) section = self.get_section(entry.section) return section[entry.offset:entry.offset+entry.size] def get_section(self, section): data = self.section_data[section] if not data: - data = self.get_section_uncached(section) + data = self._get_section(section) self.section_data[section] = data return data - def get_section_uncached(self, section): + def _get_section(self, section): name = self.section_names[section] path = '::DataSpace/Storage/' + name transform = self.get_file(path + '/Transform/List') @@ -726,29 +721,29 @@ class LitFile(object): raise LitError("ControlData is too short") guid = msguid(transform) if guid == DESENCRYPT_GUID: - content = self.decrypt(content) + content = self._decrypt(content) control = control[csize:] elif guid == LZXCOMPRESS_GUID: reset_table = self.get_file( '/'.join(('::DataSpace/Storage', name, 'Transform', LZXCOMPRESS_GUID, 'InstanceData/ResetTable'))) - content = self.decompress(content, control, reset_table) + content = self._decompress(content, control, reset_table) control = control[csize:] else: raise LitError("Unrecognized transform: %s." % repr(guid)) transform = transform[16:] return content - def decrypt(self, content): + def _decrypt(self, content): length = len(content) extra = length & 0x7 if extra > 0: - self.warn("content length not a multiple of block size") + self._warn("content length not a multiple of block size") content += "\0" * (8 - extra) msdes.deskey(self.bookkey, msdes.DE1) return msdes.des(content) - def decompress(self, content, control, reset_table): + def _decompress(self, content, control, reset_table): if len(control) < 32 or control[CONTROL_TAG:CONTROL_TAG+4] != "LZXC": raise LitError("Invalid ControlData tag value") if len(reset_table) < (RESET_INTERVAL + 8): @@ -789,7 +784,7 @@ class LitFile(object): result.append( lzx.decompress(content[base:size], window_bytes)) except lzx.LZXError: - self.warn("LZX decompression error; skipping chunk") + self._warn("LZX decompression error; skipping chunk") bytes_remaining -= window_bytes base = size accum += int32(reset_table[RESET_INTERVAL:]) @@ -799,88 +794,55 @@ class LitFile(object): try: result.append(lzx.decompress(content[base:], bytes_remaining)) except lzx.LZXError: - self.warn("LZX decompression error; skipping chunk") + self._warn("LZX decompression error; skipping chunk") bytes_remaining = 0 if bytes_remaining > 0: raise LitError("Failed to completely decompress section") return ''.join(result) - -class LitReader(object): - def __init__(self, filename_or_stream): - self._litfile = LitFile(filename_or_stream) - - def namelist(self): - return self._litfile.paths.keys() - - def exists(self, name): - return urlunquote(name) in self._litfile.paths - - def read_xml(self, name): - entry = self._litfile.paths[urlunquote(name)] if name else None - if entry is None: - content = self._read_meta() - elif 'spine' in entry.state: - internal = '/'.join(('/data', entry.internal, 'content')) - raw = self._litfile.get_file(internal) - unbin = UnBinary(raw, name, self._litfile.manifest, HTML_MAP) - content = unbin.tree - else: - raise LitError('Requested non-XML content as XML') - return content - - def read(self, name, pretty_print=False): - entry = self._litfile.paths[urlunquote(name)] if name else None - if entry is None: - meta = self._read_meta() - content = OPF_DECL + etree.tostring( - meta, encoding='ascii', pretty_print=pretty_print) - elif 'spine' in entry.state: - internal = '/'.join(('/data', entry.internal, 'content')) - raw = self._litfile.get_file(internal) - unbin = UnBinary(raw, name, self._litfile.manifest, HTML_MAP) - content = HTML_DECL + def get_entry_content(self, entry, pretty_print=False): + if 'spine' in entry.state: + name = '/'.join(('/data', entry.internal, 'content')) + path = entry.path + raw = self.get_file(name) + decl, map = (OPF_DECL, OPF_MAP) \ + if name == '/meta' else (HTML_DECL, HTML_MAP) + content = decl + unicode(UnBinary(raw, path, self.manifest, map)) if pretty_print: - content += etree.tostring(unbin.tree, - encoding='ascii', pretty_print=True) - else: - content += str(unbin) + content = self._pretty_print(content) + content = content.encode('utf-8') else: - internal = '/'.join(('/data', entry.internal)) - content = self._litfile.get_file(internal) + name = '/'.join(('/data', entry.internal)) + content = self.get_file(name) return content - - def meta(): - def fget(self): - return self.read(self._litfile.opf_path) - return property(fget=fget) - meta = meta() - + + def extract_content(self, output_dir=os.getcwdu(), pretty_print=False): + output_dir = os.path.abspath(output_dir) + try: + opf_path = os.path.splitext( + os.path.basename(self._stream.name))[0] + '.opf' + except AttributeError: + opf_path = 'content.opf' + opf_path = os.path.join(output_dir, opf_path) + self._ensure_dir(opf_path) + with open(opf_path, 'wb') as f: + xml = self.meta + if pretty_print: + xml = self._pretty_print(xml) + f.write(xml.encode('utf-8')) + for entry in self.manifest.values(): + path = os.path.join(output_dir, entry.path) + self._ensure_dir(path) + with open(path, 'wb') as f: + f.write(self.get_entry_content(entry, pretty_print)) + def _ensure_dir(self, path): dir = os.path.dirname(path) if not os.path.isdir(dir): os.makedirs(dir) - - def extract_content(self, output_dir=os.getcwdu(), pretty_print=False): - for name in self.namelist(): - path = os.path.join(output_dir, name) - self._ensure_dir(path) - with open(path, 'wb') as f: - f.write(self.read(name, pretty_print=pretty_print)) - - def _read_meta(self): - path = 'content.opf' - raw = self._litfile.get_file('/meta') - try: - unbin = UnBinary(raw, path, self._litfile.manifest, OPF_MAP) - except LitError: - if 'PENGUIN group' not in raw: raise - print "WARNING: attempting PENGUIN malformed OPF fix" - raw = raw.replace( - 'PENGUIN group', '\x00\x01\x18\x00PENGUIN group', 1) - unbin = UnBinary(raw, path, self._litfile.manifest, OPF_MAP) - return unbin.tree + def _warn(self, msg): + print "WARNING: %s" % (msg,) def option_parser(): from calibre.utils.config import OptionParser @@ -890,8 +852,7 @@ def option_parser(): help=_('Output directory. Defaults to current directory.')) parser.add_option( '-p', '--pretty-print', default=False, action='store_true', - help=_('Legibly format extracted markup.' \ - ' May modify meaningful whitespace.')) + help=_('Legibly format extracted markup. May modify meaningful whitespace.')) parser.add_option( '--verbose', default=False, action='store_true', help=_('Useful for debugging.')) diff --git a/src/calibre/ebooks/lit/writer.py b/src/calibre/ebooks/lit/writer.py index 1d8f9020bd..94b8017f2e 100644 --- a/src/calibre/ebooks/lit/writer.py +++ b/src/calibre/ebooks/lit/writer.py @@ -27,11 +27,16 @@ from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES, OEB_CSS_MIME, \ CSS_MIME, OPF_MIME, XML_NS, XML from calibre.ebooks.oeb.base import namespace, barename, prefixname, \ urlnormalize, xpath -from calibre.ebooks.oeb.base import FauxLogger, OEBBook +from calibre.ebooks.oeb.base import Logger, OEBBook +from calibre.ebooks.oeb.profile import Context from calibre.ebooks.oeb.stylizer import Stylizer +from calibre.ebooks.oeb.transforms.flatcss import CSSFlattener +from calibre.ebooks.oeb.transforms.rasterize import SVGRasterizer +from calibre.ebooks.oeb.transforms.trimmanifest import ManifestTrimmer +from calibre.ebooks.oeb.transforms.htmltoc import HTMLTOCAdder +from calibre.ebooks.oeb.transforms.manglecase import CaseMangler from calibre.ebooks.lit.lzx import Compressor import calibre -from calibre import LoggingInterface from calibre import plugins msdes, msdeserror = plugins['msdes'] import calibre.ebooks.lit.mssha1 as mssha1 @@ -138,17 +143,16 @@ def warn(x): class ReBinary(object): NSRMAP = {'': None, XML_NS: 'xml'} - def __init__(self, root, path, oeb, map=HTML_MAP, logger=FauxLogger()): - self.path = path - self.logger = logger - self.dir = os.path.dirname(path) + def __init__(self, root, path, oeb, map=HTML_MAP): + self.item = item + self.logger = oeb.logger self.manifest = oeb.manifest self.tags, self.tattrs = map self.buf = StringIO() self.anchors = [] self.page_breaks = [] self.is_html = is_html = map is HTML_MAP - self.stylizer = Stylizer(root, path, oeb) if is_html else None + self.stylizer = Stylizer(root, item.href, oeb) if is_html else None self.tree_to_binary(root) self.content = self.buf.getvalue() self.ahc = self.build_ahc() if is_html else None @@ -205,6 +209,8 @@ class ReBinary(object): if attr in ('href', 'src'): value = urlnormalize(value) path, frag = urldefrag(value) + if self.item: + path = self.item.abshref(path) prefix = unichr(3) if path in self.manifest.hrefs: prefix = unichr(2) @@ -217,7 +223,7 @@ class ReBinary(object): elif attr.startswith('ms--'): attr = '%' + attr[4:] elif tag == 'link' and attr == 'type' and value in OEB_STYLES: - value = OEB_CSS_MIME + value = CSS_MIME if attr in tattrs: self.write(tattrs[attr]) else: @@ -270,7 +276,7 @@ class ReBinary(object): def build_ahc(self): if len(self.anchors) > 6: self.logger.log_warn("More than six anchors in file %r. " \ - "Some links may not work properly." % self.path) + "Some links may not work properly." % self.item.href) data = StringIO() data.write(unichr(len(self.anchors)).encode('utf-8')) for anchor, offset in self.anchors: @@ -294,10 +300,9 @@ def preserve(function): return wrapper class LitWriter(object): - def __init__(self, oeb, logger=FauxLogger()): - self._oeb = oeb - self._logger = logger - self._litize_oeb() + def __init__(self): + # Wow, no options + pass def _litize_oeb(self): oeb = self._oeb @@ -306,32 +311,27 @@ class LitWriter(object): if oeb.metadata.cover: id = str(oeb.metadata.cover[0]) cover = oeb.manifest[id] - elif MS_COVER_TYPE in oeb.guide: - href = oeb.guide[MS_COVER_TYPE].href - cover = oeb.manifest.hrefs[href] - elif 'cover' in oeb.guide: - href = oeb.guide['cover'].href - cover = oeb.manifest.hrefs[href] - else: - html = oeb.spine[0].data - imgs = xpath(html, '//img[position()=1]') - href = imgs[0].get('src') if imgs else None - cover = oeb.manifest.hrefs[href] if href else None - if cover: - if not oeb.metadata.cover: - oeb.metadata.add('cover', cover.id) for type, title in ALL_MS_COVER_TYPES: if type not in oeb.guide: oeb.guide.add(type, title, cover.href) else: - self._logger.log_warn('No suitable cover image found.') + self._logger.warn('No suitable cover image found.') - def dump(self, stream): + def dump(self, oeb, path): + if hasattr(path, 'write'): + return self._dump_stream(oeb, path) + with open(path, 'w+b') as stream: + return self._dump_stream(oeb, stream) + + def _dump_stream(self, oeb, stream): + self._oeb = oeb + self._logger = oeb.logger self._stream = stream self._sections = [StringIO() for i in xrange(4)] self._directory = [] self._meta = None - self._dump() + self._litize_oeb() + self._write_content() def _write(self, *data): for datum in data: @@ -345,7 +345,7 @@ class LitWriter(object): def _tell(self): return self._stream.tell() - def _dump(self): + def _write_content(self): # Build content sections self._build_sections() @@ -474,8 +474,7 @@ class LitWriter(object): secnum = 0 if not isinstance(data, basestring): self._add_folder(name) - rebin = ReBinary(data, item.href, self._oeb, map=HTML_MAP, - logger=self._logger) + rebin = ReBinary(data, item, self._oeb, map=HTML_MAP) self._add_file(name + '/ahc', rebin.ahc, 0) self._add_file(name + '/aht', rebin.aht, 0) item.page_breaks = rebin.page_breaks @@ -554,8 +553,7 @@ class LitWriter(object): meta.attrib['ms--minimum_level'] = '0' meta.attrib['ms--attr5'] = '1' meta.attrib['ms--guid'] = '{%s}' % str(uuid.uuid4()).upper() - rebin = ReBinary(meta, 'content.opf', self._oeb, map=OPF_MAP, - logger=self._logger) + rebin = ReBinary(meta, None, self._oeb, map=OPF_MAP) meta = rebin.content self._meta = meta self._add_file('/meta', meta) @@ -719,19 +717,31 @@ def option_parser(): help=_('Useful for debugging.')) return parser -def oeb2lit(opts, opfpath): - logger = LoggingInterface(logging.getLogger('oeb2lit')) +def oeb2lit(opts, inpath): + logger = Logger(logging.getLogger('oeb2lit')) logger.setup_cli_handler(opts.verbose) - litpath = opts.output - if litpath is None: - litpath = os.path.basename(opfpath) - litpath = os.path.splitext(litpath)[0] + '.lit' - litpath = os.path.abspath(litpath) - lit = LitWriter(OEBBook(opfpath, logger=logger), logger=logger) - with open(litpath, 'wb') as f: - lit.dump(f) - run_plugins_on_postprocess(litpath, 'lit') - logger.log_info(_('Output written to ')+litpath) + outpath = opts.output + if outpath is None: + outpath = os.path.basename(inpath) + outpath = os.path.splitext(outpath)[0] + '.lit' + outpath = os.path.abspath(outpath) + context = Context('Firefox', 'MSReader') + oeb = OEBBook(inpath, logger=logger) + tocadder = HTMLTOCAdder() + tocadder.transform(oeb, context) + mangler = CaseMangler() + mangler.transform(oeb, context) + fbase = context.dest.fbase + flattener = CSSFlattener(fbase=fbase, unfloat=True, untable=True) + flattener.transform(oeb, context) + rasterizer = SVGRasterizer() + rasterizer.transform(oeb, context) + trimmer = ManifestTrimmer() + trimmer.transform(oeb, context) + lit = LitWriter() + lit.dump(oeb, outpath) + run_plugins_on_postprocess(outpath, 'lit') + logger.info(_('Output written to ') + outpath) def main(argv=sys.argv): @@ -740,8 +750,8 @@ def main(argv=sys.argv): if len(args) != 1: parser.print_help() return 1 - opfpath = args[0] - oeb2lit(opts, opfpath) + inpath = args[0] + oeb2lit(opts, inpath) return 0 if __name__ == '__main__': diff --git a/src/calibre/ebooks/lrf/comic/convert_from.py b/src/calibre/ebooks/lrf/comic/convert_from.py index 21c2587d10..0569bf3733 100755 --- a/src/calibre/ebooks/lrf/comic/convert_from.py +++ b/src/calibre/ebooks/lrf/comic/convert_from.py @@ -425,7 +425,7 @@ def do_convert(path_to_file, opts, notification=lambda m, p: p, output_format='l thumbnail = None if not pages: raise ValueError('Could not find any pages in the comic: %s'%source) - if not opts.no_process: + if not getattr(opts, 'no_process', False): pages, failures, tdir2 = process_pages(pages, opts, notification) if not pages: raise ValueError('Could not find any valid pages in the comic: %s'%source) @@ -443,7 +443,7 @@ def do_convert(path_to_file, opts, notification=lambda m, p: p, output_format='l if output_format == 'pdf': create_pdf(pages, opts.profile, opts, thumbnail=thumbnail) shutil.rmtree(tdir) - if not opts.no_process: + if not getattr(opts, 'no_process', False): shutil.rmtree(tdir2) @@ -457,7 +457,7 @@ def main(args=sys.argv, notification=None, output_format='lrf'): if not callable(notification): pb = ProgressBar(terminal_controller, _('Rendering comic pages...'), - no_progress_bar=opts.no_progress_bar) + no_progress_bar=opts.no_progress_bar or getattr(opts, 'no_process', False)) notification = pb.update source = os.path.abspath(args[1]) diff --git a/src/calibre/ebooks/lrf/html/convert_from.py b/src/calibre/ebooks/lrf/html/convert_from.py index 292ae0b50b..e884ea7213 100644 --- a/src/calibre/ebooks/lrf/html/convert_from.py +++ b/src/calibre/ebooks/lrf/html/convert_from.py @@ -109,6 +109,10 @@ class HTMLConverter(object, LoggingInterface): # Remove self closing script tags as they also mess up BeautifulSoup (re.compile(r'(?i)]+?/>'), lambda match: ''), + # BeautifulSoup treats self closing

tags as open
tags + (re.compile(r'(?i)<\s*div([^>]*)/\s*>'), + lambda match: '
'%match.group(1)) + ] # Fix Baen markup BAEN = [ @@ -122,7 +126,7 @@ class HTMLConverter(object, LoggingInterface): # Fix pdftohtml markup PDFTOHTML = [ # Remove
tags - (re.compile(r'', re.IGNORECASE), lambda match: ' '), + (re.compile(r'', re.IGNORECASE), lambda match: '
'), # Remove page numbers (re.compile(r'\d+
', re.IGNORECASE), lambda match: ''), # Remove
and replace

with

@@ -576,20 +580,20 @@ class HTMLConverter(object, LoggingInterface): if (css.has_key('display') and css['display'].lower() == 'none') or \ (css.has_key('visibility') and css['visibility'].lower() == 'hidden'): return '' - text = u'' + text, alt_text = u'', u'' for c in tag.contents: if limit != None and len(text) > limit: break if isinstance(c, HTMLConverter.IGNORED_TAGS): - return u'' + continue if isinstance(c, NavigableString): text += unicode(c) elif isinstance(c, Tag): if c.name.lower() == 'img' and c.has_key('alt'): - text += c['alt'] - return text + alt_text += c['alt'] + continue text += self.get_text(c) - return text + return text if text.strip() else alt_text def process_links(self): def add_toc_entry(text, target): diff --git a/src/calibre/ebooks/lrf/objects.py b/src/calibre/ebooks/lrf/objects.py index 29c0d8de44..ff9edc4e47 100644 --- a/src/calibre/ebooks/lrf/objects.py +++ b/src/calibre/ebooks/lrf/objects.py @@ -700,7 +700,7 @@ class Text(LRFStream): def add_text(self, text): s = unicode(text, "utf-16-le") if s: - s = s.translate(self.text_map) + s = s.translate(self.text_map) self.content.append(self.entity_pattern.sub(entity_to_unicode, s)) def end_container(self, tag, stream): @@ -799,18 +799,39 @@ class Text(LRFStream): length = len(self.stream) style = self.style.as_dict() current_style = style.copy() + text_tags = set(list(TextAttr.tag_map.keys()) + \ + list(Text.text_tags.keys()) + \ + list(ruby_tags.keys())) + text_tags -= set([0xf500+i for i in range(10)]) + text_tags.add(0xf5cc) while stream.tell() < length: - # Is there some text beofre a tag? - pos = self.stream.find('\xf5', stream.tell()) - 1 - if pos > 0: - self.add_text(self.stream[stream.tell():pos]) - stream.seek(pos) - elif pos == -2: # No tags in this stream + # Is there some text before a tag? + def find_first_tag(start): + pos = self.stream.find('\xf5', start) + if pos == -1: + return -1 + try: + stream.seek(pos-1) + _t = Tag(stream) + if _t.id in text_tags: + return pos-1 + return find_first_tag(pos+1) + + + except: + return find_first_tag(pos+1) + + start_pos = stream.tell() + tag_pos = find_first_tag(start_pos) + if tag_pos >= start_pos: + if tag_pos > start_pos: + self.add_text(self.stream[start_pos:tag_pos]) + stream.seek(tag_pos) + else: # No tags in this stream self.add_text(self.stream) stream.seek(0, 2) - print repr(self.stream) break tag = Tag(stream) @@ -1166,7 +1187,8 @@ class TOCObject(LRFStream): refpage = struct.unpack("' +__docformat__ = 'restructuredtext en' + +import sys, os, glob, logging + +from calibre.ebooks.epub.from_any import any2epub, formats, USAGE +from calibre.ebooks.epub import config as common_config +from calibre.ptempfile import TemporaryDirectory +from calibre.ebooks.mobi.writer import oeb2mobi, add_mobi_options + +def config(defaults=None): + return common_config(defaults=defaults, name='mobi') + +def option_parser(usage=USAGE): + usage = usage % ('Mobipocket', formats()) + parser = config().option_parser(usage=usage) + add_mobi_options(parser) + return parser + +def any2mobi(opts, path): + ext = os.path.splitext(path)[1] + if not ext: + raise ValueError('Unknown file type: '+path) + ext = ext.lower()[1:] + + if opts.output is None: + opts.output = os.path.splitext(os.path.basename(path))[0]+'.mobi' + + opts.output = os.path.abspath(opts.output) + orig_output = opts.output + + with TemporaryDirectory('_any2mobi') as tdir: + oebdir = os.path.join(tdir, 'oeb') + os.mkdir(oebdir) + opts.output = os.path.join(tdir, 'dummy.epub') + opts.profile = 'None' + any2epub(opts, path, create_epub=False, oeb_cover=True, extract_to=oebdir) + opf = glob.glob(os.path.join(oebdir, '*.opf'))[0] + opts.output = orig_output + logging.getLogger('html2epub').info(_('Creating Mobipocket file from EPUB...')) + oeb2mobi(opts, opf) + + +def main(args=sys.argv): + parser = option_parser() + opts, args = parser.parse_args(args) + if len(args) < 2: + parser.print_help() + print 'No input file specified.' + return 1 + any2mobi(opts, args[1]) + return 0 + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/calibre/ebooks/mobi/mobiml.py b/src/calibre/ebooks/mobi/mobiml.py index 973ee34b36..7a74bd9401 100644 --- a/src/calibre/ebooks/mobi/mobiml.py +++ b/src/calibre/ebooks/mobi/mobiml.py @@ -114,10 +114,10 @@ class MobiMLizer(object): def mobimlize_measure(self, ptsize): if isinstance(ptsize, basestring): return ptsize - fbase = self.profile.fbase - if ptsize < fbase: + embase = self.profile.fbase + if round(ptsize) < embase: return "%dpt" % int(round(ptsize)) - return "%dem" % int(round(ptsize / fbase)) + return "%dem" % int(round(ptsize / embase)) def preize_text(self, text): text = unicode(text).replace(u' ', u'\xa0') @@ -171,8 +171,7 @@ class MobiMLizer(object): para = etree.SubElement(para, XHTML('blockquote')) emleft -= 1 else: - ptag = 'p' #tag if tag in HEADER_TAGS else 'p' - para = wrapper = etree.SubElement(parent, XHTML(ptag)) + para = wrapper = etree.SubElement(parent, XHTML('p')) bstate.inline = bstate.para = para vspace = bstate.vpadding + bstate.vmargin bstate.vpadding = bstate.vmargin = 0 @@ -213,11 +212,11 @@ class MobiMLizer(object): inline = etree.SubElement(inline, XHTML('sup')) elif valign == 'sub': inline = etree.SubElement(inline, XHTML('sub')) - if istate.family == 'monospace': - inline = etree.SubElement(inline, XHTML('tt')) - if fsize != 3: + elif fsize != 3: inline = etree.SubElement(inline, XHTML('font'), size=str(fsize)) + if istate.family == 'monospace': + inline = etree.SubElement(inline, XHTML('tt')) if istate.italic: inline = etree.SubElement(inline, XHTML('i')) if istate.bold: @@ -241,7 +240,8 @@ class MobiMLizer(object): or namespace(elem.tag) != XHTML_NS: return style = stylizer.style(elem) - if style['display'] == 'none' \ + # does not exist lalalala + if style['display'] in ('none', 'oeb-page-head', 'oeb-page-foot') \ or style['visibility'] == 'hidden': return tag = barename(elem.tag) @@ -303,7 +303,7 @@ class MobiMLizer(object): else: istate.family = 'serif' valign = style['vertical-align'] - if valign in ('super', 'sup') or asfloat(valign) > 0: + if valign in ('super', 'text-top') or asfloat(valign) > 0: istate.valign = 'super' elif valign == 'sub' or asfloat(valign) < 0: istate.valign = 'sub' diff --git a/src/calibre/ebooks/mobi/palmdoc.py b/src/calibre/ebooks/mobi/palmdoc.py index 340b6d37fd..eedab1c88f 100644 --- a/src/calibre/ebooks/mobi/palmdoc.py +++ b/src/calibre/ebooks/mobi/palmdoc.py @@ -69,15 +69,15 @@ def compress_doc(data): out.write(pack('>B', onch ^ 0x80)) i += 1 continue - if och == 0 or (och >= 9 and och < 0x80): + if och == 0 or (och > 8 and och < 0x80): out.write(ch) else: j = i binseq = [ch] - while j < ldata: + while j < ldata and len(binseq) < 8: ch = data[j] och = ord(ch) - if och < 1 or (och > 8 and och < 0x80): + if och == 0 or (och > 8 and och < 0x80): break binseq.append(ch) j += 1 diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py index e46f3a8a82..63ef7b0b14 100644 --- a/src/calibre/ebooks/mobi/reader.py +++ b/src/calibre/ebooks/mobi/reader.py @@ -33,8 +33,7 @@ class EXTHHeader(object): self.length, self.num_items = struct.unpack('>LL', raw[4:12]) raw = raw[12:] pos = 0 - - self.mi = MetaInformation('Unknown', ['Unknown']) + self.mi = MetaInformation(_('Unknown'), [_('Unknown')]) self.has_fake_cover = True for i in range(self.num_items): @@ -49,14 +48,24 @@ class EXTHHeader(object): self.cover_offset, = struct.unpack('>L', content) elif id == 202: self.thumbnail_offset, = struct.unpack('>L', content) + #else: + # print 'unknown record', id, repr(content) title = re.search(r'\0+([^\0]+)\0+', raw[pos:]) if title: - self.mi.title = title.group(1).decode(codec, 'ignore') + title = title.group(1).decode(codec, 'replace') + if len(title) > 2: + self.mi.title = title + else: + title = re.search(r'\0+([^\0]+)\0+', ''.join(reversed(raw[pos:]))) + if title: + self.mi.title = ''.join(reversed(title.group(1).decode(codec, 'replace'))) def process_metadata(self, id, content, codec): if id == 100: - self.mi.authors = [content.decode(codec, 'ignore').strip()] + if self.mi.authors == [_('Unknown')]: + self.mi.authors = [] + self.mi.authors.append(content.decode(codec, 'ignore').strip()) elif id == 101: self.mi.publisher = content.decode(codec, 'ignore').strip() elif id == 103: @@ -67,7 +76,8 @@ class EXTHHeader(object): if not self.mi.tags: self.mi.tags = [] self.mi.tags.append(content.decode(codec, 'ignore')) - + #else: + # print 'unhandled metadata record', id, repr(content), codec class BookHeader(object): @@ -466,6 +476,10 @@ def get_metadata(stream): cover = os.path.join(tdir, mi.cover) if os.access(cover, os.R_OK): mi.cover_data = ('JPEG', open(os.path.join(tdir, mi.cover), 'rb').read()) + else: + path = os.path.join(tdir, 'images', '00001.jpg') + if os.access(path, os.R_OK): + mi.cover_data = ('JPEG', open(path, 'rb').read()) return mi def option_parser(): diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py index 9332e13ac5..9162bf8d2d 100644 --- a/src/calibre/ebooks/mobi/writer.py +++ b/src/calibre/ebooks/mobi/writer.py @@ -17,26 +17,30 @@ import re from itertools import izip, count from collections import defaultdict from urlparse import urldefrag +import logging from lxml import etree from PIL import Image from calibre.ebooks.oeb.base import XML_NS, XHTML, XHTML_NS, OEB_DOCS, \ OEB_RASTER_IMAGES from calibre.ebooks.oeb.base import xpath, barename, namespace, prefixname -from calibre.ebooks.oeb.base import FauxLogger, OEBBook +from calibre.ebooks.oeb.base import Logger, OEBBook from calibre.ebooks.oeb.profile import Context from calibre.ebooks.oeb.transforms.flatcss import CSSFlattener from calibre.ebooks.oeb.transforms.rasterize import SVGRasterizer from calibre.ebooks.oeb.transforms.trimmanifest import ManifestTrimmer from calibre.ebooks.oeb.transforms.htmltoc import HTMLTOCAdder +from calibre.ebooks.oeb.transforms.manglecase import CaseMangler from calibre.ebooks.mobi.palmdoc import compress_doc from calibre.ebooks.mobi.langcodes import iana2mobi from calibre.ebooks.mobi.mobiml import MBP_NS, MBP, MobiMLizer +from calibre.customize.ui import run_plugins_on_postprocess +from calibre.utils.config import OptionParser +from optparse import OptionGroup # TODO: # - Allow override CSS (?) # - Generate index records -# - Generate in-content ToC -# - Command line options, etc. +# - Optionally rasterize tables EXTH_CODES = { 'creator': 100, @@ -59,7 +63,8 @@ UNCOMPRESSED = 1 PALMDOC = 2 HUFFDIC = 17480 -MAX_IMAGE_SIZE = 63 * 1024 +PALM_MAX_IMAGE_SIZE = 63 * 1024 +OTHER_MAX_IMAGE_SIZE = 10 * 1024 * 1024 MAX_THUMB_SIZE = 16 * 1024 MAX_THUMB_DIMEN = (180, 240) @@ -88,7 +93,6 @@ class Serializer(object): NSRMAP = {'': None, XML_NS: 'xml', XHTML_NS: '', MBP_NS: 'mbp'} def __init__(self, oeb, images): - oeb.logger.info('Serializing markup content...') self.oeb = oeb self.images = images self.id_offsets = {} @@ -117,10 +121,16 @@ class Serializer(object): path, frag = urldefrag(ref.href) if hrefs[path].media_type not in OEB_DOCS: continue - buffer.write('') + # Space required or won't work, I kid you not + buffer.write(' />') buffer.write('') def serialize_href(self, href, base=None): @@ -144,6 +154,12 @@ class Serializer(object): def serialize_body(self): buffer = self.buffer buffer.write('') + # CybookG3 'Start Reading' link + if 'text' in self.oeb.guide: + href = self.oeb.guide['text'].href + buffer.write('') spine = [item for item in self.oeb.spine if item.linear] spine.extend([item for item in self.oeb.spine if not item.linear]) for item in spine: @@ -185,10 +201,12 @@ class Serializer(object): if attr == 'href': if self.serialize_href(val, item): continue - elif attr == 'src' and val in hrefs: - index = self.images[val] - buffer.write('recindex="%05d"' % index) - continue + elif attr == 'src': + href = item.abshref(val) + if href in hrefs: + index = self.images[href] + buffer.write('recindex="%05d"' % index) + continue buffer.write(attr) buffer.write('="') self.serialize_text(val, quot=True) @@ -223,9 +241,11 @@ class Serializer(object): class MobiWriter(object): - def __init__(self, compression=None, logger=FauxLogger()): + COLLAPSE_RE = re.compile(r'[ \t\r\n\v]+') + + def __init__(self, compression=None, imagemax=None): self._compression = compression or UNCOMPRESSED - self._logger = logger + self._imagemax = imagemax or OTHER_MAX_IMAGE_SIZE def dump(self, oeb, path): if hasattr(path, 'write'): @@ -293,6 +313,7 @@ class MobiWriter(object): return data, overlap def _generate_text(self): + self._oeb.logger.info('Serializing markup content...') serializer = Serializer(self._oeb, self._images) breaks = serializer.breaks text = serializer.text @@ -300,6 +321,8 @@ class MobiWriter(object): text = StringIO(text) nrecords = 0 offset = 0 + if self._compression != UNCOMPRESSED: + self._oeb.logger.info('Compressing markup content...') data, overlap = self._read_text_record(text) while len(data) > 0: if self._compression == PALMDOC: @@ -335,7 +358,9 @@ class MobiWriter(object): format = image.format changed = False if image.format not in ('JPEG', 'GIF'): - format = 'GIF' + width, height = image.size + area = width * height + format = 'GIF' if area <= 40000 else 'JPEG' changed = True if dimen is not None: image.thumbnail(dimen, Image.ANTIALIAS) @@ -368,13 +393,14 @@ class MobiWriter(object): return data def _generate_images(self): + self._oeb.logger.warn('Serializing images...') images = [(index, href) for href, index in self._images.items()] images.sort() metadata = self._oeb.metadata coverid = metadata.cover[0] if metadata.cover else None for _, href in images: item = self._oeb.manifest.hrefs[href] - data = self._rescale_image(item.data, MAX_IMAGE_SIZE) + data = self._rescale_image(item.data, self._imagemax) self._records.append(data) def _generate_record0(self): @@ -418,7 +444,8 @@ class MobiWriter(object): if term not in EXTH_CODES: continue code = EXTH_CODES[term] for item in oeb.metadata[term]: - data = unicode(item).encode('utf-8') + data = self.COLLAPSE_RE.sub(' ', unicode(item)) + data = data.encode('utf-8') exth.write(pack('>II', code, len(data) + 8)) exth.write(data) nrecs += 1 @@ -467,29 +494,90 @@ class MobiWriter(object): self._write(record) -def main(argv=sys.argv): - from calibre.ebooks.oeb.base import DirWriter - inpath, outpath = argv[1:] - context = Context('Firefox', 'MobiDesktop') - oeb = OEBBook(inpath) - #writer = MobiWriter(compression=PALMDOC) - writer = MobiWriter(compression=UNCOMPRESSED) - #writer = DirWriter() +def add_mobi_options(parser): + profiles = Context.PROFILES.keys() + profiles.sort() + profiles = ', '.join(profiles) + group = OptionGroup(parser, _('Mobipocket'), + _('Mobipocket-specific options.')) + group.add_option( + '-c', '--compress', default=False, action='store_true', + help=_('Compress file text using PalmDOC compression.')) + group.add_option( + '-r', '--rescale-images', default=False, action='store_true', + help=_('Modify images to meet Palm device size limitations.')) + parser.add_option_group(group) + group = OptionGroup(parser, _('Profiles'), _('Device renderer profiles. ' + 'Affects conversion of default font sizes and rasterization ' + 'resolution. Valid profiles are: %s.') % profiles) + group.add_option( + '--source-profile', default='Browser', metavar='PROFILE', + help=_("Source renderer profile. Default is 'Browser'.")) + group.add_option( + '--dest-profile', default='CybookG3', metavar='PROFILE', + help=_("Destination renderer profile. Default is 'CybookG3'.")) + parser.add_option_group(group) + return + +def option_parser(): + parser = OptionParser(usage=_('%prog [options] OPFFILE')) + parser.add_option( + '-o', '--output', default=None, + help=_('Output file. Default is derived from input filename.')) + parser.add_option( + '-v', '--verbose', default=False, action='store_true', + help=_('Useful for debugging.')) + add_mobi_options(parser) + return parser + +def oeb2mobi(opts, inpath): + logger = Logger(logging.getLogger('oeb2mobi')) + logger.setup_cli_handler(opts.verbose) + outpath = opts.output + if outpath is None: + outpath = os.path.basename(inpath) + outpath = os.path.splitext(outpath)[0] + '.mobi' + source = opts.source_profile + if source not in Context.PROFILES: + logger.error(_('Unknown source profile %r') % source) + return 1 + dest = opts.dest_profile + if dest not in Context.PROFILES: + logger.error(_('Unknown destination profile %r') % dest) + return 1 + compression = PALMDOC if opts.compress else UNCOMPRESSED + imagemax = PALM_MAX_IMAGE_SIZE if opts.rescale_images else None + context = Context(source, dest) + oeb = OEBBook(inpath, logger=logger) + tocadder = HTMLTOCAdder() + tocadder.transform(oeb, context) + mangler = CaseMangler() + mangler.transform(oeb, context) fbase = context.dest.fbase fkey = context.dest.fnums.values() - tocadder = HTMLTOCAdder() flattener = CSSFlattener( fbase=fbase, fkey=fkey, unfloat=True, untable=True) - rasterizer = SVGRasterizer() - trimmer = ManifestTrimmer() - mobimlizer = MobiMLizer() - tocadder.transform(oeb, context) flattener.transform(oeb, context) + rasterizer = SVGRasterizer() rasterizer.transform(oeb, context) - mobimlizer.transform(oeb, context) + trimmer = ManifestTrimmer() trimmer.transform(oeb, context) + mobimlizer = MobiMLizer() + mobimlizer.transform(oeb, context) + writer = MobiWriter(compression=compression, imagemax=imagemax) writer.dump(oeb, outpath) - return 0 + run_plugins_on_postprocess(outpath, 'mobi') + logger.info(_('Output written to ') + outpath) + +def main(argv=sys.argv): + parser = option_parser() + opts, args = parser.parse_args(argv[1:]) + if len(args) != 1: + parser.print_help() + return 1 + inpath = args[0] + retval = oeb2mobi(opts, inpath) + return retval if __name__ == '__main__': sys.exit(main()) diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index f5262c977f..4248657e23 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -67,11 +67,13 @@ OEB_IMAGES = set([GIF_MIME, JPEG_MIME, PNG_MIME, SVG_MIME]) MS_COVER_TYPE = 'other.ms-coverimage-standard' -ENTITYDEFS = dict(htmlentitydefs.entitydefs) +recode = lambda s: s.decode('iso-8859-1').encode('ascii', 'xmlcharrefreplace') +ENTITYDEFS = dict((k, recode(v)) for k, v in htmlentitydefs.entitydefs.items()) del ENTITYDEFS['lt'] del ENTITYDEFS['gt'] del ENTITYDEFS['quot'] del ENTITYDEFS['amp'] +del recode def element(parent, *args, **kwargs): @@ -341,16 +343,19 @@ class Manifest(object): self._data = None return property(fget, fset, fdel) data = data() - + def __str__(self): data = self.data if isinstance(data, etree._Element): return xml2str(data) return str(data) - + def __eq__(self, other): return id(self) == id(other) - + + def __ne__(self, other): + return not self.__eq__(other) + def __cmp__(self, other): result = cmp(self.spine_position, other.spine_position) if result != 0: @@ -534,52 +539,81 @@ class Spine(object): class Guide(object): class Reference(object): + _TYPES_TITLES = [('cover', 'Cover'), ('title-page', 'Title Page'), + ('toc', 'Table of Contents'), ('index', 'Index'), + ('glossary', 'Glossary'), ('acknowledgements', 'Acknowledgements'), + ('bibliography', 'Bibliography'), ('colophon', 'Colophon'), + ('copyright-page', 'Copyright'), ('dedication', 'Dedication'), + ('epigraph', 'Epigraph'), ('foreword', 'Foreword'), + ('loi', 'List of Illustrations'), ('lot', 'List of Tables'), + ('notes', 'Notes'), ('preface', 'Preface'), + ('text', 'Main Text')] + TYPES = set(t for t, _ in _TYPES_TITLES) + TITLES = dict(_TYPES_TITLES) + ORDER = dict((t, i) for (t, _), i in izip(_TYPES_TITLES, count(0))) + def __init__(self, type, title, href): + if type.lower() in self.TYPES: + type = type.lower() + elif type not in self.TYPES and \ + not type.startswith('other.'): + type = 'other.' + type + if not title: + title = self.TITLES.get(type, None) self.type = type self.title = title self.href = urlnormalize(href) - + def __repr__(self): return 'Reference(type=%r, title=%r, href=%r)' \ % (self.type, self.title, self.href) + + def _order(): + def fget(self): + return self.ORDER.get(self.type, self.type) + return property(fget=fget) + _order = _order() + + def __cmp__(self, other): + if not isinstance(other, Guide.Reference): + return NotImplemented + return cmp(self._order, other._order) def __init__(self, oeb): self.oeb = oeb self.refs = {} - + def add(self, type, title, href): ref = self.Reference(type, title, href) self.refs[type] = ref return ref - - def by_type(self, type): - return self.ref_types[type] - + def iterkeys(self): for type in self.refs: yield type __iter__ = iterkeys - + def values(self): - for ref in self.refs.values(): - yield ref - + values = list(self.refs.values()) + values.sort() + return values + def items(self): for type, ref in self.refs.items(): yield type, ref def __getitem__(self, key): return self.refs[key] - + def __delitem__(self, key): del self.refs[key] def __contains__(self, key): return key in self.refs - + def __len__(self): return len(self.refs) - + def to_opf1(self, parent=None): elem = element(parent, 'guide') for ref in self.refs.values(): @@ -914,11 +948,11 @@ class OEBBook(object): cover = self.manifest.hrefs[href] elif xpath(html, '//h:img[position()=1]'): img = xpath(html, '//h:img[position()=1]')[0] - href = img.get('src') + href = spine0.abshref(img.get('src')) cover = self.manifest.hrefs[href] elif xpath(html, '//h:object[position()=1]'): object = xpath(html, '//h:object[position()=1]')[0] - href = object.get('data') + href = spine0.abshref(object.get('data')) cover = self.manifest.hrefs[href] elif xpath(html, '//svg:svg[position()=1]'): svg = copy.deepcopy(xpath(html, '//svg:svg[position()=1]')[0]) diff --git a/src/calibre/ebooks/oeb/profile.py b/src/calibre/ebooks/oeb/profile.py index 3fdec6c8a5..17408fac78 100644 --- a/src/calibre/ebooks/oeb/profile.py +++ b/src/calibre/ebooks/oeb/profile.py @@ -36,26 +36,36 @@ PROFILES = { fsizes=[7.5, 9, 10, 12, 15.5, 20, 22, 24]), 'MSReader': - Profile(width=480, height=652, dpi=100.0, fbase=13, + Profile(width=480, height=652, dpi=96, fbase=13, fsizes=[10, 11, 13, 16, 18, 20, 22, 26]), # Not really, but let's pretend - 'MobiDesktop': - Profile(width=280, height=300, dpi=96, fbase=18, - fsizes=[14, 14, 16, 18, 20, 22, 22, 24]), + 'Mobipocket': + Profile(width=600, height=800, dpi=96, fbase=18, + fsizes=[14, 14, 16, 18, 20, 22, 24, 26]), - # No clue on usable screen size and DPI - 'CybookG3': - Profile(width=584, height=754, dpi=168.451, fbase=12, - fsizes=[9, 10, 11, 12, 14, 17, 20, 24]), + # No clue on usable screen size; DPI should be good + 'HanlinV3': + Profile(width=584, height=754, dpi=168.451, fbase=16, + fsizes=[12, 12, 14, 16, 18, 20, 22, 24]), - 'Firefox': + 'CybookG3': + Profile(width=600, height=800, dpi=168.451, fbase=16, + fsizes=[12, 12, 14, 16, 18, 20, 22, 24]), + + 'Kindle': + Profile(width=525, height=640, dpi=168.451, fbase=16, + fsizes=[12, 12, 14, 16, 18, 20, 22, 24]), + + 'Browser': Profile(width=800, height=600, dpi=100.0, fbase=12, fsizes=[5, 7, 9, 12, 13.5, 17, 20, 22, 24]) } class Context(object): + PROFILES = PROFILES + def __init__(self, source, dest): if source in PROFILES: source = PROFILES[source] diff --git a/src/calibre/ebooks/oeb/stylizer.py b/src/calibre/ebooks/oeb/stylizer.py index 9c8af446f4..8668d89975 100644 --- a/src/calibre/ebooks/oeb/stylizer.py +++ b/src/calibre/ebooks/oeb/stylizer.py @@ -23,7 +23,7 @@ from cssutils.css import CSSStyleRule, CSSPageRule, CSSStyleDeclaration, \ from lxml import etree from lxml.cssselect import css_to_xpath, ExpressionError from calibre.ebooks.oeb.base import XHTML, XHTML_NS, CSS_MIME, OEB_STYLES -from calibre.ebooks.oeb.base import barename, urlnormalize +from calibre.ebooks.oeb.base import XPNSMAP, xpath, barename, urlnormalize from calibre.ebooks.oeb.profile import PROFILES from calibre.resources import html_css @@ -87,10 +87,6 @@ FONT_SIZE_NAMES = set(['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large']) -XPNSMAP = {'h': XHTML_NS,} -def xpath(elem, expr): - return elem.xpath(expr, namespaces=XPNSMAP) - class CSSSelector(etree.XPath): MIN_SPACE_RE = re.compile(r' *([>~+]) *') LOCAL_NAME_RE = re.compile(r"(?' + +import sys +import os +from lxml import etree +from calibre.ebooks.oeb.base import XML, XHTML, XHTML_NS +from calibre.ebooks.oeb.base import XHTML_MIME, CSS_MIME +from calibre.ebooks.oeb.base import element + +STYLE_CSS = { + 'nested': """ +.calibre_toc_header { + text-align: center; +} +.calibre_toc_block { + margin-left: 1.2em; + text-indent: -1.2em; +} +.calibre_toc_block .calibre_toc_block { + margin-left: 2.4em; +} +.calibre_toc_block .calibre_toc_block .calibre_toc_block { + margin-left: 3.6em; +} +""", + + 'centered': """ +.calibre_toc_header { + text-align: center; +} +.calibre_toc_block { + text-align: center; +} +body > .calibre_toc_block { + margin-top: 1.2em; +} +""" + } + +class HTMLTOCAdder(object): + def __init__(self, style='nested'): + self.style = style + + def transform(self, oeb, context): + if 'toc' in oeb.guide: + return + oeb.logger.info('Generating in-line TOC...') + style = self.style + if style not in STYLE_CSS: + oeb.logger.error('Unknown TOC style %r' % style) + style = 'nested' + id, css_href = oeb.manifest.generate('tocstyle', 'tocstyle.css') + oeb.manifest.add(id, css_href, CSS_MIME, data=STYLE_CSS[style]) + language = str(oeb.metadata.language[0]) + contents = element(None, XHTML('html'), nsmap={None: XHTML_NS}, + attrib={XML('lang'): language}) + head = element(contents, XHTML('head')) + title = element(head, XHTML('title')) + title.text = 'Table of Contents' + element(head, XHTML('link'), rel='stylesheet', type=CSS_MIME, + href=css_href) + body = element(contents, XHTML('body'), + attrib={'class': 'calibre_toc'}) + h1 = element(body, XHTML('h1'), + attrib={'class': 'calibre_toc_header'}) + h1.text = 'Table of Contents' + self.add_toc_level(body, oeb.toc) + id, href = oeb.manifest.generate('contents', 'contents.xhtml') + item = oeb.manifest.add(id, href, XHTML_MIME, data=contents) + oeb.spine.add(item, linear=False) + oeb.guide.add('toc', 'Table of Contents', href) + + def add_toc_level(self, elem, toc): + for node in toc: + block = element(elem, XHTML('div'), + attrib={'class': 'calibre_toc_block'}) + line = element(block, XHTML('a'), + attrib={'href': node.href, + 'class': 'calibre_toc_line'}) + line.text = node.title + self.add_toc_level(block, node) diff --git a/src/calibre/ebooks/oeb/transforms/manglecase.py b/src/calibre/ebooks/oeb/transforms/manglecase.py new file mode 100644 index 0000000000..3a3d91364f --- /dev/null +++ b/src/calibre/ebooks/oeb/transforms/manglecase.py @@ -0,0 +1,112 @@ +''' +CSS case-mangling transform. +''' +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2008, Marshall T. Vandegrift ' + +import sys +import os +import re +import operator +import math +from itertools import chain +from collections import defaultdict +from lxml import etree +from calibre.ebooks.oeb.base import XHTML, XHTML_NS +from calibre.ebooks.oeb.base import CSS_MIME +from calibre.ebooks.oeb.base import namespace +from calibre.ebooks.oeb.stylizer import Stylizer + +CASE_MANGLER_CSS = """ +.calibre_lowercase { + font-variant: normal; + font-size: 0.65em; +} +""" + +TEXT_TRANSFORMS = set(['capitalize', 'uppercase', 'lowercase']) + +class CaseMangler(object): + def transform(self, oeb, context): + oeb.logger.info('Applying case-transforming CSS...') + self.oeb = oeb + self.profile = context.source + self.mangle_spine() + + def mangle_spine(self): + id, href = self.oeb.manifest.generate('manglecase', 'manglecase.css') + self.oeb.manifest.add(id, href, CSS_MIME, data=CASE_MANGLER_CSS) + for item in self.oeb.spine: + html = item.data + relhref = item.relhref(href) + etree.SubElement(html.find(XHTML('head')), XHTML('link'), + rel='stylesheet', href=relhref, type=CSS_MIME) + stylizer = Stylizer(html, item.href, self.oeb, self.profile) + self.mangle_elem(html.find(XHTML('body')), stylizer) + + def text_transform(self, transform, text): + if transform == 'capitalize': + return text.title() + elif transform == 'uppercase': + return text.upper() + elif transform == 'lowercase': + return text.lower() + return text + + def split_text(self, text): + results = [''] + isupper = text[0].isupper() + for char in text: + if char.isupper() == isupper: + results[-1] += char + else: + isupper = not isupper + results.append(char) + return results + + def smallcaps_elem(self, elem, attr): + texts = self.split_text(getattr(elem, attr)) + setattr(elem, attr, None) + last = elem if attr == 'tail' else None + attrib = {'class': 'calibre_lowercase'} + for text in texts: + if text.isupper(): + if last is None: + elem.text = text + else: + last.tail = text + else: + child = etree.Element(XHTML('span'), attrib=attrib) + child.text = text.upper() + if last is None: + elem.insert(0, child) + else: + # addnext() moves the tail for some reason + tail = last.tail + last.addnext(child) + last.tail = tail + child.tail = None + last = child + + def mangle_elem(self, elem, stylizer): + if not isinstance(elem.tag, basestring) or \ + namespace(elem.tag) != XHTML_NS: + return + children = list(elem) + style = stylizer.style(elem) + transform = style['text-transform'] + variant = style['font-variant'] + if elem.text: + if transform in TEXT_TRANSFORMS: + elem.text = self.text_transform(transform, elem.text) + if variant == 'small-caps': + self.smallcaps_elem(elem, 'text') + for child in children: + self.mangle_elem(child, stylizer) + if child.tail: + if transform in TEXT_TRANSFORMS: + child.tail = self.text_transform(transform, child.tail) + if variant == 'small-caps': + self.smallcaps_elem(child, 'tail') diff --git a/src/calibre/ebooks/oeb/transforms/rasterize.py b/src/calibre/ebooks/oeb/transforms/rasterize.py index 4ca2ccb856..69f1d0d133 100644 --- a/src/calibre/ebooks/oeb/transforms/rasterize.py +++ b/src/calibre/ebooks/oeb/transforms/rasterize.py @@ -21,11 +21,12 @@ from PyQt4.QtGui import QPainter from PyQt4.QtSvg import QSvgRenderer from PyQt4.QtGui import QApplication from calibre.ebooks.oeb.base import XHTML_NS, XHTML, SVG_NS, SVG, XLINK -from calibre.ebooks.oeb.base import SVG_MIME, PNG_MIME +from calibre.ebooks.oeb.base import SVG_MIME, PNG_MIME, JPEG_MIME from calibre.ebooks.oeb.base import xml2str, xpath, namespace, barename from calibre.ebooks.oeb.stylizer import Stylizer IMAGE_TAGS = set([XHTML('img'), XHTML('object')]) +KEEP_ATTRS = set(['class', 'style', 'width', 'height', 'align']) class SVGRasterizer(object): def __init__(self): @@ -41,7 +42,7 @@ class SVGRasterizer(object): self.rasterize_spine() self.rasterize_cover() - def rasterize_svg(self, elem, width=0, height=0): + def rasterize_svg(self, elem, width=0, height=0, format='PNG'): data = QByteArray(xml2str(elem)) svg = QSvgRenderer(data) size = svg.defaultSize() @@ -52,6 +53,9 @@ class SVGRasterizer(object): size.setHeight(box[3] - box[1]) if width or height: size.scale(width, height, Qt.KeepAspectRatio) + logger = self.oeb.logger + logger.info('Rasterizing %r to %dx%d' + % (elem, size.width(), size.height())) image = QImage(size, QImage.Format_ARGB32_Premultiplied) image.fill(QColor("white").rgb()) painter = QPainter(image) @@ -60,7 +64,7 @@ class SVGRasterizer(object): array = QByteArray() buffer = QBuffer(array) buffer.open(QIODevice.WriteOnly) - image.save(buffer, 'PNG') + image.save(buffer, format) return str(array) def dataize_manifest(self): @@ -113,11 +117,7 @@ class SVGRasterizer(object): def rasterize_inline(self, elem, style, item): width = style['width'] - if width == 'auto': - width = self.profile.width height = style['height'] - if height == 'auto': - height = self.profile.height width = (width / 72) * self.profile.dpi height = (height / 72) * self.profile.dpi elem = self.dataize_svg(item, elem) @@ -134,11 +134,7 @@ class SVGRasterizer(object): def rasterize_external(self, elem, style, item, svgitem): width = style['width'] - if width == 'auto': - width = self.profile.width height = style['height'] - if height == 'auto': - height = self.profile.height width = (width / 72) * self.profile.dpi height = (height / 72) * self.profile.dpi data = QByteArray(str(svgitem)) @@ -168,11 +164,16 @@ class SVGRasterizer(object): manifest.add(id, href, PNG_MIME, data=data) self.images[key] = href elem.tag = XHTML('img') + for attr in elem.attrib: + if attr not in KEEP_ATTRS: + del elem.attrib[attr] elem.attrib['src'] = item.relhref(href) - elem.text = None + if elem.text: + elem.attrib['alt'] = elem.text + elem.text = None for child in elem: elem.remove(child) - + def rasterize_cover(self): covers = self.oeb.metadata.cover if not covers: @@ -180,9 +181,9 @@ class SVGRasterizer(object): cover = self.oeb.manifest.ids[str(covers[0])] if not cover.media_type == SVG_MIME: return - logger = self.oeb.logger - logger.info('Rasterizing %r to %dx%d' % (cover.href, 600, 800)) - data = self.rasterize_svg(cover.data, 600, 800) + width = (self.profile.width / 72) * self.profile.dpi + height = (self.profile.height / 72) * self.profile.dpi + data = self.rasterize_svg(cover.data, width, height) href = os.path.splitext(cover.href)[0] + '.png' id, href = self.oeb.manifest.generate(cover.id, href) self.oeb.manifest.add(id, href, PNG_MIME, data=data) diff --git a/src/calibre/ebooks/oeb/transforms/trimmanifest.py b/src/calibre/ebooks/oeb/transforms/trimmanifest.py index b65116d16b..bd2c388245 100644 --- a/src/calibre/ebooks/oeb/transforms/trimmanifest.py +++ b/src/calibre/ebooks/oeb/transforms/trimmanifest.py @@ -9,6 +9,7 @@ __copyright__ = '2008, Marshall T. Vandegrift ' import sys import os from itertools import chain +from urlparse import urldefrag from lxml import etree import cssutils from calibre.ebooks.oeb.base import XPNSMAP, CSS_MIME, OEB_DOCS @@ -29,6 +30,11 @@ class ManifestTrimmer(object): used.add(oeb.manifest.hrefs[item.value]) elif item.value in oeb.manifest.ids: used.add(oeb.manifest.ids[item.value]) + for ref in oeb.guide.values(): + path, _ = urldefrag(ref.href) + if path in oeb.manifest.hrefs: + used.add(oeb.manifest.hrefs[path]) + # TOC items are required to be in the spine for item in oeb.spine: used.add(item) unchecked = used @@ -56,7 +62,6 @@ class ManifestTrimmer(object): cssutils.replaceUrls(sheet, replacer) used.update(new) unchecked = new - # All guide and TOC items are required to be in the spine for item in oeb.manifest.values(): if item not in used: oeb.logger.info('Trimming %r from manifest' % item.href) diff --git a/src/calibre/ebooks/pdf/pdftrim.py b/src/calibre/ebooks/pdf/pdftrim.py index b194d93b9d..c1e8fa2494 100644 --- a/src/calibre/ebooks/pdf/pdftrim.py +++ b/src/calibre/ebooks/pdf/pdftrim.py @@ -29,7 +29,7 @@ def config(defaults=None): c.add_opt('top_right_y', [ '-w', '--righty'], default=default_crop, help=_('Number of pixels to crop from the right most y (default is %d)')%default_crop ) c.add_opt('bounding', ['-b', '--bounding'], - help=_('A file generated by ghostscript which allows each page to be individually cropped')) + help=_('A file generated by ghostscript which allows each page to be individually cropped [gs -dSAFER -dNOPAUSE -dBATCH -sDEVICE=bbox > bounding] ')) return c @@ -38,14 +38,28 @@ def option_parser(): return c.option_parser(usage=_('''\ %prog [options] file.pdf - Crop a pdf. + Crops a pdf. ''')) def main(args=sys.argv): parser = option_parser() opts, args = parser.parse_args(args) - source = os.path.abspath(args[1]) - input_pdf = PdfFileReader(file(source, "rb")) + try: + source = os.path.abspath(args[1]) + input_pdf = PdfFileReader(file(source, "rb")) + except: + print "Unable to read input" + return 2 + title = _('Unknown') + author = _('Unknown') + try: + info = input_pdf.getDocumentInfo() + if info.title: + title = info.title + if info.author: + author = info.author + except: + pass if opts.bounding != None: try: bounding = open( opts.bounding , 'r' ) @@ -53,7 +67,7 @@ def main(args=sys.argv): except: print 'Error opening %s' % opts.bounding return 1 - output_pdf = PdfFileWriter() + output_pdf = PdfFileWriter(title=title,author=author) for page_number in range (0, input_pdf.getNumPages() ): page = input_pdf.getPage(page_number) if opts.bounding != None: diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 33f5585411..a77ec1beb4 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -136,16 +136,18 @@ class DeviceManager(Thread): return self.create_job(self._sync_booklists, done, args=[booklists], description=_('Send metadata to device')) - def _upload_books(self, files, names, on_card=False): + def _upload_books(self, files, names, on_card=False, metadata=None): '''Upload books to device: ''' - return self.device.upload_books(files, names, on_card, end_session=False) + return self.device.upload_books(files, names, on_card, + metadata=metadata, end_session=False) - def upload_books(self, done, files, names, on_card=False, titles=None): + def upload_books(self, done, files, names, on_card=False, titles=None, + metadata=None): desc = _('Upload %d books to device')%len(names) if titles: desc += u':' + u', '.join(titles) return self.create_job(self._upload_books, done, args=[files, names], - kwargs={'on_card':on_card}, description=desc) + kwargs={'on_card':on_card,'metadata':metadata}, description=desc) def add_books_to_metadata(self, locations, metadata, booklists): self.device.add_books_to_metadata(locations, metadata, booklists) diff --git a/src/calibre/gui2/dialogs/job_view.ui b/src/calibre/gui2/dialogs/job_view.ui index 953887ce7a..ffd4419da9 100644 --- a/src/calibre/gui2/dialogs/job_view.ui +++ b/src/calibre/gui2/dialogs/job_view.ui @@ -28,9 +28,6 @@ true - - 400 - diff --git a/src/calibre/gui2/dialogs/scheduler.py b/src/calibre/gui2/dialogs/scheduler.py index f61c220bb9..a4bfe9f665 100644 --- a/src/calibre/gui2/dialogs/scheduler.py +++ b/src/calibre/gui2/dialogs/scheduler.py @@ -75,7 +75,13 @@ def save_recipes(recipes): def load_recipes(): config.refresh() - return [Recipe().unpickle(r) for r in config.get('scheduled_recipes', [])] + recipes = [] + for r in config.get('scheduled_recipes', []): + r = Recipe().unpickle(r) + if r.builtin and not str(r.id).startswith('recipe_'): + continue + recipes.append(r) + return recipes class RecipeModel(QAbstractListModel, SearchQueryParser): @@ -438,7 +444,7 @@ class Scheduler(QObject): self.lock.unlock() def main(args=sys.argv): - app = QApplication([]) + QApplication([]) from calibre.library.database2 import LibraryDatabase2 d = SchedulerDialog(LibraryDatabase2('/home/kovid/documents/library')) d.exec_() diff --git a/src/calibre/gui2/images/news/telepolis.png b/src/calibre/gui2/images/news/telepolis.png new file mode 100644 index 0000000000..7b1c14b96c Binary files /dev/null and b/src/calibre/gui2/images/news/telepolis.png differ diff --git a/src/calibre/gui2/images/news/tomshardware_de.png b/src/calibre/gui2/images/news/tomshardware_de.png new file mode 100644 index 0000000000..c31412474c Binary files /dev/null and b/src/calibre/gui2/images/news/tomshardware_de.png differ diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index 3129fec47a..792d011883 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -385,13 +385,35 @@ class BooksModel(QAbstractTableModel): metadata.append(mi) return metadata + def get_preferred_formats_from_ids(self, ids, all_formats, mode='r+b'): + ans = [] + for id in ids: + format = None + fmts = self.db.formats(id, index_is_id=True) + if not fmts: + fmts = '' + available_formats = set(fmts.lower().split(',')) + for f in all_formats: + if f.lower() in available_formats: + format = f.lower() + break + if format is None: + ans.append(format) + else: + f = self.db.format(id, format, index_is_id=True, as_file=True, + mode=mode) + ans.append(f) + return ans + + + def get_preferred_formats(self, rows, formats, paths=False): ans = [] for row in (row.row() for row in rows): format = None fmts = self.db.formats(row) if not fmts: - return [] + fmts = '' db_formats = set(fmts.lower().split(',')) available_formats = set([f.lower() for f in formats]) u = available_formats.intersection(db_formats) diff --git a/src/calibre/gui2/lrf_renderer/document.py b/src/calibre/gui2/lrf_renderer/document.py index 691e1481ee..76c94d23f1 100644 --- a/src/calibre/gui2/lrf_renderer/document.py +++ b/src/calibre/gui2/lrf_renderer/document.py @@ -406,7 +406,8 @@ class Document(QGraphicsScene): for font in lrf.font_map: fdata = QByteArray(lrf.font_map[font].data) id = QFontDatabase.addApplicationFontFromData(fdata) - font_map[font] = [str(i) for i in QFontDatabase.applicationFontFamilies(id)][0] + if id != -1: + font_map[font] = [str(i) for i in QFontDatabase.applicationFontFamilies(id)][0] if load_substitutions: from calibre.ebooks.lrf.fonts.liberation import LiberationMono_BoldItalic diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 97ad934eeb..6ff5df412d 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -745,8 +745,8 @@ class Main(MainWindow, Ui_MainWindow): ''' titles = [i['title'] for i in metadata] job = self.device_manager.upload_books(Dispatcher(self.books_uploaded), - files, names, on_card=on_card, - titles=titles + files, names, on_card=on_card, + metadata=metadata, titles=titles ) self.upload_memory[job] = (metadata, on_card, memory, files) @@ -887,8 +887,12 @@ class Main(MainWindow, Ui_MainWindow): if self.device_connected: ids = list(dynamic.get('news_to_be_synced', set([]))) ids = [id for id in ids if self.library_view.model().db.has_id(id)] - files = [self.library_view.model().db.format(id, prefs['output_format'], index_is_id=True, as_file=True) for id in ids] + files = self.library_view.model().get_preferred_formats_from_ids( + ids, self.device_manager.device_class.FORMATS) files = [f for f in files if f is not None] + if not files: + dynamic.set('news_to_be_synced', set([])) + return metadata = self.library_view.model().get_metadata(ids, rows_are_ids=True) names = [] for mi in metadata: @@ -919,7 +923,7 @@ class Main(MainWindow, Ui_MainWindow): if cdata: mi['cover'] = self.cover_to_thumbnail(cdata) metadata = iter(metadata) - _files = self.library_view.model().get_preferred_formats(rows, + _files = self.library_view.model().get_preferred_formats(rows, self.device_manager.device_class.FORMATS, paths=True) files = [getattr(f, 'name', None) for f in _files] bad, good, gf, names = [], [], [], [] @@ -1479,8 +1483,9 @@ in which you want to store your books files. Any existing books will be automati return True - def shutdown(self): - self.write_settings() + def shutdown(self, write_settings=True): + if write_settings: + self.write_settings() self.job_manager.terminate_all_jobs() self.device_manager.keep_going = False self.cover_cache.stop() @@ -1500,6 +1505,7 @@ in which you want to store your books files. Any existing books will be automati def closeEvent(self, e): + self.write_settings() if self.system_tray_icon.isVisible(): if not dynamic['systray_msg'] and not isosx: info_dialog(self, 'calibre', 'calibre '+_('will keep running in the system tray. To close it, choose Quit in the context menu of the system tray.')).exec_() @@ -1509,7 +1515,7 @@ in which you want to store your books files. Any existing books will be automati else: if self.confirm_quit(): try: - self.shutdown() + self.shutdown(write_settings=False) except: pass e.accept() diff --git a/src/calibre/library/database.py b/src/calibre/library/database.py index f14a1174fc..ecf272618f 100644 --- a/src/calibre/library/database.py +++ b/src/calibre/library/database.py @@ -1551,9 +1551,6 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; - def has_book(self, mi): - return bool(self.conn.get('SELECT id FROM books where title=?', (mi.title,), all=False)) - def has_id(self, id): return self.conn.get('SELECT id FROM books where id=?', (id,), all=False) is not None diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index d3cefa47fa..da41dba57f 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -217,7 +217,11 @@ class ResultCache(SearchQueryParser): return self.index(id) def has_id(self, id): - return self._data[id] is not None + try: + return self._data[id] is not None + except IndexError: + pass + return False def refresh_ids(self, conn, ids): for id in ids: @@ -557,7 +561,15 @@ class LibraryDatabase2(LibraryDatabase): img.loadFromData(f.read()) return img return f if as_file else f.read() - + + def has_book(self, mi): + title = mi.title + if title: + if not isinstance(title, unicode): + title = title.decode(preferred_encoding, 'replace') + return bool(self.conn.get('SELECT id FROM books where title=?', (title,), all=False)) + return False + def has_cover(self, index, index_is_id=False): id = index if index_is_id else self.id(index) path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg') diff --git a/src/calibre/library/static/gui.js b/src/calibre/library/static/gui.js index b0a88db7f9..17f6f7f8fd 100644 --- a/src/calibre/library/static/gui.js +++ b/src/calibre/library/static/gui.js @@ -7,8 +7,8 @@ var column_titles = { 'rating' : 'Rating', 'date' : 'Date', 'tags' : 'Tags', - 'series' : 'Series', -} + 'series' : 'Series' +}; String.prototype.format = function() { var pattern = /\{\d+\}/g; @@ -47,7 +47,7 @@ function render_book(book) { // Render title cell var title = '{0}'.format(book.attr("title")) + '
'; var id = book.attr("id"); - var comments = $.trim(book.text()).replace(/\n\n/, '
'); + var comments = $.trim(book.text()).replace(/\n\n/, '
'); var formats = new Array(); var size = (parseFloat(book.attr('size'))/(1024*1024)).toFixed(1); var tags = book.attr('tags').replace(/,/g, ', '); @@ -70,22 +70,22 @@ function render_book(book) { authors += jQuery.trim(_authors[i]).replace(/ /g, ' ')+'
'; } if (authors) { authors = authors.slice(0, authors.length-6); } - + // Render rating cell var _rating = parseFloat(book.attr('rating'))/2.; var rating = ''; for (i = 0; i < _rating; i++) { rating += '★'} - + // Render date cell var _date = Date.parseExact(book.attr('timestamp'), 'yyyy/MM/dd HH:mm:ss'); var date = _date.toString('d MMM yyyy').replace(/ /g, ' '); - + // Render series cell var series = book.attr("series") if (series) { series += ' [{0}]'.format(book.attr('series_index')); } - + var cells = { 'title' : title, 'authors' : authors, @@ -93,12 +93,12 @@ function render_book(book) { 'date' : date, 'series' : series }; - + var row = ''; for (i = 0; i < cmap.length; i++) { row += '{1}'.format(cmap[i], cells[cmap[i]]); } - return '{1}'.format(id, row); + return '{1}'.format(id, row); } function fetch_library_books(start, num, timeout, sort, order, search) { @@ -112,15 +112,15 @@ function fetch_library_books(start, num, timeout, sort, order, search) { last_search = search; last_sort = sort; last_sort_order = order; - + if (current_library_request != null) { current_library_request.abort(); current_library_request = null; } - + $('#cover_pane').css('visibility', 'hidden'); $('#loading').css('visibility', 'visible'); - + current_library_request = $.ajax({ type: "GET", url: "library", @@ -128,18 +128,18 @@ function fetch_library_books(start, num, timeout, sort, order, search) { cache: false, timeout: timeout, //milliseconds dataType: "xml", - + error : function(XMLHttpRequest, textStatus, errorThrown) { - alert('Error: '+textStatus+'\n\n'+errorThrown); + alert('Error: '+textStatus+'\n\n'+errorThrown); }, - + success : function(xml, textStatus) { var library = $(xml).find('library'); total = parseInt(library.attr('total')); var num = parseInt(library.attr('num')); var start = parseInt(library.attr('start')); update_count_bar(start, num, total); - var display = ''; + var display = ''; library.find('book').each( function() { var book = $(this); var row = render_book(book); @@ -170,18 +170,18 @@ function fetch_library_books(start, num, timeout, sort, order, search) { $('#cover_pane').css('visibility', 'visible'); } }); - - + + layout(); $('#book_list tbody tr:even()').css('background-color', '#eeeeee'); }, - + complete : function(XMLHttpRequest, textStatus) { current_library_request = null; document.getElementById('main').scrollTop = 0; $('#loading').css('visibility', 'hidden'); } - + }); } @@ -196,7 +196,7 @@ function update_count_bar(start, num, total) { left.css('opacity', (start <= 0) ? 0.3 : 1); var right = cb.find('#right'); right.css('opacity', (start + num >= total) ? 0.3 : 1); - + } function setup_count_bar() { @@ -205,7 +205,7 @@ function setup_count_bar() { fetch_library_books(0, last_num, LIBRARY_FETCH_TIMEOUT, last_sort, last_sort_order, last_search); } }); - + $('#count_bar * img:eq(1)').click(function(){ if (last_start > 0) { var new_start = last_start - last_num; @@ -215,14 +215,14 @@ function setup_count_bar() { fetch_library_books(new_start, last_num, LIBRARY_FETCH_TIMEOUT, last_sort, last_sort_order, last_search); } }); - + $('#count_bar * img:eq(2)').click(function(){ if (last_start + last_num < total) { var new_start = last_start + last_num; fetch_library_books(new_start, last_num, LIBRARY_FETCH_TIMEOUT, last_sort, last_sort_order, last_search); } }); - + $('#count_bar * img:eq(3)').click(function(){ if (total - last_num > 0) { fetch_library_books(total - last_num, last_num, LIBRARY_FETCH_TIMEOUT, last_sort, last_sort_order, last_search); @@ -234,7 +234,7 @@ function setup_count_bar() { function search() { var search = $.trim($('#search_box * #s').val()); - fetch_library_books(0, last_num, LIBRARY_FETCH_TIMEOUT, + fetch_library_books(0, last_num, LIBRARY_FETCH_TIMEOUT, last_sort, last_sort_order, search); } @@ -245,11 +245,11 @@ function setup_sorting() { $('table#book_list thead tr td').mouseover(function() { this.style.backgroundColor = "#fff2a8"; }); - + $('table#book_list thead tr td').mouseout(function() { this.style.backgroundColor = "inherit"; }); - + for (i = 0; i < cmap.length; i++) { $('table#book_list span#{0}_sort'.format(cmap[i])).parent().click(function() { var sort_indicator = $($(this).find('span')); @@ -258,7 +258,7 @@ function setup_sorting() { var col = id.slice(0, id.indexOf("_")); var order = 'ascending'; var html = '↑'; - + if (sort_indicator.html() == '↑') { order = 'descending'; html = '↓'; } @@ -291,13 +291,13 @@ function layout() { $(function() { // document is ready create_table_headers(); - + // Setup widgets setup_sorting(); setup_count_bar(); $('#search_box * #s').val(''); $(window).resize(layout); - + $($('#book_list * span#date_sort').parent()).click(); }); diff --git a/src/calibre/linux.py b/src/calibre/linux.py index cef2e5ddb7..8314bc4bb9 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -48,12 +48,14 @@ entry_points = { 'any2lrf = calibre.ebooks.lrf.any.convert_from:main', 'any2epub = calibre.ebooks.epub.from_any:main', 'any2lit = calibre.ebooks.lit.from_any:main', + 'any2mobi = calibre.ebooks.mobi.from_any:main', 'lrf2lrs = calibre.ebooks.lrf.lrfparser:main', 'lrs2lrf = calibre.ebooks.lrf.lrs.convert_from:main', 'pdfreflow = calibre.ebooks.lrf.pdf.reflow:main', 'isbndb = calibre.ebooks.metadata.isbndb:main', 'librarything = calibre.ebooks.metadata.library_thing:main', 'mobi2oeb = calibre.ebooks.mobi.reader:main', + 'oeb2mobi = calibre.ebooks.mobi.writer:main', 'lrf2html = calibre.ebooks.lrf.html.convert_to:main', 'lit2oeb = calibre.ebooks.lit.reader:main', 'oeb2lit = calibre.ebooks.lit.writer:main', diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index 4b3c217698..0fb1bc8996 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -102,7 +102,7 @@ Device Integration What devices does |app| support? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -At the moment |app| has full support for the SONY PRS 500/505/700 as well as the iPhone. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk. +At the moment |app| has full support for the SONY PRS 500/505/700, Cybook Gen 3 as well as the iPhone. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk. I used |app| to transfer some books to my reader, and now the SONY software hangs every time I connect the reader? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/calibre/parallel.py b/src/calibre/parallel.py index 45ceff5132..6cbe1c96e4 100644 --- a/src/calibre/parallel.py +++ b/src/calibre/parallel.py @@ -286,7 +286,7 @@ def write(socket, msg, timeout=5): def read(socket, timeout=5): ''' Read a message from `socket`. The message must have been sent with the :function:`write` - function. Raises a `RuntimeError` if the message is corrpted. Can return an + function. Raises a `RuntimeError` if the message is corrupted. Can return an empty string. ''' if isworker: @@ -299,7 +299,12 @@ def read(socket, timeout=5): if not msg: break if length is None: - length, msg = int(msg[:12]), msg[12:] + try: + length, msg = int(msg[:12]), msg[12:] + except ValueError: + if DEBUG: + print >>sys.__stdout__, 'read(%s):'%('worker' if isworker else 'overseer'), 'no length in', msg + return '' buf.write(msg) if buf.tell() >= length: break diff --git a/src/calibre/trac/donations/server.py b/src/calibre/trac/donations/server.py index cbb68d972e..c3e0337290 100644 --- a/src/calibre/trac/donations/server.py +++ b/src/calibre/trac/donations/server.py @@ -217,8 +217,7 @@ class Server(object): pos = pos.replace(month = 1) else: pos = pos.replace(month = pos.month + 1) - - _months = list(months(self.earliest, self.latest))[:-1][:12] + _months = list(months(self.earliest, self.latest))[:-1][-12:] _months = [range_for_month(*m) for m in _months] _months = [self.get_slice(*m) for m in _months] x = [m.min for m in _months] diff --git a/src/calibre/trac/plugins/download.py b/src/calibre/trac/plugins/download.py index 6436aba448..0fee4fdb0d 100644 --- a/src/calibre/trac/plugins/download.py +++ b/src/calibre/trac/plugins/download.py @@ -35,7 +35,7 @@ class Distribution(object): ('xdg-utils', '1.0.2', 'xdg-utils', 'xdg-utils', 'xdg-utils'), ('dbus-python', '0.82.2', 'dbus-python', 'python-dbus', 'dbus-python'), ('lxml', '2.0.5', 'lxml', 'python-lxml', 'python-lxml'), - ('BeautifulSoup', '3.0.5', 'beautifulsoup', 'python-beautifulsoup', 'python-beautifulsoup'), + ('BeautifulSoup', '3.0.5', 'beautifulsoup', 'python-beautifulsoup', 'python-BeautifulSoup'), ('help2man', '1.36.4', 'help2man', 'help2man', 'help2man'), ] @@ -205,23 +205,7 @@ select Install.

  1. Before trying to use the command line tools, you must run the app at least once. This will ask you for you password and then setup the symbolic links for the command line tools.
  2. The app cannot be run from within the dmg. You must drag it to a folder on your filesystem (The Desktop, Applications, wherever).
  3. -
  4. In order for the conversion of RTF to LRF to support WMF images (common in older RTF files) you need to install ImageMagick.
  5. -
  6. In order for localization of the user interface in your language you must create the file ~/.MacOSX/environment.plist as shown below: -
    -<?xml version="1.0" encoding="UTF-8"?>
    -<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    -<plist version="1.0">
    -<dict>
    -        <key>LANG</key>
    -        <string>de_DE</string>
    -</dict>
    -</plist>
    -
    -The example above is for the German language. Substitute the language code you need. -After creating the file you need to log out and log in again for the changes to become -active. Of course, this will only work if calibre has been translated for your language. -If not, head over to
    Translations to see how you can translate it. -
  7. +
  8. In order for localization of the user interface in your language, select your language in the configuration dialog (by clicking the hammer icon next to the search bar) and select your language.
''')) return 'binary.html', data, None diff --git a/src/calibre/translations/bg.po b/src/calibre/translations/bg.po index cc9a7935d0..45a3feb12b 100644 --- a/src/calibre/translations/bg.po +++ b/src/calibre/translations/bg.po @@ -6,14 +6,14 @@ msgid "" msgstr "" "Project-Id-Version: calibre 0.4.51\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-12-30 15:33+0000\n" +"POT-Creation-Date: 2009-01-10 01:19+0000\n" "PO-Revision-Date: 2008-05-24 06:23+0000\n" "Last-Translator: Kovid Goyal \n" "Language-Team: bg\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-01-07 18:40+0000\n" +"X-Launchpad-Export-Date: 2009-01-13 07:12+0000\n" "X-Generator: Launchpad (build Unknown)\n" "Generated-By: pygettext.py 1.5\n" @@ -23,14 +23,14 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:44 #: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_any.py:44 -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:478 -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:948 -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:961 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:492 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:965 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:978 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:77 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:79 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:81 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:86 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:292 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:300 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:61 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:95 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:97 @@ -49,7 +49,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:78 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf.py:334 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf.py:449 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:793 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:819 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdf.py:12 #: /home/kovid/work/calibre/src/calibre/ebooks/odt/to_oeb.py:46 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:48 @@ -58,20 +58,20 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:365 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:37 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:38 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:329 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:343 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:835 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:678 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:912 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:915 -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:52 -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:112 -#: /home/kovid/work/calibre/src/calibre/library/cli.py:245 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:346 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:360 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:861 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:709 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:949 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:952 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:116 +#: /home/kovid/work/calibre/src/calibre/library/cli.py:257 #: /home/kovid/work/calibre/src/calibre/library/database.py:920 -#: /home/kovid/work/calibre/src/calibre/library/database.py:1392 -#: /home/kovid/work/calibre/src/calibre/library/database.py:1423 -#: /home/kovid/work/calibre/src/calibre/library/database.py:1452 -#: /home/kovid/work/calibre/src/calibre/library/database.py:1580 +#: /home/kovid/work/calibre/src/calibre/library/database.py:1404 +#: /home/kovid/work/calibre/src/calibre/library/database.py:1435 +#: /home/kovid/work/calibre/src/calibre/library/database.py:1464 +#: /home/kovid/work/calibre/src/calibre/library/database.py:1592 #: /home/kovid/work/calibre/src/calibre/library/database2.py:461 #: /home/kovid/work/calibre/src/calibre/library/database2.py:473 #: /home/kovid/work/calibre/src/calibre/library/database2.py:808 @@ -103,33 +103,38 @@ msgid "" msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:32 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:42 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:43 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:53 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:63 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:73 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:83 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:64 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:74 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:84 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:94 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:105 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:115 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:125 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:135 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:145 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:116 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:126 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:136 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:146 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:156 msgid "Read metadata from %s files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:155 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:166 msgid "Extract cover from comic files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:175 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:186 +msgid "Read metadata from ebooks in ZIP archives" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:196 msgid "Set metadata in EPUB files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:185 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:206 msgid "Set metadata in LRF files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:195 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:216 msgid "Set metadata in RTF files" msgstr "" @@ -153,11 +158,11 @@ msgstr "" msgid "No valid plugin found in " msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:170 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:184 msgid "Initialization of plugin %s failed with traceback:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:247 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:261 msgid "" " %prog options\n" " \n" @@ -165,29 +170,29 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:253 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:267 msgid "Add a plugin by specifying the path to the zip file containing it." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:255 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:269 msgid "Remove a custom plugin by name. Has no effect on builtin plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:257 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:271 msgid "" "Customize plugin. Specify name of plugin and customization string separated " "by a comma." msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:259 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:273 msgid "List all installed plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:261 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:275 msgid "Enable the named plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/ui.py:263 +#: /home/kovid/work/calibre/src/calibre/customize/ui.py:277 msgid "Disable the named plugin" msgstr "" @@ -195,13 +200,25 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:158 #: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:196 #: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:224 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:182 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:223 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:250 msgid "Unable to detect the %s disk drive. Try rebooting." msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:412 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:51 msgid "The reader has no storage card connected." msgstr "" +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:62 +msgid "There is insufficient free space on the storage card" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:64 +msgid "There is insufficient free space in main memory" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:92 msgid "Options to control the conversion to EPUB" msgstr "" @@ -261,7 +278,16 @@ msgid "" "cover." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:127 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:126 +msgid "" +"Turn off splitting at page breaks. Normally, input files are automatically " +"split at every page break into two files. This gives an output ebook that " +"can be parsed faster and with less resources. However, splitting is slow and " +"if your source file contains a very large number of page breaks, you should " +"turn off splitting on page breaks." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:128 msgid "" "Control the automatic generation of a Table of Contents. If an OPF file is " "detected\n" @@ -270,38 +296,38 @@ msgid "" "to auto-generate a Table of Contents.\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:133 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:134 msgid "" "Maximum number of links to insert into the TOC. Set to 0 to disable. Default " "is: %default. Links are only added to the TOC if less than the --toc-" "threshold number of chapters were detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:135 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:136 msgid "Don't add auto-detected chapters to the Table of Contents." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:137 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:138 msgid "" "If fewer than this number of chapters is detected, then links are added to " "the Table of Contents. Default: %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:139 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:140 msgid "" "XPath expression that specifies all tags that should be added to the Table " "of Contents at level one. If this is specified, it takes precedence over " "other forms of auto-detection." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:141 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:142 msgid "" "XPath expression that specifies all tags that should be added to the Table " "of Contents at level two. Each entry is added under the previous level one " "entry." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:143 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:144 msgid "" "Path to a .ncx file that contains the table of contents to use for this " "ebook. The NCX file should contain links relative to the directory it is " @@ -309,65 +335,65 @@ msgid "" "an overview of the NCX format." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:145 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:146 msgid "" "Normally, if the source file already has a Table of Contents, it is used in " "preference to the autodetected one. With this option, the autodetected one " "is always used." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:147 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:148 msgid "Control page layout" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:149 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:150 msgid "Set the top margin in pts. Default is %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:151 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:152 msgid "Set the bottom margin in pts. Default is %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:153 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:154 msgid "Set the left margin in pts. Default is %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:155 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:156 msgid "Set the right margin in pts. Default is %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:157 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:158 msgid "" "The base font size in pts. Default is %defaultpt. Set to 0 to disable " "rescaling of fonts." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:159 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:160 msgid "" "Remove spacing between paragraphs. Will not work if the source file forces " "inter-paragraph spacing." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:161 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:162 msgid "" "Preserve the HTML tag structure while splitting large HTML files. This is " "only neccessary if the HTML files contain CSS that uses sibling selectors. " "Enabling this greatly slows down processing of large HTML files." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:164 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:165 msgid "Print generated OPF file to stdout" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:166 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:167 msgid "Print generated NCX file to stdout" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:168 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:169 msgid "Keep intermediate files during processing by html2epub" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:170 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:171 msgid "" "Extract the contents of the produced EPUB file to the specified directory." msgstr "" @@ -384,7 +410,7 @@ msgstr "" msgid "Could not find an ebook inside the archive" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:158 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:162 msgid "" "%prog [options] file.html|opf\n" "\n" @@ -395,13 +421,13 @@ msgid "" "the element of the OPF file. \n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:391 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:413 #: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:739 msgid "Output written to " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:413 -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:1046 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:435 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:1063 msgid "You must specify an input HTML file" msgstr "" @@ -414,100 +440,99 @@ msgid "" "Could not find reasonable point at which to split: %s Sub-tree size: %d KB" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/split.py:136 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/split.py:140 msgid "" "\t\tToo much markup. Re-splitting without structure preservation. This may " "cause incorrect rendering." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:490 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:504 msgid "Written processed HTML to " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:831 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:848 msgid "Options to control the traversal of HTML" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:838 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:855 msgid "The output directory. Default is the current directory." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:840 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:857 msgid "Character encoding for HTML files. Default is to auto detect." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:842 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:859 msgid "" "Create the output in a zip file. If this option is specified, the --output " "should be the name of a file not a directory." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:844 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:861 msgid "Control the following of links in HTML files." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:846 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:863 msgid "" "Traverse links in HTML files breadth first. Normally, they are traversed " "depth first" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:848 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:865 msgid "" "Maximum levels of recursion when following links in HTML files. Must be non-" "negative. 0 implies that no links in the root HTML file are followed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:850 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:867 msgid "Set metadata of the generated ebook" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:852 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:869 msgid "Set the title. Default is to autodetect." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:854 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:871 msgid "The author(s) of the ebook, as a comma separated list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:856 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:873 msgid "The subject(s) of this book, as a comma separated list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:858 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:875 msgid "Set the publisher of this book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:860 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:877 msgid "A summary of this book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:862 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:879 msgid "Load metadata from the specified OPF file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:864 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:881 msgid "Options useful for debugging" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:866 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:883 msgid "" "Be more verbose while processing. Can be specified multiple times to " "increase verbosity." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:868 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:885 msgid "Output HTML is \"pretty printed\" for easier parsing by humans" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:874 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:891 msgid "" "%prog [options] file.html|opf\n" "\n" "Follow all links in an HTML file and collect them into the specified " "directory.\n" -"Also collects any references resources like images, stylesheets, scripts, " -"etc. \n" +"Also collects any resources like images, stylesheets, scripts, etc. \n" "If an OPF file is specified instead, the list of files in its " "element\n" "is used.\n" @@ -522,7 +547,7 @@ msgid "%prog [options] LITFILE" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:852 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:444 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:475 msgid "Output directory. Defaults to current directory." msgstr "" @@ -536,7 +561,7 @@ msgid "Useful for debugging." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:869 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:468 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:499 msgid "OEB ebook created in" msgstr "" @@ -577,7 +602,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:87 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:275 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:39 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:108 msgid "Publisher" msgstr "" @@ -631,100 +656,104 @@ msgid "" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:112 +msgid "Add extra spacing below the header. Default is %default px." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:114 msgid "" "Override the CSS. Can be either a path to a CSS stylesheet or a string. If " "it is a string it is interpreted as CSS." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:114 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:116 msgid "" "Use the element from the OPF file to determine the order in which " "the HTML files are appended to the LRF. The .opf file must be in the same " "directory as the base HTML file." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:116 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:118 msgid "" "Minimum paragraph indent (the indent of the first line of a paragraph) in " "pts. Default: %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:118 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:120 msgid "" "Increase the font size by 2 * FONT_DELTA pts and the line spacing by " "FONT_DELTA pts. FONT_DELTA can be a fraction.If FONT_DELTA is negative, the " "font size is decreased." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:123 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:125 msgid "" "Render all content as black on white instead of the colors specified by the " "HTML or CSS." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:129 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:131 msgid "" "Profile of the target device for which this LRF is being generated. The " "profile determines things like the resolution and screen size of the target " "device. Default: %s Supported profiles: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:135 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:137 msgid "Left margin of page. Default is %default px." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:137 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:139 msgid "Right margin of page. Default is %default px." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:139 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:141 msgid "Top margin of page. Default is %default px." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:141 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:143 msgid "Bottom margin of page. Default is %default px." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:143 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:145 msgid "" "Render tables in the HTML as images (useful if the document has large or " "complex tables)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:145 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:147 msgid "" "Multiply the size of text in rendered tables by this factor. Default is " "%default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:150 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:152 msgid "" "The maximum number of levels to recursively process links. A value of 0 " "means thats links are not followed. A negative value means that tags are " "ignored." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:154 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:156 msgid "" "A regular expression. tags whose href matches will be ignored. Defaults " "to %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:158 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:160 msgid "Don't add links to the table of contents." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:162 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:164 msgid "Prevent the automatic detection chapters." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:165 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:167 msgid "" "The regular expression used to detect chapter titles. It is searched for in " "heading tags (h1-h6). Defaults to %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:168 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:170 msgid "" "Detect a chapter beginning at an element having the specified attribute. The " "format for this option is tagname regexp,attribute name,attribute value " @@ -734,7 +763,7 @@ msgid "" "all h2 tags, you would use \"h2,none,\". Default is %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:170 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:172 msgid "" "If html2lrf does not find any page breaks in the html file and cannot detect " "chapter headings, it will automatically insert page-breaks before the tags " @@ -745,12 +774,12 @@ msgid "" "has only a few elements." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:180 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:182 msgid "" "Force a page break before tags whose names match this regular expression." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:182 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:184 msgid "" "Force a page break before an element having the specified attribute. The " "format for this option is tagname regexp,attribute name,attribute value " @@ -758,25 +787,25 @@ msgid "" "class=\"chapter\" you would use \"h\\d,class,chapter\". Default is %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:185 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:187 msgid "Add detected chapters to the table of contents." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:188 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:190 msgid "Preprocess Baen HTML files to improve generated LRF." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:190 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:192 msgid "" "You must add this option if processing files generated by pdftohtml, " "otherwise conversion will fail." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:192 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:194 msgid "Use this option on html0 files from Book Designer." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:195 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:197 msgid "" "Specify trutype font families for serif, sans-serif and monospace fonts. " "These fonts will be embedded in the LRF file. Note that custom fonts lead to " @@ -784,33 +813,33 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:203 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:205 msgid "The serif family of fonts to embed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:206 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:208 msgid "The sans-serif family of fonts to embed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:209 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:211 msgid "The monospace family of fonts to embed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:213 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:215 msgid "Be verbose while processing" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:215 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:217 msgid "Convert to LRS" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:217 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:219 msgid "" "Minimize memory usage at the cost of longer processing times. Use this " "option if you are on a memory constrained machine." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:219 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:221 msgid "" "Specify the character encoding of the source file. If the output LRF file " "contains strange characters, try changing this option. A common encoding for " @@ -837,114 +866,120 @@ msgstr "" msgid "No file to convert specified." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:221 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:229 msgid "Rendered %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:224 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:232 msgid "Failed %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:276 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:284 msgid "" "Failed to process comic: %s\n" "\n" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:283 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:291 msgid "" "Options to control the conversion of comics (CBR, CBZ) files into ebooks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:289 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:297 msgid "Title for generated ebook. Default is to use the filename." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:291 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:299 msgid "" "Set the author in the metadata of the generated ebook. Default is %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:294 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:302 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:22 msgid "" "Path to output file. By default a file is created in the current directory." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:296 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:304 msgid "Number of colors for grayscale image conversion. Default: %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:298 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:306 msgid "" "Disable normalize (improve contrast) color range for pictures. Default: False" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:300 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:308 msgid "Maintain picture aspect ratio. Default is to fill the screen." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:302 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:310 msgid "Disable sharpening." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:304 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:312 msgid "Don't split landscape images into two portrait images" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:306 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:314 msgid "" "Keep aspect ratio and scale image using screen height as image width for " "viewing in landscape mode." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:308 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:316 msgid "" "Used for right-to-left publications like manga. Causes landscape pages to be " "split into portrait pages from right to left." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:310 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:318 msgid "" "Enable Despeckle. Reduces speckle noise. May greatly increase processing " "time." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:312 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:320 msgid "" "Don't sort the files found in the comic alphabetically by name. Instead use " "the order they were added to the comic." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:314 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:322 msgid "" "Choose a profile for the device you are generating this file for. The " "default is the SONY PRS-500 with a screen size of 584x754 pixels. This is " "suitable for any reader with the same screen size. Choices are %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:316 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:324 +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:20 msgid "" "Be verbose, useful for debugging. Can be specified multiple times for " "greater verbosity." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:318 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:326 msgid "Don't show progress bar." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:323 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:329 +msgid "Apply no processing to the image" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:334 msgid "" "%prog [options] comic.cb[z|r]\n" "\n" "Convert a comic in a CBZ or CBR file to an ebook. \n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:383 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:394 msgid "Output written to" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:426 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:459 msgid "Rendering comic pages..." msgstr "" @@ -985,95 +1020,95 @@ msgstr "" msgid "Fetching of recipe failed: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:318 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:313 msgid "\tBook Designer file detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:320 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:315 msgid "\tParsing HTML..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:343 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:338 msgid "\tBaen file detected. Re-parsing..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:359 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:354 msgid "Written preprocessed HTML to " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:377 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:372 msgid "Processing %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:391 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:386 msgid "\tConverting to BBeB..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:537 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:550 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:532 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:545 msgid "Could not parse file: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:542 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:537 msgid "%s is an empty file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:562 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:557 msgid "Failed to parse link %s %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:606 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:601 msgid "Cannot add link %s to TOC" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:958 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:953 msgid "Unable to process image %s. Error: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:996 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:991 msgid "Unable to process interlaced PNG %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1011 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1006 msgid "" "Could not process image: %s\n" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1768 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1759 msgid "" "An error occurred while processing a table: %s. Ignoring table markup." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1770 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1761 msgid "" "Bad table:\n" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1792 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1783 msgid "Table has cell that is too large" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1822 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1813 msgid "" "You have to save the website %s as an html file first and then run html2lrf " "on it." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1865 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1856 msgid "Could not read cover image: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1868 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1859 msgid "Cannot read from: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:2003 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1984 msgid "Failed to process opf file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:2009 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1990 msgid "" "Usage: %prog [options] mybook.html\n" "\n" @@ -1092,25 +1127,29 @@ msgid "" "%prog converts mybook.lit to mybook.lrf" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:134 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:136 msgid "" "%prog book.lrf\n" "Convert an LRF file into an LRS (XML UTF-8 encoded) file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:135 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:137 msgid "Output LRS file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:153 -msgid "Parsing LRF..." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:156 -msgid "Creating XML..." +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:139 +msgid "Do not save embedded image and font files to disk" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:158 +msgid "Parsing LRF..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:161 +msgid "Creating XML..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:163 msgid "LRS written to " msgstr "" @@ -1185,20 +1224,32 @@ msgstr "" msgid "Extract thumbnail from LRF file" msgstr "" +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:606 +msgid "Set the publisher" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:607 +msgid "Set the book classification" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:608 +msgid "Set the book creator" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:609 +msgid "Set the book producer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:611 msgid "" "Extract cover from LRF file. Note that the LRF format has no defined cover, " "so we use some heuristics to guess the cover." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:609 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:613 msgid "Set book ID" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:611 -msgid "Don't know what this is for" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/mobi/convert_from.py:43 msgid "" "Usage: %prog [options] mybook.mobi|prc\n" @@ -1272,17 +1323,17 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:69 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:70 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:36 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:92 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:326 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:905 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:103 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:343 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:931 msgid "Title" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:274 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:37 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:93 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:331 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:906 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:348 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:932 msgid "Author(s)" msgstr "" @@ -1291,34 +1342,34 @@ msgid "Producer" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:277 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:504 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:512 msgid "Category" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:278 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:71 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:391 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:527 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:308 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:287 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:394 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:535 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:338 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:304 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:58 msgid "Comments" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:280 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:276 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:845 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:909 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:293 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:871 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:935 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:60 #: /home/kovid/work/calibre/src/calibre/gui2/tags.py:42 msgid "Tags" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:281 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:99 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:292 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:309 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:59 #: /home/kovid/work/calibre/src/calibre/gui2/tags.py:42 msgid "Series" @@ -1423,7 +1474,7 @@ msgstr "" msgid "Cover saved to" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:938 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:964 msgid "Set the dc:language field" msgstr "" @@ -1435,11 +1486,11 @@ msgstr "" msgid "Usage: rb-meta file.rb" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:442 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:473 msgid "%prog [options] myebook.mobi" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:466 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:497 msgid "Raw MOBI HTML saved in" msgstr "" @@ -1447,8 +1498,42 @@ msgstr "" msgid "The output directory. Defaults to the current directory." msgstr "" +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:13 +msgid "Options to control the transformation of pdf" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:24 +msgid "Number of pixels to crop from the left most x (default is %d) " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:26 +msgid "Number of pixels to crop from the left most y (default is %d) " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:28 +msgid "Number of pixels to crop from the right most x (default is %d) " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:30 +msgid "Number of pixels to crop from the right most y (default is %d)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:32 +msgid "" +"A file generated by ghostscript which allows each page to be individually " +"cropped [gs -dSAFER -dNOPAUSE -dBATCH -sDEVICE=bbox > bounding] " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:38 +msgid "" +"\t%prog [options] file.pdf\n" +"\n" +"\tCrops a pdf. \n" +"\t" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:25 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:424 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:427 msgid "Frequently used directories" msgstr "" @@ -1520,14 +1605,20 @@ msgstr "" msgid "Show system tray icon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:57 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:58 msgid "Upload downloaded news to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:60 msgid "Delete books from library after uploading to device" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:62 +msgid "" +"Show the cover flow in a separate window instead of in the main calibre " +"window" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/device.py:72 msgid "Device no longer connected." msgstr "" @@ -1563,8 +1654,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:84 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:85 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:86 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:283 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:840 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:300 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:866 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:56 msgid "Path" msgstr "" @@ -1573,7 +1664,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:88 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:89 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:92 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:282 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:299 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:57 #: /home/kovid/work/calibre/src/calibre/gui2/tags.py:42 msgid "Formats" @@ -1627,8 +1718,8 @@ msgid "&Number of Colors:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:92 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:398 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:548 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:401 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:558 msgid "&Profile:" msgstr "" @@ -1694,122 +1785,123 @@ msgstr "" msgid "Plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:256 msgid "No valid plugin path" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:257 msgid "%s is not a valid plugin path" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:260 msgid "Choose plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:270 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:271 msgid "Plugin cannot be disabled" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:271 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:272 msgid "The plugin: %s cannot be disabled" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:281 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:282 msgid "Plugin not customizable" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:282 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:283 msgid "Plugin: %s does not need customization" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:285 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:286 msgid "Customize %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:295 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:296 msgid "Cannot remove builtin plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:296 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:297 msgid " cannot be removed. It is a builtin plugin. Try disabling it instead." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:317 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:318 msgid "Error log:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:321 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:322 msgid "Access log:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:343 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:382 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:344 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:389 msgid "Failed to start content server" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:383 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:384 msgid "Invalid size" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:383 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:384 msgid "The size %s is invalid. must be of the form widthxheight" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:419 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:423 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:421 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:425 msgid "Invalid database location" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:420 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:422 msgid "
Must be a directory." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:420 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:422 msgid "Invalid database location " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:424 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:426 msgid "Invalid database location.
Cannot write to " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:436 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:438 msgid "Compacting database. This may take a while." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:436 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:438 msgid "Compacting..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:408 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:411 #: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:329 msgid "Configuration" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:409 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:412 msgid "" "&Location of ebooks (The ebooks are stored in folders sorted by author and " "metadata is stored in the file metadata.db)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:410 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:413 msgid "Browse for the new database location" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:411 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:426 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:428 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:438 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:439 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:467 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:373 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:509 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:282 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:288 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:303 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:312 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:414 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:429 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:431 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:441 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:442 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:471 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:377 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:517 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:314 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:318 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:320 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:319 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:333 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:342 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:344 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:346 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:350 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:352 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:126 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:128 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:131 @@ -1823,196 +1915,200 @@ msgstr "" msgid "..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:412 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:415 msgid "Show notification when &new version is available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:413 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:416 msgid "" "If you disable this setting, metadata is guessed from the filename instead. " "This can be configured in the Advanced section." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:414 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:417 msgid "Read &metadata from files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:415 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:418 msgid "Format for &single file save:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:416 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:419 msgid "Default network &timeout:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:417 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:420 msgid "" "Set the default timeout for network fetches (i.e. anytime we go out to the " "internet to get information)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:418 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:421 msgid " seconds" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:419 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:422 msgid "Choose &language (requires restart):" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:420 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:423 msgid "Normal" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:421 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:424 msgid "High" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:422 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:425 msgid "Low" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:423 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:426 msgid "Job &priority:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:425 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:428 msgid "Add a directory to the frequently used directories list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:427 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:430 msgid "Remove a directory from the frequently used directories list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:429 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:432 msgid "Use &Roman numerals for series number" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:430 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:433 msgid "&Number of covers to show in browse mode (after restart):" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:431 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:434 msgid "Toolbar" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:432 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:435 msgid "Large" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:433 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:436 msgid "Medium" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:434 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:437 msgid "Small" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:435 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:438 msgid "&Button size in toolbar" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:436 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:439 msgid "Show &text in toolbar buttons" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:437 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:440 msgid "Select visible &columns in library view" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:440 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:443 msgid "Use internal &viewer for the following formats:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:441 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:444 msgid "Enable system &tray icon (needs restart)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:442 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:445 msgid "Automatically send downloaded &news to ebook reader" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:443 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:446 msgid "&Delete news from library when it is sent to reader" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:444 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:447 +msgid "Show cover &browser in a separate window (needs restart)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:448 msgid "Free unused diskspace from the database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:445 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:449 msgid "&Compact database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:446 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:450 msgid "&Metadata from file name" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:447 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:451 msgid "" "calibre contains a network server that allows you to access your book " "collection using a browser from anywhere in the world. Any changes to the " "settings will only take effect after a server restart." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:448 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:452 msgid "Server &port:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:449 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:453 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:57 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:146 msgid "&Username:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:450 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:454 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:58 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:147 msgid "&Password:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:451 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:455 msgid "" "If you leave the password blank, anyone will be able to access your book " "collection using the web interface." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:452 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:456 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:59 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:148 msgid "&Show password" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:453 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:457 msgid "" "The maximum size (widthxheight) for displayed covers. Larger covers are " "resized. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:454 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:458 msgid "Max. &cover size:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:455 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:459 msgid "&Start Server" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:456 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:460 msgid "St&op Server" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:457 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:461 msgid "&Test Server" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:458 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:462 msgid "Run server &automatically on startup" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:459 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:463 msgid "View &server logs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:460 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:464 msgid "" "If you want to use the content server to access your ebook collection on " "your iphone with Stanza, you will need to add the URL " @@ -2021,33 +2117,33 @@ msgid "" "address of this computer." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:461 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:465 msgid "" "Here you can customize the behavior of Calibre by controlling what plugins " "it uses." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:462 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:466 msgid "Enable/&Disable plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:463 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:467 msgid "&Customize plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:464 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:468 msgid "&Remove plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:465 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:469 msgid "Add new plugin" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:466 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:470 msgid "Plugin &file:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:468 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:472 msgid "&Add" msgstr "" @@ -2130,25 +2226,25 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:100 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:174 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:51 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:59 msgid "Cannot read" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:101 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:175 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:52 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:60 msgid "You do not have permission to read the file: " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:109 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:183 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:68 msgid "Error reading file" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:110 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:184 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:69 msgid "

There was an error reading from file:
" msgstr "" @@ -2158,7 +2254,7 @@ msgid " is not a valid picture" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:227 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1002 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1039 msgid "Cannot convert" msgstr "" @@ -2188,194 +2284,196 @@ msgstr "" msgid "The expression %s is invalid. Error: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:369 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:372 msgid "Convert to EPUB" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:370 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:506 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:315 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:373 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:347 msgid "Book Cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:371 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:507 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:316 -msgid "Change &cover image:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:372 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:508 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:317 -msgid "Browse for an image to use as the cover of this book." -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:374 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:510 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:518 msgid "Use cover from &source file" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:375 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:511 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:279 -msgid "&Title: " +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:348 +msgid "Change &cover image:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:376 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:512 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:280 -msgid "Change the title of this book" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:377 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:513 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:128 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:283 -msgid "&Author(s): " +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:516 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:349 +msgid "Browse for an image to use as the cover of this book." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:378 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:519 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:311 +msgid "&Title: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:379 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:520 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:312 +msgid "Change the title of this book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:380 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:521 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:128 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:315 +msgid "&Author(s): " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:381 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:522 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:129 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:284 msgid "" "Change the author(s) of this book. Multiple authors should be separated by " "an &. If the author name contains an &, use && to represent it." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:379 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:382 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:523 msgid "Author So&rt:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:380 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:516 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:383 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:524 msgid "" "Change the author(s) of this book. Multiple authors should be separated by a " "comma" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:381 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:517 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:384 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:525 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:136 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:293 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:324 msgid "&Publisher: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:382 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:518 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:385 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:526 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:137 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:294 msgid "Change the publisher of this book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:383 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:519 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:295 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:386 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:527 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:325 msgid "Ta&gs: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:384 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:520 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:387 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:528 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:139 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:296 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:326 msgid "" "Tags categorize the book. This is particularly useful while searching. " "

They can be any words or phrases, separated by commas." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:385 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:521 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:388 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:529 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:144 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:299 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:329 msgid "&Series:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:386 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:387 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:522 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:523 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:389 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:390 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:530 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:531 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:145 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:146 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:300 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:301 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:330 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:331 msgid "List of known series. You can add new series." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:388 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:389 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:524 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:525 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:304 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:305 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:391 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:392 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:532 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:533 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:334 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:335 msgid "Series index." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:390 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:526 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:306 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:393 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:534 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:336 msgid "Book " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:392 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:534 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:395 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:542 msgid "Source en&coding:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:393 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:528 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:396 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:536 msgid "Base &font size:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:394 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:400 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:402 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:404 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:406 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:397 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:403 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:405 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:407 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:409 msgid " pt" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:395 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:398 msgid "Remove &spacing between paragraphs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:396 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:399 msgid "Preserve &tag structure when splitting" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:397 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:400 msgid "Override &CSS" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:399 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:549 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:402 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:559 msgid "&Left Margin:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:401 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:551 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:404 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:561 msgid "&Right Margin:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:403 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:553 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:406 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:563 msgid "&Top Margin:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:405 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:555 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:408 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:565 msgid "&Bottom Margin:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:407 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:410 +msgid "Do not &split on page breaks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:411 msgid "Automatic &chapter detection" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:408 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:412 msgid "&XPath:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:409 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:413 msgid "" "\n" @@ -2393,35 +2491,35 @@ msgid "" "tutorial

" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:414 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:418 msgid "Chapter &mark:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:415 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:419 msgid "Automatic &Table of Contents" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:416 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:420 msgid "Number of &links to add to Table of Contents" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:417 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:421 msgid "Do not add &detected chapters to the Table of Contents" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:418 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:422 msgid "Chapter &threshold" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:419 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:423 msgid "&Force use of auto-generated Table of Contents" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:420 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:424 msgid "Level &1 TOC" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:421 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:425 msgid "Level &2 TOC" msgstr "" @@ -2514,7 +2612,7 @@ msgid "Convert %s to LRF" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:109 -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:347 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:353 msgid "Set conversion defaults" msgstr "" @@ -2564,153 +2662,158 @@ msgstr "" msgid "Bulk convert ebooks to LRF" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:503 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:511 msgid "Convert to LRF" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:505 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:513 msgid "Options" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:529 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:536 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:538 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:537 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:544 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:546 msgid " pts" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:530 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:538 msgid "Embedded Fonts" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:531 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:539 msgid "&Serif:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:532 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:540 msgid "S&ans-serif:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:533 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:541 msgid "&Monospace:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:535 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:543 msgid "Minimum &indent:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:537 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:545 msgid "&Word spacing:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:539 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:547 msgid "Enable auto &rotation of images" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:540 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:548 msgid "Insert &blank lines between paragraphs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:541 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:549 msgid "Ignore &tables" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:542 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:550 msgid "Ignore &colors" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:543 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:551 msgid "&Preprocess:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:544 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:552 msgid "Header" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:545 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:553 msgid "&Show header" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:546 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:554 msgid "&Header format:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:547 -msgid "Override
CSS" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:550 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:552 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:554 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:556 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:555 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:560 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:562 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:564 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:566 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:107 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:109 msgid " px" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:556 +msgid "Header &separation:" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:557 -msgid "&Convert tables to images (good for large/complex tables)" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:558 -msgid "&Multiplier for text size in rendered tables:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:559 -msgid "Title based detection" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:560 -msgid "&Disable chapter detection" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:561 -msgid "&Regular expression:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:562 -msgid "Add &chapters to table of contents" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:563 -msgid "Don't add &links to the table of contents" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:564 -msgid "Tag based detection" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:565 -msgid "&Page break before tag:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:566 -msgid "&Force page break before tag:" +msgid "Override
CSS" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:567 -msgid "Force page break before &attribute:" +msgid "&Convert tables to images (good for large/complex tables)" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:568 -msgid "Detect chapter &at tag:" +msgid "&Multiplier for text size in rendered tables:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:569 -msgid "Help on item" +msgid "Title based detection" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:570 +msgid "&Disable chapter detection" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:571 +msgid "&Regular expression:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:572 +msgid "Add &chapters to table of contents" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:573 +msgid "Don't add &links to the table of contents" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:574 +msgid "Tag based detection" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:575 +msgid "&Page break before tag:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:576 +msgid "&Force page break before tag:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:577 +msgid "Force page break before &attribute:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:578 +msgid "Detect chapter &at tag:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:579 +msgid "Help on item" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:580 msgid "" "\n" "\n" +"\n" "

" +"margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-" +"family:'DejaVu Sans';\">

" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:126 @@ -2718,36 +2821,36 @@ msgid "Edit Meta information" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:278 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:310 msgid "Meta information" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:130 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:285 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:316 msgid "Author S&ort: " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:131 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:286 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:317 msgid "" "Specify how the author(s) of this book should be sorted. For example Charles " "Dickens should be sorted as Dickens, Charles." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:132 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:289 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:320 msgid "&Rating:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:133 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:134 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:290 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:291 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:321 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:322 msgid "Rating of this book. 0-5 stars" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:135 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:292 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:323 msgid " stars" msgstr "" @@ -2757,8 +2860,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:140 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:141 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:297 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:298 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:327 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:328 msgid "Open Tag Editor" msgstr "" @@ -2778,80 +2881,105 @@ msgstr "" msgid "A&utomatically set author sort" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:262 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:124 +msgid "Could not read metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:125 +msgid "Could not read metadata from %s format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:139 +msgid "Could not read cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:134 +msgid "Could not read cover from %s format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:140 +msgid "The cover in the %s format is invalid" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:321 msgid "" "

Enter your username and password for LibraryThing.com.
If you " "do not have one, you can register " "for free!.

" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:292 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:351 msgid "Could not fetch cover.
" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:292 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:351 msgid "Could not fetch cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:298 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:357 msgid "Cannot fetch cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:298 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:357 msgid "You must specify the ISBN identifier for this book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:277 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:309 msgid "Edit Meta Information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:281 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:313 msgid "Swap the author and title" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:287 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:318 msgid "" "Automatically create the author sort entry based on the current author entry" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:332 msgid "Remove unused series (Series that have no books)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:307 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:337 msgid "IS&BN:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:309 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:339 msgid "Fetch metadata from server" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:310 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:340 msgid "Available Formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:311 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:341 msgid "Add a new format for this book to the database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:313 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:343 msgid "Remove the selected formats for this book from the database." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:319 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:345 +msgid "Set the cover for the book from the selected format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:351 msgid "Reset cover to default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:321 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:353 msgid "Fetch cover image from server" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:322 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:354 msgid "" "Change the username and/or password for your account at LibraryThing.com" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:323 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:355 msgid "Change password" msgstr "" @@ -2863,49 +2991,49 @@ msgstr "" msgid "You" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:169 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:170 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:218 msgid "Search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:234 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:235 msgid "%d recipes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:264 msgid "Must set account information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:264 msgid "This recipe requires a username and password" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:276 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:277 msgid "Created by: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:296 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:297 msgid "Last downloaded: %s days ago" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:298 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:299 msgid "Last downloaded: never" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:325 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:326 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:136 msgid "Schedule news download" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:328 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:329 msgid "Add a custom news source" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:335 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:336 #: /home/kovid/work/calibre/src/calibre/gui2/tags.py:42 #: /home/kovid/work/calibre/src/calibre/library/database2.py:752 #: /home/kovid/work/calibre/src/calibre/library/database2.py:756 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1055 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1063 msgid "News" msgstr "" @@ -3387,49 +3515,49 @@ msgstr "" msgid "Job has already run" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:94 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:907 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:933 msgid "Size (MB)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:95 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:908 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:106 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:934 msgid "Date" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:96 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:107 msgid "Rating" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:275 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:281 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:286 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:292 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:298 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:303 msgid "None" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:292 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:309 msgid "Book %s of %s." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:675 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:701 msgid "Not allowed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:676 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:702 msgid "" "Dropping onto a device is not supported. First add the book to the calibre " "library." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:839 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:865 msgid "Format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:844 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:870 msgid "Timestamp" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:942 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:968 msgid "Search (For Advanced Search click the button to the left)" msgstr "" @@ -3458,7 +3586,7 @@ msgid "No matches for the search phrase %s were found." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:157 -#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:377 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:378 msgid "No matches found" msgstr "" @@ -3505,128 +3633,128 @@ msgstr "" msgid "Configure" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:89 msgid "Error communicating with device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:100 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:101 msgid "&Restore" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:102 msgid "&Donate" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:102 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:103 msgid "&Quit" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:105 msgid "&Restart" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:148 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:149 msgid "" "

For help visit %s.kovidgoyal.net
" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:150 msgid "%s: %s by Kovid Goyal %%(version)s
%%(device)s

" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:167 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:169 -msgid "Send to main memory" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/main.py:168 #: /home/kovid/work/calibre/src/calibre/gui2/main.py:170 -msgid "Send to storage card" +msgid "Send to main memory" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/main.py:169 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:171 +msgid "Send to storage card" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/main.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:171 msgid "and delete from library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:172 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:173 msgid "Send to storage card by default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:185 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:186 msgid "Edit metadata individually" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:187 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:188 msgid "Edit metadata in bulk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:190 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:191 msgid "Add books from a single directory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:191 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:192 msgid "" "Add books from directories, including sub-directories (One book per " "directory, assumes every ebook file is the same book in a different format)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:193 msgid "" "Add books from directories, including sub directories (Multiple books per " "directory, assumes every ebook file is a different book)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:207 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:208 #: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:342 msgid "Save to disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:208 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:209 msgid "Save to disk in a single directory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:209 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1200 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:210 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1243 msgid "Save only %s format to disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:212 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:213 #: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:348 msgid "View" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:213 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:214 msgid "View specific format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:230 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:231 msgid "Convert individually" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:231 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:232 msgid "Bulk convert" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:233 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:234 msgid "Set defaults for conversion" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:234 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:235 msgid "Set defaults for conversion of comics" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:255 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:256 msgid "Similar books..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:301 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:302 msgid "Bad database location" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:303 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1356 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:304 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1398 msgid "Choose a location for your ebook library." msgstr "" @@ -3634,23 +3762,27 @@ msgstr "" msgid "Migrating database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:487 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:428 +msgid "Browse by covers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:517 msgid "Device: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:488 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:518 msgid " detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:510 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:540 msgid "Connected " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:521 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:551 msgid "Device database corrupted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:522 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:552 msgid "" "\n" "

The database of books on the reader is corrupted. Try the " @@ -3666,320 +3798,320 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:571 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:658 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:601 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:689 msgid "Stop" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:574 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:604 msgid "Adding books recursively..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:578 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:608 msgid "Added " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:578 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:608 msgid "Searching..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:589 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:695 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:620 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:726 msgid "" "

Books with the same title as the following already exist in the database. " "Add them anyway?