113 lines
4.3 KiB
Python
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>'''
|