Merge upstream changes

This commit is contained in:
Marshall T. Vandegrift 2009-01-26 08:25:06 -05:00
commit fc85bb5a20
101 changed files with 19902 additions and 15486 deletions

View File

@ -2,8 +2,9 @@
<?eclipse-pydev version="1.0"?> <?eclipse-pydev version="1.0"?>
<pydev_project> <pydev_project>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.5</pydev_property> <pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</pydev_property>
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH"> <pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/calibre/src</path> <path>/calibre/src</path>
</pydev_pathproperty> </pydev_pathproperty>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
</pydev_project> </pydev_project>

View File

@ -8,22 +8,25 @@ __docformat__ = 'restructuredtext en'
import sys, time, subprocess, os, re import sys, time, subprocess, os, re
from calibre import __appname__, __version__ from calibre import __appname__, __version__
INSTALLJAMMER = '/usr/local/installjammer/installjammer'
sv = re.sub(r'[a-z]\d+', '', __version__) sv = re.sub(r'[a-z]\d+', '', __version__)
cmdline = [ cmdline = [
'/usr/local/installjammer/installjammer', INSTALLJAMMER,
'--build-dir', '/tmp/calibre-installjammer', '--build-dir', '/tmp/calibre-installjammer',
'-DAppName', __appname__, '-DAppName', __appname__,
'-DShortAppName', __appname__, '-DShortAppName', __appname__,
'-DApplicationURL', 'http://%s.kovidgoyal.net'%__appname__, '-DApplicationURL', 'http://%s.kovidgoyal.net'%__appname__,
'-DCopyright', time.strftime('%Y Kovid Goyal'), '-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__, '-DPackageSummary', '%s: E-book library management'%__appname__,
'-DVersion', __version__, '-DVersion', __version__,
'-DInstallVersion', sv + '.0', '-DInstallVersion', sv + '.0',
'-DLicense', open(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'LICENSE')).read().replace('\n', '\r\n'), '-DLicense', open(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'LICENSE')).read().replace('\n', '\r\n'),
'--output-dir', os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'dist'), '--output-dir', os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'dist'),
'--platform', 'Windows', '--platform', 'Windows',
'--verbose'
] ]
def run_install_jammer(installer_name='<%AppName%>-<%Version%><%Ext%>', build_for_release=True): def run_install_jammer(installer_name='<%AppName%>-<%Version%><%Ext%>', build_for_release=True):
@ -41,7 +44,7 @@ def run_install_jammer(installer_name='<%AppName%>-<%Version%><%Ext%>', build_fo
subprocess.check_call(cmdline) subprocess.check_call(cmdline)
def main(args=sys.argv): def main(args=sys.argv):
run_install_jammer(build_for_release=False) run_install_jammer(build_for_release=True)
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -208,11 +208,352 @@ test
} }
FileGroup ::BEF8D398-58BA-1F66-39D6-D4A63D5BEEF9 -setup Install -active Yes -platforms {AIX-ppc FreeBSD-4-x86 FreeBSD-x86 HPUX-hppa Linux-x86 Solaris-sparc Windows TarArchive ZipArchive} -name {Program Files} -parent FileGroups FileGroup ::BEF8D398-58BA-1F66-39D6-D4A63D5BEEF9 -setup Install -active Yes -platforms {AIX-ppc FreeBSD-4-x86 FreeBSD-x86 HPUX-hppa Linux-x86 Solaris-sparc Windows TarArchive ZipArchive} -name {Program Files} -parent FileGroups
File ::8E5D85A4-7608-47A1-CF7C-309060D5FF40 -filemethod {Always overwrite files} -type dir -directory <%InstallDir%> -name /home/kovid/work/calibre/build/py2exe -parent BEF8D398-58BA-1F66-39D6-D4A63D5BEEF9 File ::8E5D85A4-7608-47A1-CF7C-309060D5FF40 -filemethod {Always overwrite files} -type dir -directory <%InstallDir%> -name /home/kovid/work/calibre/build/py2exe -parent BEF8D398-58BA-1F66-39D6-D4A63D5BEEF9
File ::FC870EE7-667B-481F-113B-B4504DFCCFA5 -type dir -name bin -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::377C588B-B324-CA09-ED49-4DB5F82A15ED -type dir -name etc -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::B95D03D4-EA59-F00E-59E1-BA05758879DA -type dir -name imageformats -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::5EF561A9-E70B-8F01-A852-C36D28D1FA14 -type dir -name codecs -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::48EA1D8C-F4C8-3D34-229D-B501057802F3 -type dir -name driver -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::0AC00D67-8452-CABB-6843-FE6A464E9AE9 -type dir -name plugins -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::01034EB7-C79C-42B9-6FF0-E06C72EF2623 -type dir -name iconengines -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::7FC7557D-E1AF-082B-7286-24939CD5EE76 -name IM_MOD_RL_sfw_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::EF383C5E-5939-F73E-5F9C-26009B469ADA -name feeds2mobi.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::71A18B69-E38B-AB9F-3B81-BECFD4454D38 -name PyQt4.QtNetwork.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::E1BF1A4F-2942-73DB-1770-1ECB02CA9799 -name comic2lrf.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::EEC00227-DED5-B9BF-A36D-24239AEDE932 -name rtf-meta.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::F6C99732-1835-1C2A-02F9-F2BB631BEC81 -name dde.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::3334C5D8-8513-326D-A96C-C86D1C325EBB -name pdf2lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::20079501-EBD5-FEFD-8E49-2CF6A63E83E0 -name IM_MOD_RL_rle_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::D98A0557-D988-24A1-9F32-67F3E9A5A123 -name opf-meta.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::470243F6-C8E4-DB45-678F-6A314747097E -name sqlite3.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::4AA221F6-EBE3-997E-AECF-DD8E03265385 -name html2epub.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::0213D597-D9C7-F155-EE44-1BD9E19520E3 -name IM_MOD_RL_tiff_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::F782E3D0-C2CA-481C-70C4-2257FEF52EAF -name CORE_RL_libxml_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::414CB1DC-A13E-CE54-60A9-157381D4DDC4 -name oeb2lit.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::9306B7C1-11DE-7B54-1AFB-67AFF82B8013 -name _sqlite3.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::4D6B4FC9-F900-E0AD-5258-8918CBFEB9AD -name lrs2lrf.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::05329209-3CCE-90C0-FFEA-BECEBA6A6AB4 -name lit2lrf.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A2AF7001-960E-F5EA-AA09-025E35FD09BC -name IM_MOD_RL_sun_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::C472ABCF-2160-C218-41CA-0DDE625AAEC6 -name IM_MOD_RL_msl_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::DC7D8C3A-81C2-7FC5-4AC0-8CDC4069FC97 -name IM_MOD_RL_jp2_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::555AF3FA-9A08-CCFF-4028-3F8891A6D212 -name CORE_RL_ttf_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::92F06FE6-C2C5-FF23-0A9B-884AB21D8221 -name PIL._imagingft.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::DA2ABF54-3BC2-87E4-BA86-24C8B2915AC0 -name txt2lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::C3B86C9F-1A59-9573-DCA2-A2747E7C3B4F -name odt2oeb.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::ED8542BE-DDE4-72B2-B05A-A09D00818994 -name web2disk.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A2154B84-F146-FD50-1DA9-1DB374A5439E -name IM_MOD_RL_exr_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A442B5C3-724F-6542-816D-30C3D3EDD1E3 -name CORE_RL_Magick++_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A18318B3-88EF-11EB-4E14-B02CBD4F0E5E -name calibre-parallel.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::E26C9A86-F12B-7F1E-F2E8-8A42C0436321 -name IM_MOD_RL_dib_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2E94EBE2-F588-C484-009C-48C0DA7D1932 -name IM_MOD_RL_preview_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::9D75046B-57DC-872C-39F1-4C6E44337227 -name html2lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::7294280B-8324-B1A0-A054-92F330874F48 -name numpy.fft.fftpack_lite.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::5322B2EC-82CF-9446-964E-38CB7C688C1B -name oeb2mobi.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::74DC3F8D-5C56-8FB8-F59A-0C4C5ECB70A0 -name CORE_RL_magick_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::1638722F-A861-F762-1514-5649B2AD08FF -name IM_MOD_RL_svg_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::D345F98D-EDB4-53D0-95A6-82968E47E080 -name IM_MOD_RL_mpeg_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::91CA5392-5F4E-12F7-C670-939B1500616B -name ebook-viewer.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::4706580D-D101-13EA-DED0-2593D7FBADE8 -name LICENSE -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::63F996B0-07D9-EA5B-2176-F02D9851EAE4 -name feeds2disk.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::89CAC9B2-DA65-DBF2-6295-757553882213 -name numpy.core.umath.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2F90282D-B59F-B6BA-090B-45858AF7F3B2 -name IM_MOD_RL_clipboard_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::B512D139-B295-D7C3-F0B4-43775849CF58 -name numpy.core._sort.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A999B024-1024-E709-07C1-22BA67A850BB -name feeds2mobi.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A2C063AC-2F12-9260-501A-0E8BD0B8A932 -name calibre.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::439B6D05-7DE6-061D-4BCC-3F04F4FA2FA2 -name IM_MOD_RL_png_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::BA464D11-BBCE-DEDA-C354-0C7BE60FAA05 -name IM_MOD_RL_braille_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::8F9FF823-AF6D-A288-8AE6-7D74F55DCE29 -name CORE_RL_bzlib_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::ABAEA510-B003-29CB-6ADE-F6E1826DD501 -name web2lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::57A3F5D5-BFC8-CB38-5A57-548EE0DB033B -name QtNetwork4.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::4DB7E8DE-905A-822A-AF14-17BD5ACEF915 -name IM_MOD_RL_wmf_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::559057ED-0FEA-62A1-68C2-023116B5636C -name win32ui.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::83F3AC3F-A485-B791-D4F0-6E67135FF19D -name IM_MOD_RL_txt_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::FABA7768-743B-08F7-B871-ED5E9082DF38 -name win32gui.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A8D10C63-CDFB-0809-CA84-4CB7A0A451FC -name fb2-meta.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::C49805D2-C0B8-01C4-DF6F-674D9C0BFD15 -name IM_MOD_RL_viff_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::1B9F2F00-20A5-B207-5A80-8F75470286AD -name txt2lrf.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::826F1915-9F97-59DD-6637-3EEC0744A79C -name IM_MOD_RL_ps2_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::519A6618-8A1F-93A5-93B4-6EEF5A4A3DE9 -name comic2pdf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::B0CEAA35-52BF-0DE0-BAC7-7B23157E29BD -name isbndb.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A5F23791-BCDC-A997-4941-5D1F2F227E6D -name type.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::0A1C107A-C0AA-3ED6-4F37-A6894386DCBE -name IM_MOD_RL_ps3_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::EEBA64E7-6509-EBAF-3E23-1A203216F39A -name epub2lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2655F4FC-F682-46D8-B75C-6AF322323EF5 -name IM_MOD_RL_dot_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::176456BB-237C-9EBE-60E1-D8F78AAFFEC8 -name IM_MOD_RL_xwd_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::9AE8498B-C89C-8B12-B8A1-35E1B6650469 -name lxml.objectify.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::EA37C1C2-57BB-4E7A-C004-0010D79142C2 -name IM_MOD_RL_fits_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::05F5C10D-6988-F1F4-A486-86C96DB20302 -name pywintypes26.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::0137A2B1-EB94-EB26-7295-0C7CD941A1DF -name IM_MOD_RL_histogram_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::7F199A1F-4FA4-2ABA-DED3-36ECF3C089CA -name epub2lrf.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::F9F112C9-B61B-E041-1A9D-47641B047135 -name isbndb.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::CF6398D8-2140-53CF-1DA6-421A82E92621 -name any2epub.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::8DFA6C69-360D-FA63-7FF9-860E3DB00B19 -name any2lrf.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::5BB7579D-9183-412C-81F8-B411B07C57B3 -name IM_MOD_RL_pnm_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::97B8BB83-7772-D87B-C8D1-5215E324AF2C -name library.ico -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::DF1361A2-2580-EFBF-65D2-156AD9919DE1 -name library.zip -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A6419A84-6C22-784E-6D84-D09972770770 -name unicodedata.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::E658FBE0-5860-D041-12D3-76ADD18F804B -name servicemanager.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::C98A6FC4-E341-7FD4-005C-DA2B384E11D8 -name win32api.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::ADA36EEA-7DE1-447C-B1AB-A4908E65E2CD -name IM_MOD_RL_ipl_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::53C2EC15-850F-8F49-6425-C228FB6E6D0E -name libfontconfig-1.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2BCD9281-2CBC-CF0D-0E12-2CE11F6ED758 -name comic2epub.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::EDE6F457-C83F-C5FA-9AF4-38FDFF17D929 -name PIL._imagingtk.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::09D0906E-3611-3DB7-32CF-A140585694A7 -name win32pdh.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::4C84F0DC-7157-0C90-2062-180139B03E25 -name IM_MOD_RL_rgb_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::F402F507-87C5-BDB1-80AE-AD3FF4A4BCE7 -name bzrlib._patiencediff_c.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A732EDE7-4796-241F-BECA-68E59F88F8AF -name lrs2lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::69072379-7D16-B9F7-9F39-3E6403C48267 -name IM_MOD_RL_xbm_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::FBD11D98-D1E7-5DD9-BF02-01CE92518859 -name IM_MOD_RL_otb_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::8D0CFD32-2B7F-2BB3-8FA0-760A8DB24B52 -name win32service.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::C7F9AB12-AAF7-2954-3DB6-F2C84F41655B -name win32clipboard.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::76676624-68A9-D9D9-6EC7-40CF201520A4 -name pdfreflow.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::62CD997A-A5C7-D71B-A8F7-54567B36A071 -name msvcp90.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::7D76BBE6-FD20-1290-4DBA-93D14FC45B81 -name sip.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::17D27485-128E-E247-14CE-9C3B6988E182 -name pdfreflow.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::BF2007D6-5AFE-6D04-4DB0-36A3644D988D -name pdf-meta.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::D4354621-69ED-DFCB-068A-0812DB1C09C3 -name IM_MOD_RL_avi_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::109FBE61-82CD-D5AB-AA9D-F9D52947DD22 -name rtf-meta.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::9BA20E46-014F-DA6A-DEF3-D78E2AEFDD47 -name odt-meta.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::EAFAC6D5-A7E8-1843-6D98-6663D899BB11 -name PyQt4.QtCore.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::155505A4-F0D8-05B2-10AD-149E178976A4 -name calibredb.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::39D5114E-1E70-5402-7E19-D86490678506 -name CORE_RL_lcms_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::75C3F298-B07C-DA98-38B2-40FFD39C32ED -name oeb2lit.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::388E0308-35A8-69B9-6837-383FD72E99E9 -name IM_MOD_RL_xpm_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::3DB88954-1B0D-FDBC-CE29-EFCC01D4121E -name epub-meta.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::43C0DE14-A935-2139-6690-256C49C461C4 -name IM_MOD_RL_xcf_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::E185A35A-93DB-61BB-E7EE-2C2222FD4939 -name win32security.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::498CCC26-383A-87CA-30C0-626D52555B37 -name librarything.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::404A98F1-84FD-B6D0-B130-354EECD9253C -name IM_MOD_RL_emf_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::17034C34-403E-B405-99C1-F80B7F00E27C -name log.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::34E63A2C-65C5-0A84-ACF1-BD6A844D4579 -name pythoncom26.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::CE737360-1B73-DEC3-E511-3FAEC61F5292 -name epub-meta.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2F20484B-53B8-B08E-B691-C5B2D49A9CB4 -name QtWebKit4.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::8AF134C8-9189-3F9A-A081-9143FFD44C45 -name freetype6.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::E8A4442D-D0D3-31CD-997A-3CEB641CF5B7 -name IM_MOD_RL_mtv_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::56B7883F-B4FE-BE25-BCBA-4AF17CC84C93 -name fb22lrf.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::0CA87D0B-5A04-1439-AEE8-C97072D47BA7 -name CORE_RL_tiff_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::AC24F520-88D4-D1CF-5797-27C715CE8ACA -name pyexpat.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::16848F38-71CD-55B8-4D96-1537F6773744 -name IM_MOD_RL_dps_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::33A46CC5-BAC4-5863-C83D-303DCCA0CAA1 -name tk85.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::39E11D64-CC0C-E565-B3CC-882A5AA9F4AF -name html2oeb.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::81116DD3-1715-AA87-472F-544FC616EDAF -name IM_MOD_RL_dcm_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A6EF7DB0-FC94-8794-1F15-394432CD283D -name imp-meta.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::335A4CFB-5C2D-44E4-C438-7018E8244C3D -name ebook-viewer.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::62A16C3B-ED9C-5187-2807-58857DF3A990 -name calibre-debug.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2B8B1DCD-AA68-7612-80A5-C20CAAF06019 -name any2lit.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::55ECA7B7-279A-F51D-81C2-C8DC44CF0E22 -name select.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A6AF5ECC-A981-4CBD-DBEE-303A9340C603 -name IM_MOD_RL_xps_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::5BCBF71F-18E7-5C52-E3F5-7D7F3028AD46 -name locale.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2EDE0641-B5DF-220F-9FF3-486E5A081EFC -name rb-meta.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::5C9FA94C-B8B0-A94B-548D-1D24FDEA5770 -name CORE_RL_wand_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::E39E60BE-DE77-AB8C-42C6-5A7D7DC073E3 -name IM_MOD_RL_ttf_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::8F1AF028-E819-4ED6-8B69-704183C3BD1D -name pdf-meta.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::D8F566FB-93DA-3128-5DBD-DF1068B3E3ED -name IM_MOD_RL_dpx_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::FFE5F178-A1A4-3691-9C88-E5109D144437 -name IM_MOD_RL_jpeg_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::0C9C97F6-5622-D4B5-E7CD-B4E5E9A8634C -name odt-meta.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::C05AD359-A7A5-9760-A4C8-310074353C89 -name feeds2lrf.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::03665A74-B895-EB3D-EBA0-2D2B6D26DDDA -name PIL._imagingmath.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::81AED538-B97F-6272-5C6E-9B27D7285B35 -name IM_MOD_RL_fax_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::BDCCAEC0-4847-B8D8-50B1-1B434B73A01F -name IM_MOD_RL_avs_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::727B9BD6-B55E-386E-D3B9-D99D0D9ADBB1 -name QtGui4.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::1C7421A2-1BA7-7807-EB41-67578E0302E8 -name IM_MOD_RL_mvg_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::C3466B70-23C3-31C9-3B4F-1B3B56E4D013 -name markdown-calibre.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A190F216-F7F7-A425-5F9D-F6B5C35D3A8F -name lrf-meta.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::6ED1C675-C4D5-6BFF-7C8A-9AB4BF39D00C -name _hashlib.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::21F7F333-3063-71E3-85F5-5C88584B15CC -name IM_MOD_RL_tga_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::738CADE4-6C0A-5155-A1BE-8F789C1D92E7 -name feeds2lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::26741B21-C241-E100-8BB1-8B679BC3E662 -name configure.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::7D491E89-C6D3-1E6E-F4BD-8E55260FE33E -name libexpat.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A4910EB3-0F1C-F6F0-CD2D-16A64BBAA92B -name calibre-fontconfig.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::8711327A-716D-B162-6AC6-2FB4AD071266 -name fb22lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::0FDD3A7A-31F3-8089-CE32-D80EAA6F62B2 -name bzrlib._btree_serializer_c.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::476CB977-5155-D56F-26CA-EB243AEBBA99 -name unrar.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2DA1CC8D-AF5C-3B03-2060-301DFE0356CC -name mobi2oeb.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2E2A9EDA-5386-444E-8479-557386794552 -name IM_MOD_RL_uil_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::4530C4C8-BCFA-E461-5F72-0EF5B553C7F5 -name pdf2lrf.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::29582E09-00E0-B2F9-475C-8C6D2E4BF7E8 -name any2lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::D3301E15-1B1A-E2AB-1B04-30A601B3FB44 -name IM_MOD_RL_cut_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::181B28B9-CF13-0C28-8380-B39DF6E7397D -name markdown-calibre.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::BC1D2D23-48ED-ECFF-2180-37C83554FDED -name Xext.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::15289622-D0DC-4F61-4990-AF6CA73C3F8C -name IM_MOD_RL_scr_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::76594353-4D68-08A0-B2AD-B3BA58FABB75 -name IM_MOD_RL_map_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::E0F9749A-2B00-4A6C-ABB0-9AD1910A81A6 -name lxml.etree.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::5BFB62EB-32B6-EA16-752E-2F29CC9B6202 -name numpy.random.mtrand.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::452196C8-28B2-4BA4-1F00-27C3C0BDDC04 -name thresholds.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::DE476170-B65E-B429-A23C-F822E4190FFC -name IM_MOD_RL_thumbnail_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::31E46936-560D-1E88-8FC1-F8E590D2FD02 -name IM_MOD_RL_mpc_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::0C04692E-5DCF-864F-6640-2FF5BF12BA91 -name IM_MOD_RL_cmyk_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A620FEB9-39FE-B102-6963-FEDA25EBF2F1 -name IM_MOD_RL_pcd_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::0A4D27B3-157D-357A-6A86-32D8EB5B30D7 -name lit2lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::80D1F9CA-D7DA-578E-CE25-4E0EE988D280 -name IM_MOD_RL_sct_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::C85307C1-5F11-01EF-2193-95F37CB49822 -name IM_MOD_RL_pict_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::3ECF2FC0-91AA-9573-42B3-4BD784FA5BC9 -name IM_MOD_RL_gradient_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::544BC0E6-66E4-EB06-CCD5-A172F230C83E -name IM_MOD_RL_icon_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::305631EA-9397-0384-259C-643046B72E44 -name X11.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::CBF83E89-39E0-D7C3-7902-A4A0ADCDEF48 -name IM_MOD_RL_jbig_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::FFF32C7F-BB57-4BB3-A52D-4E4F3496B967 -name calibre-debug.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::80A3C65A-553D-EA27-FAED-4F831578F4F8 -name magic.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::E2DF0DC3-3372-3CDF-5177-5B3F3BF84E66 -name IM_MOD_RL_mat_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::955B176E-5F6A-FFFA-9387-893E38C23E33 -name prs500.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::4D938678-59AF-F5F6-697B-E3A5BE76B43B -name QtXml4.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::FCAEFF6A-1E96-FA23-C8AC-0F5F3297B14A -name rtf2lrf.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::0A5A70A8-39EC-B733-E807-9C358E5EA7A3 -name IM_MOD_RL_meta_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::E6419523-253A-3052-B9B4-0EA792EF4A64 -name pdftohtml.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::EF062C24-09D8-1DC8-891A-F9563BBA57C2 -name IM_MOD_RL_gray_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::E1D4796B-733B-F45A-2ECC-D498A1AC4EAD -name opf-meta.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::0E21E0B1-C2D4-3F6D-E788-9EEB821128C3 -name web2disk.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::12DE2886-5926-F8AD-77C4-EAAC5DD9413C -name rtf2lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::E96D95C6-2D53-A67B-C704-06BD213BBC86 -name mobi-meta.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A4D76085-EFC7-A237-7BB7-AA8A33BFB849 -name html2epub.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::27127583-9DC6-4397-3E86-052A515ED051 -name lrfviewer.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::7229472A-2A5A-2685-0223-2DF2CED0C9D4 -name comic2epub.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::396B4F78-FB45-C0B2-ACB3-97769CF5CD5D -name msvcr90.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::1DE767EE-4891-4E54-422D-67A4DFF8C3B5 -name lrfviewer.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::AFFEC28A-615C-E3E6-0026-CCE2594A6D25 -name calibre-server.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2C64F86B-9366-B52D-F7B2-5BBD51F6982A -name IM_MOD_RL_pwp_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::187D965E-40CE-18AB-5684-18C31A7FD8D4 -name any2epub.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::F4B2EF9C-EB18-B865-6E99-75CFB9B60D87 -name IM_MOD_RL_dds_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::9D0CC46C-E254-7F11-4B64-A6E8E16CF6FB -name mobi2lrf.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::72769321-EF4D-C796-5E76-3D5807772233 -name any2mobi.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::38770D87-6CA9-9E3E-FBA1-A8CCFCD88FB5 -name IM_MOD_RL_fpx_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A31CA43B-4456-BB26-A8EE-73C4C4B1F6FC -name pdftrim.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::BE2D7BC3-D294-AF3F-65E7-3B372DEFDE36 -name PIL._imaging.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::200B9AEC-809F-75B7-DC12-A51BFC2A6F93 -name PyQt4.QtSvg.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::3A424677-A66A-CAA0-4FD2-4FE8086DBBA6 -name comic2mobi.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::52132A31-D3AE-C617-7568-BF2AF46B5D74 -name IM_MOD_RL_pcl_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::F94472C3-C8D0-950F-5ED9-1611D1CE30E5 -name IM_MOD_RL_inline_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::794E99A2-79AB-BABB-97A1-9E3482002EA8 -name imp-meta.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::055ADB4B-20C5-E071-442F-4DA0A8D6F3C5 -name english.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::DE4F9AD0-3D79-865A-2DD9-4A4CB6886AFC -name lit-meta.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::B10B6B91-0C03-642D-90D8-37B607B164AD -name IM_MOD_RL_wpg_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::BFBB144B-1794-8304-9772-F103A42F2CA4 -name IM_MOD_RL_pdb_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::EAED9280-84D7-9768-9F89-CEC61CE549DF -name mobi-meta.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2FE2FF56-5CF5-15B7-2BD8-32100DF421DC -name _ssl.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::AE47BDA8-A1E5-2E84-28D7-DF8A84B18202 -name Microsoft.VC90.CRT.manifest -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A6B1C213-DC68-B7C3-8EDE-806286829899 -name w9xpopen.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::D3E03CFA-4AF0-93FB-E04B-B282926099E0 -name IM_MOD_RL_label_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::6F40E97B-430C-08FE-30CA-9E6622EE29EB -name feeds2epub.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::345C74B8-B45D-A370-27F3-78CEA9EAB1E1 -name prs500.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::662783C0-3826-9869-8EB8-326B363D3686 -name IM_MOD_RL_clip_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::442D92A7-97B1-74BC-F150-A9992A925356 -name numpy.core.multiarray.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::54991918-D11F-EEAD-F7E3-0EE28F22249E -name IM_MOD_RL_pix_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::069E7552-C005-26F6-2837-CDDDC0159183 -name win32wnet.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::071C5A61-CAC7-1136-DCD5-B2C0847A004A -name colors.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::F209A2F0-ED6A-E241-E351-4CA8A539F2BA -name perfmon.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::74DDFF28-013C-4C41-CCE7-0CAD45F7D6B4 -name IM_MOD_RL_cin_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::5CB76241-5682-AE44-82D3-A6C8C66B88D6 -name IM_MOD_RL_bmp_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A843E4DA-8671-C4F7-BA61-998AA1D278C8 -name numpy.linalg.lapack_lite.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::908EA762-5568-17BE-D77D-5DE5551895D0 -name IM_MOD_RL_raw_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::CC186E15-8751-A4AA-B2A7-D2E522F1F9DF -name lrf2lrs.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::36135E27-30B6-A0E9-5423-FDDFBCBD4FA2 -name feeds2disk.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A99CACD7-FE57-8A5E-BAA1-2D8B41925593 -name IM_MOD_RL_cip_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::782BE25A-B08E-D914-73F5-D92430823BE1 -name mobi2lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::C4649D7A-9312-8984-E7CD-A0AB794C5A8E -name IM_MOD_RL_pdf_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::B50467F0-07D0-B634-EB43-C4B721798A84 -name analyze.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::5531BA1B-8FDA-01B2-7DEE-6E9A116F7AB3 -name lrf-meta.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::47658BFB-7389-6866-359E-7517C18E768D -name IM_MOD_RL_mpr_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::673BD772-ACE7-1695-EAAC-EDD1A3EA0425 -name pdftrim.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::3F6735E8-D4E6-A6F9-C9F8-4219E6BBC827 -name CORE_RL_jpeg_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::918BE500-1E97-90C0-3FB3-4056FE8D0E15 -name IM_MOD_RL_wbmp_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::D59F7D54-24A7-774D-79EB-423A64E8CB21 -name IM_MOD_RL_stegano_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::BB8BCD59-9EBA-3D9B-D802-79D9C384AADB -name fb2-meta.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::17F9A868-E52D-F961-9DB7-8AC69BA702EA -name CORE_RL_jp2_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2ABAD420-B987-612E-C654-10BDB18DE638 -name html2oeb.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::81715796-F363-675F-998E-EEEF890009A2 -name calibre-fontconfig.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::5CA62422-C207-8C9C-79EB-45260E3DB37B -name odt2oeb.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::878003DC-9B1D-7C58-29F8-14D4565ABA64 -name IM_MOD_RL_yuv_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::602B3244-CF6C-F934-E86C-8161800DB150 -name coder.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::C23261F0-19EA-2216-A8D6-45FCC4ECBECE -name win32file.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::100103F9-C269-9B03-A133-7D046DCC5B30 -name IM_MOD_RL_null_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::D0461A63-6890-CC43-23D1-48E938BF1E6D -name oeb2mobi.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::F3996144-7BA7-6038-FBB2-C210721BD20E -name lit-meta.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::9BEE3580-3674-F4AE-C1F3-B8623E0E4FC8 -name IM_MOD_RL_pattern_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::AC4AE65E-78A4-28C0-1C1B-B99239D0A0EC -name rb-meta.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::C025F962-60EE-E4EF-6428-93755DD24F7D -name web2lrf.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::6A41C4F0-802D-9999-2F7D-AF86466BC420 -name feeds2epub.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::58F70BF7-491E-38D1-B836-1BBF340AA602 -name IM_MOD_RL_plasma_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::4B6C22CD-D139-FCAB-45FB-E9B7DADFFFED -name win32process.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::4F391A40-8D34-DFEA-2CEB-97A45FF5D234 -name lit2oeb.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::9698339D-6A1E-7CBC-A772-C9C4B10418BC -name librarything.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A3F1DC1C-BEF6-CB6B-F7AC-39F34F4DFABD -name IM_MOD_RL_pcx_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::BE44B72D-2ED1-4983-F652-DE8814401946 -name CORE_RL_xlib_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::EC5B5C55-8347-0A8C-E9B5-28E45680E2F2 -name IM_MOD_RL_caption_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::519BA66E-0CD3-00EA-0932-8D225C4C7EC8 -name calibredb.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::EF7BD102-32B6-EEAB-1C3F-AC4059EFEB1E -name comic2lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::B7E13F99-C8D3-F2FB-1E5F-E8530A77BB71 -name IM_MOD_RL_url_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::D0762C72-29E8-85CC-8550-6AC9E9AE39E9 -name _win32sysloader.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::4921649D-06BD-A568-3879-BD2D94423FCB -name pdftohtml.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2D4976D6-B0ED-C239-5461-70A711A018BE -name QtCore4.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::AA761ACD-B728-2324-AA75-B20A2A79F125 -name lrf2lrs.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::95434C76-22F5-B9CE-6194-6E1B1EE3232D -name IM_MOD_RL_info_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::AAF45D03-322F-5553-63A7-312DB754A20B -name _ctypes.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::C3D351CA-A8D8-AB35-55D9-5AACF8DB37D1 -name python26.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::2F90B52F-A728-2CA4-5688-0283674695B7 -name _elementtree.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::B50B66A1-FB65-FAD5-1DD7-E894ACC07464 -name QtSvg4.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::906FF13D-D993-7192-7EA5-6D15A5A24BFB -name CORE_RL_png_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::5D368661-6BF0-D6AF-7C1A-87646864EB4B -name delegates.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::6BB7B9CF-131D-3B0A-3A23-21BF168D78A4 -name win32event.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::9BA85EE5-1754-67AF-736D-481CDCC72DD2 -name _imagingft.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::6254DD0C-8F2C-D4AE-2107-2597D542C181 -name IM_MOD_RL_matte_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::F159D566-88D6-C347-3E3C-55C2DDFC5FD0 -name IM_MOD_RL_mono_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::B873CAA2-011F-94C3-7977-FF344E53C44F -name CORE_RL_jbig_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::7004FCB8-C6F4-C7AF-08E4-B6151B2F7050 -name tcl85.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::6921F62A-4015-4C9F-98A6-BCBBC43B698E -name msvcm90.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::7276E0CA-C205-4B18-19A3-157F1B8523FB -name IM_MOD_RL_xtrn_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::7B9624A9-88B4-C61E-6771-9A34FB6CA3B5 -name PyQt4.QtGui.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::80EC4265-6F3B-7F74-F995-4FA85E87A877 -name IM_MOD_RL_vicar_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::4CDC2DC5-5A3D-B773-A338-3E12E8C5BB9D -name IM_MOD_RL_psd_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::4AF4C7B2-8926-4581-2896-C6436460C81F -name IM_MOD_RL_uyvy_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::BF71D048-1137-4453-960C-9267CB790EAD -name IM_MOD_RL_art_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::1BB40D21-9061-9B38-E653-26D808F7DEEA -name html2lrf.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::D9B6371C-332C-45FB-DAEE-1A247130F704 -name calibre-customize.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::605E727F-DE13-27FE-75C0-34567C468B18 -name calibre-parallel.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::61F250E4-BAA0-5AF3-F597-E53ADEC19877 -name comic2pdf.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::37306B68-BABB-418C-A2CA-BAF4E439D3F1 -name viewer.ico -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::6092C5A2-8740-2769-DB77-E0B664CD19EC -name PyQt4.QtWebKit.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::73C82473-A35A-23B8-067C-AA3120D1CEE2 -name win32pipe.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::F269DBE1-334B-2108-31C2-E28664703103 -name _imaging.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::1FF92E56-1C02-1600-8FF5-24B25DD40A51 -name IM_MOD_RL_ycbcr_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::B863CAFC-1205-0F3B-949A-68310B7F3EDB -name type-ghostscript.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::931C4A8D-5B70-AC18-605E-DE89810C778E -name IM_MOD_RL_x_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::5BF8A346-2EC6-E5E0-063F-6FB02F84FCAC -name IM_MOD_RL_dng_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::19891B45-E756-8755-9156-FA8CB87AD057 -name IM_MOD_RL_html_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::BC81419A-A3B0-45B4-6672-7EB91B249786 -name IM_MOD_RL_tim_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::4E37CF0F-60E7-5CA5-D9FB-C9CD211B8A94 -name IM_MOD_RL_tile_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::D6A57A60-DCB9-BA5C-14BD-5C6143CF34C5 -name numpy.lib._compiled_base.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::E2573481-3079-D789-64A6-CC6EA121F7B4 -name IM_MOD_RL_xc_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::F7129083-E586-0227-69C1-63D832794884 -name CORE_RL_zlib_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::29022F2B-4E69-8624-2B2C-98A40E12DC0A -name calibre-server.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::7592A236-4835-6A9D-56B1-78985267D9AD -name win32console.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::05F4A6BD-A7C3-5108-4708-1524EB0628D9 -name calibre.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::DEB8E703-365E-A7BB-2315-6306B2E5C978 -name win32evtlog.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::6C90CF50-4894-7672-BB1B-F7EE5EB631E3 -name lit2oeb.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::AB44B44C-E258-A5AB-9511-8D0FBB456EE8 -name IM_MOD_RL_gif_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::5D5F2AAD-6668-5E14-B84C-5D80C4CCC968 -name calibre-customize.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::5A0DA3BB-2659-F5AA-A305-C4810FA28D89 -name mobi2oeb.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::9A0C05D0-26E3-72C5-1F04-76F76ABB180F -name numpy.core.scalarmath.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::42616D6D-2797-559B-7E96-2685599993DF -name any2lit.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::CB47F917-A45E-B7DE-645C-CA33F005D1BD -name any2mobi.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::16AF75CF-CA5E-F533-6C28-7CF167B1215C -name comic2mobi.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::0888A744-B049-01E8-6AC8-ECC3EE2CEFE6 -name IM_MOD_RL_sgi_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::4AEEBBED-E749-15A9-E1F7-9F6BE2A2C3D0 -name _socket.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::35B19292-0C50-4AEE-0A79-B2F3BC34B529 -name w9xpopen.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::E2C2739C-56B3-E2D0-A1DA-0E3FC3087924 -name IM_MOD_RL_ept_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::C6C5BD2D-887F-5D79-F8B5-C6041C0AEF3C -name PyQt4.Qt.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::8CD7A1E2-9139-8FE7-AAF5-E18B2A6825F2 -name bz2.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::EBF917FB-15F6-9B3E-5C37-AB0B1D50C64D -name IM_MOD_RL_palm_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::A5500C64-121C-4419-9E3D-F698FE462AE8 -name IM_MOD_RL_ps_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::ADBFA663-E232-7AC1-34A1-700D056E143A -name IM_MOD_RL_miff_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::325F545D-30A8-08DA-74F0-AC1244F6C1D9 -name IM_MOD_RL_vid_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::24238371-77D0-0A8F-35D1-498A5FCC1B0D -name IM_MOD_RL_rla_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::6F5D62F3-5E63-0753-364C-01CAAF1002E0 -name IM_MOD_RL_magick_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
File ::9FDAC308-5D4F-A865-A09A-9FBF48162A47 -name IM_MOD_RL_djvu_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
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 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 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 insert -active Yes -parent StandardInstall
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 Condition D7F585DB-0DEC-A94E-DAB0-94D558D82764 -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A -title {Execute Script Condition} -component ExecuteScriptCondition -TreeObject::id D7F585DB-0DEC-A94E-DAB0-94D558D82764
InstallComponent 7CCDA4BB-861C-C21E-3011-E93DB58F07D6 -setup Install -type action -conditions ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E -title {Check for Previous Install} -component CheckForPreviousInstall -command reorder -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A
Condition ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E -active Yes -parent 7CCDA4BB-861C-C21E-3011-E93DB58F07D6 -title {Execute Script Condition} -component ExecuteScriptCondition -TreeObject::id ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E
InstallComponent 580ACF2C-517F-5E48-9DEF-7DAEFBA59FDD -setup Install -type action -conditions 6DE3B369-9D6B-6BC1-4EA0-2C54ECE159EB -title {Set Virtual Text} -component SetVirtualText -command insert -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A 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 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
InstallComponent 9ACCDC35-E034-F142-ED42-AC8EC2B9AE2D -setup Install -type pane -title {License Agreement} -component License -active Yes -parent StandardInstall InstallComponent 9ACCDC35-E034-F142-ED42-AC8EC2B9AE2D -setup Install -type pane -title {License Agreement} -component License -active Yes -parent StandardInstall
@ -513,7 +854,7 @@ false
1 1
3EA07B17-04D8-6508-B535-96CC7173B49A,Conditions 3EA07B17-04D8-6508-B535-96CC7173B49A,Conditions
{0 conditions} {1 condition}
3EA07B17-04D8-6508-B535-96CC7173B49A,Message,subst 3EA07B17-04D8-6508-B535-96CC7173B49A,Message,subst
1 1
@ -527,6 +868,9 @@ false
3F2A14F0-06AC-C9D3-1F07-311F41E1338E,Alias 3F2A14F0-06AC-C9D3-1F07-311F41E1338E,Alias
{Startup Actions} {Startup Actions}
3F2A14F0-06AC-C9D3-1F07-311F41E1338E,Conditions
{0 conditions}
442920D9-8A51-9476-14E4-787D5C230E84,Message,subst 442920D9-8A51-9476-14E4-787D5C230E84,Message,subst
1 1
@ -867,10 +1211,10 @@ LaunchApplication
160 160
7CCDA4BB-861C-C21E-3011-E93DB58F07D6,Conditions 7CCDA4BB-861C-C21E-3011-E93DB58F07D6,Conditions
{0 conditions} {1 condition}
7CCDA4BB-861C-C21E-3011-E93DB58F07D6,ExecuteAction 7CCDA4BB-861C-C21E-3011-E93DB58F07D6,ExecuteAction
{Before Pane is Displayed} {Before Next Pane is Displayed}
825CDD1E-9B3D-E64E-5381-5C5557D204A2,CheckCondition 825CDD1E-9B3D-E64E-5381-5C5557D204A2,CheckCondition
{Before Action is Executed} {Before Action is Executed}
@ -1097,9 +1441,29 @@ AAF04AF0-329D-75A6-BB68-60ECF2EB74F0,String
AAFE58A0-2DFB-CA20-1F6E-D3815F885996,Alias AAFE58A0-2DFB-CA20-1F6E-D3815F885996,Alias
{Cancel Actions} {Cancel Actions}
ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E,CheckCondition
{Before Action is Executed}
ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E,FailureMessage
{Cannot proceed because calibre is running. You can quit it by right clicking the calibre system tray icon.}
ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E,ResultVirtualText
CalibreRunning
ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E,Script
{set pid [::InstallAPI::FindProcesses -name calibre.exe]
if {$pid eq ""} {
## myapp.exe is not running
return 1
}
return 0}
AIX-ppc,Active AIX-ppc,Active
No No
AIX-ppc,BuildSeparateArchives
No
AIX-ppc,DefaultDirectoryPermission AIX-ppc,DefaultDirectoryPermission
0755 0755
@ -1286,6 +1650,26 @@ CFBE4459-450B-1FAB-3422-609544334AA2,String
D79DC0D2-38BC-9D9F-2DF4-3C76D89BF933,ExitType D79DC0D2-38BC-9D9F-2DF4-3C76D89BF933,ExitType
Finish 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 D86BBA5C-4903-33BA-59F8-4266A3D45896,Conditions
{2 conditions} {2 conditions}
@ -1475,6 +1859,9 @@ FBA33088-C809-DD6B-D337-EADBF1CEE966,String
FreeBSD-4-x86,Active FreeBSD-4-x86,Active
No No
FreeBSD-4-x86,BuildSeparateArchives
No
FreeBSD-4-x86,DefaultDirectoryPermission FreeBSD-4-x86,DefaultDirectoryPermission
0755 0755
@ -1526,6 +1913,9 @@ FreeBSD-4-x86,RootInstallDir
FreeBSD-x86,Active FreeBSD-x86,Active
No No
FreeBSD-x86,BuildSeparateArchives
No
FreeBSD-x86,DefaultDirectoryPermission FreeBSD-x86,DefaultDirectoryPermission
0755 0755
@ -1577,6 +1967,9 @@ FreeBSD-x86,RootInstallDir
HPUX-hppa,Active HPUX-hppa,Active
No No
HPUX-hppa,BuildSeparateArchives
No
HPUX-hppa,DefaultDirectoryPermission HPUX-hppa,DefaultDirectoryPermission
0755 0755
@ -1628,6 +2021,9 @@ HPUX-hppa,RootInstallDir
Linux-x86,Active Linux-x86,Active
No No
Linux-x86,BuildSeparateArchives
No
Linux-x86,DefaultDirectoryPermission Linux-x86,DefaultDirectoryPermission
0755 0755
@ -1679,6 +2075,9 @@ Linux-x86,RootInstallDir
Solaris-sparc,Active Solaris-sparc,Active
No No
Solaris-sparc,BuildSeparateArchives
No
Solaris-sparc,DefaultDirectoryPermission Solaris-sparc,DefaultDirectoryPermission
0755 0755
@ -1730,6 +2129,9 @@ Solaris-sparc,RootInstallDir
TarArchive,Active TarArchive,Active
No No
TarArchive,BuildSeparateArchives
No
TarArchive,CompressionLevel TarArchive,CompressionLevel
6 6
@ -1790,9 +2192,15 @@ TarArchive,VirtualTextMap
Windows,Active Windows,Active
Yes Yes
Windows,BuildSeparateArchives
No
Windows,Executable Windows,Executable
<%AppName%>-<%Version%><%Ext%> <%AppName%>-<%Version%><%Ext%>
Windows,FileDescription
{<%AppName%> <%Version%> Setup}
Windows,IncludeTWAPI Windows,IncludeTWAPI
Yes Yes
@ -1829,6 +2237,9 @@ Windows,WindowsIcon
ZipArchive,Active ZipArchive,Active
No No
ZipArchive,BuildSeparateArchives
No
ZipArchive,CompressionLevel ZipArchive,CompressionLevel
6 6

