画像圧縮アプリを作ってみた‼(チャッピーと遊ぶパイソン100本ノック#1)

ChatGPT
ねこ
ねこ

仕事で記事書いてて、ワードプレスに画像貼るときに、
”圧縮して”って言われるんだけど、
わざわざWebアプリ開くのが、面倒やし、複数選択したら、Zipフォルダに
なってダウンロードされてくるから、解凍しなきゃやし、面倒なんだけど!

チャッピー(ChatGPT)
チャッピー(ChatGPT)

それなら、圧縮ソフト作ればえぇんちゃう?
わいが、コード出したるから、VBCodeとかでサクッと作ってしまえば
楽やで?exeとかも希望なら出せるしなぁ。

ということで、作り方を聞きながら、チャッピーと一緒に、画像圧縮ソフト作りました!

とりあえず欲しい機能を追加→追加→追加で、わりと機能が充実してると思う、圧縮ソフトです(笑)

🏁 機能一覧

✅ 複数画像選択(ドラッグ&ブラウズ)
✅ フォルダ一括圧縮対応
✅ 保存形式:JPG / PNG / WEBP
✅ 圧縮品質と縮小比率の調整
✅ 保存先が未指定ならポップアップで警告
✅ 保存成功時はログに出力パス表示
✅ 拡張子自動変換でトラブル回避
✅ フォルダ自動作成で安定保存

見た目は以下のような感じ↓

最初は、フォルダを一括で、変換するのを作ったんだけど、どっちかというと、1個づつ圧縮するほうが多い気がしたので、1個ずつの機能も追加。
ドラックでファイルを選択しても、ちゃんとそのファイルを圧縮してくれるようにしました。

一番楽なのは、圧縮の待ち時間が短い‼
Webアプリと比べると、サクッと圧縮してくれる感があります。

仕事で記事を書くときに、これなら、さっくりを圧縮できるし、自分で作ったので、使いつつニヤニヤできるのが良い‼

こちらから作成したZipをDLできます。

※以下のような不審なダウンロードをブロックしましたと出るかもしれません。
これは自作の安全なツールですので、不審なファイルをダウンロードでダウンロードできます。
不安な場合は、コードも記載してるので、そちらを使って自作していただくのもありだと思います。

コードは以下の通りなので、作ってみたい人はコピペすれば、圧縮ソフト作れます!

import os
from PIL import Image
import PySimpleGUI as sg

valid_extensions = [".jpg", ".jpeg", ".png", ".webp"]
ext_map = {"JPEG": "jpg", "PNG": "png", "WEBP": "webp"}

def compress_image(input_path, output_path, quality, resize_ratio, output_format):
    try:
        before_size = os.path.getsize(input_path)
        img = Image.open(input_path)

        if img.mode in ("RGBA", "P"):
            img = img.convert("RGBA" if output_format == "PNG" else "RGB")
        else:
            img = img.convert("RGB")

        if resize_ratio < 1.0:
            new_size = tuple([int(dim * resize_ratio) for dim in img.size])
            img = img.resize(new_size, Image.LANCZOS)

        save_kwargs = {"optimize": True}
        if output_format in ["JPEG", "WEBP"]:
            save_kwargs["quality"] = quality

        os.makedirs(os.path.dirname(output_path), exist_ok=True)
        print(f"💾 保存先: {output_path}")
        img.save(output_path, output_format, **save_kwargs)

        after_size = os.path.getsize(output_path)
        saved_percent = (1 - after_size / before_size) * 100
        return f"✅ {os.path.basename(input_path)}: {before_size//1024}KB → {after_size//1024}KB(-{saved_percent:.1f}%)"

    except Exception as e:
        return f"❌ 失敗: {os.path.basename(input_path)} - {e}"

def compress_images_in_folder(input_folder, output_folder, quality, resize_ratio, output_format):
    os.makedirs(output_folder, exist_ok=True)
    results = []
    for filename in os.listdir(input_folder):
        name, ext = os.path.splitext(filename)
        ext_lower = ext.lower()
        if ext_lower in valid_extensions:
            input_path = os.path.join(input_folder, filename)
            file_ext = ext_map[output_format]
            output_path = os.path.join(output_folder, f"{name}.{file_ext}")
            results.append(compress_image(input_path, output_path, quality, resize_ratio, output_format))
    return results

layout = [
    [sg.Text("🧭 モード選択:"), 
     sg.Radio("画像を複数選ぶ / D&D対応", "MODE", key="-MODE_FILE-", default=True),
     sg.Radio("フォルダを一括変換", "MODE", key="-MODE_FOLDER-")],
    
    [sg.Text("🖼 画像ファイル")],
    [sg.Input(key="-FILE-", size=(60, 1)), 
     sg.FilesBrowse("画像を選ぶ", key="-BROWSE-", target="-FILE-", file_types=(("画像", "*.jpg;*.jpeg;*.png;*.webp"),))],
    
    [sg.Text("📂 入力フォルダ"), sg.Input(key="-INPUT-"), sg.FolderBrowse()],
    [sg.Text("💾 出力フォルダ"), sg.Input(key="-OUTPUT-"), sg.FolderBrowse()],
    
    [sg.Text("🔧 圧縮品質"), sg.Slider(range=(10, 100), orientation="h", default_value=75, key="-QUALITY-")],
    [sg.Text("📐 サイズ縮小比率 (%)"), sg.Slider(range=(10, 100), orientation="h", default_value=100, key="-RESIZE-")],
    
    [sg.Text("🗂 保存形式:"), 
     sg.Radio("JPG", "FORMAT", default=True, key="-FMT_JPG-"),
     sg.Radio("PNG", "FORMAT", key="-FMT_PNG-"),
     sg.Radio("WEBP", "FORMAT", key="-FMT_WEBP-")],
    
    [sg.Button("圧縮開始", key="-START-"), sg.Button("終了")],
    [sg.Output(size=(80, 15))]
]

window = sg.Window("画像圧縮ツール(Final Edition)", layout)

while True:
    event, values = window.read()
    if event in (sg.WINDOW_CLOSED, "終了"):
        break

    if event == "-START-":
        quality = int(values["-QUALITY-"])
        resize_ratio = float(values["-RESIZE-"]) / 100.0
        output_format = "JPEG" if values["-FMT_JPG-"] else "PNG" if values["-FMT_PNG-"] else "WEBP"
        file_ext = ext_map[output_format]

        out_dir = values["-OUTPUT-"]
        if not out_dir:
            sg.popup("💡出力フォルダを選択してください", title="出力先が未指定です")
            continue
        if not os.path.isdir(out_dir):
            os.makedirs(out_dir, exist_ok=True)

        print("\n▶ 圧縮開始...\n")

        if values["-MODE_FILE-"]:
            files = values["-FILE-"].split(";")
            for file_path in files:
                file_path = file_path.strip().strip("\"")
                if not os.path.isfile(file_path):
                    continue
                name, _ = os.path.splitext(os.path.basename(file_path))
                out_path = os.path.join(out_dir, f"{name}.{file_ext}")
                print(compress_image(file_path, out_path, quality, resize_ratio, output_format))
        else:
            folder_path = values["-INPUT-"]
            if not os.path.isdir(folder_path):
                sg.popup("❗ 入力フォルダが正しくありません", title="エラー")
                continue
            results = compress_images_in_folder(folder_path, out_dir, quality, resize_ratio, output_format)
            for res in results:
                print(res)

        print("\n✅ 圧縮完了!\n")

window.close()

コメント

タイトルとURLをコピーしました