nicer mobi
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Thu, 27 Oct 2011 10:00:17 +0000 (12:00 +0200)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Thu, 27 Oct 2011 10:00:17 +0000 (12:00 +0200)
librarian/cover.py
librarian/epub.py
librarian/epub/toc.html [new file with mode: 0755]
librarian/epub/xsltContent.xsl
librarian/mobi.py

index 9070344..f7d8f7d 100644 (file)
@@ -118,6 +118,13 @@ class Cover(object):
         return self.image().save(format=self.format, *args, **kwargs)
 
 
         return self.image().save(format=self.format, *args, **kwargs)
 
 
+class WLCover(Cover):
+    author_font = ImageFont.truetype(get_resource('fonts/JunicodeWL-Italic.ttf'), 40)
+    title_font = ImageFont.truetype(get_resource('fonts/JunicodeWL-Italic.ttf'), 50)
+    logo_width = 250
+    logo_bottom = 20
+
+
 
 class VirtualoCover(Cover):
     width = 600
 
 class VirtualoCover(Cover):
     width = 600
index 85c51c0..c5942a2 100644 (file)
@@ -158,19 +158,23 @@ def add_to_spine(spine, partno):
 
 
 class TOC(object):
 
 
 class TOC(object):
-    def __init__(self, name=None, part_number=None):
+    def __init__(self, name=None, part_href=None):
         self.children = []
         self.name = name
         self.children = []
         self.name = name
-        self.part_number = part_number
+        self.part_href = part_href
         self.sub_number = None
 
         self.sub_number = None
 
-    def add(self, name, part_number, level=0, is_part=True):
+    def add(self, name, part_href, level=0, is_part=True, index=None):
+        assert level == 0 or index is None
         if level > 0 and self.children:
         if level > 0 and self.children:
-            return self.children[-1].add(name, part_number, level-1, is_part)
+            return self.children[-1].add(name, "part%d.html" % part_href, level-1, is_part)
         else:
             t = TOC(name)
         else:
             t = TOC(name)
-            t.part_number = part_number
-            self.children.append(t)
+            t.part_href = part_href
+            if index is not None:
+                self.children.insert(index, t)
+            else:
+                self.children.append(t)
             if not is_part:
                 t.sub_number = len(self.children) + 1
                 return t.sub_number
             if not is_part:
                 t.sub_number = len(self.children) + 1
                 return t.sub_number
@@ -187,7 +191,13 @@ class TOC(object):
         else:
             return 0
 
         else:
             return 0
 
-    def write_to_xml(self, nav_map, counter):
+    def href(self):
+        src = self.part_href
+        if self.sub_number is not None:
+            src += '#sub%d' % self.sub_number
+        return src
+
+    def write_to_xml(self, nav_map, counter=1):
         for child in self.children:
             nav_point = nav_map.makeelement(NCXNS('navPoint'))
             nav_point.set('id', 'NavPoint-%d' % counter)
         for child in self.children:
             nav_point = nav_map.makeelement(NCXNS('navPoint'))
             nav_point.set('id', 'NavPoint-%d' % counter)
@@ -200,15 +210,26 @@ class TOC(object):
             nav_point.append(nav_label)
 
             content = nav_map.makeelement(NCXNS('content'))
             nav_point.append(nav_label)
 
             content = nav_map.makeelement(NCXNS('content'))
-            src = 'part%d.html' % child.part_number
-            if child.sub_number is not None:
-                src += '#sub%d' % child.sub_number
-            content.set('src', src)
+            content.set('src', child.href())
             nav_point.append(content)
             nav_map.append(nav_point)
             counter = child.write_to_xml(nav_point, counter + 1)
         return counter
 
             nav_point.append(content)
             nav_map.append(nav_point)
             counter = child.write_to_xml(nav_point, counter + 1)
         return counter
 
+    def html_part(self, depth=0):
+        texts = []
+        for child in self.children:
+            texts.append(
+                "<div style='margin-left:%dem;'><a href='%s'>%s</a></div>" %
+                (depth, child.href(), child.name))
+            texts.append(child.html_part(depth+1))
+        return "\n".join(texts)
+
+    def html(self):
+        with open(get_resource('epub/toc.html')) as f:
+            t = unicode(f.read(), 'utf-8')
+        return t % self.html_part()
+
 
 def used_chars(element):
     """ Lists characters used in an ETree Element """
 
 def used_chars(element):
     """ Lists characters used in an ETree Element """
