Programming & Technical May 26, 2026

ffprobe Duration and Sample-Rate Verification Matrix for Highlight Reel Audio - No More Drift 2026

2026 developer guide—batch ffprobe duration and sample-rate verification matrix for playtest highlight reel audio, drift reason codes, tolerance policies, and ffprobe_duration_matrix_receipt_v1.json before mux.

By GamineAI Team

ffprobe Duration and Sample-Rate Verification Matrix for Highlight Reel Audio - No More Drift 2026

Pixel-art hero for ffprobe duration sample rate verification matrix highlight reel audio 2026

Your VO consent metadata passed V4 on one facilitator line. The July highlight reel still shipped with facilitator_vo_03 ending 0.18 seconds early—enough to desync disclaimer captions on reel safe-zones and enough for legal to reject the cut because the waveform strip no longer matched the spoken consent_record_id window.

June–July 2026 teams learned that metadata survival (mux challenge) and duration truth are different gates. A single ffprobe one-liner per export is fine for your first line; seven VO assets, three sample-rate conversions, and two “small” ffmpeg trims need a verification matrix—one CSV row per file, one policy column per tolerance, one reason code when drift is real.

This Programming & Technical article is the batch QA layer for Lesson 250 gates V4/V5 and receipt #5 in the top 20 evidence hub. It does not re-teach Audacity export—that is the VO evening tutorial. It does give audio leads a scriptable matrix they can run in CI or before every mux night.

Non-repetition note: Mux challenge M3 is a 45-minute pre/post mux sprint on one asset. Case study recovery covers strip points, not systematic drift tables. This URL owns ffprobe duration + sample-rate matrix as primary keyword.

Who this is for and what you get

Audience You will be able to…
Audio programmer Batch-export ffprobe rows for every WAV in reel_manifest.csv
Reel editor Compare measured duration vs clip_plan.csv with team tolerances
Producer Block mux when any row is FAIL or WARN_ESCALATE

Time: ~90 minutes first matrix setup; ~8 minutes per reel refresh once templates exist.
Prerequisites: ffprobe on PATH, reel_manifest.csv + clip_plan.csv, cousin receipts #4–#5 from the July VO trend playbook.

Why this matters now (June–July 2026)

  1. Playback drift reports — Discord and facilitator feedback now cite “audio ends before caption” after 0.1 s trims—audible on mobile reels, invisible in waveform-only spot checks.
  2. Sample-rate churn — Teams bounce 44.1 kHz OBS extracts through 48 kHz VO masters and 44.1 kHz social exports; duration math changes when resample is implicit.
  3. Automation — BUILD_RECEIPT expects ffprobe_duration_matrix_ok beside wav_consent_metadata_ok; spreadsheets without ffprobe columns fail Thursday row review.
  4. Multi-line reels — One bad line poisons concat with OBS MKV gap preflight symptoms that look like video bugs.
  5. Post-mux regression — “Small” -shortest or AAC encode shifts end padding; matrix must run pre and post mux on the audio path legal reads.

Direct answer: Maintain clip_plan.csv expected durations → run batch ffprobe → join into duration_matrix.csv → apply tolerance policy → file ffprobe_duration_matrix_receipt_v1.json → only then promote mux or public reel.

Two-lane model (do not merge with consent fields)

Lane Tooling This article
Consent metadata LIST/iXML inspectors, vo_consent_metadata_receipt_v1 Cross-check only—matrix does not replace field proof
Clock truth ffprobe format=duration, stream=sample_rate Primary owner

Whisper concat decision tree cares about concat_ok on MKV batches; highlight reels care about per-line VO clocks aligned to design receipt R2 waveform strip.

Source files (beginner setup)

Create under release-evidence/audio/ffprobe-matrix/:

reel_manifest.csv

column example notes
asset_id facilitator_vo_03 stable key
wav_path art/voice/facilitator/vo_line_03.wav repo-relative
role vo_line or bed_music, sfx_sting
mux_stage pre_mux pre_mux / post_mux

clip_plan.csv

column example notes
asset_id facilitator_vo_03 joins manifest
vo_duration_expected_sec 3.450 from script timecode
tolerance_class vo_single maps policy table below
sample_rate_expected 48000 Hz integer
channels_expected 1 mono VO typical