View File

@ -121,7 +121,7 @@ if __name__ == '__main__':
buf = cStringIO.StringIO() buf = cStringIO.StringIO()
print 'Creating translations template' print 'Creating translations template'
tempdir = tempfile.mkdtemp() tempdir = tempfile.mkdtemp()
pygettext(buf, ['-p', tempdir]+files) pygettext(buf, ['-k', '__', '-p', tempdir]+files)
src = buf.getvalue() src = buf.getvalue()
pot = os.path.join(tempdir, 'calibre.pot') pot = os.path.join(tempdir, 'calibre.pot')
f = open(pot, 'wb') f = open(pot, 'wb')

View File

@ -20,6 +20,7 @@ import mechanize
mimetypes.add_type('application/epub+zip', '.epub') mimetypes.add_type('application/epub+zip', '.epub')
mimetypes.add_type('text/x-sony-bbeb+xml', '.lrs') mimetypes.add_type('text/x-sony-bbeb+xml', '.lrs')
mimetypes.add_type('application/x-sony-bbeb', '.lrf') 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'): def to_unicode(raw, encoding='utf-8', errors='strict'):
if isinstance(raw, unicode): if isinstance(raw, unicode):

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = 'calibre' __appname__ = 'calibre'
__version__ = '0.4.129' __version__ = '0.4.130'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
''' '''
Various run time constants. Various run time constants.

View File

@ -132,7 +132,7 @@ class HTMLMetadataReader(MetadataReaderPlugin):
class MOBIMetadataReader(MetadataReaderPlugin): class MOBIMetadataReader(MetadataReaderPlugin):
name = 'Read MOBI metadata' name = 'Read MOBI metadata'
file_types = set(['mobi']) file_types = set(['mobi', 'prc'])
description = _('Read metadata from %s files')%'MOBI' description = _('Read metadata from %s files')%'MOBI'
def get_metadata(self, stream, ftype): def get_metadata(self, stream, ftype):
@ -189,11 +189,22 @@ class ZipMetadataReader(MetadataReaderPlugin):
from calibre.ebooks.metadata.zip import get_metadata from calibre.ebooks.metadata.zip import get_metadata
return get_metadata(stream) return get_metadata(stream)
class RARMetadataReader(MetadataReaderPlugin):
name = 'Read RAR metadata'
file_types = set(['rar'])
description = _('Read metadata from ebooks in RAR archives')
def get_metadata(self, stream, ftype):
from calibre.ebooks.metadata.rar import get_metadata
return get_metadata(stream)
class EPUBMetadataWriter(MetadataWriterPlugin): class EPUBMetadataWriter(MetadataWriterPlugin):
name = 'Set EPUB metadata' name = 'Set EPUB metadata'
file_types = set(['epub']) file_types = set(['epub'])
description = _('Set metadata in EPUB files') description = _('Set metadata in %s files')%'EPUB'
def set_metadata(self, stream, mi, type): def set_metadata(self, stream, mi, type):
from calibre.ebooks.metadata.epub import set_metadata from calibre.ebooks.metadata.epub import set_metadata
@ -203,7 +214,7 @@ class LRFMetadataWriter(MetadataWriterPlugin):
name = 'Set LRF metadata' name = 'Set LRF metadata'
file_types = set(['lrf']) file_types = set(['lrf'])
description = _('Set metadata in LRF files') description = _('Set metadata in %s files')%'LRF'
def set_metadata(self, stream, mi, type): def set_metadata(self, stream, mi, type):
from calibre.ebooks.lrf.meta import set_metadata from calibre.ebooks.lrf.meta import set_metadata
@ -213,12 +224,24 @@ class RTFMetadataWriter(MetadataWriterPlugin):
name = 'Set RTF metadata' name = 'Set RTF metadata'
file_types = set(['rtf']) file_types = set(['rtf'])
description = _('Set metadata in RTF files') description = _('Set metadata in %s files')%'RTF'
def set_metadata(self, stream, mi, type): def set_metadata(self, stream, mi, type):
from calibre.ebooks.metadata.rtf import set_metadata from calibre.ebooks.metadata.rtf import set_metadata
set_metadata(stream, mi) set_metadata(stream, mi)
class MOBIMetadataWriter(MetadataWriterPlugin):
name = 'Set MOBI metadata'
file_types = set(['mobi', 'prc'])
description = _('Set metadata in %s files')%'MOBI'
author = 'Marshall T. Vandegrift'
def set_metadata(self, stream, mi, type):
from calibre.ebooks.metadata.mobi import set_metadata
set_metadata(stream, mi)
plugins = [HTML2ZIP] plugins = [HTML2ZIP]
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
x.__name__.endswith('MetadataReader')] x.__name__.endswith('MetadataReader')]

View File

@ -7,8 +7,10 @@ Device driver for Bookeen's Cybook Gen 3
import os, shutil import os, shutil
from itertools import cycle from itertools import cycle
from calibre.devices.errors import FreeSpaceError
from calibre.devices.usbms.driver import USBMS from calibre.devices.usbms.driver import USBMS
import calibre.devices.cybookg3.t2b as t2b import calibre.devices.cybookg3.t2b as t2b
from calibre.devices.errors import FreeSpaceError
class CYBOOKG3(USBMS): class CYBOOKG3(USBMS):
# Ordered list of supported formats # Ordered list of supported formats
@ -30,6 +32,7 @@ class CYBOOKG3(USBMS):
STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card' STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card'
EBOOK_DIR_MAIN = "eBooks" EBOOK_DIR_MAIN = "eBooks"
EBOOK_DIR_CARD = "eBooks"
SUPPORTS_SUB_DIRS = True SUPPORTS_SUB_DIRS = True
def upload_books(self, files, names, on_card=False, end_session=True, def upload_books(self, files, names, on_card=False, end_session=True,
@ -50,10 +53,10 @@ class CYBOOKG3(USBMS):
return size return size
return os.path.getsize(obj) return os.path.getsize(obj)
sizes = map(get_size, files) sizes = [get_size(f) for f in files]
size = sum(sizes) 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")) 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")) raise FreeSpaceError(_("There is insufficient free space in main memory"))

View File

@ -18,6 +18,9 @@ class Book(object):
self.thumbnail = None self.thumbnail = None
self.tags = [] self.tags = []
def __eq__(self, other):
return self.path == other.path
@apply @apply
def title_sorter(): def title_sorter():
doc = '''String to sort the title. If absent, title is returned''' doc = '''String to sort the title. If absent, title is returned'''

View File

