diff --git a/installer/linux/freeze.py b/installer/linux/freeze.py index c381041675..a6151c4931 100644 --- a/installer/linux/freeze.py +++ b/installer/linux/freeze.py @@ -81,7 +81,8 @@ def freeze(): 'PyQt4.QtScript.so', 'PyQt4.QtSql.so', 'PyQt4.QtTest.so', 'qt', 'glib', 'gobject'] - packages = ['calibre', 'encodings', 'cherrypy', 'cssutils', 'xdg'] + packages = ['calibre', 'encodings', 'cherrypy', 'cssutils', 'xdg', + 'dateutil'] includes += ['calibre.web.feeds.recipes.'+r for r in recipe_modules] diff --git a/installer/osx/freeze.py b/installer/osx/freeze.py index 3ec24d3aba..dbaad72748 100644 --- a/installer/osx/freeze.py +++ b/installer/osx/freeze.py @@ -342,6 +342,7 @@ def main(): 'calibre.ebooks.lrf.any.*', 'calibre.ebooks.lrf.feeds.*', 'keyword', 'codeop', 'pydoc', 'readline', 'BeautifulSoup', 'calibre.ebooks.lrf.fonts.prs500.*', + 'dateutil', ], 'packages' : ['PIL', 'Authorization', 'lxml'], 'excludes' : ['IPython'], diff --git a/installer/windows/calibre/calibre.mpi b/installer/windows/calibre/calibre.mpi index 2b3cca3cae..ee7ab53455 100644 --- a/installer/windows/calibre/calibre.mpi +++ b/installer/windows/calibre/calibre.mpi @@ -299,7 +299,6 @@ File ::2BCD9281-2CBC-CF0D-0E12-2CE11F6ED758 -name comic2epub.exe.local -parent 8 File ::EDE6F457-C83F-C5FA-9AF4-38FDFF17D929 -name PIL._imagingtk.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::09D0906E-3611-3DB7-32CF-A140585694A7 -name win32pdh.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::4C84F0DC-7157-0C90-2062-180139B03E25 -name IM_MOD_RL_rgb_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 -File ::F402F507-87C5-BDB1-80AE-AD3FF4A4BCE7 -name bzrlib._patiencediff_c.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::A732EDE7-4796-241F-BECA-68E59F88F8AF -name lrs2lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::69072379-7D16-B9F7-9F39-3E6403C48267 -name IM_MOD_RL_xbm_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::FBD11D98-D1E7-5DD9-BF02-01CE92518859 -name IM_MOD_RL_otb_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 @@ -365,7 +364,6 @@ File ::26741B21-C241-E100-8BB1-8B679BC3E662 -name configure.xml -parent 8E5D85A4 File ::7D491E89-C6D3-1E6E-F4BD-8E55260FE33E -name libexpat.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::A4910EB3-0F1C-F6F0-CD2D-16A64BBAA92B -name calibre-fontconfig.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::8711327A-716D-B162-6AC6-2FB4AD071266 -name fb22lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 -File ::0FDD3A7A-31F3-8089-CE32-D80EAA6F62B2 -name bzrlib._btree_serializer_c.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::476CB977-5155-D56F-26CA-EB243AEBBA99 -name unrar.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::2DA1CC8D-AF5C-3B03-2060-301DFE0356CC -name mobi2oeb.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::2E2A9EDA-5386-444E-8479-557386794552 -name IM_MOD_RL_uil_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 @@ -487,7 +485,6 @@ File ::AA761ACD-B728-2324-AA75-B20A2A79F125 -name lrf2lrs.exe -parent 8E5D85A4-7 File ::95434C76-22F5-B9CE-6194-6E1B1EE3232D -name IM_MOD_RL_info_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::AAF45D03-322F-5553-63A7-312DB754A20B -name _ctypes.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::C3D351CA-A8D8-AB35-55D9-5AACF8DB37D1 -name python26.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 -File ::2F90B52F-A728-2CA4-5688-0283674695B7 -name _elementtree.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::B50B66A1-FB65-FAD5-1DD7-E894ACC07464 -name QtSvg4.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::906FF13D-D993-7192-7EA5-6D15A5A24BFB -name CORE_RL_png_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::5D368661-6BF0-D6AF-7C1A-87646864EB4B -name delegates.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 @@ -552,7 +549,7 @@ SetupType ::D9ADE41C-B744-690C-2CED-CF826BF03D2E -setup Install -active Yes -pla InstallComponent 3EA07B17-04D8-6508-B535-96CC7173B49A -setup Install -type pane -conditions D7F585DB-0DEC-A94E-DAB0-94D558D82764 -title {Welcome Screen} -component Welcome -command insert -active Yes -parent StandardInstall Condition D7F585DB-0DEC-A94E-DAB0-94D558D82764 -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A -title {Execute Script Condition} -component ExecuteScriptCondition -TreeObject::id D7F585DB-0DEC-A94E-DAB0-94D558D82764 -InstallComponent 7CCDA4BB-861C-C21E-3011-E93DB58F07D6 -setup Install -type action -conditions ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E -title {Check for Previous Install} -component CheckForPreviousInstall -command reorder -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A +InstallComponent 7CCDA4BB-861C-C21E-3011-E93DB58F07D6 -setup Install -type action -conditions ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E -title {Check for Previous Install} -component CheckForPreviousInstall -command insert -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A Condition ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E -active Yes -parent 7CCDA4BB-861C-C21E-3011-E93DB58F07D6 -title {Execute Script Condition} -component ExecuteScriptCondition -TreeObject::id ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E InstallComponent 580ACF2C-517F-5E48-9DEF-7DAEFBA59FDD -setup Install -type action -conditions 6DE3B369-9D6B-6BC1-4EA0-2C54ECE159EB -title {Set Virtual Text} -component SetVirtualText -command insert -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A Condition 6DE3B369-9D6B-6BC1-4EA0-2C54ECE159EB -active Yes -parent 580ACF2C-517F-5E48-9DEF-7DAEFBA59FDD -title {String Is Condition} -component StringIsCondition -TreeObject::id 6DE3B369-9D6B-6BC1-4EA0-2C54ECE159EB diff --git a/installer/windows/freeze.py b/installer/windows/freeze.py index 064615f422..56486f6bd5 100644 --- a/installer/windows/freeze.py +++ b/installer/windows/freeze.py @@ -12,7 +12,7 @@ LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll' PDFTOHTML = 'C:\\pdftohtml\\pdftohtml.exe' IMAGEMAGICK_DIR = 'C:\\ImageMagick' FONTCONFIG_DIR = 'C:\\fontconfig' -VC90 = r'C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\x86\Microsoft.VC90.CRT' +VC90 = r'C:\VC90.CRT' import sys, os, py2exe, shutil, zipfile, glob, subprocess, re from distutils.core import setup @@ -179,7 +179,8 @@ def main(args=sys.argv): 'calibre.ebooks.lrf.fonts.prs500.*', 'PyQt4.QtWebKit', 'PyQt4.QtNetwork', ], - 'packages' : ['PIL', 'lxml', 'cherrypy'], + 'packages' : ['PIL', 'lxml', 'cherrypy', + 'dateutil'], 'excludes' : ["Tkconstants", "Tkinter", "tcl", "_imagingtk", "ImageTk", "FixTk" ], diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index cb3c05c7b9..aaf0c348d9 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -21,6 +21,8 @@ import mechanize mimetypes.add_type('application/epub+zip', '.epub') mimetypes.add_type('text/x-sony-bbeb+xml', '.lrs') +mimetypes.add_type('application/xhtml+xml', '.xhtml') +mimetypes.add_type('image/svg+xml', '.svg') mimetypes.add_type('application/x-sony-bbeb', '.lrf') mimetypes.add_type('application/x-dtbncx+xml', '.ncx') mimetypes.add_type('application/adobe-page-template+xml', '.xpgt') diff --git a/src/calibre/constants.py b/src/calibre/constants.py index ab5cc9f6b0..2d77964693 100644 --- a/src/calibre/constants.py +++ b/src/calibre/constants.py @@ -2,7 +2,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' __appname__ = 'calibre' -__version__ = '0.4.133' +__version__ = '0.4.136' __author__ = "Kovid Goyal " ''' Various run time constants. diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py index 733c6cd495..95bf01ff6d 100644 --- a/src/calibre/customize/ui.py +++ b/src/calibre/customize/ui.py @@ -153,6 +153,7 @@ def set_file_type_metadata(stream, mi, ftype): plugin.set_metadata(stream, mi, ftype.lower().strip()) break except: + print 'Failed to set metadata for', repr(getattr(mi, 'title', '')) traceback.print_exc() diff --git a/src/calibre/devices/cybookg3/driver.py b/src/calibre/devices/cybookg3/driver.py index f573fb1b75..f092473675 100644 --- a/src/calibre/devices/cybookg3/driver.py +++ b/src/calibre/devices/cybookg3/driver.py @@ -17,8 +17,8 @@ class CYBOOKG3(USBMS): # Be sure these have an entry in calibre.devices.mime FORMATS = ['mobi', 'prc', 'html', 'pdf', 'rtf', 'txt'] - VENDOR_ID = 0x0bda - PRODUCT_ID = 0x0703 + VENDOR_ID = [0x0bda, 0x3034] + PRODUCT_ID = [0x0703, 0x1795] BCD = [0x110, 0x132] VENDOR_NAME = 'BOOKEEN' diff --git a/src/calibre/devices/kindle/driver.py b/src/calibre/devices/kindle/driver.py index 8ef1ba6b9b..0da1f55c5e 100755 --- a/src/calibre/devices/kindle/driver.py +++ b/src/calibre/devices/kindle/driver.py @@ -12,8 +12,8 @@ class KINDLE(USBMS): # Ordered list of supported formats FORMATS = ['azw', 'mobi', 'prc', 'txt'] - VENDOR_ID = 0x1949 - PRODUCT_ID = 0x0001 + VENDOR_ID = [0x1949] + PRODUCT_ID = [0x0001] BCD = [0x399] VENDOR_NAME = 'KINDLE' diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 9308af2c5a..fb8bbf5378 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -248,15 +248,20 @@ class PRS505(Device): time.sleep(3) self.open_osx() if self._card_prefix is not None: - cachep = os.path.join(self._card_prefix, self.CACHE_XML) - if not os.path.exists(cachep): - os.makedirs(os.path.dirname(cachep), mode=0777) - f = open(cachep, 'wb') - f.write(u''' + try: + cachep = os.path.join(self._card_prefix, self.CACHE_XML) + if not os.path.exists(cachep): + os.makedirs(os.path.dirname(cachep), mode=0777) + f = open(cachep, 'wb') + f.write(u''' '''.encode('utf8')) - f.close() + f.close() + except: + self._card_prefix = None + import traceback + traceback.print_exc() def set_progress_reporter(self, pr): self.report_progress = pr diff --git a/src/calibre/devices/scanner.py b/src/calibre/devices/scanner.py index f0cd9a66eb..a93ef54c32 100644 --- a/src/calibre/devices/scanner.py +++ b/src/calibre/devices/scanner.py @@ -58,17 +58,20 @@ class DeviceScanner(object): return False def is_device_connected(self, device): + vendor_ids = device.VENDOR_ID if hasattr(device.VENDOR_ID, '__len__') else [device.VENDOR_ID] + product_ids = device.PRODUCT_ID if hasattr(device.PRODUCT_ID, '__len__') else [device.PRODUCT_ID] if iswindows: - vid, pid = 'vid_%4.4x'%device.VENDOR_ID, 'pid_%4.4x'%device.PRODUCT_ID - vidd, pidd = 'vid_%i'%device.VENDOR_ID, 'pid_%i'%device.PRODUCT_ID - for device_id in self.devices: - if (vid in device_id or vidd in device_id) and (pid in device_id or pidd in device_id): - if self.test_bcd_windows(device_id, getattr(device, 'BCD', None)): - if device.can_handle(device_id): - return True + for vendor_id, product_id in zip(vendor_ids, product_ids): + vid, pid = 'vid_%4.4x'%vendor_id, 'pid_%4.4x'%product_id + vidd, pidd = 'vid_%i'%vendor_id, 'pid_%i'%product_id + for device_id in self.devices: + if (vid in device_id or vidd in device_id) and (pid in device_id or pidd in device_id): + if self.test_bcd_windows(device_id, getattr(device, 'BCD', None)): + if device.can_handle(device_id): + return True else: for vendor, product, bcdDevice in self.devices: - if device.VENDOR_ID == vendor and device.PRODUCT_ID == product: + if vendor in vendor_ids and product in product_ids: if self.test_bcd(bcdDevice, getattr(device, 'BCD', None)): if device.can_handle((vendor, product, bcdDevice)): return True diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index 761fe9ba74..5943e2e13f 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -74,24 +74,27 @@ class Device(_Device): 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 + for vid in cls.VENDOR_ID: + for pid in cls.PRODUCT_ID: + fdi_base_values = dict( + app=__appname__, + deviceclass=cls.__name__, + vendor_id=hex(vid), + product_id=hex(pid), + 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 diff --git a/src/calibre/ebooks/epub/from_html.py b/src/calibre/ebooks/epub/from_html.py index bd9b59cfbd..fd94c9ee69 100644 --- a/src/calibre/ebooks/epub/from_html.py +++ b/src/calibre/ebooks/epub/from_html.py @@ -74,7 +74,9 @@ def check_links(opf_path, pretty_print): html_files = [] for item in opf.itermanifest(): if 'html' in item.get('media-type', '').lower(): - f = item.get('href').split('/')[-1].decode('utf-8') + f = item.get('href').split('/')[-1] + if isinstance(f, str): + f = f.decode('utf-8') html_files.append(os.path.abspath(content(f))) for path in html_files: diff --git a/src/calibre/ebooks/html.py b/src/calibre/ebooks/html.py index 5e87351375..1e9a0a9ea4 100644 --- a/src/calibre/ebooks/html.py +++ b/src/calibre/ebooks/html.py @@ -330,7 +330,8 @@ class PreProcessor(object): sanitize_head), # Convert all entities, since lxml doesn't handle them well (re.compile(r'&(\S+?);'), convert_entities), - + # Remove the ]*>'), lambda match: ''), ] # Fix pdftohtml markup @@ -467,7 +468,7 @@ class Parser(PreProcessor, LoggingInterface): if self.htmlfile.is_binary: raise ValueError('Not a valid HTML file: '+self.htmlfile.path) src = open(self.htmlfile.path, 'rb').read().decode(self.htmlfile.encoding, 'replace').strip() - src = src.replace('\x00', '') + src = src.replace('\x00', '').replace('\r', ' ') src = self.preprocess(src) # lxml chokes on unicode input when it contains encoding declarations for pat in ENCODING_PATS: diff --git a/src/calibre/ebooks/lrf/html/convert_from.py b/src/calibre/ebooks/lrf/html/convert_from.py index 673c92ebb9..48c2ffe993 100644 --- a/src/calibre/ebooks/lrf/html/convert_from.py +++ b/src/calibre/ebooks/lrf/html/convert_from.py @@ -917,7 +917,8 @@ class HTMLConverter(object, LoggingInterface): blockStyle=self.current_block.blockStyle) - def process_image(self, path, tag_css, width=None, height=None, dropcaps=False): + def process_image(self, path, tag_css, width=None, height=None, + dropcaps=False, rescale=False): def detect_encoding(im): fmt = im.format if fmt == 'JPG': @@ -936,10 +937,6 @@ class HTMLConverter(object, LoggingInterface): return encoding = detect_encoding(im) - if width == None or height == None: - width, height = im.size - - factor = 720./self.profile.dpi def scale_image(width, height): if width <= 0: @@ -955,8 +952,15 @@ class HTMLConverter(object, LoggingInterface): return pt.name except (IOError, SystemError), err: # PIL chokes on interlaced PNG images as well a some GIF images self.log_warning(_('Unable to process image %s. Error: %s')%(path, err)) - return None + if width == None or height == None: + width, height = im.size + elif rescale and (width < im.size[0] or height < im.size[1]): + path = scale_image(width, height) + if not path: + return + + factor = 720./self.profile.dpi pheight = int(self.current_page.pageStyle.attrs['textheight']) pwidth = int(self.current_page.pageStyle.attrs['textwidth']) @@ -1518,7 +1522,8 @@ class HTMLConverter(object, LoggingInterface): except: pass dropcaps = tag.has_key('class') and tag['class'] == 'libprs500_dropcaps' - self.process_image(path, tag_css, width, height, dropcaps=dropcaps) + self.process_image(path, tag_css, width, height, + dropcaps=dropcaps, rescale=True) elif not urlparse(tag['src'])[0]: self.log_warn('Could not find image: '+tag['src']) else: diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index e3c434342a..51ae044d68 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -188,7 +188,8 @@ class MetaInformation(object): for attr in ('author_sort', 'title_sort', 'comments', 'category', 'publisher', 'series', 'series_index', 'rating', 'isbn', 'tags', 'cover_data', 'application_id', 'guide', - 'manifest', 'spine', 'toc', 'cover', 'language', 'book_producer'): + 'manifest', 'spine', 'toc', 'cover', 'language', + 'book_producer', 'timestamp'): if hasattr(mi, attr): setattr(ans, attr, getattr(mi, attr)) @@ -213,7 +214,7 @@ class MetaInformation(object): for x in ('author_sort', 'title_sort', 'comments', 'category', 'publisher', 'series', 'series_index', 'rating', 'isbn', 'language', 'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover', - 'book_producer', + 'book_producer', 'timestamp' ): setattr(self, x, getattr(mi, x, None)) @@ -231,7 +232,8 @@ class MetaInformation(object): for attr in ('author_sort', 'title_sort', 'comments', 'category', 'publisher', 'series', 'series_index', 'rating', 'isbn', 'application_id', 'manifest', 'spine', 'toc', - 'cover', 'language', 'guide', 'book_producer'): + 'cover', 'language', 'guide', 'book_producer', + 'timestamp'): val = getattr(mi, attr, None) if val is not None: setattr(self, attr, val) @@ -254,7 +256,7 @@ class MetaInformation(object): ans = [] def fmt(x, y): ans.append(u'%-20s: %s'%(unicode(x), unicode(y))) - + fmt('Title', self.title) if self.title_sort: fmt('Title sort', self.title_sort) @@ -279,6 +281,8 @@ class MetaInformation(object): fmt('Language', self.language) if self.rating is not None: fmt('Rating', self.rating) + if self.timestamp is not None: + fmt('Timestamp', self.timestamp.isoformat(' ')) return u'\n'.join(ans) def to_html(self): @@ -290,14 +294,14 @@ class MetaInformation(object): ans += [('ISBN', unicode(self.isbn))] ans += [(_('Tags'), u', '.join([unicode(t) for t in self.tags]))] if self.series: - ans += [(_('Series'), unicode(self.series))+ ' #%s'%self.format_series_index()] + ans += [(_('Series'), unicode(self.series)+ ' #%s'%self.format_series_index())] ans += [(_('Language'), unicode(self.language))] + if self.timestamp is not None: + ans += [(_('Timestamp'), unicode(self.timestamp.isoformat(' ')))] for i, x in enumerate(ans): ans[i] = u'%s%s'%x return u'%s
'%u'\n'.join(ans) - - def __str__(self): return self.__unicode__().encode('utf-8') diff --git a/src/calibre/ebooks/metadata/cli.py b/src/calibre/ebooks/metadata/cli.py index 4101f34047..8053b82e90 100644 --- a/src/calibre/ebooks/metadata/cli.py +++ b/src/calibre/ebooks/metadata/cli.py @@ -31,21 +31,21 @@ from calibre import prints def config(): c = StringConfig('') - c.add_opt('title', ['-t', '--title'], + c.add_opt('title', ['-t', '--title'], help=_('Set the title.')) c.add_opt('authors', ['-a', '--authors'], help=_('Set the authors. Multiple authors should be separated ' 'by the & character. Author names should be in the order ' 'Firstname Lastname.')) - c.add_opt('title_sort', ['--title-sort'], + c.add_opt('title_sort', ['--title-sort'], help=_('The version of the title to be used for sorting. ' 'If unspecified, and the title is specified, it will ' 'be auto-generated from the title.')) - c.add_opt('author_sort', ['--author-sort'], + c.add_opt('author_sort', ['--author-sort'], help=_('String to be used when sorting by author. ' 'If unspecified, and the author(s) are specified, it will ' 'be auto-generated from the author(s).')) - c.add_opt('cover', ['--cover'], + c.add_opt('cover', ['--cover'], help=_('Set the cover to the specified file.')) c.add_opt('comments', ['-c', '--comments'], help=_('Set the ebook description.')) @@ -195,4 +195,4 @@ def main(args=sys.argv): return 0 if __name__ == '__main__': - sys.exit(main()) \ No newline at end of file + sys.exit(main()) diff --git a/src/calibre/ebooks/metadata/library_thing.py b/src/calibre/ebooks/metadata/library_thing.py index fdecf3fa99..ef41d5e937 100644 --- a/src/calibre/ebooks/metadata/library_thing.py +++ b/src/calibre/ebooks/metadata/library_thing.py @@ -37,9 +37,15 @@ def cover_from_isbn(isbn, timeout=5.): if browser is None: browser = _browser() _timeout = socket.getdefaulttimeout() - socket.setdefaulttimeout(timeout) + socket.setdefaulttimeout(timeout) + src = None try: src = browser.open('http://www.librarything.com/isbn/'+isbn).read().decode('utf-8', 'replace') + except Exception, err: + if isinstance(getattr(err, 'args', [None])[0], socket.timeout): + err = LibraryThingError(_('LibraryThing.com timed out. Try again later.')) + raise err + else: s = BeautifulSoup(src) url = s.find('td', attrs={'class':'left'}) if url is None: diff --git a/src/calibre/ebooks/metadata/meta.py b/src/calibre/ebooks/metadata/meta.py index d8116b33d3..1241238f26 100644 --- a/src/calibre/ebooks/metadata/meta.py +++ b/src/calibre/ebooks/metadata/meta.py @@ -31,8 +31,14 @@ def metadata_from_formats(formats): mi = MetaInformation(None, None) formats.sort(cmp=lambda x,y: cmp(METADATA_PRIORITIES[path_to_ext(x)], METADATA_PRIORITIES[path_to_ext(y)])) - for path in formats: - ext = path_to_ext(path) + extensions = list(map(path_to_ext, formats)) + if 'opf' in extensions: + opf = formats[extensions.index('opf')] + mi2 = opf_metadata(opf) + if mi2 is not None and mi2.title: + return mi2 + + for path, ext in zip(formats, extensions): stream = open(path, 'rb') try: mi.smart_update(get_metadata(stream, stream_type=ext, use_libprs_metadata=True)) diff --git a/src/calibre/ebooks/metadata/mobi.py b/src/calibre/ebooks/metadata/mobi.py index 15aedb768b..461210befe 100644 --- a/src/calibre/ebooks/metadata/mobi.py +++ b/src/calibre/ebooks/metadata/mobi.py @@ -22,8 +22,8 @@ class StreamSlicer(object): def __init__(self, stream, start=0, stop=None): self._stream = stream self.start = start - if stop is None: - stream.seek(0, 2) + if stop is None: + stream.seek(0, 2) stop = stream.tell() self.stop = stop self._len = stop - start @@ -73,7 +73,7 @@ class StreamSlicer(object): raise TypeError("stream indices must be integers") -class MetadataUpdater(object): +class MetadataUpdater(object): def __init__(self, stream): self.stream = stream data = self.data = StreamSlicer(stream) @@ -85,9 +85,9 @@ class MetadataUpdater(object): image_base, = unpack('>I', record0[108:112]) flags, = unpack('>I', record0[128:132]) have_exth = self.have_exth = (flags & 0x40) != 0 + self.cover_record = self.thumbnail_record = None if not have_exth: return - self.cover_record = self.thumbnail_record = None exth_off = unpack('>I', record0[20:24])[0] + 16 + record0.start exth = self.exth = StreamSlicer(stream, exth_off, record0.stop) nitems, = unpack('>I', exth[8:12]) @@ -142,6 +142,8 @@ class MetadataUpdater(object): exth = ['EXTH', pack('>II', len(exth) + 12, len(recs)), exth, pad] exth = ''.join(exth) title = (mi.title or _('Unknown')).encode(self.codec, 'replace') + if getattr(self, 'exth', None) is None: + raise MobiError('No existing EXTH record. Cannot update metadata.') title_off = (self.exth.start - self.record0.start) + len(exth) title_len = len(title) trail = len(self.exth) - len(exth) - len(title) @@ -150,18 +152,22 @@ class MetadataUpdater(object): self.exth[:] = ''.join([exth, title, '\0' * trail]) self.record0[84:92] = pack('>II', title_off, title_len) self.record0[92:96] = iana2mobi(mi.language) - if mi.cover_data[1]: - data = mi.cover_data[1] - if self.cover_record is not None: - size = len(self.cover_record) - cover = rescale_image(data, size) - cover += '\0' * (size - len(cover)) - self.cover_record[:] = cover - if self.thumbnail_record is not None: - size = len(self.thumbnail_record) - thumbnail = rescale_image(data, size, dimen=MAX_THUMB_DIMEN) - thumbnail += '\0' * (size - len(thumbnail)) - self.thumbnail_record[:] = thumbnail + if mi.cover_data[1] or mi.cover: + try: + data = mi.cover_data[1] if mi.cover_data[1] else open(mi.cover, 'rb').read() + except: + pass + else: + if self.cover_record is not None: + size = len(self.cover_record) + cover = rescale_image(data, size) + cover += '\0' * (size - len(cover)) + self.cover_record[:] = cover + if self.thumbnail_record is not None: + size = len(self.thumbnail_record) + thumbnail = rescale_image(data, size, dimen=MAX_THUMB_DIMEN) + thumbnail += '\0' * (size - len(thumbnail)) + self.thumbnail_record[:] = thumbnail return def set_metadata(stream, mi): diff --git a/src/calibre/ebooks/metadata/opf.xml b/src/calibre/ebooks/metadata/opf.xml index 703e82b5c1..619fb3301c 100644 --- a/src/calibre/ebooks/metadata/opf.xml +++ b/src/calibre/ebooks/metadata/opf.xml @@ -10,7 +10,7 @@ ${author} ${'%s (%s)'%(__appname__, __version__)} [http://${__appname__}.kovidgoyal.net] ${mi.application_id} - + ${mi.timestamp.isoformat()} ${mi.language if mi.language else 'UND'} ${mi.category} ${mi.comments} diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index c64b567d0e..e5f84e2729 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -12,6 +12,7 @@ from urllib import unquote from urlparse import urlparse from lxml import etree +from dateutil import parser from calibre.ebooks.chardet import xml_to_unicode from calibre import relpath @@ -436,6 +437,7 @@ class OPF(object): series = MetadataField('series', is_dc=False) series_index = MetadataField('series_index', is_dc=False, formatter=int, none_is=1) rating = MetadataField('rating', is_dc=False, formatter=int) + timestamp = MetadataField('date', formatter=parser.parse) def __init__(self, stream, basedir=os.getcwdu(), unquote_urls=True): @@ -618,7 +620,7 @@ class OPF(object): def fset(self, val): remove = list(self.authors_path(self.metadata)) for elem in remove: - self.metadata.remove(elem) + elem.getparent().remove(elem) for author in val: attrib = {'{%s}role'%self.NAMESPACES['opf']: 'aut'} elem = self.create_metadata_element('creator', attrib=attrib) diff --git a/src/calibre/ebooks/mobi/langcodes.py b/src/calibre/ebooks/mobi/langcodes.py index 5df11c4e38..da0b3ef62e 100644 --- a/src/calibre/ebooks/mobi/langcodes.py +++ b/src/calibre/ebooks/mobi/langcodes.py @@ -306,13 +306,15 @@ IANA_MOBI = \ 'zu': {None: (53, 0)}} def iana2mobi(icode): - subtags = list(icode.split('-')) - langdict = IANA_MOBI[None] - while len(subtags) > 0: - lang = subtags.pop(0).lower() - if lang in IANA_MOBI: - langdict = IANA_MOBI[lang] - break + langdict, subtags = IANA_MOBI[None], [] + if icode: + subtags = list(icode.split('-')) + while len(subtags) > 0: + lang = subtags.pop(0).lower() + if lang in IANA_MOBI: + langdict = IANA_MOBI[lang] + break + mcode = langdict[None] while len(subtags) > 0: subtag = subtags.pop(0) diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py index 6811f9ccda..bfbe8f5ae5 100644 --- a/src/calibre/ebooks/mobi/reader.py +++ b/src/calibre/ebooks/mobi/reader.py @@ -308,8 +308,11 @@ class MobiReader(object): if 'filepos-id' in attrib: attrib['id'] = attrib.pop('filepos-id') if 'filepos' in attrib: - filepos = int(attrib.pop('filepos')) - attrib['href'] = "#filepos%d" % filepos + filepos = attrib.pop('filepos') + try: + attrib['href'] = "#filepos%d" % int(filepos) + except: + attrib['href'] = filepos if tag.tag == 'img': recindex = None for attr in self.IMAGE_ATTRS: diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index c9d01b03fe..2e160d1571 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -44,34 +44,34 @@ OPF1_NSMAP = {'dc': DC11_NS, 'oebpackage': OPF1_NS} OPF2_NSMAP = {'opf': OPF2_NS, 'dc': DC11_NS, 'dcterms': DCTERMS_NS, 'xsi': XSI_NS, 'calibre': CALIBRE_NS} -def XML(name): +def XML(name): return '{%s}%s' % (XML_NS, name) -def XHTML(name): +def XHTML(name): return '{%s}%s' % (XHTML_NS, name) -def OPF(name): +def OPF(name): return '{%s}%s' % (OPF2_NS, name) -def DC(name): +def DC(name): return '{%s}%s' % (DC11_NS, name) -def XSI(name): +def XSI(name): return '{%s}%s' % (XSI_NS, name) -def DCTERMS(name): +def DCTERMS(name): return '{%s}%s' % (DCTERMS_NS, name) -def NCX(name): +def NCX(name): return '{%s}%s' % (NCX_NS, name) -def SVG(name): +def SVG(name): return '{%s}%s' % (SVG_NS, name) -def XLINK(name): +def XLINK(name): return '{%s}%s' % (XLINK_NS, name) -def CALIBRE(name): +def CALIBRE(name): return '{%s}%s' % (CALIBRE_NS, name) def LINK_SELECTORS(): diff --git a/src/calibre/ebooks/oeb/stylizer.py b/src/calibre/ebooks/oeb/stylizer.py index e4a0cfd7fe..b04492a167 100644 --- a/src/calibre/ebooks/oeb/stylizer.py +++ b/src/calibre/ebooks/oeb/stylizer.py @@ -17,6 +17,8 @@ import types import re import copy from itertools import izip +from weakref import WeakKeyDictionary +from xml.dom import SyntaxErr as CSSSyntaxError import cssutils from cssutils.css import CSSStyleRule, CSSPageRule, CSSStyleDeclaration, \ CSSValueList, cssproperties @@ -106,7 +108,7 @@ class CSSSelector(etree.XPath): class Stylizer(object): - STYLESHEETS = {} + STYLESHEETS = WeakKeyDictionary() def __init__(self, tree, path, oeb, profile=PROFILES['PRS505']): self.oeb = oeb @@ -131,18 +133,19 @@ class Stylizer(object): and elem.get('type', CSS_MIME) in OEB_STYLES: href = urlnormalize(elem.attrib['href']) path = item.abshref(href) - if path not in oeb.manifest.hrefs: + sitem = oeb.manifest.hrefs.get(path, None) + if sitem is None: self.logger.warn( 'Stylesheet %r referenced by file %r not in manifest' % (path, item.href)) continue - if path in self.STYLESHEETS: - stylesheet = self.STYLESHEETS[path] + if sitem in self.STYLESHEETS: + stylesheet = self.STYLESHEETS[sitem] else: data = self._fetch_css_file(path)[1] stylesheet = parser.parseString(data, href=path) stylesheet.namespaces['h'] = XHTML_NS - self.STYLESHEETS[path] = stylesheet + self.STYLESHEETS[sitem] = stylesheet stylesheets.append(stylesheet) rules = [] index = 0 @@ -288,15 +291,19 @@ class Style(object): def _update_cssdict(self, cssdict): self._style.update(cssdict) - + def _apply_style_attr(self): attrib = self._element.attrib - if 'style' in attrib: - css = attrib['style'].split(';') - css = filter(None, map(lambda x: x.strip(), css)) + if 'style' not in attrib: + return + css = attrib['style'].split(';') + css = filter(None, (x.strip() for x in css)) + try: style = CSSStyleDeclaration('; '.join(css)) - self._style.update(self._stylizer.flatten_style(style)) - + except CSSSyntaxError: + return + self._style.update(self._stylizer.flatten_style(style)) + def _has_parent(self): return (self._element.getparent() is not None) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 084b352f48..c33036e183 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -1,7 +1,7 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' """ The GUI """ -import sys, os, re, StringIO, traceback +import sys, os, re, StringIO, traceback, time from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSize, \ QByteArray, QLocale, QUrl, QTranslator, QCoreApplication, \ QModelIndex @@ -14,6 +14,9 @@ from calibre import __author__, islinux, iswindows, isosx from calibre.startup import get_lang from calibre.utils.config import Config, ConfigProxy, dynamic import calibre.resources as resources +from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats +from calibre.ebooks.metadata import MetaInformation + NONE = QVariant() #: Null value to return from the data function of item models @@ -148,7 +151,41 @@ class Dispatcher(QObject): def dispatch(self, args, kwargs): self.func(*args, **kwargs) + +class GetMetadata(QObject): + ''' + Convenience class to ensure that metadata readers are used only in the + GUI thread. Must be instantiated in the GUI thread. + ''' + + def __init__(self): + QObject.__init__(self) + self.connect(self, SIGNAL('edispatch(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'), + self._get_metadata, Qt.QueuedConnection) + self.connect(self, SIGNAL('idispatch(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'), + self._from_formats, Qt.QueuedConnection) + def __call__(self, id, *args, **kwargs): + self.emit(SIGNAL('edispatch(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'), + id, args, kwargs) + + def from_formats(self, id, *args, **kwargs): + self.emit(SIGNAL('idispatch(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'), + id, args, kwargs) + + def _from_formats(self, id, args, kwargs): + try: + mi = metadata_from_formats(*args, **kwargs) + except: + mi = MetaInformation('', [_('Unknown')]) + self.emit(SIGNAL('metadataf(PyQt_PyObject, PyQt_PyObject)'), id, mi) + + def _get_metadata(self, id, args, kwargs): + try: + mi = get_metadata(*args, **kwargs) + except: + mi = MetaInformation('', [_('Unknown')]) + self.emit(SIGNAL('metadata(PyQt_PyObject, PyQt_PyObject)'), id, mi) class TableView(QTableView): def __init__(self, parent): diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py new file mode 100644 index 0000000000..a6c6c699e8 --- /dev/null +++ b/src/calibre/gui2/add.py @@ -0,0 +1,224 @@ +''' +UI for adding books to the database +''' +import os + +from PyQt4.Qt import QThread, SIGNAL, QMutex, QWaitCondition, Qt + +from calibre.gui2.dialogs.progress import ProgressDialog +from calibre.constants import preferred_encoding +from calibre.gui2.widgets import WarningDialog + +class Add(QThread): + + def __init__(self): + QThread.__init__(self) + self._lock = QMutex() + self._waiting = QWaitCondition() + + def is_canceled(self): + if self.pd.canceled: + self.canceled = True + return self.canceled + + def wait_for_condition(self): + self._lock.lock() + self._waiting.wait(self._lock) + self._lock.unlock() + + def wake_up(self): + self._waiting.wakeAll() + +class AddFiles(Add): + + def __init__(self, paths, default_thumbnail, get_metadata, db=None): + Add.__init__(self) + self.paths = paths + self.get_metadata = get_metadata + self.default_thumbnail = default_thumbnail + self.db = db + self.formats, self.metadata, self.names, self.infos = [], [], [], [] + self.duplicates = [] + self.number_of_books_added = 0 + self.connect(self.get_metadata, + SIGNAL('metadata(PyQt_PyObject, PyQt_PyObject)'), + self.metadata_delivered) + + def metadata_delivered(self, id, mi): + if self.is_canceled(): + self.reading.wakeAll() + return + if not mi.title: + mi.title = os.path.splitext(self.names[id])[0] + mi.title = mi.title if isinstance(mi.title, unicode) else \ + mi.title.decode(preferred_encoding, 'replace') + self.metadata.append(mi) + self.infos.append({'title':mi.title, + 'authors':', '.join(mi.authors), + 'cover':self.default_thumbnail, 'tags':[]}) + if self.db is not None: + duplicates, num = self.db.add_books(self.paths[id:id+1], + self.formats[id:id+1], [mi], + add_duplicates=False) + self.number_of_books_added += num + if duplicates: + if not self.duplicates: + self.duplicates = [[], [], [], []] + for i in range(4): + self.duplicates[i] += duplicates[i] + self.emit(SIGNAL('processed(PyQt_PyObject,PyQt_PyObject)'), + mi.title, id) + self.wake_up() + + def create_progress_dialog(self, title, msg, parent): + self._parent = parent + self.pd = ProgressDialog(title, msg, -1, len(self.paths)-1, parent) + self.connect(self, SIGNAL('processed(PyQt_PyObject,PyQt_PyObject)'), + self.update_progress_dialog) + self.pd.setModal(True) + self.pd.show() + self.connect(self, SIGNAL('finished()'), self.pd.hide) + return self.pd + + + def update_progress_dialog(self, title, count): + self.pd.set_value(count) + if self.db is not None: + self.pd.set_msg(_('Added %s to library')%title) + else: + self.pd.set_msg(_('Read metadata from ')+title) + + + def run(self): + self.canceled = False + for c, book in enumerate(self.paths): + if self.pd.canceled: + self.canceled = True + break + format = os.path.splitext(book)[1] + format = format[1:] if format else None + stream = open(book, 'rb') + self.formats.append(format) + self.names.append(os.path.basename(book)) + self.get_metadata(c, stream, stream_type=format, + use_libprs_metadata=True) + self.wait_for_condition() + + + def process_duplicates(self): + if self.duplicates: + files = _('

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

