Skip to content

Commit 3a39026

Browse files
fixate caps to the nearest desired width, height & framerate
Harden the input selection process a bit. The width, height or framerate fields in a Gst cap could possibly contain non fixated values. For example, framerate could be a list of fractions (issue #3). fix: make --input-{width, height, framerate} required with sane defaults and try to fixate to the neareast of those values. Now, --input-{width, height, framerate} might not exactly match the chosen inputs, but at least we'll choose something close to it. Which is what we usually want anyways.
1 parent 7062e96 commit 3a39026

File tree

2 files changed

+77
-60
lines changed

2 files changed

+77
-60
lines changed

webcam_filters/gst.py

Lines changed: 64 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@
4646
def add_filters(
4747
input_dev: str,
4848
output_dev: str,
49-
input_width: t.Optional[int],
50-
input_height: t.Optional[int],
51-
input_framerate: t.Optional[str],
49+
input_width: int,
50+
input_height: int,
51+
input_framerate: Fraction,
5252
input_media_type: t.Optional[str],
5353
background_blur: t.Optional[int],
5454
selfie_segmentation_model: SelfieSegmentationModel,
@@ -57,48 +57,54 @@ def add_filters(
5757
"""
5858
Run filters pipeline.
5959
"""
60-
src = Gst.ElementFactory.make("v4l2src")
61-
src.set_property("device", input_dev)
62-
src.set_state(Gst.State.READY)
63-
caps = src.get_static_pad("src").query_caps(None)
60+
caps = query_device_caps(input_dev)
6461

65-
src.set_state(Gst.State.NULL)
62+
if caps is None:
63+
click.echo(f"unable to determine capabilities for device {input_dev!r}")
64+
raise click.Abort()
6665

6766
structs = []
68-
for c in caps:
69-
keep = True
70-
71-
if input_media_type is not None and input_media_type != c.get_name():
72-
keep = False
73-
74-
if input_width is not None and input_width != c.get_value("width"):
75-
keep = False
76-
77-
if input_height is not None and input_height != c.get_value("height"):
78-
keep = False
67+
for s in caps:
68+
if input_media_type is not None and input_media_type != s.get_name():
69+
continue
70+
71+
s.fixate_field_nearest_int("width", input_width)
72+
s.fixate_field_nearest_int("height", input_height)
73+
s.fixate_field_nearest_fraction(
74+
"framerate",
75+
input_framerate.numerator,
76+
input_framerate.denominator
77+
)
78+
structs.append(s)
79+
80+
if not structs:
81+
click.echo("unable to find a suitable input format")
82+
raise click.Abort()
83+
84+
def struct_sort_key(x):
85+
_, width = x.get_int("width")
86+
_, height = x.get_int("height")
87+
_, fr_numerator, fr_denomantor = x.get_fraction("framerate")
88+
return (
89+
abs(input_width - width),
90+
abs(input_height - height),
91+
abs(input_framerate - Fraction(fr_numerator, fr_denomantor or 1))
92+
)
7993

80-
if input_framerate is not None:
81-
if input_framerate != str(c.get_value("framerate")):
82-
keep = False
94+
structs = sorted(structs, key=struct_sort_key)
8395

84-
if keep:
85-
structs.append(c)
96+
s = structs[0]
97+
new_caps = Gst.Caps.new_empty()
98+
new_caps.append_structure(s)
8699

87-
structs = sorted(
88-
structs,
89-
key=lambda x: (
90-
Fraction(str(x.get_value("framerate"))),
91-
x.get_value("width"),
92-
x.get_value("height"),
93-
),
94-
reverse=True
100+
click.echo(
101+
f"Selectd input: media-type={s.get_name()}, width={s.get_value('width')} "
102+
f"height={s.get_value('height')} framerate={s.get_value('framerate')}"
95103
)
96-
new_caps = Gst.Caps.new_empty()
97-
for s in structs:
98-
new_caps.append_structure(s)
99104

100105
pipeline = Gst.Pipeline.new()
101106

107+
src = Gst.ElementFactory.make("v4l2src")
102108
inputfilter = Gst.ElementFactory.make("capsfilter")
103109
decodebin = Gst.ElementFactory.make("decodebin")
104110
rgbconvert = Gst.ElementFactory.make("videoconvert")
@@ -108,6 +114,7 @@ def add_filters(
108114
sinkfilter = Gst.ElementFactory.make("capsfilter")
109115
sink = Gst.ElementFactory.make("v4l2sink")
110116

117+
src.set_property("device", input_dev)
111118
inputfilter.set_property("caps", new_caps)
112119
rgbfilter.set_property(
113120
"caps",
@@ -212,32 +219,40 @@ def on_bus_message(
212219
return True
213220

214221

215-
def print_device_caps(
216-
ctx: click.Context,
217-
param: click.Parameter,
218-
value: str,
219-
) -> None:
220-
"""
221-
Print device capabilities and exit.
222-
"""
223-
if not value or ctx.resilient_parsing:
224-
return
225-
222+
def query_device_caps(dev: str) -> t.Optional[Gst.Caps]:
226223
src = Gst.ElementFactory.make("v4l2src")
227-
src.set_property("device", value)
224+
src.set_property("device", dev)
228225

229226
src.set_state(Gst.State.READY)
230227
res = src.get_state(1000)
231228
pad = src.get_static_pad("src")
232229

233230
if res.state != Gst.State.READY or pad is None:
234-
click.echo(f"unable to determine capabilities for device {value!r}")
235-
ctx.exit(1)
231+
return None
236232

237233
caps = pad.query_caps(None)
238234
src.set_state(Gst.State.NULL)
239235

240236
if caps.is_any():
237+
return None
238+
239+
return caps
240+
241+
242+
def print_device_caps(
243+
ctx: click.Context,
244+
param: click.Parameter,
245+
value: str,
246+
) -> None:
247+
"""
248+
Print device capabilities and exit.
249+
"""
250+
if not value or ctx.resilient_parsing:
251+
return
252+
253+
caps = query_device_caps(value)
254+
255+
if caps is None:
241256
click.echo(f"unable to determine capabilities for device {value!r}")
242257
ctx.exit(1)
243258

webcam_filters/main.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import typing as t
22

3+
from fractions import Fraction
34
from .click import (
45
click,
56
print_version,
@@ -25,26 +26,27 @@
2526
)
2627
@click.option(
2728
"--input-width",
28-
help="Input width.",
29+
help="Preferred width.",
2930
type=int,
30-
default=None,
31+
default="1280",
3132
)
3233
@click.option(
3334
"--input-height",
34-
help="Input height.",
35+
help="Preferred height.",
3536
type=int,
36-
default=None,
37+
default="720",
3738
)
3839
@click.option(
3940
"--input-framerate",
40-
help="Input framerate specified in fractional format (e.g. '30/1').",
41-
type=str,
42-
default=None,
41+
help="Preferred framerate specified in fractional format (e.g. '30/1').",
42+
type=Fraction,
43+
default="30/1",
4344
)
4445
@click.option(
4546
"--input-media-type",
4647
help="""
47-
Input media type (e.g. 'image/jpeg' or 'x-raw-video').
48+
Input media type (e.g. 'image/jpeg' or 'video/x-raw').
49+
If specified ONLY that type is allowed. Otherwise, everything is allowed.
4850
Use --list-dev-caps to see all available formats.
4951
""",
5052
type=str,
@@ -120,9 +122,9 @@
120122
)
121123
def cli(
122124
input_dev: str,
123-
input_width: t.Optional[int],
124-
input_height: t.Optional[int],
125-
input_framerate: t.Optional[str],
125+
input_width: int,
126+
input_height: int,
127+
input_framerate: Fraction,
126128
input_media_type: t.Optional[str],
127129
output_dev: str,
128130
background_blur: t.Optional[int],

0 commit comments

Comments
 (0)