From 8cfe1f8fb4f50c405ec1fa5ddfa367ef951e9c6b Mon Sep 17 00:00:00 2001 From: Radek Czajka Date: Wed, 26 Oct 2011 17:40:15 +0200 Subject: [PATCH] book2mobi (using calibre) --- librarian/epub.py | 42 ++-- librarian/epub/style.css | 10 +- librarian/epub/xsltAnnotations.xsl | 6 +- librarian/epub/xsltContent.xsl | 4 - librarian/mobi.py | 60 ++++++ librarian/mobi/style.css | 303 +++++++++++++++++++++++++++++ scripts/book2mobi | 57 ++++++ setup.py | 3 +- 8 files changed, 452 insertions(+), 33 deletions(-) create mode 100755 librarian/mobi.py create mode 100755 librarian/mobi/style.css create mode 100755 scripts/book2mobi diff --git a/librarian/epub.py b/librarian/epub.py index 70403fd..85c51c0 100644 --- a/librarian/epub.py +++ b/librarian/epub.py @@ -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 //.epub instead of /.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= '' \ '') - 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( + '' % (fname, fname))) + rmtree(tmpdir) + os.chdir(cwd) zip.writestr('OPS/content.opf', etree.tostring(opf, pretty_print=True)) contents = [] diff --git a/librarian/epub/style.css b/librarian/epub/style.css index 2a41d9b..622c8da 100644 --- a/librarian/epub/style.css +++ b/librarian/epub/style.css @@ -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; diff --git a/librarian/epub/xsltAnnotations.xsl b/librarian/epub/xsltAnnotations.xsl index c66730a..f3e6443 100644 --- a/librarian/epub/xsltAnnotations.xsl +++ b/librarian/epub/xsltAnnotations.xsl @@ -31,12 +31,12 @@ -
+

- + [] -

+

[przypis autorski]

diff --git a/librarian/epub/xsltContent.xsl b/librarian/epub/xsltContent.xsl index 6752be3..c443a15 100644 --- a/librarian/epub/xsltContent.xsl +++ b/librarian/epub/xsltContent.xsl @@ -31,10 +31,6 @@ - - - - diff --git a/librarian/mobi.py b/librarian/mobi.py new file mode 100755 index 0000000..62b275c --- /dev/null +++ b/librarian/mobi.py @@ -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 //.epub instead of /.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 index 0000000..3fa7021 --- /dev/null +++ b/librarian/mobi/style.css @@ -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 index 0000000..1c00b51 --- /dev/null +++ b/scripts/book2mobi @@ -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 + } diff --git a/setup.py b/setup.py index d1db01b..1394643 100644 --- 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', -- 2.20.1