#!/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