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:
100
artdag.py
100
artdag.py
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user