diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 28be488dce..9ca7ae590d 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -25,6 +25,17 @@ every time you add an HTML file to the library.\ html2oeb(htmlfile, of) return of.name +class OPFMetadataReader(MetadataReaderPlugin): + + name = 'Read OPF metadata' + file_types = set(['opf']) + description = _('Read metadata from %s files')%'OPF' + + def get_metadata(self, stream, ftype): + from calibre.ebooks.metadata.opf2 import OPF + from calibre.ebooks.metadata import MetaInformation + return MetaInformation(OPF(stream, os.getcwd())) + class RTFMetadataReader(MetadataReaderPlugin): name = 'Read RTF metadata' @@ -167,6 +178,16 @@ class ComicMetadataReader(MetadataReaderPlugin): ext = os.path.splitext(path)[1][1:] mi.cover_data = (ext.lower(), data) return mi + +class ZipMetadataReader(MetadataReaderPlugin): + + name = 'Read ZIP metadata' + file_types = set(['zip', 'oebzip']) + description = _('Read metadata from ebooks in ZIP archives') + + def get_metadata(self, stream, ftype): + from calibre.ebooks.metadata.zip import get_metadata + return get_metadata(stream) class EPUBMetadataWriter(MetadataWriterPlugin): diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index 067185b0c3..c79b4ed98e 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -127,6 +127,7 @@ def get_file_type_metadata(stream, ftype): mi = plugin.get_metadata(stream, ftype.lower().strip()) break except: + traceback.print_exc() continue return mi diff --git a/src/calibre/devices/cybookg3/books.py b/src/calibre/devices/cybookg3/books.py deleted file mode 100644 index 9ff8aa70a7..0000000000 --- a/src/calibre/devices/cybookg3/books.py +++ /dev/null @@ -1,85 +0,0 @@ -__license__ = 'GPL v3' -__copyright__ = '2009, John Schember ' - -''' -''' -import os, fnmatch, time - -from calibre.devices.interface import BookList as _BookList - -EBOOK_DIR = "eBooks" -EBOOK_TYPES = ['mobi', 'prc', 'html', 'pdf', 'rtf', 'txt'] - -class Book(object): - def __init__(self, path, title, authors): - self.title = title - self.authors = authors - self.size = os.path.getsize(path) - self.datetime = time.gmtime(os.path.getctime(path)) - self.path = path - self.thumbnail = None - self.tags = [] - - @apply - def title_sorter(): - doc = '''String to sort the title. If absent, title is returned''' - def fget(self): - return re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', self.title).rstrip() - return property(doc=doc, fget=fget) - - @apply - def thumbnail(): - return None - - def __str__(self): - """ Return a utf-8 encoded string with title author and path information """ - return self.title.encode('utf-8') + " by " + \ - self.authors.encode('utf-8') + " at " + self.path.encode('utf-8') - - -class BookList(_BookList): - def __init__(self, mountpath): - self._mountpath = mountpath - _BookList.__init__(self) - self.return_books(mountpath) - - def return_books(self, mountpath): - # Get all books in all directories under the root EBOOK_DIR directory - for path, dirs, files in os.walk(os.path.join(mountpath, EBOOK_DIR)): - # Filter out anything that isn't in the list of supported ebook types - for book_type in EBOOK_TYPES: - for filename in fnmatch.filter(files, '*.%s' % (book_type)): - book_title = '' - book_author = '' - # Calibre uses a specific format for file names. They take the form - # title_-_author_number.extention We want to see if the file name is - # in this format. - if fnmatch.fnmatchcase(filename, '*_-_*.*'): - # Get the title and author from the file name - title, sep, author = filename.rpartition('_-_') - author, sep, ext = author.rpartition('_') - book_title = title.replace('_', ' ') - book_author = author.replace('_', ' ') - # if the filename did not match just set the title to - # the filename without the extension - else: - book_title = os.path.splitext(filename)[0].replace('_', ' ') - - self.append(Book(os.path.join(path, filename), book_title, book_author)) - - def add_book(self, path, title): - self.append(Book(path, title, "")) - - def remove_book(self, path): - for book in self: - if path.endswith(book.path): - self.remove(book) - break - - def supports_tags(self): - ''' Return True if the the device supports tags (collections) for this book list. ''' - return False - - def set_tags(self, book, tags): - pass - diff --git a/src/calibre/devices/cybookg3/driver.py b/src/calibre/devices/cybookg3/driver.py index c689767457..cf9f2e5a41 100644 --- a/src/calibre/devices/cybookg3/driver.py +++ b/src/calibre/devices/cybookg3/driver.py @@ -1,188 +1,43 @@ __license__ = 'GPL v3' __copyright__ = '2009, John Schember ' - ''' Device driver for Bookeen's Cybook Gen 3 ''' -import os, fnmatch, shutil, time -from itertools import cycle -from calibre.devices.interface import Device -from calibre.devices.errors import DeviceError, FreeSpaceError +import os, fnmatch -from calibre.devices.cybookg3.books import BookList, EBOOK_DIR, EBOOK_TYPES -from calibre import iswindows, islinux, isosx, __appname__ +from calibre.devices.usbms.driver import USBMS -class CYBOOKG3(Device): +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 = EBOOK_TYPES + FORMATS = MIME_MAP.keys() + VENDOR_ID = 0x0bda PRODUCT_ID = 0x0703 - BCD = 0x110 - + BCD = [0x110, 0x132] + VENDOR_NAME = 'BOOKEEN' PRODUCT_NAME = 'CYBOOK_GEN3' + OSX_NAME_MAIN_MEM = 'Bookeen Cybook Gen3 -FD Media' + OSX_NAME_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' - FDI_TEMPLATE = \ -''' - - - - - - - %(main_memory)s - %(deviceclass)s - - - - - - - - - - - - - %(storage_card)s - %(deviceclass)s - - - - - - -''' - - - def __init__(self, key='-1', log_packets=False, report_progress=None) : - self._main_prefix = self._card_prefix = None - - @classmethod - def get_fdi(cls): - return cls.FDI_TEMPLATE%dict( - app=__appname__, - deviceclass=cls.__name__, - vendor_id=hex(cls.VENDOR_ID), - product_id=hex(cls.PRODUCT_ID), - bcd=hex(cls.BCD), - main_memory=cls.MAIN_MEMORY_VOLUME_LABEL, - storage_card=cls.STORAGE_CARD_VOLUME_LABEL, - ) - - def set_progress_reporter(self, report_progress): - self.report_progress = report_progress - - def get_device_information(self, end_session=True): - """ - Ask device for device information. See L{DeviceInfoQuery}. - @return: (device name, device version, software version on device, mime type) - """ - return (self.__class__.__name__, '', '', '') - - def card_prefix(self, end_session=True): - return self._card_prefix - - @classmethod - def _windows_space(cls, prefix): - if prefix is None: - return 0, 0 - win32file = __import__('win32file', globals(), locals(), [], -1) - try: - sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \ - win32file.GetDiskFreeSpace(prefix[:-1]) - except Exception, err: - if getattr(err, 'args', [None])[0] == 21: # Disk not ready - time.sleep(3) - sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \ - win32file.GetDiskFreeSpace(prefix[:-1]) - else: raise - mult = sectors_per_cluster * bytes_per_sector - return total_clusters * mult, free_clusters * mult - - def total_space(self, end_session=True): - msz = csz = 0 - print self._main_prefix - if not iswindows: - if self._main_prefix is not None: - stats = os.statvfs(self._main_prefix) - msz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree) - if self._card_prefix is not None: - stats = os.statvfs(self._card_prefix) - csz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree) - else: - msz = self._windows_space(self._main_prefix)[0] - csz = self._windows_space(self._card_prefix)[0] - - return (msz, 0, csz) - - def free_space(self, end_session=True): - msz = csz = 0 - if not iswindows: - if self._main_prefix is not None: - stats = os.statvfs(self._main_prefix) - msz = stats.f_frsize * stats.f_bavail - if self._card_prefix is not None: - stats = os.statvfs(self._card_prefix) - csz = stats.f_frsize * stats.f_bavail - else: - msz = self._windows_space(self._main_prefix)[1] - csz = self._windows_space(self._card_prefix)[1] - - return (msz, 0, csz) - - def books(self, oncard=False, end_session=True): - if oncard and self._card_prefix is None: - return [] - prefix = self._card_prefix if oncard else self._main_prefix - bl = BookList(prefix) - return bl - - def upload_books(self, files, names, on_card=False, end_session=True): - 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, EBOOK_DIR) - else: - path = os.path.join(self._card_prefix, EBOOK_DIR) - - sizes = map(os.path.getsize, 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: - raise FreeSpaceError("There is insufficient free space " +\ - "in main memory") + EBOOK_DIR = "eBooks" - paths = [] - names = iter(names) - - for infile in files: - filepath = os.path.join(path, names.next()) - paths.append(filepath) - - shutil.copy2(infile, filepath) - - return zip(paths, cycle([on_card])) - - @classmethod - def add_books_to_metadata(cls, locations, metadata, booklists): - for location in locations: - path = location[0] - on_card = 1 if location[1] else 0 - booklists[on_card].add_book(path, os.path.basename(path)) - def delete_books(self, paths, end_session=True): for path in paths: if os.path.exists(path): - # Delete the ebook os.unlink(path) filepath, ext = os.path.splitext(path) @@ -196,137 +51,4 @@ class CYBOOKG3(Device): for p, d, files in os.walk(basepath): for filen in fnmatch.filter(files, filename + "*.t2b"): os.unlink(os.path.join(p, filen)) - - @classmethod - def remove_books_from_metadata(cls, paths, booklists): - for path in paths: - for bl in booklists: - bl.remove_book(path) - - def sync_booklists(self, booklists, end_session=True): - # There is no meta data on the device to update. The device is treated - # as a mass storage device and does not use a meta data xml file like - # the Sony Readers. - pass - - def get_file(self, path, outfile, end_session=True): - path = self.munge_path(path) - src = open(path, 'rb') - shutil.copyfileobj(src, outfile, 10*1024*1024) - - def munge_path(self, path): - if path.startswith('/') and not (path.startswith(self._main_prefix) or \ - (self._card_prefix and path.startswith(self._card_prefix))): - path = self._main_prefix + path[1:] - elif path.startswith('card:'): - path = path.replace('card:', self._card_prefix[:-1]) - return path - - @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 - - return False - - # This only supports Windows >= 2000 - def open_windows(self): - 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 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] - - def open_osx(self): - raise NotImplementedError() - - def open_linux(self): - import dbus - bus = dbus.SystemBus() - hm = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager") - - def conditional_mount(dev): - mmo = bus.get_object("org.freedesktop.Hal", dev) - label = mmo.GetPropertyString('volume.label', dbus_interface='org.freedesktop.Hal.Device') - is_mounted = mmo.GetPropertyString('volume.is_mounted', dbus_interface='org.freedesktop.Hal.Device') - mount_point = mmo.GetPropertyString('volume.mount_point', dbus_interface='org.freedesktop.Hal.Device') - fstype = mmo.GetPropertyString('volume.fstype', dbus_interface='org.freedesktop.Hal.Device') - if is_mounted: - return str(mount_point) - mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'], - dbus_interface='org.freedesktop.Hal.Device.Volume') - return os.path.normpath('/media/'+label)+'/' - - mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__) - if not mm: - raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,)) - self._main_prefix = None - for dev in mm: - try: - self._main_prefix = conditional_mount(dev)+os.sep - break - except dbus.exceptions.DBusException: - continue - - if not self._main_prefix: - raise DeviceError('Could not open device for reading. Try a reboot.') - - self._card_prefix = None - cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__) - - for dev in cards: - try: - self._card_prefix = conditional_mount(dev)+os.sep - break - except: - import traceback - print traceback - continue - - def open(self): - time.sleep(5) - self._main_prefix = self._card_prefix = None - if islinux: - try: - self.open_linux() - except DeviceError: - time.sleep(3) - self.open_linux() - if iswindows: - try: - self.open_windows() - except DeviceError: - time.sleep(3) - self.open_windows() - if isosx: - try: - self.open_osx() - except DeviceError: - time.sleep(3) - self.open_osx() diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index e2959cd49f..85d25d82a4 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -20,7 +20,9 @@ class Device(object): FORMATS = ["lrf", "rtf", "pdf", "txt"] VENDOR_ID = 0x0000 PRODUCT_ID = 0x0000 - BCD = 0x0000 + # BCD can be either None to not distinguish between devices based on BCD, or + # it can be a list of the BCD numbers of all devices supported by this driver. + BCD = None THUMBNAIL_HEIGHT = 68 # Height for thumbnails on device def __init__(self, key='-1', log_packets=False, report_progress=None) : diff --git a/src/calibre/devices/kindle/books.py b/src/calibre/devices/kindle/books.py deleted file mode 100755 index 623b02fe20..0000000000 --- a/src/calibre/devices/kindle/books.py +++ /dev/null @@ -1,122 +0,0 @@ -__license__ = 'GPL v3' -__copyright__ = '2008, Kovid Goyal ' -''' -''' -import re, time, functools -import os - - -from calibre.devices.interface import BookList as _BookList -from calibre.devices import strftime as _strftime - -strftime = functools.partial(_strftime, zone=time.localtime) -MIME_MAP = { - "azw" : "application/azw", - "prc" : "application/prc", - "txt" : "text/plain", - 'mobi': 'application/mobi', - } - -def sortable_title(title): - return re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', title).rstrip() - -class Book(object): - - @apply - def title_sorter(): - doc = '''String to sort the title. If absent, title is returned''' - def fget(self): - src = self.title - return src - def fset(self, val): - self.elem.setAttribute('titleSorter', sortable_title(unicode(val))) - return property(doc=doc, fget=fget, fset=fset) - - @apply - def thumbnail(): - return 0 - - - @apply - def path(): - doc = """ Absolute path to book on device. Setting not supported. """ - def fget(self): - return self.mountpath + self.rpath - return property(fget=fget, doc=doc) - - @apply - def db_id(): - doc = '''The database id in the application database that this file corresponds to''' - def fget(self): - match = re.search(r'_(\d+)$', self.rpath.rpartition('.')[0]) - if match: - return int(match.group(1)) - return property(fget=fget, doc=doc) - - def __init__(self, mountpath, title, authors ): - self.mountpath = mountpath - self.title = title - self.authors = authors - self.mime = "" - self.rpath = "documents//" + title - self.id = 0 - self.sourceid = 0 - self.size = 0 - self.datetime = time.gmtime() - self.tags = [] - - - def __str__(self): - """ Return a utf-8 encoded string with title author and path information """ - return self.title.encode('utf-8') + " by " + \ - self.authors.encode('utf-8') + " at " + self.path.encode('utf-8') - - -class BookList(_BookList): - _mountpath = "" - - def __init__(self, mountpath): - self._mountpath = mountpath - _BookList.__init__(self) - self.return_books(mountpath) - - def return_books(self,mountpath): - docs = mountpath + "documents" - for f in os.listdir(docs): - m = re.match(".*azw", f) - if m: - self.append_book(mountpath,f) - m = re.match(".*prc", f) - if m: - self.append_book(mountpath,f) - m = re.match(".*txt", f) - if m: - self.append_book(mountpath,f) - - def append_book(self,mountpath,f): - b = Book(mountpath,f,"") - b.size = os.stat(mountpath + "//documents//" + f)[6] - b.datetime = time.gmtime(os.stat(mountpath + "//documents//" + f)[8]) - b.rpath = "//documents//" + f - self.append(b) - - def supports_tags(self): - return False - - def add_book(self, name, size, ctime): - book = Book(self._mountpath, name, "") - book.datetime = time.gmtime(ctime) - book.size = size - '''remove book if already in db''' - self.remove_book(self._mountpath + "//documents//" + name) - self.append(book) - - - def remove_book(self, path): - for book in self: - if path.startswith(book.mountpath): - if path.endswith(book.rpath): - self.remove(book) - break - - diff --git a/src/calibre/devices/kindle/driver.py b/src/calibre/devices/kindle/driver.py index 93145432db..06c3b1cf27 100755 --- a/src/calibre/devices/kindle/driver.py +++ b/src/calibre/devices/kindle/driver.py @@ -1,360 +1,32 @@ __license__ = 'GPL v3' -__copyright__ = '2008, Kovid Goyal ' -''' -Device driver for the Amazon Kindle -''' -import sys, os, shutil, time, subprocess, re -from itertools import cycle - -from calibre.devices.interface import Device -from calibre.devices.errors import DeviceError, FreeSpaceError -from calibre.devices.kindle.books import BookList -from calibre import iswindows, islinux, isosx -from calibre.devices.errors import PathError - -class File(object): - def __init__(self, path): - stats = os.stat(path) - self.is_dir = os.path.isdir(path) - self.is_readonly = not os.access(path, os.W_OK) - self.ctime = stats.st_ctime - self.wtime = stats.st_mtime - self.size = stats.st_size - if path.endswith(os.sep): - path = path[:-1] - self.path = path - self.name = os.path.basename(path) - - -class KINDLE(Device): - FORMATS = ["azw", "prc", "txt", 'mobi'] - VENDOR_ID = 0x1949 #: Amazon Vendor Id - PRODUCT_ID = 0x001 #: Product Id for the Kindle - INTERNAL_STORAGE = 'INTERNAL_STORAGE' - CARD_STORAGE = 'CARD_STORAGE' - PRODUCT_NAME = 'KINDLE' - VENDOR_NAME = 'AMAZON' - - - MAIN_MEMORY_VOLUME_LABEL = 'Kindle Internal Storage USB Device' - STORAGE_CARD_VOLUME_LABEL = 'Kindle Card Storage USB Device' - - #OSX_MAIN_NAME = 'Sony PRS-505/UC Media' - #OSX_SD_NAME = 'Sony PRS-505/UC:SD Media' - #OSX_MS_NAME = 'Sony PRS-505/UC:MS Media' - - FDI_TEMPLATE = \ -''' - - - - - - %(main_memory)s - %(deviceclass)s - - - - - - - - - - - %(storage_card)s - %(deviceclass)s - - - - - -''' - - - def __init__(self, log_packets=False): - self._main_prefix = self._card_prefix = None - - @classmethod - def get_fdi(cls): - return cls.FDI_TEMPLATE%dict( - deviceclass=cls.__name__, - vendor_id=hex(cls.VENDOR_ID), - product_id=hex(cls.PRODUCT_ID), - main_memory=cls.MAIN_MEMORY_VOLUME_LABEL, - storage_card=cls.STORAGE_CARD_VOLUME_LABEL, - ) - - @classmethod - def is_device(cls, device_id): - '''print "mimi in is device"''' - if not hasattr(device_id, 'upper'): - return False - - if 'VEN_'+cls.VENDOR_NAME in device_id.upper() and \ - 'PROD_'+cls.INTERNAL_STORAGE in device_id.upper(): - return True - if 'VEN_'+cls.VENDOR_NAME in device_id.upper() and \ - 'PROD_'+cls.CARD_STORAGE in device_id.upper(): - return True - - vid, pid = hex(cls.VENDOR_ID)[2:], hex(cls.PRODUCT_ID)[2:] - if len(vid) < 4: vid = '0'+vid - if len(pid) < 4: pid = '0'+pid - if len(pid) < 4: pid = '0'+pid - if 'VID_'+vid in device_id.upper() and \ - 'PID_'+pid in device_id.upper(): - return True - return False - - def open_osx(self): - mount = subprocess.Popen('mount', shell=True, - stdout=subprocess.PIPE).stdout.read() - src = subprocess.Popen('ioreg -n "%s"'%(self.OSX_MAIN_NAME,), - shell=True, stdout=subprocess.PIPE).stdout.read() - try: - devname = re.search(r'BSD Name.*=\s+"(\S+)"', src).group(1) - self._main_prefix = re.search('/dev/%s(\w*)\s+on\s+([^\(]+)\s+'%(devname,), mount).group(2) + os.sep - except: - raise DeviceError('Unable to find %s. Is it connected?'%(self.__class__.__name__,)) - try: - src = subprocess.Popen('ioreg -n "%s"'%(self.OSX_SD_NAME,), - shell=True, stdout=subprocess.PIPE).stdout.read() - devname = re.search(r'BSD Name.*=\s+"(\S+)"', src).group(1) - except: - try: - src = subprocess.Popen('ioreg -n "%s"'%(self.OSX_MS_NAME,), - shell=True, stdout=subprocess.PIPE).stdout.read() - devname = re.search(r'BSD Name.*=\s+"(\S+)"', src).group(1) - except: - devname = None - if devname is not None: - self._card_prefix = re.search('/dev/%s(\w*)\s+on\s+([^\(]+)\s+'%(devname,), mount).group(2) + os.sep - - - def open_windows(self): - raise NotImplementedError - - - def open_linux(self): - import dbus - bus = dbus.SystemBus() - hm = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager") - try: - mm = hm.FindDeviceStringMatch('kindle.mainvolume', self.__class__.__name__)[0] - except: - raise DeviceError('Unable to find %s. Is it connected?'%(self.__class__.__name__,)) - try: - sc = hm.FindDeviceStringMatch('kindle.cardvolume', self.__class__.__name__)[0] - except: - sc = None - - def conditional_mount(dev): - mmo = bus.get_object("org.freedesktop.Hal", dev) - label = mmo.GetPropertyString('volume.label', dbus_interface='org.freedesktop.Hal.Device') - is_mounted = mmo.GetPropertyString('volume.is_mounted', dbus_interface='org.freedesktop.Hal.Device') - mount_point = mmo.GetPropertyString('volume.mount_point', dbus_interface='org.freedesktop.Hal.Device') - fstype = mmo.GetPropertyString('volume.fstype', dbus_interface='org.freedesktop.Hal.Device') - if is_mounted: - return str(mount_point) - mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'], - dbus_interface='org.freedesktop.Hal.Device.Volume') - return os.path.normpath('/media/'+label)+'/' - - self._main_prefix = conditional_mount(mm)+os.sep - self._card_prefix = None - if sc is not None: - self._card_prefix = conditional_mount(sc)+os.sep - - def open(self): - time.sleep(5) - self._main_prefix = self._card_prefix = None - if islinux: - try: - self.open_linux() - except DeviceError: - time.sleep(3) - self.open_linux() - if iswindows: - try: - self.open_windows() - except DeviceError: - time.sleep(3) - self.open_windows() - if isosx: - try: - self.open_osx() - except DeviceError: - time.sleep(3) - self.open_osx() - - - def set_progress_reporter(self, pr): - self.report_progress = pr - - def get_device_information(self, end_session=True): - return ('Kindle', '', '', '') - - def card_prefix(self, end_session=True): - return self._card_prefix - - @classmethod - def _windows_space(cls, prefix): - if prefix is None: - return 0, 0 - import win32file - sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \ - win32file.GetDiskFreeSpace(prefix[:-1]) - mult = sectors_per_cluster * bytes_per_sector - return total_clusters * mult, free_clusters * mult - - def total_space(self, end_session=True): - msz = csz = 0 - if not iswindows: - if self._main_prefix is not None: - stats = os.statvfs(self._main_prefix) - msz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree) - if self._card_prefix is not None: - stats = os.statvfs(self._card_prefix) - csz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree) - else: - msz = self._windows_space(self._main_prefix)[0] - csz = self._windows_space(self._card_prefix)[0] - - return (msz, 0, csz) - - def free_space(self, end_session=True): - msz = csz = 0 - if not iswindows: - if self._main_prefix is not None: - stats = os.statvfs(self._main_prefix) - msz = stats.f_bsize * stats.f_bavail - if self._card_prefix is not None: - stats = os.statvfs(self._card_prefix) - csz = stats.f_bsize * stats.f_bavail - else: - msz = self._windows_space(self._main_prefix)[1] - csz = self._windows_space(self._card_prefix)[1] - - return (msz, 0, csz) - - def books(self, oncard=False, end_session=True): - if oncard and self._card_prefix is None: - return [] - prefix = self._card_prefix if oncard else self._main_prefix - bl = BookList(prefix) - return bl - - def munge_path(self, path): - if path.startswith('/') and not (path.startswith(self._main_prefix) or \ - (self._card_prefix and path.startswith(self._card_prefix))): - path = self._main_prefix + path[1:] - elif path.startswith('card:'): - path = path.replace('card:', self._card_prefix[:-1]) - return path - - def mkdir(self, path, end_session=True): - """ Make directory """ - path = self.munge_path(path) - os.mkdir(path) - - def list(self, path, recurse=False, end_session=True, munge=True): - if munge: - path = self.munge_path(path) - if os.path.isfile(path): - return [(os.path.dirname(path), [File(path)])] - entries = [File(os.path.join(path, f)) for f in os.listdir(path)] - dirs = [(path, entries)] - for _file in entries: - if recurse and _file.is_dir: - dirs[len(dirs):] = self.list(_file.path, recurse=True, munge=False) - return dirs - - def get_file(self, path, outfile, end_session=True): - path = self.munge_path(path) - src = open(path, 'rb') - shutil.copyfileobj(src, outfile, 10*1024*1024) - - def put_file(self, infile, path, replace_file=False, end_session=True): - path = self.munge_path(path) - if os.path.isdir(path): - path = os.path.join(path, infile.name) - if not replace_file and os.path.exists(path): - raise PathError('File already exists: '+path) - dest = open(path, 'wb') - shutil.copyfileobj(infile, dest, 10*1024*1024) - dest.flush() - dest.close() - - def rm(self, path, end_session=True): - path = self.munge_path(path) - os.unlink(path) - - def touch(self, path, end_session=True): - path = self.munge_path(path) - if not os.path.exists(path): - open(path, 'w').close() - if not os.path.isdir(path): - os.utime(path, None) - - def upload_books(self, files, names, on_card=False, end_session=True): - path = os.path.join(self._card_prefix, "documents") if on_card \ - else os.path.join(self._main_prefix, 'documents') - infiles = [file if hasattr(file, 'read') else open(file, 'rb') for file in files] - for f in infiles: f.seek(0, 2) - sizes = [f.tell() for f in infiles] - size = sum(sizes) - space = self.free_space() - mspace = space[0] - cspace = space[2] - if on_card and size > cspace - 1024*1024: - raise FreeSpaceError("There is insufficient free space "+\ - "on the storage card") - if not on_card and size > mspace - 2*1024*1024: - raise FreeSpaceError("There is insufficient free space " +\ - "in main memory") - - paths, ctimes = [], [] - - names = iter(names) - for infile in infiles: - infile.seek(0) - name = names.next() - paths.append(os.path.join(path, name)) - if on_card and not os.path.exists(os.path.dirname(paths[-1])): - os.mkdir(os.path.dirname(paths[-1])) - self.put_file(infile, paths[-1], replace_file=True) - ctimes.append(os.path.getctime(paths[-1])) - return zip(paths, sizes, ctimes, cycle([on_card])) - - @classmethod - def add_books_to_metadata(cls, locations, metadata, booklists): - metadata = iter(metadata) - for location in locations: - #info = metadata.next() - path = location[0] - on_card = 1 if location[3] else 0 - name = path.rpartition(os.sep)[2] - name = name.replace('//', '/') - booklists[on_card].add_book(name,*location[1:-1]) - - def delete_books(self, paths, end_session=True): - for path in paths: - os.unlink(path) - - @classmethod - def remove_books_from_metadata(cls, paths, booklists): - for path in paths: - for bl in booklists: - bl.remove_book(path) - - - def sync_booklists(self, booklists, end_session=True): - return 0; - - -def main(args=sys.argv): - return 0 - -if __name__ == '__main__': - sys.exit(main()) \ No newline at end of file +__copyright__ = '2009, John Schember ' +''' +Device driver for Amazon's Kindle +''' + +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() + + VENDOR_ID = 0x1949 + PRODUCT_ID = 0x0001 + BCD = 0x399 + + VENDOR_NAME = 'AMAZON' + PRODUCT_NAME = 'KINDLE' + + MAIN_MEMORY_VOLUME_LABEL = 'Kindle Main Memory' + STORAGE_CARD_VOLUME_LABEL = 'Kindle Storage Card' + + EBOOK_DIR = "documents" + diff --git a/src/calibre/devices/prs500/driver.py b/src/calibre/devices/prs500/driver.py index 177f57b15f..232d2c758c 100755 --- a/src/calibre/devices/prs500/driver.py +++ b/src/calibre/devices/prs500/driver.py @@ -85,7 +85,7 @@ class PRS500(Device): VENDOR_ID = 0x054c #: SONY Vendor Id PRODUCT_ID = 0x029b #: Product Id for the PRS-500 - BCD = 0x100 + BCD = [0x100] PRODUCT_NAME = 'PRS-500' VENDOR_NAME = 'SONY' INTERFACE_ID = 0 #: The interface we use to talk to the device diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 0f60d7238b..2e8a6197a2 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -29,7 +29,7 @@ class File(object): class PRS505(Device): VENDOR_ID = 0x054c #: SONY Vendor Id PRODUCT_ID = 0x031e #: Product Id for the PRS-505 - BCD = 0x229 #: Needed to disambiguate 505 and 700 on linux + BCD = [0x229] #: Needed to disambiguate 505 and 700 on linux PRODUCT_NAME = 'PRS-505' VENDOR_NAME = 'SONY' FORMATS = ['lrf', 'epub', "rtf", "pdf", "txt"] @@ -86,7 +86,7 @@ class PRS505(Device): deviceclass=cls.__name__, vendor_id=hex(cls.VENDOR_ID), product_id=hex(cls.PRODUCT_ID), - bcd=hex(cls.BCD), + bcd=hex(cls.BCD[0]), main_memory=cls.MAIN_MEMORY_VOLUME_LABEL, storage_card=cls.STORAGE_CARD_VOLUME_LABEL, ) diff --git a/src/calibre/devices/prs700/driver.py b/src/calibre/devices/prs700/driver.py index 812ac0f911..5db60ef506 100644 --- a/src/calibre/devices/prs700/driver.py +++ b/src/calibre/devices/prs700/driver.py @@ -9,7 +9,7 @@ from calibre.devices.prs505.driver import PRS505 class PRS700(PRS505): - BCD = 0x31a + BCD = [0x31a] PRODUCT_NAME = 'PRS-700' OSX_NAME = 'Sony PRS-700' diff --git a/src/calibre/devices/scanner.py b/src/calibre/devices/scanner.py index 40573114a7..ec937fc84d 100644 --- a/src/calibre/devices/scanner.py +++ b/src/calibre/devices/scanner.py @@ -39,20 +39,37 @@ class DeviceScanner(object): '''Fetch list of connected USB devices from operating system''' self.devices = self.scanner() + def test_bcd_windows(self, device_id, bcd): + if bcd is None or len(bcd) == 0: + return True + for c in bcd: + # Bug in winutil.get_usb_devices converts a to : + rev = ('rev_%4.4x'%c).replace('a', ':') + if rev in device_id: + return True + return False + + def test_bcd(self, bcdDevice, bcd): + if bcd is None or len(bcd) == 0: + return True + for c in bcd: + if c == bcdDevice: + return True + return False + def is_device_connected(self, device): if iswindows: + vid, pid = 'vid_%4.4x'%device.VENDOR_ID, 'pid_%4.4x'%device.PRODUCT_ID for device_id in self.devices: - vid, pid = 'vid_%4.4x'%device.VENDOR_ID, 'pid_%4.4x'%device.PRODUCT_ID - rev = ('rev_%4.4x'%device.BCD).replace('a', ':') # Bug in winutil.get_usb_devices converts a to : - if vid in device_id and pid in device_id and rev in device_id: - return True - return False + if vid in device_id and pid in device_id: + if self.test_bcd_windows(device_id, getattr(device, 'BCD', None)): + return True else: for vendor, product, bcdDevice in self.devices: if device.VENDOR_ID == vendor and device.PRODUCT_ID == product: - if hasattr(device, 'BCD') and device.BCD == bcdDevice: + if self.test_bcd(bcdDevice, getattr(device, 'BCD', None)): return True - return False + return False def main(args=sys.argv): diff --git a/src/calibre/devices/usbms/__init__.py b/src/calibre/devices/usbms/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py new file mode 100644 index 0000000000..3943ef94f4 --- /dev/null +++ b/src/calibre/devices/usbms/books.py @@ -0,0 +1,44 @@ +__license__ = 'GPL v3' +__copyright__ = '2009, John Schember ' +''' +''' + +import os, fnmatch, re, time + +from calibre.devices.interface import BookList as _BookList + +class Book(object): + def __init__(self, path, title, authors, mime): + self.title = title + self.authors = authors + self.mime = mime + self.size = os.path.getsize(path) + self.datetime = time.gmtime(os.path.getctime(path)) + self.path = path + self.thumbnail = None + self.tags = [] + + @apply + def title_sorter(): + doc = '''String to sort the title. If absent, title is returned''' + def fget(self): + return re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', self.title).rstrip() + return property(doc=doc, fget=fget) + + @apply + def thumbnail(): + return None + + def __str__(self): + """ Return a utf-8 encoded string with title author and path information """ + return self.title.encode('utf-8') + " by " + \ + self.authors.encode('utf-8') + " at " + self.path.encode('utf-8') + +class BookList(_BookList): + def supports_tags(self): + return False + + def set_tags(self, book, tags): + pass + + diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py new file mode 100644 index 0000000000..f6b0c6a0a8 --- /dev/null +++ b/src/calibre/devices/usbms/device.py @@ -0,0 +1,295 @@ +__license__ = 'GPL v3' +__copyright__ = '2009, John Schember ' +''' +Generic device driver. This is not a complete stand alone driver. It is +intended to be subclassed with the relevant parts implemented for a particular +device. This class handles devive detection. +''' + +import os, time + +from calibre.devices.interface import Device as _Device +from calibre.devices.errors import DeviceError +from calibre import iswindows, islinux, isosx, __appname__ + +class Device(_Device): + ''' + This class provides logic common to all drivers for devices that export themselves + as USB Mass Storage devices. If you are writing such a driver, inherit from this + class. + ''' + + VENDOR_ID = 0x0 + PRODUCT_ID = 0x0 + BCD = None + + VENDOR_NAME = '' + PRODUCT_NAME = '' + + OSX_NAME_MAIN_MEM = '' + OSX_NAME_CARD_MEM = '' + + MAIN_MEMORY_VOLUME_LABEL = '' + STORAGE_CARD_VOLUME_LABEL = '' + + FDI_TEMPLATE = \ +''' + + + + + %(BCD_start)s + + %(main_memory)s + %(deviceclass)s + + %(BCD_end)s + + + + + + + + + %(BCD_start)s + + %(storage_card)s + %(deviceclass)s + + %(BCD_end)s + + + + +''' + FDI_BCD_TEMPLATE = '' + + + def __init__(self, key='-1', log_packets=False, report_progress=None) : + self._main_prefix = self._card_prefix = None + + @classmethod + def get_fdi(cls): + fdi = '' + + fdi_base_values = dict( + app=__appname__, + deviceclass=cls.__name__, + vendor_id=hex(cls.VENDOR_ID), + product_id=hex(cls.PRODUCT_ID), + main_memory=cls.MAIN_MEMORY_VOLUME_LABEL, + storage_card=cls.STORAGE_CARD_VOLUME_LABEL, + ) + if cls.BCD is None: + fdi_base_values['BCD_start'] = '' + fdi_base_values['BCD_end'] = '' + fdi = cls.FDI_TEMPLATE % fdi_base_values + else: + for bcd in cls.BCD: + fdi_bcd_values = fdi_base_values + fdi_bcd_values['BCD_start'] = cls.FDI_BCD_TEMPLATE % dict(bcd=hex(bcd)) + fdi_bcd_values['BCD_end'] = '' + fdi += cls.FDI_TEMPLATE % fdi_bcd_values + + return fdi + + def set_progress_reporter(self, report_progress): + self.report_progress = report_progress + + def card_prefix(self, end_session=True): + return self._card_prefix + + @classmethod + def _windows_space(cls, prefix): + if prefix is None: + return 0, 0 + win32file = __import__('win32file', globals(), locals(), [], -1) + try: + sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \ + win32file.GetDiskFreeSpace(prefix[:-1]) + except Exception, err: + if getattr(err, 'args', [None])[0] == 21: # Disk not ready + time.sleep(3) + sectors_per_cluster, bytes_per_sector, free_clusters, total_clusters = \ + win32file.GetDiskFreeSpace(prefix[:-1]) + else: raise + mult = sectors_per_cluster * bytes_per_sector + return total_clusters * mult, free_clusters * mult + + def total_space(self, end_session=True): + msz = csz = 0 + print self._main_prefix + if not iswindows: + if self._main_prefix is not None: + stats = os.statvfs(self._main_prefix) + msz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree) + if self._card_prefix is not None: + stats = os.statvfs(self._card_prefix) + csz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree) + else: + msz = self._windows_space(self._main_prefix)[0] + csz = self._windows_space(self._card_prefix)[0] + + return (msz, 0, csz) + + def free_space(self, end_session=True): + msz = csz = 0 + if not iswindows: + if self._main_prefix is not None: + stats = os.statvfs(self._main_prefix) + msz = stats.f_frsize * stats.f_bavail + if self._card_prefix is not None: + stats = os.statvfs(self._card_prefix) + csz = stats.f_frsize * stats.f_bavail + else: + msz = self._windows_space(self._main_prefix)[1] + csz = self._windows_space(self._card_prefix)[1] + + 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 + return False + + # This only supports Windows >= 2000 + def open_windows(self): + 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 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] + + @classmethod + def get_osx_mountpoints(self, raw=None): + if raw is None: + ioreg = '/usr/sbin/ioreg' + if not os.access(ioreg, os.X_OK): + ioreg = 'ioreg' + raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(), stdout=subprocess.PIPE).stdout.read() + lines = raw.splitlines() + names = {} + + def get_dev_node(lines, loc): + for line in lines: + line = line.strip() + if line.endswith('}'): + break + match = re.search(r'"BSD Name"\s+=\s+"(.*?)"', line) + if match is not None: + names[loc] = match.group(1) + break + + for i, line in enumerate(lines): + if line.strip().endswith('') and self.OSX_NAME_MAIN_MEM in line: + get_dev_node(lines[i+1:], 'main') + if line.strip().endswith('') and self.OSX_NAME_CARD_MEM in line: + get_dev_node(lines[i+1:], 'card') + if len(names.keys()) == 2: + break + return names + + def open_osx(self): + mount = subprocess.Popen('mount', shell=True, stdout=subprocess.PIPE).stdout.read() + names = self.get_osx_mountpoints() + dev_pat = r'/dev/%s(\w*)\s+on\s+([^\(]+)\s+' + if 'main' not in names.keys(): + raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__) + main_pat = dev_pat%names['main'] + self._main_prefix = re.search(main_pat, mount).group(2) + os.sep + card_pat = names['card'] if 'card' in names.keys() else None + if card_pat is not None: + card_pat = dev_pat%card_pat + self._card_prefix = re.search(card_pat, mount).group(2) + os.sep + + def open_linux(self): + import dbus + bus = dbus.SystemBus() + hm = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager") + + def conditional_mount(dev): + mmo = bus.get_object("org.freedesktop.Hal", dev) + label = mmo.GetPropertyString('volume.label', dbus_interface='org.freedesktop.Hal.Device') + is_mounted = mmo.GetPropertyString('volume.is_mounted', dbus_interface='org.freedesktop.Hal.Device') + mount_point = mmo.GetPropertyString('volume.mount_point', dbus_interface='org.freedesktop.Hal.Device') + fstype = mmo.GetPropertyString('volume.fstype', dbus_interface='org.freedesktop.Hal.Device') + if is_mounted: + return str(mount_point) + mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'], + dbus_interface='org.freedesktop.Hal.Device.Volume') + return os.path.normpath('/media/'+label)+'/' + + mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__) + if not mm: + raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,)) + self._main_prefix = None + for dev in mm: + try: + self._main_prefix = conditional_mount(dev)+os.sep + break + except dbus.exceptions.DBusException: + continue + + if not self._main_prefix: + raise DeviceError('Could not open device for reading. Try a reboot.') + + self._card_prefix = None + cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__) + + for dev in cards: + try: + self._card_prefix = conditional_mount(dev)+os.sep + break + except: + import traceback + print traceback + continue + + def open(self): + time.sleep(5) + self._main_prefix = self._card_prefix = None + if islinux: + try: + self.open_linux() + except DeviceError: + time.sleep(3) + self.open_linux() + if iswindows: + try: + self.open_windows() + except DeviceError: + time.sleep(3) + self.open_windows() + if isosx: + try: + self.open_osx() + except DeviceError: + time.sleep(3) + self.open_osx() + diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py new file mode 100644 index 0000000000..733ce76ae7 --- /dev/null +++ b/src/calibre/devices/usbms/driver.py @@ -0,0 +1,146 @@ +__license__ = 'GPL v3' +__copyright__ = '2009, John Schember ' +''' +Generic USB Mass storage device driver. This is not a complete stand alone +driver. It is intended to be subclassed with the relevant parts implemented +for a particular device. +''' + +import os, fnmatch, shutil +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 + +class USBMS(Device): + EBOOK_DIR = '' + MIME_MAP = {} + FORMATS = [] + + def __init__(self, key='-1', log_packets=False, report_progress=None): + pass + + def get_device_information(self, end_session=True): + """ + Ask device for device information. See L{DeviceInfoQuery}. + @return: (device name, device version, software version on device, mime type) + """ + return (self.__class__.__name__, '', '', '') + + def books(self, oncard=False, end_session=True): + bl = BookList() + + if oncard and self._card_prefix is None: + return bl + + prefix = self._card_prefix if oncard else self._main_prefix + + # 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)): + # Filter out anything that isn't in the list of supported ebook types + for book_type in self.MIME_MAP.keys(): + 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): + 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) + else: + path = os.path.join(self._card_prefix, self.EBOOK_DIR) + + sizes = map(os.path.getsize, 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: + raise FreeSpaceError(_("There is insufficient free space in main memory")) + + paths = [] + names = iter(names) + + for infile in files: + filepath = os.path.join(path, names.next()) + paths.append(filepath) + + shutil.copy2(infile, filepath) + + return zip(paths, cycle([on_card])) + + @classmethod + def add_books_to_metadata(cls, locations, metadata, booklists): + for location in locations: + path = location[0] + on_card = 1 if location[1] else 0 + + title, author, mime = cls.extract_book_metadata_by_filename(os.path.basename(path)) + booklists[on_card].append(Book(path, title, author, mime)) + + def delete_books(self, paths, end_session=True): + for path in paths: + if os.path.exists(path): + # Delete the ebook + os.unlink(path) + + @classmethod + def remove_books_from_metadata(cls, paths, booklists): + for path in paths: + for bl in booklists: + 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 + # as a mass storage device and does not use a meta data xml file like + # the Sony Readers. + pass + + def get_file(self, path, outfile, end_session=True): + path = self.munge_path(path) + src = open(path, 'rb') + shutil.copyfileobj(src, outfile, 10*1024*1024) + + def munge_path(self, path): + if path.startswith('/') and not (path.startswith(self._main_prefix) or \ + (self._card_prefix and path.startswith(self._card_prefix))): + path = self._main_prefix + path[1:] + elif path.startswith('card:'): + path = path.replace('card:', self._card_prefix[:-1]) + return path + + @classmethod + def extract_book_metadata_by_filename(cls, filename): + book_title = '' + book_author = '' + book_mime = '' + # Calibre uses a specific format for file names. They take the form + # title_-_author_number.extention We want to see if the file name is + # in this format. + if fnmatch.fnmatchcase(filename, '*_-_*.*'): + # Get the title and author from the file name + title, sep, author = filename.rpartition('_-_') + author, sep, ext = author.rpartition('_') + book_title = title.replace('_', ' ') + book_author = author.replace('_', ' ') + # if the filename did not match just set the title to + # the filename without the extension + 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] + + return book_title, book_author, book_mime + +# ls, rm, cp, mkdir, touch, cat + diff --git a/src/calibre/ebooks/epub/__init__.py b/src/calibre/ebooks/epub/__init__.py index f1a60ab646..d8d4c9a758 100644 --- a/src/calibre/ebooks/epub/__init__.py +++ b/src/calibre/ebooks/epub/__init__.py @@ -122,7 +122,8 @@ help on using this feature. structure('prefer_metadata_cover', ['--prefer-metadata-cover'], default=False, action='store_true', help=_('Use the cover detected from the source file in preference to the specified cover.')) - + structure('dont_split_on_page_breaks', ['--dont-split-on-page-breaks'], default=False, + help=_('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.')) toc = c.add_group('toc', _('''\ Control the automatic generation of a Table of Contents. If an OPF file is detected diff --git a/src/calibre/ebooks/epub/split.py b/src/calibre/ebooks/epub/split.py index ed2a78826f..ca95f4094d 100644 --- a/src/calibre/ebooks/epub/split.py +++ b/src/calibre/ebooks/epub/split.py @@ -50,11 +50,15 @@ class Splitter(LoggingInterface): self.split_size = 0 # Split on page breaks - self.log_info('\tSplitting on page breaks...') - if self.path in stylesheet_map: - self.find_page_breaks(stylesheet_map[self.path], root) - self.split_on_page_breaks(root.getroottree()) - trees = list(self.trees) + if not opts.dont_split_on_page_breaks: + self.log_info('\tSplitting on page breaks...') + if self.path in stylesheet_map: + self.find_page_breaks(stylesheet_map[self.path], root) + self.split_on_page_breaks(root.getroottree()) + trees = list(self.trees) + else: + self.trees = [root.getroottree()] + trees = list(self.trees) # Split any remaining over-sized trees if self.opts.profile.flow_size < sys.maxint: diff --git a/src/calibre/ebooks/lrf/comic/convert_from.py b/src/calibre/ebooks/lrf/comic/convert_from.py index 5bc4a9171b..21c2587d10 100755 --- a/src/calibre/ebooks/lrf/comic/convert_from.py +++ b/src/calibre/ebooks/lrf/comic/convert_from.py @@ -10,6 +10,14 @@ Based on ideas from comiclrf created by FangornUK. import os, sys, shutil, traceback, textwrap from uuid import uuid4 +try: + from reportlab.pdfgen import canvas + _reportlab = True +except: + _reportlab = False + + + from calibre import extract, terminal_controller, __appname__, __version__ from calibre.utils.config import Config, StringConfig from calibre.ptempfile import PersistentTemporaryDirectory @@ -43,7 +51,7 @@ PROFILES = { # Name : (width, height) in pixels 'prs500':(584, 754), # The SONY's LRF renderer (on the PRS500) only uses the first 800x600 block of the image - #'prs500-landscape': (784, 1200-92) + 'prs500-landscape': (784, 1012) } def extract_comic(path_to_comic_file): @@ -279,7 +287,7 @@ def process_pages(pages, opts, update): failures += failures_ return ans, failures, tdir -def config(defaults=None): +def config(defaults=None,output_format='lrf'): desc = _('Options to control the conversion of comics (CBR, CBZ) files into ebooks') if defaults is None: c = Config('comic', desc) @@ -316,10 +324,13 @@ def config(defaults=None): help=_('Be verbose, useful for debugging. Can be specified multiple times for greater verbosity.')) c.add_opt('no_progress_bar', ['--no-progress-bar'], default=False, help=_("Don't show progress bar.")) + if output_format == 'pdf': + c.add_opt('no_process',['--no_process'], default=False, + help=_("Apply no processing to the image")) return c -def option_parser(): - c = config() +def option_parser(output_format='lrf'): + c = config(output_format=output_format) return c.option_parser(usage=_('''\ %prog [options] comic.cb[z|r] @@ -382,6 +393,24 @@ def create_lrf(pages, profile, opts, thumbnail=None): book.renderLrf(open(opts.output, 'wb')) print _('Output written to'), opts.output + +def create_pdf(pages, profile, opts, thumbnail=None): + width, height = PROFILES[profile] + + if not _reportlab: + raise RuntimeError('Failed to load reportlab') + + pdf = canvas.Canvas(filename=opts.output, pagesize=(width,height+15)) + pdf.setAuthor(opts.author) + pdf.setTitle(opts.title) + + + for page in pages: + pdf.drawImage(page, x=0,y=0,width=width, height=height) + pdf.showPage() + # Write the document to disk + pdf.save() + def do_convert(path_to_file, opts, notification=lambda m, p: p, output_format='lrf'): path_to_file = run_plugins_on_preprocess(path_to_file) @@ -393,29 +422,33 @@ def do_convert(path_to_file, opts, notification=lambda m, p: p, output_format='l opts.output = os.path.abspath(os.path.splitext(os.path.basename(source))[0]+'.'+output_format) tdir = extract_comic(source) pages = find_pages(tdir, sort_on_mtime=opts.no_sort, verbose=opts.verbose) + thumbnail = None if not pages: raise ValueError('Could not find any pages in the comic: %s'%source) - pages, failures, tdir2 = process_pages(pages, opts, notification) - if not pages: - raise ValueError('Could not find any valid pages in the comic: %s'%source) - if failures: - print 'Could not process the following pages (run with --verbose to see why):' - for f in failures: - print '\t', f - thumbnail = os.path.join(tdir2, 'thumbnail.png') - if not os.access(thumbnail, os.R_OK): - thumbnail = None - + if not opts.no_process: + pages, failures, tdir2 = process_pages(pages, opts, notification) + if not pages: + raise ValueError('Could not find any valid pages in the comic: %s'%source) + if failures: + print 'Could not process the following pages (run with --verbose to see why):' + for f in failures: + print '\t', f + thumbnail = os.path.join(tdir2, 'thumbnail.png') + if not os.access(thumbnail, os.R_OK): + thumbnail = None if output_format == 'lrf': create_lrf(pages, opts.profile, opts, thumbnail=thumbnail) - else: + if output_format == 'epub': create_epub(pages, opts.profile, opts, thumbnail=thumbnail) + if output_format == 'pdf': + create_pdf(pages, opts.profile, opts, thumbnail=thumbnail) shutil.rmtree(tdir) - shutil.rmtree(tdir2) + if not opts.no_process: + shutil.rmtree(tdir2) def main(args=sys.argv, notification=None, output_format='lrf'): - parser = option_parser() + parser = option_parser(output_format=output_format) opts, args = parser.parse_args(args) if len(args) < 2: parser.print_help() @@ -429,7 +462,6 @@ def main(args=sys.argv, notification=None, output_format='lrf'): source = os.path.abspath(args[1]) do_convert(source, opts, notification, output_format=output_format) - return 0 if __name__ == '__main__': diff --git a/src/calibre/ebooks/metadata/zip.py b/src/calibre/ebooks/metadata/zip.py new file mode 100644 index 0000000000..441aa7e3da --- /dev/null +++ b/src/calibre/ebooks/metadata/zip.py @@ -0,0 +1,24 @@ +from __future__ import with_statement +__license__ = 'GPL v3' +__copyright__ = '2008, Kovid Goyal ' + +import os +from zipfile import ZipFile +from cStringIO import StringIO + + +def get_metadata(stream): + stream_type = None + zf = ZipFile(stream, 'r') + for f in zf.namelist(): + stream_type = os.path.splitext(f)[1].lower() + if stream_type: + stream_type = stream_type[1:] + if stream_type in ('lit', 'opf', 'prc', 'mobi', 'fb2', 'epub', + 'rb', 'imp', 'pdf', 'lrf'): + from calibre.ebooks.metadata.meta import get_metadata + stream = StringIO(zf.read(f)) + return get_metadata(stream, stream_type) + raise ValueError('No ebook found in ZIP archive') + + \ No newline at end of file diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py index d76b5f1574..e46f3a8a82 100644 --- a/src/calibre/ebooks/mobi/reader.py +++ b/src/calibre/ebooks/mobi/reader.py @@ -12,15 +12,17 @@ try: except ImportError: import Image as PILImage +from lxml import html, etree + from calibre import __appname__, entity_to_unicode from calibre.ebooks import DRMError -from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag +from calibre.ebooks.chardet import ENCODING_PATS from calibre.ebooks.mobi import MobiError from calibre.ebooks.mobi.huffcdic import HuffReader from calibre.ebooks.mobi.palmdoc import decompress_doc from calibre.ebooks.mobi.langcodes import main_language, sub_language from calibre.ebooks.metadata import MetaInformation -from calibre.ebooks.metadata.opf import OPFCreator +from calibre.ebooks.metadata.opf2 import OPFCreator from calibre.ebooks.metadata.toc import TOC from calibre import sanitize_file_name @@ -176,6 +178,8 @@ class MobiReader(object): processed_records = self.extract_text() self.add_anchors() self.processed_html = self.processed_html.decode(self.book_header.codec, 'ignore') + for pat in ENCODING_PATS: + self.processed_html = pat.sub('', self.processed_html) self.extract_images(processed_records, output_dir) self.replace_page_breaks() self.cleanup_html() @@ -185,7 +189,6 @@ class MobiReader(object): self.processed_html = \ re.compile('', re.IGNORECASE).sub( '\n\n' - '\n' '\n" +"

