140 lines
4.7 KiB
Python
140 lines
4.7 KiB
Python
"""os.name provides the type of the operating system!"""
|
|
from os import name, listdir
|
|
import dataclasses
|
|
import re
|
|
import subprocess
|
|
import threading
|
|
import av
|
|
import av.container
|
|
import cv2
|
|
from ipkvm import logger
|
|
|
|
@dataclasses.dataclass
|
|
class VideoDevice:
|
|
"""
|
|
Container for video input device data.
|
|
"""
|
|
friendly_name: str
|
|
path: str
|
|
video_formats: dict[str, dict[str, list[float]]]
|
|
|
|
def check_valid_device_linux(devices: list[str], valid_cameras: dict[str, str]):
|
|
"""
|
|
Uses v4l2-ctl to determine whether a video device actually provides video.
|
|
Takes list of /dev/videoX strings.
|
|
Returns a dictionary of /dev/videoX strings as keys and friendly device names as values.
|
|
"""
|
|
for device in devices:
|
|
cmd = ["v4l2-ctl", "-d", device, "--all"]
|
|
lines = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
check=True).stdout.decode().strip().splitlines()
|
|
|
|
if v4l2_check_capability(lines):
|
|
for line in lines:
|
|
if "Model" in line:
|
|
model_name = line.split(':')[1].strip()
|
|
valid_cameras.update({model_name: device})
|
|
|
|
return valid_cameras
|
|
|
|
def v4l2_check_capability(lines: list[str]):
|
|
"""
|
|
Checks if the provided stdout from v4l2-ctl identifies a device as a video capture device.
|
|
"""
|
|
for i, line in enumerate(lines):
|
|
if "Device Caps" in line:
|
|
x = i
|
|
while "Media Driver Info" not in lines[x]:
|
|
x += 1
|
|
if "Video Capture" in lines[x]:
|
|
return True
|
|
|
|
return False
|
|
|
|
def scan_devices():
|
|
"""
|
|
Creates a list of valid video devices and returns a dictionary of friendly names and device paths.
|
|
"""
|
|
valid_devices: dict[str, str] = {}
|
|
|
|
if name == "posix":
|
|
devices: list[str] = [f"/dev/{x}" for x in listdir("/dev/") if "video" in x]
|
|
valid_devices = check_valid_device_linux(devices, valid_devices)
|
|
|
|
elif name == "nt":
|
|
# implement camera acqisition for windows
|
|
pass
|
|
|
|
return valid_devices
|
|
|
|
def get_video_formats(device: str):
|
|
"""
|
|
Use v4l2-ctl (Linux) or FFplay (Windows) to get camera operating modes and sort by quality.
|
|
"""
|
|
video_formats: dict[str, dict[str, list[str]]] = {}
|
|
# Translates fourcc codec labels to the type that FFmpeg uses, and blacklists bad codecs.
|
|
fourcc_format_translation: dict[str, str | None] = {
|
|
"YUYV": None,
|
|
"MJPG": "mjpeg"
|
|
}
|
|
|
|
if name == "posix":
|
|
cmd = ["v4l2-ctl", "-d", device, "--list-formats-ext"]
|
|
output = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True).stdout.decode().strip()
|
|
matches = re.finditer(r"(?P<format>'\S*')|(?P<resolution>\d*x\d*)|(?P<fps>\d*.\d* fps)", output)
|
|
|
|
if matches:
|
|
video_format = None
|
|
resolution = None
|
|
fps = None
|
|
|
|
for match in matches:
|
|
if re.match(r"'\S*'", match[0]):
|
|
video_format = fourcc_format_translation[match[0].strip('\'')]
|
|
resolution = None
|
|
fps = None
|
|
elif re.match(r"\d*x\d*", match[0]):
|
|
resolution = match[0]
|
|
fps = None
|
|
elif re.match(r"\d*.\d* fps", match[0]):
|
|
fps = match[0].rstrip(" fps")
|
|
|
|
if video_format and resolution and fps:
|
|
if video_format not in video_formats:
|
|
video_formats.update({
|
|
video_format: {
|
|
resolution: [fps]
|
|
}
|
|
})
|
|
elif resolution not in video_formats[video_format]:
|
|
video_formats[video_format].update({
|
|
resolution: [fps]
|
|
})
|
|
else:
|
|
video_formats[video_format][resolution].append(fps)
|
|
|
|
return video_formats
|
|
|
|
def create_device_list():
|
|
"""
|
|
Create a complete device list including name, device ID, and available video formats.
|
|
"""
|
|
device_names = scan_devices()
|
|
device_list: list[VideoDevice] = []
|
|
|
|
for device_name in device_names:
|
|
device_list.append(VideoDevice(device_name, device_names[device_name], get_video_formats(device_names[device_name])))
|
|
if len(device_list[-1].video_formats) == 0:
|
|
device_list.pop()
|
|
|
|
if len(device_list) > 0:
|
|
logger.info(f"Found {len(device_list)} video devices.")
|
|
return device_list
|
|
|
|
else:
|
|
raise RuntimeError("No video devices found on this system!")
|
|
|
|
# EZ DEBUGGING
|
|
if __name__ == '__main__':
|
|
print(create_device_list())
|