osint-dashboard/app/camera_config.py
Sirius DevOps 41433be574 feat(cameras): ODOT TripCheck parser -> cameras table
Add parse_odot_json (Esri-style cctvinventory.js) next to parse_caltrans_json:
JPEG snapshot_url, discovery_source=odot, Oregon bbox + filename guard,
no RTSP. Wire dispatch + default CAMERA_SOURCE_URLS entry. Unit tests.
2026-08-31 21:38:09 -04:00

59 lines
3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Camera discovery configuration (12-factor, env-driven).
All knobs read from the environment with container-friendly defaults.
"""
from __future__ import annotations
import os
# ── Camera scraper ─────────────────────────────────────────────────────────
# Comma-separated public directory/list/API URLs to scrape.
# Formats: Insecam-style HTML, plain-text URL lists, ALERTWest JSON.
# NOTE: docker-compose always defines CAMERA_SOURCE_URLS (empty when no .env),
# so os.getenv()'s default would never apply — use `or` semantics instead.
CALTRANS_CCTV_URLS = tuple(
f"https://cwwp2.dot.ca.gov/data/d{n}/cctv/cctvStatusD{n:02d}.json"
for n in range(1, 13)
)
_DEFAULT_SOURCE_URL = ",".join((
# Publicly published open-camera list (markdown bullets of stream URLs).
"https://raw.githubusercontent.com/fury999io/public-ip-cams/main/README.md",
# ALERTCalifornia / ALERTWest official public JPEG API (wildfire + DOT + FAA).
"https://api.cdn.prod.alertwest.com/api/getCameraDataByLoc",
# Curated global outdoor streams (HLS / YouTube / JPEG) — VDOT, MDSHA, etc.
"https://raw.githubusercontent.com/willytop8/Live-Environment-Streams/main/streams.geojson",
# Official Caltrans CWWP2 JPEG + HLS CCTV (districts 112).
*CALTRANS_CCTV_URLS,
# Oregon DOT TripCheck public CCTV JPEG inventory (Esri JSON).
"https://www.tripcheck.com/Scripts/map/data/cctvinventory.js",
))
CAMERA_SOURCE_URLS = [
u.strip()
for u in (os.getenv("CAMERA_SOURCE_URLS") or _DEFAULT_SOURCE_URL).split(",")
if u.strip() # empty env var → fall back to the default above
]
# Seconds between full scrape cycles of every source.
CAMERA_SCRAPE_INTERVAL = int(os.getenv("CAMERA_SCRAPE_INTERVAL", "3600"))
# Politeness: minimum seconds between consecutive requests to the SAME host.
CAMERA_REQUEST_DELAY = float(os.getenv("CAMERA_REQUEST_DELAY", "2.0"))
# Max cameras accepted per scrape cycle per source (safety cap).
CAMERA_MAX_PER_SOURCE = int(os.getenv("CAMERA_MAX_PER_SOURCE", "20000"))
# ── Geocoding (Nominatim — free, 1 req/s hard politeness limit) ────────────
NOMINATIM_URL = os.getenv("NOMINATIM_URL", "https://nominatim.openstreetmap.org")
NOMINATIM_MIN_INTERVAL = float(os.getenv("NOMINATIM_MIN_INTERVAL", "1.0"))
USER_AGENT = os.getenv(
"OSINT_USER_AGENT", "osint-dashboard-camera-scraper/1.0 (self-hosted)"
)
# ── Snapshot cache ─────────────────────────────────────────────────────────
SNAPSHOT_CACHE_DIR = os.getenv("SNAPSHOT_CACHE_DIR", "/data/snapshots")
SNAPSHOT_TTL_SECONDS = int(os.getenv("SNAPSHOT_TTL_SECONDS", "300"))
SNAPSHOT_TIMEOUT = float(os.getenv("SNAPSHOT_TIMEOUT", "8.0"))
# NATS subject cameras are published on (consumed by the shared ingester).
CAMERA_NATS_SUBJECT = os.getenv("CAMERA_NATS_SUBJECT", "events.camera")