Rationalize CLI for ebook metadata. Now contained in the single command ebook-metadata. Also rename the prs500 to ebook-device

This commit is contained in:
Kovid Goyal 2009-01-30 20:58:12 -08:00
commit 83eefa45b8
105 changed files with 20467 additions and 16102 deletions

View File

@ -4,7 +4,7 @@
<pydev_project> <pydev_project>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</pydev_property> <pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.6</pydev_property>
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH"> <pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
<path>/calibre/src</path> <path>/calibre-pluginize/src</path>
</pydev_pathproperty> </pydev_pathproperty>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property> <pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
</pydev_project> </pydev_project>

View File

@ -3,7 +3,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
''' Create an OSX installer ''' ''' Create an OSX installer '''
import sys, re, os, shutil, subprocess, stat, glob, zipfile import sys, re, os, shutil, subprocess, stat, glob, zipfile, plistlib
l = {} l = {}
exec open('setup.py').read() in l exec open('setup.py').read() in l
VERSION = l['VERSION'] VERSION = l['VERSION']
@ -36,7 +36,7 @@ loader = open(loader_path, 'w')
site_packages = glob.glob(resources_dir+'/lib/python*/site-packages.zip')[0] site_packages = glob.glob(resources_dir+'/lib/python*/site-packages.zip')[0]
print >>loader, '#!'+python print >>loader, '#!'+python
print >>loader, 'import sys' print >>loader, 'import sys'
print >>loader, 'sys.path.remove('+repr(dirpath)+')' print >>loader, 'if', repr(dirpath), 'in sys.path: sys.path.remove(', repr(dirpath), ')'
print >>loader, 'sys.path.append(', repr(site_packages), ')' print >>loader, 'sys.path.append(', repr(site_packages), ')'
print >>loader, 'sys.frozen = "macosx_app"' print >>loader, 'sys.frozen = "macosx_app"'
print >>loader, 'sys.frameworks_dir =', repr(frameworks_dir) print >>loader, 'sys.frameworks_dir =', repr(frameworks_dir)
@ -294,11 +294,26 @@ sys.frameworks_dir = os.path.join(os.path.dirname(os.environ['RESOURCEPATH']), '
f.close() f.close()
print print
print 'Adding main scripts to site-packages' print 'Adding main scripts to site-packages'
f = zipfile.ZipFile(os.path.join(self.dist_dir, APPNAME+'.app', 'Contents', 'Resources', 'lib', 'python2.6', 'site-packages.zip'), 'a', zipfile.ZIP_DEFLATED) f = zipfile.ZipFile(os.path.join(self.dist_dir, APPNAME+'.app', 'Contents', 'Resources', 'lib', 'python'+sys.version[:3], 'site-packages.zip'), 'a', zipfile.ZIP_DEFLATED)
for script in scripts['gui']+scripts['console']: for script in scripts['gui']+scripts['console']:
f.write(script, script.partition('/')[-1]) f.write(script, script.partition('/')[-1])
f.close() f.close()
print print
print 'Creating console.app'
contents_dir = os.path.dirname(resource_dir)
cc_dir = os.path.join(contents_dir, 'console.app', 'Contents')
os.makedirs(cc_dir)
for x in os.listdir(contents_dir):
if x == 'console.app':
continue
if x == 'Info.plist':
plist = plistlib.readPlist(os.path.join(contents_dir, x))
plist['LSUIElement'] = '1'
plistlib.writePlist(plist, os.path.join(cc_dir, x))
else:
os.symlink(os.path.join('../..', x),
os.path.join(cc_dir, x))
print
print 'Building disk image' print 'Building disk image'
BuildAPP.makedmg(os.path.join(self.dist_dir, APPNAME+'.app'), APPNAME+'-'+VERSION) BuildAPP.makedmg(os.path.join(self.dist_dir, APPNAME+'.app'), APPNAME+'-'+VERSION)

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import sys, time, subprocess, os, re import sys, time, subprocess, os, re
from calibre import __appname__, __version__ from calibre import __appname__, __version__
INSTALLJAMMER = '/home/kovid/installjammer/installjammer' INSTALLJAMMER = '/usr/local/installjammer/installjammer'
sv = re.sub(r'[a-z]\d+', '', __version__) sv = re.sub(r'[a-z]\d+', '', __version__)
@ -26,6 +26,7 @@ cmdline = [
'-DLicense', open(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'LICENSE')).read().replace('\n', '\r\n'), '-DLicense', open(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'LICENSE')).read().replace('\n', '\r\n'),
'--output-dir', os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'dist'), '--output-dir', os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'dist'),
'--platform', 'Windows', '--platform', 'Windows',
'--verbose'
] ]
def run_install_jammer(installer_name='<%AppName%>-<%Version%><%Ext%>', build_for_release=True): def run_install_jammer(installer_name='<%AppName%>-<%Version%><%Ext%>', build_for_release=True):
@ -43,7 +44,7 @@ def run_install_jammer(installer_name='<%AppName%>-<%Version%><%Ext%>', build_fo
subprocess.check_call(cmdline) subprocess.check_call(cmdline)
def main(args=sys.argv): def main(args=sys.argv):
run_install_jammer(build_for_release=False) run_install_jammer(build_for_release=True)
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':

View File

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

View File

@ -65,6 +65,23 @@ def sanitize_file_name(name, substitute='_', as_unicode=False):
return one return one
def prints(*args, **kwargs):
'''
Print unicode arguments safely by encoding them to preferred_encoding
Has the same signature as the print function from Python 3.
'''
file = kwargs.get('file', sys.stdout)
sep = kwargs.get('sep', ' ')
end = kwargs.get('end', '\n')
for i, arg in enumerate(args):
if isinstance(arg, unicode):
arg = arg.encode(preferred_encoding)
file.write(arg)
if i != len(args)-1:
file.write(sep)
file.write(end)
file.flush()
class CommandLineError(Exception): class CommandLineError(Exception):
pass pass

View File

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

View File

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

View File

@ -114,7 +114,19 @@ def reread_metadata_plugins():
_metadata_writers[ft] = [] _metadata_writers[ft] = []
_metadata_writers[ft].append(plugin) _metadata_writers[ft].append(plugin)
def metadata_readers():
ans = set([])
for plugins in _metadata_readers.values():
for plugin in plugins:
ans.add(plugin)
return ans
def metadata_writers():
ans = set([])
for plugins in _metadata_writers.values():
for plugin in plugins:
ans.add(plugin)
return ans
def get_file_type_metadata(stream, ftype): def get_file_type_metadata(stream, ftype):
mi = MetaInformation(None, None) mi = MetaInformation(None, None)

View File

@ -21,6 +21,9 @@ Run an embedded python interpreter.
'Module specifications are of the form full.name.of.module,path_to_module.py', default=None 'Module specifications are of the form full.name.of.module,path_to_module.py', default=None
) )
parser.add_option('-c', '--command', help='Run python code.', default=None) parser.add_option('-c', '--command', help='Run python code.', default=None)
parser.add_option('-e', '--exec-file', default=None, help='Run the python code in file.')
parser.add_option('-d', '--debug-device-driver', default=False, action='store_true',
help='Debug the specified device driver.')
parser.add_option('-g', '--gui', default=False, action='store_true', parser.add_option('-g', '--gui', default=False, action='store_true',
help='Run the GUI',) help='Run the GUI',)
parser.add_option('--migrate', action='store_true', default=False, parser.add_option('--migrate', action='store_true', default=False,
@ -75,6 +78,40 @@ def migrate(old, new):
prefs['library_path'] = os.path.abspath(new) prefs['library_path'] = os.path.abspath(new)
print 'Database migrated to', os.path.abspath(new) print 'Database migrated to', os.path.abspath(new)
def debug_device_driver():
from calibre.devices.scanner import DeviceScanner
s = DeviceScanner()
s.scan()
print 'USB devices on system:', repr(s.devices)
if iswindows:
wmi = __import__('wmi', globals(), locals(), [], -1)
drives = []
print 'Drives detected:'
print '\t', '(ID, Partitions, Drive letter)'
for drive in wmi.WMI().Win32_DiskDrive():
if drive.Partitions == 0:
continue
try:
partition = drive.associators("Win32_DiskDriveToDiskPartition")[0]
logical_disk = partition.associators('Win32_LogicalDiskToPartition')[0]
prefix = logical_disk.DeviceID+os.sep
drives.append((str(drive.PNPDeviceID), drive.Index, prefix))
except IndexError:
drives.append(str(drive.PNPDeviceID))
for drive in drives:
print '\t', drive
from calibre.devices import devices
for dev in devices():
print 'Looking for', dev.__name__
connected = s.is_device_connected(dev)
if connected:
print 'Device Connected:', dev
print 'Trying to open device...'
d = dev()
d.open()
print 'Total space:', d.total_space()
break
def main(args=sys.argv): def main(args=sys.argv):
opts, args = option_parser().parse_args(args) opts, args = option_parser().parse_args(args)
@ -87,6 +124,11 @@ def main(args=sys.argv):
elif opts.command: elif opts.command:
sys.argv = args[:1] sys.argv = args[:1]
exec opts.command exec opts.command
elif opts.exec_file:
sys.argv = args[:1]
execfile(opts.exec_file)
elif opts.debug_device_driver:
debug_device_driver()
elif opts.migrate: elif opts.migrate:
if len(args) < 3: if len(args) < 3:
print 'You must specify the path to library1.db and the path to the new library folder' print 'You must specify the path to library1.db and the path to the new library folder'

View File

@ -32,6 +32,7 @@ class CYBOOKG3(USBMS):
STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card' STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card'
EBOOK_DIR_MAIN = "eBooks" EBOOK_DIR_MAIN = "eBooks"
EBOOK_DIR_CARD = "eBooks"
SUPPORTS_SUB_DIRS = True SUPPORTS_SUB_DIRS = True
def upload_books(self, files, names, on_card=False, end_session=True, def upload_books(self, files, names, on_card=False, end_session=True,

View File

@ -120,7 +120,6 @@ class Device(_Device):
def total_space(self, end_session=True): def total_space(self, end_session=True):
msz = csz = 0 msz = csz = 0
print self._main_prefix
if not iswindows: if not iswindows:
if self._main_prefix is not None: if self._main_prefix is not None:
stats = os.statvfs(self._main_prefix) stats = os.statvfs(self._main_prefix)
@ -185,12 +184,12 @@ class Device(_Device):
if 'main' and 'card' in drives.keys(): if 'main' and 'card' in drives.keys():
break break
if not drives:
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.') % self.__class__.__name__)
self._main_prefix = drives.get('main', None) self._main_prefix = drives.get('main', None)
self._card_prefix = drives.get('card', None) self._card_prefix = drives.get('card', None)
if not self._main_prefix:
raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.') % self.__class__.__name__)
def get_osx_mountpoints(self, raw=None): def get_osx_mountpoints(self, raw=None):
if raw is None: if raw is None:
ioreg = '/usr/sbin/ioreg' ioreg = '/usr/sbin/ioreg'

View File

@ -9,6 +9,8 @@ for a particular device.
import os, fnmatch, shutil import os, fnmatch, shutil
from itertools import cycle from itertools import cycle
from calibre.ebooks.metadata.meta import metadata_from_formats, path_to_ext
from calibre.ebooks.metadata import authors_to_string
from calibre.devices.usbms.device import Device from calibre.devices.usbms.device import Device
from calibre.devices.usbms.books import BookList, Book from calibre.devices.usbms.books import BookList, Book
from calibre.devices.errors import FreeSpaceError, PathError from calibre.devices.errors import FreeSpaceError, PathError
@ -59,9 +61,7 @@ class USBMS(Device):
# types # types
for book_type in self.FORMATS: for book_type in self.FORMATS:
for filename in fnmatch.filter(files, '*.%s' % (book_type)): for filename in fnmatch.filter(files, '*.%s' % (book_type)):
title, author, mime = self.__class__.extract_book_metadata_by_filename(filename) bl.append(self.__class__.book_from_path(os.path.join(path, filename)))
bl.append(Book(os.path.join(path, filename), title, author, mime))
return bl return bl
def upload_books(self, files, names, on_card=False, end_session=True, def upload_books(self, files, names, on_card=False, end_session=True,
@ -132,8 +132,7 @@ class USBMS(Device):
path = location[0] path = location[0]
on_card = 1 if location[1] else 0 on_card = 1 if location[1] else 0
title, author, mime = cls.extract_book_metadata_by_filename(os.path.basename(path)) book = cls.book_from_path(path)
book = Book(path, title, author, mime)
if not book in booklists[on_card]: if not book in booklists[on_card]:
booklists[on_card].append(book) booklists[on_card].append(book)
@ -217,28 +216,13 @@ class USBMS(Device):
os.utime(path, None) os.utime(path, None)
@classmethod @classmethod
def extract_book_metadata_by_filename(cls, filename): def book_from_path(cls, path):
book_title = '' fileext = path_to_ext(path)
book_author = ''
book_mime = ''
# 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:] mi = metadata_from_formats([path])
mime = MIME_MAP[fileext] if fileext in MIME_MAP.keys() else 'Unknown'
if fileext in cls.FORMATS: authors = authors_to_string(mi.authors)
book_mime = MIME_MAP[fileext] if fileext in MIME_MAP.keys() else 'Unknown'
return book_title, book_author, book_mime return Book(path, mi.title, authors, mime)

View File

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

View File

@ -50,6 +50,7 @@ class Splitter(LoggingInterface):
self.split_size = 0 self.split_size = 0
# Split on page breaks # Split on page breaks
self.splitting_on_page_breaks = True
if not opts.dont_split_on_page_breaks: if not opts.dont_split_on_page_breaks:
self.log_info('\tSplitting on page breaks...') self.log_info('\tSplitting on page breaks...')
if self.path in stylesheet_map: if self.path in stylesheet_map:
@ -61,6 +62,7 @@ class Splitter(LoggingInterface):
trees = list(self.trees) trees = list(self.trees)
# Split any remaining over-sized trees # Split any remaining over-sized trees
self.splitting_on_page_breaks = False
if self.opts.profile.flow_size < sys.maxint: if self.opts.profile.flow_size < sys.maxint:
lt_found = False lt_found = False
self.log_info('\tLooking for large trees...') self.log_info('\tLooking for large trees...')
@ -203,6 +205,7 @@ class Splitter(LoggingInterface):
elem.set('style', 'display:none') elem.set('style', 'display:none')
def fix_split_point(sp): def fix_split_point(sp):
if not self.splitting_on_page_breaks:
sp.set('style', sp.get('style', '')+'page-break-before:avoid;page-break-after:avoid') sp.set('style', sp.get('style', '')+'page-break-before:avoid;page-break-after:avoid')
# Tree 1 # Tree 1
@ -304,7 +307,11 @@ class Splitter(LoggingInterface):
Search order is: Search order is:
* Heading tags * Heading tags
* <div> tags * <div> tags
* <pre> tags
* <hr> tags
* <p> tags * <p> tags
* <br> tags
* <li> tags
We try to split in the "middle" of the file (as defined by tag counts. We try to split in the "middle" of the file (as defined by tag counts.
''' '''
@ -324,6 +331,7 @@ class Splitter(LoggingInterface):
'//hr', '//hr',
'//p', '//p',
'//br', '//br',
'//li',
): ):
elems = root.xpath(path, namespaces={'re':'http://exslt.org/regular-expressions'}) elems = root.xpath(path, namespaces={'re':'http://exslt.org/regular-expressions'})
elem = pick_elem(elems) elem = pick_elem(elems)

View File

@ -417,39 +417,44 @@ class Parser(PreProcessor, LoggingInterface):
self.level = self.htmlfile.level self.level = self.htmlfile.level
for f in self.htmlfiles: for f in self.htmlfiles:
name = os.path.basename(f.path) name = os.path.basename(f.path)
name = os.path.splitext(name)[0] + '.xhtml'
if name in self.htmlfile_map.values(): if name in self.htmlfile_map.values():
name = os.path.splitext(name)[0] + '_cr_%d'%save_counter + os.path.splitext(name)[1] name = os.path.splitext(name)[0] + '_cr_%d'%save_counter + os.path.splitext(name)[1]
save_counter += 1 save_counter += 1
self.htmlfile_map[f.path] = name self.htmlfile_map[f.path] = name
self.parse_html() self.parse_html()
# Handle <image> tags inside embedded <svg>
# At least one source of EPUB files (Penguin) uses xlink:href
# without declaring the xlink namespace
for image in self.root.xpath('//image'):
for attr in image.attrib.keys():
if attr.endswith(':href'):
nhref = self.rewrite_links(image.get(attr))
image.set(attr, nhref)
self.root.rewrite_links(self.rewrite_links, resolve_base_href=False) self.root.rewrite_links(self.rewrite_links, resolve_base_href=False)
for bad in ('xmlns', 'lang', 'xml:lang'): # lxml also adds these attributes for XHTML documents, leading to duplicates for bad in ('xmlns', 'lang', 'xml:lang'): # lxml also adds these attributes for XHTML documents, leading to duplicates
if self.root.get(bad, None) is not None: if self.root.get(bad, None) is not None:
self.root.attrib.pop(bad) self.root.attrib.pop(bad)
def save_path(self): def save_path(self):
return os.path.join(self.tdir, self.htmlfile_map[self.htmlfile.path]) return os.path.join(self.tdir, self.htmlfile_map[self.htmlfile.path])
def declare_xhtml_namespace(self, match):
if not match.group('raw'):
return '<html xmlns="http://www.w3.org/1999/xhtml">'
raw = match.group('raw')
m = re.search(r'(?i)xmlns\s*=\s*[\'"](?P<uri>[^"\']*)[\'"]', raw)
if not m:
return '<html xmlns="http://www.w3.org/1999/xhtml" %s>'%raw
else:
return match.group().replace(m.group('uri'), "http://www.w3.org/1999/xhtml")
def save(self): def save(self):
''' '''
Save processed HTML into the content directory. Save processed HTML into the content directory.
Should be called after all HTML processing is finished. Should be called after all HTML processing is finished.
''' '''
ans = tostring(self.root, pretty_print=self.opts.pretty_print) self.root.set('xmlns', 'http://www.w3.org/1999/xhtml')
ans = re.sub(r'(?i)<\s*html(?P<raw>\s+[^>]*){0,1}>', self.declare_xhtml_namespace, ans[:1000]) + ans[1000:] self.root.set('xmlns:xlink', 'http://www.w3.org/1999/xlink')
ans = re.compile(r'<head>', re.IGNORECASE).sub('<head>\n\t<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n', ans[:1000])+ans[1000:] for svg in self.root.xpath('//svg'):
svg.set('xmlns', 'http://www.w3.org/2000/svg')
ans = tostring(self.root, pretty_print=self.opts.pretty_print)
ans = re.compile(r'<head>', re.IGNORECASE).sub('<head>\n\t<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n', ans[:1000])+ans[1000:]
with open(self.save_path(), 'wb') as f: with open(self.save_path(), 'wb') as f:
f.write(ans) f.write(ans)
return f.name return f.name
@ -823,21 +828,28 @@ class Processor(Parser):
font.set('class', cn) font.set('class', cn)
font.tag = 'span' font.tag = 'span'
id_css, id_css_counter = {}, 0
for elem in self.root.xpath('//*[@style]'): for elem in self.root.xpath('//*[@style]'):
setting = elem.get('style') setting = elem.get('style')
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) classname = cache.get(setting, None)
if classname is None: if classname is None:
classname = 'calibre_class_%d'%class_counter classname = 'calibre_class_%d'%class_counter
class_counter += 1 class_counter += 1
cache[setting] = classname cache[setting] = classname
cn = elem.get('class', '') cn = elem.get('class', classname)
if cn: cn += ' '
cn += classname
elem.set('class', cn) elem.set('class', cn)
elem.attrib.pop('style') elem.attrib.pop('style')
css = '\n'.join(['.%s {%s;}'%(cn, setting) for \ css = '\n'.join(['.%s {%s;}'%(cn, setting) for \
setting, cn in cache.items()]) setting, cn in cache.items()])
css += '\n\n'
css += '\n'.join(['%s {%s;}'%(selector, setting) for \
selector, setting in id_css.items()])
sheet = self.css_parser.parseString(self.preprocess_css(css.replace(';;}', ';}'))) sheet = self.css_parser.parseString(self.preprocess_css(css.replace(';;}', ';}')))
for rule in sheet: for rule in sheet:
self.stylesheet.add(rule) self.stylesheet.add(rule)

View File

@ -11,6 +11,7 @@ import sys, struct, cStringIO, os
import functools import functools
import re import re
from urlparse import urldefrag from urlparse import urldefrag
from urllib import unquote as urlunquote
from lxml import etree from lxml import etree
from calibre.ebooks.lit import LitError from calibre.ebooks.lit import LitError
from calibre.ebooks.lit.maps import OPF_MAP, HTML_MAP from calibre.ebooks.lit.maps import OPF_MAP, HTML_MAP
@ -611,6 +612,8 @@ class LitReader(object):
offset, raw = u32(raw), raw[4:] offset, raw = u32(raw), raw[4:]
internal, raw = consume_sized_utf8_string(raw) internal, raw = consume_sized_utf8_string(raw)
original, raw = consume_sized_utf8_string(raw) original, raw = consume_sized_utf8_string(raw)
# The path should be stored unquoted, but not always
original = urlunquote(original)
# Is this last one UTF-8 or ASCIIZ? # Is this last one UTF-8 or ASCIIZ?
mime_type, raw = consume_sized_utf8_string(raw, zpad=True) mime_type, raw = consume_sized_utf8_string(raw, zpad=True)
self.manifest[internal] = ManifestItem( self.manifest[internal] = ManifestItem(

View File

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

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
Based on ideas from comiclrf created by FangornUK. Based on ideas from comiclrf created by FangornUK.
''' '''
import os, sys, shutil, traceback, textwrap import os, sys, shutil, traceback, textwrap, fnmatch
from uuid import uuid4 from uuid import uuid4
@ -389,18 +389,86 @@ def create_lrf(pages, profile, opts, thumbnail=None):
print _('Output written to'), opts.output print _('Output written to'), opts.output
def create_pdf(pages, profile, opts, thumbnail=None): def create_pdf(pages, profile, opts, thumbnail=None,toc=None):
width, height = PROFILES[profile] width, height = PROFILES[profile]
from reportlab.pdfgen import canvas from reportlab.pdfgen import canvas
cur_page=0
heading = []
if toc != None:
if len(toc) == 1:
toc = None
else:
toc_index = 0
base_cur = 0
rem = 0
breaker = False
while True:
letter=toc[0][0][base_cur]
for i in range(len(toc)):
if letter != toc[i][0][base_cur]:
breaker = True
if breaker:
break
if letter == os.sep:
rem=base_cur
base_cur += 1
toc.append(("Not seen",-1))
pdf = canvas.Canvas(filename=opts.output, pagesize=(width,height+15)) pdf = canvas.Canvas(filename=opts.output, pagesize=(width,height+15))
pdf.setAuthor(opts.author) pdf.setAuthor(opts.author)
pdf.setTitle(opts.title) pdf.setTitle(opts.title)
for page in pages: for page in pages:
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) pdf.drawImage(page, x=0,y=0,width=width, height=height)
if toc != None:
if toc[toc_index][1] == cur_page:
tmp=toc[toc_index][0]
toc_current=tmp[rem:len(tmp)-4]
index=0
while True:
key = 'page%d-%d' % (cur_page, index)
pdf.bookmarkPage(key)
(head,dummy,list)=toc_current.partition(os.sep)
try:
if heading[index] != head:
heading[index] = head
pdf.addOutlineEntry(title=head,key=key,level=index)
except:
heading.append(head)
pdf.addOutlineEntry(title=head,key=key,level=index)
index += 1
toc_current=list
if dummy == "":
break
toc_index += 1
cur_page += 1
pdf.showPage() pdf.showPage()
# Write the document to disk # Write the document to disk
pdf.save() pdf.save()
@ -409,19 +477,31 @@ def create_pdf(pages, profile, opts, thumbnail=None):
def do_convert(path_to_file, opts, notification=lambda m, p: p, output_format='lrf'): def do_convert(path_to_file, opts, notification=lambda m, p: p, output_format='lrf'):
path_to_file = run_plugins_on_preprocess(path_to_file) path_to_file = run_plugins_on_preprocess(path_to_file)
source = path_to_file source = path_to_file
to_delete = []
toc = []
list = []
pages = []
if not opts.title: if not opts.title:
opts.title = os.path.splitext(os.path.basename(source))[0] opts.title = os.path.splitext(os.path.basename(source))[0]
if not opts.output: if not opts.output:
opts.output = os.path.abspath(os.path.splitext(os.path.basename(source))[0]+'.'+output_format) opts.output = os.path.abspath(os.path.splitext(os.path.basename(source))[0]+'.'+output_format)
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) tdir = extract_comic(source)
pages = find_pages(tdir, sort_on_mtime=opts.no_sort, verbose=opts.verbose) new_pages = find_pages(tdir, sort_on_mtime=opts.no_sort, verbose=opts.verbose)
thumbnail = None thumbnail = None
if not pages: if not new_pages:
raise ValueError('Could not find any pages in the comic: %s'%source) raise ValueError('Could not find any pages in the comic: %s'%source)
if not getattr(opts, 'no_process', False): if not getattr(opts, 'no_process', False):
pages, failures, tdir2 = process_pages(pages, opts, notification) new_pages, failures, tdir2 = process_pages(new_pages, opts, notification)
if not pages: if not new_pages:
raise ValueError('Could not find any valid pages in the comic: %s'%source) raise ValueError('Could not find any valid pages in the comic: %s'%source)
if failures: if failures:
print 'Could not process the following pages (run with --verbose to see why):' print 'Could not process the following pages (run with --verbose to see why):'
@ -430,15 +510,31 @@ def do_convert(path_to_file, opts, notification=lambda m, p: p, output_format='l
thumbnail = os.path.join(tdir2, 'thumbnail.png') thumbnail = os.path.join(tdir2, 'thumbnail.png')
if not os.access(thumbnail, os.R_OK): if not os.access(thumbnail, os.R_OK):
thumbnail = None thumbnail = None
toc.append((source,len(pages)))
pages.extend(new_pages)
to_delete.append(tdir)
if output_format == 'lrf': if output_format == 'lrf':
create_lrf(pages, opts.profile, opts, thumbnail=thumbnail) create_lrf(pages, opts.profile, opts, thumbnail=thumbnail)
if output_format == 'epub': if output_format == 'epub':
create_epub(pages, opts.profile, opts, thumbnail=thumbnail) create_epub(pages, opts.profile, opts, thumbnail=thumbnail)
if output_format == 'pdf': if output_format == 'pdf':
create_pdf(pages, opts.profile, opts, thumbnail=thumbnail) create_pdf(pages, opts.profile, opts, thumbnail=thumbnail,toc=toc)
for tdir in to_delete:
shutil.rmtree(tdir) shutil.rmtree(tdir)
if not getattr(opts, 'no_process', False):
shutil.rmtree(tdir2)
def all_files(root, patterns='*'):
# Expand patterns from semicolon-separated string to list
patterns = patterns.split('|')
for path, subdirs, files in os.walk(root):
files.sort( )
for name in files:
for pattern in patterns:
if fnmatch.fnmatch(name, pattern):
yield os.path.join(path, name)
break
def main(args=sys.argv, notification=None, output_format='lrf'): def main(args=sys.argv, notification=None, output_format='lrf'):

View File

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

View File

@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en'
""" """
Provides abstraction for metadata reading.writing from a variety of ebook formats. Provides abstraction for metadata reading.writing from a variety of ebook formats.
""" """
import os, mimetypes, sys import os, mimetypes, sys, re
from urllib import unquote, quote from urllib import unquote, quote
from urlparse import urlparse from urlparse import urlparse
@ -21,7 +21,10 @@ def string_to_authors(raw):
return authors return authors
def authors_to_string(authors): def authors_to_string(authors):
return ' & '.join([a.replace('&', '&&') for a in authors]) if authors is not None:
return ' & '.join([a.replace('&', '&&') for a in authors if a])
else:
return ''
def author_to_author_sort(author): def author_to_author_sort(author):
tokens = author.split() tokens = author.split()
@ -33,18 +36,14 @@ def author_to_author_sort(author):
def authors_to_sort_string(authors): def authors_to_sort_string(authors):
return ' & '.join(map(author_to_author_sort, authors)) return ' & '.join(map(author_to_author_sort, authors))
def get_parser(extension): _title_pat = re.compile('^(A|The|An)\s+', re.IGNORECASE)
''' Return an option parser with the basic metadata options already setup''' def title_sort(title):
parser = OptionParser(usage='%prog [options] myfile.'+extension+'\n\nRead and write metadata from an ebook file.') match = _title_pat.search(title)
parser.add_option("-t", "--title", action="store", type="string", \ if match:
dest="title", help=_("Set the book title"), default=None) prep = match.group(1)
parser.add_option("-a", "--authors", action="store", type="string", \ title = title.replace(prep, '') + ', ' + prep
dest="authors", help=_("Set the authors"), default=None) return title.strip()
parser.add_option("-c", "--category", action="store", type="string", \
dest="category", help=_("The category this book belongs to. E.g.: History"), default=None)
parser.add_option('--comment', dest='comment', default=None, action='store',
help=_('Set the comment'))
return parser
class Resource(object): class Resource(object):
''' '''
@ -186,7 +185,7 @@ class MetaInformation(object):
@staticmethod @staticmethod
def copy(mi): def copy(mi):
ans = MetaInformation(mi.title, mi.authors) ans = MetaInformation(mi.title, mi.authors)
for attr in ('author_sort', 'title_sort', 'comments', 'category', for attr in ('author_sort', 'title_sort', 'comments',
'publisher', 'series', 'series_index', 'rating', 'publisher', 'series', 'series_index', 'rating',
'isbn', 'tags', 'cover_data', 'application_id', 'guide', 'isbn', 'tags', 'cover_data', 'application_id', 'guide',
'manifest', 'spine', 'toc', 'cover', 'language', 'book_producer'): 'manifest', 'spine', 'toc', 'cover', 'language', 'book_producer'):
@ -195,7 +194,7 @@ class MetaInformation(object):
def __init__(self, title, authors=[_('Unknown')]): def __init__(self, title, authors=[_('Unknown')]):
''' '''
@param title: title or "Unknown" or a MetaInformation object @param title: title or ``_('Unknown')`` or a MetaInformation object
@param authors: List of strings or [] @param authors: List of strings or []
''' '''
mi = None mi = None
@ -211,7 +210,7 @@ class MetaInformation(object):
#: mi.cover_data = (ext, data) #: mi.cover_data = (ext, data)
self.cover_data = getattr(mi, 'cover_data', (None, None)) self.cover_data = getattr(mi, 'cover_data', (None, None))
for x in ('author_sort', 'title_sort', 'comments', 'category', 'publisher', for x in ('author_sort', 'title_sort', 'comments', 'publisher',
'series', 'series_index', 'rating', 'isbn', 'language', 'series', 'series_index', 'rating', 'isbn', 'language',
'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover', 'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover',
'book_producer', 'book_producer',
@ -229,15 +228,15 @@ class MetaInformation(object):
if mi.authors and mi.authors[0] != _('Unknown'): if mi.authors and mi.authors[0] != _('Unknown'):
self.authors = mi.authors self.authors = mi.authors
for attr in ('author_sort', 'title_sort', 'comments', 'category', for attr in ('author_sort', 'title_sort', 'comments',
'publisher', 'series', 'series_index', 'rating', 'publisher', 'series', 'series_index', 'rating',
'isbn', 'application_id', 'manifest', 'spine', 'toc', 'isbn', 'application_id', 'manifest', 'spine', 'toc',
'cover', 'language', 'guide', 'book_producer'): 'cover', 'language', 'guide', 'book_producer'):
if hasattr(mi, attr): val = getattr(mi, attr, None)
val = getattr(mi, attr)
if val is not None: if val is not None:
setattr(self, attr, val) setattr(self, attr, val)
if mi.tags:
self.tags += mi.tags self.tags += mi.tags
self.tags = list(set(self.tags)) self.tags = list(set(self.tags))
@ -246,35 +245,38 @@ class MetaInformation(object):
def __unicode__(self): def __unicode__(self):
ans = u'' ans = [ fmt('Title', self.title) ]
ans += u'Title : ' + unicode(self.title) + u'\n' def fmt(x, y):
ans.append(u'%-20s: %s'%(unicode(x), unicode(y)))
if self.title_sort:
fmt('Title sort', self.title_sort)
if self.authors: if self.authors:
ans += u'Author : ' + (' & '.join(self.authors) if self.authors is not None else _('Unknown')) fmt('Author(s)', authors_to_string(self.authors) + \
ans += ((' [' + self.author_sort + ']') if self.author_sort else '') + u'\n' ((' [' + self.author_sort + ']') if self.author_sort else ''))
if self.publisher: if self.publisher:
ans += u'Publisher: '+ unicode(self.publisher) + u'\n' fmt('Publisher', self.publisher)
if getattr(self, 'book_producer', False): if getattr(self, 'book_producer', False):
ans += u'Producer : '+ unicode(self.book_producer) + u'\n' fmt('Book Producer', self.book_producer)
if self.category:
ans += u'Category : ' + unicode(self.category) + u'\n'
if self.comments: if self.comments:
ans += u'Comments : ' + unicode(self.comments) + u'\n' fmt('Comments', self.comments)
if self.isbn: if self.isbn:
ans += u'ISBN : ' + unicode(self.isbn) + u'\n' fmt('ISBN', self.isbn)
if self.tags: if self.tags:
ans += u'Tags : ' + u', '.join([unicode(t) for t in self.tags]) + '\n' fmt('Tags', u', '.join([unicode(t) for t in self.tags]))
if self.series: if self.series:
ans += u'Series : '+unicode(self.series) + ' #%d\n'%self.series_index fmt('Series', self.series + '#%s'%self.series_index)
if self.language: if self.language:
ans += u'Language : ' + unicode(self.language) + u'\n' fmt('Language', self.language)
return ans.strip() if self.rating is not None:
fmt('Rating', self.rating)
return u'\n'.join(ans)
def to_html(self): def to_html(self):
ans = [(_('Title'), unicode(self.title))] ans = [(_('Title'), unicode(self.title))]
ans += [(_('Author(s)'), (authors_to_string(self.authors) if self.authors else _('Unknown')))] ans += [(_('Author(s)'), (authors_to_string(self.authors) if self.authors else _('Unknown')))]
ans += [(_('Publisher'), unicode(self.publisher))] ans += [(_('Publisher'), unicode(self.publisher))]
ans += [(_('Producer'), unicode(self.book_producer))] ans += [(_('Producer'), unicode(self.book_producer))]
ans += [(_('Category'), unicode(self.category))]
ans += [(_('Comments'), unicode(self.comments))] ans += [(_('Comments'), unicode(self.comments))]
ans += [('ISBN', unicode(self.isbn))] ans += [('ISBN', unicode(self.isbn))]
ans += [(_('Tags'), u', '.join([unicode(t) for t in self.tags]))] ans += [(_('Tags'), u', '.join([unicode(t) for t in self.tags]))]
@ -290,4 +292,4 @@ class MetaInformation(object):
return self.__unicode__().encode('utf-8') return self.__unicode__().encode('utf-8')
def __nonzero__(self): def __nonzero__(self):
return bool(self.title or self.author or self.comments or self.category) return bool(self.title or self.author or self.comments or self.tags)

View File

@ -5,7 +5,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'''Read meta information from epub files''' '''Read meta information from epub files'''
import sys, os, time import os, time
from cStringIO import StringIO from cStringIO import StringIO
from contextlib import closing from contextlib import closing
@ -15,10 +15,10 @@ from PyQt4.QtWebKit import QWebPage
from calibre.utils.zipfile import ZipFile, BadZipfile, safe_replace from calibre.utils.zipfile import ZipFile, BadZipfile, safe_replace
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup
from calibre.ebooks.metadata import get_parser, MetaInformation from calibre.ebooks.metadata import MetaInformation
from calibre.ebooks.metadata.opf2 import OPF from calibre.ebooks.metadata.opf2 import OPF
from calibre.ptempfile import TemporaryDirectory from calibre.ptempfile import TemporaryDirectory
from calibre import CurrentDir, fit_image from calibre import CurrentDir
class EPubException(Exception): class EPubException(Exception):
pass pass
@ -188,67 +188,10 @@ def get_metadata(stream, extract_cover=True):
def set_metadata(stream, mi): def set_metadata(stream, mi):
stream.seek(0) stream.seek(0)
reader = OCFZipReader(stream, root=os.getcwdu()) reader = OCFZipReader(stream, root=os.getcwdu())
mi = MetaInformation(mi)
for x in ('guide', 'toc', 'manifest', 'spine'):
setattr(mi, x, None)
reader.opf.smart_update(mi) reader.opf.smart_update(mi)
newopf = StringIO(reader.opf.render()) newopf = StringIO(reader.opf.render())
safe_replace(stream, reader.container[OPF.MIMETYPE], newopf) safe_replace(stream, reader.container[OPF.MIMETYPE], newopf)
def option_parser():
parser = get_parser('epub')
parser.remove_option('--category')
parser.add_option('--tags', default=None,
help=_('A comma separated list of tags to set'))
parser.add_option('--series', default=None,
help=_('The series to which this book belongs'))
parser.add_option('--series-index', default=None,
help=_('The series index'))
parser.add_option('--language', default=None,
help=_('The book language'))
parser.add_option('--get-cover', default=False, action='store_true',
help=_('Extract the cover'))
return parser
def main(args=sys.argv):
parser = option_parser()
opts, args = parser.parse_args(args)
if len(args) != 2:
parser.print_help()
return 1
with open(args[1], 'r+b') as stream:
mi = get_metadata(stream, extract_cover=opts.get_cover)
changed = False
if opts.title:
mi.title = opts.title
changed = True
if opts.authors:
mi.authors = opts.authors.split(',')
changed = True
if opts.tags:
mi.tags = opts.tags.split(',')
changed = True
if opts.comment:
mi.comments = opts.comment
changed = True
if opts.series:
mi.series = opts.series
changed = True
if opts.series_index:
mi.series_index = opts.series_index
changed = True
if opts.language is not None:
mi.language = opts.language
changed = True
if changed:
set_metadata(stream, mi)
print unicode(get_metadata(stream, extract_cover=False)).encode('utf-8')
if mi.cover_data[1] is not None:
cpath = os.path.splitext(os.path.basename(args[1]))[0] + '_cover.jpg'
with open(cpath, 'wb') as f:
f.write(mi.cover_data[1])
print 'Cover saved to', f.name
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -48,15 +48,3 @@ def get_metadata(stream):
if cdata: if cdata:
mi.cover_data = cdata mi.cover_data = cdata
return mi return mi
def main(args=sys.argv):
if len(args) != 2 or '--help' in args or '-h' in args:
print >>sys.stderr, _('Usage:'), args[0], 'mybook.fb2'
return 1
path = os.path.abspath(os.path.expanduser(args[1]))
print unicode(get_metadata(open(path, 'rb')))
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -46,17 +46,3 @@ def get_metadata(stream):
msg = u'Couldn\'t read metadata from imp: %s with error %s'%(mi.title, unicode(err)) msg = u'Couldn\'t read metadata from imp: %s with error %s'%(mi.title, unicode(err))
print >>sys.stderr, msg.encode('utf8') print >>sys.stderr, msg.encode('utf8')
return mi return mi
def main(args=sys.argv):
if len(args) != 2:
print >>sys.stderr, _('Usage: imp-meta file.imp')
print >>sys.stderr, _('No filename specified.')
return 1
path = os.path.abspath(os.path.expanduser(args[1]))
print get_metadata(open(path, 'rb'))
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -30,21 +30,3 @@ def get_metadata(stream):
mi.cover_data = ('jpg', covers[-1]) mi.cover_data = ('jpg', covers[-1])
return mi return mi
def main(args=sys.argv):
if len(args) != 2:
print >>sys.stderr, _('Usage: %s file.lit') % args[0]
return 1
fname = args[1]
mi = get_metadata(open(fname, 'rb'))
print unicode(mi)
if mi.cover_data[1]:
cover = os.path.abspath(
'.'.join((os.path.splitext(os.path.basename(fname))[0],
mi.cover_data[0])))
open(cover, 'wb').write(mi.cover_data[1])
print _('Cover saved to'), cover
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -80,10 +80,3 @@ def get_metadata(f):
else: else:
raise ValueError('Not a LRX file') raise ValueError('Not a LRX file')
def main(args=sys.argv):
print get_metadata(open(args[1], 'rb'))
return 0
if __name__ == '__main__':
sys.exit(main())

View File

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

View File

@ -1,29 +1,170 @@
#!/usr/bin/env python '''
Retrieve and modify in-place Mobipocket book metadata.
'''
from __future__ import with_statement
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net and ' \
'Marshall T. Vandegrift <llasram@gmail.com>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
''' import sys
''' import os
from struct import pack, unpack
import sys, os from cStringIO import StringIO
from calibre.ebooks.mobi import MobiError
from calibre.ebooks.mobi.reader import get_metadata 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
def main(args=sys.argv): class StreamSlicer(object):
if len(args) != 2: def __init__(self, stream, start=0, stop=None):
print >>sys.stderr, 'Usage: %s file.mobi' % args[0] self._stream = stream
return 1 self.start = start
fname = args[1] if stop is None:
mi = get_metadata(open(fname, 'rb')) stream.seek(0, 2)
print unicode(mi) 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]: if mi.cover_data[1]:
cover = os.path.abspath( data = mi.cover_data[1]
'.'.join((os.path.splitext(os.path.basename(fname))[0], if self.cover_record is not None:
mi.cover_data[0].lower()))) size = len(self.cover_record)
open(cover, 'wb').write(mi.cover_data[1]) cover = rescale_image(data, size)
print _('Cover saved to'), cover cover += '\0' * (size - len(cover))
return 0 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
if __name__ == '__main__': def set_metadata(stream, mi):
sys.exit(main()) mu = MetadataUpdater(stream)
mu.update(mi)
return

View File

@ -164,103 +164,3 @@ def get_metadata(stream):
return mi return mi
def main(args=sys.argv):
if len(args) != 2:
print 'Usage: %s file.odt'%args[0]
return 1
mi = get_metadata(open(args[1], 'rb'))
print mi
return 0
if __name__ == '__main__':
sys.exit(main())
#now = time.localtime()[:6]
#outputfile = "-"
#writemeta = False # Do we change any meta data?
#usenormalize = False
#
#try:
# opts, args = getopt.getopt(sys.argv[1:], "cdlI:A:a:o:x:X:")
#except getopt.GetoptError:
# exitwithusage()
#
#if len(opts) == 0:
# opts = [ ('-l','') ]
#
#for o, a in opts:
# if o in ('-a','-A','-I'):
# writemeta = True
# if a.find(":") >= 0:
# k,v = a.split(":",1)
# else:
# k,v = (a, "")
# if len(k) == 0:
# exitwithusage()
# k = fields.get(k,k)
# addfields[k] = unicode(v,'utf-8')
# if o == '-a':
# yieldfields[k] = True
# if o == '-I':
# deletefields[k] = True
# if o == '-d':
# writemeta = True
# addfields[(DCNS,u'date')] = "%04d-%02d-%02dT%02d:%02d:%02d" % now
# deletefields[(DCNS,u'date')] = True
# if o == '-c':
# usenormalize = True
# if o == '-l':
# Xfields = fields.values()
# if o == "-x":
# xfields.append(fields.get(a,a))
# if o == "-X":
# Xfields.append(fields.get(a,a))
# if o == "-o":
# outputfile = a
#
## The specification says we should change the element to our own,
## and must not export the original identifier.
#if writemeta:
# addfields[(METANS,u'generator')] = TOOLSVERSION
# deletefields[(METANS,u'generator')] = True
#
#odfs = odfmetaparser()
#parser = xml.sax.make_parser()
#parser.setFeature(xml.sax.handler.feature_namespaces, 1)
#parser.setContentHandler(odfs)
#
#if len(args) == 0:
# zin = zipfile.ZipFile(sys.stdin,'r')
#else:
# if not zipfile.is_zipfile(args[0]):
# exitwithusage()
# zin = zipfile.ZipFile(args[0], 'r')
#
#content = zin.read('meta.xml')
#parser.parse(StringIO(content))
#
#if writemeta:
# if outputfile == '-':
# if sys.stdout.isatty():
# sys.stderr.write("Won't write ODF file to terminal\n")
# sys.exit(1)
# zout = zipfile.ZipFile(sys.stdout,"w")
# else:
# zout = zipfile.ZipFile(outputfile,"w")
#
#
#
# # Loop through the input zipfile and copy the content to the output until we
# # get to the meta.xml. Then substitute.
# for zinfo in zin.infolist():
# if zinfo.filename == "meta.xml":
# # Write meta
# zi = zipfile.ZipInfo("meta.xml", now)
# zi.compress_type = zipfile.ZIP_DEFLATED
# zout.writestr(zi,odfs.meta() )
# else:
# payload = zin.read(zinfo.filename)
# zout.writestr(zinfo, payload)
#
# zout.close()
#zin.close()

View File

@ -11,7 +11,7 @@ from calibre.constants import __appname__, __version__
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup, BeautifulSoup from calibre.ebooks.BeautifulSoup import BeautifulStoneSoup, BeautifulSoup
from calibre.ebooks.lrf import entity_to_unicode from calibre.ebooks.lrf import entity_to_unicode
from calibre.ebooks.metadata import get_parser, Resource, ResourceCollection from calibre.ebooks.metadata import Resource, ResourceCollection
from calibre.ebooks.metadata.toc import TOC from calibre.ebooks.metadata.toc import TOC
class OPFSoup(BeautifulStoneSoup): class OPFSoup(BeautifulStoneSoup):
@ -541,45 +541,3 @@ class OPFCreator(MetaInformation):
toc.render(ncx_stream, self.application_id) toc.render(ncx_stream, self.application_id)
ncx_stream.flush() ncx_stream.flush()
def option_parser():
return get_parser('opf')
def main(args=sys.argv):
parser = option_parser()
opts, args = parser.parse_args(args)
if len(args) != 2:
parser.print_help()
return 1
mi = MetaInformation(OPFReader(open(args[1], 'rb'), os.path.abspath(os.path.dirname(args[1]))))
write = False
if opts.title is not None:
mi.title = opts.title.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
write = True
if opts.authors is not None:
aus = [i.strip().replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;') for i in opts.authors.split(',')]
mi.authors = aus
write = True
if opts.category is not None:
mi.category = opts.category.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
write = True
if opts.comment is not None:
mi.comments = opts.comment.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
write = True
if write:
mo = OPFCreator(os.path.dirname(args[1]), mi)
ncx = cStringIO.StringIO()
mo.render(open(args[1], 'wb'), ncx)
ncx = ncx.getvalue()
if ncx:
f = glob.glob(os.path.join(os.path.dirname(args[1]), '*.ncx'))
if f:
f = open(f[0], 'wb')
else:
f = open(os.path.splitext(args[1])[0]+'.ncx', 'wb')
f.write(ncx)
f.close()
print MetaInformation(OPFReader(open(args[1], 'rb'), os.path.abspath(os.path.dirname(args[1]))))
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -17,7 +17,7 @@ from calibre.ebooks.chardet import xml_to_unicode
from calibre import relpath from calibre import relpath
from calibre.constants import __appname__, __version__ from calibre.constants import __appname__, __version__
from calibre.ebooks.metadata.toc import TOC from calibre.ebooks.metadata.toc import TOC
from calibre.ebooks.metadata import MetaInformation, get_parser from calibre.ebooks.metadata import MetaInformation
class Resource(object): class Resource(object):
@ -960,54 +960,3 @@ def suite():
def test(): def test():
unittest.TextTestRunner(verbosity=2).run(suite()) unittest.TextTestRunner(verbosity=2).run(suite())
def option_parser():
parser = get_parser('opf')
parser.add_option('--language', default=None, help=_('Set the dc:language field'))
return parser
def main(args=sys.argv):
parser = option_parser()
opts, args = parser.parse_args(args)
if len(args) != 2:
parser.print_help()
return 1
opfpath = os.path.abspath(args[1])
basedir = os.path.dirname(opfpath)
mi = MetaInformation(OPF(open(opfpath, 'rb'), basedir))
write = False
if opts.title is not None:
mi.title = opts.title
write = True
if opts.authors is not None:
aus = [i.strip() for i in opts.authors.split(',')]
mi.authors = aus
write = True
if opts.category is not None:
mi.category = opts.category
write = True
if opts.comment is not None:
mi.comments = opts.comment
write = True
if opts.language is not None:
mi.language = opts.language
write = True
if write:
mo = OPFCreator(basedir, mi)
ncx = cStringIO.StringIO()
mo.render(open(args[1], 'wb'), ncx)
ncx = ncx.getvalue()
if ncx:
f = glob.glob(os.path.join(os.path.dirname(args[1]), '*.ncx'))
if f:
f = open(f[0], 'wb')
else:
f = open(os.path.splitext(args[1])[0]+'.ncx', 'wb')
f.write(ncx)
f.close()
print MetaInformation(OPF(open(opfpath, 'rb'), basedir))
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -2,9 +2,9 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'''Read meta information from PDF files''' '''Read meta information from PDF files'''
import sys, os, re import sys, re
from calibre.ebooks.metadata import MetaInformation, authors_to_string, get_parser from calibre.ebooks.metadata import MetaInformation, authors_to_string
from pyPdf import PdfFileReader from pyPdf import PdfFileReader
def get_metadata(stream): def get_metadata(stream):
@ -45,27 +45,3 @@ def set_metadata(stream, mi):
stream.write(raw) stream.write(raw)
stream.seek(0) stream.seek(0)
def option_parser():
p = get_parser('pdf')
p.remove_option('--category')
p.remove_option('--comment')
return p
def main(args=sys.argv):
#p = option_parser()
#opts, args = p.parse_args(args)
if len(args) != 2:
print >>sys.stderr, _('Usage: pdf-meta file.pdf')
print >>sys.stderr, _('No filename specified.')
return 1
stream = open(os.path.abspath(os.path.expanduser(args[1])), 'r+b')
#mi = MetaInformation(opts.title, opts.authors)
#if mi.title or mi.authors:
# set_metadata(stream, mi)
print unicode(get_metadata(stream)).encode('utf-8')
return 0
if __name__ == '__main__':
sys.exit(main())

View File

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

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Ashish Kulkarni <kulkarni.ashish@gmail.com>' __copyright__ = '2008, Ashish Kulkarni <kulkarni.ashish@gmail.com>'
'''Read meta information from RB files''' '''Read meta information from RB files'''
import sys, os, struct import sys, struct
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
@ -54,15 +54,3 @@ def get_metadata(stream):
return mi return mi
def main(args=sys.argv):
if len(args) != 2:
print >>sys.stderr, _('Usage: rb-meta file.rb')
print >>sys.stderr, _('No filename specified.')
return 1
path = os.path.abspath(os.path.expanduser(args[1]))
print get_metadata(open(path, 'rb'))
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -5,7 +5,7 @@ Edit metadata in RTF files.
""" """
import re, cStringIO, sys import re, cStringIO, sys
from calibre.ebooks.metadata import MetaInformation, get_parser from calibre.ebooks.metadata import MetaInformation
title_pat = re.compile(r'\{\\info.*?\{\\title(.*?)(?<!\\)\}', re.DOTALL) title_pat = re.compile(r'\{\\info.*?\{\\title(.*?)(?<!\\)\}', re.DOTALL)
author_pat = re.compile(r'\{\\info.*?\{\\author(.*?)(?<!\\)\}', re.DOTALL) author_pat = re.compile(r'\{\\info.*?\{\\author(.*?)(?<!\\)\}', re.DOTALL)
@ -166,22 +166,3 @@ def set_metadata(stream, options):
stream.write(src) stream.write(src)
stream.write(after) stream.write(after)
def option_parser():
return get_parser('rtf')
def main(args=sys.argv):
parser = option_parser()
options, args = parser.parse_args(args)
if len(args) != 2:
parser.print_help()
sys.exit(1)
stream = open(args[1], 'r+b')
if options.authors:
options.authors = options.authors.split(',')
options.comments = options.comment
set_metadata(stream, options)
mi = get_metadata(stream)
return mi
if __name__ == '__main__':
main()

View File

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

View File

@ -27,7 +27,7 @@ TABLE_TAGS = set(['table', 'tr', 'td', 'th'])
SPECIAL_TAGS = set(['hr', 'br']) SPECIAL_TAGS = set(['hr', 'br'])
CONTENT_TAGS = set(['img', 'hr', 'br']) CONTENT_TAGS = set(['img', 'hr', 'br'])
PAGE_BREAKS = set(['always', 'odd', 'even']) PAGE_BREAKS = set(['always', 'left', 'right'])
COLLAPSE = re.compile(r'[ \t\r\n\v]+') COLLAPSE = re.compile(r'[ \t\r\n\v]+')
@ -79,6 +79,9 @@ class FormatState(object):
class MobiMLizer(object): class MobiMLizer(object):
def __init__(self, ignore_tables=False):
self.ignore_tables = ignore_tables
def transform(self, oeb, context): def transform(self, oeb, context):
oeb.logger.info('Converting XHTML to Mobipocket markup...') oeb.logger.info('Converting XHTML to Mobipocket markup...')
self.oeb = oeb self.oeb = oeb
@ -341,6 +344,8 @@ class MobiMLizer(object):
tag = 'tr' tag = 'tr'
elif display == 'table-cell': elif display == 'table-cell':
tag = 'td' tag = 'td'
if tag in TABLE_TAGS and self.ignore_tables:
tag = 'span' if tag == 'td' else 'div'
if tag in TABLE_TAGS: if tag in TABLE_TAGS:
for attr in ('rowspan', 'colspan'): for attr in ('rowspan', 'colspan'):
if attr in elem.attrib: if attr in elem.attrib:

View File

@ -87,6 +87,49 @@ def decint(value, direction):
bytes[-1] |= 0x80 bytes[-1] |= 0x80
return ''.join(chr(b) for b in reversed(bytes)) return ''.join(chr(b) for b in reversed(bytes))
def rescale_image(data, maxsizeb, dimen=None):
image = Image.open(StringIO(data))
format = image.format
changed = False
if image.format not in ('JPEG', 'GIF'):
width, height = image.size
area = width * height
if area <= 40000:
format = 'GIF'
else:
image = image.convert('RGBA')
format = 'JPEG'
changed = True
if dimen is not None:
image.thumbnail(dimen, Image.ANTIALIAS)
changed = True
if changed:
data = StringIO()
image.save(data, format)
data = data.getvalue()
if len(data) <= maxsizeb:
return data
image = image.convert('RGBA')
for quality in xrange(95, -1, -1):
data = StringIO()
image.save(data, 'JPEG', quality=quality)
data = data.getvalue()
if len(data) <= maxsizeb:
return data
width, height = image.size
for scale in xrange(99, 0, -1):
scale = scale / 100.
data = StringIO()
scaled = image.copy()
size = (int(width * scale), (height * scale))
scaled.thumbnail(size, Image.ANTIALIAS)
scaled.save(data, 'JPEG', quality=0)
data = data.getvalue()
if len(data) <= maxsizeb:
return data
# Well, we tried?
return data
class Serializer(object): class Serializer(object):
NSRMAP = {'': None, XML_NS: 'xml', XHTML_NS: '', MBP_NS: 'mbp'} NSRMAP = {'': None, XML_NS: 'xml', XHTML_NS: '', MBP_NS: 'mbp'}
@ -356,49 +399,6 @@ class MobiWriter(object):
data, overlap = self._read_text_record(text) data, overlap = self._read_text_record(text)
self._text_nrecords = nrecords self._text_nrecords = nrecords
def _rescale_image(self, data, maxsizeb, dimen=None):
image = Image.open(StringIO(data))
format = image.format
changed = False
if image.format not in ('JPEG', 'GIF'):
width, height = image.size
area = width * height
if area <= 40000:
format = 'GIF'
else:
image = image.convert('RGBA')
format = 'JPEG'
changed = True
if dimen is not None:
image.thumbnail(dimen, Image.ANTIALIAS)
changed = True
if changed:
data = StringIO()
image.save(data, format)
data = data.getvalue()
if len(data) <= maxsizeb:
return data
image = image.convert('RGBA')
for quality in xrange(95, -1, -1):
data = StringIO()
image.save(data, 'JPEG', quality=quality)
data = data.getvalue()
if len(data) <= maxsizeb:
return data
width, height = image.size
for scale in xrange(99, 0, -1):
scale = scale / 100.
data = StringIO()
scaled = image.copy()
size = (int(width * scale), (height * scale))
scaled.thumbnail(size, Image.ANTIALIAS)
scaled.save(data, 'JPEG', quality=0)
data = data.getvalue()
if len(data) <= maxsizeb:
return data
# Well, we tried?
return data
def _generate_images(self): def _generate_images(self):
self._oeb.logger.info('Serializing images...') self._oeb.logger.info('Serializing images...')
images = [(index, href) for href, index in self._images.items()] images = [(index, href) for href, index in self._images.items()]
@ -407,7 +407,7 @@ class MobiWriter(object):
coverid = metadata.cover[0] if metadata.cover else None coverid = metadata.cover[0] if metadata.cover else None
for _, href in images: for _, href in images:
item = self._oeb.manifest.hrefs[href] item = self._oeb.manifest.hrefs[href]
data = self._rescale_image(item.data, self._imagemax) data = rescale_image(item.data, self._imagemax)
self._records.append(data) self._records.append(data)
def _generate_record0(self): def _generate_record0(self):
@ -452,6 +452,13 @@ class MobiWriter(object):
code = EXTH_CODES[term] code = EXTH_CODES[term]
for item in oeb.metadata[term]: for item in oeb.metadata[term]:
data = self.COLLAPSE_RE.sub(' ', unicode(item)) data = self.COLLAPSE_RE.sub(' ', unicode(item))
if term == 'identifier':
if data.lower().startswith('urn:isbn:'):
data = data[9:]
elif item.get('scheme', '').lower() == 'isbn':
pass
else:
continue
data = data.encode('utf-8') data = data.encode('utf-8')
exth.write(pack('>II', code, len(data) + 8)) exth.write(pack('>II', code, len(data) + 8))
exth.write(data) exth.write(data)
@ -468,12 +475,12 @@ class MobiWriter(object):
nrecs += 3 nrecs += 3
exth = exth.getvalue() exth = exth.getvalue()
trail = len(exth) % 4 trail = len(exth) % 4
pad = '' if not trail else '\0' * (4 - trail) pad = '\0' * (4 - trail) # Always pad w/ at least 1 byte
exth = ['EXTH', pack('>II', len(exth) + 12, nrecs), exth, pad] exth = ['EXTH', pack('>II', len(exth) + 12, nrecs), exth, pad]
return ''.join(exth) return ''.join(exth)
def _add_thumbnail(self, item): def _add_thumbnail(self, item):
data = self._rescale_image(item.data, MAX_THUMB_SIZE, MAX_THUMB_DIMEN) data = rescale_image(item.data, MAX_THUMB_SIZE, MAX_THUMB_DIMEN)
manifest = self._oeb.manifest manifest = self._oeb.manifest
id, href = manifest.generate('thumbnail', 'thumbnail.jpeg') id, href = manifest.generate('thumbnail', 'thumbnail.jpeg')
manifest.add(id, href, 'image/jpeg', data=data) manifest.add(id, href, 'image/jpeg', data=data)
@ -517,6 +524,10 @@ def config(defaults=None):
help=_('Modify images to meet Palm device size limitations.')) help=_('Modify images to meet Palm device size limitations.'))
mobi('toc_title', ['--toc-title'], default=None, mobi('toc_title', ['--toc-title'], default=None,
help=_('Title for any generated in-line table of contents.')) help=_('Title for any generated in-line table of contents.'))
mobi('ignore_tables', ['--ignore-tables'], default=False,
help=_('Render HTML tables as blocks of text instead of actual '
'tables. This is neccessary if the HTML contains very large '
'or complex tables.'))
profiles = c.add_group('profiles', _('Device renderer profiles. ' profiles = c.add_group('profiles', _('Device renderer profiles. '
'Affects conversion of font sizes, image rescaling and rasterization ' 'Affects conversion of font sizes, image rescaling and rasterization '
'of tables. Valid profiles are: %s.') % ', '.join(_profiles)) 'of tables. Valid profiles are: %s.') % ', '.join(_profiles))
@ -574,7 +585,7 @@ def oeb2mobi(opts, inpath):
rasterizer.transform(oeb, context) rasterizer.transform(oeb, context)
trimmer = ManifestTrimmer() trimmer = ManifestTrimmer()
trimmer.transform(oeb, context) trimmer.transform(oeb, context)
mobimlizer = MobiMLizer() mobimlizer = MobiMLizer(ignore_tables=opts.ignore_tables)
mobimlizer.transform(oeb, context) mobimlizer.transform(oeb, context)
writer = MobiWriter(compression=compression, imagemax=imagemax) writer = MobiWriter(compression=compression, imagemax=imagemax)
writer.dump(oeb, outpath) writer.dump(oeb, outpath)

View File

@ -10,7 +10,7 @@ import os
import sys import sys
from collections import defaultdict from collections import defaultdict
from types import StringTypes from types import StringTypes
from itertools import izip, count from itertools import izip, count, chain
from urlparse import urldefrag, urlparse, urlunparse from urlparse import urldefrag, urlparse, urlunparse
from urllib import unquote as urlunquote from urllib import unquote as urlunquote
import logging import logging
@ -21,15 +21,18 @@ from lxml import etree
from lxml import html from lxml import html
from calibre import LoggingInterface from calibre import LoggingInterface
from calibre.translations.dynamic import translate from calibre.translations.dynamic import translate
from calibre.startup import get_lang
from calibre.ebooks.oeb.entitydefs import ENTITYDEFS
XML_PARSER = etree.XMLParser(recover=True)
XML_NS = 'http://www.w3.org/XML/1998/namespace' XML_NS = 'http://www.w3.org/XML/1998/namespace'
XHTML_NS = 'http://www.w3.org/1999/xhtml' XHTML_NS = 'http://www.w3.org/1999/xhtml'
OEB_DOC_NS = 'http://openebook.org/namespaces/oeb-document/1.0/'
OPF1_NS = 'http://openebook.org/namespaces/oeb-package/1.0/' OPF1_NS = 'http://openebook.org/namespaces/oeb-package/1.0/'
OPF2_NS = 'http://www.idpf.org/2007/opf' OPF2_NS = 'http://www.idpf.org/2007/opf'
DC09_NS = 'http://purl.org/metadata/dublin_core' DC09_NS = 'http://purl.org/metadata/dublin_core'
DC10_NS = 'http://purl.org/dc/elements/1.0/' DC10_NS = 'http://purl.org/dc/elements/1.0/'
DC11_NS = 'http://purl.org/dc/elements/1.1/' DC11_NS = 'http://purl.org/dc/elements/1.1/'
DC_NSES = set([DC09_NS, DC10_NS, DC11_NS])
XSI_NS = 'http://www.w3.org/2001/XMLSchema-instance' XSI_NS = 'http://www.w3.org/2001/XMLSchema-instance'
DCTERMS_NS = 'http://purl.org/dc/terms/' DCTERMS_NS = 'http://purl.org/dc/terms/'
NCX_NS = 'http://www.daisy.org/z3986/2005/ncx/' NCX_NS = 'http://www.daisy.org/z3986/2005/ncx/'
@ -39,6 +42,7 @@ XPNSMAP = {'h': XHTML_NS, 'o1': OPF1_NS, 'o2': OPF2_NS,
'd09': DC09_NS, 'd10': DC10_NS, 'd11': DC11_NS, 'd09': DC09_NS, 'd10': DC10_NS, 'd11': DC11_NS,
'xsi': XSI_NS, 'dt': DCTERMS_NS, 'ncx': NCX_NS, 'xsi': XSI_NS, 'dt': DCTERMS_NS, 'ncx': NCX_NS,
'svg': SVG_NS, 'xl': XLINK_NS} 'svg': SVG_NS, 'xl': XLINK_NS}
DC_PREFIXES = ('d11', 'd10', 'd09')
def XML(name): return '{%s}%s' % (XML_NS, name) def XML(name): return '{%s}%s' % (XML_NS, name)
def XHTML(name): return '{%s}%s' % (XHTML_NS, name) def XHTML(name): return '{%s}%s' % (XHTML_NS, name)
@ -60,6 +64,7 @@ GIF_MIME = 'image/gif'
JPEG_MIME = 'image/jpeg' JPEG_MIME = 'image/jpeg'
PNG_MIME = 'image/png' PNG_MIME = 'image/png'
SVG_MIME = 'image/svg+xml' SVG_MIME = 'image/svg+xml'
BINARY_MIME = 'application/octet-stream'
OEB_STYLES = set([CSS_MIME, OEB_CSS_MIME, 'text/x-oeb-css']) OEB_STYLES = set([CSS_MIME, OEB_CSS_MIME, 'text/x-oeb-css'])
OEB_DOCS = set([XHTML_MIME, 'text/html', OEB_DOC_MIME, 'text/x-oeb-document']) OEB_DOCS = set([XHTML_MIME, 'text/html', OEB_DOC_MIME, 'text/x-oeb-document'])
@ -68,6 +73,8 @@ OEB_IMAGES = set([GIF_MIME, JPEG_MIME, PNG_MIME, SVG_MIME])
MS_COVER_TYPE = 'other.ms-coverimage-standard' MS_COVER_TYPE = 'other.ms-coverimage-standard'
ENTITY_RE = re.compile(r'&([a-zA-Z_:][a-zA-Z0-9.-_:]+);')
COLLAPSE_RE = re.compile(r'[ \t\r\n\v]+')
def element(parent, *args, **kwargs): def element(parent, *args, **kwargs):
if parent is not None: if parent is not None:
@ -138,8 +145,7 @@ class Logger(LoggingInterface, object):
class AbstractContainer(object): class AbstractContainer(object):
def read_xml(self, path): def read_xml(self, path):
return etree.fromstring( return etree.fromstring(
self.read(path), parser=XML_PARSER, self.read(path), base_url=os.path.dirname(path))
base_url=os.path.dirname(path))
class DirContainer(AbstractContainer): class DirContainer(AbstractContainer):
def __init__(self, rootdir): def __init__(self, rootdir):
@ -191,18 +197,19 @@ class Metadata(object):
def __init__(self, term, value, fq_attrib={}, **kwargs): def __init__(self, term, value, fq_attrib={}, **kwargs):
self.fq_attrib = fq_attrib = dict(fq_attrib) self.fq_attrib = fq_attrib = dict(fq_attrib)
fq_attrib.update(kwargs) fq_attrib.update(kwargs)
if term == OPF('meta') and not value: if barename(term).lower() in Metadata.TERMS and \
term = self.fq_attrib.pop('name') (not namespace(term) or namespace(term) in DC_NSES):
value = self.fq_attrib.pop('content') # Anything looking like Dublin Core is coerced
elif term in Metadata.TERMS and not namespace(term): term = DC(barename(term).lower())
term = DC(term) elif namespace(term) == OPF2_NS:
term = barename(term)
self.term = term self.term = term
self.value = value self.value = value
self.attrib = attrib = {} self.attrib = attrib = {}
for fq_attr in fq_attrib: for fq_attr in fq_attrib:
if fq_attr in Metadata.ATTRS: if fq_attr in Metadata.ATTRS:
attr = fq_attr attr = fq_attr
fq_attr = OPF2(fq_attr) fq_attr = OPF(fq_attr)
fq_attrib[fq_attr] = fq_attrib.pop(attr) fq_attrib[fq_attr] = fq_attrib.pop(attr)
else: else:
attr = barename(fq_attr) attr = barename(fq_attr)
@ -217,6 +224,15 @@ class Metadata(object):
'%r object has no attribute %r' \ '%r object has no attribute %r' \
% (self.__class__.__name__, name)) % (self.__class__.__name__, name))
def __getitem__(self, key):
return self.attrib[key]
def __contains__(self, key):
return key in self.attrib
def get(self, key, default=None):
return self.attrib.get(key, default)
def __repr__(self): def __repr__(self):
return 'Item(term=%r, value=%r, attrib=%r)' \ return 'Item(term=%r, value=%r, attrib=%r)' \
% (barename(self.term), self.value, self.attrib) % (barename(self.term), self.value, self.attrib)
@ -316,20 +332,74 @@ class Manifest(object):
% (self.id, self.href, self.media_type) % (self.id, self.href, self.media_type)
def _force_xhtml(self, data): def _force_xhtml(self, data):
# Possibly decode in user-specified encoding
if self.oeb.encoding is not None: if self.oeb.encoding is not None:
data = data.decode(self.oeb.encoding, 'replace') 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 with more & more drastic measures to parse
try: try:
data = etree.fromstring(data, parser=XML_PARSER) data = etree.fromstring(data)
except etree.XMLSyntaxError: except etree.XMLSyntaxError:
repl = lambda m: ENTITYDEFS.get(m.group(1), m.group(0))
data = ENTITY_RE.sub(repl, data)
try:
data = etree.fromstring(data)
except etree.XMLSyntaxError:
self.oeb.logger.warn('Parsing file %r as HTML' % self.href)
data = html.fromstring(data) data = html.fromstring(data)
data.attrib.pop('xmlns', None)
data = etree.tostring(data, encoding=unicode) data = etree.tostring(data, encoding=unicode)
data = etree.fromstring(data, parser=XML_PARSER) data = etree.fromstring(data)
if namespace(data.tag) != XHTML_NS: # Force into the XHTML namespace
if barename(data.tag) != 'html':
raise OEBError(
'File %r does not appear to be (X)HTML' % self.href)
elif not namespace(data.tag):
data.attrib['xmlns'] = XHTML_NS data.attrib['xmlns'] = XHTML_NS
data = etree.tostring(data, encoding=unicode) data = etree.tostring(data, encoding=unicode)
data = etree.fromstring(data, parser=XML_PARSER) data = etree.fromstring(data)
elif namespace(data.tag) != XHTML_NS:
# OEB_DOC_NS, but possibly others
ns = namespace(data.tag)
attrib = dict(data.attrib)
nroot = etree.Element(XHTML('html'),
nsmap={None: XHTML_NS}, attrib=attrib)
for elem in data.iterdescendants():
if isinstance(elem.tag, basestring) and \
namespace(elem.tag) == ns:
elem.tag = XHTML(barename(elem.tag))
for elem in data:
nroot.append(elem)
data = nroot
# Remove any encoding-specifying <meta/> elements
for meta in self.META_XP(data): for meta in self.META_XP(data):
meta.getparent().remove(meta) meta.getparent().remove(meta)
# Ensure has a <head/>
head = xpath(data, '/h:html/h:head')
head = head[0] if head else None
if head is None:
self.oeb.logger.warn(
'File %r missing <head/> element' % self.href)
head = etree.Element(XHTML('head'))
data.insert(0, head)
title = etree.SubElement(head, XHTML('title'))
title.text = self.oeb.translate(__('Unknown'))
elif not xpath(data, '/h:html/h:head/h:title'):
self.oeb.logger.warn(
'File %r missing <title/> element' % self.href)
title = etree.SubElement(head, XHTML('title'))
title.text = self.oeb.translate(__('Unknown'))
# Ensure has a <body/>
if not xpath(data, '/h:html/h:body'):
self.oeb.logger.warn(
'File %r missing <body/> element' % self.href)
etree.SubElement(data, XHTML('body'))
return data return data
def data(): def data():
@ -340,7 +410,7 @@ class Manifest(object):
if self.media_type in OEB_DOCS: if self.media_type in OEB_DOCS:
data = self._force_xhtml(data) data = self._force_xhtml(data)
elif self.media_type[-4:] in ('+xml', '/xml'): elif self.media_type[-4:] in ('+xml', '/xml'):
data = etree.fromstring(data, parser=XML_PARSER) data = etree.fromstring(data)
self._data = data self._data = data
return data return data
def fset(self, value): def fset(self, value):
@ -456,9 +526,9 @@ class Manifest(object):
elem = element(parent, 'manifest') elem = element(parent, 'manifest')
for item in self.ids.values(): for item in self.ids.values():
media_type = item.media_type media_type = item.media_type
if media_type == XHTML_MIME: if media_type in OEB_DOCS:
media_type = OEB_DOC_MIME media_type = OEB_DOC_MIME
elif media_type == CSS_MIME: elif media_type in OEB_STYLES:
media_type = OEB_CSS_MIME media_type = OEB_CSS_MIME
attrib = {'id': item.id, 'href': item.href, attrib = {'id': item.id, 'href': item.href,
'media-type': media_type} 'media-type': media_type}
@ -470,6 +540,11 @@ class Manifest(object):
def to_opf2(self, parent=None): def to_opf2(self, parent=None):
elem = element(parent, OPF('manifest')) elem = element(parent, OPF('manifest'))
for item in self.ids.values(): for item in self.ids.values():
media_type = item.media_type
if media_type in OEB_DOCS:
media_type = XHTML_MIME
elif media_type in OEB_STYLES:
media_type = CSS_MIME
attrib = {'id': item.id, 'href': item.href, attrib = {'id': item.id, 'href': item.href,
'media-type': item.media_type} 'media-type': item.media_type}
if item.fallback: if item.fallback:
@ -733,25 +808,19 @@ class OEBBook(object):
opf = self._read_opf(opfpath) opf = self._read_opf(opfpath)
self._all_from_opf(opf) self._all_from_opf(opf)
def _convert_opf1(self, opf): def _clean_opf(self, opf):
# Seriously, seriously wrong for elem in opf.iter():
if namespace(opf.tag) == OPF1_NS:
opf.tag = barename(opf.tag)
for elem in opf.iterdescendants():
if isinstance(elem.tag, basestring) \ if isinstance(elem.tag, basestring) \
and namespace(elem.tag) == OPF1_NS: and namespace(elem.tag) in ('', OPF1_NS):
elem.tag = barename(elem.tag) elem.tag = OPF(barename(elem.tag))
attrib = dict(opf.attrib) attrib = dict(opf.attrib)
attrib['version'] = '2.0'
nroot = etree.Element(OPF('package'), nroot = etree.Element(OPF('package'),
nsmap={None: OPF2_NS}, attrib=attrib) nsmap={None: OPF2_NS}, attrib=attrib)
metadata = etree.SubElement(nroot, OPF('metadata'), metadata = etree.SubElement(nroot, OPF('metadata'),
nsmap={'opf': OPF2_NS, 'dc': DC11_NS, nsmap={'opf': OPF2_NS, 'dc': DC11_NS,
'xsi': XSI_NS, 'dcterms': DCTERMS_NS}) 'xsi': XSI_NS, 'dcterms': DCTERMS_NS})
for prefix in ('d11', 'd10', 'd09'): dc = lambda prefix: xpath(opf, 'o2:metadata//%s:*' % prefix)
elements = xpath(opf, 'metadata//%s:*' % prefix) for element in chain(*(dc(prefix) for prefix in DC_PREFIXES)):
if elements: break
for element in elements:
if not element.text: continue if not element.text: continue
tag = barename(element.tag).lower() tag = barename(element.tag).lower()
element.tag = '{%s}%s' % (DC11_NS, tag) element.tag = '{%s}%s' % (DC11_NS, tag)
@ -761,28 +830,26 @@ class OEBBook(object):
element.attrib[nsname] = element.attrib[name] element.attrib[nsname] = element.attrib[name]
del element.attrib[name] del element.attrib[name]
metadata.append(element) metadata.append(element)
for element in opf.xpath('metadata//meta'): for element in xpath(opf, 'o2:metadata//o2:meta'):
metadata.append(element) metadata.append(element)
for item in opf.xpath('manifest/item'): for tag in ('o2:manifest', 'o2:spine', 'o2:tours', 'o2:guide'):
media_type = item.attrib['media-type'].lower() for element in xpath(opf, tag):
if media_type in OEB_DOCS:
media_type = XHTML_MIME
elif media_type in OEB_STYLES:
media_type = CSS_MIME
item.attrib['media-type'] = media_type
for tag in ('manifest', 'spine', 'tours', 'guide'):
for element in opf.xpath(tag):
nroot.append(element) nroot.append(element)
return etree.fromstring(etree.tostring(nroot), parser=XML_PARSER) return nroot
def _read_opf(self, opfpath): def _read_opf(self, opfpath):
opf = self.container.read_xml(opfpath) opf = self.container.read(opfpath)
version = float(opf.get('version', 1.0)) try:
opf = etree.fromstring(opf)
except etree.XMLSyntaxError:
repl = lambda m: ENTITYDEFS.get(m.group(1), m.group(0))
opf = ENTITY_RE.sub(repl, opf)
opf = etree.fromstring(opf)
self.logger.warn('OPF contains invalid HTML named entities')
ns = namespace(opf.tag) ns = namespace(opf.tag)
if ns not in ('', OPF1_NS, OPF2_NS): if ns not in ('', OPF1_NS, OPF2_NS):
raise OEBError('Invalid namespace %r for OPF document' % ns) raise OEBError('Invalid namespace %r for OPF document' % ns)
if ns != OPF2_NS or version < 2.0: opf = self._clean_opf(opf)
opf = self._convert_opf1(opf)
return opf return opf
def _metadata_from_opf(self, opf): def _metadata_from_opf(self, opf):
@ -791,8 +858,16 @@ class OEBBook(object):
self.metadata = metadata = Metadata(self) self.metadata = metadata = Metadata(self)
ignored = (OPF('dc-metadata'), OPF('x-metadata')) ignored = (OPF('dc-metadata'), OPF('x-metadata'))
for elem in xpath(opf, '/o2:package/o2:metadata//*'): for elem in xpath(opf, '/o2:package/o2:metadata//*'):
if elem.tag not in ignored and (elem.text or elem.attrib): if elem.tag in ignored: continue
metadata.add(elem.tag, elem.text, elem.attrib) term = elem.tag
value = elem.text
if term == OPF('meta'):
term = elem.attrib.pop('name', None)
value = elem.attrib.pop('content', None)
if value:
value = COLLAPSE_RE.sub(' ', value.strip())
if term and (value or elem.attrib):
metadata.add(term, value, elem.attrib)
haveuuid = haveid = False haveuuid = haveid = False
for ident in metadata.identifier: for ident in metadata.identifier:
if unicode(ident).startswith('urn:uuid:'): if unicode(ident).startswith('urn:uuid:'):
@ -807,36 +882,38 @@ class OEBBook(object):
self.uid = item self.uid = item
break break
else: else:
self.logger.warn(u'Unique-identifier %r not found.' % uid) self.logger.warn(u'Unique-identifier %r not found' % uid)
for ident in metadata.identifier: for ident in metadata.identifier:
if 'id' in ident.attrib: if 'id' in ident.attrib:
self.uid = metadata.identifier[0] self.uid = metadata.identifier[0]
break break
if not metadata.language: if not metadata.language:
self.logger.warn(u'Language not specified.') self.logger.warn(u'Language not specified')
metadata.add('language', 'en') metadata.add('language', get_lang())
if not metadata.creator: if not metadata.creator:
self.logger.warn(u'Creator not specified.') self.logger.warn('Creator not specified')
metadata.add('creator', 'Unknown') metadata.add('creator', self.translate(__('Unknown')))
if not metadata.title: if not metadata.title:
self.logger.warn(u'Title not specified.') self.logger.warn('Title not specified')
metadata.add('title', 'Unknown') metadata.add('title', self.translate(__('Unknown')))
def _manifest_from_opf(self, opf): def _manifest_from_opf(self, opf):
self.manifest = manifest = Manifest(self) self.manifest = manifest = Manifest(self)
for elem in xpath(opf, '/o2:package/o2:manifest/o2:item'): for elem in xpath(opf, '/o2:package/o2:manifest/o2:item'):
id = elem.get('id') id = elem.get('id')
href = elem.get('href') href = elem.get('href')
media_type = elem.get('media-type') media_type = elem.get('media-type', None)
if media_type is None:
media_type = elem.get('mediatype', BINARY_MIME)
fallback = elem.get('fallback') fallback = elem.get('fallback')
if href in manifest.hrefs: if href in manifest.hrefs:
self.logger.warn(u'Duplicate manifest entry for %r.' % href) self.logger.warn(u'Duplicate manifest entry for %r' % href)
continue continue
if not self.container.exists(href): if not self.container.exists(href):
self.logger.warn(u'Manifest item %r not found.' % href) self.logger.warn(u'Manifest item %r not found' % href)
continue continue
if id in manifest.ids: if id in manifest.ids:
self.logger.warn(u'Duplicate manifest id %r.' % id) self.logger.warn(u'Duplicate manifest id %r' % id)
id, href = manifest.generate(id, href) id, href = manifest.generate(id, href)
manifest.add(id, href, media_type, fallback) manifest.add(id, href, media_type, fallback)
@ -845,7 +922,7 @@ class OEBBook(object):
for elem in xpath(opf, '/o2:package/o2:spine/o2:itemref'): for elem in xpath(opf, '/o2:package/o2:spine/o2:itemref'):
idref = elem.get('idref') idref = elem.get('idref')
if idref not in self.manifest: if idref not in self.manifest:
self.logger.warn(u'Spine item %r not found.' % idref) self.logger.warn(u'Spine item %r not found' % idref)
continue continue
item = self.manifest[idref] item = self.manifest[idref]
spine.add(item, elem.get('linear')) spine.add(item, elem.get('linear'))
@ -857,6 +934,8 @@ class OEBBook(object):
extras.sort() extras.sort()
for item in extras: for item in extras:
spine.add(item, False) spine.add(item, False)
if len(spine) == 0:
raise OEBError("Spine is empty")
def _guide_from_opf(self, opf): def _guide_from_opf(self, opf):
self.guide = guide = Guide(self) self.guide = guide = Guide(self)
@ -886,9 +965,13 @@ class OEBBook(object):
if len(result) != 1: if len(result) != 1:
return False return False
id = result[0] id = result[0]
ncx = self.manifest[id].data if id not in self.manifest.ids:
self.manifest.remove(id) return False
title = xpath(ncx, 'ncx:docTitle/ncx:text/text()')[0] item = self.manifest.ids[id]
ncx = item.data
self.manifest.remove(item)
title = xpath(ncx, 'ncx:docTitle/ncx:text/text()')
title = title[0].strip() if title else unicode(self.metadata.title)
self.toc = toc = TOC(title) self.toc = toc = TOC(title)
navmaps = xpath(ncx, 'ncx:navMap') navmaps = xpath(ncx, 'ncx:navMap')
for navmap in navmaps: for navmap in navmaps:
@ -945,7 +1028,8 @@ class OEBBook(object):
if not item.linear: continue if not item.linear: continue
html = item.data html = item.data
title = xpath(html, '/h:html/h:head/h:title/text()') title = xpath(html, '/h:html/h:head/h:title/text()')
if title: titles.append(title[0]) title = title[0].strip() if title else None
if title: titles.append(title)
headers.append('(unlabled)') headers.append('(unlabled)')
for tag in ('h1', 'h2', 'h3', 'h4', 'h5', 'strong'): for tag in ('h1', 'h2', 'h3', 'h4', 'h5', 'strong'):
expr = '/h:html/h:body//h:%s[position()=1]/text()' % (tag,) expr = '/h:html/h:body//h:%s[position()=1]/text()' % (tag,)
@ -969,9 +1053,19 @@ class OEBBook(object):
def _ensure_cover_image(self): def _ensure_cover_image(self):
cover = None cover = None
spine0 = self.spine[0] hcover = self.spine[0]
html = spine0.data if 'cover' in self.guide:
if self.metadata.cover: href = self.guide['cover'].href
item = self.manifest.hrefs[href]
media_type = item.media_type
if media_type in OEB_RASTER_IMAGES:
cover = item
elif media_type in OEB_DOCS:
hcover = item
html = hcover.data
if cover is not None:
pass
elif self.metadata.cover:
id = str(self.metadata.cover[0]) id = str(self.metadata.cover[0])
cover = self.manifest.ids[id] cover = self.manifest.ids[id]
elif MS_COVER_TYPE in self.guide: elif MS_COVER_TYPE in self.guide:
@ -979,16 +1073,16 @@ class OEBBook(object):
cover = self.manifest.hrefs[href] cover = self.manifest.hrefs[href]
elif xpath(html, '//h:img[position()=1]'): elif xpath(html, '//h:img[position()=1]'):
img = xpath(html, '//h:img[position()=1]')[0] img = xpath(html, '//h:img[position()=1]')[0]
href = spine0.abshref(img.get('src')) href = hcover.abshref(img.get('src'))
cover = self.manifest.hrefs[href] cover = self.manifest.hrefs[href]
elif xpath(html, '//h:object[position()=1]'): elif xpath(html, '//h:object[position()=1]'):
object = xpath(html, '//h:object[position()=1]')[0] object = xpath(html, '//h:object[position()=1]')[0]
href = spine0.abshref(object.get('data')) href = hcover.abshref(object.get('data'))
cover = self.manifest.hrefs[href] cover = self.manifest.hrefs[href]
elif xpath(html, '//svg:svg[position()=1]'): elif xpath(html, '//svg:svg[position()=1]'):
svg = copy.deepcopy(xpath(html, '//svg:svg[position()=1]')[0]) svg = copy.deepcopy(xpath(html, '//svg:svg[position()=1]')[0])
href = os.path.splitext(spine0.href)[0] + '.svg' href = os.path.splitext(hcover.href)[0] + '.svg'
id, href = self.manifest.generate(spine0.id, href) id, href = self.manifest.generate(hcover.id, href)
cover = self.manifest.add(id, href, SVG_MIME, data=svg) cover = self.manifest.add(id, href, SVG_MIME, data=svg)
if cover and not self.metadata.cover: if cover and not self.metadata.cover:
self.metadata.add('cover', cover.id) self.metadata.add('cover', cover.id)

View File

@ -0,0 +1,256 @@
"""
Replacement for htmlentitydefs which uses purely numeric entities.
"""
__license__ = 'GPL v3'
__copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
ENTITYDEFS = \
{'AElig': '&#198;',
'Aacute': '&#193;',
'Acirc': '&#194;',
'Agrave': '&#192;',
'Alpha': '&#913;',
'Aring': '&#197;',
'Atilde': '&#195;',
'Auml': '&#196;',
'Beta': '&#914;',
'Ccedil': '&#199;',
'Chi': '&#935;',
'Dagger': '&#8225;',
'Delta': '&#916;',
'ETH': '&#208;',
'Eacute': '&#201;',
'Ecirc': '&#202;',
'Egrave': '&#200;',
'Epsilon': '&#917;',
'Eta': '&#919;',
'Euml': '&#203;',
'Gamma': '&#915;',
'Iacute': '&#205;',
'Icirc': '&#206;',
'Igrave': '&#204;',
'Iota': '&#921;',
'Iuml': '&#207;',
'Kappa': '&#922;',
'Lambda': '&#923;',
'Mu': '&#924;',
'Ntilde': '&#209;',
'Nu': '&#925;',
'OElig': '&#338;',
'Oacute': '&#211;',
'Ocirc': '&#212;',
'Ograve': '&#210;',
'Omega': '&#937;',
'Omicron': '&#927;',
'Oslash': '&#216;',
'Otilde': '&#213;',
'Ouml': '&#214;',
'Phi': '&#934;',
'Pi': '&#928;',
'Prime': '&#8243;',
'Psi': '&#936;',
'Rho': '&#929;',
'Scaron': '&#352;',
'Sigma': '&#931;',
'THORN': '&#222;',
'Tau': '&#932;',
'Theta': '&#920;',
'Uacute': '&#218;',
'Ucirc': '&#219;',
'Ugrave': '&#217;',
'Upsilon': '&#933;',
'Uuml': '&#220;',
'Xi': '&#926;',
'Yacute': '&#221;',
'Yuml': '&#376;',
'Zeta': '&#918;',
'aacute': '&#225;',
'acirc': '&#226;',
'acute': '&#180;',
'aelig': '&#230;',
'agrave': '&#224;',
'alefsym': '&#8501;',
'alpha': '&#945;',
'and': '&#8743;',
'ang': '&#8736;',
'aring': '&#229;',
'asymp': '&#8776;',
'atilde': '&#227;',
'auml': '&#228;',
'bdquo': '&#8222;',
'beta': '&#946;',
'brvbar': '&#166;',
'bull': '&#8226;',
'cap': '&#8745;',
'ccedil': '&#231;',
'cedil': '&#184;',
'cent': '&#162;',
'chi': '&#967;',
'circ': '&#710;',
'clubs': '&#9827;',
'cong': '&#8773;',
'copy': '&#169;',
'crarr': '&#8629;',
'cup': '&#8746;',
'curren': '&#164;',
'dArr': '&#8659;',
'dagger': '&#8224;',
'darr': '&#8595;',
'deg': '&#176;',
'delta': '&#948;',
'diams': '&#9830;',
'divide': '&#247;',
'eacute': '&#233;',
'ecirc': '&#234;',
'egrave': '&#232;',
'empty': '&#8709;',
'emsp': '&#8195;',
'ensp': '&#8194;',
'epsilon': '&#949;',
'equiv': '&#8801;',
'eta': '&#951;',
'eth': '&#240;',
'euml': '&#235;',
'euro': '&#8364;',
'exist': '&#8707;',
'fnof': '&#402;',
'forall': '&#8704;',
'frac12': '&#189;',
'frac14': '&#188;',
'frac34': '&#190;',
'frasl': '&#8260;',
'gamma': '&#947;',
'ge': '&#8805;',
'hArr': '&#8660;',
'harr': '&#8596;',
'hearts': '&#9829;',
'hellip': '&#8230;',
'iacute': '&#237;',
'icirc': '&#238;',
'iexcl': '&#161;',
'igrave': '&#236;',
'image': '&#8465;',
'infin': '&#8734;',
'int': '&#8747;',
'iota': '&#953;',
'iquest': '&#191;',
'isin': '&#8712;',
'iuml': '&#239;',
'kappa': '&#954;',
'lArr': '&#8656;',
'lambda': '&#955;',
'lang': '&#9001;',
'laquo': '&#171;',
'larr': '&#8592;',
'lceil': '&#8968;',
'ldquo': '&#8220;',
'le': '&#8804;',
'lfloor': '&#8970;',
'lowast': '&#8727;',
'loz': '&#9674;',
'lrm': '&#8206;',
'lsaquo': '&#8249;',
'lsquo': '&#8216;',
'macr': '&#175;',
'mdash': '&#8212;',
'micro': '&#181;',
'middot': '&#183;',
'minus': '&#8722;',
'mu': '&#956;',
'nabla': '&#8711;',
'nbsp': '&#160;',
'ndash': '&#8211;',
'ne': '&#8800;',
'ni': '&#8715;',
'not': '&#172;',
'notin': '&#8713;',
'nsub': '&#8836;',
'ntilde': '&#241;',
'nu': '&#957;',
'oacute': '&#243;',
'ocirc': '&#244;',
'oelig': '&#339;',
'ograve': '&#242;',
'oline': '&#8254;',
'omega': '&#969;',
'omicron': '&#959;',
'oplus': '&#8853;',
'or': '&#8744;',
'ordf': '&#170;',
'ordm': '&#186;',
'oslash': '&#248;',
'otilde': '&#245;',
'otimes': '&#8855;',
'ouml': '&#246;',
'para': '&#182;',
'part': '&#8706;',
'permil': '&#8240;',
'perp': '&#8869;',
'phi': '&#966;',
'pi': '&#960;',
'piv': '&#982;',
'plusmn': '&#177;',
'pound': '&#163;',
'prime': '&#8242;',
'prod': '&#8719;',
'prop': '&#8733;',
'psi': '&#968;',
'rArr': '&#8658;',
'radic': '&#8730;',
'rang': '&#9002;',
'raquo': '&#187;',
'rarr': '&#8594;',
'rceil': '&#8969;',
'rdquo': '&#8221;',
'real': '&#8476;',
'reg': '&#174;',
'rfloor': '&#8971;',
'rho': '&#961;',
'rlm': '&#8207;',
'rsaquo': '&#8250;',
'rsquo': '&#8217;',
'sbquo': '&#8218;',
'scaron': '&#353;',
'sdot': '&#8901;',
'sect': '&#167;',
'shy': '&#173;',
'sigma': '&#963;',
'sigmaf': '&#962;',
'sim': '&#8764;',
'spades': '&#9824;',
'sub': '&#8834;',
'sube': '&#8838;',
'sum': '&#8721;',
'sup': '&#8835;',
'sup1': '&#185;',
'sup2': '&#178;',
'sup3': '&#179;',
'supe': '&#8839;',
'szlig': '&#223;',
'tau': '&#964;',
'there4': '&#8756;',
'theta': '&#952;',
'thetasym': '&#977;',
'thinsp': '&#8201;',
'thorn': '&#254;',
'tilde': '&#732;',
'times': '&#215;',
'trade': '&#8482;',
'uArr': '&#8657;',
'uacute': '&#250;',
'uarr': '&#8593;',
'ucirc': '&#251;',
'ugrave': '&#249;',
'uml': '&#168;',
'upsih': '&#978;',
'upsilon': '&#965;',
'uuml': '&#252;',
'weierp': '&#8472;',
'xi': '&#958;',
'yacute': '&#253;',
'yen': '&#165;',
'yuml': '&#255;',
'zeta': '&#950;',
'zwj': '&#8205;',
'zwnj': '&#8204;'}

View File

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

View File

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

View File

@ -46,9 +46,10 @@ class SVGRasterizer(object):
data = QByteArray(xml2str(elem)) data = QByteArray(xml2str(elem))
svg = QSvgRenderer(data) svg = QSvgRenderer(data)
size = svg.defaultSize() size = svg.defaultSize()
view_box = elem.get('viewBox', elem.get('viewbox', None))
if size.width() == 100 and size.height() == 100 \ if size.width() == 100 and size.height() == 100 \
and 'viewBox' in elem.attrib: and view_box is not None:
box = [float(x) for x in elem.attrib['viewBox'].split()] box = [float(x) for x in view_box.split()]
size.setWidth(box[2] - box[0]) size.setWidth(box[2] - box[0])
size.setHeight(box[3] - box[1]) size.setHeight(box[3] - box[1])
if width or height: if width or height:

View File

@ -13,6 +13,7 @@ from urlparse import urldefrag
from lxml import etree from lxml import etree
import cssutils import cssutils
from calibre.ebooks.oeb.base import XPNSMAP, CSS_MIME, OEB_DOCS from calibre.ebooks.oeb.base import XPNSMAP, CSS_MIME, OEB_DOCS
from calibre.ebooks.oeb.base import urlnormalize
LINK_SELECTORS = [] LINK_SELECTORS = []
for expr in ('//h:link/@href', '//h:img/@src', '//h:object/@data', for expr in ('//h:link/@href', '//h:img/@src', '//h:object/@data',
@ -46,7 +47,7 @@ class ManifestTrimmer(object):
item.data is not None: item.data is not None:
hrefs = [sel(item.data) for sel in LINK_SELECTORS] hrefs = [sel(item.data) for sel in LINK_SELECTORS]
for href in chain(*hrefs): for href in chain(*hrefs):
href = item.abshref(href) href = item.abshref(urlnormalize(href))
if href in oeb.manifest.hrefs: if href in oeb.manifest.hrefs:
found = oeb.manifest.hrefs[href] found = oeb.manifest.hrefs[href]
if found not in used: if found not in used:

View File

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

View File

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

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>800</width> <width>755</width>
<height>570</height> <height>557</height>
</rect> </rect>
</property> </property>
<property name="windowTitle" > <property name="windowTitle" >
@ -328,8 +328,8 @@
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="page" > <widget class="QWidget" name="page" >
<layout class="QGridLayout" name="gridLayout_3" > <layout class="QVBoxLayout" name="verticalLayout_4" >
<item row="0" column="0" > <item>
<widget class="QCheckBox" name="roman_numerals" > <widget class="QCheckBox" name="roman_numerals" >
<property name="text" > <property name="text" >
<string>Use &amp;Roman numerals for series number</string> <string>Use &amp;Roman numerals for series number</string>
@ -339,12 +339,47 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" > <item>
<widget class="QCheckBox" name="systray_icon" >
<property name="text" >
<string>Enable system &amp;tray icon (needs restart)</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="systray_notifications" >
<property name="text" >
<string>Show &amp;notifications in system tray</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="separate_cover_flow" >
<property name="text" >
<string>Show cover &amp;browser in a separate window (needs restart)</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="sync_news" >
<property name="text" >
<string>Automatically send downloaded &amp;news to ebook reader</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="delete_news" >
<property name="text" >
<string>&amp;Delete news from library when it is sent to reader</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" > <layout class="QHBoxLayout" name="horizontalLayout" >
<item> <item>
<widget class="QLabel" name="label_6" > <widget class="QLabel" name="label_6" >
<property name="text" > <property name="text" >
<string>&amp;Number of covers to show in browse mode (after restart):</string> <string>&amp;Number of covers to show in browse mode (needs restart):</string>
</property> </property>
<property name="buddy" > <property name="buddy" >
<cstring>cover_browse</cstring> <cstring>cover_browse</cstring>
@ -356,7 +391,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="7" column="0" > <item>
<widget class="QGroupBox" name="groupBox_2" > <widget class="QGroupBox" name="groupBox_2" >
<property name="title" > <property name="title" >
<string>Toolbar</string> <string>Toolbar</string>
@ -402,14 +437,22 @@
</widget> </widget>
</item> </item>
</layout> </layout>
<zorder>toolbar_button_size</zorder>
<zorder>label_4</zorder>
<zorder>show_toolbar_text</zorder>
<zorder>columns</zorder>
<zorder></zorder>
<zorder>groupBox_3</zorder>
</widget> </widget>
</item> </item>
<item row="8" column="0" > <item>
<layout class="QHBoxLayout" name="horizontalLayout_7" >
<item>
<widget class="QGroupBox" name="groupBox" > <widget class="QGroupBox" name="groupBox" >
<property name="title" > <property name="title" >
<string>Select visible &amp;columns in library view</string> <string>Select visible &amp;columns in library view</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_4" > <layout class="QVBoxLayout" name="verticalLayout_7" >
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_3" > <layout class="QHBoxLayout" name="horizontalLayout_3" >
<item> <item>
@ -463,10 +506,14 @@
</item> </item>
</layout> </layout>
</item> </item>
</layout>
<zorder>columns</zorder>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="groupBox_3" > <widget class="QGroupBox" name="groupBox_3" >
<property name="title" > <property name="title" >
<string>Use internal &amp;viewer for the following formats:</string> <string>Use internal &amp;viewer for:</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_4" > <layout class="QGridLayout" name="gridLayout_4" >
<item row="0" column="0" > <item row="0" column="0" >
@ -483,37 +530,19 @@
</widget> </widget>
</item> </item>
</layout> </layout>
</widget>
</item>
<item row="1" column="0" >
<widget class="QCheckBox" name="systray_icon" >
<property name="text" >
<string>Enable system &amp;tray icon (needs restart)</string>
</property>
</widget>
</item>
<item row="4" column="0" >
<widget class="QCheckBox" name="sync_news" >
<property name="text" >
<string>Automatically send downloaded &amp;news to ebook reader</string>
</property>
</widget>
</item>
<item row="5" column="0" >
<widget class="QCheckBox" name="delete_news" >
<property name="text" >
<string>&amp;Delete news from library when it is sent to reader</string>
</property>
</widget>
</item>
<item row="3" column="0" >
<widget class="QCheckBox" name="separate_cover_flow" >
<property name="text" >
<string>Show cover &amp;browser in a separate window (needs restart)</string>
</property>
</widget>
</item> </item>
</layout> </layout>
<zorder>roman_numerals</zorder>
<zorder>groupBox_2</zorder>
<zorder>groupBox</zorder>
<zorder>systray_icon</zorder>
<zorder>sync_news</zorder>
<zorder>delete_news</zorder>
<zorder>separate_cover_flow</zorder>
<zorder>systray_notifications</zorder>
<zorder>groupBox_3</zorder>
<zorder></zorder>
<zorder></zorder>
</widget> </widget>
<widget class="QWidget" name="page_2" > <widget class="QWidget" name="page_2" >
<layout class="QVBoxLayout" > <layout class="QVBoxLayout" >

View File

@ -14,7 +14,7 @@ from lxml.etree import XPath
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
from calibre.gui2.dialogs.epub_ui import Ui_Dialog from calibre.gui2.dialogs.epub_ui import Ui_Dialog
from calibre.gui2 import error_dialog, choose_images, pixmap_to_data from calibre.gui2 import error_dialog, choose_images, pixmap_to_data, ResizableDialog
from calibre.ebooks.epub.from_any import SOURCE_FORMATS, config as epubconfig from calibre.ebooks.epub.from_any import SOURCE_FORMATS, config as epubconfig
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
@ -22,13 +22,12 @@ from calibre.ebooks.metadata.opf import OPFCreator
from calibre.ebooks.metadata import authors_to_string, string_to_authors from calibre.ebooks.metadata import authors_to_string, string_to_authors
class Config(QDialog, Ui_Dialog): class Config(ResizableDialog, Ui_Dialog):
OUTPUT = 'EPUB' OUTPUT = 'EPUB'
def __init__(self, parent, db, row=None, config=epubconfig): def __init__(self, parent, db, row=None, config=epubconfig):
QDialog.__init__(self, parent) ResizableDialog.__init__(self, parent)
self.setupUi(self)
self.hide_controls() self.hide_controls()
self.connect(self.category_list, SIGNAL('itemEntered(QListWidgetItem *)'), self.connect(self.category_list, SIGNAL('itemEntered(QListWidgetItem *)'),
self.show_category_help) self.show_category_help)
@ -62,17 +61,18 @@ class Config(QDialog, Ui_Dialog):
self.opt_toc_title.setVisible(False) self.opt_toc_title.setVisible(False)
self.toc_title_label.setVisible(False) self.toc_title_label.setVisible(False)
self.opt_rescale_images.setVisible(False) self.opt_rescale_images.setVisible(False)
self.opt_ignore_tables.setVisible(False)
def initialize(self): def initialize(self):
self.__w = [] self.__w = []
self.__w.append(QIcon(':/images/dialog_information.svg')) self.__w.append(QIcon(':/images/dialog_information.svg'))
self.item1 = QListWidgetItem(self.__w[-1], _('Metadata'), self.category_list) self.item1 = QListWidgetItem(self.__w[-1], _('Metadata'), self.category_list)
self.__w.append(QIcon(':/images/lookfeel.svg')) self.__w.append(QIcon(':/images/lookfeel.svg'))
self.item2 = QListWidgetItem(self.__w[-1], _('Look & Feel'), self.category_list) self.item2 = QListWidgetItem(self.__w[-1], _('Look & Feel').replace(' ','\n'), self.category_list)
self.__w.append(QIcon(':/images/page.svg')) self.__w.append(QIcon(':/images/page.svg'))
self.item3 = QListWidgetItem(self.__w[-1], _('Page Setup'), self.category_list) self.item3 = QListWidgetItem(self.__w[-1], _('Page Setup').replace(' ','\n'), self.category_list)
self.__w.append(QIcon(':/images/chapters.svg')) self.__w.append(QIcon(':/images/chapters.svg'))
self.item4 = QListWidgetItem(self.__w[-1], _('Chapter Detection'), self.category_list) self.item4 = QListWidgetItem(self.__w[-1], _('Chapter Detection').replace(' ','\n'), self.category_list)
self.setup_tooltips() self.setup_tooltips()
self.initialize_options() self.initialize_options()
@ -98,7 +98,7 @@ class Config(QDialog, Ui_Dialog):
_('Page Setup') : _('Specify the page layout settings like margins.'), _('Page Setup') : _('Specify the page layout settings like margins.'),
_('Chapter Detection') : _('Fine tune the detection of chapter and section headings.'), _('Chapter Detection') : _('Fine tune the detection of chapter and section headings.'),
} }
self.set_help(help[text]) self.set_help(help[text.replace('\n', ' ')])
def select_cover(self): def select_cover(self):
files = choose_images(self, 'change cover dialog', files = choose_images(self, 'change cover dialog',

View File

@ -5,8 +5,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>868</width> <width>965</width>
<height>670</height> <height>698</height>
</rect> </rect>
</property> </property>
<property name="windowTitle" > <property name="windowTitle" >
@ -21,14 +21,12 @@
</property> </property>
<layout class="QGridLayout" name="gridLayout_2" > <layout class="QGridLayout" name="gridLayout_2" >
<item row="0" column="0" > <item row="0" column="0" >
<layout class="QHBoxLayout" name="horizontalLayout" >
<item>
<widget class="QListWidget" name="category_list" > <widget class="QListWidget" name="category_list" >
<property name="maximumSize" > <property name="sizePolicy" >
<size> <sizepolicy vsizetype="Expanding" hsizetype="Minimum" >
<width>172</width> <horstretch>0</horstretch>
<height>16777215</height> <verstretch>0</verstretch>
</size> </sizepolicy>
</property> </property>
<property name="font" > <property name="font" >
<font> <font>
@ -42,9 +40,6 @@
<property name="verticalScrollBarPolicy" > <property name="verticalScrollBarPolicy" >
<enum>Qt::ScrollBarAlwaysOff</enum> <enum>Qt::ScrollBarAlwaysOff</enum>
</property> </property>
<property name="horizontalScrollBarPolicy" >
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="tabKeyNavigation" > <property name="tabKeyNavigation" >
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -54,30 +49,51 @@
<height>48</height> <height>48</height>
</size> </size>
</property> </property>
<property name="flow" >
<enum>QListView::TopToBottom</enum>
</property>
<property name="isWrapping" stdset="0" >
<bool>false</bool>
</property>
<property name="spacing" > <property name="spacing" >
<number>10</number> <number>20</number>
</property> </property>
<property name="viewMode" > <property name="wordWrap" >
<enum>QListView::IconMode</enum>
</property>
<property name="uniformItemSizes" >
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="currentRow" >
<number>-1</number>
</property>
</widget> </widget>
</item> </item>
<item row="0" column="1" >
<widget class="QScrollArea" name="scrollArea" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<horstretch>10</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape" >
<enum>QFrame::NoFrame</enum>
</property>
<property name="widgetResizable" >
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>697</width>
<height>554</height>
</rect>
</property>
<property name="minimumSize" >
<size>
<width>680</width>
<height>520</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3" >
<property name="margin" >
<number>0</number>
</property>
<item> <item>
<widget class="QStackedWidget" name="stack" > <widget class="QStackedWidget" name="stack" >
<property name="currentIndex" > <property name="currentIndex" >
<number>0</number> <number>1</number>
</property> </property>
<widget class="QWidget" name="metadata_page" > <widget class="QWidget" name="metadata_page" >
<layout class="QGridLayout" name="gridLayout_4" > <layout class="QGridLayout" name="gridLayout_4" >
@ -89,6 +105,36 @@
<string>Book Cover</string> <string>Book Cover</string>
</property> </property>
<layout class="QGridLayout" name="_2" > <layout class="QGridLayout" name="_2" >
<item row="0" column="0" >
<layout class="QHBoxLayout" name="_3" >
<item>
<widget class="ImageView" name="cover" >
<property name="text" >
<string/>
</property>
<property name="pixmap" >
<pixmap resource="../images.qrc" >:/images/book.svg</pixmap>
</property>
<property name="scaledContents" >
<bool>true</bool>
</property>
<property name="alignment" >
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0" >
<widget class="QCheckBox" name="opt_prefer_metadata_cover" >
<property name="text" >
<string>Use cover from &amp;source file</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" > <item row="1" column="0" >
<layout class="QVBoxLayout" name="_4" > <layout class="QVBoxLayout" name="_4" >
<property name="spacing" > <property name="spacing" >
@ -140,36 +186,6 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="2" column="0" >
<widget class="QCheckBox" name="opt_prefer_metadata_cover" >
<property name="text" >
<string>Use cover from &amp;source file</string>
</property>
<property name="checked" >
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0" >
<layout class="QHBoxLayout" name="_3" >
<item>
<widget class="ImageView" name="cover" >
<property name="text" >
<string/>
</property>
<property name="pixmap" >
<pixmap resource="../images.qrc" >:/images/book.svg</pixmap>
</property>
<property name="scaledContents" >
<bool>true</bool>
</property>
<property name="alignment" >
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
<zorder>opt_prefer_metadata_cover</zorder> <zorder>opt_prefer_metadata_cover</zorder>
<zorder></zorder> <zorder></zorder>
@ -463,6 +479,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0" >
<widget class="QCheckBox" name="opt_ignore_tables" >
<property name="text" >
<string>&amp;Ignore tables</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>
@ -775,18 +798,10 @@ p, li { white-space: pre-wrap; }
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </widget>
<item row="2" column="0" >
<widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons" >
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget> </widget>
</item> </item>
<item row="1" column="0" > <item row="1" column="0" colspan="2" >
<widget class="QTextBrowser" name="help_view" > <widget class="QTextBrowser" name="help_view" >
<property name="maximumSize" > <property name="maximumSize" >
<size> <size>
@ -799,6 +814,16 @@ p, li { white-space: pre-wrap; }
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" colspan="2" >
<widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons" >
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets> <customwidgets>
@ -820,8 +845,8 @@ p, li { white-space: pre-wrap; }
<slot>accept()</slot> <slot>accept()</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel" >
<x>222</x> <x>226</x>
<y>652</y> <y>684</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel" >
<x>157</x> <x>157</x>
@ -852,12 +877,12 @@ p, li { white-space: pre-wrap; }
<slot>setCurrentIndex(int)</slot> <slot>setCurrentIndex(int)</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel" >
<x>88</x> <x>81</x>
<y>42</y> <y>118</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel" >
<x>659</x> <x>866</x>
<y>12</y> <y>11</y>
</hint> </hint>
</hints> </hints>
</connection> </connection>

View File

@ -16,8 +16,8 @@
<iconset resource="../images.qrc" > <iconset resource="../images.qrc" >
<normaloff>:/images/convert.svg</normaloff>:/images/convert.svg</iconset> <normaloff>:/images/convert.svg</normaloff>:/images/convert.svg</iconset>
</property> </property>
<layout class="QHBoxLayout" > <layout class="QGridLayout" name="gridLayout_2" >
<item> <item rowspan="3" row="0" column="0" >
<widget class="QGroupBox" name="category" > <widget class="QGroupBox" name="category" >
<property name="sizePolicy" > <property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Fixed" > <sizepolicy vsizetype="Preferred" hsizetype="Fixed" >
@ -96,37 +96,42 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item row="0" column="1" >
<layout class="QVBoxLayout" > <widget class="QScrollArea" name="scrollArea" >
<item> <property name="frameShape" >
<layout class="QVBoxLayout" > <enum>QFrame::NoFrame</enum>
<item>
<widget class="QGroupBox" name="groupBox_3" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Expanding" >
<horstretch>10</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
<property name="title" > <property name="widgetResizable" >
<string>Options</string> <bool>true</bool>
</property> </property>
<layout class="QGridLayout" > <widget class="QWidget" name="scrollAreaWidgetContents" >
<item row="0" column="0" > <property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>664</width>
<height>515</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout" >
<property name="margin" >
<number>0</number>
</property>
<item>
<widget class="QStackedWidget" name="stack" > <widget class="QStackedWidget" name="stack" >
<property name="currentIndex" > <property name="currentIndex" >
<number>1</number> <number>0</number>
</property> </property>
<widget class="QWidget" name="metadata_page" > <widget class="QWidget" name="metadata_page" >
<layout class="QHBoxLayout" > <layout class="QHBoxLayout" name="_2" >
<item> <item>
<widget class="QGroupBox" name="groupBox_4" > <widget class="QGroupBox" name="groupBox_4" >
<property name="title" > <property name="title" >
<string>Book Cover</string> <string>Book Cover</string>
</property> </property>
<layout class="QGridLayout" > <layout class="QGridLayout" name="_3" >
<item row="0" column="0" > <item row="0" column="0" >
<layout class="QHBoxLayout" > <layout class="QHBoxLayout" name="_4" >
<item> <item>
<widget class="ImageView" name="cover" > <widget class="ImageView" name="cover" >
<property name="text" > <property name="text" >
@ -146,7 +151,7 @@
</layout> </layout>
</item> </item>
<item row="1" column="0" > <item row="1" column="0" >
<layout class="QVBoxLayout" > <layout class="QVBoxLayout" name="_5" >
<property name="spacing" > <property name="spacing" >
<number>6</number> <number>6</number>
</property> </property>
@ -164,7 +169,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" > <layout class="QHBoxLayout" name="_6" >
<property name="spacing" > <property name="spacing" >
<number>6</number> <number>6</number>
</property> </property>
@ -210,9 +215,9 @@
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QVBoxLayout" > <layout class="QVBoxLayout" name="_7" >
<item> <item>
<layout class="QGridLayout" > <layout class="QGridLayout" name="_8" >
<item row="0" column="0" > <item row="0" column="0" >
<widget class="QLabel" name="label" > <widget class="QLabel" name="label" >
<property name="text" > <property name="text" >
@ -395,25 +400,12 @@
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="maximumSize" >
<size>
<width>16777215</width>
<height>110</height>
</size>
</property>
<property name="title" > <property name="title" >
<string>Comments</string> <string>Comments</string>
</property> </property>
<layout class="QGridLayout" > <layout class="QGridLayout" name="_9" >
<item row="0" column="0" > <item row="0" column="0" >
<widget class="QTextEdit" name="gui_comment" > <widget class="QTextEdit" name="gui_comment" />
<property name="maximumSize" >
<size>
<width>16777215</width>
<height>60</height>
</size>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -423,7 +415,7 @@
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="lookandfeel_page" > <widget class="QWidget" name="lookandfeel_page" >
<layout class="QGridLayout" > <layout class="QGridLayout" name="_10" >
<item row="0" column="0" > <item row="0" column="0" >
<widget class="QLabel" name="label_8" > <widget class="QLabel" name="label_8" >
<property name="text" > <property name="text" >
@ -464,7 +456,7 @@
<property name="title" > <property name="title" >
<string>Embedded Fonts</string> <string>Embedded Fonts</string>
</property> </property>
<layout class="QGridLayout" > <layout class="QGridLayout" name="_11" >
<item row="0" column="0" colspan="2" > <item row="0" column="0" colspan="2" >
<widget class="QLabel" name="label_22" > <widget class="QLabel" name="label_22" >
<property name="text" > <property name="text" >
@ -589,7 +581,7 @@
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="4" > <item row="3" column="0" colspan="4" >
<layout class="QVBoxLayout" > <layout class="QVBoxLayout" name="_12" >
<item> <item>
<widget class="QCheckBox" name="gui_autorotation" > <widget class="QCheckBox" name="gui_autorotation" >
<property name="text" > <property name="text" >
@ -644,7 +636,7 @@
<property name="title" > <property name="title" >
<string>Header</string> <string>Header</string>
</property> </property>
<layout class="QGridLayout" > <layout class="QGridLayout" name="_13" >
<item row="0" column="0" > <item row="0" column="0" >
<widget class="QCheckBox" name="gui_header" > <widget class="QCheckBox" name="gui_header" >
<property name="text" > <property name="text" >
@ -698,7 +690,7 @@
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="pagesetup_page" > <widget class="QWidget" name="pagesetup_page" >
<layout class="QGridLayout" > <layout class="QGridLayout" name="_14" >
<item row="0" column="0" > <item row="0" column="0" >
<widget class="QLabel" name="label_11" > <widget class="QLabel" name="label_11" >
<property name="text" > <property name="text" >
@ -847,7 +839,7 @@
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="chapterdetection_page" > <widget class="QWidget" name="chapterdetection_page" >
<layout class="QVBoxLayout" > <layout class="QVBoxLayout" name="_15" >
<item> <item>
<widget class="QGroupBox" name="groupBox_6" > <widget class="QGroupBox" name="groupBox_6" >
<property name="title" > <property name="title" >
@ -896,7 +888,7 @@
<property name="title" > <property name="title" >
<string>Tag based detection</string> <string>Tag based detection</string>
</property> </property>
<layout class="QGridLayout" > <layout class="QGridLayout" name="_16" >
<item row="0" column="0" > <item row="0" column="0" >
<widget class="QLabel" name="label_18" > <widget class="QLabel" name="label_18" >
<property name="text" > <property name="text" >
@ -956,29 +948,11 @@
</widget> </widget>
</widget> </widget>
</item> </item>
<item row="1" column="0" >
<spacer>
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</widget> </widget>
</widget>
</item> </item>
<item> <item row="1" column="1" >
<widget class="QGroupBox" name="groupBox" >
<property name="title" >
<string>Help on item</string>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QTextBrowser" name="help_view" > <widget class="QTextBrowser" name="help_view" >
<property name="sizePolicy" > <property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" > <sizepolicy vsizetype="Fixed" hsizetype="Expanding" >
@ -995,24 +969,19 @@
<property name="maximumSize" > <property name="maximumSize" >
<size> <size>
<width>16777215</width> <width>16777215</width>
<height>150</height> <height>120</height>
</size> </size>
</property> </property>
<property name="html" > <property name="html" >
<string>&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <string>&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
&lt;html>&lt;head>&lt;meta name="qrichtext" content="1" />&lt;style type="text/css"> &lt;html>&lt;head>&lt;meta name="qrichtext" content="1" />&lt;style type="text/css">
p, li { white-space: pre-wrap; } p, li { white-space: pre-wrap; }
&lt;/style>&lt;/head>&lt;body style=" font-family:'Sans Serif'; font-size:10pt; font-weight:400; font-style:normal;"> &lt;/style>&lt;/head>&lt;body style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal;">
&lt;p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'DejaVu Sans';">&lt;/p>&lt;/body>&lt;/html></string> &lt;p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">&lt;/p>&lt;/body>&lt;/html></string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> <item row="2" column="1" >
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox" > <widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" > <property name="orientation" >
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
@ -1026,8 +995,6 @@ p, li { white-space: pre-wrap; }
</widget> </widget>
</item> </item>
</layout> </layout>
</item>
</layout>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
@ -1079,108 +1046,12 @@ p, li { white-space: pre-wrap; }
<slot>setCurrentIndex(int)</slot> <slot>setCurrentIndex(int)</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel" >
<x>184</x> <x>96</x>
<y>279</y> <y>120</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel" >
<x>368</x> <x>539</x>
<y>185</y> <y>220</y>
</hint>
</hints>
</connection>
<connection>
<sender>gui_disable_chapter_detection</sender>
<signal>toggled(bool)</signal>
<receiver>gui_chapter_regex</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>308</x>
<y>74</y>
</hint>
<hint type="destinationlabel" >
<x>308</x>
<y>74</y>
</hint>
</hints>
</connection>
<connection>
<sender>gui_disable_chapter_detection</sender>
<signal>toggled(bool)</signal>
<receiver>gui_add_chapters_to_toc</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>308</x>
<y>74</y>
</hint>
<hint type="destinationlabel" >
<x>308</x>
<y>74</y>
</hint>
</hints>
</connection>
<connection>
<sender>gui_render_tables_as_images</sender>
<signal>toggled(bool)</signal>
<receiver>gui_text_size_multiplier_for_rendered_tables</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>308</x>
<y>74</y>
</hint>
<hint type="destinationlabel" >
<x>308</x>
<y>74</y>
</hint>
</hints>
</connection>
<connection>
<sender>gui_header</sender>
<signal>toggled(bool)</signal>
<receiver>gui_headerformat</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>345</x>
<y>363</y>
</hint>
<hint type="destinationlabel" >
<x>837</x>
<y>435</y>
</hint>
</hints>
</connection>
<connection>
<sender>gui_disable_chapter_detection</sender>
<signal>toggled(bool)</signal>
<receiver>gui_chapter_attr</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>308</x>
<y>74</y>
</hint>
<hint type="destinationlabel" >
<x>308</x>
<y>74</y>
</hint>
</hints>
</connection>
<connection>
<sender>gui_header</sender>
<signal>toggled(bool)</signal>
<receiver>gui_header_separation</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>261</x>
<y>346</y>
</hint>
<hint type="destinationlabel" >
<x>379</x>
<y>378</y>
</hint> </hint>
</hints> </hints>
</connection> </connection>

View File

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

View File

@ -5,8 +5,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>923</width> <width>887</width>
<height>715</height> <height>750</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy" > <property name="sizePolicy" >
@ -28,6 +28,36 @@
<property name="modal" > <property name="modal" >
<bool>true</bool> <bool>true</bool>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_6" >
<item>
<widget class="QScrollArea" name="scrollArea" >
<property name="frameShape" >
<enum>QFrame::NoFrame</enum>
</property>
<property name="widgetResizable" >
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>879</width>
<height>710</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5" >
<property name="margin" >
<number>0</number>
</property>
<item>
<widget class="QWidget" native="1" name="central_widget" >
<property name="minimumSize" >
<size>
<width>800</width>
<height>665</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3" > <layout class="QVBoxLayout" name="verticalLayout_3" >
<item> <item>
<widget class="QSplitter" name="splitter" > <widget class="QSplitter" name="splitter" >
@ -192,7 +222,7 @@
</widget> </widget>
</item> </item>
<item row="5" column="1" colspan="2" > <item row="5" column="1" colspan="2" >
<layout class="QHBoxLayout" > <layout class="QHBoxLayout" name="_2" >
<item> <item>
<widget class="QLineEdit" name="tags" > <widget class="QLineEdit" name="tags" >
<property name="toolTip" > <property name="toolTip" >
@ -233,7 +263,7 @@
</widget> </widget>
</item> </item>
<item row="6" column="1" colspan="2" > <item row="6" column="1" colspan="2" >
<layout class="QHBoxLayout" > <layout class="QHBoxLayout" name="_3" >
<property name="spacing" > <property name="spacing" >
<number>5</number> <number>5</number>
</property> </property>
@ -350,7 +380,7 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="layoutWidget" > <widget class="QWidget" name="layoutWidget_2" >
<layout class="QVBoxLayout" name="verticalLayout_2" > <layout class="QVBoxLayout" name="verticalLayout_2" >
<item> <item>
<widget class="QGroupBox" name="af_group_box" > <widget class="QGroupBox" name="af_group_box" >
@ -486,7 +516,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QVBoxLayout" > <layout class="QVBoxLayout" name="_4" >
<property name="spacing" > <property name="spacing" >
<number>6</number> <number>6</number>
</property> </property>
@ -507,7 +537,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" > <layout class="QHBoxLayout" name="_5" >
<property name="spacing" > <property name="spacing" >
<number>6</number> <number>6</number>
</property> </property>
@ -554,7 +584,7 @@
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" > <layout class="QHBoxLayout" name="_6" >
<item> <item>
<widget class="QPushButton" name="fetch_cover_button" > <widget class="QPushButton" name="fetch_cover_button" >
<property name="text" > <property name="text" >
@ -581,6 +611,13 @@
</widget> </widget>
</widget> </widget>
</item> </item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item> <item>
<widget class="QDialogButtonBox" name="button_box" > <widget class="QDialogButtonBox" name="button_box" >
<property name="orientation" > <property name="orientation" >
@ -609,8 +646,8 @@
<tabstop>rating</tabstop> <tabstop>rating</tabstop>
<tabstop>publisher</tabstop> <tabstop>publisher</tabstop>
<tabstop>tags</tabstop> <tabstop>tags</tabstop>
<tabstop>tag_editor_button</tabstop>
<tabstop>series</tabstop> <tabstop>series</tabstop>
<tabstop>tag_editor_button</tabstop>
<tabstop>remove_series_button</tabstop> <tabstop>remove_series_button</tabstop>
<tabstop>series_index</tabstop> <tabstop>series_index</tabstop>
<tabstop>isbn</tabstop> <tabstop>isbn</tabstop>
@ -618,13 +655,14 @@
<tabstop>fetch_metadata_button</tabstop> <tabstop>fetch_metadata_button</tabstop>
<tabstop>fetch_cover_button</tabstop> <tabstop>fetch_cover_button</tabstop>
<tabstop>password_button</tabstop> <tabstop>password_button</tabstop>
<tabstop>formats</tabstop>
<tabstop>add_format_button</tabstop>
<tabstop>remove_format_button</tabstop>
<tabstop>button_set_cover</tabstop>
<tabstop>cover_path</tabstop>
<tabstop>cover_button</tabstop> <tabstop>cover_button</tabstop>
<tabstop>reset_cover</tabstop> <tabstop>reset_cover</tabstop>
<tabstop>cover_path</tabstop> <tabstop>scrollArea</tabstop>
<tabstop>add_format_button</tabstop>
<tabstop>button_set_cover</tabstop>
<tabstop>remove_format_button</tabstop>
<tabstop>formats</tabstop>
<tabstop>button_box</tabstop> <tabstop>button_box</tabstop>
</tabstops> </tabstops>
<resources> <resources>

View File

@ -7,11 +7,11 @@ __docformat__ = 'restructuredtext en'
Scheduler for automated recipe downloads Scheduler for automated recipe downloads
''' '''
import sys, copy import sys, copy, time
from datetime import datetime, timedelta from datetime import datetime, timedelta
from PyQt4.Qt import QDialog, QApplication, QLineEdit, QPalette, SIGNAL, QBrush, \ from PyQt4.Qt import QDialog, QApplication, QLineEdit, QPalette, SIGNAL, QBrush, \
QColor, QAbstractListModel, Qt, QVariant, QFont, QIcon, \ QColor, QAbstractListModel, Qt, QVariant, QFont, QIcon, \
QFile, QObject, QTimer, QMutex, QMenu, QAction QFile, QObject, QTimer, QMutex, QMenu, QAction, QTime
from calibre import english_sort from calibre import english_sort
from calibre.gui2.dialogs.scheduler_ui import Ui_Dialog from calibre.gui2.dialogs.scheduler_ui import Ui_Dialog
@ -66,7 +66,10 @@ class Recipe(object):
return self.id == getattr(other, 'id', None) return self.id == getattr(other, 'id', None)
def __repr__(self): def __repr__(self):
return u'%s|%s|%s|%s'%(self.id, self.title, self.last_downloaded.ctime(), self.schedule) schedule = self.schedule
if schedule and schedule > 1e5:
schedule = decode_schedule(schedule)
return u'%s|%s|%s|%s'%(self.id, self.title, self.last_downloaded.ctime(), schedule)
builtin_recipes = [Recipe(m, r, True) for r, m in zip(recipes, recipe_modules)] builtin_recipes = [Recipe(m, r, True) for r, m in zip(recipes, recipe_modules)]
@ -170,6 +173,11 @@ class RecipeModel(QAbstractListModel, SearchQueryParser):
return NONE return NONE
def update_recipe_schedule(self, recipe):
for srecipe in self.recipes:
if srecipe == recipe:
srecipe.schedule = recipe.schedule
class Search(QLineEdit): class Search(QLineEdit):
@ -210,7 +218,17 @@ class Search(QLineEdit):
text = unicode(self.text()) text = unicode(self.text())
self.emit(SIGNAL('search(PyQt_PyObject)'), text) self.emit(SIGNAL('search(PyQt_PyObject)'), text)
def encode_schedule(day, hour, minute):
day = 1e7 * (day+1)
hour = 1e4 * (hour+1)
return day + hour + minute + 1
def decode_schedule(num):
raw = '%d'%int(num)
day = int(raw[0])
hour = int(raw[2:4])
minute = int(raw[-2:])
return day-1, hour-1, minute-1
class SchedulerDialog(QDialog, Ui_Dialog): class SchedulerDialog(QDialog, Ui_Dialog):
@ -228,17 +246,22 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.connect(self.username, SIGNAL('textEdited(QString)'), self.set_account_info) self.connect(self.username, SIGNAL('textEdited(QString)'), self.set_account_info)
self.connect(self.password, SIGNAL('textEdited(QString)'), self.set_account_info) self.connect(self.password, SIGNAL('textEdited(QString)'), self.set_account_info)
self.connect(self.schedule, SIGNAL('stateChanged(int)'), self.do_schedule) self.connect(self.schedule, SIGNAL('stateChanged(int)'), self.do_schedule)
self.connect(self.schedule, SIGNAL('stateChanged(int)'),
lambda state: self.interval.setEnabled(state == Qt.Checked))
self.connect(self.show_password, SIGNAL('stateChanged(int)'), self.connect(self.show_password, SIGNAL('stateChanged(int)'),
lambda state: self.password.setEchoMode(self.password.Normal if state == Qt.Checked else self.password.Password)) lambda state: self.password.setEchoMode(self.password.Normal if state == Qt.Checked else self.password.Password))
self.connect(self.interval, SIGNAL('valueChanged(double)'), self.do_schedule) self.connect(self.interval, SIGNAL('valueChanged(double)'), self.do_schedule)
self.connect(self.day, SIGNAL('currentIndexChanged(int)'), self.do_schedule)
self.connect(self.time, SIGNAL('timeChanged(QTime)'), self.do_schedule)
for button in (self.daily_button, self.interval_button):
self.connect(button, SIGNAL('toggled(bool)'), self.do_schedule)
self.connect(self.search, SIGNAL('search(PyQt_PyObject)'), self._model.search) self.connect(self.search, SIGNAL('search(PyQt_PyObject)'), self._model.search)
self.connect(self._model, SIGNAL('modelReset()'), lambda : self.detail_box.setVisible(False)) self.connect(self._model, SIGNAL('modelReset()'), lambda : self.detail_box.setVisible(False))
self.connect(self.download, SIGNAL('clicked()'), self.download_now) self.connect(self.download, SIGNAL('clicked()'), self.download_now)
self.search.setFocus(Qt.OtherFocusReason) self.search.setFocus(Qt.OtherFocusReason)
self.old_news.setValue(gconf['oldest_news']) self.old_news.setValue(gconf['oldest_news'])
self.rnumber.setText(_('%d recipes')%self._model.rowCount(None)) self.rnumber.setText(_('%d recipes')%self._model.rowCount(None))
for day in (_('day'), _('Monday'), _('Tuesday'), _('Wednesday'),
_('Thursday'), _('Friday'), _('Saturday'), _('Sunday')):
self.day.addItem(day)
def download_now(self): def download_now(self):
recipe = self._model.data(self.recipes.currentIndex(), Qt.UserRole) recipe = self._model.data(self.recipes.currentIndex(), Qt.UserRole)
@ -252,6 +275,8 @@ class SchedulerDialog(QDialog, Ui_Dialog):
config[key] = (username, password) if username and password else None config[key] = (username, password) if username and password else None
def do_schedule(self, *args): def do_schedule(self, *args):
if not getattr(self, 'allow_scheduling', False):
return
recipe = self.recipes.currentIndex() recipe = self.recipes.currentIndex()
if not recipe.isValid(): if not recipe.isValid():
return return
@ -263,17 +288,26 @@ class SchedulerDialog(QDialog, Ui_Dialog):
else: else:
recipe.last_downloaded = datetime.fromordinal(1) recipe.last_downloaded = datetime.fromordinal(1)
recipes.append(recipe) recipes.append(recipe)
recipe.schedule = self.interval.value()
if recipe.schedule < 0.1:
recipe.schedule = 1/24.
if recipe.needs_subscription and not config['recipe_account_info_%s'%recipe.id]: if recipe.needs_subscription and not config['recipe_account_info_%s'%recipe.id]:
error_dialog(self, _('Must set account information'), _('This recipe requires a username and password')).exec_() error_dialog(self, _('Must set account information'), _('This recipe requires a username and password')).exec_()
self.schedule.setCheckState(Qt.Unchecked) self.schedule.setCheckState(Qt.Unchecked)
return return
if self.interval_button.isChecked():
recipe.schedule = self.interval.value()
if recipe.schedule < 0.1:
recipe.schedule = 1/24.
else:
day_of_week = self.day.currentIndex() - 1
if day_of_week < 0:
day_of_week = 7
t = self.time.time()
hour, minute = t.hour(), t.minute()
recipe.schedule = encode_schedule(day_of_week, hour, minute)
else: else:
if recipe in recipes: if recipe in recipes:
recipes.remove(recipe) recipes.remove(recipe)
save_recipes(recipes) save_recipes(recipes)
self._model.update_recipe_schedule(recipe)
self.emit(SIGNAL('new_schedule(PyQt_PyObject)'), recipes) self.emit(SIGNAL('new_schedule(PyQt_PyObject)'), recipes)
def show_recipe(self, index): def show_recipe(self, index):
@ -282,8 +316,26 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.title.setText(recipe.title) self.title.setText(recipe.title)
self.author.setText(_('Created by: ') + recipe.author) self.author.setText(_('Created by: ') + recipe.author)
self.description.setText(recipe.description if recipe.description else '') self.description.setText(recipe.description if recipe.description else '')
self.allow_scheduling = False
schedule = -1 if recipe.schedule is None else recipe.schedule
if schedule < 1e5 and schedule >= 0:
self.interval.setValue(schedule)
self.interval_button.setChecked(True)
self.day.setEnabled(False), self.time.setEnabled(False)
else:
if schedule > 0:
day, hour, minute = decode_schedule(schedule)
else:
day, hour, minute = 7, 12, 0
if day == 7:
day = -1
self.day.setCurrentIndex(day+1)
self.time.setTime(QTime(hour, minute))
self.daily_button.setChecked(True)
self.interval_button.setChecked(False)
self.interval.setEnabled(False)
self.schedule.setChecked(recipe.schedule is not None) self.schedule.setChecked(recipe.schedule is not None)
self.interval.setValue(recipe.schedule if recipe.schedule is not None else 1) self.allow_scheduling = True
self.detail_box.setVisible(True) self.detail_box.setVisible(True)
self.account.setVisible(recipe.needs_subscription) self.account.setVisible(recipe.needs_subscription)
self.interval.setEnabled(self.schedule.checkState() == Qt.Checked) self.interval.setEnabled(self.schedule.checkState() == Qt.Checked)
@ -365,13 +417,22 @@ class Scheduler(QObject):
self.dirtied = False self.dirtied = False
needs_downloading = set([]) needs_downloading = set([])
self.debug('Checking...') self.debug('Checking...')
now = datetime.utcnow() nowt = datetime.utcnow()
for recipe in self.recipes: for recipe in self.recipes:
if recipe.schedule is None: if recipe.schedule is None:
continue continue
delta = now - recipe.last_downloaded delta = nowt - recipe.last_downloaded
if recipe.schedule < 1e5:
if delta > timedelta(days=recipe.schedule): if delta > timedelta(days=recipe.schedule):
needs_downloading.add(recipe) needs_downloading.add(recipe)
else:
day, hour, minute = decode_schedule(recipe.schedule)
now = time.localtime()
day_matches = day > 6 or day == now.tm_wday
tnow = now.tm_hour*60 + now.tm_min
matches = day_matches and (hour*60+minute) < tnow
if matches and delta >= timedelta(days=1):
needs_downloading.add(recipe)
self.debug('Needs downloading:', needs_downloading) self.debug('Needs downloading:', needs_downloading)

View File

@ -6,7 +6,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>726</width> <width>726</width>
<height>551</height> <height>575</height>
</rect> </rect>
</property> </property>
<property name="windowTitle" > <property name="windowTitle" >
@ -42,25 +42,12 @@
</item> </item>
<item row="0" column="1" > <item row="0" column="1" >
<layout class="QVBoxLayout" name="verticalLayout_3" > <layout class="QVBoxLayout" name="verticalLayout_3" >
<item>
<spacer name="horizontalSpacer_3" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>40</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item> <item>
<widget class="QGroupBox" name="detail_box" > <widget class="QGroupBox" name="detail_box" >
<property name="title" > <property name="title" >
<string>Schedule for download</string> <string>Schedule for download</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2" > <layout class="QVBoxLayout" name="verticalLayout_4" >
<item> <item>
<widget class="QLabel" name="title" > <widget class="QLabel" name="title" >
<property name="font" > <property name="font" >
@ -110,12 +97,38 @@
<item> <item>
<widget class="QCheckBox" name="schedule" > <widget class="QCheckBox" name="schedule" >
<property name="text" > <property name="text" >
<string>&amp;Schedule for download every:</string> <string>&amp;Schedule for download:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout" > <widget class="QWidget" native="1" name="widget" >
<property name="enabled" >
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" >
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" >
<item>
<widget class="QRadioButton" name="daily_button" >
<property name="text" >
<string>Every </string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="day" />
</item>
<item>
<widget class="QLabel" name="label_4" >
<property name="text" >
<string>at</string>
</property>
</widget>
</item>
<item>
<widget class="QTimeEdit" name="time" />
</item>
<item> <item>
<spacer name="horizontalSpacer" > <spacer name="horizontalSpacer" >
<property name="orientation" > <property name="orientation" >
@ -129,6 +142,17 @@
</property> </property>
</spacer> </spacer>
</item> </item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" >
<item>
<widget class="QRadioButton" name="interval_button" >
<property name="text" >
<string>Every </string>
</property>
</widget>
</item>
<item> <item>
<widget class="QDoubleSpinBox" name="interval" > <widget class="QDoubleSpinBox" name="interval" >
<property name="sizePolicy" > <property name="sizePolicy" >
@ -160,20 +184,10 @@
</property> </property>
</widget> </widget>
</item> </item>
<item> </layout>
<spacer name="horizontalSpacer_2" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item> </item>
</layout> </layout>
</widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="last_downloaded" > <widget class="QLabel" name="last_downloaded" >
@ -315,8 +329,8 @@
<slot>accept()</slot> <slot>accept()</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel" >
<x>248</x> <x>613</x>
<y>254</y> <y>824</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel" >
<x>157</x> <x>157</x>
@ -331,8 +345,8 @@
<slot>reject()</slot> <slot>reject()</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel" >
<x>316</x> <x>681</x>
<y>260</y> <y>824</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel" >
<x>286</x> <x>286</x>
@ -340,5 +354,85 @@
</hint> </hint>
</hints> </hints>
</connection> </connection>
<connection>
<sender>schedule</sender>
<signal>toggled(bool)</signal>
<receiver>widget</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>454</x>
<y>147</y>
</hint>
<hint type="destinationlabel" >
<x>461</x>
<y>168</y>
</hint>
</hints>
</connection>
<connection>
<sender>schedule</sender>
<signal>toggled(bool)</signal>
<receiver>widget</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>458</x>
<y>137</y>
</hint>
<hint type="destinationlabel" >
<x>461</x>
<y>169</y>
</hint>
</hints>
</connection>
<connection>
<sender>daily_button</sender>
<signal>toggled(bool)</signal>
<receiver>day</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>421</x>
<y>186</y>
</hint>
<hint type="destinationlabel" >
<x>500</x>
<y>184</y>
</hint>
</hints>
</connection>
<connection>
<sender>daily_button</sender>
<signal>toggled(bool)</signal>
<receiver>time</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>442</x>
<y>193</y>
</hint>
<hint type="destinationlabel" >
<x>603</x>
<y>183</y>
</hint>
</hints>
</connection>
<connection>
<sender>interval_button</sender>
<signal>toggled(bool)</signal>
<receiver>interval</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>428</x>
<y>213</y>
</hint>
<hint type="destinationlabel" >
<x>495</x>
<y>218</y>
</hint>
</hints>
</connection>
</connections> </connections>
</ui> </ui>

View File

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

View File

@ -5,8 +5,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>744</width> <width>738</width>
<height>633</height> <height>640</height>
</rect> </rect>
</property> </property>
<property name="windowTitle" > <property name="windowTitle" >
@ -16,22 +16,44 @@
<iconset resource="../images.qrc" > <iconset resource="../images.qrc" >
<normaloff>:/images/user_profile.svg</normaloff>:/images/user_profile.svg</iconset> <normaloff>:/images/user_profile.svg</normaloff>:/images/user_profile.svg</iconset>
</property> </property>
<layout class="QGridLayout" > <layout class="QVBoxLayout" name="verticalLayout_4" >
<item row="1" column="0" > <item>
<widget class="QDialogButtonBox" name="buttonBox" > <widget class="QScrollArea" name="scrollArea" >
<property name="orientation" > <property name="frameShape" >
<enum>Qt::Horizontal</enum> <enum>QFrame::NoFrame</enum>
</property> </property>
<property name="standardButtons" > <property name="lineWidth" >
<set>QDialogButtonBox::Close</set> <number>0</number>
</property> </property>
</widget> <property name="widgetResizable" >
</item> <bool>true</bool>
<item row="0" column="0" >
<widget class="QSplitter" name="splitter" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property> </property>
<widget class="QWidget" name="scrollAreaWidgetContents" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>730</width>
<height>600</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" > <widget class="QGroupBox" name="groupBox" >
<property name="sizePolicy" > <property name="sizePolicy" >
<sizepolicy vsizetype="Preferred" hsizetype="Minimum" > <sizepolicy vsizetype="Preferred" hsizetype="Minimum" >
@ -39,16 +61,10 @@
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="maximumSize" >
<size>
<width>215</width>
<height>16777215</height>
</size>
</property>
<property name="title" > <property name="title" >
<string>Available user recipes</string> <string>Available user recipes</string>
</property> </property>
<layout class="QVBoxLayout" > <layout class="QVBoxLayout" name="verticalLayout_2" >
<item> <item>
<widget class="BasicList" name="available_profiles" > <widget class="BasicList" name="available_profiles" >
<property name="sizePolicy" > <property name="sizePolicy" >
@ -116,8 +132,16 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="layoutWidget" > </item>
<layout class="QVBoxLayout" > <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> <item>
<widget class="QPushButton" name="toggle_mode_button" > <widget class="QPushButton" name="toggle_mode_button" >
<property name="text" > <property name="text" >
@ -131,7 +155,7 @@
<number>0</number> <number>0</number>
</property> </property>
<widget class="QWidget" name="page" > <widget class="QWidget" name="page" >
<layout class="QVBoxLayout" > <layout class="QVBoxLayout" name="verticalLayout_5" >
<item> <item>
<widget class="QLabel" name="label" > <widget class="QLabel" name="label" >
<property name="text" > <property name="text" >
@ -229,12 +253,24 @@ p, li { white-space: pre-wrap; }
</item> </item>
<item> <item>
<widget class="QGroupBox" name="groupBox_2" > <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" > <property name="title" >
<string>Feeds in recipe</string> <string>Feeds in recipe</string>
</property> </property>
<layout class="QHBoxLayout" > <layout class="QHBoxLayout" >
<item> <item>
<widget class="BasicList" name="added_feeds" > <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" > <property name="selectionMode" >
<enum>QAbstractItemView::MultiSelection</enum> <enum>QAbstractItemView::MultiSelection</enum>
</property> </property>
@ -357,6 +393,12 @@ p, li { white-space: pre-wrap; }
<layout class="QVBoxLayout" > <layout class="QVBoxLayout" >
<item> <item>
<widget class="QTextEdit" name="source_code" > <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" > <property name="font" >
<font> <font>
<family>DejaVu Sans Mono</family> <family>DejaVu Sans Mono</family>
@ -379,6 +421,22 @@ p, li { white-space: pre-wrap; }
</item> </item>
</layout> </layout>
</widget> </widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons" >
<set>QDialogButtonBox::Close</set>
</property>
</widget> </widget>
</item> </item>
</layout> </layout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

View File

@ -63,7 +63,8 @@ class Main(MainWindow, Ui_MainWindow):
p.end() p.end()
self.default_thumbnail = (pixmap.width(), pixmap.height(), pixmap_to_data(pixmap)) self.default_thumbnail = (pixmap.width(), pixmap.height(), pixmap_to_data(pixmap))
def __init__(self, single_instance, opts, parent=None): def __init__(self, single_instance, opts, actions, parent=None):
self.preferences_action, self.quit_action = actions
MainWindow.__init__(self, opts, parent) MainWindow.__init__(self, opts, parent)
# Initialize fontconfig in a separate thread as this can be a lengthy # Initialize fontconfig in a separate thread as this can be a lengthy
# process if run for the first time on this machine # process if run for the first time on this machine
@ -99,8 +100,8 @@ class Main(MainWindow, Ui_MainWindow):
self.system_tray_icon.show() self.system_tray_icon.show()
self.system_tray_menu = QMenu() self.system_tray_menu = QMenu()
self.restore_action = self.system_tray_menu.addAction(QIcon(':/images/page.svg'), _('&Restore')) self.restore_action = self.system_tray_menu.addAction(QIcon(':/images/page.svg'), _('&Restore'))
self.donate_action = self.system_tray_menu.addAction(QIcon(':/images/donate.svg'), _('&Donate')) self.donate_action = self.system_tray_menu.addAction(QIcon(':/images/donate.svg'), _('&Donate to support calibre'))
self.quit_action = QAction(QIcon(':/images/window-close.svg'), _('&Quit'), self) self.donate_button.setDefaultAction(self.donate_action)
self.addAction(self.quit_action) self.addAction(self.quit_action)
self.action_restart = QAction(_('&Restart'), self) self.action_restart = QAction(_('&Restart'), self)
self.addAction(self.action_restart) self.addAction(self.action_restart)
@ -134,16 +135,8 @@ class Main(MainWindow, Ui_MainWindow):
for f in self.output_formats: for f in self.output_formats:
self.output_format.addItem(f) self.output_format.addItem(f)
self.output_format.setCurrentIndex(self.output_formats.index(prefs['output_format'])) self.output_format.setCurrentIndex(self.output_formats.index(prefs['output_format']))
def change_output_format(x):
of = unicode(x).strip()
if of != prefs['output_format']:
if of 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)
self.connect(self.output_format, SIGNAL('currentIndexChanged(QString)'), self.connect(self.output_format, SIGNAL('currentIndexChanged(QString)'),
change_output_format) self.change_output_format, Qt.QueuedConnection)
####################### Vanity ######################## ####################### Vanity ########################
self.vanity_template = _('<p>For help visit <a href="http://%s.kovidgoyal.net/user_manual">%s.kovidgoyal.net</a><br>')%(__appname__, __appname__) self.vanity_template = _('<p>For help visit <a href="http://%s.kovidgoyal.net/user_manual">%s.kovidgoyal.net</a><br>')%(__appname__, __appname__)
@ -250,6 +243,7 @@ class Main(MainWindow, Ui_MainWindow):
self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu) self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
QObject.connect(self.config_button, SIGNAL('clicked(bool)'), self.do_config) QObject.connect(self.config_button, SIGNAL('clicked(bool)'), self.do_config)
self.connect(self.preferences_action, SIGNAL('triggered(bool)'), self.do_config)
QObject.connect(self.advanced_search_button, SIGNAL('clicked(bool)'), self.do_advanced_search) QObject.connect(self.advanced_search_button, SIGNAL('clicked(bool)'), self.do_advanced_search)
####################### Library view ######################## ####################### Library view ########################
@ -377,6 +371,15 @@ class Main(MainWindow, Ui_MainWindow):
self.connect(self.action_news, SIGNAL('triggered(bool)'), self.scheduler.show_dialog) self.connect(self.action_news, SIGNAL('triggered(bool)'), self.scheduler.show_dialog)
self.location_view.setCurrentIndex(self.location_view.model().index(0)) self.location_view.setCurrentIndex(self.location_view.model().index(0))
def change_output_format(self, x):
of = unicode(x).strip()
if of != prefs['output_format']:
if of not in ('LRF',):
warning_dialog(self, 'Warning',
'<p>%s support is still in beta. If you find bugs, please report them by opening a <a href="http://calibre.kovidgoyal.net">ticket</a>.'%of).exec_()
prefs.set('output_format', of)
def test_server(self, *args): def test_server(self, *args):
if self.content_server.exception is not None: if self.content_server.exception is not None:
error_dialog(self, _('Failed to start content server'), error_dialog(self, _('Failed to start content server'),
@ -911,13 +914,14 @@ class Main(MainWindow, Ui_MainWindow):
_files = self.library_view.model().get_preferred_formats(rows, _files = self.library_view.model().get_preferred_formats(rows,
self.device_manager.device_class.FORMATS, paths=True) self.device_manager.device_class.FORMATS, paths=True)
files = [getattr(f, 'name', None) for f in _files] files = [getattr(f, 'name', None) for f in _files]
bad, good, gf, names = [], [], [], [] bad, good, gf, names, remove_ids = [], [], [], [], []
for f in files: for f in files:
mi = metadata.next() mi = metadata.next()
id = ids.next() id = ids.next()
if f is None: if f is None:
bad.append(mi['title']) bad.append(mi['title'])
else: else:
remove_ids.append(id)
aus = mi['authors'].split(',') aus = mi['authors'].split(',')
aus2 = [] aus2 = []
for a in aus: for a in aus:
@ -944,7 +948,7 @@ class Main(MainWindow, Ui_MainWindow):
prefix = prefix.decode(preferred_encoding, 'replace') prefix = prefix.decode(preferred_encoding, 'replace')
prefix = ascii_filename(prefix) prefix = ascii_filename(prefix)
names.append('%s_%d%s'%(prefix, id, os.path.splitext(f)[1])) names.append('%s_%d%s'%(prefix, id, os.path.splitext(f)[1]))
remove = [self.library_view.model().id(r) for r in rows] if delete_from_library else [] remove = remove_ids if delete_from_library else []
self.upload_books(gf, names, good, on_card, memory=(_files, remove)) self.upload_books(gf, names, good, on_card, memory=(_files, remove))
self.status_bar.showMessage(_('Sending books to device.'), 5000) self.status_bar.showMessage(_('Sending books to device.'), 5000)
if bad: if bad:
@ -1419,7 +1423,7 @@ class Main(MainWindow, Ui_MainWindow):
self.restart_after_quit = restart self.restart_after_quit = restart
QApplication.instance().quit() QApplication.instance().quit()
def donate(self): def donate(self, *args):
BUTTON = ''' BUTTON = '''
<form action="https://www.paypal.com/cgi-bin/webscr" method="post"> <form action="https://www.paypal.com/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_s-xclick"> <input type="hidden" name="cmd" value="_s-xclick">
@ -1545,6 +1549,7 @@ def main(args=sys.argv):
prefs.set('library_path', opts.with_library) prefs.set('library_path', opts.with_library)
print 'Using library at', prefs['library_path'] print 'Using library at', prefs['library_path']
app = Application(args) app = Application(args)
actions = tuple(Main.create_application_menubar())
app.setWindowIcon(QIcon(':/library')) app.setWindowIcon(QIcon(':/library'))
QCoreApplication.setOrganizationName(ORG_NAME) QCoreApplication.setOrganizationName(ORG_NAME)
QCoreApplication.setApplicationName(APP_UID) QCoreApplication.setApplicationName(APP_UID)
@ -1559,7 +1564,7 @@ def main(args=sys.argv):
'<p>%s is already running. %s</p>'%(__appname__, extra)) '<p>%s is already running. %s</p>'%(__appname__, extra))
return 1 return 1
initialize_file_icon_provider() initialize_file_icon_provider()
main = Main(single_instance, opts) main = Main(single_instance, opts, actions)
sys.excepthook = main.unhandled_exception sys.excepthook = main.unhandled_exception
if len(args) > 1: if len(args) > 1:
main.add_filesystem_book(args[1]) main.add_filesystem_book(args[1])

View File

@ -82,6 +82,29 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QToolButton" name="donate_button" >
<property name="cursor" >
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="text" >
<string>...</string>
</property>
<property name="icon" >
<iconset resource="images.qrc" >
<normaloff>:/images/donate.svg</normaloff>:/images/donate.svg</iconset>
</property>
<property name="iconSize" >
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="autoRaise" >
<bool>true</bool>
</property>
</widget>
</item>
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout_3" > <layout class="QVBoxLayout" name="verticalLayout_3" >
<item> <item>
@ -119,7 +142,11 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QComboBox" name="output_format" /> <widget class="QComboBox" name="output_format" >
<property name="toolTip" >
<string>Set the output format that is used when converting ebooks and downloading news</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</item> </item>

View File

@ -3,7 +3,8 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import StringIO, traceback, sys import StringIO, traceback, sys
from PyQt4.Qt import QMainWindow, QString, Qt, QFont, QCoreApplication, SIGNAL from PyQt4.Qt import QMainWindow, QString, Qt, QFont, QCoreApplication, SIGNAL,\
QAction, QMenu, QMenuBar, QIcon
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
from calibre.utils.config import OptionParser from calibre.utils.config import OptionParser
@ -34,6 +35,27 @@ class DebugWindow(ConversionErrorDialog):
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
_menu_bar = None
@classmethod
def create_application_menubar(cls):
mb = QMenuBar(None)
menu = QMenu()
for action in cls.get_menubar_actions():
menu.addAction(action)
yield action
mb.addMenu(menu)
cls._menu_bar = mb
@classmethod
def get_menubar_actions(cls):
preferences_action = QAction(QIcon(':/images/config.svg'), _('&Preferences'), None)
quit_action = QAction(QIcon(':/images/window-close.svg'), _('&Quit'), None)
preferences_action.setMenuRole(QAction.PreferencesRole)
quit_action.setMenuRole(QAction.QuitRole)
return preferences_action, quit_action
def __init__(self, opts, parent=None): def __init__(self, opts, parent=None):
QMainWindow.__init__(self, parent) QMainWindow.__init__(self, parent)
app = QCoreApplication.instance() app = QCoreApplication.instance()

View File

@ -6,7 +6,7 @@ from PyQt4.QtGui import QStatusBar, QMovie, QLabel, QWidget, QHBoxLayout, QPixma
QVBoxLayout, QSizePolicy, QToolButton, QIcon, QScrollArea, QFrame QVBoxLayout, QSizePolicy, QToolButton, QIcon, QScrollArea, QFrame
from PyQt4.QtCore import Qt, QSize, SIGNAL, QCoreApplication from PyQt4.QtCore import Qt, QSize, SIGNAL, QCoreApplication
from calibre import fit_image, preferred_encoding, isosx from calibre import fit_image, preferred_encoding, isosx
from calibre.gui2 import qstring_to_unicode from calibre.gui2 import qstring_to_unicode, config
class BookInfoDisplay(QWidget): class BookInfoDisplay(QWidget):
class BookCoverDisplay(QLabel): class BookCoverDisplay(QLabel):
@ -148,6 +148,7 @@ class CoverFlowButton(QToolButton):
self.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)) self.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding))
self.connect(self, SIGNAL('toggled(bool)'), self.adjust_tooltip) self.connect(self, SIGNAL('toggled(bool)'), self.adjust_tooltip)
self.adjust_tooltip(False) self.adjust_tooltip(False)
self.setCursor(Qt.PointingHandCursor)
def adjust_tooltip(self, on): def adjust_tooltip(self, on):
tt = _('Click to turn off Cover Browsing') if on else _('Click to browse books by their covers') tt = _('Click to turn off Cover Browsing') if on else _('Click to browse books by their covers')
@ -165,6 +166,7 @@ class TagViewButton(QToolButton):
self.setIcon(QIcon(':/images/tags.svg')) self.setIcon(QIcon(':/images/tags.svg'))
self.setToolTip(_('Click to browse books by tags')) self.setToolTip(_('Click to browse books by tags'))
self.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)) self.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding))
self.setCursor(Qt.PointingHandCursor)
self.setCheckable(True) self.setCheckable(True)
self.setChecked(False) self.setChecked(False)
self.setAutoRaise(True) self.setAutoRaise(True)
@ -197,7 +199,7 @@ class StatusBar(QStatusBar):
def showMessage(self, msg, timeout=0): def showMessage(self, msg, timeout=0):
ret = QStatusBar.showMessage(self, msg, timeout) ret = QStatusBar.showMessage(self, msg, timeout)
if self.systray is not None: if self.systray is not None and not config['disable_tray_notification']:
if isosx and isinstance(msg, unicode): if isosx and isinstance(msg, unicode):
try: try:
msg = msg.encode(preferred_encoding) msg = msg.encode(preferred_encoding)

View File

@ -1,16 +1,8 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
''' Code to manage ebook library''' ''' Code to manage ebook library'''
import re
from calibre.utils.config import Config, StringConfig from calibre.utils.config import Config, StringConfig
title_pat = re.compile('^(A|The|An)\s+', re.IGNORECASE)
def title_sort(title):
match = title_pat.search(title)
if match:
prep = match.group(1)
title = title.replace(prep, '') + ', ' + prep
return title.strip()
def server_config(defaults=None): def server_config(defaults=None):
desc=_('Settings to control the calibre content server') desc=_('Settings to control the calibre content server')

View File

@ -15,7 +15,7 @@ from PyQt4.QtCore import QCoreApplication, QThread, QReadWriteLock
from PyQt4.QtGui import QApplication, QPixmap, QImage from PyQt4.QtGui import QApplication, QPixmap, QImage
__app = None __app = None
from calibre.library import title_sort from calibre.ebooks.metadata import title_sort
from calibre.library.database import LibraryDatabase from calibre.library.database import LibraryDatabase
from calibre.library.sqlite import connect, IntegrityError from calibre.library.sqlite import connect, IntegrityError
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser

View File

@ -312,7 +312,8 @@ class LibraryServer(object):
book, books = MarkupTemplate(self.BOOK), [] book, books = MarkupTemplate(self.BOOK), []
for record in items[start:start+num]: for record in items[start:start+num]:
authors = '|'.join([i.replace('|', ',') for i in record[2].split(',')]) aus = record[2] if record[2] else _('Unknown')
authors = '|'.join([i.replace('|', ',') for i in aus.split(',')])
books.append(book.generate(r=record, authors=authors).render('xml').decode('utf-8')) books.append(book.generate(r=record, authors=authors).render('xml').decode('utf-8'))
updated = self.db.last_modified() updated = self.db.last_modified()

View File

@ -13,7 +13,7 @@ from threading import Thread
from Queue import Queue from Queue import Queue
from threading import RLock from threading import RLock
from calibre.library import title_sort from calibre.ebooks.metadata import title_sort
global_lock = RLock() global_lock = RLock()

View File

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

View File

@ -16,17 +16,8 @@ if os.environ.has_key('DESTDIR'):
entry_points = { entry_points = {
'console_scripts': [ \ 'console_scripts': [ \
'prs500 = calibre.devices.prs500.cli.main:main', 'ebook-device = calibre.devices.prs500.cli.main:main',
'lrf-meta = calibre.ebooks.lrf.meta:main', 'ebook-meta = calibre.ebooks.metadata.cli:main',
'rtf-meta = calibre.ebooks.metadata.rtf:main',
'pdf-meta = calibre.ebooks.metadata.pdf:main',
'lit-meta = calibre.ebooks.metadata.lit:main',
'imp-meta = calibre.ebooks.metadata.imp:main',
'rb-meta = calibre.ebooks.metadata.rb:main',
'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', 'txt2lrf = calibre.ebooks.lrf.txt.convert_from:main',
'html2lrf = calibre.ebooks.lrf.html.convert_from:main', 'html2lrf = calibre.ebooks.lrf.html.convert_from:main',
'html2oeb = calibre.ebooks.html:main', 'html2oeb = calibre.ebooks.html:main',
@ -46,7 +37,6 @@ entry_points = {
'pdf2lrf = calibre.ebooks.lrf.pdf.convert_from:main', 'pdf2lrf = calibre.ebooks.lrf.pdf.convert_from:main',
'mobi2lrf = calibre.ebooks.lrf.mobi.convert_from:main', 'mobi2lrf = calibre.ebooks.lrf.mobi.convert_from:main',
'fb22lrf = calibre.ebooks.lrf.fb2.convert_from:main', 'fb22lrf = calibre.ebooks.lrf.fb2.convert_from:main',
'fb2-meta = calibre.ebooks.metadata.fb2:main',
'any2lrf = calibre.ebooks.lrf.any.convert_from:main', 'any2lrf = calibre.ebooks.lrf.any.convert_from:main',
'any2epub = calibre.ebooks.epub.from_any:main', 'any2epub = calibre.ebooks.epub.from_any:main',
'any2lit = calibre.ebooks.lit.from_any:main', 'any2lit = calibre.ebooks.lit.from_any:main',
@ -58,11 +48,11 @@ entry_points = {
'librarything = calibre.ebooks.metadata.library_thing:main', 'librarything = calibre.ebooks.metadata.library_thing:main',
'mobi2oeb = calibre.ebooks.mobi.reader:main', 'mobi2oeb = calibre.ebooks.mobi.reader:main',
'oeb2mobi = calibre.ebooks.mobi.writer:main', 'oeb2mobi = calibre.ebooks.mobi.writer:main',
'lrf2html = calibre.ebooks.lrf.html.convert_to:main',
'lit2oeb = calibre.ebooks.lit.reader:main', 'lit2oeb = calibre.ebooks.lit.reader:main',
'oeb2lit = calibre.ebooks.lit.writer:main', 'oeb2lit = calibre.ebooks.lit.writer:main',
'comic2lrf = calibre.ebooks.lrf.comic.convert_from:main', 'comic2lrf = calibre.ebooks.lrf.comic.convert_from:main',
'comic2epub = calibre.ebooks.epub.from_comic:main', 'comic2epub = calibre.ebooks.epub.from_comic:main',
'comic2mobi = calibre.ebooks.mobi.from_comic:main',
'comic2pdf = calibre.ebooks.pdf.from_comic:main', 'comic2pdf = calibre.ebooks.pdf.from_comic:main',
'calibre-debug = calibre.debug:main', 'calibre-debug = calibre.debug:main',
'calibredb = calibre.library.cli:main', 'calibredb = calibre.library.cli:main',
@ -176,7 +166,7 @@ def setup_completion(fatal_errors):
sys.stdout.flush() sys.stdout.flush()
from calibre.ebooks.lrf.html.convert_from import option_parser as htmlop from calibre.ebooks.lrf.html.convert_from import option_parser as htmlop
from calibre.ebooks.lrf.txt.convert_from import option_parser as txtop from calibre.ebooks.lrf.txt.convert_from import option_parser as txtop
from calibre.ebooks.lrf.meta import option_parser as metaop from calibre.ebooks.metadata.cli import option_parser as metaop, filetypes as meta_filetypes
from calibre.ebooks.lrf.lrfparser import option_parser as lrf2lrsop from calibre.ebooks.lrf.lrfparser import option_parser as lrf2lrsop
from calibre.gui2.lrf_renderer.main import option_parser as lrfviewerop from calibre.gui2.lrf_renderer.main import option_parser as lrfviewerop
from calibre.ebooks.lrf.pdf.reflow import option_parser as pdfhtmlop from calibre.ebooks.lrf.pdf.reflow import option_parser as pdfhtmlop
@ -185,7 +175,6 @@ def setup_completion(fatal_errors):
from calibre.web.feeds.main import option_parser as feeds2disk from calibre.web.feeds.main import option_parser as feeds2disk
from calibre.web.feeds.recipes import titles as feed_titles from calibre.web.feeds.recipes import titles as feed_titles
from calibre.ebooks.lrf.feeds.convert_from import option_parser as feeds2lrf from calibre.ebooks.lrf.feeds.convert_from import option_parser as feeds2lrf
from calibre.ebooks.metadata.epub import option_parser as epub_meta
from calibre.ebooks.lrf.comic.convert_from import option_parser as comicop from calibre.ebooks.lrf.comic.convert_from import option_parser as comicop
from calibre.ebooks.epub.from_html import option_parser as html2epub from calibre.ebooks.epub.from_html import option_parser as html2epub
from calibre.ebooks.html import option_parser as html2oeb from calibre.ebooks.html import option_parser as html2oeb
@ -224,21 +213,14 @@ def setup_completion(fatal_errors):
f.write(opts_and_exts('any2mobi', any2mobi, any_formats)) f.write(opts_and_exts('any2mobi', any2mobi, any_formats))
f.write(opts_and_exts('oeb2mobi', oeb2mobi, ['opf'])) f.write(opts_and_exts('oeb2mobi', oeb2mobi, ['opf']))
f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf'])) f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf']))
f.write(opts_and_exts('lrf-meta', metaop, ['lrf'])) f.write(opts_and_exts('ebook-meta', metaop, list(meta_filetypes())))
f.write(opts_and_exts('rtf-meta', metaop, ['rtf']))
f.write(opts_and_exts('pdf-meta', metaop, ['pdf']))
f.write(opts_and_exts('lit-meta', metaop, ['lit']))
f.write(opts_and_exts('imp-meta', metaop, ['imp']))
f.write(opts_and_exts('rb-meta', metaop, ['rb']))
f.write(opts_and_exts('opf-meta', metaop, ['opf']))
f.write(opts_and_exts('odt-meta', metaop, ['odt', 'ods', 'odf', 'odg', 'odp']))
f.write(opts_and_exts('epub-meta', epub_meta, ['epub']))
f.write(opts_and_exts('lrfviewer', lrfviewerop, ['lrf'])) f.write(opts_and_exts('lrfviewer', lrfviewerop, ['lrf']))
f.write(opts_and_exts('pdfrelow', pdfhtmlop, ['pdf'])) f.write(opts_and_exts('pdfrelow', pdfhtmlop, ['pdf']))
f.write(opts_and_exts('mobi2oeb', mobioeb, ['mobi', 'prc'])) f.write(opts_and_exts('mobi2oeb', mobioeb, ['mobi', 'prc']))
f.write(opts_and_exts('lit2oeb', lit2oeb, ['lit'])) f.write(opts_and_exts('lit2oeb', lit2oeb, ['lit']))
f.write(opts_and_exts('comic2lrf', comicop, ['cbz', 'cbr'])) f.write(opts_and_exts('comic2lrf', comicop, ['cbz', 'cbr']))
f.write(opts_and_exts('comic2epub', comic2epub, ['cbz', 'cbr'])) f.write(opts_and_exts('comic2epub', comic2epub, ['cbz', 'cbr']))
f.write(opts_and_exts('comic2mobi', comic2epub, ['cbz', 'cbr']))
f.write(opts_and_exts('comic2pdf', comic2epub, ['cbz', 'cbr'])) f.write(opts_and_exts('comic2pdf', comic2epub, ['cbz', 'cbr']))
f.write(opts_and_words('feeds2disk', feeds2disk, feed_titles)) f.write(opts_and_words('feeds2disk', feeds2disk, feed_titles))
f.write(opts_and_words('feeds2lrf', feeds2lrf, feed_titles)) f.write(opts_and_words('feeds2lrf', feeds2lrf, feed_titles))
@ -421,10 +403,8 @@ def install_man_pages(fatal_errors):
os.environ['PATH'] += ':'+os.path.expanduser('~/bin') os.environ['PATH'] += ':'+os.path.expanduser('~/bin')
for src in entry_points['console_scripts']: for src in entry_points['console_scripts']:
prog = src[:src.index('=')].strip() prog = src[:src.index('=')].strip()
if prog in ('prs500', 'pdf-meta', 'epub-meta', 'lit-meta', if prog in ('ebook-device', 'markdown-calibre',
'markdown-calibre', 'calibre-debug', 'fb2-meta', 'calibre-fontconfig', 'calibre-parallel'):
'calibre-fontconfig', 'calibre-parallel', 'odt-meta',
'rb-meta', 'imp-meta', 'mobi-meta'):
continue continue
help2man = ('help2man', prog, '--name', 'part of %s'%__appname__, help2man = ('help2man', prog, '--name', 'part of %s'%__appname__,
@ -484,14 +464,14 @@ def post_install():
os.unlink(f) os.unlink(f)
def binary_install(): def binary_install():
manifest = os.path.join(sys.frozen_path, 'manifest') manifest = os.path.join(getattr(sys, 'frozen_path'), 'manifest')
exes = [x.strip() for x in open(manifest).readlines()] exes = [x.strip() for x in open(manifest).readlines()]
print 'Creating symlinks...' print 'Creating symlinks...'
for exe in exes: for exe in exes:
dest = os.path.join('/usr', 'bin', exe) dest = os.path.join('/usr', 'bin', exe)
if os.path.exists(dest): if os.path.exists(dest):
os.remove(dest) os.remove(dest)
os.symlink(os.path.join(sys.frozen_path, exe), dest) os.symlink(os.path.join(getattr(sys, 'frozen_path'), exe), dest)
post_install() post_install()
return 0 return 0

View File

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

View File

@ -158,21 +158,27 @@ class WorkerMother(object):
self.executable = os.path.join(os.path.dirname(sys.executable), self.executable = os.path.join(os.path.dirname(sys.executable),
'calibre-parallel.exe' if isfrozen else 'Scripts\\calibre-parallel.exe') 'calibre-parallel.exe' if isfrozen else 'Scripts\\calibre-parallel.exe')
elif isosx: elif isosx:
self.executable = sys.executable self.executable = self.gui_executable = sys.executable
self.prefix = '' self.prefix = ''
if isfrozen: if isfrozen:
fd = getattr(sys, 'frameworks_dir') fd = getattr(sys, 'frameworks_dir')
contents = os.path.dirname(fd) contents = os.path.dirname(fd)
resources = os.path.join(contents, 'Resources') self.gui_executable = os.path.join(contents, 'MacOS',
sp = os.path.join(resources, 'lib', 'python'+sys.version[:3], 'site-packages.zip') os.path.basename(sys.executable))
contents = os.path.join(contents, 'console.app', 'Contents')
self.executable = os.path.join(contents, 'MacOS',
os.path.basename(sys.executable))
resources = os.path.join(contents, 'Resources')
fd = os.path.join(contents, 'Frameworks')
sp = os.path.join(resources, 'lib', 'python'+sys.version[:3], 'site-packages.zip')
self.prefix += 'import sys; sys.frameworks_dir = "%s"; sys.frozen = "macosx_app"; '%fd self.prefix += 'import sys; sys.frameworks_dir = "%s"; sys.frozen = "macosx_app"; '%fd
self.prefix += 'sys.path.insert(0, %s); '%repr(sp) self.prefix += 'sys.path.insert(0, %s); '%repr(sp)
if fd not in os.environ['PATH']: if fd not in os.environ['PATH']:
self.env['PATH'] = os.environ['PATH']+':'+fd self.env['PATH'] = os.environ['PATH']+':'+fd
self.env['PYTHONHOME'] = resources self.env['PYTHONHOME'] = resources
self.env['MAGICK_HOME'] = os.path.join(getattr(sys, 'frameworks_dir'), 'ImageMagick') self.env['MAGICK_HOME'] = os.path.join(fd, 'ImageMagick')
self.env['DYLD_LIBRARY_PATH'] = os.path.join(getattr(sys, 'frameworks_dir'), 'ImageMagick', 'lib') self.env['DYLD_LIBRARY_PATH'] = os.path.join(fd, 'ImageMagick', 'lib')
else: else:
self.executable = os.path.join(getattr(sys, 'frozen_path'), 'calibre-parallel') \ self.executable = os.path.join(getattr(sys, 'frozen_path'), 'calibre-parallel') \
if isfrozen else 'calibre-parallel' if isfrozen else 'calibre-parallel'
@ -219,7 +225,8 @@ class WorkerMother(object):
def spawn_free_spirit_osx(self, arg, type='free_spirit'): def spawn_free_spirit_osx(self, arg, type='free_spirit'):
script = 'from calibre.parallel import main; main(args=["calibre-parallel", %s]);'%repr(arg) script = 'from calibre.parallel import main; main(args=["calibre-parallel", %s]);'%repr(arg)
cmdline = [self.executable, '-c', self.prefix+script] exe = self.gui_executable if type == 'free_spirit' else self.executable
cmdline = [exe, '-c', self.prefix+script]
child = WorkerStatus(subprocess.Popen(cmdline, env=self.get_env())) child = WorkerStatus(subprocess.Popen(cmdline, env=self.get_env()))
atexit.register(self.cleanup_child_linux, child) atexit.register(self.cleanup_child_linux, child)
return child return child

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -97,7 +97,7 @@ class OptionParser(_OptionParser):
def merge(self, parser): def merge(self, parser):
''' '''
Add options from parser to self. In case of conflicts, confilicting options from Add options from parser to self. In case of conflicts, conflicting options from
parser are skipped. parser are skipped.
''' '''
opts = list(parser.option_list) opts = list(parser.option_list)
@ -224,6 +224,8 @@ class OptionSet(object):
def update(self, other): def update(self, other):
for name in other.groups.keys(): for name in other.groups.keys():
self.groups[name] = other.groups[name] self.groups[name] = other.groups[name]
if name not in self.group_list:
self.group_list.append(name)
for pref in other.preferences: for pref in other.preferences:
if pref in self.preferences: if pref in self.preferences:
self.preferences.remove(pref) self.preferences.remove(pref)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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