Install
$ agentstack add skill-aeon-7-aeon-radio-drama-aeon-radio-drama ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Radio Drama Producer — Full Pipeline Skill
Produce complete radio dramas, audiobooks, and audio fiction from a creative premise. This skill orchestrates the full pipeline: script writing, voice casting, audio generation (dialogue + music + SFX), and final mixed output.
The Four-Phase Pipeline
Phase 1: Script — Write a structured radio drama script from the user's creative input Phase 2: Voice Casting — Design unique voices for every character using the Three-Lock system Phase 3: Audio Generation — Render all dialogue, music, and SFX via ComfyUI workflows Phase 4: Assembly & Mix — Stitch and master everything into a finished audio file
Phase 1: Script Writing
Accept the user's creative input (premise, genre, characters, tone, target length) and write a complete radio drama script in structured JSON format.
The script JSON is the backbone of the entire pipeline — every downstream phase parses it. See references/script-format.md for the full schema.
Script Structure
{
"title": "The Last Lighthouse",
"genre": "mystery/thriller",
"target_duration_minutes": 15,
"characters": ["KEEPER", "INSPECTOR", "NARRATOR"],
"acts": [
{
"act_number": 1,
"title": "The Arrival",
"scenes": [
{
"scene_id": "act1_scene1",
"location": "Lighthouse exterior, stormy night",
"elements": [
{"type": "sfx_cue", "description": "Heavy rain and crashing waves against rocks", "duration_s": 5},
{"type": "music_cue", "mood": "tense_ambient", "description": "Low drone with distant foghorn motif", "duration_s": 30, "fade_in_s": 3},
{"type": "narration", "text": "The lighthouse had been dark for three days when Inspector Mara arrived.", "emotion": "neutral"},
{"type": "sfx_cue", "description": "Car door slam, footsteps on wet gravel", "duration_s": 3},
{"type": "dialogue", "character": "INSPECTOR", "text": "Hello? Anyone here?", "emotion": "cautious"},
{"type": "pause", "duration_s": 2},
{"type": "dialogue", "character": "KEEPER", "text": "You shouldn't have come.", "emotion": "fearful"}
]
}
]
}
]
}
Element Types
| Type | Purpose | Key Fields | |------|---------|------------| | narration | Narrator voice-over | text, emotion | | dialogue | Character speech | character, text, emotion, (optional) deliverynote | | music_cue | Background score | mood, description, durations, fadeins, fadeouts | | sfx_cue | Sound effect | description, durations | | pause | Silence/beat | durations | | direction | Production note (not rendered) | note |
Pacing Guidelines
- Open scenes with SFX/ambience to establish location (3-5s)
- Music cues should overlap dialogue, not compete — use low-energy backgrounds during speech
- Insert 1-2s pauses after emotional beats to let moments land
- Scene transitions: 2-3s crossfade with transitional SFX or music shift
- Target ~120-150 words per minute for dialogue pacing
Phase 2: Voice Casting
Design voices for every character using the Three-Lock system from the tts-voice-designer skill.
For each character, define:
- voice_instruct — natural language vocal description
- seed — fixed integer for consistency
- emotion_overrides — per-emotion instruct additions
Casting Manifest
{
"characters": {
"NARRATOR": {
"voice_instruct": "Calm, measured feminine voice with BBC-documentary clarity, unhurried pacing",
"seed": 200,
"emotion_overrides": {
"tense": ", pace tightening slightly, an undercurrent of urgency",
"reflective": ", softer and more contemplative, almost speaking to herself"
}
},
"INSPECTOR": {
"voice_instruct": "Sharp, precise feminine voice with authority, clipped professional cadence",
"seed": 310,
"emotion_overrides": {
"cautious": ", speaking more quietly, carefully measuring each word",
"commanding": ", projecting with full authority"
}
},
"KEEPER": {
"voice_instruct": "Weathered elderly man, hoarse from years of sea air, speaking in halting fragments",
"seed": 445,
"emotion_overrides": {
"fearful": ", voice dropping to a whisper, words tumbling over each other",
"defiant": ", finding unexpected steel, each word planted firmly"
}
}
}
}
Design for contrast — listeners distinguish characters by voice alone. Vary pitch, pace, texture, accent, and energy across the cast. See references/voice-casting.md for detailed guidance.
Phase 3: Audio Generation
> ## ⛔ STOP — DO NOT BUILD COMFYUI WORKFLOWS BY HAND > > Every workflow shown below is already built and tested inside scene_production_tool/radio_drama.py. The JSON examples in this section are reference material — they document what the orchestrator constructs internally, not a runtime contract you should reimplement. > > The correct way to generate audio: > > ``bash > # Full pipeline (dialogue + music + SFX + mix), one command: > python ${COMFYUI_ROOT}\scene_production_tool\radio_drama.py --stage all > > # Or per-stage (idempotent — already-rendered assets are skipped on re-run): > python radio_drama.py --stage tts > python radio_drama.py --stage music > python radio_drama.py --stage sfx > python radio_drama.py --stage mix > ` > > The orchestrator handles: workflow construction, APG chain wiring (xl_base/xl_sft music), schema validation, ComfyUI submission, retry-on-transient-error, output collection, canonicalization to 48 kHz/stereo/pcm_s24le, and dynamics-preserving mastering. > > **Symptoms you're heading the wrong direction:** > - "I'm checking the model loader for ACE Step…" > - "Let me find the proper VAE node…" > - "I need to figure out the negative conditioning structure…" > - HTTP 400 on /prompt submission with cryptic node validation errors > - "I don't have a compatible workflow template" > > All of these mean **stop and use radiodrama.py**. The workflow JSON in this section exists so you can understand what's happening, not so you can rebuild it. See radio-drama-production for the SSH runbook. > > **When to use musicmaker.py instead:** if dialogue was generated outside the radio-drama project structure (or doesn't exist yet) and you only need music cues, call musictool/musicmaker.py` directly — see "Standalone music generation" subsection at the end of this Phase.
The three systems below handle different tracks. The JSON shown is what the orchestrator builds, not what you should write by hand.
Track 1: Dialogue & Narration (Qwen3-TTS)
For each narration and dialogue element in the script, the orchestrator constructs a workflow like this:
{
"1": {
"class_type": "FB_Qwen3TTSVoiceDesign",
"inputs": {
"text": "",
"voice_instruct": "",
"seed": ""
}
},
"2": {
"class_type": "SaveAudio",
"inputs": {
"audio": ["1", 0],
"filename_prefix": "__"
}
}
}
For scenes with rapid dialogue, use DialogueInference for natural conversational flow:
{
"1": {
"class_type": "FB_Qwen3TTSRoleBank",
"inputs": {
"roles": []
}
},
"2": {
"class_type": "FB_Qwen3TTSDialogueInference",
"inputs": {
"script": "[character1] Line... [character2] Line...",
"role_bank": ["1", 0],
"pause_between_roles_ms": 400,
"pause_between_sentences_ms": 200
}
},
"3": {
"class_type": "SaveAudio",
"inputs": { "audio": ["2", 0], "filename_prefix": "_dialogue" }
}
}
Track 2: Music & Score (ACE Step 1.5 XL)
> Reminder: do not assemble ACE Step workflows by hand. radio_drama.py --stage music is the entry point. It imports music_tool/music_maker.py and dispatches to the right template (APG vs simple) based on the variant in your script. The JSON below is reference for understanding what gets built, not something to copy-paste into ComfyUI.
Default: xl_base with APG chain (12-node graph)
The current default is xl_base (50 steps, CFG 7.0, full fp32 base model, with the APG chain required for clean output from base models). This is the same chain music_maker.py uses for standalone tracks — both routes share music_tool/templates/ace_step_music_apg_api.json.
{
"1": {"class_type": "UNETLoader", "inputs": {"unet_name": "acestep_v1.5_xl_base.safetensors", "weight_dtype": "default"}},
"2": {"class_type": "CLIPLoader", "inputs": {"clip_name": "umt5_base.safetensors", "type": "ace"}},
"3": {"class_type": "VAELoader", "inputs": {"vae_name": "ace_step_audio_vae.safetensors"}},
"4": {"class_type": "TextEncodeAceStepAudio1.5", "inputs": {
"tags": "", "lyrics": "[Instrumental]",
"duration": "", "seed": "",
"clip": ["2", 0]}},
"5": {"class_type": "EmptyAceStepLatentAudio", "inputs": {"seconds": "", "batch_size": 1}},
"6": {"class_type": "ModelSamplingAuraFlow", "inputs": {"shift": 3.0, "model": ["1", 0]}},
"7": {"class_type": "APG", "inputs": {"eta": 0.7, "norm_threshold": 2.5, "momentum": -0.75, "model": ["6", 0]}},
"8": {"class_type": "CFGGuider", "inputs": {"cfg": 7.0, "model": ["7", 0], "positive": ["4", 0], "negative": ["4", 1]}},
"9": {"class_type": "KSamplerSelect", "inputs": {"sampler_name": "gradient_estimation"}},
"10": {"class_type": "BasicScheduler", "inputs": {"scheduler": "simple", "steps": 50, "denoise": 1.0, "model": ["6", 0]}},
"11": {"class_type": "RandomNoise", "inputs": {"noise_seed": ""}},
"12": {"class_type": "SamplerCustomAdvanced", "inputs": {
"noise": ["11", 0], "guider": ["8", 0], "sampler": ["9", 0],
"sigmas": ["10", 0], "latent_image": ["5", 0]}},
"13": {"class_type": "VAEDecodeAudio", "inputs": {"samples": ["12", 0], "vae": ["3", 0]}},
"14": {"class_type": "SaveAudio", "inputs": {"audio": ["13", 0], "filename_prefix": "_music"}}
}
The APG node + gradient_estimation sampler + linear_quadratic/simple scheduler are required for xl_base and xl_sft to render without artifacts. Without APG, those base models produce hollow/distorted audio. Distilled variants (xl_turbo, base_turbo, xl_base_sft) use a simpler 6-node KSampler graph instead. See music_tool/templates/ace_step_music_simple_api.json for that one.
Variants — choose per cue or per script
| Variant | Graph | Steps | CFG | Time @ 90 s | Notes | |---|---|---|---|---|---| | xl_base (default) | APG | 50 | 7.0 | ~21 s | Highest fidelity. Recommended unless iterating fast. | | xl_sft | APG | 45 | 6.0 | ~18 s | Near-base quality, bf16 (~10 GB), ~20% faster. | | xl_base_sft | simple KSampler | 35 | 3.0 | ~21 s | Merged base+SFT, no APG needed. | | xl_turbo | simple KSampler | 10 | 1.0 | ~12 s | Distilled. Use for preview iterations while writing. | | base_turbo | simple KSampler | 8 | 1.0 | ~8 s | Smallest (~4.8 GB), lowest quality. |
Set per-cue or script-wide:
"music_cues": {
"open": {"tags": "...", "duration": 45, "variant": "xl_base"},
"preview": {"tags": "...", "duration": 30, "variant": "xl_turbo"}
},
"ace_variant": "xl_base" // script-wide default if cue doesn't specify
Music mastering (automatic, under-dialogue tuned)
Every music cue produced by radio_drama.py --stage music is automatically run through scene_production_tool/music_mastering.py between ACE output and canonicalization. The default is dialed for under-dialogue placement:
- Preset:
default(EQ + light saturation, no compression) - Target LUFS:
−18(quieter than standalone music — the mix's sidechain ducking + final loudnorm bring it up) - Ceiling:
−1 dBTP
Per-cue overrides in the script:
"music_cues": {
"open": {"tags": "...", "duration": 30, "master": "orchestral", "master_target_lufs": -20},
"tension": {"tags": "...", "duration": 30, "master": "default"},
"hero": {"tags": "...", "duration": 60, "master": "orchestral", "master_target_lufs": -14},
"notes": {"tags": "...", "duration": 20, "master": "off"}
}
"master" choices: "default" | "edm" | "trap" | "chill" | "orchestral" | "jazz" | "auto" | "off". "auto" detects the preset from the cue's tags.
Script-wide defaults:
{
"music_master": "default", // global preset for cues that don't set "master"
"music_master_lufs": -18.0 // global LUFS target
}
Prompting rules for under-dialogue beds: avoid heavy dynamics (no [drop], no loud-quiet-loud, no sudden silence) — they fight sidechain ducking. Prefer "sustained", "slow evolving", "consistent energy", "drone", "warm bed". Save the dynamics vocabulary for standalone music via music_maker.py.
Hero moments: when a cue takes center stage (opening titles, act breaks, silent-scene montage), override with "master": "orchestral" + "master_target_lufs": -16 or -14, and raise its mix volume to 1.0. Those moments deserve the full LRA of a proper master.
Track 3: Sound Effects (MMAudio primary, SAO fallback)
> Reminder: do not assemble SFX workflows by hand. radio_drama.py --stage sfx runs generate_sfx() which automatically routes each event to the right engine based on duration. You don't pick the engine — the orchestrator does. JSON below is reference for what it builds.
SFX routing is duration-aware. After A/B testing on transients, foley, mechanical, ambient, and biological sounds, MMAudio beat both Stable Audio Open and ACE Step on quality — it's now the default. Engine per event:
| Requested duration | Engine | Why | |---|---|---| | 1–10 s | MMAudio Large 44k v2 | Best quality for discrete SFX; 10 s coherence window | | 10–47 s | Stable Audio Open 1.0 | MMAudio drifts past 10 s; SAO handles up to 47 s cleanly | | 47+ s | ACE Step 1.5 | Only engine with unlimited duration (music-model; use for ambient only) |
This routing is already wired in radio_drama.py's generate_sfx() priority chain — you don't set the engine per event; the tool picks based on duration.
Reference: MMAudio workflow (≤10 s) — 4-node graph the orchestrator builds:
{
"1": {"class_type": "MMAudioModelLoader", "inputs": {
"mmaudio_model": "mmaudio_large_44k_v2_fp16.safetensors", "base_precision": "fp16"
}},
"2": {"class_type": "MMAudioFeatureUtilsLoader", "inputs": {
"vae_model": "mmaudio_vae_44k_fp16.safetensors",
"synchformer_model": "mmaudio_synchformer_fp16.safetensors",
"clip_model": "apple_DFN5B-CLIP-ViT-H-14-384_fp16.safetensors",
"mode": "44k", "precision": "fp16"
}},
"3": {"class_type": "MMAudioSampler", "inputs": {
"mmaudio_model": ["1", 0], "feature_utils": ["2", 0],
"duration": "", "steps": 50, "cfg": 5.5, "seed": "",
"prompt": "",
"negative_prompt": "music, speech, vocals, singing",
"mask_away_clip": true, "force_offload": true
}},
"4": {"class_type": "SaveAudio", "inputs": {"audio": ["3", 0], "filename_prefix": "_sfx_"}}
}
Reference: Stable Audio Open workflow (10–47 s, fallback) — orchestrator builds, uses ComfyUI native nodes:
{
"1": {"class_type": "CheckpointLoaderSimple", "inputs": {"ckpt_name": "stable-audio-open-1.0.safetensors"}},
"2": {"class_type": "CLIPLoader", "inputs": {"clip_name": "stable-audio-open-t5.safetensors", "type": "stable_audio"}},
"3": {"class_type": "CLIPTextEncode", "inputs": {"text": "", "clip": ["2", 0]}},
"4": {"class_type": "CLIPTextEncode", "inputs": {"text": "", "clip": ["2", 0]}},
"5": {"class_type": "EmptyLatentAudio", "inputs": {"seconds": "", "batch_size": 1}},
"6": {"class_type": "ConditioningStableAudio", "inputs": {
"positive": ["3", 0], "negative": ["4", 0], "seconds_start": 0.0, "seconds_total": ""
}},
"7": {"clas
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [AEON-7](https://github.com/AEON-7)
- **Source:** [AEON-7/aeon-radio-drama](https://github.com/AEON-7/aeon-radio-drama)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.