Sync to trunk

This commit is contained in:
John Schember 2009-02-07 12:17:28 -05:00
commit 31ba8daf2a
184 changed files with 9140 additions and 1849 deletions

View File

@ -299,7 +299,6 @@ File ::2BCD9281-2CBC-CF0D-0E12-2CE11F6ED758 -name comic2epub.exe.local -parent 8
File ::EDE6F457-C83F-C5FA-9AF4-38FDFF17D929 -name PIL._imagingtk.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::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 ::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 ::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 ::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 ::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 ::FBD11D98-D1E7-5DD9-BF02-01CE92518859 -name IM_MOD_RL_otb_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
@ -365,7 +364,6 @@ File ::26741B21-C241-E100-8BB1-8B679BC3E662 -name configure.xml -parent 8E5D85A4
File ::7D491E89-C6D3-1E6E-F4BD-8E55260FE33E -name libexpat.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::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 ::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 ::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 ::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 ::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 ::2E2A9EDA-5386-444E-8479-557386794552 -name IM_MOD_RL_uil_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
@ -487,7 +485,6 @@ File ::AA761ACD-B728-2324-AA75-B20A2A79F125 -name lrf2lrs.exe -parent 8E5D85A4-7
File ::95434C76-22F5-B9CE-6194-6E1B1EE3232D -name IM_MOD_RL_info_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40 File ::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 ::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 ::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 ::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 ::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 ::5D368661-6BF0-D6AF-7C1A-87646864EB4B -name delegates.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
@ -552,7 +549,7 @@ SetupType ::D9ADE41C-B744-690C-2CED-CF826BF03D2E -setup Install -active Yes -pla
InstallComponent 3EA07B17-04D8-6508-B535-96CC7173B49A -setup Install -type pane -conditions D7F585DB-0DEC-A94E-DAB0-94D558D82764 -title {Welcome Screen} -component Welcome -command insert -active Yes -parent StandardInstall 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 -conditions ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E -title {Check for Previous Install} -component CheckForPreviousInstall -command reorder -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A InstallComponent 7CCDA4BB-861C-C21E-3011-E93DB58F07D6 -setup Install -type action -conditions ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E -title {Check for Previous Install} -component CheckForPreviousInstall -command insert -active Yes -parent 3EA07B17-04D8-6508-B535-96CC7173B49A
Condition ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E -active Yes -parent 7CCDA4BB-861C-C21E-3011-E93DB58F07D6 -title {Execute Script Condition} -component ExecuteScriptCondition -TreeObject::id ADBCD53E-C9A6-A3CA-1AAC-0DB0CE84F71E 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

View File

@ -12,7 +12,7 @@ LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
PDFTOHTML = 'C:\\pdftohtml\\pdftohtml.exe' PDFTOHTML = 'C:\\pdftohtml\\pdftohtml.exe'
IMAGEMAGICK_DIR = 'C:\\ImageMagick' IMAGEMAGICK_DIR = 'C:\\ImageMagick'
FONTCONFIG_DIR = 'C:\\fontconfig' FONTCONFIG_DIR = 'C:\\fontconfig'
VC90 = r'C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\x86\Microsoft.VC90.CRT' VC90 = r'C:\VC90.CRT'
import sys, os, py2exe, shutil, zipfile, glob, subprocess, re import sys, os, py2exe, shutil, zipfile, glob, subprocess, re
from distutils.core import setup from distutils.core import setup

View File

@ -17,10 +17,17 @@ from calibre.constants import iswindows, isosx, islinux, isfrozen, \
filesystem_encoding filesystem_encoding
import mechanize import mechanize
mimetypes.add_type('application/epub+zip', '.epub') mimetypes.add_type('application/epub+zip', '.epub')
mimetypes.add_type('text/x-sony-bbeb+xml', '.lrs') mimetypes.add_type('text/x-sony-bbeb+xml', '.lrs')
mimetypes.add_type('application/x-sony-bbeb', '.lrf') mimetypes.add_type('http://www.w3.org/1999/xhtml', '.xhtml')
mimetypes.add_type('application/x-dtbncx+xml', '.ncx') mimetypes.add_type('image/svg+xml', '.svg')
mimetypes.add_type('application/x-sony-bbeb', '.lrf')
mimetypes.add_type('application/x-dtbncx+xml', '.ncx')
mimetypes.add_type('application/adobe-page-template+xml', '.xpgt')
mimetypes.add_type('application/x-font-opentype', '.otf')
mimetypes.add_type('application/x-font-truetype', '.ttf')
mimetypes.add_type('application/oebps-package+xml', '.opf')
def to_unicode(raw, encoding='utf-8', errors='strict'): def to_unicode(raw, encoding='utf-8', errors='strict'):
if isinstance(raw, unicode): if isinstance(raw, unicode):

View File

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

View File

@ -96,7 +96,7 @@ class PRS500(Device):
# Location of cache.xml on storage card in device # Location of cache.xml on storage card in device
CACHE_XML = "/Sony Reader/database/cache.xml" CACHE_XML = "/Sony Reader/database/cache.xml"
# Ordered list of supported formats # Ordered list of supported formats
FORMATS = ["lrf", "rtf", "pdf", "txt"] FORMATS = ["lrf", "lrx", "rtf", "pdf", "txt"]
# Height for thumbnails of books/images on the device # Height for thumbnails of books/images on the device
THUMBNAIL_HEIGHT = 68 THUMBNAIL_HEIGHT = 68
# Directory on card to which books are copied # Directory on card to which books are copied

View File

@ -27,12 +27,12 @@ class File(object):
class PRS505(Device): class PRS505(Device):
VENDOR_ID = 0x054c #: SONY Vendor Id VENDOR_ID = 0x054c #: SONY Vendor Id
PRODUCT_ID = 0x031e #: Product Id for the PRS-505 PRODUCT_ID = 0x031e #: Product Id for the PRS-505
BCD = [0x229] #: Needed to disambiguate 505 and 700 on linux BCD = [0x229] #: Needed to disambiguate 505 and 700 on linux
PRODUCT_NAME = 'PRS-505' PRODUCT_NAME = 'PRS-505'
VENDOR_NAME = 'SONY' VENDOR_NAME = 'SONY'
FORMATS = ['lrf', 'epub', "rtf", "pdf", "txt"] FORMATS = ['lrf', 'epub', 'lrx', 'rtf', 'pdf', 'txt']
MEDIA_XML = 'database/cache/media.xml' MEDIA_XML = 'database/cache/media.xml'
CACHE_XML = 'Sony Reader/database/cache.xml' CACHE_XML = 'Sony Reader/database/cache.xml'
@ -147,6 +147,7 @@ class PRS505(Device):
def open_windows(self): def open_windows(self):
time.sleep(6)
drives = [] drives = []
wmi = __import__('wmi', globals(), locals(), [], -1) wmi = __import__('wmi', globals(), locals(), [], -1)
c = wmi.WMI() c = wmi.WMI()

View File

@ -172,6 +172,7 @@ class Device(_Device):
return prefix return prefix
def open_windows(self): def open_windows(self):
time.sleep(6)
drives = {} drives = {}
wmi = __import__('wmi', globals(), locals(), [], -1) wmi = __import__('wmi', globals(), locals(), [], -1)
c = wmi.WMI() c = wmi.WMI()

View File

