Skip to content

Commit a1ccc3b

Browse files
committed
chore: imrprove clip generation
1 parent a1ebd05 commit a1ccc3b

2 files changed

Lines changed: 181 additions & 45 deletions

File tree

src/lib/inline-clip-render.sh

Lines changed: 167 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@ on_exit() {
9595
restart_capture "$MATCH_ID" || true
9696
LIVE_CAPTURE_STOPPED=0
9797
fi
98+
# Backgrounded chip render — kill it if we're exiting before the
99+
# polish pass had a chance to wait on it (e.g. cs2 stall, SIGTERM).
100+
if [ -n "${CHIP_RENDER_PID:-}" ] && kill -0 "$CHIP_RENDER_PID" 2>/dev/null; then
101+
kill -TERM "$CHIP_RENDER_PID" 2>/dev/null || true
102+
wait "$CHIP_RENDER_PID" 2>/dev/null || true
103+
fi
104+
[ -n "${CHIP_RENDER_LOG:-}" ] && rm -f "$CHIP_RENDER_LOG"
98105
# ProRes intermediates are ~20MB/s — drop the chip mov even on
99106
# error so a flapping pod doesn't fill its scratch dir.
100107
if [ -n "${CHIP_MOV:-}" ]; then rm -f "$CHIP_MOV"; fi
@@ -345,8 +352,11 @@ MOTION_DIR="${MOTION_DIR:-/opt/game-streamer/motion}"
345352
if [ -n "$CHIP_NAME" ] && [ ! -d "$MOTION_DIR" ]; then
346353
say "WARN motion project missing at $MOTION_DIR — skipping chip"
347354
fi
355+
CHIP_RENDER_PID=""
356+
CHIP_RENDER_LOG=""
348357
if [ -n "$CHIP_NAME" ] && [ -d "$MOTION_DIR" ]; then
349358
CHIP_MOV="${CLIP_OUT_DIR:-/tmp/game-streamer/clips}/${CLIP_RENDER_JOB_ID}-chip.mov"
359+
CHIP_RENDER_LOG="${CHIP_MOV}.log"
350360
mkdir -p "$(dirname "$CHIP_MOV")"
351361
CHIP_PROPS=$(CHIP_NAME="$CHIP_NAME" \
352362
CHIP_AVATAR="$CHIP_AVATAR" \
@@ -367,26 +377,63 @@ if [ -n "$CHIP_NAME" ] && [ -d "$MOTION_DIR" ]; then
367377
height: Number(process.env.CHIP_OUT_H),
368378
fps: Number(process.env.CHIP_OUT_FPS),
369379
}))')
370-
say "CHIP: rendering for '${CHIP_NAME}'"
371-
if ! (cd "$MOTION_DIR" && \
372-
node node_modules/.bin/remotion render \
373-
src/index.ts PlayerChip "$CHIP_MOV" \
374-
--codec=prores --prores-profile=4444 \
375-
--pixel-format=yuva444p10le --image-format=png \
376-
--log=error \
377-
--props="$CHIP_PROPS"); then
380+
say "CHIP: rendering for '${CHIP_NAME}' (background)"
381+
# Remotion render runs in parallel with the segment seek + capture.
382+
# The chip mov is only consumed by the per-segment polish pass; we
383+
# wait_for_chip_render before that block. Backgrounding overlaps the
384+
# ~1-3s Chromium render with the ~3-10s capture wallclock.
385+
(
386+
cd "$MOTION_DIR" && \
387+
node node_modules/.bin/remotion render \
388+
src/index.ts PlayerChip "$CHIP_MOV" \
389+
--codec=prores --prores-profile=4444 \
390+
--pixel-format=yuva444p10le --image-format=png \
391+
--log=error \
392+
--props="$CHIP_PROPS"
393+
) >"$CHIP_RENDER_LOG" 2>&1 &
394+
CHIP_RENDER_PID=$!
395+
fi
396+
397+
wait_for_chip_render() {
398+
[ -z "$CHIP_RENDER_PID" ] && return 0
399+
if ! wait "$CHIP_RENDER_PID"; then
378400
say "WARN chip render failed — continuing without chip overlay"
401+
[ -n "$CHIP_RENDER_LOG" ] && [ -s "$CHIP_RENDER_LOG" ] \
402+
&& sed 's/^/ chip: /' "$CHIP_RENDER_LOG" >&2
379403
rm -f "$CHIP_MOV"
380404
CHIP_MOV=""
381405
fi
382-
fi
406+
rm -f "$CHIP_RENDER_LOG"
407+
CHIP_RENDER_PID=""
408+
}
383409

