Files
mono/artdag/test/run.py
giles 1a74d811f7 Incorporate art-dag-mono repo into artdag/ subfolder
Merges full history from art-dag/mono.git into the monorepo
under the artdag/ directory. Contains: core (DAG engine),
l1 (Celery rendering server), l2 (ActivityPub registry),
common (shared templates/middleware), client (CLI), test (e2e).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

git-subtree-dir: artdag
git-subtree-mainline: 1a179de547
git-subtree-split: 4c2e716558
2026-02-27 09:07:23 +00:00

128 lines
3.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Run a recipe: plan then execute.
This is a convenience wrapper that:
1. Generates a plan (runs analyzers, expands SLICE_ON)
2. Executes the plan (produces video output)
"""
import json
import sys
import tempfile
from pathlib import Path
# Add artdag to path
sys.path.insert(0, str(Path(__file__).parent.parent / "artdag"))
from artdag.sexp import compile_string
from artdag.sexp.planner import create_plan
from artdag.sexp.parser import Binding
# Import execute functionality
from execute import execute_plan
class PlanEncoder(json.JSONEncoder):
"""JSON encoder that handles Binding objects."""
def default(self, obj):
if isinstance(obj, Binding):
return {
"_bind": obj.analysis_ref,
"range_min": obj.range_min,
"range_max": obj.range_max,
}
return super().default(obj)
def run_recipe(recipe_path: Path, output_path: Path = None):
"""Run a recipe file: plan then execute."""
recipe_text = recipe_path.read_text()
recipe_dir = recipe_path.parent
print(f"=== COMPILE ===")
print(f"Recipe: {recipe_path}")
compiled = compile_string(recipe_text)
print(f"Name: {compiled.name} v{compiled.version}")
print(f"Nodes: {len(compiled.nodes)}")
# Track analysis results
analysis_data = {}
def on_analysis(node_id, results):
analysis_data[node_id] = results
times = results.get("times", [])
print(f" Analysis: {len(times)} beat times @ {results.get('tempo', 0):.1f} BPM")
# Generate plan
print(f"\n=== PLAN ===")
plan = create_plan(
compiled,
inputs={},
recipe_dir=recipe_dir,
on_analysis=on_analysis,
)
print(f"Plan ID: {plan.plan_id[:16]}...")
print(f"Steps: {len(plan.steps)}")
# Write plan to temp file for execute
plan_dict = {
"plan_id": plan.plan_id,
"recipe_id": compiled.name,
"recipe_hash": plan.recipe_hash,
"encoding": compiled.encoding,
"output_step_id": plan.output_step_id,
"steps": [],
}
for step in plan.steps:
step_dict = {
"step_id": step.step_id,
"node_type": step.node_type,
"config": step.config,
"inputs": step.inputs,
"level": step.level,
"cache_id": step.cache_id,
}
if step.node_type == "ANALYZE" and step.step_id in analysis_data:
step_dict["config"]["analysis_results"] = analysis_data[step.step_id]
plan_dict["steps"].append(step_dict)
# Save plan
work_dir = Path(tempfile.mkdtemp(prefix="artdag_run_"))
plan_file = work_dir / "plan.json"
with open(plan_file, "w") as f:
json.dump(plan_dict, f, indent=2, cls=PlanEncoder)
print(f"Plan saved: {plan_file}")
# Execute plan
print(f"\n=== EXECUTE ===")
result = execute_plan(plan_file, output_path, recipe_dir)
print(f"\n=== DONE ===")
print(f"Output: {result}")
return result
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: run.py <recipe.sexp> [output.mp4]")
print()
print("Commands:")
print(" run.py <recipe> - Plan and execute recipe")
print(" plan.py <recipe> - Generate plan only")
print(" execute.py <plan> - Execute pre-generated plan")
sys.exit(1)
recipe_path = Path(sys.argv[1])
output_path = Path(sys.argv[2]) if len(sys.argv) > 2 else None
if not recipe_path.exists():
print(f"Recipe not found: {recipe_path}")
sys.exit(1)
run_recipe(recipe_path, output_path)