@ -102,13 +102,23 @@ def config(defaults=None, name='epub'):
c.remove_opt('zip') c.remove_opt('zip')
c.add_opt('output', ['-o', '--output'], default=None, c.add_opt('output', ['-o', '--output'], default=None,
help=_('The output EPUB file. If not specified, it is derived from the input file name.')) help=_('The output EPUB file. If not specified, it is '
'derived from the input file name.'))
c.add_opt('profile', ['--profile'], default='PRS505', choices=list(PROFILES.keys()), c.add_opt('profile', ['--profile'], default='PRS505', choices=list(PROFILES.keys()),
help=_('Profile of the target device this EPUB is meant for. Set to None to create a device independent EPUB. The profile is used for device specific restrictions on the EPUB. Choices are: ')+str(list(PROFILES.keys()))) help=_('Profile of the target device this EPUB is meant for. '
'Set to None to create a device independent EPUB. '
'The profile is used for device specific restrictions '
'on the EPUB. Choices are: ')+str(list(PROFILES.keys())))
c.add_opt('override_css', ['--override-css'], default=None, c.add_opt('override_css', ['--override-css'], default=None,
help=_('Either the path to a CSS stylesheet or raw CSS. This CSS will override any existing CSS declarations in the source files.')) help=_('Either the path to a CSS stylesheet or raw CSS. '
structure = c.add_group('structure detection', _('Control auto-detection of document structure.')) 'This CSS will override any existing CSS '
structure('chapter', ['--chapter'], default="//*[re:match(name(), 'h[1-2]') and re:test(., 'chapter|book|section|part', 'i')] | //*[@class = 'chapter']", 'declarations in the source files.'))
structure = c.add_group('structure detection',
_('Control auto-detection of document structure.'))
structure('chapter', ['--chapter'],
default="//*[re:match(name(), 'h[1-2]') and "
"re:test(., 'chapter|book|section|part', 'i')] | "
"//*[@class = 'chapter']",
help=_('''\ help=_('''\
An XPath expression to detect chapter titles. The default is to consider <h1> or An XPath expression to detect chapter titles. The default is to consider <h1> or
<h2> tags that contain the words "chapter","book","section" or "part" as chapter titles as <h2> tags that contain the words "chapter","book","section" or "part" as chapter titles as
@ -118,14 +128,39 @@ use the expression "/". See the XPath Tutorial in the calibre User Manual for fu
help on using this feature. help on using this feature.
''').replace('\n', ' ')) ''').replace('\n', ' '))
structure('chapter_mark', ['--chapter-mark'], choices=['pagebreak', 'rule', 'both', 'none'], structure('chapter_mark', ['--chapter-mark'], choices=['pagebreak', 'rule', 'both', 'none'],
default='pagebreak', help=_('Specify how to mark detected chapters. A value of "pagebreak" will insert page breaks before chapters. A value of "rule" will insert a line before chapters. A value of "none" will disable chapter marking and a value of "both" will use both page breaks and lines to mark chapters.')) default='pagebreak',
help=_('Specify how to mark detected chapters. A value of '
'"pagebreak" will insert page breaks before chapters. '
'A value of "rule" will insert a line before chapters. '
'A value of "none" will disable chapter marking and a '
'value of "both" will use both page breaks and lines '
'to mark chapters.'))
structure('cover', ['--cover'], default=None, structure('cover', ['--cover'], default=None,
help=_('Path to the cover to be used for this book')) help=_('Path to the cover to be used for this book'))
structure('prefer_metadata_cover', ['--prefer-metadata-cover'], default=False, structure('prefer_metadata_cover', ['--prefer-metadata-cover'], default=False,
action='store_true', action='store_true',
help=_('Use the cover detected from the source file in preference to the specified cover.')) help=_('Use the cover detected from the source file in preference '
'to the specified cover.'))
structure('remove_first_image', ['--remove-first-image'], default=False,
help=_('Remove the first image from the input ebook. Useful if '
'the first image in the source file is a cover and you '
'are specifying an external cover.'))
structure('dont_split_on_page_breaks', ['--dont-split-on-page-breaks'], default=False, structure('dont_split_on_page_breaks', ['--dont-split-on-page-breaks'], default=False,
help=_('Turn off splitting at page breaks. Normally, input files are automatically split at every page break into two files. This gives an output ebook that can be parsed faster and with less resources. However, splitting is slow and if your source file contains a very large number of page breaks, you should turn off splitting on page breaks.')) help=_('Turn off splitting at page breaks. Normally, input files '
'are automatically split at every page break into '
'two files. This gives an output ebook that can be parsed '
'faster and with less resources. However, splitting is '
'slow and if your source file contains a very large '
'number of page breaks, you should turn off splitting '
'on page breaks.'))
structure('page', ['--page'], default=None,
help=_('XPath expression to detect page boundaries for building '
'a custom pagination map, as used by AdobeDE. Default is '
'not to build an explicit pagination map.'))
structure('page_names', ['--page-names'], default=None,
help=_('XPath expression to find the name of each page in the '
'pagination map relative to its boundary element. '
'Default is to number all pages staring with 1.'))
toc = c.add_group('toc', toc = c.add_group('toc',
_('''\ _('''\
Control the automatic generation of a Table of Contents. If an OPF file is detected Control the automatic generation of a Table of Contents. If an OPF file is detected
@ -133,21 +168,36 @@ and it specifies a Table of Contents, then that will be used rather than trying
to auto-generate a Table of Contents. to auto-generate a Table of Contents.
''').replace('\n', ' ')) ''').replace('\n', ' '))
toc('max_toc_links', ['--max-toc-links'], default=50, toc('max_toc_links', ['--max-toc-links'], default=50,
help=_('Maximum number of links to insert into the TOC. Set to 0 to disable. Default is: %default. Links are only added to the TOC if less than the --toc-threshold number of chapters were detected.')) help=_('Maximum number of links to insert into the TOC. Set to 0 '
'to disable. Default is: %default. Links are only added to the '
'TOC if less than the --toc-threshold number of chapters were detected.'))
toc('no_chapters_in_toc', ['--no-chapters-in-toc'], default=False, toc('no_chapters_in_toc', ['--no-chapters-in-toc'], default=False,
help=_("Don't add auto-detected chapters to the Table of Contents.")) help=_("Don't add auto-detected chapters to the Table of Contents."))
toc('toc_threshold', ['--toc-threshold'], default=6, toc('toc_threshold', ['--toc-threshold'], default=6,
help=_('If fewer than this number of chapters is detected, then links are added to the Table of Contents. Default: %default')) help=_('If fewer than this number of chapters is detected, then links '
'are added to the Table of Contents. Default: %default'))
toc('level1_toc', ['--level1-toc'], default=None, toc('level1_toc', ['--level1-toc'], default=None,
help=_('XPath expression that specifies all tags that should be added to the Table of Contents at level one. If this is specified, it takes precedence over other forms of auto-detection.')) help=_('XPath expression that specifies all tags that should be added '
'to the Table of Contents at level one. If this is specified, '
'it takes precedence over other forms of auto-detection.'))
toc('level2_toc', ['--level2-toc'], default=None, toc('level2_toc', ['--level2-toc'], default=None,
help=_('XPath expression that specifies all tags that should be added to the Table of Contents at level two. Each entry is added under the previous level one entry.')) help=_('XPath expression that specifies all tags that should be added '
'to the Table of Contents at level two. Each entry is added '
'under the previous level one entry.'))
toc('level3_toc', ['--level3-toc'], default=None, toc('level3_toc', ['--level3-toc'], default=None,
help=_('XPath expression that specifies all tags that should be added to the Table of Contents at level three. Each entry is added under the previous level two entry.')) help=_('XPath expression that specifies all tags that should be added '
'to the Table of Contents at level three. Each entry is added '
'under the previous level two entry.'))
toc('from_ncx', ['--from-ncx'], default=None, toc('from_ncx', ['--from-ncx'], default=None,
help=_('Path to a .ncx file that contains the table of contents to use for this ebook. The NCX file should contain links relative to the directory it is placed in. See http://www.niso.org/workrooms/daisy/Z39-86-2005.html#NCX for an overview of the NCX format.')) help=_('Path to a .ncx file that contains the table of contents to use '
'for this ebook. The NCX file should contain links relative to '
'the directory it is placed in. See '
'http://www.niso.org/workrooms/daisy/Z39-86-2005.html#NCX for '
'an overview of the NCX format.'))
toc('use_auto_toc', ['--use-auto-toc'], default=False, toc('use_auto_toc', ['--use-auto-toc'], default=False,
help=_('Normally, if the source file already has a Table of Contents, it is used in preference to the autodetected one. With this option, the autodetected one is always used.')) help=_('Normally, if the source file already has a Table of Contents, '
'it is used in preference to the autodetected one. '
'With this option, the autodetected one is always used.'))
layout = c.add_group('page layout', _('Control page layout')) layout = c.add_group('page layout', _('Control page layout'))
layout('margin_top', ['--margin-top'], default=5.0, layout('margin_top', ['--margin-top'], default=5.0,
@ -159,18 +209,33 @@ to auto-generate a Table of Contents.
layout('margin_right', ['--margin-right'], default=5.0, layout('margin_right', ['--margin-right'], default=5.0,
help=_('Set the right margin in pts. Default is %default')) help=_('Set the right margin in pts. Default is %default'))
layout('base_font_size2', ['--base-font-size'], default=12.0, layout('base_font_size2', ['--base-font-size'], default=12.0,
help=_('The base font size in pts. Default is %defaultpt. Set to 0 to disable rescaling of fonts.')) help=_('The base font size in pts. Default is %defaultpt. '
'Set to 0 to disable rescaling of fonts.'))
layout('remove_paragraph_spacing', ['--remove-paragraph-spacing'], default=False, layout('remove_paragraph_spacing', ['--remove-paragraph-spacing'], default=False,
help=_('Remove spacing between paragraphs. Will not work if the source file forces inter-paragraph spacing.')) help=_('Remove spacing between paragraphs. '
'Also sets a indent on paragraphs of 1.5em. '
'You can override this by adding p {text-indent: 0cm} to '
'--override-css. Spacing removal will not work if the source '
'file forces inter-paragraph spacing.'))
layout('no_justification', ['--no-justification'], default=False,
help=_('Do not force text to be justified in output.'))
layout('linearize_tables', ['--linearize-tables'], default=False,
help=_('Remove table markup, converting it into paragraphs. '
'This is useful if your source file uses a table to manage layout.'))
layout('preserve_tag_structure', ['--preserve-tag-structure'], default=False, layout('preserve_tag_structure', ['--preserve-tag-structure'], default=False,
help=_('Preserve the HTML tag structure while splitting large HTML files. This is only neccessary if the HTML files contain CSS that uses sibling selectors. Enabling this greatly slows down processing of large HTML files.')) help=_('Preserve the HTML tag structure while splitting large HTML files. '
'This is only neccessary if the HTML files contain CSS that '
'uses sibling selectors. Enabling this greatly slows down '
'processing of large HTML files.'))
c.add_opt('show_opf', ['--show-opf'], default=False, group='debug', c.add_opt('show_opf', ['--show-opf'], default=False, group='debug',
help=_('Print generated OPF file to stdout')) help=_('Print generated OPF file to stdout'))
c.add_opt('show_ncx', ['--show-ncx'], default=False, group='debug', c.add_opt('show_ncx', ['--show-ncx'], default=False, group='debug',
help=_('Print generated NCX file to stdout')) help=_('Print generated NCX file to stdout'))
c.add_opt('keep_intermediate', ['--keep-intermediate-files'], group='debug', default=False, c.add_opt('keep_intermediate', ['--keep-intermediate-files'], group='debug',
default=False,
help=_('Keep intermediate files during processing by html2epub')) help=_('Keep intermediate files during processing by html2epub'))
c.add_opt('extract_to', ['--extract-to'], group='debug', default=None, c.add_opt('extract_to', ['--extract-to'], group='debug', default=None,
help=_('Extract the contents of the produced EPUB file to the specified directory.')) help=_('Extract the contents of the produced EPUB file to the '
return c 'specified directory.'))
return c

View File

@ -46,6 +46,7 @@ from calibre.ebooks.metadata.toc import TOC
from calibre.ebooks.metadata.opf2 import OPF from calibre.ebooks.metadata.opf2 import OPF
from calibre.ebooks.epub import initialize_container, PROFILES from calibre.ebooks.epub import initialize_container, PROFILES
from calibre.ebooks.epub.split import split from calibre.ebooks.epub.split import split
from calibre.ebooks.epub.pages import add_page_map
from calibre.ebooks.epub.fonts import Rationalizer from calibre.ebooks.epub.fonts import Rationalizer
from calibre.constants import preferred_encoding from calibre.constants import preferred_encoding
from calibre.customize.ui import run_plugins_on_postprocess from calibre.customize.ui import run_plugins_on_postprocess
@ -141,7 +142,7 @@ class HTMLProcessor(Processor, Rationalizer):
p = QPixmap() p = QPixmap()
p.load(path) p.load(path)
if not p.isNull(): if not p.isNull():
p.save(path+'_calibre_converted.jpg') p.save(path + '_calibre_converted.jpg')
os.remove(path) os.remove(path)
for key, val in self.resource_map.items(): for key, val in self.resource_map.items():
if val == rpath: if val == rpath:
@ -194,7 +195,10 @@ class HTMLProcessor(Processor, Rationalizer):
if not tag.text and not tag.get('src', False): if not tag.text and not tag.get('src', False):
tag.getparent().remove(tag) tag.getparent().remove(tag)
if self.opts.linearize_tables:
for tag in self.root.xpath('//table | //tr | //th | //td'):
tag.tag = 'div'
def save(self): def save(self):
for meta in list(self.root.xpath('//meta')): for meta in list(self.root.xpath('//meta')):
@ -203,6 +207,13 @@ class HTMLProcessor(Processor, Rationalizer):
# self.convert_image(img) # self.convert_image(img)
Processor.save(self) Processor.save(self)
def remove_first_image(self):
images = self.root.xpath('//img')
if images:
images[0].getparent().remove(images[0])
return True
return False
@ -224,10 +235,13 @@ def parse_content(filelist, opts, tdir):
resource_map, stylesheets = {}, {} resource_map, stylesheets = {}, {}
toc = TOC(base_path=tdir, type='root') toc = TOC(base_path=tdir, type='root')
stylesheet_map = {} stylesheet_map = {}
first_image_removed = False
for htmlfile in filelist: for htmlfile in filelist:
logging.getLogger('html2epub').debug('Processing %s...'%htmlfile) logging.getLogger('html2epub').debug('Processing %s...'%htmlfile)
hp = HTMLProcessor(htmlfile, opts, os.path.join(tdir, 'content'), hp = HTMLProcessor(htmlfile, opts, os.path.join(tdir, 'content'),
resource_map, filelist, stylesheets) resource_map, filelist, stylesheets)
if not first_image_removed and opts.remove_first_image:
first_image_removed = hp.remove_first_image()
hp.populate_toc(toc) hp.populate_toc(toc)
hp.save() hp.save()
stylesheet_map[os.path.basename(hp.save_path())] = \ stylesheet_map[os.path.basename(hp.save_path())] = \
@ -425,6 +439,9 @@ def convert(htmlfile, opts, notification=None, create_epub=True,
if opts.show_ncx: if opts.show_ncx:
print toc print toc
split(opf_path, opts, stylesheet_map) split(opf_path, opts, stylesheet_map)
if opts.page:
logger.info('\tBuilding page map...')
add_page_map(opf_path, opts)
check_links(opf_path, opts.pretty_print) check_links(opf_path, opts.pretty_print)
opf = OPF(opf_path, tdir) opf = OPF(opf_path, tdir)
@ -441,12 +458,9 @@ def convert(htmlfile, opts, notification=None, create_epub=True,
cpath = os.path.join(tdir, 'content', 'resources', '_cover_.jpg') cpath = os.path.join(tdir, 'content', 'resources', '_cover_.jpg')
if os.path.exists(cpath): if os.path.exists(cpath):
opf.add_path_to_manifest(cpath, 'image/jpeg') opf.add_path_to_manifest(cpath, 'image/jpeg')
with open(opf_path, 'wb') as f: with open(opf_path, 'wb') as f:
raw = opf.render() f.write(opf.render())
if not raw.startswith('<?xml '):
raw = '<?xml version="1.0" encoding="UTF-8"?>\n'+raw
f.write(raw)
ncx_path = os.path.join(os.path.dirname(opf_path), 'toc.ncx') ncx_path = os.path.join(os.path.dirname(opf_path), 'toc.ncx')
if os.path.exists(ncx_path) and os.stat(ncx_path).st_size > opts.profile.flow_size: if os.path.exists(ncx_path) and os.stat(ncx_path).st_size > opts.profile.flow_size:
logger.info('Condensing NCX from %d bytes...'%os.stat(ncx_path).st_size) logger.info('Condensing NCX from %d bytes...'%os.stat(ncx_path).st_size)
@ -462,7 +476,7 @@ def convert(htmlfile, opts, notification=None, create_epub=True,
logger.info(_('Output written to ')+opts.output) logger.info(_('Output written to ')+opts.output)
if opts.show_opf: if opts.show_opf:
print open(os.path.join(tdir, 'metadata.opf')).read() print open(opf_path, 'rb').read()
if opts.extract_to is not None: if opts.extract_to is not None:
if os.path.exists(opts.extract_to): if os.path.exists(opts.extract_to):

View File

@ -0,0 +1,59 @@
'''
Add page mapping information to an EPUB book.
'''
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
__docformat__ = 'restructuredtext en'
import os, re
from itertools import count, chain
from calibre.ebooks.oeb.base import XHTML, XHTML_NS
from calibre.ebooks.oeb.base import OEBBook, DirWriter
from lxml import etree, html
from lxml.etree import XPath
NSMAP = {'h': XHTML_NS, 'html': XHTML_NS, 'xhtml': XHTML_NS}
PAGE_RE = re.compile(r'page', re.IGNORECASE)
ROMAN_RE = re.compile(r'^[ivxlcdm]+$', re.IGNORECASE)
def filter_name(name):
name = name.strip()
name = PAGE_RE.sub('', name)
for word in name.split():
if word.isdigit() or ROMAN_RE.match(word):
name = word
break
return name
def build_name_for(expr):
if not expr:
counter = count(1)
return lambda elem: str(counter.next())
selector = XPath(expr, namespaces=NSMAP)
def name_for(elem):
results = selector(elem)
if not results:
return ''
name = ' '.join(results)
return filter_name(name)
return name_for
def add_page_map(opfpath, opts):
oeb = OEBBook(opfpath)
selector = XPath(opts.page, namespaces=NSMAP)
name_for = build_name_for(opts.page_names)
idgen = ("calibre-page-%d" % n for n in count(1))
for item in oeb.spine:
data = item.data
for elem in selector(data):
name = name_for(elem)
id = elem.get('id', None)
if id is None:
id = elem.attrib['id'] = idgen.next()
href = '#'.join((item.href, id))
oeb.pages.add(name, href)
writer = DirWriter(version='2.0', page_map=True)
writer.dump(oeb, opfpath)

View File

@ -9,7 +9,7 @@ directory or zip file. All the action starts in :function:`create_dir`.
''' '''
import sys, re, os, shutil, logging, tempfile, cStringIO, operator, functools import sys, re, os, shutil, logging, tempfile, cStringIO, operator, functools
from urlparse import urlparse from urlparse import urlparse, urlunparse
from urllib import unquote from urllib import unquote
from lxml import etree from lxml import etree
@ -98,7 +98,8 @@ class Link(object):
@classmethod @classmethod
def url_to_local_path(cls, url, base): def url_to_local_path(cls, url, base):
path = url.path path = urlunparse(('', '', url.path, url.params, url.query, ''))
path = unquote(path)
if os.path.isabs(path): if os.path.isabs(path):
return path return path
return os.path.abspath(os.path.join(base, path)) return os.path.abspath(os.path.join(base, path))
@ -111,11 +112,11 @@ class Link(object):
''' '''
assert isinstance(url, unicode) and isinstance(base, unicode) assert isinstance(url, unicode) and isinstance(base, unicode)
self.url = url self.url = url
self.parsed_url = urlparse(unquote(self.url)) self.parsed_url = urlparse(self.url)
self.is_local = self.parsed_url.scheme in ('', 'file') self.is_local = self.parsed_url.scheme in ('', 'file')
self.is_internal = self.is_local and not bool(self.parsed_url.path) self.is_internal = self.is_local and not bool(self.parsed_url.path)
self.path = None self.path = None
self.fragment = self.parsed_url.fragment self.fragment = unquote(self.parsed_url.fragment)
if self.is_local and not self.is_internal: if self.is_local and not self.is_internal:
self.path = self.url_to_local_path(self.parsed_url, base) self.path = self.url_to_local_path(self.parsed_url, base)
@ -594,15 +595,21 @@ class Processor(Parser):
''' '''
Populate the Table of Contents from detected chapters and links. Populate the Table of Contents from detected chapters and links.
''' '''
class Adder(object):
def add_item(href, fragment, text, target, type='link'):
for entry in toc.flat(): def __init__(self, toc):
if entry.href == href and entry.fragment == fragment: self.next_play_order = max([x.play_order for x in toc.flat()])
return entry
if len(text) > 50: def __call__(self, href, fragment, text, target, type='link'):
text = text[:50] + u'\u2026' for entry in toc.flat():
return target.add_item(href, fragment, text, type=type) if entry.href == href and entry.fragment == fragment:
return entry
if len(text) > 50:
text = text[:50] + u'\u2026'
self.next_play_order += 1
return target.add_item(href, fragment, text, type=type,
play_order=self.next_play_order)
add_item = Adder(toc)
name = self.htmlfile_map[self.htmlfile.path] name = self.htmlfile_map[self.htmlfile.path]
href = 'content/'+name href = 'content/'+name
@ -629,13 +636,15 @@ class Processor(Parser):
if self.opts.level1_toc is not None: if self.opts.level1_toc is not None:
level1 = self.opts.level1_toc(self.root) level1 = self.opts.level1_toc(self.root)
level1_order = []
if level1: if level1:
added = {} added = {}
for elem in level1: for elem in level1:
text, _href, frag = elem_to_link(elem, href, counter) text, _href, frag = elem_to_link(elem, href, counter)
counter += 1 counter += 1
if text: if text:
added[elem] = add_item(_href, frag, text, toc, type='chapter') level1_order.append(add_item(_href, frag, text, toc, type='chapter'))
added[elem] = level1_order[-1]
add_item(_href, frag, 'Top', added[elem], type='chapter') add_item(_href, frag, 'Top', added[elem], type='chapter')
if self.opts.level2_toc is not None: if self.opts.level2_toc is not None:
added2 = {} added2 = {}
@ -664,6 +673,15 @@ class Processor(Parser):
if text: if text:
add_item(_href, frag, text, level2, type='chapter') add_item(_href, frag, text, level2, type='chapter')
if level1_order: # Fix play order
next_play_order = level1_order[0].play_order
for x in level1_order:
for y in x.flat():
y.play_order = next_play_order
next_play_order += 1
if len(toc) > 0: if len(toc) > 0:
return return
@ -867,6 +885,8 @@ class Processor(Parser):
css += '\n\na { color: inherit; text-decoration: inherit; cursor: default; }\na[href] { color: blue; text-decoration: underline; cursor:pointer; }' css += '\n\na { color: inherit; text-decoration: inherit; cursor: default; }\na[href] { color: blue; text-decoration: underline; cursor:pointer; }'
if self.opts.remove_paragraph_spacing: if self.opts.remove_paragraph_spacing:
css += '\n\np {text-indent: 1.5em; margin-top:0pt; margin-bottom:0pt; padding:0pt; border:0pt;}' css += '\n\np {text-indent: 1.5em; margin-top:0pt; margin-bottom:0pt; padding:0pt; border:0pt;}'
if not self.opts.no_justification:
css += '\n\nbody {text-align: justify}'
if self.opts.override_css: if self.opts.override_css:
css += '\n\n' + self.opts.override_css css += '\n\n' + self.opts.override_css
self.override_css = self.css_parser.parseString(self.preprocess_css(css)) self.override_css = self.css_parser.parseString(self.preprocess_css(css))
@ -987,7 +1007,6 @@ def merge_metadata(htmlfile, opf, opts):
mi = get_metadata(open(htmlfile, 'rb'), 'html') mi = get_metadata(open(htmlfile, 'rb'), 'html')
except: except:
mi = MetaInformation(None, None) mi = MetaInformation(None, None)
if opts.from_opf is not None and os.access(opts.from_opf, os.R_OK): if opts.from_opf is not None and os.access(opts.from_opf, os.R_OK):
mi.smart_update(OPF(open(opts.from_opf, 'rb'), os.path.abspath(os.path.dirname(opts.from_opf)))) mi.smart_update(OPF(open(opts.from_opf, 'rb'), os.path.abspath(os.path.dirname(opts.from_opf))))
for attr in ('title', 'authors', 'publisher', 'tags', 'comments'): for attr in ('title', 'authors', 'publisher', 'tags', 'comments'):

View File

@ -23,7 +23,7 @@ from urllib import unquote as urlunquote
from lxml import etree from lxml import etree
from calibre.ebooks.lit.reader import DirectoryEntry from calibre.ebooks.lit.reader import DirectoryEntry
import calibre.ebooks.lit.maps as maps import calibre.ebooks.lit.maps as maps
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES, OEB_CSS_MIME, \ from calibre.ebooks.oeb.base import OEB_DOCS, XHTML_MIME, OEB_STYLES, \
CSS_MIME, OPF_MIME, XML_NS, XML CSS_MIME, OPF_MIME, XML_NS, XML
from calibre.ebooks.oeb.base import namespace, barename, prefixname, \ from calibre.ebooks.oeb.base import namespace, barename, prefixname, \
urlnormalize, xpath urlnormalize, xpath
@ -474,7 +474,7 @@ class LitWriter(object):
name = '/data/' + item.id name = '/data/' + item.id
data = item.data data = item.data
secnum = 0 secnum = 0
if not isinstance(data, basestring): if isinstance(data, etree._Element):
self._add_folder(name) self._add_folder(name)
rebin = ReBinary(data, item, self._oeb, map=HTML_MAP) rebin = ReBinary(data, item, self._oeb, map=HTML_MAP)
self._add_file(name + '/ahc', rebin.ahc, 0) self._add_file(name + '/ahc', rebin.ahc, 0)
@ -483,6 +483,8 @@ class LitWriter(object):
data = rebin.content data = rebin.content
name = name + '/content' name = name + '/content'
secnum = 1 secnum = 1
elif isinstance(data, unicode):
data = data.encode('utf-8')
self._add_file(name, data, secnum) self._add_file(name, data, secnum)
item.size = len(data) item.size = len(data)
@ -493,7 +495,7 @@ class LitWriter(object):
if item.spine_position is not None: if item.spine_position is not None:
key = 'linear' if item.linear else 'nonlinear' key = 'linear' if item.linear else 'nonlinear'
manifest[key].append(item) manifest[key].append(item)
elif item.media_type == CSS_MIME: elif item.media_type in OEB_STYLES:
manifest['css'].append(item) manifest['css'].append(item)
elif item.media_type in LIT_IMAGES: elif item.media_type in LIT_IMAGES:
manifest['images'].append(item) manifest['images'].append(item)
@ -506,6 +508,11 @@ class LitWriter(object):
data.write(pack('<I', len(items))) data.write(pack('<I', len(items)))
for item in items: for item in items:
id, media_type = item.id, item.media_type id, media_type = item.id, item.media_type
if media_type in OEB_DOCS:
# Needs to have 'html' in media-type
media_type = XHTML_MIME
elif media_type in OEB_STYLES:
media_type = CSS_MIME
href = urlunquote(item.href) href = urlunquote(item.href)
item.offset = offset \ item.offset = offset \
if state in ('linear', 'nonlinear') else 0 if state in ('linear', 'nonlinear') else 0
@ -525,7 +532,12 @@ class LitWriter(object):
pb3 = StringIO() pb3 = StringIO()
pb3cur = 0 pb3cur = 0
bits = 0 bits = 0
linear = []
nonlinear = []
for item in self._oeb.spine: for item in self._oeb.spine:
dest = linear if item.linear else nonlinear
dest.append(item)
for item in chain(linear, nonlinear):
page_breaks = copy.copy(item.page_breaks) page_breaks = copy.copy(item.page_breaks)
if not item.linear: if not item.linear:
page_breaks.insert(0, (0, [])) page_breaks.insert(0, (0, []))

View File

@ -214,7 +214,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', 'category', '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',
@ -247,6 +247,12 @@ class MetaInformation(object):
if getattr(mi, 'cover_data', None) and mi.cover_data[0] is not None: if getattr(mi, 'cover_data', None) and mi.cover_data[0] is not None:
self.cover_data = mi.cover_data self.cover_data = mi.cover_data
def format_series_index(self):
try:
x = float(self.series_index)
except ValueError:
x = 1.0
return '%d'%x if int(x) == x else '%.2f'%x
def __unicode__(self): def __unicode__(self):
ans = u'' ans = u''
@ -267,7 +273,7 @@ class MetaInformation(object):
if self.tags: if self.tags:
ans += u'Tags : ' + u', '.join([unicode(t) for t in self.tags]) + '\n' ans += u'Tags : ' + u', '.join([unicode(t) for t in self.tags]) + '\n'
if self.series: if self.series:
ans += u'Series : '+unicode(self.series) + ' #%d\n'%self.series_index ans += u'Series : '+unicode(self.series) + ' #%s\n'%self.format_series_index()
if self.language: if self.language:
ans += u'Language : ' + unicode(self.language) + u'\n' ans += u'Language : ' + unicode(self.language) + u'\n'
return ans.strip() return ans.strip()
@ -277,11 +283,11 @@ class MetaInformation(object):
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]))]
ans += [(_('Series'), unicode(self.series))] if self.series:
ans += [(_('Series'), unicode(self.series))+ ' #%s'%self.format_series_index()]
ans += [(_('Language'), unicode(self.language))] ans += [(_('Language'), unicode(self.language))]
for i, x in enumerate(ans): for i, x in enumerate(ans):
ans[i] = u'<tr><td><b>%s</b></td><td>%s</td></tr>'%x ans[i] = u'<tr><td><b>%s</b></td><td>%s</td></tr>'%x

View File

@ -6,9 +6,9 @@
> >
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf" xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata"> <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf" xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata">
<dc:title py:with="attrs={'opf:files-as':mi.title_sort}" py:attrs="attrs">${mi.title}</dc:title> <dc:title py:with="attrs={'opf:file-as':mi.title_sort}" py:attrs="attrs">${mi.title}</dc:title>
<dc:creator opf:role="aut" py:for="i, author in enumerate(mi.authors)" py:attrs="{'opf:file-as':mi.author_sort} if mi.author_sort and i == 0 else {}">${author}</dc:creator> <dc:creator opf:role="aut" py:for="i, author in enumerate(mi.authors)" py:attrs="{'opf:file-as':mi.author_sort} if mi.author_sort and i == 0 else {}">${author}</dc:creator>
<dc:contributor opf:role="bkp" py:with="attrs={'opf:files-as':__appname__}" py:attrs="attrs">${'%s (%s)'%(__appname__, __version__)} [http://${__appname__}.kovidgoyal.net]</dc:contributor> <dc:contributor opf:role="bkp" py:with="attrs={'opf:file-as':__appname__}" py:attrs="attrs">${'%s (%s)'%(__appname__, __version__)} [http://${__appname__}.kovidgoyal.net]</dc:contributor>
<dc:identifier opf:scheme="${__appname__}" id="${__appname__}_id">${mi.application_id}</dc:identifier> <dc:identifier opf:scheme="${__appname__}" id="${__appname__}_id">${mi.application_id}</dc:identifier>
<dc:language>${mi.language if mi.language else 'UND'}</dc:language> <dc:language>${mi.language if mi.language else 'UND'}</dc:language>

View File

@ -17,21 +17,21 @@ 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):
''' '''
Represents a resource (usually a file on the filesystem or a URL pointing Represents a resource (usually a file on the filesystem or a URL pointing
to the web. Such resources are commonly referred to in OPF files. to the web. Such resources are commonly referred to in OPF files.
They have the interface: They have the interface:
:member:`path` :member:`path`
:member:`mime_type` :member:`mime_type`
:method:`href` :method:`href`
''' '''
def __init__(self, href_or_path, basedir=os.getcwd(), is_path=True): def __init__(self, href_or_path, basedir=os.getcwd(), is_path=True):
self.orig = href_or_path self.orig = href_or_path
self._href = None self._href = None
@ -63,13 +63,13 @@ class Resource(object):
pc = pc.decode('utf-8') pc = pc.decode('utf-8')
self.path = os.path.abspath(os.path.join(basedir, pc.replace('/', os.sep))) self.path = os.path.abspath(os.path.join(basedir, pc.replace('/', os.sep)))
self.fragment = url[-1] self.fragment = url[-1]
def href(self, basedir=None): def href(self, basedir=None):
''' '''
Return a URL pointing to this resource. If it is a file on the filesystem Return a URL pointing to this resource. If it is a file on the filesystem
the URL is relative to `basedir`. the URL is relative to `basedir`.
`basedir`: If None, the basedir of this resource is used (see :method:`set_basedir`). `basedir`: If None, the basedir of this resource is used (see :method:`set_basedir`).
If this resource has no basedir, then the current working directory is used as the basedir. If this resource has no basedir, then the current working directory is used as the basedir.
''' '''
@ -91,54 +91,54 @@ class Resource(object):
if isinstance(rpath, unicode): if isinstance(rpath, unicode):
rpath = rpath.encode('utf-8') rpath = rpath.encode('utf-8')
return rpath.replace(os.sep, '/')+frag return rpath.replace(os.sep, '/')+frag
def set_basedir(self, path): def set_basedir(self, path):
self._basedir = path self._basedir = path
def basedir(self): def basedir(self):
return self._basedir return self._basedir
def __repr__(self): def __repr__(self):
return 'Resource(%s, %s)'%(repr(self.path), repr(self.href())) return 'Resource(%s, %s)'%(repr(self.path), repr(self.href()))
class ResourceCollection(object): class ResourceCollection(object):
def __init__(self): def __init__(self):
self._resources = [] self._resources = []
def __iter__(self): def __iter__(self):
for r in self._resources: for r in self._resources:
yield r yield r
def __len__(self): def __len__(self):
return len(self._resources) return len(self._resources)
def __getitem__(self, index): def __getitem__(self, index):
return self._resources[index] return self._resources[index]
def __bool__(self): def __bool__(self):
return len(self._resources) > 0 return len(self._resources) > 0
def __str__(self): def __str__(self):
resources = map(repr, self) resources = map(repr, self)
return '[%s]'%', '.join(resources) return '[%s]'%', '.join(resources)
def __repr__(self): def __repr__(self):
return str(self) return str(self)
def append(self, resource): def append(self, resource):
if not isinstance(resource, Resource): if not isinstance(resource, Resource):
raise ValueError('Can only append objects of type Resource') raise ValueError('Can only append objects of type Resource')
self._resources.append(resource) self._resources.append(resource)
def remove(self, resource): def remove(self, resource):
self._resources.remove(resource) self._resources.remove(resource)
def replace(self, start, end, items): def replace(self, start, end, items):
'Same as list[start:end] = items' 'Same as list[start:end] = items'
self._resources[start:end] = items self._resources[start:end] = items
@staticmethod @staticmethod
def from_directory_contents(top, topdown=True): def from_directory_contents(top, topdown=True):
collection = ResourceCollection() collection = ResourceCollection()
@ -148,16 +148,16 @@ class ResourceCollection(object):
res.set_basedir(top) res.set_basedir(top)
collection.append(res) collection.append(res)
return collection return collection
def set_basedir(self, path): def set_basedir(self, path):
for res in self: for res in self:
res.set_basedir(path) res.set_basedir(path)
class ManifestItem(Resource): class ManifestItem(Resource):
@staticmethod @staticmethod
def from_opf_manifest_item(item, basedir): def from_opf_manifest_item(item, basedir):
href = item.get('href', None) href = item.get('href', None)
@ -167,7 +167,7 @@ class ManifestItem(Resource):
if mt: if mt:
res.mime_type = mt res.mime_type = mt
return res return res
@apply @apply
def media_type(): def media_type():
def fget(self): def fget(self):
@ -175,18 +175,18 @@ class ManifestItem(Resource):
def fset(self, val): def fset(self, val):
self.mime_type = val self.mime_type = val
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
def __unicode__(self): def __unicode__(self):
return u'<item id="%s" href="%s" media-type="%s" />'%(self.id, self.href(), self.media_type) return u'<item id="%s" href="%s" media-type="%s" />'%(self.id, self.href(), self.media_type)
def __str__(self): def __str__(self):
return unicode(self).encode('utf-8') return unicode(self).encode('utf-8')
def __repr__(self): def __repr__(self):
return unicode(self) return unicode(self)
def __getitem__(self, index): def __getitem__(self, index):
if index == 0: if index == 0:
return self.href() return self.href()
@ -196,7 +196,7 @@ class ManifestItem(Resource):
class Manifest(ResourceCollection): class Manifest(ResourceCollection):
@staticmethod @staticmethod
def from_opf_manifest_element(items, dir): def from_opf_manifest_element(items, dir):
m = Manifest() m = Manifest()
@ -211,7 +211,7 @@ class Manifest(ResourceCollection):
except ValueError: except ValueError:
continue continue
return m return m
@staticmethod @staticmethod
def from_paths(entries): def from_paths(entries):
''' '''
@ -226,7 +226,7 @@ class Manifest(ResourceCollection):
m.next_id += 1 m.next_id += 1
m.append(mi) m.append(mi)
return m return m
def add_item(self, path, mime_type=None): def add_item(self, path, mime_type=None):
mi = ManifestItem(path, is_path=True) mi = ManifestItem(path, is_path=True)
if mime_type: if mime_type:
@ -235,37 +235,37 @@ class Manifest(ResourceCollection):
self.next_id += 1 self.next_id += 1
self.append(mi) self.append(mi)
return mi.id return mi.id
def __init__(self): def __init__(self):
ResourceCollection.__init__(self) ResourceCollection.__init__(self)
self.next_id = 1 self.next_id = 1
def item(self, id): def item(self, id):
for i in self: for i in self:
if i.id == id: if i.id == id:
return i return i
def id_for_path(self, path): def id_for_path(self, path):
path = os.path.normpath(os.path.abspath(path)) path = os.path.normpath(os.path.abspath(path))
for i in self: for i in self:
if i.path and os.path.normpath(i.path) == path: if i.path and os.path.normpath(i.path) == path:
return i.id return i.id
def path_for_id(self, id): def path_for_id(self, id):
for i in self: for i in self:
if i.id == id: if i.id == id:
return i.path return i.path
class Spine(ResourceCollection): class Spine(ResourceCollection):
class Item(Resource): class Item(Resource):
def __init__(self, idfunc, *args, **kwargs): def __init__(self, idfunc, *args, **kwargs):
Resource.__init__(self, *args, **kwargs) Resource.__init__(self, *args, **kwargs)
self.is_linear = True self.is_linear = True
self.id = idfunc(self.path) self.id = idfunc(self.path)
@staticmethod @staticmethod
def from_opf_spine_element(itemrefs, manifest): def from_opf_spine_element(itemrefs, manifest):
s = Spine(manifest) s = Spine(manifest)
@ -278,7 +278,7 @@ class Spine(ResourceCollection):
r.is_linear = itemref.get('linear', 'yes') == 'yes' r.is_linear = itemref.get('linear', 'yes') == 'yes'
s.append(r) s.append(r)
return s return s
@staticmethod @staticmethod
def from_paths(paths, manifest): def from_paths(paths, manifest):
s = Spine(manifest) s = Spine(manifest)
@ -288,14 +288,14 @@ class Spine(ResourceCollection):
except: except:
continue continue
return s return s
def __init__(self, manifest): def __init__(self, manifest):
ResourceCollection.__init__(self) ResourceCollection.__init__(self)
self.manifest = manifest self.manifest = manifest
def replace(self, start, end, ids): def replace(self, start, end, ids):
''' '''
Replace the items between start (inclusive) and end (not inclusive) with Replace the items between start (inclusive) and end (not inclusive) with
@ -308,7 +308,7 @@ class Spine(ResourceCollection):
raise ValueError('id %s not in manifest') raise ValueError('id %s not in manifest')
items.append(Spine.Item(lambda x: id, path, is_path=True)) items.append(Spine.Item(lambda x: id, path, is_path=True))
ResourceCollection.replace(start, end, items) ResourceCollection.replace(start, end, items)
def linear_items(self): def linear_items(self):
for r in self: for r in self:
if r.is_linear: if r.is_linear:
@ -318,15 +318,15 @@ class Spine(ResourceCollection):
for r in self: for r in self:
if not r.is_linear: if not r.is_linear:
yield r.path yield r.path
def items(self): def items(self):
for i in self: for i in self:
yield i.path yield i.path
class Guide(ResourceCollection): class Guide(ResourceCollection):
class Reference(Resource): class Reference(Resource):
@staticmethod @staticmethod
def from_opf_resource_item(ref, basedir): def from_opf_resource_item(ref, basedir):
title, href, type = ref.get('title', ''), ref.get('href'), ref.get('type') title, href, type = ref.get('title', ''), ref.get('href'), ref.get('type')
@ -334,14 +334,14 @@ class Guide(ResourceCollection):
res.title = title res.title = title
res.type = type res.type = type
return res return res
def __repr__(self): def __repr__(self):
ans = '<reference type="%s" href="%s" '%(self.type, self.href()) ans = '<reference type="%s" href="%s" '%(self.type, self.href())
if self.title: if self.title:
ans += 'title="%s" '%self.title ans += 'title="%s" '%self.title
return ans + '/>' return ans + '/>'
@staticmethod @staticmethod
def from_opf_guide(references, base_dir=os.getcwdu()): def from_opf_guide(references, base_dir=os.getcwdu()):
coll = Guide() coll = Guide()
@ -352,7 +352,7 @@ class Guide(ResourceCollection):
except: except:
continue continue
return coll return coll
def set_cover(self, path): def set_cover(self, path):
map(self.remove, [i for i in self if 'cover' in i.type.lower()]) map(self.remove, [i for i in self if 'cover' in i.type.lower()])
for type in ('cover', 'other.ms-coverimage-standard', 'other.ms-coverimage'): for type in ('cover', 'other.ms-coverimage-standard', 'other.ms-coverimage'):
@ -362,13 +362,13 @@ class Guide(ResourceCollection):
class MetadataField(object): class MetadataField(object):
def __init__(self, name, is_dc=True, formatter=None, none_is=None): def __init__(self, name, is_dc=True, formatter=None, none_is=None):
self.name = name self.name = name
self.is_dc = is_dc self.is_dc = is_dc
self.formatter = formatter self.formatter = formatter
self.none_is = none_is self.none_is = none_is
def __real_get__(self, obj, type=None): def __real_get__(self, obj, type=None):
ans = obj.get_metadata_element(self.name) ans = obj.get_metadata_element(self.name)
if ans is None: if ans is None:
@ -382,13 +382,13 @@ class MetadataField(object):
except: except:
return None return None
return ans return ans
def __get__(self, obj, type=None): def __get__(self, obj, type=None):
ans = self.__real_get__(obj, type) ans = self.__real_get__(obj, type)
if ans is None: if ans is None:
ans = self.none_is ans = self.none_is
return ans return ans
def __set__(self, obj, val): def __set__(self, obj, val):
elem = obj.get_metadata_element(self.name) elem = obj.get_metadata_element(self.name)
if elem is None: if elem is None:
@ -410,10 +410,11 @@ class OPF(object):
XPath = functools.partial(etree.XPath, namespaces=xpn) XPath = functools.partial(etree.XPath, namespaces=xpn)
CONTENT = XPath('self::*[re:match(name(), "meta$", "i")]/@content') CONTENT = XPath('self::*[re:match(name(), "meta$", "i")]/@content')
TEXT = XPath('string()') TEXT = XPath('string()')
metadata_path = XPath('descendant::*[re:match(name(), "metadata", "i")]') metadata_path = XPath('descendant::*[re:match(name(), "metadata", "i")]')
metadata_elem_path = XPath('descendant::*[re:match(name(), concat($name, "$"), "i") or (re:match(name(), "meta$", "i") and re:match(@name, concat("^calibre:", $name, "$"), "i"))]') metadata_elem_path = XPath('descendant::*[re:match(name(), concat($name, "$"), "i") or (re:match(name(), "meta$", "i") and re:match(@name, concat("^calibre:", $name, "$"), "i"))]')
title_path = XPath('descendant::*[re:match(name(), "title", "i")]')
authors_path = XPath('descendant::*[re:match(name(), "creator", "i") and (@role="aut" or @opf:role="aut" or (not(@role) and not(@opf:role)))]') authors_path = XPath('descendant::*[re:match(name(), "creator", "i") and (@role="aut" or @opf:role="aut" or (not(@role) and not(@opf:role)))]')
bkp_path = XPath('descendant::*[re:match(name(), "contributor", "i") and (@role="bkp" or @opf:role="bkp")]') bkp_path = XPath('descendant::*[re:match(name(), "contributor", "i") and (@role="bkp" or @opf:role="bkp")]')
tags_path = XPath('descendant::*[re:match(name(), "subject", "i")]') tags_path = XPath('descendant::*[re:match(name(), "subject", "i")]')
@ -423,10 +424,10 @@ class OPF(object):
application_id_path = XPath('descendant::*[re:match(name(), "identifier", "i") and '+ application_id_path = XPath('descendant::*[re:match(name(), "identifier", "i") and '+
'(re:match(@opf:scheme, "calibre|libprs500", "i") or re:match(@scheme, "calibre|libprs500", "i"))]') '(re:match(@opf:scheme, "calibre|libprs500", "i") or re:match(@scheme, "calibre|libprs500", "i"))]')
manifest_path = XPath('descendant::*[re:match(name(), "manifest", "i")]/*[re:match(name(), "item", "i")]') manifest_path = XPath('descendant::*[re:match(name(), "manifest", "i")]/*[re:match(name(), "item", "i")]')
manifest_ppath = XPath('descendant::*[re:match(name(), "manifest", "i")]') manifest_ppath = XPath('descendant::*[re:match(name(), "manifest", "i")]')
spine_path = XPath('descendant::*[re:match(name(), "spine", "i")]/*[re:match(name(), "itemref", "i")]') spine_path = XPath('descendant::*[re:match(name(), "spine", "i")]/*[re:match(name(), "itemref", "i")]')
guide_path = XPath('descendant::*[re:match(name(), "guide", "i")]/*[re:match(name(), "reference", "i")]') guide_path = XPath('descendant::*[re:match(name(), "guide", "i")]/*[re:match(name(), "reference", "i")]')
title = MetadataField('title') title = MetadataField('title')
publisher = MetadataField('publisher') publisher = MetadataField('publisher')
language = MetadataField('language') language = MetadataField('language')
@ -435,8 +436,8 @@ class OPF(object):
series = MetadataField('series', is_dc=False) series = MetadataField('series', is_dc=False)
series_index = MetadataField('series_index', is_dc=False, formatter=int, none_is=1) series_index = MetadataField('series_index', is_dc=False, formatter=int, none_is=1)
rating = MetadataField('rating', is_dc=False, formatter=int) rating = MetadataField('rating', is_dc=False, formatter=int)
def __init__(self, stream, basedir=os.getcwdu(), unquote_urls=True): def __init__(self, stream, basedir=os.getcwdu(), unquote_urls=True):
if not hasattr(stream, 'read'): if not hasattr(stream, 'read'):
stream = open(stream, 'rb') stream = open(stream, 'rb')
@ -463,7 +464,7 @@ class OPF(object):
self.guide = Guide.from_opf_guide(guide, basedir) if guide else None self.guide = Guide.from_opf_guide(guide, basedir) if guide else None
self.cover_data = (None, None) self.cover_data = (None, None)
self.find_toc() self.find_toc()
def find_toc(self): def find_toc(self):
self.toc = None self.toc = None
try: try:
@ -480,7 +481,7 @@ class OPF(object):
for item in self.manifest: for item in self.manifest:
if 'toc' in item.href().lower(): if 'toc' in item.href().lower():
toc = item.path toc = item.path
if toc is None: return if toc is None: return
self.toc = TOC(base_path=self.base_dir) self.toc = TOC(base_path=self.base_dir)
if toc.lower() in ('ncx', 'ncxtoc'): if toc.lower() in ('ncx', 'ncxtoc'):
@ -494,22 +495,22 @@ class OPF(object):
else: else:
self.toc.read_html_toc(toc) self.toc.read_html_toc(toc)
except: except:
pass pass
def get_text(self, elem): def get_text(self, elem):
return u''.join(self.CONTENT(elem) or self.TEXT(elem)) return u''.join(self.CONTENT(elem) or self.TEXT(elem))
def set_text(self, elem, content): def set_text(self, elem, content):
if elem.tag == self.META: if elem.tag == self.META:
elem.attib['content'] = content elem.attrib['content'] = content
else: else:
elem.text = content elem.text = content
def itermanifest(self): def itermanifest(self):
return self.manifest_path(self.root) return self.manifest_path(self.root)
def create_manifest_item(self, href, media_type): def create_manifest_item(self, href, media_type):
ids = [i.get('id', None) for i in self.itermanifest()] ids = [i.get('id', None) for i in self.itermanifest()]
id = None id = None
@ -519,11 +520,11 @@ class OPF(object):
break break
if not media_type: if not media_type:
media_type = 'application/xhtml+xml' media_type = 'application/xhtml+xml'
ans = etree.Element('{%s}item'%self.NAMESPACES['opf'], ans = etree.Element('{%s}item'%self.NAMESPACES['opf'],
attrib={'id':id, 'href':href, 'media-type':media_type}) attrib={'id':id, 'href':href, 'media-type':media_type})
ans.tail = '\n\t\t' ans.tail = '\n\t\t'
return ans return ans
def replace_manifest_item(self, item, items): def replace_manifest_item(self, item, items):
items = [self.create_manifest_item(*i) for i in items] items = [self.create_manifest_item(*i) for i in items]
for i, item2 in enumerate(items): for i, item2 in enumerate(items):
@ -532,7 +533,7 @@ class OPF(object):
index = manifest.index(item) index = manifest.index(item)
manifest[index:index+1] = items manifest[index:index+1] = items
return [i.get('id') for i in items] return [i.get('id') for i in items]
def add_path_to_manifest(self, path, media_type): def add_path_to_manifest(self, path, media_type):
has_path = False has_path = False
path = os.path.abspath(path) path = os.path.abspath(path)
@ -546,22 +547,22 @@ class OPF(object):
item = self.create_manifest_item(href, media_type) item = self.create_manifest_item(href, media_type)
manifest = self.manifest_ppath(self.root)[0] manifest = self.manifest_ppath(self.root)[0]
manifest.append(item) manifest.append(item)
def iterspine(self): def iterspine(self):
return self.spine_path(self.root) return self.spine_path(self.root)
def spine_items(self): def spine_items(self):
for item in self.iterspine(): for item in self.iterspine():
idref = item.get('idref', '') idref = item.get('idref', '')
for x in self.itermanifest(): for x in self.itermanifest():
if x.get('id', None) == idref: if x.get('id', None) == idref:
yield x.get('href', '') yield x.get('href', '')
def create_spine_item(self, idref): def create_spine_item(self, idref):
ans = etree.Element('{%s}itemref'%self.NAMESPACES['opf'], idref=idref) ans = etree.Element('{%s}itemref'%self.NAMESPACES['opf'], idref=idref)
ans.tail = '\n\t\t' ans.tail = '\n\t\t'
return ans return ans
def replace_spine_items_by_idref(self, idref, new_idrefs): def replace_spine_items_by_idref(self, idref, new_idrefs):
items = list(map(self.create_spine_item, new_idrefs)) items = list(map(self.create_spine_item, new_idrefs))
spine = self.XPath('/opf:package/*[re:match(name(), "spine", "i")]')(self.root)[0] spine = self.XPath('/opf:package/*[re:match(name(), "spine", "i")]')(self.root)[0]
@ -569,31 +570,31 @@ class OPF(object):
for x in old: for x in old:
i = spine.index(x) i = spine.index(x)
spine[i:i+1] = items spine[i:i+1] = items
def create_guide_element(self): def create_guide_element(self):
e = etree.SubElement(self.root, '{%s}guide'%self.NAMESPACES['opf']) e = etree.SubElement(self.root, '{%s}guide'%self.NAMESPACES['opf'])
e.text = '\n ' e.text = '\n '
e.tail = '\n' e.tail = '\n'
return e return e
def remove_guide(self): def remove_guide(self):
self.guide = None self.guide = None
for g in self.root.xpath('./*[re:match(name(), "guide", "i")]', namespaces={'re':'http://exslt.org/regular-expressions'}): for g in self.root.xpath('./*[re:match(name(), "guide", "i")]', namespaces={'re':'http://exslt.org/regular-expressions'}):
self.root.remove(g) self.root.remove(g)
def create_guide_item(self, type, title, href): def create_guide_item(self, type, title, href):
e = etree.Element('{%s}reference'%self.NAMESPACES['opf'], e = etree.Element('{%s}reference'%self.NAMESPACES['opf'],
type=type, title=title, href=href) type=type, title=title, href=href)
e.tail='\n' e.tail='\n'
return e return e
def add_guide_item(self, type, title, href): def add_guide_item(self, type, title, href):
g = self.root.xpath('./*[re:match(name(), "guide", "i")]', namespaces={'re':'http://exslt.org/regular-expressions'})[0] g = self.root.xpath('./*[re:match(name(), "guide", "i")]', namespaces={'re':'http://exslt.org/regular-expressions'})[0]
g.append(self.create_guide_item(type, title, href)) g.append(self.create_guide_item(type, title, href))
def iterguide(self): def iterguide(self):
return self.guide_path(self.root) return self.guide_path(self.root)
def unquote_urls(self): def unquote_urls(self):
def get_href(item): def get_href(item):
raw = unquote(item.get('href', '')) raw = unquote(item.get('href', ''))
@ -604,16 +605,16 @@ class OPF(object):
item.set('href', get_href(item)) item.set('href', get_href(item))
for item in self.iterguide(): for item in self.iterguide():
item.set('href', get_href(item)) item.set('href', get_href(item))
@apply @apply
def authors(): def authors():
def fget(self): def fget(self):
ans = [] ans = []
for elem in self.authors_path(self.metadata): for elem in self.authors_path(self.metadata):
ans.extend([x.strip() for x in self.get_text(elem).split(',')]) ans.extend([x.strip() for x in self.get_text(elem).split(',')])
return ans return ans
def fset(self, val): def fset(self, val):
remove = list(self.authors_path(self.metadata)) remove = list(self.authors_path(self.metadata))
for elem in remove: for elem in remove:
@ -622,12 +623,12 @@ class OPF(object):
attrib = {'{%s}role'%self.NAMESPACES['opf']: 'aut'} attrib = {'{%s}role'%self.NAMESPACES['opf']: 'aut'}
elem = self.create_metadata_element('creator', attrib=attrib) elem = self.create_metadata_element('creator', attrib=attrib)
self.set_text(elem, author) self.set_text(elem, author)
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
@apply @apply
def author_sort(): def author_sort():
def fget(self): def fget(self):
matches = self.authors_path(self.metadata) matches = self.authors_path(self.metadata)
if matches: if matches:
@ -637,39 +638,65 @@ class OPF(object):
ans = match.get('file-as', None) ans = match.get('file-as', None)
if ans: if ans:
return ans return ans
def fset(self, val): def fset(self, val):
matches = self.authors_path(self.metadata) matches = self.authors_path(self.metadata)
if matches: if matches:
for key in matches[0].attrib:
if key.endswith('file-as'):
matches[0].attrib.pop(key)
matches[0].set('file-as', unicode(val)) matches[0].set('file-as', unicode(val))
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
@apply
def title_sort():
def fget(self):
matches = self.title_path(self.metadata)
if matches:
for match in matches:
ans = match.get('{%s}file-as'%self.NAMESPACES['opf'], None)
if not ans:
ans = match.get('file-as', None)
if ans:
return ans
def fset(self, val):
matches = self.title_path(self.metadata)
if matches:
for key in matches[0].attrib:
if key.endswith('file-as'):
matches[0].attrib.pop(key)
matches[0].set('file-as', unicode(val))
return property(fget=fget, fset=fset)
@apply @apply
def tags(): def tags():
def fget(self): def fget(self):
ans = [] ans = []
for tag in self.tags_path(self.metadata): for tag in self.tags_path(self.metadata):
ans.append(self.get_text(tag)) ans.append(self.get_text(tag))
return ans return ans
def fset(self, val): def fset(self, val):
for tag in list(self.tags_path(self.metadata)): for tag in list(self.tags_path(self.metadata)):
self.metadata.remove(tag) self.metadata.remove(tag)
for tag in val: for tag in val:
elem = self.create_metadata_element('subject') elem = self.create_metadata_element('subject')
self.set_text(elem, unicode(tag)) self.set_text(elem, unicode(tag))
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
@apply @apply
def isbn(): def isbn():
def fget(self): def fget(self):
for match in self.isbn_path(self.metadata): for match in self.isbn_path(self.metadata):
return self.get_text(match) or None return self.get_text(match) or None
def fset(self, val): def fset(self, val):
matches = self.isbn_path(self.metadata) matches = self.isbn_path(self.metadata)
if not matches: if not matches:
@ -682,11 +709,11 @@ class OPF(object):
@apply @apply
def application_id(): def application_id():
def fget(self): def fget(self):
for match in self.application_id_path(self.metadata): for match in self.application_id_path(self.metadata):
return self.get_text(match) or None return self.get_text(match) or None
def fset(self, val): def fset(self, val):
matches = self.application_id_path(self.metadata) matches = self.application_id_path(self.metadata)
if not matches: if not matches:
@ -696,14 +723,14 @@ class OPF(object):
self.set_text(matches[0], unicode(val)) self.set_text(matches[0], unicode(val))
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
@apply @apply
def book_producer(): def book_producer():
def fget(self): def fget(self):
for match in self.bkp_path(self.metadata): for match in self.bkp_path(self.metadata):
return self.get_text(match) or None return self.get_text(match) or None
def fset(self, val): def fset(self, val):
matches = self.bkp_path(self.metadata) matches = self.bkp_path(self.metadata)
if not matches: if not matches:
@ -712,8 +739,8 @@ class OPF(object):
attrib=attrib)] attrib=attrib)]
self.set_text(matches[0], unicode(val)) self.set_text(matches[0], unicode(val))
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
def guess_cover(self): def guess_cover(self):
''' '''
Try to guess a cover. Needed for some old/badly formed OPF files. Try to guess a cover. Needed for some old/badly formed OPF files.
@ -733,11 +760,11 @@ class OPF(object):
cpath = os.access(os.path.join(self.base_dir, prefix+suffix), os.R_OK) cpath = os.access(os.path.join(self.base_dir, prefix+suffix), os.R_OK)
if os.access(os.path.join(self.base_dir, prefix+suffix), os.R_OK): if os.access(os.path.join(self.base_dir, prefix+suffix), os.R_OK):
return cpath return cpath
@apply @apply
def cover(): def cover():
def fget(self): def fget(self):
if self.guide is not None: if self.guide is not None:
for t in ('cover', 'other.ms-coverimage-standard', 'other.ms-coverimage'): for t in ('cover', 'other.ms-coverimage-standard', 'other.ms-coverimage'):
@ -748,19 +775,19 @@ class OPF(object):
return self.guess_cover() return self.guess_cover()
except: except:
pass pass
def fset(self, path): def fset(self, path):
if self.guide is not None: if self.guide is not None:
self.guide.set_cover(path) self.guide.set_cover(path)
for item in list(self.iterguide()): for item in list(self.iterguide()):
if 'cover' in item.get('type', ''): if 'cover' in item.get('type', ''):
item.getparent().remove(item) item.getparent().remove(item)
else: else:
g = self.create_guide_element() g = self.create_guide_element()
self.guide = Guide() self.guide = Guide()
self.guide.set_cover(path) self.guide.set_cover(path)
etree.SubElement(g, 'opf:reference', nsmap=self.NAMESPACES, etree.SubElement(g, 'opf:reference', nsmap=self.NAMESPACES,
attrib={'type':'cover', 'href':self.guide[-1].href()}) attrib={'type':'cover', 'href':self.guide[-1].href()})
id = self.manifest.id_for_path(self.cover) id = self.manifest.id_for_path(self.cover)
if id is None: if id is None:
@ -768,14 +795,14 @@ class OPF(object):
for item in self.guide: for item in self.guide:
if item.type.lower() == t: if item.type.lower() == t:
self.create_manifest_item(item.href(), mimetypes.guess_type(path)[0]) self.create_manifest_item(item.href(), mimetypes.guess_type(path)[0])
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
def get_metadata_element(self, name): def get_metadata_element(self, name):
matches = self.metadata_elem_path(self.metadata, name=name) matches = self.metadata_elem_path(self.metadata, name=name)
if matches: if matches:
return matches[-1] return matches[-1]
def create_metadata_element(self, name, attrib=None, is_dc=True): def create_metadata_element(self, name, attrib=None, is_dc=True):
if is_dc: if is_dc:
name = '{%s}%s' % (self.NAMESPACES['dc'], name) name = '{%s}%s' % (self.NAMESPACES['dc'], name)
@ -787,21 +814,24 @@ class OPF(object):
nsmap=self.NAMESPACES) nsmap=self.NAMESPACES)
elem.tail = '\n' elem.tail = '\n'
return elem return elem
def render(self, encoding='utf-8'): def render(self, encoding='utf-8'):
return etree.tostring(self.root, encoding='utf-8', pretty_print=True) raw = etree.tostring(self.root, encoding=encoding, pretty_print=True)
if not raw.lstrip().startswith('<?xml '):
raw = '<?xml version="1.0" encoding="%s"?>\n'%encoding.upper()+raw
return raw
def smart_update(self, mi): def smart_update(self, mi):
for attr in ('author_sort', 'title_sort', 'comments', 'category', for attr in ('title', 'authors', 'author_sort', 'title_sort',
'publisher', 'series', 'series_index', 'rating', 'publisher', 'series', 'series_index', 'rating',
'isbn', 'language', 'tags', 'title', 'authors'): 'isbn', 'language', 'tags', 'category', 'comments'):
val = getattr(mi, attr, None) val = getattr(mi, attr, None)
if val is not None and val != [] and val != (None, None): if val is not None and val != [] and val != (None, None):
setattr(self, attr, val) setattr(self, attr, val)
class OPFCreator(MetaInformation): class OPFCreator(MetaInformation):
def __init__(self, base_path, *args, **kwargs): def __init__(self, base_path, *args, **kwargs):
''' '''
Initialize. Initialize.
@ -821,63 +851,64 @@ class OPFCreator(MetaInformation):
self.guide = Guide() self.guide = Guide()
if self.cover: if self.cover:
self.guide.set_cover(self.cover) self.guide.set_cover(self.cover)
def create_manifest(self, entries): def create_manifest(self, entries):
''' '''
Create <manifest> Create <manifest>
`entries`: List of (path, mime-type) If mime-type is None it is autodetected `entries`: List of (path, mime-type) If mime-type is None it is autodetected
''' '''
entries = map(lambda x: x if os.path.isabs(x[0]) else entries = map(lambda x: x if os.path.isabs(x[0]) else
(os.path.abspath(os.path.join(self.base_path, x[0])), x[1]), (os.path.abspath(os.path.join(self.base_path, x[0])), x[1]),
entries) entries)
self.manifest = Manifest.from_paths(entries) self.manifest = Manifest.from_paths(entries)
self.manifest.set_basedir(self.base_path) self.manifest.set_basedir(self.base_path)
def create_manifest_from_files_in(self, files_and_dirs): def create_manifest_from_files_in(self, files_and_dirs):
entries = [] entries = []
def dodir(dir): def dodir(dir):
for spec in os.walk(dir): for spec in os.walk(dir):
root, files = spec[0], spec[-1] root, files = spec[0], spec[-1]
for name in files: for name in files:
path = os.path.join(root, name) path = os.path.join(root, name)
if os.path.isfile(path): if os.path.isfile(path):
entries.append((path, None)) entries.append((path, None))
for i in files_and_dirs: for i in files_and_dirs:
if os.path.isdir(i): if os.path.isdir(i):
dodir(i) dodir(i)
else: else:
entries.append((i, None)) entries.append((i, None))
self.create_manifest(entries) self.create_manifest(entries)
def create_spine(self, entries): def create_spine(self, entries):
''' '''
Create the <spine> element. Must first call :method:`create_manifest`. Create the <spine> element. Must first call :method:`create_manifest`.
`entries`: List of paths `entries`: List of paths
''' '''
entries = map(lambda x: x if os.path.isabs(x) else entries = map(lambda x: x if os.path.isabs(x) else
os.path.abspath(os.path.join(self.base_path, x)), entries) os.path.abspath(os.path.join(self.base_path, x)), entries)
self.spine = Spine.from_paths(entries, self.manifest) self.spine = Spine.from_paths(entries, self.manifest)
def set_toc(self, toc): def set_toc(self, toc):
''' '''
Set the toc. You must call :method:`create_spine` before calling this Set the toc. You must call :method:`create_spine` before calling this
method. method.
:param toc: A :class:`TOC` object :param toc: A :class:`TOC` object
''' '''
self.toc = toc self.toc = toc
def create_guide(self, guide_element): def create_guide(self, guide_element):
self.guide = Guide.from_opf_guide(guide_element, self.base_path) self.guide = Guide.from_opf_guide(guide_element, self.base_path)
self.guide.set_basedir(self.base_path) self.guide.set_basedir(self.base_path)
def render(self, opf_stream, ncx_stream=None, ncx_manifest_entry=None): def render(self, opf_stream=sys.stdout, ncx_stream=None,
ncx_manifest_entry=None):
from calibre.resources import opf_template from calibre.resources import opf_template
from calibre.utils.genshi.template import MarkupTemplate from calibre.utils.genshi.template import MarkupTemplate
template = MarkupTemplate(opf_template) template = MarkupTemplate(opf_template)
@ -910,14 +941,14 @@ class OPFCreator(MetaInformation):
class OPFTest(unittest.TestCase): class OPFTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.stream = cStringIO.StringIO( self.stream = cStringIO.StringIO(
'''\ '''\
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<package version="2.0" xmlns="http://www.idpf.org/2007/opf" > <package version="2.0" xmlns="http://www.idpf.org/2007/opf" >
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf"> <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
<dc:title>A Cool &amp; &copy; &#223; Title</dc:title> <dc:title opf:file-as="Wow">A Cool &amp; &copy; &#223; Title</dc:title>
<creator opf:role="aut" file-as="Monkey">Monkey Kitchen, Next</creator> <creator opf:role="aut" file-as="Monkey">Monkey Kitchen, Next</creator>
<dc:subject>One</dc:subject><dc:subject>Two</dc:subject> <dc:subject>One</dc:subject><dc:subject>Two</dc:subject>
<dc:identifier scheme="ISBN">123456789</dc:identifier> <dc:identifier scheme="ISBN">123456789</dc:identifier>
@ -932,34 +963,51 @@ class OPFTest(unittest.TestCase):
''' '''
) )
self.opf = OPF(self.stream, os.getcwd()) self.opf = OPF(self.stream, os.getcwd())
def testReading(self): def testReading(self, opf=None):
opf = self.opf if opf is None:
opf = self.opf
self.assertEqual(opf.title, u'A Cool & \xa9 \xdf Title') self.assertEqual(opf.title, u'A Cool & \xa9 \xdf Title')
self.assertEqual(opf.authors, u'Monkey Kitchen,Next'.split(',')) self.assertEqual(opf.authors, u'Monkey Kitchen,Next'.split(','))
self.assertEqual(opf.author_sort, 'Monkey') self.assertEqual(opf.author_sort, 'Monkey')
self.assertEqual(opf.title_sort, 'Wow')
self.assertEqual(opf.tags, ['One', 'Two']) self.assertEqual(opf.tags, ['One', 'Two'])
self.assertEqual(opf.isbn, '123456789') self.assertEqual(opf.isbn, '123456789')
self.assertEqual(opf.series, 'A one book series') self.assertEqual(opf.series, 'A one book series')
self.assertEqual(opf.series_index, None) self.assertEqual(opf.series_index, 1)
self.assertEqual(list(opf.itermanifest())[0].get('href'), 'a ~ b') self.assertEqual(list(opf.itermanifest())[0].get('href'), 'a ~ b')
def testWriting(self): def testWriting(self):
for test in [('title', 'New & Title'), ('authors', ['One', 'Two']), for test in [('title', 'New & Title'), ('authors', ['One', 'Two']),
('author_sort', "Kitchen"), ('tags', ['Three']), ('author_sort', "Kitchen"), ('tags', ['Three']),
('isbn', 'a'), ('rating', 3), ('series_index', 1)]: ('isbn', 'a'), ('rating', 3), ('series_index', 1),
('title_sort', 'ts')]:
setattr(self.opf, *test) setattr(self.opf, *test)
self.assertEqual(getattr(self.opf, test[0]), test[1]) attr, val = test
self.assertEqual(getattr(self.opf, attr), val)
self.opf.render() self.opf.render()
def testCreator(self):
opf = OPFCreator(os.getcwd(), self.opf)
buf = cStringIO.StringIO()
opf.render(buf)
raw = buf.getvalue()
self.testReading(opf=OPF(cStringIO.StringIO(raw), os.getcwd()))
def testSmartUpdate(self):
self.opf.smart_update(MetaInformation(self.opf))
self.testReading()
def suite(): def suite():
return unittest.TestLoader().loadTestsFromTestCase(OPFTest) return unittest.TestLoader().loadTestsFromTestCase(OPFTest)
def test(): def test():
unittest.TextTestRunner(verbosity=2).run(suite()) unittest.TextTestRunner(verbosity=2).run(suite())
def option_parser(): def option_parser():
from calibre.ebooks.metadata import get_parser
parser = get_parser('opf') parser = get_parser('opf')
parser.add_option('--language', default=None, help=_('Set the dc:language field')) parser.add_option('--language', default=None, help=_('Set the dc:language field'))
return parser return parser
@ -1006,7 +1054,7 @@ def main(args=sys.argv):
print MetaInformation(OPF(open(opfpath, 'rb'), basedir)) print MetaInformation(OPF(open(opfpath, 'rb'), basedir))
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(main()) sys.exit(main())

