Skip to content

Instantly share code, notes, and snippets.

@fujimogn
Created April 4, 2025 11:24
Show Gist options
  • Save fujimogn/435710a2b479434047fa3a49556dab03 to your computer and use it in GitHub Desktop.
Save fujimogn/435710a2b479434047fa3a49556dab03 to your computer and use it in GitHub Desktop.
ファイル自動でリネームするworkflow(クイックアクション)用スクリプト
#!/bin/zsh
set -e
export PATH="/usr/local/bin:/opt/homebrew/bin:$PATH"
###############################################################################
# 必要な Brew インストールライブラリ:
# - poppler: brew install poppler
# - tesseract: brew install tesseract
# - csvkit: brew install csvkit
# - jq: brew install jq
# - docx2txt: brew install docx2txt
# - xlsx2csv: brew install xlsx2csv
# - base64: macOS 標準搭載
# - file: macOS 標準搭載
###############################################################################
# APIキー
API_KEY=""
# 定数設定
MODEL="gpt-4o"
TEMPERATURE=0.7
HEAD_CHAR_LIMIT=1000
function encode_file_to_base64() {
local file="$1"
base64 -i "$file" | tr -d '\n'
}
function get_mime_type() {
local file="$1"
file --mime-type -b "$file"
}
function get_file_content() {
local file="$1"
local ext content
ext="${file##*.}"
ext=$(echo "$ext" | tr '[:upper:]' '[:lower:]')
if [[ "$ext" == "pdf" ]]; then
command -v pdftotext >/dev/null 2>&1 || { echo "エラー: pdftotext が必要です。" >&2; return 1; }
content=$(pdftotext "$file" - 2>/dev/null | head -c "$HEAD_CHAR_LIMIT")
elif [[ "$ext" =~ ^(jpg|jpeg|png|bmp|gif|tiff)$ ]]; then
# 画像は対象外(空文字返す)
content=""
elif [[ "$ext" == "docx" ]]; then
if command -v docx2txt >/dev/null 2>&1; then
content=$(docx2txt "$file" - 2>/dev/null | head -c "$HEAD_CHAR_LIMIT")
elif command -v textutil >/dev/null 2>&1; then
content=$(textutil -convert txt -stdout "$file" 2>/dev/null | head -c "$HEAD_CHAR_LIMIT")
else
echo "エラー: docx2txt または textutil が必要です。" >&2
return 1
fi
elif [[ "$ext" == "doc" ]]; then
command -v textutil >/dev/null 2>&1 || { echo "エラー: textutil が必要です。" >&2; return 1; }
content=$(textutil -convert txt -stdout "$file" 2>/dev/null | head -c "$HEAD_CHAR_LIMIT")
elif [[ "$ext" == "xlsx" ]]; then
if command -v xlsx2csv >/dev/null 2>&1; then
content=$(xlsx2csv "$file" 2>/dev/null | head -c "$HEAD_CHAR_LIMIT")
elif command -v in2csv >/dev/null 2>&1; then
content=$(in2csv "$file" 2>/dev/null | head -c "$HEAD_CHAR_LIMIT")
else
echo "エラー: xlsx2csv または in2csv が必要です。" >&2
return 1
fi
elif [[ "$ext" == "xls" ]]; then
command -v in2csv >/dev/null 2>&1 || { echo "エラー: in2csv が必要です。" >&2; return 1; }
content=$(in2csv "$file" 2>/dev/null | head -c "$HEAD_CHAR_LIMIT")
else
content=$(head -c "$HEAD_CHAR_LIMIT" "$file")
fi
echo "$content"
}
function get_new_filename() {
local original_name="$1"
local file_content="$2"
local file_date="$3"
local is_image="$4"
local base64_data="$5"
local mime_type="$6"
local prompt response new_name tmp_payload tmp_base64
prompt=$(cat <<'EOF'
# 命令
以下のファイル情報をもとに、内容に合った適切な新しいファイル名を提案してください。
**必ず** "YYYY-MM-DD_ファイルの名前" の形式で、ファイル名のみを1行で出力してください。
もし適切なファイル名が提案できない場合は、元のファイル名をそのまま返してください。
# 出力形式
{YYYY-MM-DD}_{ファイル名}
# 出力例
2025-01-01_アカウント請求書
# ルール
・{YYYY-MM-DD} はファイル内に記載の日付、なければファイル作成日を使用してください。
・ファイル名は簡潔に、日本語でわかりやすく命名してください。
・拡張子は不要です(こちらで追加します)。
EOF
)
tmp_payload=$(mktemp /tmp/chatgpt_payload.XXXXXX.json)
if [[ "$is_image" == "true" ]]; then
tmp_base64=$(mktemp /tmp/base64_data.XXXXXX.txt)
echo "$base64_data" > "$tmp_base64"
jq -n \
--arg model "$MODEL" \
--arg text_prompt "$prompt\n\n# 元のファイル名\n$original_name\n\n# ファイル作成日\n$file_date" \
--arg mime "$mime_type" \
--rawfile image_data "$tmp_base64" \
'{
model: $model,
messages: [{
role: "user",
content: [
{ "type": "text", "text": $text_prompt },
{ "type": "image_url", "image_url": { "url": ("data:" + $mime + ";base64," + $image_data) } }
]
}],
temperature: 0.7
}' > "$tmp_payload"
rm -f "$tmp_base64"
else
prompt=$(printf "%s\n\n# 元のファイル名\n%s\n\n# ファイル作成日\n%s\n\n# ファイルの内容(先頭部分)\n%s" \
"$prompt" "$original_name" "$file_date" "$file_content")
jq -n \
--arg model "$MODEL" \
--arg content "$prompt" \
'{
model: $model,
messages: [{ role: "user", content: $content }],
temperature: 0.7
}' > "$tmp_payload"
fi
response=$(curl -s "https://api.openai.com/v1/chat/completions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${API_KEY}" \
-d @"$tmp_payload")
rm -f "$tmp_payload"
if echo "$response" | grep -q "\"error\":"; then
echo "APIエラーが発生しました:$response" >&2
return 1
fi
new_name=$(echo "$response" | jq -r '.choices[0].message.content' | tr -d '\n\r' | sed 's/[\/:*?"<>|]/_/g' | sed 's/ */ /g')
echo "$new_name"
}
for file in "$@"; do
if [[ ! -e "$file" ]]; then
echo "エラー: ファイル '$file' は存在しません。" >&2
continue
fi
original_name=$(basename "$file")
extension="${original_name##*.}"
extension=$(echo "$extension" | tr '[:upper:]' '[:lower:]')
file_creation_date=$(stat -f "%SB" -t "%Y-%m-%d" "$file")
mime_type=$(get_mime_type "$file")
if [[ "$mime_type" =~ ^image/ ]]; then
is_image="true"
base64_data=$(encode_file_to_base64 "$file")
file_content=""
else
is_image="false"
base64_data=""
file_content=$(get_file_content "$file")
fi
new_name=$(get_new_filename "$original_name" "$file_content" "$file_creation_date" "$is_image" "$base64_data" "$mime_type") || {
echo "ファイル「${original_name}」のリネームに失敗しました。" >&2
continue
}
new_name="${new_name}.${extension}"
if [[ "$new_name" == "$original_name" ]]; then
echo "新しいファイル名が元のファイル名と同じため、リネームは行いません。"
continue
fi
dir=$(dirname "$file")
new_path="${dir}/${new_name}"
mv "$file" "$new_path"
echo "「${original_name}」→「${new_name}」にリネームしました。"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment