book2mobi (using calibre)
authorRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Wed, 26 Oct 2011 15:40:15 +0000 (17:40 +0200)
committerRadek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>
Wed, 26 Oct 2011 15:40:15 +0000 (17:40 +0200)
librarian/epub.py
librarian/epub/style.css
librarian/epub/xsltAnnotations.xsl
librarian/epub/xsltContent.xsl
librarian/mobi.py [new file with mode: 0755]
librarian/mobi/style.css [new file with mode: 0755]
scripts/book2mobi [new file with mode: 0755]
setup.py

index 70403fd..85c51c0 100644 (file)
@@ -267,6 +267,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,
+              style=None,
               sample=None, cover=None, flags=None):
     """ produces a EPUB file
 
@@ -277,7 +278,7 @@ def transform(provider, slug=None, file_path=None, output_file=None, output_dir=
     make_dir: writes output to <output_dir>/<author>/<slug>.epub instead of <output_dir>/<slug>.epub
     sample=n: generate sample e-book (with at least n paragraphs)
     cover: a cover.Cover object
-    flags: less-advertising,
+    flags: less-advertising, without-fonts
     """
 
     def transform_file(input_xml, chunk_counter=1, first=True, sample=None):
@@ -395,9 +396,11 @@ def transform(provider, slug=None, file_path=None, output_file=None, output_dir=
                        '<rootfiles><rootfile full-path="OPS/content.opf" ' \
                        'media-type="application/oebps-package+xml" />' \
                        '</rootfiles></container>')
-    zip.write(get_resource('epub/style.css'), os.path.join('OPS', 'style.css'))
     zip.write(get_resource('res/wl-logo-small.png'), os.path.join('OPS', 'logo_wolnelektury.png'))
     zip.write(get_resource('res/jedenprocent.png'), os.path.join('OPS', 'jedenprocent.png'))
+    if not style:
+        style = get_resource('epub/style.css')
+    zip.write(style, os.path.join('OPS', 'style.css'))
 
     opf = xslt(metadata, get_resource('epub/xsltContent.xsl'))
     manifest = opf.find('.//' + OPFNS('manifest'))
@@ -470,22 +473,25 @@ def transform(provider, slug=None, file_path=None, output_file=None, output_dir=
     zip.writestr('OPS/last.html', etree.tostring(
                         html_tree, method="html", pretty_print=True))
 
-    # strip fonts
-    tmpdir = mkdtemp('-librarian-epub')
-    cwd = os.getcwd()
-
-    os.chdir(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'font-optimizer'))
-    for fname in 'DejaVuSerif.ttf', 'DejaVuSerif-Bold.ttf', 'DejaVuSerif-Italic.ttf', 'DejaVuSerif-BoldItalic.ttf':
-        optimizer_call = ['perl', 'subset.pl', '--chars', ''.join(chars).encode('utf-8'),
-                          get_resource('fonts/' + fname), os.path.join(tmpdir, fname)]
-        if verbose:
-            print "Running font-optimizer"
-            subprocess.check_call(optimizer_call)
-        else:
-            subprocess.check_call(optimizer_call, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-        zip.write(os.path.join(tmpdir, fname), os.path.join('OPS', fname))
-    rmtree(tmpdir)
-    os.chdir(cwd)
+    if not flags or not 'without-fonts' in flags:
+        # strip fonts
+        tmpdir = mkdtemp('-librarian-epub')
+        cwd = os.getcwd()
+
+        os.chdir(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'font-optimizer'))
+        for fname in 'DejaVuSerif.ttf', 'DejaVuSerif-Bold.ttf', 'DejaVuSerif-Italic.ttf', 'DejaVuSerif-BoldItalic.ttf':
+            optimizer_call = ['perl', 'subset.pl', '--chars', ''.join(chars).encode('utf-8'),
+                              get_resource('fonts/' + fname), os.path.join(tmpdir, fname)]
+            if verbose:
+                print "Running font-optimizer"
+                subprocess.check_call(optimizer_call)
+            else:
+                subprocess.check_call(optimizer_call, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            zip.write(os.path.join(tmpdir, fname), os.path.join('OPS', fname))
+            manifest.append(etree.fromstring(
+                '<item id="%s" href="%s" media-type="font/ttf" />' % (fname, fname)))
+        rmtree(tmpdir)
+        os.chdir(cwd)
 
     zip.writestr('OPS/content.opf', etree.tostring(opf, pretty_print=True))
     contents = []
index 2a41d9b..622c8da 100644 (file)
@@ -107,28 +107,24 @@ p
        text-align: left;
 }
 
