Add stream command for streaming recipes

- artdag stream <recipe.sexp> runs streaming recipes
- Supports --duration, --fps, --sources, --audio options
- --wait flag polls for completion

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-02 19:18:57 +00:00
parent 3d663950f1
commit 0242c0bb22

100
artdag.py
View File

@@ -2245,5 +2245,105 @@ def run_status_v2(run_id):
click.echo(f"Error: {run['error']}")
@cli.command("stream")
@click.argument("recipe_file", type=click.Path(exists=True))
@click.option("--output", "-o", default="output.mp4", help="Output filename")
@click.option("--duration", "-d", type=float, help="Duration in seconds")
@click.option("--fps", type=float, help="FPS override")
@click.option("--sources", type=click.Path(exists=True), help="Sources config .sexp file")
@click.option("--audio", type=click.Path(exists=True), help="Audio config .sexp file")
@click.option("--wait", "-w", is_flag=True, help="Wait for completion")
def run_stream(recipe_file, output, duration, fps, sources, audio, wait):
"""Run a streaming S-expression recipe. Requires login.
RECIPE_FILE: Path to the recipe .sexp file
Example: artdag stream effects/my_effect.sexp --duration 10 --fps 30 -w
"""
token_data = load_token()
if not token_data.get("access_token"):
click.echo("Not logged in. Please run: artdag login <username>", err=True)
sys.exit(1)
# Read recipe file
recipe_path = Path(recipe_file)
recipe_sexp = recipe_path.read_text()
# Read optional config files
sources_sexp = None
if sources:
sources_sexp = Path(sources).read_text()
audio_sexp = None
if audio:
audio_sexp = Path(audio).read_text()
# Build request
request_data = {
"recipe_sexp": recipe_sexp,
"output_name": output,
}
if duration:
request_data["duration"] = duration
if fps:
request_data["fps"] = fps
if sources_sexp:
request_data["sources_sexp"] = sources_sexp
if audio_sexp:
request_data["audio_sexp"] = audio_sexp
# Submit
try:
headers = get_auth_header(require_token=True)
resp = requests.post(
f"{get_server()}/runs/stream",
json=request_data,
headers=headers
)
if resp.status_code == 401:
click.echo("Authentication failed. Please login again.", err=True)
sys.exit(1)
if resp.status_code == 400:
error = resp.json().get("detail", "Bad request")
click.echo(f"Error: {error}", err=True)
sys.exit(1)
resp.raise_for_status()
result = resp.json()
except requests.RequestException as e:
click.echo(f"Stream failed: {e}", err=True)
sys.exit(1)
run_id = result["run_id"]
click.echo(f"Stream started: {run_id}")
click.echo(f"Task ID: {result.get('celery_task_id', 'N/A')}")
click.echo(f"Status: {result.get('status', 'pending')}")
if wait:
click.echo("Waiting for completion...")
while True:
time.sleep(2)
try:
resp = requests.get(
f"{get_server()}/runs/{run_id}",
headers=get_auth_header()
)
resp.raise_for_status()
run = resp.json()
except requests.RequestException:
continue
status = run.get("status")
if status == "completed":
click.echo(f"\nCompleted!")
if run.get("output_cid"):
click.echo(f"Output CID: {run['output_cid']}")
break
elif status == "failed":
click.echo(f"\nFailed: {run.get('error', 'Unknown error')}", err=True)
sys.exit(1)
else:
click.echo(".", nl=False)
if __name__ == "__main__":
cli()