Beginner mistake: Copying After Effects duration into clip_plan while the shipped asset is WAV from Audacity—always measure the file you mux, not the NLE comp.

Gate overview (P1–P8)

Gate Minutes Pass when
P1 Manifest lock 10 Every reel WAV listed with mux_stage
P2 Batch ffprobe export 15 ffprobe_raw.csv generated
P3 Matrix join 10 duration_matrix.csv has delta columns
P4 Sample-rate policy 10 All sample_rate_hz match expected or documented exception
P5 Duration policy 15 All rows PASS or approved WARN
P6 Post-mux repeat 15 post_mux rows exist for legal audio path
P7 Reason-code triage 10 Every FAIL has drift_reason_code
P8 Receipt + BUILD_RECEIPT 15 JSON filed; row reviewed

P2 — Batch ffprobe export (developer path)

Bash loop (Linux/macOS/Git Bash)

#!/usr/bin/env bash
set -euo pipefail
out="release-evidence/audio/ffprobe-matrix/ffprobe_raw.csv"
echo "asset_id,wav_path,duration_sec,sample_rate_hz,channels,codec_name" > "$out"
tail -n +2 reel_manifest.csv | while IFS=, read -r asset_id wav_path role mux_stage; do
  dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$wav_path")
  sr=$(ffprobe -v error -select_streams a:0 -show_entries stream=sample_rate -of csv=p=0 "$wav_path")
  ch=$(ffprobe -v error -select_streams a:0 -show_entries stream=channels -of csv=p=0 "$wav_path")
  codec=$(ffprobe -v error -select_streams a:0 -show_entries stream=codec_name -of csv=p=0 "$wav_path")
  echo "$asset_id,$wav_path,$dur,$sr,$ch,$codec" >> "$out"
done

PowerShell variant (Windows-friendly)

$manifest = Import-Csv reel_manifest.csv
$rows = foreach ($m in $manifest) {
  $dur = ffprobe -v error -show_entries format=duration -of csv=p=0 $m.wav_path
  $sr  = ffprobe -v error -select_streams a:0 -show_entries stream=sample_rate -of csv=p=0 $m.wav_path
  $ch  = ffprobe -v error -select_streams a:0 -show_entries stream=channels -of csv=p=0 $m.wav_path
  $codec = ffprobe -v error -select_streams a:0 -show_entries stream=codec_name -of csv=p=0 $m.wav_path
  [pscustomobject]@{
    asset_id = $m.asset_id
    wav_path = $m.wav_path
    duration_sec = [double]$dur
    sample_rate_hz = [int]$sr
    channels = [int]$ch
    codec_name = $codec
  }
}
$rows | Export-Csv release-evidence/audio/ffprobe-matrix/ffprobe_raw.csv -NoTypeInformation

Developer note: format=duration is container-level; for weird WAV subformats, add -show_entries stream=duration as secondary signal and log both in notes column when they disagree by >1 frame.

P3 — Matrix join and delta columns

Python join script (paste into scripts/join_duration_matrix.py)

#!/usr/bin/env python3
"""Join ffprobe_raw.csv + clip_plan.csv -> duration_matrix.csv"""
import csv
from pathlib import Path

RAW = Path("release-evidence/audio/ffprobe-matrix/ffprobe_raw.csv")
PLAN = Path("release-evidence/audio/ffprobe-matrix/clip_plan.csv")
OUT = Path("release-evidence/audio/ffprobe-matrix/duration_matrix.csv")

TOLERANCE = {
    "vo_single": 0.05,
    "vo_padded": 0.15,
    "bed_music": 0.25,
    "sfx_sting": 0.10,
    "post_mux_extract": 0.05,
}

def row_status(delta: float, tol_class: str, sr_ok: bool, ch_ok: bool) -> str:
    if not sr_ok or not ch_ok:
        return "FAIL"
    limit = TOLERANCE.get(tol_class, 0.05)
    ad = abs(delta)
    if ad <= limit:
        return "PASS"
    if ad <= 2 * limit:
        return "WARN"
    return "FAIL"

