report - log5r/simple-media-player GitHub Wiki

#71: タグ上書きの追加確認

確認日: 2026-10-11(JST)。既存 Wiki In-Place-Metadata-Write の実ファイル計測とスパースファイルの結果は維持し、以下を追記する。実 fixture の生成・構造確認、実 LibraryService によるタイトル保存、保存前後の全 decode/hash 照合を完了した。再生中保存と他アプリ UI は別担当/後続枠で確認する。

4 GiB 超の実 M4V と co64

既存の 1.32 GB の実 M4V 資材は残っていなかった。短い H.264(yuv420p)と AAC を実際にエンコードし、そのパケットを concat demuxer と stream copy で繰り返し、物理的に 4 GiB を超える M4V を作る。スパースファイルで代用しない。作成後の大きさの上限は 6 GiB とし、moov を末尾に置く。

確認する内容:

  • ファイルの論理サイズと割り当て済みブロック、co64 のエントリー数と最大 offset。実際のチャンク offset が UInt32.max を超えていること。
  • タイトル保存の入口、revision、保存前後の inode、elapsed time、proc_pid_rusage の書込量。
  • 保存前後の ffprobe のタイトル、全 stream、再生時間。変更していないタグも同じ fixture で比較する。
  • 全ファイルを ffmpeg でデコードし、エラーがないこと。保存前後の streamhash が一致すること。
  • AVFoundation からの時間・タグ読取と、可能なら末尾付近の再生位置への seek。

生成・測定は #74 の性能枠が終わった後に親と調整する。空きディスクは準備時点で約 291 GiB。ffmpeg には libx264、libx265、h264/hevc_videotoolbox、aac がある。

再生中の保存

専用の一時 fixture を実 LibraryService.updateEmbeddedMetadata 経由で更新する。MP3/M4A/FLAC の余白内の上書きと余白不足の置換を対象に、AudioEngine の currentTime の進行、onError、保存後の fresh decode とタグを確認する。M4V は VideoPlayer の観測も検討する。

可能なら mixer tap の buffer 時刻・フレーム連続性・無音区間を保存前後で収集する。参考資材は /private/tmp/smp-issues-75-76/Issue75Bench.swift と sources-after/AudioEngineService.swift。音声サンプルの無音間隔を捕捉しない場合、時間の進行とエラーの有無だけで「聴感上の途切れなし」とは判定しない。動画は timeControlStatus/reasonForWaitingToPlay/error と保存後 decode の観測範囲を記録し、聴き比べ済みとはしない。ユーザーのメディアやライブラリは使わない。

他アプリと iOS

他アプリとの互換性は #65 の草稿で変更項目と未変更項目を整理する。Music の元からの非対応形式や Finder 情報に表示されない項目を、保存後の破損として数えない。実 UI は macOS UI テストの終了後に親と調整する。iOS の Files 経由の取り込みは iOS 担当と調整する。

生成済み fixture の構造

12秒の実エンコード seed(319,804,030 bytes)の動画パケットを16回 mux した media/real-co64.m4v は 5,116,869,605 bytes、割当済み容量は 5,123,608,576 bytes だった。H.264 Constrained Baseline/yuv420p/Level 5.1、1280×720、24 fps、192秒と AAC LC/48 kHz/ステレオで、全体の時間は192.020996秒。音声・動画を含む mdat が先頭側にあり、末尾の moov は134,969 bytes。

stream co64 entries 最大 offset UInt32.max 超の entries
動画 4,608 5,115,627,681 740
音声 4,608 5,115,626,723 740

実 chunk offset が4 GiBを超える物理ファイルであり、巨大な free やスパース領域でサイズを増やしていない。

最初に動画と音声をともに concat したところ、AAC priming に由来する境界の non-monotonic DTS 補正 warning が出た。そのファイルは主fixtureに使わず、実動画パケットだけを同じ条件で concat し、全192秒の AAC を連続エンコードして mux し直した。新muxにはDTS warningはない。生成時の音声filterにはffmpeg内部optionのdeprecated warningが出た。

