feat: live frame progress streaming for cinematic renders
Replaced communicate() (blocking) with selectors-based line-by-line stdout streaming — same pattern as still render. Each frame now streams live to the frontend: [cinematic_render] Frame 42/480 -- 55.3s elapsed (0.76 fps) Pipeline: Blender stdout → log_callback → emit() → Redis → LiveRenderLog poll (2s) → frontend display Also added log_callback parameter to cinematic render task call. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -644,27 +644,47 @@ def render_cinematic_to_file(
|
||||
log_lines: list[str] = []
|
||||
|
||||
t_render = time.monotonic()
|
||||
import selectors as _sel
|
||||
proc = subprocess.Popen(
|
||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
text=True, env=env, start_new_session=True,
|
||||
)
|
||||
try:
|
||||
stdout, stderr = proc.communicate(timeout=7200) # 2hr max for cinematic (480 frames)
|
||||
except subprocess.TimeoutExpired:
|
||||
try:
|
||||
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
|
||||
except (ProcessLookupError, OSError):
|
||||
pass
|
||||
stdout, stderr = proc.communicate()
|
||||
stderr_lines: list[str] = []
|
||||
deadline = time.monotonic() + 7200 # 2hr max
|
||||
|
||||
for line in (stdout or "").splitlines():
|
||||
logger.info("[cinematic] %s", line)
|
||||
if "[cinematic_render]" in line:
|
||||
log_lines.append(line)
|
||||
if log_callback:
|
||||
log_callback(line)
|
||||
for line in (stderr or "").splitlines():
|
||||
logger.warning("[cinematic stderr] %s", line)
|
||||
sel = _sel.DefaultSelector()
|
||||
sel.register(proc.stdout, _sel.EVENT_READ, "stdout")
|
||||
sel.register(proc.stderr, _sel.EVENT_READ, "stderr")
|
||||
|
||||
try:
|
||||
while sel.get_map():
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining <= 0:
|
||||
try:
|
||||
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
|
||||
except (ProcessLookupError, OSError):
|
||||
pass
|
||||
break
|
||||
events = sel.select(timeout=min(remaining, 2.0))
|
||||
for key, _ in events:
|
||||
line = key.fileobj.readline()
|
||||
if not line:
|
||||
sel.unregister(key.fileobj)
|
||||
continue
|
||||
line = line.rstrip("\n")
|
||||
if key.data == "stdout":
|
||||
logger.info("[cinematic] %s", line)
|
||||
if "[cinematic_render]" in line:
|
||||
log_lines.append(line)
|
||||
if log_callback:
|
||||
log_callback(line)
|
||||
else:
|
||||
stderr_lines.append(line)
|
||||
logger.warning("[cinematic stderr] %s", line)
|
||||
finally:
|
||||
sel.close()
|
||||
|
||||
proc.wait()
|
||||
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError(
|
||||
|
||||
Reference in New Issue
Block a user