From a9e6e8cb823d9f0ce7b6bd246a0e557d4dd10530 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Sun, 29 Mar 2026 14:11:01 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20KC=20color=20strip=20test=20preview=20?= =?UTF-8?q?=E2=80=94=20use=20LiveStreamManager=20instead=20of=20raw=20engi?= =?UTF-8?q?ne?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BetterCam (DXGI) only supports one capture session per display, so creating a second engine for the KC test produced no frames when a target was already running. Now uses LiveStreamManager.acquire() which ref-counts and reuses the running capture. Also removed double postprocessing — ProcessedLiveStream already applies filters, so re-applying them in the KC test halved the resolution (960x400 → 240x100). --- .../api/routes/color_strip_sources.py | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/server/src/wled_controller/api/routes/color_strip_sources.py b/server/src/wled_controller/api/routes/color_strip_sources.py index 82f9058..5ca0c42 100644 --- a/server/src/wled_controller/api/routes/color_strip_sources.py +++ b/server/src/wled_controller/api/routes/color_strip_sources.py @@ -485,7 +485,6 @@ async def test_key_colors_ws( calculate_dominant_color, calculate_median_color, ) - from wled_controller.core.filters import FilterRegistry, ImagePool from wled_controller.storage.picture_source import ScreenCapturePictureSource from wled_controller.utils.image_codec import encode_jpeg_data_uri, resize_down @@ -497,7 +496,6 @@ async def test_key_colors_ws( source_store = get_picture_source_store() manager = get_processor_manager() device_store = get_device_store() - pp_store = get_pp_template_store() try: source = store.get_source(source_id) @@ -546,11 +544,19 @@ async def test_key_colors_ws( await websocket.accept() logger.info(f"KC CSS test WS connected for {source_id} (fps={fps})") + # Use LiveStreamManager — reuses the already-running capture engine when a + # target is active. BetterCam (DXGI) only supports one session per display, + # so creating a second engine would produce no frames. live_stream_mgr = manager._live_stream_manager - live_stream = None - try: live_stream = await asyncio.to_thread(live_stream_mgr.acquire, source.picture_source_id) + except Exception as e: + logger.error(f"KC test: LiveStream acquire failed: {e}") + await websocket.send_text(ws_json.dumps({"type": "error", "detail": str(e)})) + await websocket.close(code=4003, reason=str(e)) + return + + try: prev_frame_ref = None while True: @@ -564,26 +570,14 @@ async def test_key_colors_ws( await asyncio.sleep(frame_interval * 0.5) continue prev_frame_ref = capture - cur_image = capture.image + if not isinstance(cur_image, np.ndarray): await asyncio.sleep(frame_interval) continue - # Apply postprocessing - pp_ids = chain.get("postprocessing_template_ids", []) - if pp_ids and pp_store: - pool = ImagePool() - for pp_id in pp_ids: - try: - pp = pp_store.get_template(pp_id) - for fi in pp_store.resolve_filter_instances(pp.filters): - f = FilterRegistry.create_instance(fi.filter_id, fi.options) - result = f.process_image(cur_image, pool) - if result is not None: - cur_image = result - except Exception: - pass + # NOTE: postprocessing is already applied by the ProcessedLiveStream + # when using LiveStreamManager.acquire() — do NOT re-apply here. # Re-read source for hot-update support try: @@ -647,11 +641,10 @@ async def test_key_colors_ws( except Exception as e: logger.error(f"KC CSS test WS error: {e}", exc_info=True) finally: - if live_stream is not None: - try: - await asyncio.to_thread(live_stream_mgr.release, source.picture_source_id) - except Exception: - pass + try: + await asyncio.to_thread(live_stream_mgr.release, source.picture_source_id) + except Exception: + pass # ===== CALIBRATION TEST =====