mmh3testing / export.py
observantdistressed's picture
refactor: split app.py into focused modules (#5)
64db20c
Raw History Blame Contribute Delete
1.97 kB
"""Frame export (last-frame extraction from a generated video).
`export_last_frame` is bound to the "Export Last Frame" button in the UI.
It validates that the input path lies inside the managed task temp root or the
legacy `/tmp/h3-outputs` directory, then runs ffmpeg to extract the final frame.
This is the only place that exposes the disk to client-supplied paths; the
path validation is intentionally strict.
"""
from __future__ import annotations
import os
import subprocess
import tempfile
import uuid
import gradio as gr
from task import _TASK_TEMP_ROOT
def export_last_frame(video_path):
"""Extract the last frame from a generated video and return it as an image file.
Path is validated to lie under the managed task/temp root; arbitrary client paths are rejected.
Destination is unique per call.
"""
if not video_path:
raise gr.Error("No video to extract from.")
# Resolve and constrain to managed roots (task temp or legacy h3-outputs).
try:
resolved = os.path.realpath(video_path)
except OSError:
raise gr.Error("Invalid video path.")
# Only managed task/output roots — never the bare system tempdir (symlink surface).
allowed_roots = [
os.path.realpath(_TASK_TEMP_ROOT),
os.path.realpath(os.path.join(tempfile.gettempdir(), "h3-outputs")),
]
if not any(resolved == root or resolved.startswith(root + os.sep) for root in allowed_roots):
raise gr.Error("Video path is outside the managed output area.")
# Unique destination under the same parent or task intermediates.
parent = os.path.dirname(resolved)
out = os.path.join(parent, f"lastframe-{uuid.uuid4().hex[:12]}.png")
result = subprocess.run(
["ffmpeg", "-sseof", "-0.1", "-i", resolved, "-frames:v", "1", "-y", out],
capture_output=True, text=True,
)
if result.returncode != 0:
raise gr.Error(f"ffmpeg failed: {result.stderr[-300:]}")
return out