You can control how " +"calibre detects chapters using a XPath expression. To learn how to use XPath " +"expressions see the XPath " +"tutorial

" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:414 +msgid "Chapter &mark:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:415 +msgid "Automatic &Table of Contents" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:416 +msgid "Number of &links to add to Table of Contents" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:417 +msgid "Do not add &detected chapters to the Table of Contents" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:418 +msgid "Chapter &threshold" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:419 +msgid "&Force use of auto-generated Table of Contents" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:420 +msgid "Level &1 TOC" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:421 +msgid "Level &2 TOC" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:38 +msgid "Author Sort" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:40 +msgid "ISBN" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:104 +msgid "Cannot connect" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:105 +msgid "You must specify a valid access key for isbndb.com" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:139 +msgid "Error fetching metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:144 +msgid "No metadata found" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:144 +msgid "" +"No metadata found, try adjusting the title and author or the ISBN key." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:77 +msgid "Fetch metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:78 +msgid "Fetching metadata for %1" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:79 +msgid "" +"Sign up for a free account from ISBNdb.com to get an access key." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:80 +msgid "&Access Key:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:81 +msgid "Fetch" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:82 +msgid "Matches" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata_ui.py:83 +msgid "" +"Select the book that most closely matches your copy from the list below" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/job_view_ui.py:33 +msgid "Details of job" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/jobs.py:27 +msgid "Unavailable" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/jobs.py:38 +msgid " - Jobs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/jobs_ui.py:38 +msgid "Active Jobs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/jobs_ui.py:39 +msgid "&Stop selected job" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:98 +msgid "Choose the format to convert into LRF" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:106 +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 +msgid "Set conversion defaults" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:260 +msgid "" +"Preprocess the file before converting to LRF. This is useful if you know " +"that the file is from a specific source. Known sources:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:261 +msgid "
  1. baen - Books from BAEN Publishers
  2. " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:262 +msgid "" +"
  3. pdftohtml - HTML files that are the output of the program " +"pdftohtml
  4. " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:263 +msgid "
  5. book-designer - HTML0 files from Book Designer
  6. " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:296 +msgid "" +"Specify metadata such as title and author for the book.

    Metadata will be " +"updated in the database as well as the generated LRF file." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:297 +msgid "" +"Adjust the look of the generated LRF file by specifying things like font " +"sizes and the spacing between words." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:298 +msgid "" +"Specify the page settings like margins and the screen size of the target " +"device." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:308 +msgid "No help available" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:411 +msgid "Bulk convert ebooks to LRF" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:503 +msgid "Convert to LRF" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:505 +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 +msgid " pts" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:530 +msgid "Embedded Fonts" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:531 +msgid "&Serif:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:532 +msgid "S&ans-serif:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:533 +msgid "&Monospace:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:535 +msgid "Minimum &indent:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:537 +msgid "&Word spacing:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:539 +msgid "Enable auto &rotation of images" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:540 +msgid "Insert &blank lines between paragraphs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:541 +msgid "Ignore &tables" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:542 +msgid "Ignore &colors" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:543 +msgid "&Preprocess:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:544 +msgid "Header" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:545 +msgid "&Show header" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:546 +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/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: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:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:567 +msgid "Force page break before &attribute:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:568 +msgid "Detect chapter &at tag:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:569 +msgid "Help on item" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:570 +msgid "" +"\n" +"\n" +"

    " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:126 +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 +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 +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 +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 +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 +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 +msgid " stars" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:138 +msgid "Add Ta&gs: " +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 +msgid "Open Tag Editor" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:142 +msgid "&Remove tags:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:143 +msgid "Comma separated list of tags to remove from the books. " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:147 +msgid "Remove &format:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:148 +msgid "A&utomatically set author sort" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:262 +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 +msgid "Could not fetch cover.
    " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:292 +msgid "Could not fetch cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:298 +msgid "Cannot fetch cover" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:298 +msgid "You must specify the ISBN identifier for this book." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:277 +msgid "Edit Meta Information" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:281 +msgid "Swap the author and title" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:287 +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 +msgid "Remove unused series (Series that have no books)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:307 +msgid "IS&BN:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:309 +msgid "Fetch metadata from server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:310 +msgid "Available Formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:311 +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 +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 +msgid "Reset cover to default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:321 +msgid "Fetch cover image from server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:322 +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 +msgid "Change password" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:55 +msgid "Password needed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:39 +msgid "You" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:169 +#: /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 +msgid "%d recipes" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:263 +msgid "Must set account information" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:263 +msgid "This recipe requires a username and password" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:276 +msgid "Created by: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:296 +msgid "Last downloaded: %s days ago" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:298 +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_ui.py:136 +msgid "Schedule news download" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:328 +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/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 +msgid "News" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:222 +msgid "Recipes" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:138 +msgid "Schedule for download" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:139 +msgid "title" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:140 +msgid "description" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:141 +msgid "author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:142 +msgid "&Schedule for download every:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:143 +msgid "" +"Interval at which to download this recipe. A value of zero means that the " +"recipe will be downloaded every hour." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:144 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:229 +msgid " days" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:145 +msgid "&Account" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:149 +msgid "For the scheduling to work, you must leave calibre running." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:150 +msgid "&Download now" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:151 +msgid "" +"Delete downloaded news older than the specified number of days. Set to zero " +"to disable." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:153 +msgid "Delete downloaded news older than " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:35 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:96 +msgid "Form" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:36 +msgid "contains" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:37 +msgid "The text to search for. It is interpreted as a regular expression." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:38 +msgid "" +"

    Negate this match. That is, only return results that do not match " +"this query." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_item_ui.py:39 +msgid "Negate" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:87 +msgid "Advanced Search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:88 +msgid "Find entries that have..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:89 +msgid "&All these words:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:90 +msgid "This exact &phrase:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:91 +msgid "&One or more of these words:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:92 +msgid "But dont show entries that have..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:93 +msgid "Any of these &unwanted words:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:94 +msgid "" +"See the User Manual for more help" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:123 +msgid "Tag Editor" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:124 +msgid "A&vailable tags" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:125 +msgid "" +"Delete tag from database. This will unapply the tag from all books and then " +"remove it from the database." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:127 +msgid "Apply tag to current book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:129 +msgid "A&pplied tags" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:130 +msgid "Unapply (remove) tag from current book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:132 +msgid "&Add tag:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:133 +msgid "" +"If the tag you want is not in the available list, you can add it here. " +"Accepts a comma separated list of tags." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:134 +msgid "Add tag to available tags and apply it to current book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:63 +msgid "No recipe selected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:69 +msgid "The attached file: %s is a recipe to download %s." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:70 +msgid "Recipe for " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:86 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:221 +msgid "Switch to Advanced mode" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:92 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:100 +msgid "Switch to Basic mode" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:110 +msgid "Feed must have a title" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:111 +msgid "The feed must have a title" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:115 +msgid "Feed must have a URL" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:116 +msgid "The feed %s must have a URL" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:121 +msgid "Already exists" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:122 +msgid "This feed has already been added to the recipe" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:163 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:172 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:229 +msgid "Invalid input" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:173 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:230 +msgid "

    Could not create recipe. Error:
    %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:211 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:235 +msgid "Replace recipe?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:180 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:212 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:236 +msgid "A custom recipe named %s already exists. Do you want to replace it?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:202 +msgid "Pick recipe" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:202 +msgid "Pick the recipe to customize" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:222 +msgid "Choose a recipe file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:214 +msgid "Add custom news source" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:215 +msgid "Available user recipes" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:216 +msgid "Add/Update &recipe" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:217 +msgid "&Remove recipe" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:218 +msgid "&Share recipe" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:219 +msgid "Customize &builtin recipe" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:220 +msgid "&Load recipe from file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:222 +msgid "" +"\n" +"

    Create a basic news " +"recipe, by adding RSS feeds to it.
    For most feeds, you will have to " +"use the \"Advanced mode\" to further customize the fetch " +"process.

    " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:226 +msgid "Recipe &title:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:227 +msgid "&Oldest article:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:228 +msgid "The oldest article to download" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:230 +msgid "&Max. number of articles per feed:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:231 +msgid "Maximum number of articles to download per feed." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:232 +msgid "Feeds in recipe" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:234 +msgid "Remove feed from recipe" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:237 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:240 +msgid "Add feed to recipe" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:238 +msgid "&Feed title:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:239 +msgid "Feed &URL:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:241 +msgid "&Add feed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:242 +msgid "" +"For help with writing advanced news recipes, please visit User Recipes" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:243 +msgid "Recipe source code (python)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:97 +msgid "" +"\n" +"\n" +"

    Set a regular expression " +"pattern to use when trying to guess ebook metadata from filenames.

    \n" +"

    A reference on the syntax " +"of regular expressions is available.

    \n" +"

    Use the Test functionality below to test your regular " +"expression on a few sample filenames. The group names for the various " +"metadata entries are documented in tooltips.

    " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:104 +msgid "Regular &expression" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:105 +msgid "&Test" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:106 +msgid "File &name:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:107 +msgid "Test" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:108 +msgid "Title:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:109 +msgid "Regular expression (?P<title>)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:110 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:113 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:116 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:119 +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:122 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:54 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:63 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:70 +msgid "No match" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:111 +msgid "Authors:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:112 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:114 +msgid "Series:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:115 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:117 +msgid "Series index:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:118 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:120 +msgid "ISBN:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:121 +msgid "Regular expression (?P)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs2.py:46 +msgid "Job" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs2.py:47 +msgid "Status" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs2.py:48 +msgid "Progress" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs2.py:49 +msgid "Running time" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs2.py:65 +msgid "Unknown job" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs2.py:70 +msgid "Finished" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs2.py:72 +msgid "Error" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs2.py:74 +msgid "Waiting" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs2.py:76 +msgid "Working" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs2.py:172 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs2.py:176 +msgid "Cannot kill job" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs2.py:173 +msgid "Cannot kill jobs that communicate with the device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/jobs2.py:177 +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 +msgid "Size (MB)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:908 +msgid "Date" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:96 +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 +msgid "None" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:292 +msgid "Book %s of %s." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:675 +msgid "Not allowed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:676 +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 +msgid "Format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:844 +msgid "Timestamp" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:942 +msgid "Search (For Advanced Search click the button to the left)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/config_ui.py:47 +msgid "Configure Viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/config_ui.py:48 +msgid "Use white background" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/config_ui.py:49 +msgid "Hyphenate" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/config_ui.py:50 +msgid "Changes will only take effect after a restart." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:64 +msgid " - LRF Viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main.py:157 +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 +msgid "No matches found" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:128 +msgid "LRF Viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:129 +msgid "Parsing LRF file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:130 +msgid "LRF Viewer toolbar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:131 +msgid "Next Page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:132 +msgid "Previous Page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:154 +msgid "Back" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:155 +msgid "Forward" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:135 +msgid "Next match" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:136 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:162 +msgid "Open ebook" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/lrf_renderer/main_ui.py:137 +msgid "Configure" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:88 +msgid "Error communicating with device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:100 +msgid "&Restore" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:101 +msgid "&Donate" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:102 +msgid "&Quit" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:104 +msgid "&Restart" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:148 +msgid "" +"

    For help visit %s.kovidgoyal.net
    " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:149 +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" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:169 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:170 +msgid "and delete from library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:172 +msgid "Send to storage card by default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:185 +msgid "Edit metadata individually" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:187 +msgid "Edit metadata in bulk" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:190 +msgid "Add books from a single directory" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:191 +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 +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_ui.py:342 +msgid "Save to disk" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:208 +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 +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_ui.py:348 +msgid "View" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:213 +msgid "View specific format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:230 +msgid "Convert individually" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:231 +msgid "Bulk convert" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:233 +msgid "Set defaults for conversion" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:234 +msgid "Set defaults for conversion of comics" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:255 +msgid "Similar books..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:301 +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 +msgid "Choose a location for your ebook library." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:315 +msgid "Migrating database" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:487 +msgid "Device: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:488 +msgid " detected." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:510 +msgid "Connected " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:521 +msgid "Device database corrupted" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:522 +msgid "" +"\n" +"

    The database of books on the reader is corrupted. Try the " +"following:\n" +"

      \n" +"
    1. Unplug the reader. Wait for it to finish regenerating " +"the database (i.e. wait till it is ready to be used). Plug it back in. Now " +"it should work with %(app)s. If not try the next step.
    2. \n" +"
    3. Quit %(app)s. Find the file media.xml in the reader's " +"main memory. Delete it. Unplug the reader. Wait for it to regenerate the " +"file. Re-connect it and start %(app)s.
    4. \n" +"
    \n" +" " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:571 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:658 +msgid "Stop" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:574 +msgid "Adding books recursively..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:578 +msgid "Added " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:578 +msgid "Searching..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:589 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:695 +msgid "" +"

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

      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:592 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:698 +msgid "Duplicates found!" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:625 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:649 +msgid "Uploading books to device." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:633 +msgid "Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:634 +msgid "EPUB Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:635 +msgid "LRF Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:636 +msgid "HTML Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:637 +msgid "LIT Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:638 +msgid "MOBI Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:639 +msgid "Text books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:640 +msgid "PDF Books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:641 +msgid "Comics" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:642 +msgid "Archives" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:658 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:661 +msgid "Reading metadata..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:659 +msgid "Adding books..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:685 +msgid "Read metadata from " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:688 +msgid "Adding books to database..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:730 +msgid "No space on device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:731 +msgid "" +"

      Cannot upload books to device there is no more free space available " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:763 +msgid "" +"The selected books will be permanently deleted and the files removed " +"from your computer. Are you sure?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:772 +msgid "Deleting books from device." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:802 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:824 +msgid "Cannot edit metadata" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:802 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:824 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:943 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1002 +msgid "No books selected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:871 +msgid "Sending news to device." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:923 +msgid "Sending books to device." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:926 +msgid "No suitable formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:927 +msgid "" +"Could not upload the following books to the device, as no suitable formats " +"were found:

        %s
      " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:943 +msgid "Cannot save to disk" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:947 +msgid "Choose destination directory" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:954 +msgid "" +"

      Could not save the following books to disk, because the %s format is not " +"available for them:

        " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:958 +msgid "Could not save some ebooks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:978 +msgid "Fetching news from " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:992 +msgid " fetched." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1106 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1124 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1136 +msgid "No book selected" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1106 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1136 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1152 +msgid "Cannot view" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1112 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1157 +msgid "Choose the format to view" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1124 +msgid "Cannot open folder" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1153 +msgid "%s has no available formats." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1191 +msgid "Cannot configure" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1191 +msgid "Cannot configure while there are running jobs." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1210 +msgid "Copying database" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1212 +msgid "Copying library to " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1222 +msgid "Invalid database" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1223 +msgid "" +"

        An invalid database already exists at %s, delete it before trying to move " +"the existing database.
        Error: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1229 +msgid "Could not move database" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1249 +msgid "No detailed info available" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1250 +msgid "No detailed information is available for books on the device." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1293 +msgid "Error talking to device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1294 +msgid "" +"There was a temporary error talking to the device. Please unplug and " +"reconnect the device and or reboot." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1307 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1322 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1326 +msgid "Conversion Error" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1308 +msgid "" +"

        Could not convert: %s

        It is a DRMed book. You must " +"first remove the DRM using 3rd party tools." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1342 +msgid "Database does not exist" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1343 +msgid "" +"The directory in which the database should be: %s no longer exists. Please " +"choose a new database location." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1345 +msgid "Choose new location for database" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1398 +msgid "" +"is the result of the efforts of many volunteers from all over the world. If " +"you find it useful, please consider donating to support its development." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1419 +msgid "There are active jobs. Are you sure you want to quit?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1421 +msgid "" +" is communicating with the device!
        \n" +" 'Quitting may cause corruption on the device.
        \n" +" 'Are you sure you want to quit?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1425 +msgid "WARNING: Active jobs" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1454 +msgid "" +"will keep running in the system tray. To close it, choose Quit in the " +"context menu of the system tray." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1467 +msgid "" +"Latest version: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1472 +msgid "" +"%s has been updated to version %s. See the new features. " +"Visit the download page?" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1472 +msgid "Update available" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1487 +msgid "Use the library located at the specified path." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1489 +msgid "Log debugging information to console" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:319 +msgid "calibre" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:320 +msgid "Output:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:321 +msgid "Advanced search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:323 +msgid "Alt+S" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:324 +msgid "&Search:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:325 +msgid "" +"Search the list of books by title or author

        Words separated by spaces " +"are ANDed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:326 +msgid "" +"Search the list of books by title, author, publisher, tags and " +"comments

        Words separated by spaces are ANDed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:327 +msgid "Reset Quick Search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:331 +msgid "Match any" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:332 +msgid "Match all" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:333 +msgid "Sort by &popularity" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:334 +msgid "Add books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:335 +msgid "A" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:336 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:337 +msgid "Remove books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:338 +msgid "Del" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:339 +msgid "Edit meta information" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:340 +msgid "E" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:341 +msgid "Send to device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:343 +msgid "S" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:344 +msgid "Fetch news" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:345 +msgid "F" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:346 +msgid "Convert E-books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:347 +msgid "C" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:349 +msgid "V" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:350 +msgid "Open containing folder" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:351 +msgid "Show book details" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:352 +msgid "Books by same author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:353 +msgid "Books in this series" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:354 +msgid "Books by this publisher" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:355 +msgid "Books with the same tags" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:17 +msgid "" +"Redirect console output to a dialog window (both stdout and stderr). Useful " +"on windows where GUI apps do not have a output streams." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/main_window.py:58 +msgid "ERROR: Unhandled exception" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/status.py:115 +msgid "Jobs:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/status.py:124 +msgid "Click to see list of active jobs." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/status.py:153 +msgid "Click to browse books by their covers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/status.py:153 +msgid "Click to turn off Cover Browsing" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/status.py:158 +msgid "" +"

        Browsing books by their covers is disabled.
        Import of pictureflow " +"module failed:
        " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/status.py:166 +msgid "Click to browse books by tags" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tags.py:42 +msgid "Authors" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tags.py:42 +msgid "Publishers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:46 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:106 +msgid "Convert book: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:78 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:140 +msgid "Convert comic: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:162 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:255 +msgid "Starting Bulk conversion of %d books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:198 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:226 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:294 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:322 +msgid "Convert book %d of %d (%s)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:235 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:331 +msgid "" +"

        Could not convert %d of %d books, because no suitable source format was " +"found.

          %s
        " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:236 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:332 +msgid "Could not convert some books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:360 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:375 +msgid "Fetch news from " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:372 +msgid "You must set a username and password for %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:101 +msgid "Configure Ebook viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:102 +msgid "&Font options" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:103 +msgid "Se&rif family:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:104 +msgid "&Sans family:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:105 +msgid "&Monospace family:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:106 +msgid "&Default font size:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:108 +msgid "Monospace &font size:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:110 +msgid "S&tandard font:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:111 +msgid "Serif" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:112 +msgid "Sans-serif" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:113 +msgid "Monospace" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:114 +msgid "&User stylesheet" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:49 +msgid "Options to customize the ebook viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:82 +msgid "" +"Set the user CSS stylesheet. This can be used to customize the look of all " +"books." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:58 +msgid "Font options" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:59 +msgid "The serif font family" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:60 +msgid "The sans-serif font family" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:61 +msgid "The monospaced font family" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:62 +msgid "The standard font size in px" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:63 +msgid "The monospaced font size in px" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/documentview.py:64 +msgid "The standard font type" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:47 +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:160 +msgid "Table of Contents" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:166 +msgid "Go to..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:207 +msgid "Position in book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:208 +msgid "/Unknown" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:213 +msgid "Go to a reference. To get reference numbers, use the reference mode." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:219 +msgid "Search for text in book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:338 +msgid "Choose ebook" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:339 +msgid "Ebooks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:357 +msgid "Add bookmark" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:357 +msgid "Enter title for bookmark:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:378 +msgid "No matches found for: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:418 +msgid "Loading flow..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:445 +msgid "Laying out %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:497 +msgid "Loading ebook..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:505 +msgid "

        This book is protected by DRM" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:505 +msgid "DRM Error" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:507 +msgid "Could not open ebook" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:508 +msgid "%s

        %s

        " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:568 +msgid "Options to control the ebook viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:577 +msgid "" +"%prog [options] file\n" +"\n" +"View an ebook. \n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:152 +msgid "Ebook Viewer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:153 +msgid "toolBar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:156 +msgid "Next page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:157 +msgid "Previous page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:158 +msgid "Font size larger" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:159 +msgid "Font size smaller" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:163 +msgid "Find next" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:164 +msgid "Copy to clipboard" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:165 +msgid "Preferences" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:166 +msgid "Reference Mode" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:167 +msgid "Bookmark" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:168 +msgid "Toggle full screen" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:47 +msgid "Invalid regular expression" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:48 +msgid "Invalid regular expression: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:139 +msgid "" +"Library\n" +"%d\n" +"books" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:140 +msgid "" +"Reader\n" +"%s\n" +"available" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:141 +msgid "" +"Card\n" +"%s\n" +"available" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:146 +msgid "Click to see the list of books available on your computer" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:147 +msgid "Click to see the list of books in the main memory of your reader" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:148 +msgid "Click to see the list of books on the storage card in your reader" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/__init__.py:16 +msgid "Settings to control the calibre content server" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/__init__.py:20 +msgid "The port on which to listen. Default is %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/__init__.py:22 +msgid "The server timeout in seconds. Default is %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/__init__.py:24 +msgid "The max number of worker threads to use. Default is %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/__init__.py:26 +msgid "Set a password to restrict access. By default access is unrestricted." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/__init__.py:28 +msgid "Username for access. By default, it is: %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/__init__.py:32 +msgid "The maximum size for displayed covers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:95 +msgid "" +"Path to the calibre library. Default is to use the path stored in the " +"settings." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:172 +msgid "" +"%prog list [options]\n" +"\n" +"List the books available in the calibre database.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:180 +msgid "" +"The fields to display when listing books in the database. Should be a comma " +"separated list of fields.\n" +"Available fields: %s\n" +"Default: %%default. The special field \"all\" can be used to select all " +"fields. Only has effect in the text output format." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:182 +msgid "" +"The field by which to sort the results.\n" +"Available fields: %s\n" +"Default: %%default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:184 +msgid "Sort results in ascending order" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:186 +msgid "" +"Filter the results by the search query. For the format of the search query, " +"please see the search related documentation in the User Manual. Default is " +"to do no filtering." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:188 +msgid "" +"The maximum width of a single line in the output. Defaults to detecting " +"screen size." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:189 +msgid "The string used to separate fields. Default is a space." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:190 +msgid "" +"The prefix for all file paths. Default is the absolute path to the library " +"folder." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:193 +msgid "" +"The format in which to output the data. Available choices: %s. Defaults is " +"text." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:201 +msgid "Invalid fields. Available fields:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:208 +msgid "Invalid sort field. Available fields:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:274 +msgid "" +"The following books were not added as they already exist in the database " +"(see --duplicates option):" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:298 +msgid "" +"%prog add [options] file1 file2 file3 ...\n" +"\n" +"Add the specified files as books to the database. You can also specify " +"directories, see\n" +"the directory related options below.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:307 +msgid "" +"Assume that each directory has only a single logical book and that all files " +"in it are different e-book formats of that book" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:309 +msgid "Process directories recursively" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:311 +msgid "" +"Add books to database even if they already exist. Comparison is done based " +"on book titles." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:316 +msgid "You must specify at least one file to add" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:334 +msgid "" +"%prog remove ids\n" +"\n" +"Remove the books identified by ids from the database. ids should be a comma " +"separated list of id numbers (you can get id numbers by using the list " +"command). For example, 23,34,57-85\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:346 +msgid "You must specify at least one book to remove" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:366 +msgid "" +"%prog add_format [options] id ebook_file\n" +"\n" +"Add the ebook in ebook_file to the available formats for the logical book " +"identified by id. You can get id by using the list command. If the format " +"already exists, it is replaced.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:377 +msgid "You must specify an id and an ebook file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:382 +msgid "ebook file must have an extension" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:390 +msgid "" +"\n" +"%prog remove_format [options] id fmt\n" +"\n" +"Remove the format fmt from the logical book identified by id. You can get id " +"by using the list command. fmt should be a file extension like LRF or TXT or " +"EPUB. If the logical book does not have fmt available, do nothing.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:403 +msgid "You must specify an id and a format" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:421 +msgid "" +"\n" +"%prog show_metadata [options] id\n" +"\n" +"Show the metadata stored in the calibre database for the book identified by " +"id.\n" +"id is an id number from the list command.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:429 +msgid "Print metadata in OPF form (XML)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:434 +msgid "You must specify an id" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:448 +msgid "" +"\n" +"%prog set_metadata [options] id /path/to/metadata.opf\n" +"\n" +"Set the metadata stored in the calibre database for the book identified by " +"id\n" +"from the OPF file metadata.opf. id is an id number from the list command. " +"You\n" +"can get a quick feel for the OPF format by using the --as-opf switch to the\n" +"show_metadata command.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:461 +msgid "You must specify an id and a metadata file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:473 +msgid "" +"%prog export [options] ids\n" +"\n" +"Export the books specified by ids (a comma separated list) to the " +"filesystem.\n" +"The export operation saves all formats of the book, its cover and metadata " +"(in\n" +"an opf file). You can get id numbers from the list command.\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:481 +msgid "Export all books in database, ignoring the list of ids." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:483 +msgid "Export books to the specified directory. Default is" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:485 +msgid "Export all books into a single directory" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:487 +msgid "Create file names as author - title instead of title - author" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:492 +msgid "You must specify some ids or the %s option" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/cli.py:502 +msgid "" +"%%prog command [options] [arguments]\n" +"\n" +"%%prog is the command line interface to the calibre books database.\n" +"\n" +"command is one of:\n" +" %s\n" +"\n" +"For help on an individual command: %%prog command --help\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1146 +msgid "

        Copying books to %s

        " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1159 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1268 +msgid "Copying %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1239 +msgid "

        Migrating old database to ebook library in %s

        " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1285 +msgid "Compacting database" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server.py:139 +msgid "Password to access your calibre library. Username is " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/library/server.py:382 +msgid "" +"[options]\n" +"\n" +"Start the calibre content server." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/parallel.py:367 +msgid "Could not launch worker process." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/parallel.py:791 +msgid "Job stopped by user" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:41 +msgid "%sUsage%s: %s\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:79 +msgid "Created by " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:527 +msgid "Path to the database in which books are stored" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:529 +msgid "Pattern to guess metadata from filenames" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:531 +msgid "Access key for isbndb.com" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:533 +msgid "Default timeout for network operations (seconds)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:535 +msgid "Path to directory in which your library of books is stored" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:537 +msgid "The language in which to display the user interface" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:539 +msgid "The default output format for ebook conversions." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:541 +msgid "Read metadata from files" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/config.py:543 +msgid "The priority of worker processes" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/fontconfig.py:172 +msgid "Could not initialize the fontconfig library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:53 +msgid "URL must have the scheme sftp" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:57 +msgid "host must be of the form user@hostname" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:68 +msgid "Failed to negotiate SSH session: " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/utils/sftp.py:71 +msgid "Failed to authenticate with server: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:76 +#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:97 +msgid "Unknown feed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:115 +#: /home/kovid/work/calibre/src/calibre/web/feeds/__init__.py:137 +msgid "Untitled article" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:15 +msgid "Options to control the fetching of periodical content from the web." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:18 +msgid "Customize the download engine" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:20 +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:442 +msgid "" +"Timeout in seconds to wait for a response from the server. Default: %default " +"s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:22 +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:450 +msgid "" +"Minimum interval in seconds between consecutive fetches. Default is %default " +"s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:24 +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:452 +msgid "" +"The character encoding for the websites you are trying to download. The " +"default is to try and guess the encoding." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:26 +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:454 +msgid "" +"Only links that match this regular expression will be followed. This option " +"can be specified multiple times, in which case as long as a link matches any " +"one regexp, it will be followed. By default all links are followed." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:28 +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:456 +msgid "" +"Any link that matches this regular expression will be ignored. This option " +"can be specified multiple times, in which case as long as any regexp matches " +"a link, it will be ignored.By default, no links are ignored. If both --" +"filter-regexp and --match-regexp are specified, then --filter-regexp is " +"applied first." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:30 +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:458 +msgid "Do not download CSS stylesheets." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:33 +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:88 +msgid "" +"Specify a list of feeds to download. For example: \n" +"\"['http://feeds.newsweek.com/newsweek/TopNews', " +"'http://feeds.newsweek.com/headlines/politics']\"\n" +"If you specify this option, any argument to %prog is ignored and a default " +"recipe is used to download the feeds." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:37 +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:92 +msgid "Be more verbose while processing." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:39 +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:94 +msgid "" +"The title for this recipe. Used as the title for any ebooks created from the " +"downloaded feeds." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:41 +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:95 +msgid "Username for sites that require a login to access content." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:43 +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:96 +msgid "Password for sites that require a login to access content." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:49 +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:99 +msgid "" +"Number of levels of links to follow on webpages that are linked to from " +"feeds. Defaul %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:51 +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:101 +msgid "" +"The directory in which to store the downloaded feeds. Defaults to the " +"current directory." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:53 +msgid "Don't show the progress bar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:55 +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:105 +msgid "Very verbose output, useful for debugging." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:57 +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:107 +msgid "" +"Useful for recipe development. Forces max_articles_per_feed to 2 and " +"downloads at most 2 feeds." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:61 +msgid "" +"%%prog [options] ARG\n" +"\n" +"%%prog parses an online source of articles, like an RSS or ATOM feed and \n" +"fetches the article contents organized in a nice hierarchy.\n" +"\n" +"ARG can be one of:\n" +"\n" +"file name - %%prog will try to load a recipe from the file\n" +"\n" +"builtin recipe title - %%prog will load the builtin recipe and use it to " +"fetch the feed. For e.g. Newsweek or \"The BBC\" or \"The New York Times\"\n" +"\n" +"recipe as a string - %%prog will load the recipe directly from the string " +"arg.\n" +"\n" +"Available builtin recipes are:\n" +"%s\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:85 +msgid "" +"Options to control web2disk (used to fetch websites linked from feeds)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:103 +msgid "Dont show the progress bar" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/main.py:118 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:675 +msgid "Fetching feeds..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:41 +msgid "Unknown News Source" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:556 +msgid "Download finished" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:558 +msgid "Failed to download the following articles:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:560 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:566 +msgid " from " +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:564 +msgid "Failed to download parts of the following articles:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:568 +msgid "\tFailed links:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:654 +msgid "Could not fetch article. Run with --debug to see the reason" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:679 +msgid "Got feeds from index page" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:685 +msgid "Trying to download cover..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:738 +msgid "Starting download [%d thread(s)]..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:754 +msgid "Feeds downloaded to %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:764 +msgid "Could not download cover: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:769 +msgid "Downloading cover from %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:898 +msgid "Untitled Article" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:953 +msgid "" +"\n" +"Downloaded article %s from %s\n" +"%s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:959 +msgid "Article downloaded: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:965 +msgid "Failed to download article: %s from %s\n" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:970 +msgid "Article download failed: %s" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:985 +msgid "Fetching feed" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:436 +msgid "" +"%prog URL\n" +"\n" +"Where URL is for example http://google.com" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:439 +msgid "Base directory into which URL is saved. Default is %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:445 +msgid "" +"Maximum number of levels to recurse i.e. depth of links to follow. Default " +"%default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:448 +msgid "" +"The maximum number of files to download. This only applies to files from tags. Default is %default" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/web/fetch/simple.py:459 +msgid "Show detailed output information. Useful for debugging" +msgstr "" diff --git a/src/calibre/translations/ru.po b/src/calibre/translations/ru.po index fbe94e2969..9d5bd1cd7a 100644 --- a/src/calibre/translations/ru.po +++ b/src/calibre/translations/ru.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: calibre 0.4.55\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2008-12-30 15:33+0000\n" -"PO-Revision-Date: 2009-01-03 17:58+0000\n" +"PO-Revision-Date: 2009-01-07 18:26+0000\n" "Last-Translator: Kovid Goyal \n" "Language-Team: American English \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-04 04:33+0000\n" +"X-Launchpad-Export-Date: 2009-01-07 18:40+0000\n" "X-Generator: Launchpad (build Unknown)\n" "X-Poedit-Country: RUSSIAN FEDERATION\n" "X-Poedit-Language: Russian\n" @@ -444,6 +444,8 @@ msgid "" "takes from\n" "the element of the OPF file. \n" msgstr "" +"%prog [options] file.html|opf\n" +"\n" "Преобразование файла HTML в EPUB книгу. Рекурсивно отслеживает линки в файле " "HTML.\n" "Если вы задаете файл OPF вместо HTML, список ссылок содержится в элементе " @@ -1103,7 +1105,7 @@ msgid "" msgstr "" "Выбрать профайл устройства для которого вы преобразуете файл. По умолчанию " "SONY PRS-500 имеет размер экрана 584x754 пикселей. Это соответствует многим " -"ридерам с таким же разрешением экрана." +"ридерам с таким же разрешением экрана. Choices are %s" #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:316 msgid "" diff --git a/src/calibre/translations/sk.po b/src/calibre/translations/sk.po index 6f9f006e35..296aa7817c 100644 --- a/src/calibre/translations/sk.po +++ b/src/calibre/translations/sk.po @@ -8,13 +8,13 @@ msgstr "" "Project-Id-Version: calibre\n" "Report-Msgid-Bugs-To: FULL NAME \n" "POT-Creation-Date: 2008-12-30 15:33+0000\n" -"PO-Revision-Date: 2008-12-15 22:58+0000\n" -"Last-Translator: Michael Gallo \n" +"PO-Revision-Date: 2009-01-07 18:36+0000\n" +"Last-Translator: Kovid Goyal \n" "Language-Team: Slovak \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-04 04:32+0000\n" +"X-Launchpad-Export-Date: 2009-01-07 18:40+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 @@ -441,7 +441,7 @@ msgid "" "takes from\n" "the element of the OPF file. \n" msgstr "" -"%%prog [možnosti] súbor.html|opf\n" +"%prog [možnosti] súbor.html|opf\n" "\n" "Konverzia HTML súboru na elektronickú knihu vo formáte EPUB. Rekurzívne " "sleduje odkazy v HTML súbore.\n" @@ -1263,7 +1263,7 @@ msgid "" "You have to save the website %s as an html file first and then run html2lrf " "on it." msgstr "" -"Webovú stránku je potrebné najprv uložiť ako HTML súbor, potom previesť " +"Webovú %s stránku je potrebné najprv uložiť ako HTML súbor, potom previesť " "programom html2lrf." #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1865 diff --git a/src/calibre/translations/sl.po b/src/calibre/translations/sl.po index 9cd2491f30..11db3bd61f 100644 --- a/src/calibre/translations/sl.po +++ b/src/calibre/translations/sl.po @@ -7,13 +7,13 @@ msgstr "" "Project-Id-Version: calibre 0.4.17\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2008-12-30 15:33+0000\n" -"PO-Revision-Date: 2008-12-30 07:52+0000\n" -"Last-Translator: Janko Slatenšek \n" +"PO-Revision-Date: 2009-01-07 18:38+0000\n" +"Last-Translator: Kovid Goyal \n" "Language-Team: sl\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-04 04:32+0000\n" +"X-Launchpad-Export-Date: 2009-01-07 18:40+0000\n" "X-Generator: Launchpad (build Unknown)\n" "Generated-By: pygettext.py 1.5\n" @@ -3865,11 +3865,11 @@ msgstr "" "
      • Izklopite reader iz računalnika. Počakajte da konča s " "ponovnim generiranjem baze (npr. počakajte dokler ni pripravljen za " "uporabo). Vklopite reader nazaj v računalnik. Sedaj bi moral delovati z " -"%(app). Če ne deluje poskusite naslednji korak.
      • \n" -"
      • Končajte %(app). Poiščite datoteko media.xml v glavnem " +"%(app)s. Če ne deluje poskusite naslednji korak.
      • \n" +"
      • Končajte %(app)s. Poiščite datoteko media.xml v glavnem " "spominu reader-ja. Izbrišite jo in izklopite reader iz računalnika. " "Počakajte da datoteko ponovno ustvari. Ponovno priklopite reader in zaženite " -"%(app).
      • \n" +"%(app)s.\n" "
