Initial commit
This commit is contained in:
commit
63a427716f
|
@ -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()
|
Loading…
Reference in New Issue