Add parse_mdot_json for the MDOT MiDrive camera/list JSON where coordinates/id live in the county field's map link (lat=/lon=/id=) and the JPEG still lives in the image field's <img src>. Michigan bbox filter, missing-coords drop, RTSP reject. discovery_source=mdot, stable source_url keyed on camera id, url_hash dedupe. Wired into scrape_source dispatch + CAMERA_SOURCE_URLS defaults. Unit tests: HTML field extract, bbox drop, missing-coords/image drop, malformed payload.
61 lines
3.1 KiB
Python
61 lines
3.1 KiB
Python
"""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)
|
||
)
|
||
# MDOT MiDrive official DOT CCTV list (fields carry rendered HTML).
|
||
MDOT_CAMERA_URL = "https://mdotjboss.state.mi.us/MiDrive/camera/list"
|
||
_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 1–12).
|
||
*CALTRANS_CCTV_URLS,
|
||
# Official MDOT MiDrive CCTV (JPEG stills, Michigan).
|
||
MDOT_CAMERA_URL,
|
||
))
|
||
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")
|