Sweep: crop the timecode band off published streams (re-encode after decode); vgdeband.sh catch-up
The published recording is the game picture + audio + tmcd track, nothing else. Streams without the band are still remuxed losslessly. vgstrip plan exports TC_BAND and marks the sidecar band_removed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
43
bin/vgdeband.sh
Executable file
43
bin/vgdeband.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
# One-off / catch-up: remove the timecode overscan band from stream recordings that were published BEFORE the
|
||||
# sweep learned to crop it. Re-encodes (libx264, frame timing preserved, audio copied), re-applies the timecode
|
||||
# from the sidecar, replaces the file, marks the sidecar band_removed. Skips files whose sidecar already says so.
|
||||
# vgdeband.sh all of /srv/media/streams/*/*.mp4 with a strip sidecar
|
||||
# vgdeband.sh FILE.mp4 ... just these
|
||||
# Long: a 3 h 1440p60 night is a few hours of CPU. Run detached: sudo systemd-run --unit vgdeband -p Nice=15 \
|
||||
# -p IOSchedulingClass=idle /usr/local/bin/vgdeband.sh ; follow with journalctl -fu vgdeband
|
||||
set -uo pipefail
|
||||
ROOT="${MEDIA_ROOT:-/srv/media}"
|
||||
log() { echo "$(date '+%F %T') $*"; }
|
||||
dur() { ffprobe -v error -show_entries format=duration -of csv=p=0 "$1" 2>/dev/null | cut -d. -f1; }
|
||||
if (( $# )); then files=("$@"); else mapfile -t files < <(find "$ROOT/streams" -mindepth 2 -maxdepth 2 -type f -name '*.mp4' | sort); fi
|
||||
for f in "${files[@]}"; do
|
||||
side="${f%.*}.json"
|
||||
[[ -f $side ]] || { log "skip $f (no sidecar)"; continue; }
|
||||
read -r src removed tc ctime < <(python3 -c '
|
||||
import json,sys; d=json.load(open(sys.argv[1]))
|
||||
print(d.get("source","none"), 1 if d.get("band_removed") else 0, d.get("timecode",""), d.get("timestamp",""))' "$side")
|
||||
[[ $src == strip ]] || { log "skip $f (source=$src)"; continue; }
|
||||
(( removed )) && { log "skip $f (band already removed)"; continue; }
|
||||
fuser -s "$f" 2>/dev/null && { log "skip $f (in use)"; continue; }
|
||||
w=$(ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=p=0 "$f")
|
||||
band=$(python3 /usr/local/bin/vgstrip.py band "$w")
|
||||
tmp="$f.part"
|
||||
tcargs=(); [[ -n $tc ]] && tcargs=(-timecode "$tc" -write_tmcd 1); [[ -n $ctime ]] && tcargs+=(-metadata "creation_time=$ctime")
|
||||
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" \
|
||||
-map 0:v:0 -map '0:a?' \
|
||||
-c:v libx264 -preset veryfast -crf 18 -maxrate 30M -bufsize 60M -pix_fmt yuv420p -profile:v high \
|
||||
-vf "crop=iw:ih-${band}:0:0" -fps_mode passthrough -c:a copy \
|
||||
"${tcargs[@]}" -movflags +faststart -f mp4 "$tmp"
|
||||
rc=$?; d1=$(dur "$f"); d2=$(dur "$tmp")
|
||||
if (( rc == 0 )) && [[ -n $d1 && -n $d2 ]] && (( d2 >= d1 - 2 && d2 <= d1 + 2 )); then
|
||||
mv -f "$tmp" "$f"; chown 1000:1000 "$f" 2>/dev/null
|
||||
python3 -c '
|
||||
import json,sys; p=sys.argv[1]; d=json.load(open(p)); d["band_rows"]=int(sys.argv[2]); d["band_removed"]=True
|
||||
json.dump(d, open(p,"w"))' "$side" "$band"; chown 1000:1000 "$side" 2>/dev/null
|
||||
log "done $f"
|
||||
else
|
||||
rm -f "$tmp"; log "FAILED $f rc=$rc src_dur=${d1:-?} out_dur=${d2:-?} (file left untouched)" >&2
|
||||
fi
|
||||
done
|
||||
Reference in New Issue
Block a user