From 25312736d48b8bff4c5e55ed2bca5cbe8a11e103 Mon Sep 17 00:00:00 2001 From: David Lynch Date: Wed, 21 Jul 2021 10:32:55 -0500 Subject: [PATCH] Use namedtuple in the epub generator so it's easier to understand --- ebook/__init__.py | 26 +++++++++++---------- ebook/epub.py | 59 ++++++++++++++++++++++------------------------- 2 files changed, 41 insertions(+), 44 deletions(-) diff --git a/ebook/__init__.py b/ebook/__init__.py index 5b1d639..c4c543a 100644 --- a/ebook/__init__.py +++ b/ebook/__init__.py @@ -1,4 +1,4 @@ -from .epub import make_epub +from .epub import make_epub, EpubFile from .cover import make_cover from .cover import make_cover_from_url @@ -89,13 +89,13 @@ def chapter_html(story, titleprefix=None, normalize=False): if normalize: title = unicodedata.normalize('NFKC', title) contents = unicodedata.normalize('NFKC', contents) - chapters.append(( - title, - f'{story.id}/chapter{i + 1}.html', - html_template.format(title=html.escape(title), text=contents) + chapters.append(EpubFile( + title=title, + path=f'{story.id}/chapter{i + 1}.html', + contents=html_template.format(title=html.escape(title), text=contents) )) if story.footnotes: - chapters.append(("Footnotes", f'{story.id}/footnotes.html', html_template.format(title="Footnotes", text='\n\n'.join(story.footnotes)))) + chapters.append(EpubFile(title="Footnotes", path=f'{story.id}/footnotes.html', contents=html_template.format(title="Footnotes", text='\n\n'.join(story.footnotes)))) return chapters @@ -124,7 +124,7 @@ def generate_epub(story, cover_options={}, output_filename=None, normalize=False cover_options = attr.asdict(cover_options, filter=lambda k, v: v is not None, retain_collection_types=True) # The cover is static, and the only change comes from the image which we generate - html = [('Cover', 'cover.html', cover_template)] + files = [EpubFile(title='Cover', path='cover.html', contents=cover_template)] if cover_options and "cover_url" in cover_options: image = make_cover_from_url(cover_options["cover_url"], story.title, story.author) @@ -133,16 +133,18 @@ def generate_epub(story, cover_options={}, output_filename=None, normalize=False else: image = make_cover(story.title, story.author, **cover_options) - cover_image = ('images/cover.png', image.read(), 'image/png') + cover_image = EpubFile(path='images/cover.png', contents=image.read(), filetype='image/png') - html.append(('Front Matter', 'frontmatter.html', frontmatter_template.format(now=datetime.datetime.now(), **metadata))) + files.append(EpubFile(title='Front Matter', path='frontmatter.html', contents=frontmatter_template.format(now=datetime.datetime.now(), **metadata))) - html.extend(chapter_html(story, normalize=normalize)) + files.extend(chapter_html(story, normalize=normalize)) - css = ('Styles/base.css', requests.Session().get('https://raw.githubusercontent.com/mattharrison/epub-css-starter-kit/master/css/base.css').text, 'text/css') + css = EpubFile(path='Styles/base.css', contents=requests.Session().get('https://raw.githubusercontent.com/mattharrison/epub-css-starter-kit/master/css/base.css').text, filetype='text/css') + + files.extend((css, cover_image)) output_filename = output_filename or story.title + '.epub' - output_filename = make_epub(output_filename, html, metadata, extra_files=(css, cover_image)) + output_filename = make_epub(output_filename, files, metadata) return output_filename diff --git a/ebook/epub.py b/ebook/epub.py index fa21295..dd52ebc 100644 --- a/ebook/epub.py +++ b/ebook/epub.py @@ -5,6 +5,7 @@ import zipfile import xml.etree.ElementTree as etree import uuid import string +from collections import namedtuple """ So, an epub is approximately a zipfile of HTML files, with @@ -14,6 +15,9 @@ This totally started from http://www.manuel-strehl.de/dev/simple_epub_ebooks_wit """ +EpubFile = namedtuple('EbookFile', 'path, contents, title, filetype', defaults=(False, False, "application/xhtml+xml")) + + def sanitize_filename(s): """Take a string and return a valid filename constructed from the string. Uses a whitelist approach: any characters not present in valid_chars are @@ -31,7 +35,7 @@ def sanitize_filename(s): return filename -def make_epub(filename, html_files, meta, extra_files=False, compress=True): +def make_epub(filename, files, meta, compress=True): unique_id = meta.get('unique_id', False) if not unique_id: unique_id = 'leech_book_' + str(uuid.uuid4()) @@ -90,49 +94,40 @@ def make_epub(filename, html_files, meta, extra_files=False, compress=True): navmap = etree.SubElement(ncx, 'navMap') # Write each HTML file to the ebook, collect information for the index - for i, html in enumerate(html_files): + for i, file in enumerate(files): file_id = 'file_%d' % (i + 1) etree.SubElement(manifest, 'item', { 'id': file_id, - 'href': html[1], - 'media-type': "application/xhtml+xml", + 'href': file.path, + 'media-type': file.filetype, }) - itemref = etree.SubElement(spine, 'itemref', idref=file_id) - point = etree.SubElement(navmap, 'navPoint', { - 'class': "h1", - 'id': file_id, - }) - etree.SubElement(etree.SubElement(point, 'navLabel'), 'text').text = html[0] - etree.SubElement(point, 'content', src=html[1]) + if file.filetype == "application/xhtml+xml": + itemref = etree.SubElement(spine, 'itemref', idref=file_id) + point = etree.SubElement(navmap, 'navPoint', { + 'class': "h1", + 'id': file_id, + }) + etree.SubElement(etree.SubElement(point, 'navLabel'), 'text').text = file.title + etree.SubElement(point, 'content', src=file.path) - if 'cover.html' == os.path.basename(html[1]): + if 'cover.html' == os.path.basename(file.path): etree.SubElement(guide, 'reference', { 'type': 'cover', 'title': 'Cover', - 'href': html[1], + 'href': file.path, }) itemref.set('linear', 'no') + if 'images/cover.png' == file.path: + etree.SubElement(metadata, 'meta', { + 'name': 'cover', + 'content': file_id, + }) # and add the actual html to the zip - if html[2]: - epub.writestr('OEBPS/' + html[1], html[2]) + if file.contents: + epub.writestr('OEBPS/' + file.path, file.contents) else: - epub.write(html[1], 'OEBPS/' + html[1]) - - if extra_files: - for i, data in enumerate(extra_files): - file_id = 'extrafile_%d' % (i + 1) - etree.SubElement(manifest, 'item', { - 'id': file_id, - 'href': data[0], - 'media-type': data[2], - }) - if 'images/cover.png' == data[0]: - etree.SubElement(metadata, 'meta', { - 'name': 'cover', - 'content': file_id, - }) - epub.writestr('OEBPS/' + data[0], data[1]) + epub.write(file.path, 'OEBPS/' + file.path) # ...and add the ncx to the manifest etree.SubElement(manifest, 'item', { @@ -151,4 +146,4 @@ def make_epub(filename, html_files, meta, extra_files=False, compress=True): if __name__ == '__main__': - make_epub('test.epub', [('Chapter 1', 'test/a.html'), ('Chapter 2', 'test/b.html')], {}) + make_epub('test.epub', [EpubFile(title='Chapter 1', path='a.html', contents="Test"), EpubFile(title='Chapter 2', path='test/b.html', contents="Still a test")], {})