feat(sim): add camera stream simulator script
Loops phone-recorded mp4 files as local RTSP streams via MediaMTX + ffmpeg so Vigilar's camera pipeline can be exercised end-to-end without physical cameras. Reads camera ids from vigilar.toml, expects videos/<id>.mp4 for each, writes a vigilar.sim.toml with rtsp_urls rewritten to the local endpoints. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
150
scripts/sim_cameras.sh
Executable file
150
scripts/sim_cameras.sh
Executable file
@@ -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/<id>.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 <camera_id>.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
|
||||||
Reference in New Issue
Block a user