', parent=self._parent) + if d.exec_() == d.Accepted: + num = self.db.add_books(*self.duplicates, + **dict(add_duplicates=True))[1] + self.number_of_books_added += num + + +class AddRecursive(Add): + + def __init__(self, path, db, get_metadata, single_book_per_directory, parent): + self.path = path + self.db = db + self.get_metadata = get_metadata + self.single_book_per_directory = single_book_per_directory + self.duplicates, self.books, self.metadata = [], [], [] + self.number_of_books_added = 0 + self.canceled = False + Add.__init__(self) + self.connect(self.get_metadata, + SIGNAL('metadataf(PyQt_PyObject, PyQt_PyObject)'), + self.metadata_delivered, Qt.QueuedConnection) + self.connect(self, SIGNAL('searching_done()'), self.searching_done, + Qt.QueuedConnection) + self._parent = parent + self.pd = ProgressDialog(_('Adding books recursively...'), + _('Searching for books in all sub-directories...'), + 0, 0, parent) + self.connect(self, SIGNAL('processed(PyQt_PyObject,PyQt_PyObject)'), + self.update_progress_dialog) + self.connect(self, SIGNAL('update(PyQt_PyObject)'), self.pd.set_msg, + Qt.QueuedConnection) + self.connect(self, SIGNAL('pupdate(PyQt_PyObject)'), self.pd.set_value, + Qt.QueuedConnection) + self.pd.setModal(True) + self.pd.show() + self.connect(self, SIGNAL('finished()'), self.pd.hide) + + def update_progress_dialog(self, title, count): + self.pd.set_value(count) + if title: + self.pd.set_msg(_('Read metadata from ')+title) + + def metadata_delivered(self, id, mi): + if self.is_canceled(): + self.reading.wakeAll() + return + self.emit(SIGNAL('processed(PyQt_PyObject,PyQt_PyObject)'), + mi.title, id) + self.metadata.append((mi if mi.title else None, self.books[id])) + if len(self.metadata) >= len(self.books): + self.metadata = [x for x in self.metadata if x[0] is not None] + self.pd.set_min(-1) + self.pd.set_max(len(self.metadata)-1) + self.pd.set_value(-1) + self.pd.set_msg(_('Adding books to database...')) + self.wake_up() + + def searching_done(self): + self.pd.set_min(-1) + self.pd.set_max(len(self.books)-1) + self.pd.set_value(-1) + self.pd.set_msg(_('Reading metadata...')) + + + def run(self): + root = os.path.abspath(self.path) + for dirpath in os.walk(root): + if self.is_canceled(): + return + self.emit(SIGNAL('update(PyQt_PyObject)'), + _('Searching in')+' '+dirpath[0]) + self.books += list(self.db.find_books_in_directory(dirpath[0], + self.single_book_per_directory)) + self.books = [formats for formats in self.books if formats] + # Reset progress bar + self.emit(SIGNAL('searching_done()')) + + for c, formats in enumerate(self.books): + self.get_metadata.from_formats(c, formats) + self.wait_for_condition() + + # Add books to database + for c, x in enumerate(self.metadata): + mi, formats = x + if self.is_canceled(): + break + if self.db.has_book(mi): + self.duplicates.append((mi, formats)) + else: + self.db.import_book(mi, formats, notify=False) + self.number_of_books_added += 1 + self.emit(SIGNAL('pupdate(PyQt_PyObject)'), c) + + + def process_duplicates(self): + if self.duplicates: + files = _('

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