View File

@ -138,7 +138,8 @@ class MobiMLizer(object):
return result return result
def mobimlize_content(self, tag, text, bstate, istates): def mobimlize_content(self, tag, text, bstate, istates):
bstate.content = True if text or tag != 'br':
bstate.content = True
istate = istates[-1] istate = istates[-1]
para = bstate.para para = bstate.para
if tag in SPECIAL_TAGS and not text: if tag in SPECIAL_TAGS and not text:
@ -188,11 +189,6 @@ class MobiMLizer(object):
vspace -= 1 vspace -= 1
if istate.halign != 'auto': if istate.halign != 'auto':
para.attrib['align'] = istate.halign para.attrib['align'] = istate.halign
if istate.ids:
last = bstate.body[-1]
for id in istate.ids:
last.addprevious(etree.Element(XHTML('a'), attrib={'id': id}))
istate.ids.clear()
pstate = bstate.istate pstate = bstate.istate
if tag in CONTENT_TAGS: if tag in CONTENT_TAGS:
bstate.inline = para bstate.inline = para
@ -200,6 +196,11 @@ class MobiMLizer(object):
etree.SubElement(para, XHTML(tag), attrib=istate.attrib) etree.SubElement(para, XHTML(tag), attrib=istate.attrib)
elif tag in TABLE_TAGS: elif tag in TABLE_TAGS:
para.attrib['valign'] = 'top' para.attrib['valign'] = 'top'
if istate.ids:
last = bstate.body[-1]
for id in istate.ids:
last.addprevious(etree.Element(XHTML('a'), attrib={'id': id}))
istate.ids.clear()
if not text: if not text:
return return
if not pstate or istate != pstate: if not pstate or istate != pstate:

