Your colleague needs the captioned version for tomorrow's training session. You have a transcript called final, another called final-fixed, and a video whose audio changed after both were made. The slow part is no longer transcription. It is working out which result anyone can trust.
Build a media pipeline that answers that question before your colleague has to ask it. Continue the project you already chose: perhaps an onboarding library, a seminar series, short product demonstrations, or an accessible audio archive. The useful result is a repeatable route from permitted recordings to reviewed transcripts and captions, not a folder that proves you clicked a transcription button ten times.
This is Level 3 Builder: someone else will depend on what you built. They need to find the right output, understand its limits, and handle the next batch when you are away. The examples below use fictional lab and company recordings; borrow them if useful, but do not replace your project with them. A successful execution is not permission to publish.
1. Forty recordings, one fragile shortcut
Imagine forty seminar recordings waiting for transcripts and subtitles. Someone proposes opening each file, clicking transcribe, downloading the result, and renaming it by hand. The first files look easy. By file twelve, two outputs have the same name, an invented project name has three spellings, and no reliable link connects a subtitle to its recording. One transcript has already reached the shared presentation folder without review.
The optional company example has the same problem in a different form. Forty fictional support-call recordings need internal transcripts and summaries. A fast batch can repeat the same recognition error forty times, retain more conversation than the task needs, or place a polished but wrong account reference in every summary. Speech-to-text creates candidate text; it does not establish who spoke, what they intended, or whether the result is accurate.
The answer is not slower clicking. It is a visible pipeline:
permitted input folder
-> manifest and preflight
-> bounded processing
-> draft transcript and subtitle
-> review_required queue
-> human correction and approval
-> approved internal output
-> colleague uses the reviewed version
failed check -> held item + reason -> repair that stage -> fresh review
Every item keeps the same stable ID from input to output. Nothing in the processing branch can publish, send, overwrite the source, or approve its own result. The final arrow matters as much as the first: an immaculate batch that your colleague cannot open is not a useful pipeline.
In Consistent assets for your company or lab, the reusable kit keeps a set of assets coherent. Here you make production repeatable across inputs, revisions, and handoffs. Automations with AI in the middle supplies the general pattern of validation and human queues; this book adds media-specific identity, timing, sound, language, and access checks. Operating models on your own hardware belongs to Self-hosted media models. You do not need a GPU project to solve a caption handoff.
2. After this you can
- Build a repeatable recording-to-transcript-and-caption workflow for your chosen recurring result.
- Preserve the mapping between each source recording, draft, corrected output, and review decision.
- Use a glossary to surface likely errors in names, jargon, identifiers, numbers, and abbreviations.
- Estimate duration, calls, storage, and review effort before starting a larger batch.
- Hand over usable outputs and a recovery route, with optional summaries or dubbing only when the project needs them.
3. Start with the recipient and the workbench
Open your approved n8n test workspace in the browser. The workflow canvas is where you connect processing steps; each node's input and output views show the data moving through it. The execution history is where you later inspect a failed item. Your media folder or approved storage location holds the files. These are different places: a path on your laptop is not automatically a path the n8n server can read.
Use an existing approved workflow runner if your team already has one. The examples name n8n nodes so the actions are concrete, not because moving platforms is part of the outcome. You need an approved speech-to-text operation that can return text and timestamped segments, access to its credential through the platform's credential store, and a media player or caption editor for listening and timing corrections. Whisper or another approved model can supply transcription; the model name alone does not tell you where data goes.
Before creating nodes, write a short opening in your project's existing handoff document, or create run-record.md if it has none:
Each time new recordings arrive, this workflow prepares [transcripts/captions] for [colleague and task]. They collect reviewed versions from [internal location]. [Person] reviews language and meaning. If an item is held, [person] decides whether to correct it or leave it out. Public release is outside this workflow.
Replace the brackets with your own decisions. A training coordinator might need captions and a searchable transcript every Friday. An archive editor might need a monthly accessible collection. A summary is optional; a dub is optional; lip-sync is usually unnecessary. Choose the smallest output set that removes a real recurring burden.
Use public, synthetic, or explicitly approved media. Public availability is not itself permission to upload a recording to another processor. Before connecting the upload step, confirm purpose, source rights, speaker notice or consent where required, approved service and region, access, retention, and deletion. Keep credentials out of node text, manifests, filenames, screenshots, and workflow exports. Never test with unapproved participant speech, customer calls, background conversations, account details, or unpublished findings. If your project's recordings are not cleared, test its workflow with synthetic stand-ins and describe it as a rehearsal, not a delivered production batch.
Leave schedules and external destinations disconnected. Review time grows with the material; neither a reading estimate nor a one-hour workshop is a promise that you can listen to forty hours of recordings in an afternoon.
4. The idea in one page
Treat the manifest as the batch contract
A folder tells you what files exist. A manifest tells the workflow what each file means and how its outputs must be named. It can be an existing media-library table, a sheet, or a CSV. Do not create a second tracker if the same fields already live in your project. Create or confirm the records before calling a model.
| Field | Purpose | Blocking rule |
|---|---|---|
item_id | Stable identity such as LAB-SYN-001 | Unique, present, and unchanged through every step |
source_file | Exact input filename or approved storage reference | Must resolve to one permitted file |
source_sha256 | Fingerprint of the bytes processed | Must match the preflight file |
duration_seconds | Cost and timestamp boundary | Must be positive and match inspected media |
language | Declared source language | Unknown or mixed language needs a suitable review route |
expected_terms | Glossary terms expected in this item | Missing terms create flags, never silent replacements |
permission_basis | Why this source may be processed | Missing or unclear means hold before upload |
output_base | Base for transcript and caption names | Unique and derived from item_id, not model text |
If these records come from outside your trusted workspace, also validate file references and output names. Do not let a row name an arbitrary URL, another project's storage, or a path containing ../. Limit access to the approved input location and use simple safe IDs for filenames.
Hashing does not prove truth or rights. It proves that later records refer to the same bytes. permission_basis need not contain private legal text; use a safe reference such as synthetic-fixture-v1 or an approved internal permission-record ID. A reviewer should be able to resolve that reference without opening it to everyone who receives the captions.
Give each batch a run_id as well as item IDs. Keep the item identity stable while distinguishing attempts and source revisions. An output path such as runs/2026-09-09-a/drafts/LAB-SYN-001.captions.vtt lets you retain an earlier reviewed version rather than overwrite it with a fresh draft.
Use states that cannot be confused with publication:
received -> preflight_passed -> processing -> review_required
-> processing_failed
review_required -> approved | held
processing_failed records a technical failure; held means an item cannot progress until a person resolves the reason. Store technical failures in the hold queue with their failed stage. Only a named person can approve corrected outputs. In the simplest implementation, automation has write access to drafts and holds only; a reviewer copies the selected files to the restricted approved location. A string saying approved is not an access boundary.
Preflight before the expensive step
Reject the batch before transcription if an ID is duplicated, a file is missing, a hash differs, permission is unclear, a duration is implausible, or an output name would collide. Perform cross-item checks on the whole input set, not after splitting it into one-item calls. Then run a two-item pilot, choosing one ordinary item and one with a likely difficulty such as jargon or uneven sound. Record actual processed minutes, elapsed time, provider calls, output size, and human review minutes.
Estimate the full batch from measured values rather than a remembered price:
media minutes = sum(duration_seconds) / 60
maximum calls = item count * sum(each selected stage's call allowance, including retries)
estimated transcription cost = media minutes * current approved per-minute rate
estimated review effort = media minutes * measured review factor
estimated storage = inputs + drafts + approved outputs + retained evidence
The cost equation applies only when the service bills by media minute. Check the current unit, minimum charge, file limit, supported format, timestamp behavior, retention, and rate limit. Price other billing units separately. Dubbing can add translation, text-to-speech, video processing, and review charges; lip-sync can add GPU or service time. Keep those lines separate so a cheap transcription estimate does not conceal an expensive downstream branch.
A one-item loop limits how many items travel together. It does not, by itself, enforce a requests-per-minute allowance. Use the approved rate-limit handling and finite retry policy for the selected service, and stop if you cannot establish a safe bound.
A glossary is a detector, not an autocorrect list
Recognition often fails on proper nouns, abbreviations, product identifiers, unfamiliar accents, overlapping speech, and poor audio. A versioned glossary helps in three ways:
- Supply it as model context only if the approved service supports that feature and the terms themselves may leave your environment.
- Compare the draft with item-specific expected terms and raise a review flag when one is absent.
- Give the reviewer the preferred spelling, pronunciation note, and source of authority.
Do not blindly replace similar words. If the audio says Aster ten, a global replacement to Aster-9 creates a false result. Preserve the raw draft, flag the mismatch, and let a reviewer listen to the relevant timestamp. The simple detector below finds missing strings; it does not locate them in audio or understand spelling variants. fourteen and 14 can describe the same quantity. Treat that flag as an invitation to listen, not an instruction to force a preferred answer.
5. Build the pipeline for your project
The fictional Aster-9 lab and Harborlight Help company are optional worked examples. Each imagines forty recordings; the ten short scripts below provide a reusable test set. Your own recurring batch determines the appropriate size and outputs. Do not add irrelevant items merely to reach ten.
| Boundary | Optional lab framing | Optional company framing |
|---|---|---|
| Source | Synthetic seminar clips about an invented training workflow | Synthetic support-call clips about an invented service |
| Allowed result | Internal transcript and caption drafts for a practice archive | Restricted transcript, caption, and optional summary drafts for support training |
| Reviewer | Someone familiar with the invented seminar terms | Someone familiar with the invented support identifiers |
| High-risk errors | Procedure words, quantities, project names, and negation | Ticket IDs, amounts, dates, promises, names, and negation |
| Forbidden action | No manuscript insertion, scientific claim, or public course upload | No customer contact, account change, case update, or public sharing |
All speech in these examples is synthetic. No real person, organisation, experiment, customer, product, account, or finding is represented. The reviewer descriptions are suggested responsibilities, not claims that such teams or reviews exist.
Keep each product and its provenance distinct
A transcript is readable text. Captions are synchronized access text checked against final audio and video, including meaningful non-speech sounds. A translation needs a competent target-language reviewer. A dub must be checked against the approved translation. Lip-sync additionally needs portrayal permission, disclosure, and fresh visual review. None may approve itself.
For each item retain source ID and hash, permission reference, input name, processing date, run ID, workflow and glossary versions, service route and model where exposed, raw result, edits, output hashes, reviewer, date, target internal channel, disclosure, and decision. Keep these records only for the approved retention period, with appropriate access. Provenance metadata supplements this run record; it does not prove permission, consent, or accuracy.
Step 1: prepare sources and output locations
In your file manager or approved project storage, create a new run folder rather than replacing the previous run. Reuse the project's existing equivalent structure if it has one. The example calls the project root media-project/:
media-project/
incoming/ # permitted sources; processing never overwrites them
manifest.csv # or the existing equivalent media table
glossary.csv # preferred terms and review notes
runs/<run_id>/
drafts/ # raw response, transcript, captions, optional summaries
review/ # item decisions and corrections
approved/ # reviewer-only writes; colleague reads selected outputs
held/ # item, failed stage, reason and recovery decision
run-record.md # estimate, workflow version, execution and handoff
Name outputs from item_id: LAB-SYN-001.transcript.txt, LAB-SYN-001.captions.vtt, and LAB-SYN-001.provenance.json. The optional company summary is CO-SYN-001.summary.json. Store the original provider response separately from edited text. Model wording, upload order, or an original display name is not a safe source of output identity.
If you need fixtures, record any useful selection of these invented scripts with an approved offline recorder, or render them with an approved general synthetic voice. Use natural pacing and measure the resulting duration; these short sentences do not necessarily take 10-20 seconds. For TTS input, record synthetic voice; no voice clone in the source record and disclosure.
| ID | Synthetic script | Expected terms |
|---|---|---|
LAB-SYN-001 | Welcome to the fictional Aster-9 seminar. | Aster-9 |
LAB-SYN-002 | The Quenby tray contains fourteen blue cards. | Quenby; fourteen |
LAB-SYN-003 | Route QZ-4 is a training label, not a result. | QZ-4; not |
LAB-SYN-004 | The Cedar gate opens after nineteen seconds. | Cedar; nineteen |
LAB-SYN-005 | No sample was collected in this synthetic exercise. | No sample; synthetic |
LAB-SYN-006 | Review Delta-V before the practice handoff. | Delta-V; review |
LAB-SYN-007 | The invented archive has three empty shelves. | three; empty |
LAB-SYN-008 | Aster-9 and Quenby are fictional project terms. | Aster-9; Quenby |
LAB-SYN-009 | Do not publish the draft caption. | not publish; draft |
LAB-SYN-010 | Human approval is required before output. | Human approval; required |
For the company framing, use CO-SYN-001 onward and parallel invented terms such as Harborlight, ticket HZ-14, no delivery date, seventeen credits, and review required. Fabricated email addresses or phone numbers add nothing to this test; omit them.
In a text editor, create manifest.csv only if you are using the file-based example. Add one row per actual media file:
item_id,source_file,source_sha256,duration_seconds,language,expected_terms,permission_basis,output_base
LAB-SYN-001,LAB-SYN-001.wav,REPLACE_WITH_ACTUAL_SHA256,12,en,"Aster-9",synthetic-fixture-v1,LAB-SYN-001
LAB-SYN-002,LAB-SYN-002.wav,REPLACE_WITH_ACTUAL_SHA256,14,en,"Quenby|fourteen",synthetic-fixture-v1,LAB-SYN-002
The durations and hashes are placeholders, not measurements. Replace them using the actual files and your approved checksum tool. Read duration in the media player's file information, then verify it against the timeline. The code below expects | between expected terms; the semicolons in the reading table are only visual separators. Use a CSV-aware import, not a split on commas, because quoted values can contain punctuation.
Create glossary.csv, or add these columns to the existing terminology resource:
term,preferred_form,pronunciation_note,authority,version
aster nine,Aster-9,"as-ter nine",synthetic fixture script,GLO-1
quenby,Quenby,"kwen-bee",synthetic fixture script,GLO-1
q z four,QZ-4,"cue zee four",synthetic fixture script,GLO-1
delta v,Delta-V,"delta vee",synthetic fixture script,GLO-1
Keep the glossary short enough to be useful during listening. A hundred unrelated terms can conceal the four that matter to this week's output.
Step 2: connect a manual workflow
On the n8n canvas, create a new workflow named Media batch - review gate. Add Manual Trigger. Do not attach a schedule or public webhook. Use your approved storage connector to read the manifest records and source files. A disk reader refers to the machine running n8n, not automatically your computer; a cloud connector needs the approved storage reference and credential. The connector and transcription operation depend on your deployment, so record their exact names and input fields in run-record.md rather than guessing a universal endpoint.
Build and inspect this route in order:
- Read records. Execute the manifest-reading step and open its output view. Expect one structured item per source, with the manifest fields intact, not one unparsed CSV string. If the connector returns a wrapper containing an array, split that array into items before continuing.
- Preflight the whole set. Use an existing validator or a Code node in Run Once for All Items mode to check the manifest contract. Check file bytes and metadata through the approved storage route. A duplicate or invalid row blocks the batch before any paid call. Preserve a safe reason in the hold record.
- Add Loop Over Items. Set Batch Size to
1for the first build and leave Reset off. Connect its loop output to the per-record processing path. n8n normally processes lists of items without a loop; here the explicit loop makes sequential media handling and inspection clear. - Read the current media file. Check the node's binary output as well as its JSON fields. The current file must still be associated with
item_id,source_sha256, duration, and run ID. A filename alone is not a verified association. - Transcribe. In the approved transcription node, select the stored credential and the actual binary input property. Request timestamped segments using that operation's documented setting. Map only necessary audio and permitted context. If the route cannot supply timestamps, it can support a transcript branch, but it has not supplied the caption branch taught here.
- Map the response. Put returned text at
transcription.textand segments attranscription.segments. Each segment needs numericstartandendin seconds and stringtext. Restore untouched manifest fields from the current loop item through the runner's item association, not from the model and not by zipping arrays after failures. Inspect one output item before connecting the formatter. - Validate and format. Add the Code node in Step 3, followed by an If node testing whether
validation_okis booleantrue. Its true branch writes drafts and areview_requiredrecord. Its false branch writes a hold record; it must not write a caption file whose value isnull. - Finish the iteration. Connect the successful draft-record path and the handled-failure record path back to Loop Over Items. Each path must return one completion item, including held items, so the next item can proceed. Use the loop's done output for the internal batch summary only, not for another transcription call.
The Code node transforms data; it is not the place to make filesystem or HTTP calls. Use the appropriate storage or HTTP node for those operations. If a technical error stops a node before it produces output, the later If node cannot catch it. Configure your runner's explicit error path to preserve the source ID and write the hold record. If that cannot be established, leave the execution stopped and resolve it manually rather than configuring errors to disappear.
A hold record needs item ID, run ID, source hash, stage, safe error category, attempt count, and time. Do not dump credentials or unrestricted provider responses into it. Both ordinary and error branches remain internal; neither has permission to write approved output.
If the provider is unavailable or rate-limited, retry only under a finite approved policy. A retry reuses the same item ID, source hash, and run ID, with its attempt recorded. Do not retry a completed transcription because caption writing failed. Resume from the saved result or hold the item. This is how the pipeline saves work rather than merely repeats it faster.
Step 3: validate and create caption drafts
Open the formatter Code node, select JavaScript and Run Once for Each Item, and replace its sample code with the following. Its input is the mapped item from Step 2, not an arbitrary raw provider response.
function validateAndFormat(source) {
const text = source.transcription?.text;
const segments = source.transcription?.segments;
const duration = Number(source.duration_seconds);
const errors = [];
if (typeof source.item_id !== 'string' || !source.item_id.trim()) errors.push('missing item_id');
if (typeof text !== 'string' || !text.trim()) errors.push('empty transcript');
if (!Number.isFinite(duration) || duration <= 0) errors.push('invalid duration_seconds');
if (!Array.isArray(segments) || segments.length === 0) errors.push('missing segments');
const safeSegments = Array.isArray(segments) ? segments : [];
let previousEnd = 0;
for (const [index, segment] of safeSegments.entries()) {
if (!segment || typeof segment !== 'object' ||
!Number.isFinite(segment.start) || !Number.isFinite(segment.end) ||
typeof segment.text !== 'string' || !segment.text.trim()) {
errors.push(`invalid segment ${index}`);
continue;
}
if (segment.start < 0 || segment.start < previousEnd || segment.end <= segment.start) {
errors.push(`non-monotonic segment ${index}`);
}
if (Number.isFinite(duration) && segment.end > duration) {
errors.push(`segment ${index} exceeds source duration`);
}
const startMs = Math.round(segment.start * 1000);
const endMs = Math.round(segment.end * 1000);
if (!Number.isSafeInteger(startMs) || !Number.isSafeInteger(endMs) || endMs <= startMs) {
errors.push(`invalid millisecond timing ${index}`);
}
previousEnd = segment.end;
}
const expected = String(source.expected_terms || '')
.split('|').map((term) => term.trim()).filter(Boolean);
const missingTerms = expected.filter((term) =>
!String(text || '').toLocaleLowerCase('en').includes(term.toLocaleLowerCase('en'))
);
const vttTime = (seconds) => {
const total = Math.round(seconds * 1000);
const hours = String(Math.floor(total / 3600000)).padStart(2, '0');
const minutes = String(Math.floor(total / 60000) % 60).padStart(2, '0');
const secs = String(Math.floor(total / 1000) % 60).padStart(2, '0');
const millis = String(total % 1000).padStart(3, '0');
return `${hours}:${minutes}:${secs}.${millis}`;
};
const vttText = (value) => value.trim().replace(/\s+/g, ' ')
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
const captionsVtt = errors.length ? null : `WEBVTT\n\n${safeSegments.map(
(segment, index) => `source-segment-${index}\n${vttTime(segment.start)} --> ${vttTime(segment.end)}\n${vttText(segment.text)}`
).join('\n\n')}\n`;
return {
...source,
validation_ok: errors.length === 0,
validation_errors: errors,
review_flags: missingTerms.map((term) => `check expected term: ${term}`),
captions_vtt: captionsVtt,
status: errors.length === 0 ? 'review_required' : 'held',
};
}
return {json: validateAndFormat($json)};
This validator detects structure and obvious timing errors. It cannot decide that recognized words are faithful to audio, confirm permission, or verify a source hash; those are separate checks. A perfect structural result still enters human review. A missing expected term is a flag, not proof that the model is wrong: the script, audio, or manifest may need correction.
The formatter escapes plain transcript text so an ampersand, angle bracket, or blank line does not accidentally become caption syntax. It also rejects a tiny segment whose start and end become the same millisecond when formatted. It deliberately holds overlapping segments, although WebVTT can represent overlapping cues: this simple single-stream pipeline needs a reviewer to decide how simultaneous speech should read. Do not silently delete a speaker to make the validator pass.
The source duration must be measured accurately. If a provider's final timestamp runs beyond it, hold and inspect the media rather than automatically trimming a possibly meaningful word. The raw provider response stays unchanged. Save the resulting caption string as a UTF-8 .vtt file, not as JSON containing an escaped string.
For {start: 0, end: 1.25, text: "Welcome to Aster-9."}, the exact caption output is:
WEBVTT
source-segment-0
00:00:00.000 --> 00:00:01.250
Welcome to Aster-9.
Before connecting a paid service, make a temporary copy of this Code node. Keep the function and replace only its final return line with this self-check. Execute it in the test workflow with one synthetic input item. It throws on a failed assertion and otherwise returns checks: passed. Remove the test copy from the processing path afterward; the real formatter retains its original final line.
const fixture = {
item_id: 'LAB-SYN-001', duration_seconds: 2, expected_terms: 'Aster-9',
transcription: {text: 'Welcome to Aster-9.', segments: [
{start: 0, end: 1.25, text: 'Welcome to Aster-9.'},
]},
};
const check = (condition) => { if (!condition) throw new Error('caption check failed'); };
const valid = validateAndFormat(fixture);
check(valid.status === 'review_required' && valid.review_flags.length === 0);
check(valid.captions_vtt === 'WEBVTT\n\nsource-segment-0\n00:00:00.000 --> 00:00:01.250\nWelcome to Aster-9.\n');
const withSegments = (segments) => ({...fixture, transcription: {...fixture.transcription, segments}});
for (const segments of [
[{start: NaN, end: 1, text: 'x'}], [{start: 0, end: NaN, text: 'x'}],
{}, [null], [{start: 0, end: 0.0001, text: 'x'}],
[{start: 0, end: 3, text: 'x'}],
[{start: 0, end: 1, text: 'x'}, {start: 0.5, end: 2, text: 'y'}],
]) {
const result = validateAndFormat(withSegments(segments));
check(result.status === 'held' && result.captions_vtt === null);
}
check(validateAndFormat({...fixture, duration_seconds: 'malformed'}).status === 'held');
check(validateAndFormat({...fixture, expected_terms: 'Quenby'}).review_flags.length === 1);
const escaped = validateAndFormat(withSegments([{start: 0, end: 1, text: 'A & <B>\n\n--> C'}]));
check(escaped.captions_vtt.includes('A & <B> --> C'));
return {json: {checks: 'passed'}};
Keep model segments as source-linked drafts, not automatically readable captions. A reviewer may split or combine cues, while retaining the relationship to the original segment indexes in the edit record. If the player fails to load the file, inspect the saved file itself before paying for another transcription.
Optional branch: a source-linked summary
Add this branch only if your recipient needs a summary. The company framing illustrates one: after transcript validation, an approved text operation proposes a restricted summary from the permitted transcript. Require exactly this shape:
{
"item_id": "CO-SYN-001",
"summary": "Fictional Harborlight request mentions ticket HZ-14 and gives no delivery date.",
"evidence_segments": [0],
"status": "review_required"
}
Use this deterministic validator before the human evidence check:
function summaryErrors(draft, itemId, segmentCount) {
const keys = ['evidence_segments', 'item_id', 'status', 'summary'];
if (!draft || typeof draft !== 'object' ||
JSON.stringify(Object.keys(draft).sort()) !== JSON.stringify(keys)) {
return ['summary keys do not match schema'];
}
const errors = [];
if (draft.item_id !== itemId) errors.push('summary item_id mismatch');
if (typeof draft.summary !== 'string' || !draft.summary.trim()) errors.push('empty summary');
if (draft.status !== 'review_required') errors.push('invalid summary status');
if (!Array.isArray(draft.evidence_segments) || !draft.evidence_segments.length ||
!draft.evidence_segments.every((index) => Number.isInteger(index) && index >= 0 && index < segmentCount)) {
errors.push('invalid evidence_segments');
}
return errors;
}
This block defines a function, not a complete n8n node. In a per-item Code node, map the parsed summary object to summary_draft, keep the original validated transcription.segments, and call summaryErrors($json.summary_draft, $json.item_id, $json.transcription.segments.length). Return the errors with the item and branch to held when any exist. Parsing failures also go to held; do not substitute an empty object and pretend the model returned it.
Extra keys, a mismatched ID, empty text, or nonexistent segment indexes go to held without a model retry. Then the reviewer checks every name, number, date, promise, and action against the cited segments and original audio. Test a supported summary and a draft that invents a delivery date. The latter must be held by the reviewer even when its schema passes. A valid index points to evidence to inspect; it does not prove the claim. Keep segment indexes stable, or invalidate summaries when their source transcript changes. The summary cannot update a case or contact a customer.
Step 4: review the actual media, not the success badge
In the run's review/ location, create one decision record per item or use the equivalent fields in your existing review tool:
item_id | run_id | source_sha256 | transcript_file | captions_file | summary_file
glossary_version | flags | transcript_edits | caption_edits | summary_edits
accessibility_check | disclosure | target_internal_channel
reviewer | reviewed_at | decision | approved_output_sha256
For multiple approved files, store each filename with its hash rather than one unexplained hash for the whole item. Mark unused summary fields as not applicable. Do not turn the template into a requirement to generate a summary.
Open the recording in the media player beside the transcript. Listen from beginning to end while reading, paying particular attention to names, identifiers, quantities, negation, omissions, and flagged terms. A workflow can transcribe not approved as approved without producing any structural error. This is why a green node badge cannot authorize use.
Next open the draft captions in your approved caption editor or player's supported subtitle-loading interface. Select the matching source version, not another file with the same display title. Correct timing, phrase breaks, punctuation, speaker labels where needed, and meaningful sound cues. Check the final encoded media after any edit that changes its sound or duration. A caption perfectly aligned to yesterday's audio is still wrong for today's video.
Accessibility is more than visible words. Watch muted: can someone understand speech and important non-speech sounds? Listen without watching: is essential visual information, such as an onscreen instruction or chart, missing from the audio? Provide the necessary description through the project's appropriate audio or descriptive-transcript route. Check the player controls, keyboard operation, caption readability, contrast, likely crop, and space obscured by controls. A short listing description does not replace a description of essential visual content.
Require a reviewer to choose approved or held, add their identity and time, and hash the corrected outputs. A blank reviewer, bulk approval without item evidence, or model-generated approval fails the gate. Keep originals and prior reviewed versions intact. If the transcript changes, revisit dependent captions and summaries; if final audio changes, review timing and meaning again.
For synthetic fixtures, use disclosure such as Synthetic exercise audio transcribed with AI; transcript and captions reviewed by [actual reviewer or role]. If a general synthetic voice created the input, say so. For your project, describe the assistance that actually occurred, without claiming review that has not happened. Disclosure records provenance; it grants neither rights nor accuracy.
The reviewer now copies only the corrected, approved files into this run's restricted approved/ location and records their filenames and hashes. The workflow credential cannot perform that copy. Check the colleague's read access to the intended outputs without widening access to raw speech, private permission records, or provider logs.
Step 5: attach captions; add dubbing only if useful
For a video project, attach the reviewed caption file as a selectable track where the internal target player supports it. Keep the transcript available alongside the video. Record the player and attachment action your team actually uses in the handoff; its labels are platform-specific. Test that the selected caption track belongs to this final media version, can be enabled, and remains usable with sound on and off. For audio-only projects, deliver the reviewed transcript and any relevant sound descriptions rather than manufacturing a video to satisfy the example.
If your recipient needs another language, begin dubbing with one permitted clip and the approved source-language transcript. Create a draft translation and obtain a separate language review. Then synthesize with an approved general voice. Do not clone a seminar speaker or caller. Compare the final dub word for word with the approved translation, inspect timing and mix, provide reviewed captions in the delivered language, retain the source-language transcript, and update disclosure and provenance. If no competent language reviewer is available, leave the dub held; fluency is not evidence of fidelity.
Do not add lip-sync merely because a tool offers it. If dubbed audio and captions meet the need, stop there. A lip-synced version is a new manipulated visual asset requiring a new portrayal-permission decision, frame review, disclosure, output hash, and approval. It stays review_required until those checks are complete.
Step 6: size the next batch and hand it over
Keep the two-item pilot separate from the accepted project run. Suppose a pilot contains 0.5 media minutes, takes 1 minute of service processing, and takes 2 minutes of human transcript and caption review. Those invented figures illustrate a review factor of four minutes per media minute, not measured tool performance. Use your own observations, and allow extra time for difficult audio and corrections.
For the optional forty-item example, scale with the actual total duration of all forty files, not 40 x average when the manifest already contains exact durations. For your project, estimate its next real batch. Add translation, dubbing, and rework separately. Compare the result with the agreed budget and delivery date before submitting it. If either limit is exceeded, reduce scope, split the batch, or stop. Discovering the cost boundary after item thirty-nine is not a useful form of measurement.
Finish run-record.md with the instructions your colleague will need on that next occasion: the workflow's exact name and version, input location, required fields, approved output location, who reviews, who can stop the run, how holds are resolved, and where the last known-good version remains. Record counts for received, reviewed, approved, and held items so an omitted file cannot disappear behind a general statement that the run succeeded.
Now ask the intended colleague to do their actual task using the reviewed output. Can they find the right clip, enable its captions or open its transcript, and use it for the planned training, archive, or briefing? Ask them to identify a held item and explain what they would do next. Observe that handoff if possible. If no colleague is available, perform the read-through yourself and label the colleague trial as pending; do not invent acceptance.
Recovery should be boring. If caption writing failed after transcription succeeded, open that item's execution, confirm the run ID and source hash, and restart from the saved validated result into a new draft attempt. If the source bytes changed, record a new source revision and reprocess the dependent outputs. If an incorrect approved caption was already shared internally, tell the recipient which version to stop using, retain the previous files, and point them to a newly reviewed correction. Never quietly replace a shared file while keeping the old approval record.
6. What goes wrong
There is no glossary
Symptom: Aster-9, Quenby, and QZ-4 become different plausible words across the batch.
Fix: version a small authoritative glossary, associate expected terms with item IDs, and inspect likely misses against the audio. Never global-replace without listening.
Output loses its source
Symptom: files are named transcript-final-7.txt, and nobody can establish which recording or run produced them.
Fix: assign the ID before processing, retain the source hash, derive output bases from that ID, and reject duplicate IDs or names during preflight. Preserve the run folder so a later attempt cannot disguise an earlier result.
The batch approves its own drafts
Symptom: a successful transcription node writes captions into the colleague's approved collection.
Fix: end processing in review_required. Use reviewer-only write access for approved files. A condition inspecting a model-produced approved field is not a human review gate. Keep public publication outside this workflow.
A high score replaces listening
Symptom: outputs with provider confidence or no glossary flags are approved without opening the media.
Fix: treat scores and flags as review prioritization only. Listen to each item being approved and check terms, numbers, negation, omissions, timing, and meaning. Fewer approved items with honest holds are more useful than a fully green but unreliable folder.
Retry behavior is decided after the run
Symptom: a timeout reruns completed transcription, or a result arriving late is attached to the next item.
Fix: record completion by stage, use stable item and run IDs, compare source revisions, and reject results from an obsolete attempt. Never infer identity from execution order. Keep retry and call ceilings within the approved budget.
Raw transcript becomes captions
Symptom: cues are too long, speaker changes disappear, meaningful sounds are missing, and timing follows model segments rather than readable phrases.
Fix: use timestamps only as a draft. Edit captions against final encoded audio and check reading, synchronization, speakers, sound cues, contrast, safe areas, and muted playback. Preserve source-segment relationships when splitting or combining cues.
Dubbing hides a changed claim
Symptom: translation changes not approved to approved, while fluent synthetic speech makes the error sound authoritative.
Fix: approve the source transcript first, review translation separately, compare the final dub with approved text, and repeat caption, disclosure, provenance, and release checks.
Access and retention copy the whole source forever
Symptom: raw calls, transcripts, temporary audio, and provider responses remain in broad execution logs with no deletion date.
Fix: minimize mapped fields and logs, restrict the packet, apply the approved retention schedule, verify provider and workflow deletion paths, and retain only evidence justified by the stated purpose. A colleague needing captions does not automatically need the raw conversation.
The recipient cannot use the result
Symptom: the workflow finished, but the colleague sees an access error, an old video, or no caption track.
Fix: verify the intended internal link with the recipient's access, identify the final media and caption versions together, and test the actual player. Do not solve an access error by making the entire working folder public.
7. Build your next useful batch
Continue your chosen project rather than starting an unrelated demonstration. Select a representative slice of its recurring work and agree what the recipient will use. Two short pilot items can establish the route; the accepted batch should be large enough to exercise the real handoff, not an arbitrary quota. The ten synthetic scripts remain available for a fuller rehearsal or for cases where real media is not yet cleared.
First make the input contract true: permitted sources, stable IDs, actual durations and hashes, a relevant glossary, and distinct run locations. Build the manual route and inspect its item mapping before connecting paid processing. Run the caption self-check, then the two-item pilot. Use its observed cost and review effort to decide how much of the batch to process.
In a synthetic test copy, duplicate one output base and confirm preflight stops before transcription. Repair it. Introduce one missing expected term and one malformed segment result; confirm the former remains visible for review and the latter reaches a hold without losing its ID. These checks support the media project; they are not the deliverable. Do not corrupt a real source recording to stage a failure.
Process the selected batch, review each item you intend to approve, and leave unresolved items held. Deliver only corrected outputs through the reviewer-controlled location. Demonstrate one stage-specific recovery without overwriting the source or a previous approved version. Finally, have your colleague use the output and read the next-batch instructions. Fix the actual obstacle they encounter, whether it is a broken caption, an inaccessible link, or an unclear hold reason.
8. Exit check
Deliver one reusable media workflow and its reviewed project batch. The useful artifact is the media your colleague needs, together with the source mapping and handoff that make it dependable. An existing project folder, archive entry, or internal collection can hold it; no new standalone tracker is required.
It passes when the recipient can find and use the reviewed transcripts and appropriate accessible media outputs; each output maps to its source ID, source hash, run, and human decision; and an unreviewed item cannot enter the approved location through the processing credential. Include the workflow version or safe export, relevant glossary, pilot-based next-batch estimate, item decisions, necessary disclosure and provenance, a demonstrated hold and recovery, and instructions for the next operator. Keep raw speech and restricted evidence out of the recipient's view unless they need and are permitted to access them.
State the boundary honestly. Synthetic rehearsal is not proof of production readiness. A self-read is not a colleague trial. Generated captions are not reviewed captions. If a required permission, language reviewer, player check, or recipient acceptance is missing, identify what remains held or pending instead of marking the whole project passed.
It fails if sources are unapproved, identity mapping is missing, credentials leak, the model approves output, captions refer to the wrong audio version, or uncertain rights, consent, privacy, translation, identity, or factual issues are marked resolved. No public release is required or authorized by this exit check. Correct the same project and preserve the version history rather than manufacturing a cleaner-looking second submission.
9. Rule to remember
Batch the work, not the judgement.
A good pipeline makes the next colleague's job easier, including the day one item goes wrong.
10. Further reading & tools
- Consistent assets for your company or lab: reusable production kits, source rights, and coherent asset sets.
- Automations with AI in the middle: validation, bounded retries, and human queues.
- Self-hosted media models: the separate decision to operate media models on controlled hardware.
- n8n Loop Over Items (opens in a new tab): batch size, loop and done outputs, and Reset behavior. Freshly checked for this revision.
- n8n Code node (opens in a new tab): per-item and all-item modes, JavaScript, and the boundary between code, file operations, and HTTP nodes. Freshly checked for this revision.
- W3C Making Audio and Video Media Accessible (opens in a new tab): captions, transcripts, essential visual descriptions, and accessible players. Freshly checked for this revision.
- MDN WebVTT format (opens in a new tab): cue timing, text escaping, and blank-line rules. Freshly checked for the formatter revision.
- OpenAI Whisper repository (opens in a new tab): inherited primary reference for transcription. Verify the exact deployment, formats, license, compute, and data route you use.
- n8n looping guidance (opens in a new tab): inherited background on normal item processing and explicit iteration.
- ElevenLabs dubbing documentation (opens in a new tab): inherited optional provider reference; present terms, consent controls, languages, retention, and review behavior were not freshly verified here.
- LatentSync repository (opens in a new tab): inherited optional lip-sync reference. Model availability does not establish identity permission, output rights, fitness, or approval.
- C2PA specifications (opens in a new tab): inherited version-neutral provenance reference. Record the version actually used; signed provenance is not proof of truth or consent.
- Tools index: compare current tools after defining the input permission, data route, batch contract, budget, and review boundary.
The published source also drew on the legacy curriculum chapters Realtime voice agents, Voice: text to speech and speech to text, AI video generation, n8n, and AI avatars. Their inherited contributions are retained here in the separation of candidate recognition, human decisions, accessibility, retention, portrayal, and release. These legacy references are attribution, not a required detour or freshly checked tool instructions.