This commit is contained in:
s0len 2026-07-07 21:22:45 +02:00 committed by GitHub
commit 756b67f07a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -84,55 +84,91 @@ class Epub(OutputFormat):
async def _download_epub_in_parts(self, data: EpubInParts, metadata: Metadata, location: str, update: Update) -> None:
files = data.files
file_count = len(files)
progress = 1/(file_count)
progress = 1/len(files)
temporary_file_location = f"{location}.tmp"
added_files: set[str] = set()
def get_new_files(zipfile: ZipFile):
"""Returns files in zipfile not already added to file"""
for filename in zipfile.namelist():
if filename in added_files or filename.endswith(".opf") or filename.endswith(".ncx"):
continue
yield filename
# First occurrence wins; parts arrive in reading order and share most
# files, so insertion order doubles as spine order.
collected: dict[str, bytes] = {}
try:
for file in files:
await self._download_and_write_file(file, temporary_file_location)
with ZipFile(temporary_file_location, "r") as zipfile:
for filepath in zipfile.namelist():
if filepath.endswith("/") or filepath in collected or self._is_part_packaging(filepath):
continue
collected[filepath] = zipfile.read(filepath)
if update:
update(progress)
finally:
if os.path.exists(temporary_file_location):
os.remove(temporary_file_location)
self._build_epub_from_files(collected, data.files_in_toc, metadata, location)
@staticmethod
def _is_part_packaging(filename: str) -> bool:
"""
True for files that belong to a part's own epub packaging. Each part is
a complete epub, so copying these into the merged book would leave
stray mimetype/container/package files next to the generated ones.
"""
lowered = filename.lower()
return (
lowered == "mimetype"
or lowered.startswith("meta-inf/")
or lowered.endswith((".opf", ".ncx"))
)
@staticmethod
def _build_epub_from_files(
files: dict[str, bytes],
files_in_toc: dict[str, str],
metadata: Metadata,
location: str,
) -> None:
"""
Assemble an epub from the raw files collected from its parts. Content
documents are stored byte-for-byte as delivered by the source, so their
stylesheet links, language attributes and markup survive; only the
packaging (opf, ncx, nav) is generated.
"""
output = epub.EpubBook()
for file in files:
await self._download_and_write_file(file, temporary_file_location)
with ZipFile(temporary_file_location, "r") as zipfile:
for filepath in get_new_files(zipfile):
content = zipfile.read(filepath)
if filepath.endswith("html"):
filename = os.path.basename(filepath)
is_in_toc = False
title = None
for key, value in data.files_in_toc.items():
toc_filename = key.split("#")[0]
if filename == toc_filename:
title = value
is_in_toc = True
break
epub_file = epub.EpubHtml(
title = title,
file_name = filepath,
content = content
)
output.add_item(epub_file)
output.spine.append(epub_file)
if is_in_toc:
output.toc.append(epub_file)
else:
epub_file = epub.EpubItem(
file_name = filepath,
content = content
)
output.add_item(epub_file)
added_files.add(filepath)
if update:
update(progress)
os.remove(temporary_file_location)
output.set_title(metadata.title)
if metadata.language:
output.set_language(metadata.language)
for author in metadata.authors:
output.add_author(author)
# Toc keys may or may not carry directory prefixes and fragments
# ("Text/chap01.html#nav_1"); compare by basename like the spine files.
toc_titles = {
os.path.basename(key.split("#")[0]): title
for key, title in files_in_toc.items()
}
cover_flagged = False
for filepath, content in files.items():
is_content_document = filepath.lower().endswith((".html", ".xhtml", ".htm"))
item = epub.EpubItem(
file_name = filepath,
# Empty media type makes ebooklib guess from the extension
media_type = "application/xhtml+xml" if is_content_document else "",
content = content,
)
output.add_item(item)
if is_content_document:
output.spine.append(item)
title = toc_titles.get(os.path.basename(filepath))
if title is not None:
output.toc.append(epub.Link(filepath, title, uid=item.get_id()))
elif not cover_flagged and os.path.basename(filepath).lower().startswith("cover") \
and item.media_type.startswith("image/"):
item.properties = ["cover-image"]
output.add_metadata(None, "meta", "", {"name": "cover", "content": item.get_id()})
cover_flagged = True
output.add_item(epub.EpubNcx())
output.add_item(epub.EpubNav())
epub.write_epub(location, output)
exit()