1+ #!/usr/bin/env python3
2+ # -*- coding:utf-8 vi:ts=4:noexpandtab -*-
3+
4+ import subprocess
5+ import re
6+ import sys
7+ import json
8+ import os
9+
10+ def check_if_v4l2_ctl_avail ():
11+ try :
12+ subprocess .run (['v4l2-ctl' , '--help' ], check = True , stdout = subprocess .PIPE , stderr = subprocess .PIPE )
13+ except subprocess .CalledProcessError :
14+ print ("v4l2-ctl is not installed. Exiting." )
15+ sys .exit (1 )
16+
17+ def get_subdev_paths ():
18+ base_path = '/dev'
19+ return sorted ([os .path .join (base_path , dev ) for dev in os .listdir (base_path ) if dev .startswith ('v4l-subdev' )])
20+
21+ def get_video_device_paths ():
22+ base_path = '/dev'
23+ return sorted ([os .path .join (base_path , dev ) for dev in os .listdir (base_path ) if dev .startswith ('video' )])
24+
25+ def get_mbus_codes (dev_path ):
26+ command = f"v4l2-ctl -d { dev_path } --list-subdev-mbus-codes 0"
27+ result = subprocess .run (command , shell = True , text = True , capture_output = True )
28+ if result .returncode != 0 :
29+ return []
30+ pattern = r"0x([0-9a-fA-F]+):\s+([A-Za-z0-9_]+)"
31+ return re .findall (pattern , result .stdout )
32+
33+ def get_resolutions (dev_path , mbus_code ):
34+ command = f"v4l2-ctl -d { dev_path } --list-subdev-framesizes pad=0,code=0x{ mbus_code } "
35+ result = subprocess .run (command , shell = True , text = True , capture_output = True )
36+ if result .returncode != 0 :
37+ return []
38+ pattern = r"Size Range: (\d+)x(\d+)"
39+ matches = re .findall (pattern , result .stdout )
40+ return [{'width' : int (w ), 'height' : int (h )} for w , h in matches ]
41+
42+ def get_formats_and_resolutions (dev_path ):
43+ command = f"v4l2-ctl -d { dev_path } --list-formats-ext"
44+ result = subprocess .run (command , shell = True , text = True , capture_output = True )
45+ if result .returncode != 0 :
46+ return []
47+
48+ devices = []
49+ fmt_pattern = r"^\s*\[\d+\]: '(\w+)' \(.*?\)"
50+ size_pattern = r"\s+Size: Discrete (\d+)x(\d+)"
51+ current_fmt = None
52+
53+ for line in result .stdout .splitlines ():
54+ fmt_match = re .match (fmt_pattern , line )
55+ if fmt_match :
56+ current_fmt = fmt_match .group (1 )
57+ continue
58+
59+ size_match = re .match (size_pattern , line )
60+ if size_match and current_fmt :
61+ width , height = map (int , size_match .groups ())
62+ devices .append ({
63+ 'format' : current_fmt ,
64+ 'width' : width ,
65+ 'height' : height ,
66+ 'label' : f"{ width } x{ height } _{ current_fmt } " ,
67+ 'value' : f"{ current_fmt } _{ width } x{ height } "
68+ })
69+
70+ return devices
71+
72+ def get_card_name (dev_path ):
73+ command = f"v4l2-ctl -d { dev_path } --all"
74+ result = subprocess .run (command , shell = True , text = True , capture_output = True )
75+ if result .returncode != 0 :
76+ return None
77+
78+ match = re .search (r"Card type\s+:\s+(.+)" , result .stdout )
79+ if match :
80+ return match .group (1 ).strip ()
81+
82+ return None
83+
84+ check_if_v4l2_ctl_avail ()
85+
86+ devices = []
87+
88+ # Process CSI cameras
89+ for dev_path in get_subdev_paths ():
90+ mbus_codes = get_mbus_codes (dev_path )
91+ if not mbus_codes :
92+ continue
93+
94+ card_name = get_card_name (dev_path ) or "Unnamed CSI Camera"
95+
96+ device_caps = {
97+ # Don't specify a device path for CSI cameras,
98+ # but generate a unique ID for them.
99+ 'id' : f"CSI-{ re .sub (r'[^a-zA-Z0-9]' , '_' , card_name )} " ,
100+ 'device' : None ,
101+ 'type' : 'CSI' ,
102+ 'card_name' : card_name ,
103+ 'caps' : []
104+ }
105+
106+ for mbus_code , pixel_format in mbus_codes :
107+ resolutions = get_resolutions (dev_path , mbus_code )
108+
109+ for res in resolutions :
110+ fmt = pixel_format .split ("MEDIA_BUS_FMT_" )[1 ] if "MEDIA_BUS_FMT_" in pixel_format else pixel_format
111+ cap_info = {
112+ 'format' : fmt ,
113+ 'width' : res ['width' ],
114+ 'height' : res ['height' ],
115+ 'label' : f"{ res ['width' ]} x{ res ['height' ]} _{ fmt } " ,
116+ 'value' : f"{ mbus_code } _{ fmt } _{ res ['width' ]} x{ res ['height' ]} "
117+ }
118+ device_caps ['caps' ].append (cap_info )
119+
120+ if device_caps ['caps' ]:
121+ devices .append (device_caps )
122+
123+ # Process UVC cameras
124+ for dev_path in get_video_device_paths ():
125+ caps = get_formats_and_resolutions (dev_path )
126+ if caps :
127+ devices .append ({
128+ 'id' : dev_path , # use the device path as the unique ID for UVC cameras
129+ 'device' : dev_path ,
130+ 'type' : 'UVC' ,
131+ 'card_name' : get_card_name (dev_path ) or "Unnamed UVC Camera" ,
132+ 'caps' : caps
133+ })
134+
135+ print (json .dumps (devices , indent = 4 ))
0 commit comments