View File

@ -121,6 +121,7 @@ class BookHeader(object):
sublangid = (langcode >> 10) & 0xFF sublangid = (langcode >> 10) & 0xFF
self.language = main_language.get(langid, 'ENGLISH') self.language = main_language.get(langid, 'ENGLISH')
self.sublanguage = sub_language.get(sublangid, 'NEUTRAL') self.sublanguage = sub_language.get(sublangid, 'NEUTRAL')
self.mobi_version = struct.unpack('>I', raw[0x68:0x6c])[0]
self.first_image_index = struct.unpack('>L', raw[0x6c:0x6c+4])[0] self.first_image_index = struct.unpack('>L', raw[0x6c:0x6c+4])[0]
self.exth_flag, = struct.unpack('>L', raw[0x80:0x84]) self.exth_flag, = struct.unpack('>L', raw[0x80:0x84])
@ -132,11 +133,8 @@ class BookHeader(object):
class MobiReader(object): class MobiReader(object):
PAGE_BREAK_PAT = re.compile(r'(<[/]{0,1}mbp:pagebreak\s*[/]{0,1}>)+', re.IGNORECASE) PAGE_BREAK_PAT = re.compile(r'(<[/]{0,1}mbp:pagebreak\s*[/]{0,1}>)+', re.IGNORECASE)
IMAGE_PATS = map(re.compile, (r'\shirecindex=[\'"]{0,1}(\d+)[\'"]{0,1}', IMAGE_ATTRS = ('lowrecindex', 'recindex', 'hirecindex')
r'\srecindex=[\'"]{0,1}(\d+)[\'"]{0,1}',
r'\slorecindex=[\'"]{0,1}(\d+)[\'"]{0,1}'))
def __init__(self, filename_or_stream, verbose=False): def __init__(self, filename_or_stream, verbose=False):
self.verbose = verbose self.verbose = verbose
@ -247,6 +245,7 @@ class MobiReader(object):
self.processed_html = re.sub(r'<div height="0(pt|px|ex|em|%){0,1}"></div>', '', self.processed_html) self.processed_html = re.sub(r'<div height="0(pt|px|ex|em|%){0,1}"></div>', '', self.processed_html)
if self.book_header.ancient and '<html' not in self.mobi_html[:300].lower(): if self.book_header.ancient and '<html' not in self.mobi_html[:300].lower():
self.processed_html = '<html><p>'+self.processed_html.replace('\n\n', '<p>')+'</html>' self.processed_html = '<html><p>'+self.processed_html.replace('\n\n', '<p>')+'</html>'
self.processed_html = self.processed_html.replace('\r\n', '\n')
self.processed_html = self.processed_html.replace('> <', '>\n<') self.processed_html = self.processed_html.replace('> <', '>\n<')
for t, c in [('b', 'bold'), ('i', 'italic')]: for t, c in [('b', 'bold'), ('i', 'italic')]:
self.processed_html = re.sub(r'(?i)<%s>'%t, r'<span class="%s">'%c, self.processed_html) self.processed_html = re.sub(r'(?i)<%s>'%t, r'<span class="%s">'%c, self.processed_html)
@ -264,6 +263,7 @@ class MobiReader(object):
'x-large' : '5', 'x-large' : '5',
'xx-large' : '6', 'xx-large' : '6',
} }
mobi_version = self.book_header.mobi_version
for tag in root.iter(etree.Element): for tag in root.iter(etree.Element):
if tag.tag in ('country-region', 'place', 'placetype', 'placename', if tag.tag in ('country-region', 'place', 'placetype', 'placename',
'state', 'city'): 'state', 'city'):
@ -290,6 +290,11 @@ class MobiReader(object):
align = attrib.pop('align').strip() align = attrib.pop('align').strip()
if align: if align:
styles.append('text-align: %s' % align) styles.append('text-align: %s' % align)
if mobi_version == 1 and tag.tag == 'hr':
tag.tag = 'div'
styles.append('page-break-before: always')
styles.append('display: block')
styles.append('margin: 0')
if styles: if styles:
attrib['style'] = '; '.join(styles) attrib['style'] = '; '.join(styles)
@ -300,6 +305,17 @@ class MobiReader(object):
except ValueError: except ValueError:
if sz in size_map.keys(): if sz in size_map.keys():
attrib['size'] = size_map[sz] attrib['size'] = size_map[sz]
if 'filepos-id' in attrib:
attrib['id'] = attrib.pop('filepos-id')
if 'filepos' in attrib:
filepos = int(attrib.pop('filepos'))
attrib['href'] = "#filepos%d" % filepos
if tag.tag == 'img':
recindex = None
for attr in self.IMAGE_ATTRS:
recindex = attrib.pop(attr, None) or recindex
if recindex is not None:
attrib['src'] = 'images/%s.jpg' % recindex
def create_opf(self, htmlfile, guide=None): def create_opf(self, htmlfile, guide=None):
mi = self.book_header.exth.mi mi = self.book_header.exth.mi
@ -399,38 +415,40 @@ class MobiReader(object):
def replace_page_breaks(self): def replace_page_breaks(self):
self.processed_html = self.PAGE_BREAK_PAT.sub('<br style="page-break-after:always" />', self.processed_html = self.PAGE_BREAK_PAT.sub(
self.processed_html) '<div style="page-break-after: always; margin: 0; display: block" />',
self.processed_html)
def add_anchors(self): def add_anchors(self):
if self.verbose: if self.verbose:
print 'Adding anchors...' print 'Adding anchors...'
positions = set([]) positions = set([])
link_pattern = re.compile(r'<[^<>]+filepos=[\'"]{0,1}(\d+)[^<>]*>', re.IGNORECASE) link_pattern = re.compile(r'''<[^<>]+filepos=['"]{0,1}(\d+)[^<>]*>''',
re.IGNORECASE)
for match in link_pattern.finditer(self.mobi_html): for match in link_pattern.finditer(self.mobi_html):
positions.add(int(match.group(1))) positions.add(int(match.group(1)))
positions = list(positions)
positions.sort()
pos = 0 pos = 0
self.processed_html = '' self.processed_html = ''
for end in positions: end_tag_re = re.compile(r'<\s*/')
for end in sorted(positions):
if end == 0: if end == 0:
continue continue
oend = end oend = end
l = self.mobi_html.find('<', end) l = self.mobi_html.find('<', end)
r = self.mobi_html.find('>', end) r = self.mobi_html.find('>', end)
if r > -1 and r < l: # Move out of tag anchor = '<a id="filepos%d"></a>'
end = r+1 if r > -1 and (r < l or l == end or l == -1):
self.processed_html += self.mobi_html[pos:end] + '<a id="filepos%d" name="filepos%d"></a>'%(oend, oend) p = self.mobi_html.rfind('<', 0, end + 1)
if pos < end and p > -1 and \
not end_tag_re.match(self.mobi_html[p:r]):
anchor = ' filepos-id="filepos%d"'
end = r
else:
end = r + 1
self.processed_html += self.mobi_html[pos:end] + (anchor % oend)
pos = end pos = end
self.processed_html += self.mobi_html[pos:] self.processed_html += self.mobi_html[pos:]
fpat = re.compile(r'filepos=[\'"]{0,1}(\d+)[\'"]{0,1}', re.IGNORECASE)
def fpos_to_href(match):
return fpat.sub('href="#filepos%d"'%int(match.group(1)), match.group())
self.processed_html = link_pattern.sub(fpos_to_href,
self.processed_html)
def extract_images(self, processed_records, output_dir): def extract_images(self, processed_records, output_dir):
if self.verbose: if self.verbose:
print 'Extracting images...' print 'Extracting images...'
@ -454,19 +472,6 @@ class MobiReader(object):
path = os.path.join(output_dir, '%05d.jpg'%image_index) path = os.path.join(output_dir, '%05d.jpg'%image_index)
self.image_names.append(os.path.basename(path)) self.image_names.append(os.path.basename(path))
im.convert('RGB').save(open(path, 'wb'), format='JPEG') im.convert('RGB').save(open(path, 'wb'), format='JPEG')
def fix_images(match):
tag = match.group()
for pat in self.IMAGE_PATS:
m = pat.search(tag)
if m:
return pat.sub(' src="images/%s.jpg"'%m.group(1), tag)
if hasattr(self, 'processed_html'):
self.processed_html = \
re.compile(r'<img (.*?)>', re.IGNORECASE|re.DOTALL)\
.sub(fix_images, self.processed_html)
def get_metadata(stream): def get_metadata(stream):
mr = MobiReader(stream) mr = MobiReader(stream)

