From b95627ae7c0397e8afbf5f405b546c4b0fb66bc5 Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Thu, 22 Aug 2024 17:59:22 +0200 Subject: [PATCH 01/14] Add tests for vivid video capture with synch IO and gevent --- tests/test_video.py | 46 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/test_video.py b/tests/test_video.py index b5c5011..533b266 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -23,6 +23,7 @@ numpy = None from linuxpy.device import device_number +from linuxpy.io import GeventIO, fopen from linuxpy.video import raw from linuxpy.video.device import ( BufferFlag, @@ -829,6 +830,51 @@ def _(source=input_type): frame2 = next(stream) test_frame(frame2, width, height, pixel_format, source) + @test_vivid_only(f"vivid gevent capture ({iname})") + def _(source=input_type): + with Device(VIVID_CAPTURE_DEVICE, io=GeventIO) as capture_dev: + capture_dev.set_input(0) + width, height, pixel_format = 640, 480, PixelFormat.RGB24 + capture = VideoCapture(capture_dev, source=source) + capture.set_format(width, height, pixel_format) + fmt = capture.get_format() + assert fmt.width == width + assert fmt.height == height + assert fmt.pixel_format == pixel_format + + capture.set_fps(120) + assert capture.get_fps() >= 60 + + with capture: + stream = iter(capture) + frame1 = next(stream) + test_frame(frame1, width, height, pixel_format, source) + frame2 = next(stream) + test_frame(frame2, width, height, pixel_format, source) + + @test_vivid_only(f"vivid sync capture ({iname})") + def _(source=input_type): + with fopen(VIVID_CAPTURE_DEVICE, rw=True, blocking=True) as fobj: + capture_dev = Device(fobj) + capture_dev.set_input(0) + width, height, pixel_format = 640, 480, PixelFormat.RGB24 + capture = VideoCapture(capture_dev, source=source) + capture.set_format(width, height, pixel_format) + fmt = capture.get_format() + assert fmt.width == width + assert fmt.height == height + assert fmt.pixel_format == pixel_format + + capture.set_fps(120) + assert capture.get_fps() >= 60 + + with capture: + stream = iter(capture) + frame1 = next(stream) + test_frame(frame1, width, height, pixel_format, source) + frame2 = next(stream) + test_frame(frame2, width, height, pixel_format, source) + @test_vivid_only(f"vivid async capture ({iname})") async def _(source=input_type): with Device(VIVID_CAPTURE_DEVICE) as capture_dev: From ee3a4224a8b44be1158a60b5b8b7ec9fcbe1f4df Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Mon, 26 Aug 2024 07:28:44 +0200 Subject: [PATCH 02/14] Add missing gevent dev dependency --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 01218ab..0c1a642 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ Source = "https://github.com/tiagocoutinho/linuxpy" [project.optional-dependencies] dev = [ "build>=0.10.0", + "gevent>=21", "twine>=4.0.2", "ward>=0.68.0b0", "ward-coverage>=0.3.0", From d8e2ef68588fc3fc2aadeee37d496329571bf413 Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Mon, 26 Aug 2024 08:53:50 +0200 Subject: [PATCH 03/14] Add selection tests --- linuxpy/video/device.py | 4 ++-- tests/test_video.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/linuxpy/video/device.py b/linuxpy/video/device.py index 5bdc0fa..cb50c28 100644 --- a/linuxpy/video/device.py +++ b/linuxpy/video/device.py @@ -569,8 +569,8 @@ def set_selection(fd, buffer_type, target, rectangle): def get_selection( fd, buffer_type: BufferType, - target: SelectionTarget = SelectionTarget.CROP_DEFAULT, -): + target: SelectionTarget = SelectionTarget.CROP, +) -> Rect: sel = raw.v4l2_selection() sel.type = buffer_type sel.target = target diff --git a/tests/test_video.py b/tests/test_video.py index 533b266..7e62375 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -35,6 +35,7 @@ Memory, PixelFormat, Priority, + SelectionTarget, V4L2Error, VideoCapture, VideoOutput, @@ -787,6 +788,25 @@ def _(): capture_dev.set_input(active_input) +@test_vivid_only("selection with vivid") +def _(): + with Device(VIVID_CAPTURE_DEVICE) as capture_dev: + capture_dev.set_input(1) + dft_sel = capture_dev.get_selection(BufferType.VIDEO_CAPTURE, SelectionTarget.CROP_DEFAULT) + assert dft_sel.left >= 0 + assert dft_sel.top >= 0 + assert 0 < dft_sel.width < 10_000 + assert 0 < dft_sel.height < 10_000 + + sel = capture_dev.get_selection(BufferType.VIDEO_CAPTURE, SelectionTarget.CROP) + assert sel.left >= 0 + assert sel.top >= 0 + assert 0 < sel.width < 10_000 + assert 0 < sel.height < 10_000 + + capture_dev.set_selection(BufferType.VIDEO_CAPTURE, SelectionTarget.CROP, dft_sel) + + def test_frame(frame, width, height, pixel_format, source): size = width * height * 3 assert len(frame.data) == size From 6e54ad4db15ad06e4c72f3888913c6868f0c0a12 Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Mon, 26 Aug 2024 08:54:47 +0200 Subject: [PATCH 04/14] Add meta format tests --- tests/test_video.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_video.py b/tests/test_video.py index 7e62375..bfa964a 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -33,6 +33,7 @@ Device, InputCapabilities, Memory, + MetaFormat, PixelFormat, Priority, SelectionTarget, @@ -773,6 +774,11 @@ def _(): assert len(meta_capture_dev.info.frame_sizes) == 0 assert len(meta_capture_dev.info.formats) > 0 + meta_fmt = meta_capture_dev.get_format(BufferType.META_CAPTURE) + assert meta_fmt.format in MetaFormat + assert meta_fmt.width >= 0 + assert meta_fmt.height >= 0 + @test_vivid_only("vivid inputs") def _(): From 641bb9b168bcc2f0396bb2536ee72ec3fcdc58ee Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Mon, 26 Aug 2024 17:27:03 +0200 Subject: [PATCH 05/14] Fix n>=2 dimension controls --- linuxpy/video/device.py | 29 ++++++++--------------------- tests/test_video.py | 25 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/linuxpy/video/device.py b/linuxpy/video/device.py index cb50c28..5103b08 100644 --- a/linuxpy/video/device.py +++ b/linuxpy/video/device.py @@ -594,31 +594,18 @@ def get_control(fd, id): CTRL_TYPE_CTYPE_STRUCT = { - ControlType.AREA: raw.v4l2_area, + # ControlType.AREA: raw.v4l2_area, } def _struct_for_ctrl_type(ctrl_type): ctrl_type = ControlType(ctrl_type).name.lower() name = f"v4l2_ctrl_{ctrl_type}" - return getattr(raw, name) - - -def _field_for_control(control): - has_payload = ControlFlag.HAS_PAYLOAD in ControlFlag(control.flags) - if has_payload: - if control.type == ControlType.INTEGER: - return "p_s32" - elif control.type == ControlType.INTEGER64: - return "p_s64" - elif control.type == ControlType.STRING: - return "string" - else: - ctrl_name = ControlType(control.type).name.lower() - return f"p_{ctrl_name}" - if control.type == ControlType.INTEGER64: - return "value64" - return "value" + try: + return getattr(raw, name) + except AttributeError: + name = f"v4l2_{ctrl_type}" + return getattr(raw, name) def get_ctrl_type_struct(ctrl_type): @@ -703,13 +690,13 @@ def _prepare_write_controls_values(control: raw.v4l2_query_ext_ctrl, value: obje else: array_type = CTRL_TYPE_CTYPE_ARRAY.get(control.type) raw_control.size = control.elem_size * control.elems - field = _field_for_control(control) # a struct: assume value is proper raw struct if array_type is None: value = ctypes.pointer(value) else: value = convert_to_ctypes_array(value, control.nr_of_dims, array_type) - setattr(raw_control, field, value) + ptr = ctypes.cast(value, ctypes.c_void_p) + raw_control.ptr = ptr else: if control.type == ControlType.INTEGER64: raw_control.value64 = value diff --git a/tests/test_video.py b/tests/test_video.py index bfa964a..7052554 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -668,6 +668,15 @@ def _(): assert "" in repr(controls.s32_2_element_array) + # matrix 8x16 + value = [list(range(i + 16, i + 16 + 8)) for i in range(16)] + controls.u16_8x16_matrix.value = value + result = [row[:] for row in controls.u16_8x16_matrix.value] + assert value == result + + # struct + assert controls.area is controls["area"] + area = controls.area.value + assert isinstance(area, raw.v4l2_area) + width, height = randint(10, 1000), randint(10, 1000) + controls.area.value = raw.v4l2_area(width, height) + area = controls.area.value + assert area.width == width + assert area.height == height + # Unknown with raises(KeyError): _ = list(controls.with_class("unknown class")) From 91d681d17eeb5eba28027d2f11764bf34db0f426 Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Mon, 26 Aug 2024 17:46:30 +0200 Subject: [PATCH 06/14] Add vidio get/set priority test --- tests/test_video.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/test_video.py b/tests/test_video.py index 7052554..f6cc5e0 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -746,8 +746,6 @@ def _(): outputs = capture_dev.info.outputs assert len(outputs) == 0 - assert isinstance(capture_dev.get_priority(), Priority) - crop = capture_dev.info.crop_capabilities assert not crop for inp in inputs: @@ -973,3 +971,13 @@ def _(): with out: data = os.urandom(size) out.write(data) + + +@test_vivid_only("vivid priority") +def _(): + with Device(VIVID_CAPTURE_DEVICE) as capture_dev: + assert isinstance(capture_dev.get_priority(), Priority) + + capture_dev.set_priority(Priority.BACKGROUND) + + assert capture_dev.get_priority() == Priority.BACKGROUND From 8cabcb92b9c2034886dfa525d5ed4637d712aa06 Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Mon, 26 Aug 2024 18:16:05 +0200 Subject: [PATCH 07/14] Vivid std --- linuxpy/codegen/video.py | 52 ++++++++++++++++++++-------------------- linuxpy/video/device.py | 12 +++++----- linuxpy/video/raw.py | 52 ++++++++++++++++++++-------------------- tests/test_video.py | 13 +++++++++- 4 files changed, 70 insertions(+), 59 deletions(-) diff --git a/linuxpy/codegen/video.py b/linuxpy/codegen/video.py index 5c3c802..2370d60 100644 --- a/linuxpy/codegen/video.py +++ b/linuxpy/codegen/video.py @@ -67,32 +67,32 @@ # STD macros are too complicated to auto generate class StandardID(enum.IntFlag): - V4L2_STD_PAL_B = 0x00000001 - V4L2_STD_PAL_B1 = 0x00000002 - V4L2_STD_PAL_G = 0x00000004 - V4L2_STD_PAL_H = 0x00000008 - V4L2_STD_PAL_I = 0x00000010 - V4L2_STD_PAL_D = 0x00000020 - V4L2_STD_PAL_D1 = 0x00000040 - V4L2_STD_PAL_K = 0x00000080 - V4L2_STD_PAL_M = 0x00000100 - V4L2_STD_PAL_N = 0x00000200 - V4L2_STD_PAL_Nc = 0x00000400 - V4L2_STD_PAL_60 = 0x00000800 - V4L2_STD_NTSC_M = 0x00001000 # BTSC - V4L2_STD_NTSC_M_JP = 0x00002000 # EIA-J - V4L2_STD_NTSC_443 = 0x00004000 - V4L2_STD_NTSC_M_KR = 0x00008000 # FM A2 - V4L2_STD_SECAM_B = 0x00010000 - V4L2_STD_SECAM_D = 0x00020000 - V4L2_STD_SECAM_G = 0x00040000 - V4L2_STD_SECAM_H = 0x00080000 - V4L2_STD_SECAM_K = 0x00100000 - V4L2_STD_SECAM_K1 = 0x00200000 - V4L2_STD_SECAM_L = 0x00400000 - V4L2_STD_SECAM_LC = 0x00800000 - V4L2_STD_ATSC_8_VSB = 0x01000000 - V4L2_STD_ATSC_16_VSB = 0x02000000 + PAL_B = 0x00000001 + PAL_B1 = 0x00000002 + PAL_G = 0x00000004 + PAL_H = 0x00000008 + PAL_I = 0x00000010 + PAL_D = 0x00000020 + PAL_D1 = 0x00000040 + PAL_K = 0x00000080 + PAL_M = 0x00000100 + PAL_N = 0x00000200 + PAL_Nc = 0x00000400 + PAL_60 = 0x00000800 + NTSC_M = 0x00001000 # BTSC + NTSC_M_JP = 0x00002000 # EIA-J + NTSC_443 = 0x00004000 + NTSC_M_KR = 0x00008000 # FM A2 + SECAM_B = 0x00010000 + SECAM_D = 0x00020000 + SECAM_G = 0x00040000 + SECAM_H = 0x00080000 + SECAM_K = 0x00100000 + SECAM_K1 = 0x00200000 + SECAM_L = 0x00400000 + SECAM_LC = 0x00800000 + ATSC_8_VSB = 0x01000000 + ATSC_16_VSB = 0x02000000 {iocs_body}""" diff --git a/linuxpy/video/device.py b/linuxpy/video/device.py index 5103b08..fa0a215 100644 --- a/linuxpy/video/device.py +++ b/linuxpy/video/device.py @@ -805,20 +805,20 @@ def set_output(fd, index: int): ioctl(fd, IOC.S_OUTPUT, index) -def get_std(fd): +def get_std(fd) -> StandardID: out = ctypes.c_uint64() ioctl(fd, IOC.G_STD, out) - return out.value + return StandardID(out.value) def set_std(fd, std): ioctl(fd, IOC.S_STD, std) -def query_std(fd): +def query_std(fd) -> StandardID: out = ctypes.c_uint64() ioctl(fd, IOC.QUERYSTD, out) - return out.value + return StandardID(out.value) # Helpers @@ -973,13 +973,13 @@ def get_output(self): def set_output(self, index: int): return set_output(self.fileno(), index) - def get_std(self): + def get_std(self) -> StandardID: return get_std(self.fileno()) def set_std(self, std): return set_std(self.fileno(), std) - def query_std(self): + def query_std(self) -> StandardID: return query_std(self.fileno()) diff --git a/linuxpy/video/raw.py b/linuxpy/video/raw.py index df20ec5..7c2750b 100644 --- a/linuxpy/video/raw.py +++ b/linuxpy/video/raw.py @@ -4038,32 +4038,32 @@ class v4l2_subdev_client_capability(Struct): class StandardID(enum.IntFlag): - V4L2_STD_PAL_B = 0x00000001 - V4L2_STD_PAL_B1 = 0x00000002 - V4L2_STD_PAL_G = 0x00000004 - V4L2_STD_PAL_H = 0x00000008 - V4L2_STD_PAL_I = 0x00000010 - V4L2_STD_PAL_D = 0x00000020 - V4L2_STD_PAL_D1 = 0x00000040 - V4L2_STD_PAL_K = 0x00000080 - V4L2_STD_PAL_M = 0x00000100 - V4L2_STD_PAL_N = 0x00000200 - V4L2_STD_PAL_Nc = 0x00000400 - V4L2_STD_PAL_60 = 0x00000800 - V4L2_STD_NTSC_M = 0x00001000 # BTSC - V4L2_STD_NTSC_M_JP = 0x00002000 # EIA-J - V4L2_STD_NTSC_443 = 0x00004000 - V4L2_STD_NTSC_M_KR = 0x00008000 # FM A2 - V4L2_STD_SECAM_B = 0x00010000 - V4L2_STD_SECAM_D = 0x00020000 - V4L2_STD_SECAM_G = 0x00040000 - V4L2_STD_SECAM_H = 0x00080000 - V4L2_STD_SECAM_K = 0x00100000 - V4L2_STD_SECAM_K1 = 0x00200000 - V4L2_STD_SECAM_L = 0x00400000 - V4L2_STD_SECAM_LC = 0x00800000 - V4L2_STD_ATSC_8_VSB = 0x01000000 - V4L2_STD_ATSC_16_VSB = 0x02000000 + PAL_B = 0x00000001 + PAL_B1 = 0x00000002 + PAL_G = 0x00000004 + PAL_H = 0x00000008 + PAL_I = 0x00000010 + PAL_D = 0x00000020 + PAL_D1 = 0x00000040 + PAL_K = 0x00000080 + PAL_M = 0x00000100 + PAL_N = 0x00000200 + PAL_Nc = 0x00000400 + PAL_60 = 0x00000800 + NTSC_M = 0x00001000 # BTSC + NTSC_M_JP = 0x00002000 # EIA-J + NTSC_443 = 0x00004000 + NTSC_M_KR = 0x00008000 # FM A2 + SECAM_B = 0x00010000 + SECAM_D = 0x00020000 + SECAM_G = 0x00040000 + SECAM_H = 0x00080000 + SECAM_K = 0x00100000 + SECAM_K1 = 0x00200000 + SECAM_L = 0x00400000 + SECAM_LC = 0x00800000 + ATSC_8_VSB = 0x01000000 + ATSC_16_VSB = 0x02000000 class IOC(enum.IntEnum): diff --git a/tests/test_video.py b/tests/test_video.py index f6cc5e0..c352b64 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -37,6 +37,7 @@ PixelFormat, Priority, SelectionTarget, + StandardID, V4L2Error, VideoCapture, VideoOutput, @@ -646,6 +647,8 @@ def _(): @test_vivid_only("controls with vivid") def _(): with Device(VIVID_CAPTURE_DEVICE) as device: + device.set_input(0) + controls = device.controls # Brightness @@ -939,7 +942,6 @@ async def _(source=input_type): capture.set_fps(120) assert capture.get_fps() >= 60 - with capture: i = 0 async for frame in capture: @@ -981,3 +983,12 @@ def _(): capture_dev.set_priority(Priority.BACKGROUND) assert capture_dev.get_priority() == Priority.BACKGROUND + + +@test_vivid_only("vivid std") +def _(): + with Device(VIVID_CAPTURE_DEVICE) as capture_dev: + capture_dev.set_input(1) + assert StandardID.PAL_B in capture_dev.get_std() + + assert StandardID.PAL_B in capture_dev.query_std() From 5d3dcc730e7d9c8b01b736f1742a2161e867d2c3 Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Mon, 26 Aug 2024 18:16:18 +0200 Subject: [PATCH 08/14] Vivid set/get output --- tests/test_video.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_video.py b/tests/test_video.py index c352b64..08041d7 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -962,6 +962,9 @@ def _(): @test_vivid_only("vivid output") def _(): with Device(VIVID_OUTPUT_DEVICE) as output_dev: + output_dev.set_output(0) + assert output_dev.get_output() == 0 + width, height, pixel_format = 640, 480, PixelFormat.RGB24 size = width * height * 3 out = VideoOutput(output_dev) From 5c5d35d0a7867a17055f19e3076fcdc4a8176ebd Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Wed, 28 Aug 2024 18:25:55 +0200 Subject: [PATCH 09/14] Update test prepare docs --- docs/develop.md | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/docs/develop.md b/docs/develop.md index e8e4c59..db34476 100644 --- a/docs/develop.md +++ b/docs/develop.md @@ -36,24 +36,37 @@ $ python -m linuxpy.codegen.cli ## Running tests -Some video tests will only run with a properly configured `vivid` driver. +First make sure your user belongs to `input` and `video` groups (create those +groups if they don't exist): ```console -$ sudo modprobe vivid n_devs=1 vid_cap_nr=190 vid_out_nr=191 meta_cap_nr=192 meta_out_nr=193 +$ sudo addgroup input +$ sudo addgroup video +$ sudo addgroup led +$ sudo adduser $USER input +$ sudo adduser $USER video ``` -Additionally the user which runs the tests will need read/write access to -`/dev/video190`, `/dev/video191`, `/dev/video192` and `/dev/video193`. -On most systems this can be achieved by adding the user to the `video` group: +(reboot if necessary for those changes to take effect) -```console -$ sudo addgroup $USER video +Change the udev rules so these groups have access to the devices used by tests: + +Create a new rules file (ex: `/etc/udev/rules.d/80-device.rules`): + +``` +KERNEL=="event[0-9]*", SUBSYSTEM=="input", GROUP="input", MODE:="0660" +KERNEL=="uinput", SUBSYSTEM=="misc", GROUP="input", MODE:="0660" +SUBSYSTEM=="video4linux", MODE:="0666" +KERNEL=="uleds", GROUP="input", MODE:="0660" +SUBSYSTEM=="leds", ACTION=="add", RUN+="/bin/chmod -R g=u,o=u /sys%p" +SUBSYSTEM=="leds", ACTION=="change", ENV{TRIGGER}!="none", RUN+="/bin/chmod -R g=u,o=u /sys%p" ``` -Some input tests require the user which runs the tests to have read/write -access to `/dev/uinput`. -On most systems this can be achieved by adding the user to the `input` group: +Finally, make sure all kernel modules are installed: ```console -$ sudo addgroup $USER input +$ sudo modprobe uinput +$ sudo modprobe uleds +$ sudo modprobe -r vivid +$ sudo modprobe vivid n_devs=1 vid_cap_nr=190 vid_out_nr=191 meta_cap_nr=192 meta_out_nr=193 ``` From 952bd977a3b3b07161eb76184f94faa2e85e6de5 Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Wed, 28 Aug 2024 18:30:01 +0200 Subject: [PATCH 10/14] Update pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9348348..2ef298e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks default_language_version: - python: python3.10 + python: python3.12 repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 From 54c51a3dd1d010beecbf3be9d710c155a2a2105a Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Wed, 28 Aug 2024 18:26:16 +0200 Subject: [PATCH 11/14] Rework vivid test conditions --- tests/test_video.py | 71 +++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/tests/test_video.py b/tests/test_video.py index 08041d7..7b71580 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -5,10 +5,8 @@ # Distributed under the GPLv3 license. See LICENSE for more info. import os -import subprocess from contextlib import ExitStack, contextmanager from errno import EINVAL, ENODATA -from functools import cache from inspect import isgenerator from math import isclose from pathlib import Path @@ -573,34 +571,23 @@ def _(display=hardware): VIVID_CAPTURE_DEVICE, VIVID_OUTPUT_DEVICE, VIVID_META_CAPTURE_DEVICE, VIVID_META_OUTPUT_DEVICE = VIVID_TEST_DEVICES -@cache -def is_vivid_installed(): - result = subprocess.run("lsmod", capture_output=True, check=False, timeout=1) - return result.returncode == 0 and b"vivid" in result.stdout - - def is_vivid_prepared(): return all(path.exists() for path in VIVID_TEST_DEVICES) -vivid_only = skip(when=not is_vivid_prepared(), reason="vivid is not prepared")( - skip(when=not is_vivid_installed(), reason="vivid is not installed") -) - - -def test_vivid_only(*args, **kwargs): - kwargs.setdefault("tags", []).append("vivid") - return vivid_only(test(*args, **kwargs)) +vivid_only = skip(when=not is_vivid_prepared(), reason="vivid is not prepared") -@test_vivid_only("list vivid files") +@vivid_only +@test("list vivid files") def _(): video_files = list(iter_video_files()) for video_file in VIVID_TEST_DEVICES: assert video_file in video_files -@test_vivid_only("list vivid capture files") +@vivid_only +@test("list vivid capture files") def _(): video_files = list(iter_video_capture_files()) assert VIVID_CAPTURE_DEVICE in video_files @@ -608,7 +595,8 @@ def _(): assert video_file not in video_files -@test_vivid_only("list vivid output files") +@vivid_only +@test("list vivid output files") def _(): video_files = list(iter_video_output_files()) assert VIVID_OUTPUT_DEVICE in video_files @@ -617,7 +605,8 @@ def _(): assert video_file not in video_files -@test_vivid_only("list vivid devices") +@vivid_only +@test("list vivid devices") def _(): devices = list(iter_devices()) device_names = [dev.filename for dev in devices] @@ -625,7 +614,8 @@ def _(): assert video_file in device_names -@test_vivid_only("list vivid capture devices") +@vivid_only +@test("list vivid capture devices") def _(): devices = list(iter_video_capture_devices()) device_names = [dev.filename for dev in devices] @@ -634,7 +624,8 @@ def _(): assert video_file not in device_names -@test_vivid_only("list vivid output devices") +@vivid_only +@test("list vivid output devices") def _(): devices = list(iter_video_output_devices()) device_names = [dev.filename for dev in devices] @@ -644,7 +635,8 @@ def _(): assert video_file not in device_names -@test_vivid_only("controls with vivid") +@vivid_only +@test("controls with vivid") def _(): with Device(VIVID_CAPTURE_DEVICE) as device: device.set_input(0) @@ -726,7 +718,8 @@ def _(): _ = list(controls.with_class(55)) -@test_vivid_only("info with vivid") +@vivid_only +@test("info with vivid") def _(): with Device(VIVID_CAPTURE_DEVICE) as capture_dev: dev_caps = capture_dev.info.device_capabilities @@ -806,7 +799,8 @@ def _(): assert meta_fmt.height >= 0 -@test_vivid_only("vivid inputs") +@vivid_only +@test("vivid inputs") def _(): with Device(VIVID_CAPTURE_DEVICE) as capture_dev: inputs = capture_dev.info.inputs @@ -820,7 +814,8 @@ def _(): capture_dev.set_input(active_input) -@test_vivid_only("selection with vivid") +@vivid_only +@test("selection with vivid") def _(): with Device(VIVID_CAPTURE_DEVICE) as capture_dev: capture_dev.set_input(1) @@ -860,7 +855,8 @@ def test_frame(frame, width, height, pixel_format, source): for input_type in (None, Capability.STREAMING): iname = "auto" if input_type is None else input_type.name - @test_vivid_only(f"vivid capture ({iname})") + @vivid_only + @test(f"vivid capture ({iname})") def _(source=input_type): with Device(VIVID_CAPTURE_DEVICE) as capture_dev: capture_dev.set_input(0) @@ -882,7 +878,8 @@ def _(source=input_type): frame2 = next(stream) test_frame(frame2, width, height, pixel_format, source) - @test_vivid_only(f"vivid gevent capture ({iname})") + @vivid_only + @test(f"vivid gevent capture ({iname})") def _(source=input_type): with Device(VIVID_CAPTURE_DEVICE, io=GeventIO) as capture_dev: capture_dev.set_input(0) @@ -904,7 +901,8 @@ def _(source=input_type): frame2 = next(stream) test_frame(frame2, width, height, pixel_format, source) - @test_vivid_only(f"vivid sync capture ({iname})") + @vivid_only + @test(f"vivid sync capture ({iname})") def _(source=input_type): with fopen(VIVID_CAPTURE_DEVICE, rw=True, blocking=True) as fobj: capture_dev = Device(fobj) @@ -927,7 +925,8 @@ def _(source=input_type): frame2 = next(stream) test_frame(frame2, width, height, pixel_format, source) - @test_vivid_only(f"vivid async capture ({iname})") + @vivid_only + @test(f"vivid async capture ({iname})") async def _(source=input_type): with Device(VIVID_CAPTURE_DEVICE) as capture_dev: capture_dev.set_input(0) @@ -951,7 +950,8 @@ async def _(source=input_type): break -@test_vivid_only("vivid capture no capability") +@vivid_only +@test("vivid capture no capability") def _(): with Device(VIVID_OUTPUT_DEVICE) as output_dev: stream = iter(output_dev) @@ -959,7 +959,8 @@ def _(): next(stream) -@test_vivid_only("vivid output") +@vivid_only +@test("vivid output") def _(): with Device(VIVID_OUTPUT_DEVICE) as output_dev: output_dev.set_output(0) @@ -978,7 +979,8 @@ def _(): out.write(data) -@test_vivid_only("vivid priority") +@vivid_only +@test("vivid priority") def _(): with Device(VIVID_CAPTURE_DEVICE) as capture_dev: assert isinstance(capture_dev.get_priority(), Priority) @@ -988,7 +990,8 @@ def _(): assert capture_dev.get_priority() == Priority.BACKGROUND -@test_vivid_only("vivid std") +@vivid_only +@test("vivid std") def _(): with Device(VIVID_CAPTURE_DEVICE) as capture_dev: capture_dev.set_input(1) From a319100efd50d36b8f4218717af4d773d3f9ca01 Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Wed, 28 Aug 2024 18:48:00 +0200 Subject: [PATCH 12/14] Remove unused del control by id --- linuxpy/video/device.py | 7 ------- tests/test_video.py | 1 + 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/linuxpy/video/device.py b/linuxpy/video/device.py index fa0a215..4782b49 100644 --- a/linuxpy/video/device.py +++ b/linuxpy/video/device.py @@ -1054,13 +1054,6 @@ def __setattr__(self, key, value): self._init_if_needed() self[key] = value - def __delattr__(self, key): - self._init_if_needed() - try: - del self[key] - except KeyError as error: - raise AttributeError(key) from error - def __missing__(self, key): self._init_if_needed() for v in self.values(): diff --git a/tests/test_video.py b/tests/test_video.py index 7b71580..7d38bc2 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -401,6 +401,7 @@ def _(camera=hardware): controls = device.controls assert len(controls) == 3 brightness = controls["brightness"] + assert controls.brightness is brightness contrast = controls["contrast"] white_balance_automatic = controls["white_balance_automatic"] From a76048c38db6d6f68cf0fbfc617f6559f347e79c Mon Sep 17 00:00:00 2001 From: Jose Tiago Macara Coutinho Date: Thu, 29 Aug 2024 07:38:40 +0200 Subject: [PATCH 13/14] Add boolean control test --- tests/test_video.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_video.py b/tests/test_video.py index 7d38bc2..cc3f005 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -673,6 +673,22 @@ def _(): assert " Date: Thu, 29 Aug 2024 07:58:51 +0200 Subject: [PATCH 14/14] More control tests --- tests/test_video.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/test_video.py b/tests/test_video.py index cc3f005..67640c3 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -28,6 +28,7 @@ BufferType, Capability, ControlClass, + ControlType, Device, InputCapabilities, Memory, @@ -689,7 +690,19 @@ def _(): boolean.value = value assert boolean.value is expected - # String + # menu + menu = controls.menu + assert menu is controls["menu"] + assert menu.type == ControlType.MENU + assert menu[1] == menu.data[1] + + menu.value = 1 + assert menu.value == 1 + + with raises(OSError): + menu.value = 0 + + # string assert controls.string is controls["string"] current_value = controls.string.value try: @@ -709,6 +722,8 @@ def _(): finally: controls.s32_2_element_array.value = current_value + assert controls.s32_2_element_array.default[:] == [2, 2] + assert "" in repr(controls.s32_2_element_array) # matrix 8x16 @@ -727,6 +742,9 @@ def _(): assert area.width == width assert area.height == height + # button + controls.button.push() + # Unknown with raises(KeyError): _ = list(controls.with_class("unknown class"))