arcsecond 3.16.1__tar.gz → 3.16.2__tar.gz
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.
Potentially problematic release.
This version of arcsecond might be problematic. Click here for more details.
- {arcsecond-3.16.1 → arcsecond-3.16.2}/PKG-INFO +2 -2
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/backups.py +12 -2
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/local.py +6 -4
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/sources/opencv.py +30 -3
- {arcsecond-3.16.1 → arcsecond-3.16.2}/pyproject.toml +1 -1
- arcsecond-3.16.2/tests/test_imagesources_webcam.py +140 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/uv.lock +1 -1
- {arcsecond-3.16.1 → arcsecond-3.16.2}/.docker/Dockerfile_postgres +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/.docker/Dockerfile_redis +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/.github/dependabot.yml +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/.github/workflows/pythonpublish.yml +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/.github/workflows/tests.yml +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/.gitignore +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/LICENSE +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/Makefile +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/README.md +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/__init__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/__version__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/alpaca/__init__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/alpaca/commands.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/alpaca/dome_probe.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/api/__init__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/api/config.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/api/constants.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/api/endpoint.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/api/main.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/api/resources.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cli.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/__init__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/auth.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/resources.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/__init__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/constants.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/context.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/datafiles/__init__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/datafiles/context.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/datafiles/errors.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/datafiles/uploader.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/datafiles/utils.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/errors.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/logger.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/uploader.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/utils.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploader/walker.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/cloud/uploads.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/errors.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/__init__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/checks.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/constants.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/database.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/docker/__init__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/docker/constants.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/docker/containers.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/docker/docker-compose.yml +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/docker/images.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/docker/utils.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/keygen/__init__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/keygen/client.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/keygen/utils.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/main.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/postgres/init-db.sh +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/setup.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/utils.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/hosting/validation.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/__init__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/commands.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/proxy.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/registry.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/sources/__init__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/sources/base.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/sources/filewatch.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/sources/network.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/imagesources/store.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/options.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/arcsecond/targets.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/examples/example_upload_files.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/requirements.txt +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/setup.cfg +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/setup.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/sonar-project.properties +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/__init__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/api/__init__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/api/test_api.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/api/test_api_endpoint.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/api/test_config.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/api/test_targets.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/__init__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/__init__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/datafiles/__init__.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/datafiles/test_uploader_errors.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/datafiles/test_uploader_full_process.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/datafiles/test_uploader_init.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/datafiles/test_uploader_prepare.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/datafiles/test_uploader_upload.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/conftest.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/fixtures/file1.fits +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/test_alpaca_probe.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/test_cli.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/test_hosting_database.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/test_hosting_local.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/test_imagesources_netcam.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/test_imagesources_store.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/test_targets_planning.py +0 -0
- {arcsecond-3.16.1 → arcsecond-3.16.2}/tests/utils.py +0 -0
|
@@ -331,7 +331,15 @@ def _run_destination_backups_command(*args, timeout=60):
|
|
|
331
331
|
return None
|
|
332
332
|
try:
|
|
333
333
|
out = subprocess.run(
|
|
334
|
-
[
|
|
334
|
+
[
|
|
335
|
+
"docker",
|
|
336
|
+
"exec",
|
|
337
|
+
API_CONTAINER,
|
|
338
|
+
"python",
|
|
339
|
+
"manage.py",
|
|
340
|
+
"destination_backups",
|
|
341
|
+
*args,
|
|
342
|
+
],
|
|
335
343
|
capture_output=True,
|
|
336
344
|
text=True,
|
|
337
345
|
check=False,
|
|
@@ -367,7 +375,9 @@ def _remote_only_items(items, remote_names):
|
|
|
367
375
|
if not m or name in local_names:
|
|
368
376
|
continue
|
|
369
377
|
try:
|
|
370
|
-
ts = datetime.strptime(m.group(1), "%Y%m%d-%H%M%S").replace(
|
|
378
|
+
ts = datetime.strptime(m.group(1), "%Y%m%d-%H%M%S").replace(
|
|
379
|
+
tzinfo=timezone.utc
|
|
380
|
+
)
|
|
371
381
|
except ValueError:
|
|
372
382
|
continue
|
|
373
383
|
result.append((name, ts))
|
|
@@ -228,7 +228,7 @@ def _extract_optional_service_block(packaged_text, name):
|
|
|
228
228
|
end_index = next((i for i, line in enumerate(lines) if line.strip() == end), None)
|
|
229
229
|
if begin_index is None or end_index is None or end_index < begin_index:
|
|
230
230
|
return None
|
|
231
|
-
return lines[begin_index:end_index + 1]
|
|
231
|
+
return lines[begin_index : end_index + 1]
|
|
232
232
|
|
|
233
233
|
|
|
234
234
|
def _with_trailing_newline_like(lines, original_text):
|
|
@@ -244,7 +244,7 @@ def _strip_optional_service_block(text, name):
|
|
|
244
244
|
end_index = next((i for i, line in enumerate(lines) if line.strip() == end), None)
|
|
245
245
|
if begin_index is None or end_index is None or end_index < begin_index:
|
|
246
246
|
return text
|
|
247
|
-
del lines[begin_index:end_index + 1]
|
|
247
|
+
del lines[begin_index : end_index + 1]
|
|
248
248
|
# Drop the blank separator the block carried, so strip(splice(x)) == x.
|
|
249
249
|
if begin_index < len(lines) and not lines[begin_index].strip():
|
|
250
250
|
del lines[begin_index]
|
|
@@ -297,12 +297,14 @@ def _reconcile_version_header(current_text, expected_text):
|
|
|
297
297
|
updated = (
|
|
298
298
|
current_text[: current_match.start()]
|
|
299
299
|
+ expected_match.group(0)
|
|
300
|
-
+ current_text[current_match.end():]
|
|
300
|
+
+ current_text[current_match.end() :]
|
|
301
301
|
)
|
|
302
302
|
return updated if updated == expected_text else None
|
|
303
303
|
|
|
304
304
|
|
|
305
|
-
def write_docker_compose_file(
|
|
305
|
+
def write_docker_compose_file(
|
|
306
|
+
enabled_services=frozenset(), removed_services=frozenset()
|
|
307
|
+
) -> Path:
|
|
306
308
|
"""
|
|
307
309
|
Materialise the packaged docker-compose.yml in the current directory.
|
|
308
310
|
|
|
@@ -7,6 +7,7 @@ Designed for USB webcams attached to the host running the proxy.
|
|
|
7
7
|
|
|
8
8
|
import asyncio
|
|
9
9
|
import logging
|
|
10
|
+
import sys
|
|
10
11
|
from typing import Optional
|
|
11
12
|
|
|
12
13
|
from .base import FrameSource, SourceInfo
|
|
@@ -18,6 +19,28 @@ _JPEG_QUALITY = 60 # 0-100
|
|
|
18
19
|
_MAX_PROBE = 10 # device indices to probe during detection
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
def _capture_backend():
|
|
23
|
+
"""The OpenCV backend to open device *indices* with, for this platform.
|
|
24
|
+
|
|
25
|
+
Never ``CAP_ANY``. Left to choose for itself, OpenCV walks its backend list
|
|
26
|
+
and — on a machine with no camera at that index — ends up at FFMPEG, which
|
|
27
|
+
interprets an integer index by asking libavdevice to enumerate DirectShow
|
|
28
|
+
devices. That path is slow, cannot work, and prints a wall of
|
|
29
|
+
``Could not enumerate audio only devices`` to stderr for every index probed.
|
|
30
|
+
|
|
31
|
+
Pinning the platform's native backend also keeps index numbering stable:
|
|
32
|
+
detection and ``open()`` must agree on what device index 1 means, and each
|
|
33
|
+
backend enumerates in its own order.
|
|
34
|
+
"""
|
|
35
|
+
import cv2
|
|
36
|
+
|
|
37
|
+
if sys.platform == "win32":
|
|
38
|
+
return cv2.CAP_DSHOW
|
|
39
|
+
if sys.platform == "darwin":
|
|
40
|
+
return cv2.CAP_AVFOUNDATION
|
|
41
|
+
return cv2.CAP_V4L2
|
|
42
|
+
|
|
43
|
+
|
|
21
44
|
class OpenCVWebcamSource(FrameSource):
|
|
22
45
|
kind = "webcam"
|
|
23
46
|
poll_interval = _FRAME_INTERVAL
|
|
@@ -29,10 +52,13 @@ class OpenCVWebcamSource(FrameSource):
|
|
|
29
52
|
self._cap = None
|
|
30
53
|
|
|
31
54
|
async def open(self) -> None:
|
|
32
|
-
import cv2
|
|
55
|
+
import cv2 # noqa: F401
|
|
33
56
|
|
|
34
57
|
loop = asyncio.get_running_loop()
|
|
35
|
-
|
|
58
|
+
backend = _capture_backend()
|
|
59
|
+
self._cap = await loop.run_in_executor(
|
|
60
|
+
None, cv2.VideoCapture, self.index, backend
|
|
61
|
+
)
|
|
36
62
|
if not await loop.run_in_executor(None, self._cap.isOpened):
|
|
37
63
|
raise RuntimeError(f"Cannot open webcam at device index {self.index}.")
|
|
38
64
|
|
|
@@ -75,9 +101,10 @@ def detect_webcams(max_index: int = _MAX_PROBE) -> list[SourceInfo]:
|
|
|
75
101
|
"""Blocking probe of device indices 0..max_index-1."""
|
|
76
102
|
import cv2
|
|
77
103
|
|
|
104
|
+
backend = _capture_backend()
|
|
78
105
|
found: list[SourceInfo] = []
|
|
79
106
|
for i in range(max_index):
|
|
80
|
-
cap = cv2.VideoCapture(i)
|
|
107
|
+
cap = cv2.VideoCapture(i, backend)
|
|
81
108
|
if not cap.isOpened():
|
|
82
109
|
cap.release()
|
|
83
110
|
continue
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Tests for the OpenCV webcam source (``webcam:``).
|
|
2
|
+
|
|
3
|
+
The point of most of these is the capture *backend*. Device indices must never
|
|
4
|
+
be opened with ``CAP_ANY``: OpenCV then falls through its backend list to
|
|
5
|
+
FFMPEG, which tries to enumerate DirectShow devices, floods stderr and cannot
|
|
6
|
+
succeed. Detection and ``open()`` must also agree on the backend, or they would
|
|
7
|
+
not agree on what device index 1 refers to.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import sys
|
|
12
|
+
from unittest.mock import MagicMock, patch
|
|
13
|
+
|
|
14
|
+
from arcsecond.imagesources.sources.opencv import (
|
|
15
|
+
OpenCVWebcamSource,
|
|
16
|
+
_capture_backend,
|
|
17
|
+
detect_webcams,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _fake_cv2():
|
|
22
|
+
cv2 = MagicMock()
|
|
23
|
+
cv2.CAP_ANY = 0
|
|
24
|
+
cv2.CAP_DSHOW = 700
|
|
25
|
+
cv2.CAP_AVFOUNDATION = 1200
|
|
26
|
+
cv2.CAP_V4L2 = 200
|
|
27
|
+
cv2.CAP_PROP_FRAME_WIDTH = 3
|
|
28
|
+
cv2.CAP_PROP_FRAME_HEIGHT = 4
|
|
29
|
+
cv2.CAP_PROP_FPS = 5
|
|
30
|
+
cv2.IMWRITE_JPEG_QUALITY = 1
|
|
31
|
+
cv2.VideoCapture.return_value.isOpened.return_value = True
|
|
32
|
+
cv2.VideoCapture.return_value.get.return_value = 0
|
|
33
|
+
return cv2
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# Backend selection
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_capture_backend_is_dshow_on_windows():
|
|
42
|
+
cv2 = _fake_cv2()
|
|
43
|
+
with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "win32"):
|
|
44
|
+
assert _capture_backend() == cv2.CAP_DSHOW
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_capture_backend_is_avfoundation_on_macos():
|
|
48
|
+
cv2 = _fake_cv2()
|
|
49
|
+
with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "darwin"):
|
|
50
|
+
assert _capture_backend() == cv2.CAP_AVFOUNDATION
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_capture_backend_is_v4l2_on_linux():
|
|
54
|
+
cv2 = _fake_cv2()
|
|
55
|
+
with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "linux"):
|
|
56
|
+
assert _capture_backend() == cv2.CAP_V4L2
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_capture_backend_is_never_cap_any():
|
|
60
|
+
cv2 = _fake_cv2()
|
|
61
|
+
for platform in ("win32", "darwin", "linux", "freebsd14"):
|
|
62
|
+
with (
|
|
63
|
+
patch.dict(sys.modules, {"cv2": cv2}),
|
|
64
|
+
patch.object(sys, "platform", platform),
|
|
65
|
+
):
|
|
66
|
+
assert _capture_backend() != cv2.CAP_ANY
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
# Detection
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_detect_webcams_pins_the_backend_on_every_probe():
|
|
75
|
+
cv2 = _fake_cv2()
|
|
76
|
+
with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "win32"):
|
|
77
|
+
detect_webcams(max_index=3)
|
|
78
|
+
|
|
79
|
+
assert cv2.VideoCapture.call_count == 3
|
|
80
|
+
for i, call in enumerate(cv2.VideoCapture.call_args_list):
|
|
81
|
+
assert call.args == (i, cv2.CAP_DSHOW)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_detect_webcams_reports_every_open_index():
|
|
85
|
+
cv2 = _fake_cv2()
|
|
86
|
+
with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "linux"):
|
|
87
|
+
found = detect_webcams(max_index=2)
|
|
88
|
+
|
|
89
|
+
assert [i.id for i in found] == ["webcam:0", "webcam:1"]
|
|
90
|
+
assert all(i.kind == "webcam" for i in found)
|
|
91
|
+
assert [i.extra["index"] for i in found] == [0, 1]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_detect_webcams_skips_indices_that_do_not_open():
|
|
95
|
+
cv2 = _fake_cv2()
|
|
96
|
+
cv2.VideoCapture.return_value.isOpened.side_effect = [False, True, False]
|
|
97
|
+
|
|
98
|
+
with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "linux"):
|
|
99
|
+
found = detect_webcams(max_index=3)
|
|
100
|
+
|
|
101
|
+
assert [i.id for i in found] == ["webcam:1"]
|
|
102
|
+
# Every probe is released, including the ones that never opened.
|
|
103
|
+
assert cv2.VideoCapture.return_value.release.call_count == 3
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_detect_webcams_finds_nothing_when_no_device_opens():
|
|
107
|
+
cv2 = _fake_cv2()
|
|
108
|
+
cv2.VideoCapture.return_value.isOpened.return_value = False
|
|
109
|
+
|
|
110
|
+
with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "win32"):
|
|
111
|
+
assert detect_webcams(max_index=4) == []
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
# Opening a source
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_open_uses_the_same_backend_as_detection():
|
|
120
|
+
cv2 = _fake_cv2()
|
|
121
|
+
source = OpenCVWebcamSource(2)
|
|
122
|
+
|
|
123
|
+
with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "win32"):
|
|
124
|
+
asyncio.run(source.open())
|
|
125
|
+
|
|
126
|
+
cv2.VideoCapture.assert_called_once_with(2, cv2.CAP_DSHOW)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_open_raises_when_the_device_will_not_open():
|
|
130
|
+
cv2 = _fake_cv2()
|
|
131
|
+
cv2.VideoCapture.return_value.isOpened.return_value = False
|
|
132
|
+
source = OpenCVWebcamSource(0)
|
|
133
|
+
|
|
134
|
+
with patch.dict(sys.modules, {"cv2": cv2}), patch.object(sys, "platform", "linux"):
|
|
135
|
+
try:
|
|
136
|
+
asyncio.run(source.open())
|
|
137
|
+
except RuntimeError as e:
|
|
138
|
+
assert "device index 0" in str(e)
|
|
139
|
+
else:
|
|
140
|
+
raise AssertionError("expected open() to raise")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/datafiles/test_uploader_errors.py
RENAMED
|
File without changes
|
{arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/datafiles/test_uploader_full_process.py
RENAMED
|
File without changes
|
|
File without changes
|
{arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/datafiles/test_uploader_prepare.py
RENAMED
|
File without changes
|
{arcsecond-3.16.1 → arcsecond-3.16.2}/tests/cloud/uploader/datafiles/test_uploader_upload.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|