from __future__ import annotations import argparse import json import time from typing import Any import requests EVENT_META_INFO_URL = "https://d.minieye.tech/ess-api/dashoard/event/meta_info" CLIP_ID_KEYS = ("clip_id", "clip_uuid", "clip_ukey", "ukey", "id") def fetch_event_meta( event_id: str, timeout: float = 60.0, max_retries: int = 3, retry_backoff_sec: float = 2.0, ) -> dict[str, Any]: event_id = str(event_id).strip() if not event_id: raise ValueError("event_id is required") last_error: Exception | None = None total_attempts = max(1, int(max_retries)) for attempt in range(1, total_attempts + 1): try: response = requests.post( url=EVENT_META_INFO_URL, json={"event_id": event_id}, timeout=timeout, ) response.raise_for_status() payload = response.json() if not isinstance(payload, dict): raise ValueError(f"Unexpected event meta response type: {type(payload).__name__}") return payload except (requests.exceptions.Timeout, requests.exceptions.RequestException, ValueError) as exc: last_error = exc if attempt >= total_attempts: break sleep_sec = max(0.0, float(retry_backoff_sec)) * (2 ** (attempt - 1)) print( f"[warn] event_id={event_id} request attempt {attempt}/{total_attempts} failed: " f"{type(exc).__name__}: {exc}. retry in {sleep_sec:.1f}s" ) if sleep_sec > 0: time.sleep(sleep_sec) assert last_error is not None raise last_error def _append_clip_id(clip_ids: list[str], seen: set[str], value: Any) -> None: clip_id = str(value).strip() if clip_id and clip_id not in seen: seen.add(clip_id) clip_ids.append(clip_id) def _extract_clip_ids_from_item(item: Any, clip_ids: list[str], seen: set[str]) -> None: if item is None: return if isinstance(item, str): _append_clip_id(clip_ids, seen, item) return if isinstance(item, (list, tuple, set)): for sub_item in item: _extract_clip_ids_from_item(sub_item, clip_ids, seen) return if isinstance(item, dict): matched_key = False for key in CLIP_ID_KEYS: value = item.get(key) if value is not None: matched_key = True _extract_clip_ids_from_item(value, clip_ids, seen) if matched_key: return for value in item.values(): _extract_clip_ids_from_item(value, clip_ids, seen) return _append_clip_id(clip_ids, seen, item) def extract_associated_clip_ids(payload: dict[str, Any]) -> list[str]: clip_items = payload.get("associated_clip_list", []) if clip_items is None: return [] clip_ids: list[str] = [] seen: set[str] = set() _extract_clip_ids_from_item(clip_items, clip_ids, seen) return clip_ids def get_associated_clip_ids( event_id: str, timeout: float = 60.0, max_retries: int = 3, retry_backoff_sec: float = 2.0, ) -> list[str]: payload = fetch_event_meta( event_id, timeout=timeout, max_retries=max_retries, retry_backoff_sec=retry_backoff_sec, ) return extract_associated_clip_ids(payload) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Resolve PDCL clip ids from one or more event ids.") parser.add_argument("event_ids", nargs="+", help="One or more event ids to resolve.") parser.add_argument("--timeout", type=float, default=60.0, help="HTTP request timeout in seconds.") parser.add_argument("--retries", type=int, default=3, help="Max request attempts per event id.") parser.add_argument("--retry-backoff-sec", type=float, default=2.0, help="Base retry backoff in seconds.") parser.add_argument("--pretty", action="store_true", help="Pretty-print the JSON result.") return parser.parse_args() def main() -> None: args = parse_args() result = { event_id: get_associated_clip_ids( event_id, timeout=args.timeout, max_retries=args.retries, retry_backoff_sec=args.retry_backoff_sec, ) for event_id in args.event_ids } if args.pretty: print(json.dumps(result, indent=2, ensure_ascii=False)) else: print(json.dumps(result, ensure_ascii=False)) if __name__ == "__main__": main()