Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for configuration files #43

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 74 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ Format(width=640, height=480, pixelformat=<PixelFormat.MJPEG: 1196444237>}

v4l2py is asyncio friendly:

```bash
```python
$ python -m asyncio

>>> from v4l2py import Device
Expand All @@ -142,7 +142,7 @@ frame 10136

v4l2py is also gevent friendly:

```
```python
$ python

>>> from v4l2py import Device, GeventIO
Expand All @@ -158,6 +158,78 @@ frame 10136

(check [basic gevent](examples/basic_gevent.py) and [web gevent](examples/web/sync.py) examples)

## Configuration files

v4l2py now supports configuration files, allowing to save the current settings
(controls only at this time) of a device to a file:

```python
from v4l2py import Device
from v4l2py.config import ConfigManager

with Device.from_id(0) as cam:
cfg = ConfigManager(cam)
cfg.acquire()
cfg.save("cam.ini")
...
```

The configuration is written to an ini-style file, which might look like this:

```dosini
[device]
driver = uvcvideo
card = Integrated Camera: Integrated C
bus_info = usb-0000:00:14.0-8
version = 6.1.15
legacy_controls = False

[controls]
brightness = 128
contrast = 32
saturation = 64
hue = 0
white_balance_automatic = True
...
```

When loading a configuration file, the content may be validated to ensure it
fits the device it's going to be applied to, and after applying the
configuration it can be verified that the device is in the state that the
configuration file describes:

```python
from v4l2py import Device
from v4l2py.config import ConfigManager

with Device.from_id(0) as cam:
cfg = ConfigManager(cam)
cfg.load("cam.ini")
cfg.validate(pedantic=True)
cfg.apply()
cfg.verify()
```

[v4l2py-ctl](examples/v4l2py-ctl.py) can be used for that purpose, too:

```bash
$ python v4l2py-ctl.py --device /dev/video2 --reset-all
Resetting all controls to default ...

Done.
$ python v4l2py-ctl.py --device /dev/video2 --save cam-defaults.ini
Saving device configuration to /home/mrenzmann/src/v4l2py-o42/cam-defaults.ini

Done.
$
$ # ... after messing around with the controls ...
$ python v4l2py-ctl.py --device /dev/video2 --load cam-defaults.ini
Loading device configuration from /home/mrenzmann/src/v4l2py-o42/cam-defaults.ini

Done.
$
```

## Bonus track

You've been patient enough to read until here so, just for you,
Expand Down
131 changes: 118 additions & 13 deletions examples/v4l2py-ctl.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import argparse
import pathlib

from v4l2py.device import Device, MenuControl, LegacyControl
from v4l2py.device import iter_video_capture_devices, Capability
from v4l2py.config import ConfigManager


def _get_ctrl(cam, control):
Expand All @@ -17,6 +20,26 @@ def _get_ctrl(cam, control):
return ctrl


def list_devices() -> None:
print("Listing all video capture devices ...\n")
for dev in iter_video_capture_devices():
with dev as cam:
print(f"{cam.index:>2}: {cam.info.card}")
print(f"\tdriver : {cam.info.driver}")
print(f"\tversion : {cam.info.version}")
print(f"\tbus : {cam.info.bus_info}")
caps = [
cap.name.lower()
for cap in Capability
if ((cam.info.device_capabilities & cap) == cap)
]
if caps:
print("\tcaps :", ", ".join(caps))
else:
print("\tcaps : none")
print()


def show_control_status(device: str, legacy_controls: bool) -> None:
with Device(device, legacy_controls=legacy_controls) as cam:
print("Showing current status of all controls ...\n")
Expand Down Expand Up @@ -126,58 +149,134 @@ def reset_all_controls(device: str, legacy_controls: bool) -> None:
cam.controls.set_to_default()


def save_to_file(device: str, legacy_controls: bool, filename) -> None:
if isinstance(filename, pathlib.Path):
pass
elif isinstance(filename, str):
filename = pathlib.Path(filename)
else:
raise TypeError(
f"filename expected to be str or pathlib.Path, not {filename.__class__.__name__}"
)