plan = {r["asset_id"]: r for r in csv.DictReader(PLAN.open(encoding="utf-8"))}
out_fields = [
    "asset_id", "wav_path", "duration_sec", "vo_duration_expected_sec",
    "duration_delta_sec", "duration_abs_delta", "sample_rate_hz",
    "sample_rate_expected", "sample_rate_match", "channels", "channels_expected",
    "channels_match", "tolerance_class", "row_status", "drift_reason_code",
]

with OUT.open("w", newline="", encoding="utf-8") as fout:
    w = csv.DictWriter(fout, fieldnames=out_fields)
    w.writeheader()
    for raw in csv.DictReader(RAW.open(encoding="utf-8")):
        p = plan.get(raw["asset_id"])
        if not p:
            continue
        dur = float(raw["duration_sec"])
        exp = float(p["vo_duration_expected_sec"])
        delta = dur - exp
        sr_ok = int(raw["sample_rate_hz"]) == int(p["sample_rate_expected"])
        ch_ok = int(raw["channels"]) == int(p["channels_expected"])
        status = row_status(delta, p["tolerance_class"], sr_ok, ch_ok)
        w.writerow({
            "asset_id": raw["asset_id"],
            "wav_path": raw["wav_path"],
            "duration_sec": f"{dur:.3f}",
            "vo_duration_expected_sec": f"{exp:.3f}",
            "duration_delta_sec": f"{delta:.3f}",
            "duration_abs_delta": f"{abs(delta):.3f}",
            "sample_rate_hz": raw["sample_rate_hz"],
            "sample_rate_expected": p["sample_rate_expected"],
            "sample_rate_match": str(sr_ok).lower(),
            "channels": raw["channels"],
            "channels_expected": p["channels_expected"],
            "channels_match": str(ch_ok).lower(),
            "tolerance_class": p["tolerance_class"],
            "row_status": status,
            "drift_reason_code": "" if status == "PASS" else "",
        })

Run after every P2 export: python scripts/join_duration_matrix.py. Commit the script beside release-evidence/ so facilitators on Windows and macOS share one truth.

Join ffprobe_raw.csv to clip_plan.csv on asset_id. Add computed columns:

column formula / rule
duration_delta_sec duration_sec - vo_duration_expected_sec
duration_abs_delta abs(duration_delta_sec)
sample_rate_match sample_rate_hz == sample_rate_expected
channels_match channels == channels_expected
row_status see policy table

Tolerance policy table

tolerance_class max duration_abs_delta typical use
vo_single 0.05 s one facilitator line
vo_padded 0.15 s intentional tail silence
bed_music 0.25 s looped bed under VO
sfx_sting 0.10 s short hit
post_mux_extract 0.05 s extracted AAC→PCM path
row_status Meaning
PASS Within tolerance; sample rate + channels match
WARN Within 2× tolerance—requires human note in receipt
FAIL Outside tolerance or sample-rate mismatch
SKIP opt_out or internal-only line not in public reel

Working dev rule: Never auto-PASS a row with sample_rate_match=false even if duration looks fine—resample hidden in DaVinci can shift lip-sync on the next export.

P4 — Sample-rate verification matrix (expanded)

Scenario ffprobe reads Action
VO master 48 kHz 48000 Expected for July reel spine
OBS extract 44.1 kHz 44100 Resample explicitly; log drift_reason_code=SR_OBS_SOURCE
Accidental 32 kHz 32000 FAIL; block mux—likely wrong export preset
Post-mux AAC extract 48000 Compare to pre-mux; delta duration not sample rate
Stereo VO channels=2 Only pass if channels_expected=2 documented

Document team house sample rate in audio_policy.md (one paragraph): public facilitator reels = 48 kHz mono PCM unless legal approves otherwise.

P5 — Duration drift reason codes (P7 triage)

When row_status=FAIL, assign exactly one primary code:

Code Meaning First fix
D1_TRIM ffmpeg -ss/-t removed tail Re-export without silent trim
D2_SHORTEST -shortest ended VO early Lengthen video or pad audio
D3_RESAMPLE implicit resample changed length Force aresample=async=1 policy or fix source
D4_NLE_ROUNDTRIP editor re-encoded with different tail Re-spot from Audacity master
D5_WRONG_MASTER wav_path not the file in clip_plan Fix manifest paths
D6_PLAN_STALE script changed; plan not updated Update clip_plan.csv deliberately