File diff suppressed because it is too large Load Diff

View File

@ -109,6 +109,7 @@ class Stylizer(object):
STYLESHEETS = {} STYLESHEETS = {}
def __init__(self, tree, path, oeb, profile=PROFILES['PRS505']): def __init__(self, tree, path, oeb, profile=PROFILES['PRS505']):
self.oeb = oeb
self.profile = profile self.profile = profile
self.logger = oeb.logger self.logger = oeb.logger
item = oeb.manifest.hrefs[path] item = oeb.manifest.hrefs[path]
@ -117,7 +118,7 @@ class Stylizer(object):
stylesheets = [HTML_CSS_STYLESHEET] stylesheets = [HTML_CSS_STYLESHEET]
head = xpath(tree, '/h:html/h:head')[0] head = xpath(tree, '/h:html/h:head')[0]
parser = cssutils.CSSParser() parser = cssutils.CSSParser()
parser.setFetcher(lambda path: ('utf-8', oeb.container.read(path))) parser.setFetcher(self._fetch_css_file)
for elem in head: for elem in head:
if elem.tag == XHTML('style') and elem.text \ if elem.tag == XHTML('style') and elem.text \
and elem.get('type', CSS_MIME) in OEB_STYLES: and elem.get('type', CSS_MIME) in OEB_STYLES:
@ -138,8 +139,7 @@ class Stylizer(object):
if path in self.STYLESHEETS: if path in self.STYLESHEETS:
stylesheet = self.STYLESHEETS[path] stylesheet = self.STYLESHEETS[path]
else: else:
data = XHTML_CSS_NAMESPACE data = self._fetch_css_file(path)[1]
data += oeb.manifest.hrefs[path].data
stylesheet = parser.parseString(data, href=path) stylesheet = parser.parseString(data, href=path)
stylesheet.namespaces['h'] = XHTML_NS stylesheet.namespaces['h'] = XHTML_NS
self.STYLESHEETS[path] = stylesheet self.STYLESHEETS[path] = stylesheet
@ -167,6 +167,14 @@ class Stylizer(object):
for elem in xpath(tree, '//h:*[@style]'): for elem in xpath(tree, '//h:*[@style]'):
self.style(elem)._apply_style_attr() self.style(elem)._apply_style_attr()
def _fetch_css_file(self, path):
hrefs = self.oeb.manifest.hrefs
if path not in hrefs:
return (None, None)
data = hrefs[path].data
data = XHTML_CSS_NAMESPACE + data
return ('utf-8', data)
def flatten_rule(self, rule, href, index): def flatten_rule(self, rule, href, index):
results = [] results = []
if isinstance(rule, CSSStyleRule): if isinstance(rule, CSSStyleRule):

View File

@ -13,13 +13,9 @@ 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 LINK_SELECTORS, CSSURL_RE
from calibre.ebooks.oeb.base import urlnormalize from calibre.ebooks.oeb.base import urlnormalize
LINK_SELECTORS = []
for expr in ('//h:link/@href', '//h:img/@src', '//h:object/@data',
'//*/@xl:href'):
LINK_SELECTORS.append(etree.XPath(expr, namespaces=XPNSMAP))
class ManifestTrimmer(object): class ManifestTrimmer(object):
def transform(self, oeb, context): def transform(self, oeb, context):
oeb.logger.info('Trimming unused files from manifest...') oeb.logger.info('Trimming unused files from manifest...')
@ -53,15 +49,13 @@ class ManifestTrimmer(object):
if found not in used: if found not in used:
new.add(found) new.add(found)
elif item.media_type == CSS_MIME: elif item.media_type == CSS_MIME:
def replacer(uri): for match in CSSURL_RE.finditer(item.data):
absuri = item.abshref(urlnormalize(uri)) href = match.group('url')
if absuri in oeb.manifest.hrefs: href = item.abshref(urlnormalize(href))
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:
new.add(found) new.add(found)
return uri
sheet = cssutils.parseString(item.data, href=item.href)
cssutils.replaceUrls(sheet, replacer)
used.update(new) used.update(new)
unchecked = new unchecked = new
for item in oeb.manifest.values(): for item in oeb.manifest.values():

View File

@ -18,7 +18,7 @@ from calibre.gui2 import error_dialog, choose_images, pixmap_to_data, ResizableD
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
from calibre.ebooks.metadata.opf import OPFCreator from calibre.ebooks.metadata.opf2 import OPFCreator
from calibre.ebooks.metadata import authors_to_string, string_to_authors from calibre.ebooks.metadata import authors_to_string, string_to_authors
@ -224,6 +224,7 @@ class Config(ResizableDialog, Ui_Dialog):
g.setValue(val) g.setValue(val)
elif isinstance(g, (QLineEdit, QTextEdit)): elif isinstance(g, (QLineEdit, QTextEdit)):
getattr(g, 'setPlainText', g.setText)(val) getattr(g, 'setPlainText', g.setText)(val)
getattr(g, 'setCursorPosition', lambda x: x)(0)
elif isinstance(g, QComboBox): elif isinstance(g, QComboBox):
for value in pref.choices: for value in pref.choices:
g.addItem(value) g.addItem(value)
@ -253,7 +254,8 @@ class Config(ResizableDialog, Ui_Dialog):
self.source_format = d.format() self.source_format = d.format()
def accept(self): def accept(self):
for opt in ('chapter', 'level1_toc', 'level2_toc', 'level3_toc'): for opt in ('chapter', 'level1_toc', 'level2_toc', 'level3_toc', 'page',
'page_names'):
text = unicode(getattr(self, 'opt_'+opt).text()) text = unicode(getattr(self, 'opt_'+opt).text())
if text: if text:
try: try:

View File

@ -493,6 +493,20 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="0" >
<widget class="QCheckBox" name="opt_no_justification" >
<property name="text" >
<string>No text &amp;justification</string>
</property>
</widget>
</item>
<item row="8" column="0" >
<widget class="QCheckBox" name="opt_linearize_tables" >
<property name="text" >
<string>&amp;Linearize tables</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>
@ -510,7 +524,7 @@
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="pagesetup_page" > <widget class="QWidget" name="pagesetup_page" >
<layout class="QGridLayout" name="_13" > <layout class="QGridLayout" name="gridLayout_7" >
<item row="0" column="0" > <item row="0" column="0" >
<widget class="QLabel" name="profile_label" > <widget class="QLabel" name="profile_label" >
<property name="text" > <property name="text" >
@ -531,6 +545,32 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0" >
<widget class="QLabel" name="source_profile_label" >
<property name="text" >
<string>&amp;Source profile:</string>
</property>
<property name="buddy" >
<cstring>opt_source_profile</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QComboBox" name="opt_source_profile" />
</item>
<item row="2" column="0" >
<widget class="QLabel" name="dest_profile_label" >
<property name="text" >
<string>&amp;Destination profile:</string>
</property>
<property name="buddy" >
<cstring>opt_dest_profile</cstring>
</property>
</widget>
</item>
<item row="2" column="1" >
<widget class="QComboBox" name="opt_dest_profile" />
</item>
<item row="3" column="0" > <item row="3" column="0" >
<widget class="QLabel" name="label_12" > <widget class="QLabel" name="label_12" >
<property name="text" > <property name="text" >
@ -630,31 +670,72 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0" > <item row="8" column="0" colspan="2" >
<widget class="QLabel" name="source_profile_label" > <widget class="QGroupBox" name="page_map_box" >
<property name="text" > <property name="title" >
<string>&amp;Source profile:</string> <string>&amp;Page map</string>
</property>
<property name="buddy" >
<cstring>opt_source_profile</cstring>
</property> </property>
<layout class="QGridLayout" name="gridLayout" >
<item rowspan="2" row="0" column="0" colspan="4" >
<widget class="QLabel" name="label_23" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Minimum" hsizetype="Preferred" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text" >
<string>&lt;p>You can control how calibre detects page boundaries using a XPath expression. To learn how to use XPath expressions see the &lt;a href="http://calibre.kovidgoyal.net/user_manual/xpath.html">XPath tutorial&lt;/a>. The page boundaries are useful only if you want a mapping from pages in a paper book, to locations in the e-book. This controls where Adobe Digital Editions displays the page numbers in the right margin.&lt;/p></string>
</property>
<property name="wordWrap" >
<bool>true</bool>
</property>
<property name="openExternalLinks" >
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_21" >
<property name="text" >
<string>&amp;Boundary XPath:</string>
</property>
<property name="buddy" >
<cstring>opt_page</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QLineEdit" name="opt_page" />
</item>
<item row="1" column="2" >
<widget class="QLabel" name="label_22" >
<property name="text" >
<string>&amp;Name XPath:</string>
</property>
<property name="buddy" >
<cstring>opt_page_names</cstring>
</property>
</widget>
</item>
<item row="1" column="3" >
<widget class="QLineEdit" name="opt_page_names" />
</item>
</layout>
</widget> </widget>
</item> </item>
<item row="1" column="1" > <item row="9" column="0" >
<widget class="QComboBox" name="opt_source_profile" /> <spacer name="verticalSpacer" >
</item> <property name="orientation" >
<item row="2" column="0" > <enum>Qt::Vertical</enum>
<widget class="QLabel" name="dest_profile_label" >
<property name="text" >
<string>&amp;Destination profile:</string>
</property> </property>
<property name="buddy" > <property name="sizeHint" stdset="0" >
<cstring>opt_dest_profile</cstring> <size>
<width>20</width>
<height>40</height>
</size>
</property> </property>
</widget> </spacer>
</item>
<item row="2" column="1" >
<widget class="QComboBox" name="opt_dest_profile" />
</item> </item>
</layout> </layout>
</widget> </widget>
@ -665,28 +746,11 @@
<property name="title" > <property name="title" >
<string>Automatic &amp;chapter detection</string> <string>Automatic &amp;chapter detection</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout" > <layout class="QVBoxLayout" name="verticalLayout_4" >
<item row="1" column="0" > <item>
<widget class="QLabel" name="label_17" >
<property name="text" >
<string>&amp;XPath:</string>
</property>
<property name="buddy" >
<cstring>opt_chapter</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QLineEdit" name="opt_chapter" />
</item>
<item row="0" column="0" colspan="2" >
<widget class="QLabel" name="label_8" > <widget class="QLabel" name="label_8" >
<property name="text" > <property name="text" >
<string>&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <string>&lt;p>You can control how calibre detects chapters using a XPath expression. To learn how to use XPath expressions see the &lt;a href="http://calibre.kovidgoyal.net/user_manual/xpath.html">XPath tutorial&lt;/a>&lt;/p></string>
&lt;html>&lt;head>&lt;meta name="qrichtext" content="1" />&lt;style type="text/css">
p, li { white-space: pre-wrap; }
&lt;/style>&lt;/head>&lt;body style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal;">
&lt;p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">You can control how calibre detects chapters using a XPath expression. To learn how to use XPath expressions see the &lt;a href="https://calibre.kovidgoyal.net/user_manual/xpath.html">&lt;span style=" text-decoration: underline; color:#0000ff;">XPath tutorial&lt;/span>&lt;/a>&lt;/p>&lt;/body>&lt;/html></string>
</property> </property>
<property name="textFormat" > <property name="textFormat" >
<enum>Qt::RichText</enum> <enum>Qt::RichText</enum>
@ -694,21 +758,41 @@ p, li { white-space: pre-wrap; }
<property name="wordWrap" > <property name="wordWrap" >
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> <property name="openExternalLinks" >
</item> <bool>true</bool>
<item row="2" column="1" >
<widget class="QComboBox" name="opt_chapter_mark" />
</item>
<item row="2" column="0" >
<widget class="QLabel" name="label_9" >
<property name="text" >
<string>Chapter &amp;mark:</string>
</property>
<property name="buddy" >
<cstring>opt_chapter_mark</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3" >
<item>
<widget class="QLabel" name="label_17" >
<property name="text" >
<string>&amp;XPath:</string>
</property>
<property name="buddy" >
<cstring>opt_chapter</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="opt_chapter" />
</item>
<item>
<widget class="QLabel" name="label_9" >
<property name="text" >
<string>Chapter &amp;mark:</string>
</property>
<property name="buddy" >
<cstring>opt_chapter_mark</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="opt_chapter_mark" />
</item>
</layout>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -834,7 +918,7 @@ p, li { white-space: pre-wrap; }
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" colspan="2" > <item row="3" column="0" colspan="2" >
<widget class="QDialogButtonBox" name="buttonBox" > <widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" > <property name="orientation" >
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>

View File

@ -17,4 +17,7 @@ class Config(_Config):
self.profile_label.setVisible(False) self.profile_label.setVisible(False)
self.opt_profile.setVisible(False) self.opt_profile.setVisible(False)
self.opt_dont_split_on_page_breaks.setVisible(False) self.opt_dont_split_on_page_breaks.setVisible(False)
self.opt_preserve_tag_structure.setVisible(False) self.opt_preserve_tag_structure.setVisible(False)
self.opt_linearize_tables.setVisible(False)
self.opt_no_justification.setVisible(False)
self.page_map_box.setVisible(False)

View File

@ -1,16 +0,0 @@
__license__ = 'GPL v3'
__copyright__ = '2009, John Schember john@nachtimwald.com'
'''
List View for showing recipies. Allows for keyboad events when selecting new
items.
'''
from PyQt4.Qt import QListView, SIGNAL
class RecipeListView(QListView):
def __init__(self, *args):
QListView.__init__(self, *args)
def selectionChanged(self, selected, deselected):
self.emit(SIGNAL('itemChanged(QModelIndex)'), selected.indexes()[0])

View File

