consolidate-watch.sh
Rama main · réplica de lectura del repositorio original.
#!/bin/bash
# inotify watcher (close_write / moved_to): runs the pipeline on new videos, and on the sibling
# video when a sidecar .srt/.ass/.vtt arrives.
set -u
_here="$(cd "$(dirname "$0")" && pwd)"
PATH="$_here/.venv/bin:$PATH" # the stack runs from the repo venv
# shellcheck source=lib/paths.sh
. "$_here/lib/paths.sh"
ROOT="$MEDIA_ROOT"
SCRIPT="$MEDIA_STACK_ROOT/consolidate-subs.py"
LOG="$VAR_LOG/consolidate-watch.log"
COOLDOWN=15
STATE_DEBOUNCE_DIR="$VAR_RUN/consolidate-watch-debounce"
mkdir -p "$STATE_DEBOUNCE_DIR"
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "$LOG"; }
trigger() {
local video="$1" reason="$2"
[ -f "$video" ] || return 0
local key
key=$(echo -n "$video" | md5sum | awk '{print $1}')
local debfile="$STATE_DEBOUNCE_DIR/$key"
if [ -f "$debfile" ]; then
local age=$(( $(date +%s) - $(stat -c %Y "$debfile" 2>/dev/null || echo 0) ))
[ "$age" -lt 60 ] && return 0
fi
touch "$debfile"
(
sleep "$COOLDOWN"
[ -f "$video" ] || exit 0
local s1 s2
s1=$(stat -c %s "$video" 2>/dev/null || echo 0)
sleep 5
s2=$(stat -c %s "$video" 2>/dev/null || echo 0)
[ "$s1" != "$s2" ] && { log "still writing, skip: $video"; exit 0; }
local out
out=$(python3 "$SCRIPT" "$video" 2>&1 | tail -1)
log "[$reason] $out"
) &
}
log "watcher start: ROOT=$ROOT"
# --exclude keeps the watcher out of $ROOT/.dupe-recycle/ (re-normalizing files about to be
# purged cascades); the case-guard below is belt-and-suspenders.
inotifywait -m -r -e close_write,moved_to --exclude '/\.dupe-recycle(/|$)' \
--format '%w%f|%e' "$ROOT" 2>>"$LOG" |
while IFS='|' read -r path event; do
case "$path" in
*.consol.*.tmp.mkv|*.tmp.mkv|*.partial|*.consolidate-subs.state.*) continue ;;
*/.dupe-recycle/*) continue ;;
*/.normalize-tmp/*) continue ;;
esac
case "${path,,}" in
*.mkv|*.mp4)
log "video event: $path ($event)"
trigger "$path" "video-import"
;;
*.srt|*.ass|*.ssa|*.vtt)
dir=$(dirname "$path")
base=$(basename "$path")
# "Show.S01E01.en.cc.srt" -> "Show.S01E01": peel up to 3 trailing lang/forced/sdh/
# generated/dub tags ("generated" = sub-fallback.py's Whisper sidecar, "dub" = the
# subtitle for the English dub audio). A video named exactly like the stem at a step
# wins and stops the peeling ("The.Generated.mkv" is never cut). A video takes the
# sidecar only under media_stack.subtitles's rule (find_sidecar_subs and
# find_dub_sidecars): no tags left after its stem, or an English one (has_en: any tag
# starting with "en") among them.
stem="${base%.*}"
found=""
has_en=0
for step in 0 1 2 3; do
if [ "$step" -eq 0 ] || [ "$has_en" -eq 1 ]; then
for ext in mkv mp4; do
if [ -f "$dir/$stem.$ext" ]; then
found="$dir/$stem.$ext"
break 2
fi
done
fi
[ "$step" -eq 3 ] && break
last="${stem##*.}"
ll="${last,,}"
if [[ "$ll" == en* || "$ll" =~ ^(cc|sdh|forced|hi|generated|dub)$ ]]; then
if [[ "$ll" == en* ]]; then
has_en=1
fi
stem="${stem%.*}"
else
break
fi
done
if [ -n "$found" ]; then
log "sidecar event: $path → $found ($event)"
trigger "$found" "sidecar-arrival"
fi
;;
esac
done