Phase 12: frame-accurate timecode via an overscan band — vgstrip encoder/decoder, sweep writes tmcd + creation_time + sidecar, dumpd /time, Caddy /time routes, MediaMTX microsecond names, /dock/clock/ page, dock band setup via obs-websocket, Go Live band composite + toggle, portal crops the band
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -104,9 +104,32 @@ while IFS= read -r -d '' src; do
|
||||
fi
|
||||
tmp="$out.part"
|
||||
log "$verdict $src ($reason)"
|
||||
tcargs=(); tcjson=""
|
||||
if (( live )); then
|
||||
# timecode: decode the overscan strip (server clock) or fall back to the server arrival time in the filename
|
||||
stem="$(basename "${src%.*}")"
|
||||
arrival_ms=$(python3 - "$stem" <<'PYEOF'
|
||||
import re, sys
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
m = re.search(r"(\d{4})-(\d{2})-(\d{2})_(\d{2})-(\d{2})-(\d{2})(?:-(\d{1,6}))?", sys.argv[1])
|
||||
if not m: sys.exit(0)
|
||||
y, mo, d, h, mi, s, us = m.groups()
|
||||
us = int((us or "0").ljust(6, "0"))
|
||||
t = datetime(int(y), int(mo), int(d), int(h), int(mi), int(s), us, tzinfo=ZoneInfo("America/New_York"))
|
||||
print(int(t.timestamp() * 1000))
|
||||
PYEOF
|
||||
)
|
||||
eval "$(python3 /usr/local/bin/vgstrip.py plan "$src" "${arrival_ms:-}" 2>/dev/null || true)"
|
||||
if [[ -n ${TC_TIMECODE:-} ]]; then
|
||||
tcargs=(-timecode "$TC_TIMECODE" -metadata "creation_time=$TC_CREATION" -write_tmcd 1)
|
||||
tcjson="${TC_JSON:-}"
|
||||
log "tc $src source=$TC_SOURCE timecode=$TC_TIMECODE"
|
||||
fi
|
||||
fi
|
||||
if [[ $verdict == remux ]]; then
|
||||
nice -n 15 ionice -c 3 ffmpeg -nostdin -hide_banner -loglevel error -y -i "$src" \
|
||||
-map 0 -c copy -movflags +faststart -f mp4 "$tmp"
|
||||
-map 0 -c copy "${tcargs[@]}" -movflags +faststart -f mp4 "$tmp"
|
||||
else
|
||||
# scale down into the cap (never up), keep aspect, even dimensions; cap fps; CFR; all audio -> AAC
|
||||
vf="scale='min(iw,$MAX_W)':'min(ih,$MAX_H)':force_original_aspect_ratio=decrease:force_divisible_by=2"
|
||||
@@ -117,13 +140,16 @@ while IFS= read -r -d '' src; do
|
||||
-c:v libx264 -preset veryfast -crf 18 -maxrate 30M -bufsize 60M -pix_fmt yuv420p -profile:v high \
|
||||
-vf "$vf" -fps_mode cfr "${fpsargs[@]}" \
|
||||
-c:a aac -b:a 160k \
|
||||
-movflags +faststart -f mp4 "$tmp"
|
||||
"${tcargs[@]}" -movflags +faststart -f mp4 "$tmp"
|
||||
fi
|
||||
rc=$?
|
||||
d1=$(dur "$src"); d2=$(dur "$tmp")
|
||||
if (( rc == 0 )) && [[ -n $d1 && -n $d2 ]] && (( d2 >= d1 - 2 && d2 <= d1 + 2 )); then
|
||||
if (( live )); then
|
||||
mv -f "$tmp" "$out"; chown 1000:1000 "$out" 2>/dev/null
|
||||
if [[ -n $tcjson ]]; then # sidecar: exact capture time (read by the Jellyfin plugin) + drift samples
|
||||
printf '%s\n' "$tcjson" > "${out%.*}.json"; chown 1000:1000 "${out%.*}.json" 2>/dev/null
|
||||
fi
|
||||
rm -f "$src"; rmdir "$(dirname "$src")" "$titledir" 2>/dev/null || true # drop .live and the empty title folder
|
||||
log "done $out"; continue
|
||||
elif (( synced )); then
|
||||
|
||||
Reference in New Issue
Block a user