cajpany's picture
Local Nemotron 4B writer via GGUF/llama.cpp behind opt-in button
152b5ee verified
Raw
History Blame Contribute Delete
4.56 kB
"""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()