@ -3,10 +3,10 @@ __copyright__ = '2009, John Schember <john at nachtimwald.com>'
''' '''
Generic device driver. This is not a complete stand alone driver. It is 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 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.interface import Device as _Device
from calibre.devices.errors import DeviceError 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 as USB Mass Storage devices. If you are writing such a driver, inherit from this
class. class.
''' '''
VENDOR_ID = 0x0 VENDOR_ID = 0x0
PRODUCT_ID = 0x0 PRODUCT_ID = 0x0
BCD = None BCD = None
VENDOR_NAME = None VENDOR_NAME = None
WINDOWS_MAIN_MEM = None WINDOWS_MAIN_MEM = None
WINDOWS_CARD_MEM = None WINDOWS_CARD_MEM = None
OSX_MAIN_MEM = None OSX_MAIN_MEM = None
OSX_CARD_MEM = None OSX_CARD_MEM = None
MAIN_MEMORY_VOLUME_LABEL = '' MAIN_MEMORY_VOLUME_LABEL = ''
STORAGE_CARD_VOLUME_LABEL = '' STORAGE_CARD_VOLUME_LABEL = ''
FDI_TEMPLATE = \ FDI_TEMPLATE = \
''' '''
<device> <device>
@ -65,15 +65,15 @@ class Device(_Device):
</device> </device>
''' '''
FDI_BCD_TEMPLATE = '<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.device_revision_bcd" int="%(bcd)s">' FDI_BCD_TEMPLATE = '<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.device_revision_bcd" int="%(bcd)s">'
def __init__(self, key='-1', log_packets=False, report_progress=None) : def __init__(self, key='-1', log_packets=False, report_progress=None) :
self._main_prefix = self._card_prefix = None self._main_prefix = self._card_prefix = None
@classmethod @classmethod
def get_fdi(cls): def get_fdi(cls):
fdi = '' fdi = ''
fdi_base_values = dict( fdi_base_values = dict(
app=__appname__, app=__appname__,
deviceclass=cls.__name__, 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_start'] = cls.FDI_BCD_TEMPLATE % dict(bcd=hex(bcd))
fdi_bcd_values['BCD_end'] = '</match>' fdi_bcd_values['BCD_end'] = '</match>'
fdi += cls.FDI_TEMPLATE % fdi_bcd_values fdi += cls.FDI_TEMPLATE % fdi_bcd_values
return fdi return fdi
def set_progress_reporter(self, report_progress): def set_progress_reporter(self, report_progress):
self.report_progress = report_progress self.report_progress = report_progress
def card_prefix(self, end_session=True): def card_prefix(self, end_session=True):
return self._card_prefix return self._card_prefix
@ -117,7 +117,7 @@ class Device(_Device):
else: raise else: raise
mult = sectors_per_cluster * bytes_per_sector mult = sectors_per_cluster * bytes_per_sector
return total_clusters * mult, free_clusters * mult return total_clusters * mult, free_clusters * mult
def total_space(self, end_session=True): def total_space(self, end_session=True):
msz = csz = 0 msz = csz = 0
print self._main_prefix print self._main_prefix
@ -131,9 +131,9 @@ class Device(_Device):
else: else:
msz = self._windows_space(self._main_prefix)[0] msz = self._windows_space(self._main_prefix)[0]
csz = self._windows_space(self._card_prefix)[0] csz = self._windows_space(self._card_prefix)[0]
return (msz, 0, csz) return (msz, 0, csz)
def free_space(self, end_session=True): def free_space(self, end_session=True):
msz = csz = 0 msz = csz = 0
if not iswindows: if not iswindows:
@ -146,15 +146,15 @@ class Device(_Device):
else: else:
msz = self._windows_space(self._main_prefix)[1] msz = self._windows_space(self._main_prefix)[1]
csz = self._windows_space(self._card_prefix)[1] csz = self._windows_space(self._card_prefix)[1]
return (msz, 0, csz) return (msz, 0, csz)
def windows_match_device(self, pnp_id, device_id): def windows_match_device(self, pnp_id, device_id):
pnp_id = pnp_id.upper() pnp_id = pnp_id.upper()
if device_id and pnp_id is not None: if device_id and pnp_id is not None:
device_id = device_id.upper() device_id = device_id.upper()
if 'VEN_' + self.VENDOR_NAME in pnp_id and 'PROD_' + device_id in pnp_id: if 'VEN_' + self.VENDOR_NAME in pnp_id and 'PROD_' + device_id in pnp_id:
return True return True
@ -162,45 +162,45 @@ class Device(_Device):
def windows_get_drive_prefix(self, drive): def windows_get_drive_prefix(self, drive):
prefix = None prefix = None
try: try:
partition = drive.associators("Win32_DiskDriveToDiskPartition")[0] partition = drive.associators("Win32_DiskDriveToDiskPartition")[0]
logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0] logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0]
prefix = logical_disk.DeviceID + os.sep prefix = logical_disk.DeviceID + os.sep
except IndexError: except IndexError:
pass pass
return prefix return prefix
def open_windows(self): def open_windows(self):
drives = {} drives = {}
wmi = __import__('wmi', globals(), locals(), [], -1) wmi = __import__('wmi', globals(), locals(), [], -1)
c = wmi.WMI() c = wmi.WMI()
for drive in c.Win32_DiskDrive(): 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) 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) drives['card'] = self.windows_get_drive_prefix(drive)
if 'main' and 'card' in drives.keys(): if 'main' and 'card' in drives.keys():
break break
if not drives: if not drives:
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.') % self.__class__.__name__) 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): def get_osx_mountpoints(self, raw=None):
if raw is None: if raw is None:
ioreg = '/usr/sbin/ioreg' ioreg = '/usr/sbin/ioreg'
if not os.access(ioreg, os.X_OK): if not os.access(ioreg, os.X_OK):
ioreg = 'ioreg' 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() lines = raw.splitlines()
names = {} names = {}
def get_dev_node(lines, loc): def get_dev_node(lines, loc):
for line in lines: for line in lines:
line = line.strip() line = line.strip()
@ -210,7 +210,7 @@ class Device(_Device):
if match is not None: if match is not None:
names[loc] = match.group(1) names[loc] = match.group(1)
break break
for i, line in enumerate(lines): for i, line in enumerate(lines):
if self.OSX_MAIN_MEM is not None and line.strip().endswith('<class IOMedia>') and self.OSX_MAIN_MEM in line: if self.OSX_MAIN_MEM is not None and line.strip().endswith('<class IOMedia>') and self.OSX_MAIN_MEM in line:
get_dev_node(lines[i+1:], 'main') get_dev_node(lines[i+1:], 'main')
@ -219,25 +219,25 @@ class Device(_Device):
if len(names.keys()) == 2: if len(names.keys()) == 2:
break break
return names return names
def open_osx(self): def open_osx(self):
mount = subprocess.Popen('mount', shell=True, stdout=subprocess.PIPE).stdout.read() mount = subprocess.Popen('mount', shell=True, stdout=subprocess.PIPE).stdout.read()
names = self.get_osx_mountpoints() names = self.get_osx_mountpoints()
dev_pat = r'/dev/%s(\w*)\s+on\s+([^\(]+)\s+' dev_pat = r'/dev/%s(\w*)\s+on\s+([^\(]+)\s+'
if 'main' not in names.keys(): if 'main' not in names.keys():
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__) 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 self._main_prefix = re.search(main_pat, mount).group(2) + os.sep
card_pat = names['card'] if 'card' in names.keys() else None card_pat = names['card'] if 'card' in names.keys() else None
if card_pat is not 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 self._card_prefix = re.search(card_pat, mount).group(2) + os.sep
def open_linux(self): def open_linux(self):
import dbus 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") hm = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager")
def conditional_mount(dev): def conditional_mount(dev):
mmo = bus.get_object("org.freedesktop.Hal", dev) mmo = bus.get_object("org.freedesktop.Hal", dev)
label = mmo.GetPropertyString('volume.label', dbus_interface='org.freedesktop.Hal.Device') 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') fstype = mmo.GetPropertyString('volume.fstype', dbus_interface='org.freedesktop.Hal.Device')
if is_mounted: if is_mounted:
return str(mount_point) 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') dbus_interface='org.freedesktop.Hal.Device.Volume')
return os.path.normpath('/media/'+label)+'/' return os.path.normpath('/media/'+label)+'/'
mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__) mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__)
if not mm: if not mm:
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,)) raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,))
@ -260,13 +260,13 @@ class Device(_Device):
break break
except dbus.exceptions.DBusException: except dbus.exceptions.DBusException:
continue continue
if not self._main_prefix: if not self._main_prefix:
raise DeviceError('Could not open device for reading. Try a reboot.') raise DeviceError('Could not open device for reading. Try a reboot.')
self._card_prefix = None self._card_prefix = None
cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__) cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__)
for dev in cards: for dev in cards:
try: try:
self._card_prefix = conditional_mount(dev)+os.sep self._card_prefix = conditional_mount(dev)+os.sep

View File

@ -9,11 +9,26 @@ for a particular device.
import os, fnmatch, shutil import os, fnmatch, shutil
from itertools import cycle from itertools import cycle
from calibre.ebooks.metadata.meta import metadata_from_formats, path_to_ext
from calibre.ebooks.metadata import authors_to_string
from calibre.devices.usbms.device import Device from calibre.devices.usbms.device import Device
from calibre.devices.usbms.books import BookList, Book 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 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): class USBMS(Device):
FORMATS = [] FORMATS = []
EBOOK_DIR_MAIN = '' EBOOK_DIR_MAIN = ''
@ -21,39 +36,39 @@ class USBMS(Device):
SUPPORTS_SUB_DIRS = False SUPPORTS_SUB_DIRS = False
def __init__(self, key='-1', log_packets=False, report_progress=None): 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): 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: (device name, device version, software version on device, mime type)
""" """
return (self.__class__.__name__, '', '', '') return (self.__class__.__name__, '', '', '')
def books(self, oncard=False, end_session=True): def books(self, oncard=False, end_session=True):
bl = BookList() bl = BookList()
if oncard and self._card_prefix is None: if oncard and self._card_prefix is None:
return bl return bl
prefix = self._card_prefix if oncard else self._main_prefix prefix = self._card_prefix if oncard else self._main_prefix
ebook_dir = self.EBOOK_DIR_CARD if oncard else self.EBOOK_DIR_MAIN ebook_dir = self.EBOOK_DIR_CARD if oncard else self.EBOOK_DIR_MAIN
# Get all books in all directories under the root ebook_dir directory # Get all books in all directories under the root ebook_dir directory
for path, dirs, files in os.walk(os.path.join(prefix, ebook_dir)): 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 book_type in self.FORMATS:
for filename in fnmatch.filter(files, '*.%s' % (book_type)): for filename in fnmatch.filter(files, '*.%s' % (book_type)):
title, author, mime = self.__class__.extract_book_metadata_by_filename(filename) bl.append(self.__class__.book_from_path(os.path.join(path, filename)))
bl.append(Book(os.path.join(path, filename), title, author, mime))
return bl 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): metadata=None):
if on_card and not self._card_prefix: if on_card and not self._card_prefix:
raise ValueError(_('The reader has no storage card connected.')) raise ValueError(_('The reader has no storage card connected.'))
if not on_card: if not on_card:
path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN) path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN)
else: else:
@ -67,24 +82,24 @@ class USBMS(Device):
return size return size
return os.path.getsize(obj) return os.path.getsize(obj)
sizes = map(get_size, files) sizes = [get_size(f) for f in files]
size = sum(sizes) 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")) 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")) raise FreeSpaceError(_("There is insufficient free space in main memory"))
paths = [] paths = []
names = iter(names) names = iter(names)
metadata = iter(metadata) metadata = iter(metadata)
for infile in files: for infile in files:
newpath = path newpath = path
if self.SUPPORTS_SUB_DIRS: if self.SUPPORTS_SUB_DIRS:
mdata = metadata.next() mdata = metadata.next()
if 'tags' in mdata.keys(): if 'tags' in mdata.keys():
for tag in mdata['tags']: for tag in mdata['tags']:
if tag.startswith('/'): if tag.startswith('/'):
@ -94,32 +109,35 @@ class USBMS(Device):
if not os.path.exists(newpath): if not os.path.exists(newpath):
os.makedirs(newpath) os.makedirs(newpath)
filepath = os.path.join(newpath, names.next()) filepath = os.path.join(newpath, names.next())
paths.append(filepath) paths.append(filepath)
if hasattr(infile, 'read'): if hasattr(infile, 'read'):
infile.seek(0) infile.seek(0)
dest = open(filepath, 'wb') dest = open(filepath, 'wb')
shutil.copyfileobj(infile, dest, 10*1024*1024) shutil.copyfileobj(infile, dest, 10*1024*1024)
dest.flush() dest.flush()
dest.close() dest.close()
else: else:
shutil.copy2(infile, filepath) shutil.copy2(infile, filepath)
return zip(paths, cycle([on_card])) return zip(paths, cycle([on_card]))
@classmethod @classmethod
def add_books_to_metadata(cls, locations, metadata, booklists): def add_books_to_metadata(cls, locations, metadata, booklists):
for location in locations: for location in locations:
path = location[0] path = location[0]
on_card = 1 if location[1] else 0 on_card = 1 if location[1] else 0
title, author, mime = cls.extract_book_metadata_by_filename(os.path.basename(path)) book = cls.book_from_path(path)
booklists[on_card].append(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): def delete_books(self, paths, end_session=True):
for path in paths: for path in paths:
if os.path.exists(path): if os.path.exists(path):
@ -130,7 +148,7 @@ class USBMS(Device):
os.removedirs(os.path.dirname(path)) os.removedirs(os.path.dirname(path))
except: except:
pass pass
@classmethod @classmethod
def remove_books_from_metadata(cls, paths, booklists): def remove_books_from_metadata(cls, paths, booklists):
for path in paths: for path in paths:
@ -138,14 +156,14 @@ class USBMS(Device):
for book in bl: for book in bl:
if path.endswith(book.path): if path.endswith(book.path):
bl.remove(book) bl.remove(book)
def sync_booklists(self, booklists, end_session=True): def sync_booklists(self, booklists, end_session=True):
# There is no meta data on the device to update. The device is treated # 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 # as a mass storage device and does not use a meta data xml file like
# the Sony Readers. # the Sony Readers.
pass pass
def get_file(self, path, outfile, end_session=True): def get_file(self, path, outfile, end_session=True):
path = self.munge_path(path) path = self.munge_path(path)
src = open(path, 'rb') src = open(path, 'rb')
shutil.copyfileobj(src, outfile, 10*1024*1024) shutil.copyfileobj(src, outfile, 10*1024*1024)
@ -198,28 +216,13 @@ class USBMS(Device):
os.utime(path, None) os.utime(path, None)
@classmethod @classmethod
def extract_book_metadata_by_filename(cls, filename): def book_from_path(cls, path):
book_title = '' fileext = path_to_ext(path)
book_author = ''
book_mime = '' mi = metadata_from_formats([path])
# Calibre uses a specific format for file names. They take the form mime = MIME_MAP[fileext] if fileext in MIME_MAP.keys() else 'Unknown'
# title_-_author_number.extention We want to see if the file name is
# in this format. authors = authors_to_string(mi.authors)
if fnmatch.fnmatchcase(filename, '*_-_*.*'):
# Get the title and author from the file name return Book(path, mi.title, authors, mime)
title, sep, author = filename.rpartition('_-_')
author, sep, ext = author.rpartition('_')
book_title = title.replace('_', ' ')
book_author = author.replace('_', ' ')
# if the filename did not match just set the title to
# the filename without the extension
else:
book_title = os.path.splitext(filename)[0].replace('_', ' ')
fileext = os.path.splitext(filename)[1][1:]
if fileext in cls.FORMATS:
book_mime = MIME_MAP[fileext] if fileext in MIME_MAP.keys() else 'Unknown'
return book_title, book_author, book_mime

View File

@ -15,15 +15,17 @@ from lxml import etree
class DefaultProfile(object): class DefaultProfile(object):
flow_size = sys.maxint flow_size = sys.maxint
screen_size = None screen_size = None
remove_special_chars = False remove_special_chars = False
remove_object_tags = False
class PRS505(DefaultProfile): class PRS505(DefaultProfile):
flow_size = 270000 flow_size = 270000
screen_size = (590, 765) screen_size = (590, 765)
remove_special_chars = re.compile(u'[\u200b\u00ad]') remove_special_chars = re.compile(u'[\u200b\u00ad]')
remove_object_tags = True
PROFILES = { PROFILES = {

View File

@ -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.ebooks.epub.from_html import convert as html2epub, find_html_index
from calibre.ptempfile import TemporaryDirectory from calibre.ptempfile import TemporaryDirectory
from calibre.ebooks.metadata import MetaInformation 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.utils.zipfile import ZipFile
from calibre.customize.ui import run_plugins_on_preprocess from calibre.customize.ui import run_plugins_on_preprocess
@ -25,9 +25,36 @@ def lit2opf(path, tdir, opts):
print 'Exploding LIT file:', path print 'Exploding LIT file:', path
reader = LitReader(path) reader = LitReader(path)
reader.extract_content(tdir, False) reader.extract_content(tdir, False)
for f in walk(tdir): opf = None
if f.lower().endswith('.opf'): for opf in walk(tdir):
return f 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): def mobi2opf(path, tdir, opts):
from calibre.ebooks.mobi.reader import MobiReader from calibre.ebooks.mobi.reader import MobiReader

View File

@ -153,11 +153,46 @@ class HTMLProcessor(Processor, Rationalizer):
Perform various markup transforms to get the output to render correctly Perform various markup transforms to get the output to render correctly
in the quirky ADE. in the quirky ADE.
''' '''
# Replace <br> that are children of <body> with <p>&nbsp;</p> # Replace <br> that are children of <body> as ADE doesn't handle them
if hasattr(self.body, 'xpath'): if hasattr(self.body, 'xpath'):
for br in self.body.xpath('./br'): for br in self.body.xpath('./br'):
if br.getparent() is None:
continue
try:
sibling = br.itersiblings().next()
except:
sibling = None
br.tag = 'p' br.tag = 'p'
br.text = u'\u00a0' br.text = u'\u00a0'
if (br.tail and br.tail.strip()) or sibling is None or \
getattr(sibling, 'tag', '') != 'br':
style = br.get('style', '').split(';')
style = filter(None, map(lambda x: x.strip(), style))
style.append('margin: 0pt; border:0pt; height:0pt')
br.set('style', '; '.join(style))
else:
sibling.getparent().remove(sibling)
if sibling.tail:
if not br.tail:
br.tail = ''
br.tail += sibling.tail
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)
for tag in self.root.xpath('//title|//style'):
if not tag.text:
tag.getparent().remove(tag)
for tag in self.root.xpath('//script'):
if not tag.text and not tag.get('src', False):
tag.getparent().remove(tag)
def save(self): def save(self):
for meta in list(self.root.xpath('//meta')): for meta in list(self.root.xpath('//meta')):

View File

@ -50,6 +50,7 @@ class Splitter(LoggingInterface):
self.split_size = 0 self.split_size = 0
# Split on page breaks # Split on page breaks
self.splitting_on_page_breaks = True
if not opts.dont_split_on_page_breaks: if not opts.dont_split_on_page_breaks:
self.log_info('\tSplitting on page breaks...') self.log_info('\tSplitting on page breaks...')
if self.path in stylesheet_map: if self.path in stylesheet_map:
@ -61,6 +62,7 @@ class Splitter(LoggingInterface):
trees = list(self.trees) trees = list(self.trees)
# Split any remaining over-sized trees # Split any remaining over-sized trees
self.splitting_on_page_breaks = False
if self.opts.profile.flow_size < sys.maxint: if self.opts.profile.flow_size < sys.maxint:
lt_found = False lt_found = False
self.log_info('\tLooking for large trees...') self.log_info('\tLooking for large trees...')
@ -203,7 +205,8 @@ class Splitter(LoggingInterface):
elem.set('style', 'display:none') elem.set('style', 'display:none')
def fix_split_point(sp): def fix_split_point(sp):
sp.set('style', sp.get('style', '')+'page-break-before:avoid;page-break-after:avoid') if not self.splitting_on_page_breaks:
sp.set('style', sp.get('style', '')+'page-break-before:avoid;page-break-after:avoid')
# Tree 1 # Tree 1
hit_split_point = False hit_split_point = False

View File

@ -823,21 +823,28 @@ class Processor(Parser):
font.set('class', cn) font.set('class', cn)
font.tag = 'span' font.tag = 'span'
id_css, id_css_counter = {}, 0
for elem in self.root.xpath('//*[@style]'): for elem in self.root.xpath('//*[@style]'):
setting = elem.get('style') setting = elem.get('style')
classname = cache.get(setting, None) if elem.get('id', False) or elem.get('class', False):
if classname is None: elem.set('id', elem.get('id', 'calibre_css_id_%d'%id_css_counter))
classname = 'calibre_class_%d'%class_counter id_css_counter += 1
class_counter += 1 id_css[elem.tag+'#'+elem.get('id')] = setting
cache[setting] = classname else:
cn = elem.get('class', '') classname = cache.get(setting, None)
if cn: cn += ' ' if classname is None:
cn += classname classname = 'calibre_class_%d'%class_counter
elem.set('class', cn) class_counter += 1
cache[setting] = classname
cn = elem.get('class', classname)
elem.set('class', cn)
elem.attrib.pop('style') elem.attrib.pop('style')
css = '\n'.join(['.%s {%s;}'%(cn, setting) for \ css = '\n'.join(['.%s {%s;}'%(cn, setting) for \
setting, cn in cache.items()]) setting, cn in cache.items()])
css += '\n\n'
css += '\n'.join(['%s {%s;}'%(selector, setting) for \
selector, setting in id_css.items()])
sheet = self.css_parser.parseString(self.preprocess_css(css.replace(';;}', ';}'))) sheet = self.css_parser.parseString(self.preprocess_css(css.replace(';;}', ';}')))
for rule in sheet: for rule in sheet:
self.stylesheet.add(rule) self.stylesheet.add(rule)

View File

@ -122,6 +122,8 @@ LZXC_CONTROL = \
COLLAPSE = re.compile(r'[ \t\r\n\v]+') COLLAPSE = re.compile(r'[ \t\r\n\v]+')
PAGE_BREAKS = set(['always', 'left', 'right'])
def decint(value): def decint(value):
bytes = [] bytes = []
while True: while True:
@ -202,7 +204,7 @@ class ReBinary(object):
self.write(FLAG_CUSTOM, len(tag)+1, tag) self.write(FLAG_CUSTOM, len(tag)+1, tag)
last_break = self.page_breaks[-1][0] if self.page_breaks else None last_break = self.page_breaks[-1][0] if self.page_breaks else None
if style and last_break != tag_offset \ if style and last_break != tag_offset \
and style['page-break-before'] not in ('avoid', 'auto'): and style['page-break-before'] in PAGE_BREAKS:
self.page_breaks.append((tag_offset, list(parents))) self.page_breaks.append((tag_offset, list(parents)))
for attr, value in attrib.items(): for attr, value in attrib.items():
attr = prefixname(attr, nsrmap) attr = prefixname(attr, nsrmap)

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
Based on ideas from comiclrf created by FangornUK. Based on ideas from comiclrf created by FangornUK.
''' '''
import os, sys, shutil, traceback, textwrap import os, sys, shutil, traceback, textwrap, fnmatch
from uuid import uuid4 from uuid import uuid4
@ -389,10 +389,33 @@ def create_lrf(pages, profile, opts, thumbnail=None):
print _('Output written to'), opts.output print _('Output written to'), opts.output
def create_pdf(pages, profile, opts, thumbnail=None): def create_pdf(pages, profile, opts, thumbnail=None,toc=None):
width, height = PROFILES[profile] width, height = PROFILES[profile]
from reportlab.pdfgen import canvas from reportlab.pdfgen import canvas
cur_page=0
heading = []
if toc != None:
if len(toc) == 1:
toc = None
else:
toc_index = 0
base_cur = 0
rem = 0
breaker = False
while True:
letter=toc[0][0][base_cur]
for i in range(len(toc)):
if letter != toc[i][0][base_cur]:
breaker = True
if breaker:
break
if letter == os.sep:
rem=base_cur
base_cur += 1
toc.append(("Not seen",-1))
pdf = canvas.Canvas(filename=opts.output, pagesize=(width,height+15)) pdf = canvas.Canvas(filename=opts.output, pagesize=(width,height+15))
pdf.setAuthor(opts.author) pdf.setAuthor(opts.author)
@ -400,7 +423,52 @@ def create_pdf(pages, profile, opts, thumbnail=None):
for page in pages: for page in pages:
pdf.drawImage(page, x=0,y=0,width=width, height=height) if opts.keep_aspect_ratio:
img = NewMagickWand()
if img < 0:
raise RuntimeError('Cannot create wand.')
if not MagickReadImage(img, page):
raise IOError('Failed to read image from: %'%page)
sizex = MagickGetImageWidth(img)
sizey = MagickGetImageHeight(img)
if opts.keep_aspect_ratio:
# Preserve the aspect ratio by adding border
aspect = float(sizex) / float(sizey)
if aspect <= (float(width) / float(height)):
newsizey = height
newsizex = int(newsizey * aspect)
deltax = (width - newsizex) / 2
deltay = 0
else:
newsizex = width
newsizey = int(newsizex / aspect)
deltax = 0
deltay = (height - newsizey) / 2
pdf.drawImage(page, x=deltax,y=deltay,width=newsizex, height=newsizey)
else:
pdf.drawImage(page, x=0,y=0,width=width, height=height)
if toc != None:
if toc[toc_index][1] == cur_page:
tmp=toc[toc_index][0]
toc_current=tmp[rem:len(tmp)-4]
index=0
while True:
key = 'page%d-%d' % (cur_page, index)
pdf.bookmarkPage(key)
(head,dummy,list)=toc_current.partition(os.sep)
try:
if heading[index] != head:
heading[index] = head
pdf.addOutlineEntry(title=head,key=key,level=index)
except:
heading.append(head)
pdf.addOutlineEntry(title=head,key=key,level=index)
index += 1
toc_current=list
if dummy == "":
break
toc_index += 1
cur_page += 1
pdf.showPage() pdf.showPage()
# Write the document to disk # Write the document to disk
pdf.save() pdf.save()
@ -409,36 +477,64 @@ def create_pdf(pages, profile, opts, thumbnail=None):
def do_convert(path_to_file, opts, notification=lambda m, p: p, output_format='lrf'): def do_convert(path_to_file, opts, notification=lambda m, p: p, output_format='lrf'):
path_to_file = run_plugins_on_preprocess(path_to_file) path_to_file = run_plugins_on_preprocess(path_to_file)
source = path_to_file source = path_to_file
to_delete = []
toc = []
list = []
pages = []
if not opts.title: if not opts.title:
opts.title = os.path.splitext(os.path.basename(source))[0] opts.title = os.path.splitext(os.path.basename(source))[0]
if not opts.output: if not opts.output:
opts.output = os.path.abspath(os.path.splitext(os.path.basename(source))[0]+'.'+output_format) opts.output = os.path.abspath(os.path.splitext(os.path.basename(source))[0]+'.'+output_format)
tdir = extract_comic(source) if os.path.isdir(source):
pages = find_pages(tdir, sort_on_mtime=opts.no_sort, verbose=opts.verbose) for path in all_files( source , '*.cbr|*.cbz' ):
thumbnail = None list.append( path )
if not pages: else:
raise ValueError('Could not find any pages in the comic: %s'%source) list= [ os.path.abspath(source) ]
if not getattr(opts, 'no_process', False):
pages, failures, tdir2 = process_pages(pages, opts, notification) for source in list:
if not pages: tdir = extract_comic(source)
raise ValueError('Could not find any valid pages in the comic: %s'%source) new_pages = find_pages(tdir, sort_on_mtime=opts.no_sort, verbose=opts.verbose)
if failures: thumbnail = None
print 'Could not process the following pages (run with --verbose to see why):' if not new_pages:
for f in failures: raise ValueError('Could not find any pages in the comic: %s'%source)
print '\t', f if not getattr(opts, 'no_process', False):
thumbnail = os.path.join(tdir2, 'thumbnail.png') new_pages, failures, tdir2 = process_pages(new_pages, opts, notification)
if not os.access(thumbnail, os.R_OK): if not new_pages:
thumbnail = None raise ValueError('Could not find any valid pages in the comic: %s'%source)
if failures:
print 'Could not process the following pages (run with --verbose to see why):'
for f in failures:
print '\t', f
thumbnail = os.path.join(tdir2, 'thumbnail.png')
if not os.access(thumbnail, os.R_OK):
thumbnail = None
toc.append((source,len(pages)))
pages.extend(new_pages)
to_delete.append(tdir)
if output_format == 'lrf': if output_format == 'lrf':
create_lrf(pages, opts.profile, opts, thumbnail=thumbnail) create_lrf(pages, opts.profile, opts, thumbnail=thumbnail)
if output_format == 'epub': if output_format == 'epub':
create_epub(pages, opts.profile, opts, thumbnail=thumbnail) create_epub(pages, opts.profile, opts, thumbnail=thumbnail)
if output_format == 'pdf': if output_format == 'pdf':
create_pdf(pages, opts.profile, opts, thumbnail=thumbnail) create_pdf(pages, opts.profile, opts, thumbnail=thumbnail,toc=toc)
shutil.rmtree(tdir) for tdir in to_delete:
if not getattr(opts, 'no_process', False): shutil.rmtree(tdir)
shutil.rmtree(tdir2)
def all_files(root, patterns='*'):
# Expand patterns from semicolon-separated string to list
patterns = patterns.split('|')
for path, subdirs, files in os.walk(root):
files.sort( )
for name in files:
for pattern in patterns:
if fnmatch.fnmatch(name, pattern):
yield os.path.join(path, name)
break
def main(args=sys.argv, notification=None, output_format='lrf'): def main(args=sys.argv, notification=None, output_format='lrf'):

View File

@ -1720,7 +1720,7 @@ class HTMLConverter(object, LoggingInterface):
self.previous_text = '\n' self.previous_text = '\n'
elif tagname in ['hr', 'tr']: # tr needed for nested tables elif tagname in ['hr', 'tr']: # tr needed for nested tables
self.end_current_block() self.end_current_block()
if tagname == 'hr': if tagname == 'hr' and not tag_css.get('width', '').strip().startswith('0'):
self.current_page.RuledLine(linelength=int(self.current_page.pageStyle.attrs['textwidth'])) self.current_page.RuledLine(linelength=int(self.current_page.pageStyle.attrs['textwidth']))
self.previous_text = '\n' self.previous_text = '\n'
self.process_children(tag, tag_css, tag_pseudo_css) self.process_children(tag, tag_css, tag_pseudo_css)

