When ffmpeg Concat Fails Should You Retry Whisper Per Clip or Cloud API - 2026 Decision Tree
Your ffmpeg -f concat run printed Non-monotonous DTS at 02:14. The merged WAV is 0 bytes. Someone on Discord asked whether to retry concat, run Whisper on every Replay Buffer clip, or upload to the OpenAI API and hope chunking saves the night.
You already shipped the OBS Replay Buffer concat evening pipeline. You already batch-transcribe with the local Whisper playtest VOD triage workflow. concat_ok: false is not a personality test—it is a fork in the road. Pick a named path, write it to ffmpeg_concat_decision_receipt_v1.json, and only then set whisper_batch_allowed on playtest_vod_triage_receipt_v1.json.
June–July 2026 async playtest volume is high enough that teams hit this fork weekly. This AI Integration / Workflow post is the decision tree only—not a second concat tutorial, not a full Whisper install guide. It answers one search-shaped question: when merge fails, what do I do next without lying to my receipt JSON?
Non-repetition note: 16 free OBS ffmpeg Silero concat tools is a tool list; OBS zero-duration audio help is missing audio tracks; Whisper API 413 chunking help is cloud size limits after you already have audio. This URL owns path selection when concat_ok is false.
Pair with BUILD_RECEIPT, Thursday row review, course Lesson 207 — Whisper path when concat fails, preventive uniform fragments preflight (ninety-second OBS profile gate), and Lesson 258 — replay buffer uniform receipt on BUILD_RECEIPT. Forward link target: Help #5 concat fix (pairs backlog).
Who this is for and what you get
| Audience | You will be able to… |
|---|---|
| First-time facilitator | Stop guessing after a failed concat |
| Solo dev | Choose local vs cloud without NDA panic |
| Producer | Require chosen_path before triage standup |
| Engineer | Wire whisper_batch_allowed honestly in CI |
Time: ~20 minutes to run the tree on one failed session folder.
Prerequisites: ffprobe on PATH; a session folder with Replay Buffer MKVs; an existing playtest_vod_triage_receipt_v1.json row (or template from the Whisper pipeline blog).
Why this matters now (June–July 2026)
- Replay Buffer fragments — One playtest night produces 8–20 MKVs, not one MP4. Concat fails are normal, not exceptional.
- Receipt culture — Teams adopted BUILD_RECEIPT rows;
concat_okwithoutchosen_pathcreates false-green triage. - Discord ops questions — “Should we cloud Whisper?” spikes after fest playtests when local GPU time is scarce.
- Privacy defaults — Uploading twelve clips because merge failed is a policy decision, not a ffmpeg accident.
- October deadline — Fixing path discipline in July prevents October triage collapse.
Direct answer: Log concat_ok: false, run gates D1–D6, pick reencode_concat, per_clip_local, or cloud_chunked, file ffmpeg_concat_decision_receipt_v1.json, then set whisper_batch_allowed only when that path’s verification gates pass.
Mental model — three clocks
| Clock | What it measures | Receipt field |
|---|---|---|
| Merge clock | Can fragments become one timeline-safe WAV? | concat_ok |
| Path clock | Which recovery lane did you choose? | chosen_path |
| ASR clock | May batch Whisper run on merged or per-clip audio? | whisper_batch_allowed |
Never start the ASR clock while the Path clock is blank. That is how summaries claim “we transcribed the session” when half the clips were never merged or tagged.
Beginner path — five steps when concat fails
- Confirm failure honestly — Open
playtest_vod_triage_receipt_v1.jsonand setconcat_ok: false. Do not delete the row. - Archive ffprobe — Save
ffprobe_fragments.csvper OBS ffprobe concat_ok preflight. - Walk the decision tree — Answer the yes/no questions in the next section.
- File path receipt — Write
ffmpeg_concat_decision_receipt_v1.jsonwithchosen_path. - Re-run only the ASR step — Per-clip loop, successful re-merge, or cloud upload—never all three silently.
Success check: A teammate reading only your JSON knows why batch Whisper did or did not run.
Decision tree (D1–D6 gates)
Use these gates in order. Stop at the first branch that determines chosen_path.
| Gate | Question | If YES | If NO |
|---|---|---|---|
| D1 | Is concat_ok recorded false on the triage receipt? |
Continue | Fix honesty first—do not pick a path on a lie |
| D2 | Does every fragment have audio with duration greater than 0? | Continue | Fix OBS capture—see zero-duration audio help |
| D3 | Did concat fail with DTS/timestamp errors but fragments are otherwise valid? | Candidate reencode_concat |
Skip to D4 |
| D4 | Do you have GPU time and NDA allows local-only processing? | Candidate per_clip_local |
Lean cloud_chunked |
| D5 | Is total session audio under your cloud size budget per clip after chunking? | cloud_chunked viable |
Split clips or reencode_concat |
| D6 | Did the chosen path pass its verification gate? | Set whisper_batch_allowed per path rules below |
Remediation—do not transcribe yet |
ASCII decision flow
concat_ok == false?
|
+-- NO --> (wrong article; use concat evening pipeline)
|
YES
|
+-- all fragments have audio? --NO--> fix OBS / re-capture
|
YES
|
+-- DTS/codec concat error only? --YES--> reencode_concat
| |
| +-- post_path_concat_ok? --YES--> whisper_batch_allowed true
|
+-- privacy requires local only? --YES--> per_clip_local
| |
| +-- all clips transcribed? --YES--> whisper_batch_allowed true (batch summaries OK)
|
+-- else --> cloud_chunked (per clip or chunked upload)
|
+-- API 413? --> see cloud chunking help
Path 1 — reencode_concat (retry merge with encode, not copy)
Choose when: ffprobe shows valid audio on every fragment, but ffmpeg -f concat -c copy failed with Non-monotonous DTS, Invalid data, or codec mismatch.
Do not choose when: Fragments lack audio (D2 fail) or half the folder is from a different OBS profile with incompatible channel layouts you have not normalized.
Working dev procedure
- Re-run normalize → concat from the concat evening pipeline Block 3, forcing encode:
ffmpeg -y -f concat -safe 0 -i concat_list.txt -ac 2 -ar 48000 -c:a pcm_s16le session_merged.wav
- Re-verify duration within ±2 s of fragment sum.
- Set on decision receipt:
{
"chosen_path": "reencode_concat",
"post_path_concat_ok": true,
"whisper_batch_allowed": true
}
- Update triage receipt: keep original
concat_ok: falseon the failed attempt row; add a new row orrecovery_attemptblock withpost_path_concat_ok: trueso Thursday row review shows recovery, not overwrite history.
Common mistake: Marking concat_ok: true on the same row without noting the failed first pass—auditors cannot see you recovered.
Path 2 — per_clip_local (local Whisper per fragment)
Choose when: Merge is not worth the engineering time tonight, local GPU is available, and NDA / player consent requires audio never leaves the machine.
Tradeoffs:
| Pros | Cons |
|---|---|
| No cloud upload | Manual loop over N files |
| Keeps privacy story simple | Session-level summaries need merge in spreadsheet |
| Works when DTS is hopeless | Higher operator attention |
Per-clip loop (operator checklist)
For each 00N_replay_*.mkv in sort order:
ffmpeg -i clip.mkv -vn -ac 1 -ar 16000 clip.wav- Run local Whisper (same model table as Whisper pipeline blog).
- Write
transcripts/00N.txtand one-linesummaries/00N.mdwithbuild_id,surface, timestamp offset. - Append row to batch manifest
per_clip_manifest.json.
Set decision receipt:
{
"chosen_path": "per_clip_local",
"fragment_count": 12,
"per_clip_transcripts_complete": true,
"whisper_batch_allowed": true,
"merged_audio": null,
"notes": "No merged WAV; triage uses per-clip summaries"
}
whisper_batch_allowed: true here means you may run your batch summary script over per-clip outputs—it does not mean concat_ok became true.
Facilitator README snippet
## When concat fails
1. Set concat_ok false on the triage receipt.
2. Do NOT upload VODs to cloud unless producer approves.
3. Run per-clip Whisper loop; file ffmpeg_concat_decision_receipt_v1.json.
4. Post daily note with fragment indices, not "the session."
Path 3 — cloud_chunked (API Whisper with chunking discipline)
Choose when: Local GPU is unavailable, you need fastest text tonight, and consent docs allow cloud transcription for this playtest cohort.
Do not choose when: Only failure mode is fixable merge (Path 1 is cheaper and keeps data local). Do not choose when 413 Payload Too Large is likely without a chunk plan—read Whisper API 413 chunking help first.
Cloud path steps
- Extract 48 kHz WAV per clip (same as local path extract).
- Chunk to under API limit (often 24–25 MB per request depending on account).
- Upload chunks with filename prefix
001_,002_preserving order. - Stitch transcript text files with headers:
### Fragment 003 — 2026-05-26T19:14:22 — build rc4
<transcript>
- File decision receipt:
{
"chosen_path": "cloud_chunked",
"cloud_provider": "openai_whisper",
"chunk_count": 28,
"consent_doc_version": "playtest-vod/README.md#cloud",
"whisper_batch_allowed": true,
"concat_ok": false
}
Cost discipline: Cloud is for time, not laziness. If Path 1 succeeds in under 30 minutes, prefer local merge.
Outbound reference: OpenAI Whisper API documentation for current limits and formats.
Proof table — map path to receipt columns
| Path | concat_ok |
chosen_path |
whisper_batch_allowed |
Required proof artifact |
|---|---|---|---|---|
| Happy merge (not this article) | true | null or omitted | true | session_merged.wav |
| reencode_concat | false on attempt 1 | reencode_concat | true after post_path_concat_ok |
session_merged.wav + encode log |
| per_clip_local | false | per_clip_local | true when all clips done | per_clip_manifest.json |
| cloud_chunked | false | cloud_chunked | true when chunks stitched | cloud_stitch_index.md |
Add these columns to your spreadsheet triage board if JSON feels heavy for non-dev producers.
ffmpeg_concat_decision_receipt_v1.json (full example)
{
"schema": "ffmpeg_concat_decision_receipt_v1",
"build_id": "nextfest-oct-2026-rc4",
"surface": "playtest_invite",
"session_folder": "playtest-vod/inbox/2026-05-26_session-rc4",
"concat_ok": false,
"chosen_path": "per_clip_local",
"decision_gates": {
"D1_concat_ok_false_logged": "pass",
"D2_fragment_audio_present": "pass",
"D3_dts_error_only": "fail",
"D4_local_allowed": "pass",
"D5_cloud_budget": "skipped",
"D6_path_verification": "pass"
},
"fragment_count": 11,
"per_clip_transcripts_complete": true,
"whisper_batch_allowed": true,
"operator": "facilitator-east",
"decided_at_utc": "2026-05-26T22:15:00Z",
"notes": "Concat copy failed DTS; per-clip faster than third merge attempt before standup"
}
Store beside playtest_vod_triage_receipt_v1.json in the same receipts/ folder.
Working dev path — CI and batch scripts
Guardrail sketch (fail closed)
Pseudologic your nightly job should enforce:
if receipt.concat_ok == false:
require ffmpeg_concat_decision_receipt_v1.json
require receipt.chosen_path in ALLOWED_PATHS
if receipt.whisper_batch_allowed == true:
assert path_verification_passed(receipt.chosen_path)
else:
allow batch whisper on session_merged.wav
Aligns with Lesson 207 verify_whisper_path_decision_v1 intent.
Batch script branch (Python outline)
def whisper_entry(receipt, decision):
if receipt["concat_ok"]:
return transcribe_merged(receipt["merged_audio"])
path = decision["chosen_path"]
if path == "reencode_concat" and decision.get("post_path_concat_ok"):
return transcribe_merged(receipt["merged_audio"])
if path == "per_clip_local":
return transcribe_per_clip_manifest(decision["per_clip_manifest"])
if path == "cloud_chunked":
raise RuntimeError("Cloud path must not run in unattended batch without explicit flag")
raise RuntimeError("concat_ok false but no valid chosen_path")
Why cloud is gated: Unattended upload is a policy event, not a build step.
Cost and time comparison (honest bands)
Numbers vary by GPU and clip length; use bands for planning, not quotes.
| Path | Operator time (11 clips) | Money | Privacy |
|---|---|---|---|
| reencode_concat | 25–45 min | $0 | Local |
| per_clip_local | 40–90 min | $0 electricity | Local |
| cloud_chunked | 20–40 min | API $ | Cloud |
When operator time is the bottleneck and merge is likely fixable, try Path 1 twice before Path 3.
Failure modes and remediation
| Symptom | Wrong conclusion | Correct path |
|---|---|---|
| 0-byte merged WAV | “Whisper broken” | reencode_concat or per_clip_local |
| Chipmunk audio after merge | “Whisper hallucinating” | reencode_concat with wrong rate |
| API 413 on upload | “game too long” | chunk per clip; see help |
| Mixed OBS profiles in folder | “concat impossible” | split session folders by profile |
whisper_batch_allowed true with no transcripts |
“done” | D6 fail—remediation |
Three-surface tagging reminder
When filing summaries, keep surface aligned with playtest isolation:
- playtest_invite — Steam Playtest key cohort
- fest_public — Next Fest library install
- internal — team dogfood
Path choice does not change surface. A per-clip loop still tags each fragment row.
Tooling cross-links (not a tool list)
Use the 16-tool concat prep list for stack inventory. Use this tree when concat_ok is already false and you must decide what happens next.
| Tool role | When in this tree |
|---|---|
| ffprobe | D2 fragment proof |
| ffmpeg encode concat | Path 1 |
| faster-whisper | Path 2 |
| OpenAI API | Path 3 |
| jq | Validate receipt JSON in CI |
Producer-facing one-pager (paste into contract)
From multi-channel facilitator contract teams already require audio gate proof. Add one bullet:
- If concat fails: facilitator files
ffmpeg_concat_decision_receipt_v1.jsonwithin 4 hours and notifies#playtest-opswithchosen_path—no cloud upload without producer reply.
That single bullet prevents midnight API uploads.
Scenario walkthrough A — DTS error on eleven MKV fragments
Context: Tuesday playtest, build_id rc4, eleven Replay Buffer saves, ffmpeg -f concat -c copy exits with Non-monotonous DTS.
D1: Receipt row already shows concat_ok: false from operator—pass.
D2: ffprobe_table.csv shows audio on all eleven—pass.
D3: Error log mentions DTS only—reencode_concat candidate.
D4: Local GPU free—Path 1 preferred over per-clip.
D5: Skipped—cloud not needed.
D6: Re-encode merge duration 1388 s vs sum 1390 s—pass.
Actions: Operator runs encode concat from normalized WAV list, updates decision receipt with post_path_concat_ok: true, runs single Whisper pass on session_merged.wav. Do not flip original concat_ok on failed row.
Standup sentence: “Merge failed on copy; we re-encoded concat, merged WAV verified, batch Whisper allowed.”
Scenario walkthrough B — two OBS profiles in one folder
Context: Facilitator changed OBS sample rate mid-session; fragments mix 44100 and 48000.
D3: Copy concat fails—expected.
Path choice: If normalize-then-encode still fails after splitting folders by profile, use per_clip_local rather than a third merge attempt past 45 minutes.
Lesson: The tree is not “retry until magic.” Splitting bad inputs is valid remediation.
Scenario walkthrough C — NDA blocks cloud, GPU busy rendering trailer
Context: Art team needs the workstation; merge failed; standup in 90 minutes.
Path: per_clip_local on laptop #2 while desktop renders. File per_clip_manifest.json with eleven rows. Producer merges summaries in spreadsheet manually.
Receipt: whisper_batch_allowed: true with note “summary merge manual.”
concat_ok vs whisper_batch_allowed — do not conflate
| Field | Meaning | Common bug |
|---|---|---|
concat_ok |
Did merge produce one verified timeline? | Set true after per-clip because “we have text” |
whisper_batch_allowed |
May automated batch ASR/summary run proceed? | True while zero transcripts exist |
chosen_path |
Which recovery lane when merge failed? | Omitted entirely |
Audit sentence for Thursday row review: “concat_ok false + chosen_path set + whisper_batch_allowed true only after D6.”
Spreadsheet columns for producers (no JSON required)
| Column | Example |
|---|---|
| session_id | 2026-05-26_rc4 |
| concat_ok | false |
| chosen_path | per_clip_local |
| whisper_batch_allowed | true |
| fragment_count | 11 |
| blocker | none |
| owner | Alex |
| standup_note | Per-clip transcripts in summaries/ |
JSON remains source of truth for engineers; spreadsheet is the human dashboard.
PowerShell variant — batch ffprobe table export
Windows facilitators without bash loops:
$rows = @()
Get-ChildItem *.mkv | Sort-Object Name | ForEach-Object {
$dur = ffprobe -v error -show_entries format=duration -of csv=p=0 $_.Name
$rows += [pscustomobject]@{ file=$_.Name; duration_sec=$dur; pass_o1=([double]$dur -gt 0) }
}
$rows | Export-Csv ffprobe_table.csv -NoTypeInformation
Attach ffprobe_table.csv to decision receipt as fragment_proof.
Silero VAD optional gate (when clips are huge)
From the 16-tool list, Silero VAD can mark speech regions before Whisper spend on menu idle fragments.
Optional gate V1: If more than 40% of a clip is non-speech, tag triage_priority: low in per-clip summary—still transcribe, but do not block standup on that fragment.
This does not change chosen_path; it changes summary ordering.
Reencode_concat debug checklist (second attempt discipline)
Before abandoning Path 1:
- Confirm
concat_list.txtuses sortednorm_*.wavonly. - Confirm no absolute paths with unescaped quotes on Windows.
- Try pcm_s16le encode, not copy.
- Compare merged duration to CSV sum.
- Listen to seconds 0–5 and last 5 for truncation.
Log each attempt in decision receipt merge_attempts[] array—auditors see discipline.
Per-clip manifest schema (minimal)
{
"schema": "per_clip_manifest_v1",
"build_id": "rc4",
"clips": [
{
"index": 1,
"source_mkv": "001_replay_2026-05-26_19-02.mkv",
"transcript": "transcripts/001.txt",
"summary": "summaries/001.md",
"whisper_model": "medium",
"status": "complete"
}
]
}
Batch summary scripts read this instead of guessing filenames.
Cloud path consent README paragraph
Add to playtest-vod/README.md:
## Cloud transcription
Cloud Whisper is **opt-in per session**. Facilitator must receive written approval in #playtest-ops with build_id and retention days. Default path when concat fails is **per_clip_local** unless producer selects cloud_chunked.
Link README version in consent_doc_version field.
Anti-patterns that recreate October triage pain
- Deleting failed merge outputs — keep logs for DTS errors.
- Running cloud on entire session zip without chunk plan—413 and policy risk.
- Transcribing before
build_idconfirmed — tag fragments wrong. - Mixing playtest_invite clips with fest_public in one folder—surface lies.
- Setting
whisper_batch_allowedin spreadsheet only — JSON must match for CI.
Integration with local Whisper pipeline gates T1–T6
After path selection, resume Whisper pipeline at the correct entry:
| chosen_path | Enter pipeline at |
|---|---|
| reencode_concat + post_path_concat_ok | T2 extract merged WAV |
| per_clip_local | T3 per-clip loop (custom) |
| cloud_chunked | T4 cloud stitch then T5 summary |
Gate T5 surface still applies—Playtest vs fest_public must match isolation playbook.
Weekly ops ritual (15 minutes)
Every Friday with Friday Block 5 energy:
- Count sessions with
concat_ok: false. - Count how many have
ffmpeg_concat_decision_receipt_v1.json. - If gap greater than zero, README or facilitator contract needs another sentence.
- Pick one failed session for 15-minute tree drill so new volunteers learn paths.
Engine and platform notes (merge failures are not engine bugs)
Godot, Unity, and Construct do not cause ffmpeg DTS errors—OBS fragment encoding does. Do not open engine tickets when this tree applies.
| Symptom location | Owner |
|---|---|
| OBS fragment encoding | Facilitator + this tree |
| Gameplay soft-lock | Engineering |
| Steam branch mismatch | BUILD_RECEIPT |
Extended FAQ — search phrasing
What does Non-monotonous DTS mean for playtest VOD concat
It means fragment timestamps cannot be stitched with stream copy. Re-encode concat (Path 1) or per-clip Whisper (Path 2) is expected—not a reason to skip receipts.
Is whisper_batch_allowed the same as concat_ok
No. You can have concat_ok: false and whisper_batch_allowed: true only when chosen_path verification completed (for example per-clip transcripts all exist).
Should facilitators decide cloud alone
No. Default per_clip_local when in doubt. Cloud requires producer approval documented in consent_doc_version.
How long should path selection take before standup
Target 15 minutes for D1–D6 with existing fragments. If you exceed 45 minutes without chosen_path, escalate to producer with receipt partial—do not silently upload.
Does Lesson 207 replace this blog post
No. Lesson 207 is the course milestone with BUILD_RECEIPT wiring; this article is the standup-facing strategy tree with scenarios, manifests, and facilitator contract language.
Copy-paste standup templates (three lanes)
reencode_concat: “Merge copy failed on build_id {id}; re-encoded concat passed D6; batch Whisper on merged WAV allowed; receipt filed.”
per_clip_local: “Merge failed; running per-clip Whisper on {n} fragments; whisper_batch_allowed true after manifest complete; no cloud.”
cloud_chunked: “Merge failed; producer approved cloud_chunked for {n} chunks; consent {version}; retention {days} days; receipts attached.”
Paste the matching sentence into #playtest-ops so producers do not re-ask which lane you chose.
Key takeaways
concat_ok: falsedemands a namedchosen_path—never silent batch Whisper.reencode_concatis first when fragments are healthy but copy-concat failed.per_clip_localpreserves privacy when merge is not worth tonight.cloud_chunkedis a time trade, not a default—check consent and 413 limits.whisper_batch_allowedfollows path verification (D6), not wishful thinking.- File
ffmpeg_concat_decision_receipt_v1.jsonbeside triage receipts for audit trails. - This article complements—not replaces—the concat evening and Whisper triage pipelines.
FAQ
Should I retry ffmpeg concat or skip straight to per-clip Whisper
Retry merge with encode (Path 1) when ffprobe shows audio on every fragment and the error is timestamp or codec related. Skip to per-clip when you have already failed two disciplined merge attempts or standup is in under an hour and local GPU is free.
Does per-clip local mean concat_ok should be true
No. Keep concat_ok: false on the failed merge attempt. Set chosen_path: per_clip_local and whisper_batch_allowed: true only after every clip has a transcript file listed in your manifest.
When is cloud API the right answer
When local GPU time is unavailable, consent allows cloud processing, and chunk sizes fit API limits—or when fragments are few and upload cost is negligible. It is wrong when Path 1 would succeed in one encode pass.
What is post_path_concat_ok
A field on the decision receipt meaning Path 1 recovery produced a verified merged WAV after an initial concat_ok: false. Do not overwrite the failed attempt; add recovery metadata for auditors.
How does this relate to playtest_vod_triage_receipt_v1.json
Triage receipt tracks session ingest and merge. Decision receipt tracks what you did when merge failed. Batch scripts should read both before setting whisper_batch_allowed.
Can I run cloud and local on the same session
Pick one path per session folder per night. Mixing paths without documentation recreates the “which transcript is canonical?” fights this tree prevents.
Next step
Tonight when concat fails:
- Set
concat_ok: false. - Run D1–D6.
- Write
ffmpeg_concat_decision_receipt_v1.json. - Only then run Whisper—merged, per-clip, or cloud—matching
chosen_path.
Your triage board stays honest. Your October fest self stays out of refund-driven fire drills.