Sekun utilities

Artifact [f04b3d800d]
Login

Artifact f04b3d800d7a4f48b3001d0093a80585242736b94d609465340919400ad0fbc0:


#!/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()