c4se記:さっちゃんですよ☆

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

.。oO(此のblogは、主に音樂考察Programming に分類されますよ。ヾ(〃l _ l)ノ゙♬♪♡)

音樂は SoundCloud に公開中です。

考察は現在は主に Scrapbox で公表中です。

Programming は GitHub で開發中です。

JPEG / PNG 畫像を可逆壓縮する @ Windows

畫像を Internet に上げる前に壓縮する習慣を持ってゐる。macOS では ImageOptim を使ってゐるが、Windows では無い。

imageoptim.com

JPEG 畫像は mozjpeg で、PNG 畫像は zopfli で可逆壓縮できればよい。以下の通りにやってゐる。

mozjpeg は scoop で入れる。今なら winget でよい。これで WSL2 からも jpegtran.exe が呼べる。

scoop install mozjpeg

zopfli は WSL2 で build してある。

git clone https://github.com/google/zopfli.git
cd zopfli
make

これ等を Python から呼ぶ。

#!/usr/bin/env python3
"""
Optimize JPEG images by mozjpeg & PNG images by zopflipng.

Usage :
ag -0 -g '\.((jp(e?)g)|png)$' | xargs -0 -t -n2 -P$(nproc) /mnt/c/<PATH TO>/imageoptim.py
"""
import os
import subprocess
from shlex import quote
import sys
import uuid


class Image:
    @classmethod
    def from_input_file_name(cls, input_file_name: str) -> "Image":
        if input_file_name.endswith(".jpg") or input_file_name.endswith(".jpeg"):
            return JpegImage(input_file_name)
        elif input_file_name.endswith(".png"):
            return PngImage(input_file_name)
        raise f"Can not detect fle type: {input_file_name}"

    input_file_name: str
    output_file_name: str

    def compress(self) -> None:
        raise NotImplementedError()


class JpegImage(Image):
    def __init__(self, input_file_name: str):
        self.input_file_name = input_file_name
        self.output_file_name = f"{uuid.uuid4()}.jpg"

    def compress(self) -> None:
        try:
            command = f"jpegtran.exe -copy none -optimize -outfile {quote(self.output_file_name)} {quote(self.input_file_name)}"
            print(command)
            subprocess.check_call(command, shell=True)
            print(f"{self.input_file_name} {os.stat(self.input_file_name).st_size} -> {os.stat(self.output_file_name).st_size}")
            os.replace(self.output_file_name, self.input_file_name)
        except subprocess.CalledProcessError as err:
            print(err)
            if os.path.isfile(self.output_file_name):
                os.remove(self.output_file_name)


class PngImage(Image):
    def __init__(self, input_file_name: str):
        self.input_file_name = input_file_name
        self.output_file_name = f"{uuid.uuid4()}.png"

    def compress(self) -> None:
        try:
            command = f"/mnt/c/<PATH TO>/zopflipng -m --lossy_transparent {quote(self.input_file_name)} {quote(self.output_file_name)}"
            print(command)
            subprocess.check_call(command, shell=True)
            print(f"{self.input_file_name} {os.stat(self.input_file_name).st_size} -> {os.stat(self.output_file_name).st_size}")
            os.replace(self.output_file_name, self.input_file_name)
        except subprocess.CalledProcessError as err:
            print(err)
            if os.path.isfile(self.output_file_name):
                os.remove(self.output_file_name)


for input_file_name in sys.argv[1:]:
    Image.from_input_file_name(input_file_name).compress()

昔書いたので Python だ。今なら Clojure で書く。書き直すか…。

追記 2021-09-19 書き直した。 c4se.hatenablog.com

書く程の事ではなかったが書いた。