About Multi-camera viewer optimized for RTSP streams
0

Configure Feed

Select the types of activity you want to include in your feed.

at master 6.8 kB View raw
1#!/usr/bin/env python3 2""" 3ReolinkProxy Manager fuer WildCam. 4 5Generiert reolinkproxy.env ausschliesslich aus camera_config.json. 6""" 7import json 8import sys 9from pathlib import Path 10 11from camera_utils import ( 12 _parse_rtsp_url, 13 _reolinkproxy_camera_name, 14 _reolinkproxy_proxy_config, 15 _reolinkproxy_rtsp_url, 16) 17 18 19def parse_rtsp_url(rtsp_url): 20 host, port, username, password = _parse_rtsp_url(rtsp_url or "") 21 if not host: 22 return None 23 return { 24 "host": host, 25 "port": port, 26 "username": username or "", 27 "password": password or "", 28 "scheme": "rtsp", 29 } 30 31 32def camera_name(name): 33 return _reolinkproxy_camera_name(name) 34 35 36def is_proxy_url(rtsp_url): 37 info = parse_rtsp_url(rtsp_url or "") 38 return bool(info and info["host"] in ("localhost", "127.0.0.1") and info["port"] == 8554) 39 40 41def is_reolinkproxy_camera(camera): 42 proxy = camera.get("proxy") or {} 43 if proxy.get("type") == "reolinkproxy": 44 return True 45 46 url_info = parse_rtsp_url(camera.get("url", "")) 47 if url_info and url_info["port"] == 9000: 48 return True 49 return False 50 51 52def env_value(value): 53 value = "" if value is None else str(value) 54 value = value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") 55 return f'"{value}"' 56 57 58def make_proxy_from_rtsp(camera): 59 return _reolinkproxy_proxy_config( 60 rtsp_url=camera.get("url", ""), 61 name=camera.get("name", ""), 62 username="", 63 password="", 64 uid=camera.get("uid", ""), 65 model=camera.get("model", ""), 66 manufacturer=camera.get("manufacturer", ""), 67 ) 68 69 70def write_env(cameras, output_path): 71 lines = [ 72 "# Auto-generated by WildCam reolinkproxy_manager.py", 73 "# Existing WildCam RTSP paths are preserved with REOLINK_CAMERA_N_RTSP_PATH.", 74 "REOLINK_HEALTHCHECK_RTSP_ONLY=true", 75 "", 76 ] 77 78 proxy_cameras = [cam for cam in cameras if is_reolinkproxy_camera(cam)] 79 incomplete_proxy_cameras = [ 80 cam for cam in cameras 81 if is_proxy_url(cam.get("url", "")) and (cam.get("proxy") or {}).get("type") != "reolinkproxy" 82 ] 83 if incomplete_proxy_cameras: 84 names = ", ".join(camera_name(cam.get("name", "")) for cam in incomplete_proxy_cameras) 85 print(f"Unvollstaendige Proxy-Konfiguration fuer: {names}") 86 print("Bitte in camera_config.json je Kamera einen proxy-Block mit host/username/password ergaenzen.") 87 return False 88 if not proxy_cameras: 89 print("Keine ReolinkProxy-Kameras gefunden.") 90 return False 91 92 print(f"{len(proxy_cameras)} ReolinkProxy-Kamera(s) gefunden:") 93 94 for index, cam in enumerate(proxy_cameras): 95 name = camera_name(cam.get("name", f"Camera_{index}")) 96 proxy = cam.get("proxy") or make_proxy_from_rtsp(cam) or {} 97 98 use_uid_only = bool(proxy.get("uid_only", False)) 99 host = "" if use_uid_only else proxy.get("host", "") 100 port = int(proxy.get("port") or 9000) 101 username = proxy.get("username", "") 102 password = proxy.get("password", "") 103 stream = proxy.get("stream", "main") 104 uid = proxy.get("uid") or cam.get("uid", "") 105 battery = bool(proxy.get("battery", True)) 106 pause_on_client = bool(proxy.get("pause_on_client", True)) 107 idle_disconnect = bool(proxy.get("idle_disconnect", True)) 108 idle_timeout = proxy.get("idle_timeout", "30s") 109 110 print(f" - {name}: host={host or 'UID-only'}, uid={uid or '-'}") 111 112 lines.extend( 113 [ 114 f"REOLINK_CAMERA_{index}_NAME={env_value(name)}", 115 f"REOLINK_CAMERA_{index}_PORT={port}", 116 f"REOLINK_CAMERA_{index}_USERNAME={env_value(username)}", 117 f"REOLINK_CAMERA_{index}_PASSWORD={env_value(password)}", 118 f"REOLINK_CAMERA_{index}_STREAM={env_value(stream)}", 119 f"REOLINK_CAMERA_{index}_RTSP_PATH={env_value(f'{name}/mainStream')}", 120 f"REOLINK_CAMERA_{index}_BATTERY_CAMERA={str(battery).lower()}", 121 f"REOLINK_CAMERA_{index}_PAUSE_ON_CLIENT={str(pause_on_client).lower()}", 122 f"REOLINK_CAMERA_{index}_IDLE_DISCONNECT={str(idle_disconnect).lower()}", 123 f"REOLINK_CAMERA_{index}_IDLE_TIMEOUT={env_value(idle_timeout)}", 124 ] 125 ) 126 if host: 127 lines.append(f"REOLINK_CAMERA_{index}_HOST={env_value(host)}") 128 if uid: 129 lines.append(f"REOLINK_CAMERA_{index}_UID={env_value(uid)}") 130 lines.append("") 131 132 output_path.write_text("\n".join(lines), encoding="utf-8") 133 print(f"{output_path} geschrieben.") 134 return True 135 136 137def update_camera_config(camera_config_path): 138 try: 139 config = json.loads(camera_config_path.read_text(encoding="utf-8")) 140 except Exception as e: 141 print(f"Fehler beim Laden der Config: {e}") 142 return False 143 144 updated = False 145 for cam in config.get("cameras", []): 146 if not is_reolinkproxy_camera(cam): 147 continue 148 name = camera_name(cam.get("name", "")) 149 if "proxy" not in cam: 150 proxy = make_proxy_from_rtsp(cam) 151 if proxy: 152 cam["proxy"] = proxy 153 updated = True 154 new_url = _reolinkproxy_rtsp_url(name) 155 if cam.get("url") != new_url: 156 print(f"{cam.get('name', name)}: {cam.get('url')} -> {new_url}") 157 cam["url"] = new_url 158 updated = True 159 160 if not updated: 161 return False 162 163 backup_path = camera_config_path.with_suffix(camera_config_path.suffix + ".backup") 164 backup_path.write_text(camera_config_path.read_text(encoding="utf-8"), encoding="utf-8") 165 camera_config_path.write_text(json.dumps(config, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") 166 print(f"{camera_config_path} aktualisiert, Backup: {backup_path}") 167 return True 168 169 170def main(): 171 script_dir = Path(__file__).parent 172 config_path = script_dir / "camera_config.json" 173 env_path = script_dir / "reolinkproxy.env" 174 175 print("WildCam ReolinkProxy Manager") 176 print("=" * 50) 177 178 try: 179 config = json.loads(config_path.read_text(encoding="utf-8")) 180 except FileNotFoundError: 181 print(f"Fehler: {config_path} nicht gefunden.") 182 sys.exit(0) 183 except json.JSONDecodeError as e: 184 print(f"Fehler beim Parsen der Config: {e}") 185 sys.exit(1) 186 187 success = write_env(config.get("cameras", []), env_path) 188 if not success: 189 sys.exit(0) 190 191 if "--auto-update" in sys.argv or "--yes" in sys.argv: 192 update_camera_config(config_path) 193 else: 194 print("camera_config.json bleibt unveraendert. Nutze --auto-update zum Umschreiben alter Port-9000-URLs.") 195 196 197if __name__ == "__main__": 198 main()