From 63a427716f4b8b6b75ea3314708517a2529213f7 Mon Sep 17 00:00:00 2001 From: LilyRose2798 Date: Sun, 16 Jun 2024 03:32:34 +1000 Subject: [PATCH] Initial commit --- mangautils.py | 126 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100755 mangautils.py diff --git a/mangautils.py b/mangautils.py new file mode 100755 index 0000000..6ee03bc --- /dev/null +++ b/mangautils.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 + +from sys import stderr +from fpdf import FPDF +from natsort import natsorted +from pathlib import Path +from argparse import ArgumentParser +from PIL import Image + +class MangaPDF(FPDF): + page_height = 210 + page_width = 297 + image_margin = 4.2 + text_margin = 7 + + def write_image(self, path: Path, right_side: bool): + name = str(path) + if path.suffix == ".jpg" or path.suffix == ".jpeg": + info = self._parsejpg(name) + elif path.suffix == ".png": + info = self._parsepng(name) + else: + raise ValueError("invalid image extension") + i = len(self.images) + 1 + info["i"] = i + self.images[name] = info + iw = info["w"] + ih = info["h"] + image_ratio = iw / ih + pw = self.page_width / 2 - self.image_margin - self.image_margin + ph = self.page_height - self.image_margin - self.image_margin + page_ratio = pw / ph + px = ((self.page_width / 2) if right_side else 0) + self.image_margin + py = self.image_margin + if image_ratio > page_ratio: + w = pw + h = ph * (page_ratio / image_ratio) + x = px + y = py + (ph - h) / 2 + else: + w = pw * (image_ratio / page_ratio) + h = ph + x = px + (pw - w) / 2 + y = py + self._out(f"q {w * self.k:.2f} 0 0 {h * self.k:.2f} {x * self.k:.2f} {(self.h - (y + h)) * self.k:.2f} cm /I{i} Do Q") + + def text_width(self, text: str) -> float: + return sum(self.current_font["cw"].get(c) for c in text) * self.font_size / 1000.0 + + def write_page_num(self, page_num: int, right_side: bool): + x = (self.page_width - self.text_margin - self.text_width(str(page_num))) if right_side else self.text_margin + y = self.page_height - self.text_margin + self._out(f"BT {x * self.k:.2f} {(self.h - y) * self.k:.2f} Td ({page_num}) Tj ET") + + def __init__(self, left_pages: list[tuple[int, Path | None]], right_pages: list[tuple[int, Path | None]]): + super().__init__(orientation = "landscape", unit = "mm", format = (self.page_height, self.page_width)) + self.set_font(family = "Times", style = "B", size = 12) + self.set_text_color(0, 0, 0) + for (left_page_num, left_img_path), (right_page_num, right_img_path) in zip(left_pages, right_pages): + self.add_page() + if left_img_path is not None: + self.write_image(left_img_path, False) + self.write_page_num(left_page_num, False) + if right_img_path is not None: + self.write_image(right_img_path, True) + self.write_page_num(right_page_num, True) + +def iter_img_paths(imgs_path: Path): + img_extensions = ["jpg", "jpeg", "png"] + return ( + img_path + for img_path_iter in ( + imgs_path.rglob(f"*.{ext}") + for ext in img_extensions + ) + for img_path in img_path_iter + if img_path.is_file() + ) + +def build_pdf(imgs_path: Path, num_blanks: int = 0): + blanks = [None] * num_blanks + img_paths = blanks + natsorted(iter_img_paths(imgs_path)) + empty_pages = [None] * ((4 - (len(img_paths) % 4)) % 4) + padded_img_paths = img_paths + empty_pages + num_pages = len(padded_img_paths) + middle_index = num_pages // 2 + page_info = list(enumerate(padded_img_paths, 1 - num_blanks)) + back_right_pages = page_info[:middle_index:-2] + back_left_pages = page_info[:middle_index:2] + front_right_pages = page_info[1:middle_index:2] + front_left_pages = page_info[num_pages-2:middle_index-1:-2] + MangaPDF(back_left_pages, back_right_pages).output(imgs_path.with_name(f"{imgs_path.stem}-back.pdf"), "F") + MangaPDF(front_left_pages, front_right_pages).output(imgs_path.with_name(f"{imgs_path.stem}-front.pdf"), "F") + +def split_images(imgs_path: Path): + for img_path in iter_img_paths(imgs_path): + img = Image.open(img_path) + if img.width > img.height: + mid = img.width // 2 + img.crop((mid, 0, img.width, img.height)).save(img_path.with_stem(f"{img_path.stem}a")) + img.crop((0, 0, mid, img.height)).save(img_path.with_stem(f"{img_path.stem}b")) + img_path.unlink() + +def main(): + parser = ArgumentParser("mangautils") + subparsers = parser.add_subparsers(dest = "command", required = True, help = "the command to run") + split_parser = subparsers.add_parser("split", help = "split two page spreads in the given images") + split_parser.add_argument("input", help = "the directory containing the images") + build_parser = subparsers.add_parser("build", help = "create a pdf from the given images") + build_parser.add_argument("input", help = "the directory containing the images") + build_parser.add_argument("-b", "--blanks", default = 0, type = int, help = "number of blank pages to add at the start (default: %(default)s)") + args = parser.parse_args() + + try: + input_path = Path(args.input) + match args.command: + case "split": + split_images(input_path) + case "build": + build_pdf(input_path, args.blanks) + except Exception as e: + print(f"error: {e}", file = stderr) + exit(1) + +if __name__ == "__main__": + main()