Add lab restream pipeline with pirate_drift logo and lib/env.sh.

Encode-once/fanout scripts, HLS output, and single overloadable env source.
Commits assets/pirate_drift.png as default watermark for cluster runs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Anton Afanasyeu
2026-07-11 15:27:24 +02:00
parent d990862b64
commit 5ca8a9f539
27 changed files with 911 additions and 1 deletions

118
lib/source.sh Normal file
View File

@@ -0,0 +1,118 @@
#!/usr/bin/env bash
# Resolve pipeline input from CLI arg, sources.list index, or env SOURCE_URL.
set -euo pipefail
# shellcheck disable=SC1091
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
SOURCE_URL=""
SOURCE_FMT=""
SOURCE_VIDEO_STREAM="v:0"
SOURCE_AUDIO_STREAM="a:0"
AUDIO_INPUT_ARGS=()
USE_YTDLP=0
is_page_url() {
local url="$1"
[[ "$url" =~ ^https?:// ]] || return 1
[[ "$url" =~ \.(m3u8|mpd|mp4|mkv|mov|ts|flv)(\?|$) ]] && return 1
[[ -f "$url" ]] && return 1
return 0
}
resolve_source() {
local arg="${1:-}"
SOURCE_URL="${SOURCE_URL:-}"
SOURCE_FMT=""
if [[ -n "${SOURCE_URL}" ]]; then
:
elif [[ -n "${arg}" && -f "${arg}" && "${arg}" != *".list" ]]; then
SOURCE_URL="${arg}"
elif [[ -n "${arg}" && "${arg}" =~ ^https?:// ]]; then
SOURCE_URL="${arg}"
elif [[ -n "${arg}" && "${arg}" =~ ^[0-9]+$ ]]; then
if [[ ! -f "${SOURCES_LIST}" ]]; then
die "sources list missing: ${SOURCES_LIST} (copy sources.example.list)"
fi
local line
line="$(sed -n "${arg}p" "${SOURCES_LIST}" | sed 's/#.*//' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
[[ -n "${line}" ]] || die "no source on line ${arg} of ${SOURCES_LIST}"
SOURCE_URL="$(awk '{print $1}' <<<"${line}")"
SOURCE_FMT="$(awk '{print $2}' <<<"${line}")"
elif [[ -f "${SOURCES_LIST}" ]]; then
local line
line="$(grep -v '^[[:space:]]*#' "${SOURCES_LIST}" | grep -v '^[[:space:]]*$' | head -1)"
SOURCE_URL="$(awk '{print $1}' <<<"${line}")"
SOURCE_FMT="$(awk '{print $2}' <<<"${line}")"
else
die "usage: pass URL, file path, or 1-based index into ${SOURCES_LIST}"
fi
[[ -n "${SOURCE_URL}" ]] || die "empty source URL"
if [[ -n "${AUDIO_SOURCE:-}" ]]; then
read_rate_flag
AUDIO_INPUT_ARGS=("${RE_FLAG[@]}" -thread_queue_size "${THREAD_QUEUE_SIZE}" -i "${AUDIO_SOURCE}")
SOURCE_AUDIO_STREAM="a:0"
fi
if [[ "${YTDLP_ENABLED}" == "1" ]] && is_page_url "${SOURCE_URL}"; then
USE_YTDLP=1
fi
echo "${SOURCE_URL}" > "${ROOT}/.current_source"
log "source: ${SOURCE_URL} (ytdlp=${USE_YTDLP})"
}
ytdlp_auth_args() {
local auth="${ROOT}/config/yt-dlp.auth.env"
if [[ -f "${auth}" ]]; then
# shellcheck disable=SC1090
source "${auth}"
fi
local args=()
[[ -n "${YTDLP_USERNAME:-}" ]] && args+=(--username "${YTDLP_USERNAME}")
[[ -n "${YTDLP_PASSWORD:-}" ]] && args+=(--password "${YTDLP_PASSWORD}")
[[ -n "${YTDLP_NETRC:-}" ]] && args+=(--netrc)
[[ -n "${YTDLP_EXTRA_ARGS:-}" ]] && args+=(${YTDLP_EXTRA_ARGS})
printf '%s\n' "${args[@]}"
}
build_ytdlp_pipeline() {
local fmt_args=()
[[ -n "${SOURCE_FMT}" ]] && fmt_args=(-f "${SOURCE_FMT}")
mapfile -t auth < <(ytdlp_auth_args)
printf '%s\0' "${YTDLP_BIN}" \
-o - --no-part --no-live-from-start --no-write-subs --no-embed-subs \
--remux-video mkv --merge-output-format mkv --no-audio-multistreams \
--geo-bypass --ignore-errors --hls-use-mpegts \
"${auth[@]}" \
"${fmt_args[@]}" \
"${SOURCE_URL}"
}
# Returns ffmpeg input argv: either direct -i URL or pipe from yt-dlp.
build_primary_input() {
read_rate_flag
if [[ "${USE_YTDLP}" == "1" ]]; then
require_cmd "${YTDLP_BIN}"
printf '%s\0' pipe
printf '%s\0' -thread_queue_size "${THREAD_QUEUE_SIZE}" -i pipe:0
return
fi
local url="${SOURCE_URL}"
if [[ "${url}" == *.jpg || "${url}" == *.jpeg || "${url}" == *.png ]]; then
printf '%s\0' direct
printf '%s\0' "${RE_FLAG[@]}" -thread_queue_size "${THREAD_QUEUE_SIZE}" \
-loop 1 -i "${url}" -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=48000
return
fi
printf '%s\0' direct
printf '%s\0' "${RE_FLAG[@]}" -thread_queue_size "${THREAD_QUEUE_SIZE}" \
-reconnect_on_network_error 1 -reconnect_streamed 1 -reconnect_delay_max 5 \
-i "${url}"
}