View File

@ -21,7 +21,10 @@ def string_to_authors(raw):
return authors return authors
def authors_to_string(authors): def authors_to_string(authors):
return ' & '.join([a.replace('&', '&&') for a in authors]) if authors is not None:
return ' & '.join([a.replace('&', '&&') for a in authors if a])
else:
return ''
def author_to_author_sort(author): def author_to_author_sort(author):
tokens = author.split() tokens = author.split()

View File

@ -6,33 +6,28 @@ Support for reading the metadata from a LIT file.
import sys, cStringIO, os import sys, cStringIO, os
from calibre import relpath
from calibre.ebooks.metadata import MetaInformation 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 from calibre.ebooks.lit.reader import LitReader
def get_metadata(stream): def get_metadata(stream):
try: litfile = LitReader(stream)
litfile = LitReader(stream) src = litfile.meta.encode('utf-8')
src = litfile.meta.encode('utf-8') opf = OPF(cStringIO.StringIO(src), os.getcwd())
mi = OPFReader(cStringIO.StringIO(src), dir=os.getcwd()) mi = MetaInformation(opf)
cover_url, cover_item = mi.cover, None covers = []
if cover_url: for item in opf.iterguide():
cover_url = relpath(cover_url, os.getcwd()) if 'cover' not in item.get('type', '').lower():
for item in litfile.manifest.values(): continue
if item.path == cover_url: href = item.get('href', '')
cover_item = item.internal candidates = [href, href.replace('&', '%26')]
if cover_item is not None: for item in litfile.manifest.values():
ext = cover_url.rpartition('.')[-1] if item.path in candidates:
if not ext: covers.append(item.internal)
ext = 'jpg' break
else: covers = [litfile.get_file('/data/' + i) for i in covers]
ext = ext.lower() covers.sort(cmp=lambda x, y:cmp(len(x), len(y)))
cd = litfile.get_file('/data/' + cover_item) mi.cover_data = ('jpg', covers[-1])
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'])
return mi return mi
def main(args=sys.argv): def main(args=sys.argv):

View File

@ -72,6 +72,17 @@ def get_metadata(stream, stream_type='lrf', use_libprs_metadata=False):
name = os.path.basename(getattr(stream, 'name', '')) name = os.path.basename(getattr(stream, 'name', ''))
base = metadata_from_filename(name) base = metadata_from_filename(name)
if base.title == os.path.splitext(name)[0] and base.authors is None:
# Assume that there was no metadata in the file and the user set pattern
# to match meta info from the file name did not match.
# The regex is meant to match the standard format filenames are written
# in: title_-_author_number.extension
base.smart_update(metadata_from_filename(name, re.compile(
r'^(?P<title>\S+?)_-_(?P<author>\S+?)_+\d+')))
if base.title:
base.title = base.title.replace('_', ' ')
if base.authors:
base.authors = [a.replace('_', ' ').strip() for a in base.authors]
if not base.authors: if not base.authors:
base.authors = [_('Unknown')] base.authors = [_('Unknown')]
if not base.title: if not base.title:

View File

@ -0,0 +1,231 @@
'''
Retrieve and modify in-place Mobipocket book metadata.
'''
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net and ' \
'Marshall T. Vandegrift <llasram@gmail.com>'
__docformat__ = 'restructuredtext en'
import sys
import os
from struct import pack, unpack
from cStringIO import StringIO
from calibre.ebooks.metadata import get_parser
from calibre.ebooks.mobi import MobiError
from calibre.ebooks.mobi.reader import get_metadata
from calibre.ebooks.mobi.writer import rescale_image, MAX_THUMB_DIMEN
from calibre.ebooks.mobi.langcodes import iana2mobi
class StreamSlicer(object):
def __init__(self, stream, start=0, stop=None):
self._stream = stream
self.start = start
if stop is None:
stream.seek(0, 2)
stop = stream.tell()
self.stop = stop
self._len = stop - start
def __len__(self):
return self._len
def __getitem__(self, key):
stream = self._stream
base = self.start
if isinstance(key, (int, long)):
stream.seek(base + key)
return stream.read(1)
if isinstance(key, slice):
start, stop, stride = key.indices(self._len)
if stride < 0:
start, stop = stop, start
size = stop - start
if size <= 0:
return ""
stream.seek(base + start)
data = stream.read(size)
if stride != 1:
data = data[::stride]
return data
raise TypeError("stream indices must be integers")
def __setitem__(self, key, value):
stream = self._stream
base = self.start
if isinstance(key, (int, long)):
if len(value) != 1:
raise ValueError("key and value lengths must match")
stream.seek(base + key)
return stream.write(value)
if isinstance(key, slice):
start, stop, stride = key.indices(self._len)
if stride < 0:
start, stop = stop, start
size = stop - start
if stride != 1:
value = value[::stride]
if len(value) != size:
raise ValueError("key and value lengths must match")
stream.seek(base + start)
return stream.write(value)
raise TypeError("stream indices must be integers")
class MetadataUpdater(object):
def __init__(self, stream):
self.stream = stream
data = self.data = StreamSlicer(stream)
type = self.type = data[60:68]
self.nrecs, = unpack('>H', data[76:78])
record0 = self.record0 = self.record(0)
codepage, = unpack('>I', record0[28:32])
self.codec = 'utf-8' if codepage == 65001 else 'cp1252'
image_base, = unpack('>I', record0[108:112])
flags, = unpack('>I', record0[128:132])
have_exth = self.have_exth = (flags & 0x40) != 0
if not have_exth:
return
self.cover_record = self.thumbnail_record = None
exth_off = unpack('>I', record0[20:24])[0] + 16 + record0.start
exth = self.exth = StreamSlicer(stream, exth_off, record0.stop)
nitems, = unpack('>I', exth[8:12])
pos = 12
for i in xrange(nitems):
id, size = unpack('>II', exth[pos:pos + 8])
content = exth[pos + 8: pos + size]
pos += size
if id == 201:
rindex, = self.cover_rindex, = unpack('>I', content)
self.cover_record = self.record(rindex + image_base)
elif id == 202:
rindex, = self.thumbnail_rindex, = unpack('>I', content)
self.thumbnail_record = self.record(rindex + image_base)
def record(self, n):
if n >= self.nrecs:
raise ValueError('non-existent record %r' % n)
offoff = 78 + (8 * n)
start, = unpack('>I', self.data[offoff + 0:offoff + 4])
stop = None
if n < (self.nrecs - 1):
stop, = unpack('>I', self.data[offoff + 8:offoff + 12])
return StreamSlicer(self.stream, start, stop)
def update(self, mi):
recs = []
if mi.authors:
authors = '; '.join(mi.authors)
recs.append((100, authors.encode(self.codec, 'replace')))
if mi.publisher:
recs.append((101, mi.publisher.encode(self.codec, 'replace')))
if mi.comments:
recs.append((103, mi.comments.encode(self.codec, 'replace')))
if mi.isbn:
recs.append((104, mi.isbn.encode(self.codec, 'replace')))
if mi.tags:
subjects = '; '.join(mi.tags)
recs.append((105, subjects.encode(self.codec, 'replace')))
if self.cover_record is not None:
recs.append((201, pack('>I', self.cover_rindex)))
recs.append((203, pack('>I', 0)))
if self.thumbnail_record is not None:
recs.append((202, pack('>I', self.thumbnail_rindex)))
exth = StringIO()
for code, data in recs:
exth.write(pack('>II', code, len(data) + 8))
exth.write(data)
exth = exth.getvalue()
trail = len(exth) % 4
pad = '\0' * (4 - trail) # Always pad w/ at least 1 byte
exth = ['EXTH', pack('>II', len(exth) + 12, len(recs)), exth, pad]
exth = ''.join(exth)
title = (mi.title or _('Unknown')).encode(self.codec, 'replace')
title_off = (self.exth.start - self.record0.start) + len(exth)
title_len = len(title)
trail = len(self.exth) - len(exth) - len(title)
if trail < 0:
raise MobiError("Insufficient space to update metadata")
self.exth[:] = ''.join([exth, title, '\0' * trail])
self.record0[84:92] = pack('>II', title_off, title_len)
self.record0[92:96] = iana2mobi(mi.language)
if mi.cover_data[1]:
data = mi.cover_data[1]
if self.cover_record is not None:
size = len(self.cover_record)
cover = rescale_image(data, size)
cover += '\0' * (size - len(cover))
self.cover_record[:] = cover
if self.thumbnail_record is not None:
size = len(self.thumbnail_record)
thumbnail = rescale_image(data, size, dimen=MAX_THUMB_DIMEN)
thumbnail += '\0' * (size - len(thumbnail))
self.thumbnail_record[:] = thumbnail
return
def set_metadata(stream, mi):
mu = MetadataUpdater(stream)
mu.update(mi)
return
def option_parser():
parser = get_parser('mobi')
parser.remove_option('--category')
parser.add_option('--tags', default=None,
help=_('Set the subject tags'))
parser.add_option('--language', default=None,
help=_('Set the language'))
parser.add_option('--publisher', default=None,
help=_('Set the publisher'))
parser.add_option('--isbn', default=None,
help=_('Set the ISBN'))
return parser
def main(args=sys.argv):
parser = option_parser()
opts, args = parser.parse_args(args)
if len(args) != 2:
parser.print_help()
print >>sys.stderr, 'Usage: %s file.mobi' % args[0]
return 1
fname = args[1]
changed = False
with open(fname, 'r+b') as stream:
mi = get_metadata(stream)
if opts.title:
mi.title = opts.title
changed = True
if opts.authors:
mi.authors = opts.authors.split(',')
changed = True
if opts.comment:
mi.comments = opts.comment
changed = True
if opts.tags is not None:
mi.tags = opts.tags.split(',')
changed = True
if opts.language is not None:
mi.language = opts.language
changed = True
if opts.publisher is not None:
mi.publisher = opts.publisher
changed = True
if opts.isbn is not None:
mi.isbn = opts.isbn
changed = True
if changed:
set_metadata(stream, mi)
print unicode(get_metadata(stream))
if not changed and 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())

View File

@ -435,7 +435,7 @@ class OPF(object):
rating = MetadataField('rating', is_dc=False, formatter=int) 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'): if not hasattr(stream, 'read'):
stream = open(stream, 'rb') stream = open(stream, 'rb')
self.basedir = self.base_dir = basedir self.basedir = self.base_dir = basedir
@ -446,7 +446,8 @@ class OPF(object):
if not self.metadata: if not self.metadata:
raise ValueError('Malformed OPF file: No <metadata> element') raise ValueError('Malformed OPF file: No <metadata> element')
self.metadata = self.metadata[0] self.metadata = self.metadata[0]
self.unquote_urls() if unquote_urls:
self.unquote_urls()
self.manifest = Manifest() self.manifest = Manifest()
m = self.manifest_path(self.root) m = self.manifest_path(self.root)
if m: if m:

View File

@ -0,0 +1,36 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
'''
Read metadata from RAR archives
'''
import os
from cStringIO import StringIO
from calibre.ptempfile import PersistentTemporaryFile
from calibre.libunrar import extract_member, names
def get_metadata(stream):
path = getattr(stream, 'name', False)
if not path:
pt = PersistentTemporaryFile('_rar-meta.rar')
pt.write(stream.read())
pt.close()
path = pt.name
path = os.path.abspath(path)
file_names = list(names(path))
for f in file_names:
stream_type = os.path.splitext(f)[1].lower()
if stream_type:
stream_type = stream_type[1:]
if stream_type in ('lit', 'opf', 'prc', 'mobi', 'fb2', 'epub',
'rb', 'imp', 'pdf', 'lrf'):
data = extract_member(path, match=None, name=f)[1]
stream = StringIO(data)
from calibre.ebooks.metadata.meta import get_metadata
return get_metadata(stream, stream_type)
raise ValueError('No ebook found in RAR archive')

View File

@ -14,18 +14,21 @@ import sys, os, glob, logging
from calibre.ebooks.epub.from_any import any2epub, formats, USAGE from calibre.ebooks.epub.from_any import any2epub, formats, USAGE
from calibre.ebooks.epub import config as common_config from calibre.ebooks.epub import config as common_config
from calibre.ptempfile import TemporaryDirectory 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): 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): def option_parser(usage=USAGE):
usage = usage % ('Mobipocket', formats()) usage = usage % ('Mobipocket', formats())
parser = config().option_parser(usage=usage) parser = config().option_parser(usage=usage)
add_mobi_options(parser)
return parser return parser
def any2mobi(opts, path): def any2mobi(opts, path, notification=None):
ext = os.path.splitext(path)[1] ext = os.path.splitext(path)[1]
if not ext: if not ext:
raise ValueError('Unknown file type: '+path) raise ValueError('Unknown file type: '+path)

View File

@ -0,0 +1,44 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
'''
'''
import sys, os
from calibre.ebooks.lrf.comic.convert_from import do_convert, option_parser, \
ProgressBar, terminal_controller
from calibre.ebooks.mobi.from_any import config, any2mobi
from calibre.ptempfile import PersistentTemporaryFile
def convert(path_to_file, opts, notification=lambda m, p: p):
pt = PersistentTemporaryFile('_comic2mobi.epub')
pt.close()
orig_output = opts.output
opts.output = pt.name
do_convert(path_to_file, opts, notification=notification, output_format='epub')
opts = config('').parse()
if orig_output is None:
orig_output = os.path.splitext(path_to_file)[0]+'.mobi'
opts.output = orig_output
any2mobi(opts, pt.name)
def main(args=sys.argv):
parser = option_parser()
opts, args = parser.parse_args(args)
if len(args) < 2:
parser.print_help()
print '\nYou must specify a file to convert'
return 1
pb = ProgressBar(terminal_controller, _('Rendering comic pages...'),
no_progress_bar=opts.no_progress_bar or getattr(opts, 'no_process', False))
notification = pb.update
source = os.path.abspath(args[1])
convert(source, opts, notification=notification)
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -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())

View File

@ -27,7 +27,7 @@ TABLE_TAGS = set(['table', 'tr', 'td', 'th'])
SPECIAL_TAGS = set(['hr', 'br']) SPECIAL_TAGS = set(['hr', 'br'])
CONTENT_TAGS = set(['img', 'hr', 'br']) CONTENT_TAGS = set(['img', 'hr', 'br'])
PAGE_BREAKS = set(['always', 'odd', 'even']) PAGE_BREAKS = set(['always', 'left', 'right'])
COLLAPSE = re.compile(r'[ \t\r\n\v]+') COLLAPSE = re.compile(r'[ \t\r\n\v]+')

View File

@ -311,7 +311,7 @@ class MobiReader(object):
opf.cover = 'images/%05d.jpg'%(self.book_header.exth.cover_offset+1) opf.cover = 'images/%05d.jpg'%(self.book_header.exth.cover_offset+1)
manifest = [(htmlfile, 'text/x-oeb1-document')] manifest = [(htmlfile, 'text/x-oeb1-document')]
bp = os.path.dirname(htmlfile) 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')) manifest.append((os.path.join(bp, 'images/', i), 'image/jpg'))
opf.create_manifest(manifest) opf.create_manifest(manifest)
@ -451,7 +451,7 @@ class MobiReader(object):
image_index += 1 image_index += 1
try: try:
im = PILImage.open(buf) im = PILImage.open(buf)
except IOError, e: except IOError:
continue continue
path = os.path.join(output_dir, '%05d.jpg'%image_index) path = os.path.join(output_dir, '%05d.jpg'%image_index)
@ -476,31 +476,23 @@ def get_metadata(stream):
if mr.book_header.exth is None: if mr.book_header.exth is None:
mi = MetaInformation(mr.name, [_('Unknown')]) mi = MetaInformation(mr.name, [_('Unknown')])
else: else:
tdir = tempfile.mkdtemp('_mobi_meta', __appname__+'_')
atexit.register(shutil.rmtree, tdir)
#print tdir
mr.extract_images([], tdir)
mi = mr.create_opf('dummy.html') mi = mr.create_opf('dummy.html')
if mi.cover: try:
cover = os.path.join(tdir, mi.cover) if hasattr(mr.book_header.exth, 'cover_offset'):
if not os.access(cover, os.R_OK): cover_index = mr.book_header.first_image_index + mr.book_header.exth.cover_offset
fname = os.path.basename(cover) data = mr.sections[cover_index][0]
match = re.match(r'(\d+)(.+)', fname) else:
if match: data = mr.sections[mr.book_header.first_image_index][0]
num, ext = int(match.group(1), 10), match.group(2) buf = cStringIO.StringIO(data)
while num > 0: im = PILImage.open(buf)
num -= 1 obuf = cStringIO.StringIO()
candidate = os.path.join(os.path.dirname(cover), '%05d%s'%(num, ext)) im.convert('RGBA').save(obuf, format='JPEG')
if os.access(candidate, os.R_OK): mi.cover_data = ('jpg', obuf.getvalue())
cover = candidate except:
break import traceback
if os.access(cover, os.R_OK): traceback.print_exc()
mi.cover_data = ('JPEG', open(os.path.join(tdir, cover), 'rb').read()) return mi
else:
path = os.path.join(tdir, 'images', '00001.jpg')
if os.access(path, os.R_OK):
mi.cover_data = ('JPEG', open(path, 'rb').read())
return mi
def option_parser(): def option_parser():
from calibre.utils.config import OptionParser from calibre.utils.config import OptionParser

View File