384410
CLIP_OUT_DIR="${CLIP_OUT_DIR:-/tmp/game-streamer/clips}"
385411
mkdir -p "$CLIP_OUT_DIR"
386412
CLIP_OUT_FILE="${CLIP_OUT_DIR}/${CLIP_RENDER_JOB_ID}.mp4"
387413
CLIP_THUMB_FILE="${CLIP_OUT_DIR}/${CLIP_RENDER_JOB_ID}.jpg"
388414
rm -f "$CLIP_OUT_FILE" "$CLIP_THUMB_FILE"
389415

416+
# Precompute: will an outro be appended at concat time? If yes AND we
417+
# would have run a per-segment polish pass (chip or speed change), we
418+
# can fuse both into a single ffmpeg encode at the end — eliminating
419+
# one full 1080p60 NVENC pass per clip. The polish-skip gate below
420+
# reads OUTRO_WILL_APPEND; the fused encode reads it at concat time.
421+
OUTRO_WILL_APPEND=0
422+
OUTRO_FUSED_FILE=""
423+
if [ "$BRANDING_ENABLED" = "1" ] && [ "${CLIP_DISABLE_OUTRO:-0}" != "1" ]; then
424+
OUTRO_DIMS_PRE="${CLIP_OUTPUT_DIMS:-1920x1080}"
425+
OUTRO_FPS_PRE="${CLIP_OUTPUT_FPS:-60}"
426+
OUTRO_FUSED_FILE="${OUTRO_DIR:-/opt/game-streamer/resources/video}/outro_${OUTRO_DIMS_PRE}_${OUTRO_FPS_PRE}.mp4"
427+
if [ -f "$OUTRO_FUSED_FILE" ]; then
428+
OUTRO_WILL_APPEND=1
429+
fi
430+
fi
431+
WILL_FUSE_POLISH_OUTRO=0
432+
if [ "$OUTRO_WILL_APPEND" = "1" ] \
433+
&& { [ "$CLIP_RENDER_SPEED" != "1" ] || [ -n "$CHIP_NAME" ]; }; then
434+
WILL_FUSE_POLISH_OUTRO=1
435+
fi
436+
390437
# Per-segment output paths + concat list. We render each segment to
391438
# its own file and let ffmpeg concat-demux glue them — this keeps each
392439
# capture session independent (a stall in one doesn't ruin the rest)
@@ -538,8 +585,13 @@ for SEG_IDX in $(seq 0 $((SEG_COUNT - 1))); do
538585
# Per-segment polish pass — combines slowdown (when CLIP_RENDER_SPEED
539586
# != 1) and chip overlay (when CHIP_MOV is set) into a single ffmpeg
540587
# call so we don't re-encode twice. Skipped when neither applies so
541-
# the speed=1 + no-chip path keeps gstreamer's capture intact.
542-
if [ "$CLIP_RENDER_SPEED" != "1" ] || [ -n "$CHIP_MOV" ]; then
588+
# the speed=1 + no-chip path keeps gstreamer's capture intact. Also
589+
# skipped when WILL_FUSE_POLISH_OUTRO=1 — the chip/slowdown gets
590+
# baked into the same filter_complex as the outro concat, saving
591+
# one full NVENC encode per clip.
592+
wait_for_chip_render
593+
if [ "$WILL_FUSE_POLISH_OUTRO" != "1" ] \
594+
&& { [ "$CLIP_RENDER_SPEED" != "1" ] || [ -n "$CHIP_MOV" ]; }; then
543595
HAS_AUDIO=0
544596
if has_audio_stream "$SEG_FILE"; then HAS_AUDIO=1; fi
545597
POLISH_FILE="${SEG_FILE}.polish.mp4"
@@ -669,18 +721,87 @@ elif [ "$OUTRO_APPENDED" = "1" ]; then
669721
# captured segments carry trailing PTS that pushes the outro ~30s
670722
# past with -c copy. Filter-graph concat is the reliable splice
671723
# across heterogeneous sources.
672-
say "STEP 9: ffmpeg concat ${SEG_COUNT} segments (with outro, filter-graph)"
724+
#
725+
# WILL_FUSE_POLISH_OUTRO=1: the per-segment polish pass was skipped,
726+
# so the chip overlay + slowdown gets folded into this same encode
727+
# — one NVENC pass instead of two (polish-per-segment + concat).
728+
CAP_SEG_COUNT=$((SEG_COUNT - 1)) # last entry in concat.txt is outro
673729
CONCAT_INPUTS=()
674730
while IFS= read -r line; do
675731
f=$(printf '%s' "$line" | awk -F"'" '/^file/{print $2}')
676732
[ -n "$f" ] && CONCAT_INPUTS+=("-i" "$f")
677733
done <"$SEG_DIR/concat.txt"
678-
N=${#SEG_COUNT}
734+
679735
FC=""
680-
for i in $(seq 0 $((SEG_COUNT - 1))); do
681-
FC+="[${i}:v:0][${i}:a:0]"
682-
done
683-
FC+="concat=n=${SEG_COUNT}:v=1:a=1[v][a]"
736+
if [ "$WILL_FUSE_POLISH_OUTRO" != "1" ]; then
737+
# Segments were already polished per-segment; simple concat-only graph.
738+
say "STEP 9: ffmpeg concat ${SEG_COUNT} segments (with outro, filter-graph)"
739+
for i in $(seq 0 $((SEG_COUNT - 1))); do
740+
FC+="[${i}:v:0][${i}:a:0]"
741+
done
742+
FC+="concat=n=${SEG_COUNT}:v=1:a=1[v][a]"
743+
else
744+
# Fused path: bake chip overlay + slowdown into the same encode as
745+
# the outro concat — one NVENC pass instead of two.
746+
say "STEP 9: ffmpeg fused polish+concat ${CAP_SEG_COUNT} seg(s) + outro"
747+
748+
# Chip is appended as one extra input after segments+outro. Split it
749+
# once per captured segment when there's more than one segment (so
750+
# each gets its own ~3.5s chip head, matching the per-segment polish
751+
# behaviour). split=1 isn't valid, so single-segment skips the split.
752+
if [ -n "$CHIP_MOV" ]; then
753+
CHIP_IDX=$SEG_COUNT
754+
CONCAT_INPUTS+=("-i" "$CHIP_MOV")
755+
if [ "$CAP_SEG_COUNT" -gt 1 ]; then
756+
FC+="[${CHIP_IDX}:v]split=${CAP_SEG_COUNT}"
757+
for i in $(seq 0 $((CAP_SEG_COUNT - 1))); do FC+="[chip${i}]"; done
758+
FC+=";"
759+
fi
760+
fi
761+
762+
if [ "$CLIP_RENDER_SPEED" != "1" ]; then
763+
case "$CLIP_RENDER_SPEED" in
764+
2) ATEMPO_CHAIN="atempo=0.5" ;;
765+
3) ATEMPO_CHAIN="atempo=0.5,atempo=0.667" ;;
766+
4) ATEMPO_CHAIN="atempo=0.5,atempo=0.5" ;;
767+
*) ATEMPO_CHAIN="atempo=0.5" ;;
768+
esac
769+
else
770+
ATEMPO_CHAIN=""
771+
fi
772+
773+
for i in $(seq 0 $((CAP_SEG_COUNT - 1))); do
774+
if [ -n "$CHIP_MOV" ]; then
775+
if [ "$CAP_SEG_COUNT" -gt 1 ]; then
776+
FC+="[${i}:v][chip${i}]overlay=0:0:eof_action=pass:format=auto"
777+
else
778+
FC+="[${i}:v][${CHIP_IDX}:v]overlay=0:0:eof_action=pass:format=auto"
779+
fi
780+
else
781+
FC+="[${i}:v]null"
782+
fi
783+
if [ "$CLIP_RENDER_SPEED" != "1" ]; then
784+
FC+=",setpts=${CLIP_RENDER_SPEED}*PTS"
785+
fi
786+
FC+="[v${i}];"
787+
if [ -n "$ATEMPO_CHAIN" ]; then
788+
FC+="[${i}:a]${ATEMPO_CHAIN}[a${i}];"
789+
fi
790+
done
791+
792+
# Final concat: per-segment polished streams + raw outro streams.
793+
for i in $(seq 0 $((CAP_SEG_COUNT - 1))); do
794+
FC+="[v${i}]"
795+
if [ -n "$ATEMPO_CHAIN" ]; then
796+
FC+="[a${i}]"
797+
else
798+
FC+="[${i}:a]"
799+
fi
800+
done
801+
FC+="[${CAP_SEG_COUNT}:v][${CAP_SEG_COUNT}:a]"
802+
FC+="concat=n=${SEG_COUNT}:v=1:a=1[v][a]"
803+
fi
804+
684805
if ! ffmpeg -y -hide_banner -loglevel warning \
685806
"${CONCAT_INPUTS[@]}" \
686807
-filter_complex "$FC" \
@@ -742,25 +863,32 @@ THUMB_DURATION_SECS=$(awk -v ms="$REAL_DURATION_MS" 'BEGIN{printf "%.3f", ms/100
742863
if awk -v d="$THUMB_DURATION_SECS" -v t="$THUMB_SEEK_SECS" 'BEGIN{exit !(d <= t)}'; then
743864
THUMB_SEEK_SECS=$(awk -v d="$THUMB_DURATION_SECS" 'BEGIN{printf "%.3f", d/2}')
744865
fi
745-
if ffmpeg -y -hide_banner -loglevel warning \
746-
-ss "$THUMB_SEEK_SECS" -i "$CLIP_OUT_FILE" -frames:v 1 -q:v 3 \
747-
"$CLIP_THUMB_FILE" 2>/dev/null \
748-
&& [ -s "$CLIP_THUMB_FILE" ]; then
749-
THUMB_URL="${STATUS_API_BASE}/clip-renders/${CLIP_RENDER_JOB_ID}/thumbnail"
750-
say "POST $THUMB_URL"
751-
if ! curl --fail --silent --show-error \
752-
--max-time 60 \
753-
--header "x-origin-auth: ${CLIP_RENDER_JOB_ID}:${CLIP_RENDER_TOKEN}" \
754-
--header "content-type: image/jpeg" \
755-
--data-binary "@${CLIP_THUMB_FILE}" \
756-
--output /dev/null \
757-
"$THUMB_URL"; then
758-
say "WARN thumbnail upload failed — continuing without thumbnail"
866+
867+
# Thumbnail extract + POST runs in parallel with the clip upload —
868+
# both read $CLIP_OUT_FILE independently. The thumb POST is
869+
# best-effort (no die_failed), so failures only warn.
870+
THUMB_URL="${STATUS_API_BASE}/clip-renders/${CLIP_RENDER_JOB_ID}/thumbnail"
871+
say "thumbnail extract + POST $THUMB_URL (background)"
872+
(
873+
if ffmpeg -y -hide_banner -loglevel warning \
874+
-ss "$THUMB_SEEK_SECS" -i "$CLIP_OUT_FILE" -frames:v 1 -q:v 3 \
875+
"$CLIP_THUMB_FILE" 2>/dev/null \
876+
&& [ -s "$CLIP_THUMB_FILE" ]; then
877+
if ! curl --fail --silent --show-error \
878+
--max-time 60 \
879+
--header "x-origin-auth: ${CLIP_RENDER_JOB_ID}:${CLIP_RENDER_TOKEN}" \
880+
--header "content-type: image/jpeg" \
881+
--data-binary "@${CLIP_THUMB_FILE}" \
882+
--output /dev/null \
883+
"$THUMB_URL"; then
884+
say "WARN thumbnail upload failed — continuing without thumbnail"
885+
fi
886+
else
887+
say "WARN ffmpeg thumbnail extraction failed — continuing without thumbnail"
759888
fi
760-
else
761-
say "WARN ffmpeg thumbnail extraction failed — continuing without thumbnail"
762-
fi
763-
rm -f "$CLIP_THUMB_FILE"
889+
rm -f "$CLIP_THUMB_FILE"
890+
) &
891+
THUMB_BG_PID=$!
764892

765893
api_status "status=uploading" "progress=0.0"
766894
UPLOAD_URL="${STATUS_API_BASE}/clip-renders/${CLIP_RENDER_JOB_ID}/upload"
@@ -776,6 +904,10 @@ if ! curl --fail --silent --show-error \
776904
die_failed "clip upload failed"
777905
fi
778906

907+
# Thumbnail is best-effort but we still want it posted before the
908+
# pod exits (batch mode reaps the job right after status=done).
909+
wait "$THUMB_BG_PID" 2>/dev/null || true
910+
779911
api_status "status=done" "progress=1.0"
780912
CLIP_REACHED_TERMINAL=1
781913
rm -f "$CLIP_OUT_FILE"

src/lib/steam.sh

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,15 +1192,17 @@ wait_for_steam_pipe() {
11921192
# - first 90s, every 5s: poke_steam_dialog (Space-press the focused
11931193
# button on any modal CEF dialog Steam pops — cloud-out-of-date,
11941194
# shader pre-cache, etc).
1195-
# - every 30s, up to 4 retries: re-invoke <applaunch_fn>. Steam
1196-
# sometimes silently drops the very first applaunch on a cold
1197-
# login (logs "Steam is already running, command line was
1198-
# forwarded" but no cs2 follows). One retry was the original
1199-
# fallback; bumping it to 4 spaced-out retries covers the cases
1200-
# where Steam is still doing first-cold init (auth refresh,
1201-
# manifest sync) past the 30s mark — observed in the wild on a
1202-
# pod where cs2 only spawned after Steam finished its background
1203-
# update check ~2 min in.
1195+
# - first retry at 8s, then every 30s, up to 4 retries: re-invoke
1196+
# <applaunch_fn>. Steam sometimes silently drops the very first
1197+
# applaunch on a cold login (logs "Steam is already running,
1198+
# command line was forwarded" but no cs2 follows). The 8s first
1199+
# retry covers that fast-drop path — empirically Steam either
1200+
# spawns cs2 within ~7s of a successful applaunch or never does.
1201+
# The 30s back-off on later retries covers the slower cases where
1202+
# Steam is still doing first-cold init (auth refresh, manifest
1203+
# sync) past the 30s mark — observed in the wild on a pod where
1204+
# cs2 only spawned after Steam finished its background update
1205+
# check ~2 min in.
12041206
# - at 60s/120s/180s: dump open X windows + console-linux.txt tail
12051207
# so a future failure leaves evidence (which dialog was up, what
12061208
# Steam was doing) instead of a silent 5-min wait.
@@ -1211,6 +1213,7 @@ wait_for_steam_pipe() {
12111213
wait_for_cs2_process() {
12121214
local applaunch_fn="${1:?applaunch function name required}"
12131215
local relaunch_count=0
1216+
local next_retry_at=8
12141217
local pid="" i
12151218

12161219
for i in $(seq 1 "$CS2_LAUNCH_TIMEOUT"); do
@@ -1226,10 +1229,11 @@ wait_for_cs2_process() {
12261229

12271230
[ $(( i % 15 )) -eq 0 ] && log " ${i}s elapsed waiting on cs2..."
12281231

1229-
if [ $(( i % 30 )) -eq 0 ] && [ "$relaunch_count" -lt 4 ]; then
1232+
if [ "$i" -ge "$next_retry_at" ] && [ "$relaunch_count" -lt 4 ]; then
12301233
relaunch_count=$(( relaunch_count + 1 ))
12311234
log " ${i}s without cs2 — re-issuing -applaunch (retry ${relaunch_count}/4)"
12321235
"$applaunch_fn"
1236+
next_retry_at=$(( i + 30 ))
12331237
fi
12341238

12351239
case "$i" in

0 commit comments

Comments
 (0)