"""Day 1 fixture pipeline.""" from __future__ import annotations import argparse import json from pathlib import Path from .asset_library import ( dialogue_duration_seconds, resolve_broadcast_assets, write_traces, ) from .audio_placeholder import render_placeholder_wav from .mixer import mix_broadcast, mix_crude_broadcast from .schema import Genre from .tts import render_script_wav from .writer import generate_script, get_writer, script_to_json_dict def run_fixture_pipeline( premise: str, output_dir: Path = Path("outputs"), mode: str = "placeholder", writer_name: str = "fixture", genre: Genre | str = Genre.WEIRD, ) -> dict[str, str]: writer = get_writer(writer_name) # The local Nemotron (llama.cpp) is slow; don't pay for a retry — a bad/slow # attempt should drop straight to the fixture fallback. retries = 0 if writer_name == "nemotron" else 1 script = generate_script(writer, premise, genre=Genre(genre), retries=retries) safe_stem = _safe_stem(script.title) output_dir.mkdir(parents=True, exist_ok=True) script_path = output_dir / f"{safe_stem}.json" script_path.write_text(json.dumps(script_to_json_dict(script), indent=2), encoding="utf-8") if mode == "placeholder": audio_path = output_dir / f"{safe_stem}.placeholder.wav" render_placeholder_wav(script, audio_path) return { "script": str(script_path), "audio": str(audio_path), "mode": mode, "writer": writer.name, } if mode in ("crude", "production"): dialogue_path = output_dir / f"{safe_stem}.dialogue.wav" render_script_wav(script, dialogue_path) # Agentic step: resolve SFX/music against the published asset library # (retrieve-vs-generate), load matched clips, and log the decision traces. resolved = resolve_broadcast_assets( script, dialogue_duration_seconds(dialogue_path) ) traces_path = output_dir / f"{safe_stem}.traces.jsonl" write_traces(resolved.traces, traces_path) if mode == "crude": audio_path = output_dir / f"{safe_stem}.crude.wav" mix_crude_broadcast( dialogue_path, audio_path, sfx_layers=resolved.sfx_layers, bed=resolved.bed, opening_sting=resolved.opening_sting, closing_sting=resolved.closing_sting, ) else: audio_path = mix_broadcast( dialogue_path, output_dir / f"{safe_stem}.mp3", sfx_layers=resolved.sfx_layers, bed=resolved.bed, opening_sting=resolved.opening_sting, closing_sting=resolved.closing_sting, ) return { "script": str(script_path), "dialogue": str(dialogue_path), "audio": str(audio_path), "traces": str(traces_path), "asset_source": resolved.source, "mode": mode, "writer": writer.name, } raise ValueError(f"unknown pipeline mode: {mode}") def main() -> None: parser = argparse.ArgumentParser(description="Run the Midnight Static fixture pipeline.") parser.add_argument("premise", help="Short user premise to turn into a placeholder broadcast.") parser.add_argument("--output-dir", default="outputs", help="Directory for generated artifacts.") parser.add_argument( "--mode", choices=["placeholder", "crude", "production"], default="placeholder", help="placeholder tones, crude Kokoro mix, or the mastered production chain.", ) parser.add_argument( "--writer", default="fixture", help="Writer implementation name. Currently: fixture.", ) parser.add_argument( "--genre", choices=[genre.value for genre in Genre], default=Genre.WEIRD.value, help="Radio-drama genre/station to tune before writing.", ) args = parser.parse_args() result = run_fixture_pipeline( args.premise, Path(args.output_dir), mode=args.mode, writer_name=args.writer, genre=args.genre, ) print(json.dumps(result, indent=2)) def _safe_stem(value: str) -> str: stem = "".join(ch.lower() if ch.isalnum() else "-" for ch in value).strip("-") while "--" in stem: stem = stem.replace("--", "-") return stem[:80] or "broadcast" if __name__ == "__main__": main()