Sync to trunk
@ -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 ::09D0906E-3611-3DB7-32CF-A140585694A7 -name win32pdh.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::4C84F0DC-7157-0C90-2062-180139B03E25 -name IM_MOD_RL_rgb_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::F402F507-87C5-BDB1-80AE-AD3FF4A4BCE7 -name bzrlib._patiencediff_c.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::A732EDE7-4796-241F-BECA-68E59F88F8AF -name lrs2lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::69072379-7D16-B9F7-9F39-3E6403C48267 -name IM_MOD_RL_xbm_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::FBD11D98-D1E7-5DD9-BF02-01CE92518859 -name IM_MOD_RL_otb_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
@ -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 ::A4910EB3-0F1C-F6F0-CD2D-16A64BBAA92B -name calibre-fontconfig.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::8711327A-716D-B162-6AC6-2FB4AD071266 -name fb22lrf.exe -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::0FDD3A7A-31F3-8089-CE32-D80EAA6F62B2 -name bzrlib._btree_serializer_c.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::476CB977-5155-D56F-26CA-EB243AEBBA99 -name unrar.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::2DA1CC8D-AF5C-3B03-2060-301DFE0356CC -name mobi2oeb.exe.local -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::2E2A9EDA-5386-444E-8479-557386794552 -name IM_MOD_RL_uil_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
@ -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 ::AAF45D03-322F-5553-63A7-312DB754A20B -name _ctypes.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::C3D351CA-A8D8-AB35-55D9-5AACF8DB37D1 -name python26.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::2F90B52F-A728-2CA4-5688-0283674695B7 -name _elementtree.pyd -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::B50B66A1-FB65-FAD5-1DD7-E894ACC07464 -name QtSvg4.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::906FF13D-D993-7192-7EA5-6D15A5A24BFB -name CORE_RL_png_.dll -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
File ::5D368661-6BF0-D6AF-7C1A-87646864EB4B -name delegates.xml -parent 8E5D85A4-7608-47A1-CF7C-309060D5FF40
|
||||
@ -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
|
||||
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
|
||||
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
|
||||
|
@ -12,7 +12,7 @@ LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
|
||||
PDFTOHTML = 'C:\\pdftohtml\\pdftohtml.exe'
|
||||
IMAGEMAGICK_DIR = 'C:\\ImageMagick'
|
||||
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
|
||||
from distutils.core import setup
|
||||
|
@ -19,8 +19,15 @@ import mechanize
|
||||
|
||||
mimetypes.add_type('application/epub+zip', '.epub')
|
||||
mimetypes.add_type('text/x-sony-bbeb+xml', '.lrs')
|
||||
mimetypes.add_type('http://www.w3.org/1999/xhtml', '.xhtml')
|
||||
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'):
|
||||
if isinstance(raw, unicode):
|
||||
|
@ -2,7 +2,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = 'calibre'
|
||||
__version__ = '0.4.132'
|
||||
__version__ = '0.4.134'
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
'''
|
||||
Various run time constants.
|
||||
|
@ -96,7 +96,7 @@ class PRS500(Device):
|
||||
# Location of cache.xml on storage card in device
|
||||
CACHE_XML = "/Sony Reader/database/cache.xml"
|
||||
# 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
|
||||
THUMBNAIL_HEIGHT = 68
|
||||
# Directory on card to which books are copied
|
||||
|
@ -32,7 +32,7 @@ class PRS505(Device):
|
||||
BCD = [0x229] #: Needed to disambiguate 505 and 700 on linux
|
||||
PRODUCT_NAME = 'PRS-505'
|
||||
VENDOR_NAME = 'SONY'
|
||||
FORMATS = ['lrf', 'epub', "rtf", "pdf", "txt"]
|
||||
FORMATS = ['lrf', 'epub', 'lrx', 'rtf', 'pdf', 'txt']
|
||||
|
||||
MEDIA_XML = 'database/cache/media.xml'
|
||||
CACHE_XML = 'Sony Reader/database/cache.xml'
|
||||
@ -147,6 +147,7 @@ class PRS505(Device):
|
||||
|
||||
|
||||
def open_windows(self):
|
||||
time.sleep(6)
|
||||
drives = []
|
||||
wmi = __import__('wmi', globals(), locals(), [], -1)
|
||||
c = wmi.WMI()
|
||||
|
@ -172,6 +172,7 @@ class Device(_Device):
|
||||
return prefix
|
||||
|
||||
def open_windows(self):
|
||||
time.sleep(6)
|
||||
drives = {}
|
||||
wmi = __import__('wmi', globals(), locals(), [], -1)
|
||||
c = wmi.WMI()
|
||||
|
@ -102,13 +102,23 @@ def config(defaults=None, name='epub'):
|
||||
c.remove_opt('zip')
|
||||
|
||||
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()),
|
||||
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,
|
||||
help=_('Either the path to a CSS stylesheet or raw CSS. This CSS will override any existing CSS 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=_('Either the path to a CSS stylesheet or raw CSS. '
|
||||
'This CSS will override any existing CSS '
|
||||
'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=_('''\
|
||||
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
|
||||
@ -118,14 +128,39 @@ use the expression "/". See the XPath Tutorial in the calibre User Manual for fu
|
||||
help on using this feature.
|
||||
''').replace('\n', ' '))
|
||||
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,
|
||||
help=_('Path to the cover to be used for this book'))
|
||||
structure('prefer_metadata_cover', ['--prefer-metadata-cover'], default=False,
|
||||
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,
|
||||
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',
|
||||
_('''\
|
||||
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.
|
||||
''').replace('\n', ' '))
|
||||
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,
|
||||
help=_("Don't add auto-detected chapters to the Table of Contents."))
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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('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,
|
||||
help=_('Set the right margin in pts. Default is %default'))
|
||||
layout('base_font_size2', ['--base-font-size'], default=12.0,
|
||||
help=_('The base font size in pts. Default is %defaultpt. Set to 0 to disable rescaling of fonts.'))
|
||||
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,
|
||||
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,
|
||||
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',
|
||||
help=_('Print generated OPF file to stdout'))
|
||||
c.add_opt('show_ncx', ['--show-ncx'], default=False, group='debug',
|
||||
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'))
|
||||
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 '
|
||||
'specified directory.'))
|
||||
return c
|
@ -46,6 +46,7 @@ from calibre.ebooks.metadata.toc import TOC
|
||||
from calibre.ebooks.metadata.opf2 import OPF
|
||||
from calibre.ebooks.epub import initialize_container, PROFILES
|
||||
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.constants import preferred_encoding
|
||||
from calibre.customize.ui import run_plugins_on_postprocess
|
||||
@ -141,7 +142,7 @@ class HTMLProcessor(Processor, Rationalizer):
|
||||
p = QPixmap()
|
||||
p.load(path)
|
||||
if not p.isNull():
|
||||
p.save(path+'_calibre_converted.jpg')
|
||||
p.save(path + '_calibre_converted.jpg')
|
||||
os.remove(path)
|
||||
for key, val in self.resource_map.items():
|
||||
if val == rpath:
|
||||
@ -194,6 +195,9 @@ class HTMLProcessor(Processor, Rationalizer):
|
||||
if not tag.text and not tag.get('src', False):
|
||||
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):
|
||||
@ -203,6 +207,13 @@ class HTMLProcessor(Processor, Rationalizer):
|
||||
# self.convert_image(img)
|
||||
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 = {}, {}
|
||||
toc = TOC(base_path=tdir, type='root')
|
||||
stylesheet_map = {}
|
||||
first_image_removed = False
|
||||
for htmlfile in filelist:
|
||||
logging.getLogger('html2epub').debug('Processing %s...'%htmlfile)
|
||||
hp = HTMLProcessor(htmlfile, opts, os.path.join(tdir, 'content'),
|
||||
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.save()
|
||||
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:
|
||||
print toc
|
||||
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)
|
||||
|
||||
opf = OPF(opf_path, tdir)
|
||||
@ -443,10 +460,7 @@ def convert(htmlfile, opts, notification=None, create_epub=True,
|
||||
if os.path.exists(cpath):
|
||||
opf.add_path_to_manifest(cpath, 'image/jpeg')
|
||||
with open(opf_path, 'wb') as f:
|
||||
raw = opf.render()
|
||||
if not raw.startswith('<?xml '):
|
||||
raw = '<?xml version="1.0" encoding="UTF-8"?>\n'+raw
|
||||
f.write(raw)
|
||||
f.write(opf.render())
|
||||
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:
|
||||
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)
|
||||
|
||||
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 os.path.exists(opts.extract_to):
|
||||
|
59
src/calibre/ebooks/epub/pages.py
Normal 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)
|
@ -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
|
||||
from urlparse import urlparse
|
||||
from urlparse import urlparse, urlunparse
|
||||
from urllib import unquote
|
||||
|
||||
from lxml import etree
|
||||
@ -98,7 +98,8 @@ class Link(object):
|
||||
|
||||
@classmethod
|
||||
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):
|
||||
return 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)
|
||||
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_internal = self.is_local and not bool(self.parsed_url.path)
|
||||
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:
|
||||
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.
|
||||
'''
|
||||
class Adder(object):
|
||||
|
||||
def add_item(href, fragment, text, target, type='link'):
|
||||
def __init__(self, toc):
|
||||
self.next_play_order = max([x.play_order for x in toc.flat()])
|
||||
|
||||
def __call__(self, href, fragment, text, target, type='link'):
|
||||
for entry in toc.flat():
|
||||
if entry.href == href and entry.fragment == fragment:
|
||||
return entry
|
||||
if len(text) > 50:
|
||||
text = text[:50] + u'\u2026'
|
||||
return target.add_item(href, fragment, text, type=type)
|
||||
|
||||
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]
|
||||
href = 'content/'+name
|
||||
|
||||
@ -629,13 +636,15 @@ class Processor(Parser):
|
||||
|
||||
if self.opts.level1_toc is not None:
|
||||
level1 = self.opts.level1_toc(self.root)
|
||||
level1_order = []
|
||||
if level1:
|
||||
added = {}
|
||||
for elem in level1:
|
||||
text, _href, frag = elem_to_link(elem, href, counter)
|
||||
counter += 1
|
||||
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')
|
||||
if self.opts.level2_toc is not None:
|
||||
added2 = {}
|
||||
@ -665,6 +674,15 @@ class Processor(Parser):
|
||||
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:
|
||||
return
|
||||
# Add chapters to TOC
|
||||
@ -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; }'
|
||||
if self.opts.remove_paragraph_spacing:
|
||||
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:
|
||||
css += '\n\n' + self.opts.override_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')
|
||||
except:
|
||||
mi = MetaInformation(None, None)
|
||||
|
||||
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))))
|
||||
for attr in ('title', 'authors', 'publisher', 'tags', 'comments'):
|
||||
|
@ -23,7 +23,7 @@ from urllib import unquote as urlunquote
|
||||
from lxml import etree
|
||||
from calibre.ebooks.lit.reader import DirectoryEntry
|
||||
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
|
||||
from calibre.ebooks.oeb.base import namespace, barename, prefixname, \
|
||||
urlnormalize, xpath
|
||||
@ -474,7 +474,7 @@ class LitWriter(object):
|
||||
name = '/data/' + item.id
|
||||
data = item.data
|
||||
secnum = 0
|
||||
if not isinstance(data, basestring):
|
||||
if isinstance(data, etree._Element):
|
||||
self._add_folder(name)
|
||||
rebin = ReBinary(data, item, self._oeb, map=HTML_MAP)
|
||||
self._add_file(name + '/ahc', rebin.ahc, 0)
|
||||
@ -483,6 +483,8 @@ class LitWriter(object):
|
||||
data = rebin.content
|
||||
name = name + '/content'
|
||||
secnum = 1
|
||||
elif isinstance(data, unicode):
|
||||
data = data.encode('utf-8')
|
||||
self._add_file(name, data, secnum)
|
||||
item.size = len(data)
|
||||
|
||||
@ -493,7 +495,7 @@ class LitWriter(object):
|
||||
if item.spine_position is not None:
|
||||
key = 'linear' if item.linear else 'nonlinear'
|
||||
manifest[key].append(item)
|
||||
elif item.media_type == CSS_MIME:
|
||||
elif item.media_type in OEB_STYLES:
|
||||
manifest['css'].append(item)
|
||||
elif item.media_type in LIT_IMAGES:
|
||||
manifest['images'].append(item)
|
||||
@ -506,6 +508,11 @@ class LitWriter(object):
|
||||
data.write(pack('<I', len(items)))
|
||||
for item in items:
|
||||
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)
|
||||
item.offset = offset \
|
||||
if state in ('linear', 'nonlinear') else 0
|
||||
@ -525,7 +532,12 @@ class LitWriter(object):
|
||||
pb3 = StringIO()
|
||||
pb3cur = 0
|
||||
bits = 0
|
||||
linear = []
|
||||
nonlinear = []
|
||||
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)
|
||||
if not item.linear:
|
||||
page_breaks.insert(0, (0, []))
|
||||
|
@ -247,6 +247,12 @@ class MetaInformation(object):
|
||||
if getattr(mi, 'cover_data', None) and mi.cover_data[0] is not None:
|
||||
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):
|
||||
ans = u''
|
||||
@ -267,7 +273,7 @@ class MetaInformation(object):
|
||||
if self.tags:
|
||||
ans += u'Tags : ' + u', '.join([unicode(t) for t in self.tags]) + '\n'
|
||||
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:
|
||||
ans += u'Language : ' + unicode(self.language) + u'\n'
|
||||
return ans.strip()
|
||||
@ -277,11 +283,11 @@ class MetaInformation(object):
|
||||
ans += [(_('Author(s)'), (authors_to_string(self.authors) if self.authors else _('Unknown')))]
|
||||
ans += [(_('Publisher'), unicode(self.publisher))]
|
||||
ans += [(_('Producer'), unicode(self.book_producer))]
|
||||
ans += [(_('Category'), unicode(self.category))]
|
||||
ans += [(_('Comments'), unicode(self.comments))]
|
||||
ans += [('ISBN', unicode(self.isbn))]
|
||||
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))]
|
||||
for i, x in enumerate(ans):
|
||||
ans[i] = u'<tr><td><b>%s</b></td><td>%s</td></tr>'%x
|
||||
|
@ -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">
|
||||
<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: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:language>${mi.language if mi.language else 'UND'}</dc:language>
|
||||
|
@ -17,7 +17,7 @@ from calibre.ebooks.chardet import xml_to_unicode
|
||||
from calibre import relpath
|
||||
from calibre.constants import __appname__, __version__
|
||||
from calibre.ebooks.metadata.toc import TOC
|
||||
from calibre.ebooks.metadata import MetaInformation, get_parser
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
|
||||
|
||||
class Resource(object):
|
||||
@ -414,6 +414,7 @@ class OPF(object):
|
||||
|
||||
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"))]')
|
||||
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)))]')
|
||||
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")]')
|
||||
@ -503,7 +504,7 @@ class OPF(object):
|
||||
|
||||
def set_text(self, elem, content):
|
||||
if elem.tag == self.META:
|
||||
elem.attib['content'] = content
|
||||
elem.attrib['content'] = content
|
||||
else:
|
||||
elem.text = content
|
||||
|
||||
@ -641,6 +642,32 @@ class OPF(object):
|
||||
def fset(self, val):
|
||||
matches = self.authors_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
|
||||
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)
|
||||
@ -789,12 +816,15 @@ class OPF(object):
|
||||
return elem
|
||||
|
||||
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):
|
||||
for attr in ('author_sort', 'title_sort', 'comments', 'category',
|
||||
for attr in ('title', 'authors', 'author_sort', 'title_sort',
|
||||
'publisher', 'series', 'series_index', 'rating',
|
||||
'isbn', 'language', 'tags', 'title', 'authors'):
|
||||
'isbn', 'language', 'tags', 'category', 'comments'):
|
||||
val = getattr(mi, attr, None)
|
||||
if val is not None and val != [] and val != (None, None):
|
||||
setattr(self, attr, val)
|
||||
@ -877,7 +907,8 @@ class OPFCreator(MetaInformation):
|
||||
self.guide = Guide.from_opf_guide(guide_element, 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.utils.genshi.template import MarkupTemplate
|
||||
template = MarkupTemplate(opf_template)
|
||||
@ -917,7 +948,7 @@ class OPFTest(unittest.TestCase):
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<dc:title>A Cool & © ß Title</dc:title>
|
||||
<dc:title opf:file-as="Wow">A Cool & © ß Title</dc:title>
|
||||
<creator opf:role="aut" file-as="Monkey">Monkey Kitchen, Next</creator>
|
||||
<dc:subject>One</dc:subject><dc:subject>Two</dc:subject>
|
||||
<dc:identifier scheme="ISBN">123456789</dc:identifier>
|
||||
@ -933,33 +964,50 @@ class OPFTest(unittest.TestCase):
|
||||
)
|
||||
self.opf = OPF(self.stream, os.getcwd())
|
||||
|
||||
def testReading(self):
|
||||
def testReading(self, opf=None):
|
||||
if opf is None:
|
||||
opf = self.opf
|
||||
self.assertEqual(opf.title, u'A Cool & \xa9 \xdf Title')
|
||||
self.assertEqual(opf.authors, u'Monkey Kitchen,Next'.split(','))
|
||||
self.assertEqual(opf.author_sort, 'Monkey')
|
||||
self.assertEqual(opf.title_sort, 'Wow')
|
||||
self.assertEqual(opf.tags, ['One', 'Two'])
|
||||
self.assertEqual(opf.isbn, '123456789')
|
||||
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')
|
||||
|
||||
def testWriting(self):
|
||||
for test in [('title', 'New & Title'), ('authors', ['One', 'Two']),
|
||||
('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)
|
||||
self.assertEqual(getattr(self.opf, test[0]), test[1])
|
||||
attr, val = test
|
||||
self.assertEqual(getattr(self.opf, attr), val)
|
||||
|
||||
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():
|
||||
return unittest.TestLoader().loadTestsFromTestCase(OPFTest)
|
||||
|
||||
def test():
|
||||
unittest.TextTestRunner(verbosity=2).run(suite())
|
||||
|
||||
|
||||
def option_parser():
|
||||
from calibre.ebooks.metadata import get_parser
|
||||
parser = get_parser('opf')
|
||||
parser.add_option('--language', default=None, help=_('Set the dc:language field'))
|
||||
return parser
|
||||
|
@ -138,6 +138,7 @@ class MobiMLizer(object):
|
||||
return result
|
||||
|
||||
def mobimlize_content(self, tag, text, bstate, istates):
|
||||
if text or tag != 'br':
|
||||
bstate.content = True
|
||||
istate = istates[-1]
|
||||
para = bstate.para
|
||||
@ -188,11 +189,6 @@ class MobiMLizer(object):
|
||||
vspace -= 1
|
||||
if istate.halign != 'auto':
|
||||
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
|
||||
if tag in CONTENT_TAGS:
|
||||
bstate.inline = para
|
||||
@ -200,6 +196,11 @@ class MobiMLizer(object):
|
||||
etree.SubElement(para, XHTML(tag), attrib=istate.attrib)
|
||||
elif tag in TABLE_TAGS:
|
||||
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:
|
||||
return
|
||||
if not pstate or istate != pstate:
|
||||
|
@ -121,6 +121,7 @@ class BookHeader(object):
|
||||
sublangid = (langcode >> 10) & 0xFF
|
||||
self.language = main_language.get(langid, 'ENGLISH')
|
||||
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.exth_flag, = struct.unpack('>L', raw[0x80:0x84])
|
||||
@ -132,11 +133,8 @@ class BookHeader(object):
|
||||
|
||||
|
||||
class MobiReader(object):
|
||||
|
||||
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}',
|
||||
r'\srecindex=[\'"]{0,1}(\d+)[\'"]{0,1}',
|
||||
r'\slorecindex=[\'"]{0,1}(\d+)[\'"]{0,1}'))
|
||||
IMAGE_ATTRS = ('lowrecindex', 'recindex', 'hirecindex')
|
||||
|
||||
def __init__(self, filename_or_stream, verbose=False):
|
||||
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)
|
||||
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 = self.processed_html.replace('\r\n', '\n')
|
||||
self.processed_html = self.processed_html.replace('> <', '>\n<')
|
||||
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)
|
||||
@ -264,6 +263,7 @@ class MobiReader(object):
|
||||
'x-large' : '5',
|
||||
'xx-large' : '6',
|
||||
}
|
||||
mobi_version = self.book_header.mobi_version
|
||||
for tag in root.iter(etree.Element):
|
||||
if tag.tag in ('country-region', 'place', 'placetype', 'placename',
|
||||
'state', 'city'):
|
||||
@ -290,6 +290,11 @@ class MobiReader(object):
|
||||
align = attrib.pop('align').strip()
|
||||
if 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:
|
||||
attrib['style'] = '; '.join(styles)
|
||||
|
||||
@ -300,6 +305,17 @@ class MobiReader(object):
|
||||
except ValueError:
|
||||
if sz in size_map.keys():
|
||||
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):
|
||||
mi = self.book_header.exth.mi
|
||||
@ -399,37 +415,39 @@ class MobiReader(object):
|
||||
|
||||
|
||||
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(
|
||||
'<div style="page-break-after: always; margin: 0; display: block" />',
|
||||
self.processed_html)
|
||||
|
||||
def add_anchors(self):
|
||||
if self.verbose:
|
||||
print 'Adding anchors...'
|
||||
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):
|
||||
positions.add(int(match.group(1)))
|
||||
positions = list(positions)
|
||||
positions.sort()
|
||||
pos = 0
|
||||
self.processed_html = ''
|
||||
for end in positions:
|
||||
end_tag_re = re.compile(r'<\s*/')
|
||||
for end in sorted(positions):
|
||||
if end == 0:
|
||||
continue
|
||||
oend = end
|
||||
l = self.mobi_html.find('<', end)
|
||||
r = self.mobi_html.find('>', end)
|
||||
if r > -1 and r < l: # Move out of tag
|
||||
end = r+1
|
||||
self.processed_html += self.mobi_html[pos:end] + '<a id="filepos%d" name="filepos%d"></a>'%(oend, oend)
|
||||
anchor = '<a id="filepos%d"></a>'
|
||||
if r > -1 and (r < l or l == end or l == -1):
|
||||
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
|
||||
|
||||
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):
|
||||
if self.verbose:
|
||||
@ -455,19 +473,6 @@ class MobiReader(object):
|
||||
self.image_names.append(os.path.basename(path))
|
||||
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):
|
||||
mr = MobiReader(stream)
|
||||
if mr.book_header.exth is None:
|
||||
|
@ -109,6 +109,7 @@ class Stylizer(object):
|
||||
STYLESHEETS = {}
|
||||
|
||||
def __init__(self, tree, path, oeb, profile=PROFILES['PRS505']):
|
||||
self.oeb = oeb
|
||||
self.profile = profile
|
||||
self.logger = oeb.logger
|
||||
item = oeb.manifest.hrefs[path]
|
||||
@ -117,7 +118,7 @@ class Stylizer(object):
|
||||
stylesheets = [HTML_CSS_STYLESHEET]
|
||||
head = xpath(tree, '/h:html/h:head')[0]
|
||||
parser = cssutils.CSSParser()
|
||||
parser.setFetcher(lambda path: ('utf-8', oeb.container.read(path)))
|
||||
parser.setFetcher(self._fetch_css_file)
|
||||
for elem in head:
|
||||
if elem.tag == XHTML('style') and elem.text \
|
||||
and elem.get('type', CSS_MIME) in OEB_STYLES:
|
||||
@ -138,8 +139,7 @@ class Stylizer(object):
|
||||
if path in self.STYLESHEETS:
|
||||
stylesheet = self.STYLESHEETS[path]
|
||||
else:
|
||||
data = XHTML_CSS_NAMESPACE
|
||||
data += oeb.manifest.hrefs[path].data
|
||||
data = self._fetch_css_file(path)[1]
|
||||
stylesheet = parser.parseString(data, href=path)
|
||||
stylesheet.namespaces['h'] = XHTML_NS
|
||||
self.STYLESHEETS[path] = stylesheet
|
||||
@ -167,6 +167,14 @@ class Stylizer(object):
|
||||
for elem in xpath(tree, '//h:*[@style]'):
|
||||
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):
|
||||
results = []
|
||||
if isinstance(rule, CSSStyleRule):
|
||||
|
@ -13,13 +13,9 @@ from urlparse import urldefrag
|
||||
from lxml import etree
|
||||
import cssutils
|
||||
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
|
||||
|
||||
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):
|
||||
def transform(self, oeb, context):
|
||||
oeb.logger.info('Trimming unused files from manifest...')
|
||||
@ -53,15 +49,13 @@ class ManifestTrimmer(object):
|
||||
if found not in used:
|
||||
new.add(found)
|
||||
elif item.media_type == CSS_MIME:
|
||||
def replacer(uri):
|
||||
absuri = item.abshref(urlnormalize(uri))
|
||||
if absuri in oeb.manifest.hrefs:
|
||||
for match in CSSURL_RE.finditer(item.data):
|
||||
href = match.group('url')
|
||||
href = item.abshref(urlnormalize(href))
|
||||
if href in oeb.manifest.hrefs:
|
||||
found = oeb.manifest.hrefs[href]
|
||||
if found not in used:
|
||||
new.add(found)
|
||||
return uri
|
||||
sheet = cssutils.parseString(item.data, href=item.href)
|
||||
cssutils.replaceUrls(sheet, replacer)
|
||||
used.update(new)
|
||||
unchecked = new
|
||||
for item in oeb.manifest.values():
|
||||
|
@ -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.metadata import MetaInformation
|
||||
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
|
||||
|
||||
|
||||
@ -224,6 +224,7 @@ class Config(ResizableDialog, Ui_Dialog):
|
||||
g.setValue(val)
|
||||
elif isinstance(g, (QLineEdit, QTextEdit)):
|
||||
getattr(g, 'setPlainText', g.setText)(val)
|
||||
getattr(g, 'setCursorPosition', lambda x: x)(0)
|
||||
elif isinstance(g, QComboBox):
|
||||
for value in pref.choices:
|
||||
g.addItem(value)
|
||||
@ -253,7 +254,8 @@ class Config(ResizableDialog, Ui_Dialog):
|
||||
self.source_format = d.format()
|
||||
|
||||
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())
|
||||
if text:
|
||||
try:
|
||||
|
@ -493,6 +493,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" >
|
||||
<widget class="QCheckBox" name="opt_no_justification" >
|
||||
<property name="text" >
|
||||
<string>No text &justification</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0" >
|
||||
<widget class="QCheckBox" name="opt_linearize_tables" >
|
||||
<property name="text" >
|
||||
<string>&Linearize tables</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
@ -510,7 +524,7 @@
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="pagesetup_page" >
|
||||
<layout class="QGridLayout" name="_13" >
|
||||
<layout class="QGridLayout" name="gridLayout_7" >
|
||||
<item row="0" column="0" >
|
||||
<widget class="QLabel" name="profile_label" >
|
||||
<property name="text" >
|
||||
@ -531,6 +545,32 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QLabel" name="source_profile_label" >
|
||||
<property name="text" >
|
||||
<string>&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>&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" >
|
||||
<widget class="QLabel" name="label_12" >
|
||||
<property name="text" >
|
||||
@ -630,31 +670,72 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" >
|
||||
<widget class="QLabel" name="source_profile_label" >
|
||||
<item row="8" column="0" colspan="2" >
|
||||
<widget class="QGroupBox" name="page_map_box" >
|
||||
<property name="title" >
|
||||
<string>&Page map</string>
|
||||
</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>&Source profile:</string>
|
||||
<string><p>You can control how calibre detects page boundaries using a XPath expression. To learn how to use XPath expressions see the <a href="http://calibre.kovidgoyal.net/user_manual/xpath.html">XPath tutorial</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.</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>&Boundary XPath:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>opt_source_profile</cstring>
|
||||
<cstring>opt_page</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" >
|
||||
<widget class="QComboBox" name="opt_source_profile" />
|
||||
<widget class="QLineEdit" name="opt_page" />
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<widget class="QLabel" name="dest_profile_label" >
|
||||
<item row="1" column="2" >
|
||||
<widget class="QLabel" name="label_22" >
|
||||
<property name="text" >
|
||||
<string>&Destination profile:</string>
|
||||
<string>&Name XPath:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>opt_dest_profile</cstring>
|
||||
<cstring>opt_page_names</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" >
|
||||
<widget class="QComboBox" name="opt_dest_profile" />
|
||||
<item row="1" column="3" >
|
||||
<widget class="QLineEdit" name="opt_page_names" />
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0" >
|
||||
<spacer name="verticalSpacer" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0" >
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@ -665,8 +746,26 @@
|
||||
<property name="title" >
|
||||
<string>Automatic &chapter detection</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout" >
|
||||
<item row="1" column="0" >
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4" >
|
||||
<item>
|
||||
<widget class="QLabel" name="label_8" >
|
||||
<property name="text" >
|
||||
<string><p>You can control how calibre detects chapters using a XPath expression. To learn how to use XPath expressions see the <a href="http://calibre.kovidgoyal.net/user_manual/xpath.html">XPath tutorial</a></p></string>
|
||||
</property>
|
||||
<property name="textFormat" >
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="wordWrap" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3" >
|
||||
<item>
|
||||
<widget class="QLabel" name="label_17" >
|
||||
<property name="text" >
|
||||
<string>&XPath:</string>
|
||||
@ -676,30 +775,10 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" >
|
||||
<item>
|
||||
<widget class="QLineEdit" name="opt_chapter" />
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2" >
|
||||
<widget class="QLabel" name="label_8" >
|
||||
<property name="text" >
|
||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'DejaVu Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">You can control how calibre detects chapters using a XPath expression. To learn how to use XPath expressions see the <a href="https://calibre.kovidgoyal.net/user_manual/xpath.html"><span style=" text-decoration: underline; color:#0000ff;">XPath tutorial</span></a></p></body></html></string>
|
||||
</property>
|
||||
<property name="textFormat" >
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="wordWrap" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" >
|
||||
<widget class="QComboBox" name="opt_chapter_mark" />
|
||||
</item>
|
||||
<item row="2" column="0" >
|
||||
<item>
|
||||
<widget class="QLabel" name="label_9" >
|
||||
<property name="text" >
|
||||
<string>Chapter &mark:</string>
|
||||
@ -709,6 +788,11 @@ p, li { white-space: pre-wrap; }
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="opt_chapter_mark" />
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -834,7 +918,7 @@ p, li { white-space: pre-wrap; }
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2" >
|
||||
<item row="3" column="0" colspan="2" >
|
||||
<widget class="QDialogButtonBox" name="buttonBox" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
|
@ -18,3 +18,6 @@ class Config(_Config):
|
||||
self.opt_profile.setVisible(False)
|
||||
self.opt_dont_split_on_page_breaks.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)
|
@ -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])
|
||||
|
@ -10,8 +10,8 @@ Scheduler for automated recipe downloads
|
||||
import sys, copy, time
|
||||
from datetime import datetime, timedelta, date
|
||||
from PyQt4.Qt import QDialog, QApplication, QLineEdit, QPalette, SIGNAL, QBrush, \
|
||||
QColor, QAbstractListModel, Qt, QVariant, QFont, QIcon, \
|
||||
QFile, QObject, QTimer, QMutex, QMenu, QAction, QTime
|
||||
QColor, QAbstractItemModel, Qt, QVariant, QFont, QIcon, \
|
||||
QFile, QObject, QTimer, QMutex, QMenu, QAction, QTime, QModelIndex
|
||||
|
||||
from calibre import english_sort
|
||||
from calibre.gui2.dialogs.scheduler_ui import Ui_Dialog
|
||||
@ -30,6 +30,7 @@ class Recipe(object):
|
||||
self.id = id
|
||||
self.title = getattr(recipe_class, 'title', None)
|
||||
self.description = getattr(recipe_class, 'description', None)
|
||||
self.language = getattr(recipe_class, 'language', _('Unknown'))
|
||||
self.last_downloaded = datetime.fromordinal(1)
|
||||
self.downloading = False
|
||||
self.builtin = builtin
|
||||
@ -86,12 +87,12 @@ def load_recipes():
|
||||
recipes.append(r)
|
||||
return recipes
|
||||
|
||||
class RecipeModel(QAbstractListModel, SearchQueryParser):
|
||||
class RecipeModel(QAbstractItemModel, SearchQueryParser):
|
||||
|
||||
LOCATIONS = ['all']
|
||||
|
||||
def __init__(self, db, *args):
|
||||
QAbstractListModel.__init__(self, *args)
|
||||
QAbstractItemModel.__init__(self, *args)
|
||||
SearchQueryParser.__init__(self)
|
||||
self.default_icon = QIcon(':/images/news.svg')
|
||||
self.custom_icon = QIcon(':/images/user_profile.svg')
|
||||
@ -100,7 +101,10 @@ class RecipeModel(QAbstractListModel, SearchQueryParser):
|
||||
recipe = compile_recipe(x[1])
|
||||
self.recipes.append(Recipe(x[0], recipe, False))
|
||||
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):
|
||||
sr = load_recipes()
|
||||
@ -110,6 +114,34 @@ class RecipeModel(QAbstractListModel, SearchQueryParser):
|
||||
recipe.last_downloaded = sr[sr.index(recipe)].last_downloaded
|
||||
|
||||
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):
|
||||
@ -129,36 +161,46 @@ class RecipeModel(QAbstractListModel, SearchQueryParser):
|
||||
try:
|
||||
results = self.parse(unicode(query))
|
||||
except ParseException:
|
||||
self._map = list(range(len(self.recipes)))
|
||||
self._map = dict(self.category_map)
|
||||
else:
|
||||
self._map = []
|
||||
for i, recipe in enumerate(self.recipes):
|
||||
self._map = {}
|
||||
for category in self.categories:
|
||||
self._map[category] = []
|
||||
for recipe in self.category_map[category]:
|
||||
if recipe in results:
|
||||
self._map.append(i)
|
||||
self._map[category].append(recipe)
|
||||
self.reset()
|
||||
|
||||
def resort(self):
|
||||
self.recipes.sort()
|
||||
self.reset()
|
||||
|
||||
def columnCount(self, *args):
|
||||
return 1
|
||||
def index(self, row, column, parent):
|
||||
return self.createIndex(row, column, parent.row() if parent.isValid() else -1)
|
||||
|
||||
def rowCount(self, *args):
|
||||
return len(self._map)
|
||||
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 data(self, index, role):
|
||||
recipe = self.recipes[self._map[index.row()]]
|
||||
if role == Qt.FontRole:
|
||||
if recipe.schedule is not None:
|
||||
font = QFont()
|
||||
font.setBold(True)
|
||||
return QVariant(font)
|
||||
if not recipe.builtin:
|
||||
font = QFont()
|
||||
font.setItalic(True)
|
||||
return QVariant(font)
|
||||
elif role == Qt.DisplayRole:
|
||||
if index.parent().isValid():
|
||||
category = self.categories[index.parent().row()]
|
||||
recipe = self._map[category][index.row()]
|
||||
if role == Qt.DisplayRole:
|
||||
return QVariant(recipe.title)
|
||||
elif role == Qt.UserRole:
|
||||
return recipe
|
||||
@ -170,7 +212,13 @@ class RecipeModel(QAbstractListModel, SearchQueryParser):
|
||||
elif QFile().exists(icon_path):
|
||||
icon = QIcon(icon_path)
|
||||
return QVariant(icon)
|
||||
|
||||
else:
|
||||
category = self.categories[index.row()]
|
||||
if role == Qt.DisplayRole:
|
||||
num = len(self._map[category])
|
||||
return QVariant(category + ' [%d]'%num)
|
||||
elif role == Qt.FontRole:
|
||||
return self.bold_font
|
||||
return NONE
|
||||
|
||||
def update_recipe_schedule(self, recipe):
|
||||
@ -241,7 +289,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
self._model = RecipeModel(db)
|
||||
self.current_recipe = None
|
||||
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.password, SIGNAL('textEdited(QString)'), self.set_account_info)
|
||||
self.connect(self.schedule, SIGNAL('stateChanged(int)'), self.do_schedule)
|
||||
@ -257,11 +305,15 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
self.connect(self.download, SIGNAL('clicked()'), self.download_now)
|
||||
self.search.setFocus(Qt.OtherFocusReason)
|
||||
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'),
|
||||
_('Thursday'), _('Friday'), _('Saturday'), _('Sunday')):
|
||||
self.day.addItem(day)
|
||||
|
||||
def currentChanged(self, current, previous):
|
||||
if current.parent().isValid():
|
||||
self.show_recipe(current)
|
||||
|
||||
def download_now(self):
|
||||
recipe = self._model.data(self.recipes.currentIndex(), Qt.UserRole)
|
||||
self.emit(SIGNAL('download_now(PyQt_PyObject)'), recipe)
|
||||
@ -304,6 +356,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
hour, minute = t.hour(), t.minute()
|
||||
recipe.schedule = encode_schedule(day_of_week, hour, minute)
|
||||
else:
|
||||
recipe.schedule = None
|
||||
if recipe in recipes:
|
||||
recipes.remove(recipe)
|
||||
save_recipes(recipes)
|
||||
@ -432,7 +485,7 @@ class Scheduler(QObject):
|
||||
day_matches = day > 6 or day == now.tm_wday
|
||||
tnow = now.tm_hour*60 + now.tm_min
|
||||
matches = day_matches and (hour*60+minute) < tnow
|
||||
if matches and nowt.toordinal() < date.today().toordinal():
|
||||
if matches and recipe.last_downloaded.toordinal() < date.today().toordinal():
|
||||
needs_downloading.add(recipe)
|
||||
|
||||
self.debug('Needs downloading:', needs_downloading)
|
||||
@ -464,7 +517,7 @@ class Scheduler(QObject):
|
||||
recipe = self.recipes[self.recipes.index(recipe)]
|
||||
now = datetime.utcnow()
|
||||
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)
|
||||
if abs(d - interval) < timedelta(hours=1):
|
||||
recipe.last_downloaded += interval
|
||||
|
@ -24,8 +24,20 @@
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" >
|
||||
<item>
|
||||
<widget class="RecipeListView" name="recipes" >
|
||||
<property name="alternatingRowColors" >
|
||||
<widget class="QTreeView" name="recipes" >
|
||||
<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>
|
||||
</property>
|
||||
</widget>
|
||||
@ -321,13 +333,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>RecipeListView</class>
|
||||
<extends>QListView</extends>
|
||||
<header>recipelistview.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../images.qrc" />
|
||||
</resources>
|
||||
|
@ -114,6 +114,9 @@
|
||||
<property name="text" >
|
||||
<string>See the <a href="http://calibre.kovidgoyal.net/user_manual/gui.html#the-search-interface">User Manual</a> for more help</string>
|
||||
</property>
|
||||
<property name="openExternalLinks" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
BIN
src/calibre/gui2/images/news/el_mercurio_chile.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
src/calibre/gui2/images/news/elmundo.png
Normal file
After Width: | Height: | Size: 550 B |
BIN
src/calibre/gui2/images/news/estadao.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/calibre/gui2/images/news/granma.png
Normal file
After Width: | Height: | Size: 685 B |
BIN
src/calibre/gui2/images/news/jb_online.png
Normal file
After Width: | Height: | Size: 942 B |
BIN
src/calibre/gui2/images/news/la_cuarta.png
Normal file
After Width: | Height: | Size: 514 B |
BIN
src/calibre/gui2/images/news/la_tercera.png
Normal file
After Width: | Height: | Size: 534 B |
BIN
src/calibre/gui2/images/news/lanacion_chile.png
Normal file
After Width: | Height: | Size: 393 B |
BIN
src/calibre/gui2/images/news/o_globo.png
Normal file
After Width: | Height: | Size: 701 B |
BIN
src/calibre/gui2/images/news/the_oz.png
Normal file
After Width: | Height: | Size: 701 B |
BIN
src/calibre/gui2/images/news/vijesti.png
Normal file
After Width: | Height: | Size: 636 B |
@ -115,16 +115,11 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
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.action_show_book_details, SIGNAL('triggered(bool)'), self.show_book_info)
|
||||
def restart_app(c):
|
||||
self.quit(None, restart=True)
|
||||
self.connect(self.action_restart, SIGNAL('triggered(bool)'), restart_app)
|
||||
def sta(r):
|
||||
if r == QSystemTrayIcon.Trigger:
|
||||
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
|
||||
self.connect(self.action_restart, SIGNAL('triggered(bool)'),
|
||||
lambda c : self.quit(None, restart=True))
|
||||
self.connect(self.system_tray_icon, SIGNAL('activated(QSystemTrayIcon::ActivationReason)'),
|
||||
self.system_tray_icon_activated)
|
||||
self.tool_bar.contextMenuEvent = self.no_op
|
||||
####################### Location View ########################
|
||||
QObject.connect(self.location_view, SIGNAL('location_selected(PyQt_PyObject)'),
|
||||
self.location_selected)
|
||||
@ -165,15 +160,11 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
sm.addSeparator()
|
||||
sm.addAction(_('Send to storage card by default'))
|
||||
sm.actions()[-1].setCheckable(True)
|
||||
def default_sync(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)
|
||||
QObject.connect(sm.actions()[-1], SIGNAL('toggled(bool)'), default_sync)
|
||||
QObject.connect(sm.actions()[-1], SIGNAL('toggled(bool)'),
|
||||
self.do_default_sync)
|
||||
|
||||
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
|
||||
md = QMenu()
|
||||
md.addAction(_('Edit metadata individually'))
|
||||
@ -294,7 +285,7 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
self.stack.setCurrentIndex(0)
|
||||
try:
|
||||
db = LibraryDatabase2(self.library_path)
|
||||
except OSError, err:
|
||||
except Exception, err:
|
||||
error_dialog(self, _('Bad database location'), unicode(err)).exec_()
|
||||
dir = unicode(QFileDialog.getExistingDirectory(self,
|
||||
_('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.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):
|
||||
of = unicode(x).strip()
|
||||
if of != prefs['output_format']:
|
||||
@ -1426,10 +1443,10 @@ class Main(MainWindow, Ui_MainWindow):
|
||||
def donate(self, *args):
|
||||
BUTTON = '''
|
||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
|
||||
<input type="hidden" name="cmd" value="_s-xclick">
|
||||
<input type="hidden" name="hosted_button_id" value="1335186">
|
||||
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="">
|
||||
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
|
||||
<input type="hidden" name="cmd" value="_s-xclick" />
|
||||
<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="Donate to support calibre development" />
|
||||
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />
|
||||
</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.')
|
||||
|
@ -27,15 +27,6 @@ from calibre.customize.ui import run_plugins_on_import
|
||||
from calibre import sanitize_file_name
|
||||
|
||||
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,
|
||||
'size':6, 'tags':7, 'comments':8, 'series':9, 'series_index':10,
|
||||
@ -355,6 +346,8 @@ class LibraryDatabase2(LibraryDatabase):
|
||||
if isinstance(self.dbpath, unicode):
|
||||
self.dbpath = self.dbpath.encode(filesystem_encoding)
|
||||
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
|
||||
while True:
|
||||
meth = getattr(self, 'upgrade_version_%d'%self.user_version, None)
|
||||
@ -488,6 +481,16 @@ class LibraryDatabase2(LibraryDatabase):
|
||||
name = title + ' - ' + author
|
||||
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):
|
||||
'''
|
||||
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)
|
||||
# Delete not needed directories
|
||||
if current_path and os.path.exists(spath):
|
||||
if normpath(spath) != normpath(tpath):
|
||||
shutil.rmtree(spath)
|
||||
if self.normpath(spath) != self.normpath(tpath):
|
||||
self.rmtree(spath)
|
||||
parent = os.path.dirname(spath)
|
||||
if len(os.listdir(parent)) == 0:
|
||||
shutil.rmtree(parent)
|
||||
self.rmtree(parent)
|
||||
|
||||
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))
|
||||
self.data.remove(id)
|
||||
if os.path.exists(path):
|
||||
shutil.rmtree(path)
|
||||
self.rmtree(path)
|
||||
parent = os.path.dirname(path)
|
||||
if len(os.listdir(parent)) == 0:
|
||||
shutil.rmtree(parent)
|
||||
self.rmtree(parent)
|
||||
self.conn.execute('DELETE FROM books WHERE id=?', (id,))
|
||||
self.conn.commit()
|
||||
self.clean()
|
||||
|
@ -102,7 +102,7 @@ Device Integration
|
||||
|
||||
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?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -161,7 +161,7 @@ class WorkerMother(object):
|
||||
self.executable = self.gui_executable = sys.executable
|
||||
self.prefix = ''
|
||||
if isfrozen:
|
||||
fd = getattr(sys, 'frameworks_dir')
|
||||
fd = os.path.realpath(getattr(sys, 'frameworks_dir'))
|
||||
contents = os.path.dirname(fd)
|
||||
self.gui_executable = os.path.join(contents, 'MacOS',
|
||||
os.path.basename(sys.executable))
|
||||
|
@ -196,7 +196,7 @@ class Server(object):
|
||||
|
||||
def calculate_month_trend(self, days=31):
|
||||
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)
|
||||
x = list(range(days-1, -1, -1))
|
||||
y = stats.daily_totals
|
||||
@ -205,6 +205,17 @@ class Server(object):
|
||||
ax.set_ylabel('Income ($)')
|
||||
ax.hlines([stats.daily_average], 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)
|
||||
|
||||
def calculate_trend(self):
|
||||
@ -223,7 +234,7 @@ class Server(object):
|
||||
x = [m.min for m in _months]
|
||||
y = [m.total for m in _months]
|
||||
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.bar(x, y, align='center', width=20, color='g')
|
||||
ax.xaxis.set_major_locator(ml)
|
||||
|
@ -25,13 +25,12 @@
|
||||
<div>
|
||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
|
||||
<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" />
|
||||
<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>
|
||||
|
||||
</div>
|
||||
<br />
|
||||
</div>
|
||||
|
||||
<h2>Note</h2>
|
||||
<div class="note">$note</div>
|
||||
|
@ -58,9 +58,9 @@ python setup.py build && sudo python setup.py install
|
||||
<div>
|
||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
|
||||
<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" />
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
5427
src/calibre/translations/ar.po
Normal file
@ -13,7 +13,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
@ -17,7 +17,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41
|
||||
|
@ -14,7 +14,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41
|
||||
|
@ -14,7 +14,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
@ -14,7 +14,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41
|
||||
|
@ -17,7 +17,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41
|
||||
|
@ -13,7 +13,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
@ -14,7 +14,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41
|
||||
|
@ -8,13 +8,13 @@ msgstr ""
|
||||
"Project-Id-Version: calibre\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2009-01-27 01:54+0000\n"
|
||||
"PO-Revision-Date: 2009-01-23 21:22+0000\n"
|
||||
"Last-Translator: Molnár Gábor <csirkus@gmail.com>\n"
|
||||
"PO-Revision-Date: 2009-02-04 20:39+0000\n"
|
||||
"Last-Translator: Kovid Goyal <Unknown>\n"
|
||||
"Language-Team: Hungarian <hu@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2009-01-30 19:20+0000\n"
|
||||
"X-Launchpad-Export-Date: 2009-02-04 21:04+0000\n"
|
||||
"X-Generator: Launchpad (build Unknown)\n"
|
||||
|
||||
#: /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
|
||||
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:217
|
||||
@ -274,7 +274,7 @@ msgstr ""
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/epub/__init__.py:110
|
||||
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
|
||||
msgid ""
|
||||
@ -832,6 +832,9 @@ msgid ""
|
||||
"FONT_DELTA pts. FONT_DELTA can be a fraction.If FONT_DELTA is negative, the "
|
||||
"font size is decreased."
|
||||
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
|
||||
msgid ""
|
||||
@ -1086,6 +1089,8 @@ msgstr "Szerző a fájl metaadataiban. Alapértelmezés: %default"
|
||||
msgid ""
|
||||
"Path to output file. By default a file is created in the current directory."
|
||||
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
|
||||
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 "
|
||||
"the order they were added to the comic."
|
||||
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
|
||||
msgid ""
|
||||
@ -1185,6 +1192,10 @@ msgid ""
|
||||
" \n"
|
||||
"%prog converts mybook.epub to mybook.lrf"
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
msgid "\tParsing HTML..."
|
||||
msgstr ""
|
||||
msgstr "\tHTML beolvasása..."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:342
|
||||
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
|
||||
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:549
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
msgid ""
|
||||
"Could not process image: %s\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
"Nem tudtam feldolgozni a képet: %s\n"
|
||||
"%s"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1763
|
||||
msgid ""
|
||||
"An error occurred while processing a table: %s. Ignoring table markup."
|
||||
msgstr ""
|
||||
"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
|
||||
msgid ""
|
||||
"Bad table:\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
"Hibás táblázat:\n"
|
||||
"%s"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/html/convert_from.py:1787
|
||||
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
|
||||
msgid ""
|
||||
"You have to save the website %s as an html file first and then run html2lrf "
|
||||
"on it."
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
msgid ""
|
||||
@ -1317,6 +1336,13 @@ msgid ""
|
||||
"to local files recursively. Thus, you can use it to \n"
|
||||
"convert a whole tree of HTML files."
|
||||
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
|
||||
msgid ""
|
||||
@ -1325,16 +1351,22 @@ msgid ""
|
||||
"\n"
|
||||
"%prog converts mybook.lit to mybook.lrf"
|
||||
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
|
||||
msgid ""
|
||||
"%prog book.lrf\n"
|
||||
"Convert an LRF file into an LRS (XML UTF-8 encoded) file"
|
||||
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
|
||||
msgid "Output LRS file"
|
||||
msgstr ""
|
||||
msgstr "Kimeneti LRS fájl"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:139
|
||||
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
|
||||
msgid "Parsing LRF..."
|
||||
msgstr ""
|
||||
msgstr "LRF fájl beolvasása..."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:161
|
||||
msgid "Creating XML..."
|
||||
msgstr ""
|
||||
msgstr "XML létrehozása..."
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrfparser.py:163
|
||||
msgid "LRS written to "
|
||||
msgstr ""
|
||||
msgstr "Az LRS fájl helye: "
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:249
|
||||
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
|
||||
msgid ""
|
||||
"%prog [options] file.lrs\n"
|
||||
"Compile an LRS file into an LRF file."
|
||||
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
|
||||
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/metadata/isbndb.py:115
|
||||
msgid "Verbose processing"
|
||||
msgstr ""
|
||||
msgstr "Informatívabb üzenetek feldolgozásnál"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/lrs/convert_from.py:274
|
||||
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
|
||||
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
|
||||
msgid ""
|
||||
@ -1391,42 +1425,44 @@ msgstr ""
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:587
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/metadata/__init__.py:43
|
||||
msgid "Set the book title"
|
||||
msgstr ""
|
||||
msgstr "A könyv címe"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:589
|
||||
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
|
||||
msgid "Set the author"
|
||||
msgstr ""
|
||||
msgstr "Szerző"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:593
|
||||
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/metadata/__init__.py:47
|
||||
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
|
||||
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
|
||||
msgid ""
|
||||
"Path to a txt file containing the comment to be stored in the lrf file."
|
||||
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
|
||||
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/metadata/mobi.py:182
|
||||
msgid "Set the publisher"
|
||||
msgstr ""
|
||||
msgstr "Kiadó"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/ebooks/lrf/meta.py:607
|
||||
msgid "Set the book classification"
|
||||
|
@ -15,7 +15,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
@ -14,7 +14,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41
|
||||
|
@ -14,7 +14,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
@ -14,7 +14,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41
|
||||
|
@ -14,7 +14,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41
|
||||
|
@ -14,7 +14,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41
|
||||
|
@ -14,7 +14,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41
|
||||
|
@ -13,7 +13,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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-Poedit-Country: RUSSIAN FEDERATION\n"
|
||||
"X-Poedit-Language: Russian\n"
|
||||
|
@ -14,7 +14,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41
|
||||
|
@ -13,7 +13,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
"Generated-By: pygettext.py 1.5\n"
|
||||
|
||||
|
@ -14,7 +14,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41
|
||||
|
@ -14,7 +14,7 @@ msgstr ""
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\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"
|
||||
|
||||
#: /home/kovid/work/calibre/src/calibre/customize/__init__.py:41
|
||||
|
@ -47,6 +47,9 @@ class BasicNewsRecipe(object, LoggingInterface):
|
||||
#: The author of this recipe
|
||||
__author__ = __appname__
|
||||
|
||||
#: The language that the news is in
|
||||
language = _('Unknown')
|
||||
|
||||
#: 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
|
||||
#: use :attr:`BasicNewsRecipe.oldest_article`
|
||||
|
@ -24,7 +24,9 @@ recipe_modules = ['recipe_' + r for r in (
|
||||
'joelonsoftware', 'telepolis', 'common_dreams', 'nin', 'tomshardware_de',
|
||||
'pagina12', 'infobae', 'ambito', 'elargentino', 'sueddeutsche', 'the_age',
|
||||
'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
|
||||
|
@ -18,6 +18,7 @@ class Ambito(BasicNewsRecipe):
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
encoding = 'iso--8859-1'
|
||||
language = _('Spanish')
|
||||
cover_url = 'http://www.ambito.com/img/logo_.jpg'
|
||||
|
||||
html2lrf_options = [
|
||||
|
@ -11,7 +11,8 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
||||
class TheAmericanSpectator(BasicNewsRecipe):
|
||||
title = 'The American Spectator'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'news from USA'
|
||||
language = _('English')
|
||||
description = 'News from USA'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
|
@ -8,6 +8,7 @@ class AssociatedPress(BasicNewsRecipe):
|
||||
description = 'Global news'
|
||||
__author__ = 'Kovid Goyal'
|
||||
use_embedded_content = False
|
||||
language = _('English')
|
||||
max_articles_per_feed = 15
|
||||
html2lrf_options = ['--force-page-break-before-tag="chapter"']
|
||||
|
||||
|
@ -13,6 +13,7 @@ class ArsTechnica(BasicNewsRecipe):
|
||||
title = 'Ars Technica'
|
||||
description = 'The art of technology'
|
||||
oldest_article = 7
|
||||
language = _('English')
|
||||
no_stylesheets = True
|
||||
__author__ = 'Michael Warner'
|
||||
max_articles_per_feed = 100
|
||||
|
@ -14,7 +14,7 @@ class TheAtlantic(BasicNewsRecipe):
|
||||
__author__ = 'Kovid Goyal'
|
||||
description = 'Current affairs and politics focussed on the US'
|
||||
INDEX = 'http://www.theatlantic.com/doc/current'
|
||||
|
||||
language = _('English')
|
||||
remove_tags_before = dict(name='div', id='storytop')
|
||||
remove_tags = [dict(name='div', id=['seealso', 'storybottom', 'footer', 'ad_banner_top', 'sidebar'])]
|
||||
no_stylesheets = True
|
||||
|
@ -6,12 +6,13 @@ __copyright__ = '2008, Darko Miletic <darko.miletic at gmail.com>'
|
||||
b92.net
|
||||
'''
|
||||
|
||||
import string,re
|
||||
import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class B92(BasicNewsRecipe):
|
||||
title = u'B92'
|
||||
__author__ = 'Darko Miletic'
|
||||
language = _('Serbian')
|
||||
description = 'Dnevne vesti iz Srbije i sveta'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
|
@ -15,6 +15,7 @@ class Barrons(BasicNewsRecipe):
|
||||
title = 'Barron\'s'
|
||||
max_articles_per_feed = 50
|
||||
needs_subscription = True
|
||||
language = _('English')
|
||||
__author__ = 'Kovid Goyal'
|
||||
description = 'Weekly publication for investors from the publisher of the Wall Street Journal'
|
||||
timefmt = ' [%a, %b %d, %Y]'
|
||||
|
@ -13,6 +13,7 @@ class BBC(BasicNewsRecipe):
|
||||
__author__ = 'Kovid Goyal'
|
||||
description = 'Global news and current affairs from the British Broadcasting Corporation'
|
||||
no_stylesheets = True
|
||||
language = _('English')
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'class':'footer'})]
|
||||
extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }'
|
||||
|
@ -13,6 +13,7 @@ class BusinessWeek(BasicNewsRecipe):
|
||||
title = 'Business Week'
|
||||
description = 'Business News, Stock Market and Financial Advice'
|
||||
__author__ = 'ChuckEggDotCom'
|
||||
language = _('English')
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 10
|
||||
|
||||
|
@ -8,6 +8,7 @@ class ChristianScienceMonitor(BasicNewsRecipe):
|
||||
description = 'Providing context and clarity on national and international news, peoples and cultures'
|
||||
max_articles_per_feed = 20
|
||||
__author__ = 'Kovid Goyal'
|
||||
language = _('English')
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
|
||||
|
@ -15,6 +15,7 @@ class Clarin(BasicNewsRecipe):
|
||||
description = 'Noticias de Argentina y mundo'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 100
|
||||
language = _('Spanish')
|
||||
use_embedded_content = False
|
||||
no_stylesheets = True
|
||||
cover_url = strftime('http://www.clarin.com/diario/%Y/%m/%d/portada.jpg')
|
||||
|
@ -12,6 +12,7 @@ class CNN(BasicNewsRecipe):
|
||||
description = 'Global news'
|
||||
timefmt = ' [%d %b %Y]'
|
||||
__author__ = 'Kovid Goyal'
|
||||
language = _('English')
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
oldest_article = 15
|
||||
|
@ -5,6 +5,7 @@ class CommonDreams(BasicNewsRecipe):
|
||||
title = u'Common Dreams'
|
||||
description = u'Progressive news and views'
|
||||
__author__ = u'XanthanGum'
|
||||
language = _('English')
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
|
||||
|
@ -14,6 +14,7 @@ class CriticaDigital(BasicNewsRecipe):
|
||||
description = 'Noticias de Argentina'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 100
|
||||
language = _('Spanish')
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
encoding = 'cp1252'
|
||||
|
@ -6,6 +6,7 @@ class Cyberpresse(BasicNewsRecipe):
|
||||
title = u'Cyberpresse'
|
||||
__author__ = 'balok'
|
||||
description = 'Canadian news in French'
|
||||
language = _('French')
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
|
@ -12,6 +12,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
||||
class DailyTelegraph(BasicNewsRecipe):
|
||||
title = u'Daily Telegraph'
|
||||
__author__ = u'AprilHare'
|
||||
language = _('English')
|
||||
description = u'News from down under'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 10
|
||||
|
@ -9,6 +9,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
||||
class DeStandaard(BasicNewsRecipe):
|
||||
title = u'De Standaard'
|
||||
__author__ = u'Darko Miletic'
|
||||
language = _('French')
|
||||
description = u'News from Belgium'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
|
@ -14,6 +14,7 @@ class DiscoverMagazine(BasicNewsRecipe):
|
||||
description = u'Science, Technology and the Future'
|
||||
__author__ = 'Mike Diaz'
|
||||
oldest_article = 33
|
||||
language = _('English')
|
||||
max_articles_per_feed = 20
|
||||
feeds = [
|
||||
(u'Technology', u'http://discovermagazine.com/topics/technology/rss.xml'),
|
||||
|
@ -14,6 +14,7 @@ from urllib2 import quote
|
||||
class Economist(BasicNewsRecipe):
|
||||
|
||||
title = 'The Economist'
|
||||
language = _('English')
|
||||
__author__ = "Kovid Goyal"
|
||||
description = 'Global news and current affairs from a European perspective'
|
||||
oldest_article = 7.0
|
||||
|
48
src/calibre/web/feeds/recipes/recipe_el_mercurio_chile.py
Normal 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')
|
||||
]
|
||||
|
@ -11,6 +11,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class ElPais(BasicNewsRecipe):
|
||||
title = u'EL PAIS'
|
||||
language = _('Spanish')
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
|
||||
|
@ -12,6 +12,7 @@ class ElArgentino(BasicNewsRecipe):
|
||||
title = 'ElArgentino.com'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Informacion Libre las 24 horas'
|
||||
language = _('Spanish')
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
|
@ -13,6 +13,7 @@ class ElCronista(BasicNewsRecipe):
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Noticias de Argentina'
|
||||
oldest_article = 2
|
||||
language = _('Spanish')
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
|
46
src/calibre/web/feeds/recipes/recipe_elmundo.py
Normal 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')
|
||||
]
|
@ -13,6 +13,7 @@ class Engadget(BasicNewsRecipe):
|
||||
title = u'Engadget'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Tech news'
|
||||
language = _('English')
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
|
@ -14,6 +14,7 @@ class ESPN(BasicNewsRecipe):
|
||||
title = 'ESPN'
|
||||
description = 'Sports news'
|
||||
__author__ = 'Kovid Goyal'
|
||||
language = _('English')
|
||||
|
||||
needs_subscription = True
|
||||
remove_tags = [dict(name='font', attrs={'class':'footer'}), dict(name='hr', noshade='noshade')]
|
||||
|
55
src/calibre/web/feeds/recipes/recipe_estadao.py
Normal 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
|
51
src/calibre/web/feeds/recipes/recipe_exiled.py
Normal 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
|
||||
|
@ -11,8 +11,9 @@ class FazNet(BasicNewsRecipe):
|
||||
|
||||
title = 'FAZ NET'
|
||||
__author__ = 'Kovid Goyal'
|
||||
description = '"Frankfurter Allgemeine Zeitung'
|
||||
description = 'Frankfurter Allgemeine Zeitung'
|
||||
use_embedded_content = False
|
||||
language = _('German')
|
||||
max_articles_per_feed = 30
|
||||
|
||||
preprocess_regexps = [
|
||||
|
@ -13,6 +13,7 @@ class FinancialTimes(BasicNewsRecipe):
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Financial world news'
|
||||
oldest_article = 2
|
||||
language = _('English')
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
|