Modularize bridge and improve control GUI

This commit is contained in:
2026-08-03 00:55:12 +02:00
parent 73b302a965
commit f4ab49e0c8
26 changed files with 3104 additions and 1953 deletions
+12 -21
View File
@@ -1,20 +1,11 @@
import importlib.util
import unittest
from pathlib import Path
def load_bridge_module():
root = Path(__file__).resolve().parents[1]
path = root / "frigate-camera-control-bridge.py"
spec = importlib.util.spec_from_file_location("bridge", path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
from frigate_camera_control_bridge.app import CameraControlBridge
class DiscoveryMappingTest(unittest.TestCase):
def setUp(self):
self.bridge = load_bridge_module()
self.bridge = CameraControlBridge
def test_loopback_ffmpeg_path_resolves_through_go2rtc(self):
camera_cfg = {
@@ -30,10 +21,10 @@ class DiscoveryMappingTest(unittest.TestCase):
]
}
urls = self.bridge.CameraControlBridge.camera_candidate_urls("camera-a", camera_cfg, streams)
urls = self.bridge.camera_candidate_urls("camera-a", camera_cfg, streams)
self.assertEqual(urls, ["rtsp://camera-a.example.test:554/Streaming/Channels/101"])
self.assertEqual(self.bridge.CameraControlBridge.hosts_from_urls(urls), ["camera-a.example.test"])
self.assertEqual(self.bridge.hosts_from_urls(urls), ["camera-a.example.test"])
def test_wrapped_go2rtc_stream_resolves_inner_camera_url(self):
camera_cfg = {
@@ -49,7 +40,7 @@ class DiscoveryMappingTest(unittest.TestCase):
]
}
urls = self.bridge.CameraControlBridge.camera_candidate_urls("camera-a", camera_cfg, streams)
urls = self.bridge.camera_candidate_urls("camera-a", camera_cfg, streams)
self.assertEqual(
urls,
@@ -57,7 +48,7 @@ class DiscoveryMappingTest(unittest.TestCase):
"rtsp://camera-a.example.test:554/Streaming/Channels/101?profile=main#hardware",
],
)
self.assertEqual(self.bridge.CameraControlBridge.hosts_from_urls(urls), ["camera-a.example.test"])
self.assertEqual(self.bridge.hosts_from_urls(urls), ["camera-a.example.test"])
def test_direct_ffmpeg_path_is_used_without_go2rtc(self):
camera_cfg = {
@@ -68,10 +59,10 @@ class DiscoveryMappingTest(unittest.TestCase):
}
}
urls = self.bridge.CameraControlBridge.camera_candidate_urls("camera-b", camera_cfg, {})
urls = self.bridge.camera_candidate_urls("camera-b", camera_cfg, {})
self.assertEqual(urls, ["rtsp://camera-b.example.test:554/cam/realmonitor?channel=1&subtype=0"])
self.assertEqual(self.bridge.CameraControlBridge.hosts_from_urls(urls), ["camera-b.example.test"])
self.assertEqual(self.bridge.hosts_from_urls(urls), ["camera-b.example.test"])
def test_wrapped_direct_ffmpeg_path_is_used_without_go2rtc(self):
camera_cfg = {
@@ -82,7 +73,7 @@ class DiscoveryMappingTest(unittest.TestCase):
}
}
urls = self.bridge.CameraControlBridge.camera_candidate_urls("camera-b", camera_cfg, {})
urls = self.bridge.camera_candidate_urls("camera-b", camera_cfg, {})
self.assertEqual(
urls,
@@ -90,7 +81,7 @@ class DiscoveryMappingTest(unittest.TestCase):
"rtsp://camera-b.example.test:554/cam/realmonitor?channel=1&subtype=0#video=copy",
],
)
self.assertEqual(self.bridge.CameraControlBridge.hosts_from_urls(urls), ["camera-b.example.test"])
self.assertEqual(self.bridge.hosts_from_urls(urls), ["camera-b.example.test"])
def test_direct_path_wins_when_loopback_is_also_present(self):
camera_cfg = {
@@ -109,7 +100,7 @@ class DiscoveryMappingTest(unittest.TestCase):
}
}
urls = self.bridge.CameraControlBridge.camera_candidate_urls("camera-a", camera_cfg, streams)
urls = self.bridge.camera_candidate_urls("camera-a", camera_cfg, streams)
self.assertEqual(
urls,
@@ -118,7 +109,7 @@ class DiscoveryMappingTest(unittest.TestCase):
],
)
self.assertEqual(
self.bridge.CameraControlBridge.hosts_from_urls(urls),
self.bridge.hosts_from_urls(urls),
[
"direct-camera.example.test",
],
+37
View File
@@ -0,0 +1,37 @@
import unittest
from frigate_camera_control_bridge.ui import render_page
class DummyAction:
def __init__(self):
self.id = "example_camera_siren"
self.platform = "switch"
self.kind = "dahua_coaxial_io"
self.state = "ON"
self.auto_off_seconds = 180
self.auto_off_at = 1234567890
self.cfg = {
"name_key": "action.siren",
"requires_ack": True,
"snapshot": {"camera": "example", "frigate_url": "http://frigate.example.test:5000"},
}
def display_name(self, lang):
return "Example siren"
def command_list(self):
return ["on", "off"]
class UiRenderTest(unittest.TestCase):
def test_timer_timestamp_and_ack_survive_server_render(self):
html = render_page([DummyAction()], "en")
self.assertIn('data-auto-off-at="1234567890"', html)
self.assertIn('data-requires-ack="true"', html)
self.assertIn("Example siren", html)
if __name__ == "__main__":
unittest.main()