@ -34,8 +34,7 @@ from calibre.ebooks.mobi.palmdoc import compress_doc
from calibre.ebooks.mobi.langcodes import iana2mobi from calibre.ebooks.mobi.langcodes import iana2mobi
from calibre.ebooks.mobi.mobiml import MBP_NS, MBP, MobiMLizer from calibre.ebooks.mobi.mobiml import MBP_NS, MBP, MobiMLizer
from calibre.customize.ui import run_plugins_on_postprocess from calibre.customize.ui import run_plugins_on_postprocess
from calibre.utils.config import OptionParser from calibre.utils.config import Config, StringConfig
from optparse import OptionGroup
# TODO: # TODO:
# - Allow override CSS (?) # - Allow override CSS (?)
@ -88,6 +87,49 @@ def decint(value, direction):
bytes[-1] |= 0x80 bytes[-1] |= 0x80
return ''.join(chr(b) for b in reversed(bytes)) return ''.join(chr(b) for b in reversed(bytes))
def rescale_image(data, maxsizeb, dimen=None):
image = Image.open(StringIO(data))
format = image.format
changed = False
if image.format not in ('JPEG', 'GIF'):
width, height = image.size
area = width * height
if area <= 40000:
format = 'GIF'
else:
image = image.convert('RGBA')
format = 'JPEG'
changed = True
if dimen is not None:
image.thumbnail(dimen, Image.ANTIALIAS)
changed = True
if changed:
data = StringIO()
image.save(data, format)
data = data.getvalue()
if len(data) <= maxsizeb:
return data
image = image.convert('RGBA')
for quality in xrange(95, -1, -1):
data = StringIO()
image.save(data, 'JPEG', quality=quality)
data = data.getvalue()
if len(data) <= maxsizeb:
return data
width, height = image.size
for scale in xrange(99, 0, -1):
scale = scale / 100.
data = StringIO()
scaled = image.copy()
size = (int(width * scale), (height * scale))
scaled.thumbnail(size, Image.ANTIALIAS)
scaled.save(data, 'JPEG', quality=0)
data = data.getvalue()
if len(data) <= maxsizeb:
return data
# Well, we tried?
return data
class Serializer(object): class Serializer(object):
NSRMAP = {'': None, XML_NS: 'xml', XHTML_NS: '', MBP_NS: 'mbp'} NSRMAP = {'': None, XML_NS: 'xml', XHTML_NS: '', MBP_NS: 'mbp'}
@ -356,50 +398,7 @@ class MobiWriter(object):
offset += RECORD_SIZE offset += RECORD_SIZE
data, overlap = self._read_text_record(text) data, overlap = self._read_text_record(text)
self._text_nrecords = nrecords self._text_nrecords = nrecords
def _rescale_image(self, data, maxsizeb, dimen=None):
image = Image.open(StringIO(data))
format = image.format
changed = False
if image.format not in ('JPEG', 'GIF'):
width, height = image.size
area = width * height
if area <= 40000:
format = 'GIF'
else:
image = image.convert('RGBA')
format = 'JPEG'
changed = True
if dimen is not None:
image.thumbnail(dimen, Image.ANTIALIAS)
changed = True
if changed:
data = StringIO()
image.save(data, format)
data = data.getvalue()
if len(data) <= maxsizeb:
return data
image = image.convert('RGBA')
for quality in xrange(95, -1, -1):
data = StringIO()
image.save(data, 'JPEG', quality=quality)
data = data.getvalue()
if len(data) <= maxsizeb:
return data
width, height = image.size
for scale in xrange(99, 0, -1):
scale = scale / 100.
data = StringIO()
scaled = image.copy()
size = (int(width * scale), (height * scale))
scaled.thumbnail(size, Image.ANTIALIAS)
scaled.save(data, 'JPEG', quality=0)
data = data.getvalue()
if len(data) <= maxsizeb:
return data
# Well, we tried?
return data
def _generate_images(self): def _generate_images(self):
self._oeb.logger.info('Serializing images...') self._oeb.logger.info('Serializing images...')
images = [(index, href) for href, index in self._images.items()] images = [(index, href) for href, index in self._images.items()]
@ -408,7 +407,7 @@ class MobiWriter(object):
coverid = metadata.cover[0] if metadata.cover else None coverid = metadata.cover[0] if metadata.cover else None
for _, href in images: for _, href in images:
item = self._oeb.manifest.hrefs[href] item = self._oeb.manifest.hrefs[href]
data = self._rescale_image(item.data, self._imagemax) data = rescale_image(item.data, self._imagemax)
self._records.append(data) self._records.append(data)
def _generate_record0(self): def _generate_record0(self):
@ -453,6 +452,13 @@ class MobiWriter(object):
code = EXTH_CODES[term] code = EXTH_CODES[term]
for item in oeb.metadata[term]: for item in oeb.metadata[term]:
data = self.COLLAPSE_RE.sub(' ', unicode(item)) data = self.COLLAPSE_RE.sub(' ', unicode(item))
if term == 'identifier':
if data.lower().startswith('urn:isbn:'):
data = data[9:]
elif item.get('scheme', '').lower() == 'isbn':
pass
else:
continue
data = data.encode('utf-8') data = data.encode('utf-8')
exth.write(pack('>II', code, len(data) + 8)) exth.write(pack('>II', code, len(data) + 8))
exth.write(data) exth.write(data)
@ -469,12 +475,12 @@ class MobiWriter(object):
nrecs += 3 nrecs += 3
exth = exth.getvalue() exth = exth.getvalue()
trail = len(exth) % 4 trail = len(exth) % 4
pad = '' if not trail else '\0' * (4 - trail) pad = '\0' * (4 - trail) # Always pad w/ at least 1 byte
exth = ['EXTH', pack('>II', len(exth) + 12, nrecs), exth, pad] exth = ['EXTH', pack('>II', len(exth) + 12, nrecs), exth, pad]
return ''.join(exth) return ''.join(exth)
def _add_thumbnail(self, item): def _add_thumbnail(self, item):
data = self._rescale_image(item.data, MAX_THUMB_SIZE, MAX_THUMB_DIMEN) data = rescale_image(item.data, MAX_THUMB_SIZE, MAX_THUMB_DIMEN)
manifest = self._oeb.manifest manifest = self._oeb.manifest
id, href = manifest.generate('thumbnail', 'thumbnail.jpeg') id, href = manifest.generate('thumbnail', 'thumbnail.jpeg')
manifest.add(id, href, 'image/jpeg', data=data) manifest.add(id, href, 'image/jpeg', data=data)
@ -502,41 +508,45 @@ class MobiWriter(object):
self._write(record) self._write(record)
def add_mobi_options(parser): def config(defaults=None):
profiles = Context.PROFILES.keys() desc = _('Options to control the conversion to MOBI')
profiles.sort() _profiles = list(sorted(Context.PROFILES.keys()))
profiles = ', '.join(profiles) if defaults is None:
group = OptionGroup(parser, _('Mobipocket'), c = Config('mobi', desc)
_('Mobipocket-specific options.')) else:
group.add_option( c = StringConfig(defaults, desc)
'-c', '--compress', default=False, action='store_true',
help=_('Compress file text using PalmDOC compression. ' 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.')) 'Results in smaller files, but takes a long time to run.'))
group.add_option( mobi('rescale_images', ['--rescale-images'], default=False,
'-r', '--rescale-images', default=False, action='store_true',
help=_('Modify images to meet Palm device size limitations.')) help=_('Modify images to meet Palm device size limitations.'))
parser.add_option_group(group) mobi('toc_title', ['--toc-title'], default=None,
group = OptionGroup(parser, _('Profiles'), _('Device renderer profiles. ' help=_('Title for any generated in-line table of contents.'))
'Affects conversion of default font sizes and rasterization ' profiles = c.add_group('profiles', _('Device renderer profiles. '
'resolution. Valid profiles are: %s.') % profiles) 'Affects conversion of font sizes, image rescaling and rasterization '
group.add_option( 'of tables. Valid profiles are: %s.') % ', '.join(_profiles))
'--source-profile', default='Browser', metavar='PROFILE', profiles('source_profile', ['--source-profile'],
help=_("Source renderer profile. Default is 'Browser'.")) default='Browser', choices=_profiles,
group.add_option( help=_("Source renderer profile. Default is %default."))
'--dest-profile', default='CybookG3', metavar='PROFILE', profiles('dest_profile', ['--dest-profile'],
help=_("Destination renderer profile. Default is 'CybookG3'.")) default='CybookG3', choices=_profiles,
parser.add_option_group(group) help=_("Destination renderer profile. Default is %default."))
return c.add_opt('encoding', ['--encoding'], default=None,
help=_('Character encoding for HTML files. Default is to auto detect.'))
return c
def option_parser(): def option_parser():
parser = OptionParser(usage=_('%prog [options] OPFFILE')) c = config()
parser = c.option_parser(usage='%prog '+_('[options]')+' file.opf')
parser.add_option( parser.add_option(
'-o', '--output', default=None, '-o', '--output', default=None,
help=_('Output file. Default is derived from input filename.')) help=_('Output file. Default is derived from input filename.'))
parser.add_option( parser.add_option(
'-v', '--verbose', default=0, action='count', '-v', '--verbose', default=0, action='count',
help=_('Useful for debugging.')) help=_('Useful for debugging.'))
add_mobi_options(parser)
return parser return parser
def oeb2mobi(opts, inpath): def oeb2mobi(opts, inpath):
@ -557,8 +567,8 @@ def oeb2mobi(opts, inpath):
compression = PALMDOC if opts.compress else UNCOMPRESSED compression = PALMDOC if opts.compress else UNCOMPRESSED
imagemax = PALM_MAX_IMAGE_SIZE if opts.rescale_images else None imagemax = PALM_MAX_IMAGE_SIZE if opts.rescale_images else None
context = Context(source, dest) context = Context(source, dest)
oeb = OEBBook(inpath, logger=logger) oeb = OEBBook(inpath, logger=logger, encoding=opts.encoding)
tocadder = HTMLTOCAdder() tocadder = HTMLTOCAdder(title=opts.toc_title)
tocadder.transform(oeb, context) tocadder.transform(oeb, context)
mangler = CaseMangler() mangler = CaseMangler()
mangler.transform(oeb, context) mangler.transform(oeb, context)

View File

@ -20,8 +20,9 @@ import copy
from lxml import etree from lxml import etree
from lxml import html from lxml import html
from calibre import LoggingInterface from calibre import LoggingInterface
from calibre.translations.dynamic import translate
from calibre.startup import get_lang
XML_PARSER = etree.XMLParser(recover=True)
XML_NS = 'http://www.w3.org/XML/1998/namespace' XML_NS = 'http://www.w3.org/XML/1998/namespace'
XHTML_NS = 'http://www.w3.org/1999/xhtml' XHTML_NS = 'http://www.w3.org/1999/xhtml'
OPF1_NS = 'http://openebook.org/namespaces/oeb-package/1.0/' OPF1_NS = 'http://openebook.org/namespaces/oeb-package/1.0/'
@ -29,6 +30,7 @@ OPF2_NS = 'http://www.idpf.org/2007/opf'
DC09_NS = 'http://purl.org/metadata/dublin_core' DC09_NS = 'http://purl.org/metadata/dublin_core'
DC10_NS = 'http://purl.org/dc/elements/1.0/' DC10_NS = 'http://purl.org/dc/elements/1.0/'
DC11_NS = 'http://purl.org/dc/elements/1.1/' DC11_NS = 'http://purl.org/dc/elements/1.1/'
DC_NSES = set([DC09_NS, DC10_NS, DC11_NS])
XSI_NS = 'http://www.w3.org/2001/XMLSchema-instance' XSI_NS = 'http://www.w3.org/2001/XMLSchema-instance'
DCTERMS_NS = 'http://purl.org/dc/terms/' DCTERMS_NS = 'http://purl.org/dc/terms/'
NCX_NS = 'http://www.daisy.org/z3986/2005/ncx/' NCX_NS = 'http://www.daisy.org/z3986/2005/ncx/'
@ -89,6 +91,9 @@ def prefixname(name, nsrmap):
return barename(name) return barename(name)
return ':'.join((prefix, barename(name))) return ':'.join((prefix, barename(name)))
def XPath(expr):
return etree.XPath(expr, namespaces=XPNSMAP)
def xpath(elem, expr): def xpath(elem, expr):
return elem.xpath(expr, namespaces=XPNSMAP) return elem.xpath(expr, namespaces=XPNSMAP)
@ -134,8 +139,7 @@ class Logger(LoggingInterface, object):
class AbstractContainer(object): class AbstractContainer(object):
def read_xml(self, path): def read_xml(self, path):
return etree.fromstring( return etree.fromstring(
self.read(path), parser=XML_PARSER, self.read(path), base_url=os.path.dirname(path))
base_url=os.path.dirname(path))
class DirContainer(AbstractContainer): class DirContainer(AbstractContainer):
def __init__(self, rootdir): def __init__(self, rootdir):
@ -190,15 +194,19 @@ class Metadata(object):
if term == OPF('meta') and not value: if term == OPF('meta') and not value:
term = self.fq_attrib.pop('name') term = self.fq_attrib.pop('name')
value = self.fq_attrib.pop('content') value = self.fq_attrib.pop('content')
elif term in Metadata.TERMS and not namespace(term): elif barename(term).lower() in Metadata.TERMS and \
term = DC(term) (not namespace(term) or namespace(term) in DC_NSES):
# Anything looking like Dublin Core is coerced
term = DC(barename(term).lower())
elif namespace(term) == OPF2_NS:
term = barename(term)
self.term = term self.term = term
self.value = value self.value = value
self.attrib = attrib = {} self.attrib = attrib = {}
for fq_attr in fq_attrib: for fq_attr in fq_attrib:
if fq_attr in Metadata.ATTRS: if fq_attr in Metadata.ATTRS:
attr = fq_attr attr = fq_attr
fq_attr = OPF2(fq_attr) fq_attr = OPF(fq_attr)
fq_attrib[fq_attr] = fq_attrib.pop(attr) fq_attrib[fq_attr] = fq_attrib.pop(attr)
else: else:
attr = barename(fq_attr) attr = barename(fq_attr)
@ -212,7 +220,16 @@ class Metadata(object):
raise AttributeError( raise AttributeError(
'%r object has no attribute %r' \ '%r object has no attribute %r' \
% (self.__class__.__name__, name)) % (self.__class__.__name__, name))
def __getitem__(self, key):
return self.attrib[key]
def __contains__(self, key):
return key in self.attrib
def get(self, key, default=None):
return self.attrib.get(key, default)
def __repr__(self): def __repr__(self):
return 'Item(term=%r, value=%r, attrib=%r)' \ return 'Item(term=%r, value=%r, attrib=%r)' \
% (barename(self.term), self.value, self.attrib) % (barename(self.term), self.value, self.attrib)
@ -291,15 +308,19 @@ class Metadata(object):
class Manifest(object): class Manifest(object):
class Item(object): class Item(object):
NUM_RE = re.compile('^(.*)([0-9][0-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): fallback=None, loader=str, data=None):
self.oeb = oeb
self.id = id self.id = id
self.href = self.path = urlnormalize(href) self.href = self.path = urlnormalize(href)
self.media_type = media_type self.media_type = media_type
self.fallback = fallback self.fallback = fallback
self.spine_position = None self.spine_position = None
self.linear = True self.linear = True
if loader is None and data is None:
loader = oeb.container.read
self._loader = loader self._loader = loader
self._data = data self._data = data
@ -308,16 +329,27 @@ class Manifest(object):
% (self.id, self.href, self.media_type) % (self.id, self.href, self.media_type)
def _force_xhtml(self, data): def _force_xhtml(self, data):
if self.oeb.encoding is not None:
data = data.decode(self.oeb.encoding, 'replace')
# Handle broken XHTML w/ SVG (ugh)
if 'svg:' in data and SVG_NS not in data:
data = data.replace(
'<html', '<html xmlns:svg="%s"' % SVG_NS, 1)
if 'xlink:' in data and XLINK_NS not in data:
data = data.replace(
'<html', '<html xmlns:xlink="%s"' % XLINK_NS, 1)
try: try:
data = etree.fromstring(data, parser=XML_PARSER) data = etree.fromstring(data)
except etree.XMLSyntaxError: except etree.XMLSyntaxError:
data = html.fromstring(data, parser=XML_PARSER) data = html.fromstring(data)
data = etree.tostring(data, encoding=unicode) data = etree.tostring(data, encoding=unicode)
data = etree.fromstring(data, parser=XML_PARSER) data = etree.fromstring(data)
if namespace(data.tag) != XHTML_NS: if namespace(data.tag) != XHTML_NS:
data.attrib['xmlns'] = XHTML_NS data.attrib['xmlns'] = XHTML_NS
data = etree.tostring(data) data = etree.tostring(data, encoding=unicode)
data = etree.fromstring(data, parser=XML_PARSER) data = etree.fromstring(data)
for meta in self.META_XP(data):
meta.getparent().remove(meta)
return data return data
def data(): def data():
@ -328,7 +360,7 @@ class Manifest(object):
if self.media_type in OEB_DOCS: if self.media_type in OEB_DOCS:
data = self._force_xhtml(data) data = self._force_xhtml(data)
elif self.media_type[-4:] in ('+xml', '/xml'): elif self.media_type[-4:] in ('+xml', '/xml'):
data = etree.fromstring(data, parser=XML_PARSER) data = etree.fromstring(data)
self._data = data self._data = data
return data return data
def fset(self, value): def fset(self, value):
@ -394,9 +426,8 @@ class Manifest(object):
self.hrefs = {} self.hrefs = {}
def add(self, id, href, media_type, fallback=None, loader=None, data=None): def add(self, id, href, media_type, fallback=None, loader=None, data=None):
loader = loader or self.oeb.container.read
item = self.Item( 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.ids[item.id] = item
self.hrefs[item.href] = item self.hrefs[item.href] = item
return item return item
@ -534,27 +565,36 @@ class Spine(object):
class Guide(object): class Guide(object):
class Reference(object): class Reference(object):
_TYPES_TITLES = [('cover', 'Cover'), ('title-page', 'Title Page'), _TYPES_TITLES = [('cover', __('Cover')),
('toc', 'Table of Contents'), ('index', 'Index'), ('title-page', __('Title Page')),
('glossary', 'Glossary'), ('acknowledgements', 'Acknowledgements'), ('toc', __('Table of Contents')),
('bibliography', 'Bibliography'), ('colophon', 'Colophon'), ('index', __('Index')),
('copyright-page', 'Copyright'), ('dedication', 'Dedication'), ('glossary', __('Glossary')),
('epigraph', 'Epigraph'), ('foreword', 'Foreword'), ('acknowledgements', __('Acknowledgements')),
('loi', 'List of Illustrations'), ('lot', 'List of Tables'), ('bibliography', __('Bibliography')),
('notes', 'Notes'), ('preface', 'Preface'), ('colophon', __('Colophon')),
('text', 'Main Text')] ('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) TYPES = set(t for t, _ in _TYPES_TITLES)
TITLES = dict(_TYPES_TITLES) TITLES = dict(_TYPES_TITLES)
ORDER = dict((t, i) for (t, _), i in izip(_TYPES_TITLES, count(0))) 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: if type.lower() in self.TYPES:
type = type.lower() type = type.lower()
elif type not in self.TYPES and \ elif type not in self.TYPES and \
not type.startswith('other.'): not type.startswith('other.'):
type = 'other.' + type type = 'other.' + type
if not title: if not title and type in self.TITLES:
title = self.TITLES.get(type, None) title = oeb.translate(self.TITLES[type])
self.type = type self.type = type
self.title = title self.title = title
self.href = urlnormalize(href) self.href = urlnormalize(href)
@ -573,13 +613,21 @@ class Guide(object):
if not isinstance(other, Guide.Reference): if not isinstance(other, Guide.Reference):
return NotImplemented return NotImplemented
return cmp(self._order, other._order) 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): def __init__(self, oeb):
self.oeb = oeb self.oeb = oeb
self.refs = {} self.refs = {}
def add(self, type, title, href): def add(self, type, title, href):
ref = self.Reference(type, title, href) ref = self.Reference(self.oeb, type, title, href)
self.refs[type] = ref self.refs[type] = ref
return ref return ref
@ -589,9 +637,7 @@ class Guide(object):
__iter__ = iterkeys __iter__ = iterkeys
def values(self): def values(self):
values = list(self.refs.values()) return sorted(self.refs.values())
values.sort()
return values
def items(self): def items(self):
for type, ref in self.refs.items(): for type, ref in self.refs.items():
@ -675,31 +721,33 @@ class TOC(object):
node.to_opf1(tour) node.to_opf1(tour)
return tour return tour
def to_ncx(self, parent, playorder=None, depth=1): def to_ncx(self, parent, order=None, depth=1):
if not playorder: playorder = [0] if not order: order = [0]
for node in self.nodes: 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, point = etree.SubElement(parent,
NCX('navPoint'), attrib={'playOrder': str(playorder[0])}) NCX('navPoint'), id=id, playOrder=playOrder)
if self.klass: if self.klass:
point.attrib['class'] = node.klass point.attrib['class'] = node.klass
if self.id:
point.attrib['id'] = node.id
label = etree.SubElement(point, NCX('navLabel')) label = etree.SubElement(point, NCX('navLabel'))
etree.SubElement(label, NCX('text')).text = node.title etree.SubElement(label, NCX('text')).text = node.title
href = node.href if depth > 1 else urldefrag(node.href)[0] href = node.href if depth > 1 else urldefrag(node.href)[0]
child = etree.SubElement(point, child = etree.SubElement(point,
NCX('content'), attrib={'src': href}) NCX('content'), attrib={'src': href})
node.to_ncx(point, playorder, depth+1) node.to_ncx(point, order, depth+1)
return parent return parent
class OEBBook(object): 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: if opfpath and not container:
container = DirContainer(os.path.dirname(opfpath)) container = DirContainer(os.path.dirname(opfpath))
opfpath = os.path.basename(opfpath) opfpath = os.path.basename(opfpath)
self.container = container self.container = container
self.encoding = encoding
self.logger = logger self.logger = logger
if opfpath or container: if opfpath or container:
opf = self._read_opf(opfpath) opf = self._read_opf(opfpath)
@ -745,7 +793,7 @@ class OEBBook(object):
for tag in ('manifest', 'spine', 'tours', 'guide'): for tag in ('manifest', 'spine', 'tours', 'guide'):
for element in opf.xpath(tag): for element in opf.xpath(tag):
nroot.append(element) nroot.append(element)
return etree.fromstring(etree.tostring(nroot), parser=XML_PARSER) return etree.fromstring(etree.tostring(nroot))
def _read_opf(self, opfpath): def _read_opf(self, opfpath):
opf = self.container.read_xml(opfpath) opf = self.container.read_xml(opfpath)
@ -786,13 +834,13 @@ class OEBBook(object):
break break
if not metadata.language: if not metadata.language:
self.logger.warn(u'Language not specified.') self.logger.warn(u'Language not specified.')
metadata.add('language', 'en') metadata.add('language', get_lang())
if not metadata.creator: if not metadata.creator:
self.logger.warn(u'Creator not specified.') self.logger.warn(u'Creator not specified.')
metadata.add('creator', 'Unknown') metadata.add('creator', _('Unknown'))
if not metadata.title: if not metadata.title:
self.logger.warn(u'Title not specified.') self.logger.warn(u'Title not specified.')
metadata.add('title', 'Unknown') metadata.add('title', _('Unknown'))
def _manifest_from_opf(self, opf): def _manifest_from_opf(self, opf):
self.manifest = manifest = Manifest(self) self.manifest = manifest = Manifest(self)
@ -829,6 +877,8 @@ class OEBBook(object):
extras.sort() extras.sort()
for item in extras: for item in extras:
spine.add(item, False) spine.add(item, False)
if len(spine) == 0:
raise OEBError("Spine is empty")
def _guide_from_opf(self, opf): def _guide_from_opf(self, opf):
self.guide = guide = Guide(self) self.guide = guide = Guide(self)
@ -858,8 +908,11 @@ class OEBBook(object):
if len(result) != 1: if len(result) != 1:
return False return False
id = result[0] id = result[0]
ncx = self.manifest[id].data if id not in self.manifest.ids:
self.manifest.remove(id) return False
item = self.manifest.ids[id]
ncx = item.data
self.manifest.remove(item)
title = xpath(ncx, 'ncx:docTitle/ncx:text/text()')[0] title = xpath(ncx, 'ncx:docTitle/ncx:text/text()')[0]
self.toc = toc = TOC(title) self.toc = toc = TOC(title)
navmaps = xpath(ncx, 'ncx:navMap') navmaps = xpath(ncx, 'ncx:navMap')
@ -973,6 +1026,11 @@ class OEBBook(object):
self._toc_from_opf(opf) self._toc_from_opf(opf)
self._ensure_cover_image() 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): def to_opf1(self):
package = etree.Element('package', package = etree.Element('package',
attrib={'unique-identifier': self.uid.id}) attrib={'unique-identifier': self.uid.id})
@ -986,22 +1044,11 @@ class OEBBook(object):
guide = self.guide.to_opf1(package) guide = self.guide.to_opf1(package)
return {OPF_MIME: ('content.opf', 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): def _to_ncx(self):
ncx = etree.Element(NCX('ncx'), attrib={'version': '2005-1'}, lang = unicode(self.metadata.language[0])
nsmap={None: NCX_NS}) ncx = etree.Element(NCX('ncx'),
attrib={'version': '2005-1', XML('lang'): lang},
nsmap={None: NCX_NS})
head = etree.SubElement(ncx, NCX('head')) head = etree.SubElement(ncx, NCX('head'))
etree.SubElement(head, NCX('meta'), etree.SubElement(head, NCX('meta'),
attrib={'name': 'dtb:uid', 'content': unicode(self.uid)}) attrib={'name': 'dtb:uid', 'content': unicode(self.uid)})
@ -1024,7 +1071,7 @@ class OEBBook(object):
nsmap={None: OPF2_NS}) nsmap={None: OPF2_NS})
metadata = self.metadata.to_opf2(package) metadata = self.metadata.to_opf2(package)
manifest = self.manifest.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'), etree.SubElement(manifest, OPF('item'),
attrib={'id': id, 'href': href, 'media-type': NCX_MIME}) attrib={'id': id, 'href': href, 'media-type': NCX_MIME})
spine = self.spine.to_opf2(package) spine = self.spine.to_opf2(package)

View File

@ -110,7 +110,8 @@ class Stylizer(object):
def __init__(self, tree, path, oeb, profile=PROFILES['PRS505']): def __init__(self, tree, path, oeb, profile=PROFILES['PRS505']):
self.profile = profile self.profile = profile
base = os.path.dirname(path) self.logger = oeb.logger
item = oeb.manifest.hrefs[path]
basename = os.path.basename(path) basename = os.path.basename(path)
cssname = os.path.splitext(basename)[0] + '.css' cssname = os.path.splitext(basename)[0] + '.css'
stylesheets = [HTML_CSS_STYLESHEET] stylesheets = [HTML_CSS_STYLESHEET]
@ -128,8 +129,12 @@ class Stylizer(object):
and elem.get('rel', 'stylesheet') == 'stylesheet' \ and elem.get('rel', 'stylesheet') == 'stylesheet' \
and elem.get('type', CSS_MIME) in OEB_STYLES: and elem.get('type', CSS_MIME) in OEB_STYLES:
href = urlnormalize(elem.attrib['href']) href = urlnormalize(elem.attrib['href'])
path = os.path.join(base, href) path = item.abshref(href)
path = os.path.normpath(path).replace('\\', '/') if path not in oeb.manifest.hrefs:
self.logger.warn(
'Stylesheet %r referenced by file %r not in manifest' %
(path, item.href))
continue
if path in self.STYLESHEETS: if path in self.STYLESHEETS:
stylesheet = self.STYLESHEETS[path] stylesheet = self.STYLESHEETS[path]
else: else:
@ -223,8 +228,11 @@ class Stylizer(object):
for key in composition: for key in composition:
style[key] = 'inherit' style[key] = 'inherit'
else: else:
primitives = [v.cssText for v in cssvalue] try:
primitites.reverse() primitives = [v.cssText for v in cssvalue]
except TypeError:
primitives = [cssvalue.cssText]
primitives.reverse()
value = primitives.pop() value = primitives.pop()
for key in composition: for key in composition:
if cssproperties.cssvalues[key](value): if cssproperties.cssvalues[key](value):
@ -274,7 +282,9 @@ class Style(object):
def _apply_style_attr(self): def _apply_style_attr(self):
attrib = self._element.attrib attrib = self._element.attrib
if 'style' in attrib: if 'style' in attrib:
style = CSSStyleDeclaration(attrib['style']) css = attrib['style'].split(';')
css = filter(None, map(lambda x: x.strip(), css))
style = CSSStyleDeclaration('; '.join(css))
self._style.update(self._stylizer.flatten_style(style)) self._style.update(self._stylizer.flatten_style(style))
def _has_parent(self): def _has_parent(self):

View File

@ -199,7 +199,8 @@ class CSSFlattener(object):
items = cssdict.items() items = cssdict.items()
items.sort() items.sort()
css = u';\n'.join(u'%s: %s' % (key, val) for key, val in items) css = u';\n'.join(u'%s: %s' % (key, val) for key, val in items)
klass = STRIPNUM.sub('', node.get('class', 'calibre').split()[0]) classes = node.get('class', None) or 'calibre'
klass = STRIPNUM.sub('', classes.split()[0])
if css in styles: if css in styles:
match = styles[css] match = styles[css]
else: else:

View File