Escalate to iXML recovery case study when duration passes but consent fields vanished—different failure family.

P6 — Post-mux matrix row (legal audio path)

Repeat P2–P5 for mux_stage=post_mux on the WAV your legal tooling reads (Lane A extract, Lane B parallel master, or Lane C Audacity re-export per mux challenge).

Check Pre-mux Post-mux
duration_sec baseline must stay within post_mux_extract tolerance
sample_rate_hz 48000 must not drop to 44100 silently
consent fields not this article cousin VO receipt

Fail P6 when post-mux duration drifts but pre-mux passed—open mux ticket before updating design templates.

Example duration_matrix.csv (three rows)

asset_id,duration_sec,vo_duration_expected_sec,duration_delta_sec,duration_abs_delta,sample_rate_hz,sample_rate_match,row_status,drift_reason_code
facilitator_vo_01,3.21,3.21,0.00,0.00,48000,true,PASS,
facilitator_vo_02,3.08,3.25,-0.17,0.17,48000,true,FAIL,D2_SHORTEST
facilitator_vo_03,3.45,3.45,0.00,0.00,44100,false,FAIL,SR_OBS_SOURCE

ffprobe_duration_matrix_receipt_v1.json

{
  "schema": "ffprobe_duration_matrix_receipt_v1",
  "build_label": "playtest-july-2027-rc1",
  "matrix_path": "release-evidence/audio/ffprobe-matrix/duration_matrix.csv",
  "raw_probe_path": "release-evidence/audio/ffprobe-matrix/ffprobe_raw.csv",
  "policy_version": "2026-05-vo-tolerance-v1",
  "rows_total": 7,
  "rows_pass": 5,
  "rows_warn": 0,
  "rows_fail": 2,
  "sample_rate_mismatches": 1,
  "gates": {
    "P1_manifest": "pass",
    "P2_ffprobe_batch": "pass",
    "P3_join": "pass",
    "P4_sample_rate": "fail",
    "P5_duration": "fail",
    "P6_post_mux": "pass",
    "P7_reason_codes": "pass",
    "P8_receipt_filed": "pass"
  },
  "ffprobe_duration_matrix_ok": false,
  "cousin_receipts": {
    "vo_consent_metadata": "release-evidence/audio/VO_CONSENT_METADATA_RECEIPT.json",
    "metadata_survives_mux": "release-evidence/audio/mux-challenge/2026-05-26-rc1/metadata_survives_mux_receipt_v1.json"
  },
  "public_reel_blocked": true,
  "notes": "Resolve facilitator_vo_02 shortest trim and resample vo_03 before mux."
}

Pass P8 only when rows_fail=0 or producer signs warn_accepted with ticket id—never silent override.

BUILD_RECEIPT column suggestion

Add boolean row:

"ffprobe_duration_matrix_ok": false,
"ffprobe_matrix_receipt": "release-evidence/audio/ffprobe-matrix/ffprobe_duration_matrix_receipt_v1.json"

Review on Thursday row review beside wav_consent_metadata_ok.

DaVinci Resolve and NLE roundtrip notes (developer)

Editors often “fix” VO in Resolve while programmers certify WAV masters. Document two paths:

Path Matrix behavior
WAV master authoritative Matrix runs on art/voice/... only; NLE is preview
NLE export authoritative Add wav_path pointing to exports/reel_vo_03.wav and re-run P2 after every export

Fail pattern: Resolve timeline shows 3.25 s but exported WAV is 3.08 s—matrix catches D4_NLE_ROUNDTRIP while editors insist “timeline is correct.”

Fix discipline: Spot from Audacity master into Resolve; never re-record consent lines only in NLE without re-running VO consent V1–V3.

AAC and MP4 intermediate traps

Teams sometimes ffprobe AAC inside MP4 instead of PCM WAV:

ffprobe -v error -select_streams a:0 -show_entries stream=codec_name,sample_rate,duration -of json clip.mp4
codec_name Risk
aac encoder padding changes end silence vs PCM master
pcm_s16le preferred for matrix baseline on VO masters

When legal reads extracted PCM from deliverable MP4, set mux_stage=post_mux and compare against pre_mux PCM row—do not compare AAC bitstream duration to WAV plan without extraction step documented in receipt.

Multi-line reel manifest example (seven assets)

asset_id role vo_duration_expected_sec tolerance_class
facilitator_vo_01 vo_line 3.21 vo_single
facilitator_vo_02 vo_line 3.25 vo_single
facilitator_vo_03 vo_line 3.45 vo_padded
facilitator_vo_04 vo_line 2.90 vo_single
bed_loop_01 bed_music 12.00 bed_music
sting_hit_01 sfx_sting 0.35 sfx_sting
disclaimer_bed bed_music 4.00 bed_music

Producer rule: Any row with opt_out=true in consent log gets row_status=SKIP in matrix—remove from reel_manifest.csv for public cuts, do not widen tolerance to absorb illegal lines.

jq one-liners for standup (working dev)

Count failures without opening Excel:

jq -r 'select(.row_status=="FAIL") | .asset_id' duration_matrix.json

Convert CSV to JSON once for jq:

import csv, json, sys
rows = list(csv.DictReader(open("duration_matrix.csv", encoding="utf-8")))
json.dump(rows, sys.stdout, indent=2)

Paste failing asset_id list into standup—faster than scrolling ffprobe stdout.

Workshop — 90-minute team install

Minute block Activity
0–15 Agree audio_policy.md sample rate + tolerance table
15–30 Author clip_plan.csv from locked script
30–45 Run P2 bash or PowerShell on all pre_mux paths
45–60 Run join script; review first FAIL row live
60–75 Dry-run mux; run post_mux rows
75–90 File receipt; update BUILD_RECEIPT booleans

Exit criterion: Everyone on call can explain difference between D2_SHORTEST and D3_RESAMPLE without opening ffmpeg docs.

Integration with BUILD_RECEIPT beginner pipeline

If your studio just adopted first BUILD_RECEIPT evening, add matrix columns in week two—not day one. Order of operations:

  1. playtest_clip_consent_ok (video)
  2. wav_consent_metadata_ok (embedded fields)
  3. ffprobe_duration_matrix_ok (clocks)
  4. metadata_survives_mux_ok (post-mux fields)
  5. vo_reel_design_ok (layout R1–R6)

Skipping step 3 while step 2 is GREEN still ships reels that sound compliant but end early.

Evidence for facilitators (plain language)

Tell facilitators: “We are not judging your performance—we are measuring file length against the script so captions and consent disclaimers line up.” Share only asset_id + row_status columns in standup, not raw UUIDs from cousin consent receipts.

Regression log template (MATRIX_LOG.md)

## 2026-05-26 playtest-july-2027-rc1
- vo_02 FAIL D2_SHORTEST: removed -shortest on QA mux
- vo_03 FAIL SR_OBS_SOURCE: resampled 44100 -> 48000
- post_mux vo_02 PASS after fix

Link each bullet to ticket id—auditors read logs months later.

CI hook sketch (optional)

- name: ffprobe duration matrix
  run: |
    bash scripts/run_ffprobe_matrix.sh
    python scripts/assert_matrix_pass.py release-evidence/audio/ffprobe-matrix/duration_matrix.csv

assert_matrix_pass.py should exit 1 on any FAIL row—do not parse stderr from ffmpeg in the same step; keep probe and assert separate for clearer logs.

Facilitator multi-laptop handoff

Role Owns
VO editor P1–P5 on pre_mux
Reel lead P6 on post_mux
Producer P8 + BUILD_RECEIPT

Sync via shared drive path—attach duration_matrix.csv, not screenshots of ffprobe stdout.

Pairing with OBS concat receipts

When highlight reel pulls MKV fragments + VO lines, run:

  1. OBS highlight concat preflight for video consent.
  2. This matrix for VO clocks.
  3. Mux challenge for metadata survival.

Skipping step 2 produces GREEN video receipts with RED reel playback.

Three scenarios (beginner walkthrough)