@ -10,8 +10,8 @@ Scheduler for automated recipe downloads
import sys, copy, time import sys, copy, time
from datetime import datetime, timedelta, date from datetime import datetime, timedelta, date
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, QAbstractItemModel, Qt, QVariant, QFont, QIcon, \
QFile, QObject, QTimer, QMutex, QMenu, QAction, QTime QFile, QObject, QTimer, QMutex, QMenu, QAction, QTime, QModelIndex
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
@ -30,6 +30,7 @@ class Recipe(object):
self.id = id self.id = id
self.title = getattr(recipe_class, 'title', None) self.title = getattr(recipe_class, 'title', None)
self.description = getattr(recipe_class, 'description', None) self.description = getattr(recipe_class, 'description', None)
self.language = getattr(recipe_class, 'language', _('Unknown'))
self.last_downloaded = datetime.fromordinal(1) self.last_downloaded = datetime.fromordinal(1)
self.downloading = False self.downloading = False
self.builtin = builtin self.builtin = builtin
@ -86,12 +87,12 @@ def load_recipes():
recipes.append(r) recipes.append(r)
return recipes return recipes
class RecipeModel(QAbstractListModel, SearchQueryParser): class RecipeModel(QAbstractItemModel, SearchQueryParser):
LOCATIONS = ['all'] LOCATIONS = ['all']
def __init__(self, db, *args): def __init__(self, db, *args):
QAbstractListModel.__init__(self, *args) QAbstractItemModel.__init__(self, *args)
SearchQueryParser.__init__(self) SearchQueryParser.__init__(self)
self.default_icon = QIcon(':/images/news.svg') self.default_icon = QIcon(':/images/news.svg')
self.custom_icon = QIcon(':/images/user_profile.svg') self.custom_icon = QIcon(':/images/user_profile.svg')
@ -99,8 +100,11 @@ class RecipeModel(QAbstractListModel, SearchQueryParser):
for x in db.get_recipes(): for x in db.get_recipes():
recipe = compile_recipe(x[1]) recipe = compile_recipe(x[1])
self.recipes.append(Recipe(x[0], recipe, False)) self.recipes.append(Recipe(x[0], recipe, False))
self.refresh() self.refresh()
self._map = list(range(len(self.recipes))) self.bold_font = QFont()
self.bold_font.setBold(True)
self.bold_font = QVariant(self.bold_font)
def refresh(self): def refresh(self):
sr = load_recipes() sr = load_recipes()
@ -110,7 +114,35 @@ class RecipeModel(QAbstractListModel, SearchQueryParser):
recipe.last_downloaded = sr[sr.index(recipe)].last_downloaded recipe.last_downloaded = sr[sr.index(recipe)].last_downloaded
self.recipes.sort() self.recipes.sort()
self.num_of_recipes = len(self.recipes)
self.category_map = {}
for r in self.recipes:
category = getattr(r, 'language', _('Unknown'))
if not r.builtin:
category = _('Custom')
if r.schedule is not None:
category = _('Scheduled')
if category not in self.category_map.keys():
self.category_map[category] = []
self.category_map[category].append(r)
self.categories = sorted(self.category_map.keys(), cmp=self.sort_categories)
self._map = dict(self.category_map)
def sort_categories(self, x, y):
def decorate(x):
if x == _('Scheduled'):
x = '0' + x
elif x == _('Custom'):
x = '1' + x
else:
x = '2' + x
return x
return cmp(decorate(x), decorate(y))
def universal_set(self): def universal_set(self):
return set(self.recipes) return set(self.recipes)
@ -129,48 +161,64 @@ class RecipeModel(QAbstractListModel, SearchQueryParser):
try: try:
results = self.parse(unicode(query)) results = self.parse(unicode(query))
except ParseException: except ParseException:
self._map = list(range(len(self.recipes))) self._map = dict(self.category_map)
else: else:
self._map = [] self._map = {}
for i, recipe in enumerate(self.recipes): for category in self.categories:
if recipe in results: self._map[category] = []
self._map.append(i) for recipe in self.category_map[category]:
if recipe in results:
self._map[category].append(recipe)
self.reset() self.reset()
def resort(self): def resort(self):
self.recipes.sort() self.recipes.sort()
self.reset() self.reset()
def index(self, row, column, parent):
return self.createIndex(row, column, parent.row() if parent.isValid() else -1)
def parent(self, index):
if index.internalId() == -1:
return QModelIndex()
return self.createIndex(index.internalId(), 0, -1)
def columnCount(self, parent):
if not parent.isValid() or not parent.parent().isValid():
return 1
return 0
def rowCount(self, parent):
if not parent.isValid():
return len(self.categories)
if not parent.parent().isValid():
category = self.categories[parent.row()]
return len(self._map[category])
return 0
def columnCount(self, *args):
return 1
def rowCount(self, *args):
return len(self._map)
def data(self, index, role): def data(self, index, role):
recipe = self.recipes[self._map[index.row()]] if index.parent().isValid():
if role == Qt.FontRole: category = self.categories[index.parent().row()]
if recipe.schedule is not None: recipe = self._map[category][index.row()]
font = QFont() if role == Qt.DisplayRole:
font.setBold(True) return QVariant(recipe.title)
return QVariant(font) elif role == Qt.UserRole:
if not recipe.builtin: return recipe
font = QFont() elif role == Qt.DecorationRole:
font.setItalic(True) icon = self.default_icon
return QVariant(font) icon_path = (':/images/news/%s.png'%recipe.id).replace('recipe_', '')
elif role == Qt.DisplayRole: if not recipe.builtin:
return QVariant(recipe.title) icon = self.custom_icon
elif role == Qt.UserRole: elif QFile().exists(icon_path):
return recipe icon = QIcon(icon_path)
elif role == Qt.DecorationRole: return QVariant(icon)
icon = self.default_icon else:
icon_path = (':/images/news/%s.png'%recipe.id).replace('recipe_', '') category = self.categories[index.row()]
if not recipe.builtin: if role == Qt.DisplayRole:
icon = self.custom_icon num = len(self._map[category])
elif QFile().exists(icon_path): return QVariant(category + ' [%d]'%num)
icon = QIcon(icon_path) elif role == Qt.FontRole:
return QVariant(icon) return self.bold_font
return NONE return NONE
def update_recipe_schedule(self, recipe): def update_recipe_schedule(self, recipe):
@ -241,7 +289,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self._model = RecipeModel(db) self._model = RecipeModel(db)
self.current_recipe = None self.current_recipe = None
self.recipes.setModel(self._model) self.recipes.setModel(self._model)
self.connect(self.recipes, SIGNAL('itemChanged(QModelIndex)'), self.show_recipe) self.recipes.currentChanged = self.currentChanged
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)
@ -257,10 +305,14 @@ class SchedulerDialog(QDialog, Ui_Dialog):
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.num_of_recipes)
for day in (_('day'), _('Monday'), _('Tuesday'), _('Wednesday'), for day in (_('day'), _('Monday'), _('Tuesday'), _('Wednesday'),
_('Thursday'), _('Friday'), _('Saturday'), _('Sunday')): _('Thursday'), _('Friday'), _('Saturday'), _('Sunday')):
self.day.addItem(day) self.day.addItem(day)
def currentChanged(self, current, previous):
if current.parent().isValid():
self.show_recipe(current)
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)
@ -304,6 +356,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
hour, minute = t.hour(), t.minute() hour, minute = t.hour(), t.minute()
recipe.schedule = encode_schedule(day_of_week, hour, minute) recipe.schedule = encode_schedule(day_of_week, hour, minute)
else: else:
recipe.schedule = None
if recipe in recipes: if recipe in recipes:
recipes.remove(recipe) recipes.remove(recipe)
save_recipes(recipes) save_recipes(recipes)
@ -432,7 +485,7 @@ class Scheduler(QObject):
day_matches = day > 6 or day == now.tm_wday day_matches = day > 6 or day == now.tm_wday
tnow = now.tm_hour*60 + now.tm_min tnow = now.tm_hour*60 + now.tm_min
matches = day_matches and (hour*60+minute) < tnow matches = day_matches and (hour*60+minute) < tnow
if matches and nowt.toordinal() < date.today().toordinal(): if matches and recipe.last_downloaded.toordinal() < date.today().toordinal():
needs_downloading.add(recipe) needs_downloading.add(recipe)
self.debug('Needs downloading:', needs_downloading) self.debug('Needs downloading:', needs_downloading)
@ -464,7 +517,7 @@ class Scheduler(QObject):
recipe = self.recipes[self.recipes.index(recipe)] recipe = self.recipes[self.recipes.index(recipe)]
now = datetime.utcnow() now = datetime.utcnow()
d = now - recipe.last_downloaded d = now - recipe.last_downloaded
if recipe.schedule is not None: if recipe.schedule is not None and recipe.schedule < 1e4:
interval = timedelta(days=recipe.schedule) interval = timedelta(days=recipe.schedule)
if abs(d - interval) < timedelta(hours=1): if abs(d - interval) < timedelta(hours=1):
recipe.last_downloaded += interval recipe.last_downloaded += interval
@ -487,7 +540,7 @@ class Scheduler(QObject):
if recipe not in self.queue: if recipe not in self.queue:
self.do_download(recipe) self.do_download(recipe)
finally: finally:
self.lock.unlock() self.lock.unlock()
def refresh_schedule(self, recipes): def refresh_schedule(self, recipes):
self.recipes = recipes self.recipes = recipes

View File

@ -24,8 +24,20 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout" > <layout class="QVBoxLayout" name="verticalLayout" >
<item> <item>
<widget class="RecipeListView" name="recipes" > <widget class="QTreeView" name="recipes" >
<property name="alternatingRowColors" > <property name="showDropIndicator" stdset="0" >
<bool>false</bool>
</property>
<property name="iconSize" >
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="animated" >
<bool>true</bool>
</property>
<property name="headerHidden" >
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
@ -321,13 +333,6 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>RecipeListView</class>
<extends>QListView</extends>
<header>recipelistview.h</header>
</customwidget>
</customwidgets>
<resources> <resources>
<include location="../images.qrc" /> <include location="../images.qrc" />
</resources> </resources>

View File

@ -114,6 +114,9 @@
<property name="text" > <property name="text" >
<string>See the &lt;a href="http://calibre.kovidgoyal.net/user_manual/gui.html#the-search-interface">User Manual&lt;/a> for more help</string> <string>See the &lt;a href="http://calibre.kovidgoyal.net/user_manual/gui.html#the-search-interface">User Manual&lt;/a> for more help</string>
</property> </property>
<property name="openExternalLinks" >
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item> <item>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

View File

@ -115,16 +115,11 @@ class Main(MainWindow, Ui_MainWindow):
self.connect(self.donate_action, SIGNAL('triggered(bool)'), self.donate) self.connect(self.donate_action, SIGNAL('triggered(bool)'), self.donate)
self.connect(self.restore_action, SIGNAL('triggered(bool)'), lambda c : self.show()) self.connect(self.restore_action, SIGNAL('triggered(bool)'), lambda c : self.show())
self.connect(self.action_show_book_details, SIGNAL('triggered(bool)'), self.show_book_info) self.connect(self.action_show_book_details, SIGNAL('triggered(bool)'), self.show_book_info)
def restart_app(c): self.connect(self.action_restart, SIGNAL('triggered(bool)'),
self.quit(None, restart=True) lambda c : self.quit(None, restart=True))
self.connect(self.action_restart, SIGNAL('triggered(bool)'), restart_app) self.connect(self.system_tray_icon, SIGNAL('activated(QSystemTrayIcon::ActivationReason)'),
def sta(r): self.system_tray_icon_activated)
if r == QSystemTrayIcon.Trigger: self.tool_bar.contextMenuEvent = self.no_op
self.hide() if self.isVisible() else self.show()
self.connect(self.system_tray_icon, SIGNAL('activated(QSystemTrayIcon::ActivationReason)'), sta)
def tcme(self, *args):
pass
self.tool_bar.contextMenuEvent = tcme
####################### Location View ######################## ####################### Location View ########################
QObject.connect(self.location_view, SIGNAL('location_selected(PyQt_PyObject)'), QObject.connect(self.location_view, SIGNAL('location_selected(PyQt_PyObject)'),
self.location_selected) self.location_selected)
@ -165,15 +160,11 @@ class Main(MainWindow, Ui_MainWindow):
sm.addSeparator() sm.addSeparator()
sm.addAction(_('Send to storage card by default')) sm.addAction(_('Send to storage card by default'))
sm.actions()[-1].setCheckable(True) sm.actions()[-1].setCheckable(True)
def default_sync(checked): QObject.connect(sm.actions()[-1], SIGNAL('toggled(bool)'),
config.set('send_to_storage_card_by_default', bool(checked)) self.do_default_sync)
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_main_memory)
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_card)
QObject.connect(self.action_sync, SIGNAL("triggered(bool)"), self.sync_to_card if checked else self.sync_to_main_memory)
QObject.connect(sm.actions()[-1], SIGNAL('toggled(bool)'), default_sync)
sm.actions()[-1].setChecked(config.get('send_to_storage_card_by_default')) sm.actions()[-1].setChecked(config.get('send_to_storage_card_by_default'))
default_sync(sm.actions()[-1].isChecked()) self.do_default_sync(sm.actions()[-1].isChecked())
self.sync_menu = sm # Needed self.sync_menu = sm # Needed
md = QMenu() md = QMenu()
md.addAction(_('Edit metadata individually')) md.addAction(_('Edit metadata individually'))
@ -294,7 +285,7 @@ class Main(MainWindow, Ui_MainWindow):
self.stack.setCurrentIndex(0) self.stack.setCurrentIndex(0)
try: try:
db = LibraryDatabase2(self.library_path) db = LibraryDatabase2(self.library_path)
except OSError, err: except Exception, err:
error_dialog(self, _('Bad database location'), unicode(err)).exec_() error_dialog(self, _('Bad database location'), unicode(err)).exec_()
dir = unicode(QFileDialog.getExistingDirectory(self, dir = unicode(QFileDialog.getExistingDirectory(self,
_('Choose a location for your ebook library.'), os.path.expanduser('~'))) _('Choose a location for your ebook library.'), os.path.expanduser('~')))
@ -371,6 +362,32 @@ 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 no_op(self, *args):
pass
def system_tray_icon_activated(self, r):
if r == QSystemTrayIcon.Trigger:
if self.isVisible():
for window in QApplication.topLevelWidgets():
if isinstance(window, (MainWindow, QDialog)) and window.isVisible():
window.hide()
setattr(window, '__systray_minimized', True)
else:
for window in QApplication.topLevelWidgets():
if getattr(window, '__systray_minimized', False):
window.show()
setattr(window, '__systray_minimized', False)
def do_default_sync(self, checked):
config.set('send_to_storage_card_by_default', bool(checked))
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"),
self.sync_to_main_memory)
QObject.disconnect(self.action_sync, SIGNAL("triggered(bool)"),
self.sync_to_card)
QObject.connect(self.action_sync, SIGNAL("triggered(bool)"),
self.sync_to_card if checked else self.sync_to_main_memory)
def change_output_format(self, x): def change_output_format(self, x):
of = unicode(x).strip() of = unicode(x).strip()
if of != prefs['output_format']: if of != prefs['output_format']:
@ -1426,10 +1443,10 @@ class Main(MainWindow, Ui_MainWindow):
def donate(self, *args): 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" />
<input type="hidden" name="hosted_button_id" value="1335186"> <input type="hidden" name="hosted_button_id" value="3029467" />
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt=""> <input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="Donate to support calibre development" />
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1"> <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />
</form> </form>
''' '''
MSG = _('is the result of the efforts of many volunteers from all over the world. If you find it useful, please consider donating to support its development.') MSG = _('is the result of the efforts of many volunteers from all over the world. If you find it useful, please consider donating to support its development.')

View File

