無料でギャラリーホスティングしたいなと思って。
GPTさんに相談したところ、タイトルの方法が良いのではと打診を受け。
実装途中で透かし(WaterMark)を動的に入れたいと思って追加リクエストをして、実装しました。
初版ではBLIP部分が英単語だったため、タイトルとして不適当と考え、日本語化(翻訳)するように変換をかけています。(ファイル名の観点では英語が良いと思いますが、今回は日本語も許されるので。)
概要:ローカルで画像生成AIを逆利用して画像をOCRで自動キャプション→安全な英数字ファイル名へリネームし、GitHub Actionsでindex.jsonを自動生成、Cloudflare Pagesで配信します。
効果:検索性・並び順の安定、URLの汚染防止、作品タイトルの自動化。
- アーキテクチャ(図解)
[Dev PC] --(auto-caption rename)--> /public/images/*
--push--> [GitHub Repo]
--CI--> public/index.json を生成
--> [Cloudflare Pages] で配信
--> Browser が index.json を fetch してグリッド表示
2. 自動キャプション・リネーム(ローカル運用)
画像の内容を短い説明語に要約し、半角英数字+ハイフンの安全なファイル名に自動変換します。EXIF日時(なければ更新日時)を先頭に付けることで、時系列にも強い命名にします。
スクリプト(CPUでOK):rename_by_content.py
使い方
1) 依存をインストール
pip install -U transformers pillow tqdm unidecode
2) ドライラン(計画だけ表示)
python rename_by_content.py "public/images"
3) 実行(リネーム適用)
python rename_by_content.py "public/images" --apply --date-prefix
モデルは Salesforce/blip-image-captioning-base(初回のみ自動DL)。
長いキャプションは3〜8語程度に圧縮→slug化(英数字/hyphenのみ)。
重複名は -1, -2… を自動付加して衝突回避。
Git連携の例(任意/安全運用)
# .git/hooks/pre-commit (実行権限を付与: chmod +x .git/hooks/pre-commit)
#!/bin/sh
python3 tools/rename_by_content.py ./public/images --date-prefix
git add public/images
イラスト/アニメ調が中心の場合(代替):自動タグ付け(WD14/DeepDanbooru)で上位タグを連結して命名すると安定します。
スクショ/書類中心の場合:OCR(Tesseract+pytesseract)で見出し行を抽出→命名が有効。
- リポジトリ構成(例)
repo-root/
├─ public/
│ ├─ index.html
│ ├─ index.json ← Actionsで自動生成
│ └─ images/
│ ├─ 20250907_vampire-throne.png
│ └─ 20250906_azure-solitude.jpg
└─ .github/
└─ workflows/
└─ build-images-index.yml - GitHub Actions(index.json 自動生成)
/.github/workflows/build-images-index.yml
※ 1イベント内で paths と paths-ignore を同時指定しないこと。
name: Build images index
on:
push:
paths:
- 'public/images/**'
workflow_dispatch:
permissions:
contents: write
jobs:
build-index:
runs-on: ubuntu-latest
if: ${{ github.actor != 'github-actions[bot]' }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Generate public/index.json
run: |
node -e "
const fs = require('fs');
const path = require('path');
const root = process.cwd();
const imagesDir = path.join(root, 'public', 'images');
const outPath = path.join(root, 'public', 'index.json');
const exts = new Set(['.png', '.jpg', '.jpeg', '.webp', '.gif']);
const list = [];
function walk(dir) {
for (const name of fs.readdirSync(dir)) {
const p = path.join(dir, name);
const stat = fs.statSync(p);
if (stat.isDirectory()) walk(p);
else {
const ext = path.extname(p).toLowerCase();
if (exts.has(ext)) {
const rel = path.relative(path.join(root, 'public'), p).replace(/\\\\/g, '/');
const title = path.basename(p, ext);
list.push({ src: rel, title });
}
}
}
}
if (!fs.existsSync(imagesDir)) { console.error('images dir not found:', imagesDir); process.exit(1); }
walk(imagesDir);
list.sort((a, b) => a.src.localeCompare(b.src));
fs.writeFileSync(outPath, JSON.stringify(list, null, 2));
console.log('Wrote', outPath, list.length, 'items');
"
- name: Commit & Push index.json
run: |
if [ -n "$(git status --porcelain)" ]; then
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
git add public/index.json
git commit -m 'chore: update images index'
git push
else
echo 'No changes to commit'
fi
- フロント(最小実装)
public/index.html(index.jsonをfetch→グリッド表示)
<!doctype html>
<meta charset="utf-8">
<title>Image Gallery</title>
<div id="g" style="display:grid; grid-template-columns:repeat(auto-fill, minmax(180px,1fr)); gap:12px;"></div>
<script>
fetch('./index.json')
.then(r => r.json())
.then(list => {
const g = document.getElementById('g');
list.forEach(({src, title}) => {
const wrap = document.createElement('div');
wrap.style.border = '1px solid #ddd';
wrap.style.padding = '8px';
wrap.innerHTML =
'<img loading="lazy" style="width:100%;height:auto;display:block;" src=\"'+ src +'\" alt=\"\">' +
'<div style=\"font:12px/1.4 sans-serif; margin-top:6px; color:#555;\">'+ title +'</div>';
g.appendChild(wrap);
});
})
.catch(e => {
document.getElementById('g').textContent = 'index.json が見つかりませんでした: ' + e;
});
</script>
- 運用Tips(命名規則と品質)
命名規則:YYYYMMDD_短い説明語.ext(半角英数字とハイフンのみ)。
一貫性優先:あとで一括置換・集計しやすい。
タグ駆動:アニメ調はWD14タグ上位5件を-連結すると検索性UP。
テキスト系:OCRで見出し行→短縮→スラグ化が有効。 - トラブルシューティング
画像が表示されない:index.htmlとindex.jsonの相対パス(./index.jsonか/index.json)を確認。拡張子の大文字小文字も厳密。
Cloudflare Pages「Skipped」:コミットメッセージの [skip ci] など/差分なし/自動ビルド設定を確認。
Actionsのイベント定義エラー:1イベントで paths と paths-ignore を同時指定しない。
モデルDL不可:社内プロキシでブロックされる場合は、事前に開発端末でモデルを取得してキャッシュを配布。
必要に応じて、自動サムネ生成・タグ別 index.json・OGP対応・作品ページ自動生成なども拡張できます。