', parent=self._parent) + if d.exec_() == d.Accepted: + for mi, formats in self.duplicates: + self.db.import_book(mi, formats, notify=False) + self.number_of_books_added += 1 + + \ No newline at end of file diff --git a/src/calibre/gui2/dialogs/confirm_delete.py b/src/calibre/gui2/dialogs/confirm_delete.py index 08db53e9a7..8c496987fb 100644 --- a/src/calibre/gui2/dialogs/confirm_delete.py +++ b/src/calibre/gui2/dialogs/confirm_delete.py @@ -5,7 +5,7 @@ __docformat__ = 'restructuredtext en' from calibre.gui2 import dynamic from calibre.gui2.dialogs.confirm_delete_ui import Ui_Dialog -from PyQt4.Qt import QDialog, SIGNAL +from PyQt4.Qt import QDialog, SIGNAL, Qt def _config_name(name): return name + '_again' @@ -19,6 +19,7 @@ class Dialog(QDialog, Ui_Dialog): self.msg.setText(msg) self.name = name self.connect(self.again, SIGNAL('stateChanged(int)'), self.toggle) + self.buttonBox.setFocus(Qt.OtherFocusReason) def toggle(self, x): diff --git a/src/calibre/gui2/dialogs/epub.ui b/src/calibre/gui2/dialogs/epub.ui index 2d5c0fa153..b6e2299e1d 100644 --- a/src/calibre/gui2/dialogs/epub.ui +++ b/src/calibre/gui2/dialogs/epub.ui @@ -105,36 +105,6 @@ Book Cover - - - - - - - - - :/images/book.svg - - - true - - - Qt::AlignCenter - - - - - - - - - Use cover from &source file - - - true - - - @@ -186,6 +156,36 @@ + + + + Use cover from &source file + + + true + + + + + + + + + + + + :/images/book.svg + + + true + + + Qt::AlignCenter + + + + + opt_prefer_metadata_cover @@ -507,6 +507,13 @@ + + + + Remove &first image from source file + + + diff --git a/src/calibre/gui2/dialogs/progress.py b/src/calibre/gui2/dialogs/progress.py index 2543cefb4d..0f64d7b041 100644 --- a/src/calibre/gui2/dialogs/progress.py +++ b/src/calibre/gui2/dialogs/progress.py @@ -20,6 +20,7 @@ class ProgressDialog(QDialog, Ui_Dialog): self.setWindowModality(Qt.ApplicationModal) self.set_min(min) self.set_max(max) + self.bar.setValue(min) self.canceled = False self.connect(self.button_box, SIGNAL('rejected()'), self._canceled) diff --git a/src/calibre/gui2/images/news/pobjeda.png b/src/calibre/gui2/images/news/pobjeda.png new file mode 100644 index 0000000000..d7612b4e9e Binary files /dev/null and b/src/calibre/gui2/images/news/pobjeda.png differ diff --git a/src/calibre/gui2/images/news/starbulletin.png b/src/calibre/gui2/images/news/starbulletin.png new file mode 100644 index 0000000000..bb3afd636a Binary files /dev/null and b/src/calibre/gui2/images/news/starbulletin.png differ diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index 2b0148a194..199c4ada67 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -1,3 +1,4 @@ +from calibre.ebooks.metadata import authors_to_string __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' import os, textwrap, traceback, time, re @@ -18,6 +19,7 @@ from calibre.library.database2 import FIELD_MAP from calibre.gui2 import NONE, TableView, qstring_to_unicode, config, \ error_dialog from calibre.utils.search_query_parser import SearchQueryParser +from calibre.ebooks.metadata.meta import set_metadata as _set_metadata class LibraryDelegate(QItemDelegate): COLOR = QColor("blue") @@ -370,35 +372,24 @@ class BooksModel(QAbstractTableModel): if not rows_are_ids: rows = [self.db.id(row.row()) for row in rows] for id in rows: - au = self.db.authors(id, index_is_id=True) - tags = self.db.tags(id, index_is_id=True) - if not au: - au = _('Unknown') - au = au.split(',') - if len(au) > 1: - t = ', '.join(au[:-1]) - t += ' & ' + au[-1] - au = t - else: - au = ' & '.join(au) - if not tags: - tags = [] - else: - tags = tags.split(',') - series = self.db.series(id, index_is_id=True) - if series is not None: - tags.append(series) - mi = { - 'title' : self.db.title(id, index_is_id=True), + mi = self.db.get_metadata(id, index_is_id=True) + au = authors_to_string(mi.authors if mi.authors else [_('Unknown')]) + tags = mi.tags if mi.tags else [] + if mi.series is not None: + tags.append(mi.series) + info = { + 'title' : mi.title, 'authors' : au, 'cover' : self.db.cover(id, index_is_id=True), 'tags' : tags, - 'comments': self.db.comments(id, index_is_id=True), + 'comments': mi.comments, } - if series is not None: - mi['tag order'] = {series:self.db.books_in_series_of(id, index_is_id=True)} + if mi.series is not None: + info['tag order'] = { + mi.series:self.db.books_in_series_of(id, index_is_id=True) + } - metadata.append(mi) + metadata.append(info) return metadata def get_preferred_formats_from_ids(self, ids, all_formats, mode='r+b'): @@ -423,7 +414,7 @@ class BooksModel(QAbstractTableModel): - def get_preferred_formats(self, rows, formats, paths=False): + def get_preferred_formats(self, rows, formats, paths=False, set_metadata=False): ans = [] for row in (row.row() for row in rows): format = None @@ -441,6 +432,9 @@ class BooksModel(QAbstractTableModel): pt = PersistentTemporaryFile(suffix='.'+format) pt.write(self.db.format(row, format)) pt.flush() + if set_metadata: + _set_metadata(pt, self.db.get_metadata(row, get_cover=True), + format) pt.close() if paths else pt.seek(0) ans.append(pt) else: diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index e6475dd020..83665ac8a7 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -13,7 +13,6 @@ from PyQt4.QtSvg import QSvgRenderer from calibre import __version__, __appname__, islinux, sanitize_file_name, \ iswindows, isosx, preferred_encoding from calibre.ptempfile import PersistentTemporaryFile -from calibre.ebooks.metadata.meta import get_metadata from calibre.devices.errors import FreeSpaceError from calibre.devices.interface import Device from calibre.utils.config import prefs, dynamic @@ -23,7 +22,7 @@ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \ set_sidebar_directories, Dispatcher, \ SingleApplication, Application, available_height, \ max_available_height, config, info_dialog, \ - available_width + available_width, GetMetadata from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror from calibre.gui2.dialogs.scheduler import Scheduler from calibre.gui2.update import CheckForUpdates @@ -49,7 +48,6 @@ from calibre.ebooks import BOOK_EXTENSIONS from calibre.library.database2 import LibraryDatabase2, CoverCache from calibre.parallel import JobKilled from calibre.utils.filenames import ascii_filename -from calibre.gui2.widgets import WarningDialog from calibre.gui2.dialogs.confirm_delete import confirm class Main(MainWindow, Ui_MainWindow): @@ -78,6 +76,7 @@ class Main(MainWindow, Ui_MainWindow): self.setupUi(self) self.setWindowTitle(__appname__) self.verbose = opts.verbose + self.get_metadata = GetMetadata() self.read_settings() self.job_manager = JobManager() self.jobs_dialog = JobsDialog(self, self.job_manager) @@ -369,13 +368,14 @@ class Main(MainWindow, Ui_MainWindow): if r == QSystemTrayIcon.Trigger: if self.isVisible(): for window in QApplication.topLevelWidgets(): - if isinstance(window, (MainWindow, QDialog)): + if isinstance(window, (MainWindow, QDialog)) and window.isVisible(): window.hide() + setattr(window, '__systray_minimized', True) else: for window in QApplication.topLevelWidgets(): - if isinstance(window, (MainWindow, QDialog)): - if window not in (self.device_error_dialog, self.jobs_dialog): - window.show() + if getattr(window, '__systray_minimized', False): + window.show() + setattr(window, '__systray_minimized', False) def do_default_sync(self, checked): @@ -390,7 +390,7 @@ class Main(MainWindow, Ui_MainWindow): def change_output_format(self, x): of = unicode(x).strip() if of != prefs['output_format']: - if of not in ('LRF',): + if of not in ('LRF', 'EPUB'): warning_dialog(self, 'Warning', '

%s support is still in beta. If you find bugs, please report them by opening a ticket.'%of).exec_() prefs.set('output_format', of) @@ -607,36 +607,26 @@ class Main(MainWindow, Ui_MainWindow): ################################# Add books ################################ def add_recursive(self, single): - root = choose_dir(self, 'recursive book import root dir dialog', 'Select root folder') + root = choose_dir(self, 'recursive book import root dir dialog', + 'Select root folder') if not root: return - progress = ProgressDialog(_('Adding books recursively...'), - min=0, max=0, parent=self) - progress.show() - def callback(msg): - if msg != '.': - progress.set_msg((_('Added ')+msg) if msg else _('Searching...')) - QApplication.processEvents() - QApplication.sendPostedEvents() - QApplication.flush() - return progress.canceled - try: - duplicates = self.library_view.model().db.recursive_import(root, single, callback=callback) - finally: - progress.hide() - if duplicates: - files = _('

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

    ') - for mi, formats in duplicates: - files += '
  • '+mi.title+'
  • \n' - d = WarningDialog(_('Duplicates found!'), _('Duplicates found!'), - files+'

', self) - if d.exec_() == QDialog.Accepted: - for mi, formats in duplicates: - self.library_view.model().db.import_book(mi, formats ) - - self.library_view.model().resort() - self.library_view.model().research() - + from calibre.gui2.add import AddRecursive + self._add_recursive_thread = AddRecursive(root, + self.library_view.model().db, self.get_metadata, + single, self) + self.connect(self._add_recursive_thread, SIGNAL('finished()'), + self._recursive_files_added) + self._add_recursive_thread.start() + + def _recursive_files_added(self): + self._add_recursive_thread.process_duplicates() + if self._add_recursive_thread.number_of_books_added > 0: + self.library_view.model().resort(reset=False) + self.library_view.model().research() + self.library_view.model().count_changed() + self._add_recursive_thread = None + def add_recursive_single(self, checked): ''' Add books from the local filesystem to either the library or the device @@ -685,63 +675,41 @@ class Main(MainWindow, Ui_MainWindow): return to_device = self.stack.currentIndex() != 0 self._add_books(books, to_device) - if to_device: - self.status_bar.showMessage(_('Uploading books to device.'), 2000) + def _add_books(self, paths, to_device, on_card=None): if on_card is None: on_card = self.stack.currentIndex() == 2 if not paths: return - # Get format and metadata information - formats, metadata, names, infos = [], [], [], [] - progress = ProgressDialog(_('Adding books...'), _('Reading metadata...'), - min=0, max=len(paths), parent=self) - progress.show() - try: - for c, book in enumerate(paths): - progress.set_value(c) - if progress.canceled: - return - format = os.path.splitext(book)[1] - format = format[1:] if format else None - stream = open(book, 'rb') - try: - mi = get_metadata(stream, stream_type=format, use_libprs_metadata=True) - except: - mi = MetaInformation(None, None) - if not mi.title: - mi.title = os.path.splitext(os.path.basename(book))[0] - if not mi.authors: - mi.authors = [_('Unknown')] - formats.append(format) - metadata.append(mi) - names.append(os.path.basename(book)) - infos.append({'title':mi.title, 'authors':', '.join(mi.authors), - 'cover':self.default_thumbnail, 'tags':[]}) - title = mi.title if isinstance(mi.title, unicode) else mi.title.decode(preferred_encoding, 'replace') - progress.set_msg(_('Read metadata from ')+title) - - if not to_device: - progress.set_msg(_('Adding books to database...')) - model = self.library_view.model() - - paths = list(paths) - duplicates, number_added = model.add_books(paths, formats, metadata) - if duplicates: - files = _('

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

    ') - for mi in duplicates[2]: - files += '
  • '+mi.title+'
  • \n' - d = WarningDialog(_('Duplicates found!'), _('Duplicates found!'), files+'

', parent=self) - if d.exec_() == QDialog.Accepted: - num = model.add_books(*duplicates, **dict(add_duplicates=True))[1] - number_added += num - model.books_added(number_added) + from calibre.gui2.add import AddFiles + self._add_files_thread = AddFiles(paths, self.default_thumbnail, + self.get_metadata, + None if to_device else \ + self.library_view.model().db + ) + self._add_files_thread.send_to_device = to_device + self._add_files_thread.on_card = on_card + self._add_files_thread.create_progress_dialog(_('Adding books...'), + _('Reading metadata...'), self) + self.connect(self._add_files_thread, SIGNAL('finished()'), + self._files_added) + self._add_files_thread.start() + + def _files_added(self): + t = self._add_files_thread + self._add_files_thread = None + if not t.canceled: + if t.send_to_device: + self.upload_books(t.paths, + list(map(sanitize_file_name, t.names)), + t.infos, on_card=t.on_card) + self.status_bar.showMessage(_('Uploading books to device.'), 2000) else: - self.upload_books(paths, list(map(sanitize_file_name, names)), infos, on_card=on_card) - finally: - progress.hide() - + t.process_duplicates() + if t.number_of_books_added > 0: + self.library_view.model().books_added(t.number_of_books_added) + def upload_books(self, files, names, metadata, on_card=False, memory=None): ''' Upload books to device. @@ -797,7 +765,10 @@ class Main(MainWindow, Ui_MainWindow): if not rows or len(rows) == 0: return if self.stack.currentIndex() == 0: - if not confirm('

'+_('The selected books will be permanently deleted and the files removed from your computer. Are you sure?')+'

', 'library_delete_books', self): + if not confirm('

'+_('The selected books will be ' + 'permanently deleted and the files ' + 'removed from your computer. Are you sure?') + +'

', 'library_delete_books', self): return view.model().delete_books(rows) else: @@ -928,7 +899,8 @@ class Main(MainWindow, Ui_MainWindow): mi['cover'] = self.cover_to_thumbnail(cdata) metadata = iter(metadata) _files = self.library_view.model().get_preferred_formats(rows, - self.device_manager.device_class.FORMATS, paths=True) + self.device_manager.device_class.FORMATS, + paths=True, set_metadata=True) files = [getattr(f, 'name', None) for f in _files] bad, good, gf, names, remove_ids = [], [], [], [], [] for f in files: @@ -1222,6 +1194,8 @@ class Main(MainWindow, Ui_MainWindow): format = 'LRF' if 'EPUB' in formats: format = 'EPUB' + if 'MOBI' in formats: + format = 'MOBI' if not formats: d = error_dialog(self, _('Cannot view'), _('%s has no available formats.')%(title,)) @@ -1403,8 +1377,15 @@ class Main(MainWindow, Ui_MainWindow): def initialize_database(self): self.library_path = prefs['library_path'] if self.library_path is None: # Need to migrate to new database layout + base = os.path.expanduser('~') + if iswindows: + from calibre import plugins + from PyQt4.Qt import QDir + base = plugins['winutil'][0].special_folder_path(plugins['winutil'][0].CSIDL_PERSONAL) + if not base or not os.path.exists(base): + base = unicode(QDir.homePath()).replace('/', os.sep) dir = unicode(QFileDialog.getExistingDirectory(self, - _('Choose a location for your ebook library.'), os.getcwd())) + _('Choose a location for your ebook library.'), base)) if not dir: dir = os.path.expanduser('~/Library') self.library_path = os.path.abspath(dir) @@ -1590,6 +1571,11 @@ def main(args=sys.argv): print 'Restarting with:', e, sys.argv os.execvp(e, sys.argv) else: + if iswindows: + try: + main.system_tray_icon.hide() + except: + pass return ret return 0 diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py index c81085f5b7..b694562d97 100644 --- a/src/calibre/gui2/viewer/documentview.py +++ b/src/calibre/gui2/viewer/documentview.py @@ -15,6 +15,7 @@ from calibre.utils.config import Config, StringConfig from calibre.gui2.viewer.config_ui import Ui_Dialog from calibre.gui2.viewer.js import bookmarks, referencing from calibre.ptempfile import PersistentTemporaryFile +from calibre.constants import iswindows def load_builtin_fonts(): from calibre.ebooks.lrf.fonts.liberation import LiberationMono_BoldItalic @@ -56,9 +57,12 @@ def config(defaults=None): help=_('Set the user CSS stylesheet. This can be used to customize the look of all books.')) fonts = c.add_group('FONTS', _('Font options')) - fonts('serif_family', default='Liberation Serif', help=_('The serif font family')) - fonts('sans_family', default='Liberation Sans', help=_('The sans-serif font family')) - fonts('mono_family', default='Liberation Mono', help=_('The monospaced font family')) + fonts('serif_family', default='Times New Roman' if iswindows else 'Liberation Serif', + help=_('The serif font family')) + fonts('sans_family', default='Verdana' if iswindows else 'Liberation Sans', + help=_('The sans-serif font family')) + fonts('mono_family', default='Courier New' if iswindows else 'Liberation Mono', + help=_('The monospaced font family')) fonts('default_font_size', default=20, help=_('The standard font size in px')) fonts('mono_font_size', default=16, help=_('The monospaced font size in px')) fonts('standard_font', default='serif', help=_('The standard font type')) diff --git a/src/calibre/library/database.py b/src/calibre/library/database.py index cc9ad62f05..cf352c464d 100644 --- a/src/calibre/library/database.py +++ b/src/calibre/library/database.py @@ -4,14 +4,10 @@ __copyright__ = '2008, Kovid Goyal ' Backend that implements storage of ebooks in an sqlite database. ''' import sqlite3 as sqlite -import datetime, re, os, cPickle, traceback, sre_constants +import datetime, re, cPickle, sre_constants from zlib import compress, decompress -from calibre import sanitize_file_name -from calibre.ebooks.metadata.meta import set_metadata, metadata_from_formats -from calibre.ebooks.metadata.opf2 import OPFCreator from calibre.ebooks.metadata import MetaInformation -from calibre.ebooks import BOOK_EXTENSIONS from calibre.web.feeds.recipes import migrate_automatic_profile_to_automatic_recipe class Concatenate(object): @@ -1389,227 +1385,16 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE; def all_ids(self): return [i[0] for i in self.conn.get('SELECT id FROM books')] - def export_to_dir(self, dir, indices, byauthor=False, single_dir=False, - index_is_id=False, callback=None): - if not os.path.exists(dir): - raise IOError('Target directory does not exist: '+dir) - by_author = {} - count = 0 - for index in indices: - id = index if index_is_id else self.id(index) - au = self.conn.get('SELECT author_sort FROM books WHERE id=?', - (id,), all=False) - if not au: - au = self.authors(index, index_is_id=index_is_id) - if not au: - au = _('Unknown') - au = au.split(',')[0] - if not by_author.has_key(au): - by_author[au] = [] - by_author[au].append(index) - for au in by_author.keys(): - apath = os.path.join(dir, sanitize_file_name(au)) - if not single_dir and not os.path.exists(apath): - os.mkdir(apath) - for idx in by_author[au]: - title = re.sub(r'\s', ' ', self.title(idx, index_is_id=index_is_id)) - tpath = os.path.join(apath, sanitize_file_name(title)) - id = idx if index_is_id else self.id(idx) - id = str(id) - if not single_dir and not os.path.exists(tpath): - os.mkdir(tpath) - - name = au + ' - ' + title if byauthor else title + ' - ' + au - name += '_'+id - base = dir if single_dir else tpath - mi = self.get_metadata(idx, index_is_id=index_is_id) - cover = self.cover(idx, index_is_id=index_is_id) - if cover is not None: - cname = sanitize_file_name(name) + '.jpg' - cpath = os.path.join(base, cname) - open(cpath, 'wb').write(cover) - mi.cover = cname - f = open(os.path.join(base, sanitize_file_name(name)+'.opf'), 'wb') - if not mi.authors: - mi.authors = [_('Unknown')] - opf = OPFCreator(base, mi) - opf.render(f) - f.close() - - fmts = self.formats(idx, index_is_id=index_is_id) - if not fmts: - fmts = '' - for fmt in fmts.split(','): - data = self.format(idx, fmt, index_is_id=index_is_id) - if not data: - continue - fname = name +'.'+fmt.lower() - fname = sanitize_file_name(fname) - f = open(os.path.join(base, fname), 'w+b') - f.write(data) - f.flush() - f.seek(0) - try: - set_metadata(f, mi, fmt.lower()) - except: - print 'Error setting metadata for book:', mi.title - traceback.print_exc() - f.close() - count += 1 - if callable(callback): - if not callback(count, mi.title): - return - + - def import_book(self, mi, formats): - series_index = 1 if mi.series_index is None else mi.series_index - if not mi.authors: - mi.authors = [_('Unknown')] - aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors) - obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)', - (mi.title, None, series_index, aus)) - id = obj.lastrowid - self.conn.commit() - self.set_metadata(id, mi) - for path in formats: - ext = os.path.splitext(path)[1][1:].lower() - stream = open(path, 'rb') - stream.seek(0, 2) - usize = stream.tell() - stream.seek(0) - data = sqlite.Binary(compress(stream.read())) - try: - self.conn.execute('INSERT INTO data(book, format, uncompressed_size, data) VALUES (?,?,?,?)', - (id, ext, usize, data)) - except sqlite.IntegrityError: - self.conn.execute('UPDATE data SET uncompressed_size=?, data=? WHERE book=? AND format=?', - (usize, data, id, ext)) - self.conn.commit() - - def import_book_directory_multiple(self, dirpath, callback=None): - dirpath = os.path.abspath(dirpath) - duplicates = [] - books = {} - for path in os.listdir(dirpath): - if callable(callback): - callback('.') - path = os.path.abspath(os.path.join(dirpath, path)) - if os.path.isdir(path) or not os.access(path, os.R_OK): - continue - ext = os.path.splitext(path)[1] - if not ext: - continue - ext = ext[1:].lower() - if ext not in BOOK_EXTENSIONS: - continue - - key = os.path.splitext(path)[0] - if not books.has_key(key): - books[key] = [] - - books[key].append(path) - - for formats in books.values(): - mi = metadata_from_formats(formats) - if mi.title is None: - continue - if self.has_book(mi): - duplicates.append((mi, formats)) - continue - self.import_book(mi, formats) - if callable(callback): - if callback(mi.title): - break - return duplicates - - - def import_book_directory(self, dirpath, callback=None): - dirpath = os.path.abspath(dirpath) - formats = [] - for path in os.listdir(dirpath): - if callable(callback): - callback('.') - path = os.path.abspath(os.path.join(dirpath, path)) - if os.path.isdir(path) or not os.access(path, os.R_OK): - continue - ext = os.path.splitext(path)[1] - if not ext: - continue - ext = ext[1:].lower() - if ext not in BOOK_EXTENSIONS: - continue - formats.append(path) - - if not formats: - return - - mi = metadata_from_formats(formats) - if mi.title is None: - return - if self.has_book(mi): - return [(mi, formats)] - self.import_book(mi, formats) - if callable(callback): - callback(mi.title) - - + def has_id(self, id): return self.conn.get('SELECT id FROM books where id=?', (id,), all=False) is not None - def recursive_import(self, root, single_book_per_directory=True, callback=None): - root = os.path.abspath(root) - duplicates = [] - for dirpath in os.walk(root): - res = self.import_book_directory(dirpath[0], callback=callback) if \ - single_book_per_directory else \ - self.import_book_directory_multiple(dirpath[0], callback=callback) - if res is not None: - duplicates.extend(res) - if callable(callback): - if callback(''): - break - - return duplicates - - def export_single_format_to_dir(self, dir, indices, format, - index_is_id=False, callback=None): - dir = os.path.abspath(dir) - if not index_is_id: - indices = map(self.id, indices) - failures = [] - for count, id in enumerate(indices): - try: - data = self.format(id, format, index_is_id=True) - if not data: - failures.append((id, self.title(id, index_is_id=True))) - continue - except: - failures.append((id, self.title(id, index_is_id=True))) - continue - title = self.title(id, index_is_id=True) - au = self.authors(id, index_is_id=True) - if not au: - au = _('Unknown') - fname = '%s - %s.%s'%(title, au, format.lower()) - fname = sanitize_file_name(fname) - if not os.path.exists(dir): - os.makedirs(dir) - f = open(os.path.join(dir, fname), 'w+b') - f.write(data) - f.seek(0) - try: - set_metadata(f, self.get_metadata(id, index_is_id=True), stream_type=format.lower()) - except: - pass - f.close() - if callable(callback): - if not callback(count, title): - break - return failures - - + + class SearchToken(object): diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index b13906e9f3..b4ddc92902 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -12,30 +12,34 @@ from itertools import repeat from datetime import datetime from PyQt4.QtCore import QCoreApplication, QThread, QReadWriteLock -from PyQt4.QtGui import QApplication, QPixmap, QImage +from PyQt4.QtGui import QApplication, QImage __app = None from calibre.library import title_sort from calibre.library.database import LibraryDatabase from calibre.library.sqlite import connect, IntegrityError from calibre.utils.search_query_parser import SearchQueryParser -from calibre.ebooks.metadata import string_to_authors, authors_to_string -from calibre.ebooks.metadata.meta import get_metadata +from calibre.ebooks.metadata import string_to_authors, authors_to_string, MetaInformation +from calibre.ebooks.metadata.meta import get_metadata, set_metadata, \ + metadata_from_formats +from calibre.ebooks.metadata.opf2 import OPFCreator from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding from calibre.ptempfile import PersistentTemporaryFile from calibre.customize.ui import run_plugins_on_import + from calibre import sanitize_file_name +from calibre.ebooks import BOOK_EXTENSIONS copyfile = os.link if hasattr(os, 'link') else shutil.copyfile -FIELD_MAP = {'id':0, 'title':1, 'authors':2, 'publisher':3, 'rating':4, 'timestamp':5, +FIELD_MAP = {'id':0, 'title':1, 'authors':2, 'publisher':3, 'rating':4, 'timestamp':5, 'size':6, 'tags':7, 'comments':8, 'series':9, 'series_index':10, 'sort':11, 'author_sort':12, 'formats':13, 'isbn':14, 'path':15} INDEX_MAP = dict(zip(FIELD_MAP.values(), FIELD_MAP.keys())) class CoverCache(QThread): - + def __init__(self, library_path, parent=None): QThread.__init__(self, parent) self.library_path = library_path @@ -47,7 +51,7 @@ class CoverCache(QThread): self.cache_lock = QReadWriteLock() self.id_map_stale = True self.keep_running = True - + def build_id_map(self): self.id_map_lock.lockForWrite() self.id_map = {} @@ -60,8 +64,8 @@ class CoverCache(QThread): continue self.id_map_lock.unlock() self.id_map_stale = False - - + + def set_cache(self, ids): self.cache_lock.lockForWrite() already_loaded = set([]) @@ -75,8 +79,8 @@ class CoverCache(QThread): self.load_queue_lock.lockForWrite() self.load_queue = collections.deque(ids) self.load_queue_lock.unlock() - - + + def run(self): while self.keep_running: if self.id_map is None or self.id_map_stale: @@ -89,7 +93,7 @@ class CoverCache(QThread): break finally: self.load_queue_lock.unlock() - + self.cache_lock.lockForRead() need = True if id in self.cache.keys(): @@ -116,19 +120,19 @@ class CoverCache(QThread): self.cache_lock.lockForWrite() self.cache[id] = img self.cache_lock.unlock() - + self.sleep(1) - + def stop(self): self.keep_running = False - + def cover(self, id): val = None if self.cache_lock.tryLockForRead(50): val = self.cache.get(id, None) self.cache_lock.unlock() return val - + def clear_cache(self): self.cache_lock.lockForWrite() self.cache = {} @@ -143,23 +147,23 @@ class CoverCache(QThread): for id in ids: self.load_queue.appendleft(id) self.load_queue_lock.unlock() - + class ResultCache(SearchQueryParser): - + ''' Stores sorted and filtered metadata in memory. ''' - + def __init__(self): self._map = self._map_filtered = self._data = [] SearchQueryParser.__init__(self) - + def __getitem__(self, row): return self._data[self._map_filtered[row]] - + def __len__(self): return len(self._map_filtered) - + def __iter__(self): for id in self._map_filtered: yield self._data[id] @@ -188,32 +192,32 @@ class ResultCache(SearchQueryParser): matches.add(item[0]) break return matches - + def remove(self, id): self._data[id] = None if id in self._map: self._map.remove(id) if id in self._map_filtered: self._map_filtered.remove(id) - + def set(self, row, col, val, row_is_id=False): - id = row if row_is_id else self._map_filtered[row] + id = row if row_is_id else self._map_filtered[row] self._data[id][col] = val - + def index(self, id, cache=False): x = self._map if cache else self._map_filtered return x.index(id) - + def row(self, id): return self.index(id) - + def has_id(self, id): try: return self._data[id] is not None except IndexError: pass return False - + def refresh_ids(self, conn, ids): ''' Refresh the data in the cache for books identified by ids. @@ -226,7 +230,7 @@ class ResultCache(SearchQueryParser): except ValueError: pass return None - + def books_added(self, ids, conn): if not ids: return @@ -235,16 +239,16 @@ class ResultCache(SearchQueryParser): self._data[id] = conn.get('SELECT * from meta WHERE id=?', (id,))[0] self._map[0:0] = ids self._map_filtered[0:0] = ids - + def books_deleted(self, ids): for id in ids: self._data[id] = None if id in self._map: self._map.remove(id) if id in self._map_filtered: self._map_filtered.remove(id) - + def count(self): return len(self._map) - + def refresh(self, db, field=None, ascending=True): temp = db.conn.get('SELECT * FROM meta') self._data = list(itertools.repeat(None, temp[-1][0]+2)) if temp else [] @@ -254,7 +258,7 @@ class ResultCache(SearchQueryParser): if field is not None: self.sort(field, ascending) self._map_filtered = list(self._map) - + def seriescmp(self, x, y): try: ans = cmp(self._data[x][9].lower(), self._data[y][9].lower()) if str else\ @@ -263,7 +267,7 @@ class ResultCache(SearchQueryParser): ans = cmp(self._data[x][9], self._data[y][9]) if ans != 0: return ans return cmp(self._data[x][10], self._data[y][10]) - + def cmp(self, loc, x, y, str=True): try: ans = cmp(self._data[x][loc].lower(), self._data[y][loc].lower()) if str else\ @@ -272,7 +276,7 @@ class ResultCache(SearchQueryParser): ans = cmp(self._data[x][loc], self._data[y][loc]) if ans != 0: return ans return cmp(self._data[x][11].lower(), self._data[y][11].lower()) - + def sort(self, field, ascending): field = field.lower().strip() if field in ('author', 'tag', 'comment'): @@ -281,28 +285,28 @@ class ResultCache(SearchQueryParser): elif field == 'title': field = 'sort' elif field == 'authors': field = 'author_sort' fcmp = self.seriescmp if field == 'series' else \ - functools.partial(self.cmp, FIELD_MAP[field], + functools.partial(self.cmp, FIELD_MAP[field], str=field not in ('size', 'rating', 'timestamp')) - + self._map.sort(cmp=fcmp, reverse=not ascending) self._map_filtered = [id for id in self._map if id in self._map_filtered] - + def search(self, query): if not query or not query.strip(): self._map_filtered = list(self._map) return matches = sorted(self.parse(query)) self._map_filtered = [id for id in self._map if id in matches] - - + + class Tag(unicode): - + def __new__(cls, *args): obj = super(Tag, cls).__new__(cls, *args) obj.count = 0 obj.state = 0 return obj - + def as_string(self): return u'[%d] %s'%(self.count, self) @@ -314,16 +318,16 @@ class LibraryDatabase2(LibraryDatabase): @dynamic_property def user_version(self): doc = 'The user version of this database' - + def fget(self): return self.conn.get('pragma user_version;', all=False) - + def fset(self, val): self.conn.execute('pragma user_version=%d'%int(val)) self.conn.commit() - + return property(doc=doc, fget=fget, fset=fset) - + def connect(self): if 'win32' in sys.platform and len(self.library_path) + 4*self.PATH_LIMIT + 10 > 259: raise ValueError('Path to library too long. Must be less than %d characters.'%(259-4*self.PATH_LIMIT-10)) @@ -333,9 +337,9 @@ class LibraryDatabase2(LibraryDatabase): self.conn.close() os.remove(self.dbpath) self.conn = connect(self.dbpath, self.row_factory) - if self.user_version == 0: + if self.user_version == 0: self.initialize_database() - + def __init__(self, library_path, row_factory=False): if not os.path.exists(library_path): os.makedirs(library_path) @@ -348,7 +352,7 @@ class LibraryDatabase2(LibraryDatabase): self.connect() self.is_case_sensitive = not iswindows and not isosx and \ not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB')) - # Upgrade database + # Upgrade database while True: meth = getattr(self, 'upgrade_version_%d'%self.user_version, None) if meth is None: @@ -358,7 +362,7 @@ class LibraryDatabase2(LibraryDatabase): meth() self.conn.commit() self.user_version += 1 - + self.data = ResultCache() self.search = self.data.search self.refresh = functools.partial(self.data.refresh, self) @@ -368,22 +372,24 @@ class LibraryDatabase2(LibraryDatabase): self.row = self.data.row self.has_id = self.data.has_id self.count = self.data.count - + self.refresh() - + def get_property(idx, index_is_id=False, loc=-1): row = self.data._data[idx] if index_is_id else self.data[idx] return row[loc] - - for prop in ('author_sort', 'authors', 'comment', 'comments', 'isbn', - 'publisher', 'rating', 'series', 'series_index', 'tags', 'title'): - setattr(self, prop, functools.partial(get_property, loc=FIELD_MAP['comments' if prop == 'comment' else prop])) - + + for prop in ('author_sort', 'authors', 'comment', 'comments', 'isbn', + 'publisher', 'rating', 'series', 'series_index', 'tags', + 'title', 'timestamp'): + setattr(self, prop, functools.partial(get_property, + loc=FIELD_MAP['comments' if prop == 'comment' else prop])) + def initialize_database(self): from calibre.resources import metadata_sqlite self.conn.executescript(metadata_sqlite) self.user_version = 1 - + def upgrade_version_1(self): ''' Normalize indices. @@ -395,7 +401,7 @@ class LibraryDatabase2(LibraryDatabase): CREATE INDEX series_idx ON series (name COLLATE NOCASE); CREATE INDEX series_sort_idx ON books (series_index, id); ''')) - + def upgrade_version_2(self): ''' Fix Foreign key constraints for deleting from link tables. ''' script = textwrap.dedent('''\ @@ -414,7 +420,7 @@ class LibraryDatabase2(LibraryDatabase): self.conn.executescript(script%dict(ltable='publishers', table='publishers', ltable_col='publisher')) self.conn.executescript(script%dict(ltable='tags', table='tags', ltable_col='tag')) self.conn.executescript(script%dict(ltable='series', table='series', ltable_col='series')) - + def upgrade_version_3(self): ' Add path to result cache ' self.conn.executescript(''' @@ -438,25 +444,25 @@ class LibraryDatabase2(LibraryDatabase): FROM books; ''') - + def last_modified(self): ''' Return last modified time as a UTC datetime object''' return datetime.utcfromtimestamp(os.stat(self.dbpath).st_mtime) - + def path(self, index, index_is_id=False): 'Return the relative path to the directory containing this books files as a unicode string.' row = self.data._data[index] if index_is_id else self.data[index] return row[FIELD_MAP['path']].replace('/', os.sep) - - + + def abspath(self, index, index_is_id=False): 'Return the absolute path to the directory containing this books files as a unicode string.' path = os.path.join(self.library_path, self.path(index, index_is_id=index_is_id)) if not os.path.exists(path): os.makedirs(path) return path - - + + def construct_path_name(self, id): ''' Construct the directory name for this book based on its metadata. @@ -468,7 +474,7 @@ class LibraryDatabase2(LibraryDatabase): title = sanitize_file_name(self.title(id, index_is_id=True)[:self.PATH_LIMIT]).decode(filesystem_encoding, 'ignore') path = author + '/' + title + ' (%d)'%id return path - + def construct_file_name(self, id): ''' Construct the file name for this book based on its metadata. @@ -480,17 +486,17 @@ class LibraryDatabase2(LibraryDatabase): title = sanitize_file_name(self.title(id, index_is_id=True)[:self.PATH_LIMIT]).decode(filesystem_encoding, 'replace') name = title + ' - ' + author return name - + def rmtree(self, path): if not self.normpath(self.library_path).startswith(self.normpath(path)): shutil.rmtree(path) - + def normpath(self, path): path = os.path.abspath(os.path.realpath(path)) if not self.is_case_sensitive: path = path.lower() return path - + def set_path(self, index, index_is_id=False): ''' Set the path to the directory containing this books files based on its @@ -512,12 +518,12 @@ class LibraryDatabase2(LibraryDatabase): break if path == current_path and not changed: return - + tpath = os.path.join(self.library_path, *path.split('/')) if not os.path.exists(tpath): os.makedirs(tpath) spath = os.path.join(self.library_path, *current_path.split('/')) - + if current_path and os.path.exists(spath): # Migrate existing files cdata = self.cover(id, index_is_id=True) if cdata is not None: @@ -539,14 +545,14 @@ class LibraryDatabase2(LibraryDatabase): parent = os.path.dirname(spath) if len(os.listdir(parent)) == 0: self.rmtree(parent) - + def add_listener(self, listener): ''' Add a listener. Will be called on change events with two arguments. Event name and list of affected ids. ''' self.listeners.add(listener) - + def notify(self, event, ids=[]): 'Notify all listeners' for listener in self.listeners: @@ -555,24 +561,52 @@ class LibraryDatabase2(LibraryDatabase): except: traceback.print_exc() continue - - def cover(self, index, index_is_id=False, as_file=False, as_image=False): + + def cover(self, index, index_is_id=False, as_file=False, as_image=False, + as_path=False): ''' Return the cover image as a bytestring (in JPEG format) or None. - + `as_file` : If True return the image as an open file object `as_image`: If True return the image as a QImage object ''' id = index if index_is_id else self.id(index) path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg') if os.access(path, os.R_OK): + if as_path: + return path f = open(path, 'rb') if as_image: img = QImage() img.loadFromData(f.read()) return img return f if as_file else f.read() - + + def get_metadata(self, idx, index_is_id=False, get_cover=False): + ''' + Convenience method to return metadata as a L{MetaInformation} object. + ''' + aum = self.authors(idx, index_is_id=index_is_id) + if aum: aum = [a.strip().replace('|', ',') for a in aum.split(',')] + mi = MetaInformation(self.title(idx, index_is_id=index_is_id), aum) + mi.author_sort = self.author_sort(idx, index_is_id=index_is_id) + mi.comments = self.comments(idx, index_is_id=index_is_id) + mi.publisher = self.publisher(idx, index_is_id=index_is_id) + mi.timestamp = self.timestamp(idx, index_is_id=index_is_id) + tags = self.tags(idx, index_is_id=index_is_id) + if tags: + mi.tags = [i.strip() for i in tags.split(',')] + mi.series = self.series(idx, index_is_id=index_is_id) + if mi.series: + mi.series_index = self.series_index(idx, index_is_id=index_is_id) + mi.rating = self.rating(idx, index_is_id=index_is_id) + mi.isbn = self.isbn(idx, index_is_id=index_is_id) + id = idx if index_is_id else self.id(idx) + mi.application_id = id + if get_cover: + mi.cover = self.cover(id, index_is_id=True, as_path=True) + return mi + def has_book(self, mi): title = mi.title if title: @@ -580,16 +614,16 @@ class LibraryDatabase2(LibraryDatabase): title = title.decode(preferred_encoding, 'replace') return bool(self.conn.get('SELECT id FROM books where title=?', (title,), all=False)) return False - + def has_cover(self, index, index_is_id=False): id = index if index_is_id else self.id(index) path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg') return os.access(path, os.R_OK) - + def set_cover(self, id, data): ''' Set the cover for this book. - + `data`: Can be either a QImage, QPixmap, file object or bytestring ''' path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg') @@ -599,18 +633,18 @@ class LibraryDatabase2(LibraryDatabase): if not QCoreApplication.instance(): global __app __app = QApplication([]) - p = QPixmap() + p = QImage() if callable(getattr(data, 'read', None)): data = data.read() p.loadFromData(data) p.save(path) - + def all_formats(self): formats = self.conn.get('SELECT format from data') if not formats: return set([]) return set([f[0] for f in formats]) - + def formats(self, index, index_is_id=False): ''' Return available formats as a comma separated list or None if there are no available formats ''' id = index if index_is_id else self.id(index) @@ -627,7 +661,7 @@ class LibraryDatabase2(LibraryDatabase): if os.access(os.path.join(path, name+_format), os.R_OK|os.W_OK): ans.append(format) return ','.join(ans) - + def has_format(self, index, format, index_is_id=False): id = index if index_is_id else self.id(index) name = self.conn.get('SELECT name FROM data WHERE book=? AND format=?', (id, format), all=False) @@ -637,7 +671,7 @@ class LibraryDatabase2(LibraryDatabase): path = os.path.join(path, name+format) return os.access(path, os.R_OK|os.W_OK) return False - + def format_abspath(self, index, format, index_is_id=False): 'Return absolute path to the ebook file of format `format`' id = index if index_is_id else self.id(index) @@ -648,13 +682,13 @@ class LibraryDatabase2(LibraryDatabase): path = os.path.join(path, name+format) if os.access(path, os.R_OK|os.W_OK): return path - + def format(self, index, format, index_is_id=False, as_file=False, mode='r+b'): ''' Return the ebook format as a bytestring or `None` if the format doesn't exist, - or we don't have permission to write to the ebook file. - - `as_file`: If True the ebook format is returned as a file object opened in `mode` + or we don't have permission to write to the ebook file. + + `as_file`: If True the ebook format is returned as a file object opened in `mode` ''' path = self.format_abspath(index, format, index_is_id=index_is_id) if path is not None: @@ -662,14 +696,14 @@ class LibraryDatabase2(LibraryDatabase): return f if as_file else f.read() if self.has_format(index, format, index_is_id): self.remove_format(id, format, index_is_id=True) - - def add_format_with_hooks(self, index, format, fpath, index_is_id=False, + + def add_format_with_hooks(self, index, format, fpath, index_is_id=False, path=None, notify=True): npath = self.run_import_plugins(fpath, format) format = os.path.splitext(npath)[-1].lower().replace('.', '').upper() - return self.add_format(index, format, open(npath, 'rb'), + return self.add_format(index, format, open(npath, 'rb'), index_is_id=index_is_id, path=path, notify=notify) - + def add_format(self, index, format, stream, index_is_id=False, path=None, notify=True): id = index if index_is_id else self.id(index) if path is None: @@ -693,7 +727,7 @@ class LibraryDatabase2(LibraryDatabase): self.refresh_ids([id]) if notify: self.notify('metadata', [id]) - + def delete_book(self, id): ''' Removes book from the result cache and the underlying database. @@ -710,7 +744,7 @@ class LibraryDatabase2(LibraryDatabase): self.clean() self.data.books_deleted([id]) self.notify('delete', [id]) - + def remove_format(self, index, format, index_is_id=False, notify=True): id = index if index_is_id else self.id(index) path = os.path.join(self.library_path, *self.path(id, index_is_id=True).split(os.sep)) @@ -727,7 +761,7 @@ class LibraryDatabase2(LibraryDatabase): self.refresh_ids([id]) if notify: self.notify('metadata', [id]) - + def clean(self): ''' Remove orphaned entries. @@ -738,13 +772,13 @@ class LibraryDatabase2(LibraryDatabase): self.conn.execute(st%dict(ltable='tags', table='tags', ltable_col='tag')) self.conn.execute(st%dict(ltable='series', table='series', ltable_col='series')) self.conn.commit() - + def get_recipes(self): return self.conn.get('SELECT id, script FROM feeds') - + def get_recipe(self, id): return self.conn.get('SELECT script FROM feeds WHERE id=?', (id,), all=False) - + def get_categories(self, sort_on_count=False): categories = {} def get(name, category, field='name'): @@ -766,11 +800,11 @@ class LibraryDatabase2(LibraryDatabase): for tag in tags: tag.count = self.conn.get('SELECT COUNT(format) FROM data WHERE format=?', (tag,), all=False) tags.sort(reverse=sort_on_count, cmp=(lambda x,y:cmp(x.count,y.count)) if sort_on_count else cmp) - for x in (('authors', 'author'), ('tags', 'tag'), ('publishers', 'publisher'), + for x in (('authors', 'author'), ('tags', 'tag'), ('publishers', 'publisher'), ('series', 'series')): get(*x) get('data', 'format', 'format') - + categories['news'] = [] newspapers = self.conn.get('SELECT name FROM tags WHERE id IN (SELECT DISTINCT tag FROM books_tags_link WHERE book IN (select book from books_tags_link where tag IN (SELECT id FROM tags WHERE name=?)))', (_('News'),)) if newspapers: @@ -782,10 +816,10 @@ class LibraryDatabase2(LibraryDatabase): categories['news'] = list(map(Tag, newspapers)) for tag in categories['news']: tag.count = self.conn.get('SELECT COUNT(id) FROM books_tags_link WHERE tag IN (SELECT DISTINCT id FROM tags WHERE name=?)', (tag,), all=False) - + return categories - - + + def tags_older_than(self, tag, delta): tag = tag.lower().strip() now = datetime.now() @@ -795,9 +829,9 @@ class LibraryDatabase2(LibraryDatabase): tags = r[FIELD_MAP['tags']] if tags and tag in tags.lower(): yield r[FIELD_MAP['id']] - - - + + + def set(self, row, column, val): ''' Convenience method for setting the title, authors, publisher or rating @@ -820,10 +854,10 @@ class LibraryDatabase2(LibraryDatabase): self.data.refresh_ids(self.conn, [id]) self.set_path(id, True) self.notify('metadata', [id]) - + def set_metadata(self, id, mi): ''' - Set metadata for the book `id` from the `MetaInformation` object `mi` + Set metadata for the book `id` from the `MetaInformation` object `mi` ''' if mi.title: self.set_title(id, mi.title) @@ -853,9 +887,11 @@ class LibraryDatabase2(LibraryDatabase): self.set_isbn(id, mi.isbn, notify=False) if mi.series_index and mi.series_index > 0: self.set_series_index(id, mi.series_index, notify=False) + if getattr(mi, 'timestamp', None) is not None: + self.set_timestamp(id, mi.timestamp, notify=False) self.set_path(id, True) self.notify('metadata', [id]) - + def set_authors(self, id, authors, notify=True): ''' `authors`: A list of authors. @@ -883,11 +919,11 @@ class LibraryDatabase2(LibraryDatabase): pass self.conn.commit() self.data.set(id, FIELD_MAP['authors'], ','.join([a.replace(',', '|') for a in authors]), row_is_id=True) - self.data.set(id, FIELD_MAP['author_sort'], self.data[self.data.row(id)][FIELD_MAP['authors']], row_is_id=True) + self.data.set(id, FIELD_MAP['author_sort'], self.data[self.data.row(id)][FIELD_MAP['authors']], row_is_id=True) self.set_path(id, True) if notify: self.notify('metadata', [id]) - + def set_title(self, id, title, notify=True): if not title: return @@ -900,7 +936,7 @@ class LibraryDatabase2(LibraryDatabase): self.conn.commit() if notify: self.notify('metadata', [id]) - + def set_timestamp(self, id, dt, notify=True): if dt: self.conn.execute('UPDATE books SET timestamp=? WHERE id=?', (dt, id)) @@ -908,7 +944,7 @@ class LibraryDatabase2(LibraryDatabase): self.conn.commit() if notify: self.notify('metadata', [id]) - + def set_publisher(self, id, publisher, notify=True): self.conn.execute('DELETE FROM books_publishers_link WHERE book=?',(id,)) self.conn.execute('DELETE FROM publishers WHERE (SELECT COUNT(id) FROM books_publishers_link WHERE publisher=publishers.id) < 1') @@ -925,7 +961,7 @@ class LibraryDatabase2(LibraryDatabase): self.data.set(id, FIELD_MAP['publisher'], publisher, row_is_id=True) if notify: self.notify('metadata', [id]) - + def set_tags(self, id, tags, append=False, notify=True): ''' @param tags: list of strings @@ -969,7 +1005,7 @@ class LibraryDatabase2(LibraryDatabase): self.data.set(id, FIELD_MAP['tags'], tags, row_is_id=True) if notify: self.notify('metadata', [id]) - + def unapply_tags(self, book_id, tags, notify=True): for tag in tags: id = self.conn.get('SELECT id FROM tags WHERE name=?', (tag,), all=False) @@ -979,7 +1015,7 @@ class LibraryDatabase2(LibraryDatabase): self.data.refresh_ids(self.conn, [book_id]) if notify: self.notify('metadata', [id]) - + def is_tag_used(self, tag): existing_tags = self.all_tags() lt = [t.lower() for t in existing_tags] @@ -988,7 +1024,7 @@ class LibraryDatabase2(LibraryDatabase): return True except ValueError: return False - + def delete_tag(self, tag): existing_tags = self.all_tags() lt = [t.lower() for t in existing_tags] @@ -1003,7 +1039,7 @@ class LibraryDatabase2(LibraryDatabase): self.conn.execute('DELETE FROM tags WHERE id=?', (id,)) self.conn.commit() - + def set_series(self, id, series, notify=True): self.conn.execute('DELETE FROM books_series_link WHERE book=?',(id,)) self.conn.execute('DELETE FROM series WHERE (SELECT COUNT(id) FROM books_series_link WHERE series=series.id) < 1') @@ -1026,7 +1062,7 @@ class LibraryDatabase2(LibraryDatabase): self.data.set(id, FIELD_MAP['series'], series, row_is_id=True) if notify: self.notify('metadata', [id]) - + def set_series_index(self, id, idx, notify=True): if idx is None: idx = 1 @@ -1042,7 +1078,7 @@ class LibraryDatabase2(LibraryDatabase): self.data.set(id, FIELD_MAP['series_index'], int(idx), row_is_id=True) if notify: self.notify('metadata', [id]) - + def set_rating(self, id, rating, notify=True): rating = int(rating) self.conn.execute('DELETE FROM books_ratings_link WHERE book=?',(id,)) @@ -1053,7 +1089,7 @@ class LibraryDatabase2(LibraryDatabase): self.data.set(id, FIELD_MAP['rating'], rating, row_is_id=True) if notify: self.notify('metadata', [id]) - + def set_comment(self, id, text, notify=True): self.conn.execute('DELETE FROM comments WHERE book=?', (id,)) self.conn.execute('INSERT INTO comments(book,text) VALUES (?,?)', (id, text)) @@ -1061,21 +1097,21 @@ class LibraryDatabase2(LibraryDatabase): self.data.set(id, FIELD_MAP['comments'], text, row_is_id=True) if notify: self.notify('metadata', [id]) - + def set_author_sort(self, id, sort, notify=True): self.conn.execute('UPDATE books SET author_sort=? WHERE id=?', (sort, id)) self.conn.commit() self.data.set(id, FIELD_MAP['author_sort'], sort, row_is_id=True) if notify: self.notify('metadata', [id]) - + def set_isbn(self, id, isbn, notify=True): self.conn.execute('UPDATE books SET isbn=? WHERE id=?', (isbn, id)) self.conn.commit() self.data.set(id, FIELD_MAP['isbn'], isbn, row_is_id=True) if notify: self.notify('metadata', [id]) - + def add_news(self, path, recipe): format = os.path.splitext(path)[1][1:].lower() stream = path if hasattr(path, 'read') else open(path, 'rb') @@ -1084,21 +1120,21 @@ class LibraryDatabase2(LibraryDatabase): stream.seek(0) mi.series_index = 1 mi.tags = [_('News'), recipe.title] - obj = self.conn.execute('INSERT INTO books(title, author_sort) VALUES (?, ?)', + obj = self.conn.execute('INSERT INTO books(title, author_sort) VALUES (?, ?)', (mi.title, mi.authors[0])) id = obj.lastrowid self.data.books_added([id], self.conn) self.set_path(id, index_is_id=True) self.conn.commit() self.set_metadata(id, mi) - + self.add_format(id, format, stream, index_is_id=True) if not hasattr(path, 'read'): stream.close() self.conn.commit() self.data.refresh_ids(self.conn, [id]) # Needed to update format list and size return id - + def run_import_plugins(self, path_or_stream, format): format = format.lower() if hasattr(path_or_stream, 'seek'): @@ -1110,11 +1146,11 @@ class LibraryDatabase2(LibraryDatabase): else: path = path_or_stream return run_plugins_on_import(path, format) - + def add_books(self, paths, formats, metadata, uris=[], add_duplicates=True): ''' Add a book to the database. The result cache is not updated. - @param paths: List of paths to book files or file-like objects + :param:`paths` List of paths to book files or file-like objects ''' formats, metadata, uris = iter(formats), iter(metadata), iter(uris) duplicates = [] @@ -1136,7 +1172,7 @@ class LibraryDatabase2(LibraryDatabase): aus = aus.decode(preferred_encoding, 'replace') if isinstance(title, str): title = title.decode(preferred_encoding) - obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)', + obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)', (title, uri, series_index, aus)) id = obj.lastrowid self.data.books_added([id], self.conn) @@ -1152,19 +1188,19 @@ class LibraryDatabase2(LibraryDatabase): self.conn.commit() self.data.refresh_ids(self.conn, ids) # Needed to update format list and size if duplicates: - paths = tuple(duplicate[0] for duplicate in duplicates) - formats = tuple(duplicate[1] for duplicate in duplicates) - metadata = tuple(duplicate[2] for duplicate in duplicates) - uris = tuple(duplicate[3] for duplicate in duplicates) + paths = list(duplicate[0] for duplicate in duplicates) + formats = list(duplicate[1] for duplicate in duplicates) + metadata = list(duplicate[2] for duplicate in duplicates) + uris = list(duplicate[3] for duplicate in duplicates) return (paths, formats, metadata, uris), len(ids) return None, len(ids) - - def import_book(self, mi, formats): + + def import_book(self, mi, formats, notify=True): series_index = 1 if mi.series_index is None else mi.series_index if not mi.authors: - mi.authors = ['Unknown'] + mi.authors = [_('Unknown')] aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors) - obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)', + obj = self.conn.execute('INSERT INTO books(title, uri, series_index, author_sort) VALUES (?, ?, ?, ?)', (mi.title, None, series_index, aus)) id = obj.lastrowid self.data.books_added([id], self.conn) @@ -1172,12 +1208,15 @@ class LibraryDatabase2(LibraryDatabase): self.set_metadata(id, mi) for path in formats: ext = os.path.splitext(path)[1][1:].lower() + if ext == 'opf': + continue stream = open(path, 'rb') self.add_format(id, ext, stream, index_is_id=True) self.conn.commit() self.data.refresh_ids(self.conn, [id]) # Needed to update format list and size - self.notify('add', [id]) - + if notify: + self.notify('add', [id]) + def move_library_to(self, newloc, progress=None): header = _(u'

Copying books to %s

')%newloc books = self.conn.get('SELECT id, path, title FROM books') @@ -1206,7 +1245,7 @@ class LibraryDatabase2(LibraryDatabase): old_dirs.add(srcdir) if progress is not None: progress.setValue(i+1) - + dbpath = os.path.join(newloc, os.path.basename(self.dbpath)) shutil.copyfile(self.dbpath, dbpath) opath = self.dbpath @@ -1222,22 +1261,22 @@ class LibraryDatabase2(LibraryDatabase): if progress is not None: progress.reset() progress.hide() - - + + def __iter__(self): for record in self.data._data: if record is not None: yield record - + def all_ids(self): for i in iter(self): yield i['id'] - + def get_data_as_dict(self, prefix=None, authors_as_string=False): ''' Return all metadata stored in the database as a dict. Includes paths to the cover and each format. - + :param prefix: The prefix for all paths. By default, the prefix is the absolute path to the library folder. ''' @@ -1268,9 +1307,9 @@ class LibraryDatabase2(LibraryDatabase): x['formats'].append(path%fmt.lower()) x['fmt_'+fmt.lower()] = path%fmt.lower() x['available_formats'] = [i.upper() for i in formats.split(',')] - + return data - + def migrate_old(self, db, progress): header = _(u'

Migrating old database to ebook library in %s

')%self.library_path progress.setValue(0) @@ -1281,23 +1320,23 @@ class LibraryDatabase2(LibraryDatabase): books = db.conn.get('SELECT id, title, sort, timestamp, uri, series_index, author_sort, isbn FROM books ORDER BY id ASC') progress.setAutoReset(False) progress.setRange(0, len(books)) - + for book in books: self.conn.execute('INSERT INTO books(id, title, sort, timestamp, uri, series_index, author_sort, isbn) VALUES(?, ?, ?, ?, ?, ?, ?, ?);', book) - + tables = ''' -authors ratings tags series books_tags_link +authors ratings tags series books_tags_link comments publishers -books_authors_link conversion_options -books_publishers_link -books_ratings_link +books_authors_link conversion_options +books_publishers_link +books_ratings_link books_series_link feeds '''.split() for table in tables: - rows = db.conn.get('SELECT * FROM %s ORDER BY id ASC'%table) + rows = db.conn.get('SELECT * FROM %s ORDER BY id ASC'%table) for row in rows: self.conn.execute('INSERT INTO %s VALUES(%s)'%(table, ','.join(repeat('?', len(row)))), row) - + self.conn.commit() self.refresh('timestamp', True) for i, book in enumerate(books): @@ -1322,5 +1361,191 @@ books_series_link feeds self.vacuum() progress.reset() return len(books) - - \ No newline at end of file + + def export_to_dir(self, dir, indices, byauthor=False, single_dir=False, + index_is_id=False, callback=None): + if not os.path.exists(dir): + raise IOError('Target directory does not exist: '+dir) + by_author = {} + count = 0 + for index in indices: + id = index if index_is_id else self.id(index) + au = self.conn.get('SELECT author_sort FROM books WHERE id=?', + (id,), all=False) + if not au: + au = self.authors(index, index_is_id=index_is_id) + if not au: + au = _('Unknown') + au = au.split(',')[0] + if not by_author.has_key(au): + by_author[au] = [] + by_author[au].append(index) + for au in by_author.keys(): + apath = os.path.join(dir, sanitize_file_name(au)) + if not single_dir and not os.path.exists(apath): + os.mkdir(apath) + for idx in by_author[au]: + title = re.sub(r'\s', ' ', self.title(idx, index_is_id=index_is_id)) + tpath = os.path.join(apath, sanitize_file_name(title)) + id = idx if index_is_id else self.id(idx) + id = str(id) + if not single_dir and not os.path.exists(tpath): + os.mkdir(tpath) + + name = au + ' - ' + title if byauthor else title + ' - ' + au + name += '_'+id + base = dir if single_dir else tpath + mi = self.get_metadata(idx, index_is_id=index_is_id, get_cover=True) + f = open(os.path.join(base, sanitize_file_name(name)+'.opf'), 'wb') + if not mi.authors: + mi.authors = [_('Unknown')] + cdata = self.cover(int(id), index_is_id=True) + if cdata is not None: + cname = sanitize_file_name(name)+'.jpg' + open(os.path.join(base, cname), 'wb').write(cdata) + mi.cover = cname + opf = OPFCreator(base, mi) + opf.render(f) + f.close() + + fmts = self.formats(idx, index_is_id=index_is_id) + if not fmts: + fmts = '' + for fmt in fmts.split(','): + data = self.format(idx, fmt, index_is_id=index_is_id) + if not data: + continue + fname = name +'.'+fmt.lower() + fname = sanitize_file_name(fname) + f = open(os.path.join(base, fname), 'w+b') + f.write(data) + f.flush() + f.seek(0) + try: + set_metadata(f, mi, fmt.lower()) + except: + pass + f.close() + count += 1 + if callable(callback): + if not callback(count, mi.title): + return + + def export_single_format_to_dir(self, dir, indices, format, + index_is_id=False, callback=None): + dir = os.path.abspath(dir) + if not index_is_id: + indices = map(self.id, indices) + failures = [] + for count, id in enumerate(indices): + try: + data = self.format(id, format, index_is_id=True) + if not data: + failures.append((id, self.title(id, index_is_id=True))) + continue + except: + failures.append((id, self.title(id, index_is_id=True))) + continue + title = self.title(id, index_is_id=True) + au = self.authors(id, index_is_id=True) + if not au: + au = _('Unknown') + fname = '%s - %s.%s'%(title, au, format.lower()) + fname = sanitize_file_name(fname) + if not os.path.exists(dir): + os.makedirs(dir) + f = open(os.path.join(dir, fname), 'w+b') + f.write(data) + f.seek(0) + try: + set_metadata(f, self.get_metadata(id, index_is_id=True, get_cover=True), + stream_type=format.lower()) + except: + pass + f.close() + if callable(callback): + if not callback(count, title): + break + return failures + + def find_books_in_directory(self, dirpath, single_book_per_directory): + dirpath = os.path.abspath(dirpath) + if single_book_per_directory: + formats = [] + for path in os.listdir(dirpath): + path = os.path.abspath(os.path.join(dirpath, path)) + if os.path.isdir(path) or not os.access(path, os.R_OK): + continue + ext = os.path.splitext(path)[1] + if not ext: + continue + ext = ext[1:].lower() + if ext not in BOOK_EXTENSIONS and ext != 'opf': + continue + formats.append(path) + yield formats + else: + books = {} + for path in os.listdir(dirpath): + path = os.path.abspath(os.path.join(dirpath, path)) + if os.path.isdir(path) or not os.access(path, os.R_OK): + continue + ext = os.path.splitext(path)[1] + if not ext: + continue + ext = ext[1:].lower() + if ext not in BOOK_EXTENSIONS: + continue + + key = os.path.splitext(path)[0] + if not books.has_key(key): + books[key] = [] + books[key].append(path) + + for formats in books.values(): + yield formats + + def import_book_directory_multiple(self, dirpath, callback=None): + duplicates = [] + for formats in self.find_books_in_directory(dirpath, False): + mi = metadata_from_formats(formats) + if mi.title is None: + continue + if self.has_book(mi): + duplicates.append((mi, formats)) + continue + self.import_book(mi, formats) + if callable(callback): + if callback(mi.title): + break + return duplicates + + def import_book_directory(self, dirpath, callback=None): + dirpath = os.path.abspath(dirpath) + formats = self.find_books_in_directory(dirpath, True) + if not formats: + return + + mi = metadata_from_formats(formats) + if mi.title is None: + return + if self.has_book(mi): + return [(mi, formats)] + self.import_book(mi, formats) + if callable(callback): + callback(mi.title) + + def recursive_import(self, root, single_book_per_directory=True, callback=None): + root = os.path.abspath(root) + duplicates = [] + for dirpath in os.walk(root): + res = self.import_book_directory(dirpath[0], callback=callback) if \ + single_book_per_directory else \ + self.import_book_directory_multiple(dirpath[0], callback=callback) + if res is not None: + duplicates.extend(res) + if callable(callback): + if callback(''): + break + + return duplicates diff --git a/src/calibre/trac/donations/server.py b/src/calibre/trac/donations/server.py index 0141c6a317..d7ffde63e2 100644 --- a/src/calibre/trac/donations/server.py +++ b/src/calibre/trac/donations/server.py @@ -236,7 +236,9 @@ Donors per day: %(dpd).2f ml = mdates.MonthLocator() # every month fig = plt.figure(1, (8, 4), 96)#, facecolor, edgecolor, frameon, FigureClass) ax = fig.add_subplot(111) + average = sum(y)/len(y) ax.bar(x, y, align='center', width=20, color='g') + ax.hlines([average], x[0], x[-1]) ax.xaxis.set_major_locator(ml) ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %y')) ax.set_xlim(_months[0].min-timedelta(days=15), _months[-1].min+timedelta(days=15)) diff --git a/src/calibre/trac/plugins/download.py b/src/calibre/trac/plugins/download.py index 0fee4fdb0d..7abf7faed4 100644 --- a/src/calibre/trac/plugins/download.py +++ b/src/calibre/trac/plugins/download.py @@ -30,11 +30,12 @@ class Distribution(object): ('libusb', '0.1.12', None, None, None), ('Qt', '4.4.0', 'qt', 'libqt4-core libqt4-gui', 'qt4'), ('PyQt', '4.4.2', 'PyQt4', 'python-qt4', 'PyQt4'), - ('mechanize for python', '0.1.8', 'dev-python/mechanize', 'python-mechanize', 'python-mechanize'), + ('mechanize for python', '0.1.11', 'dev-python/mechanize', 'python-mechanize', 'python-mechanize'), ('ImageMagick', '6.3.5', 'imagemagick', 'imagemagick', 'ImageMagick'), ('xdg-utils', '1.0.2', 'xdg-utils', 'xdg-utils', 'xdg-utils'), ('dbus-python', '0.82.2', 'dbus-python', 'python-dbus', 'dbus-python'), ('lxml', '2.0.5', 'lxml', 'python-lxml', 'python-lxml'), + ('python-dateutil', '1.4.1', 'python-dateutil', 'python-dateutil', 'python-dateutil'), ('BeautifulSoup', '3.0.5', 'beautifulsoup', 'python-beautifulsoup', 'python-BeautifulSoup'), ('help2man', '1.36.4', 'help2man', 'help2man', 'help2man'), ] diff --git a/src/calibre/translations/ar.po b/src/calibre/translations/ar.po index f615c21206..2c62c5cd3c 100644 --- a/src/calibre/translations/ar.po +++ b/src/calibre/translations/ar.po @@ -7,14 +7,14 @@ msgid "" msgstr "" "Project-Id-Version: calibre\n" "Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2009-01-27 01:54+0000\n" +"POT-Creation-Date: 2009-02-08 21:09+0000\n" "PO-Revision-Date: 2009-02-04 10:04+0000\n" -"Last-Translator: Abdellah Chelli \n" +"Last-Translator: عبد الله شلي (Abdellah Chelli) \n" "Language-Team: Arabic \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n" +"X-Launchpad-Export-Date: 2009-02-09 05:36+0000\n" "X-Generator: Launchpad (build Unknown)\n" #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 @@ -23,9 +23,10 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:44 #: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_any.py:71 -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:497 -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:989 -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:1002 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:498 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:1014 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:1030 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:1032 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:77 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:79 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:81 @@ -42,8 +43,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:199 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:229 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:232 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:255 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:277 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:261 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:283 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:45 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:47 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:87 @@ -51,30 +52,33 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/mobi.py:145 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf.py:334 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf.py:449 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:820 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:849 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdf.py:12 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:36 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:66 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:477 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:60 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:118 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:479 #: /home/kovid/work/calibre/src/calibre/ebooks/odt/to_oeb.py:46 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:361 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:366 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:858 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:861 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:569 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:574 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1150 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1153 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:53 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:48 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:168 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:170 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:365 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:37 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:172 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:366 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:33 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:38 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:39 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:121 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:362 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:376 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:899 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:700 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:942 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:945 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:900 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:717 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:962 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:965 #: /home/kovid/work/calibre/src/calibre/gui2/tools.py:61 #: /home/kovid/work/calibre/src/calibre/gui2/tools.py:123 #: /home/kovid/work/calibre/src/calibre/library/cli.py:257 @@ -83,10 +87,12 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/library/database.py:1434 #: /home/kovid/work/calibre/src/calibre/library/database.py:1468 #: /home/kovid/work/calibre/src/calibre/library/database.py:1594 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:473 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:485 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:828 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:861 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:466 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:478 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:831 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:864 +#: /home/kovid/work/calibre/src/calibre/library/server.py:315 +#: /home/kovid/work/calibre/src/calibre/web/feeds/news.py:51 msgid "Unknown" msgstr "مجهول" @@ -207,7 +213,7 @@ msgid "Disable the named plugin" msgstr "عطل الملحق المسمى" #: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:41 -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:384 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:385 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:70 msgid "The reader has no storage card connected." msgstr "" @@ -223,11 +229,11 @@ msgid "There is insufficient free space in main memory" msgstr "لا توجد مساحة كافية في الذاكرة الرئيسية" #: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:140 -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:167 -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:195 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:191 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:227 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:254 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:168 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:196 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:192 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:228 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:255 msgid "Unable to detect the %s disk drive. Try rebooting." msgstr "" @@ -241,24 +247,24 @@ msgid "" "name." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:107 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:108 msgid "" "Profile of the target device this EPUB is meant for. Set to None to create a " "device independent EPUB. The profile is used for device specific " "restrictions on the EPUB. Choices are: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:109 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:113 msgid "" "Either the path to a CSS stylesheet or raw CSS. This CSS will override any " "existing CSS declarations in the source files." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:110 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:117 msgid "Control auto-detection of document structure." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:112 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:122 msgid "" "An XPath expression to detect chapter titles. The default is to consider " "

or\n" @@ -272,7 +278,7 @@ msgid "" "help on using this feature.\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:121 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:132 msgid "" "Specify how to mark detected chapters. A value of \"pagebreak\" will insert " "page breaks before chapters. A value of \"rule\" will insert a line before " @@ -280,17 +286,23 @@ msgid "" "\"both\" will use both page breaks and lines to mark chapters." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:123 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:139 msgid "Path to the cover to be used for this book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:126 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:142 msgid "" "Use the cover detected from the source file in preference to the specified " "cover." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:128 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:145 +msgid "" +"Remove the first image from the input ebook. Useful if the first image in " +"the source file is a cover and you are specifying an external cover." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:149 msgid "" "Turn off splitting at page breaks. Normally, input files are automatically " "split at every page break into two files. This gives an output ebook that " @@ -299,7 +311,20 @@ msgid "" "turn off splitting on page breaks." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:130 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:157 +msgid "" +"XPath expression to detect page boundaries for building a custom pagination " +"map, as used by AdobeDE. Default is not to build an explicit pagination map." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:161 +msgid "" +"XPath expression to find the name of each page in the pagination map " +"relative to its boundary element. Default is to number all pages staring " +"with 1." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:165 msgid "" "Control the automatic generation of a Table of Contents. If an OPF file is " "detected\n" @@ -308,38 +333,45 @@ msgid "" "to auto-generate a Table of Contents.\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:136 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:171 msgid "" "Maximum number of links to insert into the TOC. Set to 0 to disable. Default " "is: %default. Links are only added to the TOC if less than the --toc-" "threshold number of chapters were detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:138 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:175 msgid "Don't add auto-detected chapters to the Table of Contents." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:140 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:177 msgid "" "If fewer than this number of chapters is detected, then links are added to " "the Table of Contents. Default: %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:142 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:180 msgid "" "XPath expression that specifies all tags that should be added to the Table " "of Contents at level one. If this is specified, it takes precedence over " "other forms of auto-detection." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:144 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:184 msgid "" "XPath expression that specifies all tags that should be added to the Table " "of Contents at level two. Each entry is added under the previous level one " "entry." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:146 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:188 +msgid "" +"XPath expression that specifies all tags that should be added to the Table " +"of Contents at level three. Each entry is added under the previous level two " +"entry." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:192 msgid "" "Path to a .ncx file that contains the table of contents to use for this " "ebook. The NCX file should contain links relative to the directory it is " @@ -347,65 +379,77 @@ msgid "" "an overview of the NCX format." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:148 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:198 msgid "" "Normally, if the source file already has a Table of Contents, it is used in " "preference to the autodetected one. With this option, the autodetected one " "is always used." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:150 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:202 msgid "Control page layout" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:152 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:204 msgid "Set the top margin in pts. Default is %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:154 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:206 msgid "Set the bottom margin in pts. Default is %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:156 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:208 msgid "Set the left margin in pts. Default is %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:158 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:210 msgid "Set the right margin in pts. Default is %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:160 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:212 msgid "" "The base font size in pts. Default is %defaultpt. Set to 0 to disable " "rescaling of fonts." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:162 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:215 msgid "" -"Remove spacing between paragraphs. Will not work if the source file forces " -"inter-paragraph spacing." +"Remove spacing between paragraphs. Also sets a indent on paragraphs of " +"1.5em. You can override this by adding p {text-indent: 0cm} to --override-" +"css. Spacing removal will not work if the source file forces inter-paragraph " +"spacing." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:164 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:221 +msgid "Do not force text to be justified in output." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:223 +msgid "" +"Remove table markup, converting it into paragraphs. This is useful if your " +"source file uses a table to manage layout." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:226 msgid "" "Preserve the HTML tag structure while splitting large HTML files. This is " "only neccessary if the HTML files contain CSS that uses sibling selectors. " "Enabling this greatly slows down processing of large HTML files." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:167 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:232 msgid "Print generated OPF file to stdout" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:169 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:234 msgid "Print generated NCX file to stdout" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:171 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:237 msgid "Keep intermediate files during processing by html2epub" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:173 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:239 msgid "" "Extract the contents of the produced EPUB file to the specified directory." msgstr "" @@ -418,11 +462,11 @@ msgid "" "formats are: %s\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:102 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:103 msgid "Could not find an ebook inside the archive" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:214 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:225 msgid "" "%prog [options] file.html|opf\n" "\n" @@ -433,14 +477,14 @@ msgid "" "the element of the OPF file. \n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:465 -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:746 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:589 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:476 +#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:758 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:613 msgid "Output written to " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:487 -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:1087 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:498 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:1117 msgid "You must specify an input HTML file" msgstr "" @@ -459,88 +503,88 @@ msgid "" "cause incorrect rendering." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:509 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:510 msgid "Written processed HTML to " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:872 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:898 msgid "Options to control the traversal of HTML" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:879 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:905 msgid "The output directory. Default is the current directory." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:881 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:537 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:907 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:560 msgid "Character encoding for HTML files. Default is to auto detect." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:883 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:909 msgid "" "Create the output in a zip file. If this option is specified, the --output " "should be the name of a file not a directory." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:885 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:911 msgid "Control the following of links in HTML files." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:887 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:913 msgid "" "Traverse links in HTML files breadth first. Normally, they are traversed " "depth first" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:889 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:915 msgid "" "Maximum levels of recursion when following links in HTML files. Must be non-" "negative. 0 implies that no links in the root HTML file are followed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:891 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:917 msgid "Set metadata of the generated ebook" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:893 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:919 msgid "Set the title. Default is to autodetect." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:895 -msgid "The author(s) of the ebook, as a comma separated list." +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:921 +msgid "The author(s) of the ebook, as a & separated list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:897 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:923 msgid "The subject(s) of this book, as a comma separated list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:899 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:925 msgid "Set the publisher of this book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:901 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:927 msgid "A summary of this book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:903 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:929 msgid "Load metadata from the specified OPF file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:905 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:931 msgid "Options useful for debugging" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:907 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:933 msgid "" "Be more verbose while processing. Can be specified multiple times to " "increase verbosity." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:909 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:935 msgid "Output HTML is \"pretty printed\" for easier parsing by humans" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:915 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:941 msgid "" "%prog [options] file.html|opf\n" "\n" @@ -561,7 +605,7 @@ msgid "%prog [options] LITFILE" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:855 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:501 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:503 msgid "Output directory. Defaults to current directory." msgstr "" @@ -570,23 +614,23 @@ msgid "Legibly format extracted markup. May modify meaningful whitespace." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:861 -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:719 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:549 +#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:731 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:572 msgid "Useful for debugging." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:872 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:525 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:527 msgid "OEB ebook created in" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:713 +#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:725 msgid "%prog [options] OPFFILE" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:716 +#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:728 #: /home/kovid/work/calibre/src/calibre/ebooks/mobi/from_feeds.py:26 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:546 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:569 msgid "Output file. Default is derived from input filename." msgstr "" @@ -617,7 +661,7 @@ msgid "Sort key for the author" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:87 -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:278 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:284 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:39 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:108 msgid "Publisher" @@ -638,6 +682,7 @@ msgid "Output file name. Default is derived from input filename" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:96 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:544 msgid "" "Render HTML tables as blocks of text instead of actual tables. This is " "neccessary if the HTML contains very large or complex tables." @@ -1079,54 +1124,54 @@ msgstr "" msgid "Cannot add link %s to TOC" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:957 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:954 msgid "Unable to process image %s. Error: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:995 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:999 msgid "Unable to process interlaced PNG %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1010 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1014 msgid "" "Could not process image: %s\n" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1763 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1768 msgid "" "An error occurred while processing a table: %s. Ignoring table markup." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1765 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1770 msgid "" "Bad table:\n" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1787 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1792 msgid "Table has cell that is too large" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1817 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1822 msgid "" "You have to save the website %s as an html file first and then run html2lrf " "on it." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1860 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1865 msgid "Could not read cover image: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1863 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1868 msgid "Cannot read from: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1988 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1993 msgid "Failed to process opf file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1994 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1999 msgid "" "Usage: %prog [options] mybook.html\n" "\n" @@ -1338,55 +1383,50 @@ msgstr "" msgid "Set the comment" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:276 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:282 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:69 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:70 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:36 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:103 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:359 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:969 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:970 msgid "Title" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:277 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:283 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:37 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:104 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:364 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:970 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:971 msgid "Author(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:279 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:285 msgid "Producer" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:280 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:493 -msgid "Category" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:281 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:286 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:71 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:64 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:432 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:515 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:332 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:489 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:517 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:357 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:320 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:58 msgid "Comments" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:283 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:288 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:109 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:309 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:909 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:973 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:910 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:974 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:60 #: /home/kovid/work/calibre/src/calibre/gui2/tags.py:50 msgid "Tags" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:284 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:290 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:110 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:325 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:59 @@ -1394,27 +1434,27 @@ msgstr "" msgid "Series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:285 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:291 msgid "Language" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:199 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:202 msgid "A comma separated list of tags to set" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:201 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:204 msgid "The series to which this book belongs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:203 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:206 msgid "The series index" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:205 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:208 msgid "The book language" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:207 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/epub.py:210 msgid "Extract the cover" msgstr "" @@ -1506,7 +1546,7 @@ msgstr "" msgid "Set the ISBN" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:965 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1012 msgid "Set the dc:language field" msgstr "" @@ -1522,59 +1562,65 @@ msgstr "" msgid "Creating Mobipocket file from EPUB..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:499 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:501 msgid "%prog [options] myebook.mobi" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:523 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:525 msgid "Raw MOBI HTML saved in" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:512 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:528 msgid "Options to control the conversion to MOBI" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:519 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:535 msgid "Mobipocket-specific options." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:521 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:537 msgid "" "Compress file text using PalmDOC compression. Results in smaller files, but " "takes a long time to run." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:524 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:540 msgid "Modify images to meet Palm device size limitations." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:526 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:542 msgid "Title for any generated in-line table of contents." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:527 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:548 +msgid "" +"When present, use the author sorting information for generating the " +"Mobipocket author metadata." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:550 msgid "" "Device renderer profiles. Affects conversion of font sizes, image rescaling " "and rasterization of tables. Valid profiles are: %s." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:532 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:555 msgid "Source renderer profile. Default is %default." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:535 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:558 msgid "Destination renderer profile. Default is %default." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:543 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:566 msgid "[options]" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:561 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:584 msgid "Unknown source profile %r" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:565 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:588 msgid "Unknown destination profile %r" msgstr "" @@ -1582,74 +1628,74 @@ msgstr "" msgid "The output directory. Defaults to the current directory." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:586 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:822 msgid "Cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:587 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:823 msgid "Title Page" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:588 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:824 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:18 #: /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/ebooks/oeb/base.py:589 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:825 msgid "Index" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:590 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:826 msgid "Glossary" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:591 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:827 msgid "Acknowledgements" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:592 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:828 msgid "Bibliography" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:593 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:829 msgid "Colophon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:594 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:830 msgid "Copyright" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:595 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:831 msgid "Dedication" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:596 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:832 msgid "Epigraph" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:597 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:833 msgid "Foreword" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:598 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:834 msgid "List of Illustrations" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:599 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:835 msgid "List of Tables" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:600 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:836 msgid "Notes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:601 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:837 msgid "Preface" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:602 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:838 msgid "Main Text" msgstr "" @@ -1814,7 +1860,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:85 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:86 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:316 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:904 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:905 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:56 msgid "Path" msgstr "" @@ -1880,8 +1926,8 @@ msgid "&Number of Colors:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:92 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:440 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:538 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:502 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:540 msgid "&Profile:" msgstr "" @@ -1996,7 +2042,7 @@ msgid "Access log:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:345 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:385 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:402 msgid "Failed to start content server" msgstr "" @@ -2034,7 +2080,7 @@ msgid "Compacting..." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:417 -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:343 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:340 msgid "Configuration" msgstr "" @@ -2054,16 +2100,16 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:452 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:453 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:478 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:414 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:497 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:308 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:313 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:327 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:336 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:471 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:499 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:333 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:338 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:340 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:344 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:346 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:352 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:361 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:363 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:365 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:369 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:371 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:126 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:128 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_editor_ui.py:131 @@ -2071,10 +2117,10 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:267 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:269 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:270 -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:332 -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:336 -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:342 -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:344 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:329 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:333 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:339 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:341 msgid "..." msgstr "" @@ -2223,13 +2269,13 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:460 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:57 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:174 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:178 msgid "&Username:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:461 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:58 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:179 msgid "&Password:" msgstr "" @@ -2241,7 +2287,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config_ui.py:463 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:59 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:176 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:180 msgid "&Show password" msgstr "" @@ -2334,377 +2380,413 @@ msgstr "" msgid "Convert %s to " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:68 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:70 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:97 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:55 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:296 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:297 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:143 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:161 msgid "Metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:70 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:96 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:72 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:57 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:297 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:298 msgid "Look & Feel" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:72 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:74 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:99 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:59 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:298 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:299 msgid "Page Setup" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:74 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:98 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:100 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:61 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:299 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:300 msgid "Chapter Detection" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:97 msgid "" "Specify metadata such as title and author for the book.\n" "\n" "Metadata will be updated in the database as well as the generated %s file." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:96 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:98 msgid "" "Adjust the look of the generated ebook by specifying things like font sizes." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:99 msgid "Specify the page layout settings like margins." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:299 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:100 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:300 msgid "Fine tune the detection of chapter and section headings." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:104 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:106 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:167 msgid "Choose cover for " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:111 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:113 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:174 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:59 msgid "Cannot read" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:112 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:114 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:175 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:60 msgid "You do not have permission to read the file: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:120 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:122 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:183 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:68 msgid "Error reading file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:121 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:123 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:184 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single.py:69 msgid "

There was an error reading from file:
" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:127 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:129 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:190 msgid " is not a valid picture" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:238 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1053 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:241 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1073 msgid "Cannot convert" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:239 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:242 msgid "This book has no available formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:244 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:247 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:93 msgid "No available formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:245 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:248 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:94 msgid "Cannot convert %s as this book has no supported formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:249 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:252 msgid "Choose the format to convert to " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:260 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:264 msgid "Invalid XPath expression" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:261 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:265 msgid "The expression %s is invalid. Error: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:410 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:467 msgid "Convert to EPUB" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:411 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:494 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:341 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:468 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:496 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:366 msgid "Book Cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:412 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:495 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:342 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:469 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:497 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:367 msgid "Change &cover image:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:413 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:496 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:343 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:470 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:498 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:368 msgid "Browse for an image to use as the cover of this book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:415 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:498 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:472 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:500 msgid "Use cover from &source file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:416 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:499 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:305 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:473 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:501 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:330 msgid "&Title: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:417 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:500 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:306 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:474 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:502 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:331 msgid "Change the title of this book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:418 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:501 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:475 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:503 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:128 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:309 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:334 msgid "&Author(s): " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:419 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:502 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:476 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:504 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:129 msgid "" "Change the author(s) of this book. Multiple authors should be separated by " "an &. If the author name contains an &, use && to represent it." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:420 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:503 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:477 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:505 msgid "Author So&rt:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:421 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:504 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:478 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:506 msgid "" "Change the author(s) of this book. Multiple authors should be separated by a " "comma" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:422 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:505 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:479 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:507 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:136 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:318 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:343 msgid "&Publisher: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:423 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:506 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:480 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:508 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:137 msgid "Change the publisher of this book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:424 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:507 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:319 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:481 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:509 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:344 msgid "Ta&gs: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:425 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:508 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:482 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:510 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:139 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:320 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:345 msgid "" "Tags categorize the book. This is particularly useful while searching. " "

They can be any words or phrases, separated by commas." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:426 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:509 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:483 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:511 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:144 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:323 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:348 msgid "&Series:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:427 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:428 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:510 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:511 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:484 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:485 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:512 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:513 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:145 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:146 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:324 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:325 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:349 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:350 msgid "List of known series. You can add new series." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:429 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:430 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:512 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:513 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:328 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:329 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:486 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:487 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:515 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:353 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:354 msgid "Series index." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:431 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:514 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:330 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:488 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:516 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:355 msgid "Book " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:433 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:522 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:490 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:524 msgid "Source en&coding:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:434 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:516 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:491 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:518 msgid "Base &font size:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:435 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:442 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:444 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:446 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:448 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:492 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:506 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:508 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:510 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:512 msgid " pt" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:436 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:493 msgid "Remove &spacing between paragraphs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:437 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:494 msgid "Preserve &tag structure when splitting" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:438 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:495 msgid "&Rescale images" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:439 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:496 +msgid "&Ignore tables" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:497 +msgid "&Use author sort to set author field in output" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:498 +msgid "No text &justification" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:499 +msgid "&Linearize tables" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:500 +msgid "Remove &first image from source file" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:501 msgid "Override &CSS" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:441 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:539 -msgid "&Left Margin:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:443 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:541 -msgid "&Right Margin:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:445 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:543 -msgid "&Top Margin:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:447 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:545 -msgid "&Bottom Margin:" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:449 -msgid "Do not &split on page breaks" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:450 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:503 msgid "&Source profile:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:451 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:504 msgid "&Destination profile:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:452 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:505 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:541 +msgid "&Left Margin:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:507 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:543 +msgid "&Right Margin:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:509 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:545 +msgid "&Top Margin:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:511 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:547 +msgid "&Bottom Margin:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:513 +msgid "Do not &split on page breaks" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:514 +msgid "&Page map" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:515 +msgid "" +"

You can control how calibre detects page boundaries using a XPath " +"expression. To learn how to use XPath expressions see the XPath " +"tutorial. The page boundaries are useful only if you want a mapping from " +"pages in a paper book, to locations in the e-book. This controls where Adobe " +"Digital Editions displays the page numbers in the right margin.

" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:516 +msgid "&Boundary XPath:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:517 +msgid "&Name XPath:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:518 msgid "Automatic &chapter detection" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:453 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:519 +msgid "" +"

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:520 msgid "&XPath:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:454 -msgid "" -"\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:459 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:521 msgid "Chapter &mark:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:460 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:522 msgid "Automatic &Table of Contents" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:461 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:523 msgid "Number of &links to add to Table of Contents" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:462 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:524 msgid "Do not add &detected chapters to the Table of Contents" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:463 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:525 msgid "Chapter &threshold" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:464 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:526 msgid "&Force use of auto-generated Table of Contents" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:465 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:527 msgid "Level &1 TOC" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:466 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:528 msgid "Level &2 TOC" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:467 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:529 msgid "&Title for generated TOC" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:530 +msgid "Level &3 TOC" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:38 msgid "Author Sort" msgstr "" @@ -2765,7 +2847,7 @@ 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:32 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/job_view_ui.py:37 msgid "Details of job" msgstr "" @@ -2798,185 +2880,189 @@ msgstr "" msgid "Set conversion defaults" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:260 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:261 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 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:262 msgid "
  1. baen - Books from BAEN Publishers
  2. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:262 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:263 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 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:264 msgid "
  5. book-designer - HTML0 files from Book Designer
  6. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:296 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:297 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 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:298 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 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:299 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 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:309 msgid "No help available" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:411 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:412 msgid "Bulk convert ebooks to LRF" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:492 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:494 msgid "Convert to LRF" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:517 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:524 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:526 -msgid " pts" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:518 -msgid "Embedded Fonts" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:495 +msgid "Category" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:519 -msgid "&Serif:" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:526 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:528 +msgid " pts" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:520 -msgid "S&ans-serif:" +msgid "Embedded Fonts" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:521 -msgid "&Monospace:" +msgid "&Serif:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:522 +msgid "S&ans-serif:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:523 -msgid "Minimum &indent:" +msgid "&Monospace:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:525 -msgid "&Word spacing:" +msgid "Minimum &indent:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:527 -msgid "Enable auto &rotation of images" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:528 -msgid "Insert &blank lines between paragraphs" +msgid "&Word spacing:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:529 -msgid "Ignore &tables" +msgid "Enable auto &rotation of images" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:530 -msgid "Ignore &colors" +msgid "Insert &blank lines between paragraphs" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:531 -msgid "&Preprocess:" +msgid "Ignore &tables" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:532 -msgid "Header" +msgid "Ignore &colors" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:533 -msgid "&Show header" +msgid "&Preprocess:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:534 -msgid "&Header format:" +msgid "Header" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:535 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:540 +msgid "&Show header" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:536 +msgid "&Header format:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:537 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:542 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:544 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:546 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:548 #: /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:536 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:538 msgid "Header &separation:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:537 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:539 msgid "Override
    CSS" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:547 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:549 msgid "&Convert tables to images (good for large/complex tables)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:548 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:550 msgid "&Multiplier for text size in rendered tables:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:549 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:551 msgid "Title based detection" 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 msgid "&Disable chapter detection" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:551 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:553 msgid "&Regular expression:" msgstr "" -#: /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 msgid "Add &chapters to table of contents" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:553 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:555 msgid "Don't add &links to the table of contents" msgstr "" -#: /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 msgid "Tag based detection" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:555 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:557 msgid "&Page break before tag:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:556 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:558 msgid "&Force page break before tag:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:557 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:559 msgid "Force page break before &attribute:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:558 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:560 msgid "Detect chapter &at tag:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:559 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:561 msgid "" "\n" @@ -2995,36 +3081,36 @@ msgid "Edit Meta information" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:304 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:329 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:310 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:335 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:311 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:336 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:314 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:339 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:315 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:316 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:340 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:341 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:317 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:342 msgid " stars" msgstr "" @@ -3034,8 +3120,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:140 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:141 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:321 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:322 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:346 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:347 msgid "Open Tag Editor" msgstr "" @@ -3099,61 +3185,61 @@ msgstr "" msgid "You must specify the ISBN identifier for this book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:303 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:328 msgid "Edit Meta Information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:307 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:332 msgid "Swap the author and title" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:312 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:337 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:326 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:351 msgid "Remove unused series (Series that have no books)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:331 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:356 msgid "IS&BN:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:333 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:358 msgid "Fetch metadata from server" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:334 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:359 msgid "Available Formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:335 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:360 msgid "Add a new format for this book to the database" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:337 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:362 msgid "Remove the selected formats for this book from the database." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:339 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:364 msgid "Set the cover for the book from the selected format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:345 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:370 msgid "Reset cover to default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:347 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:372 msgid "Fetch cover image from server" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:348 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:373 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:349 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:374 msgid "Change password" msgstr "" @@ -3165,153 +3251,167 @@ msgstr "" msgid "Aborting..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:39 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:40 msgid "You" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:184 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:123 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:138 +msgid "Custom" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:136 +msgid "Scheduled" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:232 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:218 msgid "Search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:261 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:308 msgid "%d recipes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:262 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:309 msgid "Monday" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:262 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:309 msgid "Tuesday" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:262 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:309 msgid "Wednesday" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:262 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:309 msgid "day" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:310 msgid "Friday" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:310 msgid "Saturday" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:310 msgid "Sunday" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:310 msgid "Thursday" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:292 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:343 msgid "Must set account information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:292 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:344 msgid "This recipe requires a username and password" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:317 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:370 msgid "Created by: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:355 -msgid "Last downloaded: %s days ago" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:408 +msgid "%d days, %d hours and %d minutes ago" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:357 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:410 +msgid "Last downloaded" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:412 msgid "Last downloaded: never" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:384 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:161 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:438 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:165 msgid "Schedule news download" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:387 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:441 msgid "Add a custom news source" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:394 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:448 #: /home/kovid/work/calibre/src/calibre/gui2/tags.py:50 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:772 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:776 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1083 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:775 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:779 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1086 msgid "News" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:162 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:166 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:221 msgid "Recipes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:163 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:167 msgid "Schedule for download" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:168 msgid "title" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:165 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:169 msgid "description" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:166 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:170 msgid "author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:167 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:171 msgid "&Schedule for download:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:168 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:172 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:174 msgid "Every " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:169 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:173 msgid "at" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:171 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:175 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:172 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:180 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:176 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:184 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles_ui.py:263 msgid " days" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:173 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:177 msgid "&Account" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:177 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:181 msgid "For the scheduling to work, you must leave calibre running." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:178 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:182 msgid "&Download now" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:183 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:181 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:185 msgid "Delete downloaded news older than " msgstr "" @@ -3338,35 +3438,35 @@ msgstr "" msgid "Negate" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:88 msgid "Advanced Search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:89 msgid "Find entries that have..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:90 msgid "&All these words:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:90 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:91 msgid "This exact &phrase:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:92 msgid "&One or more of these words:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:92 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:93 msgid "But dont show entries that have..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:94 msgid "Any of these &unwanted words:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:94 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:95 msgid "" "See the User Manual for more help" @@ -3735,12 +3835,12 @@ msgid "Job has already run" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/library.py:105 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:971 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:972 msgid "Size (MB)" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/library.py:106 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:972 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:973 msgid "Date" msgstr "" @@ -3758,25 +3858,25 @@ msgstr "" msgid "Book %s of %s." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:739 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:740 msgid "Not allowed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:740 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:741 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:903 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:904 msgid "Format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:908 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:909 msgid "Timestamp" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1006 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:1007 msgid "Search (For Advanced Search click the button to the left)" msgstr "" @@ -3868,132 +3968,132 @@ msgstr "" msgid "&Restart" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:142 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:137 msgid "" "

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

    " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:161 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:163 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:156 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:158 msgid "Send to main memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:162 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:157 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:159 msgid "Send to storage card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:163 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:164 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:158 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:159 msgid "and delete from library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:166 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:161 msgid "Send to storage card by default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:179 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:170 msgid "Edit metadata individually" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:181 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:172 msgid "Edit metadata in bulk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:184 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:175 msgid "Add books from a single directory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:185 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:176 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:186 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:177 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:201 -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:356 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:353 msgid "Save to disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:202 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:193 msgid "Save to disk in a single directory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:203 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1258 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:194 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1280 msgid "Save only %s format to disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:206 -#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:362 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:197 +#: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:359 msgid "View" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:207 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:198 msgid "View specific format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:224 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:215 msgid "Convert individually" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:225 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:216 msgid "Bulk convert" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:227 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:218 msgid "Set defaults for conversion" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:228 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:219 msgid "Set defaults for conversion of comics" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:250 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:241 msgid "Similar books..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:298 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:289 msgid "Bad database location" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:300 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1391 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:291 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1413 msgid "Choose a location for your ebook library." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:424 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:441 msgid "Browse by covers" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:513 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:530 msgid "Device: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:531 msgid " detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:536 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:553 msgid "Connected " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:547 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:564 msgid "Device database corrupted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:548 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:565 msgid "" "\n" "

    The database of books on the reader is corrupted. Try the " @@ -4009,312 +4109,312 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:597 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:614 msgid "Adding books recursively..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:602 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:619 msgid "Added " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:602 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:619 msgid "Searching..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:612 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:716 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:629 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:735 msgid "" "

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