@ -27,15 +27,6 @@ from calibre.customize.ui import run_plugins_on_import
from calibre import sanitize_file_name from calibre import sanitize_file_name
copyfile = os.link if hasattr(os, 'link') else shutil.copyfile copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
iscaseinsensitive = iswindows or isosx
def normpath(x):
# The builtin os.path.normcase doesn't work on OS X
x = os.path.abspath(x)
if iscaseinsensitive:
x = x.lower()
return x
FIELD_MAP = {'id':0, 'title':1, 'authors':2, 'publisher':3, 'rating':4, 'timestamp':5, FIELD_MAP = {'id':0, 'title':1, 'authors':2, 'publisher':3, 'rating':4, 'timestamp':5,
'size':6, 'tags':7, 'comments':8, 'series':9, 'series_index':10, 'size':6, 'tags':7, 'comments':8, 'series':9, 'series_index':10,
@ -355,6 +346,8 @@ class LibraryDatabase2(LibraryDatabase):
if isinstance(self.dbpath, unicode): if isinstance(self.dbpath, unicode):
self.dbpath = self.dbpath.encode(filesystem_encoding) self.dbpath = self.dbpath.encode(filesystem_encoding)
self.connect() self.connect()
self.is_case_sensitive = not iswindows and not isosx and \
not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB'))
# Upgrade database # Upgrade database
while True: while True:
meth = getattr(self, 'upgrade_version_%d'%self.user_version, None) meth = getattr(self, 'upgrade_version_%d'%self.user_version, None)
@ -488,6 +481,16 @@ class LibraryDatabase2(LibraryDatabase):
name = title + ' - ' + author name = title + ' - ' + author
return name return name
def rmtree(self, path):
if not self.normpath(self.library_path).startswith(self.normpath(path)):
shutil.rmtree(path)
def normpath(self, path):
path = os.path.abspath(os.path.realpath(path))
if not self.is_case_sensitive:
path = path.lower()
return path
def set_path(self, index, index_is_id=False): def set_path(self, index, index_is_id=False):
''' '''
Set the path to the directory containing this books files based on its Set the path to the directory containing this books files based on its
@ -531,11 +534,11 @@ class LibraryDatabase2(LibraryDatabase):
self.data.set(id, FIELD_MAP['path'], path, row_is_id=True) self.data.set(id, FIELD_MAP['path'], path, row_is_id=True)
# Delete not needed directories # Delete not needed directories
if current_path and os.path.exists(spath): if current_path and os.path.exists(spath):
if normpath(spath) != normpath(tpath): if self.normpath(spath) != self.normpath(tpath):
shutil.rmtree(spath) self.rmtree(spath)
parent = os.path.dirname(spath) parent = os.path.dirname(spath)
if len(os.listdir(parent)) == 0: if len(os.listdir(parent)) == 0:
shutil.rmtree(parent) self.rmtree(parent)
def add_listener(self, listener): def add_listener(self, listener):
''' '''
@ -698,10 +701,10 @@ class LibraryDatabase2(LibraryDatabase):
path = os.path.join(self.library_path, self.path(id, index_is_id=True)) path = os.path.join(self.library_path, self.path(id, index_is_id=True))
self.data.remove(id) self.data.remove(id)
if os.path.exists(path): if os.path.exists(path):
shutil.rmtree(path) self.rmtree(path)
parent = os.path.dirname(path) parent = os.path.dirname(path)
if len(os.listdir(parent)) == 0: if len(os.listdir(parent)) == 0:
shutil.rmtree(parent) self.rmtree(parent)
self.conn.execute('DELETE FROM books WHERE id=?', (id,)) self.conn.execute('DELETE FROM books WHERE id=?', (id,))
self.conn.commit() self.conn.commit()
self.clean() self.clean()

View File

@ -102,7 +102,7 @@ Device Integration
What devices does |app| support? What devices does |app| support?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
At the moment |app| has full support for the SONY PRS 500/505/700, Cybook Gen 3 as well as the iPhone. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk. At the moment |app| has full support for the SONY PRS 500/505/700, Cybook Gen 3, Amazon Kindle as well as the iPhone. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk.
I used |app| to transfer some books to my reader, and now the SONY software hangs every time I connect the reader? I used |app| to transfer some books to my reader, and now the SONY software hangs every time I connect the reader?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -161,7 +161,7 @@ class WorkerMother(object):
self.executable = self.gui_executable = sys.executable self.executable = self.gui_executable = sys.executable
self.prefix = '' self.prefix = ''
if isfrozen: if isfrozen:
fd = getattr(sys, 'frameworks_dir') fd = os.path.realpath(getattr(sys, 'frameworks_dir'))
contents = os.path.dirname(fd) contents = os.path.dirname(fd)
self.gui_executable = os.path.join(contents, 'MacOS', self.gui_executable = os.path.join(contents, 'MacOS',
os.path.basename(sys.executable)) os.path.basename(sys.executable))

View File

@ -196,7 +196,7 @@ class Server(object):
def calculate_month_trend(self, days=31): def calculate_month_trend(self, days=31):
stats = self.get_slice(date.today()-timedelta(days=days-1), date.today()) stats = self.get_slice(date.today()-timedelta(days=days-1), date.today())
fig = plt.figure(2, (8, 3), 96)#, facecolor, edgecolor, frameon, FigureClass) fig = plt.figure(2, (12, 4), 96)#, facecolor, edgecolor, frameon, FigureClass)
ax = fig.add_subplot(111) ax = fig.add_subplot(111)
x = list(range(days-1, -1, -1)) x = list(range(days-1, -1, -1))
y = stats.daily_totals y = stats.daily_totals
@ -205,6 +205,17 @@ class Server(object):
ax.set_ylabel('Income ($)') ax.set_ylabel('Income ($)')
ax.hlines([stats.daily_average], 0, days-1) ax.hlines([stats.daily_average], 0, days-1)
ax.set_xlim([0, days-1]) ax.set_xlim([0, days-1])
text = u'''\
Total: $%(total).2f
Daily average: $%(da).2f \u00b1 %(dd).2f
Average contribution: $%(ac).2f \u00b1 %(ad).2f
Donors per day: %(dpd).2f
'''%dict(total=stats.total, da=stats.daily_average,
dd=stats.daily_deviation, ac=stats.average,
ad=stats.average_deviation,
dpd=len(stats.totals)/float(stats.period.days),
)
text = ax.annotate(text, (0.6, 0.65), textcoords='axes fraction')
fig.savefig(self.MONTH_TRENDS) fig.savefig(self.MONTH_TRENDS)
def calculate_trend(self): def calculate_trend(self):
@ -223,7 +234,7 @@ class Server(object):
x = [m.min for m in _months] x = [m.min for m in _months]
y = [m.total for m in _months] y = [m.total for m in _months]
ml = mdates.MonthLocator() # every month ml = mdates.MonthLocator() # every month
fig = plt.figure(1, (8, 3), 96)#, facecolor, edgecolor, frameon, FigureClass) fig = plt.figure(1, (8, 4), 96)#, facecolor, edgecolor, frameon, FigureClass)
ax = fig.add_subplot(111) ax = fig.add_subplot(111)
ax.bar(x, y, align='center', width=20, color='g') ax.bar(x, y, align='center', width=20, color='g')
ax.xaxis.set_major_locator(ml) ax.xaxis.set_major_locator(ml)

View File

@ -23,15 +23,14 @@
While you wait for the download to complete, please consider donating to support the development While you wait for the download to complete, please consider donating to support the development
of ${app}. of ${app}.
<div> <div>
<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" />
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!" /> <input type="hidden" name="hosted_button_id" value="3029289" />
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="Donate to support calibre development" />
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" /> <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />
<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHbwYJKoZIhvcNAQcEoIIHYDCCB1wCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYBn7jneGiSLVO8rcDrBtOUXL+HftY+CiC47hTntwICio6qqpLKezIryyG8tKcjY58Rcocur/kDwljEutIafVG7XRA7BJL9eZdHAZsZdX04f4dApzkWwR9w6GQhj0kwmO2ZNE878UcgGZBve4qQKWM8bf2pMY7vJwCNoo6ozpIi3VTELMAkGBSsOAwIaBQAwgewGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIBTALt7s1gJmAgcjEAwUMRYeIdIOE/yi0Y5vrVKBFxOUCbqTx/lu3Rk4EHsODZXLHT+BDA5WSWYO3AXfv2Lmlv1kJ7jWrjUVirYoQ5M4qdIhY9DtvPioIMMRoTJmYM9JKH8n2TWcjJ1XIzIuDP4zn8/Ya9hap3RHOrj2RBj89g7iSuFRsjoA0PYZgtWAKwR7g3LLpjRachn041JO55BEd3YWUgorNQeo3WEHgowLFfTWgFFePkm8OoWA1klWkYp4S07IhX5NaRc8OegkdshpkiIHGAKCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA4MDQzMDE1MzkyMlowIwYJKoZIhvcNAQkEMRYEFJSI9/zWx7TUlKPY7kLjnvzB1h6sMA0GCSqGSIb3DQEBAQUABIGAikZNCmQdkWPdfmYnGqOb1f65ViaK0zjHf50azvsigWQLlhHqJ3PgB+jEJH3JU9Pm9M4wgiK23Bg2oIGuIsAfQkYO9mw/HjtDtOQHqXyZZbrM32YGtNWUD4ynakLYnaz7OnPl40aTPD4iDApgsGcj1oMdmw7KA2E9J0l2J9iJXF4=-----END PKCS7-----" /> </form>
</form> <br />
</div>
</div>
<br />
<h2>Note</h2> <h2>Note</h2>
<div class="note">$note</div> <div class="note">$note</div>

View File

@ -57,11 +57,11 @@ python setup.py build &amp;&amp; sudo python setup.py install
While you wait for the installation to complete, please consider donating to support the development of ${distro.app}. While you wait for the installation to complete, please consider donating to support the development of ${distro.app}.
<div> <div>
<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" />
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!" /> <input type="hidden" name="hosted_button_id" value="3029289" />
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" /> <input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="Donate to support calibre development" />
<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----MIIHbwYJKoZIhvcNAQcEoIIHYDCCB1wCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYBn7jneGiSLVO8rcDrBtOUXL+HftY+CiC47hTntwICio6qqpLKezIryyG8tKcjY58Rcocur/kDwljEutIafVG7XRA7BJL9eZdHAZsZdX04f4dApzkWwR9w6GQhj0kwmO2ZNE878UcgGZBve4qQKWM8bf2pMY7vJwCNoo6ozpIi3VTELMAkGBSsOAwIaBQAwgewGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIBTALt7s1gJmAgcjEAwUMRYeIdIOE/yi0Y5vrVKBFxOUCbqTx/lu3Rk4EHsODZXLHT+BDA5WSWYO3AXfv2Lmlv1kJ7jWrjUVirYoQ5M4qdIhY9DtvPioIMMRoTJmYM9JKH8n2TWcjJ1XIzIuDP4zn8/Ya9hap3RHOrj2RBj89g7iSuFRsjoA0PYZgtWAKwR7g3LLpjRachn041JO55BEd3YWUgorNQeo3WEHgowLFfTWgFFePkm8OoWA1klWkYp4S07IhX5NaRc8OegkdshpkiIHGAKCCA4cwggODMIIC7KADAgECAgEAMA0GCSqGSIb3DQEBBQUAMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTAeFw0wNDAyMTMxMDEzMTVaFw0zNTAyMTMxMDEzMTVaMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwUdO3fxEzEtcnI7ZKZL412XvZPugoni7i7D7prCe0AtaHTc97CYgm7NsAtJyxNLixmhLV8pyIEaiHXWAh8fPKW+R017+EmXrr9EaquPmsVvTywAAE1PMNOKqo2kl4Gxiz9zZqIajOm1fZGWcGS0f5JQ2kBqNbvbg2/Za+GJ/qwUCAwEAAaOB7jCB6zAdBgNVHQ4EFgQUlp98u8ZvF71ZP1LXChvsENZklGswgbsGA1UdIwSBszCBsIAUlp98u8ZvF71ZP1LXChvsENZklGuhgZSkgZEwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAgV86VpqAWuXvX6Oro4qJ1tYVIT5DgWpE692Ag422H7yRIr/9j/iKG4Thia/Oflx4TdL+IFJBAyPK9v6zZNZtBgPBynXb048hsP16l2vi0k5Q2JKiPDsEfBhGI+HnxLXEaUWAcVfCsQFvd2A1sxRr67ip5y2wwBelUecP3AjJ+YcxggGaMIIBlgIBATCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwCQYFKw4DAhoFAKBdMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTA4MDQzMDE1MzkyMlowIwYJKoZIhvcNAQkEMRYEFJSI9/zWx7TUlKPY7kLjnvzB1h6sMA0GCSqGSIb3DQEBAQUABIGAikZNCmQdkWPdfmYnGqOb1f65ViaK0zjHf50azvsigWQLlhHqJ3PgB+jEJH3JU9Pm9M4wgiK23Bg2oIGuIsAfQkYO9mw/HjtDtOQHqXyZZbrM32YGtNWUD4ynakLYnaz7OnPl40aTPD4iDApgsGcj1oMdmw7KA2E9J0l2J9iJXF4=-----END PKCS7-----" /> <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />
</form> </form>
</div> </div>
</div> </div>
</body> </body>

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
"Generated-By: pygettext.py 1.5\n" "Generated-By: pygettext.py 1.5\n"

View File

@ -17,7 +17,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
"Generated-By: pygettext.py 1.5\n" "Generated-By: pygettext.py 1.5\n"

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -17,7 +17,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -13,7 +13,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
"Generated-By: pygettext.py 1.5\n" "Generated-By: pygettext.py 1.5\n"

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -8,13 +8,13 @@ msgstr ""
"Project-Id-Version: calibre\n" "Project-Id-Version: calibre\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n" "Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2009-01-27 01:54+0000\n" "POT-Creation-Date: 2009-01-27 01:54+0000\n"
"PO-Revision-Date: 2009-01-23 21:22+0000\n" "PO-Revision-Date: 2009-02-04 20:39+0000\n"
"Last-Translator: Molnár Gábor <csirkus@gmail.com>\n" "Last-Translator: Kovid Goyal <Unknown>\n"
"Language-Team: Hungarian <hu@li.org>\n" "Language-Team: Hungarian <hu@li.org>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41
@ -142,7 +142,7 @@ msgstr "Tömörített könyvek metaadatait is olvassa be"
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:196 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:196
msgid "Read metadata from ebooks in RAR archives" msgid "Read metadata from ebooks in RAR archives"
msgstr "" msgstr "Metaadatok olvasása a RAR-ral tömörített könyvekből is"
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:207 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:207
#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:217 #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:217
@ -274,7 +274,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:110 #: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:110
msgid "Control auto-detection of document structure." msgid "Control auto-detection of document structure."
msgstr "" msgstr "Dokumentum-struktúra automatikus felismerése."
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:112 #: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:112
msgid "" msgid ""
@ -832,6 +832,9 @@ msgid ""
"FONT_DELTA pts. FONT_DELTA can be a fraction.If FONT_DELTA is negative, the " "FONT_DELTA pts. FONT_DELTA can be a fraction.If FONT_DELTA is negative, the "
"font size is decreased." "font size is decreased."
msgstr "" msgstr ""
"A betűméret nővelése 2*FONT_DELTA ponttal, és a sortávolság növelése "
"FONT_DELTA ponttal. A FONT_DELTA érték törtszám is lehet, valamint ha "
"negatív az értéke, a betűméret csökkenni fog."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:125 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/__init__.py:125
msgid "" msgid ""
@ -1086,6 +1089,8 @@ msgstr "Szerző a fájl metaadataiban. Alapértelmezés: %default"
msgid "" msgid ""
"Path to output file. By default a file is created in the current directory." "Path to output file. By default a file is created in the current directory."
msgstr "" msgstr ""
"A kimeneti fájl útvonala. Alapértelmezés szerint az aktuális könyvtárba "
"kerül a fájl."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:299 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:299
msgid "Number of colors for grayscale image conversion. Default: %default" msgid "Number of colors for grayscale image conversion. Default: %default"
@ -1134,6 +1139,8 @@ msgid ""
"Don't sort the files found in the comic alphabetically by name. Instead use " "Don't sort the files found in the comic alphabetically by name. Instead use "
"the order they were added to the comic." "the order they were added to the comic."
msgstr "" msgstr ""
"A képregény csomagban talált fájlokat a képregényhez való hozzáadás "
"sorrendje alapján rendezze a névsorrend helyett."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:317 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/comic/convert_from.py:317
msgid "" msgid ""
@ -1185,6 +1192,10 @@ msgid ""
" \n" " \n"
"%prog converts mybook.epub to mybook.lrf" "%prog converts mybook.epub to mybook.lrf"
msgstr "" msgstr ""
"Használat: %prog [beállítások] konyv.epub\n"
" \n"
" \n"
"A %prog a konyv.epub fájlt lrf formátumba konvertálja."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:23 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:23
msgid "" msgid ""
@ -1205,7 +1216,7 @@ msgstr "A létrehozott HTML kiírása a kimenetre, majd kilépés."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:30 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/fb2/convert_from.py:30
msgid "Keep generated HTML files after completing conversion to LRF." msgid "Keep generated HTML files after completing conversion to LRF."
msgstr "" msgstr "Ne törölje a konvertálásnál keletkező átmeneti HTML fájlokat."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/feeds/convert_from.py:20 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/feeds/convert_from.py:20
msgid "Options to control the behavior of feeds2disk" msgid "Options to control the behavior of feeds2disk"
@ -1225,7 +1236,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:319 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:319
msgid "\tParsing HTML..." msgid "\tParsing HTML..."
msgstr "" msgstr "\tHTML beolvasása..."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:342 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:342
msgid "\tBaen file detected. Re-parsing..." msgid "\tBaen file detected. Re-parsing..."
@ -1241,71 +1252,79 @@ msgstr "Feldolgozás: %s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:390 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:390
msgid "\tConverting to BBeB..." msgid "\tConverting to BBeB..."
msgstr "" msgstr "\tKonvertálás BBeB formátumba..."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:536 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:536
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:549 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:549
msgid "Could not parse file: %s" msgid "Could not parse file: %s"
msgstr "" msgstr "Nem tudtam feldolgozni a fájlt: %s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:541 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:541
msgid "%s is an empty file" msgid "%s is an empty file"
msgstr "" msgstr "A %s fájl üres!"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:561 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:561
msgid "Failed to parse link %s %s" msgid "Failed to parse link %s %s"
msgstr "" msgstr "A link feldolgozása nem sikerült: %s %s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:605 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:605
msgid "Cannot add link %s to TOC" msgid "Cannot add link %s to TOC"
msgstr "" msgstr "Nem tudtam a linket hozzáadni a tartalomjegyzékhez: %s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:957 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:957
msgid "Unable to process image %s. Error: %s" msgid "Unable to process image %s. Error: %s"
msgstr "" msgstr "Hiba a \"%s\" kép feldolgozása közben: %s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:995 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:995
msgid "Unable to process interlaced PNG %s" msgid "Unable to process interlaced PNG %s"
msgstr "" msgstr "Nem tudtam feldolgozni a PNG képet: %s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1010 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1010
msgid "" msgid ""
"Could not process image: %s\n" "Could not process image: %s\n"
"%s" "%s"
msgstr "" msgstr ""
"Nem tudtam feldolgozni a képet: %s\n"
"%s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1763 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1763
msgid "" msgid ""
"An error occurred while processing a table: %s. Ignoring table markup." "An error occurred while processing a table: %s. Ignoring table markup."
msgstr "" msgstr ""
"Hiba történt a táblázat feldolgozása közben: %s. A táblázat formázást "
"figyelmen kívül hagyom."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1765 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1765
msgid "" msgid ""
"Bad table:\n" "Bad table:\n"
"%s" "%s"
msgstr "" msgstr ""
"Hibás táblázat:\n"
"%s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1787 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1787
msgid "Table has cell that is too large" msgid "Table has cell that is too large"
msgstr "" msgstr "A táblázatban olyan cellák vannak, amelyek túl nagyok."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1817 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1817
msgid "" msgid ""
"You have to save the website %s as an html file first and then run html2lrf " "You have to save the website %s as an html file first and then run html2lrf "
"on it." "on it."
msgstr "" msgstr ""
"Mentsd le a %s weboldalt egy mappába, majd a html fájlon futtasd a html2lrf "
"konvertáló programot."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1860 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1860
msgid "Could not read cover image: %s" msgid "Could not read cover image: %s"
msgstr "" msgstr "Nem tudtam a borító képet olvasni: %s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1863 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1863
msgid "Cannot read from: %s" msgid "Cannot read from: %s"
msgstr "" msgstr "Hiba olvasás közben: %s"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1988 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1988
msgid "Failed to process opf file" msgid "Failed to process opf file"
msgstr "" msgstr "Hiba az opf fájl feldolgozása közben"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1994 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1994
msgid "" msgid ""
@ -1317,6 +1336,13 @@ msgid ""
"to local files recursively. Thus, you can use it to \n" "to local files recursively. Thus, you can use it to \n"
"convert a whole tree of HTML files." "convert a whole tree of HTML files."
msgstr "" msgstr ""
"Használat: %prog [beállítások] konyv.html\n"
"\n"
"\n"
"A %prog a konyv.html fájlt LRF formátumba konvertálja. \n"
"A %prog minden linket rekurzívan követ ami helyi \n"
"html fájlokra hivatkozik, tehát lehetséges sok összefüggő \n"
"html fájl konvertálása is."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lit/convert_from.py:15 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lit/convert_from.py:15
msgid "" msgid ""
@ -1325,16 +1351,22 @@ msgid ""
"\n" "\n"
"%prog converts mybook.lit to mybook.lrf" "%prog converts mybook.lit to mybook.lrf"
msgstr "" msgstr ""
"Használat: %prog [beállítások] konyv.lit\n"
"\n"
"\n"
"A %prog a konyv.lit fájlt LRF formátumba konvertálja"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:136 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:136
msgid "" msgid ""
"%prog book.lrf\n" "%prog book.lrf\n"
"Convert an LRF file into an LRS (XML UTF-8 encoded) file" "Convert an LRF file into an LRS (XML UTF-8 encoded) file"
msgstr "" msgstr ""
"%prog konyv.lrf\n"
"A prog a konyv.lrf fájlt LRS formátumba (UTF-8 kódolású XML) konvertálja."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:137 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:137
msgid "Output LRS file" msgid "Output LRS file"
msgstr "" msgstr "Kimeneti LRS fájl"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:139 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:139
msgid "Do not save embedded image and font files to disk" msgid "Do not save embedded image and font files to disk"
@ -1342,42 +1374,44 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:158 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:158
msgid "Parsing LRF..." msgid "Parsing LRF..."
msgstr "" msgstr "LRF fájl beolvasása..."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:161 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:161
msgid "Creating XML..." msgid "Creating XML..."
msgstr "" msgstr "XML létrehozása..."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:163 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:163
msgid "LRS written to " msgid "LRS written to "
msgstr "" msgstr "Az LRS fájl helye: "
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:249 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:249
msgid "Could not read from thumbnail file:" msgid "Could not read from thumbnail file:"
msgstr "" msgstr "Nem tudtam az ikon fájlt olvasni:"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:269 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:269
msgid "" msgid ""
"%prog [options] file.lrs\n" "%prog [options] file.lrs\n"
"Compile an LRS file into an LRF file." "Compile an LRS file into an LRF file."
msgstr "" msgstr ""
"%prog [beállítások] konyv.lrf\n"
"LRS fájlt lefordítása LRF formátumba."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:270 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:270
msgid "Path to output file" msgid "Path to output file"
msgstr "" msgstr "A célfájl elérési útja"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:272 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:272
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:115 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/isbndb.py:115
msgid "Verbose processing" msgid "Verbose processing"
msgstr "" msgstr "Informatívabb üzenetek feldolgozásnál"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:274 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:274
msgid "Convert LRS to LRS, useful for debugging." msgid "Convert LRS to LRS, useful for debugging."
msgstr "" msgstr "LRS fájl konvertálása LRS formátumba (hibakeresésnél hasznos)."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:455 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:455
msgid "Invalid LRF file. Could not set metadata." msgid "Invalid LRF file. Could not set metadata."
msgstr "" msgstr "Érvénytelen LRF fájl. Nem tudtam a metadatokat beállítani."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:580 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:580
msgid "" msgid ""
@ -1391,42 +1425,44 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:587 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:587
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:43 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:43
msgid "Set the book title" msgid "Set the book title"
msgstr "" msgstr "A könyv címe"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:589 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:589
msgid "Set sort key for the title" msgid "Set sort key for the title"
msgstr "" msgstr "Rendezési kulcs a címhez"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:591 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:591
msgid "Set the author" msgid "Set the author"
msgstr "" msgstr "Szerző"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:593 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:593
msgid "Set sort key for the author" msgid "Set sort key for the author"
msgstr "" msgstr "Rendezési kulcs a szerzőhöz"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:595 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:595
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:47 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:47
msgid "The category this book belongs to. E.g.: History" msgid "The category this book belongs to. E.g.: History"
msgstr "" msgstr "A kategória, amibe a könyv tartozik. Pl.: történelem"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:598 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:598
msgid "Path to a graphic that will be set as this files' thumbnail" msgid "Path to a graphic that will be set as this files' thumbnail"
msgstr "" msgstr "A fájlhoz használandó ikon elérési útja"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:601 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:601
msgid "" msgid ""
"Path to a txt file containing the comment to be stored in the lrf file." "Path to a txt file containing the comment to be stored in the lrf file."
msgstr "" msgstr ""
"A txt fájl útvonala, amelynek tartalma az lrf fájlhoz lesz csatolva "
"megjegyzésként."
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:605 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:605
msgid "Extract thumbnail from LRF file" msgid "Extract thumbnail from LRF file"
msgstr "" msgstr "Ikon kinyerése az LRF fájlból"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:606 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:606
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/mobi.py:182 #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/mobi.py:182
msgid "Set the publisher" msgid "Set the publisher"
msgstr "" msgstr "Kiadó"
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:607 #: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:607
msgid "Set the book classification" msgid "Set the book classification"

View File

@ -15,7 +15,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
"Generated-By: pygettext.py 1.5\n" "Generated-By: pygettext.py 1.5\n"

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
"Generated-By: pygettext.py 1.5\n" "Generated-By: pygettext.py 1.5\n"

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -13,7 +13,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
"X-Poedit-Country: RUSSIAN FEDERATION\n" "X-Poedit-Country: RUSSIAN FEDERATION\n"
"X-Poedit-Language: Russian\n" "X-Poedit-Language: Russian\n"

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -13,7 +13,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
"Generated-By: pygettext.py 1.5\n" "Generated-By: pygettext.py 1.5\n"

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

View File

@ -14,7 +14,7 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n" "X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
"X-Generator: Launchpad (build Unknown)\n" "X-Generator: Launchpad (build Unknown)\n"
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41 #: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41

File diff suppressed because it is too large Load Diff

View File

@ -47,6 +47,9 @@ class BasicNewsRecipe(object, LoggingInterface):
#: The author of this recipe #: The author of this recipe
__author__ = __appname__ __author__ = __appname__
#: The language that the news is in
language = _('Unknown')
#: Maximum number of articles to download from each feed. This is primarily #: Maximum number of articles to download from each feed. This is primarily
#: useful for feeds that don't have article dates. For most feeds, you should #: useful for feeds that don't have article dates. For most feeds, you should
#: use :attr:`BasicNewsRecipe.oldest_article` #: use :attr:`BasicNewsRecipe.oldest_article`

View File

@ -24,7 +24,9 @@ recipe_modules = ['recipe_' + r for r in (
'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', 'amspec', 'freakonomics', 'criticadigital', 'elcronista', 'laprensa', 'amspec', 'freakonomics', 'criticadigital', 'elcronista',
'shacknews', 'teleread', 'shacknews', 'teleread', 'granma', 'juventudrebelde', 'juventudrebelde_english',
'la_tercera', 'el_mercurio_chile', 'la_cuarta', 'lanacion_chile', 'la_segunda',
'jb_online', 'estadao', 'o_globo', 'vijesti', 'elmundo', 'the_oz', 'exiled',
)] )]
import re, imp, inspect, time, os import re, imp, inspect, time, os