@ -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 XHTML_MIME, CSS_MIME
from calibre.ebooks.oeb.base import element from calibre.ebooks.oeb.base import element
__all__ = ['HTMLTOCAdder']
DEFAULT_TITLE = __('Table of Contents')
STYLE_CSS = { STYLE_CSS = {
'nested': """ 'nested': """
.calibre_toc_header { .calibre_toc_header {
@ -44,13 +48,15 @@ body > .calibre_toc_block {
} }
class HTMLTOCAdder(object): class HTMLTOCAdder(object):
def __init__(self, style='nested'): def __init__(self, title=None, style='nested'):
self.title = title
self.style = style self.style = style
def transform(self, oeb, context): def transform(self, oeb, context):
if 'toc' in oeb.guide: if 'toc' in oeb.guide:
return return
oeb.logger.info('Generating in-line TOC...') oeb.logger.info('Generating in-line TOC...')
title = self.title or oeb.translate(DEFAULT_TITLE)
style = self.style style = self.style
if style not in STYLE_CSS: if style not in STYLE_CSS:
oeb.logger.error('Unknown TOC style %r' % style) oeb.logger.error('Unknown TOC style %r' % style)
@ -61,15 +67,15 @@ class HTMLTOCAdder(object):
contents = element(None, XHTML('html'), nsmap={None: XHTML_NS}, contents = element(None, XHTML('html'), nsmap={None: XHTML_NS},
attrib={XML('lang'): language}) attrib={XML('lang'): language})
head = element(contents, XHTML('head')) head = element(contents, XHTML('head'))
title = element(head, XHTML('title')) htitle = element(head, XHTML('title'))
title.text = 'Table of Contents' htitle.text = title
element(head, XHTML('link'), rel='stylesheet', type=CSS_MIME, element(head, XHTML('link'), rel='stylesheet', type=CSS_MIME,
href=css_href) href=css_href)
body = element(contents, XHTML('body'), body = element(contents, XHTML('body'),
attrib={'class': 'calibre_toc'}) attrib={'class': 'calibre_toc'})
h1 = element(body, XHTML('h1'), h1 = element(body, XHTML('h1'),
attrib={'class': 'calibre_toc_header'}) attrib={'class': 'calibre_toc_header'})
h1.text = 'Table of Contents' h1.text = title
self.add_toc_level(body, oeb.toc) self.add_toc_level(body, oeb.toc)
id, href = oeb.manifest.generate('contents', 'contents.xhtml') id, href = oeb.manifest.generate('contents', 'contents.xhtml')
item = oeb.manifest.add(id, href, XHTML_MIME, data=contents) item = oeb.manifest.add(id, href, XHTML_MIME, data=contents)

View File

@ -6,7 +6,7 @@ from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSiz
QByteArray, QLocale, QUrl, QTranslator, QCoreApplication, \ QByteArray, QLocale, QUrl, QTranslator, QCoreApplication, \
QModelIndex QModelIndex
from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \ from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
QIcon, QTableView, QDialogButtonBox, QApplication QIcon, QTableView, QDialogButtonBox, QApplication, QDialog
ORG_NAME = 'KovidsBrain' ORG_NAME = 'KovidsBrain'
APP_UID = 'libprs500' APP_UID = 'libprs500'
@ -60,6 +60,8 @@ def _config():
help=_('Delete books from library after uploading to device')) help=_('Delete books from library after uploading to device'))
c.add_opt('separate_cover_flow', default=False, c.add_opt('separate_cover_flow', default=False,
help=_('Show the cover flow in a separate window instead of in the main calibre window')) help=_('Show the cover flow in a separate window instead of in the main calibre window'))
c.add_opt('disable_tray_notification', default=False,
help=_('Disable notifications from the system tray icon'))
return ConfigProxy(c) return ConfigProxy(c)
config = _config() config = _config()
@ -392,7 +394,20 @@ def pixmap_to_data(pixmap, format='JPEG'):
pixmap.save(buf, format) pixmap.save(buf, format)
return str(ba.data()) return str(ba.data())
class ResizableDialog(QDialog):
def __init__(self, *args, **kwargs):
QDialog.__init__(self, *args)
self.setupUi(self)
nh, nw = min_available_height()-25, available_width()-10
if nh < 0:
nh = 800
if nw < 0:
nw = 600
nh = min(self.height(), nh)
nw = min(self.width(), nw)
self.resize(nw, nh)
try: try:
from calibre.utils.single_qt_application import SingleApplication from calibre.utils.single_qt_application import SingleApplication
except: except:

View File

@ -186,6 +186,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
single_format = config['save_to_disk_single_format'] single_format = config['save_to_disk_single_format']
self.single_format.setCurrentIndex(BOOK_EXTENSIONS.index(single_format)) self.single_format.setCurrentIndex(BOOK_EXTENSIONS.index(single_format))
self.cover_browse.setValue(config['cover_flow_queue_length']) self.cover_browse.setValue(config['cover_flow_queue_length'])
self.systray_notifications.setChecked(not config['disable_tray_notification'])
from calibre.translations.compiled import translations from calibre.translations.compiled import translations
from calibre.translations import language_codes from calibre.translations import language_codes
from calibre.startup import get_lang from calibre.startup import get_lang
@ -394,6 +395,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
config['toolbar_icon_size'] = self.ICON_SIZES[self.toolbar_button_size.currentIndex()] config['toolbar_icon_size'] = self.ICON_SIZES[self.toolbar_button_size.currentIndex()]
config['show_text_in_toolbar'] = bool(self.show_toolbar_text.isChecked()) config['show_text_in_toolbar'] = bool(self.show_toolbar_text.isChecked())
config['separate_cover_flow'] = bool(self.separate_cover_flow.isChecked()) config['separate_cover_flow'] = bool(self.separate_cover_flow.isChecked())
config['disable_tray_notification'] = not self.systray_notifications.isChecked()
pattern = self.filename_pattern.commit() pattern = self.filename_pattern.commit()
prefs['filename_pattern'] = pattern prefs['filename_pattern'] = pattern
p = {0:'normal', 1:'high', 2:'low'}[self.priority.currentIndex()] p = {0:'normal', 1:'high', 2:'low'}[self.priority.currentIndex()]

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>800</width> <width>755</width>
<height>570</height> <height>557</height>
</rect> </rect>
</property> </property>
<property name="windowTitle" > <property name="windowTitle" >
@ -328,8 +328,8 @@
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="page" > <widget class="QWidget" name="page" >
<layout class="QGridLayout" name="gridLayout_3" > <layout class="QVBoxLayout" name="verticalLayout_4" >
<item row="0" column="0" > <item>
<widget class="QCheckBox" name="roman_numerals" > <widget class="QCheckBox" name="roman_numerals" >
<property name="text" > <property name="text" >
<string>Use &amp;Roman numerals for series number</string> <string>Use &amp;Roman numerals for series number</string>
@ -339,12 +339,47 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" > <item>
<widget class="QCheckBox" name="systray_icon" >
<property name="text" >
<string>Enable system &amp;tray icon (needs restart)</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="systray_notifications" >
<property name="text" >
<string>Show &amp;notifications in system tray</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="separate_cover_flow" >
<property name="text" >
<string>Show cover &amp;browser in a separate window (needs restart)</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="sync_news" >
<property name="text" >
<string>Automatically send downloaded &amp;news to ebook reader</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="delete_news" >
<property name="text" >
<string>&amp;Delete news from library when it is sent to reader</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" > <layout class="QHBoxLayout" name="horizontalLayout" >
<item> <item>
<widget class="QLabel" name="label_6" > <widget class="QLabel" name="label_6" >
<property name="text" > <property name="text" >
<string>&amp;Number of covers to show in browse mode (after restart):</string> <string>&amp;Number of covers to show in browse mode (needs restart):</string>
</property> </property>
<property name="buddy" > <property name="buddy" >
<cstring>cover_browse</cstring> <cstring>cover_browse</cstring>
@ -356,7 +391,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="7" column="0" > <item>
<widget class="QGroupBox" name="groupBox_2" > <widget class="QGroupBox" name="groupBox_2" >
<property name="title" > <property name="title" >
<string>Toolbar</string> <string>Toolbar</string>
@ -402,118 +437,112 @@
</widget> </widget>
</item> </item>
</layout> </layout>
<zorder>toolbar_button_size</zorder>
<zorder>label_4</zorder>
<zorder>show_toolbar_text</zorder>
<zorder>columns</zorder>
<zorder></zorder>
<zorder>groupBox_3</zorder>
</widget> </widget>
</item> </item>
<item row="8" column="0" > <item>
<widget class="QGroupBox" name="groupBox" > <layout class="QHBoxLayout" name="horizontalLayout_7" >
<property name="title" > <item>
<string>Select visible &amp;columns in library view</string> <widget class="QGroupBox" name="groupBox" >
</property> <property name="title" >
<layout class="QVBoxLayout" name="verticalLayout_4" > <string>Select visible &amp;columns in library view</string>
<item> </property>
<layout class="QHBoxLayout" name="horizontalLayout_3" > <layout class="QVBoxLayout" name="verticalLayout_7" >
<item> <item>
<widget class="QListWidget" name="columns" > <layout class="QHBoxLayout" name="horizontalLayout_3" >
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
<property name="selectionBehavior" >
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3" >
<item> <item>
<widget class="QToolButton" name="column_up" > <widget class="QListWidget" name="columns" >
<property name="text" > <property name="alternatingRowColors" >
<string>...</string> <bool>true</bool>
</property> </property>
<property name="icon" > <property name="selectionBehavior" >
<iconset resource="../images.qrc" > <enum>QAbstractItemView::SelectRows</enum>
<normaloff>:/images/arrow-up.svg</normaloff>:/images/arrow-up.svg</iconset>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<spacer name="verticalSpacer_2" > <layout class="QVBoxLayout" name="verticalLayout_3" >
<property name="orientation" > <item>
<enum>Qt::Vertical</enum> <widget class="QToolButton" name="column_up" >
</property> <property name="text" >
<property name="sizeHint" stdset="0" > <string>...</string>
<size> </property>
<width>20</width> <property name="icon" >
<height>40</height> <iconset resource="../images.qrc" >
</size> <normaloff>:/images/arrow-up.svg</normaloff>:/images/arrow-up.svg</iconset>
</property> </property>
</spacer> </widget>
</item> </item>
<item> <item>
<widget class="QToolButton" name="column_down" > <spacer name="verticalSpacer_2" >
<property name="text" > <property name="orientation" >
<string>...</string> <enum>Qt::Vertical</enum>
</property> </property>
<property name="icon" > <property name="sizeHint" stdset="0" >
<iconset resource="../images.qrc" > <size>
<normaloff>:/images/arrow-down.svg</normaloff>:/images/arrow-down.svg</iconset> <width>20</width>
</property> <height>40</height>
</widget> </size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="column_down" >
<property name="text" >
<string>...</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/arrow-down.svg</normaloff>:/images/arrow-down.svg</iconset>
</property>
</widget>
</item>
</layout>
</item> </item>
</layout> </layout>
</item> </item>
</layout> </layout>
</item> <zorder>columns</zorder>
<item> </widget>
<widget class="QGroupBox" name="groupBox_3" > </item>
<property name="title" > <item>
<string>Use internal &amp;viewer for the following formats:</string> <widget class="QGroupBox" name="groupBox_3" >
</property> <property name="title" >
<layout class="QGridLayout" name="gridLayout_4" > <string>Use internal &amp;viewer for:</string>
<item row="0" column="0" > </property>
<widget class="QListWidget" name="viewer" > <layout class="QGridLayout" name="gridLayout_4" >
<property name="alternatingRowColors" > <item row="0" column="0" >
<bool>true</bool> <widget class="QListWidget" name="viewer" >
</property> <property name="alternatingRowColors" >
<property name="selectionMode" > <bool>true</bool>
<enum>QAbstractItemView::NoSelection</enum> </property>
</property> <property name="selectionMode" >
</widget> <enum>QAbstractItemView::NoSelection</enum>
</item> </property>
</layout> </widget>
</widget> </item>
</item> </layout>
</layout> </widget>
</widget> </item>
</item> </layout>
<item row="1" column="0" >
<widget class="QCheckBox" name="systray_icon" >
<property name="text" >
<string>Enable system &amp;tray icon (needs restart)</string>
</property>
</widget>
</item>
<item row="4" column="0" >
<widget class="QCheckBox" name="sync_news" >
<property name="text" >
<string>Automatically send downloaded &amp;news to ebook reader</string>
</property>
</widget>
</item>
<item row="5" column="0" >
<widget class="QCheckBox" name="delete_news" >
<property name="text" >
<string>&amp;Delete news from library when it is sent to reader</string>
</property>
</widget>
</item>
<item row="3" column="0" >
<widget class="QCheckBox" name="separate_cover_flow" >
<property name="text" >
<string>Show cover &amp;browser in a separate window (needs restart)</string>
</property>
</widget>
</item> </item>
</layout> </layout>
<zorder>roman_numerals</zorder>
<zorder>groupBox_2</zorder>
<zorder>groupBox</zorder>
<zorder>systray_icon</zorder>
<zorder>sync_news</zorder>
<zorder>delete_news</zorder>
<zorder>separate_cover_flow</zorder>
<zorder>systray_notifications</zorder>
<zorder>groupBox_3</zorder>
<zorder></zorder>
<zorder></zorder>
</widget> </widget>
<widget class="QWidget" name="page_2" > <widget class="QWidget" name="page_2" >
<layout class="QVBoxLayout" > <layout class="QVBoxLayout" >

View File

@ -14,19 +14,21 @@ from lxml.etree import XPath
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
from calibre.gui2.dialogs.epub_ui import Ui_Dialog from calibre.gui2.dialogs.epub_ui import Ui_Dialog
from calibre.gui2 import error_dialog, choose_images, pixmap_to_data from calibre.gui2 import error_dialog, choose_images, pixmap_to_data, ResizableDialog
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.ebooks.metadata import MetaInformation
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.ebooks.metadata.opf import OPFCreator from calibre.ebooks.metadata.opf import OPFCreator
from calibre.ebooks.metadata import authors_to_string, string_to_authors from calibre.ebooks.metadata import authors_to_string, string_to_authors
class Config(QDialog, Ui_Dialog): class Config(ResizableDialog, Ui_Dialog):
def __init__(self, parent, db, row=None): OUTPUT = 'EPUB'
QDialog.__init__(self, parent)
self.setupUi(self) def __init__(self, parent, db, row=None, config=epubconfig):
ResizableDialog.__init__(self, parent)
self.hide_controls()
self.connect(self.category_list, SIGNAL('itemEntered(QListWidgetItem *)'), self.connect(self.category_list, SIGNAL('itemEntered(QListWidgetItem *)'),
self.show_category_help) self.show_category_help)
self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover) self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover)
@ -38,7 +40,7 @@ class Config(QDialog, Ui_Dialog):
if row is not None: if row is not None:
self.id = db.id(row) self.id = db.id(row)
base = config().as_string() + '\n\n' 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 '') defaults = base + (defaults if defaults else '')
self.config = config(defaults=defaults) self.config = config(defaults=defaults)
else: else:
@ -47,20 +49,29 @@ class Config(QDialog, Ui_Dialog):
self.get_source_format() self.get_source_format()
self.category_list.setCurrentRow(0) self.category_list.setCurrentRow(0)
if self.row is None: if self.row is None:
self.setWindowTitle(_('Bulk convert to EPUB')) self.setWindowTitle(_('Bulk convert to ')+self.OUTPUT)
else: 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): def initialize(self):
self.__w = [] self.__w = []
self.__w.append(QIcon(':/images/dialog_information.svg')) self.__w.append(QIcon(':/images/dialog_information.svg'))
self.item1 = QListWidgetItem(self.__w[-1], _('Metadata'), self.category_list) self.item1 = QListWidgetItem(self.__w[-1], _('Metadata'), self.category_list)
self.__w.append(QIcon(':/images/lookfeel.svg')) self.__w.append(QIcon(':/images/lookfeel.svg'))
self.item2 = QListWidgetItem(self.__w[-1], _('Look & Feel'), self.category_list) self.item2 = QListWidgetItem(self.__w[-1], _('Look & Feel').replace(' ','\n'), self.category_list)
self.__w.append(QIcon(':/images/page.svg')) self.__w.append(QIcon(':/images/page.svg'))
self.item3 = QListWidgetItem(self.__w[-1], _('Page Setup'), self.category_list) self.item3 = QListWidgetItem(self.__w[-1], _('Page Setup').replace(' ','\n'), self.category_list)
self.__w.append(QIcon(':/images/chapters.svg')) self.__w.append(QIcon(':/images/chapters.svg'))
self.item4 = QListWidgetItem(self.__w[-1], _('Chapter Detection'), self.category_list) self.item4 = QListWidgetItem(self.__w[-1], _('Chapter Detection').replace(' ','\n'), self.category_list)
self.setup_tooltips() self.setup_tooltips()
self.initialize_options() self.initialize_options()
@ -81,12 +92,12 @@ class Config(QDialog, Ui_Dialog):
def show_category_help(self, item): def show_category_help(self, item):
text = unicode(item.text()) text = unicode(item.text())
help = { 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.'), _('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 EPUB file by specifying things like font sizes.'), _('Look & Feel') : _('Adjust the look of the generated ebook by specifying things like font sizes.'),
_('Page Setup') : _('Specify the page layout settings like margins.'), _('Page Setup') : _('Specify the page layout settings like margins.'),
_('Chapter Detection') : _('Fine tune the detection of chapter and section headings.'), _('Chapter Detection') : _('Fine tune the detection of chapter and section headings.'),
} }
self.set_help(help[text]) self.set_help(help[text.replace('\n', ' ')])
def select_cover(self): def select_cover(self):
files = choose_images(self, 'change cover dialog', files = choose_images(self, 'change cover dialog',
@ -195,7 +206,7 @@ class Config(QDialog, Ui_Dialog):
elif isinstance(g, QCheckBox): elif isinstance(g, QCheckBox):
self.config.set(pref.name, bool(g.isChecked())) self.config.set(pref.name, bool(g.isChecked()))
if self.row is not None: 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): def initialize_options(self):
@ -235,7 +246,7 @@ class Config(QDialog, Ui_Dialog):
elif len(choices) == 1: elif len(choices) == 1:
self.source_format = choices[0] self.source_format = choices[0]
else: 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: if d.exec_() == QDialog.Accepted:
self.source_format = d.format() self.source_format = d.format()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@ from PyQt4.QtGui import QPixmap, QListWidgetItem, QErrorMessage, QDialog, QCompl
from calibre.gui2 import qstring_to_unicode, error_dialog, file_icon_provider, \ from calibre.gui2 import qstring_to_unicode, error_dialog, file_icon_provider, \
choose_files, pixmap_to_data, choose_images choose_files, pixmap_to_data, choose_images, ResizableDialog
from calibre.gui2.dialogs.metadata_single_ui import Ui_MetadataSingleDialog from calibre.gui2.dialogs.metadata_single_ui import Ui_MetadataSingleDialog
from calibre.gui2.dialogs.fetch_metadata import FetchMetadata from calibre.gui2.dialogs.fetch_metadata import FetchMetadata
from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.gui2.dialogs.tag_editor import TagEditor
@ -40,7 +40,7 @@ class AuthorCompleter(QCompleter):
all_authors.sort(cmp=lambda x, y : cmp(x[1], y[1])) all_authors.sort(cmp=lambda x, y : cmp(x[1], y[1]))
QCompleter.__init__(self, [x[1] for x in all_authors]) QCompleter.__init__(self, [x[1] for x in all_authors])
class MetadataSingleDialog(QDialog, Ui_MetadataSingleDialog): class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
def do_reset_cover(self, *args): def do_reset_cover(self, *args):
pix = QPixmap(':/images/book.svg') pix = QPixmap(':/images/book.svg')
@ -164,9 +164,7 @@ class MetadataSingleDialog(QDialog, Ui_MetadataSingleDialog):
self.db.remove_format(self.row, ext, notify=False) self.db.remove_format(self.row, ext, notify=False)
def __init__(self, window, row, db, accepted_callback=None): def __init__(self, window, row, db, accepted_callback=None):
QDialog.__init__(self, window) ResizableDialog.__init__(self, window)
Ui_MetadataSingleDialog.__init__(self)
self.setupUi(self)
self.bc_box.layout().setAlignment(self.cover, Qt.AlignCenter|Qt.AlignHCenter) self.bc_box.layout().setAlignment(self.cover, Qt.AlignCenter|Qt.AlignHCenter)
self.splitter.setStretchFactor(100, 1) self.splitter.setStretchFactor(100, 1)
self.db = db self.db = db

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -3,21 +3,20 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import time, os import time, os
from PyQt4.QtCore import SIGNAL, QUrl from PyQt4.QtCore import SIGNAL, QUrl
from PyQt4.QtGui import QDialog, QMessageBox, QDesktopServices from PyQt4.QtGui import QMessageBox, QDesktopServices
from calibre.web.feeds.recipes import compile_recipe from calibre.web.feeds.recipes import compile_recipe
from calibre.web.feeds.news import AutomaticNewsRecipe from calibre.web.feeds.news import AutomaticNewsRecipe
from calibre.gui2.dialogs.user_profiles_ui import Ui_Dialog from calibre.gui2.dialogs.user_profiles_ui import Ui_Dialog
from calibre.gui2 import qstring_to_unicode, error_dialog, question_dialog, choose_files from calibre.gui2 import qstring_to_unicode, error_dialog, question_dialog, \
choose_files, ResizableDialog
from calibre.gui2.widgets import PythonHighlighter from calibre.gui2.widgets import PythonHighlighter
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
class UserProfiles(QDialog, Ui_Dialog): class UserProfiles(ResizableDialog, Ui_Dialog):
def __init__(self, parent, feeds): def __init__(self, parent, feeds):
QDialog.__init__(self, parent) ResizableDialog.__init__(self, parent)
Ui_Dialog.__init__(self)
self.setupUi(self)
self.connect(self.remove_feed_button, SIGNAL('clicked(bool)'), self.connect(self.remove_feed_button, SIGNAL('clicked(bool)'),
self.added_feeds.remove_selected_items) self.added_feeds.remove_selected_items)

View File

@ -5,8 +5,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>744</width> <width>719</width>
<height>633</height> <height>612</height>
</rect> </rect>
</property> </property>
<property name="windowTitle" > <property name="windowTitle" >
@ -16,8 +16,420 @@
<iconset resource="../images.qrc" > <iconset resource="../images.qrc" >
<normaloff>:/images/user_profile.svg</normaloff>:/images/user_profile.svg</iconset> <normaloff>:/images/user_profile.svg</normaloff>:/images/user_profile.svg</iconset>
</property> </property>
<layout class="QGridLayout" > <layout class="QVBoxLayout" name="verticalLayout_4" >
<item row="1" column="0" > <item>
<widget class="QScrollArea" name="scrollArea" >
<property name="frameShape" >
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth" >
<number>0</number>
</property>
<property name="widgetResizable" >
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>711</width>
<height>572</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3" >
<property name="margin" >
<number>0</number>
</property>
<item>
<widget class="QWidget" native="1" name="central_widget" >
<property name="minimumSize" >
<size>
<width>680</width>
<height>550</height>
</size>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" >
<property name="margin" >
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="groupBox" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Minimum" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title" >
<string>Available user recipes</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" >
<item>
<widget class="BasicList" name="available_profiles" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Minimum" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="add_profile_button" >
<property name="text" >
<string>Add/Update &amp;recipe</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/plus.svg</normaloff>:/images/plus.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="remove_profile_button" >
<property name="text" >
<string>&amp;Remove recipe</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/list_remove.svg</normaloff>:/images/list_remove.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="share_button" >
<property name="text" >
<string>&amp;Share recipe</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/forward.svg</normaloff>:/images/forward.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="builtin_recipe_button" >
<property name="text" >
<string>Customize &amp;builtin recipe</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/news.svg</normaloff>:/images/news.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="load_button" >
<property name="text" >
<string>&amp;Load recipe from file</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/chapters.svg</normaloff>:/images/chapters.svg</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame" >
<property name="frameShape" >
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow" >
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout" >
<item>
<widget class="QPushButton" name="toggle_mode_button" >
<property name="text" >
<string>Switch to Advanced mode</string>
</property>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="stacks" >
<property name="currentIndex" >
<number>0</number>
</property>
<widget class="QWidget" name="page" >
<layout class="QVBoxLayout" name="verticalLayout_5" >
<item>
<widget class="QLabel" name="label" >
<property name="text" >
<string>&lt;html>&lt;head>&lt;meta name="qrichtext" content="1" />&lt;style type="text/css">
p, li { white-space: pre-wrap; }
&lt;/style>&lt;/head>&lt;body style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal;">
&lt;p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Create a basic news recipe, by adding RSS feeds to it. &lt;br />For most feeds, you will have to use the "Advanced mode" to further customize the fetch process.&lt;/p>&lt;/body>&lt;/html></string>
</property>
<property name="textFormat" >
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>Recipe &amp;title:</string>
</property>
<property name="buddy" >
<cstring>profile_title</cstring>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2" >
<widget class="QLineEdit" name="profile_title" >
<property name="font" >
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
</widget>
</item>
<item row="2" column="0" >
<widget class="QLabel" name="label_6" >
<property name="text" >
<string>&amp;Oldest article:</string>
</property>
<property name="buddy" >
<cstring>oldest_article</cstring>
</property>
</widget>
</item>
<item row="2" column="2" >
<widget class="QSpinBox" name="oldest_article" >
<property name="toolTip" >
<string>The oldest article to download</string>
</property>
<property name="suffix" >
<string> days</string>
</property>
<property name="minimum" >
<number>1</number>
</property>
<property name="maximum" >
<number>365</number>
</property>
<property name="value" >
<number>7</number>
</property>
</widget>
</item>
<item row="3" column="0" >
<widget class="QLabel" name="label_7" >
<property name="text" >
<string>&amp;Max. number of articles per feed:</string>
</property>
<property name="buddy" >
<cstring>max_articles</cstring>
</property>
</widget>
</item>
<item row="3" column="2" >
<widget class="QSpinBox" name="max_articles" >
<property name="toolTip" >
<string>Maximum number of articles to download per feed.</string>
</property>
<property name="minimum" >
<number>5</number>
</property>
<property name="maximum" >
<number>100</number>
</property>
<property name="value" >
<number>10</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
<horstretch>100</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title" >
<string>Feeds in recipe</string>
</property>
<layout class="QHBoxLayout" >
<item>
<widget class="BasicList" name="added_feeds" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<horstretch>100</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="selectionMode" >
<enum>QAbstractItemView::MultiSelection</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" >
<item>
<widget class="QToolButton" name="up_button" >
<property name="text" >
<string>...</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/arrow-up.svg</normaloff>:/images/arrow-up.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="remove_feed_button" >
<property name="toolTip" >
<string>Remove feed from recipe</string>
</property>
<property name="text" >
<string>...</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/list_remove.svg</normaloff>:/images/list_remove.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="down_button" >
<property name="text" >
<string>...</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/arrow-down.svg</normaloff>:/images/arrow-down.svg</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3" >
<property name="title" >
<string>Add feed to recipe</string>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QLabel" name="label_4" >
<property name="text" >
<string>&amp;Feed title:</string>
</property>
<property name="buddy" >
<cstring>feed_title</cstring>
</property>
</widget>
</item>
<item row="0" column="1" >
<widget class="QLineEdit" name="feed_title" />
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_5" >
<property name="text" >
<string>Feed &amp;URL:</string>
</property>
<property name="buddy" >
<cstring>feed_url</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QLineEdit" name="feed_url" />
</item>
<item row="2" column="0" colspan="2" >
<widget class="QPushButton" name="add_feed_button" >
<property name="toolTip" >
<string>Add feed to recipe</string>
</property>
<property name="text" >
<string>&amp;Add feed</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/plus.svg</normaloff>:/images/plus.svg</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2" >
<layout class="QVBoxLayout" >
<item>
<widget class="QLabel" name="label_8" >
<property name="text" >
<string>For help with writing advanced news recipes, please visit &lt;a href="http://__appname__.kovidgoyal.net/user_manual/news.html">User Recipes&lt;/a></string>
</property>
<property name="wordWrap" >
<bool>true</bool>
</property>
<property name="openExternalLinks" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4" >
<property name="title" >
<string>Recipe source code (python)</string>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QTextEdit" name="source_code" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<horstretch>100</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font" >
<font>
<family>DejaVu Sans Mono</family>
</font>
</property>
<property name="lineWrapMode" >
<enum>QTextEdit::NoWrap</enum>
</property>
<property name="acceptRichText" >
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox" > <widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" > <property name="orientation" >
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
@ -27,360 +439,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0" >
<widget class="QSplitter" name="splitter" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<widget class="QGroupBox" name="groupBox" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Minimum" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize" >
<size>
<width>215</width>
<height>16777215</height>
</size>
</property>
<property name="title" >
<string>Available user recipes</string>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="BasicList" name="available_profiles" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Minimum" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="add_profile_button" >
<property name="text" >
<string>Add/Update &amp;recipe</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/plus.svg</normaloff>:/images/plus.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="remove_profile_button" >
<property name="text" >
<string>&amp;Remove recipe</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/list_remove.svg</normaloff>:/images/list_remove.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="share_button" >
<property name="text" >
<string>&amp;Share recipe</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/forward.svg</normaloff>:/images/forward.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="builtin_recipe_button" >
<property name="text" >
<string>Customize &amp;builtin recipe</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/news.svg</normaloff>:/images/news.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="load_button" >
<property name="text" >
<string>&amp;Load recipe from file</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/chapters.svg</normaloff>:/images/chapters.svg</iconset>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="layoutWidget" >
<layout class="QVBoxLayout" >
<item>
<widget class="QPushButton" name="toggle_mode_button" >
<property name="text" >
<string>Switch to Advanced mode</string>
</property>
</widget>
</item>
<item>
<widget class="QStackedWidget" name="stacks" >
<property name="currentIndex" >
<number>0</number>
</property>
<widget class="QWidget" name="page" >
<layout class="QVBoxLayout" >
<item>
<widget class="QLabel" name="label" >
<property name="text" >
<string>&lt;html>&lt;head>&lt;meta name="qrichtext" content="1" />&lt;style type="text/css">
p, li { white-space: pre-wrap; }
&lt;/style>&lt;/head>&lt;body style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal;">
&lt;p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Create a basic news recipe, by adding RSS feeds to it. &lt;br />For most feeds, you will have to use the "Advanced mode" to further customize the fetch process.&lt;/p>&lt;/body>&lt;/html></string>
</property>
<property name="textFormat" >
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>Recipe &amp;title:</string>
</property>
<property name="buddy" >
<cstring>profile_title</cstring>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2" >
<widget class="QLineEdit" name="profile_title" >
<property name="font" >
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
</widget>
</item>
<item row="2" column="0" >
<widget class="QLabel" name="label_6" >
<property name="text" >
<string>&amp;Oldest article:</string>
</property>
<property name="buddy" >
<cstring>oldest_article</cstring>
</property>
</widget>
</item>
<item row="2" column="2" >
<widget class="QSpinBox" name="oldest_article" >
<property name="toolTip" >
<string>The oldest article to download</string>
</property>
<property name="suffix" >
<string> days</string>
</property>
<property name="minimum" >
<number>1</number>
</property>
<property name="maximum" >
<number>365</number>
</property>
<property name="value" >
<number>7</number>
</property>
</widget>
</item>
<item row="3" column="0" >
<widget class="QLabel" name="label_7" >
<property name="text" >
<string>&amp;Max. number of articles per feed:</string>
</property>
<property name="buddy" >
<cstring>max_articles</cstring>
</property>
</widget>
</item>
<item row="3" column="2" >
<widget class="QSpinBox" name="max_articles" >
<property name="toolTip" >
<string>Maximum number of articles to download per feed.</string>
</property>
<property name="minimum" >
<number>5</number>
</property>
<property name="maximum" >
<number>100</number>
</property>
<property name="value" >
<number>10</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2" >
<property name="title" >
<string>Feeds in recipe</string>
</property>
<layout class="QHBoxLayout" >
<item>
<widget class="BasicList" name="added_feeds" >
<property name="selectionMode" >
<enum>QAbstractItemView::MultiSelection</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" >
<item>
<widget class="QToolButton" name="up_button" >
<property name="text" >
<string>...</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/arrow-up.svg</normaloff>:/images/arrow-up.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="remove_feed_button" >
<property name="toolTip" >
<string>Remove feed from recipe</string>
</property>
<property name="text" >
<string>...</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/list_remove.svg</normaloff>:/images/list_remove.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="down_button" >
<property name="text" >
<string>...</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/arrow-down.svg</normaloff>:/images/arrow-down.svg</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3" >
<property name="title" >
<string>Add feed to recipe</string>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QLabel" name="label_4" >
<property name="text" >
<string>&amp;Feed title:</string>
</property>
<property name="buddy" >
<cstring>feed_title</cstring>
</property>
</widget>
</item>
<item row="0" column="1" >
<widget class="QLineEdit" name="feed_title" />
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_5" >
<property name="text" >
<string>Feed &amp;URL:</string>
</property>
<property name="buddy" >
<cstring>feed_url</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QLineEdit" name="feed_url" />
</item>
<item row="2" column="0" colspan="2" >
<widget class="QPushButton" name="add_feed_button" >
<property name="toolTip" >
<string>Add feed to recipe</string>
</property>
<property name="text" >
<string>&amp;Add feed</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/plus.svg</normaloff>:/images/plus.svg</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2" >
<layout class="QVBoxLayout" >
<item>
<widget class="QLabel" name="label_8" >
<property name="text" >
<string>For help with writing advanced news recipes, please visit &lt;a href="http://__appname__.kovidgoyal.net/user_manual/news.html">User Recipes&lt;/a></string>
</property>
<property name="wordWrap" >
<bool>true</bool>
</property>
<property name="openExternalLinks" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4" >
<property name="title" >
<string>Recipe source code (python)</string>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QTextEdit" name="source_code" >
<property name="font" >
<font>
<family>DejaVu Sans Mono</family>
</font>
</property>
<property name="lineWrapMode" >
<enum>QTextEdit::NoWrap</enum>
</property>
<property name="acceptRichText" >
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets> <customwidgets>

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -162,7 +162,8 @@ class BooksModel(QAbstractTableModel):
def refresh_ids(self, ids, current_row=-1): def refresh_ids(self, ids, current_row=-1):
rows = self.db.refresh_ids(ids) 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): def refresh_rows(self, rows, current_row=-1):
for row in rows: for row in rows:
@ -261,7 +262,17 @@ class BooksModel(QAbstractTableModel):
self.reset() self.reset()
self.sorted_on = (self.column_map[col], order) 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): def resort(self, reset=True):
try: try:
col = self.column_map.index(self.sorted_on[0]) col = self.column_map.index(self.sorted_on[0])