\n" " " diff --git a/src/calibre/translations/sv.po b/src/calibre/translations/sv.po index c94aa8e5f7..b26a1df140 100644 --- a/src/calibre/translations/sv.po +++ b/src/calibre/translations/sv.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-01-04 04:32+0000\n" +"X-Launchpad-Export-Date: 2009-01-07 18:40+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 diff --git a/src/calibre/translations/te.po b/src/calibre/translations/te.po index 6f52206fc6..1d08d8f75e 100644 --- a/src/calibre/translations/te.po +++ b/src/calibre/translations/te.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-01-04 04:32+0000\n" +"X-Launchpad-Export-Date: 2009-01-07 18:40+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 diff --git a/src/calibre/web/feeds/recipes/recipe_joelonsoftware.py b/src/calibre/web/feeds/recipes/recipe_joelonsoftware.py index 7c061562f5..136dddf367 100644 --- a/src/calibre/web/feeds/recipes/recipe_joelonsoftware.py +++ b/src/calibre/web/feeds/recipes/recipe_joelonsoftware.py @@ -5,6 +5,7 @@ __copyright__ = '2008, Darko Miletic ' ''' joelonsoftware.com ''' +from calibre.web.feeds.news import BasicNewsRecipe class Joelonsoftware(BasicNewsRecipe):