{ "pack_id": "voice-narration", "title": "Voice narration", "version": "0.1.0", "summary": "A collection of nouns and verbs for narration with a text-to-speech voice: a pronunciation lexicon applied before synthesis and confirmed by ear, seeded takes logged with the exact text the model received, loudness, peak, noise-floor and pace measurements with a round-trip transcript check, takes chosen by a rule fixed in advance, lines placed on the beats they must fit, a person's listening check, and an AI-voice label wherever the voice plays.", "dependencies": [ "video-motion" ], "boundaries": [ "Stock voices of a licensed model only; never a clone or imitation of a real person's voice.", "Every generated line is labeled as an AI voice and never presented as a person.", "Synthesis and measurement run on the worker's own machine from pinned files; this pack grants no spending.", "Importing the pack writes nothing and enqueues nothing." ], "nouns": [ { "slug": "pack", "category": "noun.skill_pack", "role": "pack", "title": "A collection of nouns and verbs for narration with a text-to-speech voice, labeled as AI", "value": { "lets_workers": "Narrate a script with a licensed text-to-speech model's stock voice: a pronunciation lexicon applied before synthesis, seeded takes with the exact text the model received, loudness, peak, noise-floor and pace measurements with a round-trip transcript check, takes chosen by a rule fixed in advance, lines placed on the beats they must fit, a person's listening check, and an AI-voice label wherever the voice plays.", "flow": [ { "verb": { "$verb": "voice-narration.prepare-lexicon" }, "when": "First, from the script." }, { "verb": { "$verb": "voice-narration.synthesize-lines" }, "when": "With the lexicon, on the worker's machine." }, { "verb": { "$verb": "voice-narration.measure-lines" }, "when": "Every take, before any choice." }, { "verb": { "$verb": "voice-narration.select-takes" }, "when": "By the rule fixed before scoring." }, { "verb": { "$verb": "voice-narration.place-on-beats" }, "when": "Once the cut plan gives each line its start." }, { "verb": { "$verb": "voice-narration.listen-and-approve" }, "when": "Last, by a person." } ], "guardrails": [ { "$pin": "guardrail.label-ai-voice" }, { "$pin": "guardrail.no-voice-cloning" }, { "$pin": "guardrail.license-before-synthesis" }, { "$pin": "guardrail.script-words-unchanged" } ], "worked_example": "Synthetic: a two-line script with a coined street name and an acronym, five takes (one clipped, one tie, one truncated), the rule's choice and the placed track that the narrated explainer video pack captions.", "complements": "A person's own recorded voice uses the video showcase pack's record-narration verb instead.", "provenance_notes": [ "Generalizes the narration steps of ta-amps's own explainer video production on October 29 and 30, 2026: a local open text-to-speech model with an explicit phoneme override for a coined name, seeded per line, measured with FFmpeg and checked by a local recognizer, with every line labeled as an AI voice." ] }, "relations": [ [ "requires_noun", "guardrail.label-ai-voice" ], [ "requires_noun", "guardrail.no-voice-cloning" ], [ "requires_noun", "guardrail.license-before-synthesis" ], [ "requires_noun", "guardrail.script-words-unchanged" ], [ "requires_noun", "rules.lexicon" ], [ "requires_noun", "rules.take-selection" ], [ "requires_noun", "rules.audio-measurements" ], [ "requires_noun", "rules.fit-to-beats" ], [ "requires_noun", "template.voice-model-card" ], [ "requires_noun", "template.narration-script" ], [ "requires_noun", "template.lexicon" ], [ "requires_noun", "template.synthesis-log" ], [ "requires_noun", "template.line-measurements" ], [ "requires_noun", "template.take-choice" ], [ "requires_noun", "template.narration-track" ], [ "requires_noun", "template.listening-record" ] ] }, { "slug": "guardrail.label-ai-voice", "category": "noun.skill_pack", "role": "guardrail", "title": "Guardrail: label every AI voice", "category_reason": "A guardrail is a reusable rule every verb in this work follows, with the check that proves it.", "value": { "rule": "Label every generated voice as AI-generated: in each line's and each track's disclosure, on screen for as long as the voice plays, and in the release notes. Never present a generated voice as a person's.", "how_it_is_checked": "Every narration track has a disclosure that names the voice as AI-generated and the model and stock voice used; the listening check and the video review both confirm the on-screen label whenever the voice plays." } }, { "slug": "guardrail.no-voice-cloning", "category": "noun.skill_pack", "role": "guardrail", "title": "Guardrail: no cloning or imitating a real person's voice", "category_reason": "A guardrail is a reusable rule every verb in this work follows, with the check that proves it.", "value": { "rule": "Use a model's stock voices, or a person who records their own voice. Never clone, imitate or approximate a real person's voice, and never prompt for one by name or description.", "how_it_is_checked": "Every synthesis log names a stock voice listed on the voice model card; a request for a real person's voice ends blocked." } }, { "slug": "guardrail.license-before-synthesis", "category": "noun.skill_pack", "role": "guardrail", "title": "Guardrail: a license that permits the use, checked before synthesis", "category_reason": "A guardrail is a reusable rule every verb in this work follows, with the check that proves it.", "value": { "rule": "Use only a voice model whose license permits the intended use, recorded on a voice model card with its revision and weight digests. Leave non-commercial and research-only models out of anything that may be released, and record every obligation the license sets.", "how_it_is_checked": "Every synthesis log pins a voice model card whose permitted uses include this one, and every loaded weight file matches the card's SHA-256." } }, { "slug": "guardrail.script-words-unchanged", "category": "noun.skill_pack", "role": "guardrail", "title": "Guardrail: the script's words never change in synthesis", "category_reason": "A guardrail is a reusable rule every verb in this work follows, with the check that proves it.", "value": { "rule": "The lexicon changes how a word is said, never which words are said. A line whose words must change goes back to the script's author; shorter variants are cut at measured pauses, not rewritten.", "how_it_is_checked": "Removing the lexicon's markup from the text sent to the model gives the script line exactly, character for character." } }, { "slug": "rules.lexicon", "category": "noun.reflex_procedure", "role": "rules", "title": "Pronunciation lexicon rules", "category_reason": "A zero-call deterministic procedure: the same inputs always give the same result.", "value": { "steps": [ "List the words the voice model may not know or may stress wrongly: coined names, place names, acronyms and borrowed words.", "For each, write IPA phonemes, a plain respelling for people, and the form the engine accepts: an inline phoneme override, an SSML phoneme element, or a respelling when the engine takes no phonemes.", "Audition two to five variants in a carrier sentence and record how the round-trip recognizer hears each.", "Apply entries whole-word and case-sensitively, longest first, and log the exact text the model received for every line.", "Keep the script's words; the lexicon changes only pronunciation." ], "rules": [ "A recognizer cannot hear stress, so a person confirms every entry by ear before release.", "An acronym said as letters gets an entry too, such as A I for AI.", "When the engine takes no phonemes, choose a respelling by audition and record how its reading drifts between takes.", "An entry is pending until the listening check sets confirmed_by_ear." ], "model_calls": 0 }, "relations": [ [ "requires_noun", "guardrail.script-words-unchanged" ] ] }, { "slug": "rules.take-selection", "category": "noun.reflex_procedure", "role": "rules", "title": "Take selection rule, fixed before the first take is scored", "category_reason": "A zero-call deterministic procedure: the same inputs always give the same result.", "value": { "steps": [ "Drop a take whose output is invalid or truncated, that has no end of speech, or that has any full-scale sample.", "Among the rest, keep the take with the lowest round-trip character error rate.", "Break a tie on the pace closest to the script's target words per minute, then on the lower take number.", "Keep every take and every reason on the record; a dropped take is never deleted." ], "committed": "The rule is fixed before the first take is scored. A change makes a new version of the rule.", "model_calls": 0 } }, { "slug": "rules.audio-measurements", "category": "noun.reflex_procedure", "role": "rules", "title": "Audio measurements for every take, and the edge trim", "category_reason": "A zero-call deterministic procedure: the same inputs always give the same result.", "value": { "measure": [ "Integrated loudness in LUFS and true peak in dBTP with FFmpeg's ebur128 filter (ITU-R BS.1770 measurement).", "Full-scale samples, counted exactly from the 16-bit samples.", "Noise floor: the 10th percentile of 10 ms peak levels, in dBFS.", "Lead and trail silence at -50 dB and at -40 dB, because a model's noise floor can sit above -50 dB.", "Speech span and internal pauses: 10 ms peaks at or above -45 dBFS are speech, and quiet runs of at least 0.18 s inside the line are pauses.", "Pace: words over the speech span in minutes, rounded to one decimal. A word is a whitespace-separated token with a letter or digit." ], "trim": "Trim each edge at an adaptive threshold, the noise floor plus 10 dB and never below -60 dBFS, then keep a 0.05 s pad and a 10 ms fade.", "defaults_not_standards": { "line_loudness_spread_lu_max": 1.0, "full_scale_samples_max": 0, "pace_wpm_band": [ 120, 190 ], "delivery_copy": "Two-pass loudnorm to -16 LUFS integrated, -1.5 dBTP and LRA 11 for web playback; record when FFmpeg falls back to dynamic mode because linear gain would pass the peak ceiling.", "broadcast": "Broadcast work uses EBU R 128's -23 LUFS target instead." }, "round_trip": "Transcribe each take with a pinned local recognizer using greedy decoding and no language model, and compute the character error rate against the script line with spaces removed.", "model_calls": 0 }, "relations": [ [ "requires_noun", "video-motion:tool.ffmpeg" ] ] }, { "slug": "rules.fit-to-beats", "category": "noun.reflex_procedure", "role": "rules", "title": "Fitting narration lines to their beats", "category_reason": "A zero-call deterministic procedure: the same inputs always give the same result.", "value": { "rules": [ "Each line starts where the cut plan says, inside its beat.", "A line ends at least 0.2 s before the next line starts and at least 0.1 s before the cut ends.", "Record each line's headroom: the time from its end to the next line's start or its beat's end, whichever comes first.", "A line that does not fit is never sped up past the pace band. Its author shortens it, the cut plan gives its beat more time, or it is cut at a measured pause.", "A shorter variant of a line is cut from the same take at a measured sentence pause, with the 0.05 s pad and a 10 ms fade, and is not synthesized again.", "Each track is exactly as long as its cut, at the model's sample rate, with every placement listed." ], "model_calls": 0 } }, { "slug": "template.voice-model-card", "category": "noun.model_spec", "role": "template", "title": "Template: a text-to-speech model as used: identity, license, weights, voices, input and determinism", "value": { "describes": "a text-to-speech model card: the exact revision, license and obligations, weight digests, stock voices, how it takes phonemes and whether its output is reproducible", "schema": { "type": "object", "additionalProperties": false, "required": [ "model", "revision", "model_license", "permitted_uses", "obligations", "weights", "stock_voices", "phoneme_input", "seed_rule", "byte_reproducible", "device", "network_at_runtime", "checked_on", "checked_against" ], "properties": { "model": { "type": "string", "minLength": 1, "maxLength": 120 }, "revision": { "type": "string", "minLength": 1, "maxLength": 80 }, "model_license": { "type": "string", "minLength": 1, "maxLength": 80 }, "permitted_uses": { "type": "array", "minItems": 1, "maxItems": 10, "items": { "type": "string", "minLength": 1, "maxLength": 120 } }, "obligations": { "type": "array", "minItems": 0, "maxItems": 10, "items": { "type": "string", "minLength": 1, "maxLength": 300 } }, "weights": { "type": "array", "minItems": 1, "maxItems": 20, "items": { "type": "object", "additionalProperties": false, "required": [ "file", "sha256", "bytes" ], "properties": { "file": { "type": "string", "minLength": 1, "maxLength": 200 }, "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, "bytes": { "type": "integer", "minimum": 1 } } } }, "stock_voices": { "type": "array", "minItems": 1, "maxItems": 60, "items": { "type": "string", "minLength": 1, "maxLength": 60 } }, "phoneme_input": { "type": "string", "enum": [ "inline_override", "ssml_phoneme", "none" ] }, "seed_rule": { "type": "string", "minLength": 1, "maxLength": 300 }, "byte_reproducible": { "type": "string", "minLength": 1, "maxLength": 300 }, "device": { "type": "string", "minLength": 1, "maxLength": 200 }, "network_at_runtime": { "type": "string", "enum": [ "none", "provider" ] }, "checked_on": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" }, "checked_against": { "type": "string", "minLength": 1, "maxLength": 300 } } }, "rules": [ "Weights are loaded only when their SHA-256 matches the card.", "A provider-hosted model spends only within the worker's own allowance." ] } }, { "slug": "template.narration-script", "category": "noun.scene_recipe", "role": "template", "title": "Template: narration lines with their beats, target times and pace", "value": { "describes": "the narration as numbered lines, each tied to a beat with the seconds it has, the target pace and the disclosure", "schema": { "type": "object", "additionalProperties": false, "required": [ "revision", "language", "target_wpm", "disclosure", "lines" ], "properties": { "revision": { "type": "string", "minLength": 1, "maxLength": 60 }, "language": { "type": "string", "minLength": 2, "maxLength": 20 }, "target_wpm": { "type": "integer", "minimum": 80, "maximum": 220 }, "disclosure": { "type": "string", "minLength": 1, "maxLength": 300 }, "lines": { "type": "array", "minItems": 1, "maxItems": 200, "items": { "type": "object", "additionalProperties": false, "required": [ "line", "text", "beat_id" ], "properties": { "line": { "type": "string", "minLength": 1, "maxLength": 20, "pattern": "^[a-z0-9-]+$" }, "text": { "type": "string", "minLength": 1, "maxLength": 600 }, "beat_id": { "type": "string", "minLength": 1, "maxLength": 60, "pattern": "^[a-z0-9][a-z0-9.-]*$" }, "seconds_available": { "type": "number", "minimum": 0.5, "maximum": 120 } } } } } } } }, { "slug": "template.lexicon", "category": "noun.skill_pack", "role": "template", "title": "Template: words with their phonemes, respellings and audition results", "category_reason": "A lexicon is reusable data every synthesis verb applies, with the audition evidence behind each entry.", "value": { "describes": "a pronunciation lexicon: each word with IPA phonemes, a plain respelling, the engine's input form, how each audition was heard and whether a person confirmed it by ear", "schema": { "type": "object", "additionalProperties": false, "required": [ "language", "version", "entries" ], "properties": { "language": { "type": "string", "minLength": 2, "maxLength": 20 }, "version": { "type": "string", "minLength": 1, "maxLength": 40 }, "entries": { "type": "array", "minItems": 1, "maxItems": 200, "items": { "type": "object", "additionalProperties": false, "required": [ "grapheme", "ipa", "respelling", "engine_input", "auditions", "chosen_variant", "confirmed_by_ear" ], "properties": { "grapheme": { "type": "string", "minLength": 1, "maxLength": 80 }, "ipa": { "type": "string", "minLength": 1, "maxLength": 120 }, "respelling": { "type": "string", "minLength": 1, "maxLength": 120 }, "engine_input": { "type": "string", "enum": [ "inline_override", "ssml_phoneme", "respelling" ] }, "auditions": { "type": "array", "minItems": 1, "maxItems": 6, "items": { "type": "object", "additionalProperties": false, "required": [ "variant", "heard_as" ], "properties": { "variant": { "type": "string", "minLength": 1, "maxLength": 120 }, "heard_as": { "type": "array", "minItems": 1, "maxItems": 6, "items": { "type": "string", "minLength": 1, "maxLength": 80 } } } } }, "chosen_variant": { "type": "string", "minLength": 1, "maxLength": 120 }, "confirmed_by_ear": { "type": "boolean" } } } } } }, "rules": [ "The listening check alone sets confirmed_by_ear.", "Entries never change a script word." ] } }, { "slug": "template.synthesis-log", "category": "noun.eval_run", "role": "template", "title": "Template: every take synthesized, with the exact text the model received", "value": { "describes": "a synthesis log: the pinned model card, voice, speed and seed rule, and for every line the text sent with the lexicon applied and every take with its seed and raw digest", "schema": { "type": "object", "additionalProperties": false, "required": [ "model_card", "voice", "speed", "seed_rule", "device", "network", "lines" ], "properties": { "model_card": { "type": "string", "minLength": 1, "maxLength": 300 }, "voice": { "type": "string", "minLength": 1, "maxLength": 60 }, "speed": { "type": "number", "minimum": 0.25, "maximum": 4 }, "seed_rule": { "type": "string", "minLength": 1, "maxLength": 200 }, "device": { "type": "string", "minLength": 1, "maxLength": 40 }, "network": { "type": "string", "const": "none" }, "lines": { "type": "array", "minItems": 1, "maxItems": 200, "items": { "type": "object", "additionalProperties": false, "required": [ "line", "text_sent", "takes" ], "properties": { "line": { "type": "string", "minLength": 1, "maxLength": 20, "pattern": "^[a-z0-9-]+$" }, "text_sent": { "type": "string", "minLength": 1, "maxLength": 1200 }, "takes": { "type": "array", "minItems": 1, "maxItems": 20, "items": { "type": "object", "additionalProperties": false, "required": [ "take", "seed", "valid", "end_of_speech", "raw_sha256" ], "properties": { "take": { "type": "integer", "minimum": 0, "maximum": 20 }, "seed": { "type": "integer", "minimum": 0 }, "valid": { "type": "boolean" }, "end_of_speech": { "type": "boolean" }, "raw_sha256": { "type": [ "string", "null" ], "pattern": "^[0-9a-f]{64}$" } } } } } } } } }, "rules": [ "Every take is listed, valid or not.", "network stays none: weights load from local files." ] } }, { "slug": "template.line-measurements", "category": "noun.eval_run", "role": "template", "title": "Template: loudness, peaks, silences, pace and round-trip errors for every take", "value": { "describes": "measurements of every take of every line after the edge trim: loudness, true peak, full-scale samples, noise floor, speech span, pauses, pace and the round-trip transcript's error rate; times are seconds within the trimmed take", "schema": { "type": "object", "additionalProperties": false, "required": [ "tool", "recognizer", "lines" ], "properties": { "tool": { "type": "string", "minLength": 1, "maxLength": 200 }, "recognizer": { "type": "string", "minLength": 1, "maxLength": 200 }, "lines": { "type": "array", "minItems": 1, "maxItems": 200, "items": { "type": "object", "additionalProperties": false, "required": [ "line", "takes" ], "properties": { "line": { "type": "string", "minLength": 1, "maxLength": 20, "pattern": "^[a-z0-9-]+$" }, "takes": { "type": "array", "minItems": 1, "maxItems": 20, "items": { "type": "object", "additionalProperties": false, "required": [ "take", "valid", "end_of_speech", "full_scale_samples", "duration_s", "speech_s", "pauses_s", "integrated_lufs", "true_peak_dbtp", "noise_floor_dbfs", "words", "wpm", "cer", "heard" ], "properties": { "take": { "type": "integer", "minimum": 0, "maximum": 20 }, "valid": { "type": "boolean" }, "end_of_speech": { "type": "boolean" }, "full_scale_samples": { "type": [ "integer", "null" ], "minimum": 0 }, "duration_s": { "type": [ "number", "null" ], "minimum": 0, "maximum": 600 }, "speech_s": { "type": [ "array", "null" ], "minItems": 2, "maxItems": 2, "items": { "type": "number", "minimum": 0, "maximum": 600 } }, "pauses_s": { "type": [ "array", "null" ], "minItems": 0, "maxItems": 40, "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": { "type": "number", "minimum": 0, "maximum": 600 } } }, "integrated_lufs": { "type": [ "number", "null" ], "minimum": -70, "maximum": 0 }, "true_peak_dbtp": { "type": [ "number", "null" ], "minimum": -70, "maximum": 6 }, "noise_floor_dbfs": { "type": [ "number", "null" ], "minimum": -120, "maximum": 0 }, "words": { "type": "integer", "minimum": 0 }, "wpm": { "type": [ "number", "null" ], "minimum": 0, "maximum": 600 }, "cer": { "type": [ "number", "null" ], "minimum": 0, "maximum": 1 }, "heard": { "type": [ "string", "null" ], "maxLength": 600 } } } } } } } } }, "rules": [ "An invalid take keeps its row with null measurements.", "wpm uses the speech span, not the padded file." ] } }, { "slug": "template.take-choice", "category": "noun.eval_run", "role": "template", "title": "Template: the take chosen for each line by the rule, and every take dropped", "value": { "describes": "the take chosen for each line by the take-selection rule, with every other take and the reason it was not chosen", "schema": { "type": "object", "additionalProperties": false, "required": [ "rule", "target_wpm", "chosen", "not_chosen", "loudness_spread_lu" ], "properties": { "rule": { "type": "string", "minLength": 1, "maxLength": 200 }, "target_wpm": { "type": "integer", "minimum": 80, "maximum": 220 }, "chosen": { "type": "array", "minItems": 1, "maxItems": 200, "items": { "type": "object", "additionalProperties": false, "required": [ "line", "take", "reason" ], "properties": { "line": { "type": "string", "minLength": 1, "maxLength": 20, "pattern": "^[a-z0-9-]+$" }, "take": { "type": "integer", "minimum": 0, "maximum": 20 }, "reason": { "type": "string", "minLength": 1, "maxLength": 200 } } } }, "not_chosen": { "type": "array", "minItems": 0, "maxItems": 400, "items": { "type": "object", "additionalProperties": false, "required": [ "line", "take", "reason" ], "properties": { "line": { "type": "string", "minLength": 1, "maxLength": 20, "pattern": "^[a-z0-9-]+$" }, "take": { "type": "integer", "minimum": 0, "maximum": 20 }, "reason": { "type": "string", "enum": [ "invalid_or_truncated", "no_end_of_speech", "clipping", "higher_error_rate", "pace_further_from_target", "later_take" ] } } } }, "loudness_spread_lu": { "type": "number", "minimum": 0, "maximum": 60 } } } } }, { "slug": "template.narration-track", "category": "noun.scene_recipe", "role": "template", "title": "Template: one cut's narration track with every line placed on the cut's timeline", "value": { "describes": "one cut's narration track: its file digest and length, the AI-voice disclosure, and every placed line with its text, take, start, end, speech span and pauses in seconds on the cut's timeline, which captions are timed from", "schema": { "type": "object", "additionalProperties": false, "required": [ "cut_id", "duration_s", "sample_rate_hz", "channels", "file_sha256", "disclosure", "placements" ], "properties": { "cut_id": { "type": "string", "minLength": 1, "maxLength": 60, "pattern": "^[a-z0-9][a-z0-9.-]*$" }, "duration_s": { "type": "number", "minimum": 1, "maximum": 7200 }, "sample_rate_hz": { "type": "integer", "minimum": 8000, "maximum": 192000 }, "channels": { "type": "integer", "minimum": 1, "maximum": 2 }, "file_sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, "disclosure": { "type": "string", "minLength": 10, "maxLength": 300 }, "placements": { "type": "array", "minItems": 1, "maxItems": 200, "items": { "type": "object", "additionalProperties": false, "required": [ "line", "take", "text", "beat_id", "start_s", "end_s", "speech_s", "pauses_s" ], "properties": { "line": { "type": "string", "minLength": 1, "maxLength": 20, "pattern": "^[a-z0-9-]+$" }, "take": { "type": "integer", "minimum": 0, "maximum": 20 }, "text": { "type": "string", "minLength": 1, "maxLength": 600 }, "beat_id": { "type": "string", "minLength": 1, "maxLength": 60, "pattern": "^[a-z0-9][a-z0-9.-]*$" }, "start_s": { "type": "number", "minimum": 0, "maximum": 7200 }, "end_s": { "type": "number", "minimum": 0, "maximum": 7200 }, "speech_s": { "type": "array", "minItems": 2, "maxItems": 2, "items": { "type": "number", "minimum": 0, "maximum": 7200 } }, "pauses_s": { "type": "array", "minItems": 0, "maxItems": 40, "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": { "type": "number", "minimum": 0, "maximum": 7200 } } } } } } } }, "rules": [ "The disclosure names the voice as AI-generated.", "Placement texts equal the script lines exactly." ] } }, { "slug": "template.listening-record", "category": "noun.eval_run", "role": "template", "title": "Template: a person's listening check of every line and track", "value": { "describes": "a person's listening check: every line approved or flagged with a note, every lexicon word confirmed or not, the on-screen label seen, and the decision", "schema": { "type": "object", "additionalProperties": false, "required": [ "listened_by_role", "on", "headphones", "lines", "lexicon", "label_seen_on_screen", "decision" ], "properties": { "listened_by_role": { "type": "string", "minLength": 3, "maxLength": 80 }, "on": { "type": "string", "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" }, "headphones": { "type": "boolean" }, "lines": { "type": "array", "minItems": 1, "maxItems": 200, "items": { "type": "object", "additionalProperties": false, "required": [ "line", "decision" ], "properties": { "line": { "type": "string", "minLength": 1, "maxLength": 20, "pattern": "^[a-z0-9-]+$" }, "decision": { "type": "string", "enum": [ "approve", "flag" ] }, "note": { "type": "string", "minLength": 1, "maxLength": 300 } } } }, "lexicon": { "type": "array", "minItems": 0, "maxItems": 200, "items": { "type": "object", "additionalProperties": false, "required": [ "grapheme", "confirmed_by_ear" ], "properties": { "grapheme": { "type": "string", "minLength": 1, "maxLength": 80 }, "confirmed_by_ear": { "type": "boolean" } } } }, "label_seen_on_screen": { "type": [ "boolean", "null" ] }, "decision": { "type": "string", "enum": [ "approve", "hold" ] } } }, "rules": [ "listened_by_role names a role, never a person.", "A hold lists every flagged line." ] } }, { "slug": "voice-model.open-82m", "category": "noun.model_spec", "role": "voice_model_card", "title": "Voice model card: an open 82-million-parameter text-to-speech model with phoneme input", "category_reason": "A model capability card for a text-to-speech model: revision, license, weights and inputs.", "value": { "conforms_to": { "$pin": "template.voice-model-card" }, "instance": { "model": "hexgrad/Kokoro-82M v1.0", "revision": "f3ff3571791e39611d31c381e3a41a3af07b4987", "model_license": "Apache-2.0", "permitted_uses": [ "public explainer videos", "local narration drafts" ], "obligations": [ "Apache-2.0's conditions apply when the model or its files are redistributed.", "Label every output as an AI voice regardless of the license." ], "weights": [ { "file": "kokoro-v1_0.pth", "sha256": "496dba118d1a58f5f3db2efc88dbdc216e0483fc89fe6e47ee1f2c53f18ad1e4", "bytes": 327212226 }, { "file": "voices/af_heart.pt", "sha256": "0ab5709b8ffab19bfd849cd11d98f75b60af7733253ad0d67b12382a102cb4ff", "bytes": 523425 } ], "stock_voices": [ "af_heart" ], "phoneme_input": "inline_override", "seed_rule": "Set the framework's manual seed immediately before each line or take.", "byte_reproducible": "In ta-amps's qualification run on 2026-10-29, re-running three lines on CPU with the same seed gave byte-identical raw files. Recheck on your own build.", "device": "CPU; about two to three times faster than real time in that run, on an 8-core desktop", "network_at_runtime": "none", "checked_on": "2026-10-30", "checked_against": "The publisher's model API at the pinned revision: license, base model, and the weight files' sizes and SHA-256." }, "notes": [ "Words missing from its dictionary fall back to rule-based phonemes with guessed stress, so coined names need a lexicon entry.", "Its voice notes (VOICES.md at the pinned revision) say most voices do best at 100 to 200 tokens, may sound weak under 10 to 20 tokens and may rush over 400." ], "source": "https://huggingface.co/hexgrad/Kokoro-82M" } }, { "slug": "checklist", "category": "noun.skill_pack", "role": "checklist", "title": "Listening checklist a person runs before narration goes into a cut", "category_reason": "A short checklist a person runs is part of the narration's guardrails.", "value": { "items": [ "Listen to every line and the whole track at normal speed, on headphones.", "Each lexicon word sounds as its respelling says, stress included.", "Acronyms are said as intended, such as A I as two letters.", "No clicks, metallic artifacts, clipped peaks or jumps in level between lines.", "Short lines do not sound weak and long lines do not sound rushed.", "The AI-voice label is on screen for as long as the voice plays." ] } }, { "slug": "sources", "category": "noun.public_source", "role": "sources", "title": "Dated public sources for voice narration", "value": { "sources": [ { "title": "W3C Pronunciation Lexicon Specification (PLS) Version 1.0", "url": "https://www.w3.org/TR/pronunciation-lexicon/", "used_for": "Lexicon entries: graphemes, phonemes and the IPA alphabet", "checked_date": "2026-10-30" }, { "title": "W3C Speech Synthesis Markup Language (SSML) Version 1.1", "url": "https://www.w3.org/TR/speech-synthesis11/", "used_for": "The phoneme element, for engines that take SSML", "checked_date": "2026-10-30" }, { "title": "International Phonetic Association: the IPA chart", "url": "https://www.internationalphoneticassociation.org/content/ipa-chart", "used_for": "IPA symbols for lexicon entries", "checked_date": "2026-10-30" }, { "title": "ITU-R BS.1770: algorithms to measure audio programme loudness and true-peak audio level", "url": "https://www.itu.int/rec/R-REC-BS.1770", "used_for": "What LUFS and dBTP measure", "checked_date": "2026-10-30" }, { "title": "EBU R 128 v5.0 (2023-11-21): loudness normalisation and permitted maximum level of audio signals", "url": "https://tech.ebu.ch/publications/r128", "used_for": "The -23 LUFS broadcast target, beside the -16 LUFS web default", "checked_date": "2026-10-30" }, { "title": "FFmpeg Filters Documentation: ebur128, loudnorm and silencedetect", "url": "https://ffmpeg.org/ffmpeg-filters.html", "used_for": "How loudness, peaks and silences are measured and normalized", "checked_date": "2026-10-30" }, { "title": "European Commission AI Act Service Desk: Article 50, transparency obligations", "url": "https://ai-act-service-desk.ec.europa.eu/en/ai-act/article-50", "used_for": "Why synthetic audio is marked and disclosed; a reading aid, not legal advice", "checked_date": "2026-10-30" }, { "title": "Hugging Face: hexgrad/Kokoro-82M model card", "url": "https://huggingface.co/hexgrad/Kokoro-82M", "used_for": "The example voice model card's license, revision and weights", "checked_date": "2026-10-30" } ], "recheck": "Recheck each source before relying on it; terms, limits and versions change." } }, { "slug": "test-cases", "category": "noun.benchmark_suite", "role": "test_cases", "title": "Synthetic test cases for voice narration", "value": { "cases": [ { "id": "VN-LEX-COINED", "scenario": "A coined street name is missing from the model's dictionary", "sample_input": "SYNTHETIC: The script says Velmora Street; with no entry the fallback reads it VILL-more with the stress on the first syllable.", "expected_outcome": "lexicon_ready", "expected_indicators": [ "entry_with_ipa_override", "override_logged_in_text_sent", "pending_listening_check" ], "expected_human_escalation": true }, { "id": "VN-LEX-NO-PHONEMES", "scenario": "The engine takes no phonemes, and a respelling is read as separate words", "sample_input": "SYNTHETIC: The respelling Vel-more-ah is heard as VEL MORE AH in two of three takes.", "expected_outcome": "lexicon_ready", "expected_indicators": [ "auditions_recorded", "drift_between_takes_recorded", "flag_for_listening" ], "expected_human_escalation": true }, { "id": "VN-TAKE-CLIP", "scenario": "One take clips and the other is clean", "sample_input": "SYNTHETIC: Line 1, take 0 has 2 full-scale samples; take 1 has none and an error rate of 0.0.", "expected_outcome": "takes_chosen", "expected_indicators": [ "take_1_chosen", "take_0_kept_as_clipping" ], "expected_human_escalation": false }, { "id": "VN-TAKE-TIE", "scenario": "Two clean takes tie on error rate", "sample_input": "SYNTHETIC: Line 2, takes 0 and 1 both have an error rate of 0.0, at 172.3 and 156.4 words per minute; the target is 150.", "expected_outcome": "takes_chosen", "expected_indicators": [ "take_1_chosen_on_pace", "tie_break_recorded" ], "expected_human_escalation": false }, { "id": "VN-FIT-LONG", "scenario": "A line runs past its beat", "sample_input": "SYNTHETIC: Line 2 is 8.6 s long but its beat has 8.05 s from the line's start.", "expected_outcome": "blocked", "expected_indicators": [ "not_sped_past_band", "author_or_cut_plan_decides" ], "expected_human_escalation": true }, { "id": "VN-LOUD-SPREAD", "scenario": "Line loudness differs by 5.6 LU", "sample_input": "SYNTHETIC: Chosen lines measure from -24.3 to -29.9 LUFS integrated.", "expected_outcome": "unknown", "expected_indicators": [ "spread_over_limit_flagged", "gain_match_before_mixing" ], "expected_human_escalation": false }, { "id": "VN-CLONE-ASK", "scenario": "A request to sound like a named public figure", "sample_input": "SYNTHETIC: The brief asks for the narration to sound exactly like a famous news anchor.", "expected_outcome": "blocked", "expected_indicators": [ "no_cloning", "offer_stock_voices" ], "expected_human_escalation": true }, { "id": "VN-LABEL-MISSING", "scenario": "A track without the AI-voice disclosure", "sample_input": "SYNTHETIC: The track's disclosure reads only: Narration for the 20-second cut.", "expected_outcome": "blocked", "expected_indicators": [ "disclosure_required", "release_held" ], "expected_human_escalation": false } ] }, "synthetic": true }, { "slug": "example.narration-script", "category": "noun.scene_recipe", "role": "example", "title": "Worked example: a two-line narration script for a 20-second explainer", "value": { "notice": "SYNTHETIC. A made-up street and fridge; no real place or organization.", "conforms_to": { "$pin": "template.narration-script" }, "instance": { "revision": "example-r1", "language": "en-US", "target_wpm": 150, "disclosure": "AI-generated voice, labeled on screen while it plays.", "lines": [ { "line": "l1", "text": "Every evening, volunteers check the Velmora Street fridge. They log what is running low.", "beat_id": "check", "seconds_available": 9.25 }, { "line": "l2", "text": "An AI drafts the restock list overnight, and by morning a person signs off on every change.", "beat_id": "restock", "seconds_available": 8.05 } ] } }, "synthetic": true }, { "slug": "example.lexicon", "category": "noun.skill_pack", "role": "example", "title": "Worked example: a lexicon for a coined street name and an acronym", "value": { "notice": "SYNTHETIC. Audition results are illustrative; no audio was made for this example.", "conforms_to": { "$pin": "template.lexicon" }, "instance": { "language": "en-US", "version": "example-r1", "entries": [ { "grapheme": "Velmora", "ipa": "vɛlˈmɔɹə", "respelling": "vel-MOR-uh", "engine_input": "inline_override", "auditions": [ { "variant": "vɛlˈmɔɹə", "heard_as": [ "VELMORA", "VELMORA" ] }, { "variant": "ˈvɛlmɔɹə", "heard_as": [ "VELMERA", "VELL MORA" ] }, { "variant": "no entry (the engine's fallback)", "heard_as": [ "VOLMORAH", "VILL MORE" ] } ], "chosen_variant": "vɛlˈmɔɹə", "confirmed_by_ear": false }, { "grapheme": "AI", "ipa": "ˌeɪˈaɪ", "respelling": "A I, two letters", "engine_input": "inline_override", "auditions": [ { "variant": "ˌeɪˈaɪ", "heard_as": [ "A I" ] } ], "chosen_variant": "ˌeɪˈaɪ", "confirmed_by_ear": false } ] } }, "synthetic": true }, { "slug": "example.synthesis-log", "category": "noun.eval_run", "role": "example", "title": "Worked example: two lines, five takes, the lexicon applied", "value": { "notice": "SYNTHETIC. No audio exists; each raw_sha256 is the SHA-256 of the UTF-8 text 'synthetic stand-in: take ' so the field keeps its real shape.", "conforms_to": { "$pin": "template.synthesis-log" }, "instance": { "model_card": "voice-model.open-82m (template.voice-model-card)", "voice": "af_heart", "speed": 0.81, "seed_rule": "base seed 2026 plus the take number, set before each take", "device": "cpu", "network": "none", "lines": [ { "line": "l1", "text_sent": "Every evening, volunteers check the [Velmora](/vɛlˈmɔɹə/) Street fridge. They log what is running low.", "takes": [ { "take": 0, "seed": 2026, "valid": true, "end_of_speech": true, "raw_sha256": "4deae6e4b67f001c37edeee5590bc33677901db5a3d1fa658dc621fe5cd74c27" }, { "take": 1, "seed": 2027, "valid": true, "end_of_speech": true, "raw_sha256": "faf5b05721b9804ace8a6506a26f7e8de9f415e4816da4b76d0595d2a529a86e" } ] }, { "line": "l2", "text_sent": "An [AI](/ˌeɪˈaɪ/) drafts the restock list overnight, and by morning a person signs off on every change.", "takes": [ { "take": 0, "seed": 2026, "valid": true, "end_of_speech": true, "raw_sha256": "d126eb767e0d8455e60d2e5ac4933f7711af3a03925256f8b0726e4ee5babe19" }, { "take": 1, "seed": 2027, "valid": true, "end_of_speech": true, "raw_sha256": "e4e276b3abc321c6e1fdbced2a1492597e0520608fb8b744cbb60c38d385204b" }, { "take": 2, "seed": 2028, "valid": false, "end_of_speech": false, "raw_sha256": null } ] } ] } }, "synthetic": true }, { "slug": "example.line-measurements", "category": "noun.eval_run", "role": "example", "title": "Worked example: measurements of every take", "value": { "notice": "SYNTHETIC. Plausible values, not measurements of real audio.", "conforms_to": { "$pin": "template.line-measurements" }, "instance": { "tool": "FFmpeg ebur128 and silencedetect (the build pinned on the FFmpeg tool card)", "recognizer": "a pinned local recognizer, greedy decoding, no language model", "lines": [ { "line": "l1", "takes": [ { "take": 0, "valid": true, "end_of_speech": true, "full_scale_samples": 2, "duration_s": 5.66, "speech_s": [ 0.05, 5.61 ], "pauses_s": [ [ 0.81, 1.02 ], [ 3.51, 3.88 ] ], "integrated_lufs": -25.4, "true_peak_dbtp": 0.3, "noise_floor_dbfs": -90.1, "words": 14, "wpm": 151.1, "cer": 0.0, "heard": "EVERY EVENING VOLUNTEERS CHECK THE VELMORA STREET FRIDGE THEY LOG WHAT IS RUNNING LOW" }, { "take": 1, "valid": true, "end_of_speech": true, "full_scale_samples": 0, "duration_s": 5.86, "speech_s": [ 0.05, 5.81 ], "pauses_s": [ [ 0.83, 1.05 ], [ 3.62, 4.0 ] ], "integrated_lufs": -25.6, "true_peak_dbtp": -6.8, "noise_floor_dbfs": -90.3, "words": 14, "wpm": 145.8, "cer": 0.0, "heard": "EVERY EVENING VOLUNTEERS CHECK THE VELMORA STREET FRIDGE THEY LOG WHAT IS RUNNING LOW" } ] }, { "line": "l2", "takes": [ { "take": 0, "valid": true, "end_of_speech": true, "full_scale_samples": 0, "duration_s": 6.02, "speech_s": [ 0.05, 5.97 ], "pauses_s": [ [ 2.71, 2.98 ] ], "integrated_lufs": -25.0, "true_peak_dbtp": -7.9, "noise_floor_dbfs": -90.2, "words": 17, "wpm": 172.3, "cer": 0.0, "heard": "AN A I DRAFTS THE RESTOCK LIST OVERNIGHT AND BY MORNING A PERSON SIGNS OFF ON EVERY CHANGE" }, { "take": 1, "valid": true, "end_of_speech": true, "full_scale_samples": 0, "duration_s": 6.62, "speech_s": [ 0.05, 6.57 ], "pauses_s": [ [ 2.95, 3.25 ] ], "integrated_lufs": -25.1, "true_peak_dbtp": -7.2, "noise_floor_dbfs": -90.4, "words": 17, "wpm": 156.4, "cer": 0.0, "heard": "AN A I DRAFTS THE RESTOCK LIST OVERNIGHT AND BY MORNING A PERSON SIGNS OFF ON EVERY CHANGE" }, { "take": 2, "valid": false, "end_of_speech": false, "full_scale_samples": null, "duration_s": null, "speech_s": null, "pauses_s": null, "integrated_lufs": null, "true_peak_dbtp": null, "noise_floor_dbfs": null, "words": 17, "wpm": null, "cer": null, "heard": null } ] } ] } }, "synthetic": true }, { "slug": "example.take-choice", "category": "noun.eval_run", "role": "example", "title": "Worked example: the rule applied: one clipped take, one tie, one truncated take", "value": { "notice": "SYNTHETIC. Recomputed from example.line-measurements by the library's tests.", "conforms_to": { "$pin": "template.take-choice" }, "instance": { "rule": "rules.take-selection", "target_wpm": 150, "chosen": [ { "line": "l1", "take": 1, "reason": "take 0 has full-scale samples; take 1 is the only clean take" }, { "line": "l2", "take": 1, "reason": "takes 0 and 1 tie on error rate; take 1's pace is nearer 150" } ], "not_chosen": [ { "line": "l1", "take": 0, "reason": "clipping" }, { "line": "l2", "take": 0, "reason": "pace_further_from_target" }, { "line": "l2", "take": 2, "reason": "invalid_or_truncated" } ], "loudness_spread_lu": 0.5 } }, "synthetic": true }, { "slug": "example.narration-track", "category": "noun.scene_recipe", "role": "example", "title": "Worked example: the chosen takes placed on a 20-second cut", "value": { "notice": "SYNTHETIC. No audio file exists; file_sha256 is the SHA-256 of the UTF-8 text 'synthetic stand-in: narration-20s.wav'. The narrated explainer video pack times its worked example's captions from these placements.", "conforms_to": { "$pin": "template.narration-track" }, "instance": { "cut_id": "20s", "duration_s": 20.0, "sample_rate_hz": 24000, "channels": 1, "file_sha256": "f9da34af6db8b8e649a4a276dc15cd96fc5ee251977fe2c38ce8028ce713edb0", "disclosure": "AI-generated voice (an open text-to-speech model's stock voice af_heart), labeled on screen while it plays.", "placements": [ { "line": "l1", "take": 1, "text": "Every evening, volunteers check the Velmora Street fridge. They log what is running low.", "beat_id": "check", "start_s": 0.5, "end_s": 6.36, "speech_s": [ 0.55, 6.31 ], "pauses_s": [ [ 1.33, 1.55 ], [ 4.12, 4.5 ] ] }, { "line": "l2", "take": 1, "text": "An AI drafts the restock list overnight, and by morning a person signs off on every change.", "beat_id": "restock", "start_s": 10.2, "end_s": 16.82, "speech_s": [ 10.25, 16.77 ], "pauses_s": [ [ 13.15, 13.45 ] ] } ] } }, "synthetic": true }, { "slug": "narration-checks", "category": "noun.benchmark_suite", "role": "worked_example", "title": "Narration release checks, with good and bad candidates the validator recomputes", "category_reason": "A golden fixture: fixed candidates and the recorded results of every check.", "value": { "fixture_kind": "finding_checks", "purpose": "Deterministic checks a narration must pass before it goes into a cut, each shown failing on a bad candidate for exactly its own reason.", "finding_schema": { "type": "object", "additionalProperties": false, "required": [ "track", "disclosure", "stock_voice", "label_on_screen", "max_full_scale_samples", "every_line_logged", "real_voice_requested", "listening_record" ], "properties": { "track": { "type": "string", "minLength": 1, "maxLength": 120 }, "disclosure": { "type": "string", "minLength": 0, "maxLength": 300 }, "stock_voice": { "type": [ "string", "null" ], "maxLength": 60 }, "label_on_screen": { "type": "boolean" }, "max_full_scale_samples": { "type": "integer", "minimum": 0 }, "every_line_logged": { "type": "boolean" }, "real_voice_requested": { "type": "boolean" }, "listening_record": { "type": [ "string", "null" ], "maxLength": 120 } } }, "deterministic_checks": [ { "id": "disclosed-as-ai", "rule": { "rule": "pattern_present", "path": "disclosure", "pattern": "(?i)\\bAI[- ]generated voice\\b" }, "why": "Every track names its voice as AI-generated." }, { "id": "stock-voice", "rule": { "rule": "present", "path": "stock_voice" }, "why": "Only a stock voice from the model card, never a cloned one." }, { "id": "label-on-screen", "rule": { "rule": "enum", "path": "label_on_screen", "values": [ true ] }, "why": "The label is on screen while the voice plays." }, { "id": "no-clipping", "rule": { "rule": "enum", "path": "max_full_scale_samples", "values": [ 0 ] }, "why": "No chosen take has a full-scale sample." }, { "id": "every-line-logged", "rule": { "rule": "enum", "path": "every_line_logged", "values": [ true ] }, "why": "The exact text sent for every line is on the record." }, { "id": "no-real-voice", "rule": { "rule": "enum", "path": "real_voice_requested", "values": [ false ] }, "why": "No real person's voice is cloned or imitated." }, { "id": "listened", "rule": { "rule": "present", "path": "listening_record" }, "why": "A person listened before the narration goes into a cut." } ], "golden_fixtures": [ { "id": "good-labeled-and-heard", "label": "known_good", "finding": { "track": "narration track 20s", "disclosure": "AI-generated voice (stock voice af_heart), labeled on screen.", "stock_voice": "af_heart", "label_on_screen": true, "max_full_scale_samples": 0, "every_line_logged": true, "real_voice_requested": false, "listening_record": "listening record, approved" } }, { "id": "bad-unlabeled-disclosure", "label": "known_bad", "fails": "disclosed-as-ai", "finding": { "track": "narration track 20s", "disclosure": "Narration track for the 20-second cut.", "stock_voice": "af_heart", "label_on_screen": true, "max_full_scale_samples": 0, "every_line_logged": true, "real_voice_requested": false, "listening_record": "listening record, approved" } }, { "id": "bad-no-voice-id", "label": "known_bad", "fails": "stock-voice", "finding": { "track": "narration track 20s", "disclosure": "AI-generated voice (stock voice af_heart), labeled on screen.", "stock_voice": null, "label_on_screen": true, "max_full_scale_samples": 0, "every_line_logged": true, "real_voice_requested": false, "listening_record": "listening record, approved" } }, { "id": "bad-label-missing", "label": "known_bad", "fails": "label-on-screen", "finding": { "track": "narration track 20s", "disclosure": "AI-generated voice (stock voice af_heart), labeled on screen.", "stock_voice": "af_heart", "label_on_screen": false, "max_full_scale_samples": 0, "every_line_logged": true, "real_voice_requested": false, "listening_record": "listening record, approved" } }, { "id": "bad-clipped", "label": "known_bad", "fails": "no-clipping", "finding": { "track": "narration track 20s", "disclosure": "AI-generated voice (stock voice af_heart), labeled on screen.", "stock_voice": "af_heart", "label_on_screen": true, "max_full_scale_samples": 2, "every_line_logged": true, "real_voice_requested": false, "listening_record": "listening record, approved" } }, { "id": "bad-unlogged-line", "label": "known_bad", "fails": "every-line-logged", "finding": { "track": "narration track 20s", "disclosure": "AI-generated voice (stock voice af_heart), labeled on screen.", "stock_voice": "af_heart", "label_on_screen": true, "max_full_scale_samples": 0, "every_line_logged": false, "real_voice_requested": false, "listening_record": "listening record, approved" } }, { "id": "bad-imitation", "label": "known_bad", "fails": "no-real-voice", "finding": { "track": "narration track 20s", "disclosure": "AI-generated voice (stock voice af_heart), labeled on screen.", "stock_voice": "af_heart", "label_on_screen": true, "max_full_scale_samples": 0, "every_line_logged": true, "real_voice_requested": true, "listening_record": "listening record, approved" } }, { "id": "bad-unheard", "label": "known_bad", "fails": "listened", "finding": { "track": "narration track 20s", "disclosure": "AI-generated voice (stock voice af_heart), labeled on screen.", "stock_voice": "af_heart", "label_on_screen": true, "max_full_scale_samples": 0, "every_line_logged": true, "real_voice_requested": false, "listening_record": null } } ], "expected": { "good-labeled-and-heard": { "verdict": "pass", "failed_checks": [] }, "bad-unlabeled-disclosure": { "verdict": "fail", "failed_checks": [ "disclosed-as-ai" ] }, "bad-no-voice-id": { "verdict": "fail", "failed_checks": [ "stock-voice" ] }, "bad-label-missing": { "verdict": "fail", "failed_checks": [ "label-on-screen" ] }, "bad-clipped": { "verdict": "fail", "failed_checks": [ "no-clipping" ] }, "bad-unlogged-line": { "verdict": "fail", "failed_checks": [ "every-line-logged" ] }, "bad-imitation": { "verdict": "fail", "failed_checks": [ "no-real-voice" ] }, "bad-unheard": { "verdict": "fail", "failed_checks": [ "listened" ] } } }, "relations": [ [ "requires_noun", "guardrail.label-ai-voice" ], [ "requires_noun", "guardrail.no-voice-cloning" ] ], "synthetic": true } ], "verbs": [ { "slug": "prepare-lexicon", "kind": "verb.generate_seed", "title": "Prepare the pronunciation lexicon", "claimant": "agent", "summary": "Find the words in a narration script that the voice model may misread, write each one's phonemes and a plain respelling, audition variants and record how a round-trip recognizer hears each.", "outcome": "A lexicon with auditions, every entry waiting for a person's ear.", "ready_outcome": "lexicon_ready", "pinned": [ "rules.lexicon", "guardrail.script-words-unchanged" ], "slots": [ { "name": "narration_script", "category": "noun.scene_recipe", "description": "Pin of the narration script." }, { "name": "voice_model_card", "category": "noun.model_spec", "description": "Pin of the voice model card." } ], "network": [ "local_only", "Only the worker's own machine: the voice model and the recognizer run from pinned local files with the network off; no provider and no other host." ], "produces": [ { "name": "lexicon", "template": "template.lexicon" } ], "steps": [ "List every coined name, place name, acronym and borrowed word in the script.", "Write IPA phonemes and a plain respelling for each, in the form the engine accepts.", "Audition two to five variants in a carrier sentence and record how the recognizer hears each.", "Leave confirmed_by_ear false on every entry; only the listening check sets it." ], "acceptance": [ { "id": "matches-template", "check": "The output validates against template.lexicon's schema.", "method": "deterministic" }, { "id": "words-covered", "check": "Every capitalized word that is not sentence-initial and every all-capital acronym in the script has an entry or a stated reason for none.", "method": "deterministic" }, { "id": "not-self-confirmed", "check": "No entry has confirmed_by_ear true.", "method": "deterministic" }, { "id": "phonemes-plausible", "check": "Each IPA string matches its respelling.", "method": "judgment" } ] }, { "slug": "synthesize-lines", "kind": "verb.compile_dag", "title": "Synthesize every line", "claimant": "agent", "summary": "Synthesize every script line with the pinned voice model on the worker's own machine, applying the lexicon, seeding each take and logging the exact text the model received.", "outcome": "A synthesis log with every take, valid or not.", "ready_outcome": "lines_synthesized", "model_use": "none", "pinned": [ "guardrail.license-before-synthesis", "guardrail.no-voice-cloning", "guardrail.label-ai-voice", "guardrail.script-words-unchanged", "rules.lexicon" ], "slots": [ { "name": "narration_script", "category": "noun.scene_recipe", "description": "Pin of the narration script." }, { "name": "lexicon", "category": "noun.skill_pack", "description": "Pin of the lexicon." }, { "name": "voice_model_card", "category": "noun.model_spec", "description": "Pin of the voice model card." } ], "params": { "voice": { "schema": { "type": "string", "minLength": 1, "maxLength": 60 }, "description": "A stock voice listed on the voice model card.", "default": "af_heart" }, "takes": { "schema": { "type": "integer", "minimum": 1, "maximum": 5 }, "description": "Takes per line; one when the model is byte-reproducible.", "default": 1 }, "base_seed": { "schema": { "type": "integer", "minimum": 0 }, "description": "The seed of take 0; take n uses base_seed plus n.", "default": 2026 } }, "network": [ "local_only", "Only the worker's own machine: the voice model and the recognizer run from pinned local files with the network off; no provider and no other host." ], "produces": [ { "name": "synthesis_log", "template": "template.synthesis-log" } ], "steps": [ "Load weights only from files whose SHA-256 matches the voice model card, with the network off.", "Apply the lexicon to each line whole-word and longest first, changing no word.", "Seed the model before every take and record the seed, the voice and the exact text sent.", "Keep every take, valid or not, and mark every output synthetic with an AI-voice disclosure." ], "acceptance": [ { "id": "matches-template", "check": "The output validates against template.synthesis-log's schema.", "method": "deterministic" }, { "id": "weights-match", "check": "Every loaded weight file's SHA-256 equals the voice model card's.", "method": "deterministic" }, { "id": "words-unchanged", "check": "Removing the lexicon's markup from each text_sent gives its script line exactly.", "method": "deterministic" }, { "id": "stock-voice", "check": "The voice is one of the card's stock voices.", "method": "deterministic" }, { "id": "every-take-listed", "check": "Every line lists the number of takes asked for.", "method": "deterministic" } ], "follows": [ "voice-narration.prepare-lexicon" ] }, { "slug": "measure-lines", "kind": "verb.verify_gate", "title": "Measure every take", "claimant": "agent", "summary": "Measure every take's loudness, true peak, full-scale samples, noise floor, silences, speech span, pauses and pace, trim its edges by rule, and transcribe it with the pinned recognizer.", "outcome": "Measurements of every take, with round-trip error rates.", "ready_outcome": "lines_measured", "model_use": "none", "pinned": [ "rules.audio-measurements", "rules.lexicon", "video-motion:tool.ffmpeg" ], "slots": [ { "name": "synthesis_log", "category": "noun.eval_run", "description": "Pin of the synthesis log." }, { "name": "narration_script", "category": "noun.scene_recipe", "description": "Pin of the narration script." } ], "network": [ "local_only", "Only the worker's own machine: the voice model and the recognizer run from pinned local files with the network off; no provider and no other host." ], "produces": [ { "name": "line_measurements", "template": "template.line-measurements" } ], "steps": [ "Measure loudness and true peak with FFmpeg's ebur128 filter and count full-scale samples exactly.", "Find the noise floor, speech span and pauses from 10 ms peaks, then trim edges at the adaptive threshold with the pad and fades.", "Transcribe each take with the pinned recognizer and compute its error rate against the script line.", "Record how each lexicon word was heard; a recognizer never confirms stress." ], "acceptance": [ { "id": "matches-template", "check": "The output validates against template.line-measurements's schema.", "method": "deterministic" }, { "id": "every-take-measured", "check": "Every take in the synthesis log has a row; invalid takes keep null measurements.", "method": "deterministic" }, { "id": "pace-computed", "check": "wpm equals words divided by the speech span in minutes, rounded to one decimal.", "method": "deterministic" }, { "id": "repeatable", "check": "Measuring the same file twice gives identical values.", "method": "deterministic" } ], "follows": [ "voice-narration.synthesize-lines" ] }, { "slug": "select-takes", "kind": "verb.verify_gate", "title": "Choose one take per line by the fixed rule", "claimant": "agent", "summary": "Choose one take per line by the take-selection rule fixed in advance, and list every other take with the reason it was not chosen.", "outcome": "A take choice that anyone can recompute from the measurements.", "ready_outcome": "takes_chosen", "model_use": "none", "pinned": [ "rules.take-selection" ], "slots": [ { "name": "line_measurements", "category": "noun.eval_run", "description": "Pin of the line measurements." }, { "name": "narration_script", "category": "noun.scene_recipe", "description": "Pin of the narration script, for the target pace." } ], "params": { "max_spread_lu": { "schema": { "type": "number", "minimum": 0, "maximum": 10 }, "description": "Largest loudness spread allowed among chosen takes.", "default": 1.0 } }, "produces": [ { "name": "take_choice", "template": "template.take-choice" } ], "steps": [ "Apply the rule exactly: drop invalid, truncated and clipped takes, then the lowest error rate, then the pace nearest the target, then the lower take number.", "List every take not chosen with its reason; delete nothing.", "Report the loudness spread of the chosen takes and end unknown when it is over max_spread_lu." ], "acceptance": [ { "id": "matches-template", "check": "The output validates against template.take-choice's schema.", "method": "deterministic" }, { "id": "rule-recomputed", "check": "Recomputing the rule from the measurements gives the same choices.", "method": "deterministic" }, { "id": "none-lost", "check": "Every take is either chosen or listed with a reason.", "method": "deterministic" } ], "follows": [ "voice-narration.measure-lines" ] }, { "slug": "place-on-beats", "kind": "verb.compile_dag", "title": "Place the chosen lines on their beats", "claimant": "agent", "summary": "Place each chosen line on its beat at the start the cut plan gives, check every fit and gap, cut shorter variants at measured pauses, and write one narration track per cut with its AI-voice disclosure.", "outcome": "A narration track for each cut, with every placement listed.", "ready_outcome": "tracks_placed", "model_use": "none", "pinned": [ "rules.fit-to-beats", "guardrail.label-ai-voice", "guardrail.script-words-unchanged" ], "slots": [ { "name": "take_choice", "category": "noun.eval_run", "description": "Pin of the take choice." }, { "name": "line_measurements", "category": "noun.eval_run", "description": "Pin of the line measurements." }, { "name": "narration_script", "category": "noun.scene_recipe", "description": "Pin of the narration script." }, { "name": "cut_plan", "category": "noun.scene_recipe", "description": "Pin of the cut plan with each line's start and beat." } ], "produces": [ { "name": "narration_track", "template": "template.narration-track", "max": 4 } ], "steps": [ "Place each line at its planned start and copy its text from the script, unchanged.", "Check that each line starts inside its beat and leaves the required gaps; end blocked when one does not fit, naming the line, and never speed it up past the pace band.", "Make each track exactly as long as its cut and record the speech span and pauses of every line on the cut's timeline." ], "acceptance": [ { "id": "matches-template", "check": "The output validates against template.narration-track's schema.", "method": "deterministic" }, { "id": "fits", "check": "Every line starts inside its beat, ends at least 0.2 s before the next line and at least 0.1 s before the cut ends.", "method": "deterministic" }, { "id": "texts-unchanged", "check": "Every placement's text equals its script line.", "method": "deterministic" }, { "id": "track-length", "check": "Each track's duration equals its cut's duration.", "method": "deterministic" }, { "id": "disclosed", "check": "Each track's disclosure names the voice as AI-generated.", "method": "deterministic" } ], "follows": [ "voice-narration.select-takes" ] }, { "slug": "listen-and-approve", "kind": "verb.verify_gate", "title": "Listen to every line and approve or hold (a person)", "claimant": "human", "summary": "A person listens to every line and every track on headphones, confirms each lexicon word by ear, checks the on-screen label, and approves the narration or holds it with reasons.", "outcome": "A listening record: approve or hold.", "ready_outcome": "listening_recorded", "model_use": "none", "pinned": [ "checklist", "guardrail.label-ai-voice" ], "slots": [ { "name": "narration_tracks", "category": "noun.scene_recipe", "description": "Pins of the narration tracks.", "many": true, "max": 4 }, { "name": "lexicon", "category": "noun.skill_pack", "description": "Pin of the lexicon." } ], "produces": [ { "name": "listening_record", "template": "template.listening-record" } ], "steps": [ "Run the listening checklist on every line and track.", "Confirm or reject each lexicon entry by ear; holding is a valid decision, with every flagged line listed." ], "acceptance": [ { "id": "matches-template", "check": "The output validates against template.listening-record's schema.", "method": "deterministic" }, { "id": "every-line-heard", "check": "Every placed line has a decision.", "method": "deterministic" }, { "id": "person-decides", "check": "The submitting key belongs to a person, not to the worker that synthesized or placed the lines.", "method": "deterministic" } ], "bounds": { "max_elapsed_seconds": 604800 }, "follows": [ "voice-narration.place-on-beats" ] } ] }