第9章 · 実例 1

写真を撮影日でフォルダ分けする CLI ツール

第 08 章「アプリを作る ── CLIツール、Fletアプリ、Flutterアプリ」の主張を裏付ける。

章のどの主張に対応するか

写真整理アプリ(撮影日でフォルダ分け):

  • CLI で書く: 30 行の Python、開発時間 30 分、配布は GitHub に置くだけ
  • iOS アプリで作る: Swift で 200 行、Xcode 環境 50 GB、App Store 審査 1 週間、年会費 $99

(章本文「実例: 数字で見る」より)

実際に書いてみると 約 90 行 の Python(EXIF を自前で読む実装込み)。 50 枚を 0.044 秒 で振り分けた。

やること

  1. 入力を作る: generate_samples.py で EXIF 入りの 1×1 JPEG を 50 枚生成 (撮影日を 2026-01〜2026-06 にバラす)
  2. 整理する: photo_organizer.py photos -o out/by-month --copy
  3. 検算する: 結果フォルダのファイル数を月別に表示

すべて make all 一発。

構成

example-1/
├── README.md
├── photo_organizer.py    ── 本体 CLI(EXIF 読み + フォルダ振り分け)
├── generate_samples.py   ── サンプル JPEG 生成(EXIF DateTimeOriginal を埋め込む)
├── Makefile
├── results.md
├── photos/               ── 入力(50 枚の JPEG)
└── out/by-month/
    ├── 2026-01/   ── 11 枚
    ├── 2026-02/   ── 8 枚
    ├── 2026-03/   ── 10 枚
    ├── 2026-04/   ── 3 枚
    ├── 2026-05/   ── 5 枚
    └── 2026-06/   ── 13 枚

実行

# 標準ライブラリだけで動く(Pillow 不要)
python3 generate_samples.py
python3 photo_organizer.py photos -o out/by-month --copy

# あるいは
make clean && make all

なぜこれが「実例」になるのか

「撮影日で写真を分ける」── これを Swift / Xcode で iOS アプリとして作ると:

CLI で書くと:

しかも、他のシェルツールと組み合わせられる:

# 1 年分のスマホ写真を整理して NAS に同期
python3 photo_organizer.py ~/Pictures/iphone -o /nas/photos

# RAW + JPEG の組み合わせも対応
find . -name "*.CR3" -o -name "*.jpg" | xargs python3 photo_organizer.py ...

# 古い写真を 1 年単位の zip にする
python3 photo_organizer.py photos -o /tmp/sorted
for d in /tmp/sorted/*/; do zip -r "$(basename $d).zip" "$d"; done

GUI が要らない処理に GUI を被せると、他のものと組み合わせるとき GUI が壁になる。CLI のままなら、cron に入れて毎晩走らせることもできる。 これが章で言う「まず CLI から」の意味。

GUI が必要になったら(Flet で Python のまま GUI 化、それでも足りなければ Flutter)。 ステップを上げる順序がある。

EXIF を自前で読む

このスクリプトの面白いところは、Pillow を使わずに EXIF DateTimeOriginal (tag 0x9003) を 50 行で読んでいる点。標準ライブラリ struct だけで JPEG → APP1 → TIFF → IFD → ExifIFD を辿る。

「まず Claude にアウトラインを書いてもらって、自分で詰める」── このサイズの コードはまさに章 04 で言う「Claude が書く、人間が読む」の対象。


計測結果 — 第 08 章 example-1

実行環境: Linux 6.18 / Python 3.x(標準ライブラリのみ)

規模と性能(主目的)

項目 数値
photo_organizer.py 行数 約 90 行(EXIF 読み込み込み)
依存 標準ライブラリのみ
処理対象 50 枚の JPEG
処理時間 0.044 秒
1 枚あたり 約 0.9 ms
1,000 枚処理(推定) 約 0.9 秒

章本文との比較

項目 iOS アプリ この CLI
開発環境のディスク Xcode + iOS SDK = 50 GB 0(Python が入っていれば終わり)
年会費 Apple Developer $99 0
コード行数 Swift で約 200 行 + UI 約 90 行
配布までの時間 App Store 審査 1 週間以上 GitHub に push して終わり
動く OS iOS のみ macOS / Windows / Linux
1 ヶ月後の修正 再ビルド + 再審査 エディタで直して終わり

実行ログ(make all)

=== 撮影日でフォルダ分け ===
  copy  IMG_0001.jpg → by-month/2026-04/IMG_0001.jpg
  copy  IMG_0002.jpg → by-month/2026-02/IMG_0002.jpg
  ...
  50 ファイル処理完了

real    0m0.044s
user    0m0.037s
sys     0m0.008s

=== 結果 ===
  2026-01 : 11 枚
  2026-02 : 8 枚
  2026-03 : 10 枚
  2026-04 : 3 枚
  2026-05 : 5 枚
  2026-06 : 13 枚
  ── 合計 50 枚を 6 フォルダに振り分け

配布

# 1. GitHub に push して終わり
git add photo_organizer.py
git commit -m "Photo organizer CLI"
git push

ユーザは:

curl -O https://raw.githubusercontent.com/you/repo/main/photo_organizer.py
python3 photo_organizer.py ~/Pictures -o ~/sorted-photos

これで完了。App Store も Google Play も要らない

PyPI に上げるなら:

# pyproject.toml を 1 個作って
pip install build twine
python3 -m build
twine upload dist/*

これで pip install photo-organizer で世界中に配布される。

CLI から GUI に上げる(必要があれば)

CLI で動くものを、Flet で 30 行追加するだけで GUI 化できる:

import flet as ft
import photo_organizer

def main(page: ft.Page):
    src = ft.TextField(label="入力")
    dst = ft.TextField(label="出力")
    def run(e): photo_organizer.organize(src.value, dst.value)
    page.add(src, dst, ft.ElevatedButton("整理", on_click=run))

ft.app(target=main)

Flutter で書き直すなら 1 ヶ月、Flet なら 1 時間。CLI のロジックは そのまま再利用される。これが章で言う「CLI を一段ずつ昇格させる」。

再現手順

# Pillow も python-docx も不要、Python 3 だけ
make clean && make all

ファイル一覧

out/

photos/

第9章「アプリを作る ── CLIツール、Fletアプリ、Flutterアプリ」に戻る