Spaces:
Build error
Build error
faizr206 commited on
Commit ·
b93c8f2
1
Parent(s): 453eaf6
remove visual refinement for testing
Browse files- Dockerfile +1 -2
- app/main.py +68 -154
Dockerfile
CHANGED
|
@@ -3,10 +3,9 @@ FROM manimcommunity/manim:stable
|
|
| 3 |
WORKDIR /app
|
| 4 |
COPY app /app
|
| 5 |
|
| 6 |
-
# Web +
|
| 7 |
RUN pip install --no-cache-dir fastapi uvicorn[standard] pydantic python-dotenv google-genai
|
| 8 |
|
| 9 |
-
# Space default port
|
| 10 |
ENV PORT=7860
|
| 11 |
EXPOSE 7860
|
| 12 |
|
|
|
|
| 3 |
WORKDIR /app
|
| 4 |
COPY app /app
|
| 5 |
|
| 6 |
+
# Web server + Gemini SDK + dotenv (optional for local dev)
|
| 7 |
RUN pip install --no-cache-dir fastapi uvicorn[standard] pydantic python-dotenv google-genai
|
| 8 |
|
|
|
|
| 9 |
ENV PORT=7860
|
| 10 |
EXPOSE 7860
|
| 11 |
|
app/main.py
CHANGED
|
@@ -1,28 +1,27 @@
|
|
| 1 |
-
import os, re, uuid, subprocess, sys,
|
| 2 |
from pathlib import Path
|
| 3 |
-
from typing import
|
| 4 |
|
| 5 |
from fastapi import FastAPI, HTTPException, Response
|
| 6 |
from fastapi.middleware.cors import CORSMiddleware
|
| 7 |
from pydantic import BaseModel
|
| 8 |
|
| 9 |
-
# Optional .env
|
| 10 |
from dotenv import load_dotenv
|
| 11 |
load_dotenv()
|
| 12 |
|
| 13 |
-
# --------
|
| 14 |
from google import genai
|
| 15 |
from google.genai import types
|
| 16 |
|
| 17 |
API_KEY = os.getenv("GEMINI_API_KEY", "")
|
| 18 |
-
MODEL
|
| 19 |
-
PORT
|
| 20 |
|
| 21 |
-
# Configure Gemini client (exactly like your Flask code)
|
| 22 |
client = genai.Client(api_key=API_KEY) if API_KEY else None
|
| 23 |
|
| 24 |
-
# --------
|
| 25 |
-
app = FastAPI(title="Manim Render API (
|
| 26 |
app.add_middleware(
|
| 27 |
CORSMiddleware,
|
| 28 |
allow_origins=["*"], # tighten in prod
|
|
@@ -30,122 +29,43 @@ app.add_middleware(
|
|
| 30 |
allow_headers=["*"],
|
| 31 |
)
|
| 32 |
|
| 33 |
-
|
| 34 |
-
RUNS = Path("runs"); RUNS.mkdir(parents=True, exist_ok=True)
|
| 35 |
|
| 36 |
-
SYSTEM_PROMPT = """You are a Manim CE (0.19.x) code generator
|
| 37 |
Return ONLY valid Python code (no backticks, no prose).
|
| 38 |
Define exactly one class: AutoScene(Scene).
|
| 39 |
-
Keep it short (≤ ~
|
| 40 |
|
| 41 |
Use: from manim import *
|
| 42 |
Allowed imports: manim, math, numpy.
|
| 43 |
-
Forbidden: os, subprocess, sys, requests, pathlib, socket, shutil, psutil, file/network/OS access.
|
| 44 |
-
|
| 45 |
-
Instructions for frame capture:
|
| 46 |
-
- Capture a frame after each logical scene segment to help with automated evaluation.
|
| 47 |
-
- Use:
|
| 48 |
-
frame_image = self.camera.get_image()
|
| 49 |
-
frame_image.save(FRAME_DIR / "frame_name.png")
|
| 50 |
-
- Place this immediately after each self.play() that introduces or transforms major objects.
|
| 51 |
-
- Choose descriptive names for frames, e.g., "frame_title", "frame_vector_addition".
|
| 52 |
-
- Ensure all frames are saved inside the run-specific folder (FRAME_DIR) to allow automated refinement loops to detect them.
|
| 53 |
"""
|
| 54 |
|
| 55 |
DEFAULT_SCENE = """from manim import *
|
| 56 |
|
| 57 |
class AutoScene(Scene):
|
| 58 |
def construct(self):
|
| 59 |
-
t = Text("Hello").scale(1)
|
| 60 |
self.play(Write(t))
|
| 61 |
-
frame_image = self.camera.get_image()
|
| 62 |
-
frame_image.save(FRAME_DIR / "hello.png")
|
| 63 |
self.wait(0.5)
|
| 64 |
"""
|
| 65 |
|
| 66 |
-
# ---------- helpers ----------
|
| 67 |
def _clean_code(text: str) -> str:
|
| 68 |
-
"""Strip code fences and whitespace."""
|
| 69 |
if not text:
|
| 70 |
return ""
|
| 71 |
-
|
| 72 |
-
return
|
| 73 |
-
|
| 74 |
-
def _prepare_frame_prefixes(frame_paths: List[str], prefix_len=300):
|
| 75 |
-
"""(Kept from original) Produce small b64 prefixes (not sent unless you want)."""
|
| 76 |
-
mapping = []
|
| 77 |
-
for p in frame_paths or []:
|
| 78 |
-
try:
|
| 79 |
-
b = Path(p).read_bytes()
|
| 80 |
-
b64 = base64.b64encode(b).decode("utf-8")
|
| 81 |
-
short = b64[:prefix_len]
|
| 82 |
-
mapping.append((Path(p).name, short))
|
| 83 |
-
except Exception as e:
|
| 84 |
-
print("Failed reading frame", p, e, file=sys.stderr)
|
| 85 |
-
return mapping
|
| 86 |
-
|
| 87 |
-
def llm_generate_manim_code(prompt: str, previous_code: Optional[str] = None, frame_paths: Optional[List[str]] = None) -> str:
|
| 88 |
-
"""Generate or refine Manim AutoScene code using Gemini (multimodal with frames)."""
|
| 89 |
-
if not client:
|
| 90 |
-
return DEFAULT_SCENE
|
| 91 |
-
|
| 92 |
-
try:
|
| 93 |
-
contents = []
|
| 94 |
-
if previous_code and frame_paths:
|
| 95 |
-
# Refinement mode (use captured frames as image Parts)
|
| 96 |
-
user_prompt = f"""Refine the following Manim CE (0.19.x) code to fix layout/overlap/readability issues.
|
| 97 |
-
Keep the math concept and animations intact; make minimal, clear fixes.
|
| 98 |
-
|
| 99 |
-
Original code:
|
| 100 |
-
{previous_code}
|
| 101 |
-
|
| 102 |
-
Notes:
|
| 103 |
-
- Use the captured frames to identify overlapping objects or text outside the viewport.
|
| 104 |
-
- Adjust positions, font sizes, or layout as needed.
|
| 105 |
-
- Return ONLY the full corrected Python code that defines class AutoScene(Scene) and uses 'from manim import *'.
|
| 106 |
-
"""
|
| 107 |
-
contents.append(user_prompt)
|
| 108 |
-
for path in frame_paths:
|
| 109 |
-
try:
|
| 110 |
-
image_bytes = Path(path).read_bytes()
|
| 111 |
-
contents.append(types.Part.from_bytes(image_bytes, mime_type="image/png"))
|
| 112 |
-
except Exception as e:
|
| 113 |
-
print("Skipping frame include:", path, e, file=sys.stderr)
|
| 114 |
-
else:
|
| 115 |
-
# First generation
|
| 116 |
-
contents.append(f"Create AutoScene for: {prompt}")
|
| 117 |
-
|
| 118 |
-
response = client.models.generate_content(
|
| 119 |
-
model=MODEL,
|
| 120 |
-
config=types.GenerateContentConfig(system_instruction=SYSTEM_PROMPT),
|
| 121 |
-
contents=contents,
|
| 122 |
-
)
|
| 123 |
-
code = _clean_code(getattr(response, "text", str(response)))
|
| 124 |
-
|
| 125 |
-
if "class AutoScene" not in code:
|
| 126 |
-
code = previous_code or DEFAULT_SCENE
|
| 127 |
-
return code
|
| 128 |
-
|
| 129 |
-
except Exception:
|
| 130 |
-
print("LLM error:", file=sys.stderr)
|
| 131 |
-
traceback.print_exc()
|
| 132 |
-
return previous_code or DEFAULT_SCENE
|
| 133 |
|
| 134 |
-
def _run_manim(scene_code: str, run_id: Optional[str] = None) ->
|
| 135 |
-
"""Render
|
| 136 |
run_id = run_id or str(uuid.uuid4())[:8]
|
| 137 |
work = RUNS / run_id; work.mkdir(parents=True, exist_ok=True)
|
| 138 |
media = work / "media"; media.mkdir(parents=True, exist_ok=True)
|
| 139 |
scene_path = work / "scene.py"
|
| 140 |
|
| 141 |
-
#
|
| 142 |
-
|
| 143 |
-
import os
|
| 144 |
-
from pathlib import Path
|
| 145 |
-
FRAME_DIR = Path(r"{work.resolve()}")
|
| 146 |
-
{scene_code}
|
| 147 |
-
"""
|
| 148 |
-
scene_path.write_text(scene_with_path, encoding="utf-8")
|
| 149 |
|
| 150 |
env = os.environ.copy()
|
| 151 |
env["PYTHONPATH"] = str(work)
|
|
@@ -158,27 +78,42 @@ FRAME_DIR = Path(r"{work.resolve()}")
|
|
| 158 |
]
|
| 159 |
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, env=env)
|
| 160 |
if proc.returncode != 0:
|
| 161 |
-
print("Manim stdout/stderr:\n", proc.stdout, file=sys.stderr)
|
| 162 |
raise RuntimeError("Manim render failed")
|
| 163 |
|
| 164 |
-
#
|
| 165 |
-
|
| 166 |
for p in media.rglob(f"{run_id}.mp4"):
|
| 167 |
-
|
| 168 |
-
if not
|
| 169 |
for p in media.rglob("*.mp4"):
|
| 170 |
-
|
| 171 |
-
if not
|
| 172 |
raise RuntimeError("Rendered video not found")
|
| 173 |
|
| 174 |
-
|
| 175 |
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
|
| 180 |
def llm_refine_from_error(previous_code: str, error_message: str) -> str:
|
| 181 |
-
"""
|
| 182 |
if not client:
|
| 183 |
return previous_code or DEFAULT_SCENE
|
| 184 |
try:
|
|
@@ -187,14 +122,14 @@ def llm_refine_from_error(previous_code: str, error_message: str) -> str:
|
|
| 187 |
Code:
|
| 188 |
{previous_code}
|
| 189 |
|
| 190 |
-
Error
|
| 191 |
{error_message}
|
| 192 |
|
| 193 |
Task:
|
| 194 |
- Fix the bug while preserving the math logic and animations.
|
| 195 |
-
- Common mistakes:
|
| 196 |
- Ensure the corrected code defines exactly one class AutoScene(Scene).
|
| 197 |
-
- Return ONLY the corrected Python code.
|
| 198 |
"""
|
| 199 |
response = client.models.generate_content(
|
| 200 |
model=MODEL,
|
|
@@ -206,45 +141,26 @@ Task:
|
|
| 206 |
return previous_code or DEFAULT_SCENE
|
| 207 |
return code
|
| 208 |
except Exception:
|
| 209 |
-
print("LLM
|
| 210 |
traceback.print_exc()
|
| 211 |
return previous_code or DEFAULT_SCENE
|
| 212 |
|
| 213 |
-
def refine_loop(prompt: str,
|
| 214 |
-
"""
|
| 215 |
code = llm_generate_manim_code(prompt)
|
| 216 |
-
last_video_bytes: Optional[bytes] = None
|
| 217 |
-
|
| 218 |
-
for i in range(max_iters):
|
| 219 |
-
try:
|
| 220 |
-
video_bytes, frames = _run_manim(code, run_id=f"iter{i}")
|
| 221 |
-
last_video_bytes = video_bytes
|
| 222 |
-
except Exception:
|
| 223 |
-
print("Render error (iter):", file=sys.stderr)
|
| 224 |
-
traceback.print_exc()
|
| 225 |
-
|
| 226 |
-
if i < max_error_refines:
|
| 227 |
-
error_msg = traceback.format_exc()
|
| 228 |
-
code = llm_refine_from_error(previous_code=code, error_message=error_msg)
|
| 229 |
-
continue
|
| 230 |
-
else:
|
| 231 |
-
break
|
| 232 |
-
|
| 233 |
-
if not frames:
|
| 234 |
-
print("No captured frames detected; stopping refinement loop.")
|
| 235 |
-
break
|
| 236 |
-
|
| 237 |
-
# Visual refinement with frames
|
| 238 |
-
code = llm_generate_manim_code(prompt, previous_code=code, frame_paths=frames)
|
| 239 |
-
|
| 240 |
-
# Final render
|
| 241 |
try:
|
| 242 |
-
|
| 243 |
-
return final_video
|
| 244 |
except Exception:
|
| 245 |
-
|
|
|
|
|
|
|
|
|
|
| 246 |
|
| 247 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
class PromptIn(BaseModel):
|
| 249 |
prompt: str
|
| 250 |
|
|
@@ -254,16 +170,14 @@ def health():
|
|
| 254 |
|
| 255 |
@app.post("/generate-and-render")
|
| 256 |
def generate_and_render(inp: PromptIn):
|
| 257 |
-
"""
|
| 258 |
-
Send: {"prompt": "..."} -> Returns: MP4 bytes (video/mp4)
|
| 259 |
-
"""
|
| 260 |
if not inp.prompt or not inp.prompt.strip():
|
| 261 |
raise HTTPException(400, "Missing prompt")
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
|
|
|
| 265 |
return Response(
|
| 266 |
-
content=
|
| 267 |
media_type="video/mp4",
|
| 268 |
headers={"Content-Disposition": 'inline; filename="result.mp4"'}
|
| 269 |
)
|
|
|
|
| 1 |
+
import os, re, uuid, subprocess, sys, traceback
|
| 2 |
from pathlib import Path
|
| 3 |
+
from typing import Optional, Tuple
|
| 4 |
|
| 5 |
from fastapi import FastAPI, HTTPException, Response
|
| 6 |
from fastapi.middleware.cors import CORSMiddleware
|
| 7 |
from pydantic import BaseModel
|
| 8 |
|
| 9 |
+
# Optional .env for local testing
|
| 10 |
from dotenv import load_dotenv
|
| 11 |
load_dotenv()
|
| 12 |
|
| 13 |
+
# -------- Gemini (same SDK style as your Flask app) --------
|
| 14 |
from google import genai
|
| 15 |
from google.genai import types
|
| 16 |
|
| 17 |
API_KEY = os.getenv("GEMINI_API_KEY", "")
|
| 18 |
+
MODEL = os.getenv("GEMINI_MODEL", "gemini-2.5-pro")
|
| 19 |
+
PORT = int(os.getenv("PORT", "7860"))
|
| 20 |
|
|
|
|
| 21 |
client = genai.Client(api_key=API_KEY) if API_KEY else None
|
| 22 |
|
| 23 |
+
# -------- FastAPI app --------
|
| 24 |
+
app = FastAPI(title="Manim Render API (error-refine only)")
|
| 25 |
app.add_middleware(
|
| 26 |
CORSMiddleware,
|
| 27 |
allow_origins=["*"], # tighten in prod
|
|
|
|
| 29 |
allow_headers=["*"],
|
| 30 |
)
|
| 31 |
|
| 32 |
+
RUNS = Path("runs"); RUNS.mkdir(parents=True, exist_ok=True)
|
|
|
|
| 33 |
|
| 34 |
+
SYSTEM_PROMPT = """You are a Manim CE (0.19.x) code generator/refiner.
|
| 35 |
Return ONLY valid Python code (no backticks, no prose).
|
| 36 |
Define exactly one class: AutoScene(Scene).
|
| 37 |
+
Keep it short (preferably ≤ ~60 s) and quickly renderable.
|
| 38 |
|
| 39 |
Use: from manim import *
|
| 40 |
Allowed imports: manim, math, numpy.
|
| 41 |
+
Forbidden: os, subprocess, sys, requests, pathlib, socket, shutil, psutil, any file/network/OS access.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
"""
|
| 43 |
|
| 44 |
DEFAULT_SCENE = """from manim import *
|
| 45 |
|
| 46 |
class AutoScene(Scene):
|
| 47 |
def construct(self):
|
| 48 |
+
t = Text("Hello from Manim").scale(1)
|
| 49 |
self.play(Write(t))
|
|
|
|
|
|
|
| 50 |
self.wait(0.5)
|
| 51 |
"""
|
| 52 |
|
| 53 |
+
# ---------------- helpers ----------------
|
| 54 |
def _clean_code(text: str) -> str:
|
|
|
|
| 55 |
if not text:
|
| 56 |
return ""
|
| 57 |
+
# strip code fences if present
|
| 58 |
+
return re.sub(r"^```(?:python)?\\n|```$", "", text, flags=re.MULTILINE).strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
+
def _run_manim(scene_code: str, run_id: Optional[str] = None) -> bytes:
|
| 61 |
+
"""Render with Manim (low quality, fast) and return MP4 bytes."""
|
| 62 |
run_id = run_id or str(uuid.uuid4())[:8]
|
| 63 |
work = RUNS / run_id; work.mkdir(parents=True, exist_ok=True)
|
| 64 |
media = work / "media"; media.mkdir(parents=True, exist_ok=True)
|
| 65 |
scene_path = work / "scene.py"
|
| 66 |
|
| 67 |
+
# Write scene code
|
| 68 |
+
scene_path.write_text(scene_code, encoding="utf-8")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
env = os.environ.copy()
|
| 71 |
env["PYTHONPATH"] = str(work)
|
|
|
|
| 78 |
]
|
| 79 |
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, env=env)
|
| 80 |
if proc.returncode != 0:
|
| 81 |
+
print("Manim stdout/stderr:\\n", proc.stdout, file=sys.stderr)
|
| 82 |
raise RuntimeError("Manim render failed")
|
| 83 |
|
| 84 |
+
# Locate output mp4
|
| 85 |
+
mp4 = None
|
| 86 |
for p in media.rglob(f"{run_id}.mp4"):
|
| 87 |
+
mp4 = p; break
|
| 88 |
+
if not mp4:
|
| 89 |
for p in media.rglob("*.mp4"):
|
| 90 |
+
mp4 = p; break
|
| 91 |
+
if not mp4:
|
| 92 |
raise RuntimeError("Rendered video not found")
|
| 93 |
|
| 94 |
+
return mp4.read_bytes()
|
| 95 |
|
| 96 |
+
def llm_generate_manim_code(prompt: str, previous_code: Optional[str] = None) -> str:
|
| 97 |
+
"""First-pass generation (no visual refinement)."""
|
| 98 |
+
if not client:
|
| 99 |
+
return DEFAULT_SCENE
|
| 100 |
+
try:
|
| 101 |
+
response = client.models.generate_content(
|
| 102 |
+
model=MODEL,
|
| 103 |
+
config=types.GenerateContentConfig(system_instruction=SYSTEM_PROMPT),
|
| 104 |
+
contents=f"Create AutoScene for: {prompt}",
|
| 105 |
+
)
|
| 106 |
+
code = _clean_code(getattr(response, "text", str(response)))
|
| 107 |
+
if "class AutoScene" not in code:
|
| 108 |
+
code = previous_code or DEFAULT_SCENE
|
| 109 |
+
return code
|
| 110 |
+
except Exception:
|
| 111 |
+
print("LLM generate error:", file=sys.stderr)
|
| 112 |
+
traceback.print_exc()
|
| 113 |
+
return previous_code or DEFAULT_SCENE
|
| 114 |
|
| 115 |
def llm_refine_from_error(previous_code: str, error_message: str) -> str:
|
| 116 |
+
"""Only used when Manim fails; no frames / visual step."""
|
| 117 |
if not client:
|
| 118 |
return previous_code or DEFAULT_SCENE
|
| 119 |
try:
|
|
|
|
| 122 |
Code:
|
| 123 |
{previous_code}
|
| 124 |
|
| 125 |
+
Error / stack trace:
|
| 126 |
{error_message}
|
| 127 |
|
| 128 |
Task:
|
| 129 |
- Fix the bug while preserving the math logic and animations.
|
| 130 |
+
- Common mistakes: invalid method chaining, wrong .animate usage, bad mobject usage, etc.
|
| 131 |
- Ensure the corrected code defines exactly one class AutoScene(Scene).
|
| 132 |
+
- Return ONLY the corrected Python code (no backticks).
|
| 133 |
"""
|
| 134 |
response = client.models.generate_content(
|
| 135 |
model=MODEL,
|
|
|
|
| 141 |
return previous_code or DEFAULT_SCENE
|
| 142 |
return code
|
| 143 |
except Exception:
|
| 144 |
+
print("LLM refine error:", file=sys.stderr)
|
| 145 |
traceback.print_exc()
|
| 146 |
return previous_code or DEFAULT_SCENE
|
| 147 |
|
| 148 |
+
def refine_loop(prompt: str, max_error_refines: int = 1) -> bytes:
|
| 149 |
+
"""Generate → render; if render fails, LLM-refine once → re-render."""
|
| 150 |
code = llm_generate_manim_code(prompt)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
try:
|
| 152 |
+
return _run_manim(code, run_id="iter0")
|
|
|
|
| 153 |
except Exception:
|
| 154 |
+
print("Render failed (iter0), attempting error-based refinement...", file=sys.stderr)
|
| 155 |
+
traceback.print_exc()
|
| 156 |
+
if max_error_refines <= 0:
|
| 157 |
+
raise
|
| 158 |
|
| 159 |
+
refined = llm_refine_from_error(previous_code=code, error_message=traceback.format_exc())
|
| 160 |
+
# Try a second render
|
| 161 |
+
return _run_manim(refined, run_id="iter1")
|
| 162 |
+
|
| 163 |
+
# ---------------- API ----------------
|
| 164 |
class PromptIn(BaseModel):
|
| 165 |
prompt: str
|
| 166 |
|
|
|
|
| 170 |
|
| 171 |
@app.post("/generate-and-render")
|
| 172 |
def generate_and_render(inp: PromptIn):
|
|
|
|
|
|
|
|
|
|
| 173 |
if not inp.prompt or not inp.prompt.strip():
|
| 174 |
raise HTTPException(400, "Missing prompt")
|
| 175 |
+
try:
|
| 176 |
+
mp4 = refine_loop(inp.prompt.strip(), max_error_refines=1)
|
| 177 |
+
except Exception:
|
| 178 |
+
raise HTTPException(500, "Failed to produce video after refinement")
|
| 179 |
return Response(
|
| 180 |
+
content=mp4,
|
| 181 |
media_type="video/mp4",
|
| 182 |
headers={"Content-Disposition": 'inline; filename="result.mp4"'}
|
| 183 |
)
|