Scenario A — One line fails shortest

Symptom: facilitator_vo_02 D2_SHORTEST.
Fix: Remove -shortest for QA export or extend picture by 0.2 s; re-run matrix.
Proof: duration_delta_sec moves inside vo_single tolerance.

Scenario B — 44.1 kHz source

Symptom: SR_OBS_SOURCE on line recorded from OBS extract.
Fix: ffmpeg -i in.wav -ar 48000 -ac 1 out.wav with documented command in receipt resample_command field.
Proof: sample_rate_match=true and duration re-spotted.

Scenario C — Post-mux only drift

Symptom: pre-mux PASS, post-mux FAIL D1_TRIM.
Fix: Change AAC extract to PCM -acodec pcm_s16le or keep parallel WAV master (Lane B).
Proof: post-mux row matches pre within post_mux_extract.

Proof folder layout

release-evidence/audio/ffprobe-matrix/
  reel_manifest.csv
  clip_plan.csv
  ffprobe_raw.csv
  duration_matrix.csv
  ffprobe_duration_matrix_receipt_v1.json
  MATRIX_LOG.md          # human notes for WARN rows

Key takeaways

  1. One ffprobe line is not a matrix—batch every VO asset in the reel manifest.
  2. Duration and sample rate are separate FAIL conditions—do not mask SR mismatch with duration luck.
  3. Tolerance classes belong in clip_plan.csv, not in engineers’ heads.
  4. Post-mux rows are mandatory on the audio path legal certifies.
  5. Reason codes turn drift reports into fix tickets (D1D6, SR_OBS_SOURCE).
  6. ffprobe_duration_matrix_ok complements wav_consent_metadata_ok on BUILD_RECEIPT.
  7. Pair with VO tutorial V4 for first-line setup, then this matrix for reel-scale QA.
  8. Block public reel when receipt shows rows_fail > 0 without signed warn.
  9. Re-run matrix after any “small” ffmpeg edit—even 0.1 s.
  10. Pair with #20 consent governance before public reel; forward #23 ten-minute ritual (planned).
  11. 16-tool concat listicle includes ffprobe—this article is the highlight-reel matrix pattern, not general VOD triage.
  12. Audacity VO preflight stays ninety-second; matrix is pre-mux night batch.

Channel layout and loudness (adjacent checks)

Duration matrix does not replace LUFS metering—pair with 14 free LUFS tools for trailer lanes. Still document channel layout in ffprobe because mono/stereo mismatch breaks some mux maps:

channels Typical VO Matrix rule
1 facilitator mono default expected
2 stereo ambience bed set channels_expected=2
6 accidental surround export FAIL until downmixed
ffprobe -v error -select_streams a:0 -show_entries stream=channel_layout -of csv=p=0 vo_line_01.wav

If channel_layout reads stereo but you expected mono, downmix with explicit ffmpeg -ac 1 and log command in receipt—do not silently accept stereo VO over mono disclaimer timing.

Comparison — matrix vs cousin articles

Question Answer on this URL Answer elsewhere
How do I export WAV with consent fields? cross-link only VO tutorial
Does metadata survive mux? post-mux duration rows Mux challenge
Where do captions go? timing must match matrix Reel design R4
What receipts exist? ffprobe_duration_matrix_receipt_v1 Top 20 hub
Why July urgency? drift after small edits July trend playbook

assert_matrix_pass.py (minimal gate)

#!/usr/bin/env python3
import csv, sys
from pathlib import Path

path = Path(sys.argv[1])
rows = list(csv.DictReader(path.open(encoding="utf-8")))
fails = [r for r in rows if r.get("row_status") == "FAIL"]
if fails:
    for r in fails:
        print(f"FAIL {r['asset_id']} delta={r.get('duration_delta_sec')} code={r.get('drift_reason_code')}")
    sys.exit(1)
print(f"OK {len(rows)} rows")

Wire into CI after join script—exit code 1 blocks merge to fest-reel branch.

Floating-point and frame-rate pitfalls