with Device(device, legacy_controls) as cam:
print(f"Saving device configuration to {filename.resolve()}")
cfg = ConfigManager(cam)
cfg.acquire()
cfg.save(filename)
print("")


def load_from_file(
device: str, legacy_controls: bool, filename, pedantic: bool
) -> None:
if isinstance(filename, pathlib.Path):
pass
elif isinstance(filename, str):
filename = pathlib.Path(filename)
else:
raise TypeError(
f"filename expected to be str or pathlib.Path, not {filename.__class__.__name__}"
)

with Device(device, legacy_controls) as cam:
print(f"Loading device configuration from {filename.resolve()}")
cfg = ConfigManager(cam)
cfg.load(filename)
cfg.validate(pedantic=pedantic)
cfg.apply()
cfg.verify()
print("")


def csv(string: str) -> list:
return [v.strip() for v in string.split(",")]


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(
prog="v4l2py-ctl",
description="Example utility to control video capture devices.",
epilog="When no action is given, the control status of the selected device is shown.",
)
parser.add_argument(
"--device",
type=str,
default="0",
metavar="<dev>",
help="use device <dev> instead of /dev/video0; if <dev> starts with a digit, then /dev/video<dev> is used",
)

flags = parser.add_argument_group("Flags")
flags.add_argument(
"--legacy",
default=False,
action="store_true",
help="use legacy controls (default: %(default)s)",
)
parser.add_argument(
flags.add_argument(
"--clipping",
default=False,
action="store_true",
help="when changing numeric controls, enforce the written value to be within allowed range (default: %(default)s)",
)
parser.add_argument(
"--device",
type=str,
default="0",
metavar="<dev>",
help="use device <dev> instead of /dev/video0; if <dev> starts with a digit, then /dev/video<dev> is used",
flags.add_argument(
"--pedantic",
default=False,
action="store_true",
help="be pedantic when validating a loaded configuration (default: %(default)s)",
)
parser.add_argument(

actions = parser.add_argument_group("Actions")
actions.add_argument(
"--list-devices",
default=False,
action="store_true",
help="list all video capture devices",
)
actions.add_argument(
"--get-ctrl",
type=csv,
default=[],
metavar="<ctrl>[,<ctrl>...]",
help="get the values of the specified controls",
)
parser.add_argument(
actions.add_argument(
"--set-ctrl",
type=csv,
default=[],
metavar="<ctrl>=<val>[,<ctrl>=<val>...]",
help="set the values of the specified controls",
)
parser.add_argument(
actions.add_argument(
"--reset-ctrl",
type=csv,
default=[],
metavar="<ctrl>[,<ctrl>...]",
help="reset the specified controls to their default values",
)
parser.add_argument(
actions.add_argument(
"--reset-all",
default=False,
action="store_true",
help="reset all controls to their default value",
)
actions.add_argument(
"--save",
type=str,
dest="save_file",
default=None,
metavar="<filename>",
help="save current configuration to <filename>",
)
actions.add_argument(
"--load",
type=str,
dest="load_file",
default=None,
metavar="<filename>",
help="load configuration from <filename> and apply it to selected device",
)

args = parser.parse_args()

Expand All @@ -186,14 +285,20 @@ def csv(string: str) -> list:
else:
dev = args.device

if args.reset_all:
if args.list_devices:
list_devices()
elif args.reset_all:
reset_all_controls(dev, args.legacy)
elif args.reset_ctrl:
reset_controls(dev, args.reset_ctrl, args.legacy)
elif args.get_ctrl:
get_controls(dev, args.get_ctrl, args.legacy)
elif args.set_ctrl:
set_controls(dev, args.set_ctrl, args.legacy, args.clipping)
elif args.save_file is not None:
save_to_file(dev, args.legacy, args.save_file)
elif args.load_file is not None:
load_from_file(dev, args.legacy, args.load_file, args.pedantic)
else:
show_control_status(dev, args.legacy)

Expand Down
Loading