Files
hub/tools/mkbooks.py

55 lines
5.3 KiB
Python

# Builds tiny EPUB and PDF files for tools/books-test.js and manual checks: python3 tools/mkbooks.py <outdir>
import sys, zipfile, os
out = sys.argv[1]; os.makedirs(out, exist_ok=True)
with zipfile.ZipFile(os.path.join(out, 'test.epub'), 'w') as z:
z.writestr('mimetype', 'application/epub+zip', compress_type=zipfile.ZIP_STORED)
z.writestr('META-INF/container.xml', '<?xml version="1.0"?><container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container"><rootfiles><rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/></rootfiles></container>')
z.writestr('OEBPS/content.opf', '<?xml version="1.0"?><package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="id"><metadata xmlns:dc="http://purl.org/dc/elements/1.1/"><dc:identifier id="id">x1</dc:identifier><dc:title>Rabbit Tales</dc:title><dc:creator>A. Writer</dc:creator><dc:language>en</dc:language></metadata><manifest><item id="c1" href="c1.xhtml" media-type="application/xhtml+xml"/><item id="c2" href="c2.xhtml" media-type="application/xhtml+xml"/><item id="nav" href="nav.xhtml" media-type="application/xhtml+xml" properties="nav"/></manifest><spine><itemref idref="c1"/><itemref idref="c2"/></spine></package>')
z.writestr('OEBPS/nav.xhtml', '<?xml version="1.0"?><html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops"><body><nav epub:type="toc"><ol><li><a href="c1.xhtml">Chapter One</a></li><li><a href="c2.xhtml">Chapter Two</a></li></ol></nav></body></html>')
para = ' '.join(['The quick brown rabbit jumps over the lazy dog and keeps hopping through the green meadow.'] * 40)
for i, n in ((1, 'One'), (2, 'Two')):
z.writestr(f'OEBPS/c{i}.xhtml', f'<?xml version="1.0"?><html xmlns="http://www.w3.org/1999/xhtml"><head><title>{n}</title></head><body><h1>Chapter {n}</h1>' + ''.join(f'<p>{para} Marker{i}x{k}.</p>' for k in range(8)) + '</body></html>')
# minimal 3-page PDF
objs = []
def add(s): objs.append(s); return len(objs)
add('<< /Type /Catalog /Pages 2 0 R >>'); add('<< /Type /Pages /Kids [3 0 R 5 0 R 7 0 R] /Count 3 >>')
for i in range(3):
pid = add(f'<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents {4+i*2} 0 R /Resources << /Font << /F1 9 0 R >> >> >>')
text = f'BT /F1 24 Tf 72 700 Td (Page {i+1} of the rabbit manual) Tj 0 -40 Td (Carrots are great on page {i+1}.) Tj ET'
add(f'<< /Length {len(text)} >>\nstream\n{text}\nendstream')
add('<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>')
data = '%PDF-1.4\n'; offs = []
for i, o in enumerate(objs): offs.append(len(data)); data += f'{i+1} 0 obj\n{o}\nendobj\n'
x = len(data); data += f'xref\n0 {len(objs)+1}\n0000000000 65535 f \n' + ''.join(f'{o:010d} 00000 n \n' for o in offs) + f'trailer\n<< /Size {len(objs)+1} /Root 1 0 R >>\nstartxref\n{x}\n%%EOF\n'
open(os.path.join(out, 'test.pdf'), 'w').write(data)
# minimal MOBI 6 (uncompressed text, no images), enough to open in a reader. Set DRM=1 in the environment to mark it encrypted.
import struct
def mkmobi(path, drm=False):
html = '<html><head><title>Mobi Rabbits</title></head><body><h1>Chapter One</h1>' + ''.join('<p>The rabbit hops through the clover field number %d, nibbling as it goes.</p>' % i for i in range(30)) + '<mbp:pagebreak/><h1>Chapter Two</h1>' + ''.join('<p>Carrots are the second chapter topic, paragraph %d.</p>' % i for i in range(30)) + '</body></html>'
text = html.encode('utf8'); recs = [text[i:i+4096] for i in range(0, len(text), 4096)]
title = b'Mobi Rabbits'; hdrlen = 232
exth = b''
def item(t, v): return struct.pack('>II', t, 8 + len(v)) + v
body = item(100, b'M. Author') + item(503, title); exth = b'EXTH' + struct.pack('>II', 12 + len(body), 2) + body
exth += b'\0' * (-len(exth) % 4)
r0 = struct.pack('>HHIHHHH', 1, 0, len(text), len(recs), 4096, 2 if drm else 0, 0)
mobi = bytearray(hdrlen)
mobi[0:4] = b'MOBI'; struct.pack_into('>I', mobi, 4, hdrlen); struct.pack_into('>I', mobi, 8, 2); struct.pack_into('>I', mobi, 12, 65001); struct.pack_into('>I', mobi, 16, 1234); struct.pack_into('>I', mobi, 20, 6)
for off in range(24, 64, 4): struct.pack_into('>I', mobi, off, 0xFFFFFFFF)
name_off = 16 + hdrlen + len(exth)
struct.pack_into('>II', mobi, 68 - 16, name_off, len(title)); struct.pack_into('>I', mobi, 72 - 16 + 4, 9)
struct.pack_into('>I', mobi, 0x5C - 16 + 0, 0)
struct.pack_into('>I', mobi, 0x6C - 16, 0xFFFFFFFF); struct.pack_into('>I', mobi, 0x70 - 16, 0xFFFFFFFF)
struct.pack_into('>I', mobi, 0x80 - 16, 0x40)
for off in (0x90 - 16, 0x94 - 16, 0x98 - 16, 0xA0 - 16): struct.pack_into('>I', mobi, off, 0xFFFFFFFF)
struct.pack_into('>I', mobi, 0xE0 - 16, 0xFFFFFFFF); struct.pack_into('>H', mobi, 0xF2 - 16, 0)
rec0 = r0 + bytes(mobi) + exth + title + b'\0\0'
allrecs = [rec0] + recs; n = len(allrecs); off = 78 + 8 * n + 2; offs = []
for r in allrecs: offs.append(off); off += len(r)
hdr = b'MobiRabbits'.ljust(32, b'\0') + struct.pack('>HHIIIIII', 0, 0, 0, 0, 0, 0, 0, 0) + b'BOOKMOBI' + struct.pack('>IIH', 1234, 0, n)
hdr = hdr[:78 - 0] if len(hdr) >= 78 else hdr.ljust(78, b'\0')
lst = b''.join(struct.pack('>IBBBB', o, 0, 0, 0, i & 255) for i, o in enumerate(offs))
open(path, 'wb').write(hdr + lst + b'\0\0' + b''.join(allrecs))
mkmobi(os.path.join(out, 'test.mobi')); mkmobi(os.path.join(out, 'drm.mobi'), True)