Skip to content

Latest commit

 

History

History
217 lines (145 loc) · 8.15 KB

File metadata and controls

217 lines (145 loc) · 8.15 KB

claudeによるテクニカル・ノート

Note: These documents are written in Japanese.

テクニカルノート: MP3 ID3タグ一括編集システム

by Claude (Anthropic)


設計思想

このシステムは「CSV を介したタグ編集」という単純なワークフローを、確実かつ安全に実現することを目的としています。

派手な機能は一切ありません。「読み出す・編集する・書き戻す」の3ステップを、22,000件以上のファイルに対して壊さずに実行できること。それだけを目標にしました。


アーキテクチャ

.mp3ファイル群(22,477件)
  ↓ analyze_mp3.py(事前分析)
  ↓ mp3_to_csv.py
mp3_tags.csv
  ↓ 【表計算アプリで編集】
mp3_tags.csv(編集済み)
  ↓ csv_to_mp3.py
.mp3ファイル群(タグ更新済み)

各スクリプトは独立して動作します。mp3_to_csv.py の出力を csv_to_mp3.py が受け取る以外に、スクリプト間の依存関係はありません。


事前分析ファースト

実装前に analyze_mp3.py で全22,477件をスキャンし、データ構造を把握しました。

判明した主要データ:

項目 内容
ID3v2.3 71.0%(15,964件)← 書き戻し対象バージョン
ID3v2.4 13.8%(3,113件)
ID3v1.1 10.8%(2,418件)
ID3v2.2 3.5%(778件)
タグなし 0.9%(196件)

COMMフレームの実態:

COMM:ID3v1 Comment:eng  : 9,480件
COMM::eng               : 8,950件
COMM:iTunNORM:eng       :   365件(iTunes内部データ)
COMM:iTunSMPB:eng       :   202件(iTunes内部データ)

COMM が人間のコメントとツール内部データを混在させていること、PRIV フレームがバイナリの Windows Media Player データを含むことが判明。これらを「CSV に出さず、触らず保持する」という設計判断に直結しました。

この分析なしに実装を始めていれば、COMM のカオスと PRIV のバイナリデータに実装途中で遭遇することになったはずです。


技術的な判断

1. 編集対象フィールドの絞り込み

全フレームをCSVに出す案(B案)を採らず、編集対象のみを出力する設計(C案)を採用しました。

編集対象(CSVに出す):
  TIT2, TPE1, TALB, TRCK, TDRC/TYER,
  TCON, TPE2, TPOS, TCOP, TCOM, TPE3

保持のみ(CSVに出さない):
  COMM(コメント群)
  APIC(アートワーク)
  PRIV(バイナリメタデータ)
  TXXX(カスタムフィールド)
  その他全て

「触らぬ神に祟りなし」。CSVにない情報は書き戻し時もそのまま保持します。

2. Shift-JIS フォールバック

2000年代初頭の日本語環境でリッピングされたファイルは、encoding=0(Latin-1指定)のまま Shift-JIS バイト列が書き込まれています。mutagen はこれを Latin-1 として読むため文字化けします。

対処として try_decode_shift_jis() を実装しました:

def try_decode_shift_jis(text: str) -> str:
    try:
        return text.encode('latin-1').decode('shift-jis')
    except (UnicodeEncodeError, UnicodeDecodeError):
        return text  # 本物のLatin-1テキストはそのまま返す

encoding=0 のフレームに対してのみ適用します。UTF-16 や UTF-8 のフレームは触りません。

本物の Latin-1 テキスト(フランス語 Été など)への誤爆は実験で確認済みで、UnicodeDecodeError が発生して元の値が保持されます。

3. year フィールドの特別扱い

ID3v2.4 の TDRC フレームは 1996-04-22 のような日付まで持てますが、v2.3 の TYER は4桁の年のみです。