@@ -248,9 +269,9 @@ def transform_chunk(chunk_xml, chunk_no, annotations, empty=False, _empty_html_s
     toc = TOC()
     for element in chunk_xml[0]:
         if element.tag in ("naglowek_czesc", "naglowek_rozdzial", "naglowek_akt", "srodtytul"):
     toc = TOC()
     for element in chunk_xml[0]:
         if element.tag in ("naglowek_czesc", "naglowek_rozdzial", "naglowek_akt", "srodtytul"):
-            toc.add(node_name(element), chunk_no)
+            toc.add(node_name(element), "part%d.html" % chunk_no)
         elif element.tag in ('naglowek_podrozdzial', 'naglowek_scena'):
         elif element.tag in ('naglowek_podrozdzial', 'naglowek_scena'):
-            subnumber = toc.add(node_name(element), chunk_no, level=1, is_part=False)
+            subnumber = toc.add(node_name(element), "part%d.html" % chunk_no, level=1, is_part=False)
             element.set('sub', str(subnumber))
     if empty:
         if not _empty_html_static:
             element.set('sub', str(subnumber))
     if empty:
         if not _empty_html_static:
@@ -267,7 +288,7 @@ def transform_chunk(chunk_xml, chunk_no, annotations, empty=False, _empty_html_s
 
 
 def transform(provider, slug=None, file_path=None, output_file=None, output_dir=None, make_dir=False, verbose=False,
 
 
 def transform(provider, slug=None, file_path=None, output_file=None, output_dir=None, make_dir=False, verbose=False,
-              style=None,
+              style=None, html_toc=False,
               sample=None, cover=None, flags=None):
     """ produces a EPUB file
 
               sample=None, cover=None, flags=None):
     """ produces a EPUB file
 
@@ -298,6 +319,8 @@ def transform(provider, slug=None, file_path=None, output_file=None, output_dir=
             chars = used_chars(html_tree.getroot())
             zip.writestr('OPS/title.html',
                  etree.tostring(html_tree, method="html", pretty_print=True))
             chars = used_chars(html_tree.getroot())
             zip.writestr('OPS/title.html',
                  etree.tostring(html_tree, method="html", pretty_print=True))
+            # add a title page TOC entry
+            toc.add(u"Strona tytułowa", "title.html")
         elif children:
             # write title page for every parent
             if sample is not None and sample <= 0:
         elif children:
             # write title page for every parent
             if sample is not None and sample <= 0:
@@ -404,6 +427,7 @@ def transform(provider, slug=None, file_path=None, output_file=None, output_dir=
 
     opf = xslt(metadata, get_resource('epub/xsltContent.xsl'))
     manifest = opf.find('.//' + OPFNS('manifest'))
 
     opf = xslt(metadata, get_resource('epub/xsltContent.xsl'))
     manifest = opf.find('.//' + OPFNS('manifest'))
+    guide = opf.find('.//' + OPFNS('guide'))
     spine = opf.find('.//' + OPFNS('spine'))
 
     if cover:
     spine = opf.find('.//' + OPFNS('spine'))
 
     if cover:
@@ -423,9 +447,9 @@ def transform(provider, slug=None, file_path=None, output_file=None, output_dir=
             '<item id="cover" href="cover.html" media-type="application/xhtml+xml" />'))
         manifest.append(etree.fromstring(
             '<item id="cover-image" href="%s" media-type="%s" />' % (c_name, c.mime_type())))
             '<item id="cover" href="cover.html" media-type="application/xhtml+xml" />'))
         manifest.append(etree.fromstring(
             '<item id="cover-image" href="%s" media-type="%s" />' % (c_name, c.mime_type())))
-        spine.insert(0, etree.fromstring('<itemref idref="cover" />'))
+        spine.insert(0, etree.fromstring('<itemref idref="cover" linear="no" />'))
         opf.getroot()[0].append(etree.fromstring('<meta name="cover" content="cover-image"/>'))
         opf.getroot()[0].append(etree.fromstring('<meta name="cover" content="cover-image"/>'))
-        opf.getroot().append(etree.fromstring('<guide><reference href="cover.html" type="cover" title="Okładka"/></guide>'))
+        guide.append(etree.fromstring('<reference href="cover.html" type="cover" title="Okładka"/>'))
 
 
     annotations = etree.Element('annotations')
 
 
     annotations = etree.Element('annotations')
@@ -434,23 +458,24 @@ def transform(provider, slug=None, file_path=None, output_file=None, output_dir=
                                '"-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">' \
                                '<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" xml:lang="pl" ' \
                                'version="2005-1"><head></head><docTitle></docTitle><navMap>' \
                                '"-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">' \
                                '<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" xml:lang="pl" ' \
                                'version="2005-1"><head></head><docTitle></docTitle><navMap>' \
-                               '<navPoint id="NavPoint-1" playOrder="1"><navLabel>' \
-                               '<text>Strona tytułowa</text></navLabel><content src="title.html" />' \
-                               '</navPoint></navMap></ncx>')
+                               '</navMap></ncx>')
     nav_map = toc_file[-1]
 
     nav_map = toc_file[-1]
 
+    if html_toc:
+        manifest.append(etree.fromstring(
+            '<item id="html_toc" href="toc.html" media-type="application/xhtml+xml" />'))
+        spine.append(etree.fromstring(
+            '<itemref idref="html_toc" />'))
+        guide.append(etree.fromstring('<reference href="toc.html" type="toc" title="Spis treści"/>'))
+
     toc, chunk_counter, chars, sample = transform_file(input_xml, sample=sample)
 
     toc, chunk_counter, chars, sample = transform_file(input_xml, sample=sample)
 
-    if not toc.children:
-        toc.add(u"Początek utworu", 1)
-    toc_counter = toc.write_to_xml(nav_map, 2)
+    if len(toc.children) < 2:
+        toc.add(u"Początek utworu", "part1.html")
 
     # Last modifications in container files and EPUB creation
     if len(annotations) > 0:
 
     # Last modifications in container files and EPUB creation
     if len(annotations) > 0:
-        nav_map.append(etree.fromstring(
-            '<navPoint id="NavPoint-%(i)d" playOrder="%(i)d" ><navLabel><text>Przypisy</text>'\
-            '</navLabel><content src="annotations.html" /></navPoint>' % {'i': toc_counter}))
-        toc_counter += 1
+        toc.add("Przypisy", "annotations.html")
         manifest.append(etree.fromstring(
             '<item id="annotations" href="annotations.html" media-type="application/xhtml+xml" />'))
         spine.append(etree.fromstring(
         manifest.append(etree.fromstring(
             '<item id="annotations" href="annotations.html" media-type="application/xhtml+xml" />'))
         spine.append(etree.fromstring(
@@ -461,9 +486,7 @@ def transform(provider, slug=None, file_path=None, output_file=None, output_dir=
         zip.writestr('OPS/annotations.html', etree.tostring(
                             html_tree, method="html", pretty_print=True))
 
         zip.writestr('OPS/annotations.html', etree.tostring(
                             html_tree, method="html", pretty_print=True))
 
-    nav_map.append(etree.fromstring(
-        '<navPoint id="NavPoint-%(i)d" playOrder="%(i)d" ><navLabel><text>Strona redakcyjna</text>'\
-        '</navLabel><content src="last.html" /></navPoint>' % {'i': toc_counter}))
+    toc.add("Strona redakcyjna", "last.html")
     manifest.append(etree.fromstring(
         '<item id="last" href="last.html" media-type="application/xhtml+xml" />'))
     spine.append(etree.fromstring(
     manifest.append(etree.fromstring(
         '<item id="last" href="last.html" media-type="application/xhtml+xml" />'))
     spine.append(etree.fromstring(
@@ -505,5 +528,11 @@ def transform(provider, slug=None, file_path=None, output_file=None, output_dir=
     toc_file[0][0].set('content', ''.join((title, 'WolneLektury.pl')))
     toc_file[0][1].set('content', str(toc.depth()))
     set_inner_xml(toc_file[1], ''.join(('<text>', title, '</text>')))
     toc_file[0][0].set('content', ''.join((title, 'WolneLektury.pl')))
     toc_file[0][1].set('content', str(toc.depth()))
     set_inner_xml(toc_file[1], ''.join(('<text>', title, '</text>')))
+
+    # write TOC
+    if html_toc:
+        toc.add(u"Spis treści", "toc.html", index=1)
+        zip.writestr('OPS/toc.html', toc.html().encode('utf-8'))
+    toc.write_to_xml(nav_map)
     zip.writestr('OPS/toc.ncx', etree.tostring(toc_file, pretty_print=True))
     zip.close()
     zip.writestr('OPS/toc.ncx', etree.tostring(toc_file, pretty_print=True))
     zip.close()
diff --git a/librarian/epub/toc.html b/librarian/epub/toc.html
new file mode 100755 (executable)
index 0000000..69d8724
--- /dev/null
@@ -0,0 +1,11 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+    <head>
+        <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
+        <title>WolneLektury.pl</title>
+    </head>
+    <body>
+        <h1>Spis treści</h1>
+        %s
+    </body>
+</html>
index c443a15..83eb376 100644 (file)
@@ -35,6 +35,9 @@
       <spine toc="toc">
         <itemref idref="titlePage" />
       </spine>
       <spine toc="toc">
         <itemref idref="titlePage" />
       </spine>
+      <guide>
+        <reference type="text" title="Początek" href="part1.html" />
+      </guide>
     </package>
   </xsl:template>
 
     </package>
   </xsl:template>
 
index 62b275c..a1f5127 100755 (executable)
@@ -3,23 +3,13 @@
 # This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
 # This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
 # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
 #
-from __future__ import with_statement
-
 import os
 import os.path
 import subprocess
 import os
 import os.path
 import subprocess
-from StringIO import StringIO
-from copy import deepcopy
-from lxml import etree
-import zipfile
 from tempfile import NamedTemporaryFile
 from tempfile import NamedTemporaryFile
-from shutil import rmtree
-
-import sys
 
 
-from librarian import epub
-
-from librarian import functions, get_resource
+from librarian.cover import WLCover
+from librarian import epub, get_resource
 
 
 def transform(provider, slug=None, file_path=None, output_file=None, output_dir=None, make_dir=False, verbose=False,
 
 
 def transform(provider, slug=None, file_path=None, output_file=None, output_dir=None, make_dir=False, verbose=False,
@@ -30,7 +20,7 @@ def transform(provider, slug=None, file_path=None, output_file=None, output_dir=
     slug: slug of file to process, available by provider
     output_file: path to output file
     output_dir: path to directory to save output file to; either this or output_file must be present
     slug: slug of file to process, available by provider
     output_file: path to output file
     output_dir: path to directory to save output file to; either this or output_file must be present
-    make_dir: writes output to <output_dir>/<author>/<slug>.epub instead of <output_dir>/<slug>.epub
+    make_dir: writes output to <output_dir>/<author>/<slug>.mobi instead of <output_dir>/<slug>.mobi
     sample=n: generate sample e-book (with at least n paragraphs)
     cover: a cover.Cover object
     flags: less-advertising,
     sample=n: generate sample e-book (with at least n paragraphs)
     cover: a cover.Cover object
     flags: less-advertising,
@@ -50,11 +40,22 @@ def transform(provider, slug=None, file_path=None, output_file=None, output_dir=
         else:
             output_file = os.path.join(output_dir, os.path.splitext(os.path.basename(file_path))[0] + '.mobi')
 
         else:
             output_file = os.path.join(output_dir, os.path.splitext(os.path.basename(file_path))[0] + '.mobi')
 
+    # provide a cover by default
+    if not cover:
+        cover = WLCover
+
     epub_file = NamedTemporaryFile(suffix='.epub', delete=False)
     if not flags:
         flags = []
     flags = list(flags) + ['without-fonts']
     epub.transform(provider, file_path=file_path, output_file=epub_file, verbose=verbose,
     epub_file = NamedTemporaryFile(suffix='.epub', delete=False)
     if not flags:
         flags = []
     flags = list(flags) + ['without-fonts']
     epub.transform(provider, file_path=file_path, output_file=epub_file, verbose=verbose,
-              sample=sample, cover=None, flags=flags, style=get_resource('mobi/style.css'))
-    subprocess.check_call(['ebook-convert', epub_file.name, output_file])
+              sample=sample, html_toc=True, cover=cover, flags=flags, style=get_resource('mobi/style.css'))
+
+    if verbose:
+        kwargs = {}
+    else:
+        devnull = open("/dev/null", 'w')
+        kwargs = {"stdout": devnull, "stderr": devnull}
+    subprocess.check_call(['ebook-convert', epub_file.name, output_file,
+            '--no-inline-toc'], **kwargs)
     os.unlink(epub_file.name)
     os.unlink(epub_file.name)