View File

@ -25,7 +25,6 @@ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
max_available_height, config, info_dialog, \ max_available_height, config, info_dialog, \
available_width available_width
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror 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.dialogs.scheduler import Scheduler
from calibre.gui2.update import CheckForUpdates from calibre.gui2.update import CheckForUpdates
from calibre.gui2.dialogs.progress import ProgressDialog from calibre.gui2.dialogs.progress import ProgressDialog
@ -131,20 +130,12 @@ class Main(MainWindow, Ui_MainWindow):
QObject.connect(self.stack, SIGNAL('currentChanged(int)'), QObject.connect(self.stack, SIGNAL('currentChanged(int)'),
self.location_view.location_changed) self.location_view.location_changed)
self.output_formats = sorted(['EPUB', 'LRF']) self.output_formats = sorted(['EPUB', 'MOBI', 'LRF'])
for f in self.output_formats: for f in self.output_formats:
self.output_format.addItem(f) self.output_format.addItem(f)
self.output_format.setCurrentIndex(self.output_formats.index(prefs['output_format'])) 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'):
warning_dialog(self, 'Warning',
'<p>%s support is still in beta. If you find bugs, please report them by opening a <a href="http://calibre.kovidgoyal.net">ticket</a>.'%of).exec_()
prefs.set('output_format', of)
self.connect(self.output_format, SIGNAL('currentIndexChanged(QString)'), self.connect(self.output_format, SIGNAL('currentIndexChanged(QString)'),
change_output_format) self.change_output_format, Qt.QueuedConnection)
####################### Vanity ######################## ####################### Vanity ########################
self.vanity_template = _('<p>For help visit <a href="http://%s.kovidgoyal.net/user_manual">%s.kovidgoyal.net</a><br>')%(__appname__, __appname__) self.vanity_template = _('<p>For help visit <a href="http://%s.kovidgoyal.net/user_manual">%s.kovidgoyal.net</a><br>')%(__appname__, __appname__)
@ -296,6 +287,8 @@ class Main(MainWindow, Ui_MainWindow):
self.card_view.connect_dirtied_signal(self.upload_booklists) self.card_view.connect_dirtied_signal(self.upload_booklists)
self.show() self.show()
if self.system_tray_icon.isVisible() and opts.start_in_tray:
self.hide()
self.stack.setCurrentIndex(0) self.stack.setCurrentIndex(0)
try: try:
db = LibraryDatabase2(self.library_path) db = LibraryDatabase2(self.library_path)
@ -375,6 +368,15 @@ class Main(MainWindow, Ui_MainWindow):
self.action_news.setMenu(self.scheduler.news_menu) self.action_news.setMenu(self.scheduler.news_menu)
self.connect(self.action_news, SIGNAL('triggered(bool)'), self.scheduler.show_dialog) self.connect(self.action_news, SIGNAL('triggered(bool)'), self.scheduler.show_dialog)
self.location_view.setCurrentIndex(self.location_view.model().index(0)) self.location_view.setCurrentIndex(self.location_view.model().index(0))
def change_output_format(self, x):
of = unicode(x).strip()
if of != prefs['output_format']:
if of not in ('LRF',):
warning_dialog(self, 'Warning',
'<p>%s support is still in beta. If you find bugs, please report them by opening a <a href="http://calibre.kovidgoyal.net">ticket</a>.'%of).exec_()
prefs.set('output_format', of)
def test_server(self, *args): def test_server(self, *args):
if self.content_server.exception is not None: if self.content_server.exception is not None:
@ -477,7 +479,7 @@ class Main(MainWindow, Ui_MainWindow):
self.raise_() self.raise_()
self.activateWindow() self.activateWindow()
elif msg.startswith('refreshdb:'): elif msg.startswith('refreshdb:'):
self.library_view.model().resort() self.library_view.model().refresh()
self.library_view.model().research() self.library_view.model().research()
else: else:
print msg print msg
@ -910,13 +912,14 @@ class Main(MainWindow, Ui_MainWindow):
_files = self.library_view.model().get_preferred_formats(rows, _files = self.library_view.model().get_preferred_formats(rows,
self.device_manager.device_class.FORMATS, paths=True) self.device_manager.device_class.FORMATS, paths=True)
files = [getattr(f, 'name', None) for f in _files] files = [getattr(f, 'name', None) for f in _files]
bad, good, gf, names = [], [], [], [] bad, good, gf, names, remove_ids = [], [], [], [], []
for f in files: for f in files:
mi = metadata.next() mi = metadata.next()
id = ids.next() id = ids.next()
if f is None: if f is None:
bad.append(mi['title']) bad.append(mi['title'])
else: else:
remove_ids.append(id)
aus = mi['authors'].split(',') aus = mi['authors'].split(',')
aus2 = [] aus2 = []
for a in aus: for a in aus:
@ -943,7 +946,7 @@ class Main(MainWindow, Ui_MainWindow):
prefix = prefix.decode(preferred_encoding, 'replace') prefix = prefix.decode(preferred_encoding, 'replace')
prefix = ascii_filename(prefix) prefix = ascii_filename(prefix)
names.append('%s_%d%s'%(prefix, id, os.path.splitext(f)[1])) names.append('%s_%d%s'%(prefix, id, os.path.splitext(f)[1]))
remove = [self.library_view.model().id(r) for r in rows] if delete_from_library else [] remove = remove_ids if delete_from_library else []
self.upload_books(gf, names, good, on_card, memory=(_files, remove)) self.upload_books(gf, names, good, on_card, memory=(_files, remove))
self.status_bar.showMessage(_('Sending books to device.'), 5000) self.status_bar.showMessage(_('Sending books to device.'), 5000)
if bad: if bad:
@ -1527,6 +1530,8 @@ path_to_ebook to the database.
''') ''')
parser.add_option('--with-library', default=None, action='store', parser.add_option('--with-library', default=None, action='store',
help=_('Use the library located at the specified path.')) 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', parser.add_option('-v', '--verbose', default=0, action='count',
help=_('Log debugging information to console')) help=_('Log debugging information to console'))
return parser return parser

View File

@ -119,7 +119,11 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QComboBox" name="output_format" /> <widget class="QComboBox" name="output_format" >
<property name="toolTip" >
<string>Set the output format that is used when converting ebooks and downloading news</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</item> </item>

View File

@ -6,7 +6,7 @@ from PyQt4.QtGui import QStatusBar, QMovie, QLabel, QWidget, QHBoxLayout, QPixma
QVBoxLayout, QSizePolicy, QToolButton, QIcon, QScrollArea, QFrame QVBoxLayout, QSizePolicy, QToolButton, QIcon, QScrollArea, QFrame
from PyQt4.QtCore import Qt, QSize, SIGNAL, QCoreApplication from PyQt4.QtCore import Qt, QSize, SIGNAL, QCoreApplication
from calibre import fit_image, preferred_encoding, isosx from calibre import fit_image, preferred_encoding, isosx
from calibre.gui2 import qstring_to_unicode from calibre.gui2 import qstring_to_unicode, config
class BookInfoDisplay(QWidget): class BookInfoDisplay(QWidget):
class BookCoverDisplay(QLabel): class BookCoverDisplay(QLabel):
@ -197,7 +197,7 @@ class StatusBar(QStatusBar):
def showMessage(self, msg, timeout=0): def showMessage(self, msg, timeout=0):
ret = QStatusBar.showMessage(self, msg, timeout) ret = QStatusBar.showMessage(self, msg, timeout)
if self.systray is not None: if self.systray is not None and not config['disable_tray_notification']:
if isosx and isinstance(msg, unicode): if isosx and isinstance(msg, unicode):
try: try:
msg = msg.encode(preferred_encoding) msg = msg.encode(preferred_encoding)

View File

@ -12,6 +12,7 @@ from PyQt4.Qt import QDialog
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.gui2.dialogs.lrf_single import LRFSingleDialog, LRFBulkDialog from calibre.gui2.dialogs.lrf_single import LRFSingleDialog, LRFBulkDialog
from calibre.gui2.dialogs.epub import Config as EPUBConvert 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 import calibre.gui2.dialogs.comicconf as ComicConf
from calibre.gui2 import warning_dialog from calibre.gui2 import warning_dialog
from calibre.ptempfile import PersistentTemporaryFile 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.metadata.opf import OPFCreator
from calibre.ebooks.epub.from_any import SOURCE_FORMATS as EPUB_PREFERRED_SOURCE_FORMATS 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 changed = False
jobs = [] jobs = []
others_ids = [db.id(row) for row in others] others_ids = [db.id(row) for row in others]
comics_ids = [db.id(row) for row in comics] comics_ids = [db.id(row) for row in comics]
for row, row_id in zip(others, others_ids): for row, row_id in zip(others, others_ids):
temp_files = [] temp_files = []
d = EPUBConvert(parent, db, row) d = get_dialog(fmt)(parent, db, row)
if d.source_format is not None: if d.source_format is not None:
d.exec_() d.exec_()
if d.result() == QDialog.Accepted: if d.result() == QDialog.Accepted:
@ -35,7 +42,7 @@ def convert_single_epub(parent, db, comics, others):
pt = PersistentTemporaryFile('.'+d.source_format.lower()) pt = PersistentTemporaryFile('.'+d.source_format.lower())
pt.write(data) pt.write(data)
pt.close() pt.close()
of = PersistentTemporaryFile('.epub') of = PersistentTemporaryFile('.'+fmt)
of.close() of.close()
opts.output = of.name opts.output = of.name
opts.from_opf = d.opf_file.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) temp_files.append(d.cover_file)
opts.cover = d.cover_file.name opts.cover = d.cover_file.name
temp_files.extend([d.opf_file, pt, of]) temp_files.extend([d.opf_file, pt, of])
jobs.append(('any2epub', args, _('Convert book: ')+d.mi.title, jobs.append(('any2'+fmt, args, _('Convert book: ')+d.mi.title,
'EPUB', row_id, temp_files)) fmt.upper(), row_id, temp_files))
changed = True changed = True
for row, row_id in zip(comics, comics_ids): for row, row_id in zip(comics, comics_ids):
@ -61,24 +68,24 @@ def convert_single_epub(parent, db, comics, others):
if defaults is not None: if defaults is not None:
db.set_conversion_options(db.id(row), 'comic', defaults) db.set_conversion_options(db.id(row), 'comic', defaults)
if opts is None: continue if opts is None: continue
for fmt in ['cbz', 'cbr']: for _fmt in ['cbz', 'cbr']:
try: try:
data = db.format(row, fmt.upper()) data = db.format(row, _fmt.upper())
if data is not None: if data is not None:
break break
except: except:
continue continue
pt = PersistentTemporaryFile('.'+fmt) pt = PersistentTemporaryFile('.'+_fmt)
pt.write(data) pt.write(data)
pt.close() pt.close()
of = PersistentTemporaryFile('.epub') of = PersistentTemporaryFile('.'+fmt)
of.close() of.close()
opts.output = of.name opts.output = of.name
opts.verbose = 2 opts.verbose = 2
args = [pt.name, opts] args = [pt.name, opts]
changed = True changed = True
jobs.append(('comic2epub', args, _('Convert comic: ')+opts.title, jobs.append(('comic2'+fmt, args, _('Convert comic: ')+opts.title,
'EPUB', row_id, [pt, of])) fmt.upper(), row_id, [pt, of]))
return jobs, changed return jobs, changed
@ -146,9 +153,9 @@ def convert_single_lrf(parent, db, comics, others):
return jobs, changed return jobs, changed
def convert_bulk_epub(parent, db, comics, others): def convert_bulk(fmt, parent, db, comics, others):
if others: if others:
d = EPUBConvert(parent, db) d = get_dialog(fmt)(parent, db)
if d.exec_() != QDialog.Accepted: if d.exec_() != QDialog.Accepted:
others = [] others = []
else: else:
@ -169,9 +176,9 @@ def convert_bulk_epub(parent, db, comics, others):
row_id = db.id(row) row_id = db.id(row)
if row in others: if row in others:
data = None data = None
for fmt in EPUB_PREFERRED_SOURCE_FORMATS: for _fmt in EPUB_PREFERRED_SOURCE_FORMATS:
try: try:
data = db.format(row, fmt.upper()) data = db.format(row, _fmt.upper())
if data is not None: if data is not None:
break break
except: except:
@ -185,10 +192,10 @@ def convert_bulk_epub(parent, db, comics, others):
opf_file = PersistentTemporaryFile('.opf') opf_file = PersistentTemporaryFile('.opf')
opf.render(opf_file) opf.render(opf_file)
opf_file.close() opf_file.close()
pt = PersistentTemporaryFile('.'+fmt.lower()) pt = PersistentTemporaryFile('.'+_fmt.lower())
pt.write(data) pt.write(data)
pt.close() pt.close()
of = PersistentTemporaryFile('.epub') of = PersistentTemporaryFile('.'+fmt)
of.close() of.close()
cover = db.cover(row) cover = db.cover(row)
cf = None 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)) desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
temp_files = [cf] if cf is not None else [] temp_files = [cf] if cf is not None else []
temp_files.extend([opf_file, pt, of]) 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: else:
options = comic_opts.copy() options = comic_opts.copy()
mi = db.get_metadata(row) mi = db.get_metadata(row)
@ -212,24 +219,24 @@ def convert_bulk_epub(parent, db, comics, others):
if mi.authors: if mi.authors:
options.author = ','.join(mi.authors) options.author = ','.join(mi.authors)
data = None data = None
for fmt in ['cbz', 'cbr']: for _fmt in ['cbz', 'cbr']:
try: try:
data = db.format(row, fmt.upper()) data = db.format(row, _fmt.upper())
if data is not None: if data is not None:
break break
except: except:
continue continue
pt = PersistentTemporaryFile('.'+fmt.lower()) pt = PersistentTemporaryFile('.'+_fmt.lower())
pt.write(data) pt.write(data)
pt.close() pt.close()
of = PersistentTemporaryFile('.epub') of = PersistentTemporaryFile('.'+fmt)
of.close() of.close()
setattr(options, 'output', of.name) setattr(options, 'output', of.name)
options.verbose = 1 options.verbose = 1
args = [pt.name, options] args = [pt.name, options]
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title)) desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
jobs.append(('comic2epub', args, desc, 'EPUB', row_id, [pt, of])) jobs.append(('comic2'+fmt, args, desc, fmt.upper(), row_id, [pt, of]))
if bad_rows: if bad_rows:
res = [] res = []
@ -345,15 +352,14 @@ def set_conversion_defaults_lrf(comic, parent, db):
else: else:
LRFSingleDialog(parent, None, None).exec_() LRFSingleDialog(parent, None, None).exec_()
def set_conversion_defaults_epub(comic, parent, db): def _set_conversion_defaults(dialog, comic, parent, db):
if comic: if comic:
ComicConf.set_conversion_defaults(parent) ComicConf.set_conversion_defaults(parent)
else: else:
d = EPUBConvert(parent, db) d = dialog(parent, db)
d.setWindowTitle(_('Set conversion defaults')) d.setWindowTitle(_('Set conversion defaults'))
d.exec_() d.exec_()
def _fetch_news(data, fmt): def _fetch_news(data, fmt):
pt = PersistentTemporaryFile(suffix='_feeds2%s.%s'%(fmt.lower(), fmt.lower())) pt = PersistentTemporaryFile(suffix='_feeds2%s.%s'%(fmt.lower(), fmt.lower()))
pt.close() pt.close()
@ -385,22 +391,22 @@ def convert_single_ebook(*args):
fmt = prefs['output_format'].lower() fmt = prefs['output_format'].lower()
if fmt == 'lrf': if fmt == 'lrf':
return convert_single_lrf(*args) return convert_single_lrf(*args)
elif fmt == 'epub': elif fmt in ('epub', 'mobi'):
return convert_single_epub(*args) return convert_single(fmt, *args)
def convert_bulk_ebooks(*args): def convert_bulk_ebooks(*args):
fmt = prefs['output_format'].lower() fmt = prefs['output_format'].lower()
if fmt == 'lrf': if fmt == 'lrf':
return convert_bulk_lrf(*args) return convert_bulk_lrf(*args)
elif fmt == 'epub': elif fmt in ('epub', 'mobi'):
return convert_bulk_epub(*args) return convert_bulk(fmt, *args)
def set_conversion_defaults(comic, parent, db): def set_conversion_defaults(comic, parent, db):
fmt = prefs['output_format'].lower() fmt = prefs['output_format'].lower()
if fmt == 'lrf': if fmt == 'lrf':
return set_conversion_defaults_lrf(comic, parent, db) return set_conversion_defaults_lrf(comic, parent, db)
elif fmt == 'epub': elif fmt in ('epub', 'mobi'):
return set_conversion_defaults_epub(comic, parent, db) return _set_conversion_defaults(get_dialog(fmt), comic, parent, db)
def fetch_news(data): def fetch_news(data):
fmt = prefs['output_format'].lower() fmt = prefs['output_format'].lower()

View File

@ -29,5 +29,5 @@ def server_config(defaults=None):
c.add_opt('develop', ['--develop'], default=False, c.add_opt('develop', ['--develop'], default=False,
help='Development mode. Server automatically restarts on file changes and serves code files (html, css, js) from the file system instead of calibre\'s resource system.') help='Development mode. Server automatically restarts on file changes and serves code files (html, css, js) from the file system instead of calibre\'s resource system.')
c.add_opt('max_cover', ['--max-cover'], default='600x800', c.add_opt('max_cover', ['--max-cover'], default='600x800',
help=_('The maximum size for displayed covers')) help=_('The maximum size for displayed covers. Default is %default.'))
return c return c

View File

@ -224,9 +224,17 @@ class ResultCache(SearchQueryParser):
return False return False
def refresh_ids(self, conn, ids): 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: for id in ids:
self._data[id] = conn.get('SELECT * from meta WHERE id=?', (id,))[0] 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): def books_added(self, ids, conn):
if not ids: if not ids:

View File