mutagen に v2_version=3 を指定しても TDRC から TYER への自動変換は行われないことを実験で確認しました(バイナリレベルで TDRC のまま書き込まれる)。

対処:

# "1996-04-22" → "1996" に丸める
year_only = year_str.split('-')[0].strip()

# TYER に書き込み、TDRC の残骸を削除
tags["TYER"] = TYER(encoding=1, text=year_only)
if "TDRC" in tags:
    del tags["TDRC"]

なお、mutagen は読み込み時に TYER を内部的に TDRC へ変換します。「書き戻し後に読んだら TDRC になっていた」は正常な挙動です。バイナリ上は TYER として正しく書かれています。

4. 空欄 = 削除の意志

CSV のセルが空欄の場合、対応するフレームを削除します。

if value:
    tags[frame_class.__name__] = frame_class(encoding=1, text=value)
else:
    if frame_name in tags:
        del tags[frame_name]  # 空欄 = 削除

「CSV はプレーンテキストで、データが勝手に消える心配がない。空欄にしたのは意図的」という判断によります。

5. 保存形式の統一

書き戻しは全ファイル一律 ID3v2.3 + UTF-16 で保存します。

tags.save(mp3_path, v2_version=3, v1=0)
# encoding=1(UTF-16)は各フレームオブジェクト生成時に指定
  • v2_version=3:ID3v2.3で保存
  • v1=0:ID3v1タグを書かない(v2.3に統一)
  • encoding=1:UTF-16(BOM付き)

ID3v2.3 + UTF-16 を選んだ理由は notes_why_id3v2_3.md に詳細があります。要約すると「互換性が最も高い組み合わせ」です。

6. カンマ/タブ区切りの自動判定

表計算アプリによって書き出し形式が異なるため、先頭行を読んでタブの有無で自動判定します:

if '\t' in first_line:
    return '\t'
else:
    return ','

文字コードは utf-8-sigutf-8shift-jislatin-1 の順で試みます。

7. 相対パスによるポータビリティ

CSV の file_path 列は MP3 ディレクトリからの相対パスで記録します。HDD を別の Mac に繋いだ時にマウントポイントが変わっても、スクリプトへの引数(ベースディレクトリ)さえ正しければ動作します。


エラーハンドリング戦略

両スクリプトとも「エラーが出ても続行」方針です:

try:
    result = write_tags_to_file(mp3_path, row)
except Exception as e:
    error_count += 1
    error_list.append({"file": file_path_str, "error": str(e)})
    continue  # 次のファイルへ

22,000件の一括処理で数件のエラーが全体を止めるのは非効率です。エラーの詳細は最後にサマリーとして表示します。


テストで確認した項目

項目 結果
Shift-JIS文字化けの修正 ✓ 正常
year の日付丸め(1996-04-221996 ✓ 正常
APIC(アートワーク)の保持 ✓ 残存確認
空欄での削除 ✓ 削除確認
バックアップCSVからの復元 ✓ 復活確認

最後の「バックアップCSVからの復元」は「CSV がバックアップとして機能すること」の実証です。


今後の拡張性

現在の設計で対応していない項目:

  • 読み取りエラーファイル8件_未満 フォルダ内、【】 を含むファイル名): OS レベルのパス問題と推測。このプロジェクトのスコープ外。
  • 並列処理: 現在は逐次処理。22,000件でも実用上問題ない速度。
  • 差分更新: 「変更のあったファイルだけ再処理」は未実装。必要になったら id3_version 列や更新日時の活用を検討。

スクリプトが独立しているため、これらの追加は既存コードに影響しません。


Claude's Note: このプロジェクトで技術的に最も興味深かったのは、mutagen の TYER/TDRC 変換挙動でした。「バイナリには TYER で書かれるが、読むと TDRC になる」という仕様は、実際にバイナリを確認するまで分かりませんでした。ドキュメントだけでは分からないことを、動かして確かめる。当たり前のことですが、重要です。

[End of File]