import asyncio
import sys
from pathlib import Path
# Ensure project root is on sys.path when running as a script
PROJECT_ROOT = str(Path(__file__).resolve().parents[1])
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
from scenecraft_mcp.engine import script_parser, shot_planner
from scenecraft_mcp.models import Scene, Storyboard, StoryStylePreset
from scenecraft_mcp.storage.file_repository import FileStoryboardRepository
from scenecraft_mcp.utils.ids import generate_project_id
from scenecraft_mcp.video.framegen_sd import SDFrameGenerator
from scenecraft_mcp.video.assembler import assemble_video
from scenecraft_mcp.config import LLM_PROVIDER
TEST_SCRIPT = """
INT. SMALL APARTMENT – NIGHT
A tired programmer sits in front of a glowing laptop.
Lines of code reflect in their glasses.
Their phone buzzes: "Deployment Failed".
They rub their eyes, sigh, and glance at the city lights through the window.
"""
async def main():
print(f"Using LLM provider: {LLM_PROVIDER.value}")
# 1) Parse script -> scenes
scenes_raw = script_parser.parse_script(TEST_SCRIPT)
scenes: list[Scene] = []
for idx, s in enumerate(scenes_raw, start=1):
print(f"\nPlanning shots for scene {idx}: {s['slugline']}")
shots = await shot_planner.plan_shots(
scene_text=s["raw_text"],
scene_number=idx,
style_preset=StoryStylePreset.YOUTUBE_SHORT,
)
scene = Scene(
scene_number=idx,
slugline=s["slugline"],
summary=s["summary"],
location=s["location"],
time_of_day=s["time_of_day"],
mood=s["mood"],
raw_text=s["raw_text"],
shots=shots,
)
scenes.append(scene)
# 2) Build storyboard + store (optional)
project_id = generate_project_id()
storyboard = Storyboard(project_id=project_id, scenes=scenes)
repo = FileStoryboardRepository()
repo.save_storyboard(storyboard)
print(f"\nCreated storyboard {project_id}")
print(f"Total scenes: {len(storyboard.scenes)}")
print(f"Total shots: {storyboard.total_shots}\n")
# 3) Generate image per shot (FrameGen)
frame_gen = SDFrameGenerator(
# you can swap to any SD model here:
model_id="runwayml/stable-diffusion-v1-5",
output_dir=Path("outputs") / "frames",
)
frame_specs: list[dict] = []
global_style = (
"cinematic still, soft lighting, subtle film grain, "
"natural skin tones, realistic, 4k"
)
for scene in storyboard.scenes:
print(f"Generating frames for Scene {scene.scene_number}...")
for shot in scene.shots:
# Fallback duration if model didn't set one
duration = float(shot.duration_seconds or 2.5)
# Build a visual prompt from shot + scene context
lighting = shot.lighting or "moody low-key lighting"
mood = scene.mood or "introspective, late night"
prompt = (
f"{global_style}, {shot.description}, mood: {mood}, "
f"lighting: {lighting}"
)
image_path = frame_gen.generate_frame(
prompt=prompt,
shot_id=shot.shot_id,
scene_number=scene.scene_number,
)
print(f" - Shot {shot.shot_id}: {image_path.name} ({duration}s)")
frame_specs.append(
{
"image_path": image_path,
"duration": duration,
}
)
# 4) Assemble MP4
output_video = Path("outputs") / "videos" / f"{project_id}.mp4"
print(f"\nAssembling video -> {output_video}")
assemble_video(frame_specs, output_video, fps=24)
print(f"\nDone ✅ Video saved at: {output_video}")
if __name__ == "__main__":
asyncio.run(main())