-.annotation
+.annotation-anchor
 {
        font-style: normal;
        font-weight: normal;
        font-size: 0.875em;
-}
-
-#footnotes .annotation
-{
        display: block;
        float: left;
        width: 2.5em;
        clear: both;
 }
 
-#footnotes div
+.annotation
 {
     margin: 0;
     margin-top: 1.5em;
 }
 
-#footnotes p
+.annotation-body
 {
        margin-left: 2.5em;
        font-size: 0.875em;
index c66730a..f3e6443 100644 (file)
   </xsl:template>
 
   <xsl:template match="pa|pe|pr|pt" mode="przypis">
-    <div xmlns="http://www.w3.org/1999/xhtml">
+    <div xmlns="http://www.w3.org/1999/xhtml" class="annotation">
       <p id="annotation-{@number}" xmlns="http://www.w3.org/1999/xhtml"></p>
-      <a class="annotation" href="part{@part}.html#anchor-{@number}" xmlns="http://www.w3.org/1999/xhtml">
+      <a class="annotation-anchor" href="part{@part}.html#anchor-{@number}" xmlns="http://www.w3.org/1999/xhtml">
         [<xsl:value-of select="@number" />]
       </a>
-      <p xmlns="http://www.w3.org/1999/xhtml">
+      <p class="annotation-body" xmlns="http://www.w3.org/1999/xhtml">
         <xsl:apply-templates />
         <xsl:if test="name()='pa'"> [przypis autorski]</xsl:if>
       </p>
index 6752be3..c443a15 100644 (file)
         <item id="titlePage" href="title.html" media-type="application/xhtml+xml" />
         <item id="logo_wolnelektury" href="logo_wolnelektury.png" media-type="image/png" />
         <item id="jedenprocent" href="jedenprocent.png" media-type="image/png" />
-        <item id="DejaVuSerif.ttf" href="DejaVuSerif.ttf" media-type="font/ttf" />
-        <item id="DejaVuSerif-Bold.ttf" href="DejaVuSerif-Bold.ttf" media-type="font/ttf" />
-        <item id="DejaVuSerif-BoldItalic.ttf" href="DejaVuSerif-BoldItalic.ttf" media-type="font/ttf" />
-        <item id="DejaVuSerif-Italic.ttf" href="DejaVuSerif-Italic.ttf" media-type="font/ttf" />
       </manifest>
       <spine toc="toc">
         <itemref idref="titlePage" />
diff --git a/librarian/mobi.py b/librarian/mobi.py
new file mode 100755 (executable)
index 0000000..62b275c
--- /dev/null
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+#
+# 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
+from StringIO import StringIO
+from copy import deepcopy
+from lxml import etree
+import zipfile
+from tempfile import NamedTemporaryFile
+from shutil import rmtree
+
+import sys
+
+from librarian import epub
+
+from librarian import functions, get_resource
+
+
+def transform(provider, slug=None, file_path=None, output_file=None, output_dir=None, make_dir=False, verbose=False,
+              sample=None, cover=None, flags=None):
+    """ produces a MOBI file
+
+    provider: a DocProvider
+    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
+    sample=n: generate sample e-book (with at least n paragraphs)
+    cover: a cover.Cover object
+    flags: less-advertising,
+    """
+
+    # if output to dir, create the file
+    if output_dir is not None:
+        if make_dir:
+            author = unicode(book_info.author)
+            output_dir = os.path.join(output_dir, author)
+            try:
+                os.makedirs(output_dir)
+            except OSError:
+                pass
+        if slug:
+            output_file = os.path.join(output_dir, '%s.mobi' % slug)
+        else:
+            output_file = os.path.join(output_dir, os.path.splitext(os.path.basename(file_path))[0] + '.mobi')
+
+    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])
+    os.unlink(epub_file.name)
diff --git a/librarian/mobi/style.css b/librarian/mobi/style.css
new file mode 100755 (executable)
index 0000000..3fa7021
--- /dev/null
@@ -0,0 +1,303 @@
+/* =================================================== */
+/* = Common elements: headings, paragraphs and lines = */
+/* =================================================== */
+
+
+.h2
+{
+       font-size: 2em;
+       margin: 0;
+       margin-top: 1.5em;
+       font-weight: bold;
+       line-height: 1.5em;
+}
+
+.h3
+{
+    text-align:left;
+    font-size: 1.5em;
+    margin-top: 1.5em;
+    font-weight: normal;
+    line-height: 1.5em;
+}
+
+.h4
+{
+       font-size: 1em;
+    margin: 0;
+    margin-top: 1.5em;
+       line-height: 1.5em;
+}
+
+p
+{
+       margin: 0;
+}
+
+/* ======================== */
+/* = Footnotes and themes = */
+/* ======================== */
+
+.annotation-anchor
+{
+       font-style: normal;
+       font-weight: normal;
+       font-size: 0.875em;
+       display: block;
+       float: left;
+       width: 2.5em;
+       clear: both;
+}
+
+.annotation
+{
+    margin: 0;
+    margin-top: 1.5em;
+}
+
+.annotation-body
+{
+       margin-left: 2.5em;
+       font-size: 0.875em;
+}
+
+.block
+{
+       font-size: 0.875em;
+       padding: 1em;
+}
+
+/* ============= */
+/* = Numbering = */
+/* ============= */
+
+.anchor
+{
+       margin: -0.25em -0.5em;
+       color: #777;
+       font-size: 0.875em;
+       width: 2em;
+       text-align: center;
+       padding: 0.25em 0.5em;
+       line-height: 1.5em;
+}
+
+/* =================== */
+/* = Custom elements = */
+/* =================== */
+
+.title-page
+{
+    margin-top: 1.5em;
+}
+
+.title
+{
+    font-size: 3em;
+    text-align: center;
+    line-height: 1.5em;
+    font-weight: bold;
+}
+
+.author
+{
+    margin: 0;
+    text-align: center;
+    font-weight: bold;
+
+    font-size: 1.5em;
+    line-height: 1.5em;
+    margin-bottom: 0.25em;
+}
+
+.intitle
+{
+    margin: 0;
+    text-align: center;
+    font-weight: bold;
+
+    font-size: 1.5em;
+    line-height: 1.5em;
+    margin-bottom: 0.25em;
+}
+
+.insubtitle
+{
+    margin: 0;
+    text-align: center;
+    font-weight: bold;
+
+    font-size: 1em;
+    line-height: 1.5em;
+    margin-bottom: 0.25em;
+}
+
+.collection
+{
+    margin: 0;
+    text-align: center;
+    font-weight: bold;
+
+       font-size: 1.125em;
+       line-height: 1.5em;
+       margin-bottom: -0.25em;
+}
+
+.subtitle
+{
+    margin: 0;
+    text-align: center;
+    font-weight: bold;
+
+       font-size: 1.5em;
+       line-height: 1.5em;
+       margin-top: -0.25em;
+}
+
+div.didaskalia
+{
+       font-style: italic;
+       margin-top: 0.5em;
+       margin-left: 1.5em;
+}
+
+div.kwestia
+{
+       margin-top: 0.5em;
+}
+
+.paragraph
+{
+       text-align: justify;
+       margin-top: 1.5em;
+}
+
+.motto
+{
+       text-align: justify;
+       font-style: italic;
+       margin-top: 1.5em;
+}
+
+.motto_podpis
+{
+       font-size: 0.875em;
+       text-align: right;
+}
+
+div.fragment
+{
+       border-bottom: 0.1em solid #999;
+       padding-bottom: 1.5em;
+}
+
+div.note
+{
+       text-align: right;
+       font-style: italic;
+}
+div.note div.paragraph
+{
+    text-align: right;
+    font-style: italic;
+}
+div.dedication
+{
+    text-align: right;
+    font-style: italic;
+}
+div.dedication div.paragaph
+{
+    text-align: right;
+    font-style: italic;
+}
+
+
+hr.spacer
+{
+       height: 3em;
+       visibility: hidden;
+}
+
+hr.spacer-line
+{
+       margin: 0;
+       margin-top: 1.5em;
+       margin-bottom: 1.5em;
+       border: none;
+       border-bottom: 0.1em solid #000;
+}
+
+.spacer-asterisk
+{
+       padding: 0;
+    margin: 0;
+    margin-top: 1.5em;
+    margin-bottom: 1.5em;
+       text-align: center;
+}
+
+div.person-list ol
+{
+       list-style: none;
+       padding: 0;
+       padding-left: 1.5em;
+}
+
+.place-and-time
+{
+       font-style: italic;
+}
+
+em.math
+{
+       font-style: italic;
+}
+em.foreign-word
+{
+    font-style: italic;
+}
+em.book-title
+{
+    font-style: italic;
+}
+em.didaskalia
+{
+    font-style: italic;
+}
+
+em.author-emphasis
+{
+       letter-spacing: 0.1em;
+}
+
+.person-list em.person
+{
+       font-style: normal;
+       font-variant: small-caps;
+       /*text-transform: uppercase;*/
+}
+
+.info
+{
+       text-align: center;
+       margin-bottom: 1em;
+}
+.info div
+{
+    text-align: center;
+}
+
+.info img
+{
+       margin: 0;
+       margin-left: 2em;
+       margin-right: 2em;
+}
+
+p.minor {
+    font-size: 0.75em;
+}
+p.footer {
+    margin-top: 2em;
+}
diff --git a/scripts/book2mobi b/scripts/book2mobi
new file mode 100755 (executable)
index 0000000..1c00b51
--- /dev/null
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# This file is part of Librarian, licensed under GNU Affero GPLv3 or later.
+# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
+#
+import os.path
+import optparse
+
+from librarian import mobi, DirDocProvider, ParseError
+
+
+if __name__ == '__main__':
+    # Parse commandline arguments
+    usage = """Usage: %prog [options] SOURCE [SOURCE...]
+    Convert SOURCE files to MOBI format."""
+
+    parser = optparse.OptionParser(usage=usage)
+
+    parser.add_option('-v', '--verbose', action='store_true', dest='verbose', default=False,
+        help='print status messages to stdout')
+    parser.add_option('-d', '--make-dir', action='store_true', dest='make_dir', default=False,
+                      help='create a directory for author and put the PDF in it')
+    parser.add_option('-o', '--output-file', dest='output_file', metavar='FILE',
+                      help='specifies the output file')
+    parser.add_option('-O', '--output-dir', dest='output_dir', metavar='DIR',
+                      help='specifies the directory for output')
+
+    options, input_filenames = parser.parse_args()
+
+    if len(input_filenames) < 1:
+        parser.print_help()
+        exit(1)
+
+    # Do some real work
+    try:
+        for main_input in input_filenames:
+            if options.verbose:
+                print main_input
+            path, fname = os.path.realpath(main_input).rsplit('/', 1)
+            provider = DirDocProvider(path)
+
+            output_dir = output_file = None
+            if options.output_dir:
+                output_dir = options.output_dir
+            elif options.output_file:
+                output_file = options.output_file
+            else:
+                output_dir = path
+
+            mobi.transform(provider, file_path=main_input, output_dir=output_dir, output_file=output_file, make_dir=options.make_dir)
+    except ParseError, e:
+        print '%(file)s:%(name)s:%(message)s' % {
+            'file': main_input,
+            'name': e.__class__.__name__,
+            'message': e
+        }
index d1db01b..1394643 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -29,13 +29,14 @@ setup(
     maintainer_email='radek.czajka@gmail.com',
     url='http://github.com/fnp/librarian',
     packages=['librarian'],
-    package_data={'librarian': ['xslt/*.xslt', 'epub/*', 'pdf/*', 'fonts/*', 'res/*'] +
+    package_data={'librarian': ['xslt/*.xslt', 'epub/*', 'mobi/*', 'pdf/*', 'fonts/*', 'res/*'] +
                                 whole_tree(os.path.join(os.path.dirname(__file__), 'librarian'), 'font-optimizer')},
     include_package_data=True,
     install_requires=['lxml>=2.2'],
     scripts=['scripts/book2html',
              'scripts/book2txt',
              'scripts/book2epub',
+             'scripts/book2mobi',
              'scripts/book2pdf',
              'scripts/book2partner',
              'scripts/bookfragments',