@ -40,7 +40,7 @@ function create_table_headers() {
function format_url(format, id, title) { 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) { function render_book(book) {

View File

@ -188,8 +188,31 @@ def extract(path, dir):
finally: finally:
os.chdir(cwd) os.chdir(cwd)
_libunrar.RARCloseArchive(arc_data) _libunrar.RARCloseArchive(arc_data)
def extract_member(path, match=re.compile(r'\.(jpg|jpeg|gif|png)\s*$', re.I)): def names(path):
if hasattr(path, 'read'):
data = path.read()
f = NamedTemporaryFile(suffix='.rar')
f.write(data)
f.flush()
path = f.name
open_archive_data = RAROpenArchiveDataEx(ArcName=path, OpenMode=RAR_OM_LIST, CmtBuf=None)
arc_data = _libunrar.RAROpenArchiveEx(byref(open_archive_data))
try:
if open_archive_data.OpenResult != 0:
raise UnRARException(_interpret_open_error(open_archive_data.OpenResult, path))
header_data = RARHeaderDataEx(CmtBuf=None)
while True:
if _libunrar.RARReadHeaderEx(arc_data, byref(header_data)) != 0:
break
PFCode = _libunrar.RARProcessFileW(arc_data, RAR_SKIP, None, None)
if PFCode != 0:
raise UnRARException(_interpret_process_file_error(PFCode))
yield header_data.FileNameW
finally:
_libunrar.RARCloseArchive(arc_data)
def extract_member(path, match=re.compile(r'\.(jpg|jpeg|gif|png)\s*$', re.I), name=None):
if hasattr(path, 'read'): if hasattr(path, 'read'):
data = path.read() data = path.read()
f = NamedTemporaryFile(suffix='.rar') f = NamedTemporaryFile(suffix='.rar')
@ -210,7 +233,9 @@ def extract_member(path, match=re.compile(r'\.(jpg|jpeg|gif|png)\s*$', re.I)):
PFCode = _libunrar.RARProcessFileW(arc_data, RAR_EXTRACT, None, None) PFCode = _libunrar.RARProcessFileW(arc_data, RAR_EXTRACT, None, None)
if PFCode != 0: if PFCode != 0:
raise UnRARException(_interpret_process_file_error(PFCode)) raise UnRARException(_interpret_process_file_error(PFCode))
if match.search(header_data.FileNameW): file_name = header_data.FileNameW
if (name is not None and file_name == name) or \
(match is not None and match.search(file_name)):
return header_data.FileNameW.replace('/', os.sep), \ return header_data.FileNameW.replace('/', os.sep), \
open(os.path.join(dir, *header_data.FileNameW.split('/')), 'rb').read() open(os.path.join(dir, *header_data.FileNameW.split('/')), 'rb').read()
finally: finally:

View File

@ -26,6 +26,7 @@ entry_points = {
'opf-meta = calibre.ebooks.metadata.opf2:main', 'opf-meta = calibre.ebooks.metadata.opf2:main',
'odt-meta = calibre.ebooks.metadata.odt:main', 'odt-meta = calibre.ebooks.metadata.odt:main',
'epub-meta = calibre.ebooks.metadata.epub:main', 'epub-meta = calibre.ebooks.metadata.epub:main',
'mobi-meta = calibre.ebooks.metadata.mobi:main',
'txt2lrf = calibre.ebooks.lrf.txt.convert_from:main', 'txt2lrf = calibre.ebooks.lrf.txt.convert_from:main',
'html2lrf = calibre.ebooks.lrf.html.convert_from:main', 'html2lrf = calibre.ebooks.lrf.html.convert_from:main',
'html2oeb = calibre.ebooks.html:main', 'html2oeb = calibre.ebooks.html:main',
@ -40,6 +41,7 @@ entry_points = {
'calibre-server = calibre.library.server:main', 'calibre-server = calibre.library.server:main',
'feeds2lrf = calibre.ebooks.lrf.feeds.convert_from:main', 'feeds2lrf = calibre.ebooks.lrf.feeds.convert_from:main',
'feeds2epub = calibre.ebooks.epub.from_feeds:main', 'feeds2epub = calibre.ebooks.epub.from_feeds:main',
'feeds2mobi = calibre.ebooks.mobi.from_feeds:main',
'web2lrf = calibre.ebooks.lrf.web.convert_from:main', 'web2lrf = calibre.ebooks.lrf.web.convert_from:main',
'pdf2lrf = calibre.ebooks.lrf.pdf.convert_from:main', 'pdf2lrf = calibre.ebooks.lrf.pdf.convert_from:main',
'mobi2lrf = calibre.ebooks.lrf.mobi.convert_from:main', 'mobi2lrf = calibre.ebooks.lrf.mobi.convert_from:main',
@ -56,11 +58,11 @@ entry_points = {
'librarything = calibre.ebooks.metadata.library_thing:main', 'librarything = calibre.ebooks.metadata.library_thing:main',
'mobi2oeb = calibre.ebooks.mobi.reader:main', 'mobi2oeb = calibre.ebooks.mobi.reader:main',
'oeb2mobi = calibre.ebooks.mobi.writer:main', 'oeb2mobi = calibre.ebooks.mobi.writer:main',
'lrf2html = calibre.ebooks.lrf.html.convert_to:main',
'lit2oeb = calibre.ebooks.lit.reader:main', 'lit2oeb = calibre.ebooks.lit.reader:main',
'oeb2lit = calibre.ebooks.lit.writer:main', 'oeb2lit = calibre.ebooks.lit.writer:main',
'comic2lrf = calibre.ebooks.lrf.comic.convert_from:main', 'comic2lrf = calibre.ebooks.lrf.comic.convert_from:main',
'comic2epub = calibre.ebooks.epub.from_comic:main', 'comic2epub = calibre.ebooks.epub.from_comic:main',
'comic2mobi = calibre.ebooks.mobi.from_comic:main',
'comic2pdf = calibre.ebooks.pdf.from_comic:main', 'comic2pdf = calibre.ebooks.pdf.from_comic:main',
'calibre-debug = calibre.debug:main', 'calibre-debug = calibre.debug:main',
'calibredb = calibre.library.cli:main', 'calibredb = calibre.library.cli:main',
@ -189,6 +191,7 @@ def setup_completion(fatal_errors):
from calibre.ebooks.html import option_parser as html2oeb from calibre.ebooks.html import option_parser as html2oeb
from calibre.ebooks.odt.to_oeb import option_parser as odt2oeb 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.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.epub.from_any import option_parser as any2epub
from calibre.ebooks.lit.from_any import option_parser as any2lit from calibre.ebooks.lit.from_any import option_parser as any2lit
from calibre.ebooks.epub.from_comic import option_parser as comic2epub 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('any2epub', any2epub, any_formats))
f.write(opts_and_exts('any2lit', any2lit, 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('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('lrf2lrs', lrf2lrsop, ['lrf']))
f.write(opts_and_exts('lrf-meta', metaop, ['lrf'])) f.write(opts_and_exts('lrf-meta', metaop, ['lrf']))
f.write(opts_and_exts('rtf-meta', metaop, ['rtf'])) f.write(opts_and_exts('rtf-meta', metaop, ['rtf']))
@ -236,10 +239,12 @@ def setup_completion(fatal_errors):
f.write(opts_and_exts('lit2oeb', lit2oeb, ['lit'])) f.write(opts_and_exts('lit2oeb', lit2oeb, ['lit']))
f.write(opts_and_exts('comic2lrf', comicop, ['cbz', 'cbr'])) f.write(opts_and_exts('comic2lrf', comicop, ['cbz', 'cbr']))
f.write(opts_and_exts('comic2epub', comic2epub, ['cbz', 'cbr'])) f.write(opts_and_exts('comic2epub', comic2epub, ['cbz', 'cbr']))
f.write(opts_and_exts('comic2mobi', comic2epub, ['cbz', 'cbr']))
f.write(opts_and_exts('comic2pdf', comic2epub, ['cbz', 'cbr'])) f.write(opts_and_exts('comic2pdf', comic2epub, ['cbz', 'cbr']))
f.write(opts_and_words('feeds2disk', feeds2disk, feed_titles)) f.write(opts_and_words('feeds2disk', feeds2disk, feed_titles))
f.write(opts_and_words('feeds2lrf', feeds2lrf, 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('html2epub', html2epub, ['html', 'htm', 'xhtm', 'xhtml', 'opf']))
f.write(opts_and_exts('html2oeb', html2oeb, ['html', 'htm', 'xhtm', 'xhtml'])) f.write(opts_and_exts('html2oeb', html2oeb, ['html', 'htm', 'xhtm', 'xhtml']))
f.write(opts_and_exts('odt2oeb', odt2oeb, ['odt'])) f.write(opts_and_exts('odt2oeb', odt2oeb, ['odt']))
@ -420,7 +425,7 @@ def install_man_pages(fatal_errors):
if prog in ('prs500', 'pdf-meta', 'epub-meta', 'lit-meta', if prog in ('prs500', 'pdf-meta', 'epub-meta', 'lit-meta',
'markdown-calibre', 'calibre-debug', 'fb2-meta', 'markdown-calibre', 'calibre-debug', 'fb2-meta',
'calibre-fontconfig', 'calibre-parallel', 'odt-meta', 'calibre-fontconfig', 'calibre-parallel', 'odt-meta',
'rb-meta', 'imp-meta'): 'rb-meta', 'imp-meta', 'mobi-meta'):
continue continue
help2man = ('help2man', prog, '--name', 'part of %s'%__appname__, help2man = ('help2man', prog, '--name', 'part of %s'%__appname__,

View File

@ -19,35 +19,35 @@ What formats does |app| support conversion to/from?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|app| supports the conversion of the following formats: |app| supports the conversion of the following formats:
+----------------------------+------------------------------------------+ +----------------------------+------------------------------------------------------------------+
| | **Output formats** | | | **Output formats** |
| +------------------+-----------------------+ | +------------------+-----------------------+-----------------------+
| | EPUB | LRF | | | EPUB | LRF | MOBI |
+===================+========+==================+=======================+ +===================+========+==================+=======================+=======================+
| | MOBI | ✔ | ✔ | | | MOBI | ✔ | ✔ | ✔ |
| | | | | | | | | | |
| | LIT | ✔ | ✔ | | | LIT | ✔ | ✔ | ✔ |
| | | | | | | | | | |
| | PRC | ✔ | ✔ | | | PRC | ✔ | ✔ | ✔ |
| | | | | | | | | | |
| | EPUB | ✔ | ✔ | | | EPUB | ✔ | ✔ | ✔ |
| | | | | | | | | | |
| | ODT | ✔ | ✔ | | | ODT | ✔ | ✔ | ✔ |
| | | | | | | | | | |
| | HTML | ✔ | ✔ | | | HTML | ✔ | ✔ | ✔ |
| | | | | | | | | | |
| **Input formats** | CBR | ✔ | ✔ | | **Input formats** | CBR | ✔ | ✔ | ✔ |
| | | | | | | | | | |
| | CBZ | ✔ | ✔ | | | CBZ | ✔ | ✔ | ✔ |
| | | | | | | | | | |
| | RTF | ✔ | ✔ | | | RTF | ✔ | ✔ | ✔ |
| | | | | | | | | | |
| | TXT | ✔ | ✔ | | | TXT | ✔ | ✔ | ✔ |
| | | | | | | | | | |
| | PDF | ✔ | ✔ | | | PDF | ✔ | ✔ | ✔ |
| | | | | | | | | | |
| | LRS | | ✔ | | | LRS | | ✔ | ✔ |
+-------------------+--------+------------------+-----------------------+ +-------------------+--------+------------------+-----------------------+-----------------------+

View File

@ -67,7 +67,15 @@ PARALLEL_FUNCS = {
'comic2epub' : 'comic2epub' :
('calibre.ebooks.epub.from_comic', 'convert', {}, 'notification'), ('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'),
} }

View File

@ -13,6 +13,10 @@ from gettext import GNUTranslations
import __builtin__ import __builtin__
__builtin__.__dict__['_'] = lambda s: s __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.constants import iswindows, preferred_encoding, plugins
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.translations.msgfmt import make from calibre.translations.msgfmt import make

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
'''
Dynamic language lookup of translations for user-visible strings.
'''
__license__ = 'GPL v3'
__copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
from cStringIO import StringIO
from gettext import GNUTranslations
from calibre.translations.compiled import translations
__all__ = ['translate']
_CACHE = {}
def translate(lang, text):
trans = None
if lang in _CACHE:
trans = _CACHE[lang]
elif lang in translations:
buf = StringIO(translations[lang])
trans = GNUTranslations(buf)
_CACHE[lang] = trans
if trans is None:
return getattr(__builtins__, '_', lambda x: x)(text)
return trans.ugettext(text)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -45,6 +45,8 @@ If you specify this option, any argument to %prog is ignored and a default recip
help='Optimize fetching for subsequent conversion to LRF.') help='Optimize fetching for subsequent conversion to LRF.')
c.add_opt('epub', ['--epub'], default=False, action='store_true', c.add_opt('epub', ['--epub'], default=False, action='store_true',
help='Optimize fetching for subsequent conversion to EPUB.') help='Optimize fetching for subsequent conversion to EPUB.')
c.add_opt('mobi', ['--mobi'], default=False, action='store_true',
help='Optimize fetching for subsequent conversion to MOBI.')
c.add_opt('recursions', ['--recursions'], default=0, c.add_opt('recursions', ['--recursions'], default=0,
help=_('Number of levels of links to follow on webpages that are linked to from feeds. Defaul %default')) help=_('Number of levels of links to follow on webpages that are linked to from feeds. Defaul %default'))
c.add_opt('output_dir', ['--output-dir'], default='.', c.add_opt('output_dir', ['--output-dir'], default='.',

View File

@ -20,7 +20,7 @@ from PyQt4.QtWebKit import QWebPage
from calibre import browser, __appname__, iswindows, LoggingInterface, \ from calibre import browser, __appname__, iswindows, LoggingInterface, \
strftime, __version__, preferred_encoding strftime, __version__, preferred_encoding
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, CData, Tag from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString, CData, Tag
from calibre.ebooks.metadata.opf import OPFCreator from calibre.ebooks.metadata.opf2 import OPFCreator
from calibre.ebooks.lrf import entity_to_unicode from calibre.ebooks.lrf import entity_to_unicode
from calibre.ebooks.metadata.toc import TOC from calibre.ebooks.metadata.toc import TOC
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
@ -152,6 +152,8 @@ class BasicNewsRecipe(object, LoggingInterface):
#: Options to pass to html2epub to customize generation of EPUB ebooks. #: Options to pass to html2epub to customize generation of EPUB ebooks.
html2epub_options = '' html2epub_options = ''
#: Options to pass to oeb2mobi to customize generation of MOBI ebooks.
oeb2mobi_options = ''
#: List of tags to be removed. Specified tags are removed from downloaded HTML. #: List of tags to be removed. Specified tags are removed from downloaded HTML.
#: A tag is specified as a dictionary of the form:: #: A tag is specified as a dictionary of the form::
@ -532,7 +534,9 @@ class BasicNewsRecipe(object, LoggingInterface):
if body is not None: if body is not None:
templ = self.navbar.generate(False, f, a, feed_len, templ = self.navbar.generate(False, f, a, feed_len,
not self.has_single_feed, not self.has_single_feed,
url, __appname__, center=self.center_navbar) url, __appname__,
center=self.center_navbar,
extra_css=self.extra_css)
elem = BeautifulSoup(templ.render(doctype='xhtml').decode('utf-8')).find('div') elem = BeautifulSoup(templ.render(doctype='xhtml').decode('utf-8')).find('div')
body.insert(0, elem) body.insert(0, elem)
if self.remove_javascript: if self.remove_javascript:
@ -575,7 +579,8 @@ class BasicNewsRecipe(object, LoggingInterface):
def feeds2index(self, feeds): def feeds2index(self, feeds):
templ = templates.IndexTemplate() templ = templates.IndexTemplate()
return templ.generate(self.title, self.timefmt, feeds).render(doctype='xhtml') return templ.generate(self.title, self.timefmt, feeds,
extra_css=self.extra_css).render(doctype='xhtml')
@classmethod @classmethod
def description_limiter(cls, src): def description_limiter(cls, src):
@ -626,7 +631,8 @@ class BasicNewsRecipe(object, LoggingInterface):
templ = templates.FeedTemplate() templ = templates.FeedTemplate()
return templ.generate(feed, self.description_limiter).render(doctype='xhtml') return templ.generate(feed, self.description_limiter,
extra_css=self.extra_css).render(doctype='xhtml')
def create_logger(self, feed_number, article_number): def create_logger(self, feed_number, article_number):
@ -872,6 +878,7 @@ class BasicNewsRecipe(object, LoggingInterface):
manifest = [os.path.join(dir, 'feed_%d'%i) for i in range(len(feeds))] manifest = [os.path.join(dir, 'feed_%d'%i) for i in range(len(feeds))]
manifest.append(os.path.join(dir, 'index.html')) manifest.append(os.path.join(dir, 'index.html'))
manifest.append(os.path.join(dir, 'index.ncx'))
cpath = getattr(self, 'cover_path', None) cpath = getattr(self, 'cover_path', None)
if cpath is None: if cpath is None:
pf = PersistentTemporaryFile('_recipe_cover.jpg') pf = PersistentTemporaryFile('_recipe_cover.jpg')
@ -881,6 +888,9 @@ class BasicNewsRecipe(object, LoggingInterface):
opf.cover = cpath opf.cover = cpath
manifest.append(cpath) manifest.append(cpath)
opf.create_manifest_from_files_in(manifest) opf.create_manifest_from_files_in(manifest)
for mani in opf.manifest:
if mani.path.endswith('.ncx'):
mani.id = 'ncx'
entries = ['index.html'] entries = ['index.html']
toc = TOC(base_path=dir) toc = TOC(base_path=dir)

View File

@ -23,7 +23,8 @@ recipe_modules = ['recipe_' + r for r in (
'spiegel_int', 'themarketticker', 'tomshardware', 'xkcd', 'ftd', 'zdnet', 'spiegel_int', 'themarketticker', 'tomshardware', 'xkcd', 'ftd', 'zdnet',
'joelonsoftware', 'telepolis', 'common_dreams', 'nin', 'tomshardware_de', 'joelonsoftware', 'telepolis', 'common_dreams', 'nin', 'tomshardware_de',
'pagina12', 'infobae', 'ambito', 'elargentino', 'sueddeutsche', 'the_age', 'pagina12', 'infobae', 'ambito', 'elargentino', 'sueddeutsche', 'the_age',
'laprensa', 'laprensa', 'amspec', 'freakonomics', 'criticadigital', 'elcronista',
'shacknews',
)] )]
import re, imp, inspect, time, os import re, imp, inspect, time, os

View File

@ -0,0 +1,53 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
spectator.org
'''
from calibre.web.feeds.news import BasicNewsRecipe
class TheAmericanSpectator(BasicNewsRecipe):
title = 'The American Spectator'
__author__ = 'Darko Miletic'
description = 'news from USA'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
INDEX = 'http://spectator.org'
html2lrf_options = [
'--comment' , description
, '--category' , 'news, politics, USA'
, '--publisher' , title
]
keep_only_tags = [
dict(name='div', attrs={'class':'post inner'})
,dict(name='div', attrs={'class':'author-bio'})
]
remove_tags = [
dict(name='object')
,dict(name='div', attrs={'class':'col3' })
,dict(name='div', attrs={'class':'post-options' })
,dict(name='p' , attrs={'class':'letter-editor'})
,dict(name='div', attrs={'class':'social' })
]
feeds = [ (u'Articles', u'http://feedproxy.google.com/amspecarticles')]
def get_cover_url(self):
cover_url = None
soup = self.index_to_soup(self.INDEX)
link_item = soup.find('a',attrs={'class':'cover'})
if link_item:
soup2 = self.index_to_soup(link_item['href'])
link_item2 = soup2.find('div',attrs={'class':'post inner issues'})
cover_url = self.INDEX + link_item2.img['src']
return cover_url
def print_version(self, url):
return url + '/print'

View File

@ -0,0 +1,60 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
'''
criticadigital.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class CriticaDigital(BasicNewsRecipe):
title = 'Critica de la Argentina'
__author__ = 'Darko Miletic'
description = 'Noticias de Argentina'
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'cp1252'
html2lrf_options = [
'--comment' , description
, '--category' , 'news, Argentina'
, '--publisher' , title
]
keep_only_tags = [
dict(name='div', attrs={'class':'bloqueTitulosNoticia'})
,dict(name='div', attrs={'id':'c453-1' })
]
remove_tags = [
dict(name='div', attrs={'class':'box300' })
,dict(name='div', style=True )
,dict(name='div', attrs={'class':'titcomentario'})
,dict(name='div', attrs={'class':'comentario' })
,dict(name='div', attrs={'class':'paginador' })
]
feeds = [
(u'Politica', u'http://www.criticadigital.com/herramientas/rss.php?ch=politica' )
,(u'Economia', u'http://www.criticadigital.com/herramientas/rss.php?ch=economia' )
,(u'Deportes', u'http://www.criticadigital.com/herramientas/rss.php?ch=deportes' )
,(u'Espectaculos', u'http://www.criticadigital.com/herramientas/rss.php?ch=espectaculos')
,(u'Mundo', u'http://www.criticadigital.com/herramientas/rss.php?ch=mundo' )
,(u'Policiales', u'http://www.criticadigital.com/herramientas/rss.php?ch=policiales' )
,(u'Sociedad', u'http://www.criticadigital.com/herramientas/rss.php?ch=sociedad' )
,(u'Salud', u'http://www.criticadigital.com/herramientas/rss.php?ch=salud' )
,(u'Tecnologia', u'http://www.criticadigital.com/herramientas/rss.php?ch=tecnologia' )
,(u'Santa Fe', u'http://www.criticadigital.com/herramientas/rss.php?ch=santa_fe' )
]
def get_cover_url(self):
cover_url = None
index = 'http://www.criticadigital.com/impresa/'
soup = self.index_to_soup(index)
link_item = soup.find('div',attrs={'class':'tapa'})
if link_item:
cover_url = index + link_item.img['src']
return cover_url

View File

@ -0,0 +1,70 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
'''
cronista.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class ElCronista(BasicNewsRecipe):
title = 'El Cronista'
__author__ = 'Darko Miletic'
description = 'Noticias de Argentina'
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'cp1252'
html2lrf_options = [
'--comment' , description
, '--category' , 'news, Argentina'
, '--publisher' , title
]
keep_only_tags = [
dict(name='table', attrs={'width':'100%' })
,dict(name='h1' , attrs={'class':'Arialgris16normal'})
]
remove_tags = [dict(name='a', attrs={'class':'Arialazul12'})]
feeds = [
(u'Economia' , u'http://www.cronista.com/adjuntos/8/rss/Economia_EI.xml' )
,(u'Negocios' , u'http://www.cronista.com/adjuntos/8/rss/negocios_EI.xml' )
,(u'Ultimo momento' , u'http://www.cronista.com/adjuntos/8/rss/ultimo_momento.xml' )
,(u'Finanzas y Mercados' , u'http://www.cronista.com/adjuntos/8/rss/Finanzas_Mercados_EI.xml' )
,(u'Financial Times' , u'http://www.cronista.com/adjuntos/8/rss/FT_EI.xml' )
,(u'Opinion edicion impresa' , u'http://www.cronista.com/adjuntos/8/rss/opinion_edicion_impresa.xml' )
,(u'Socialmente Responsables', u'http://www.cronista.com/adjuntos/8/rss/Socialmente_Responsables.xml')
,(u'Asuntos Legales' , u'http://www.cronista.com/adjuntos/8/rss/asuntoslegales.xml' )
,(u'IT Business' , u'http://www.cronista.com/adjuntos/8/rss/itbusiness.xml' )
,(u'Management y RR.HH.' , u'http://www.cronista.com/adjuntos/8/rss/management.xml' )
,(u'Inversiones Personales' , u'http://www.cronista.com/adjuntos/8/rss/inversionespersonales.xml' )
]
def print_version(self, url):
main, sep, rest = url.partition('.com/notas/')
article_id, lsep, rrest = rest.partition('-')
return 'http://www.cronista.com/interior/index.php?p=imprimir_nota&idNota=' + article_id
def preprocess_html(self, soup):
mtag = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">'
soup.head.insert(0,mtag)
soup.head.base.extract()
htext = soup.find('h1',attrs={'class':'Arialgris16normal'})
htext.name = 'p'
soup.prettify()
return soup
def get_cover_url(self):
cover_url = None
index = 'http://www.cronista.com/contenidos/'
soup = self.index_to_soup(index + 'ee.html')
link_item = soup.find('a',attrs={'href':"javascript:Close()"})
if link_item:
cover_url = index + link_item.img['src']
return cover_url

View File

@ -11,7 +11,7 @@ class FazNet(BasicNewsRecipe):
title = 'FAZ NET' title = 'FAZ NET'
__author__ = 'Kovid Goyal' __author__ = 'Kovid Goyal'
description = 'News from Germany' description = '"Frankfurter Allgemeine Zeitung'
use_embedded_content = False use_embedded_content = False
max_articles_per_feed = 30 max_articles_per_feed = 30

View File

@ -0,0 +1,20 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
from calibre.web.feeds.news import BasicNewsRecipe
class Freakonomics(BasicNewsRecipe):
title = 'Freakonomics Blog'
description = 'The Hidden side of everything'
__author__ = 'Kovid Goyal'
feeds = [('Blog', 'http://freakonomics.blogs.nytimes.com/feed/atom/')]
def get_article_url(self, article):
return article.get('feedburner_origlink', None)
def print_version(self, url):
return url + '?pagemode=print'

View File

@ -16,6 +16,14 @@ class NewYorker(BasicNewsRecipe):
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = False no_stylesheets = False
use_embedded_content = False use_embedded_content = False
extra_css = '''
.calibre_feed_list {font-size:xx-small}
.calibre_article_list {font-size:xx-small}
.calibre_feed_title {font-size:normal}
.calibre_recipe_title {font-size:normal}
.calibre_feed_description {font-size:xx-small}
'''
keep_only_tags = [ keep_only_tags = [
dict(name='div' , attrs={'id':'printbody' }) dict(name='div' , attrs={'id':'printbody' })

View File

@ -19,9 +19,16 @@ class Newsweek(BasicNewsRecipe):
remove_tags = [ remove_tags = [
dict(name=['script', 'noscript']), dict(name=['script', 'noscript']),
dict(name='div', attrs={'class':['ad', 'SocialLinks', 'SocialLinksDiv', 'channel', 'bot', 'nav', 'top', 'EmailArticleBlock']}), dict(name='div', attrs={'class':['ad', 'SocialLinks', 'SocialLinksDiv',
'channel', 'bot', 'nav', 'top',
'EmailArticleBlock',
'comments-and-social-links-wrapper',
'inline-social-links-wrapper',
'inline-social-links',
]}),
dict(name='div', attrs={'class':re.compile('box')}), dict(name='div', attrs={'class':re.compile('box')}),
dict(id=['ToolBox', 'EmailMain', 'EmailArticle', ]) dict(id=['ToolBox', 'EmailMain', 'EmailArticle', 'comment-box',
'nw-comments'])
] ]
recursions = 1 recursions = 1

View File

@ -0,0 +1,26 @@
from calibre.web.feeds.news import BasicNewsRecipe
class Shacknews(BasicNewsRecipe):
__author__ = 'Docbrown00'
__license__ = 'GPL v3'
title = u'Shacknews'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
remove_tags = [dict(name='div', attrs={'class': ['nuggets', 'comments']}),
dict(name='p', attrs={'class': 'videoembed'})]
keep_only_tags = [dict(name='div', attrs={'class':'story'})]
feeds = [
(u'Latest News', u'http://feed.shacknews.com/shackfeed.xml'),
(u'PC', u'http://feed.shacknews.com/extras/tag_rss.x/PC'),
(u'Wii', u'http://feed.shacknews.com/extras/tag_rss.x/Nintendo+Wii'),
(u'Xbox 360', u'http://feed.shacknews.com/extras/tag_rss.x/Xbox+360'),
(u'Playstation 3', u'http://feed.shacknews.com/extras/tag_rss.x/PlayStation+3'),
(u'PSP', u'http://feed.shacknews.com/extras/tag_rss.x/PSP'),
(u'Nintendo DS', u'http://feed.shacknews.com/extras/tag_rss.x/Nintendo+DS'),
(u'iPhone', u'http://feed.shacknews.com/extras/tag_rss.x/iPhone'),
(u'DLC', u'http://feed.shacknews.com/extras/tag_rss.x/DLC'),
(u'Valve', u'http://feed.shacknews.com/extras/tag_rss.x/Valve'),
(u'Electronic Arts', u'http://feed.shacknews.com/extras/tag_rss.x/Electronic+Arts')
]

View File

@ -13,7 +13,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
class SpeigelOnline(BasicNewsRecipe): class SpeigelOnline(BasicNewsRecipe):
title = 'Spiegel Online' title = 'Spiegel Online'
description = 'News from Germany' description = 'Nachrichten des Magazins Der Spiegel'
__author__ = 'Kovid Goyal' __author__ = 'Kovid Goyal'
use_embedded_content = False use_embedded_content = False
timefmt = ' [ %Y-%m-%d %a]' timefmt = ' [ %Y-%m-%d %a]'

View File

@ -5,7 +5,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
Fetch xkcd. Fetch xkcd.
''' '''
import time import time, re
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class XkcdCom(BasicNewsRecipe): class XkcdCom(BasicNewsRecipe):
@ -17,6 +17,11 @@ class XkcdCom(BasicNewsRecipe):
keep_only_tags = [dict(id='middleContent')] keep_only_tags = [dict(id='middleContent')]
remove_tags = [dict(name='ul'), dict(name='h3'), dict(name='br')] remove_tags = [dict(name='ul'), dict(name='h3'), dict(name='br')]
no_stylesheets = True no_stylesheets = True
# turn image bubblehelp into a paragraph
preprocess_regexps = [
(re.compile(r'(<img.*title=")([^"]+)(".*>)'),
lambda m: '%s%s<p>%s</p>' % (m.group(1), m.group(3), m.group(2)))
]
def parse_index(self): def parse_index(self):
INDEX = 'http://xkcd.com/archive/' INDEX = 'http://xkcd.com/archive/'

View File

@ -11,7 +11,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
class ZeitDe(BasicNewsRecipe): class ZeitDe(BasicNewsRecipe):
title = 'Die Zeit Nachrichten' title = 'Die Zeit Nachrichten'
description = 'News from Germany' description = 'Die Zeit - Online Nachrichten'
__author__ = 'Kovid Goyal' __author__ = 'Kovid Goyal'
use_embedded_content = False use_embedded_content = False
timefmt = ' [%d %b %Y]' timefmt = ' [%d %b %Y]'

View File

@ -32,6 +32,11 @@ class NavBarTemplate(Template):
xmlns:py="http://genshi.edgewall.org/" xmlns:py="http://genshi.edgewall.org/"
> >
<head>
<style py:if="extra_css" type="text/css">
${extra_css}
</style>
</head>
<body> <body>
<div class="navbar" style="text-align:${'center' if center else 'left'};"> <div class="navbar" style="text-align:${'center' if center else 'left'};">
<hr py:if="bottom" /> <hr py:if="bottom" />
@ -60,14 +65,15 @@ class NavBarTemplate(Template):
''') ''')
def generate(self, bottom, feed, art, number_of_articles_in_feed, def generate(self, bottom, feed, art, number_of_articles_in_feed,
two_levels, url, __appname__, prefix='', center=True): two_levels, url, __appname__, prefix='', center=True,
extra_css=None):
if prefix and not prefix.endswith('/'): if prefix and not prefix.endswith('/'):
prefix += '/' prefix += '/'
return Template.generate(self, bottom=bottom, art=art, feed=feed, return Template.generate(self, bottom=bottom, art=art, feed=feed,
num=number_of_articles_in_feed, num=number_of_articles_in_feed,
two_levels=two_levels, url=url, two_levels=two_levels, url=url,
__appname__=__appname__, prefix=prefix, __appname__=__appname__, prefix=prefix,
center=center) center=center, extra_css=extra_css)
class IndexTemplate(Template): class IndexTemplate(Template):
@ -88,11 +94,14 @@ class IndexTemplate(Template):
<style py:if="style" type="text/css"> <style py:if="style" type="text/css">
${style} ${style}
</style> </style>
<style py:if="extra_css" type="text/css">
${extra_css}
</style>
</head> </head>
<body> <body>
<h1>${title}</h1> <h1 class="calibre_recipe_title">${title}</h1>
<p style="text-align:right">${date}</p> <p style="text-align:right">${date}</p>
<ul> <ul class="calibre_feed_list">
<py:for each="i, feed in enumerate(feeds)"> <py:for each="i, feed in enumerate(feeds)">
<li py:if="feed" id="feed_${str(i)}"> <li py:if="feed" id="feed_${str(i)}">
<a class="feed" href="${'feed_%d/index.html'%i}">${feed.title}</a> <a class="feed" href="${'feed_%d/index.html'%i}">${feed.title}</a>
@ -103,11 +112,12 @@ class IndexTemplate(Template):
</html> </html>
''') ''')
def generate(self, title, datefmt, feeds): def generate(self, title, datefmt, feeds, extra_css=None):
if isinstance(datefmt, unicode): if isinstance(datefmt, unicode):
datefmt = datefmt.encode(preferred_encoding) datefmt = datefmt.encode(preferred_encoding)
date = strftime(datefmt) date = strftime(datefmt)
return Template.generate(self, title=title, date=date, feeds=feeds) return Template.generate(self, title=title, date=date, feeds=feeds,
extra_css=extra_css)
class FeedTemplate(Template): class FeedTemplate(Template):
@ -128,18 +138,21 @@ class FeedTemplate(Template):
<style py:if="style" type="text/css"> <style py:if="style" type="text/css">
${style} ${style}
</style> </style>
<style py:if="extra_css" type="text/css">
${extra_css}
</style>
</head> </head>
<body style="page-break-before:always"> <body style="page-break-before:always">
<h2 class="feed_title">${feed.title}</h2> <h2 class="calibre_feed_title">${feed.title}</h2>
<py:if test="getattr(feed, 'image', None)"> <py:if test="getattr(feed, 'image', None)">
<div class="feed_image"> <div class="calibre_feed_image">
<img alt="${feed.image_alt}" src="${feed.image_url}" /> <img alt="${feed.image_alt}" src="${feed.image_url}" />
</div> </div>
</py:if> </py:if>
<div py:if="getattr(feed, 'description', None)"> <div class="calibre_feed_description" py:if="getattr(feed, 'description', None)">
${feed.description}<br /> ${feed.description}<br />
</div> </div>
<ul> <ul class="calibre_article_list">
<py:for each="i, article in enumerate(feed.articles)"> <py:for each="i, article in enumerate(feed.articles)">
<li id="${'article_%d'%i}" py:if="getattr(article, 'downloaded', False)" style="padding-bottom:0.5em"> <li id="${'article_%d'%i}" py:if="getattr(article, 'downloaded', False)" style="padding-bottom:0.5em">
<a class="article" href="${article.url}">${article.title}</a> <a class="article" href="${article.url}">${article.title}</a>
@ -157,8 +170,9 @@ class FeedTemplate(Template):
</html> </html>
''') ''')
def generate(self, feed, cutoff): def generate(self, feed, cutoff, extra_css=None):
return Template.generate(self, feed=feed, cutoff=cutoff) return Template.generate(self, feed=feed, cutoff=cutoff,
extra_css=extra_css)
class EmbeddedContent(Template): class EmbeddedContent(Template):

Some files were not shown because too many files have changed in this diff Show More