From 5ca8a9f539aed3d0ecd5934557a9c37464c3be3f Mon Sep 17 00:00:00 2001 From: Anton Afanasyeu Date: Sat, 11 Jul 2026 15:27:24 +0200 Subject: [PATCH] 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 --- .gitignore | 10 +++ README.md | 34 +++++++++- assets/README.txt | 3 + assets/pirate_drift.png | Bin 0 -> 94590 bytes bin/encode-once.sh | 82 +++++++++++++++++++++++ bin/encode-only.sh | 81 ++++++++++++++++++++++ bin/fanout-copy.sh | 36 ++++++++++ bin/install-nginx-hls.sh | 8 +++ bin/probe-source.sh | 51 ++++++++++++++ bin/run-encode-only.sh | 23 +++++++ bin/run.sh | 42 ++++++++++++ bin/stop.sh | 27 ++++++++ bin/switch-source.sh | 50 ++++++++++++++ config/local.env.example | 6 ++ config/sinks.example.list | 5 ++ config/sinks.list | 14 ++++ config/sources.example.list | 6 ++ config/sources.list | 13 ++++ config/yt-dlp.auth.example.env | 7 ++ lib/common.sh | 98 +++++++++++++++++++++++++++ lib/env.sh | 65 ++++++++++++++++++ lib/filters.sh | 45 +++++++++++++ lib/logging.sh | 6 ++ lib/outputs.sh | 68 +++++++++++++++++++ lib/source.sh | 118 +++++++++++++++++++++++++++++++++ nginx/hls-location.conf | 14 ++++ storage/.gitkeep | 0 27 files changed, 911 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 assets/README.txt create mode 100644 assets/pirate_drift.png create mode 100755 bin/encode-once.sh create mode 100755 bin/encode-only.sh create mode 100755 bin/fanout-copy.sh create mode 100755 bin/install-nginx-hls.sh create mode 100755 bin/probe-source.sh create mode 100755 bin/run-encode-only.sh create mode 100755 bin/run.sh create mode 100755 bin/stop.sh create mode 100755 bin/switch-source.sh create mode 100644 config/local.env.example create mode 100644 config/sinks.example.list create mode 100644 config/sinks.list create mode 100644 config/sources.example.list create mode 100644 config/sources.list create mode 100644 config/yt-dlp.auth.example.env create mode 100644 lib/common.sh create mode 100644 lib/env.sh create mode 100644 lib/filters.sh create mode 100644 lib/logging.sh create mode 100644 lib/outputs.sh create mode 100644 lib/source.sh create mode 100644 nginx/hls-location.conf create mode 100644 storage/.gitkeep diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f421b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +storage/* +!storage/.gitkeep +run/ +*.log +config/local.env +config/yt-dlp.auth.env +.stream_pipe +.current_source +.current_index +.intro_shown diff --git a/README.md b/README.md index 1464930..f318270 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,35 @@ # ac-ms-media-transcode -Future VOD/transcode. Remote: `git://f0xx.org/ac/ac-ms-media-transcode` +V.2x lab restream / transcode worker (F2). Shell pipeline: **encode once → HLS + optional RTMP fanout**. + +Remote: `git://f0xx.org/ac/ac-ms-media-transcode` + +## Layout + +| Path | Role | +|------|------| +| `lib/env.sh` | **Single config source** — all variables, overload via `export` or `config/local.env` | +| `assets/pirate_drift.png` | Default logo watermark (committed) | +| `bin/` | encode-once, fanout-copy, run, probe, … | +| `config/sources.list` | Lab source URLs | +| `config/sinks.list` | RTMP publish targets (fanout stage) | + +## Lab cluster + +Composed BE `config.php` (via `compose-lab-backend.sh`): + +- `media_pipelines.enabled` = **true** +- `script_root` = `/var/www/ac/workspace/ac-ms-media-transcode` +- `logo_path` = `…/assets/pirate_drift.png` +- HLS nginx alias = `/app/androidcast_project/media/hls/` → `/var/www/ac/media-pipelines/hls/` + +Console UI: `?view=media_pipelines` (global admin / `media_pipelines_*` grants). + +## Local override + +```bash +cp config/local.env.example config/local.env +./bin/run.sh 1 +``` + +All tunables live in `lib/env.sh` using `export VAR="${VAR:-default}"`. diff --git a/assets/README.txt b/assets/README.txt new file mode 100644 index 0000000..fc0d87e --- /dev/null +++ b/assets/README.txt @@ -0,0 +1,3 @@ +Default watermark: **pirate_drift.png** (committed in this repo). + +Override: `export LOGO_PATH=/other/path.png` or set in `config/local.env`. diff --git a/assets/pirate_drift.png b/assets/pirate_drift.png new file mode 100644 index 0000000000000000000000000000000000000000..1d12e188b8d5a9ca0c621e16d4d446c83a15adae GIT binary patch literal 94590 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

agqj&OL^2X&`v1S)d7fq}c3j-A%yCNX8SVqbU- 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 "${LOG_FILE}" 2>&1 +} + +cleanup() { + remove_pipe +} +trap cleanup EXIT + +log "recording → ${RECORD_PATH}" +log "pipe → ${PIPE_PATH}" +[[ -n "${HLS_OUTPUT_DIR}" ]] && log "HLS → ${HLS_OUTPUT_DIR}" + +if [[ "${USE_YTDLP}" == "1" ]]; then + require_cmd "${YTDLP_BIN}" + mapfile -d '' -t YTDLP_CMD < <(build_ytdlp_pipeline) + "${YTDLP_CMD[@]}" 2>>"${LOG_FILE}" | \ + run_ffmpeg -thread_queue_size "${THREAD_QUEUE_SIZE}" -i pipe:0 \ + "${AUDIO_INPUT_ARGS[@]}" +else + read_rate_flag + run_ffmpeg \ + "${RE_FLAG[@]}" \ + -thread_queue_size "${THREAD_QUEUE_SIZE}" \ + -reconnect_on_network_error 1 -reconnect_streamed 1 -reconnect_delay_max 5 \ + -i "${SOURCE_URL}" \ + "${AUDIO_INPUT_ARGS[@]}" +fi diff --git a/bin/encode-only.sh b/bin/encode-only.sh new file mode 100755 index 0000000..785b61f --- /dev/null +++ b/bin/encode-only.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# Encode once → local recording (+ optional HLS). No named-pipe fanout stage. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# shellcheck disable=SC1091 +source "${ROOT}/lib/common.sh" +# shellcheck disable=SC1091 +source "${ROOT}/lib/source.sh" +# shellcheck disable=SC1091 +source "${ROOT}/lib/filters.sh" +# shellcheck disable=SC1091 +source "${ROOT}/lib/outputs.sh" + +resolve_source "${1:-}" +require_cmd "${FFMPEG_BIN}" +require_logo +ensure_storage + +compute_layout() { + VIDEO_INPUT="0:v" + if [[ ${#AUDIO_INPUT_ARGS[@]} -gt 0 ]]; then + AUDIO_MAP_INPUT=1 + LOGO_INPUT=2 + else + AUDIO_MAP_INPUT=0 + LOGO_INPUT=1 + fi +} + +compute_layout +RECORD_PATH="$(next_record_path)" +LOG_FILE="${STORAGE_DIR}/encode.log" +build_filter_complex "${VIDEO_INPUT}" "${LOGO_INPUT}:v" +MAP_V="$(map_video_out)" +MAP_A="$(map_audio_out)" + +build_record_tee() { + local record="$1" + local parts=() + parts+=("$(file_tee_entry "${record}")") + if [[ -n "${HLS_OUTPUT_DIR}" ]]; then + parts+=("$(hls_tee_entry "${HLS_OUTPUT_DIR}")") + fi + local IFS='|' + echo "${parts[*]}" +} + +TEE_SPEC="$(build_record_tee "${RECORD_PATH}")" + +run_ffmpeg() { + "${FFMPEG_BIN}" -hide_banner -loglevel info \ + -rtbufsize 256M -probesize 100M -analyzeduration 250M \ + "$@" \ + -i "${LOGO_PATH}" \ + "${FFMPEG_TS_FLAGS[@]}" \ + -filter_complex "${FILTER_COMPLEX}" \ + -map "${MAP_V}" -map "${MAP_A}" \ + -threads "${THREADS}" -framerate "${FPS}" -r "${FPS}" \ + -s "${RES}" ${VCODEC} ${PRESET} ${BITRATE} ${ACODEC} -bufsize "${BUFSIZE}" \ + -f tee "${TEE_SPEC}" >> "${LOG_FILE}" 2>&1 +} + +log "encode-only → ${RECORD_PATH}" +[[ -n "${HLS_OUTPUT_DIR}" ]] && log "HLS → ${HLS_OUTPUT_DIR}" + +if [[ "${USE_YTDLP}" == "1" ]]; then + require_cmd "${YTDLP_BIN}" + mapfile -d '' -t YTDLP_CMD < <(build_ytdlp_pipeline) + "${YTDLP_CMD[@]}" 2>>"${LOG_FILE}" | \ + run_ffmpeg -thread_queue_size "${THREAD_QUEUE_SIZE}" -i pipe:0 \ + "${AUDIO_INPUT_ARGS[@]}" +else + read_rate_flag + run_ffmpeg \ + "${RE_FLAG[@]}" \ + -thread_queue_size "${THREAD_QUEUE_SIZE}" \ + -reconnect_on_network_error 1 -reconnect_streamed 1 -reconnect_delay_max 5 \ + -i "${SOURCE_URL}" \ + "${AUDIO_INPUT_ARGS[@]}" +fi diff --git a/bin/fanout-copy.sh b/bin/fanout-copy.sh new file mode 100755 index 0000000..22a6182 --- /dev/null +++ b/bin/fanout-copy.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Fan-out: read pre-encoded FLV from pipe → RTMP sinks (stream copy, no re-encode). +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# shellcheck disable=SC1091 +source "${ROOT}/lib/common.sh" +# shellcheck disable=SC1091 +source "${ROOT}/lib/outputs.sh" + +require_cmd "${FFMPEG_BIN}" +ensure_storage +ensure_pipe + +TEE_SPEC="$(build_fanout_tee_spec)" +LOG_FILE="${STORAGE_DIR}/fanout.log" +MAX_RETRIES="${FANOUT_RETRIES:-5}" +attempt=0 + +log "fanout → ${#SINKS[@]} sink(s)" + +while [[ "${attempt}" -le "${MAX_RETRIES}" ]]; do + attempt=$((attempt + 1)) + log "fanout attempt ${attempt}/${MAX_RETRIES}" + if "${FFMPEG_BIN}" -hide_banner -loglevel info \ + -thread_queue_size "${THREAD_QUEUE_SIZE}" \ + -i "${PIPE_PATH}" \ + -map 0 \ + -c copy \ + -f tee "${TEE_SPEC}" >> "${LOG_FILE}" 2>&1; then + log "fanout finished normally" + break + fi + warn "fanout ffmpeg exited — retrying after pipe flush" + sleep 1 +done diff --git a/bin/install-nginx-hls.sh b/bin/install-nginx-hls.sh new file mode 100755 index 0000000..4a0ea28 --- /dev/null +++ b/bin/install-nginx-hls.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Lab helper: print nginx fragment with HLS_ROOT substituted. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +HLS_ROOT="${1:-${ROOT}/storage/hls}" + +sed "s|@HLS_ROOT@|${HLS_ROOT}|g" "${ROOT}/nginx/hls-location.conf" diff --git a/bin/probe-source.sh b/bin/probe-source.sh new file mode 100755 index 0000000..983e0db --- /dev/null +++ b/bin/probe-source.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# ffprobe → normalized source profile (draft SPEC-1 preview). +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# shellcheck disable=SC1091 +source "${ROOT}/lib/common.sh" +# shellcheck disable=SC1091 +source "${ROOT}/lib/source.sh" + +resolve_source "${1:-}" +require_cmd "${FFPROBE_BIN}" + +probe_target="${SOURCE_URL}" +if [[ "${USE_YTDLP}" == "1" ]]; then + warn "page URL — ffprobe may fail; use a direct m3u8/mp4 URL or run encode-once" +fi + +log "probing ${probe_target}" +"${FFPROBE_BIN}" -hide_banner -loglevel error \ + -show_format -show_streams -print_format json \ + "${probe_target}" | python3 -c ' +import json, sys +data = json.load(sys.stdin) +fmt = data.get("format", {}) +streams = data.get("streams", []) +v = next((s for s in streams if s.get("codec_type") == "video"), None) +a = next((s for s in streams if s.get("codec_type") == "audio"), None) +out = { + "duration": fmt.get("duration"), + "bitrate": fmt.get("bit_rate"), + "format": fmt.get("format_name"), + "video": None, + "audio": None, +} +if v: + out["video"] = { + "codec": v.get("codec_name"), + "width": v.get("width"), + "height": v.get("height"), + "fps": v.get("avg_frame_rate"), + "profile": v.get("profile"), + } +if a: + out["audio"] = { + "codec": a.get("codec_name"), + "channels": a.get("channels"), + "sample_rate": a.get("sample_rate"), + } +print(json.dumps(out, indent=2)) +' diff --git a/bin/run-encode-only.sh b/bin/run-encode-only.sh new file mode 100755 index 0000000..de4edf1 --- /dev/null +++ b/bin/run-encode-only.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# shellcheck disable=SC1091 +source "${ROOT}/lib/common.sh" + +SRC_ARG="${1:-1}" +PID_ENCODE="${RUN_DIR}/encode.pid" +LOG_RUN="${STORAGE_DIR}/run-encode-only.log" + +ensure_storage +mkdir -p "${RUN_DIR}" + +if [[ -f "${PID_ENCODE}" ]] && kill -0 "$(cat "${PID_ENCODE}")" 2>/dev/null; then + die "encode-only already running (pid $(cat "${PID_ENCODE}")). use bin/stop.sh first" +fi + +chmod +x "${ROOT}/bin/"*.sh 2>/dev/null || true +log "starting encode-only source=${SRC_ARG}" +nohup "${ROOT}/bin/encode-only.sh" "${SRC_ARG}" >> "${LOG_RUN}" 2>&1 & +echo $! > "${PID_ENCODE}" +log "encode pid $(cat "${PID_ENCODE}")" diff --git a/bin/run.sh b/bin/run.sh new file mode 100755 index 0000000..000fe1b --- /dev/null +++ b/bin/run.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Start encode-once + fanout-copy (two-stage pipeline from v0). +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# shellcheck disable=SC1091 +source "${ROOT}/lib/common.sh" + +SRC_ARG="${1:-1}" +PID_ENCODE="${RUN_DIR}/encode.pid" +PID_FANOUT="${RUN_DIR}/fanout.pid" +LOG_RUN="${STORAGE_DIR}/run.log" + +ensure_storage +mkdir -p "${RUN_DIR}" + +if [[ -f "${PID_ENCODE}" ]] && kill -0 "$(cat "${PID_ENCODE}")" 2>/dev/null; then + die "pipeline already running (encode pid $(cat "${PID_ENCODE}")). use bin/stop.sh first" +fi + +chmod +x "${ROOT}/bin/"*.sh 2>/dev/null || true + +# shellcheck disable=SC1091 +source "${ROOT}/lib/outputs.sh" +read_sinks + +log "starting pipeline source=${SRC_ARG}" +nohup "${ROOT}/bin/encode-once.sh" "${SRC_ARG}" >> "${LOG_RUN}" 2>&1 & +echo $! > "${PID_ENCODE}" +sleep 1 + +if [[ ${#SINKS[@]} -gt 0 ]]; then + nohup "${ROOT}/bin/fanout-copy.sh" >> "${LOG_RUN}" 2>&1 & + echo $! > "${PID_FANOUT}" + log "fanout pid $(cat "${PID_FANOUT}")" +else + log "no RTMP sinks configured — fanout stage skipped" + rm -f "${PID_FANOUT}" 2>/dev/null || true +fi + +log "encode pid $(cat "${PID_ENCODE}")" +log "logs: ${LOG_RUN}, ${STORAGE_DIR}/encode.log, ${STORAGE_DIR}/fanout.log" diff --git a/bin/stop.sh b/bin/stop.sh new file mode 100755 index 0000000..b910a0f --- /dev/null +++ b/bin/stop.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# shellcheck disable=SC1091 +source "${ROOT}/lib/common.sh" + +stop_pidfile() { + local pf="$1" + [[ -f "${pf}" ]] || return 0 + local pid + pid="$(cat "${pf}")" + if kill -0 "${pid}" 2>/dev/null; then + kill "${pid}" 2>/dev/null || true + sleep 1 + kill -9 "${pid}" 2>/dev/null || true + fi + rm -f "${pf}" +} + +stop_pidfile "${RUN_DIR}/fanout.pid" +stop_pidfile "${RUN_DIR}/encode.pid" +pkill -f "${ROOT}/bin/fanout-copy.sh" 2>/dev/null || true +pkill -f "${ROOT}/bin/encode-once.sh" 2>/dev/null || true +remove_pipe +rm -f "${ROOT}/.intro_shown" "${ROOT}/.current_source" 2>/dev/null || true +log "pipeline stopped" diff --git a/bin/switch-source.sh b/bin/switch-source.sh new file mode 100755 index 0000000..d27e3ad --- /dev/null +++ b/bin/switch-source.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Stop pipeline, optionally rotate source index, restart (v0 switch_quality.sh cleanup). +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +# shellcheck disable=SC1091 +source "${ROOT}/lib/common.sh" + +VARIANT="${1:-}" +SRC="${SRC:-}" + +if [[ "${VARIANT}" == "stop" ]]; then + exec "${ROOT}/bin/stop.sh" +fi + +case "${VARIANT}" in + blur|blurred) export FILTER_PROFILE=blurred ;; + nologo) export FILTER_PROFILE=nologo ;; + default|'') export FILTER_PROFILE=default ;; + encode-only|record) + export RUN_ENCODE_ONLY=1 + ;; + [0-9]*) + SRC="${VARIANT}" + ;; +esac + +if [[ -z "${SRC}" && -f "${ROOT}/.current_source" ]]; then + # rotate index in sources.list + if [[ -f "${SOURCES_LIST}" ]]; then + count="$(grep -vc '^[[:space:]]*#' "${SOURCES_LIST}" || true)" + cur=1 + [[ -f "${ROOT}/.current_index" ]] && cur="$(cat "${ROOT}/.current_index")" + cur=$((cur + 1)) + [[ "${count}" -gt 0 && "${cur}" -gt "${count}" ]] && cur=1 + echo "${cur}" > "${ROOT}/.current_index" + SRC="${cur}" + fi +fi + +SRC="${SRC:-1}" + +"${ROOT}/bin/stop.sh" +sleep 1 +log "switch → source index ${SRC} profile ${FILTER_PROFILE}" +if [[ -n "${RUN_ENCODE_ONLY:-}" || "${VARIANT}" == "encode-only" || "${VARIANT}" == "record" ]]; then + exec "${ROOT}/bin/run-encode-only.sh" "${SRC}" +fi + +exec "${ROOT}/bin/run.sh" "${SRC}" diff --git a/config/local.env.example b/config/local.env.example new file mode 100644 index 0000000..ed569c9 --- /dev/null +++ b/config/local.env.example @@ -0,0 +1,6 @@ +# Copy to config/local.env — overrides lib/env.sh defaults (sourced last among repo config). + +# HLS on disk for nginx / web player: +# HLS_OUTPUT_DIR="${ROOT}/storage/hls/live" + +# Lab RTMP sinks: edit config/sinks.list or per-pipeline work dir sinks.list diff --git a/config/sinks.example.list b/config/sinks.example.list new file mode 100644 index 0000000..f46e3b5 --- /dev/null +++ b/config/sinks.example.list @@ -0,0 +1,5 @@ +# RTMP (or other ffmpeg tee URL) destinations — fanout stage copies without re-encode. +# Lines starting with # are ignored. +# +# rtmp://127.0.0.1/live/stream_key +# rtmp://a.rtmp.youtube.com/live2/STREAM_KEY diff --git a/config/sinks.list b/config/sinks.list new file mode 100644 index 0000000..d119afc --- /dev/null +++ b/config/sinks.list @@ -0,0 +1,14 @@ +# Migrated from streaming_v0 (sanitized — replace STREAM_KEY / host for your lab). +# Active sinks for fanout-copy stage (stream copy, no re-encode). +# +# Local nginx-rtmp or MediaMTX lab ingest: +# rtmp://127.0.0.1/live/dev +# +# YouTube Live (lab only — paste stream key from YouTube Studio): +# rtmp://a.rtmp.youtube.com/live2/STREAM_KEY_HERE +# +# Twitch: +# rtmp://live.twitch.tv/app/STREAM_KEY_HERE +# +# Previous v0 hopto relay (example — do not use without your own endpoint): +# rtmp://your-relay.example.org/live/stream diff --git a/config/sources.example.list b/config/sources.example.list new file mode 100644 index 0000000..803b212 --- /dev/null +++ b/config/sources.example.list @@ -0,0 +1,6 @@ +# One source per line: URL, file path, or page URL (yt-dlp when YTDLP_ENABLED=1). +# Optional second column: yt-dlp format selector (e.g. bv*+ba/b). +# +# https://example.com/live/master.m3u8 +# /path/to/local/file.mkv +# https://www.youtube.com/watch?v=EXAMPLE bv*+ba/b diff --git a/config/sources.list b/config/sources.list new file mode 100644 index 0000000..ea65149 --- /dev/null +++ b/config/sources.list @@ -0,0 +1,13 @@ +# Migrated from streaming_v0 (sanitized — no expiring CDN tokens or credentials). +# Format: URL [yt-dlp format selector] +# +# Page URLs (YouTube, etc.) — require YTDLP_ENABLED=1 in defaults.env or local.env +https://www.youtube.com/watch?v=WQ4mwufOjEI +https://www.youtube.com/watch?v=xaiQUHz8re4 +https://www.youtube.com/watch?v=Z-QVb-3D6zA +https://www.youtube.com/watch?v=Trwer1xXYtY +https://www.youtube.com/watch?v=n2CxmPwqq8w +# +# Direct HLS / file examples (uncomment for tests) +# https://example.com/live/master.m3u8 +# /path/to/local/recording.mkv diff --git a/config/yt-dlp.auth.example.env b/config/yt-dlp.auth.example.env new file mode 100644 index 0000000..ff8e39b --- /dev/null +++ b/config/yt-dlp.auth.example.env @@ -0,0 +1,7 @@ +# Copy to config/yt-dlp.auth.env — never commit real credentials. +# Sourced only when yt-dlp needs authenticated pages (lab / non-prod). + +# YTDLP_USERNAME= +# YTDLP_PASSWORD= +# YTDLP_NETRC=1 +# YTDLP_EXTRA_ARGS=--referer https://example.com/ diff --git a/lib/common.sh b/lib/common.sh new file mode 100644 index 0000000..aff65d9 --- /dev/null +++ b/lib/common.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +# Shared bootstrap: ROOT, env defaults, encoding presets, pipe helpers. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +export ROOT + +# shellcheck disable=SC1091 +source "${ROOT}/lib/logging.sh" +# shellcheck disable=SC1091 +source "${ROOT}/lib/env.sh" + +apply_encoding_preset() { + case "${OUTPUT_PRESET}" in + 720p) + WIDTH=1280 + HEIGHT=720 + FASTNESS=veryfast + BV=2500k + BUFSIZE=5000k + ;; + 1080p|*) + WIDTH=1920 + HEIGHT=1080 + FASTNESS=faster + BV=5000k + BUFSIZE=10000k + ;; + esac + export WIDTH HEIGHT FASTNESS BV BUFSIZE + RES="${WIDTH}x${HEIGHT}" + PROFILE=high + KEYFRAMES_INTL="${FPS}" + GOP_INTL=$((KEYFRAMES_INTL * 2)) + PIX_FMT="-pix_fmt yuv420p" + PRESET="-preset ${FASTNESS} -tune zerolatency -profile:v ${PROFILE} ${PIX_FMT} -x264-params keyint=${KEYFRAMES_INTL}:min-keyint=${KEYFRAMES_INTL} -g ${GOP_INTL} -flags +global_header" + BITRATE="-b:v ${BV} -maxrate ${BV} -minrate ${BV}" + VCODEC="-c:v libx264" + ACODEC="-c:a aac -b:a 128k" + export RES PROFILE PRESET BITRATE VCODEC ACODEC +} + +apply_encoding_preset + +FFMPEG_TS_FLAGS=( + -fflags +genpts+igndts + -avoid_negative_ts make_zero + -sn + -movflags +faststart +) + +read_rate_flag() { + if [[ "${NEAR_REALTIME}" == "1" ]]; then + RE_FLAG=(-re) + else + RE_FLAG=() + fi +} + +ensure_storage() { + mkdir -p "${STORAGE_DIR}" "${RUN_DIR}" +} + +ensure_pipe() { + if [[ ! -p "${PIPE_PATH}" ]]; then + mkfifo "${PIPE_PATH}" + if command -v perl >/dev/null 2>&1; then + perl -MFcntl -e 'fcntl(STDIN, 1031, shift) or die $!' "${PIPE_FIFO_SIZE}" <> "${PIPE_PATH}" || true + fi + fi + exec 7<> "${PIPE_PATH}" +} + +remove_pipe() { + exec 7>&- 7<&- 2>/dev/null || true + rm -f "${PIPE_PATH}" 2>/dev/null || true +} + +next_record_path() { + local counter=0 name + ensure_storage + while true; do + name="$(printf "video%04d.%s" "${counter}" "${RECORD_FORMAT}")" + if [[ ! -f "${STORAGE_DIR}/${name}" ]]; then + echo "${STORAGE_DIR}/${name}" + return + fi + counter=$((counter + 1)) + done +} + +require_cmd() { + command -v "$1" >/dev/null 2>&1 || die "required command not found: $1" +} + +require_logo() { + [[ -f "${LOGO_PATH}" ]] || die "logo not found at ${LOGO_PATH} (expected ${ROOT}/assets/pirate_drift.png)" +} diff --git a/lib/env.sh b/lib/env.sh new file mode 100644 index 0000000..ca2a900 --- /dev/null +++ b/lib/env.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# Single source of truth for pipeline variables. +# Override: export VAR=value before running, or set in config/local.env (sourced last). +# +# Requires ROOT (set by lib/common.sh before this file is sourced). + +if [[ -z "${ROOT:-}" ]]; then + ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + export ROOT +fi + +# --- binaries --- +export FFMPEG_BIN="${FFMPEG_BIN:-ffmpeg}" +export FFPROBE_BIN="${FFPROBE_BIN:-ffprobe}" +export YTDLP_BIN="${YTDLP_BIN:-yt-dlp}" + +# --- paths (logo default: assets/pirate_drift.png in repo root) --- +export LOGO_PATH="${LOGO_PATH:-${ROOT}/assets/pirate_drift.png}" +export PIPE_NAME="${PIPE_NAME:-.stream_pipe}" +export PIPE_PATH="${PIPE_PATH:-${ROOT}/${PIPE_NAME}}" +export PIPE_FIFO_SIZE="${PIPE_FIFO_SIZE:-1048576}" +export STORAGE_DIR="${STORAGE_DIR:-${ROOT}/storage}" +export RUN_DIR="${RUN_DIR:-${ROOT}/run}" +export SOURCES_LIST="${SOURCES_LIST:-${ROOT}/config/sources.list}" +export SINKS_LIST="${SINKS_LIST:-${ROOT}/config/sinks.list}" +export HLS_OUTPUT_DIR="${HLS_OUTPUT_DIR:-}" + +# --- encode quality --- +export OUTPUT_PRESET="${OUTPUT_PRESET:-1080p}" +export FILTER_PROFILE="${FILTER_PROFILE:-default}" +export FPS="${FPS:-30}" +export THREADS="${THREADS:-8}" +export THREAD_QUEUE_SIZE="${THREAD_QUEUE_SIZE:-512}" +export RECORD_FORMAT="${RECORD_FORMAT:-mkv}" + +# --- ingest behaviour --- +export YTDLP_ENABLED="${YTDLP_ENABLED:-1}" +export NEAR_REALTIME="${NEAR_REALTIME:-1}" +export FANOUT_RETRIES="${FANOUT_RETRIES:-5}" + +# optional delogo rectangle (source coords): x:y:w:h +export DELOGO="${DELOGO:-}" + +# separate audio when video track has no audio +export AUDIO_SOURCE="${AUDIO_SOURCE:-}" + +# --- optional file overrides (highest priority among defaults) --- +_mp_load_env_file() { + local f="$1" + if [[ -f "${f}" ]]; then + set -a + # shellcheck disable=SC1090 + source "${f}" + set +a + fi +} +_mp_load_env_file "${ROOT}/config/local.env" + +# Re-apply path defaults if local.env used relative paths +export LOGO_PATH="${LOGO_PATH:-${ROOT}/assets/pirate_drift.png}" +export PIPE_PATH="${PIPE_PATH:-${ROOT}/${PIPE_NAME}}" +export STORAGE_DIR="${STORAGE_DIR:-${ROOT}/storage}" +export RUN_DIR="${RUN_DIR:-${ROOT}/run}" +export SOURCES_LIST="${SOURCES_LIST:-${ROOT}/config/sources.list}" +export SINKS_LIST="${SINKS_LIST:-${ROOT}/config/sinks.list}" diff --git a/lib/filters.sh b/lib/filters.sh new file mode 100644 index 0000000..8bfee95 --- /dev/null +++ b/lib/filters.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# filter_complex builders: logo overlay, optional delogo, blurred variant. +set -euo pipefail + +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" + +build_filter_complex() { + local v_in="${1:-0:v}" + local logo_in="${2:-1:v}" + local profile="${FILTER_PROFILE}" + local chain scaled delogo filter + + case "${profile}" in + nologo) + FILTER_COMPLEX="[${v_in}]fps=${FPS},scale=${WIDTH}:${HEIGHT}[ovl]" + return + ;; + blurred) + chain="[${v_in}]scale=1446:1040,crop=1280:920,scale=1446:1040,crop=1280:920,noise=alls=25:allf=t+u,avgblur=16:16" + ;; + default|*) + chain="[${v_in}]fps=${FPS},scale=${WIDTH}:${HEIGHT}" + ;; + esac + + if [[ -n "${DELOGO:-}" ]]; then + IFS=: read -r dx dy dw dh <<<"${DELOGO}" + chain="${chain},delogo=x=${dx}:y=${dy}:w=${dw}:h=${dh}:show=0" + fi + + scaled="${chain}[scaled]" + filter="[${logo_in}][scaled]scale2ref=(${WIDTH}/${HEIGHT})*ih/8:ih/8[wm][base];" + filter+="[wm]setsar=1[wmsar];[base][wmsar]overlay=x=main_w-overlay_w-(main_w*0.01):y=main_h*0.01[ovl]" + FILTER_COMPLEX="${scaled};${filter}" +} + +map_video_out() { + echo '[ovl]' +} + +map_audio_out() { + local audio_input="${AUDIO_MAP_INPUT:-0}" + echo "${audio_input}:a:0" +} diff --git a/lib/logging.sh b/lib/logging.sh new file mode 100644 index 0000000..b64ce5f --- /dev/null +++ b/lib/logging.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +log() { printf '[%s] %s\n' "$(date '+%H:%M:%S')" "$*" >&2; } +warn() { log "WARN: $*"; } +die() { log "ERROR: $*"; exit 1; } diff --git a/lib/outputs.sh b/lib/outputs.sh new file mode 100644 index 0000000..7dc6099 --- /dev/null +++ b/lib/outputs.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Tee muxer targets: local file, named pipe, HLS dir, RTMP sinks list. +set -euo pipefail + +# shellcheck disable=SC1091 +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" + +read_sinks() { + SINKS=() + [[ -f "${SINKS_LIST}" ]] || return 0 + while IFS= read -r line; do + line="${line%%#*}" + line="$(echo "${line}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')" + [[ -n "${line}" ]] && SINKS+=("${line}") + done < "${SINKS_LIST}" +} + +pipe_tee_entry() { + local pipe="$1" + printf "[select='v:0,a':f=flv:onfail=ignore:use_fifo=1:fifo_options='fifo_format=flv:drop_pkts_on_overflow=1:attempt_recovery=1:recovery_wait_time=1']%s" "${pipe}" +} + +file_tee_entry() { + local path="$1" + local fmt="${RECORD_FORMAT}" + case "${fmt}" in + mkv) printf "[select='v:0,a':f=matroska:onfail=ignore]%s" "${path}" ;; + flv|*) printf "[select='v:0,a':f=flv:onfail=ignore]%s" "${path}" ;; + esac +} + +hls_tee_entry() { + local dir="$1" + mkdir -p "${dir}" + printf "[select='v:0,a':f=hls:onfail=ignore:hls_time=2:hls_list_size=6:hls_flags=delete_segments+append_list]%s/index.m3u8" "${dir}" +} + +rtmp_tee_entry() { + local url="$1" + printf "[select='v:0,a':f=flv:onfail=ignore]%s" "${url}" +} + +# Encode stage: record + pipe (+ optional HLS). RTMP fanout is copy stage. +build_encode_tee_spec() { + local record="$1" + local pipe="$2" + local parts=() + parts+=("$(file_tee_entry "${record}")") + parts+=("$(pipe_tee_entry "${pipe}")") + if [[ -n "${HLS_OUTPUT_DIR}" ]]; then + parts+=("$(hls_tee_entry "${HLS_OUTPUT_DIR}")") + fi + local IFS='|' + echo "${parts[*]}" +} + +# Fanout stage: copy from pipe to all sinks (no re-encode). +build_fanout_tee_spec() { + read_sinks + local parts=() + local s + for s in "${SINKS[@]}"; do + parts+=("$(rtmp_tee_entry "${s}")") + done + [[ ${#parts[@]} -gt 0 ]] || die "no sinks in ${SINKS_LIST}" + local IFS='|' + echo "${parts[*]}" +} diff --git a/lib/source.sh b/lib/source.sh new file mode 100644 index 0000000..2d1868a --- /dev/null +++ b/lib/source.sh @@ -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}" +} diff --git a/nginx/hls-location.conf b/nginx/hls-location.conf new file mode 100644 index 0000000..157227e --- /dev/null +++ b/nginx/hls-location.conf @@ -0,0 +1,14 @@ +# Snippet for Alpine BE nginx (apps-port80.conf). +# Serves HLS segments written by streaming_v2 when HLS_OUTPUT_DIR is set. +# Replace @HLS_ROOT@ with the absolute storage path (e.g. /var/www/ac/media-pipelines/hls). + +location ^~ /app/androidcast_project/media/hls/ { + alias @HLS_ROOT@/; + add_header Cache-Control "no-cache"; + add_header Access-Control-Allow-Origin "*"; + types { + application/vnd.apple.mpegurl m3u8; + video/mp2t ts; + } + default_type application/vnd.apple.mpegurl; +} diff --git a/storage/.gitkeep b/storage/.gitkeep new file mode 100644 index 0000000..e69de29