From 75e6221d6d9adb0d5dde9580df9eeae71f42eba5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 22 Jan 2009 12:36:34 -0800 Subject: [PATCH 1/8] IGN:Fix parsing of metadata in oeb.base --- src/calibre/ebooks/oeb/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index c2d30eb2c3..7f90183324 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -202,7 +202,7 @@ class Metadata(object): for fq_attr in fq_attrib: if fq_attr in Metadata.ATTRS: attr = fq_attr - fq_attr = OPF2(fq_attr) + fq_attr = OPF(fq_attr) fq_attrib[fq_attr] = fq_attrib.pop(attr) else: attr = barename(fq_attr) @@ -817,10 +817,10 @@ class OEBBook(object): metadata.add('language', 'en') if not metadata.creator: self.logger.warn(u'Creator not specified.') - metadata.add('creator', 'Unknown') + metadata.add('creator', _('Unknown')) if not metadata.title: self.logger.warn(u'Title not specified.') - metadata.add('title', 'Unknown') + metadata.add('title', _('Unknown')) def _manifest_from_opf(self, opf): self.manifest = manifest = Manifest(self) From 741e13c0e23b96f887e314271a79eb4b56880e57 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 22 Jan 2009 13:30:06 -0800 Subject: [PATCH 2/8] Recipe for the Freakonomics Blog by Kovid Goyal --- src/calibre/gui2/images/news/freakonomics.png | Bin 0 -> 4046 bytes src/calibre/web/feeds/recipes/__init__.py | 2 +- .../web/feeds/recipes/recipe_freakonomics.py | 20 ++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 src/calibre/gui2/images/news/freakonomics.png create mode 100644 src/calibre/web/feeds/recipes/recipe_freakonomics.py diff --git a/src/calibre/gui2/images/news/freakonomics.png b/src/calibre/gui2/images/news/freakonomics.png new file mode 100644 index 0000000000000000000000000000000000000000..7cb9e79916bd07f9455a1260fd4b8c2da218706a GIT binary patch literal 4046 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 Tdnnm^VF2J&wQ&`9twR0_Nkc`T literal 0 HcmV?d00001 diff --git a/src/calibre/web/feeds/recipes/__init__.py b/src/calibre/web/feeds/recipes/__init__.py index 71c4b71483..f2ed6d2d24 100644 --- a/src/calibre/web/feeds/recipes/__init__.py +++ b/src/calibre/web/feeds/recipes/__init__.py @@ -23,7 +23,7 @@ recipe_modules = ['recipe_' + r for r in ( 'spiegel_int', 'themarketticker', 'tomshardware', 'xkcd', 'ftd', 'zdnet', 'joelonsoftware', 'telepolis', 'common_dreams', 'nin', 'tomshardware_de', 'pagina12', 'infobae', 'ambito', 'elargentino', 'sueddeutsche', 'the_age', - 'laprensa', 'amspec', + 'laprensa', 'amspec', 'freakonomics', )] import re, imp, inspect, time, os diff --git a/src/calibre/web/feeds/recipes/recipe_freakonomics.py b/src/calibre/web/feeds/recipes/recipe_freakonomics.py new file mode 100644 index 0000000000..704f7f727d --- /dev/null +++ b/src/calibre/web/feeds/recipes/recipe_freakonomics.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net' +__docformat__ = 'restructuredtext en' + +from calibre.web.feeds.news import BasicNewsRecipe + +class Freakonomics(BasicNewsRecipe): + + title = 'Freakonomics Blog' + description = 'The Hidden side of everything' + __author__ = 'Kovid Goyal' + + feeds = [('Blog', 'http://freakonomics.blogs.nytimes.com/feed/atom/')] + + def get_article_url(self, article): + return article.get('feedburner_origlink', None) + + def print_version(self, url): + return url + '?pagemode=print' \ No newline at end of file From 77176797339e68c002cb1aad38aa5de15e43f291 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 22 Jan 2009 13:41:40 -0800 Subject: [PATCH 3/8] Strip empty tags from EPUB. Fix #1660 (epub to LRF minor problems) --- src/calibre/ebooks/epub/from_html.py | 8 ++++++++ src/calibre/ebooks/lrf/html/convert_from.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/epub/from_html.py b/src/calibre/ebooks/epub/from_html.py index d61dc0051a..e0224cea88 100644 --- a/src/calibre/ebooks/epub/from_html.py +++ b/src/calibre/ebooks/epub/from_html.py @@ -166,6 +166,14 @@ class HTMLProcessor(Processor, Rationalizer): if tag.get('type', '').lower().strip() in ('image/svg+xml',): continue tag.getparent().remove(tag) + + + for tag in self.root.xpath('//title|//style'): + if not tag.text: + tag.getparent().remove(tag) + for tag in self.root.xpath('//script'): + if not tag.text and not tag.get('src', False): + tag.getparent().remove(tag) def save(self): for meta in list(self.root.xpath('//meta')): diff --git a/src/calibre/ebooks/lrf/html/convert_from.py b/src/calibre/ebooks/lrf/html/convert_from.py index e884ea7213..673c92ebb9 100644 --- a/src/calibre/ebooks/lrf/html/convert_from.py +++ b/src/calibre/ebooks/lrf/html/convert_from.py @@ -1720,7 +1720,7 @@ class HTMLConverter(object, LoggingInterface): self.previous_text = '\n' elif tagname in ['hr', 'tr']: # tr needed for nested tables self.end_current_block() - if tagname == 'hr': + if tagname == 'hr' and not tag_css.get('width', '').strip().startswith('0'): self.current_page.RuledLine(linelength=int(self.current_page.pageStyle.attrs['textwidth'])) self.previous_text = '\n' self.process_children(tag, tag_css, tag_pseudo_css) From 310cd9e96ed8f4c7b7ac37dddeaa88042eebb447 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 22 Jan 2009 14:18:01 -0800 Subject: [PATCH 4/8] IGN:Default implementation of comic2mobi as wrapper for comic2epub --- src/calibre/ebooks/mobi/from_comic.py | 44 +++++++++++++++++++++++++++ src/calibre/linux.py | 2 ++ 2 files changed, 46 insertions(+) create mode 100644 src/calibre/ebooks/mobi/from_comic.py diff --git a/src/calibre/ebooks/mobi/from_comic.py b/src/calibre/ebooks/mobi/from_comic.py new file mode 100644 index 0000000000..87d63ea15f --- /dev/null +++ b/src/calibre/ebooks/mobi/from_comic.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net' +__docformat__ = 'restructuredtext en' + +''' +''' +import sys, os +from calibre.ebooks.lrf.comic.convert_from import do_convert, option_parser, \ + ProgressBar, terminal_controller +from calibre.ebooks.mobi.from_any import config, any2mobi +from calibre.ptempfile import PersistentTemporaryFile + + +def convert(path_to_file, opts, notification=lambda m, p: p): + pt = PersistentTemporaryFile('_comic2mobi.epub') + pt.close() + orig_output = opts.output + opts.output = pt.name + do_convert(path_to_file, opts, notification=notification, output_format='epub') + opts = config('').parse() + if orig_output is None: + orig_output = os.path.splitext(path_to_file)[0]+'.mobi' + opts.output = orig_output + any2mobi(opts, pt.name) + +def main(args=sys.argv): + parser = option_parser() + opts, args = parser.parse_args(args) + if len(args) < 2: + parser.print_help() + print '\nYou must specify a file to convert' + return 1 + + pb = ProgressBar(terminal_controller, _('Rendering comic pages...'), + no_progress_bar=opts.no_progress_bar or getattr(opts, 'no_process', False)) + notification = pb.update + + source = os.path.abspath(args[1]) + convert(source, opts, notification=notification) + return 0 + +if __name__ == '__main__': + sys.exit(main()) \ No newline at end of file diff --git a/src/calibre/linux.py b/src/calibre/linux.py index a05a7ea7a8..93571cce4f 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -63,6 +63,7 @@ entry_points = { 'oeb2lit = calibre.ebooks.lit.writer:main', 'comic2lrf = calibre.ebooks.lrf.comic.convert_from:main', 'comic2epub = calibre.ebooks.epub.from_comic:main', + 'comic2mobi = calibre.ebooks.mobi.from_comic:main', 'comic2pdf = calibre.ebooks.pdf.from_comic:main', 'calibre-debug = calibre.debug:main', 'calibredb = calibre.library.cli:main', @@ -239,6 +240,7 @@ def setup_completion(fatal_errors): f.write(opts_and_exts('lit2oeb', lit2oeb, ['lit'])) f.write(opts_and_exts('comic2lrf', comicop, ['cbz', 'cbr'])) f.write(opts_and_exts('comic2epub', comic2epub, ['cbz', 'cbr'])) + f.write(opts_and_exts('comic2mobi', comic2epub, ['cbz', 'cbr'])) f.write(opts_and_exts('comic2pdf', comic2epub, ['cbz', 'cbr'])) f.write(opts_and_words('feeds2disk', feeds2disk, feed_titles)) f.write(opts_and_words('feeds2lrf', feeds2lrf, feed_titles)) From d338f4193da68796de64d52cd2091ffb400c33c2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 22 Jan 2009 18:01:40 -0800 Subject: [PATCH 5/8] IGN:... --- src/calibre/gui2/main.ui | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/main.ui b/src/calibre/gui2/main.ui index 2733a61be3..2b243ba2b9 100644 --- a/src/calibre/gui2/main.ui +++ b/src/calibre/gui2/main.ui @@ -119,7 +119,11 @@ - + + + Set the output format that is used when converting ebooks and downloading news + + From fbdac2f78f487ea1ed9d2669c31d2a7864e49ee7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 22 Jan 2009 18:53:49 -0800 Subject: [PATCH 6/8] EPUB Output:Improve handling of
tags that are children of --- src/calibre/ebooks/epub/from_html.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/epub/from_html.py b/src/calibre/ebooks/epub/from_html.py index e0224cea88..722b6cd4e3 100644 --- a/src/calibre/ebooks/epub/from_html.py +++ b/src/calibre/ebooks/epub/from_html.py @@ -153,11 +153,27 @@ class HTMLProcessor(Processor, Rationalizer): Perform various markup transforms to get the output to render correctly in the quirky ADE. ''' - # Replace
that are children of with

 

+ # Replace
that are children of as ADE doesn't handle them if hasattr(self.body, 'xpath'): for br in self.body.xpath('./br'): + if br.getparent() is None: + continue + try: + sibling = br.itersiblings().next() + except: + sibling = None br.tag = 'p' br.text = u'\u00a0' + if (br.tail and br.tail.strip()) or sibling is None or \ + getattr(sibling, 'tag', '') != 'br': + br.set('style', br.get('style', '')+'; margin: 0pt; border:0pt; height:0pt') + else: + sibling.getparent().remove(sibling) + if sibling.tail: + if not br.tail: + br.tail = '' + br.tail += sibling.tail + if self.opts.profile.remove_object_tags: for tag in self.root.xpath('//embed'): From 5060e619ba34b7ff4f26dd8147684ffce05c360c Mon Sep 17 00:00:00 2001 From: "Marshall T. Vandegrift" Date: Thu, 22 Jan 2009 22:30:00 -0500 Subject: [PATCH 7/8] Slightly improve handling of OEB metadata. --- src/calibre/ebooks/oeb/base.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index 7f90183324..8218525a37 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -21,6 +21,7 @@ from lxml import etree from lxml import html from calibre import LoggingInterface from calibre.translations.dynamic import translate +from calibre.startup import get_lang XML_PARSER = etree.XMLParser(recover=True) XML_NS = 'http://www.w3.org/XML/1998/namespace' @@ -30,6 +31,7 @@ OPF2_NS = 'http://www.idpf.org/2007/opf' DC09_NS = 'http://purl.org/metadata/dublin_core' DC10_NS = 'http://purl.org/dc/elements/1.0/' DC11_NS = 'http://purl.org/dc/elements/1.1/' +DC_NSES = set([DC09_NS, DC10_NS, DC11_NS]) XSI_NS = 'http://www.w3.org/2001/XMLSchema-instance' DCTERMS_NS = 'http://purl.org/dc/terms/' NCX_NS = 'http://www.daisy.org/z3986/2005/ncx/' @@ -194,8 +196,12 @@ class Metadata(object): if term == OPF('meta') and not value: term = self.fq_attrib.pop('name') value = self.fq_attrib.pop('content') - elif term in Metadata.TERMS and not namespace(term): - term = DC(term) + elif barename(term).lower() in Metadata.TERMS and \ + (not namespace(term) or namespace(term) in DC_NSES): + # Anything looking like Dublin Core is coerced + term = DC(barename(term).lower()) + elif namespace(term) == OPF2_NS: + term = barename(term) self.term = term self.value = value self.attrib = attrib = {} @@ -814,7 +820,7 @@ class OEBBook(object): break if not metadata.language: self.logger.warn(u'Language not specified.') - metadata.add('language', 'en') + metadata.add('language', get_lang()) if not metadata.creator: self.logger.warn(u'Creator not specified.') metadata.add('creator', _('Unknown')) @@ -857,6 +863,8 @@ class OEBBook(object): extras.sort() for item in extras: spine.add(item, False) + if len(spine) == 0: + raise OEBError("Spine is empty") def _guide_from_opf(self, opf): self.guide = guide = Guide(self) @@ -886,8 +894,11 @@ class OEBBook(object): if len(result) != 1: return False id = result[0] - ncx = self.manifest[id].data - self.manifest.remove(id) + if id not in self.manifest.ids: + return False + item = self.manifest.ids[id] + ncx = item.data + self.manifest.remove(item) title = xpath(ncx, 'ncx:docTitle/ncx:text/text()')[0] self.toc = toc = TOC(title) navmaps = xpath(ncx, 'ncx:navMap') From e6eb0e26f33f09603c1842926915acdf5fec56be Mon Sep 17 00:00:00 2001 From: "Marshall T. Vandegrift" Date: Thu, 22 Jan 2009 22:49:00 -0500 Subject: [PATCH 8/8] Improve Mobipocket metadata: - Only add ISBN s - Add the book title in a way which can be found without actually processing the MOBI header --- src/calibre/ebooks/mobi/writer.py | 9 ++++++++- src/calibre/ebooks/oeb/base.py | 11 ++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/mobi/writer.py b/src/calibre/ebooks/mobi/writer.py index fdafd2e08b..39c77eace5 100644 --- a/src/calibre/ebooks/mobi/writer.py +++ b/src/calibre/ebooks/mobi/writer.py @@ -452,6 +452,13 @@ class MobiWriter(object): code = EXTH_CODES[term] for item in oeb.metadata[term]: data = self.COLLAPSE_RE.sub(' ', unicode(item)) + if term == 'identifier': + if data.lower().startswith('urn:isbn:'): + data = data[9:] + elif item.get('scheme', '').lower() == 'isbn': + pass + else: + continue data = data.encode('utf-8') exth.write(pack('>II', code, len(data) + 8)) exth.write(data) @@ -468,7 +475,7 @@ class MobiWriter(object): nrecs += 3 exth = exth.getvalue() trail = len(exth) % 4 - pad = '' if not trail else '\0' * (4 - trail) + pad = '\0' * (4 - trail) # Always pad w/ at least 1 byte exth = ['EXTH', pack('>II', len(exth) + 12, nrecs), exth, pad] return ''.join(exth) diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py index 8218525a37..3336391a38 100644 --- a/src/calibre/ebooks/oeb/base.py +++ b/src/calibre/ebooks/oeb/base.py @@ -222,7 +222,16 @@ class Metadata(object): raise AttributeError( '%r object has no attribute %r' \ % (self.__class__.__name__, name)) - + + def __getitem__(self, key): + return self.attrib[key] + + def __contains__(self, key): + return key in self.attrib + + def get(self, key, default=None): + return self.attrib.get(key, default) + def __repr__(self): return 'Item(term=%r, value=%r, attrib=%r)' \ % (barename(self.term), self.value, self.attrib)