About Multi-camera viewer optimized for RTSP streams
1CURRENT_LANG = "de"
2
3
4TRANSLATIONS = {
5 "de": {
6 "app.title": "Reolink Multi-Camera Viewer",
7 "tab.cameras": "Kameras",
8 "tab.config": "Konfiguration",
9 "group.camera_config": "Kamera-Konfiguration",
10 "group.detection_config": "Objekterkennung",
11 "group.email_config": "E-Mail-Benachrichtigung",
12 "label.rtsp_url": "RTSP URL:",
13 "label.name": "Name:",
14 "label.detection_model": "Modell:",
15 "label.detection_device": "Gerät:",
16 "label.detection_imgsz": "Bildgröße:",
17 "label.detection_conf": "Konfidenz:",
18 "label.detection_fps": "FPS/Kamera:",
19 "label.detection_cooldown": "Cooldown s:",
20 "label.detection_suppress": "Event-Sperre s:",
21 "label.detection_clip": "Clip-Länge s:",
22 "label.detection_classes": "Klassen:",
23 "label.detection_motion_classes": "Bewegung nötig für:",
24 "label.detection_motion_pixels": "Min. Bewegung px:",
25 "detection.device.auto": "Auto",
26 "detection.device.cuda": "CUDA",
27 "detection.device.cpu": "CPU",
28 "label.email_enabled": "E-Mail aktiv",
29 "label.smtp_host": "SMTP Host:",
30 "label.smtp_port": "Port:",
31 "label.smtp_tls": "TLS",
32 "label.smtp_username": "User:",
33 "label.smtp_password": "Passwort:",
34 "label.email_from": "Von:",
35 "label.email_to": "An:",
36 "placeholder.rtsp_url": "rtsp://admin:password@192.168.1.100:554/h264Preview_01_main",
37 "placeholder.name.short": "z.B. Eingang",
38 "btn.add": "Hinzufügen",
39 "btn.discover": "Auto-Suche",
40 "btn.clear_all": "Alle entfernen",
41 "btn.path": "Speicherort",
42 "btn.start_all": "Alle Streams starten",
43 "btn.stop_all": "Alle Streams stoppen",
44 "btn.record_all": "Alle aufnehmen",
45 "btn.record_all_stop": "Alle stoppen",
46 "btn.email_test": "Testmail versenden",
47 "btn.model_test": "Runtime/Modell installieren/testen",
48 "label.camera_count": "Kameras: {total} | Aktiv: {active}",
49 "big.select_camera": "Kamera auswählen…",
50 "status.ready": "Bereit - CPU-optimiert für parallele Streams",
51 "status.auto_added": "{count} Kameras automatisch hinzugefügt",
52 "status.camera_added": "{name} hinzugefügt",
53 "status.camera_updated": "Kamera {name} aktualisiert",
54 "status.stream_started": "Stream für {name} gestartet",
55 "status.streams_starting": "{count} Streams werden parallel gestartet...",
56 "status.streams_stopped": "Alle Streams gestoppt",
57 "status.camera_removed": "Kamera {id} entfernt",
58 "status.cameras_removed": "Alle Kameras entfernt",
59 "dialog.title.info": "Info",
60 "dialog.title.error": "Fehler",
61 "dialog.title.confirm": "Bestätigung",
62 "dialog.msg.no_cameras": "Keine Kameras konfiguriert!",
63 "dialog.confirm.remove_all": "Alle Kameras entfernen?",
64 "dialog.confirm.remove_one": "Kamera '{name}' wirklich entfernen?",
65 "dialog.path.choose": "Speicherort wählen",
66 "label.cameras_per_row": "Kameras pro Reihe:",
67 "status.path": "Speicherort: {path}",
68 "status.no_image": "Kein Bild verfügbar",
69 "status.snapshot_saved": "Snapshot gespeichert: {name}",
70 "status.snapshot_error": "Snapshot Fehler: {error}",
71 "status.recording": "Aufnahme: {name}",
72 "status.recording_stopped": "Aufnahme gestoppt: {name}",
73 "status.recordings_started": "{count} Aufnahmen gestartet",
74 "status.recordings_stopped": "{count} Aufnahmen gestoppt",
75 "status.detection_stopped": "Objekterkennung gestoppt",
76 "status.detection_event": "{label} auf {camera} erkannt ({confidence})",
77 "email.detection.subject": "WildCam: {label} auf {camera}",
78 "email.detection.body": "WildCam hat {label} auf {camera} erkannt. Konfidenz: {confidence}",
79 "email.test.subject": "WildCam Testmail",
80 "email.test.body": "Diese Testmail wurde von WildCam versendet. Die SMTP-Konfiguration funktioniert.",
81 "status.email_test_sending": "Testmail wird versendet...",
82 "status.email_test_sent": "Testmail erfolgreich versendet",
83 "status.email_test_failed": "Testmail fehlgeschlagen: {error}",
84 "status.model_test_running": "Runtime und Modell werden geprüft/installiert...",
85 "status.model_test_ok": "Modell bereit: {path}",
86 "status.model_test_failed": "Modelltest fehlgeschlagen: {error}",
87 "status.model_retry_scheduled": "Modell-Laden fehlgeschlagen; neuer Versuch in {seconds}s",
88 "camera.preview.click_to_start": "Stream starten klicken",
89 "camera.preview.waiting": "Warte auf Stream...",
90 "camera.preview.retrying": "Versuche erneut...",
91 "camera.status.offline": "Offline",
92 "camera.status.stopped": "Gestoppt",
93 "camera.status.connected": "Verbunden",
94 "camera.status.connecting": "Verbinde...",
95 "camera.status.sleep": "Sleep/Offline",
96 "camera.default_name.id": "Kamera {id}",
97 "camera.default_name.ip": "Kamera {ip}",
98 "camera.meta.unknown": "Unbekannt",
99 "camera.meta.rtsp_camera": "RTSP-Kamera",
100 "camera.error.stream_unreachable": "Stream nicht erreichbar",
101 "camera.error.stream_interrupted": "Stream unterbrochen",
102 "camera.tooltip.record": "Aufzeichnung starten/stoppen",
103 "camera.tooltip.stream": "Stream starten/stoppen",
104 "camera.tooltip.snapshot": "Einzelbild speichern",
105 "camera.tooltip.detection": "Objekterkennung für diese Kamera starten/stoppen",
106 "camera.tooltip.edit": "Kamera bearbeiten",
107 "camera.tooltip.remove": "Kamera entfernen",
108 "dialog.edit.title": "Kamera bearbeiten",
109 "dialog.edit.name": "Name:",
110 "dialog.edit.name_ph": "z.B. Eingang Haupttür",
111 "dialog.edit.rtsp_url": "RTSP URL:",
112 "dialog.edit.rtsp_ph": "rtsp://admin:password@192.168.1.100:554/h264Preview_01_main",
113 "dialog.edit.help_group": "RTSP URL Format",
114 "dialog.edit.help_text": "Standard Format: rtsp://username:password@ip:port/pfad\n\nReolink Beispiele:\n• Main Stream: rtsp://admin:pass@192.168.1.100:554/h264Preview_01_main\n• Sub Stream: rtsp://admin:pass@192.168.1.100:554/h264Preview_01_sub\n\nAndere Kameras:\n• ONVIF: rtsp://admin:pass@192.168.1.100:554/onvif1\n• Hikvision: rtsp://admin:pass@192.168.1.100:554/Streaming/Channels/101",
115 "dialog.edit.builder_group": "Schnell-Editor",
116 "dialog.edit.ip": "IP:",
117 "dialog.edit.ip_ph": "192.168.1.100",
118 "dialog.edit.port": "Port:",
119 "dialog.edit.username": "Username:",
120 "dialog.edit.password": "Password:",
121 "dialog.edit.path": "Pfad:",
122 "dialog.edit.build_url": "→ URL Generieren",
123 "dialog.edit.err_ip": "Bitte IP-Adresse eingeben!",
124 "dialog.discovery.title": "Kamera-Suche im Netzwerk",
125 "dialog.discovery.scan_config": "Scan-Konfiguration",
126 "dialog.discovery.network": "Netzwerk:",
127 "dialog.discovery.network_ph": "192.168.1.0/24",
128 "dialog.discovery.username": "Benutzername:",
129 "dialog.discovery.password": "Passwort:",
130 "dialog.discovery.start": "🔍 Suche starten",
131 "dialog.discovery.stop": "⏹ Stoppen",
132 "dialog.discovery.ready": "Bereit zum Scannen",
133 "dialog.discovery.found": "Gefundene Kameras",
134 "dialog.discovery.col.select": "Auswählen",
135 "dialog.discovery.col.ip": "IP-Adresse",
136 "dialog.discovery.col.name": "Name",
137 "dialog.discovery.col.model": "Modell",
138 "dialog.discovery.col.manufacturer": "Hersteller",
139 "dialog.discovery.col.ports": "Ports",
140 "dialog.discovery.col.uid": "UID",
141 "dialog.discovery.err_network": "Bitte Netzwerk-Bereich eingeben!",
142 "dialog.discovery.scan_cancelled": "Scan abgebrochen - {count} Kameras gefunden",
143 "dialog.discovery.scan_done": "Scan abgeschlossen - {count} Kameras gefunden",
144 "label.uid": "UID (optional):",
145 "placeholder.uid": "z.B. 9527000000000000",
146 "scan.checking": "Prüfe {ip}...",
147 "scan.error": "Fehler: {error}",
148 "error.prefix": "Fehler: {error}",
149 "label.language": "Sprache:",
150 "language.de": "Deutsch",
151 "language.en": "English",
152 "battery.warning.title": "⚠️ Akku-Kamera erkannt",
153 "battery.warning.message": "Die Kamera '{name}' ({model}) ist eine Akku-betriebene Kamera.\n\n"
154 "⚡ WICHTIG:\n"
155 "• RTSP-Streaming ist eingeschränkt (Kamera schläft automatisch)\n"
156 "• Akku wird bei Dauerstream stark belastet\n"
157 "• Stream kann nach 30-60 Sekunden abbrechen\n\n"
158 "💡 EMPFEHLUNG:\n"
159 "Verwende ReolinkProxy als Proxy für stabiles Streaming:\n"
160 "https://github.com/Shareed2k/reolinkproxy\n\n"
161 "Siehe README für ReolinkProxy-Konfiguration.\n\n"
162 "Kamera trotzdem hinzufügen?",
163 "battery.indicator": "🔋 AKKU",
164 },
165 "en": {
166 "app.title": "Reolink Multi-Camera Viewer",
167 "tab.cameras": "Cameras",
168 "tab.config": "Settings",
169 "group.camera_config": "Camera Configuration",
170 "group.detection_config": "Object Detection",
171 "group.email_config": "Email Alerts",
172 "label.rtsp_url": "RTSP URL:",
173 "label.name": "Name:",
174 "label.detection_model": "Model:",
175 "label.detection_device": "Device:",
176 "label.detection_imgsz": "Image size:",
177 "label.detection_conf": "Confidence:",
178 "label.detection_fps": "FPS/camera:",
179 "label.detection_cooldown": "Cooldown s:",
180 "label.detection_suppress": "Event lock s:",
181 "label.detection_clip": "Clip length s:",
182 "label.detection_classes": "Classes:",
183 "label.detection_motion_classes": "Motion required for:",
184 "label.detection_motion_pixels": "Min. motion px:",
185 "detection.device.auto": "Auto",
186 "detection.device.cuda": "CUDA",
187 "detection.device.cpu": "CPU",
188 "label.email_enabled": "Email enabled",
189 "label.smtp_host": "SMTP host:",
190 "label.smtp_port": "Port:",
191 "label.smtp_tls": "TLS",
192 "label.smtp_username": "User:",
193 "label.smtp_password": "Password:",
194 "label.email_from": "From:",
195 "label.email_to": "To:",
196 "placeholder.rtsp_url": "rtsp://admin:password@192.168.1.100:554/h264Preview_01_main",
197 "placeholder.name.short": "e.g. Entrance",
198 "btn.add": "➕ Add",
199 "btn.discover": "🔍 Auto-Discover",
200 "btn.clear_all": "Remove all",
201 "btn.path": "📁 Storage",
202 "btn.start_all": "▶ Start all streams",
203 "btn.stop_all": "⏹ Stop all streams",
204 "btn.record_all": "● Record all",
205 "btn.record_all_stop": "■ Stop all",
206 "btn.email_test": "Send test mail",
207 "btn.model_test": "Install/test runtime/model",
208 "label.camera_count": "Cameras: {total} | Active: {active}",
209 "big.select_camera": "Select a camera…",
210 "status.ready": "Ready - CPU-optimized for parallel streams",
211 "status.auto_added": "{count} cameras added automatically",
212 "status.camera_added": "{name} added",
213 "status.camera_updated": "Camera {name} updated",
214 "status.stream_started": "Stream started for {name}",
215 "status.streams_starting": "Starting {count} streams in parallel...",
216 "status.streams_stopped": "All streams stopped",
217 "status.camera_removed": "Camera {id} removed",
218 "status.cameras_removed": "All cameras removed",
219 "dialog.title.info": "Info",
220 "dialog.title.error": "Error",
221 "dialog.title.confirm": "Confirm",
222 "dialog.msg.no_cameras": "No cameras configured!",
223 "dialog.confirm.remove_all": "Remove all cameras?",
224 "dialog.confirm.remove_one": "Remove camera '{name}'?",
225 "dialog.path.choose": "Choose storage folder",
226 "label.cameras_per_row": "Cameras per row:",
227 "status.path": "Storage: {path}",
228 "status.no_image": "No image available",
229 "status.snapshot_saved": "Snapshot saved: {name}",
230 "status.snapshot_error": "Snapshot error: {error}",
231 "status.recording": "Recording: {name}",
232 "status.recording_stopped": "Recording stopped: {name}",
233 "status.recordings_started": "{count} recordings started",
234 "status.recordings_stopped": "{count} recordings stopped",
235 "status.detection_stopped": "Object detection stopped",
236 "status.detection_event": "Detected {label} on {camera} ({confidence})",
237 "email.detection.subject": "WildCam: {label} on {camera}",
238 "email.detection.body": "WildCam detected {label} on {camera}. Confidence: {confidence}",
239 "email.test.subject": "WildCam test mail",
240 "email.test.body": "This test mail was sent by WildCam. The SMTP configuration works.",
241 "status.email_test_sending": "Sending test mail...",
242 "status.email_test_sent": "Test mail sent successfully",
243 "status.email_test_failed": "Test mail failed: {error}",
244 "status.model_test_running": "Checking/installing runtime and model...",
245 "status.model_test_ok": "Model ready: {path}",
246 "status.model_test_failed": "Model test failed: {error}",
247 "status.model_retry_scheduled": "Model load failed; retrying in {seconds}s",
248 "camera.preview.click_to_start": "Click start stream",
249 "camera.preview.waiting": "Waiting for stream...",
250 "camera.preview.retrying": "Retrying...",
251 "camera.status.offline": "Offline",
252 "camera.status.stopped": "Stopped",
253 "camera.status.connected": "Connected",
254 "camera.status.connecting": "Connecting...",
255 "camera.status.sleep": "Sleep/Offline",
256 "camera.default_name.id": "Camera {id}",
257 "camera.default_name.ip": "Camera {ip}",
258 "camera.meta.unknown": "Unknown",
259 "camera.meta.rtsp_camera": "RTSP camera",
260 "camera.error.stream_unreachable": "Stream unreachable",
261 "camera.error.stream_interrupted": "Stream interrupted",
262 "camera.tooltip.record": "Start/stop recording",
263 "camera.tooltip.stream": "Start/stop stream",
264 "camera.tooltip.snapshot": "Save snapshot",
265 "camera.tooltip.detection": "Start/stop object detection for this camera",
266 "camera.tooltip.edit": "Edit camera",
267 "camera.tooltip.remove": "Remove camera",
268 "dialog.edit.title": "Edit camera",
269 "dialog.edit.name": "Name:",
270 "dialog.edit.name_ph": "e.g. Front door",
271 "dialog.edit.rtsp_url": "RTSP URL:",
272 "dialog.edit.rtsp_ph": "rtsp://admin:password@192.168.1.100:554/h264Preview_01_main",
273 "dialog.edit.help_group": "RTSP URL format",
274 "dialog.edit.help_text": "Standard format: rtsp://username:password@ip:port/path\n\nReolink examples:\n• Main stream: rtsp://admin:pass@192.168.1.100:554/h264Preview_01_main\n• Sub stream: rtsp://admin:pass@192.168.1.100:554/h264Preview_01_sub\n\nOther cameras:\n• ONVIF: rtsp://admin:pass@192.168.1.100:554/onvif1\n• Hikvision: rtsp://admin:pass@192.168.1.100:554/Streaming/Channels/101",
275 "dialog.edit.builder_group": "Quick editor",
276 "dialog.edit.ip": "IP:",
277 "dialog.edit.ip_ph": "192.168.1.100",
278 "dialog.edit.port": "Port:",
279 "dialog.edit.username": "Username:",
280 "dialog.edit.password": "Password:",
281 "dialog.edit.path": "Path:",
282 "dialog.edit.build_url": "→ Build URL",
283 "dialog.edit.err_ip": "Please enter an IP address!",
284 "dialog.discovery.title": "Network camera discovery",
285 "dialog.discovery.scan_config": "Scan configuration",
286 "dialog.discovery.network": "Network:",
287 "dialog.discovery.network_ph": "192.168.1.0/24",
288 "dialog.discovery.username": "Username:",
289 "dialog.discovery.password": "Password:",
290 "dialog.discovery.start": "🔍 Start scan",
291 "dialog.discovery.stop": "⏹ Stop",
292 "dialog.discovery.ready": "Ready to scan",
293 "dialog.discovery.found": "Found cameras",
294 "dialog.discovery.col.select": "Select",
295 "dialog.discovery.col.ip": "IP address",
296 "dialog.discovery.col.name": "Name",
297 "dialog.discovery.col.model": "Model",
298 "dialog.discovery.col.manufacturer": "Vendor",
299 "dialog.discovery.col.ports": "Ports",
300 "dialog.discovery.col.uid": "UID",
301 "dialog.discovery.err_network": "Please enter a network range!",
302 "dialog.discovery.scan_cancelled": "Scan cancelled - {count} cameras found",
303 "dialog.discovery.scan_done": "Scan finished - {count} cameras found",
304 "label.uid": "UID (optional):",
305 "placeholder.uid": "e.g. 9527000000000000",
306 "scan.checking": "Checking {ip}...",
307 "scan.error": "Error: {error}",
308 "error.prefix": "Error: {error}",
309 "label.language": "Language:",
310 "language.de": "Deutsch",
311 "language.en": "English",
312 "battery.warning.title": "⚠️ Battery Camera Detected",
313 "battery.warning.message": "Camera '{name}' ({model}) is battery-powered.\n\n"
314 "⚡ IMPORTANT:\n"
315 "• RTSP streaming is limited (camera sleeps automatically)\n"
316 "• Battery drains quickly with continuous streaming\n"
317 "• Stream may disconnect after 30-60 seconds\n\n"
318 "💡 RECOMMENDATION:\n"
319 "Use ReolinkProxy as proxy for stable streaming:\n"
320 "https://github.com/Shareed2k/reolinkproxy\n\n"
321 "See README for ReolinkProxy configuration.\n\n"
322 "Add camera anyway?",
323 "battery.indicator": "🔋 BATTERY",
324 },
325}
326
327
328def set_language(lang: str):
329 global CURRENT_LANG
330 if lang in TRANSLATIONS:
331 CURRENT_LANG = lang
332
333
334def tr(key: str, **kwargs) -> str:
335 lang_map = TRANSLATIONS.get(CURRENT_LANG) or TRANSLATIONS["de"]
336 s = lang_map.get(key) or TRANSLATIONS["de"].get(key) or key
337 try:
338 return s.format(**kwargs)
339 except Exception:
340 return s