framesource 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- frame_processors/__init__.py +37 -0
- frame_source/__init__.py +50 -0
- framesource/__init__.py +93 -0
- framesource/_msmf_config.py +26 -0
- framesource/_version.py +24 -0
- framesource/discovery.py +109 -0
- framesource/errors.py +82 -0
- framesource/factory.py +290 -0
- framesource/processors/__init__.py +34 -0
- framesource/processors/equirectangular360_processor.py +577 -0
- framesource/processors/fisheye2equirectangular_processor.py +328 -0
- framesource/processors/frame_processor.py +30 -0
- framesource/processors/hyperspectral_processor.py +23 -0
- framesource/processors/realsense_depth_processor.py +90 -0
- framesource/py.typed +0 -0
- framesource/sources/__init__.py +40 -0
- framesource/sources/audiospectrogram_capture.py +1068 -0
- framesource/sources/basler_capture.py +477 -0
- framesource/sources/folder_capture.py +920 -0
- framesource/sources/genicam_capture.py +681 -0
- framesource/sources/huateng_capture.py +245 -0
- framesource/sources/ipcamera_capture.py +254 -0
- framesource/sources/mvsdk.py +2454 -0
- framesource/sources/realsense_capture.py +565 -0
- framesource/sources/screen_capture.py +800 -0
- framesource/sources/video_capture_base.py +560 -0
- framesource/sources/video_file_capture.py +259 -0
- framesource/sources/webcam_capture.py +511 -0
- framesource/sources/ximea_capture.py +299 -0
- framesource/threading_utils.py +790 -0
- framesource-0.3.0.dist-info/METADATA +787 -0
- framesource-0.3.0.dist-info/RECORD +37 -0
- framesource-0.3.0.dist-info/WHEEL +5 -0
- framesource-0.3.0.dist-info/licenses/LICENSE +21 -0
- framesource-0.3.0.dist-info/scm_file_list.json +276 -0
- framesource-0.3.0.dist-info/scm_version.json +8 -0
- framesource-0.3.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Deprecated compatibility shim for the old ``frame_processors`` package.
|
|
2
|
+
|
|
3
|
+
This package moved to :mod:`framesource.processors`. Importing
|
|
4
|
+
``frame_processors`` still works for now but emits a
|
|
5
|
+
:class:`DeprecationWarning` and will be removed in a future major release.
|
|
6
|
+
Update imports to ``framesource.processors``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import importlib
|
|
10
|
+
import sys
|
|
11
|
+
import warnings
|
|
12
|
+
|
|
13
|
+
warnings.warn(
|
|
14
|
+
"'frame_processors' is deprecated and will be removed in a future major "
|
|
15
|
+
"release; import from 'framesource.processors' instead.",
|
|
16
|
+
DeprecationWarning,
|
|
17
|
+
stacklevel=2,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
import framesource.processors as _processors
|
|
21
|
+
from framesource.processors import * # noqa: F401,F403
|
|
22
|
+
|
|
23
|
+
__all__ = getattr(_processors, "__all__", [])
|
|
24
|
+
|
|
25
|
+
_SUBMODULE_ALIASES = {
|
|
26
|
+
"frame_processor": "framesource.processors.frame_processor",
|
|
27
|
+
"equirectangular360_processor": "framesource.processors.equirectangular360_processor",
|
|
28
|
+
"fisheye2equirectangular_processor": "framesource.processors.fisheye2equirectangular_processor",
|
|
29
|
+
"realsense_depth_processor": "framesource.processors.realsense_depth_processor",
|
|
30
|
+
"hyperspectral_processor": "framesource.processors.hyperspectral_processor",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for _alias, _target in _SUBMODULE_ALIASES.items():
|
|
34
|
+
try:
|
|
35
|
+
sys.modules[f"{__name__}.{_alias}"] = importlib.import_module(_target)
|
|
36
|
+
except Exception:
|
|
37
|
+
pass
|
frame_source/__init__.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Deprecated compatibility shim for the old ``frame_source`` package.
|
|
2
|
+
|
|
3
|
+
This package was renamed to :mod:`framesource`. Importing ``frame_source``
|
|
4
|
+
continues to work for now but emits a :class:`DeprecationWarning` and will be
|
|
5
|
+
removed in a future major release. Update imports to ``framesource``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import importlib
|
|
9
|
+
import sys
|
|
10
|
+
import warnings
|
|
11
|
+
|
|
12
|
+
warnings.warn(
|
|
13
|
+
"'frame_source' is deprecated and will be removed in a future major "
|
|
14
|
+
"release; import from 'framesource' instead.",
|
|
15
|
+
DeprecationWarning,
|
|
16
|
+
stacklevel=2,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
import framesource as _framesource
|
|
20
|
+
from framesource import * # noqa: F401,F403
|
|
21
|
+
from framesource import FrameSourceFactory, VideoCaptureBase # noqa: F401
|
|
22
|
+
|
|
23
|
+
__all__ = getattr(_framesource, "__all__", [])
|
|
24
|
+
__version__ = getattr(_framesource, "__version__", "0.0.0")
|
|
25
|
+
|
|
26
|
+
# Alias the old flat module paths (e.g. ``frame_source.webcam_capture``) onto
|
|
27
|
+
# the new locations so existing deep imports keep working.
|
|
28
|
+
_SUBMODULE_ALIASES = {
|
|
29
|
+
"factory": "framesource.factory",
|
|
30
|
+
"threading_utils": "framesource.threading_utils",
|
|
31
|
+
"video_capture_base": "framesource.sources.video_capture_base",
|
|
32
|
+
"webcam_capture": "framesource.sources.webcam_capture",
|
|
33
|
+
"ipcamera_capture": "framesource.sources.ipcamera_capture",
|
|
34
|
+
"basler_capture": "framesource.sources.basler_capture",
|
|
35
|
+
"genicam_capture": "framesource.sources.genicam_capture",
|
|
36
|
+
"realsense_capture": "framesource.sources.realsense_capture",
|
|
37
|
+
"ximea_capture": "framesource.sources.ximea_capture",
|
|
38
|
+
"huateng_capture": "framesource.sources.huateng_capture",
|
|
39
|
+
"video_file_capture": "framesource.sources.video_file_capture",
|
|
40
|
+
"folder_capture": "framesource.sources.folder_capture",
|
|
41
|
+
"screen_capture": "framesource.sources.screen_capture",
|
|
42
|
+
"audiospectrogram_capture": "framesource.sources.audiospectrogram_capture",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for _alias, _target in _SUBMODULE_ALIASES.items():
|
|
46
|
+
try:
|
|
47
|
+
sys.modules[f"{__name__}.{_alias}"] = importlib.import_module(_target)
|
|
48
|
+
except Exception:
|
|
49
|
+
# Optional/vendor backend unavailable; skip aliasing it.
|
|
50
|
+
pass
|
framesource/__init__.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
# Must be imported before any capture module pulls in cv2 (it configures the
|
|
5
|
+
# OpenCV MSMF backend, which reads its env var at cv2-import time). The `_`
|
|
6
|
+
# prefix sorts it first within this first-party import group.
|
|
7
|
+
from . import _msmf_config as _msmf_config # noqa: F401
|
|
8
|
+
from .discovery import DeviceInfo
|
|
9
|
+
from .errors import (
|
|
10
|
+
FrameSourceError,
|
|
11
|
+
MissingDependencyError,
|
|
12
|
+
NotConnectedError,
|
|
13
|
+
UnknownSourceTypeError,
|
|
14
|
+
)
|
|
15
|
+
from .factory import FrameSourceFactory
|
|
16
|
+
from .sources.video_capture_base import Frame, FrameSourceProtocol, VideoCaptureBase
|
|
17
|
+
from .threading_utils import (
|
|
18
|
+
AsyncFrameSource,
|
|
19
|
+
FrameProducer,
|
|
20
|
+
ProducerConsumer,
|
|
21
|
+
SharedProducer,
|
|
22
|
+
create_producer_consumer_pair,
|
|
23
|
+
multiprocess_frame_producer,
|
|
24
|
+
simple_frame_producer,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
from ._version import version as __version__ # generated by setuptools_scm
|
|
31
|
+
except Exception: # pragma: no cover - fallback when not built/installed
|
|
32
|
+
try:
|
|
33
|
+
from importlib.metadata import version as _pkg_version
|
|
34
|
+
|
|
35
|
+
__version__ = _pkg_version("framesource")
|
|
36
|
+
except Exception:
|
|
37
|
+
__version__ = "0.0.0"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Import capture classes with graceful handling for missing optional
|
|
41
|
+
# dependencies. Unavailable classes are set to None and logged at debug level.
|
|
42
|
+
_OPTIONAL_CLASSES = {
|
|
43
|
+
"WebcamCapture": "webcam_capture",
|
|
44
|
+
"AudioSpectrogramCapture": "audiospectrogram_capture",
|
|
45
|
+
"BaslerCapture": "basler_capture",
|
|
46
|
+
"GenicamCapture": "genicam_capture",
|
|
47
|
+
"IPCameraCapture": "ipcamera_capture",
|
|
48
|
+
"VideoFileCapture": "video_file_capture",
|
|
49
|
+
"FolderCapture": "folder_capture",
|
|
50
|
+
"RealsenseCapture": "realsense_capture",
|
|
51
|
+
"ScreenCapture": "screen_capture",
|
|
52
|
+
"XimeaCapture": "ximea_capture",
|
|
53
|
+
"HuatengCapture": "huateng_capture",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for _class_name, _module_name in _OPTIONAL_CLASSES.items():
|
|
57
|
+
try:
|
|
58
|
+
_module = importlib.import_module(f".sources.{_module_name}", __name__)
|
|
59
|
+
globals()[_class_name] = getattr(_module, _class_name)
|
|
60
|
+
except Exception as e: # noqa: BLE001 - vendor SDKs may raise non-ImportError
|
|
61
|
+
globals()[_class_name] = None
|
|
62
|
+
logger.debug("%s unavailable: %s", _class_name, e)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
__all__ = [
|
|
66
|
+
"FrameSourceFactory",
|
|
67
|
+
"VideoCaptureBase",
|
|
68
|
+
"Frame",
|
|
69
|
+
"FrameSourceProtocol",
|
|
70
|
+
"DeviceInfo",
|
|
71
|
+
"FrameSourceError",
|
|
72
|
+
"MissingDependencyError",
|
|
73
|
+
"UnknownSourceTypeError",
|
|
74
|
+
"NotConnectedError",
|
|
75
|
+
"simple_frame_producer",
|
|
76
|
+
"FrameProducer",
|
|
77
|
+
"multiprocess_frame_producer",
|
|
78
|
+
"create_producer_consumer_pair",
|
|
79
|
+
"ProducerConsumer",
|
|
80
|
+
"AsyncFrameSource",
|
|
81
|
+
"SharedProducer",
|
|
82
|
+
"WebcamCapture",
|
|
83
|
+
"IPCameraCapture",
|
|
84
|
+
"BaslerCapture",
|
|
85
|
+
"GenicamCapture",
|
|
86
|
+
"RealsenseCapture",
|
|
87
|
+
"XimeaCapture",
|
|
88
|
+
"HuatengCapture",
|
|
89
|
+
"VideoFileCapture",
|
|
90
|
+
"FolderCapture",
|
|
91
|
+
"ScreenCapture",
|
|
92
|
+
"AudioSpectrogramCapture",
|
|
93
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Configure OpenCV's Media Foundation (MSMF) backend before ``cv2`` loads.
|
|
2
|
+
|
|
3
|
+
On Windows, OpenCV's MSMF capture backend reads
|
|
4
|
+
``OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS`` **once, when cv2 is first
|
|
5
|
+
imported**. With hardware transforms enabled (the default), opening some
|
|
6
|
+
webcams stalls for 20+ seconds while Media Foundation initializes hardware
|
|
7
|
+
transforms — even though frame throughput afterwards is fine. Disabling them
|
|
8
|
+
makes the camera open near-instantly (measured 22.6s -> 0.33s) with no
|
|
9
|
+
measurable throughput cost.
|
|
10
|
+
|
|
11
|
+
Because the value is read at ``import cv2`` time, it must be set *before* cv2
|
|
12
|
+
is imported anywhere in the process. This module carries no cv2 dependency and
|
|
13
|
+
is imported first by :mod:`framesource`, ahead of any capture module, so the
|
|
14
|
+
setting is in place in time. ``setdefault`` is used so a user who configured
|
|
15
|
+
the variable explicitly (to ``"0"`` or ``"1"``) always wins.
|
|
16
|
+
|
|
17
|
+
Caveat: if the host application runs ``import cv2`` and opens an MSMF device
|
|
18
|
+
*before* importing :mod:`framesource`, cv2 has already cached the value and
|
|
19
|
+
this has no effect; set the environment variable yourself in that case.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
import platform
|
|
24
|
+
|
|
25
|
+
if platform.system() == "Windows":
|
|
26
|
+
os.environ.setdefault("OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS", "0")
|
framesource/_version.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '0.3.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
framesource/discovery.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Structured device descriptor returned by ``discover()`` implementations.
|
|
2
|
+
|
|
3
|
+
Historically each capture type's :meth:`discover` returned a list of plain
|
|
4
|
+
``dict`` objects with type-specific keys. :class:`DeviceInfo` replaces those
|
|
5
|
+
dicts with a typed :mod:`dataclasses` dataclass while remaining fully
|
|
6
|
+
dict-compatible (indexing, ``get``, ``in``, ``keys``/``items``) so existing
|
|
7
|
+
consumers that treated discovery results as dictionaries keep working
|
|
8
|
+
unchanged.
|
|
9
|
+
|
|
10
|
+
The historical webcam dicts used an ``'id'`` key; that is preserved as an
|
|
11
|
+
alias for :attr:`DeviceInfo.device_id`, and any extra type-specific keys are
|
|
12
|
+
kept flat via :attr:`DeviceInfo.metadata` so ``dev['id']``, ``dev['name']``,
|
|
13
|
+
``dev['index']`` and ``dev['backend_name']`` all still resolve.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from typing import Any, Optional
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class DeviceInfo:
|
|
22
|
+
"""A discovered capture device.
|
|
23
|
+
|
|
24
|
+
Instances behave like the plain dicts previously returned by
|
|
25
|
+
``discover()``: they support ``dev[key]``, ``dev.get(key)``,
|
|
26
|
+
``key in dev``, ``dev.keys()`` and ``dev.items()``. The ``'id'`` key is an
|
|
27
|
+
alias for :attr:`device_id`, and :attr:`metadata` entries are exposed as
|
|
28
|
+
flat top-level keys.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
device_id: String identifier used as the ``source`` when creating a
|
|
32
|
+
capture. Exposed under the alias ``'id'`` for dict-compatibility.
|
|
33
|
+
index: OS/enumeration index, if applicable.
|
|
34
|
+
name: Human-readable device name.
|
|
35
|
+
driver: Backend/driver that produced this entry (e.g. ``'opencv'`` or
|
|
36
|
+
``'pyaudio'``).
|
|
37
|
+
id_stable: True when ``device_id`` is stable across reboots/replug
|
|
38
|
+
(e.g. a serial number); False for OS index-based identifiers.
|
|
39
|
+
metadata: Free-form extra fields, kept flat for dict-compatibility.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
device_id: str
|
|
43
|
+
index: Optional[int] = None
|
|
44
|
+
name: str = ""
|
|
45
|
+
driver: str = ""
|
|
46
|
+
id_stable: bool = False
|
|
47
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
48
|
+
|
|
49
|
+
def _flat(self) -> dict[str, Any]:
|
|
50
|
+
"""Return the flat, dict-compatible view (``'id'`` alias + metadata)."""
|
|
51
|
+
flat: dict[str, Any] = {
|
|
52
|
+
"id": self.device_id,
|
|
53
|
+
"index": self.index,
|
|
54
|
+
"name": self.name,
|
|
55
|
+
"driver": self.driver,
|
|
56
|
+
"id_stable": self.id_stable,
|
|
57
|
+
}
|
|
58
|
+
flat.update(self.metadata)
|
|
59
|
+
return flat
|
|
60
|
+
|
|
61
|
+
def __getitem__(self, key: str) -> Any:
|
|
62
|
+
if key in ("id", "device_id"):
|
|
63
|
+
return self.device_id
|
|
64
|
+
if key in ("index", "name", "driver", "id_stable"):
|
|
65
|
+
return getattr(self, key)
|
|
66
|
+
if key in self.metadata:
|
|
67
|
+
return self.metadata[key]
|
|
68
|
+
raise KeyError(key)
|
|
69
|
+
|
|
70
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
71
|
+
"""Dict-style ``get`` with alias/metadata fallback."""
|
|
72
|
+
try:
|
|
73
|
+
return self[key]
|
|
74
|
+
except KeyError:
|
|
75
|
+
return default
|
|
76
|
+
|
|
77
|
+
def __contains__(self, key: str) -> bool:
|
|
78
|
+
if key in ("id", "device_id", "index", "name", "driver", "id_stable"):
|
|
79
|
+
return True
|
|
80
|
+
return key in self.metadata
|
|
81
|
+
|
|
82
|
+
def keys(self):
|
|
83
|
+
"""Return the flat top-level keys (``'id'`` alias + metadata keys)."""
|
|
84
|
+
return self._flat().keys()
|
|
85
|
+
|
|
86
|
+
def values(self):
|
|
87
|
+
"""Return the flat top-level values."""
|
|
88
|
+
return self._flat().values()
|
|
89
|
+
|
|
90
|
+
def items(self):
|
|
91
|
+
"""Return the flat ``(key, value)`` pairs."""
|
|
92
|
+
return self._flat().items()
|
|
93
|
+
|
|
94
|
+
def as_dict(self) -> dict[str, Any]:
|
|
95
|
+
"""Return a faithful dict of the dataclass fields.
|
|
96
|
+
|
|
97
|
+
Unlike the flat dict-compat view, this uses the canonical
|
|
98
|
+
``'device_id'`` key and keeps ``metadata`` nested, so the result
|
|
99
|
+
round-trips: ``DeviceInfo(**info.as_dict())`` reconstructs an
|
|
100
|
+
equivalent instance.
|
|
101
|
+
"""
|
|
102
|
+
return {
|
|
103
|
+
"device_id": self.device_id,
|
|
104
|
+
"index": self.index,
|
|
105
|
+
"name": self.name,
|
|
106
|
+
"driver": self.driver,
|
|
107
|
+
"id_stable": self.id_stable,
|
|
108
|
+
"metadata": dict(self.metadata),
|
|
109
|
+
}
|
framesource/errors.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Exception hierarchy for FrameSource.
|
|
2
|
+
|
|
3
|
+
All library-specific errors derive from :class:`FrameSourceError` so callers
|
|
4
|
+
that want to catch "anything FrameSource raises" have a single type to
|
|
5
|
+
catch. At the same time, several of these exceptions are *also* subclassed
|
|
6
|
+
from the built-in exception type that FrameSource historically raised at the
|
|
7
|
+
same call site (e.g. ``ImportError`` for missing optional dependencies,
|
|
8
|
+
``ValueError`` for bad/unknown arguments).
|
|
9
|
+
|
|
10
|
+
This dual inheritance is deliberate: it preserves backwards compatibility.
|
|
11
|
+
Existing user code written against earlier FrameSource versions may contain
|
|
12
|
+
``except ImportError:`` or ``except ValueError:`` clauses around calls into
|
|
13
|
+
this library. Because ``MissingDependencyError`` is-a ``ImportError`` and
|
|
14
|
+
``UnknownSourceTypeError`` is-a ``ValueError``, that old code keeps working
|
|
15
|
+
unchanged, while new code can catch the more specific FrameSource types (or
|
|
16
|
+
the common ``FrameSourceError`` base) for finer-grained handling.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FrameSourceError(RuntimeError):
|
|
23
|
+
"""Base class for all errors raised by FrameSource.
|
|
24
|
+
|
|
25
|
+
Catch this to handle any FrameSource-specific failure without needing
|
|
26
|
+
to know which built-in exception type it also derives from.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MissingDependencyError(FrameSourceError, ImportError):
|
|
31
|
+
"""Raised when an optional third-party dependency is not installed.
|
|
32
|
+
|
|
33
|
+
Inherits from :class:`ImportError` so existing ``except ImportError:``
|
|
34
|
+
handlers written against earlier FrameSource versions continue to catch
|
|
35
|
+
this exception unchanged.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
package (str): Name of the missing package (or packages).
|
|
39
|
+
extra (Optional[str]): Name of the FrameSource extra that installs
|
|
40
|
+
the dependency (e.g. ``'audio'`` for ``pip install
|
|
41
|
+
framesource[audio]``), if applicable.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, package: str, extra: Optional[str] = None, details: Optional[str] = None):
|
|
45
|
+
"""Initialize the error.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
package: Name of the missing package (or packages).
|
|
49
|
+
extra: Name of the FrameSource extra that installs the
|
|
50
|
+
dependency, if any. When provided, the message includes an
|
|
51
|
+
install hint (``pip install framesource[<extra>]``).
|
|
52
|
+
details: Additional details to append to the message, such as
|
|
53
|
+
the original ``ImportError`` text.
|
|
54
|
+
"""
|
|
55
|
+
self.package = package
|
|
56
|
+
self.extra = extra
|
|
57
|
+
|
|
58
|
+
message = f"{package} is required but not installed."
|
|
59
|
+
if extra:
|
|
60
|
+
message += f" Install with: pip install framesource[{extra}]"
|
|
61
|
+
if details:
|
|
62
|
+
message += f" ({details})"
|
|
63
|
+
|
|
64
|
+
super().__init__(message)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class UnknownSourceTypeError(FrameSourceError, ValueError):
|
|
68
|
+
"""Raised when a requested capture source type is not registered.
|
|
69
|
+
|
|
70
|
+
Inherits from :class:`ValueError` so existing ``except ValueError:``
|
|
71
|
+
handlers written against earlier FrameSource versions continue to catch
|
|
72
|
+
this exception unchanged.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class NotConnectedError(FrameSourceError):
|
|
77
|
+
"""Raised when an operation requires a connected source but it isn't.
|
|
78
|
+
|
|
79
|
+
Reserved for use by subclasses and downstream code that wants to signal
|
|
80
|
+
reads/operations attempted on a disconnected capture source. FrameSource
|
|
81
|
+
itself does not currently raise this from its read paths.
|
|
82
|
+
"""
|