View File

@ -18,6 +18,7 @@ class Ambito(BasicNewsRecipe):
no_stylesheets = True no_stylesheets = True
use_embedded_content = False use_embedded_content = False
encoding = 'iso--8859-1' encoding = 'iso--8859-1'
language = _('Spanish')
cover_url = 'http://www.ambito.com/img/logo_.jpg' cover_url = 'http://www.ambito.com/img/logo_.jpg'
html2lrf_options = [ html2lrf_options = [

View File

@ -9,27 +9,28 @@ spectator.org
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class TheAmericanSpectator(BasicNewsRecipe): class TheAmericanSpectator(BasicNewsRecipe):
title = 'The American Spectator' title = 'The American Spectator'
__author__ = 'Darko Miletic' __author__ = 'Darko Miletic'
description = 'news from USA' language = _('English')
oldest_article = 7 description = 'News from USA'
max_articles_per_feed = 100 oldest_article = 7
no_stylesheets = True max_articles_per_feed = 100
use_embedded_content = False no_stylesheets = True
INDEX = 'http://spectator.org' use_embedded_content = False
INDEX = 'http://spectator.org'
html2lrf_options = [ html2lrf_options = [
'--comment' , description '--comment' , description
, '--category' , 'news, politics, USA' , '--category' , 'news, politics, USA'
, '--publisher' , title , '--publisher' , title
] ]
keep_only_tags = [ keep_only_tags = [
dict(name='div', attrs={'class':'post inner'}) dict(name='div', attrs={'class':'post inner'})
,dict(name='div', attrs={'class':'author-bio'}) ,dict(name='div', attrs={'class':'author-bio'})
] ]
remove_tags = [ remove_tags = [
dict(name='object') dict(name='object')
,dict(name='div', attrs={'class':'col3' }) ,dict(name='div', attrs={'class':'col3' })
,dict(name='div', attrs={'class':'post-options' }) ,dict(name='div', attrs={'class':'post-options' })
@ -37,17 +38,17 @@ class TheAmericanSpectator(BasicNewsRecipe):
,dict(name='div', attrs={'class':'social' }) ,dict(name='div', attrs={'class':'social' })
] ]
feeds = [ (u'Articles', u'http://feedproxy.google.com/amspecarticles')] feeds = [ (u'Articles', u'http://feedproxy.google.com/amspecarticles')]
def get_cover_url(self): def get_cover_url(self):
cover_url = None cover_url = None
soup = self.index_to_soup(self.INDEX) soup = self.index_to_soup(self.INDEX)
link_item = soup.find('a',attrs={'class':'cover'}) link_item = soup.find('a',attrs={'class':'cover'})
if link_item: if link_item:
soup2 = self.index_to_soup(link_item['href']) soup2 = self.index_to_soup(link_item['href'])
link_item2 = soup2.find('div',attrs={'class':'post inner issues'}) link_item2 = soup2.find('div',attrs={'class':'post inner issues'})
cover_url = self.INDEX + link_item2.img['src'] cover_url = self.INDEX + link_item2.img['src']
return cover_url return cover_url
def print_version(self, url): def print_version(self, url):
return url + '/print' return url + '/print'

View File

@ -8,6 +8,7 @@ class AssociatedPress(BasicNewsRecipe):
description = 'Global news' description = 'Global news'
__author__ = 'Kovid Goyal' __author__ = 'Kovid Goyal'
use_embedded_content = False use_embedded_content = False
language = _('English')
max_articles_per_feed = 15 max_articles_per_feed = 15
html2lrf_options = ['--force-page-break-before-tag="chapter"'] html2lrf_options = ['--force-page-break-before-tag="chapter"']

View File

@ -13,6 +13,7 @@ class ArsTechnica(BasicNewsRecipe):
title = 'Ars Technica' title = 'Ars Technica'
description = 'The art of technology' description = 'The art of technology'
oldest_article = 7 oldest_article = 7
language = _('English')
no_stylesheets = True no_stylesheets = True
__author__ = 'Michael Warner' __author__ = 'Michael Warner'
max_articles_per_feed = 100 max_articles_per_feed = 100

View File

@ -14,7 +14,7 @@ class TheAtlantic(BasicNewsRecipe):
__author__ = 'Kovid Goyal' __author__ = 'Kovid Goyal'
description = 'Current affairs and politics focussed on the US' description = 'Current affairs and politics focussed on the US'
INDEX = 'http://www.theatlantic.com/doc/current' INDEX = 'http://www.theatlantic.com/doc/current'
language = _('English')
remove_tags_before = dict(name='div', id='storytop') remove_tags_before = dict(name='div', id='storytop')
remove_tags = [dict(name='div', id=['seealso', 'storybottom', 'footer', 'ad_banner_top', 'sidebar'])] remove_tags = [dict(name='div', id=['seealso', 'storybottom', 'footer', 'ad_banner_top', 'sidebar'])]
no_stylesheets = True no_stylesheets = True

View File

@ -6,12 +6,13 @@ __copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
b92.net b92.net
''' '''
import string,re import re
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class B92(BasicNewsRecipe): class B92(BasicNewsRecipe):
title = u'B92' title = u'B92'
__author__ = 'Darko Miletic' __author__ = 'Darko Miletic'
language = _('Serbian')
description = 'Dnevne vesti iz Srbije i sveta' description = 'Dnevne vesti iz Srbije i sveta'
oldest_article = 7 oldest_article = 7
max_articles_per_feed = 100 max_articles_per_feed = 100

View File

@ -15,6 +15,7 @@ class Barrons(BasicNewsRecipe):
title = 'Barron\'s' title = 'Barron\'s'
max_articles_per_feed = 50 max_articles_per_feed = 50
needs_subscription = True needs_subscription = True
language = _('English')
__author__ = 'Kovid Goyal' __author__ = 'Kovid Goyal'
description = 'Weekly publication for investors from the publisher of the Wall Street Journal' description = 'Weekly publication for investors from the publisher of the Wall Street Journal'
timefmt = ' [%a, %b %d, %Y]' timefmt = ' [%a, %b %d, %Y]'

View File

@ -13,6 +13,7 @@ class BBC(BasicNewsRecipe):
__author__ = 'Kovid Goyal' __author__ = 'Kovid Goyal'
description = 'Global news and current affairs from the British Broadcasting Corporation' description = 'Global news and current affairs from the British Broadcasting Corporation'
no_stylesheets = True no_stylesheets = True
language = _('English')
remove_tags = [dict(name='div', attrs={'class':'footer'})] remove_tags = [dict(name='div', attrs={'class':'footer'})]
extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }' extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }'

View File

@ -13,6 +13,7 @@ class BusinessWeek(BasicNewsRecipe):
title = 'Business Week' title = 'Business Week'
description = 'Business News, Stock Market and Financial Advice' description = 'Business News, Stock Market and Financial Advice'
__author__ = 'ChuckEggDotCom' __author__ = 'ChuckEggDotCom'
language = _('English')
oldest_article = 7 oldest_article = 7
max_articles_per_feed = 10 max_articles_per_feed = 10

View File

@ -8,6 +8,7 @@ class ChristianScienceMonitor(BasicNewsRecipe):
description = 'Providing context and clarity on national and international news, peoples and cultures' description = 'Providing context and clarity on national and international news, peoples and cultures'
max_articles_per_feed = 20 max_articles_per_feed = 20
__author__ = 'Kovid Goyal' __author__ = 'Kovid Goyal'
language = _('English')
no_stylesheets = True no_stylesheets = True
use_embedded_content = False use_embedded_content = False

View File

@ -15,6 +15,7 @@ class Clarin(BasicNewsRecipe):
description = 'Noticias de Argentina y mundo' description = 'Noticias de Argentina y mundo'
oldest_article = 2 oldest_article = 2
max_articles_per_feed = 100 max_articles_per_feed = 100
language = _('Spanish')
use_embedded_content = False use_embedded_content = False
no_stylesheets = True no_stylesheets = True
cover_url = strftime('http://www.clarin.com/diario/%Y/%m/%d/portada.jpg') cover_url = strftime('http://www.clarin.com/diario/%Y/%m/%d/portada.jpg')

View File

@ -12,6 +12,7 @@ class CNN(BasicNewsRecipe):
description = 'Global news' description = 'Global news'
timefmt = ' [%d %b %Y]' timefmt = ' [%d %b %Y]'
__author__ = 'Kovid Goyal' __author__ = 'Kovid Goyal'
language = _('English')
no_stylesheets = True no_stylesheets = True
use_embedded_content = False use_embedded_content = False
oldest_article = 15 oldest_article = 15

View File

@ -5,6 +5,7 @@ class CommonDreams(BasicNewsRecipe):
title = u'Common Dreams' title = u'Common Dreams'
description = u'Progressive news and views' description = u'Progressive news and views'
__author__ = u'XanthanGum' __author__ = u'XanthanGum'
language = _('English')
oldest_article = 7 oldest_article = 7
max_articles_per_feed = 100 max_articles_per_feed = 100

View File

@ -14,6 +14,7 @@ class CriticaDigital(BasicNewsRecipe):
description = 'Noticias de Argentina' description = 'Noticias de Argentina'
oldest_article = 2 oldest_article = 2
max_articles_per_feed = 100 max_articles_per_feed = 100
language = _('Spanish')
no_stylesheets = True no_stylesheets = True
use_embedded_content = False use_embedded_content = False
encoding = 'cp1252' encoding = 'cp1252'

View File

@ -6,6 +6,7 @@ class Cyberpresse(BasicNewsRecipe):
title = u'Cyberpresse' title = u'Cyberpresse'
__author__ = 'balok' __author__ = 'balok'
description = 'Canadian news in French' description = 'Canadian news in French'
language = _('French')
oldest_article = 7 oldest_article = 7
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = True no_stylesheets = True

View File

@ -12,6 +12,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
class DailyTelegraph(BasicNewsRecipe): class DailyTelegraph(BasicNewsRecipe):
title = u'Daily Telegraph' title = u'Daily Telegraph'
__author__ = u'AprilHare' __author__ = u'AprilHare'
language = _('English')
description = u'News from down under' description = u'News from down under'
oldest_article = 2 oldest_article = 2
max_articles_per_feed = 10 max_articles_per_feed = 10

View File

@ -9,6 +9,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
class DeStandaard(BasicNewsRecipe): class DeStandaard(BasicNewsRecipe):
title = u'De Standaard' title = u'De Standaard'
__author__ = u'Darko Miletic' __author__ = u'Darko Miletic'
language = _('French')
description = u'News from Belgium' description = u'News from Belgium'
oldest_article = 7 oldest_article = 7
max_articles_per_feed = 100 max_articles_per_feed = 100

View File

@ -13,7 +13,8 @@ class DiscoverMagazine(BasicNewsRecipe):
title = u'Discover Magazine' title = u'Discover Magazine'
description = u'Science, Technology and the Future' description = u'Science, Technology and the Future'
__author__ = 'Mike Diaz' __author__ = 'Mike Diaz'
oldest_article = 33 oldest_article = 33
language = _('English')
max_articles_per_feed = 20 max_articles_per_feed = 20
feeds = [ feeds = [
(u'Technology', u'http://discovermagazine.com/topics/technology/rss.xml'), (u'Technology', u'http://discovermagazine.com/topics/technology/rss.xml'),

View File

@ -14,6 +14,7 @@ from urllib2 import quote
class Economist(BasicNewsRecipe): class Economist(BasicNewsRecipe):
title = 'The Economist' title = 'The Economist'
language = _('English')
__author__ = "Kovid Goyal" __author__ = "Kovid Goyal"
description = 'Global news and current affairs from a European perspective' description = 'Global news and current affairs from a European perspective'
oldest_article = 7.0 oldest_article = 7.0

View File

@ -0,0 +1,48 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
emol.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class ElMercurio(BasicNewsRecipe):
title = 'El Mercurio online'
language = _('Spanish')
__author__ = 'Darko Miletic'
description = 'El sitio de noticias online de Chile'
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'cp1252'
cover_url = 'http://www.emol.com/especiales/logo_emol/logo_emol.gif'
html2lrf_options = [
'--comment' , description
, '--category' , 'news, Chile'
, '--publisher' , title
]
keep_only_tags = [
dict(name='div', attrs={'class':'despliegue-txt_750px'})
,dict(name='div', attrs={'id':'div_cuerpo_participa'})
]
remove_tags = [
dict(name='div', attrs={'class':'contenedor_despliegue-col-left300'})
,dict(name='div', attrs={'id':['div_centro_dn_opc','div_cabezera','div_secciones','div_contenidos','div_pie','nav']})
]
feeds = [
(u'Noticias de ultima hora', u'http://www.emol.com/rss20/rss.asp?canal=0')
,(u'Nacional', u'http://www.emol.com/rss20/rss.asp?canal=1')
,(u'Mundo', u'http://www.emol.com/rss20/rss.asp?canal=2')
,(u'Deportes', u'http://www.emol.com/rss20/rss.asp?canal=4')
,(u'Magazine', u'http://www.emol.com/rss20/rss.asp?canal=6')
,(u'Tecnologia', u'http://www.emol.com/rss20/rss.asp?canal=5')
,(u'La Musica', u'http://www.emol.com/rss20/rss.asp?canal=7')
]

View File

@ -11,6 +11,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
class ElPais(BasicNewsRecipe): class ElPais(BasicNewsRecipe):
title = u'EL PAIS' title = u'EL PAIS'
language = _('Spanish')
oldest_article = 7 oldest_article = 7
max_articles_per_feed = 100 max_articles_per_feed = 100

View File

@ -12,6 +12,7 @@ class ElArgentino(BasicNewsRecipe):
title = 'ElArgentino.com' title = 'ElArgentino.com'
__author__ = 'Darko Miletic' __author__ = 'Darko Miletic'
description = 'Informacion Libre las 24 horas' description = 'Informacion Libre las 24 horas'
language = _('Spanish')
oldest_article = 2 oldest_article = 2
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = True no_stylesheets = True

View File

@ -13,6 +13,7 @@ class ElCronista(BasicNewsRecipe):
__author__ = 'Darko Miletic' __author__ = 'Darko Miletic'
description = 'Noticias de Argentina' description = 'Noticias de Argentina'
oldest_article = 2 oldest_article = 2
language = _('Spanish')
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = True no_stylesheets = True
use_embedded_content = False use_embedded_content = False

View File

@ -0,0 +1,46 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
elmundo.es
'''
from calibre.web.feeds.news import BasicNewsRecipe
class ElMundo(BasicNewsRecipe):
title = 'El Mundo'
__author__ = 'Darko Miletic'
description = 'News from Spain'
language = _('Spanish')
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'iso8859_15'
cover_url = 'http://estaticos02.cache.el-mundo.net/papel/imagenes/v2.0/logoverde.gif'
html2lrf_options = [
'--comment' , description
, '--category' , 'news, Spain'
, '--publisher' , title
]
keep_only_tags = [dict(name='div', attrs={'class':'noticia'})]
remove_tags = [
dict(name='div', attrs={'class':['herramientas','publicidad_google','video','herramientasarriba','contenido_noticia_02']})
,dict(name='div', attrs={'id':'modulo_multimedia' })
,dict(name=['object','script','link', 'a'])
,dict(name='ul', attrs={'class':'herramientas'})
]
feeds = [
(u'Portada' , u'http://rss.elmundo.es/rss/descarga.htm?data2=4' )
,(u'Television' , u'http://rss.elmundo.es/rss/descarga.htm?data2=76')
,(u'Espana' , u'http://rss.elmundo.es/rss/descarga.htm?data2=8' )
,(u'Internacional' , u'http://rss.elmundo.es/rss/descarga.htm?data2=9' )
,(u'Cultura' , u'http://rss.elmundo.es/rss/descarga.htm?data2=6' )
,(u'Ciencia/Ecologia', u'http://rss.elmundo.es/rss/descarga.htm?data2=5' )
,(u'Comunicacion' , u'http://rss.elmundo.es/rss/descarga.htm?data2=26')
]

View File

@ -12,7 +12,8 @@ from calibre.web.feeds.news import BasicNewsRecipe
class Engadget(BasicNewsRecipe): class Engadget(BasicNewsRecipe):
title = u'Engadget' title = u'Engadget'
__author__ = 'Darko Miletic' __author__ = 'Darko Miletic'
description = 'Tech news' description = 'Tech news'
language = _('English')
oldest_article = 7 oldest_article = 7
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = True no_stylesheets = True

View File

@ -14,6 +14,7 @@ class ESPN(BasicNewsRecipe):
title = 'ESPN' title = 'ESPN'
description = 'Sports news' description = 'Sports news'
__author__ = 'Kovid Goyal' __author__ = 'Kovid Goyal'
language = _('English')
needs_subscription = True needs_subscription = True
remove_tags = [dict(name='font', attrs={'class':'footer'}), dict(name='hr', noshade='noshade')] remove_tags = [dict(name='font', attrs={'class':'footer'}), dict(name='hr', noshade='noshade')]

View File

@ -0,0 +1,55 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
estadao.com.br
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Estadao(BasicNewsRecipe):
title = 'O Estado de S. Paulo'
__author__ = 'Darko Miletic'
description = 'News from Brasil'
language = _('Spanish')
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'utf8'
cover_url = 'http://www.estadao.com.br/img/logo_estadao.png'
html2lrf_options = [
'--comment' , description
, '--category' , 'news, Brasil'
, '--publisher' , title
]
keep_only_tags = [dict(name='div', attrs={'id':'c1'})]
remove_tags = [
dict(name=['script','object','form','ul'])
,dict(name='div', attrs={'id':['votacao','estadaohoje']})
,dict(name='p', attrs={'id':'ctrl_texto'})
,dict(name='p', attrs={'class':'texto'})
]
feeds = [
(u'Manchetes Estadao', u'http://www.estadao.com.br/rss/manchetes.xml')
,(u'Ultimas noticias', u'http://www.estadao.com.br/rss/ultimas.xml')
,(u'Nacional', u'http://www.estadao.com.br/rss/nacional.xml')
,(u'Internacional', u'http://www.estadao.com.br/rss/internacional.xml')
,(u'Cidades', u'http://www.estadao.com.br/rss/cidades.xml')
,(u'Esportes', u'http://www.estadao.com.br/rss/esportes.xml')
,(u'Arte & Lazer', u'http://www.estadao.com.br/rss/arteelazer.xml')
,(u'Economia', u'http://www.estadao.com.br/rss/economia.xml')
,(u'Vida &', u'http://www.estadao.com.br/rss/vidae.xml')
]
def preprocess_html(self, soup):
ifr = soup.find('iframe')
if ifr:
ifr.extract()
return soup

View File

@ -0,0 +1,51 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
'''
exiledonline.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Exiled(BasicNewsRecipe):
title = 'Exiled Online'
__author__ = 'Darko Miletic'
description = "Mankind's only alternative since 1997 - Formerly known as The eXile"
publisher = 'Exiled Online'
language = _('English')
category = 'news, politics, international'
oldest_article = 15
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'utf8'
remove_javascript = True
cover_url = 'http://exiledonline.com/wp-content/themes/exiledonline_theme/images/header-sm.gif'
html2lrf_options = [
'--comment' , description
, '--category' , category
, '--publisher' , publisher
]
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
keep_only_tags = [dict(name='div', attrs={'id':'main'})]
remove_tags = [
dict(name=['object','link'])
,dict(name='div', attrs={'class':'info'})
,dict(name='div', attrs={'id':['comments','navig']})
]
feeds = [(u'Articles', u'http://exiledonline.com/feed/' )]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
mtag = '\n<meta http-equiv="Content-Language" content="en"/>\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n'
soup.head.insert(0,mtag)
return soup

View File

@ -11,8 +11,9 @@ class FazNet(BasicNewsRecipe):
title = 'FAZ NET' title = 'FAZ NET'
__author__ = 'Kovid Goyal' __author__ = 'Kovid Goyal'
description = '"Frankfurter Allgemeine Zeitung' description = 'Frankfurter Allgemeine Zeitung'
use_embedded_content = False use_embedded_content = False
language = _('German')
max_articles_per_feed = 30 max_articles_per_feed = 30
preprocess_regexps = [ preprocess_regexps = [

View File

@ -13,6 +13,7 @@ class FinancialTimes(BasicNewsRecipe):
__author__ = 'Darko Miletic' __author__ = 'Darko Miletic'
description = 'Financial world news' description = 'Financial world news'
oldest_article = 2 oldest_article = 2
language = _('English')
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = True no_stylesheets = True
use_embedded_content = False use_embedded_content = False

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