生成コマンドは results/generation-commands.json、条件は repro/generate_co64.py、構造は results/layout-before.json、format/stream/tags は results/ffprobe-before.json。構造とffprobeでの読取を確認済み。保存前の全decodeを伴う decoded streamhash と encoded packet streamhash は exit0、error log はともに空だった。動画・音声の両 stream に SHA-256 の結果を保存した。保存後の結果は以下のとおり。

実 LibraryService による初回タイトル保存

#70 の UI 性能枠終了後、ほかの build/再生測定/native GUI を停止した専用枠で実行した。#74 後の作業中 source snapshot(基準 HEAD 88f559c81a294505c6a5663beb551ad3a3ab58a1)を一時 Release test に追加し、実 LibraryService.updateEmbeddedMetadata(for:draft:in:) を通した。書込側の製品処理は変更していない。読取側に #74 の件数計数 hook が残る。ソース hash は results/source-sha256.json、入口のハーネスは repro/Issue71Co64SaveTests.swift、関連 source は repro/source-snapshot/。

原本 real-co64.m4v は読み取り専用で残し、APFS clone saved-real-co64.m4v のタイトルを1回だけ保存した。初回の旧値は Original co64 fixture、新値は Changed co64 title through LibraryService 2026-10-11。textFields = [.title] とし、artist/album/comment を変更しない。専用 SQLite store を使い、保存後の別 ModelContext で editedTitle も一致した。検証コードの Optional/throwing macro 展開のビルドエラー2回を修正後、Release build-for-testing と指定 test 1件が成功した。失敗した build は保存・測定を実行していない。

確認項目 保存前 保存後
ファイルサイズ(bytes) 5,116,869,605 5,116,869,636
inode 193077699 193077699
moov offset 5,116,734,636 5,116,734,636
moov size(bytes) 134,969 135,000
動画/音声 co64 entries 各4,608 各4,608、全 table SHA-256 一致
UInt32.max 超の offsets 各740 各740
ffprobe duration 192.020996秒 192.020996秒

moov は同じ末尾位置で31 bytes 拡張し、mdat/ftyp の位置とサイズ、全 chunk offset を維持した。moov を別位置に移す操作や、新しい moov を末尾へ重複して追加する操作ではない。inode だけで「その場の更新」と判定せず、layout・全 offset・書込量も確認した。

保存区間は 0.179674167秒。proc_pid_rusage の process disk bytes written は 147,456 bytes、logical writes は8,536,064 bytes、disk bytes read は6,852,608 bytesだった。これらは同じ test host の SQLite 保存や runtime も含むプロセス差分であり、writer だけの bytes や SSD 内部の書込量ではない。1回の正しさ確認であり、繰り返し中央値として扱わない。指定 test は1 test/1 suite、0.200秒で成功した。JSON が存在し、test 件数が1であることを照合した。

artist=Unchanged artist、album=Unchanged album、comment=Unchanged comment は ffprobe と native reader の保存前後で一致した。保存後の全192秒を ffmpeg でデコードし、動画/音声それぞれの decoded streamhash と encoded packet streamhash は保存前に一致した。decode・packet hash・ffprobe・layout の error log は空。結果は results/save-real-co64.json、verify-summary.json、layout-save-before.json/layout-save-after.json、各 streamhash とログに保存した。

AVFoundation でも保存後の asset は isPlayable=true、2 tracks、duration=192.021秒、title/artist/album の値が一致した。190秒付近の画像を要求し、actualTime=189.979333333秒、1280×720のフレームを取得した。要求時刻との差は約20.7msであり、要求秒との完全一致やtrack startに由来する差とは断定しない。実際に末尾付近の圧縮データを読めたことを確認した。repro/AVCo64Probe.swift と results/avfoundation-after.json に記録した。native GUI での再生や聴き比べの結果には数えない。

再現入口は repro/run_save.py ROOT CHECKOUT DERIVED PACKAGES(新しいrootの原本をAPFS cloneし、一度の保存を実施)、照合は repro/verify_saved_co64.py ROOT FFMPEG FFPROBE。既存保存済み clone の上書き/再利用を拒否する。大容量 fixture、build成果物、SQLite本体をWikiへ登録しない。

⚠️ **GitHub.com Fallback** ⚠️