Initial commit: video effects processing system
Add S-expression based video effects pipeline with modular effect definitions, constructs, and recipe files. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
127
run.py
Executable file
127
run.py
Executable file
@@ -0,0 +1,127 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user