#!/usr/bin/env python3
"""Program to convert images from cbz.
It converts png and webp to jpeg.
Test this program simply with uv:
: uv run https://forge.sekun.eu/utils/doc/tip/prog/cbz-format.py
"""
# /// script
# requires-python = ">=3.8"
# dependencies = [
# "pillow>=10.4,<10.5",
# "structlog>=24.4,<24.5",
# "path",
# ]
# ///
import argparse
import re
import shutil
import tempfile
import structlog
from path import Path
from PIL import Image
logger = structlog.get_logger(__name__)
def maxsize_type(value):
return list(map(int, value.split("x")))
def parse_args():
parser = argparse.ArgumentParser(
prog="cbz-format",
description="Convert image from cbz file to jpeg and resize them.",
)
parser.add_argument(
"-s",
"--max-size",
type=maxsize_type,
help="maximum `widthxheight` sizes for images.",
)
parser.add_argument("--log", choices=["DEBUG", "INFO"], default="info")
parser.add_argument(
"-i",
"--inplace",
default=False,
action="store_true",
help="Erase original cbz file",
)
parser.add_argument(
"-q", "--quality", type=int, default=75, help="Quality of generated jpeg images"
)
parser.add_argument("files", nargs="+", type=Path, help="Path of cbz files")
return parser.parse_args()
def operate_cbz(cbz_path, quality=75, max_size=None, inplace=False):
if not cbz_path.exists():
logger.error(f"File {cbz_path} doesn't exist")
return
with tempfile.TemporaryDirectory() as tmp_dir_, tempfile.TemporaryDirectory() as zip_tmp_dir_:
tmp_dir = Path(tmp_dir_)
zip_tmp_dir = Path(zip_tmp_dir_)
shutil.unpack_archive(cbz_path, tmp_dir, "zip")
for img_path in tmp_dir.iterdir():
if not img_path.is_file() or not re.match(
r".*\.(png|webp|jpe?g)$", img_path, re.I
):
continue
with Image.open(img_path) as im:
if max_size:
im.thumbnail(max_size)
if im.mode != "RGB":
im = im.convert("RGB")
im.save(
str(img_path.parent / img_path.stem) + ".jpg",
"JPEG",
optimize=True,
quality=quality,
)
if not img_path.endswith(".jpg"):
img_path.unlink()
shutil.make_archive(zip_tmp_dir / "output", "zip", tmp_dir, ".")
output_zip = zip_tmp_dir / "output.zip"
if not inplace:
output_cbz = cbz_path.with_stem(cbz_path.name + "_copy")
else:
cbz_path.unlink()
output_cbz = cbz_path
output_zip.copy(output_cbz)
logger.info(f"Created {output_cbz} file")
def main():
args = parse_args()
for f in args.files:
operate_cbz(
f, quality=args.quality, max_size=args.max_size, inplace=args.inplace
)
if __name__ == "__main__":
main()