Add live video streaming for in-progress renders
This commit is contained in:
@@ -955,3 +955,40 @@ async def purge_failed_runs(
|
|||||||
|
|
||||||
logger.info(f"Purged {len(deleted)} failed runs")
|
logger.info(f"Purged {len(deleted)} failed runs")
|
||||||
return {"purged": len(deleted), "run_ids": deleted}
|
return {"purged": len(deleted), "run_ids": deleted}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{run_id}/stream")
|
||||||
|
async def stream_run_output(
|
||||||
|
run_id: str,
|
||||||
|
request: Request,
|
||||||
|
):
|
||||||
|
"""Stream the video output of a running render.
|
||||||
|
|
||||||
|
Returns the partial video file as it's being written,
|
||||||
|
allowing live preview of the render progress.
|
||||||
|
"""
|
||||||
|
from fastapi.responses import StreamingResponse, FileResponse
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Check for the streaming output file in the shared cache
|
||||||
|
cache_dir = os.environ.get("CACHE_DIR", "/data/cache")
|
||||||
|
stream_path = Path(cache_dir) / "streaming" / run_id / "output.mp4"
|
||||||
|
|
||||||
|
if not stream_path.exists():
|
||||||
|
raise HTTPException(404, "Stream not available yet")
|
||||||
|
|
||||||
|
file_size = stream_path.stat().st_size
|
||||||
|
if file_size == 0:
|
||||||
|
raise HTTPException(404, "Stream not ready")
|
||||||
|
|
||||||
|
# Return the file with headers that allow streaming of growing file
|
||||||
|
return FileResponse(
|
||||||
|
path=str(stream_path),
|
||||||
|
media_type="video/mp4",
|
||||||
|
headers={
|
||||||
|
"Accept-Ranges": "bytes",
|
||||||
|
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||||
|
"X-Content-Size": str(file_size),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|||||||
@@ -255,7 +255,12 @@ def run_stream(
|
|||||||
# Create temp directory for work
|
# Create temp directory for work
|
||||||
work_dir = Path(tempfile.mkdtemp(prefix="stream_"))
|
work_dir = Path(tempfile.mkdtemp(prefix="stream_"))
|
||||||
recipe_path = work_dir / "recipe.sexp"
|
recipe_path = work_dir / "recipe.sexp"
|
||||||
output_path = work_dir / output_name
|
|
||||||
|
# Write output to shared cache for live streaming access
|
||||||
|
cache_dir = Path(os.environ.get("CACHE_DIR", "/data/cache"))
|
||||||
|
stream_dir = cache_dir / "streaming" / run_id
|
||||||
|
stream_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
output_path = stream_dir / "output.mp4" # Always mp4 for streaming
|
||||||
|
|
||||||
# Create symlinks to effect directories so relative paths work
|
# Create symlinks to effect directories so relative paths work
|
||||||
(work_dir / "sexp_effects").symlink_to(sexp_effects_dir)
|
(work_dir / "sexp_effects").symlink_to(sexp_effects_dir)
|
||||||
@@ -438,7 +443,9 @@ def run_stream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# Cleanup temp directory
|
# Cleanup temp directory and streaming directory
|
||||||
import shutil
|
import shutil
|
||||||
if work_dir.exists():
|
if work_dir.exists():
|
||||||
shutil.rmtree(work_dir, ignore_errors=True)
|
shutil.rmtree(work_dir, ignore_errors=True)
|
||||||
|
if stream_dir.exists():
|
||||||
|
shutil.rmtree(stream_dir, ignore_errors=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user