1#!/usr/local/bin/python3.8 2# vim:fileencoding=utf-8 3 4 5__license__ = 'GPL v3' 6__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' 7 8import sys, os 9 10from lxml import etree 11 12from calibre import prepare_string_for_xml, CurrentDir 13from calibre.ptempfile import TemporaryDirectory 14from calibre.ebooks.oeb.base import serialize 15from calibre.ebooks.metadata import authors_to_string 16from calibre.ebooks.metadata.opf2 import metadata_to_opf 17from calibre.ebooks.oeb.polish.parsing import parse 18from calibre.ebooks.oeb.polish.container import OPF_NAMESPACES, opf_to_azw3, Container 19from calibre.ebooks.oeb.polish.utils import guess_type 20from calibre.ebooks.oeb.polish.pretty import pretty_xml_tree, pretty_html_tree 21from calibre.ebooks.oeb.polish.toc import TOC, create_ncx 22from calibre.utils.localization import lang_as_iso639_1 23from calibre.utils.logging import DevNull 24from calibre.utils.zipfile import ZipFile, ZIP_STORED 25from polyglot.builtins import as_bytes 26 27valid_empty_formats = {'epub', 'txt', 'docx', 'azw3', 'md'} 28 29 30def create_toc(mi, opf, html_name, lang): 31 uuid = '' 32 for u in opf.xpath('//*[@id="uuid_id"]'): 33 uuid = u.text 34 toc = TOC() 35 toc.add(_('Start'), html_name) 36 return create_ncx(toc, lambda x:x, mi.title, lang, uuid) 37 38 39def create_book(mi, path, fmt='epub', opf_name='metadata.opf', html_name='start.xhtml', toc_name='toc.ncx'): 40 ''' Create an empty book in the specified format at the specified location. ''' 41 if fmt not in valid_empty_formats: 42 raise ValueError('Cannot create empty book in the %s format' % fmt) 43 if fmt == 'txt': 44 with open(path, 'wb') as f: 45 if not mi.is_null('title'): 46 f.write(as_bytes(mi.title)) 47 return 48 if fmt == 'md': 49 with open(path, 'w', encoding='utf-8') as f: 50 if not mi.is_null('title'): 51 print('#', mi.title, file=f) 52 return 53 if fmt == 'docx': 54 from calibre.ebooks.conversion.plumber import Plumber 55 from calibre.ebooks.docx.writer.container import DOCX 56 from calibre.utils.logging import default_log 57 p = Plumber('a.docx', 'b.docx', default_log) 58 p.setup_options() 59 # Use the word default of one inch page margins 60 for x in 'left right top bottom'.split(): 61 setattr(p.opts, 'margin_' + x, 72) 62 DOCX(p.opts, default_log).write(path, mi, create_empty_document=True) 63 return 64 path = os.path.abspath(path) 65 lang = 'und' 66 opf = metadata_to_opf(mi, as_string=False) 67 for l in opf.xpath('//*[local-name()="language"]'): 68 if l.text: 69 lang = l.text 70 break 71 lang = lang_as_iso639_1(lang) or lang 72 73 opfns = OPF_NAMESPACES['opf'] 74 m = opf.makeelement('{%s}manifest' % opfns) 75 opf.insert(1, m) 76 i = m.makeelement('{%s}item' % opfns, href=html_name, id='start') 77 i.set('media-type', guess_type('a.xhtml')) 78 m.append(i) 79 i = m.makeelement('{%s}item' % opfns, href=toc_name, id='ncx') 80 i.set('media-type', guess_type(toc_name)) 81 m.append(i) 82 s = opf.makeelement('{%s}spine' % opfns, toc="ncx") 83 opf.insert(2, s) 84 i = s.makeelement('{%s}itemref' % opfns, idref='start') 85 s.append(i) 86 CONTAINER = '''\ 87<?xml version="1.0"?> 88<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container"> 89 <rootfiles> 90 <rootfile full-path="{}" media-type="application/oebps-package+xml"/> 91 </rootfiles> 92</container> 93 '''.format(prepare_string_for_xml(opf_name, True)).encode('utf-8') 94 HTML = P('templates/new_book.html', data=True).decode('utf-8').replace( 95 '_LANGUAGE_', prepare_string_for_xml(lang, True) 96 ).replace( 97 '_TITLE_', prepare_string_for_xml(mi.title) 98 ).replace( 99 '_AUTHORS_', prepare_string_for_xml(authors_to_string(mi.authors)) 100 ).encode('utf-8') 101 h = parse(HTML) 102 pretty_html_tree(None, h) 103 HTML = serialize(h, 'text/html') 104 ncx = etree.tostring(create_toc(mi, opf, html_name, lang), encoding='utf-8', xml_declaration=True, pretty_print=True) 105 pretty_xml_tree(opf) 106 opf = etree.tostring(opf, encoding='utf-8', xml_declaration=True, pretty_print=True) 107 if fmt == 'azw3': 108 with TemporaryDirectory('create-azw3') as tdir, CurrentDir(tdir): 109 for name, data in ((opf_name, opf), (html_name, HTML), (toc_name, ncx)): 110 with open(name, 'wb') as f: 111 f.write(data) 112 c = Container(os.path.dirname(os.path.abspath(opf_name)), opf_name, DevNull()) 113 opf_to_azw3(opf_name, path, c) 114 else: 115 with ZipFile(path, 'w', compression=ZIP_STORED) as zf: 116 zf.writestr('mimetype', b'application/epub+zip', compression=ZIP_STORED) 117 zf.writestr('META-INF/', b'', 0o755) 118 zf.writestr('META-INF/container.xml', CONTAINER) 119 zf.writestr(opf_name, opf) 120 zf.writestr(html_name, HTML) 121 zf.writestr(toc_name, ncx) 122 123 124if __name__ == '__main__': 125 from calibre.ebooks.metadata.book.base import Metadata 126 mi = Metadata('Test book', authors=('Kovid Goyal',)) 127 path = sys.argv[-1] 128 ext = path.rpartition('.')[-1].lower() 129 if ext not in valid_empty_formats: 130 print('Unsupported format:', ext) 131 raise SystemExit(1) 132 create_book(mi, path, fmt=ext) 133