Photochk 1.0.0 — 類似写真を探して整理する macOS アプリ
フォルダ内の写真から、中身が同じものと見た目が似ているものを検出して グループにまとめ、残す1枚を選んでほかをゴミ箱へ送れるようにする。 検出は安い判定から順に4段: - ファイルサイズが同じものだけ SHA-256 でバイト一致を確定 - dHash(知覚ハッシュ)のハミング距離で候補ペアを抽出 - 候補に出てくる画像だけ Vision の feature print で精査 - Union-Find で連結成分をグループ化 判定のしきい値は実写画像で距離を実測して決めた(docs/calibration.md)。 tools/release-update.sh で Gitea のリリースを更新する。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
# Gitea リリース更新の API 部分(release-update.sh から呼ばれる)。
|
||||
# 1) タグ名からリリースを特定
|
||||
# 2) 同名の旧添付資産を削除し、最新の zip をアップロード
|
||||
# 3) 本文の「ビルド元コミット: `xxxx`」を現在の短縮ハッシュへ置換
|
||||
# 認証は環境変数 GITEA_USER/GITEA_PASS(Basic)。Gitea が自己署名証明書のため TLS 検証は無効化。
|
||||
# 引数: <host> <owner> <repo> <tag> <asset_path> <commit_short>
|
||||
import os
|
||||
import re
|
||||
import ssl
|
||||
import sys
|
||||
import json
|
||||
import base64
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.error import HTTPError
|
||||
|
||||
host, owner, repo, tag, asset, commit = sys.argv[1:7]
|
||||
user = os.environ["GITEA_USER"]
|
||||
pw = os.environ["GITEA_PASS"]
|
||||
|
||||
base = f"https://{host}/api/v1/repos/{owner}/{repo}"
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
auth = "Basic " + base64.b64encode(f"{user}:{pw}".encode()).decode()
|
||||
|
||||
|
||||
def api(path, method="GET", data=None, headers=None):
|
||||
h = {"Authorization": auth}
|
||||
if headers:
|
||||
h.update(headers)
|
||||
req = Request(base + path, data=data, method=method, headers=h)
|
||||
try:
|
||||
r = urlopen(req, context=ctx)
|
||||
except HTTPError as e:
|
||||
msg = e.read().decode("utf-8", "replace")
|
||||
sys.exit(f"ERROR: {method} {path} -> HTTP {e.code}: {msg}")
|
||||
body = r.read()
|
||||
return json.loads(body) if body else None
|
||||
|
||||
|
||||
# 1) タグ名でリリースを特定(無ければ 404 で停止)
|
||||
rel = api(f"/releases/tags/{tag}")
|
||||
rid = rel["id"]
|
||||
print(f"release id={rid} (tag {tag})")
|
||||
|
||||
# 2) 同名の旧資産を削除
|
||||
name = os.path.basename(asset)
|
||||
for a in api(f"/releases/{rid}/assets") or []:
|
||||
if a["name"] == name:
|
||||
api(f"/releases/{rid}/assets/{a['id']}", method="DELETE")
|
||||
print(f"deleted old asset id={a['id']} ({a['size']} bytes)")
|
||||
|
||||
# 3) 最新の zip をアップロード(multipart/form-data, field=attachment)
|
||||
with open(asset, "rb") as f:
|
||||
filedata = f.read()
|
||||
boundary = "----photochkReleaseBoundary7e1f"
|
||||
mp = b"".join([
|
||||
f"--{boundary}\r\n".encode(),
|
||||
f'Content-Disposition: form-data; name="attachment"; filename="{name}"\r\n'.encode(),
|
||||
b"Content-Type: application/octet-stream\r\n\r\n",
|
||||
filedata,
|
||||
b"\r\n",
|
||||
f"--{boundary}--\r\n".encode(),
|
||||
])
|
||||
new = api(
|
||||
f"/releases/{rid}/assets?name={name}",
|
||||
method="POST",
|
||||
data=mp,
|
||||
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
|
||||
)
|
||||
print(f"uploaded asset id={new['id']} ({new['size']} bytes)")
|
||||
|
||||
# 4) 本文の「ビルド元コミット: `xxxx`」を現在の短縮ハッシュへ置換
|
||||
body = rel.get("body") or ""
|
||||
new_body, n = re.subn(
|
||||
r"(ビルド元コミット: `)[0-9a-fA-F]{7,40}(`)",
|
||||
r"\g<1>" + commit + r"\g<2>",
|
||||
body,
|
||||
)
|
||||
if n == 0:
|
||||
# まだ行が無いリリースには追記する。次回以降は置換で更新される。
|
||||
new_body = body.rstrip() + f"\n\nビルド元コミット: `{commit}`\n"
|
||||
api(
|
||||
f"/releases/{rid}",
|
||||
method="PATCH",
|
||||
data=json.dumps({"body": new_body}).encode("utf-8"),
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
print(f"body {'updated' if n else 'appended'}: ビルド元コミット -> {commit}")
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/bin/sh
|
||||
# Gitea リリース更新 —— 現在の HEAD と最新ビルドでリリースを差し替える。
|
||||
# yfoxlogsrv/tools/release-update.sh と同じ流儀。手動実行専用。
|
||||
#
|
||||
# やること:
|
||||
# 1) タグ <TAG>(既定 v1.0.0)を HEAD へ強制移動して push。
|
||||
# 2) リリースの添付 zip を最新ビルドへ差し替え(同名の旧資産は削除→アップロード)。
|
||||
# 3) リリース本文の「ビルド元コミット: `xxxxxxx`」を現在の短縮ハッシュへ更新。
|
||||
#
|
||||
# 前提:
|
||||
# * zip は事前にビルド済み(make release)。本スクリプトはビルドしない。
|
||||
# * リリース <TAG> は Gitea に既存であること(無ければエラー終了)。最初の1本は web か
|
||||
# gitea MCP の create_release で作る。以後の更新を本スクリプトが担う。
|
||||
# * 認証は git credential(host=git.yfox.jp)。Gitea が自己署名証明書のため TLS 検証は無効化。
|
||||
# * 日本語を含む本文の JSON 化のため Python を使う。
|
||||
#
|
||||
# 使い方:
|
||||
# ./tools/release-update.sh # タグ v1.0.0 を更新
|
||||
# ./tools/release-update.sh v1.0.1 # 別タグを指定
|
||||
set -eu
|
||||
|
||||
HOST="git.yfox.jp"
|
||||
OWNER="yoshikazu"
|
||||
NAME="photochk"
|
||||
TAG="${1:-v1.0.0}"
|
||||
ASSET_REL="dist/Photochk-${TAG#v}.zip"
|
||||
PY="/usr/bin/python3"
|
||||
|
||||
REPO=$(git rev-parse --show-toplevel)
|
||||
cd "$REPO"
|
||||
ASSET="$REPO/$ASSET_REL"
|
||||
|
||||
[ -f "$ASSET" ] || { echo "ERROR: zip が無い: $ASSET (先に make release が必要)" >&2; exit 1; }
|
||||
[ -x "$PY" ] || { echo "ERROR: Python が無い: $PY" >&2; exit 1; }
|
||||
|
||||
HASH=$(git rev-parse --short HEAD)
|
||||
FULL=$(git rev-parse HEAD)
|
||||
echo "repo: $REPO"
|
||||
echo "HEAD: $HASH tag: $TAG"
|
||||
echo "asset: $ASSET_REL ($(stat -f %z "$ASSET") bytes)"
|
||||
|
||||
# 0) 先に認証情報を取り出す。ここで落ちるなら push もアップロードもできないので、
|
||||
# タグを動かす前に止めたほうが状態がきれいなまま済む。
|
||||
# 認証情報は argv に載せず環境変数で渡す(プロセス一覧への漏洩回避)。
|
||||
#
|
||||
# プロトコルは origin の URL から決める。http と https で別のエントリが登録されて
|
||||
# いることがあり、決め打ちすると push は通るのに API だけ 401 になる。
|
||||
case "$(git remote get-url origin 2>/dev/null)" in
|
||||
https://*) PROTO="https" ;;
|
||||
http://*) PROTO="http" ;;
|
||||
*) PROTO="https" ;;
|
||||
esac
|
||||
creds=$(printf 'protocol=%s\nhost=%s\n\n' "$PROTO" "$HOST" | git credential fill 2>/dev/null || true)
|
||||
GITEA_USER=$(printf '%s\n' "$creds" | sed -n 's/^username=//p')
|
||||
GITEA_PASS=$(printf '%s\n' "$creds" | sed -n 's/^password=//p')
|
||||
[ -n "$GITEA_USER" ] && [ -n "$GITEA_PASS" ] || {
|
||||
cat >&2 <<EOF
|
||||
ERROR: git credential から認証情報を取得できない(protocol=$PROTO host=$HOST)
|
||||
この Mac には保存されていません。次の1行で登録できます(トークンは Gitea の
|
||||
設定 → アプリケーション で発行):
|
||||
|
||||
printf 'protocol=$PROTO\\nhost=$HOST\\nusername=<ユーザー名>\\npassword=<トークン>\\n\\n' | git credential approve
|
||||
|
||||
osxkeychain ヘルパが未設定なら先に: git config --global credential.helper osxkeychain
|
||||
EOF
|
||||
exit 1; }
|
||||
export GITEA_USER GITEA_PASS
|
||||
|
||||
# 1) タグを HEAD へ強制移動して push(annotated)。
|
||||
git tag -f -a "$TAG" "$FULL" -m "Photochk ${TAG#v}"
|
||||
git push --force origin "$TAG"
|
||||
echo "tag $TAG -> $HASH を push しました"
|
||||
|
||||
# 2)+3) 資産差し替えと本文更新を Python に委譲。
|
||||
"$PY" "$REPO/tools/_release_api.py" "$HOST" "$OWNER" "$NAME" "$TAG" "$ASSET" "$HASH"
|
||||
|
||||
echo "完了: https://$HOST/$OWNER/$NAME/releases/tag/$TAG"
|
||||
Reference in New Issue
Block a user