diff --git a/.pydevproject b/.pydevproject index fba36e6fb7..b6d22db5e1 100644 --- a/.pydevproject +++ b/.pydevproject @@ -2,8 +2,9 @@ -python 2.5 +python 2.6 /calibre/src +Default diff --git a/installer/windows/build_installer.py b/installer/windows/build_installer.py index 7119d92383..e19bff3e7b 100644 --- a/installer/windows/build_installer.py +++ b/installer/windows/build_installer.py @@ -8,16 +8,18 @@ __docformat__ = 'restructuredtext en' import sys, time, subprocess, os, re from calibre import __appname__, __version__ +INSTALLJAMMER = '/home/kovid/installjammer/installjammer' + sv = re.sub(r'[a-z]\d+', '', __version__) cmdline = [ - '/usr/local/installjammer/installjammer', + INSTALLJAMMER, '--build-dir', '/tmp/calibre-installjammer', '-DAppName', __appname__, '-DShortAppName', __appname__, '-DApplicationURL', 'http://%s.kovidgoyal.net'%__appname__, '-DCopyright', time.strftime('%Y Kovid Goyal'), - '-DPackageDescription', '%s is an e-book library manager. It can view, convert and catalog e-books in most of the major e-book formats. It can also talk to a few e-book reader devices. It can go out to the internet and fetch metadata for your books. It can download newspapers and convert them into e-books for convenient reading.'%__appname__, + '-DPackageDescription', '%s is an e-book library manager. It can view, convert and catalog e-books in most of the major e-book formats. It can also talk to e-book reader devices. It can go out to the internet and fetch metadata for your books. It can download newspapers and convert them into e-books for convenient reading.'%__appname__, '-DPackageSummary', '%s: E-book library management'%__appname__, '-DVersion', __version__, '-DInstallVersion', sv + '.0', diff --git a/installer/windows/calibre/calibre.mpi b/installer/windows/calibre/calibre.mpi index 50dc81dcbd..6916256c81 100644 --- a/installer/windows/calibre/calibre.mpi +++ b/installer/windows/calibre/calibre.mpi @@ -138,7 +138,7 @@ ProjectID DA98A0C6-9102-73EC-2516-B147E972D3F7 ProjectVersion -1.2.7.0 +1.2.12.0 SaveOnlyToplevelDirs No @@ -211,7 +211,8 @@ File ::8E5D85A4-7608-47A1-CF7C-309060D5FF40 -filemethod {Always overwrite files} Component ::F6829AB7-9F66-4CEE-CA0E-21F54C6D3609 -setup Install -active Yes -platforms {AIX-ppc FreeBSD-4-x86 FreeBSD-x86 HPUX-hppa Linux-x86 Solaris-sparc Windows} -name Main -parent Components SetupType ::D9ADE41C-B744-690C-2CED-CF826BF03D2E -setup Install -active Yes -platforms {AIX-ppc FreeBSD-4-x86 FreeBSD-x86 HPUX-hppa Linux-x86 Solaris-sparc Windows} -name Typical -parent SetupTypes -InstallComponent 3EA07B17-04D8-6508-B535-96CC7173B49A -setup Install -type pane -title {Welcome Screen} -component Welcome -active Yes -parent StandardInstall +InstallComponent 3EA07B17-04D8-6508-B535-96CC7173B49A -setup Install -type pane -conditions D7F585DB-0DEC-A94E-DAB0-94D558D82764 -title {Welcome Screen} -component Welcome -command reorder -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 -title {Check for Previous Install} -component CheckForPreviousInstall -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A 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 @@ -513,7 +514,7 @@ false 1 3EA07B17-04D8-6508-B535-96CC7173B49A,Conditions -{0 conditions} +{1 condition} 3EA07B17-04D8-6508-B535-96CC7173B49A,Message,subst 1 @@ -1100,6 +1101,9 @@ AAFE58A0-2DFB-CA20-1F6E-D3815F885996,Alias AIX-ppc,Active No +AIX-ppc,BuildSeparateArchives +No + AIX-ppc,DefaultDirectoryPermission 0755 @@ -1286,6 +1290,26 @@ CFBE4459-450B-1FAB-3422-609544334AA2,String D79DC0D2-38BC-9D9F-2DF4-3C76D89BF933,ExitType Finish +D7F585DB-0DEC-A94E-DAB0-94D558D82764,CheckCondition +{Before Next Pane is Displayed} + +D7F585DB-0DEC-A94E-DAB0-94D558D82764,Comment +{Check if calibre.exe is still running} + +D7F585DB-0DEC-A94E-DAB0-94D558D82764,FailureMessage +{calibre is still running. Please shut it down before proceeding. You can quit calibre by right clicking on the calibre system tray icon.} + +D7F585DB-0DEC-A94E-DAB0-94D558D82764,ResultVirtualText +CalibreRunning + +D7F585DB-0DEC-A94E-DAB0-94D558D82764,Script +{set pid [::InstallAPI::FindProcesses -name calibre.exe] +if {$pid eq ""} { + ## myapp.exe is not running + return 1 +} +return 0} + D86BBA5C-4903-33BA-59F8-4266A3D45896,Conditions {2 conditions} @@ -1475,6 +1499,9 @@ FBA33088-C809-DD6B-D337-EADBF1CEE966,String FreeBSD-4-x86,Active No +FreeBSD-4-x86,BuildSeparateArchives +No + FreeBSD-4-x86,DefaultDirectoryPermission 0755 @@ -1526,6 +1553,9 @@ FreeBSD-4-x86,RootInstallDir FreeBSD-x86,Active No +FreeBSD-x86,BuildSeparateArchives +No + FreeBSD-x86,DefaultDirectoryPermission 0755 @@ -1577,6 +1607,9 @@ FreeBSD-x86,RootInstallDir HPUX-hppa,Active No +HPUX-hppa,BuildSeparateArchives +No + HPUX-hppa,DefaultDirectoryPermission 0755 @@ -1628,6 +1661,9 @@ HPUX-hppa,RootInstallDir Linux-x86,Active No +Linux-x86,BuildSeparateArchives +No + Linux-x86,DefaultDirectoryPermission 0755 @@ -1679,6 +1715,9 @@ Linux-x86,RootInstallDir Solaris-sparc,Active No +Solaris-sparc,BuildSeparateArchives +No + Solaris-sparc,DefaultDirectoryPermission 0755 @@ -1730,6 +1769,9 @@ Solaris-sparc,RootInstallDir TarArchive,Active No +TarArchive,BuildSeparateArchives +No + TarArchive,CompressionLevel 6 @@ -1790,9 +1832,15 @@ TarArchive,VirtualTextMap Windows,Active Yes +Windows,BuildSeparateArchives +No + Windows,Executable <%AppName%>-<%Version%><%Ext%> +Windows,FileDescription +{<%AppName%> <%Version%> Setup} + Windows,IncludeTWAPI Yes @@ -1829,6 +1877,9 @@ Windows,WindowsIcon ZipArchive,Active No +ZipArchive,BuildSeparateArchives +No + ZipArchive,CompressionLevel 6 diff --git a/setup.py b/setup.py index 2f0c5aad26..6bc4109277 100644 --- a/setup.py +++ b/setup.py @@ -121,7 +121,7 @@ if __name__ == '__main__': buf = cStringIO.StringIO() print 'Creating translations template' tempdir = tempfile.mkdtemp() - pygettext(buf, ['-p', tempdir]+files) + pygettext(buf, ['-k', '__', '-p', tempdir]+files) src = buf.getvalue() pot = os.path.join(tempdir, 'calibre.pot') f = open(pot, 'wb') diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index e8d4b61ce1..e11dec8688 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -20,6 +20,7 @@ import mechanize mimetypes.add_type('application/epub+zip', '.epub') mimetypes.add_type('text/x-sony-bbeb+xml', '.lrs') mimetypes.add_type('application/x-sony-bbeb', '.lrf') +mimetypes.add_type('application/x-dtbncx+xml', '.ncx') def to_unicode(raw, encoding='utf-8', errors='strict'): if isinstance(raw, unicode): diff --git a/src/calibre/devices/cybookg3/driver.py b/src/calibre/devices/cybookg3/driver.py index ea6376df0d..f05b28b538 100644 --- a/src/calibre/devices/cybookg3/driver.py +++ b/src/calibre/devices/cybookg3/driver.py @@ -7,8 +7,10 @@ Device driver for Bookeen's Cybook Gen 3 import os, shutil from itertools import cycle +from calibre.devices.errors import FreeSpaceError from calibre.devices.usbms.driver import USBMS import calibre.devices.cybookg3.t2b as t2b +from calibre.devices.errors import FreeSpaceError class CYBOOKG3(USBMS): # Ordered list of supported formats @@ -50,10 +52,10 @@ class CYBOOKG3(USBMS): return size return os.path.getsize(obj) - sizes = map(get_size, files) + sizes = [get_size(f) for f in files] size = sum(sizes) - if on_card and size > self.free_space()[2] - 1024*1024: + if on_card and size > self.free_space()[2] - 1024*1024: raise FreeSpaceError(_("There is insufficient free space on the storage card")) if not on_card and size > self.free_space()[0] - 2*1024*1024: raise FreeSpaceError(_("There is insufficient free space in main memory")) diff --git a/src/calibre/devices/cybookg3/t2b.py b/src/calibre/devices/cybookg3/t2b.py index 0baa7a1060..5bf512f22d 100644 --- a/src/calibre/devices/cybookg3/t2b.py +++ b/src/calibre/devices/cybookg3/t2b.py @@ -4,7 +4,8 @@ __copyright__ = '2009, John Schember ' Write a t2b file to disk. ''' -import Image, StringIO +import StringIO +from PIL import Image DEFAULT_T2B_DATA = '\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0f\xff\xff\xff\xf0\xff\x0f\xc3\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf8\x00\x00\xff\xff\xff\xf0\xff\x0f\xc3\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xe0\xff\xf0\xff\xff\xff\xf0\xff\xff\xc3\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc3\xff\xff\xff\xff\xff\xf0\xff\xff\xc3\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x07\xff\xff\xfc\x00?\xf0\xff\x0f\xc3\x00?\xf0\xc0\xfe\x00?\xff\xff\xff\xff\xff\xff\xff\x0f\xff\xff\xf0<\x0f\xf0\xff\x0f\xc0,\x0f\xf0\x0e\xf0,\x0f\xff\xff\xff\xff\xff\xff\xff\x0f\xff\xff\xff\xff\xc3\xf0\xff\x0f\xc0\xff\x0f\xf0\xff\xf0\xff\xc7\xff\xff\xff\xff\xff\xff\xff\x0f\xff\xff\xff\xff\xc3\xf0\xff\x0f\xc3\xff\xc3\xf0\xff\xc3\xff\xc3\xff\xff\xff\xff\xff\xff\xff\x0f\xff\xff\xff\x00\x03\xf0\xff\x0f\xc3\xff\xc3\xf0\xff\xc3\xff\xc3\xff\xff\xff\xff\xff\xff\xff\x0f\xff\xff\xf0\x1f\xc3\xf0\xff\x0f\xc3\xff\xc3\xf0\xff\xc0\x00\x03\xff\xff\xff\xff\xff\xff\xff\x0b\xff\xff\xf0\xff\xc3\xf0\xff\x0f\xc3\xff\xc3\xf0\xff\xc3\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc3\xff\xff\xf3\xff\xc3\xf0\xff\x0f\xc3\xff\xc3\xf0\xff\xc3\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc0\xff\xfc\xf0\xff\x03\xf0\xff\x0f\xc0\xff\x0f\xf0\xff\xf0\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x0f\x00\xf08\x03\xf0\xff\x0f\xc0,\x0f\xf0\xff\xf0\x1f\x03\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0f\xfc\x00\xc3\xf0\xff\x0f\xc3\x00?\xf0\xff\xff\x00\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x0f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf0\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xfe\x94\xff\xff\xff\xff\xff\xff\xff\xff\xff\xc0\x00\x00\x00\x0f\xff\xff\xff\xff\xff\xff\xfc\x7f\xfe\x94\xff\xff\xff\xff\xff\xff\xff\xff\xfc\x0f\xff\xfe\xa9@\xff\xff\xff\xff\xff\xff\xfc?\xfe\xa4\xff\xff\xff\xff\xff\xff\xff\xff\xfc\xff\xff\xff\xe9P\xff\xff\xff\xff\xff\xff\xfe/\xfe\xa8\xff\xff\xff\xff\xff\xff\xff\xff\xfc\xff\xff\xff\xf9T\xff\xff\xff\xff\xf0@\x00+\xfa\xa8?\xff\xff\xff\xff\xff\xff\xff\xfc\xbf\xff\xff\xf9T\xff\xff\xff\xff\xcb\xe4}*\xaa\xaa?\xff\xff\xff\xff\xff\xff\xff\xfc\xbf\xff\xff\xe9T\xff\xff\xff\xff\xc7\xe4\xfd\x1a\xaa\xaa?\xff\xff\xff\xff\xff\xff\xff\xfc\xaf\xea\xaa\xa6\xa4\xff@\x00\x0f\xc3\xe8\xfe\x1a\xaa\xaa?\xff\xff\xff\xff\xff\xff\xff\xfcj\x95UZ\xa4\x00\x7f\xfe\x90\x03\xe8\xfe\n\xaa\xaa?\xff\xff\xff\xff\xff\xff\xff\xfcj\x95UZ\xa4?\xff\xff\xa5C\xe8\xfe\x06\xaa\xaa?\xff\xff\xff\xff\xff\xff\xff\xfcj\x95UZ\xa4?\xff\xff\xeaC\xe8\xbe\x06\xaa\xaa\x0f\xff\xff\xff\xff\xff\xff\xff\xfcj\x95UZ\xa4/\xff\xff\xea\x82\xe8j\x06\xaa\xaa\x0f\xff\xff\xff\xff\xff\xff\xff\xfcj\x95UZ\xa4/\xff\xff\xaa\x82\xe8*F\xaa\xaa\x8f\xff\xff\xff\xff\xff\xff\xff\xfcj\x95UZ\xa4+\xff\xfe\xaa\x82\xe8*\x86\xaa\xaa\x8f\xff\xff\x80\xff\xff\xff\xff\xfcj\x95UV\xa4\x1a\xfa\xaa\xaa\x82\xe8*\x86\xaa\xaa\x8f\xf0\x00T?\xff\xff\xff\xfcj\x95UV\xa4\x1a\xfa\xaa\xaa\x82\xe8*\x81\xaa\xaa\x8c\x03\xff\x95?\xff\xff\xff\xfcj\x95UV\xa4\x1a\xfa\xaa\xaa\x82\xe8*\x81\xaa\xaa\x80\xbf\xff\x95?\xff\xff\xff\xfcj\x95UV\xa4\x1a\xfa\xaa\xaa\x82\xe8*\x81\xaa\xaa\x9b\xff\xff\x95\x0f\xff\xff\xff\xfcj\x95UV\xa4\x1a\xfa\xaa\xaa\x82\xe8\x1a\x81\xaa\xaa\x9a\xff\xfe\x95\x0f\xff\xff\xff\xfcj\x95UV\xa4\x1a\xfa\xaa\xaa\x82\xe8\n\x81\xaa\xaa\xa6\xbf\xfeUO\xff\xff\xff\xfcj\x95UV\xa4\x1a\xfa\xaa\xaa\x82\xa8\n\x91j\xaa\xa5\xaa\xa9ZO\xff\xff\xff\xfcj\x95UV\xa4\x1a\xfa\xaa\xaa\x82\xa8\n\xa0j\xaa\xa5Z\x95ZO\xff\xff\xff\xfcj\x95UV\xa4*\xfa\xaa\xaa\x82\xa9\n\xa0j\xaa\xa5UUZC\xff\xff\xff\xfcj\x95UV\xa4*\xfa\xaa\xaa\x82\xaa\n\xa0j\xaa\xa4UUZS\xff\xff\xff\xfcZ\x95UV\xa4*\xfa\xaa\xaa\x82\xaa\n\xa0j\xaa\xa4UUZS\xff\xff\xff\xfcZ\x95UU\xa4*\xfa\xaa\xaa\x82\xaa\n\xa0j\xaa\xa8UUVS\xff\xff\xff\xfcZ\x95UU\xa4*\xea\xaa\xaa\x82\xaa\x06\xa0Z\xaa\xa8UUV\x93\xff\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x81\xaa\x02\xa0\x1a\xaa\xa8UUV\x90\xff\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x80\xaa\x02\xa0\x1a\xaa\xa8\x15UU\x94\xff\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x80\xaa"\xa0\x1a\xaa\xa8\x15UU\x94\xff\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x80\xaa2\xa4\x16\xaa\xa8\x15UU\x94\xff\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x80\xaa2\xa8\x16\xa6\xa9\x15UU\x94\xff\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x80\xaa2\xa8\x16\xa6\xa9\x05UUT?\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x84\xaa2\xa8\x16\xaa\xaa\x05UUU?\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x88\xaa2\xa8\x06\xaa\xaa\x05UUU?\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x8c\xaa1\xa8\xc5\xaa\xaa\x05UUU?\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x8c\xaa0\xa8E\xa9\xaa\x05UUU/\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x8c\xaa<\xa8\x05\xa9\xaaAUUU\x0f\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x8c\xaa<\xa8\x05\xa9\xaaAUUUO\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x8c\xaa<\xa9\x05\xaa\xaaAUUUO\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x8c\xaa\x1c\xaa\x01\xaa\xaa\x81UUUO\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x8c\xaa\x0c\xaa\x01\xaa\xaa\x81UUUO\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x8c\xaa\x0c\xaa1j\xaa\x80UUUC\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x8c\xaa\x0cj1jj\x90UUUS\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x8c\xaa\x0c*1jj\x90UUUS\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x8c\xaaL*1jj\xa0UUUS\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x8c\xaa\x8f* j\xaa\xa0\x15UUS\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x8c\xaa\x8f*@j\xaa\xa0\x15UUP\xff\xff\xfcZ\x95UU\xa4*\xaa\xaa\xaa\x8c\xaa\x8f*\x8cZ\xaa\xa1\x15UUT\xff\xff\xfcZ\x95UU\xa4j\xaa\xaa\xaa\x8c\xaa\x8f*\x8cZ\x9a\xa0\x15UUT\xff\xff\xfcZ\x95UU\xa4j\xaa\xaa\xaa\x8c\xaa\x8f*\x8cZ\x9a\xa0\x15UUT\xff\xff\xfcZ\x95UU\xa4j\xaa\xaa\xaa\x8c\xaa\x8f\x1a\x8cZ\x9a\xa4\x15UUT?\xff\xfcZ\x95UU\x94j\xaa\xaa\xaa\x8cj\x8f\n\x8cVj\xa4\x05UU\xa4?\xff\xfcVUUU\xa4j\xaa\xaa\xaa\x8cj\x8fJ\x8c\x16\xaa\xa8\xc5UZ\xa5?\xff\xfcUUUV\xa4j\xaa\xaa\xaa\x8cj\x8f\xca\x8f\x16\xaa\xa8\xc5V\xaa\xa5?\xff\xfcUj\xaa\xaa\xa4j\xaa\xaa\xaa\x8cj\x8f\xca\x8f\x1a\xaa\xa8\x05Z\xaaU?\xff\xfcV\xaa\xaa\xaa\xa5j\xaa\xaa\xaa\x8e*\x8f\xca\x83\x1a\xaa\xa4\x01eUU?\xff\xfcZ\xaa\xaa\xaa\xa5j\xaa\xaa\xaa\x8f*\x8f\xca\x83\x1a\xa5U\x01U\x00\x00\x0f\xff\xfcUUUUUZ\xaa\xaa\xaaO%\x8f\xc6\x93\x15\x00\x001@\x0f\xff\xff\xff\xfcP\x00\x00\x00\x15\x00\x00\x00\x00\x0f\x00\x07\xc0\x03\x00\xff\xff0\x1f\xff\xff\xff\xff\xfc\x00\xff\xff\xf8\x00?\xff\xff\xff\x0f?\xc7\xc3\xf7\x0f\xff\xff\xf1\xff\xff\xff\xff\xff\xfc\xff\xff\xff\xff\xf4\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index fa8f6845cc..bbda8f95d2 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -146,36 +146,7 @@ class PRS505(Device): self._card_prefix = re.search(card_pat, mount).group(2) + os.sep - def open_windows_nowmi(self): - from calibre import plugins - winutil = plugins['winutil'][0] - volumes = winutil.get_mounted_volumes_for_usb_device(self.VENDOR_ID, self.PRODUCT_ID) - main = None - for device_id in volumes.keys(): - if 'PRS-505/UC&' in device_id: - main = volumes[device_id]+':\\' - if not main: - raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__) - self._main_prefix = main - card = self._card_prefix = None - win32api = __import__('win32api') - for device_id in volumes.keys(): - if 'PRS-505/UC:' in device_id: - card = volumes[device_id]+':\\' - try: - win32api.GetVolumeInformation(card) - self._card_prefix = card - break - except: - continue - - def open_windows(self): - try: - self.open_windows_nowmi() - return - except: - pass drives = [] wmi = __import__('wmi', globals(), locals(), [], -1) c = wmi.WMI() diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py index 3943ef94f4..fffed41549 100644 --- a/src/calibre/devices/usbms/books.py +++ b/src/calibre/devices/usbms/books.py @@ -18,6 +18,9 @@ class Book(object): self.thumbnail = None self.tags = [] + def __eq__(self, other): + return self.path == other.path + @apply def title_sorter(): doc = '''String to sort the title. If absent, title is returned''' diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index c1dd56385a..dcab2d7936 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -3,10 +3,10 @@ __copyright__ = '2009, John Schember ' ''' Generic device driver. This is not a complete stand alone driver. It is intended to be subclassed with the relevant parts implemented for a particular -device. This class handles devive detection. +device. This class handles device detection. ''' -import os, subprocess, time +import os, subprocess, time, re from calibre.devices.interface import Device as _Device from calibre.devices.errors import DeviceError @@ -18,21 +18,21 @@ class Device(_Device): as USB Mass Storage devices. If you are writing such a driver, inherit from this class. ''' - + VENDOR_ID = 0x0 PRODUCT_ID = 0x0 BCD = None - + VENDOR_NAME = None WINDOWS_MAIN_MEM = None WINDOWS_CARD_MEM = None - + OSX_MAIN_MEM = None OSX_CARD_MEM = None - + MAIN_MEMORY_VOLUME_LABEL = '' STORAGE_CARD_VOLUME_LABEL = '' - + FDI_TEMPLATE = \ ''' @@ -65,15 +65,15 @@ class Device(_Device): ''' FDI_BCD_TEMPLATE = '' - - + + def __init__(self, key='-1', log_packets=False, report_progress=None) : self._main_prefix = self._card_prefix = None - + @classmethod def get_fdi(cls): fdi = '' - + fdi_base_values = dict( app=__appname__, deviceclass=cls.__name__, @@ -92,12 +92,12 @@ class Device(_Device): fdi_bcd_values['BCD_start'] = cls.FDI_BCD_TEMPLATE % dict(bcd=hex(bcd)) fdi_bcd_values['BCD_end'] = '' fdi += cls.FDI_TEMPLATE % fdi_bcd_values - + return fdi - + def set_progress_reporter(self, report_progress): self.report_progress = report_progress - + def card_prefix(self, end_session=True): return self._card_prefix @@ -117,7 +117,7 @@ class Device(_Device): else: raise mult = sectors_per_cluster * bytes_per_sector return total_clusters * mult, free_clusters * mult - + def total_space(self, end_session=True): msz = csz = 0 print self._main_prefix @@ -131,9 +131,9 @@ class Device(_Device): else: msz = self._windows_space(self._main_prefix)[0] csz = self._windows_space(self._card_prefix)[0] - + return (msz, 0, csz) - + def free_space(self, end_session=True): msz = csz = 0 if not iswindows: @@ -146,15 +146,15 @@ class Device(_Device): else: msz = self._windows_space(self._main_prefix)[1] csz = self._windows_space(self._card_prefix)[1] - + return (msz, 0, csz) def windows_match_device(self, pnp_id, device_id): pnp_id = pnp_id.upper() - + if device_id and pnp_id is not None: device_id = device_id.upper() - + if 'VEN_' + self.VENDOR_NAME in pnp_id and 'PROD_' + device_id in pnp_id: return True @@ -162,45 +162,45 @@ class Device(_Device): def windows_get_drive_prefix(self, drive): prefix = None - + try: partition = drive.associators("Win32_DiskDriveToDiskPartition")[0] logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0] prefix = logical_disk.DeviceID + os.sep except IndexError: pass - + return prefix def open_windows(self): drives = {} - wmi = __import__('wmi', globals(), locals(), [], -1) + wmi = __import__('wmi', globals(), locals(), [], -1) c = wmi.WMI() for drive in c.Win32_DiskDrive(): - if self.windows_match_device(str(drive.PNPDeviceID), WINDOWS_MAIN_MEM): + if self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_MAIN_MEM): drives['main'] = self.windows_get_drive_prefix(drive) - elif self.windows_match_device(str(drive.PNPDeviceID), WINDOWS_CARD_MEM): + elif self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_CARD_MEM): drives['card'] = self.windows_get_drive_prefix(drive) - + if 'main' and 'card' in drives.keys(): break - + if not drives: raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.') % self.__class__.__name__) - - self._main_prefix = drives['main'] if 'main' in names.keys() else None - self._card_prefix = drives['card'] if 'card' in names.keys() else None - @classmethod + self._main_prefix = drives.get('main', None) + self._card_prefix = drives.get('card', None) + def get_osx_mountpoints(self, raw=None): if raw is None: ioreg = '/usr/sbin/ioreg' if not os.access(ioreg, os.X_OK): ioreg = 'ioreg' - raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(), stdout=subprocess.PIPE).stdout.read() + raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(), + stdout=subprocess.PIPE).stdout.read() lines = raw.splitlines() names = {} - + def get_dev_node(lines, loc): for line in lines: line = line.strip() @@ -210,7 +210,7 @@ class Device(_Device): if match is not None: names[loc] = match.group(1) break - + for i, line in enumerate(lines): if self.OSX_MAIN_MEM is not None and line.strip().endswith('') and self.OSX_MAIN_MEM in line: get_dev_node(lines[i+1:], 'main') @@ -219,25 +219,25 @@ class Device(_Device): if len(names.keys()) == 2: break return names - + def open_osx(self): mount = subprocess.Popen('mount', shell=True, stdout=subprocess.PIPE).stdout.read() names = self.get_osx_mountpoints() dev_pat = r'/dev/%s(\w*)\s+on\s+([^\(]+)\s+' if 'main' not in names.keys(): raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__) - main_pat = dev_pat%names['main'] + main_pat = dev_pat % names['main'] self._main_prefix = re.search(main_pat, mount).group(2) + os.sep card_pat = names['card'] if 'card' in names.keys() else None if card_pat is not None: - card_pat = dev_pat%card_pat + card_pat = dev_pat % card_pat self._card_prefix = re.search(card_pat, mount).group(2) + os.sep - + def open_linux(self): import dbus - bus = dbus.SystemBus() + bus = dbus.SystemBus() hm = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager") - + def conditional_mount(dev): mmo = bus.get_object("org.freedesktop.Hal", dev) label = mmo.GetPropertyString('volume.label', dbus_interface='org.freedesktop.Hal.Device') @@ -246,10 +246,10 @@ class Device(_Device): fstype = mmo.GetPropertyString('volume.fstype', dbus_interface='org.freedesktop.Hal.Device') if is_mounted: return str(mount_point) - mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'], + mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'], dbus_interface='org.freedesktop.Hal.Device.Volume') return os.path.normpath('/media/'+label)+'/' - + mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__) if not mm: raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,)) @@ -260,13 +260,13 @@ class Device(_Device): break except dbus.exceptions.DBusException: continue - + if not self._main_prefix: raise DeviceError('Could not open device for reading. Try a reboot.') - + self._card_prefix = None cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__) - + for dev in cards: try: self._card_prefix = conditional_mount(dev)+os.sep diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index 75f04219e7..482ddedbb8 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -11,9 +11,22 @@ from itertools import cycle from calibre.devices.usbms.device import Device from calibre.devices.usbms.books import BookList, Book -from calibre.devices.errors import FreeSpaceError +from calibre.devices.errors import FreeSpaceError, PathError from calibre.devices.mime import MIME_MAP +class File(object): + def __init__(self, path): + stats = os.stat(path) + self.is_dir = os.path.isdir(path) + self.is_readonly = not os.access(path, os.W_OK) + self.ctime = stats.st_ctime + self.wtime = stats.st_mtime + self.size = stats.st_size + if path.endswith(os.sep): + path = path[:-1] + self.path = path + self.name = os.path.basename(path) + class USBMS(Device): FORMATS = [] EBOOK_DIR_MAIN = '' @@ -21,39 +34,41 @@ class USBMS(Device): SUPPORTS_SUB_DIRS = False def __init__(self, key='-1', log_packets=False, report_progress=None): - pass - + Device.__init__(self, key=key, log_packets=log_packets, + report_progress=report_progress) + def get_device_information(self, end_session=True): - """ - Ask device for device information. See L{DeviceInfoQuery}. + """ + Ask device for device information. See L{DeviceInfoQuery}. @return: (device name, device version, software version on device, mime type) """ return (self.__class__.__name__, '', '', '') - + def books(self, oncard=False, end_session=True): bl = BookList() - - if oncard and self._card_prefix is None: - return bl + + if oncard and self._card_prefix is None: + return bl prefix = self._card_prefix if oncard else self._main_prefix ebook_dir = self.EBOOK_DIR_CARD if oncard else self.EBOOK_DIR_MAIN - + # Get all books in all directories under the root ebook_dir directory for path, dirs, files in os.walk(os.path.join(prefix, ebook_dir)): - # Filter out anything that isn't in the list of supported ebook types + # Filter out anything that isn't in the list of supported ebook + # types for book_type in self.FORMATS: for filename in fnmatch.filter(files, '*.%s' % (book_type)): title, author, mime = self.__class__.extract_book_metadata_by_filename(filename) - - bl.append(Book(os.path.join(path, filename), title, author, mime)) + + bl.append(Book(os.path.join(path, filename), title, author, mime)) return bl - - def upload_books(self, files, names, on_card=False, end_session=True, + + def upload_books(self, files, names, on_card=False, end_session=True, metadata=None): if on_card and not self._card_prefix: raise ValueError(_('The reader has no storage card connected.')) - + if not on_card: path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN) else: @@ -67,24 +82,24 @@ class USBMS(Device): return size return os.path.getsize(obj) - sizes = map(get_size, files) + sizes = [get_size(f) for f in files] size = sum(sizes) - if on_card and size > self.free_space()[2] - 1024*1024: + if on_card and size > self.free_space()[2] - 1024*1024: raise FreeSpaceError(_("There is insufficient free space on the storage card")) - if not on_card and size > self.free_space()[0] - 2*1024*1024: + if not on_card and size > self.free_space()[0] - 2*1024*1024: raise FreeSpaceError(_("There is insufficient free space in main memory")) paths = [] names = iter(names) metadata = iter(metadata) - + for infile in files: newpath = path - + if self.SUPPORTS_SUB_DIRS: mdata = metadata.next() - + if 'tags' in mdata.keys(): for tag in mdata['tags']: if tag.startswith('/'): @@ -94,32 +109,36 @@ class USBMS(Device): if not os.path.exists(newpath): os.makedirs(newpath) - - filepath = os.path.join(newpath, names.next()) + + filepath = os.path.join(newpath, names.next()) paths.append(filepath) - + if hasattr(infile, 'read'): infile.seek(0) - + dest = open(filepath, 'wb') shutil.copyfileobj(infile, dest, 10*1024*1024) - dest.flush() + dest.flush() dest.close() else: shutil.copy2(infile, filepath) - + return zip(paths, cycle([on_card])) - + @classmethod - def add_books_to_metadata(cls, locations, metadata, booklists): + def add_books_to_metadata(cls, locations, metadata, booklists): for location in locations: path = location[0] on_card = 1 if location[1] else 0 - + title, author, mime = cls.extract_book_metadata_by_filename(os.path.basename(path)) - booklists[on_card].append(Book(path, title, author, mime)) - + book = Book(path, title, author, mime) + + if not book in booklists[on_card]: + booklists[on_card].append(book) + + def delete_books(self, paths, end_session=True): for path in paths: if os.path.exists(path): @@ -130,7 +149,7 @@ class USBMS(Device): os.removedirs(os.path.dirname(path)) except: pass - + @classmethod def remove_books_from_metadata(cls, paths, booklists): for path in paths: @@ -138,14 +157,14 @@ class USBMS(Device): for book in bl: if path.endswith(book.path): bl.remove(book) - + def sync_booklists(self, booklists, end_session=True): # There is no meta data on the device to update. The device is treated # as a mass storage device and does not use a meta data xml file like # the Sony Readers. pass - - def get_file(self, path, outfile, end_session=True): + + def get_file(self, path, outfile, end_session=True): path = self.munge_path(path) src = open(path, 'rb') shutil.copyfileobj(src, outfile, 10*1024*1024) @@ -215,7 +234,7 @@ class USBMS(Device): # the filename without the extension else: book_title = os.path.splitext(filename)[0].replace('_', ' ') - + fileext = os.path.splitext(filename)[1][1:] if fileext in cls.FORMATS: diff --git a/src/calibre/ebooks/epub/__init__.py b/src/calibre/ebooks/epub/__init__.py index d8d4c9a758..4e305b000b 100644 --- a/src/calibre/ebooks/epub/__init__.py +++ b/src/calibre/ebooks/epub/__init__.py @@ -15,15 +15,17 @@ from lxml import etree class DefaultProfile(object): - flow_size = sys.maxint - screen_size = None + flow_size = sys.maxint + screen_size = None remove_special_chars = False + remove_object_tags = False class PRS505(DefaultProfile): - flow_size = 270000 - screen_size = (590, 765) + flow_size = 270000 + screen_size = (590, 765) remove_special_chars = re.compile(u'[\u200b\u00ad]') + remove_object_tags = True PROFILES = { @@ -156,7 +158,7 @@ to auto-generate a Table of Contents. help=_('Set the right margin in pts. Default is %default')) layout('base_font_size2', ['--base-font-size'], default=12.0, help=_('The base font size in pts. Default is %defaultpt. Set to 0 to disable rescaling of fonts.')) - layout('remove_paragraph_spacing', ['--remove-paragraph-spacing'], default=True, + layout('remove_paragraph_spacing', ['--remove-paragraph-spacing'], default=False, help=_('Remove spacing between paragraphs. Will not work if the source file forces inter-paragraph spacing.')) layout('preserve_tag_structure', ['--preserve-tag-structure'], default=False, help=_('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.')) diff --git a/src/calibre/ebooks/epub/from_any.py b/src/calibre/ebooks/epub/from_any.py index 51fdcc4e6a..e81821ed53 100644 --- a/src/calibre/ebooks/epub/from_any.py +++ b/src/calibre/ebooks/epub/from_any.py @@ -16,7 +16,7 @@ from calibre.ebooks.epub import config as common_config, process_encryption from calibre.ebooks.epub.from_html import convert as html2epub, find_html_index from calibre.ptempfile import TemporaryDirectory from calibre.ebooks.metadata import MetaInformation -from calibre.ebooks.metadata.opf2 import OPFCreator +from calibre.ebooks.metadata.opf2 import OPFCreator, OPF from calibre.utils.zipfile import ZipFile from calibre.customize.ui import run_plugins_on_preprocess @@ -25,9 +25,36 @@ def lit2opf(path, tdir, opts): print 'Exploding LIT file:', path reader = LitReader(path) reader.extract_content(tdir, False) - for f in walk(tdir): - if f.lower().endswith('.opf'): - return f + opf = None + for opf in walk(tdir): + if opf.lower().endswith('.opf'): + break + if not opf.endswith('.opf'): + opf = None + if opf is not None: # Check for url-quoted filenames + _opf = OPF(opf, os.path.dirname(opf)) + replacements = [] + for item in _opf.itermanifest(): + href = item.get('href', '') + path = os.path.join(os.path.dirname(opf), *(href.split('/'))) + if not os.path.exists(path) and os.path.exists(path.replace('&', '%26')): + npath = path + path = path.replace('&', '%26') + replacements.append((path, npath)) + if replacements: + print 'Fixing quoted filenames...' + for path, npath in replacements: + if os.path.exists(path): + os.rename(path, npath) + for f in walk(tdir): + with open(f, 'r+b') as f: + raw = f.read() + for path, npath in replacements: + raw = raw.replace(os.path.basename(path), os.path.basename(npath)) + f.seek(0) + f.truncate() + f.write(raw) + return opf def mobi2opf(path, tdir, opts): from calibre.ebooks.mobi.reader import MobiReader diff --git a/src/calibre/ebooks/epub/from_feeds.py b/src/calibre/ebooks/epub/from_feeds.py index fd1759712d..6a12353f50 100644 --- a/src/calibre/ebooks/epub/from_feeds.py +++ b/src/calibre/ebooks/epub/from_feeds.py @@ -52,6 +52,7 @@ def convert(opts, recipe_arg, notification=None): print 'Generating epub...' opts.encoding = 'utf-8' + opts.remove_paragraph_spacing = True html2epub(opf, opts, notification=notification) diff --git a/src/calibre/ebooks/epub/from_html.py b/src/calibre/ebooks/epub/from_html.py index 413bea4801..d61dc0051a 100644 --- a/src/calibre/ebooks/epub/from_html.py +++ b/src/calibre/ebooks/epub/from_html.py @@ -128,6 +128,8 @@ class HTMLProcessor(Processor, Rationalizer): if hasattr(self.body, 'xpath'): for script in list(self.body.xpath('descendant::script')): script.getparent().remove(script) + + self.fix_markup() def convert_image(self, img): rpath = img.get('src', '') @@ -145,6 +147,25 @@ class HTMLProcessor(Processor, Rationalizer): if val == rpath: self.resource_map[key] = rpath+'_calibre_converted.jpg' img.set('src', rpath+'_calibre_converted.jpg') + + def fix_markup(self): + ''' + Perform various markup transforms to get the output to render correctly + in the quirky ADE. + ''' + # Replace
that are children of with

 

+ if hasattr(self.body, 'xpath'): + for br in self.body.xpath('./br'): + br.tag = 'p' + br.text = u'\u00a0' + + if self.opts.profile.remove_object_tags: + for tag in self.root.xpath('//embed'): + tag.getparent().remove(tag) + for tag in self.root.xpath('//object'): + if tag.get('type', '').lower().strip() in ('image/svg+xml',): + continue + tag.getparent().remove(tag) def save(self): for meta in list(self.root.xpath('//meta')): diff --git a/src/calibre/ebooks/epub/iterator.py b/src/calibre/ebooks/epub/iterator.py index 08c275e81f..5601e9b8de 100644 --- a/src/calibre/ebooks/epub/iterator.py +++ b/src/calibre/ebooks/epub/iterator.py @@ -95,7 +95,7 @@ class EbookIterator(object): for match in re.compile(r'@font-face\s*{([^}]+)}').finditer(css): block = match.group(1) family = re.compile(r'font-family\s*:\s*([^;]+)').search(block) - url = re.compile(r'url\s*\((.+?)\)', re.DOTALL).search(block) + url = re.compile(r'url\s*\([\'"]*(.+?)[\'"]*\)', re.DOTALL).search(block) if url: path = url.group(1).split('/') path = os.path.join(os.path.dirname(item.path), *path) diff --git a/src/calibre/ebooks/html.py b/src/calibre/ebooks/html.py index 634963e775..c853f62171 100644 --- a/src/calibre/ebooks/html.py +++ b/src/calibre/ebooks/html.py @@ -848,7 +848,7 @@ class Processor(Parser): # Workaround for anchor rendering bug in ADE css += '\n\na { color: inherit; text-decoration: inherit; cursor: default; }\na[href] { color: blue; text-decoration: underline; cursor:pointer; }' if self.opts.remove_paragraph_spacing: - css += '\n\np {text-indent: 2em; margin-top:0pt; margin-bottom:0pt; padding:0pt; border:0pt;}' + css += '\n\np {text-indent: 1.5em; margin-top:0pt; margin-bottom:0pt; padding:0pt; border:0pt;}' if self.opts.override_css: css += '\n\n' + self.opts.override_css self.override_css = self.css_parser.parseString(self.preprocess_css(css)) diff --git a/src/calibre/ebooks/metadata/lit.py b/src/calibre/ebooks/metadata/lit.py index 825fe45cf4..c38450c64c 100644 --- a/src/calibre/ebooks/metadata/lit.py +++ b/src/calibre/ebooks/metadata/lit.py @@ -6,33 +6,28 @@ Support for reading the metadata from a LIT file. import sys, cStringIO, os -from calibre import relpath from calibre.ebooks.metadata import MetaInformation -from calibre.ebooks.metadata.opf import OPFReader +from calibre.ebooks.metadata.opf2 import OPF from calibre.ebooks.lit.reader import LitReader def get_metadata(stream): - try: - litfile = LitReader(stream) - src = litfile.meta.encode('utf-8') - mi = OPFReader(cStringIO.StringIO(src), dir=os.getcwd()) - cover_url, cover_item = mi.cover, None - if cover_url: - cover_url = relpath(cover_url, os.getcwd()) - for item in litfile.manifest.values(): - if item.path == cover_url: - cover_item = item.internal - if cover_item is not None: - ext = cover_url.rpartition('.')[-1] - if not ext: - ext = 'jpg' - else: - ext = ext.lower() - cd = litfile.get_file('/data/' + cover_item) - mi.cover_data = (ext, cd) if cd else (None, None) - except: - title = stream.name if hasattr(stream, 'name') and stream.name else 'Unknown' - mi = MetaInformation(title, ['Unknown']) + litfile = LitReader(stream) + src = litfile.meta.encode('utf-8') + opf = OPF(cStringIO.StringIO(src), os.getcwd()) + mi = MetaInformation(opf) + covers = [] + for item in opf.iterguide(): + if 'cover' not in item.get('type', '').lower(): + continue + href = item.get('href', '') + candidates = [href, href.replace('&', '%26')] + for item in litfile.manifest.values(): + if item.path in candidates: + covers.append(item.internal) + break + covers = [litfile.get_file('/data/' + i) for i in covers] + covers.sort(cmp=lambda x, y:cmp(len(x), len(y))) + mi.cover_data = ('jpg', covers[-1]) return mi def main(args=sys.argv): diff --git a/src/calibre/ebooks/metadata/mobi.py b/src/calibre/ebooks/metadata/mobi.py new file mode 100644 index 0000000000..933cbbdaed --- /dev/null +++ b/src/calibre/ebooks/metadata/mobi.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net' +__docformat__ = 'restructuredtext en' + +''' +''' + +import sys, os + +from calibre.ebooks.mobi.reader import get_metadata + +def main(args=sys.argv): + if len(args) != 2: + print >>sys.stderr, 'Usage: %s file.mobi' % args[0] + return 1 + fname = args[1] + mi = get_metadata(open(fname, 'rb')) + print unicode(mi) + if mi.cover_data[1]: + cover = os.path.abspath( + '.'.join((os.path.splitext(os.path.basename(fname))[0], + mi.cover_data[0].lower()))) + open(cover, 'wb').write(mi.cover_data[1]) + print _('Cover saved to'), cover + return 0 + +if __name__ == '__main__': + sys.exit(main()) \ No newline at end of file diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index 08ed9aacd1..ed0340e4a8 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -435,7 +435,7 @@ class OPF(object): rating = MetadataField('rating', is_dc=False, formatter=int) - def __init__(self, stream, basedir=os.getcwdu()): + def __init__(self, stream, basedir=os.getcwdu(), unquote_urls=True): if not hasattr(stream, 'read'): stream = open(stream, 'rb') self.basedir = self.base_dir = basedir @@ -446,7 +446,8 @@ class OPF(object): if not self.metadata: raise ValueError('Malformed OPF file: No element') self.metadata = self.metadata[0] - self.unquote_urls() + if unquote_urls: + self.unquote_urls() self.manifest = Manifest() m = self.manifest_path(self.root) if m: diff --git a/src/calibre/ebooks/mobi/from_any.py b/src/calibre/ebooks/mobi/from_any.py index 9af2e5fe68..5607690e21 100644 --- a/src/calibre/ebooks/mobi/from_any.py +++ b/src/calibre/ebooks/mobi/from_any.py @@ -14,18 +14,21 @@ import sys, os, glob, logging from calibre.ebooks.epub.from_any import any2epub, formats, USAGE from calibre.ebooks.epub import config as common_config from calibre.ptempfile import TemporaryDirectory -from calibre.ebooks.mobi.writer import oeb2mobi, add_mobi_options +from calibre.ebooks.mobi.writer import oeb2mobi, config as mobi_config def config(defaults=None): - return common_config(defaults=defaults, name='mobi') + c = common_config(defaults=defaults, name='mobi') + c.remove_opt('profile') + mobic = mobi_config(defaults=defaults) + c.update(mobic) + return c def option_parser(usage=USAGE): usage = usage % ('Mobipocket', formats()) parser = config().option_parser(usage=usage) - add_mobi_options(parser) return parser -def any2mobi(opts, path): +def any2mobi(opts, path, notification=None): ext = os.path.splitext(path)[1] if not ext: raise ValueError('Unknown file type: '+path) diff --git a/src/calibre/ebooks/mobi/from_feeds.py b/src/calibre/ebooks/mobi/from_feeds.py new file mode 100644 index 0000000000..205550f730 --- /dev/null +++ b/src/calibre/ebooks/mobi/from_feeds.py @@ -0,0 +1,74 @@ +from __future__ import with_statement +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net' +__docformat__ = 'restructuredtext en' + +''' +Convert feeds to MOBI ebook +''' + +import sys, glob, os +from calibre.web.feeds.main import config as feeds2disk_config, USAGE, run_recipe +from calibre.ebooks.mobi.writer import config as oeb2mobi_config, oeb2mobi +from calibre.ptempfile import TemporaryDirectory +from calibre import strftime, sanitize_file_name + +def config(defaults=None): + c = feeds2disk_config(defaults=defaults) + c.remove('lrf') + c.remove('epub') + c.remove('mobi') + c.remove('output_dir') + c.update(oeb2mobi_config(defaults=defaults)) + c.remove('encoding') + c.remove('source_profile') + c.add_opt('output', ['-o', '--output'], default=None, + help=_('Output file. Default is derived from input filename.')) + return c + +def option_parser(): + c = config() + return c.option_parser(usage=USAGE) + +def convert(opts, recipe_arg, notification=None): + opts.lrf = False + opts.epub = False + opts.mobi = True + if opts.debug: + opts.verbose = 2 + parser = option_parser() + with TemporaryDirectory('_feeds2mobi') as tdir: + opts.output_dir = tdir + recipe = run_recipe(opts, recipe_arg, parser, notification=notification) + c = config() + recipe_opts = c.parse_string(recipe.oeb2mobi_options) + c.smart_update(recipe_opts, opts) + opts = recipe_opts + opf = glob.glob(os.path.join(tdir, '*.opf')) + if not opf: + raise Exception('Downloading of recipe: %s failed'%recipe_arg) + opf = opf[0] + + if opts.output is None: + fname = recipe.title + strftime(recipe.timefmt) + '.mobi' + opts.output = os.path.join(os.getcwd(), sanitize_file_name(fname)) + + print 'Generating MOBI...' + opts.encoding = 'utf-8' + opts.source_profile = 'Browser' + oeb2mobi(opts, opf) + + +def main(args=sys.argv, notification=None, handler=None): + parser = option_parser() + opts, args = parser.parse_args(args) + if len(args) != 2 and opts.feeds is None: + parser.print_help() + return 1 + recipe_arg = args[1] if len(args) > 1 else None + convert(opts, recipe_arg, notification=notification) + + return 0 + +if __name__ == '__main__': + sys.exit(main()) \ No newline at end of file diff --git a/src/calibre/ebooks/mobi/mobiml.py b/src/calibre/ebooks/mobi/mobiml.py index 7a74bd9401..afc2fb63ba 100644 --- a/src/calibre/ebooks/mobi/mobiml.py +++ b/src/calibre/ebooks/mobi/mobiml.py @@ -12,7 +12,7 @@ import copy import re from lxml import etree from calibre.ebooks.oeb.base import namespace, barename -from calibre.ebooks.oeb.base import XHTML, XHTML_NS +from calibre.ebooks.oeb.base import XHTML, XHTML_NS, OEB_DOCS from calibre.ebooks.oeb.stylizer import Stylizer from calibre.ebooks.oeb.transforms.flatcss import KeyMapper @@ -96,8 +96,11 @@ class MobiMLizer(object): href = oeb.guide['cover'].href del oeb.guide['cover'] item = oeb.manifest.hrefs[href] - oeb.manifest.remove(item) - + if item.spine_position is not None: + oeb.spine.remove(item) + if item.media_type in OEB_DOCS: + self.oeb.manifest.remove(item) + def mobimlize_spine(self): for item in self.oeb.spine: stylizer = Stylizer(item.data, item.href, self.oeb, self.profile) @@ -137,7 +140,7 @@ class MobiMLizer(object): para = bstate.para if tag in SPECIAL_TAGS and not text: para = para if para is not None else bstate.body - elif para is None: + elif para is None or tag in ('td', 'th'): body = bstate.body if bstate.pbreak: etree.SubElement(body, MBP('pagebreak')) @@ -157,7 +160,8 @@ class MobiMLizer(object): elif indent != 0 and abs(indent) < self.profile.fbase: indent = (indent / abs(indent)) * self.profile.fbase if tag in NESTABLE_TAGS: - para = wrapper = etree.SubElement(parent, XHTML(tag)) + para = wrapper = etree.SubElement( + parent, XHTML(tag), attrib=istate.attrib) bstate.nested.append(para) if tag == 'li' and len(istates) > 1: istates[-2].list_num += 1 @@ -337,6 +341,10 @@ class MobiMLizer(object): tag = 'tr' elif display == 'table-cell': tag = 'td' + if tag in TABLE_TAGS: + for attr in ('rowspan', 'colspan'): + if attr in elem.attrib: + istate.attrib[attr] = elem.attrib[attr] text = None if elem.text: if istate.preserve: @@ -374,6 +382,6 @@ class MobiMLizer(object): bstate.vpadding += bstate.vmargin bstate.vmargin = 0 bstate.vpadding += vpadding - if tag in NESTABLE_TAGS and bstate.nested: + if bstate.nested and bstate.nested[-1].tag == elem.tag: bstate.nested.pop() istates.pop() diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py index 2ed41ac898..51e184a420 100644 --- a/src/calibre/ebooks/mobi/reader.py +++ b/src/calibre/ebooks/mobi/reader.py @@ -124,6 +124,7 @@ class BookHeader(object): sublangid = (langcode >> 10) & 0xFF self.language = main_language.get(langid, 'ENGLISH') self.sublanguage = sub_language.get(sublangid, 'NEUTRAL') + self.first_image_index = struct.unpack('>L', raw[0x6c:0x6c+4])[0] self.exth_flag, = struct.unpack('>L', raw[0x80:0x84]) self.exth = None @@ -310,7 +311,7 @@ class MobiReader(object): opf.cover = 'images/%05d.jpg'%(self.book_header.exth.cover_offset+1) manifest = [(htmlfile, 'text/x-oeb1-document')] bp = os.path.dirname(htmlfile) - for i in self.image_names: + for i in getattr(self, 'image_names', []): manifest.append((os.path.join(bp, 'images/', i), 'image/jpg')) opf.create_manifest(manifest) @@ -441,17 +442,18 @@ class MobiReader(object): os.makedirs(output_dir) image_index = 0 self.image_names = [] - for i in range(self.num_sections): + for i in range(self.book_header.first_image_index, self.num_sections): if i in processed_records: continue processed_records.append(i) data = self.sections[i][0] buf = cStringIO.StringIO(data) + image_index += 1 try: im = PILImage.open(buf) except IOError: continue - image_index += 1 + path = os.path.join(output_dir, '%05d.jpg'%image_index) self.image_names.append(os.path.basename(path)) im.convert('RGB').save(open(path, 'wb'), format='JPEG') @@ -474,31 +476,23 @@ def get_metadata(stream): if mr.book_header.exth is None: mi = MetaInformation(mr.name, [_('Unknown')]) else: - tdir = tempfile.mkdtemp('_mobi_meta', __appname__+'_') - atexit.register(shutil.rmtree, tdir) - mr.extract_images([], tdir) mi = mr.create_opf('dummy.html') - if mi.cover: - cover = os.path.join(tdir, mi.cover) - if not os.access(cover, os.R_OK): - fname = os.path.basename(cover) - match = re.match(r'(\d+)(.+)', fname) - if match: - num, ext = int(match.group(1), 10), match.group(2) - while num > 0: - num -= 1 - candidate = os.path.join(os.path.dirname(cover), '%05d%s'%(num, ext)) - if os.access(candidate, os.R_OK): - cover = candidate - break - - if os.access(cover, os.R_OK): - mi.cover_data = ('JPEG', open(os.path.join(tdir, cover), 'rb').read()) - else: - path = os.path.join(tdir, 'images', '00001.jpg') - if os.access(path, os.R_OK): - mi.cover_data = ('JPEG', open(path, 'rb').read()) - return mi + try: + if hasattr(mr.book_header.exth, 'cover_offset'): + cover_index = mr.book_header.first_image_index + mr.book_header.exth.cover_offset + data = mr.sections[cover_index][0] + else: + data = mr.sections[mr.book_header.first_image_index][0] + buf = cStringIO.StringIO(data) + im = PILImage.open(buf) + obuf = cStringIO.StringIO() + im.convert('RGBA').save(obuf, format='JPEG') + mi.cover_data = ('jpg', obuf.getvalue()) + except: + import traceback + traceback.print_exc() + return mi + def option_parser(): from calibre.utils.config import OptionParser diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py index 62d444ee95..fdafd2e08b 100644 --- a/src/calibre/ebooks/mobi/writer.py +++ b/src/calibre/ebooks/mobi/writer.py @@ -34,8 +34,7 @@ from calibre.ebooks.mobi.palmdoc import compress_doc from calibre.ebooks.mobi.langcodes import iana2mobi from calibre.ebooks.mobi.mobiml import MBP_NS, MBP, MobiMLizer from calibre.customize.ui import run_plugins_on_postprocess -from calibre.utils.config import OptionParser -from optparse import OptionGroup +from calibre.utils.config import Config, StringConfig # TODO: # - Allow override CSS (?) @@ -95,6 +94,7 @@ class Serializer(object): def __init__(self, oeb, images): self.oeb = oeb self.images = images + self.logger = oeb.logger self.id_offsets = {} self.href_offsets = defaultdict(list) self.breaks = [] @@ -144,8 +144,8 @@ class Serializer(object): item = hrefs[path] if path else None if item and item.spine_position is None: return False - id = item.id if item else base.id - href = '#'.join((id, frag)) if frag else id + path = item.href if item else base.href + href = '#'.join((path, frag)) if frag else path buffer.write('filepos=') self.href_offsets[href].append(buffer.tell()) buffer.write('0000000000') @@ -170,7 +170,7 @@ class Serializer(object): buffer = self.buffer if not item.linear: self.breaks.append(buffer.tell() - 1) - self.id_offsets[item.id] = buffer.tell() + self.id_offsets[item.href] = buffer.tell() for elem in item.data.find(XHTML('body')): self.serialize_elem(elem, item) buffer.write('') @@ -180,12 +180,11 @@ class Serializer(object): if not isinstance(elem.tag, basestring) \ or namespace(elem.tag) not in nsrmap: return - hrefs = self.oeb.manifest.hrefs tag = prefixname(elem.tag, nsrmap) for attr in ('name', 'id'): if attr in elem.attrib: - id = '#'.join((item.id, elem.attrib[attr])) - self.id_offsets[id] = buffer.tell() + href = '#'.join((item.href, elem.attrib[attr])) + self.id_offsets[href] = buffer.tell() del elem.attrib[attr] if tag == 'a' and not elem.attrib \ and not len(elem) and not elem.text: @@ -203,7 +202,7 @@ class Serializer(object): continue elif attr == 'src': href = item.abshref(val) - if href in hrefs: + if href in self.images: index = self.images[href] buffer.write('recindex="%05d"' % index) continue @@ -233,8 +232,12 @@ class Serializer(object): def fixup_links(self): buffer = self.buffer - for id, hoffs in self.href_offsets.items(): - ioff = self.id_offsets[id] + id_offsets = self.id_offsets + for href, hoffs in self.href_offsets.items(): + if href not in id_offsets: + self.logger.warn('Hyperlink target %r not found' % href) + href, _ = urldefrag(href) + ioff = self.id_offsets[href] for hoff in hoffs: buffer.seek(hoff) buffer.write('%010d' % ioff) @@ -360,7 +363,11 @@ class MobiWriter(object): if image.format not in ('JPEG', 'GIF'): width, height = image.size area = width * height - format = 'GIF' if area <= 40000 else 'JPEG' + if area <= 40000: + format = 'GIF' + else: + image = image.convert('RGBA') + format = 'JPEG' changed = True if dimen is not None: image.thumbnail(dimen, Image.ANTIALIAS) @@ -494,41 +501,45 @@ class MobiWriter(object): self._write(record) -def add_mobi_options(parser): - profiles = Context.PROFILES.keys() - profiles.sort() - profiles = ', '.join(profiles) - group = OptionGroup(parser, _('Mobipocket'), - _('Mobipocket-specific options.')) - group.add_option( - '-c', '--compress', default=False, action='store_true', - help=_('Compress file text using PalmDOC compression. ' +def config(defaults=None): + desc = _('Options to control the conversion to MOBI') + _profiles = list(sorted(Context.PROFILES.keys())) + if defaults is None: + c = Config('mobi', desc) + else: + c = StringConfig(defaults, desc) + + mobi = c.add_group('mobipocket', _('Mobipocket-specific options.')) + mobi('compress', ['--compress'], default=False, + help=_('Compress file text using PalmDOC compression. ' 'Results in smaller files, but takes a long time to run.')) - group.add_option( - '-r', '--rescale-images', default=False, action='store_true', + mobi('rescale_images', ['--rescale-images'], default=False, help=_('Modify images to meet Palm device size limitations.')) - parser.add_option_group(group) - group = OptionGroup(parser, _('Profiles'), _('Device renderer profiles. ' - 'Affects conversion of default font sizes and rasterization ' - 'resolution. Valid profiles are: %s.') % profiles) - group.add_option( - '--source-profile', default='Browser', metavar='PROFILE', - help=_("Source renderer profile. Default is 'Browser'.")) - group.add_option( - '--dest-profile', default='CybookG3', metavar='PROFILE', - help=_("Destination renderer profile. Default is 'CybookG3'.")) - parser.add_option_group(group) - return - + mobi('toc_title', ['--toc-title'], default=None, + help=_('Title for any generated in-line table of contents.')) + profiles = c.add_group('profiles', _('Device renderer profiles. ' + 'Affects conversion of font sizes, image rescaling and rasterization ' + 'of tables. Valid profiles are: %s.') % ', '.join(_profiles)) + profiles('source_profile', ['--source-profile'], + default='Browser', choices=_profiles, + help=_("Source renderer profile. Default is %default.")) + profiles('dest_profile', ['--dest-profile'], + default='CybookG3', choices=_profiles, + help=_("Destination renderer profile. Default is %default.")) + c.add_opt('encoding', ['--encoding'], default=None, + help=_('Character encoding for HTML files. Default is to auto detect.')) + return c + + def option_parser(): - parser = OptionParser(usage=_('%prog [options] OPFFILE')) + c = config() + parser = c.option_parser(usage='%prog '+_('[options]')+' file.opf') parser.add_option( '-o', '--output', default=None, help=_('Output file. Default is derived from input filename.')) parser.add_option( '-v', '--verbose', default=0, action='count', help=_('Useful for debugging.')) - add_mobi_options(parser) return parser def oeb2mobi(opts, inpath): @@ -549,8 +560,8 @@ def oeb2mobi(opts, inpath): compression = PALMDOC if opts.compress else UNCOMPRESSED imagemax = PALM_MAX_IMAGE_SIZE if opts.rescale_images else None context = Context(source, dest) - oeb = OEBBook(inpath, logger=logger) - tocadder = HTMLTOCAdder() + oeb = OEBBook(inpath, logger=logger, encoding=opts.encoding) + tocadder = HTMLTOCAdder(title=opts.toc_title) tocadder.transform(oeb, context) mangler = CaseMangler() mangler.transform(oeb, context) diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index 4248657e23..c2d30eb2c3 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -15,11 +15,12 @@ from urlparse import urldefrag, urlparse, urlunparse from urllib import unquote as urlunquote import logging import re -import htmlentitydefs import uuid import copy from lxml import etree +from lxml import html from calibre import LoggingInterface +from calibre.translations.dynamic import translate XML_PARSER = etree.XMLParser(recover=True) XML_NS = 'http://www.w3.org/XML/1998/namespace' @@ -67,14 +68,6 @@ OEB_IMAGES = set([GIF_MIME, JPEG_MIME, PNG_MIME, SVG_MIME]) MS_COVER_TYPE = 'other.ms-coverimage-standard' -recode = lambda s: s.decode('iso-8859-1').encode('ascii', 'xmlcharrefreplace') -ENTITYDEFS = dict((k, recode(v)) for k, v in htmlentitydefs.entitydefs.items()) -del ENTITYDEFS['lt'] -del ENTITYDEFS['gt'] -del ENTITYDEFS['quot'] -del ENTITYDEFS['amp'] -del recode - def element(parent, *args, **kwargs): if parent is not None: @@ -97,6 +90,9 @@ def prefixname(name, nsrmap): return barename(name) return ':'.join((prefix, barename(name))) +def XPath(expr): + return etree.XPath(expr, namespaces=XPNSMAP) + def xpath(elem, expr): return elem.xpath(expr, namespaces=XPNSMAP) @@ -298,17 +294,20 @@ class Metadata(object): class Manifest(object): class Item(object): - ENTITY_RE = re.compile(r'&([a-zA-Z_:][a-zA-Z0-9.-_:]+);') NUM_RE = re.compile('^(.*)([0-9][0-9.]*)(?=[.]|$)') + META_XP = XPath('/h:html/h:head/h:meta[@http-equiv="Content-Type"]') - def __init__(self, id, href, media_type, + def __init__(self, oeb, id, href, media_type, fallback=None, loader=str, data=None): + self.oeb = oeb self.id = id self.href = self.path = urlnormalize(href) self.media_type = media_type self.fallback = fallback self.spine_position = None self.linear = True + if loader is None and data is None: + loader = oeb.container.read self._loader = loader self._data = data @@ -317,13 +316,20 @@ class Manifest(object): % (self.id, self.href, self.media_type) def _force_xhtml(self, data): - repl = lambda m: ENTITYDEFS.get(m.group(1), m.group(0)) - data = self.ENTITY_RE.sub(repl, data) - data = etree.fromstring(data, parser=XML_PARSER) + if self.oeb.encoding is not None: + data = data.decode(self.oeb.encoding, 'replace') + try: + data = etree.fromstring(data, parser=XML_PARSER) + except etree.XMLSyntaxError: + data = html.fromstring(data) + data = etree.tostring(data, encoding=unicode) + data = etree.fromstring(data, parser=XML_PARSER) if namespace(data.tag) != XHTML_NS: data.attrib['xmlns'] = XHTML_NS - data = etree.tostring(data) + data = etree.tostring(data, encoding=unicode) data = etree.fromstring(data, parser=XML_PARSER) + for meta in self.META_XP(data): + meta.getparent().remove(meta) return data def data(): @@ -400,9 +406,8 @@ class Manifest(object): self.hrefs = {} def add(self, id, href, media_type, fallback=None, loader=None, data=None): - loader = loader or self.oeb.container.read item = self.Item( - id, href, media_type, fallback, loader, data) + self.oeb, id, href, media_type, fallback, loader, data) self.ids[item.id] = item self.hrefs[item.href] = item return item @@ -506,6 +511,7 @@ class Spine(object): self.items.pop(index) for i in xrange(index, len(self.items)): self.items[i].spine_position = i + item.spine_position = None def __iter__(self): for item in self.items: @@ -539,27 +545,36 @@ class Spine(object): class Guide(object): class Reference(object): - _TYPES_TITLES = [('cover', 'Cover'), ('title-page', 'Title Page'), - ('toc', 'Table of Contents'), ('index', 'Index'), - ('glossary', 'Glossary'), ('acknowledgements', 'Acknowledgements'), - ('bibliography', 'Bibliography'), ('colophon', 'Colophon'), - ('copyright-page', 'Copyright'), ('dedication', 'Dedication'), - ('epigraph', 'Epigraph'), ('foreword', 'Foreword'), - ('loi', 'List of Illustrations'), ('lot', 'List of Tables'), - ('notes', 'Notes'), ('preface', 'Preface'), - ('text', 'Main Text')] + _TYPES_TITLES = [('cover', __('Cover')), + ('title-page', __('Title Page')), + ('toc', __('Table of Contents')), + ('index', __('Index')), + ('glossary', __('Glossary')), + ('acknowledgements', __('Acknowledgements')), + ('bibliography', __('Bibliography')), + ('colophon', __('Colophon')), + ('copyright-page', __('Copyright')), + ('dedication', __('Dedication')), + ('epigraph', __('Epigraph')), + ('foreword', __('Foreword')), + ('loi', __('List of Illustrations')), + ('lot', __('List of Tables')), + ('notes', __('Notes')), + ('preface', __('Preface')), + ('text', __('Main Text'))] TYPES = set(t for t, _ in _TYPES_TITLES) TITLES = dict(_TYPES_TITLES) ORDER = dict((t, i) for (t, _), i in izip(_TYPES_TITLES, count(0))) - def __init__(self, type, title, href): + def __init__(self, oeb, type, title, href): + self.oeb = oeb if type.lower() in self.TYPES: type = type.lower() elif type not in self.TYPES and \ not type.startswith('other.'): type = 'other.' + type - if not title: - title = self.TITLES.get(type, None) + if not title and type in self.TITLES: + title = oeb.translate(self.TITLES[type]) self.type = type self.title = title self.href = urlnormalize(href) @@ -578,13 +593,21 @@ class Guide(object): if not isinstance(other, Guide.Reference): return NotImplemented return cmp(self._order, other._order) + + def item(): + def fget(self): + path, frag = urldefrag(self.href) + hrefs = self.oeb.manifest.hrefs + return hrefs.get(path, None) + return property(fget=fget) + item = item() def __init__(self, oeb): self.oeb = oeb self.refs = {} def add(self, type, title, href): - ref = self.Reference(type, title, href) + ref = self.Reference(self.oeb, type, title, href) self.refs[type] = ref return ref @@ -594,9 +617,7 @@ class Guide(object): __iter__ = iterkeys def values(self): - values = list(self.refs.values()) - values.sort() - return values + return sorted(self.refs.values()) def items(self): for type, ref in self.refs.items(): @@ -680,31 +701,33 @@ class TOC(object): node.to_opf1(tour) return tour - def to_ncx(self, parent, playorder=None, depth=1): - if not playorder: playorder = [0] + def to_ncx(self, parent, order=None, depth=1): + if not order: order = [0] for node in self.nodes: - playorder[0] += 1 + order[0] += 1 + playOrder = str(order[0]) + id = self.id or 'np' + playOrder point = etree.SubElement(parent, - NCX('navPoint'), attrib={'playOrder': str(playorder[0])}) + NCX('navPoint'), id=id, playOrder=playOrder) if self.klass: point.attrib['class'] = node.klass - if self.id: - point.attrib['id'] = node.id label = etree.SubElement(point, NCX('navLabel')) etree.SubElement(label, NCX('text')).text = node.title href = node.href if depth > 1 else urldefrag(node.href)[0] child = etree.SubElement(point, NCX('content'), attrib={'src': href}) - node.to_ncx(point, playorder, depth+1) + node.to_ncx(point, order, depth+1) return parent class OEBBook(object): - def __init__(self, opfpath=None, container=None, logger=FauxLogger()): + def __init__(self, opfpath=None, container=None, encoding=None, + logger=FauxLogger()): if opfpath and not container: container = DirContainer(os.path.dirname(opfpath)) opfpath = os.path.basename(opfpath) self.container = container + self.encoding = encoding self.logger = logger if opfpath or container: opf = self._read_opf(opfpath) @@ -802,12 +825,20 @@ class OEBBook(object): def _manifest_from_opf(self, opf): self.manifest = manifest = Manifest(self) for elem in xpath(opf, '/o2:package/o2:manifest/o2:item'): + id = elem.get('id') href = elem.get('href') + media_type = elem.get('media-type') + fallback = elem.get('fallback') + if href in manifest.hrefs: + self.logger.warn(u'Duplicate manifest entry for %r.' % href) + continue if not self.container.exists(href): self.logger.warn(u'Manifest item %r not found.' % href) continue - manifest.add(elem.get('id'), href, elem.get('media-type'), - elem.get('fallback')) + if id in manifest.ids: + self.logger.warn(u'Duplicate manifest id %r.' % id) + id, href = manifest.generate(id, href) + manifest.add(id, href, media_type, fallback) def _spine_from_opf(self, opf): self.spine = spine = Spine(self) @@ -970,6 +1001,11 @@ class OEBBook(object): self._toc_from_opf(opf) self._ensure_cover_image() + def translate(self, text): + lang = str(self.metadata.language[0]) + lang = lang.split('-', 1)[0].lower() + return translate(lang, text) + def to_opf1(self): package = etree.Element('package', attrib={'unique-identifier': self.uid.id}) @@ -983,22 +1019,11 @@ class OEBBook(object): guide = self.guide.to_opf1(package) return {OPF_MIME: ('content.opf', package)} - def _generate_ncx_item(self): - id = 'ncx' - index = 0 - while id in self.manifest: - id = 'ncx' + str(index) - index = index + 1 - href = 'toc' - index = 0 - while (href + '.ncx') in self.manifest.hrefs: - href = 'toc' + str(index) - href += '.ncx' - return (id, href) - def _to_ncx(self): - ncx = etree.Element(NCX('ncx'), attrib={'version': '2005-1'}, - nsmap={None: NCX_NS}) + lang = unicode(self.metadata.language[0]) + ncx = etree.Element(NCX('ncx'), + attrib={'version': '2005-1', XML('lang'): lang}, + nsmap={None: NCX_NS}) head = etree.SubElement(ncx, NCX('head')) etree.SubElement(head, NCX('meta'), attrib={'name': 'dtb:uid', 'content': unicode(self.uid)}) @@ -1021,7 +1046,7 @@ class OEBBook(object): nsmap={None: OPF2_NS}) metadata = self.metadata.to_opf2(package) manifest = self.manifest.to_opf2(package) - id, href = self._generate_ncx_item() + id, href = self.manifest.generate('ncx', 'toc.ncx') etree.SubElement(manifest, OPF('item'), attrib={'id': id, 'href': href, 'media-type': NCX_MIME}) spine = self.spine.to_opf2(package) diff --git a/src/calibre/ebooks/oeb/stylizer.py b/src/calibre/ebooks/oeb/stylizer.py index 8668d89975..6549c8eccd 100644 --- a/src/calibre/ebooks/oeb/stylizer.py +++ b/src/calibre/ebooks/oeb/stylizer.py @@ -223,8 +223,11 @@ class Stylizer(object): for key in composition: style[key] = 'inherit' else: - primitives = [v.cssText for v in cssvalue] - primitites.reverse() + try: + primitives = [v.cssText for v in cssvalue] + except TypeError: + primitives = [cssvalue.cssText] + primitives.reverse() value = primitives.pop() for key in composition: if cssproperties.cssvalues[key](value): diff --git a/src/calibre/ebooks/oeb/transforms/htmltoc.py b/src/calibre/ebooks/oeb/transforms/htmltoc.py index 9eaa04d41d..5508b58ec3 100644 --- a/src/calibre/ebooks/oeb/transforms/htmltoc.py +++ b/src/calibre/ebooks/oeb/transforms/htmltoc.py @@ -13,6 +13,10 @@ from calibre.ebooks.oeb.base import XML, XHTML, XHTML_NS from calibre.ebooks.oeb.base import XHTML_MIME, CSS_MIME from calibre.ebooks.oeb.base import element +__all__ = ['HTMLTOCAdder'] + +DEFAULT_TITLE = __('Table of Contents') + STYLE_CSS = { 'nested': """ .calibre_toc_header { @@ -44,13 +48,15 @@ body > .calibre_toc_block { } class HTMLTOCAdder(object): - def __init__(self, style='nested'): + def __init__(self, title=None, style='nested'): + self.title = title self.style = style def transform(self, oeb, context): if 'toc' in oeb.guide: return oeb.logger.info('Generating in-line TOC...') + title = self.title or oeb.translate(DEFAULT_TITLE) style = self.style if style not in STYLE_CSS: oeb.logger.error('Unknown TOC style %r' % style) @@ -61,15 +67,15 @@ class HTMLTOCAdder(object): contents = element(None, XHTML('html'), nsmap={None: XHTML_NS}, attrib={XML('lang'): language}) head = element(contents, XHTML('head')) - title = element(head, XHTML('title')) - title.text = 'Table of Contents' + htitle = element(head, XHTML('title')) + htitle.text = title element(head, XHTML('link'), rel='stylesheet', type=CSS_MIME, href=css_href) body = element(contents, XHTML('body'), attrib={'class': 'calibre_toc'}) h1 = element(body, XHTML('h1'), attrib={'class': 'calibre_toc_header'}) - h1.text = 'Table of Contents' + h1.text = title self.add_toc_level(body, oeb.toc) id, href = oeb.manifest.generate('contents', 'contents.xhtml') item = oeb.manifest.add(id, href, XHTML_MIME, data=contents) diff --git a/src/calibre/ebooks/oeb/transforms/trimmanifest.py b/src/calibre/ebooks/oeb/transforms/trimmanifest.py index bd2c388245..b150a12831 100644 --- a/src/calibre/ebooks/oeb/transforms/trimmanifest.py +++ b/src/calibre/ebooks/oeb/transforms/trimmanifest.py @@ -41,8 +41,9 @@ class ManifestTrimmer(object): while unchecked: new = set() for item in unchecked: - if item.media_type in OEB_DOCS or \ - item.media_type[-4:] in ('/xml', '+xml'): + if (item.media_type in OEB_DOCS or + item.media_type[-4:] in ('/xml', '+xml')) and \ + item.data is not None: hrefs = [sel(item.data) for sel in LINK_SELECTORS] for href in chain(*hrefs): href = item.abshref(href) diff --git a/src/calibre/gui2/dialogs/epub.py b/src/calibre/gui2/dialogs/epub.py index 8bd5ef9331..af5622169a 100644 --- a/src/calibre/gui2/dialogs/epub.py +++ b/src/calibre/gui2/dialogs/epub.py @@ -15,7 +15,7 @@ from lxml.etree import XPath from calibre.gui2.dialogs.choose_format import ChooseFormatDialog from calibre.gui2.dialogs.epub_ui import Ui_Dialog from calibre.gui2 import error_dialog, choose_images, pixmap_to_data -from calibre.ebooks.epub.from_any import SOURCE_FORMATS, config +from calibre.ebooks.epub.from_any import SOURCE_FORMATS, config as epubconfig from calibre.ebooks.metadata import MetaInformation from calibre.ptempfile import PersistentTemporaryFile from calibre.ebooks.metadata.opf import OPFCreator @@ -24,9 +24,12 @@ from calibre.ebooks.metadata import authors_to_string, string_to_authors class Config(QDialog, Ui_Dialog): - def __init__(self, parent, db, row=None): + OUTPUT = 'EPUB' + + def __init__(self, parent, db, row=None, config=epubconfig): QDialog.__init__(self, parent) self.setupUi(self) + self.hide_controls() self.connect(self.category_list, SIGNAL('itemEntered(QListWidgetItem *)'), self.show_category_help) self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover) @@ -38,7 +41,7 @@ class Config(QDialog, Ui_Dialog): if row is not None: self.id = db.id(row) base = config().as_string() + '\n\n' - defaults = self.db.conversion_options(self.id, 'epub') + defaults = self.db.conversion_options(self.id, self.OUTPUT.lower()) defaults = base + (defaults if defaults else '') self.config = config(defaults=defaults) else: @@ -47,9 +50,18 @@ class Config(QDialog, Ui_Dialog): self.get_source_format() self.category_list.setCurrentRow(0) if self.row is None: - self.setWindowTitle(_('Bulk convert to EPUB')) + self.setWindowTitle(_('Bulk convert to ')+self.OUTPUT) else: - self.setWindowTitle(_(u'Convert %s to EPUB')%unicode(self.title.text())) + self.setWindowTitle((_(u'Convert %s to ')%unicode(self.title.text()))+self.OUTPUT) + + def hide_controls(self): + self.source_profile_label.setVisible(False) + self.opt_source_profile.setVisible(False) + self.dest_profile_label.setVisible(False) + self.opt_dest_profile.setVisible(False) + self.opt_toc_title.setVisible(False) + self.toc_title_label.setVisible(False) + self.opt_rescale_images.setVisible(False) def initialize(self): self.__w = [] @@ -81,8 +93,8 @@ class Config(QDialog, Ui_Dialog): def show_category_help(self, item): text = unicode(item.text()) help = { - _('Metadata') : _('Specify metadata such as title and author for the book.\n\nMetadata will be updated in the database as well as the generated EPUB file.'), - _('Look & Feel') : _('Adjust the look of the generated EPUB file by specifying things like font sizes.'), + _('Metadata') : _('Specify metadata such as title and author for the book.\n\nMetadata will be updated in the database as well as the generated %s file.')%self.OUTPUT, + _('Look & Feel') : _('Adjust the look of the generated ebook by specifying things like font sizes.'), _('Page Setup') : _('Specify the page layout settings like margins.'), _('Chapter Detection') : _('Fine tune the detection of chapter and section headings.'), } @@ -195,7 +207,7 @@ class Config(QDialog, Ui_Dialog): elif isinstance(g, QCheckBox): self.config.set(pref.name, bool(g.isChecked())) if self.row is not None: - self.db.set_conversion_options(self.id, 'epub', self.config.src) + self.db.set_conversion_options(self.id, self.OUTPUT.lower(), self.config.src) def initialize_options(self): @@ -235,7 +247,7 @@ class Config(QDialog, Ui_Dialog): elif len(choices) == 1: self.source_format = choices[0] else: - d = ChooseFormatDialog(self.parent(), _('Choose the format to convert to EPUB'), choices) + d = ChooseFormatDialog(self.parent(), _('Choose the format to convert to ')+self.OUTPUT, choices) if d.exec_() == QDialog.Accepted: self.source_format = d.format() diff --git a/src/calibre/gui2/dialogs/epub.ui b/src/calibre/gui2/dialogs/epub.ui index f19ed7ed1a..7e3ad344ce 100644 --- a/src/calibre/gui2/dialogs/epub.ui +++ b/src/calibre/gui2/dialogs/epub.ui @@ -89,36 +89,6 @@ Book Cover - - - - - - - - - :/images/book.svg - - - true - - - Qt::AlignCenter - - - - - - - - - Use cover from &source file - - - true - - - @@ -170,6 +140,36 @@ + + + + Use cover from &source file + + + true + + + + + + + + + + + + :/images/book.svg + + + true + + + Qt::AlignCenter + + + + + opt_prefer_metadata_cover @@ -456,6 +456,13 @@ + + + + &Rescale images + + + @@ -475,7 +482,7 @@ - + &Profile: @@ -494,7 +501,7 @@ - + &Left Margin: @@ -504,7 +511,7 @@ - + pt @@ -517,7 +524,7 @@ - + &Right Margin: @@ -527,7 +534,7 @@ - + pt @@ -540,7 +547,7 @@ - + &Top Margin: @@ -550,7 +557,7 @@ - + pt @@ -563,7 +570,7 @@ - + &Bottom Margin: @@ -573,7 +580,7 @@ - + pt @@ -586,13 +593,39 @@ - + Do not &split on page breaks + + + + &Source profile: + + + opt_source_profile + + + + + + + + + + &Destination profile: + + + opt_dest_profile + + + + + + @@ -721,6 +754,19 @@ p, li { white-space: pre-wrap; } + + + + + + + &Title for generated TOC + + + opt_toc_title + + + diff --git a/src/calibre/gui2/dialogs/mobi.py b/src/calibre/gui2/dialogs/mobi.py new file mode 100644 index 0000000000..4019950c23 --- /dev/null +++ b/src/calibre/gui2/dialogs/mobi.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net' +__docformat__ = 'restructuredtext en' + +from calibre.gui2.dialogs.epub import Config as _Config +from calibre.ebooks.mobi.from_any import config as mobiconfig + +class Config(_Config): + + OUTPUT = 'MOBI' + + def __init__(self, parent, db, row=None): + _Config.__init__(self, parent, db, row=row, config=mobiconfig) + + def hide_controls(self): + self.profile_label.setVisible(False) + self.opt_profile.setVisible(False) + self.opt_dont_split_on_page_breaks.setVisible(False) + self.opt_preserve_tag_structure.setVisible(False) \ No newline at end of file diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index 8a737fd608..0e426aaf42 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -162,7 +162,8 @@ class BooksModel(QAbstractTableModel): def refresh_ids(self, ids, current_row=-1): rows = self.db.refresh_ids(ids) - self.refresh_rows(rows, current_row=current_row) + if rows: + self.refresh_rows(rows, current_row=current_row) def refresh_rows(self, rows, current_row=-1): for row in rows: @@ -261,7 +262,17 @@ class BooksModel(QAbstractTableModel): self.reset() self.sorted_on = (self.column_map[col], order) - + + def refresh(self, reset=True): + try: + col = self.column_map.index(self.sorted_on[0]) + except: + col = 0 + self.db.refresh(field=self.column_map[col], + ascending=self.sorted_on[1]==Qt.AscendingOrder) + if reset: + self.reset() + def resort(self, reset=True): try: col = self.column_map.index(self.sorted_on[0]) diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 0910f85dac..ba59908c9f 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -25,7 +25,6 @@ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \ max_available_height, config, info_dialog, \ available_width from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror -from calibre.library.database import LibraryDatabase from calibre.gui2.dialogs.scheduler import Scheduler from calibre.gui2.update import CheckForUpdates from calibre.gui2.dialogs.progress import ProgressDialog @@ -131,14 +130,14 @@ class Main(MainWindow, Ui_MainWindow): QObject.connect(self.stack, SIGNAL('currentChanged(int)'), self.location_view.location_changed) - self.output_formats = sorted(['EPUB', 'LRF']) + self.output_formats = sorted(['EPUB', 'MOBI', 'LRF']) for f in self.output_formats: self.output_format.addItem(f) self.output_format.setCurrentIndex(self.output_formats.index(prefs['output_format'])) def change_output_format(x): of = unicode(x).strip() if of != prefs['output_format']: - if of in ('EPUB', 'LIT'): + if of not in ('LRF',): 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) @@ -296,6 +295,8 @@ class Main(MainWindow, Ui_MainWindow): self.card_view.connect_dirtied_signal(self.upload_booklists) self.show() + if self.system_tray_icon.isVisible() and opts.start_in_tray: + self.hide() self.stack.setCurrentIndex(0) try: db = LibraryDatabase2(self.library_path) @@ -309,18 +310,7 @@ class Main(MainWindow, Ui_MainWindow): self.library_path = dir db = LibraryDatabase2(self.library_path) self.library_view.set_database(db) - if self.olddb is not None: - pd = QProgressDialog('', '', 0, 100, self) - pd.setWindowModality(Qt.ApplicationModal) - pd.setCancelButton(None) - pd.setWindowTitle(_('Migrating database')) - pd.show() - number_of_books = db.migrate_old(self.olddb, pd) - self.olddb.close() - if number_of_books == 0: - os.remove(self.olddb.dbpath) - self.olddb = None - prefs['library_path'] = self.library_path + prefs['library_path'] = self.library_path self.library_view.sortByColumn(*dynamic.get('sort_column', ('timestamp', Qt.DescendingOrder))) if not self.library_view.restore_column_widths(): self.library_view.resizeColumnsToContents() @@ -488,7 +478,7 @@ class Main(MainWindow, Ui_MainWindow): self.raise_() self.activateWindow() elif msg.startswith('refreshdb:'): - self.library_view.model().resort() + self.library_view.model().refresh() self.library_view.model().research() else: print msg @@ -1392,39 +1382,14 @@ class Main(MainWindow, Ui_MainWindow): def initialize_database(self): self.library_path = prefs['library_path'] - self.olddb = None if self.library_path is None: # Need to migrate to new database layout - QMessageBox.information(self, 'Database format changed', - '''\ -

calibre's book storage format has changed. Instead of storing book files in a database, the -files are now stored in a folder on your filesystem. You will now be asked to choose the folder -in which you want to store your books files. Any existing books will be automatically migrated. - ''') - self.database_path = prefs['database_path'] - if not os.access(os.path.dirname(self.database_path), os.W_OK): - error_dialog(self, _('Database does not exist'), - _('The directory in which the database should be: %s no longer exists. Please choose a new database location.')%self.database_path).exec_() - self.database_path = choose_dir(self, 'database path dialog', - _('Choose new location for database')) - if not self.database_path: - self.database_path = os.path.expanduser('~').decode(sys.getfilesystemencoding()) - if not os.path.exists(self.database_path): - os.makedirs(self.database_path) - self.database_path = os.path.join(self.database_path, 'library1.db') - prefs['database_path'] = self.database_path - home = os.path.dirname(self.database_path) - if not os.path.exists(home): - home = os.getcwd() dir = unicode(QFileDialog.getExistingDirectory(self, - _('Choose a location for your ebook library.'), home)) + _('Choose a location for your ebook library.'), os.getcwd())) if not dir: - dir = os.path.dirname(self.database_path) + dir = os.path.expanduser('~/Library') self.library_path = os.path.abspath(dir) - try: - self.olddb = LibraryDatabase(self.database_path) - except: - traceback.print_exc() - self.olddb = None + if not os.path.exists(self.library_path): + os.makedirs(self.library_path) def read_settings(self): @@ -1563,6 +1528,8 @@ path_to_ebook to the database. ''') parser.add_option('--with-library', default=None, action='store', help=_('Use the library located at the specified path.')) + parser.add_option('--start-in-tray', default=False, action='store_true', + help=_('Start minimized to system tray.')) parser.add_option('-v', '--verbose', default=0, action='count', help=_('Log debugging information to console')) return parser diff --git a/src/calibre/gui2/tools.py b/src/calibre/gui2/tools.py index c00fbfe8e3..aca2da74e2 100644 --- a/src/calibre/gui2/tools.py +++ b/src/calibre/gui2/tools.py @@ -12,6 +12,7 @@ from PyQt4.Qt import QDialog from calibre.utils.config import prefs from calibre.gui2.dialogs.lrf_single import LRFSingleDialog, LRFBulkDialog from calibre.gui2.dialogs.epub import Config as EPUBConvert +from calibre.gui2.dialogs.mobi import Config as MOBIConvert import calibre.gui2.dialogs.comicconf as ComicConf from calibre.gui2 import warning_dialog from calibre.ptempfile import PersistentTemporaryFile @@ -19,14 +20,20 @@ from calibre.ebooks.lrf import preferred_source_formats as LRF_PREFERRED_SOURCE_ from calibre.ebooks.metadata.opf import OPFCreator from calibre.ebooks.epub.from_any import SOURCE_FORMATS as EPUB_PREFERRED_SOURCE_FORMATS -def convert_single_epub(parent, db, comics, others): +def get_dialog(fmt): + return { + 'epub':EPUBConvert, + 'mobi':MOBIConvert, + }[fmt] + +def convert_single(fmt, parent, db, comics, others): changed = False jobs = [] others_ids = [db.id(row) for row in others] comics_ids = [db.id(row) for row in comics] for row, row_id in zip(others, others_ids): temp_files = [] - d = EPUBConvert(parent, db, row) + d = get_dialog(fmt)(parent, db, row) if d.source_format is not None: d.exec_() if d.result() == QDialog.Accepted: @@ -35,7 +42,7 @@ def convert_single_epub(parent, db, comics, others): pt = PersistentTemporaryFile('.'+d.source_format.lower()) pt.write(data) pt.close() - of = PersistentTemporaryFile('.epub') + of = PersistentTemporaryFile('.'+fmt) of.close() opts.output = of.name opts.from_opf = d.opf_file.name @@ -45,8 +52,8 @@ def convert_single_epub(parent, db, comics, others): temp_files.append(d.cover_file) opts.cover = d.cover_file.name temp_files.extend([d.opf_file, pt, of]) - jobs.append(('any2epub', args, _('Convert book: ')+d.mi.title, - 'EPUB', row_id, temp_files)) + jobs.append(('any2'+fmt, args, _('Convert book: ')+d.mi.title, + fmt.upper(), row_id, temp_files)) changed = True for row, row_id in zip(comics, comics_ids): @@ -61,24 +68,24 @@ def convert_single_epub(parent, db, comics, others): if defaults is not None: db.set_conversion_options(db.id(row), 'comic', defaults) if opts is None: continue - for fmt in ['cbz', 'cbr']: + for _fmt in ['cbz', 'cbr']: try: - data = db.format(row, fmt.upper()) + data = db.format(row, _fmt.upper()) if data is not None: break except: continue - pt = PersistentTemporaryFile('.'+fmt) + pt = PersistentTemporaryFile('.'+_fmt) pt.write(data) pt.close() - of = PersistentTemporaryFile('.epub') + of = PersistentTemporaryFile('.'+fmt) of.close() opts.output = of.name opts.verbose = 2 args = [pt.name, opts] changed = True - jobs.append(('comic2epub', args, _('Convert comic: ')+opts.title, - 'EPUB', row_id, [pt, of])) + jobs.append(('comic2'+fmt, args, _('Convert comic: ')+opts.title, + fmt.upper(), row_id, [pt, of])) return jobs, changed @@ -146,9 +153,9 @@ def convert_single_lrf(parent, db, comics, others): return jobs, changed -def convert_bulk_epub(parent, db, comics, others): +def convert_bulk(fmt, parent, db, comics, others): if others: - d = EPUBConvert(parent, db) + d = get_dialog(fmt)(parent, db) if d.exec_() != QDialog.Accepted: others = [] else: @@ -169,9 +176,9 @@ def convert_bulk_epub(parent, db, comics, others): row_id = db.id(row) if row in others: data = None - for fmt in EPUB_PREFERRED_SOURCE_FORMATS: + for _fmt in EPUB_PREFERRED_SOURCE_FORMATS: try: - data = db.format(row, fmt.upper()) + data = db.format(row, _fmt.upper()) if data is not None: break except: @@ -185,10 +192,10 @@ def convert_bulk_epub(parent, db, comics, others): opf_file = PersistentTemporaryFile('.opf') opf.render(opf_file) opf_file.close() - pt = PersistentTemporaryFile('.'+fmt.lower()) + pt = PersistentTemporaryFile('.'+_fmt.lower()) pt.write(data) pt.close() - of = PersistentTemporaryFile('.epub') + of = PersistentTemporaryFile('.'+fmt) of.close() cover = db.cover(row) cf = None @@ -203,7 +210,7 @@ def convert_bulk_epub(parent, db, comics, others): desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title)) temp_files = [cf] if cf is not None else [] temp_files.extend([opf_file, pt, of]) - jobs.append(('any2epub', args, desc, 'EPUB', row_id, temp_files)) + jobs.append(('any2'+fmt, args, desc, fmt.upper(), row_id, temp_files)) else: options = comic_opts.copy() mi = db.get_metadata(row) @@ -212,24 +219,24 @@ def convert_bulk_epub(parent, db, comics, others): if mi.authors: options.author = ','.join(mi.authors) data = None - for fmt in ['cbz', 'cbr']: + for _fmt in ['cbz', 'cbr']: try: - data = db.format(row, fmt.upper()) + data = db.format(row, _fmt.upper()) if data is not None: break except: continue - pt = PersistentTemporaryFile('.'+fmt.lower()) + pt = PersistentTemporaryFile('.'+_fmt.lower()) pt.write(data) pt.close() - of = PersistentTemporaryFile('.epub') + of = PersistentTemporaryFile('.'+fmt) of.close() setattr(options, 'output', of.name) options.verbose = 1 args = [pt.name, options] desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title)) - jobs.append(('comic2epub', args, desc, 'EPUB', row_id, [pt, of])) + jobs.append(('comic2'+fmt, args, desc, fmt.upper(), row_id, [pt, of])) if bad_rows: res = [] @@ -345,15 +352,14 @@ def set_conversion_defaults_lrf(comic, parent, db): else: LRFSingleDialog(parent, None, None).exec_() -def set_conversion_defaults_epub(comic, parent, db): +def _set_conversion_defaults(dialog, comic, parent, db): if comic: ComicConf.set_conversion_defaults(parent) else: - d = EPUBConvert(parent, db) + d = dialog(parent, db) d.setWindowTitle(_('Set conversion defaults')) d.exec_() - def _fetch_news(data, fmt): pt = PersistentTemporaryFile(suffix='_feeds2%s.%s'%(fmt.lower(), fmt.lower())) pt.close() @@ -385,22 +391,22 @@ def convert_single_ebook(*args): fmt = prefs['output_format'].lower() if fmt == 'lrf': return convert_single_lrf(*args) - elif fmt == 'epub': - return convert_single_epub(*args) + elif fmt in ('epub', 'mobi'): + return convert_single(fmt, *args) def convert_bulk_ebooks(*args): fmt = prefs['output_format'].lower() if fmt == 'lrf': return convert_bulk_lrf(*args) - elif fmt == 'epub': - return convert_bulk_epub(*args) + elif fmt in ('epub', 'mobi'): + return convert_bulk(fmt, *args) def set_conversion_defaults(comic, parent, db): fmt = prefs['output_format'].lower() if fmt == 'lrf': return set_conversion_defaults_lrf(comic, parent, db) - elif fmt == 'epub': - return set_conversion_defaults_epub(comic, parent, db) + elif fmt in ('epub', 'mobi'): + return _set_conversion_defaults(get_dialog(fmt), comic, parent, db) def fetch_news(data): fmt = prefs['output_format'].lower() diff --git a/src/calibre/library/__init__.py b/src/calibre/library/__init__.py index 77d02e1354..6cec55c471 100644 --- a/src/calibre/library/__init__.py +++ b/src/calibre/library/__init__.py @@ -29,5 +29,5 @@ def server_config(defaults=None): c.add_opt('develop', ['--develop'], default=False, help='Development mode. Server automatically restarts on file changes and serves code files (html, css, js) from the file system instead of calibre\'s resource system.') c.add_opt('max_cover', ['--max-cover'], default='600x800', - help=_('The maximum size for displayed covers')) + help=_('The maximum size for displayed covers. Default is %default.')) return c diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index da41dba57f..76f244a456 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -224,9 +224,17 @@ class ResultCache(SearchQueryParser): return False def refresh_ids(self, conn, ids): + ''' + Refresh the data in the cache for books identified by ids. + Returns a list of affected rows or None if the rows are filtered. + ''' for id in ids: self._data[id] = conn.get('SELECT * from meta WHERE id=?', (id,))[0] - return map(self.row, ids) + try: + return map(self.row, ids) + except ValueError: + pass + return None def books_added(self, ids, conn): if not ids: diff --git a/src/calibre/library/static/gui.js b/src/calibre/library/static/gui.js index 17f6f7f8fd..edefdbc5ef 100644 --- a/src/calibre/library/static/gui.js +++ b/src/calibre/library/static/gui.js @@ -40,7 +40,7 @@ function create_table_headers() { function format_url(format, id, title) { - return 'get/'+format.toLowerCase() + '/'+title + '_' + id+'.'+format.toLowerCase(); + return 'get/'+format.toLowerCase() + '/'+encodeURIComponent(title) + '_' + id+'.'+format.toLowerCase(); } function render_book(book) { diff --git a/src/calibre/linux.py b/src/calibre/linux.py index be5864033a..a05a7ea7a8 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -26,6 +26,7 @@ entry_points = { 'opf-meta = calibre.ebooks.metadata.opf2:main', 'odt-meta = calibre.ebooks.metadata.odt:main', 'epub-meta = calibre.ebooks.metadata.epub:main', + 'mobi-meta = calibre.ebooks.metadata.mobi:main', 'txt2lrf = calibre.ebooks.lrf.txt.convert_from:main', 'html2lrf = calibre.ebooks.lrf.html.convert_from:main', 'html2oeb = calibre.ebooks.html:main', @@ -40,6 +41,7 @@ entry_points = { 'calibre-server = calibre.library.server:main', 'feeds2lrf = calibre.ebooks.lrf.feeds.convert_from:main', 'feeds2epub = calibre.ebooks.epub.from_feeds:main', + 'feeds2mobi = calibre.ebooks.mobi.from_feeds:main', 'web2lrf = calibre.ebooks.lrf.web.convert_from:main', 'pdf2lrf = calibre.ebooks.lrf.pdf.convert_from:main', 'mobi2lrf = calibre.ebooks.lrf.mobi.convert_from:main', @@ -189,6 +191,7 @@ def setup_completion(fatal_errors): from calibre.ebooks.html import option_parser as html2oeb from calibre.ebooks.odt.to_oeb import option_parser as odt2oeb from calibre.ebooks.epub.from_feeds import option_parser as feeds2epub + from calibre.ebooks.mobi.from_feeds import option_parser as feeds2mobi from calibre.ebooks.epub.from_any import option_parser as any2epub from calibre.ebooks.lit.from_any import option_parser as any2lit from calibre.ebooks.epub.from_comic import option_parser as comic2epub @@ -219,7 +222,7 @@ def setup_completion(fatal_errors): f.write(opts_and_exts('any2epub', any2epub, any_formats)) f.write(opts_and_exts('any2lit', any2lit, any_formats)) f.write(opts_and_exts('any2mobi', any2mobi, any_formats)) - f.write(opts_and_exts('oeb2mobi', oeb2mobi, ['mobi', 'prc'])) + f.write(opts_and_exts('oeb2mobi', oeb2mobi, ['opf'])) f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf'])) f.write(opts_and_exts('lrf-meta', metaop, ['lrf'])) f.write(opts_and_exts('rtf-meta', metaop, ['rtf'])) @@ -239,7 +242,8 @@ def setup_completion(fatal_errors): f.write(opts_and_exts('comic2pdf', comic2epub, ['cbz', 'cbr'])) f.write(opts_and_words('feeds2disk', feeds2disk, feed_titles)) f.write(opts_and_words('feeds2lrf', feeds2lrf, feed_titles)) - f.write(opts_and_words('feeds2lrf', feeds2epub, feed_titles)) + f.write(opts_and_words('feeds2epub', feeds2epub, feed_titles)) + f.write(opts_and_words('feeds2mobi', feeds2mobi, feed_titles)) f.write(opts_and_exts('html2epub', html2epub, ['html', 'htm', 'xhtm', 'xhtml', 'opf'])) f.write(opts_and_exts('html2oeb', html2oeb, ['html', 'htm', 'xhtm', 'xhtml'])) f.write(opts_and_exts('odt2oeb', odt2oeb, ['odt'])) @@ -420,7 +424,7 @@ def install_man_pages(fatal_errors): if prog in ('prs500', 'pdf-meta', 'epub-meta', 'lit-meta', 'markdown-calibre', 'calibre-debug', 'fb2-meta', 'calibre-fontconfig', 'calibre-parallel', 'odt-meta', - 'rb-meta', 'imp-meta'): + 'rb-meta', 'imp-meta', 'mobi-meta'): continue help2man = ('help2man', prog, '--name', 'part of %s'%__appname__, diff --git a/src/calibre/parallel.py b/src/calibre/parallel.py index 6cbe1c96e4..fa9284ce46 100644 --- a/src/calibre/parallel.py +++ b/src/calibre/parallel.py @@ -67,7 +67,15 @@ PARALLEL_FUNCS = { 'comic2epub' : ('calibre.ebooks.epub.from_comic', 'convert', {}, 'notification'), - + + 'any2mobi' : + ('calibre.ebooks.mobi.from_any', 'any2mobi', {}, None), + + 'feeds2mobi' : + ('calibre.ebooks.mobi.from_feeds', 'main', {}, 'notification'), + + 'comic2mobi' : + ('calibre.ebooks.mobi.from_comic', 'convert', {}, 'notification'), } diff --git a/src/calibre/startup.py b/src/calibre/startup.py index eebec7d784..6cf155792e 100644 --- a/src/calibre/startup.py +++ b/src/calibre/startup.py @@ -13,6 +13,10 @@ from gettext import GNUTranslations import __builtin__ __builtin__.__dict__['_'] = lambda s: s +# For strings which belong in the translation tables, but which shouldn't be +# immediately translated to the environment language +__builtin__.__dict__['__'] = lambda s: s + from calibre.constants import iswindows, preferred_encoding, plugins from calibre.utils.config import prefs from calibre.translations.msgfmt import make diff --git a/src/calibre/translations/bg.po b/src/calibre/translations/bg.po index 45a3feb12b..fda52bd450 100644 --- a/src/calibre/translations/bg.po +++ b/src/calibre/translations/bg.po @@ -6,14 +6,14 @@ msgid "" msgstr "" "Project-Id-Version: calibre 0.4.51\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-01-10 01:19+0000\n" +"POT-Creation-Date: 2009-01-19 04:38+0000\n" "PO-Revision-Date: 2008-05-24 06:23+0000\n" "Last-Translator: Kovid Goyal \n" "Language-Team: bg\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2009-01-13 07:12+0000\n" +"X-Launchpad-Export-Date: 2009-01-20 17:04+0000\n" "X-Generator: Launchpad (build Unknown)\n" "Generated-By: pygettext.py 1.5\n" @@ -24,13 +24,13 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:44 #: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_any.py:44 #: /home/kovid/work/calibre/src/calibre/ebooks/html.py:492 -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:965 -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:978 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:977 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:990 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:77 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:79 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:81 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:86 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:300 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:295 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:61 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:95 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:97 @@ -42,6 +42,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:196 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:226 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:229 +#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:252 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:274 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:45 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/meta.py:47 @@ -51,31 +52,36 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf.py:449 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:819 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/pdf.py:12 +#: /home/kovid/work/calibre/src/calibre/ebooks/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:475 #: /home/kovid/work/calibre/src/calibre/ebooks/odt/to_oeb.py:46 +#: /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:157 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:159 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single.py:365 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:37 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:38 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:346 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:360 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:861 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:709 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:949 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:952 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:351 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:365 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:888 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:696 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:937 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:940 #: /home/kovid/work/calibre/src/calibre/gui2/tools.py:54 #: /home/kovid/work/calibre/src/calibre/gui2/tools.py:116 #: /home/kovid/work/calibre/src/calibre/library/cli.py:257 #: /home/kovid/work/calibre/src/calibre/library/database.py:920 -#: /home/kovid/work/calibre/src/calibre/library/database.py:1404 -#: /home/kovid/work/calibre/src/calibre/library/database.py:1435 -#: /home/kovid/work/calibre/src/calibre/library/database.py:1464 -#: /home/kovid/work/calibre/src/calibre/library/database.py:1592 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:461 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:473 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:808 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:841 +#: /home/kovid/work/calibre/src/calibre/library/database.py:1405 +#: /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:465 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:477 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:820 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:853 msgid "Unknown" msgstr "" @@ -196,29 +202,32 @@ msgstr "" msgid "Disable the named plugin" msgstr "" +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:38 +#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:413 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:55 +msgid "The reader has no storage card connected." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:57 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:74 +msgid "There is insufficient free space on the storage card" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/devices/cybookg3/driver.py:59 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:76 +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:158 #: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:196 #: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:224 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:182 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:223 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:250 +#: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:189 +#: /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 "" -#: /home/kovid/work/calibre/src/calibre/devices/prs505/driver.py:412 -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:51 -msgid "The reader has no storage card connected." -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:62 -msgid "There is insufficient free space on the storage card" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:64 -msgid "There is insufficient free space in main memory" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:92 msgid "Options to control the conversion to EPUB" msgstr "" @@ -398,7 +407,7 @@ msgid "" "Extract the contents of the produced EPUB file to the specified directory." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_any.py:156 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_any.py:157 msgid "" "%%prog [options] filename\n" "\n" @@ -406,11 +415,11 @@ msgid "" "formats are: %s\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:100 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:102 msgid "Could not find an ebook inside the archive" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:162 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:177 msgid "" "%prog [options] file.html|opf\n" "\n" @@ -421,13 +430,14 @@ msgid "" "the element of the OPF file. \n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:413 -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:739 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:428 +#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:744 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:571 msgid "Output written to " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:435 -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:1063 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/from_html.py:450 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:1075 msgid "You must specify an input HTML file" msgstr "" @@ -450,83 +460,83 @@ msgstr "" msgid "Written processed HTML to " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:848 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:860 msgid "Options to control the traversal of HTML" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:855 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:867 msgid "The output directory. Default is the current directory." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:857 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:869 msgid "Character encoding for HTML files. Default is to auto detect." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:859 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:871 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:861 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:873 msgid "Control the following of links in HTML files." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:863 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:875 msgid "" "Traverse links in HTML files breadth first. Normally, they are traversed " "depth first" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:865 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:877 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:867 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:879 msgid "Set metadata of the generated ebook" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:869 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:881 msgid "Set the title. Default is to autodetect." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:871 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:883 msgid "The author(s) of the ebook, as a comma separated list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:873 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:885 msgid "The subject(s) of this book, as a comma separated list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:875 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:887 msgid "Set the publisher of this book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:877 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:889 msgid "A summary of this book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:879 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:891 msgid "Load metadata from the specified OPF file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:881 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:893 msgid "Options useful for debugging" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:883 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:895 msgid "" "Be more verbose while processing. Can be specified multiple times to " "increase verbosity." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:885 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:897 msgid "Output HTML is \"pretty printed\" for easier parsing by humans" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:891 +#: /home/kovid/work/calibre/src/calibre/ebooks/html.py:903 msgid "" "%prog [options] file.html|opf\n" "\n" @@ -538,7 +548,7 @@ msgid "" "is used.\n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/from_any.py:44 +#: /home/kovid/work/calibre/src/calibre/ebooks/lit/from_any.py:47 msgid "Creating LIT file from EPUB..." msgstr "" @@ -547,7 +557,7 @@ msgid "%prog [options] LITFILE" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:852 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:475 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:507 msgid "Output directory. Defaults to current directory." msgstr "" @@ -556,20 +566,23 @@ msgid "Legibly format extracted markup. May modify meaningful whitespace." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:858 -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:724 +#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:717 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:530 msgid "Useful for debugging." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/lit/reader.py:869 -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:499 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:531 msgid "OEB ebook created in" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:718 +#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:711 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:524 msgid "%prog [options] OPFFILE" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:721 +#: /home/kovid/work/calibre/src/calibre/ebooks/lit/writer.py:714 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:527 msgid "Output file. Default is derived from input filename." msgstr "" @@ -866,120 +879,120 @@ msgstr "" msgid "No file to convert specified." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:229 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:224 msgid "Rendered %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:232 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:227 msgid "Failed %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:284 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:279 msgid "" "Failed to process comic: %s\n" "\n" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:291 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:286 msgid "" "Options to control the conversion of comics (CBR, CBZ) files into ebooks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:297 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:292 msgid "Title for generated ebook. Default is to use the filename." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:299 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:294 msgid "" "Set the author in the metadata of the generated ebook. Default is %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:302 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:297 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:22 msgid "" "Path to output file. By default a file is created in the current directory." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:304 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:299 msgid "Number of colors for grayscale image conversion. Default: %default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:306 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:301 msgid "" "Disable normalize (improve contrast) color range for pictures. Default: False" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:308 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:303 msgid "Maintain picture aspect ratio. Default is to fill the screen." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:310 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:305 msgid "Disable sharpening." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:312 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:307 msgid "Don't split landscape images into two portrait images" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:314 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:309 msgid "" "Keep aspect ratio and scale image using screen height as image width for " "viewing in landscape mode." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:316 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:311 msgid "" "Used for right-to-left publications like manga. Causes landscape pages to be " "split into portrait pages from right to left." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:318 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:313 msgid "" "Enable Despeckle. Reduces speckle noise. May greatly increase processing " "time." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:320 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:315 msgid "" "Don't sort the files found in the comic alphabetically by name. Instead use " "the order they were added to the comic." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:322 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:317 msgid "" "Choose a profile for the device you are generating this file for. The " "default is the SONY PRS-500 with a screen size of 584x754 pixels. This is " "suitable for any reader with the same screen size. Choices are %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:324 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:319 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/pdftrim.py:20 msgid "" "Be verbose, useful for debugging. Can be specified multiple times for " "greater verbosity." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:326 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:321 msgid "Don't show progress bar." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:329 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:324 msgid "Apply no processing to the image" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:334 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:329 msgid "" "%prog [options] comic.cb[z|r]\n" "\n" "Convert a comic in a CBZ or CBR file to an ebook. \n" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:394 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:389 msgid "Output written to" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:459 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:453 msgid "Rendering comic pages..." msgstr "" @@ -1020,95 +1033,95 @@ msgstr "" msgid "Fetching of recipe failed: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:313 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:317 msgid "\tBook Designer file detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:315 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:319 msgid "\tParsing HTML..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:338 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:342 msgid "\tBaen file detected. Re-parsing..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:354 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:358 msgid "Written preprocessed HTML to " msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:372 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:376 msgid "Processing %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:386 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:390 msgid "\tConverting to BBeB..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:532 -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:545 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:536 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:549 msgid "Could not parse file: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:537 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:541 msgid "%s is an empty file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:557 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:561 msgid "Failed to parse link %s %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:601 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:605 msgid "Cannot add link %s to TOC" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:953 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:957 msgid "Unable to process image %s. Error: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:991 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:995 msgid "Unable to process interlaced PNG %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1006 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1010 msgid "" "Could not process image: %s\n" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1759 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1763 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:1761 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1765 msgid "" "Bad table:\n" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1783 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1787 msgid "Table has cell that is too large" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1813 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1817 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:1856 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1860 msgid "Could not read cover image: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1859 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1863 msgid "Cannot read from: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1984 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1988 msgid "Failed to process opf file" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1990 +#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1994 msgid "" "Usage: %prog [options] mybook.html\n" "\n" @@ -1324,16 +1337,16 @@ msgstr "" #: /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:343 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:931 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:348 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:958 msgid "Title" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:274 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/fetch_metadata.py:37 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:104 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:348 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:932 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:353 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:959 msgid "Author(s)" msgstr "" @@ -1352,26 +1365,26 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub_ui.py:394 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/lrf_single_ui.py:535 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_single_ui.py:338 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:304 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:309 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:58 msgid "Comments" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:280 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:109 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:293 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:871 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:935 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:298 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:898 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:962 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/tags.py:42 +#: /home/kovid/work/calibre/src/calibre/gui2/tags.py:50 msgid "Tags" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:281 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:110 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:309 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:314 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:59 -#: /home/kovid/work/calibre/src/calibre/gui2/tags.py:42 +#: /home/kovid/work/calibre/src/calibre/gui2/tags.py:50 msgid "Series" msgstr "" @@ -1486,14 +1499,62 @@ msgstr "" msgid "Usage: rb-meta file.rb" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:473 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/from_any.py:52 +msgid "Creating Mobipocket file from EPUB..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:505 msgid "%prog [options] myebook.mobi" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:497 +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/reader.py:529 msgid "Raw MOBI HTML saved in" msgstr "" +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:501 +msgid "Mobipocket" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:502 +msgid "Mobipocket-specific options." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:505 +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:509 +msgid "Modify images to meet Palm device size limitations." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:511 +msgid "" +"Device renderer profiles. Affects conversion of default font sizes and " +"rasterization resolution. Valid profiles are: %s." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:511 +msgid "Profiles" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:516 +msgid "Source renderer profile. Default is 'Browser'." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:519 +msgid "Destination renderer profile. Default is 'CybookG3'." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:543 +msgid "Unknown source profile %r" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/ebooks/mobi/writer.py:547 +msgid "Unknown destination profile %r" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/ebooks/odt/to_oeb.py:57 msgid "The output directory. Defaults to the current directory." msgstr "" @@ -1635,27 +1696,27 @@ msgstr "" msgid "Send metadata to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:144 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:146 msgid "Upload %d books to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:159 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:161 msgid "Delete books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:174 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:176 msgid "Download books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:184 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:186 msgid "View book on device" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:84 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:85 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:86 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:300 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:866 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:305 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:893 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:56 msgid "Path" msgstr "" @@ -1664,14 +1725,15 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:88 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:89 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:92 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:299 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:304 #: /home/kovid/work/calibre/src/calibre/gui2/status.py:57 -#: /home/kovid/work/calibre/src/calibre/gui2/tags.py:42 +#: /home/kovid/work/calibre/src/calibre/gui2/tags.py:50 msgid "Formats" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info_ui.py:61 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:48 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/warning_ui.py:52 msgid "Dialog" msgstr "" @@ -1681,6 +1743,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_format_ui.py:41 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/confirm_delete_ui.py:49 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/password_ui.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress_ui.py:50 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/warning_ui.py:53 msgid "TextLabel" msgstr "" @@ -1834,7 +1898,7 @@ msgid "Access log:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/config.py:344 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:389 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:381 msgid "Failed to start content server" msgstr "" @@ -2254,7 +2318,7 @@ msgid " is not a valid picture" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/epub.py:227 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1039 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1048 msgid "Cannot convert" msgstr "" @@ -2583,7 +2647,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:33 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/job_view_ui.py:32 msgid "Details of job" msgstr "" @@ -2987,53 +3051,57 @@ msgstr "" msgid "Password needed" msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress.py:42 +msgid "Aborting..." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:39 msgid "You" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:176 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:218 msgid "Search" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:235 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:241 msgid "%d recipes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:264 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:270 msgid "Must set account information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:264 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:270 msgid "This recipe requires a username and password" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:277 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:283 msgid "Created by: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:297 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:303 msgid "Last downloaded: %s days ago" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:299 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:305 msgid "Last downloaded: never" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:326 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:332 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler_ui.py:136 msgid "Schedule news download" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:329 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:335 msgid "Add a custom news source" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:336 -#: /home/kovid/work/calibre/src/calibre/gui2/tags.py:42 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:752 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:756 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1063 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:342 +#: /home/kovid/work/calibre/src/calibre/gui2/tags.py:50 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:764 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:768 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1075 msgid "News" msgstr "" @@ -3516,12 +3584,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:933 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:960 msgid "Size (MB)" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/library.py:106 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:934 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:961 msgid "Date" msgstr "" @@ -3529,35 +3597,35 @@ msgstr "" msgid "Rating" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:292 -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:298 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:297 #: /home/kovid/work/calibre/src/calibre/gui2/library.py:303 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:308 msgid "None" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:309 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:314 msgid "Book %s of %s." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:701 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:728 msgid "Not allowed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:702 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:729 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:865 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:892 msgid "Format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:870 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:897 msgid "Timestamp" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library.py:968 +#: /home/kovid/work/calibre/src/calibre/gui2/library.py:995 msgid "Search (For Advanced Search click the button to the left)" msgstr "" @@ -3633,156 +3701,152 @@ msgstr "" msgid "Configure" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:89 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:90 msgid "Error communicating with device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:101 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:102 msgid "&Restore" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:102 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:103 msgid "&Donate" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:103 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:104 msgid "&Quit" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:105 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:106 msgid "&Restart" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:149 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:150 msgid "" "

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

" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:168 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:170 -msgid "Send to main memory" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/main.py:169 #: /home/kovid/work/calibre/src/calibre/gui2/main.py:171 -msgid "Send to storage card" +msgid "Send to main memory" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/main.py:170 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:172 +msgid "Send to storage card" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/main.py:171 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:172 msgid "and delete from library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:173 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:174 msgid "Send to storage card by default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:186 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:187 msgid "Edit metadata individually" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:188 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:189 msgid "Edit metadata in bulk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:191 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:192 msgid "Add books from a single directory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:193 msgid "" "Add books from directories, including sub-directories (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:193 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:194 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:208 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:209 #: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:342 msgid "Save to disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:209 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:210 msgid "Save to disk in a single directory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:210 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1243 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:211 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1253 msgid "Save only %s format to disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:213 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:214 #: /home/kovid/work/calibre/src/calibre/gui2/main_ui.py:348 msgid "View" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:214 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:215 msgid "View specific format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:231 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:232 msgid "Convert individually" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:232 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:233 msgid "Bulk convert" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:234 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:235 msgid "Set defaults for conversion" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:235 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:236 msgid "Set defaults for conversion of comics" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:256 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:257 msgid "Similar books..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:302 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:303 msgid "Bad database location" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:304 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1398 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:305 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:1386 msgid "Choose a location for your ebook library." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:315 -msgid "Migrating database" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:428 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:420 msgid "Browse by covers" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:517 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:509 msgid "Device: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:518 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:510 msgid " detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:540 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:532 msgid "Connected " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:551 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:543 msgid "Device database corrupted" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:552 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:544 msgid "" "\n" "

The database of books on the reader is corrupted. Try the " @@ -3798,320 +3862,308 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:601 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:689 -msgid "Stop" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:604 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:593 msgid "Adding books recursively..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:608 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:598 msgid "Added " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:608 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:598 msgid "Searching..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:620 -#: /home/kovid/work/calibre/src/calibre/gui2/main.py:726 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:608 +#: /home/kovid/work/calibre/src/calibre/gui2/main.py:712 msgid "" "

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