init
This commit is contained in:
127
src/exporter.py
Normal file
127
src/exporter.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
Экспорт в CBZ, PDF, EPUB.
|
||||
"""
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
from loguru import logger
|
||||
|
||||
ExportFormat = Literal["cbz", "pdf", "epub"]
|
||||
|
||||
|
||||
def export(
|
||||
image_paths: list[Path],
|
||||
output_path: Path,
|
||||
fmt: ExportFormat,
|
||||
title: str = "Manga",
|
||||
chapter: str = "",
|
||||
):
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
logger.info("Экспортирую {} страниц → {} ({})", len(image_paths), output_path.name, fmt)
|
||||
|
||||
if fmt == "cbz":
|
||||
_export_cbz(image_paths, output_path)
|
||||
elif fmt == "pdf":
|
||||
_export_pdf(image_paths, output_path)
|
||||
elif fmt == "epub":
|
||||
_export_epub(image_paths, output_path, title, chapter)
|
||||
else:
|
||||
raise ValueError(f"Неизвестный формат: {fmt}")
|
||||
|
||||
logger.info("Сохранено: {}", output_path)
|
||||
|
||||
|
||||
# ── CBZ ───────────────────────────────────────
|
||||
|
||||
def _export_cbz(images: list[Path], out: Path):
|
||||
with zipfile.ZipFile(out, "w", compression=zipfile.ZIP_DEFLATED) as zf:
|
||||
for i, img in enumerate(images):
|
||||
zf.write(img, f"{i:04d}{img.suffix}")
|
||||
|
||||
|
||||
# ── PDF ───────────────────────────────────────
|
||||
|
||||
def _export_pdf(images: list[Path], out: Path):
|
||||
try:
|
||||
import img2pdf
|
||||
with open(out, "wb") as f:
|
||||
f.write(img2pdf.convert([str(p) for p in images]))
|
||||
except Exception as e:
|
||||
logger.warning("img2pdf не сработал ({}), использую Pillow", e)
|
||||
_export_pdf_pillow(images, out)
|
||||
|
||||
|
||||
def _export_pdf_pillow(images: list[Path], out: Path):
|
||||
from PIL import Image
|
||||
pil_images = []
|
||||
for p in images:
|
||||
img = Image.open(p).convert("RGB")
|
||||
pil_images.append(img)
|
||||
if pil_images:
|
||||
pil_images[0].save(
|
||||
out,
|
||||
save_all=True,
|
||||
append_images=pil_images[1:],
|
||||
format="PDF",
|
||||
)
|
||||
|
||||
|
||||
# ── EPUB ──────────────────────────────────────
|
||||
|
||||
def _export_epub(images: list[Path], out: Path, title: str, chapter: str):
|
||||
from ebooklib import epub
|
||||
from PIL import Image
|
||||
import base64
|
||||
|
||||
book = epub.EpubBook()
|
||||
book.set_identifier(f"manga-{title}-{chapter}".replace(" ", "-"))
|
||||
book.set_title(f"{title} — {chapter}" if chapter else title)
|
||||
book.set_language("ru")
|
||||
|
||||
spine = ["nav"]
|
||||
toc = []
|
||||
|
||||
for i, img_path in enumerate(images):
|
||||
# Добавляем изображение в книгу
|
||||
with open(img_path, "rb") as f:
|
||||
img_data = f.read()
|
||||
|
||||
img_name = f"images/page_{i:04d}{img_path.suffix}"
|
||||
epub_img = epub.EpubImage()
|
||||
epub_img.file_name = img_name
|
||||
epub_img.media_type = _mime(img_path.suffix)
|
||||
epub_img.content = img_data
|
||||
book.add_item(epub_img)
|
||||
|
||||
# HTML-страница для каждого изображения
|
||||
page_html = epub.EpubHtml(
|
||||
title=f"Страница {i + 1}",
|
||||
file_name=f"page_{i:04d}.xhtml",
|
||||
lang="ru",
|
||||
)
|
||||
page_html.content = (
|
||||
f'<html><body style="margin:0;padding:0;">'
|
||||
f'<img src="{img_name}" style="max-width:100%;height:auto;display:block;margin:auto;"/>'
|
||||
f'</body></html>'
|
||||
)
|
||||
book.add_item(page_html)
|
||||
spine.append(page_html)
|
||||
toc.append(epub.Link(f"page_{i:04d}.xhtml", f"Страница {i + 1}", f"page{i}"))
|
||||
|
||||
book.toc = toc
|
||||
book.spine = spine
|
||||
book.add_item(epub.EpubNcx())
|
||||
book.add_item(epub.EpubNav())
|
||||
|
||||
epub.write_epub(str(out), book)
|
||||
|
||||
|
||||
def _mime(ext: str) -> str:
|
||||
return {
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".png": "image/png",
|
||||
".webp": "image/webp",
|
||||
}.get(ext.lower(), "image/jpeg")
|
||||
|
||||
Reference in New Issue
Block a user