diff --git a/scripts/sim_cameras.sh b/scripts/sim_cameras.sh new file mode 100755 index 0000000..9653f1f --- /dev/null +++ b/scripts/sim_cameras.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env bash +# Simulate RTSP cameras by looping mp4 files through MediaMTX + ffmpeg. +# +# Usage: ./scripts/sim_cameras.sh +# +# Reads camera ids from config/vigilar.toml, expects videos/.mp4 for each, +# starts MediaMTX on 127.0.0.1:8554, spawns one ffmpeg per camera, and writes +# config/vigilar.sim.toml with rtsp_urls rewritten to the local endpoints. +# Ctrl-C tears everything down. +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +REAL_CONFIG="${REPO_ROOT}/config/vigilar.toml" +SIM_CONFIG="${REPO_ROOT}/config/vigilar.sim.toml" +VIDEOS_DIR="${REPO_ROOT}/videos" +SIM_DIR="${REPO_ROOT}/.sim" +LOG_DIR="${SIM_DIR}/logs" +MEDIAMTX_BIN="${SIM_DIR}/mediamtx" +RTSP_HOST="127.0.0.1" +RTSP_PORT="8554" + +MEDIAMTX_PID="" +FFMPEG_PIDS=() + +cleanup() { + local pid + for pid in "${FFMPEG_PIDS[@]}"; do + if [[ -n "${pid}" ]] && kill -0 "${pid}" 2>/dev/null; then + kill "${pid}" 2>/dev/null || true + fi + done + if [[ -n "${MEDIAMTX_PID}" ]] && kill -0 "${MEDIAMTX_PID}" 2>/dev/null; then + kill "${MEDIAMTX_PID}" 2>/dev/null || true + fi + sleep 0.3 + for pid in "${FFMPEG_PIDS[@]}" "${MEDIAMTX_PID}"; do + if [[ -n "${pid}" ]] && kill -0 "${pid}" 2>/dev/null; then + kill -9 "${pid}" 2>/dev/null || true + fi + done + rm -rf "${SIM_DIR:?}/logs" +} +trap cleanup EXIT INT TERM + +die() { echo "error: $*" >&2; exit 1; } + +# --- prereq checks --------------------------------------------------------- + +command -v ffmpeg >/dev/null 2>&1 || die "ffmpeg not found on PATH. Install it and retry." +[[ -x "${MEDIAMTX_BIN}" ]] || die "MediaMTX binary not found at ${MEDIAMTX_BIN}. Run: scripts/download_mediamtx.sh" +[[ -f "${REAL_CONFIG}" ]] || die "config file not found: ${REAL_CONFIG}" +[[ -d "${VIDEOS_DIR}" ]] || die "videos directory not found: ${VIDEOS_DIR} (create it and drop .mp4 files inside)" + +# --- parse camera ids from vigilar.toml ------------------------------------ +# Grab every `id = "..."` line that sits inside a [[cameras]] block. + +mapfile -t CAMERA_IDS < <(awk ' + /^\[\[cameras\]\]/ { in_cams = 1; next } + /^\[/ && !/^\[\[cameras\]\]/ { in_cams = 0 } + in_cams && /^[[:space:]]*id[[:space:]]*=/ { + match($0, /"([^"]+)"/, m) + if (m[1] != "") print m[1] + } +' "${REAL_CONFIG}") + +[[ ${#CAMERA_IDS[@]} -gt 0 ]] || die "no [[cameras]] blocks with an id field found in ${REAL_CONFIG}" + +# --- verify every camera has a matching video ----------------------------- + +MISSING=() +for id in "${CAMERA_IDS[@]}"; do + [[ -f "${VIDEOS_DIR}/${id}.mp4" ]] || MISSING+=("${VIDEOS_DIR}/${id}.mp4") +done +if [[ ${#MISSING[@]} -gt 0 ]]; then + echo "error: missing video files for cameras:" >&2 + for f in "${MISSING[@]}"; do echo " - ${f}" >&2; done + exit 1 +fi + +# --- start MediaMTX -------------------------------------------------------- + +mkdir -p "${LOG_DIR}" +echo "Starting MediaMTX on ${RTSP_HOST}:${RTSP_PORT}..." +"${MEDIAMTX_BIN}" >"${LOG_DIR}/mediamtx.log" 2>&1 & +MEDIAMTX_PID=$! + +for _ in $(seq 1 30); do + if (exec 3<>"/dev/tcp/${RTSP_HOST}/${RTSP_PORT}") 2>/dev/null; then + exec 3<&- 3>&- + break + fi + sleep 0.1 +done +if ! (exec 3<>"/dev/tcp/${RTSP_HOST}/${RTSP_PORT}") 2>/dev/null; then + echo "MediaMTX failed to bind ${RTSP_HOST}:${RTSP_PORT}. Log tail:" >&2 + tail -n 20 "${LOG_DIR}/mediamtx.log" >&2 || true + exit 1 +fi +exec 3<&- 3>&- 2>/dev/null || true + +# --- spawn one ffmpeg per camera ------------------------------------------- + +for id in "${CAMERA_IDS[@]}"; do + video="${VIDEOS_DIR}/${id}.mp4" + url="rtsp://${RTSP_HOST}:${RTSP_PORT}/${id}" + echo "Publishing ${video} -> ${url}" + ffmpeg -nostdin -loglevel warning \ + -stream_loop -1 -re -i "${video}" \ + -c copy -f rtsp "${url}" \ + >"${LOG_DIR}/${id}.log" 2>&1 & + FFMPEG_PIDS+=($!) +done + +# --- write the sim config -------------------------------------------------- +# Copy vigilar.toml, rewrite each `rtsp_url = "..."` line inside a +# [[cameras]] block to point at the local MediaMTX endpoint for that camera. +# Relies on `id` preceding `rtsp_url` within each block (the standing +# convention in config/vigilar.toml). + +awk -v host="${RTSP_HOST}" -v port="${RTSP_PORT}" ' + /^\[\[cameras\]\]/ { in_cams = 1; current_id = ""; print; next } + /^\[/ && !/^\[\[cameras\]\]/ { in_cams = 0; print; next } + in_cams && /^[[:space:]]*id[[:space:]]*=/ { + match($0, /"([^"]+)"/, m) + current_id = m[1] + print + next + } + in_cams && /^[[:space:]]*rtsp_url[[:space:]]*=/ && current_id != "" { + printf "rtsp_url = \"rtsp://%s:%s/%s\"\n", host, port, current_id + next + } + { print } +' "${REAL_CONFIG}" > "${SIM_CONFIG}" + +# --- summary --------------------------------------------------------------- + +echo +echo "Simulator running. ${#CAMERA_IDS[@]} stream(s):" +for id in "${CAMERA_IDS[@]}"; do + echo " rtsp://${RTSP_HOST}:${RTSP_PORT}/${id}" +done +echo +echo "Sim config written to: ${SIM_CONFIG}" +echo "Start Vigilar in another terminal:" +echo " vigilar start --config ${SIM_CONFIG#"${REPO_ROOT}"/}" +echo +echo "Press Ctrl-C to stop." + +wait