diff --git a/observe/describe.py b/observe/describe.py index 07f712ac3..befcaff89 100644 --- a/observe/describe.py +++ b/observe/describe.py @@ -28,10 +28,8 @@ from enum import Enum from pathlib import Path from typing import List, Optional -import av from PIL import Image -from observe.aruco import detect_markers, mask_convey_region, polygon_area from observe.extract import DEFAULT_MAX_EXTRACTIONS, select_frames_for_extraction from observe.utils import get_segment_key from think.callosum import callosum_send @@ -222,6 +220,14 @@ class VideoProcessor: # Cache for the last qualified frame hash last_hash: Optional[int] = None + # Imports deferred: av (PyAV) and cv2 (via observe.aruco) bundle + # mismatched libavdevice majors. Keeping them out of module scope + # avoids the macOS ObjC duplicate-class warning on every caller that + # only needs CATEGORIES (see observe/screen.py). + import av + + from observe.aruco import detect_markers, mask_convey_region, polygon_area + try: with av.open(str(self.video_path)) as container: stream = container.streams.video[0] diff --git a/tests/test_describe_lazy_imports.py b/tests/test_describe_lazy_imports.py new file mode 100644 index 000000000..1487b104a --- /dev/null +++ b/tests/test_describe_lazy_imports.py @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright (c) 2026 sol pbc + +import sys + + +def test_importing_categories_does_not_load_av_or_cv2(monkeypatch): + for mod in ( + "av", + "cv2", + "observe.aruco", + "observe.describe", + "observe.screen", + ): + monkeypatch.delitem(sys.modules, mod, raising=False) + + from observe.describe import CATEGORIES + + assert isinstance(CATEGORIES, dict) + assert len(CATEGORIES) > 0 + assert "av" not in sys.modules + assert "cv2" not in sys.modules