diff --git a/api/app/main.py b/api/app/main.py index ba53f9b..7911538 100644 --- a/api/app/main.py +++ b/api/app/main.py @@ -57,6 +57,32 @@ def _clean_text(value: Optional[str]) -> Optional[str]: return cleaned or None +def _sanitize_cfg_fields(cfg: AppConfig) -> bool: + changed = False + clean_api = _clean_text(cfg.mediamtx_api_url) + clean_webrtc = _clean_text(cfg.mediamtx_webrtc_url) + clean_user = _clean_text(cfg.mediamtx_api_user) + clean_pass = _clean_text(cfg.mediamtx_api_pass) + clean_recordings = _clean_text(cfg.recordings_dir) + + if clean_api and clean_api != cfg.mediamtx_api_url: + cfg.mediamtx_api_url = clean_api + changed = True + if clean_webrtc and clean_webrtc != cfg.mediamtx_webrtc_url: + cfg.mediamtx_webrtc_url = clean_webrtc + changed = True + if clean_user != cfg.mediamtx_api_user: + cfg.mediamtx_api_user = clean_user + changed = True + if clean_pass != cfg.mediamtx_api_pass: + cfg.mediamtx_api_pass = clean_pass + changed = True + if clean_recordings and clean_recordings != cfg.recordings_dir: + cfg.recordings_dir = clean_recordings + changed = True + return changed + + def _load_mediamtx_yml() -> dict: if not MEDIAMTX_YML_PATH.exists(): raise HTTPException(status_code=500, detail="mediamtx_yml_not_found") @@ -96,6 +122,7 @@ def _build_mediamtx_view(data: dict, cfg: AppConfig) -> MediaMTXConfigView: async def _sync_app_config_from_mediamtx() -> AppConfig: cfg = await store.load() + _sanitize_cfg_fields(cfg) data = _load_mediamtx_yml() cfg.cameras = [ Camera(name=c.name, rtsp_url=c.rtsp_url) @@ -133,6 +160,8 @@ def _restart_mediamtx() -> dict: async def _apply_recording(enabled: bool) -> None: cfg = await store.load() + if _sanitize_cfg_fields(cfg): + await store.save(cfg) try: paths = await MediaMTXClient( api_url=cfg.mediamtx_api_url, @@ -140,8 +169,9 @@ async def _apply_recording(enabled: bool) -> None: password=cfg.mediamtx_api_pass, ).list_paths_status() names = [it.get("name") for it in (paths.get("items") or []) if isinstance(it, dict) and it.get("name")] - except Exception: - names = [c.name for c in cfg.cameras] + except httpx.HTTPError as e: + logger.warning("scheduler_skip_apply_recording: mediamtx_unreachable (%s)", type(e).__name__) + return if not names: return @@ -150,7 +180,10 @@ async def _apply_recording(enabled: bool) -> None: username=cfg.mediamtx_api_user, password=cfg.mediamtx_api_pass, ) - await client.set_recording_bulk(names, enabled) + try: + await client.set_recording_bulk(names, enabled) + except httpx.HTTPError as e: + logger.warning("scheduler_apply_recording_failed: %s", type(e).__name__) scheduler = Scheduler(apply=_apply_recording) @@ -169,9 +202,9 @@ async def _scheduler_loop() -> None: "Set mediamtx_api_user/mediamtx_api_pass in api/data/config.json" ) else: - logger.exception("scheduler_tick_http_status_%s", code) + logger.warning("scheduler_tick_http_status_%s", code) except httpx.HTTPError: - logger.exception("scheduler_tick_http_error") + logger.warning("scheduler_tick_http_error") except Exception: logger.exception("scheduler_tick_failed") finally: diff --git a/index.html b/index.html index 7650e18..57b1c2d 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - My Trae Project + IPCam OrangePi Dashboard