python-ebooker/ebooker/writer.py

113 lines
4.3 KiB
Python

#!/usr/bin/env python3
# -*- encoding: utf-8 -*-
import zipfile
import filetype
from PIL import Image
from io import BytesIO
from hashlib import md5
from slugify import slugify
def epubwriter(dirout, cached, xhtmls, css):
title, cover_img, chapters, _, imgs_files = cached.load()
zf = zipfile.ZipFile(f'{dirout.joinpath(slugify(title))}.epub', 'w')
zf.writestr('stylesheet.css', css)
zf.writestr('mimetype', 'application/epub+zip')
zf.writestr('META-INF/container.xml', metainfcontainer)
cover_ext = filetype.guess(cover_img).extension
cover_mime = filetype.guess(cover_img).mime
zf.writestr('content.opf', contentopf(title, xhtmls, imgs_files, cover_ext, cover_mime))
zf.writestr('toc.ncx', tocncx(title, [c[0] for c in chapters]))
im = Image.open(BytesIO(cover_img))
width, height = im.size
zf.writestr('titlepage.xhtml', titlepagexhtml('cover.'+cover_ext, width, height))
for i, s in enumerate(xhtmls):
zf.writestr(f'text/{i}.xhtml', s)
zf.writestr('cover.'+cover_ext, cover_img)
for i, b in imgs_files.items():
zf.writestr(f'images/{i}.{filetype.guess(b).extension}', b)
zf.close()
def contentopf(title, xhtmls, imgfs, cover_ext, cover_mime):
return f'''<?xml version="1.0" encoding="utf-8"?>
<package version="2.0" xmlns="http://www.idpf.org/2007/opf"
unique-identifier="BookId">
<metadata xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:opf="http://www.idpf.org/2007/opf">
<dc:title>{title}</dc:title>
<dc:language></dc:language>
<dc:identifier id="BookId">urn:uuid:book{md5(title.encode()).hexdigest()}</dc:identifier>
</metadata>
<manifest>
<item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml" />
<item id="style" href="stylesheet.css" media-type="text/css" />
<item id="titlepage" href="titlepage.xhtml" media-type="application/xhtml+xml" />
<item id="cover" href="cover.{cover_ext}" media-type="{cover_mime}" />'''+ ''.join([f'''
<item id="ch{i}" href="text/{i}.xhtml" media-type="application/xhtml+xml" />''' for i in range(len(xhtmls))]) + ''.join([f'''
<item id="im{i}" href="images/{i}.{filetype.guess(b).extension}" media-type="{filetype.guess(b).mime}" />''' for i, b in imgfs.items()]) +'''
</manifest>
<spine toc="ncx">
<itemref idref="titlepage" />'''+ ''.join([f'''
<itemref idref="ch{i}" />''' for i in range(len(xhtmls))]) +'''
</spine>
</package>'''
def tocncx(title, chapters):
return f'''<?xml version="1.0" encoding="UTF-8"?>
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
<head>
<meta name="dtb:uid" content="book{md5(title.encode()).hexdigest()}"/>
<meta name="dtb:depth" content="1"/>
<meta name="dtb:totalPageCount" content="0"/>
<meta name="dtb:maxPageNumber" content="0"/>
</head>
<docTitle>
<text>{title}</text>
</docTitle>
<navMap>
<navPoint id="titlepage" playOrder="1">
<navLabel>
<text>Cover</text>
</navLabel>
<content src="titlepage.xhtml"/>
</navPoint>''' + ''.join([f'''
<navPoint id="ch{i}" playOrder="{i + 2}">
<navLabel>
<text>{c}</text>
</navLabel>
<content src="text/{i}.xhtml"/>
</navPoint>''' for i, c in enumerate(chapters)]) + '''
</navMap>
</ncx>'''
def titlepagexhtml(filename, width, height):
return '''<?xml version='1.0' encoding='utf-8'?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Cover</title>
<style type="text/css" title="override_css">
@page {padding: 0pt; margin:0pt}
body { text-align: center; padding:0pt; margin: 0pt; }
</style>
</head>
<body>
<div>'''+f'''
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="100%" height="100%" viewBox="0 0 {width} {height}" preserveAspectRatio="none">
<image width="{width}" height="{height}" xlink:href="{filename}"/>
</svg>
</div>
</body>
</html>'''
metainfcontainer = '''<?xml version="1.0" encoding="utf-8"?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
<rootfiles>
<rootfile full-path="content.opf" media-type="application/oebps-package+xml"/>
</rootfiles>
</container>'''