mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge upstream changes
This commit is contained in:
commit
fc85bb5a20
@ -2,8 +2,9 @@
|
||||
<?eclipse-pydev version="1.0"?>
|
||||
|
||||
<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">
|
||||
<path>/calibre/src</path>
|
||||
</pydev_pathproperty>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
|
||||
</pydev_project>
|
||||
|
@ -8,22 +8,25 @@ __docformat__ = 'restructuredtext en'
|
||||
import sys, time, subprocess, os, re
|
||||
from calibre import __appname__, __version__
|
||||
|
||||
INSTALLJAMMER = '/usr/local/installjammer/installjammer'
|
||||
|
||||
sv = re.sub(r'[a-z]\d+', '', __version__)
|
||||
|
||||
cmdline = [
|
||||
'/usr/local/installjammer/installjammer',
|
||||
INSTALLJAMMER,
|
||||
'--build-dir', '/tmp/calibre-installjammer',
|
||||
'-DAppName', __appname__,
|
||||
'-DShortAppName', __appname__,
|
||||
'-DApplicationURL', 'http://%s.kovidgoyal.net'%__appname__,
|
||||
'-DCopyright', time.strftime('%Y Kovid Goyal'),
|
||||
'-DPackageDescription', '%s is an e-book library manager. It can view, convert and catalog e-books in most of the major e-book formats. It can also talk to a few e-book reader devices. It can go out to the internet and fetch metadata for your books. It can download newspapers and convert them into e-books for convenient reading.'%__appname__,
|
||||
'-DPackageDescription', '%s is an e-book library manager. It can view, convert and catalog e-books in most of the major e-book formats. It can also talk to e-book reader devices. It can go out to the internet and fetch metadata for your books. It can download newspapers and convert them into e-books for convenient reading.'%__appname__,
|
||||
'-DPackageSummary', '%s: E-book library management'%__appname__,
|
||||
'-DVersion', __version__,
|
||||
'-DInstallVersion', sv + '.0',
|
||||
'-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'),
|
||||
'--platform', 'Windows',
|
||||
'--verbose'
|
||||
]
|
||||
|
||||
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)
|
||||
|
||||
def main(args=sys.argv):
|
||||
run_install_jammer(build_for_release=False)
|
||||
run_install_jammer(build_for_release=True)
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -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
|
||||
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
|
||||
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 7CCDA4BB-861C-C21E-3011-E93DB58F07D6 -setup Install -type action -title {Check for Previous Install} -component CheckForPreviousInstall -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A
|
||||
InstallComponent 3EA07B17-04D8-6508-B535-96CC7173B49A -setup Install -type pane -conditions D7F585DB-0DEC-A94E-DAB0-94D558D82764 -title {Welcome Screen} -component Welcome -command insert -active Yes -parent StandardInstall
|
||||
Condition D7F585DB-0DEC-A94E-DAB0-94D558D82764 -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A -title {Execute Script Condition} -component ExecuteScriptCondition -TreeObject::id D7F585DB-0DEC-A94E-DAB0-94D558D82764
|
||||
InstallComponent 7CCDA4BB-861C-C21E-3011-E93DB58F07D6 -setup Install -type action -conditions ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E -title {Check for Previous Install} -component CheckForPreviousInstall -command reorder -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A
|
||||
Condition ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E -active Yes -parent 7CCDA4BB-861C-C21E-3011-E93DB58F07D6 -title {Execute Script Condition} -component ExecuteScriptCondition -TreeObject::id ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E
|
||||
InstallComponent 580ACF2C-517F-5E48-9DEF-7DAEFBA59FDD -setup Install -type action -conditions 6DE3B369-9D6B-6BC1-4EA0-2C54ECE159EB -title {Set Virtual Text} -component SetVirtualText -command insert -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A
|
||||
Condition 6DE3B369-9D6B-6BC1-4EA0-2C54ECE159EB -active Yes -parent 580ACF2C-517F-5E48-9DEF-7DAEFBA59FDD -title {String Is Condition} -component StringIsCondition -TreeObject::id 6DE3B369-9D6B-6BC1-4EA0-2C54ECE159EB
|
||||
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
|
||||
|
||||
3EA07B17-04D8-6508-B535-96CC7173B49A,Conditions
|
||||
{0 conditions}
|
||||
{1 condition}
|
||||
|
||||
3EA07B17-04D8-6508-B535-96CC7173B49A,Message,subst
|
||||
1
|
||||
@ -527,6 +868,9 @@ false
|
||||
3F2A14F0-06AC-C9D3-1F07-311F41E1338E,Alias
|
||||
{Startup Actions}
|
||||
|
||||
3F2A14F0-06AC-C9D3-1F07-311F41E1338E,Conditions
|
||||
{0 conditions}
|
||||
|
||||
442920D9-8A51-9476-14E4-787D5C230E84,Message,subst
|
||||
1
|
||||
|
||||
@ -867,10 +1211,10 @@ LaunchApplication
|
||||
160
|
||||
|
||||
7CCDA4BB-861C-C21E-3011-E93DB58F07D6,Conditions
|
||||
{0 conditions}
|
||||
{1 condition}
|
||||
|
||||
7CCDA4BB-861C-C21E-3011-E93DB58F07D6,ExecuteAction
|
||||
{Before Pane is Displayed}
|
||||
{Before Next Pane is Displayed}
|
||||
|
||||
825CDD1E-9B3D-E64E-5381-5C5557D204A2,CheckCondition
|
||||
{Before Action is Executed}
|
||||
@ -1097,9 +1441,29 @@ AAF04AF0-329D-75A6-BB68-60ECF2EB74F0,String
|
||||
AAFE58A0-2DFB-CA20-1F6E-D3815F885996,Alias
|
||||
{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
|
||||
No
|
||||
|
||||
AIX-ppc,BuildSeparateArchives
|
||||
No
|
||||
|
||||
AIX-ppc,DefaultDirectoryPermission
|
||||
0755
|
||||
|
||||
@ -1286,6 +1650,26 @@ CFBE4459-450B-1FAB-3422-609544334AA2,String
|
||||
D79DC0D2-38BC-9D9F-2DF4-3C76D89BF933,ExitType
|
||||
Finish
|
||||
|
||||
D7F585DB-0DEC-A94E-DAB0-94D558D82764,CheckCondition
|
||||
{Before Next Pane is Displayed}
|
||||
|
||||
D7F585DB-0DEC-A94E-DAB0-94D558D82764,Comment
|
||||
{Check if calibre.exe is still running}
|
||||
|
||||
D7F585DB-0DEC-A94E-DAB0-94D558D82764,FailureMessage
|
||||
{calibre is still running. Please shut it down before proceeding. You can quit calibre by right clicking on the calibre system tray icon.}
|
||||
|
||||
D7F585DB-0DEC-A94E-DAB0-94D558D82764,ResultVirtualText
|
||||
CalibreRunning
|
||||
|
||||
D7F585DB-0DEC-A94E-DAB0-94D558D82764,Script
|
||||
{set pid [::InstallAPI::FindProcesses -name calibre.exe]
|
||||
if {$pid eq ""} {
|
||||
## myapp.exe is not running
|
||||
return 1
|
||||
}
|
||||
return 0}
|
||||
|
||||
D86BBA5C-4903-33BA-59F8-4266A3D45896,Conditions
|
||||
{2 conditions}
|
||||
|
||||
@ -1475,6 +1859,9 @@ FBA33088-C809-DD6B-D337-EADBF1CEE966,String
|
||||
FreeBSD-4-x86,Active
|
||||
No
|
||||
|
||||
FreeBSD-4-x86,BuildSeparateArchives
|
||||
No
|
||||
|
||||
FreeBSD-4-x86,DefaultDirectoryPermission
|
||||
0755
|
||||
|
||||
@ -1526,6 +1913,9 @@ FreeBSD-4-x86,RootInstallDir
|
||||
FreeBSD-x86,Active
|
||||
No
|
||||
|
||||
FreeBSD-x86,BuildSeparateArchives
|
||||
No
|
||||
|
||||
FreeBSD-x86,DefaultDirectoryPermission
|
||||
0755
|
||||
|
||||
@ -1577,6 +1967,9 @@ FreeBSD-x86,RootInstallDir
|
||||
HPUX-hppa,Active
|
||||
No
|
||||
|
||||
HPUX-hppa,BuildSeparateArchives
|
||||
No
|
||||
|
||||
HPUX-hppa,DefaultDirectoryPermission
|
||||
0755
|
||||
|
||||
@ -1628,6 +2021,9 @@ HPUX-hppa,RootInstallDir
|
||||
Linux-x86,Active
|
||||
No
|
||||
|
||||
Linux-x86,BuildSeparateArchives
|
||||
No
|
||||
|
||||
Linux-x86,DefaultDirectoryPermission
|
||||
0755
|
||||
|
||||
@ -1679,6 +2075,9 @@ Linux-x86,RootInstallDir
|
||||
Solaris-sparc,Active
|
||||
No
|
||||
|
||||
Solaris-sparc,BuildSeparateArchives
|
||||
No
|
||||
|
||||
Solaris-sparc,DefaultDirectoryPermission
|
||||
0755
|
||||
|
||||
@ -1730,6 +2129,9 @@ Solaris-sparc,RootInstallDir
|
||||
TarArchive,Active
|
||||
No
|
||||
|
||||
TarArchive,BuildSeparateArchives
|
||||
No
|
||||
|
||||
TarArchive,CompressionLevel
|
||||
6
|
||||
|
||||
@ -1790,9 +2192,15 @@ TarArchive,VirtualTextMap
|
||||
Windows,Active
|
||||
Yes
|
||||
|
||||
Windows,BuildSeparateArchives
|
||||
No
|
||||
|
||||
Windows,Executable
|
||||
<%AppName%>-<%Version%><%Ext%>
|
||||
|
||||
Windows,FileDescription
|
||||
{<%AppName%> <%Version%> Setup}
|
||||
|
||||
Windows,IncludeTWAPI
|
||||
Yes
|
||||
|
||||
@ -1829,6 +2237,9 @@ Windows,WindowsIcon
|
||||
ZipArchive,Active
|
||||
No
|
||||
|
||||
ZipArchive,BuildSeparateArchives
|
||||
No
|
||||
|
||||
ZipArchive,CompressionLevel
|
||||
6
|
||||
|
||||
|
2
setup.py
2
setup.py
@ -121,7 +121,7 @@ if __name__ == '__main__':
|
||||
buf = cStringIO.StringIO()
|
||||
print 'Creating translations template'
|
||||
tempdir = tempfile.mkdtemp()
|
||||
pygettext(buf, ['-p', tempdir]+files)
|
||||
pygettext(buf, ['-k', '__', '-p', tempdir]+files)
|
||||
src = buf.getvalue()
|
||||
pot = os.path.join(tempdir, 'calibre.pot')
|
||||
f = open(pot, 'wb')
|
||||
|
@ -20,6 +20,7 @@ import mechanize
|
||||
mimetypes.add_type('application/epub+zip', '.epub')
|
||||
mimetypes.add_type('text/x-sony-bbeb+xml', '.lrs')
|
||||
mimetypes.add_type('application/x-sony-bbeb', '.lrf')
|
||||
mimetypes.add_type('application/x-dtbncx+xml', '.ncx')
|
||||
|
||||
def to_unicode(raw, encoding='utf-8', errors='strict'):
|
||||
if isinstance(raw, unicode):
|
||||
|
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = 'calibre'
|
||||
__version__ = '0.4.129'
|
||||
__version__ = '0.4.130'
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
'''
|
||||
Various run time constants.
|
||||
|
@ -132,7 +132,7 @@ class HTMLMetadataReader(MetadataReaderPlugin):
|
||||
class MOBIMetadataReader(MetadataReaderPlugin):
|
||||
|
||||
name = 'Read MOBI metadata'
|
||||
file_types = set(['mobi'])
|
||||
file_types = set(['mobi', 'prc'])
|
||||
description = _('Read metadata from %s files')%'MOBI'
|
||||
|
||||
def get_metadata(self, stream, ftype):
|
||||
@ -189,11 +189,22 @@ class ZipMetadataReader(MetadataReaderPlugin):
|
||||
from calibre.ebooks.metadata.zip import get_metadata
|
||||
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):
|
||||
|
||||
name = 'Set EPUB metadata'
|
||||
file_types = set(['epub'])
|
||||
description = _('Set metadata in EPUB files')
|
||||
description = _('Set metadata in %s files')%'EPUB'
|
||||
|
||||
def set_metadata(self, stream, mi, type):
|
||||
from calibre.ebooks.metadata.epub import set_metadata
|
||||
@ -203,7 +214,7 @@ class LRFMetadataWriter(MetadataWriterPlugin):
|
||||
|
||||
name = 'Set LRF metadata'
|
||||
file_types = set(['lrf'])
|
||||
description = _('Set metadata in LRF files')
|
||||
description = _('Set metadata in %s files')%'LRF'
|
||||
|
||||
def set_metadata(self, stream, mi, type):
|
||||
from calibre.ebooks.lrf.meta import set_metadata
|
||||
@ -213,12 +224,24 @@ class RTFMetadataWriter(MetadataWriterPlugin):
|
||||
|
||||
name = 'Set RTF metadata'
|
||||
file_types = set(['rtf'])
|
||||
description = _('Set metadata in RTF files')
|
||||
description = _('Set metadata in %s files')%'RTF'
|
||||
|
||||
def set_metadata(self, stream, mi, type):
|
||||
from calibre.ebooks.metadata.rtf import set_metadata
|
||||
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 += [x for x in list(locals().values()) if isinstance(x, type) and \
|
||||
x.__name__.endswith('MetadataReader')]
|
||||
|
@ -7,8 +7,10 @@ Device driver for Bookeen's Cybook Gen 3
|
||||
import os, shutil
|
||||
from itertools import cycle
|
||||
|
||||
from calibre.devices.errors import FreeSpaceError
|
||||
from calibre.devices.usbms.driver import USBMS
|
||||
import calibre.devices.cybookg3.t2b as t2b
|
||||
from calibre.devices.errors import FreeSpaceError
|
||||
|
||||
class CYBOOKG3(USBMS):
|
||||
# Ordered list of supported formats
|
||||
@ -30,6 +32,7 @@ class CYBOOKG3(USBMS):
|
||||
STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card'
|
||||
|
||||
EBOOK_DIR_MAIN = "eBooks"
|
||||
EBOOK_DIR_CARD = "eBooks"
|
||||
SUPPORTS_SUB_DIRS = True
|
||||
|
||||
def upload_books(self, files, names, on_card=False, end_session=True,
|
||||
@ -50,10 +53,10 @@ class CYBOOKG3(USBMS):
|
||||
return size
|
||||
return os.path.getsize(obj)
|
||||
|
||||
sizes = map(get_size, files)
|
||||
sizes = [get_size(f) for f in files]
|
||||
size = sum(sizes)
|
||||
|
||||
if on_card and size > self.free_space()[2] - 1024*1024:
|
||||
if on_card and size > self.free_space()[2] - 1024*1024:
|
||||
raise FreeSpaceError(_("There is insufficient free space on the storage card"))
|
||||
if not on_card and size > self.free_space()[0] - 2*1024*1024:
|
||||
raise FreeSpaceError(_("There is insufficient free space in main memory"))
|
||||
|
@ -18,6 +18,9 @@ class Book(object):
|
||||
self.thumbnail = None
|
||||
self.tags = []
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.path == other.path
|
||||
|
||||
@apply
|
||||
def title_sorter():
|
||||
doc = '''String to sort the title. If absent, title is returned'''
|
||||
|
@ -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
|
||||
intended to be subclassed with the relevant parts implemented for a particular
|
||||
device. This class handles devive detection.
|
||||
device. This class handles device detection.
|
||||
'''
|
||||
|
||||
import os, subprocess, time
|
||||
import os, subprocess, time, re
|
||||
|
||||
from calibre.devices.interface import Device as _Device
|
||||
from calibre.devices.errors import DeviceError
|
||||
@ -18,21 +18,21 @@ class Device(_Device):
|
||||
as USB Mass Storage devices. If you are writing such a driver, inherit from this
|
||||
class.
|
||||
'''
|
||||
|
||||
|
||||
VENDOR_ID = 0x0
|
||||
PRODUCT_ID = 0x0
|
||||
BCD = None
|
||||
|
||||
|
||||
VENDOR_NAME = None
|
||||
WINDOWS_MAIN_MEM = None
|
||||
WINDOWS_CARD_MEM = None
|
||||
|
||||
|
||||
OSX_MAIN_MEM = None
|
||||
OSX_CARD_MEM = None
|
||||
|
||||
|
||||
MAIN_MEMORY_VOLUME_LABEL = ''
|
||||
STORAGE_CARD_VOLUME_LABEL = ''
|
||||
|
||||
|
||||
FDI_TEMPLATE = \
|
||||
'''
|
||||
<device>
|
||||
@ -65,15 +65,15 @@ class Device(_Device):
|
||||
</device>
|
||||
'''
|
||||
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) :
|
||||
self._main_prefix = self._card_prefix = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_fdi(cls):
|
||||
fdi = ''
|
||||
|
||||
|
||||
fdi_base_values = dict(
|
||||
app=__appname__,
|
||||
deviceclass=cls.__name__,
|
||||
@ -92,12 +92,12 @@ class Device(_Device):
|
||||
fdi_bcd_values['BCD_start'] = cls.FDI_BCD_TEMPLATE % dict(bcd=hex(bcd))
|
||||
fdi_bcd_values['BCD_end'] = '</match>'
|
||||
fdi += cls.FDI_TEMPLATE % fdi_bcd_values
|
||||
|
||||
|
||||
return fdi
|
||||
|
||||
|
||||
def set_progress_reporter(self, report_progress):
|
||||
self.report_progress = report_progress
|
||||
|
||||
|
||||
def card_prefix(self, end_session=True):
|
||||
return self._card_prefix
|
||||
|
||||
@ -117,7 +117,7 @@ class Device(_Device):
|
||||
else: raise
|
||||
mult = sectors_per_cluster * bytes_per_sector
|
||||
return total_clusters * mult, free_clusters * mult
|
||||
|
||||
|
||||
def total_space(self, end_session=True):
|
||||
msz = csz = 0
|
||||
print self._main_prefix
|
||||
@ -131,9 +131,9 @@ class Device(_Device):
|
||||
else:
|
||||
msz = self._windows_space(self._main_prefix)[0]
|
||||
csz = self._windows_space(self._card_prefix)[0]
|
||||
|
||||
|
||||
return (msz, 0, csz)
|
||||
|
||||
|
||||
def free_space(self, end_session=True):
|
||||
msz = csz = 0
|
||||
if not iswindows:
|
||||
@ -146,15 +146,15 @@ class Device(_Device):
|
||||
else:
|
||||
msz = self._windows_space(self._main_prefix)[1]
|
||||
csz = self._windows_space(self._card_prefix)[1]
|
||||
|
||||
|
||||
return (msz, 0, csz)
|
||||
|
||||
def windows_match_device(self, pnp_id, device_id):
|
||||
pnp_id = pnp_id.upper()
|
||||
|
||||
|
||||
if device_id and pnp_id is not None:
|
||||
device_id = device_id.upper()
|
||||
|
||||
|
||||
if 'VEN_' + self.VENDOR_NAME in pnp_id and 'PROD_' + device_id in pnp_id:
|
||||
return True
|
||||
|
||||
@ -162,45 +162,45 @@ class Device(_Device):
|
||||
|
||||
def windows_get_drive_prefix(self, drive):
|
||||
prefix = None
|
||||
|
||||
|
||||
try:
|
||||
partition = drive.associators("Win32_DiskDriveToDiskPartition")[0]
|
||||
logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0]
|
||||
prefix = logical_disk.DeviceID + os.sep
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
|
||||
return prefix
|
||||
|
||||
def open_windows(self):
|
||||
drives = {}
|
||||
wmi = __import__('wmi', globals(), locals(), [], -1)
|
||||
wmi = __import__('wmi', globals(), locals(), [], -1)
|
||||
c = wmi.WMI()
|
||||
for drive in c.Win32_DiskDrive():
|
||||
if self.windows_match_device(str(drive.PNPDeviceID), WINDOWS_MAIN_MEM):
|
||||
if self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_MAIN_MEM):
|
||||
drives['main'] = self.windows_get_drive_prefix(drive)
|
||||
elif self.windows_match_device(str(drive.PNPDeviceID), WINDOWS_CARD_MEM):
|
||||
elif self.windows_match_device(str(drive.PNPDeviceID), self.WINDOWS_CARD_MEM):
|
||||
drives['card'] = self.windows_get_drive_prefix(drive)
|
||||
|
||||
|
||||
if 'main' and 'card' in drives.keys():
|
||||
break
|
||||
|
||||
|
||||
if not drives:
|
||||
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.') % self.__class__.__name__)
|
||||
|
||||
self._main_prefix = drives['main'] if 'main' in names.keys() else None
|
||||
self._card_prefix = drives['card'] if 'card' in names.keys() else None
|
||||
|
||||
@classmethod
|
||||
self._main_prefix = drives.get('main', None)
|
||||
self._card_prefix = drives.get('card', None)
|
||||
|
||||
def get_osx_mountpoints(self, raw=None):
|
||||
if raw is None:
|
||||
ioreg = '/usr/sbin/ioreg'
|
||||
if not os.access(ioreg, os.X_OK):
|
||||
ioreg = 'ioreg'
|
||||
raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(), stdout=subprocess.PIPE).stdout.read()
|
||||
raw = subprocess.Popen((ioreg+' -w 0 -S -c IOMedia').split(),
|
||||
stdout=subprocess.PIPE).stdout.read()
|
||||
lines = raw.splitlines()
|
||||
names = {}
|
||||
|
||||
|
||||
def get_dev_node(lines, loc):
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
@ -210,7 +210,7 @@ class Device(_Device):
|
||||
if match is not None:
|
||||
names[loc] = match.group(1)
|
||||
break
|
||||
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if self.OSX_MAIN_MEM is not None and line.strip().endswith('<class IOMedia>') and self.OSX_MAIN_MEM in line:
|
||||
get_dev_node(lines[i+1:], 'main')
|
||||
@ -219,25 +219,25 @@ class Device(_Device):
|
||||
if len(names.keys()) == 2:
|
||||
break
|
||||
return names
|
||||
|
||||
|
||||
def open_osx(self):
|
||||
mount = subprocess.Popen('mount', shell=True, stdout=subprocess.PIPE).stdout.read()
|
||||
names = self.get_osx_mountpoints()
|
||||
dev_pat = r'/dev/%s(\w*)\s+on\s+([^\(]+)\s+'
|
||||
if 'main' not in names.keys():
|
||||
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%self.__class__.__name__)
|
||||
main_pat = dev_pat%names['main']
|
||||
main_pat = dev_pat % names['main']
|
||||
self._main_prefix = re.search(main_pat, mount).group(2) + os.sep
|
||||
card_pat = names['card'] if 'card' in names.keys() else None
|
||||
if card_pat is not None:
|
||||
card_pat = dev_pat%card_pat
|
||||
card_pat = dev_pat % card_pat
|
||||
self._card_prefix = re.search(card_pat, mount).group(2) + os.sep
|
||||
|
||||
|
||||
def open_linux(self):
|
||||
import dbus
|
||||
bus = dbus.SystemBus()
|
||||
bus = dbus.SystemBus()
|
||||
hm = dbus.Interface(bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager")
|
||||
|
||||
|
||||
def conditional_mount(dev):
|
||||
mmo = bus.get_object("org.freedesktop.Hal", dev)
|
||||
label = mmo.GetPropertyString('volume.label', dbus_interface='org.freedesktop.Hal.Device')
|
||||
@ -246,10 +246,10 @@ class Device(_Device):
|
||||
fstype = mmo.GetPropertyString('volume.fstype', dbus_interface='org.freedesktop.Hal.Device')
|
||||
if is_mounted:
|
||||
return str(mount_point)
|
||||
mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'],
|
||||
mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid()), 'sync'],
|
||||
dbus_interface='org.freedesktop.Hal.Device.Volume')
|
||||
return os.path.normpath('/media/'+label)+'/'
|
||||
|
||||
|
||||
mm = hm.FindDeviceStringMatch(__appname__+'.mainvolume', self.__class__.__name__)
|
||||
if not mm:
|
||||
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.')%(self.__class__.__name__,))
|
||||
@ -260,13 +260,13 @@ class Device(_Device):
|
||||
break
|
||||
except dbus.exceptions.DBusException:
|
||||
continue
|
||||
|
||||
|
||||
if not self._main_prefix:
|
||||
raise DeviceError('Could not open device for reading. Try a reboot.')
|
||||
|
||||
|
||||
self._card_prefix = None
|
||||
cards = hm.FindDeviceStringMatch(__appname__+'.cardvolume', self.__class__.__name__)
|
||||
|
||||
|
||||
for dev in cards:
|
||||
try:
|
||||
self._card_prefix = conditional_mount(dev)+os.sep
|
||||
|
@ -9,11 +9,26 @@ for a particular device.
|
||||
import os, fnmatch, shutil
|
||||
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.books import BookList, Book
|
||||
from calibre.devices.errors import FreeSpaceError
|
||||
from calibre.devices.errors import FreeSpaceError, PathError
|
||||
from calibre.devices.mime import MIME_MAP
|
||||
|
||||
class File(object):
|
||||
def __init__(self, path):
|
||||
stats = os.stat(path)
|
||||
self.is_dir = os.path.isdir(path)
|
||||
self.is_readonly = not os.access(path, os.W_OK)
|
||||
self.ctime = stats.st_ctime
|
||||
self.wtime = stats.st_mtime
|
||||
self.size = stats.st_size
|
||||
if path.endswith(os.sep):
|
||||
path = path[:-1]
|
||||
self.path = path
|
||||
self.name = os.path.basename(path)
|
||||
|
||||
class USBMS(Device):
|
||||
FORMATS = []
|
||||
EBOOK_DIR_MAIN = ''
|
||||
@ -21,39 +36,39 @@ class USBMS(Device):
|
||||
SUPPORTS_SUB_DIRS = False
|
||||
|
||||
def __init__(self, key='-1', log_packets=False, report_progress=None):
|
||||
pass
|
||||
|
||||
Device.__init__(self, key=key, log_packets=log_packets,
|
||||
report_progress=report_progress)
|
||||
|
||||
def get_device_information(self, end_session=True):
|
||||
"""
|
||||
Ask device for device information. See L{DeviceInfoQuery}.
|
||||
"""
|
||||
Ask device for device information. See L{DeviceInfoQuery}.
|
||||
@return: (device name, device version, software version on device, mime type)
|
||||
"""
|
||||
return (self.__class__.__name__, '', '', '')
|
||||
|
||||
|
||||
def books(self, oncard=False, end_session=True):
|
||||
bl = BookList()
|
||||
|
||||
if oncard and self._card_prefix is None:
|
||||
return bl
|
||||
|
||||
if oncard and self._card_prefix is None:
|
||||
return bl
|
||||
|
||||
prefix = self._card_prefix if oncard else self._main_prefix
|
||||
ebook_dir = self.EBOOK_DIR_CARD if oncard else self.EBOOK_DIR_MAIN
|
||||
|
||||
|
||||
# Get all books in all directories under the root ebook_dir directory
|
||||
for path, dirs, files in os.walk(os.path.join(prefix, ebook_dir)):
|
||||
# Filter out anything that isn't in the list of supported ebook types
|
||||
# Filter out anything that isn't in the list of supported ebook
|
||||
# types
|
||||
for book_type in self.FORMATS:
|
||||
for filename in fnmatch.filter(files, '*.%s' % (book_type)):
|
||||
title, author, mime = self.__class__.extract_book_metadata_by_filename(filename)
|
||||
|
||||
bl.append(Book(os.path.join(path, filename), title, author, mime))
|
||||
bl.append(self.__class__.book_from_path(os.path.join(path, filename)))
|
||||
return bl
|
||||
|
||||
def upload_books(self, files, names, on_card=False, end_session=True,
|
||||
|
||||
def upload_books(self, files, names, on_card=False, end_session=True,
|
||||
metadata=None):
|
||||
if on_card and not self._card_prefix:
|
||||
raise ValueError(_('The reader has no storage card connected.'))
|
||||
|
||||
|
||||
if not on_card:
|
||||
path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN)
|
||||
else:
|
||||
@ -67,24 +82,24 @@ class USBMS(Device):
|
||||
return size
|
||||
return os.path.getsize(obj)
|
||||
|
||||
sizes = map(get_size, files)
|
||||
sizes = [get_size(f) for f in files]
|
||||
size = sum(sizes)
|
||||
|
||||
if on_card and size > self.free_space()[2] - 1024*1024:
|
||||
if on_card and size > self.free_space()[2] - 1024*1024:
|
||||
raise FreeSpaceError(_("There is insufficient free space on the storage card"))
|
||||
if not on_card and size > self.free_space()[0] - 2*1024*1024:
|
||||
if not on_card and size > self.free_space()[0] - 2*1024*1024:
|
||||
raise FreeSpaceError(_("There is insufficient free space in main memory"))
|
||||
|
||||
paths = []
|
||||
names = iter(names)
|
||||
metadata = iter(metadata)
|
||||
|
||||
|
||||
for infile in files:
|
||||
newpath = path
|
||||
|
||||
|
||||
if self.SUPPORTS_SUB_DIRS:
|
||||
mdata = metadata.next()
|
||||
|
||||
|
||||
if 'tags' in mdata.keys():
|
||||
for tag in mdata['tags']:
|
||||
if tag.startswith('/'):
|
||||
@ -94,32 +109,35 @@ class USBMS(Device):
|
||||
|
||||
if not os.path.exists(newpath):
|
||||
os.makedirs(newpath)
|
||||
|
||||
filepath = os.path.join(newpath, names.next())
|
||||
|
||||
filepath = os.path.join(newpath, names.next())
|
||||
paths.append(filepath)
|
||||
|
||||
|
||||
if hasattr(infile, 'read'):
|
||||
infile.seek(0)
|
||||
|
||||
|
||||
dest = open(filepath, 'wb')
|
||||
shutil.copyfileobj(infile, dest, 10*1024*1024)
|
||||
|
||||
dest.flush()
|
||||
dest.flush()
|
||||
dest.close()
|
||||
else:
|
||||
shutil.copy2(infile, filepath)
|
||||
|
||||
|
||||
return zip(paths, cycle([on_card]))
|
||||
|
||||
|
||||
@classmethod
|
||||
def add_books_to_metadata(cls, locations, metadata, booklists):
|
||||
def add_books_to_metadata(cls, locations, metadata, booklists):
|
||||
for location in locations:
|
||||
path = location[0]
|
||||
on_card = 1 if location[1] else 0
|
||||
|
||||
title, author, mime = cls.extract_book_metadata_by_filename(os.path.basename(path))
|
||||
booklists[on_card].append(Book(path, title, author, mime))
|
||||
|
||||
|
||||
book = cls.book_from_path(path)
|
||||
|
||||
if not book in booklists[on_card]:
|
||||
booklists[on_card].append(book)
|
||||
|
||||
|
||||
def delete_books(self, paths, end_session=True):
|
||||
for path in paths:
|
||||
if os.path.exists(path):
|
||||
@ -130,7 +148,7 @@ class USBMS(Device):
|
||||
os.removedirs(os.path.dirname(path))
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@classmethod
|
||||
def remove_books_from_metadata(cls, paths, booklists):
|
||||
for path in paths:
|
||||
@ -138,14 +156,14 @@ class USBMS(Device):
|
||||
for book in bl:
|
||||
if path.endswith(book.path):
|
||||
bl.remove(book)
|
||||
|
||||
|
||||
def sync_booklists(self, booklists, end_session=True):
|
||||
# There is no meta data on the device to update. The device is treated
|
||||
# as a mass storage device and does not use a meta data xml file like
|
||||
# the Sony Readers.
|
||||
pass
|
||||
|
||||
def get_file(self, path, outfile, end_session=True):
|
||||
|
||||
def get_file(self, path, outfile, end_session=True):
|
||||
path = self.munge_path(path)
|
||||
src = open(path, 'rb')
|
||||
shutil.copyfileobj(src, outfile, 10*1024*1024)
|
||||
@ -198,28 +216,13 @@ class USBMS(Device):
|
||||
os.utime(path, None)
|
||||
|
||||
@classmethod
|
||||
def extract_book_metadata_by_filename(cls, filename):
|
||||
book_title = ''
|
||||
book_author = ''
|
||||
book_mime = ''
|
||||
# Calibre uses a specific format for file names. They take the form
|
||||
# title_-_author_number.extention We want to see if the file name is
|
||||
# in this format.
|
||||
if fnmatch.fnmatchcase(filename, '*_-_*.*'):
|
||||
# Get the title and author from the file name
|
||||
title, sep, author = filename.rpartition('_-_')
|
||||
author, sep, ext = author.rpartition('_')
|
||||
book_title = title.replace('_', ' ')
|
||||
book_author = author.replace('_', ' ')
|
||||
# if the filename did not match just set the title to
|
||||
# the filename without the extension
|
||||
else:
|
||||
book_title = os.path.splitext(filename)[0].replace('_', ' ')
|
||||
|
||||
fileext = os.path.splitext(filename)[1][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
|
||||
def book_from_path(cls, path):
|
||||
fileext = path_to_ext(path)
|
||||
|
||||
mi = metadata_from_formats([path])
|
||||
mime = MIME_MAP[fileext] if fileext in MIME_MAP.keys() else 'Unknown'
|
||||
|
||||
authors = authors_to_string(mi.authors)
|
||||
|
||||
return Book(path, mi.title, authors, mime)
|
||||
|
||||
|
@ -15,15 +15,17 @@ from lxml import etree
|
||||
|
||||
class DefaultProfile(object):
|
||||
|
||||
flow_size = sys.maxint
|
||||
screen_size = None
|
||||
flow_size = sys.maxint
|
||||
screen_size = None
|
||||
remove_special_chars = False
|
||||
remove_object_tags = False
|
||||
|
||||
class PRS505(DefaultProfile):
|
||||
|
||||
flow_size = 270000
|
||||
screen_size = (590, 765)
|
||||
flow_size = 270000
|
||||
screen_size = (590, 765)
|
||||
remove_special_chars = re.compile(u'[\u200b\u00ad]')
|
||||
remove_object_tags = True
|
||||
|
||||
|
||||
PROFILES = {
|
||||
|
@ -16,7 +16,7 @@ from calibre.ebooks.epub import config as common_config, process_encryption
|
||||
from calibre.ebooks.epub.from_html import convert as html2epub, find_html_index
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.ebooks.metadata.opf2 import OPFCreator
|
||||
from calibre.ebooks.metadata.opf2 import OPFCreator, OPF
|
||||
from calibre.utils.zipfile import ZipFile
|
||||
from calibre.customize.ui import run_plugins_on_preprocess
|
||||
|
||||
@ -25,9 +25,36 @@ def lit2opf(path, tdir, opts):
|
||||
print 'Exploding LIT file:', path
|
||||
reader = LitReader(path)
|
||||
reader.extract_content(tdir, False)
|
||||
for f in walk(tdir):
|
||||
if f.lower().endswith('.opf'):
|
||||
return f
|
||||
opf = None
|
||||
for opf in walk(tdir):
|
||||
if opf.lower().endswith('.opf'):
|
||||
break
|
||||
if not opf.endswith('.opf'):
|
||||
opf = None
|
||||
if opf is not None: # Check for url-quoted filenames
|
||||
_opf = OPF(opf, os.path.dirname(opf))
|
||||
replacements = []
|
||||
for item in _opf.itermanifest():
|
||||
href = item.get('href', '')
|
||||
path = os.path.join(os.path.dirname(opf), *(href.split('/')))
|
||||
if not os.path.exists(path) and os.path.exists(path.replace('&', '%26')):
|
||||
npath = path
|
||||
path = path.replace('&', '%26')
|
||||
replacements.append((path, npath))
|
||||
if replacements:
|
||||
print 'Fixing quoted filenames...'
|
||||
for path, npath in replacements:
|
||||
if os.path.exists(path):
|
||||
os.rename(path, npath)
|
||||
for f in walk(tdir):
|
||||
with open(f, 'r+b') as f:
|
||||
raw = f.read()
|
||||
for path, npath in replacements:
|
||||
raw = raw.replace(os.path.basename(path), os.path.basename(npath))
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write(raw)
|
||||
return opf
|
||||
|
||||
def mobi2opf(path, tdir, opts):
|
||||
from calibre.ebooks.mobi.reader import MobiReader
|
||||
|
@ -153,11 +153,46 @@ class HTMLProcessor(Processor, Rationalizer):
|
||||
Perform various markup transforms to get the output to render correctly
|
||||
in the quirky ADE.
|
||||
'''
|
||||
# Replace <br> that are children of <body> with <p> </p>
|
||||
# Replace <br> that are children of <body> as ADE doesn't handle them
|
||||
if hasattr(self.body, 'xpath'):
|
||||
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.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):
|
||||
for meta in list(self.root.xpath('//meta')):
|
||||
|
@ -50,6 +50,7 @@ class Splitter(LoggingInterface):
|
||||
self.split_size = 0
|
||||
|
||||
# Split on page breaks
|
||||
self.splitting_on_page_breaks = True
|
||||
if not opts.dont_split_on_page_breaks:
|
||||
self.log_info('\tSplitting on page breaks...')
|
||||
if self.path in stylesheet_map:
|
||||
@ -61,6 +62,7 @@ class Splitter(LoggingInterface):
|
||||
trees = list(self.trees)
|
||||
|
||||
# Split any remaining over-sized trees
|
||||
self.splitting_on_page_breaks = False
|
||||
if self.opts.profile.flow_size < sys.maxint:
|
||||
lt_found = False
|
||||
self.log_info('\tLooking for large trees...')
|
||||
@ -203,7 +205,8 @@ class Splitter(LoggingInterface):
|
||||
elem.set('style', 'display:none')
|
||||
|
||||
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
|
||||
hit_split_point = False
|
||||
|
@ -823,21 +823,28 @@ class Processor(Parser):
|
||||
font.set('class', cn)
|
||||
font.tag = 'span'
|
||||
|
||||
id_css, id_css_counter = {}, 0
|
||||
for elem in self.root.xpath('//*[@style]'):
|
||||
setting = elem.get('style')
|
||||
classname = cache.get(setting, None)
|
||||
if classname is None:
|
||||
classname = 'calibre_class_%d'%class_counter
|
||||
class_counter += 1
|
||||
cache[setting] = classname
|
||||
cn = elem.get('class', '')
|
||||
if cn: cn += ' '
|
||||
cn += classname
|
||||
elem.set('class', cn)
|
||||
if elem.get('id', False) or elem.get('class', False):
|
||||
elem.set('id', elem.get('id', 'calibre_css_id_%d'%id_css_counter))
|
||||
id_css_counter += 1
|
||||
id_css[elem.tag+'#'+elem.get('id')] = setting
|
||||
else:
|
||||
classname = cache.get(setting, None)
|
||||
if classname is None:
|
||||
classname = 'calibre_class_%d'%class_counter
|
||||
class_counter += 1
|
||||
cache[setting] = classname
|
||||
cn = elem.get('class', classname)
|
||||
elem.set('class', cn)
|
||||
elem.attrib.pop('style')
|
||||
|
||||
css = '\n'.join(['.%s {%s;}'%(cn, setting) for \
|
||||
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(';;}', ';}')))
|
||||
for rule in sheet:
|
||||
self.stylesheet.add(rule)
|
||||
|
@ -122,6 +122,8 @@ LZXC_CONTROL = \
|
||||
|
||||
COLLAPSE = re.compile(r'[ \t\r\n\v]+')
|
||||
|
||||
PAGE_BREAKS = set(['always', 'left', 'right'])
|
||||
|
||||
def decint(value):
|
||||
bytes = []
|
||||
while True:
|
||||
@ -202,7 +204,7 @@ class ReBinary(object):
|
||||
self.write(FLAG_CUSTOM, len(tag)+1, tag)
|
||||
last_break = self.page_breaks[-1][0] if self.page_breaks else None
|
||||
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)))
|
||||
for attr, value in attrib.items():
|
||||
attr = prefixname(attr, nsrmap)
|
||||
|
@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
|
||||
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
|
||||
|
||||
|
||||
@ -389,10 +389,33 @@ def create_lrf(pages, profile, opts, thumbnail=None):
|
||||
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]
|
||||
|
||||
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.setAuthor(opts.author)
|
||||
@ -400,7 +423,52 @@ def create_pdf(pages, profile, opts, thumbnail=None):
|
||||
|
||||
|
||||
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()
|
||||
# Write the document to disk
|
||||
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'):
|
||||
path_to_file = run_plugins_on_preprocess(path_to_file)
|
||||
source = path_to_file
|
||||
to_delete = []
|
||||
toc = []
|
||||
list = []
|
||||
pages = []
|
||||
|
||||
|
||||
if not opts.title:
|
||||
opts.title = os.path.splitext(os.path.basename(source))[0]
|
||||
if not opts.output:
|
||||
opts.output = os.path.abspath(os.path.splitext(os.path.basename(source))[0]+'.'+output_format)
|
||||
tdir = extract_comic(source)
|
||||
pages = find_pages(tdir, sort_on_mtime=opts.no_sort, verbose=opts.verbose)
|
||||
thumbnail = None
|
||||
if not pages:
|
||||
raise ValueError('Could not find any pages in the comic: %s'%source)
|
||||
if not getattr(opts, 'no_process', False):
|
||||
pages, failures, tdir2 = process_pages(pages, opts, notification)
|
||||
if not pages:
|
||||
raise ValueError('Could not find any valid pages in the comic: %s'%source)
|
||||
if failures:
|
||||
print 'Could not process the following pages (run with --verbose to see why):'
|
||||
for f in failures:
|
||||
print '\t', f
|
||||
thumbnail = os.path.join(tdir2, 'thumbnail.png')
|
||||
if not os.access(thumbnail, os.R_OK):
|
||||
thumbnail = None
|
||||
if os.path.isdir(source):
|
||||
for path in all_files( source , '*.cbr|*.cbz' ):
|
||||
list.append( path )
|
||||
else:
|
||||
list= [ os.path.abspath(source) ]
|
||||
|
||||
for source in list:
|
||||
tdir = extract_comic(source)
|
||||
new_pages = find_pages(tdir, sort_on_mtime=opts.no_sort, verbose=opts.verbose)
|
||||
thumbnail = None
|
||||
if not new_pages:
|
||||
raise ValueError('Could not find any pages in the comic: %s'%source)
|
||||
if not getattr(opts, 'no_process', False):
|
||||
new_pages, failures, tdir2 = process_pages(new_pages, opts, notification)
|
||||
if not new_pages:
|
||||
raise ValueError('Could not find any valid pages in the comic: %s'%source)
|
||||
if failures:
|
||||
print 'Could not process the following pages (run with --verbose to see why):'
|
||||
for f in failures:
|
||||
print '\t', f
|
||||
thumbnail = os.path.join(tdir2, 'thumbnail.png')
|
||||
if not os.access(thumbnail, os.R_OK):
|
||||
thumbnail = None
|
||||
toc.append((source,len(pages)))
|
||||
pages.extend(new_pages)
|
||||
to_delete.append(tdir)
|
||||
|
||||
|
||||
if output_format == 'lrf':
|
||||
create_lrf(pages, opts.profile, opts, thumbnail=thumbnail)
|
||||
if output_format == 'epub':
|
||||
create_epub(pages, opts.profile, opts, thumbnail=thumbnail)
|
||||
if output_format == 'pdf':
|
||||
create_pdf(pages, opts.profile, opts, thumbnail=thumbnail)
|
||||
shutil.rmtree(tdir)
|
||||
if not getattr(opts, 'no_process', False):
|
||||
shutil.rmtree(tdir2)
|
||||
create_pdf(pages, opts.profile, opts, thumbnail=thumbnail,toc=toc)
|
||||
for tdir in to_delete:
|
||||
shutil.rmtree(tdir)
|
||||
|
||||
|
||||
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'):
|
||||
|
@ -1720,7 +1720,7 @@ class HTMLConverter(object, LoggingInterface):
|
||||
self.previous_text = '\n'
|
||||
elif tagname in ['hr', 'tr']: # tr needed for nested tables
|
||||
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.previous_text = '\n'
|
||||
self.process_children(tag, tag_css, tag_pseudo_css)
|
||||
|
@ -21,7 +21,10 @@ def string_to_authors(raw):
|
||||
return 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):
|
||||
tokens = author.split()
|
||||
|
@ -6,33 +6,28 @@ Support for reading the metadata from a LIT file.
|
||||
|
||||
import sys, cStringIO, os
|
||||
|
||||
from calibre import relpath
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.ebooks.metadata.opf import OPFReader
|
||||
from calibre.ebooks.metadata.opf2 import OPF
|
||||
from calibre.ebooks.lit.reader import LitReader
|
||||
|
||||
def get_metadata(stream):
|
||||
try:
|
||||
litfile = LitReader(stream)
|
||||
src = litfile.meta.encode('utf-8')
|
||||
mi = OPFReader(cStringIO.StringIO(src), dir=os.getcwd())
|
||||
cover_url, cover_item = mi.cover, None
|
||||
if cover_url:
|
||||
cover_url = relpath(cover_url, os.getcwd())
|
||||
for item in litfile.manifest.values():
|
||||
if item.path == cover_url:
|
||||
cover_item = item.internal
|
||||
if cover_item is not None:
|
||||
ext = cover_url.rpartition('.')[-1]
|
||||
if not ext:
|
||||
ext = 'jpg'
|
||||
else:
|
||||
ext = ext.lower()
|
||||
cd = litfile.get_file('/data/' + cover_item)
|
||||
mi.cover_data = (ext, cd) if cd else (None, None)
|
||||
except:
|
||||
title = stream.name if hasattr(stream, 'name') and stream.name else 'Unknown'
|
||||
mi = MetaInformation(title, ['Unknown'])
|
||||
litfile = LitReader(stream)
|
||||
src = litfile.meta.encode('utf-8')
|
||||
opf = OPF(cStringIO.StringIO(src), os.getcwd())
|
||||
mi = MetaInformation(opf)
|
||||
covers = []
|
||||
for item in opf.iterguide():
|
||||
if 'cover' not in item.get('type', '').lower():
|
||||
continue
|
||||
href = item.get('href', '')
|
||||
candidates = [href, href.replace('&', '%26')]
|
||||
for item in litfile.manifest.values():
|
||||
if item.path in candidates:
|
||||
covers.append(item.internal)
|
||||
break
|
||||
covers = [litfile.get_file('/data/' + i) for i in covers]
|
||||
covers.sort(cmp=lambda x, y:cmp(len(x), len(y)))
|
||||
mi.cover_data = ('jpg', covers[-1])
|
||||
return mi
|
||||
|
||||
def main(args=sys.argv):
|
||||
|
@ -72,6 +72,17 @@ def get_metadata(stream, stream_type='lrf', use_libprs_metadata=False):
|
||||
|
||||
name = os.path.basename(getattr(stream, '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:
|
||||
base.authors = [_('Unknown')]
|
||||
if not base.title:
|
||||
|
231
src/calibre/ebooks/metadata/mobi.py
Normal file
231
src/calibre/ebooks/metadata/mobi.py
Normal 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())
|
@ -435,7 +435,7 @@ class OPF(object):
|
||||
rating = MetadataField('rating', is_dc=False, formatter=int)
|
||||
|
||||
|
||||
def __init__(self, stream, basedir=os.getcwdu()):
|
||||
def __init__(self, stream, basedir=os.getcwdu(), unquote_urls=True):
|
||||
if not hasattr(stream, 'read'):
|
||||
stream = open(stream, 'rb')
|
||||
self.basedir = self.base_dir = basedir
|
||||
@ -446,7 +446,8 @@ class OPF(object):
|
||||
if not self.metadata:
|
||||
raise ValueError('Malformed OPF file: No <metadata> element')
|
||||
self.metadata = self.metadata[0]
|
||||
self.unquote_urls()
|
||||
if unquote_urls:
|
||||
self.unquote_urls()
|
||||
self.manifest = Manifest()
|
||||
m = self.manifest_path(self.root)
|
||||
if m:
|
||||
|
36
src/calibre/ebooks/metadata/rar.py
Normal file
36
src/calibre/ebooks/metadata/rar.py
Normal 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')
|
||||
|
||||
|
@ -14,18 +14,21 @@ import sys, os, glob, logging
|
||||
from calibre.ebooks.epub.from_any import any2epub, formats, USAGE
|
||||
from calibre.ebooks.epub import config as common_config
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from calibre.ebooks.mobi.writer import oeb2mobi, add_mobi_options
|
||||
from calibre.ebooks.mobi.writer import oeb2mobi, config as mobi_config
|
||||
|
||||
def config(defaults=None):
|
||||
return common_config(defaults=defaults, name='mobi')
|
||||
c = common_config(defaults=defaults, name='mobi')
|
||||
c.remove_opt('profile')
|
||||
mobic = mobi_config(defaults=defaults)
|
||||
c.update(mobic)
|
||||
return c
|
||||
|
||||
def option_parser(usage=USAGE):
|
||||
usage = usage % ('Mobipocket', formats())
|
||||
parser = config().option_parser(usage=usage)
|
||||
add_mobi_options(parser)
|
||||
return parser
|
||||
|
||||
def any2mobi(opts, path):
|
||||
def any2mobi(opts, path, notification=None):
|
||||
ext = os.path.splitext(path)[1]
|
||||
if not ext:
|
||||
raise ValueError('Unknown file type: '+path)
|
||||
|
44
src/calibre/ebooks/mobi/from_comic.py
Normal file
44
src/calibre/ebooks/mobi/from_comic.py
Normal 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())
|
74
src/calibre/ebooks/mobi/from_feeds.py
Normal file
74
src/calibre/ebooks/mobi/from_feeds.py
Normal 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())
|
@ -27,7 +27,7 @@ TABLE_TAGS = set(['table', 'tr', 'td', 'th'])
|
||||
SPECIAL_TAGS = set(['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]+')
|
||||
|
||||
|
@ -311,7 +311,7 @@ class MobiReader(object):
|
||||
opf.cover = 'images/%05d.jpg'%(self.book_header.exth.cover_offset+1)
|
||||
manifest = [(htmlfile, 'text/x-oeb1-document')]
|
||||
bp = os.path.dirname(htmlfile)
|
||||
for i in self.image_names:
|
||||
for i in getattr(self, 'image_names', []):
|
||||
manifest.append((os.path.join(bp, 'images/', i), 'image/jpg'))
|
||||
|
||||
opf.create_manifest(manifest)
|
||||
@ -451,7 +451,7 @@ class MobiReader(object):
|
||||
image_index += 1
|
||||
try:
|
||||
im = PILImage.open(buf)
|
||||
except IOError, e:
|
||||
except IOError:
|
||||
continue
|
||||
|
||||
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:
|
||||
mi = MetaInformation(mr.name, [_('Unknown')])
|
||||
else:
|
||||
tdir = tempfile.mkdtemp('_mobi_meta', __appname__+'_')
|
||||
atexit.register(shutil.rmtree, tdir)
|
||||
#print tdir
|
||||
mr.extract_images([], tdir)
|
||||
mi = mr.create_opf('dummy.html')
|
||||
if mi.cover:
|
||||
cover = os.path.join(tdir, mi.cover)
|
||||
if not os.access(cover, os.R_OK):
|
||||
fname = os.path.basename(cover)
|
||||
match = re.match(r'(\d+)(.+)', fname)
|
||||
if match:
|
||||
num, ext = int(match.group(1), 10), match.group(2)
|
||||
while num > 0:
|
||||
num -= 1
|
||||
candidate = os.path.join(os.path.dirname(cover), '%05d%s'%(num, ext))
|
||||
if os.access(candidate, os.R_OK):
|
||||
cover = candidate
|
||||
break
|
||||
if os.access(cover, os.R_OK):
|
||||
mi.cover_data = ('JPEG', open(os.path.join(tdir, cover), 'rb').read())
|
||||
else:
|
||||
path = os.path.join(tdir, 'images', '00001.jpg')
|
||||
if os.access(path, os.R_OK):
|
||||
mi.cover_data = ('JPEG', open(path, 'rb').read())
|
||||
return mi
|
||||
try:
|
||||
if hasattr(mr.book_header.exth, 'cover_offset'):
|
||||
cover_index = mr.book_header.first_image_index + mr.book_header.exth.cover_offset
|
||||
data = mr.sections[cover_index][0]
|
||||
else:
|
||||
data = mr.sections[mr.book_header.first_image_index][0]
|
||||
buf = cStringIO.StringIO(data)
|
||||
im = PILImage.open(buf)
|
||||
obuf = cStringIO.StringIO()
|
||||
im.convert('RGBA').save(obuf, format='JPEG')
|
||||
mi.cover_data = ('jpg', obuf.getvalue())
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return mi
|
||||
|
||||
|
||||
def option_parser():
|
||||
from calibre.utils.config import OptionParser
|
||||
|
@ -34,8 +34,7 @@ from calibre.ebooks.mobi.palmdoc import compress_doc
|
||||
from calibre.ebooks.mobi.langcodes import iana2mobi
|
||||
from calibre.ebooks.mobi.mobiml import MBP_NS, MBP, MobiMLizer
|
||||
from calibre.customize.ui import run_plugins_on_postprocess
|
||||
from calibre.utils.config import OptionParser
|
||||
from optparse import OptionGroup
|
||||
from calibre.utils.config import Config, StringConfig
|
||||
|
||||
# TODO:
|
||||
# - Allow override CSS (?)
|
||||
@ -88,6 +87,49 @@ def decint(value, direction):
|
||||
bytes[-1] |= 0x80
|
||||
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):
|
||||
NSRMAP = {'': None, XML_NS: 'xml', XHTML_NS: '', MBP_NS: 'mbp'}
|
||||
@ -356,50 +398,7 @@ class MobiWriter(object):
|
||||
offset += RECORD_SIZE
|
||||
data, overlap = self._read_text_record(text)
|
||||
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):
|
||||
self._oeb.logger.info('Serializing images...')
|
||||
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
|
||||
for _, href in images:
|
||||
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)
|
||||
|
||||
def _generate_record0(self):
|
||||
@ -453,6 +452,13 @@ class MobiWriter(object):
|
||||
code = EXTH_CODES[term]
|
||||
for item in oeb.metadata[term]:
|
||||
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')
|
||||
exth.write(pack('>II', code, len(data) + 8))
|
||||
exth.write(data)
|
||||
@ -469,12 +475,12 @@ class MobiWriter(object):
|
||||
nrecs += 3
|
||||
exth = exth.getvalue()
|
||||
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]
|
||||
return ''.join(exth)
|
||||
|
||||
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
|
||||
id, href = manifest.generate('thumbnail', 'thumbnail.jpeg')
|
||||
manifest.add(id, href, 'image/jpeg', data=data)
|
||||
@ -502,41 +508,45 @@ class MobiWriter(object):
|
||||
self._write(record)
|
||||
|
||||
|
||||
def add_mobi_options(parser):
|
||||
profiles = Context.PROFILES.keys()
|
||||
profiles.sort()
|
||||
profiles = ', '.join(profiles)
|
||||
group = OptionGroup(parser, _('Mobipocket'),
|
||||
_('Mobipocket-specific options.'))
|
||||
group.add_option(
|
||||
'-c', '--compress', default=False, action='store_true',
|
||||
help=_('Compress file text using PalmDOC compression. '
|
||||
def config(defaults=None):
|
||||
desc = _('Options to control the conversion to MOBI')
|
||||
_profiles = list(sorted(Context.PROFILES.keys()))
|
||||
if defaults is None:
|
||||
c = Config('mobi', desc)
|
||||
else:
|
||||
c = StringConfig(defaults, desc)
|
||||
|
||||
mobi = c.add_group('mobipocket', _('Mobipocket-specific options.'))
|
||||
mobi('compress', ['--compress'], default=False,
|
||||
help=_('Compress file text using PalmDOC compression. '
|
||||
'Results in smaller files, but takes a long time to run.'))
|
||||
group.add_option(
|
||||
'-r', '--rescale-images', default=False, action='store_true',
|
||||
mobi('rescale_images', ['--rescale-images'], default=False,
|
||||
help=_('Modify images to meet Palm device size limitations.'))
|
||||
parser.add_option_group(group)
|
||||
group = OptionGroup(parser, _('Profiles'), _('Device renderer profiles. '
|
||||
'Affects conversion of default font sizes and rasterization '
|
||||
'resolution. Valid profiles are: %s.') % profiles)
|
||||
group.add_option(
|
||||
'--source-profile', default='Browser', metavar='PROFILE',
|
||||
help=_("Source renderer profile. Default is 'Browser'."))
|
||||
group.add_option(
|
||||
'--dest-profile', default='CybookG3', metavar='PROFILE',
|
||||
help=_("Destination renderer profile. Default is 'CybookG3'."))
|
||||
parser.add_option_group(group)
|
||||
return
|
||||
|
||||
mobi('toc_title', ['--toc-title'], default=None,
|
||||
help=_('Title for any generated in-line table of contents.'))
|
||||
profiles = c.add_group('profiles', _('Device renderer profiles. '
|
||||
'Affects conversion of font sizes, image rescaling and rasterization '
|
||||
'of tables. Valid profiles are: %s.') % ', '.join(_profiles))
|
||||
profiles('source_profile', ['--source-profile'],
|
||||
default='Browser', choices=_profiles,
|
||||
help=_("Source renderer profile. Default is %default."))
|
||||
profiles('dest_profile', ['--dest-profile'],
|
||||
default='CybookG3', choices=_profiles,
|
||||
help=_("Destination renderer profile. Default is %default."))
|
||||
c.add_opt('encoding', ['--encoding'], default=None,
|
||||
help=_('Character encoding for HTML files. Default is to auto detect.'))
|
||||
return c
|
||||
|
||||
|
||||
def option_parser():
|
||||
parser = OptionParser(usage=_('%prog [options] OPFFILE'))
|
||||
c = config()
|
||||
parser = c.option_parser(usage='%prog '+_('[options]')+' file.opf')
|
||||
parser.add_option(
|
||||
'-o', '--output', default=None,
|
||||
help=_('Output file. Default is derived from input filename.'))
|
||||
parser.add_option(
|
||||
'-v', '--verbose', default=0, action='count',
|
||||
help=_('Useful for debugging.'))
|
||||
add_mobi_options(parser)
|
||||
return parser
|
||||
|
||||
def oeb2mobi(opts, inpath):
|
||||
@ -557,8 +567,8 @@ def oeb2mobi(opts, inpath):
|
||||
compression = PALMDOC if opts.compress else UNCOMPRESSED
|
||||
imagemax = PALM_MAX_IMAGE_SIZE if opts.rescale_images else None
|
||||
context = Context(source, dest)
|
||||
oeb = OEBBook(inpath, logger=logger)
|
||||
tocadder = HTMLTOCAdder()
|
||||
oeb = OEBBook(inpath, logger=logger, encoding=opts.encoding)
|
||||
tocadder = HTMLTOCAdder(title=opts.toc_title)
|
||||
tocadder.transform(oeb, context)
|
||||
mangler = CaseMangler()
|
||||
mangler.transform(oeb, context)
|
||||
|
@ -20,8 +20,9 @@ import copy
|
||||
from lxml import etree
|
||||
from lxml import html
|
||||
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'
|
||||
XHTML_NS = 'http://www.w3.org/1999/xhtml'
|
||||
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'
|
||||
DC10_NS = 'http://purl.org/dc/elements/1.0/'
|
||||
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'
|
||||
DCTERMS_NS = 'http://purl.org/dc/terms/'
|
||||
NCX_NS = 'http://www.daisy.org/z3986/2005/ncx/'
|
||||
@ -89,6 +91,9 @@ def prefixname(name, nsrmap):
|
||||
return barename(name)
|
||||
return ':'.join((prefix, barename(name)))
|
||||
|
||||
def XPath(expr):
|
||||
return etree.XPath(expr, namespaces=XPNSMAP)
|
||||
|
||||
def xpath(elem, expr):
|
||||
return elem.xpath(expr, namespaces=XPNSMAP)
|
||||
|
||||
@ -134,8 +139,7 @@ class Logger(LoggingInterface, object):
|
||||
class AbstractContainer(object):
|
||||
def read_xml(self, path):
|
||||
return etree.fromstring(
|
||||
self.read(path), parser=XML_PARSER,
|
||||
base_url=os.path.dirname(path))
|
||||
self.read(path), base_url=os.path.dirname(path))
|
||||
|
||||
class DirContainer(AbstractContainer):
|
||||
def __init__(self, rootdir):
|
||||
@ -190,15 +194,19 @@ class Metadata(object):
|
||||
if term == OPF('meta') and not value:
|
||||
term = self.fq_attrib.pop('name')
|
||||
value = self.fq_attrib.pop('content')
|
||||
elif term in Metadata.TERMS and not namespace(term):
|
||||
term = DC(term)
|
||||
elif barename(term).lower() in Metadata.TERMS and \
|
||||
(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.value = value
|
||||
self.attrib = attrib = {}
|
||||
for fq_attr in fq_attrib:
|
||||
if fq_attr in Metadata.ATTRS:
|
||||
attr = fq_attr
|
||||
fq_attr = OPF2(fq_attr)
|
||||
fq_attr = OPF(fq_attr)
|
||||
fq_attrib[fq_attr] = fq_attrib.pop(attr)
|
||||
else:
|
||||
attr = barename(fq_attr)
|
||||
@ -212,7 +220,16 @@ class Metadata(object):
|
||||
raise AttributeError(
|
||||
'%r object has no attribute %r' \
|
||||
% (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):
|
||||
return 'Item(term=%r, value=%r, attrib=%r)' \
|
||||
% (barename(self.term), self.value, self.attrib)
|
||||
@ -291,15 +308,19 @@ class Metadata(object):
|
||||
class Manifest(object):
|
||||
class Item(object):
|
||||
NUM_RE = re.compile('^(.*)([0-9][0-9.]*)(?=[.]|$)')
|
||||
META_XP = XPath('/h:html/h:head/h:meta[@http-equiv="Content-Type"]')
|
||||
|
||||
def __init__(self, id, href, media_type,
|
||||
def __init__(self, oeb, id, href, media_type,
|
||||
fallback=None, loader=str, data=None):
|
||||
self.oeb = oeb
|
||||
self.id = id
|
||||
self.href = self.path = urlnormalize(href)
|
||||
self.media_type = media_type
|
||||
self.fallback = fallback
|
||||
self.spine_position = None
|
||||
self.linear = True
|
||||
if loader is None and data is None:
|
||||
loader = oeb.container.read
|
||||
self._loader = loader
|
||||
self._data = data
|
||||
|
||||
@ -308,16 +329,27 @@ class Manifest(object):
|
||||
% (self.id, self.href, self.media_type)
|
||||
|
||||
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:
|
||||
data = etree.fromstring(data, parser=XML_PARSER)
|
||||
data = etree.fromstring(data)
|
||||
except etree.XMLSyntaxError:
|
||||
data = html.fromstring(data, parser=XML_PARSER)
|
||||
data = html.fromstring(data)
|
||||
data = etree.tostring(data, encoding=unicode)
|
||||
data = etree.fromstring(data, parser=XML_PARSER)
|
||||
data = etree.fromstring(data)
|
||||
if namespace(data.tag) != XHTML_NS:
|
||||
data.attrib['xmlns'] = XHTML_NS
|
||||
data = etree.tostring(data)
|
||||
data = etree.fromstring(data, parser=XML_PARSER)
|
||||
data = etree.tostring(data, encoding=unicode)
|
||||
data = etree.fromstring(data)
|
||||
for meta in self.META_XP(data):
|
||||
meta.getparent().remove(meta)
|
||||
return data
|
||||
|
||||
def data():
|
||||
@ -328,7 +360,7 @@ class Manifest(object):
|
||||
if self.media_type in OEB_DOCS:
|
||||
data = self._force_xhtml(data)
|
||||
elif self.media_type[-4:] in ('+xml', '/xml'):
|
||||
data = etree.fromstring(data, parser=XML_PARSER)
|
||||
data = etree.fromstring(data)
|
||||
self._data = data
|
||||
return data
|
||||
def fset(self, value):
|
||||
@ -394,9 +426,8 @@ class Manifest(object):
|
||||
self.hrefs = {}
|
||||
|
||||
def add(self, id, href, media_type, fallback=None, loader=None, data=None):
|
||||
loader = loader or self.oeb.container.read
|
||||
item = self.Item(
|
||||
id, href, media_type, fallback, loader, data)
|
||||
self.oeb, id, href, media_type, fallback, loader, data)
|
||||
self.ids[item.id] = item
|
||||
self.hrefs[item.href] = item
|
||||
return item
|
||||
@ -534,27 +565,36 @@ class Spine(object):
|
||||
|
||||
class Guide(object):
|
||||
class Reference(object):
|
||||
_TYPES_TITLES = [('cover', 'Cover'), ('title-page', 'Title Page'),
|
||||
('toc', 'Table of Contents'), ('index', 'Index'),
|
||||
('glossary', 'Glossary'), ('acknowledgements', 'Acknowledgements'),
|
||||
('bibliography', 'Bibliography'), ('colophon', 'Colophon'),
|
||||
('copyright-page', 'Copyright'), ('dedication', 'Dedication'),
|
||||
('epigraph', 'Epigraph'), ('foreword', 'Foreword'),
|
||||
('loi', 'List of Illustrations'), ('lot', 'List of Tables'),
|
||||
('notes', 'Notes'), ('preface', 'Preface'),
|
||||
('text', 'Main Text')]
|
||||
_TYPES_TITLES = [('cover', __('Cover')),
|
||||
('title-page', __('Title Page')),
|
||||
('toc', __('Table of Contents')),
|
||||
('index', __('Index')),
|
||||
('glossary', __('Glossary')),
|
||||
('acknowledgements', __('Acknowledgements')),
|
||||
('bibliography', __('Bibliography')),
|
||||
('colophon', __('Colophon')),
|
||||
('copyright-page', __('Copyright')),
|
||||
('dedication', __('Dedication')),
|
||||
('epigraph', __('Epigraph')),
|
||||
('foreword', __('Foreword')),
|
||||
('loi', __('List of Illustrations')),
|
||||
('lot', __('List of Tables')),
|
||||
('notes', __('Notes')),
|
||||
('preface', __('Preface')),
|
||||
('text', __('Main Text'))]
|
||||
TYPES = set(t for t, _ in _TYPES_TITLES)
|
||||
TITLES = dict(_TYPES_TITLES)
|
||||
ORDER = dict((t, i) for (t, _), i in izip(_TYPES_TITLES, count(0)))
|
||||
|
||||
def __init__(self, type, title, href):
|
||||
def __init__(self, oeb, type, title, href):
|
||||
self.oeb = oeb
|
||||
if type.lower() in self.TYPES:
|
||||
type = type.lower()
|
||||
elif type not in self.TYPES and \
|
||||
not type.startswith('other.'):
|
||||
type = 'other.' + type
|
||||
if not title:
|
||||
title = self.TITLES.get(type, None)
|
||||
if not title and type in self.TITLES:
|
||||
title = oeb.translate(self.TITLES[type])
|
||||
self.type = type
|
||||
self.title = title
|
||||
self.href = urlnormalize(href)
|
||||
@ -573,13 +613,21 @@ class Guide(object):
|
||||
if not isinstance(other, Guide.Reference):
|
||||
return NotImplemented
|
||||
return cmp(self._order, other._order)
|
||||
|
||||
def item():
|
||||
def fget(self):
|
||||
path, frag = urldefrag(self.href)
|
||||
hrefs = self.oeb.manifest.hrefs
|
||||
return hrefs.get(path, None)
|
||||
return property(fget=fget)
|
||||
item = item()
|
||||
|
||||
def __init__(self, oeb):
|
||||
self.oeb = oeb
|
||||
self.refs = {}
|
||||
|
||||
def add(self, type, title, href):
|
||||
ref = self.Reference(type, title, href)
|
||||
ref = self.Reference(self.oeb, type, title, href)
|
||||
self.refs[type] = ref
|
||||
return ref
|
||||
|
||||
@ -589,9 +637,7 @@ class Guide(object):
|
||||
__iter__ = iterkeys
|
||||
|
||||
def values(self):
|
||||
values = list(self.refs.values())
|
||||
values.sort()
|
||||
return values
|
||||
return sorted(self.refs.values())
|
||||
|
||||
def items(self):
|
||||
for type, ref in self.refs.items():
|
||||
@ -675,31 +721,33 @@ class TOC(object):
|
||||
node.to_opf1(tour)
|
||||
return tour
|
||||
|
||||
def to_ncx(self, parent, playorder=None, depth=1):
|
||||
if not playorder: playorder = [0]
|
||||
def to_ncx(self, parent, order=None, depth=1):
|
||||
if not order: order = [0]
|
||||
for node in self.nodes:
|
||||
playorder[0] += 1
|
||||
order[0] += 1
|
||||
playOrder = str(order[0])
|
||||
id = self.id or 'np' + playOrder
|
||||
point = etree.SubElement(parent,
|
||||
NCX('navPoint'), attrib={'playOrder': str(playorder[0])})
|
||||
NCX('navPoint'), id=id, playOrder=playOrder)
|
||||
if self.klass:
|
||||
point.attrib['class'] = node.klass
|
||||
if self.id:
|
||||
point.attrib['id'] = node.id
|
||||
label = etree.SubElement(point, NCX('navLabel'))
|
||||
etree.SubElement(label, NCX('text')).text = node.title
|
||||
href = node.href if depth > 1 else urldefrag(node.href)[0]
|
||||
child = etree.SubElement(point,
|
||||
NCX('content'), attrib={'src': href})
|
||||
node.to_ncx(point, playorder, depth+1)
|
||||
node.to_ncx(point, order, depth+1)
|
||||
return parent
|
||||
|
||||
|
||||
class OEBBook(object):
|
||||
def __init__(self, opfpath=None, container=None, logger=FauxLogger()):
|
||||
def __init__(self, opfpath=None, container=None, encoding=None,
|
||||
logger=FauxLogger()):
|
||||
if opfpath and not container:
|
||||
container = DirContainer(os.path.dirname(opfpath))
|
||||
opfpath = os.path.basename(opfpath)
|
||||
self.container = container
|
||||
self.encoding = encoding
|
||||
self.logger = logger
|
||||
if opfpath or container:
|
||||
opf = self._read_opf(opfpath)
|
||||
@ -745,7 +793,7 @@ class OEBBook(object):
|
||||
for tag in ('manifest', 'spine', 'tours', 'guide'):
|
||||
for element in opf.xpath(tag):
|
||||
nroot.append(element)
|
||||
return etree.fromstring(etree.tostring(nroot), parser=XML_PARSER)
|
||||
return etree.fromstring(etree.tostring(nroot))
|
||||
|
||||
def _read_opf(self, opfpath):
|
||||
opf = self.container.read_xml(opfpath)
|
||||
@ -786,13 +834,13 @@ class OEBBook(object):
|
||||
break
|
||||
if not metadata.language:
|
||||
self.logger.warn(u'Language not specified.')
|
||||
metadata.add('language', 'en')
|
||||
metadata.add('language', get_lang())
|
||||
if not metadata.creator:
|
||||
self.logger.warn(u'Creator not specified.')
|
||||
metadata.add('creator', 'Unknown')
|
||||
metadata.add('creator', _('Unknown'))
|
||||
if not metadata.title:
|
||||
self.logger.warn(u'Title not specified.')
|
||||
metadata.add('title', 'Unknown')
|
||||
metadata.add('title', _('Unknown'))
|
||||
|
||||
def _manifest_from_opf(self, opf):
|
||||
self.manifest = manifest = Manifest(self)
|
||||
@ -829,6 +877,8 @@ class OEBBook(object):
|
||||
extras.sort()
|
||||
for item in extras:
|
||||
spine.add(item, False)
|
||||
if len(spine) == 0:
|
||||
raise OEBError("Spine is empty")
|
||||
|
||||
def _guide_from_opf(self, opf):
|
||||
self.guide = guide = Guide(self)
|
||||
@ -858,8 +908,11 @@ class OEBBook(object):
|
||||
if len(result) != 1:
|
||||
return False
|
||||
id = result[0]
|
||||
ncx = self.manifest[id].data
|
||||
self.manifest.remove(id)
|
||||
if id not in self.manifest.ids:
|
||||
return False
|
||||
item = self.manifest.ids[id]
|
||||
ncx = item.data
|
||||
self.manifest.remove(item)
|
||||
title = xpath(ncx, 'ncx:docTitle/ncx:text/text()')[0]
|
||||
self.toc = toc = TOC(title)
|
||||
navmaps = xpath(ncx, 'ncx:navMap')
|
||||
@ -973,6 +1026,11 @@ class OEBBook(object):
|
||||
self._toc_from_opf(opf)
|
||||
self._ensure_cover_image()
|
||||
|
||||
def translate(self, text):
|
||||
lang = str(self.metadata.language[0])
|
||||
lang = lang.split('-', 1)[0].lower()
|
||||
return translate(lang, text)
|
||||
|
||||
def to_opf1(self):
|
||||
package = etree.Element('package',
|
||||
attrib={'unique-identifier': self.uid.id})
|
||||
@ -986,22 +1044,11 @@ class OEBBook(object):
|
||||
guide = self.guide.to_opf1(package)
|
||||
return {OPF_MIME: ('content.opf', package)}
|
||||
|
||||
def _generate_ncx_item(self):
|
||||
id = 'ncx'
|
||||
index = 0
|
||||
while id in self.manifest:
|
||||
id = 'ncx' + str(index)
|
||||
index = index + 1
|
||||
href = 'toc'
|
||||
index = 0
|
||||
while (href + '.ncx') in self.manifest.hrefs:
|
||||
href = 'toc' + str(index)
|
||||
href += '.ncx'
|
||||
return (id, href)
|
||||
|
||||
def _to_ncx(self):
|
||||
ncx = etree.Element(NCX('ncx'), attrib={'version': '2005-1'},
|
||||
nsmap={None: NCX_NS})
|
||||
lang = unicode(self.metadata.language[0])
|
||||
ncx = etree.Element(NCX('ncx'),
|
||||
attrib={'version': '2005-1', XML('lang'): lang},
|
||||
nsmap={None: NCX_NS})
|
||||
head = etree.SubElement(ncx, NCX('head'))
|
||||
etree.SubElement(head, NCX('meta'),
|
||||
attrib={'name': 'dtb:uid', 'content': unicode(self.uid)})
|
||||
@ -1024,7 +1071,7 @@ class OEBBook(object):
|
||||
nsmap={None: OPF2_NS})
|
||||
metadata = self.metadata.to_opf2(package)
|
||||
manifest = self.manifest.to_opf2(package)
|
||||
id, href = self._generate_ncx_item()
|
||||
id, href = self.manifest.generate('ncx', 'toc.ncx')
|
||||
etree.SubElement(manifest, OPF('item'),
|
||||
attrib={'id': id, 'href': href, 'media-type': NCX_MIME})
|
||||
spine = self.spine.to_opf2(package)
|
||||
|
@ -110,7 +110,8 @@ class Stylizer(object):
|
||||
|
||||
def __init__(self, tree, path, oeb, profile=PROFILES['PRS505']):
|
||||
self.profile = profile
|
||||
base = os.path.dirname(path)
|
||||
self.logger = oeb.logger
|
||||
item = oeb.manifest.hrefs[path]
|
||||
basename = os.path.basename(path)
|
||||
cssname = os.path.splitext(basename)[0] + '.css'
|
||||
stylesheets = [HTML_CSS_STYLESHEET]
|
||||
@ -128,8 +129,12 @@ class Stylizer(object):
|
||||
and elem.get('rel', 'stylesheet') == 'stylesheet' \
|
||||
and elem.get('type', CSS_MIME) in OEB_STYLES:
|
||||
href = urlnormalize(elem.attrib['href'])
|
||||
path = os.path.join(base, href)
|
||||
path = os.path.normpath(path).replace('\\', '/')
|
||||
path = item.abshref(href)
|
||||
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:
|
||||
stylesheet = self.STYLESHEETS[path]
|
||||
else:
|
||||
@ -223,8 +228,11 @@ class Stylizer(object):
|
||||
for key in composition:
|
||||
style[key] = 'inherit'
|
||||
else:
|
||||
primitives = [v.cssText for v in cssvalue]
|
||||
primitites.reverse()
|
||||
try:
|
||||
primitives = [v.cssText for v in cssvalue]
|
||||
except TypeError:
|
||||
primitives = [cssvalue.cssText]
|
||||
primitives.reverse()
|
||||
value = primitives.pop()
|
||||
for key in composition:
|
||||
if cssproperties.cssvalues[key](value):
|
||||
@ -274,7 +282,9 @@ class Style(object):
|
||||
def _apply_style_attr(self):
|
||||
attrib = self._element.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))
|
||||
|
||||
def _has_parent(self):
|
||||
|
@ -199,7 +199,8 @@ class CSSFlattener(object):
|
||||
items = cssdict.items()
|
||||
items.sort()
|
||||
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:
|
||||
match = styles[css]
|
||||
else:
|
||||
|
@ -13,6 +13,10 @@ from calibre.ebooks.oeb.base import XML, XHTML, XHTML_NS
|
||||
from calibre.ebooks.oeb.base import XHTML_MIME, CSS_MIME
|
||||
from calibre.ebooks.oeb.base import element
|
||||
|
||||
__all__ = ['HTMLTOCAdder']
|
||||
|
||||
DEFAULT_TITLE = __('Table of Contents')
|
||||
|
||||
STYLE_CSS = {
|
||||
'nested': """
|
||||
.calibre_toc_header {
|
||||
@ -44,13 +48,15 @@ body > .calibre_toc_block {
|
||||
}
|
||||
|
||||
class HTMLTOCAdder(object):
|
||||
def __init__(self, style='nested'):
|
||||
def __init__(self, title=None, style='nested'):
|
||||
self.title = title
|
||||
self.style = style
|
||||
|
||||
def transform(self, oeb, context):
|
||||
if 'toc' in oeb.guide:
|
||||
return
|
||||
oeb.logger.info('Generating in-line TOC...')
|
||||
title = self.title or oeb.translate(DEFAULT_TITLE)
|
||||
style = self.style
|
||||
if style not in STYLE_CSS:
|
||||
oeb.logger.error('Unknown TOC style %r' % style)
|
||||
@ -61,15 +67,15 @@ class HTMLTOCAdder(object):
|
||||
contents = element(None, XHTML('html'), nsmap={None: XHTML_NS},
|
||||
attrib={XML('lang'): language})
|
||||
head = element(contents, XHTML('head'))
|
||||
title = element(head, XHTML('title'))
|
||||
title.text = 'Table of Contents'
|
||||
htitle = element(head, XHTML('title'))
|
||||
htitle.text = title
|
||||
element(head, XHTML('link'), rel='stylesheet', type=CSS_MIME,
|
||||
href=css_href)
|
||||
body = element(contents, XHTML('body'),
|
||||
attrib={'class': 'calibre_toc'})
|
||||
h1 = element(body, XHTML('h1'),
|
||||
attrib={'class': 'calibre_toc_header'})
|
||||
h1.text = 'Table of Contents'
|
||||
h1.text = title
|
||||
self.add_toc_level(body, oeb.toc)
|
||||
id, href = oeb.manifest.generate('contents', 'contents.xhtml')
|
||||
item = oeb.manifest.add(id, href, XHTML_MIME, data=contents)
|
||||
|
@ -6,7 +6,7 @@ from PyQt4.QtCore import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSiz
|
||||
QByteArray, QLocale, QUrl, QTranslator, QCoreApplication, \
|
||||
QModelIndex
|
||||
from PyQt4.QtGui import QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
|
||||
QIcon, QTableView, QDialogButtonBox, QApplication
|
||||
QIcon, QTableView, QDialogButtonBox, QApplication, QDialog
|
||||
|
||||
ORG_NAME = 'KovidsBrain'
|
||||
APP_UID = 'libprs500'
|
||||
@ -60,6 +60,8 @@ def _config():
|
||||
help=_('Delete books from library after uploading to device'))
|
||||
c.add_opt('separate_cover_flow', default=False,
|
||||
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)
|
||||
|
||||
config = _config()
|
||||
@ -392,7 +394,20 @@ def pixmap_to_data(pixmap, format='JPEG'):
|
||||
pixmap.save(buf, format)
|
||||
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:
|
||||
from calibre.utils.single_qt_application import SingleApplication
|
||||
except:
|
||||
|
@ -186,6 +186,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
single_format = config['save_to_disk_single_format']
|
||||
self.single_format.setCurrentIndex(BOOK_EXTENSIONS.index(single_format))
|
||||
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 import language_codes
|
||||
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['show_text_in_toolbar'] = bool(self.show_toolbar_text.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()
|
||||
prefs['filename_pattern'] = pattern
|
||||
p = {0:'normal', 1:'high', 2:'low'}[self.priority.currentIndex()]
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>570</height>
|
||||
<width>755</width>
|
||||
<height>557</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
@ -328,8 +328,8 @@
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="page" >
|
||||
<layout class="QGridLayout" name="gridLayout_3" >
|
||||
<item row="0" column="0" >
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4" >
|
||||
<item>
|
||||
<widget class="QCheckBox" name="roman_numerals" >
|
||||
<property name="text" >
|
||||
<string>Use &Roman numerals for series number</string>
|
||||
@ -339,12 +339,47 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<item>
|
||||
<widget class="QCheckBox" name="systray_icon" >
|
||||
<property name="text" >
|
||||
<string>Enable system &tray icon (needs restart)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="systray_notifications" >
|
||||
<property name="text" >
|
||||
<string>Show &notifications in system tray</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="separate_cover_flow" >
|
||||
<property name="text" >
|
||||
<string>Show cover &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 &news to ebook reader</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="delete_news" >
|
||||
<property name="text" >
|
||||
<string>&Delete news from library when it is sent to reader</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" >
|
||||
<item>
|
||||
<widget class="QLabel" name="label_6" >
|
||||
<property name="text" >
|
||||
<string>&Number of covers to show in browse mode (after restart):</string>
|
||||
<string>&Number of covers to show in browse mode (needs restart):</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>cover_browse</cstring>
|
||||
@ -356,7 +391,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="7" column="0" >
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2" >
|
||||
<property name="title" >
|
||||
<string>Toolbar</string>
|
||||
@ -402,118 +437,112 @@
|
||||
</widget>
|
||||
</item>
|
||||
</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>
|
||||
</item>
|
||||
<item row="8" column="0" >
|
||||
<widget class="QGroupBox" name="groupBox" >
|
||||
<property name="title" >
|
||||
<string>Select visible &columns in library view</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4" >
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3" >
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7" >
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox" >
|
||||
<property name="title" >
|
||||
<string>Select visible &columns in library view</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_7" >
|
||||
<item>
|
||||
<widget class="QListWidget" name="columns" >
|
||||
<property name="alternatingRowColors" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionBehavior" >
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3" >
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3" >
|
||||
<item>
|
||||
<widget class="QToolButton" name="column_up" >
|
||||
<property name="text" >
|
||||
<string>...</string>
|
||||
<widget class="QListWidget" name="columns" >
|
||||
<property name="alternatingRowColors" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon" >
|
||||
<iconset resource="../images.qrc" >
|
||||
<normaloff>:/images/arrow-up.svg</normaloff>:/images/arrow-up.svg</iconset>
|
||||
<property name="selectionBehavior" >
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0" >
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</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>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3" >
|
||||
<item>
|
||||
<widget class="QToolButton" name="column_up" >
|
||||
<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>
|
||||
<spacer name="verticalSpacer_2" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0" >
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</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>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3" >
|
||||
<property name="title" >
|
||||
<string>Use internal &viewer for the following formats:</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QListWidget" name="viewer" >
|
||||
<property name="alternatingRowColors" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode" >
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QCheckBox" name="systray_icon" >
|
||||
<property name="text" >
|
||||
<string>Enable system &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 &news to ebook reader</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" >
|
||||
<widget class="QCheckBox" name="delete_news" >
|
||||
<property name="text" >
|
||||
<string>&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 &browser in a separate window (needs restart)</string>
|
||||
</property>
|
||||
</widget>
|
||||
<zorder>columns</zorder>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3" >
|
||||
<property name="title" >
|
||||
<string>Use internal &viewer for:</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_4" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QListWidget" name="viewer" >
|
||||
<property name="alternatingRowColors" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode" >
|
||||
<enum>QAbstractItemView::NoSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</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 class="QWidget" name="page_2" >
|
||||
<layout class="QVBoxLayout" >
|
||||
|
@ -14,19 +14,21 @@ from lxml.etree import XPath
|
||||
|
||||
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
||||
from calibre.gui2.dialogs.epub_ui import Ui_Dialog
|
||||
from calibre.gui2 import error_dialog, choose_images, pixmap_to_data
|
||||
from calibre.ebooks.epub.from_any import SOURCE_FORMATS, config
|
||||
from calibre.gui2 import error_dialog, choose_images, pixmap_to_data, ResizableDialog
|
||||
from calibre.ebooks.epub.from_any import SOURCE_FORMATS, config as epubconfig
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.ebooks.metadata.opf import OPFCreator
|
||||
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):
|
||||
QDialog.__init__(self, parent)
|
||||
self.setupUi(self)
|
||||
OUTPUT = 'EPUB'
|
||||
|
||||
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.show_category_help)
|
||||
self.connect(self.cover_button, SIGNAL("clicked()"), self.select_cover)
|
||||
@ -38,7 +40,7 @@ class Config(QDialog, Ui_Dialog):
|
||||
if row is not None:
|
||||
self.id = db.id(row)
|
||||
base = config().as_string() + '\n\n'
|
||||
defaults = self.db.conversion_options(self.id, 'epub')
|
||||
defaults = self.db.conversion_options(self.id, self.OUTPUT.lower())
|
||||
defaults = base + (defaults if defaults else '')
|
||||
self.config = config(defaults=defaults)
|
||||
else:
|
||||
@ -47,20 +49,29 @@ class Config(QDialog, Ui_Dialog):
|
||||
self.get_source_format()
|
||||
self.category_list.setCurrentRow(0)
|
||||
if self.row is None:
|
||||
self.setWindowTitle(_('Bulk convert to EPUB'))
|
||||
self.setWindowTitle(_('Bulk convert to ')+self.OUTPUT)
|
||||
else:
|
||||
self.setWindowTitle(_(u'Convert %s to EPUB')%unicode(self.title.text()))
|
||||
self.setWindowTitle((_(u'Convert %s to ')%unicode(self.title.text()))+self.OUTPUT)
|
||||
|
||||
def hide_controls(self):
|
||||
self.source_profile_label.setVisible(False)
|
||||
self.opt_source_profile.setVisible(False)
|
||||
self.dest_profile_label.setVisible(False)
|
||||
self.opt_dest_profile.setVisible(False)
|
||||
self.opt_toc_title.setVisible(False)
|
||||
self.toc_title_label.setVisible(False)
|
||||
self.opt_rescale_images.setVisible(False)
|
||||
|
||||
def initialize(self):
|
||||
self.__w = []
|
||||
self.__w.append(QIcon(':/images/dialog_information.svg'))
|
||||
self.item1 = QListWidgetItem(self.__w[-1], _('Metadata'), self.category_list)
|
||||
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.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.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.initialize_options()
|
||||
|
||||
@ -81,12 +92,12 @@ class Config(QDialog, Ui_Dialog):
|
||||
def show_category_help(self, item):
|
||||
text = unicode(item.text())
|
||||
help = {
|
||||
_('Metadata') : _('Specify metadata such as title and author for the book.\n\nMetadata will be updated in the database as well as the generated EPUB file.'),
|
||||
_('Look & Feel') : _('Adjust the look of the generated EPUB file by specifying things like font sizes.'),
|
||||
_('Metadata') : _('Specify metadata such as title and author for the book.\n\nMetadata will be updated in the database as well as the generated %s file.')%self.OUTPUT,
|
||||
_('Look & Feel') : _('Adjust the look of the generated ebook by specifying things like font sizes.'),
|
||||
_('Page Setup') : _('Specify the page layout settings like margins.'),
|
||||
_('Chapter Detection') : _('Fine tune the detection of chapter and section headings.'),
|
||||
}
|
||||
self.set_help(help[text])
|
||||
self.set_help(help[text.replace('\n', ' ')])
|
||||
|
||||
def select_cover(self):
|
||||
files = choose_images(self, 'change cover dialog',
|
||||
@ -195,7 +206,7 @@ class Config(QDialog, Ui_Dialog):
|
||||
elif isinstance(g, QCheckBox):
|
||||
self.config.set(pref.name, bool(g.isChecked()))
|
||||
if self.row is not None:
|
||||
self.db.set_conversion_options(self.id, 'epub', self.config.src)
|
||||
self.db.set_conversion_options(self.id, self.OUTPUT.lower(), self.config.src)
|
||||
|
||||
|
||||
def initialize_options(self):
|
||||
@ -235,7 +246,7 @@ class Config(QDialog, Ui_Dialog):
|
||||
elif len(choices) == 1:
|
||||
self.source_format = choices[0]
|
||||
else:
|
||||
d = ChooseFormatDialog(self.parent(), _('Choose the format to convert to EPUB'), choices)
|
||||
d = ChooseFormatDialog(self.parent(), _('Choose the format to convert to ')+self.OUTPUT, choices)
|
||||
if d.exec_() == QDialog.Accepted:
|
||||
self.source_format = d.format()
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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, \
|
||||
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.fetch_metadata import FetchMetadata
|
||||
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]))
|
||||
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):
|
||||
pix = QPixmap(':/images/book.svg')
|
||||
@ -164,9 +164,7 @@ class MetadataSingleDialog(QDialog, Ui_MetadataSingleDialog):
|
||||
self.db.remove_format(self.row, ext, notify=False)
|
||||
|
||||
def __init__(self, window, row, db, accepted_callback=None):
|
||||
QDialog.__init__(self, window)
|
||||
Ui_MetadataSingleDialog.__init__(self)
|
||||
self.setupUi(self)
|
||||
ResizableDialog.__init__(self, window)
|
||||
self.bc_box.layout().setAlignment(self.cover, Qt.AlignCenter|Qt.AlignHCenter)
|
||||
self.splitter.setStretchFactor(100, 1)
|
||||
self.db = db
|
||||
|
File diff suppressed because it is too large
Load Diff
20
src/calibre/gui2/dialogs/mobi.py
Normal file
20
src/calibre/gui2/dialogs/mobi.py
Normal 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)
|
@ -3,21 +3,20 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import time, os
|
||||
|
||||
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.news import AutomaticNewsRecipe
|
||||
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.ptempfile import PersistentTemporaryFile
|
||||
|
||||
class UserProfiles(QDialog, Ui_Dialog):
|
||||
class UserProfiles(ResizableDialog, Ui_Dialog):
|
||||
|
||||
def __init__(self, parent, feeds):
|
||||
QDialog.__init__(self, parent)
|
||||
Ui_Dialog.__init__(self)
|
||||
self.setupUi(self)
|
||||
ResizableDialog.__init__(self, parent)
|
||||
|
||||
self.connect(self.remove_feed_button, SIGNAL('clicked(bool)'),
|
||||
self.added_feeds.remove_selected_items)
|
||||
|
@ -5,8 +5,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>744</width>
|
||||
<height>633</height>
|
||||
<width>719</width>
|
||||
<height>612</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
@ -16,8 +16,420 @@
|
||||
<iconset resource="../images.qrc" >
|
||||
<normaloff>:/images/user_profile.svg</normaloff>:/images/user_profile.svg</iconset>
|
||||
</property>
|
||||
<layout class="QGridLayout" >
|
||||
<item row="1" column="0" >
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4" >
|
||||
<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 &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>&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>&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 &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>&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><html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<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. <br />For most feeds, you will have to use the "Advanced mode" to further customize the fetch process.</p></body></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 &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>&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>&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>&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 &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>&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 <a href="http://__appname__.kovidgoyal.net/user_manual/news.html">User Recipes</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" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
@ -27,360 +439,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</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 &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>&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>&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 &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>&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><html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<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. <br />For most feeds, you will have to use the "Advanced mode" to further customize the fetch process.</p></body></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 &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>&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>&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>&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 &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>&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 <a href="http://__appname__.kovidgoyal.net/user_manual/news.html">User Recipes</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>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
|
BIN
src/calibre/gui2/images/news/amspec.png
Normal file
BIN
src/calibre/gui2/images/news/amspec.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 834 B |
BIN
src/calibre/gui2/images/news/criticadigital.png
Normal file
BIN
src/calibre/gui2/images/news/criticadigital.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 710 B |
BIN
src/calibre/gui2/images/news/elcronista.png
Normal file
BIN
src/calibre/gui2/images/news/elcronista.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 770 B |
BIN
src/calibre/gui2/images/news/freakonomics.png
Normal file
BIN
src/calibre/gui2/images/news/freakonomics.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
@ -162,7 +162,8 @@ class BooksModel(QAbstractTableModel):
|
||||
|
||||
def refresh_ids(self, ids, current_row=-1):
|
||||
rows = self.db.refresh_ids(ids)
|
||||
self.refresh_rows(rows, current_row=current_row)
|
||||
if rows:
|
||||
self.refresh_rows(rows, current_row=current_row)
|
||||
|
||||
def refresh_rows(self, rows, current_row=-1):
|
||||
for row in rows:
|
||||
@ -261,7 +262,17 @@ class BooksModel(QAbstractTableModel):
|
||||
self.reset()
|
||||
self.sorted_on = (self.column_map[col], order)
|
||||
|
||||
|
||||
|
||||
def refresh(self, reset=True):
|
||||
try:
|
||||
col = self.column_map.index(self.sorted_on[0])
|
||||
except:
|
||||
col = 0
|
||||
self.db.refresh(field=self.column_map[col],
|
||||
ascending=self.sorted_on[1]==Qt.AscendingOrder)
|
||||
if reset:
|
||||
self.reset()
|
||||
|
||||
def resort(self, reset=True):
|
||||
try:
|
||||
col = self.column_map.index(self.sorted_on[0])
|
||||
|
@ -25,7 +25,6 @@ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
|
||||
max_available_height, config, info_dialog, \
|
||||
available_width
|
||||
from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
|
||||
from calibre.library.database import LibraryDatabase
|
||||
from calibre.gui2.dialogs.scheduler import Scheduler
|
||||
from calibre.gui2.update import CheckForUpdates
|
||||
from calibre.gui2.dialogs.progress import ProgressDialog
|
||||
@ -131,20 +130,12 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
QObject.connect(self.stack, SIGNAL('currentChanged(int)'),
|
||||
self.location_view.location_changed)
|
||||
|
||||
self.output_formats = sorted(['EPUB', 'LRF'])
|
||||
self.output_formats = sorted(['EPUB', 'MOBI', 'LRF'])
|
||||
for f in self.output_formats:
|
||||
self.output_format.addItem(f)
|
||||
self.output_format.setCurrentIndex(self.output_formats.index(prefs['output_format']))
|
||||
def change_output_format(x):
|
||||
of = unicode(x).strip()
|
||||
if of != prefs['output_format']:
|
||||
if of in ('EPUB', 'LIT'):
|
||||
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)'),
|
||||
change_output_format)
|
||||
self.change_output_format, Qt.QueuedConnection)
|
||||
|
||||
####################### Vanity ########################
|
||||
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.show()
|
||||
if self.system_tray_icon.isVisible() and opts.start_in_tray:
|
||||
self.hide()
|
||||
self.stack.setCurrentIndex(0)
|
||||
try:
|
||||
db = LibraryDatabase2(self.library_path)
|
||||
@ -375,6 +368,15 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.action_news.setMenu(self.scheduler.news_menu)
|
||||
self.connect(self.action_news, SIGNAL('triggered(bool)'), self.scheduler.show_dialog)
|
||||
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):
|
||||
if self.content_server.exception is not None:
|
||||
@ -477,7 +479,7 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.raise_()
|
||||
self.activateWindow()
|
||||
elif msg.startswith('refreshdb:'):
|
||||
self.library_view.model().resort()
|
||||
self.library_view.model().refresh()
|
||||
self.library_view.model().research()
|
||||
else:
|
||||
print msg
|
||||
@ -910,13 +912,14 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
_files = self.library_view.model().get_preferred_formats(rows,
|
||||
self.device_manager.device_class.FORMATS, paths=True)
|
||||
files = [getattr(f, 'name', None) for f in _files]
|
||||
bad, good, gf, names = [], [], [], []
|
||||
bad, good, gf, names, remove_ids = [], [], [], [], []
|
||||
for f in files:
|
||||
mi = metadata.next()
|
||||
id = ids.next()
|
||||
if f is None:
|
||||
bad.append(mi['title'])
|
||||
else:
|
||||
remove_ids.append(id)
|
||||
aus = mi['authors'].split(',')
|
||||
aus2 = []
|
||||
for a in aus:
|
||||
@ -943,7 +946,7 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
prefix = prefix.decode(preferred_encoding, 'replace')
|
||||
prefix = ascii_filename(prefix)
|
||||
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.status_bar.showMessage(_('Sending books to device.'), 5000)
|
||||
if bad:
|
||||
@ -1527,6 +1530,8 @@ path_to_ebook to the database.
|
||||
''')
|
||||
parser.add_option('--with-library', default=None, action='store',
|
||||
help=_('Use the library located at the specified path.'))
|
||||
parser.add_option('--start-in-tray', default=False, action='store_true',
|
||||
help=_('Start minimized to system tray.'))
|
||||
parser.add_option('-v', '--verbose', default=0, action='count',
|
||||
help=_('Log debugging information to console'))
|
||||
return parser
|
||||
|
@ -119,7 +119,11 @@
|
||||
</widget>
|
||||
</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>
|
||||
</layout>
|
||||
</item>
|
||||
|
@ -6,7 +6,7 @@ from PyQt4.QtGui import QStatusBar, QMovie, QLabel, QWidget, QHBoxLayout, QPixma
|
||||
QVBoxLayout, QSizePolicy, QToolButton, QIcon, QScrollArea, QFrame
|
||||
from PyQt4.QtCore import Qt, QSize, SIGNAL, QCoreApplication
|
||||
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 BookCoverDisplay(QLabel):
|
||||
@ -197,7 +197,7 @@ class StatusBar(QStatusBar):
|
||||
|
||||
def showMessage(self, msg, timeout=0):
|
||||
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):
|
||||
try:
|
||||
msg = msg.encode(preferred_encoding)
|
||||
|
@ -12,6 +12,7 @@ from PyQt4.Qt import QDialog
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.gui2.dialogs.lrf_single import LRFSingleDialog, LRFBulkDialog
|
||||
from calibre.gui2.dialogs.epub import Config as EPUBConvert
|
||||
from calibre.gui2.dialogs.mobi import Config as MOBIConvert
|
||||
import calibre.gui2.dialogs.comicconf as ComicConf
|
||||
from calibre.gui2 import warning_dialog
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
@ -19,14 +20,20 @@ from calibre.ebooks.lrf import preferred_source_formats as LRF_PREFERRED_SOURCE_
|
||||
from calibre.ebooks.metadata.opf import OPFCreator
|
||||
from calibre.ebooks.epub.from_any import SOURCE_FORMATS as EPUB_PREFERRED_SOURCE_FORMATS
|
||||
|
||||
def convert_single_epub(parent, db, comics, others):
|
||||
def get_dialog(fmt):
|
||||
return {
|
||||
'epub':EPUBConvert,
|
||||
'mobi':MOBIConvert,
|
||||
}[fmt]
|
||||
|
||||
def convert_single(fmt, parent, db, comics, others):
|
||||
changed = False
|
||||
jobs = []
|
||||
others_ids = [db.id(row) for row in others]
|
||||
comics_ids = [db.id(row) for row in comics]
|
||||
for row, row_id in zip(others, others_ids):
|
||||
temp_files = []
|
||||
d = EPUBConvert(parent, db, row)
|
||||
d = get_dialog(fmt)(parent, db, row)
|
||||
if d.source_format is not None:
|
||||
d.exec_()
|
||||
if d.result() == QDialog.Accepted:
|
||||
@ -35,7 +42,7 @@ def convert_single_epub(parent, db, comics, others):
|
||||
pt = PersistentTemporaryFile('.'+d.source_format.lower())
|
||||
pt.write(data)
|
||||
pt.close()
|
||||
of = PersistentTemporaryFile('.epub')
|
||||
of = PersistentTemporaryFile('.'+fmt)
|
||||
of.close()
|
||||
opts.output = of.name
|
||||
opts.from_opf = d.opf_file.name
|
||||
@ -45,8 +52,8 @@ def convert_single_epub(parent, db, comics, others):
|
||||
temp_files.append(d.cover_file)
|
||||
opts.cover = d.cover_file.name
|
||||
temp_files.extend([d.opf_file, pt, of])
|
||||
jobs.append(('any2epub', args, _('Convert book: ')+d.mi.title,
|
||||
'EPUB', row_id, temp_files))
|
||||
jobs.append(('any2'+fmt, args, _('Convert book: ')+d.mi.title,
|
||||
fmt.upper(), row_id, temp_files))
|
||||
changed = True
|
||||
|
||||
for row, row_id in zip(comics, comics_ids):
|
||||
@ -61,24 +68,24 @@ def convert_single_epub(parent, db, comics, others):
|
||||
if defaults is not None:
|
||||
db.set_conversion_options(db.id(row), 'comic', defaults)
|
||||
if opts is None: continue
|
||||
for fmt in ['cbz', 'cbr']:
|
||||
for _fmt in ['cbz', 'cbr']:
|
||||
try:
|
||||
data = db.format(row, fmt.upper())
|
||||
data = db.format(row, _fmt.upper())
|
||||
if data is not None:
|
||||
break
|
||||
except:
|
||||
continue
|
||||
pt = PersistentTemporaryFile('.'+fmt)
|
||||
pt = PersistentTemporaryFile('.'+_fmt)
|
||||
pt.write(data)
|
||||
pt.close()
|
||||
of = PersistentTemporaryFile('.epub')
|
||||
of = PersistentTemporaryFile('.'+fmt)
|
||||
of.close()
|
||||
opts.output = of.name
|
||||
opts.verbose = 2
|
||||
args = [pt.name, opts]
|
||||
changed = True
|
||||
jobs.append(('comic2epub', args, _('Convert comic: ')+opts.title,
|
||||
'EPUB', row_id, [pt, of]))
|
||||
jobs.append(('comic2'+fmt, args, _('Convert comic: ')+opts.title,
|
||||
fmt.upper(), row_id, [pt, of]))
|
||||
|
||||
return jobs, changed
|
||||
|
||||
@ -146,9 +153,9 @@ def convert_single_lrf(parent, db, comics, others):
|
||||
|
||||
return jobs, changed
|
||||
|
||||
def convert_bulk_epub(parent, db, comics, others):
|
||||
def convert_bulk(fmt, parent, db, comics, others):
|
||||
if others:
|
||||
d = EPUBConvert(parent, db)
|
||||
d = get_dialog(fmt)(parent, db)
|
||||
if d.exec_() != QDialog.Accepted:
|
||||
others = []
|
||||
else:
|
||||
@ -169,9 +176,9 @@ def convert_bulk_epub(parent, db, comics, others):
|
||||
row_id = db.id(row)
|
||||
if row in others:
|
||||
data = None
|
||||
for fmt in EPUB_PREFERRED_SOURCE_FORMATS:
|
||||
for _fmt in EPUB_PREFERRED_SOURCE_FORMATS:
|
||||
try:
|
||||
data = db.format(row, fmt.upper())
|
||||
data = db.format(row, _fmt.upper())
|
||||
if data is not None:
|
||||
break
|
||||
except:
|
||||
@ -185,10 +192,10 @@ def convert_bulk_epub(parent, db, comics, others):
|
||||
opf_file = PersistentTemporaryFile('.opf')
|
||||
opf.render(opf_file)
|
||||
opf_file.close()
|
||||
pt = PersistentTemporaryFile('.'+fmt.lower())
|
||||
pt = PersistentTemporaryFile('.'+_fmt.lower())
|
||||
pt.write(data)
|
||||
pt.close()
|
||||
of = PersistentTemporaryFile('.epub')
|
||||
of = PersistentTemporaryFile('.'+fmt)
|
||||
of.close()
|
||||
cover = db.cover(row)
|
||||
cf = None
|
||||
@ -203,7 +210,7 @@ def convert_bulk_epub(parent, db, comics, others):
|
||||
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
|
||||
temp_files = [cf] if cf is not None else []
|
||||
temp_files.extend([opf_file, pt, of])
|
||||
jobs.append(('any2epub', args, desc, 'EPUB', row_id, temp_files))
|
||||
jobs.append(('any2'+fmt, args, desc, fmt.upper(), row_id, temp_files))
|
||||
else:
|
||||
options = comic_opts.copy()
|
||||
mi = db.get_metadata(row)
|
||||
@ -212,24 +219,24 @@ def convert_bulk_epub(parent, db, comics, others):
|
||||
if mi.authors:
|
||||
options.author = ','.join(mi.authors)
|
||||
data = None
|
||||
for fmt in ['cbz', 'cbr']:
|
||||
for _fmt in ['cbz', 'cbr']:
|
||||
try:
|
||||
data = db.format(row, fmt.upper())
|
||||
data = db.format(row, _fmt.upper())
|
||||
if data is not None:
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
pt = PersistentTemporaryFile('.'+fmt.lower())
|
||||
pt = PersistentTemporaryFile('.'+_fmt.lower())
|
||||
pt.write(data)
|
||||
pt.close()
|
||||
of = PersistentTemporaryFile('.epub')
|
||||
of = PersistentTemporaryFile('.'+fmt)
|
||||
of.close()
|
||||
setattr(options, 'output', of.name)
|
||||
options.verbose = 1
|
||||
args = [pt.name, options]
|
||||
desc = _('Convert book %d of %d (%s)')%(i+1, total, repr(mi.title))
|
||||
jobs.append(('comic2epub', args, desc, 'EPUB', row_id, [pt, of]))
|
||||
jobs.append(('comic2'+fmt, args, desc, fmt.upper(), row_id, [pt, of]))
|
||||
|
||||
if bad_rows:
|
||||
res = []
|
||||
@ -345,15 +352,14 @@ def set_conversion_defaults_lrf(comic, parent, db):
|
||||
else:
|
||||
LRFSingleDialog(parent, None, None).exec_()
|
||||
|
||||
def set_conversion_defaults_epub(comic, parent, db):
|
||||
def _set_conversion_defaults(dialog, comic, parent, db):
|
||||
if comic:
|
||||
ComicConf.set_conversion_defaults(parent)
|
||||
else:
|
||||
d = EPUBConvert(parent, db)
|
||||
d = dialog(parent, db)
|
||||
d.setWindowTitle(_('Set conversion defaults'))
|
||||
d.exec_()
|
||||
|
||||
|
||||
def _fetch_news(data, fmt):
|
||||
pt = PersistentTemporaryFile(suffix='_feeds2%s.%s'%(fmt.lower(), fmt.lower()))
|
||||
pt.close()
|
||||
@ -385,22 +391,22 @@ def convert_single_ebook(*args):
|
||||
fmt = prefs['output_format'].lower()
|
||||
if fmt == 'lrf':
|
||||
return convert_single_lrf(*args)
|
||||
elif fmt == 'epub':
|
||||
return convert_single_epub(*args)
|
||||
elif fmt in ('epub', 'mobi'):
|
||||
return convert_single(fmt, *args)
|
||||
|
||||
def convert_bulk_ebooks(*args):
|
||||
fmt = prefs['output_format'].lower()
|
||||
if fmt == 'lrf':
|
||||
return convert_bulk_lrf(*args)
|
||||
elif fmt == 'epub':
|
||||
return convert_bulk_epub(*args)
|
||||
elif fmt in ('epub', 'mobi'):
|
||||
return convert_bulk(fmt, *args)
|
||||
|
||||
def set_conversion_defaults(comic, parent, db):
|
||||
fmt = prefs['output_format'].lower()
|
||||
if fmt == 'lrf':
|
||||
return set_conversion_defaults_lrf(comic, parent, db)
|
||||
elif fmt == 'epub':
|
||||
return set_conversion_defaults_epub(comic, parent, db)
|
||||
elif fmt in ('epub', 'mobi'):
|
||||
return _set_conversion_defaults(get_dialog(fmt), comic, parent, db)
|
||||
|
||||
def fetch_news(data):
|
||||
fmt = prefs['output_format'].lower()
|
||||
|
@ -29,5 +29,5 @@ def server_config(defaults=None):
|
||||
c.add_opt('develop', ['--develop'], default=False,
|
||||
help='Development mode. Server automatically restarts on file changes and serves code files (html, css, js) from the file system instead of calibre\'s resource system.')
|
||||
c.add_opt('max_cover', ['--max-cover'], default='600x800',
|
||||
help=_('The maximum size for displayed covers'))
|
||||
help=_('The maximum size for displayed covers. Default is %default.'))
|
||||
return c
|
||||
|
@ -224,9 +224,17 @@ class ResultCache(SearchQueryParser):
|
||||
return False
|
||||
|
||||
def refresh_ids(self, conn, ids):
|
||||
'''
|
||||
Refresh the data in the cache for books identified by ids.
|
||||
Returns a list of affected rows or None if the rows are filtered.
|
||||
'''
|
||||
for id in ids:
|
||||
self._data[id] = conn.get('SELECT * from meta WHERE id=?', (id,))[0]
|
||||
return map(self.row, ids)
|
||||
try:
|
||||
return map(self.row, ids)
|
||||
except ValueError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def books_added(self, ids, conn):
|
||||
if not ids:
|
||||
|
@ -40,7 +40,7 @@ function create_table_headers() {
|
||||
|
||||
|
||||
function format_url(format, id, title) {
|
||||
return 'get/'+format.toLowerCase() + '/'+title + '_' + id+'.'+format.toLowerCase();
|
||||
return 'get/'+format.toLowerCase() + '/'+encodeURIComponent(title) + '_' + id+'.'+format.toLowerCase();
|
||||
}
|
||||
|
||||
function render_book(book) {
|
||||
|
@ -188,8 +188,31 @@ def extract(path, dir):
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
_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'):
|
||||
data = path.read()
|
||||
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)
|
||||
if PFCode != 0:
|
||||
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), \
|
||||
open(os.path.join(dir, *header_data.FileNameW.split('/')), 'rb').read()
|
||||
finally:
|
||||
|
@ -26,6 +26,7 @@ entry_points = {
|
||||
'opf-meta = calibre.ebooks.metadata.opf2:main',
|
||||
'odt-meta = calibre.ebooks.metadata.odt:main',
|
||||
'epub-meta = calibre.ebooks.metadata.epub:main',
|
||||
'mobi-meta = calibre.ebooks.metadata.mobi:main',
|
||||
'txt2lrf = calibre.ebooks.lrf.txt.convert_from:main',
|
||||
'html2lrf = calibre.ebooks.lrf.html.convert_from:main',
|
||||
'html2oeb = calibre.ebooks.html:main',
|
||||
@ -40,6 +41,7 @@ entry_points = {
|
||||
'calibre-server = calibre.library.server:main',
|
||||
'feeds2lrf = calibre.ebooks.lrf.feeds.convert_from:main',
|
||||
'feeds2epub = calibre.ebooks.epub.from_feeds:main',
|
||||
'feeds2mobi = calibre.ebooks.mobi.from_feeds:main',
|
||||
'web2lrf = calibre.ebooks.lrf.web.convert_from:main',
|
||||
'pdf2lrf = calibre.ebooks.lrf.pdf.convert_from:main',
|
||||
'mobi2lrf = calibre.ebooks.lrf.mobi.convert_from:main',
|
||||
@ -56,11 +58,11 @@ entry_points = {
|
||||
'librarything = calibre.ebooks.metadata.library_thing:main',
|
||||
'mobi2oeb = calibre.ebooks.mobi.reader:main',
|
||||
'oeb2mobi = calibre.ebooks.mobi.writer:main',
|
||||
'lrf2html = calibre.ebooks.lrf.html.convert_to:main',
|
||||
'lit2oeb = calibre.ebooks.lit.reader:main',
|
||||
'oeb2lit = calibre.ebooks.lit.writer:main',
|
||||
'comic2lrf = calibre.ebooks.lrf.comic.convert_from:main',
|
||||
'comic2epub = calibre.ebooks.epub.from_comic:main',
|
||||
'comic2mobi = calibre.ebooks.mobi.from_comic:main',
|
||||
'comic2pdf = calibre.ebooks.pdf.from_comic:main',
|
||||
'calibre-debug = calibre.debug: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.odt.to_oeb import option_parser as odt2oeb
|
||||
from calibre.ebooks.epub.from_feeds import option_parser as feeds2epub
|
||||
from calibre.ebooks.mobi.from_feeds import option_parser as feeds2mobi
|
||||
from calibre.ebooks.epub.from_any import option_parser as any2epub
|
||||
from calibre.ebooks.lit.from_any import option_parser as any2lit
|
||||
from calibre.ebooks.epub.from_comic import option_parser as comic2epub
|
||||
@ -219,7 +222,7 @@ def setup_completion(fatal_errors):
|
||||
f.write(opts_and_exts('any2epub', any2epub, any_formats))
|
||||
f.write(opts_and_exts('any2lit', any2lit, any_formats))
|
||||
f.write(opts_and_exts('any2mobi', any2mobi, any_formats))
|
||||
f.write(opts_and_exts('oeb2mobi', oeb2mobi, ['mobi', 'prc']))
|
||||
f.write(opts_and_exts('oeb2mobi', oeb2mobi, ['opf']))
|
||||
f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf']))
|
||||
f.write(opts_and_exts('lrf-meta', metaop, ['lrf']))
|
||||
f.write(opts_and_exts('rtf-meta', metaop, ['rtf']))
|
||||
@ -236,10 +239,12 @@ def setup_completion(fatal_errors):
|
||||
f.write(opts_and_exts('lit2oeb', lit2oeb, ['lit']))
|
||||
f.write(opts_and_exts('comic2lrf', comicop, ['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_words('feeds2disk', feeds2disk, feed_titles))
|
||||
f.write(opts_and_words('feeds2lrf', feeds2lrf, feed_titles))
|
||||
f.write(opts_and_words('feeds2lrf', feeds2epub, feed_titles))
|
||||
f.write(opts_and_words('feeds2epub', feeds2epub, feed_titles))
|
||||
f.write(opts_and_words('feeds2mobi', feeds2mobi, feed_titles))
|
||||
f.write(opts_and_exts('html2epub', html2epub, ['html', 'htm', 'xhtm', 'xhtml', 'opf']))
|
||||
f.write(opts_and_exts('html2oeb', html2oeb, ['html', 'htm', 'xhtm', 'xhtml']))
|
||||
f.write(opts_and_exts('odt2oeb', odt2oeb, ['odt']))
|
||||
@ -420,7 +425,7 @@ def install_man_pages(fatal_errors):
|
||||
if prog in ('prs500', 'pdf-meta', 'epub-meta', 'lit-meta',
|
||||
'markdown-calibre', 'calibre-debug', 'fb2-meta',
|
||||
'calibre-fontconfig', 'calibre-parallel', 'odt-meta',
|
||||
'rb-meta', 'imp-meta'):
|
||||
'rb-meta', 'imp-meta', 'mobi-meta'):
|
||||
continue
|
||||
|
||||
help2man = ('help2man', prog, '--name', 'part of %s'%__appname__,
|
||||
|
@ -19,35 +19,35 @@ What formats does |app| support conversion to/from?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|app| supports the conversion of the following formats:
|
||||
|
||||
+----------------------------+------------------------------------------+
|
||||
| | **Output formats** |
|
||||
| +------------------+-----------------------+
|
||||
| | EPUB | LRF |
|
||||
+===================+========+==================+=======================+
|
||||
| | MOBI | ✔ | ✔ |
|
||||
| | | | |
|
||||
| | LIT | ✔ | ✔ |
|
||||
| | | | |
|
||||
| | PRC | ✔ | ✔ |
|
||||
| | | | |
|
||||
| | EPUB | ✔ | ✔ |
|
||||
| | | | |
|
||||
| | ODT | ✔ | ✔ |
|
||||
| | | | |
|
||||
| | HTML | ✔ | ✔ |
|
||||
| | | | |
|
||||
| **Input formats** | CBR | ✔ | ✔ |
|
||||
| | | | |
|
||||
| | CBZ | ✔ | ✔ |
|
||||
| | | | |
|
||||
| | RTF | ✔ | ✔ |
|
||||
| | | | |
|
||||
| | TXT | ✔ | ✔ |
|
||||
| | | | |
|
||||
| | PDF | ✔ | ✔ |
|
||||
| | | | |
|
||||
| | LRS | | ✔ |
|
||||
+-------------------+--------+------------------+-----------------------+
|
||||
+----------------------------+------------------------------------------------------------------+
|
||||
| | **Output formats** |
|
||||
| +------------------+-----------------------+-----------------------+
|
||||
| | EPUB | LRF | MOBI |
|
||||
+===================+========+==================+=======================+=======================+
|
||||
| | MOBI | ✔ | ✔ | ✔ |
|
||||
| | | | | |
|
||||
| | LIT | ✔ | ✔ | ✔ |
|
||||
| | | | | |
|
||||
| | PRC | ✔ | ✔ | ✔ |
|
||||
| | | | | |
|
||||
| | EPUB | ✔ | ✔ | ✔ |
|
||||
| | | | | |
|
||||
| | ODT | ✔ | ✔ | ✔ |
|
||||
| | | | | |
|
||||
| | HTML | ✔ | ✔ | ✔ |
|
||||
| | | | | |
|
||||
| **Input formats** | CBR | ✔ | ✔ | ✔ |
|
||||
| | | | | |
|
||||
| | CBZ | ✔ | ✔ | ✔ |
|
||||
| | | | | |
|
||||
| | RTF | ✔ | ✔ | ✔ |
|
||||
| | | | | |
|
||||
| | TXT | ✔ | ✔ | ✔ |
|
||||
| | | | | |
|
||||
| | PDF | ✔ | ✔ | ✔ |
|
||||
| | | | | |
|
||||
| | LRS | | ✔ | ✔ |
|
||||
+-------------------+--------+------------------+-----------------------+-----------------------+
|
||||
|
||||
|
||||
|
||||
|
@ -67,7 +67,15 @@ PARALLEL_FUNCS = {
|
||||
|
||||
'comic2epub' :
|
||||
('calibre.ebooks.epub.from_comic', 'convert', {}, 'notification'),
|
||||
|
||||
|
||||
'any2mobi' :
|
||||
('calibre.ebooks.mobi.from_any', 'any2mobi', {}, None),
|
||||
|
||||
'feeds2mobi' :
|
||||
('calibre.ebooks.mobi.from_feeds', 'main', {}, 'notification'),
|
||||
|
||||
'comic2mobi' :
|
||||
('calibre.ebooks.mobi.from_comic', 'convert', {}, 'notification'),
|
||||
}
|
||||
|
||||
|
||||
|
@ -13,6 +13,10 @@ from gettext import GNUTranslations
|
||||
import __builtin__
|
||||
__builtin__.__dict__['_'] = lambda s: s
|
||||
|
||||
# For strings which belong in the translation tables, but which shouldn't be
|
||||
# immediately translated to the environment language
|
||||
__builtin__.__dict__['__'] = lambda s: s
|
||||
|
||||
from calibre.constants import iswindows, preferred_encoding, plugins
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.translations.msgfmt import make
|
||||
|
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
26
src/calibre/translations/dynamic.py
Normal file
26
src/calibre/translations/dynamic.py
Normal 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
@ -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.')
|
||||
c.add_opt('epub', ['--epub'], default=False, action='store_true',
|
||||
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,
|
||||
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='.',
|
||||
|
@ -20,7 +20,7 @@ from PyQt4.QtWebKit import QWebPage
|
||||
from calibre import browser, __appname__, iswindows, LoggingInterface, \
|
||||
strftime, __version__, preferred_encoding
|
||||
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.metadata.toc import TOC
|
||||
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.
|
||||
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.
|
||||
#: A tag is specified as a dictionary of the form::
|
||||
@ -532,7 +534,9 @@ class BasicNewsRecipe(object, LoggingInterface):
|
||||
if body is not None:
|
||||
templ = self.navbar.generate(False, f, a, feed_len,
|
||||
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')
|
||||
body.insert(0, elem)
|
||||
if self.remove_javascript:
|
||||
@ -575,7 +579,8 @@ class BasicNewsRecipe(object, LoggingInterface):
|
||||
|
||||
def feeds2index(self, feeds):
|
||||
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
|
||||
def description_limiter(cls, src):
|
||||
@ -626,7 +631,8 @@ class BasicNewsRecipe(object, LoggingInterface):
|
||||
|
||||
|
||||
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):
|
||||
@ -872,6 +878,7 @@ class BasicNewsRecipe(object, LoggingInterface):
|
||||
|
||||
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.ncx'))
|
||||
cpath = getattr(self, 'cover_path', None)
|
||||
if cpath is None:
|
||||
pf = PersistentTemporaryFile('_recipe_cover.jpg')
|
||||
@ -881,6 +888,9 @@ class BasicNewsRecipe(object, LoggingInterface):
|
||||
opf.cover = cpath
|
||||
manifest.append(cpath)
|
||||
opf.create_manifest_from_files_in(manifest)
|
||||
for mani in opf.manifest:
|
||||
if mani.path.endswith('.ncx'):
|
||||
mani.id = 'ncx'
|
||||
|
||||
entries = ['index.html']
|
||||
toc = TOC(base_path=dir)
|
||||
|
@ -23,7 +23,8 @@ recipe_modules = ['recipe_' + r for r in (
|
||||
'spiegel_int', 'themarketticker', 'tomshardware', 'xkcd', 'ftd', 'zdnet',
|
||||
'joelonsoftware', 'telepolis', 'common_dreams', 'nin', 'tomshardware_de',
|
||||
'pagina12', 'infobae', 'ambito', 'elargentino', 'sueddeutsche', 'the_age',
|
||||
'laprensa',
|
||||
'laprensa', 'amspec', 'freakonomics', 'criticadigital', 'elcronista',
|
||||
'shacknews',
|
||||
)]
|
||||
|
||||
import re, imp, inspect, time, os
|
||||
|
53
src/calibre/web/feeds/recipes/recipe_amspec.py
Normal file
53
src/calibre/web/feeds/recipes/recipe_amspec.py
Normal 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'
|
60
src/calibre/web/feeds/recipes/recipe_criticadigital.py
Normal file
60
src/calibre/web/feeds/recipes/recipe_criticadigital.py
Normal 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
|
70
src/calibre/web/feeds/recipes/recipe_elcronista.py
Normal file
70
src/calibre/web/feeds/recipes/recipe_elcronista.py
Normal 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
|
||||
|
@ -11,7 +11,7 @@ class FazNet(BasicNewsRecipe):
|
||||
|
||||
title = 'FAZ NET'
|
||||
__author__ = 'Kovid Goyal'
|
||||
description = 'News from Germany'
|
||||
description = '"Frankfurter Allgemeine Zeitung'
|
||||
use_embedded_content = False
|
||||
max_articles_per_feed = 30
|
||||
|
||||
|
20
src/calibre/web/feeds/recipes/recipe_freakonomics.py
Normal file
20
src/calibre/web/feeds/recipes/recipe_freakonomics.py
Normal 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'
|
@ -16,6 +16,14 @@ class NewYorker(BasicNewsRecipe):
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = 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 = [
|
||||
dict(name='div' , attrs={'id':'printbody' })
|
||||
|
@ -19,9 +19,16 @@ class Newsweek(BasicNewsRecipe):
|
||||
|
||||
remove_tags = [
|
||||
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(id=['ToolBox', 'EmailMain', 'EmailArticle', ])
|
||||
dict(id=['ToolBox', 'EmailMain', 'EmailArticle', 'comment-box',
|
||||
'nw-comments'])
|
||||
]
|
||||
|
||||
recursions = 1
|
||||
|
26
src/calibre/web/feeds/recipes/recipe_shacknews.py
Normal file
26
src/calibre/web/feeds/recipes/recipe_shacknews.py
Normal 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')
|
||||
]
|
@ -13,7 +13,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
||||
class SpeigelOnline(BasicNewsRecipe):
|
||||
|
||||
title = 'Spiegel Online'
|
||||
description = 'News from Germany'
|
||||
description = 'Nachrichten des Magazins Der Spiegel'
|
||||
__author__ = 'Kovid Goyal'
|
||||
use_embedded_content = False
|
||||
timefmt = ' [ %Y-%m-%d %a]'
|
||||
|
@ -5,7 +5,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
Fetch xkcd.
|
||||
'''
|
||||
|
||||
import time
|
||||
import time, re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class XkcdCom(BasicNewsRecipe):
|
||||
@ -17,6 +17,11 @@ class XkcdCom(BasicNewsRecipe):
|
||||
keep_only_tags = [dict(id='middleContent')]
|
||||
remove_tags = [dict(name='ul'), dict(name='h3'), dict(name='br')]
|
||||
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):
|
||||
INDEX = 'http://xkcd.com/archive/'
|
||||
|
@ -11,7 +11,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
||||
class ZeitDe(BasicNewsRecipe):
|
||||
|
||||
title = 'Die Zeit Nachrichten'
|
||||
description = 'News from Germany'
|
||||
description = 'Die Zeit - Online Nachrichten'
|
||||
__author__ = 'Kovid Goyal'
|
||||
use_embedded_content = False
|
||||
timefmt = ' [%d %b %Y]'
|
||||
|
@ -32,6 +32,11 @@ class NavBarTemplate(Template):
|
||||
xmlns:py="http://genshi.edgewall.org/"
|
||||
|
||||
>
|
||||
<head>
|
||||
<style py:if="extra_css" type="text/css">
|
||||
${extra_css}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="navbar" style="text-align:${'center' if center else 'left'};">
|
||||
<hr py:if="bottom" />
|
||||
@ -60,14 +65,15 @@ class NavBarTemplate(Template):
|
||||
''')
|
||||
|
||||
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('/'):
|
||||
prefix += '/'
|
||||
return Template.generate(self, bottom=bottom, art=art, feed=feed,
|
||||
num=number_of_articles_in_feed,
|
||||
two_levels=two_levels, url=url,
|
||||
__appname__=__appname__, prefix=prefix,
|
||||
center=center)
|
||||
center=center, extra_css=extra_css)
|
||||
|
||||
|
||||
class IndexTemplate(Template):
|
||||
@ -88,11 +94,14 @@ class IndexTemplate(Template):
|
||||
<style py:if="style" type="text/css">
|
||||
${style}
|
||||
</style>
|
||||
<style py:if="extra_css" type="text/css">
|
||||
${extra_css}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>${title}</h1>
|
||||
<h1 class="calibre_recipe_title">${title}</h1>
|
||||
<p style="text-align:right">${date}</p>
|
||||
<ul>
|
||||
<ul class="calibre_feed_list">
|
||||
<py:for each="i, feed in enumerate(feeds)">
|
||||
<li py:if="feed" id="feed_${str(i)}">
|
||||
<a class="feed" href="${'feed_%d/index.html'%i}">${feed.title}</a>
|
||||
@ -103,11 +112,12 @@ class IndexTemplate(Template):
|
||||
</html>
|
||||
''')
|
||||
|
||||
def generate(self, title, datefmt, feeds):
|
||||
def generate(self, title, datefmt, feeds, extra_css=None):
|
||||
if isinstance(datefmt, unicode):
|
||||
datefmt = datefmt.encode(preferred_encoding)
|
||||
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):
|
||||
@ -128,18 +138,21 @@ class FeedTemplate(Template):
|
||||
<style py:if="style" type="text/css">
|
||||
${style}
|
||||
</style>
|
||||
<style py:if="extra_css" type="text/css">
|
||||
${extra_css}
|
||||
</style>
|
||||
</head>
|
||||
<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)">
|
||||
<div class="feed_image">
|
||||
<div class="calibre_feed_image">
|
||||
<img alt="${feed.image_alt}" src="${feed.image_url}" />
|
||||
</div>
|
||||
</py:if>
|
||||
<div py:if="getattr(feed, 'description', None)">
|
||||
<div class="calibre_feed_description" py:if="getattr(feed, 'description', None)">
|
||||
${feed.description}<br />
|
||||
</div>
|
||||
<ul>
|
||||
<ul class="calibre_article_list">
|
||||
<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">
|
||||
<a class="article" href="${article.url}">${article.title}</a>
|
||||
@ -157,8 +170,9 @@ class FeedTemplate(Template):
|
||||
</html>
|
||||
''')
|
||||
|
||||
def generate(self, feed, cutoff):
|
||||
return Template.generate(self, feed=feed, cutoff=cutoff)
|
||||
def generate(self, feed, cutoff, extra_css=None):
|
||||
return Template.generate(self, feed=feed, cutoff=cutoff,
|
||||
extra_css=extra_css)
|
||||
|
||||
class EmbeddedContent(Template):
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user