Sweep/vgdeband: cap x264 at ENCODE_THREADS (2) so conversions stop starving Jellyfin and the live portal

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-11-01 19:30:29 -06:00
parent f32730ba94
commit 88bfda4c9d
4 changed files with 9 additions and 4 deletions

View File

@@ -408,8 +408,9 @@ Where the time comes from, best first:
start and the sidecar records drift over the file. **Then the band is cropped off**: the sweep re-encodes the start and the sidecar records drift over the file. **Then the band is cropped off**: the sweep re-encodes the
stream (libx264 CRF 18, frame timing preserved, audio copied) so the published file is the game image + audio + stream (libx264 CRF 18, frame timing preserved, audio copied) so the published file is the game image + audio +
the `tmcd` track and nothing else; the raw with the band is deleted (`KEEP_RAW_STREAMS=1` keeps it in `.src/`). the `tmcd` track and nothing else; the raw with the band is deleted (`KEEP_RAW_STREAMS=1` keeps it in `.src/`).
Streams without the band are remuxed losslessly. Cost: a 3 h 1440p60 night is a few hours of CPU on the Streams without the band are remuxed losslessly. Cost: every re-encode (streams and clips) runs x264 on
E-2136; the sweep is niced. OBS: dock → *Timecode band* → `ENCODE_THREADS` threads (2, set in `systemd/normalize-sweep.service`) so Jellyfin and live viewers stay
responsive; a 3 h 1440p60 night therefore takes most of the next day to publish. OBS: dock → *Timecode band* →
"Add band + clock source" (canvas grows by W/160 rows, a locked browser source `Vanguard Clock` → "Add band + clock source" (canvas grows by W/160 rows, a locked browser source `Vanguard Clock` →
`https://live.BASE/dock/clock/` sits in the band; only while not streaming). Go Live: the "Timecode band" checkbox (default on). `https://live.BASE/dock/clock/` sits in the band; only while not streaming). Go Live: the "Timecode band" checkbox (default on).
2. **Server arrival time** (no band) — microseconds in the recording filename. Within ~0.1–0.3 s per streamer 2. **Server arrival time** (no band) — microseconds in the recording filename. Within ~0.1–0.3 s per streamer

View File

@@ -29,6 +29,9 @@ FILL_GAPS_MS="${FILL_GAPS_MS:-0}"
# Band removal re-encodes every stream that carried the timecode band. The raw (with band) is deleted after a # Band removal re-encodes every stream that carried the timecode band. The raw (with band) is deleted after a
# successful encode; KEEP_RAW_STREAMS=1 keeps it in the hidden .src/ instead (doubles disk per night). # successful encode; KEEP_RAW_STREAMS=1 keeps it in the hidden .src/ instead (doubles disk per night).
KEEP_RAW_STREAMS="${KEEP_RAW_STREAMS:-0}" KEEP_RAW_STREAMS="${KEEP_RAW_STREAMS:-0}"
# Encoder threads for every re-encode (clips and streams). 2 leaves the box responsive for Jellyfin/live viewers;
# conversions take longer. systemd Environment=ENCODE_THREADS=... to change.
ENCODE_THREADS="${ENCODE_THREADS:-2}"
MAX_W=2560; MAX_H=1440; MAX_FPS=60 MAX_W=2560; MAX_H=1440; MAX_FPS=60
exec 9>"${LOCK_FILE:-/run/lock/normalize-sweep.lock}" exec 9>"${LOCK_FILE:-/run/lock/normalize-sweep.lock}"
flock -n 9 || exit 0 flock -n 9 || exit 0
@@ -166,7 +169,7 @@ PYEOF
if (( live )) && [[ $reason != *audio=* ]]; then aargs=(-c:a copy); fi if (( live )) && [[ $reason != *audio=* ]]; then aargs=(-c:a copy); fi
nice -n 15 ionice -c 3 ffmpeg -nostdin -hide_banner -loglevel error -y -i "$src" \ nice -n 15 ionice -c 3 ffmpeg -nostdin -hide_banner -loglevel error -y -i "$src" \
-map 0:v:0 -map '0:a?' \ -map 0:v:0 -map '0:a?' \
-c:v libx264 -preset veryfast -crf 18 -maxrate 30M -bufsize 60M -pix_fmt yuv420p -profile:v high \ -c:v libx264 -preset veryfast -crf 18 -maxrate 30M -bufsize 60M -pix_fmt yuv420p -profile:v high -threads "$ENCODE_THREADS" \
-vf "$vf" -fps_mode "$fpsmode" "${fpsargs[@]}" \ -vf "$vf" -fps_mode "$fpsmode" "${fpsargs[@]}" \
"${aargs[@]}" \ "${aargs[@]}" \
"${tcargs[@]}" -movflags +faststart -f mp4 "$tmp" "${tcargs[@]}" -movflags +faststart -f mp4 "$tmp"

View File

@@ -27,7 +27,7 @@ print(d.get("source","none"), 1 if d.get("band_removed") else 0, d.get("timecode
log "deband $f (${w}px wide -> crop ${band} rows; tc=$tc)" log "deband $f (${w}px wide -> crop ${band} rows; tc=$tc)"
nice -n 15 ionice -c 3 ffmpeg -nostdin -hide_banner -loglevel error -y -i "$f" \ nice -n 15 ionice -c 3 ffmpeg -nostdin -hide_banner -loglevel error -y -i "$f" \
-map 0:v:0 -map '0:a?' \ -map 0:v:0 -map '0:a?' \
-c:v libx264 -preset veryfast -crf 18 -maxrate 30M -bufsize 60M -pix_fmt yuv420p -profile:v high \ -c:v libx264 -preset veryfast -crf 18 -maxrate 30M -bufsize 60M -pix_fmt yuv420p -profile:v high -threads "${ENCODE_THREADS:-2}" \
-vf "crop=iw:ih-${band}:0:0" -fps_mode passthrough -c:a copy \ -vf "crop=iw:ih-${band}:0:0" -fps_mode passthrough -c:a copy \
"${tcargs[@]}" -movflags +faststart -f mp4 "$tmp" "${tcargs[@]}" -movflags +faststart -f mp4 "$tmp"
rc=$?; d1=$(dur "$f"); d2=$(dur "$tmp") rc=$?; d1=$(dur "$f"); d2=$(dur "$tmp")

View File

@@ -8,4 +8,5 @@ Type=oneshot
ExecStart=/usr/local/bin/normalize-sweep.sh ExecStart=/usr/local/bin/normalize-sweep.sh
Nice=15 Nice=15
IOSchedulingClass=idle IOSchedulingClass=idle
Environment=ENCODE_THREADS=2 # x264 threads per conversion; keeps Jellyfin and the live portal responsive (default 2)
# Environment=FILL_GAPS_MS=1000 # opt-in: CFR re-encode streams whose dropped-frame holes total >= 1 s (hours of CPU on this box) # Environment=FILL_GAPS_MS=1000 # opt-in: CFR re-encode streams whose dropped-frame holes total >= 1 s (hours of CPU on this box)