Beginner trap: Comparing 3.21 s plan to 3.209999 s ffprobe output—use rounded milliseconds in matrix display (3.210) but compute delta on floats.
Developer trap: 29.97 fps video with 48 kHz audio—matrix is on audio files, not video avg_frame_rate; do not divide video frames to guess VO duration.

When script timecode is frames, convert once in clip_plan.csv to seconds with documented fps—do not maintain two competing expected columns.

Stereo VO with mono disclaimer bed

Some teams record VO stereo for spatial feel but mux mono disclaimer beds:

asset channels_expected note
vo_line 2 document in MATRIX_LOG
disclaimer_bed 1 do not downmix VO without re-spot

Mismatch between VO stereo and mono waveform strip in design template is a design problem—matrix still reports truth; open ticket to reel design R2.

Batch size and performance

Seven VO lines × two mux stages = 14 ffprobe calls—under one second on SSD. Scale to 40 lines before optimizing; ffprobe is not your bottleneck—human interpretation of FAIL is.

For hundred-line archives, cache ffprobe_raw.csv hash in receipt when manifest_sha256 unchanged.

Legal readout packet (what to send)

Zip for legal review without game repo access:

  1. duration_matrix.csv (redact wav_path if needed)
  2. ffprobe_duration_matrix_receipt_v1.json
  3. MATRIX_LOG.md
  4. Cousin VO_CONSENT_METADATA_RECEIPT.json (separate lane)

Legal cares that clocks match script and sample rate is consistent—not ffmpeg brand.

FAQ

Is ffprobe enough or do we need soxi / mediainfo

ffprobe is sufficient for duration, sample rate, and channels on WAV/PCM paths teams use for July reels. Add MediaInfo only when you need container tags outside ffprobe’s stream table.

What tolerance should we use for caption sync

Start ±0.05 s on vo_single lines tied to design R4 disclaimer; tighten if legal requests frame-accurate consent windows.

Does the matrix replace the mux challenge

No—challenge proves metadata survives mux; matrix proves clocks and sample rates across all lines before and after mux.

Can we run matrix only on post-mux files

You may, but you lose the ability to blame mux vs export—always keep pre_mux rows for facilitator VO masters.

How does this relate to Lesson 250 V4 and V5

V4 is duration spot on export; V5 is post-mux survival—this matrix operationalizes both at reel scale with CSV artifacts.

What if clip_plan and script disagree

Update clip_plan.csv deliberately with producer sign-off—do not “fix” by widening tolerance without documenting D6_PLAN_STALE.

Should bed music use the same sample rate as VO

Yes for July spine—48 kHz everywhere in public reel manifest unless legal approves split-rate deliverables.

Can WARN rows ship

Only with warn_accepted ticket in receipt notes—default policy is block.

Where does AI-generated VO fit

Run matrix on exported WAV files regardless of generator; pair with planned #22 AI voice QA reason-codes when tags are wrong but clocks look fine.

How often to re-run

Every manifest change, every mux command change, every “small” trim—treat like Wednesday smoke for audio clocks.

What file naming helps automation

Use facilitator_vo_## matching asset_id in CSV—avoid spaces and locale-specific decimal commas in duration columns.

Can we store matrix in Google Sheets

Export from Sheets to clip_plan.csv for git; do not treat Sheets as source of truth without version column.

Does bitrate affect duration

Codec bitrate does not change duration on PCM WAV; AAC VBR in MP4 can—always extract PCM for post-mux matrix rows when legal requires it.

Who owns P8 sign-off

Producer when rows_fail=0; audio lead co-sign when WARN rows ship with tickets.

Relationship to playtest facilitator contract

Multi-channel facilitator contract requires audio gate evidence—attach matrix CSV path in daily notes template.

What if ffprobe returns N/A

Usually corrupt path or zero-byte file—FAIL with D5_WRONG_MASTER; do not coerce to PASS.

Should we version tolerance policy

Yes—bump policy_version in receipt when legal changes caption window requirements.

Can interns run the matrix

Yes—give them P2 scripts only; producer signs P8 after reviewing FAIL/WARN rows.


Highlight reels need a verification matrix, not a single ffprobe one-liner—batch duration and sample-rate truth before July mux, file ffprobe_duration_matrix_receipt_v1.json, then promote BUILD_RECEIPT.