builders: add field_if_any and field_from_iter variants

This commit adds `field_if_any` variants for `IntoIterator` builders fields.
The field will only be set if the provided collection is not empty.

For `Element` properties and other `Value` based setters, the function takes
a parameter to indicate the `ValueType` to use for the resulting `Value`.

This allows converting this code:

```rust
let webrtcsrc = gst::ElementFactory::make("webrtcsrc")
    .build()
    .unwrap();

if !args.audio_codecs.is_empty() {
    webrtcsrc.set_property("audio-codecs", gst::Array::new(&args.audio_codecs));
}
```

to:

```rust
let webrtcsrc = gst::ElementFactory::make("webrtcsrc")
    .property_if_any::<gst::Array>("audio-codecs", &args.audio_codecs)
    .build()
    .unwrap();
````

Similarly, a new function `field_from_iter()` allows settings the property or
field, regardless of whether it is empty or not:

```rust
let webrtcsrc = gst::ElementFactory::make("webrtcsrc")
    .property_from_iter::<gst::Array>("audio-codecs", &args.audio_codecs)
    .build()
    .unwrap();
````

The above will override the default value if `args.audio_codecs` is empty.
This commit is contained in:
François Laignel 2024-04-27 15:38:59 +02:00
parent e117010bc0
commit e9b8029685
7 changed files with 405 additions and 7 deletions

View file

@ -2,6 +2,7 @@ use std::ops::{Bound::*, RangeBounds};
use gst::Caps;
use glib::value::{SendValue, Value, ValueType};
use glib::IntoGStr;
use crate::{AudioFormat, AudioLayout};
@ -118,6 +119,15 @@ impl<T> AudioCapsBuilder<T> {
}
}
pub fn format_list_if_any(self, formats: impl IntoIterator<Item = AudioFormat>) -> Self {
let mut formats = formats.into_iter().peekable();
if formats.peek().is_some() {
self.format_list(formats)
} else {
self
}
}
pub fn rate(self, rate: i32) -> Self {
Self {
builder: self.builder.field(glib::gstr!("rate"), rate),
@ -164,6 +174,15 @@ impl<T> AudioCapsBuilder<T> {
}
}
pub fn rate_list_if_any(self, rates: impl IntoIterator<Item = i32>) -> Self {
let mut rates = rates.into_iter().peekable();
if rates.peek().is_some() {
self.rate_list(rates)
} else {
self
}
}
pub fn channels(self, channels: i32) -> Self {
Self {
builder: self.builder.field(glib::gstr!("channels"), channels),
@ -210,6 +229,15 @@ impl<T> AudioCapsBuilder<T> {
}
}
pub fn channels_list_if_any(self, channels: impl IntoIterator<Item = i32>) -> Self {
let mut channels = channels.into_iter().peekable();
if channels.peek().is_some() {
self.channels_list(channels)
} else {
self
}
}
pub fn layout(self, layout: AudioLayout) -> Self {
Self {
builder: self
@ -246,6 +274,15 @@ impl<T> AudioCapsBuilder<T> {
}
}
pub fn layout_list_if_any(self, layouts: impl IntoIterator<Item = AudioLayout>) -> Self {
let mut layouts = layouts.into_iter().peekable();
if layouts.peek().is_some() {
self.layout_list(layouts)
} else {
self
}
}
pub fn channel_mask(self, channel_mask: u64) -> Self {
Self {
builder: self
@ -275,13 +312,13 @@ impl<T> AudioCapsBuilder<T> {
}
}
pub fn field(self, name: &str, value: impl Into<glib::Value> + Send) -> Self {
pub fn field(self, name: &str, value: impl Into<Value> + Send) -> Self {
Self {
builder: self.builder.field(name, value),
}
}
pub fn field_if_some(self, name: &str, value: Option<impl Into<glib::Value> + Send>) -> Self {
pub fn field_if_some(self, name: &str, value: Option<impl Into<Value> + Send>) -> Self {
if let Some(value) = value {
self.field(name, value)
} else {
@ -289,6 +326,20 @@ impl<T> AudioCapsBuilder<T> {
}
}
pub fn field_if_any<V: ValueType + FromIterator<SendValue> + Into<Value> + Send>(
self,
name: &str,
iter: impl IntoIterator<Item = impl Into<SendValue>>,
) -> Self {
let mut iter = iter.into_iter().peekable();
if iter.peek().is_some() {
let iter = iter.map(|item| item.into());
self.field(name, V::from_iter(iter))
} else {
self
}
}
#[must_use]
pub fn build(self) -> gst::Caps {
self.builder.build()
@ -321,7 +372,7 @@ fn layout_str(layout: AudioLayout) -> &'static glib::GStr {
#[cfg(test)]
mod tests {
use super::AudioCapsBuilder;
use super::{AudioCapsBuilder, AudioFormat};
#[test]
fn default_encoding() {
@ -336,4 +387,42 @@ mod tests {
let caps = AudioCapsBuilder::for_encoding("audio/mpeg").build();
assert_eq!(caps.structure(0).unwrap().name(), "audio/mpeg");
}
#[test]
fn format_if() {
gst::init().unwrap();
let formats = [AudioFormat::S24be, AudioFormat::S16be, AudioFormat::U8];
let caps_with_format = AudioCapsBuilder::for_encoding("audio/x-raw")
.format_list(formats)
.build();
assert!(caps_with_format
.structure(0)
.unwrap()
.get::<gst::List>("format")
.unwrap()
.iter()
.map(|f| f.get::<String>().unwrap())
.eq(formats.iter().map(|f| f.to_string())));
let caps = AudioCapsBuilder::for_encoding("audio/x-raw")
.format_list_if_some(Some(formats))
.build();
assert_eq!(caps, caps_with_format);
let caps = AudioCapsBuilder::for_encoding("audio/x-raw")
.format_list_if_some(Option::<Vec<AudioFormat>>::None)
.build();
assert!(!caps.structure(0).unwrap().has_field("format"));
let caps = AudioCapsBuilder::for_encoding("audio/x-raw")
.format_list_if_any(formats)
.build();
assert_eq!(caps, caps_with_format);
let caps = AudioCapsBuilder::for_encoding("audio/x-raw")
.format_list_if_any(Vec::<AudioFormat>::new())
.build();
assert!(!caps.structure(0).unwrap().has_field("format"));
}
}

View file

@ -1,6 +1,7 @@
use std::ops::{Bound::*, RangeBounds};
use glib::translate::*;
use glib::value::{SendValue, Value, ValueType};
use gst::Caps;
use crate::VideoFormat;
@ -100,6 +101,15 @@ impl<T> VideoCapsBuilder<T> {
}
}
pub fn format_list_if_any(self, formats: impl IntoIterator<Item = VideoFormat>) -> Self {
let mut formats = formats.into_iter().peekable();
if formats.peek().is_some() {
self.format_list(formats)
} else {
self
}
}
pub fn width(self, width: i32) -> Self {
Self {
builder: self.builder.field(glib::gstr!("width"), width),
@ -146,6 +156,15 @@ impl<T> VideoCapsBuilder<T> {
}
}
pub fn width_list_if_any(self, widths: impl IntoIterator<Item = i32>) -> Self {
let mut widths = widths.into_iter().peekable();
if widths.peek().is_some() {
self.width_list(widths)
} else {
self
}
}
pub fn height(self, height: i32) -> Self {
Self {
builder: self.builder.field(glib::gstr!("height"), height),
@ -192,6 +211,15 @@ impl<T> VideoCapsBuilder<T> {
}
}
pub fn height_list_if_any(self, heights: impl IntoIterator<Item = i32>) -> Self {
let mut heights = heights.into_iter().peekable();
if heights.peek().is_some() {
self.height_list(heights)
} else {
self
}
}
pub fn framerate(self, framerate: gst::Fraction) -> Self {
Self {
builder: self.builder.field(glib::gstr!("framerate"), framerate),
@ -260,6 +288,18 @@ impl<T> VideoCapsBuilder<T> {
}
}
pub fn framerate_list_if_any(
self,
framerates: impl IntoIterator<Item = gst::Fraction>,
) -> Self {
let mut framerates = framerates.into_iter().peekable();
if framerates.peek().is_some() {
self.framerate_list(framerates)
} else {
self
}
}
pub fn pixel_aspect_ratio(self, pixel_aspect_ratio: gst::Fraction) -> Self {
Self {
builder: self.builder.field("pixel-aspect-ratio", pixel_aspect_ratio),
@ -336,13 +376,25 @@ impl<T> VideoCapsBuilder<T> {
}
}
pub fn field(self, name: &str, value: impl Into<glib::Value> + Send) -> Self {
pub fn pixel_aspect_ratio_list_if_any(
self,
pixel_aspect_ratios: impl IntoIterator<Item = gst::Fraction>,
) -> Self {
let mut pixel_aspect_ratios = pixel_aspect_ratios.into_iter().peekable();
if pixel_aspect_ratios.peek().is_some() {
self.pixel_aspect_ratio_list(pixel_aspect_ratios)
} else {
self
}
}
pub fn field(self, name: &str, value: impl Into<Value> + Send) -> Self {
Self {
builder: self.builder.field(name, value),
}
}
pub fn field_if_some(self, name: &str, value: Option<impl Into<glib::Value> + Send>) -> Self {
pub fn field_if_some(self, name: &str, value: Option<impl Into<Value> + Send>) -> Self {
if let Some(value) = value {
self.field(name, value)
} else {
@ -350,6 +402,20 @@ impl<T> VideoCapsBuilder<T> {
}
}
pub fn field_if_any<V: ValueType + FromIterator<SendValue> + Into<Value> + Send>(
self,
name: &str,
iter: impl IntoIterator<Item = impl Into<SendValue>>,
) -> Self {
let mut iter = iter.into_iter().peekable();
if iter.peek().is_some() {
let iter = iter.map(|item| item.into());
self.field(name, V::from_iter(iter))
} else {
self
}
}
#[must_use]
pub fn build(self) -> gst::Caps {
self.builder.build()

View file

@ -277,6 +277,42 @@ impl<'a> ElementBuilder<'a> {
}
}
// rustdoc-stripper-ignore-next
/// Set property `name` using the given `ValueType` `V` built from the `Item`s of `iter`.
#[inline]
pub fn property_from_iter<
V: ValueType + FromIterator<glib::SendValue> + Into<glib::Value> + 'a,
>(
self,
name: &'a str,
iter: impl IntoIterator<Item = impl Into<glib::SendValue>>,
) -> Self {
let iter = iter.into_iter().map(|item| item.into());
self.property(name, V::from_iter(iter))
}
// rustdoc-stripper-ignore-next
/// Set property `name` using the given `ValueType` `V` built from the `Item`s of `iter`
/// if `iter` is not empty.
///
/// This has no effect if `iter` is empty.
#[inline]
pub fn property_if_any<
V: ValueType + FromIterator<glib::SendValue> + Into<glib::Value> + 'a,
>(
self,
name: &'a str,
iter: impl IntoIterator<Item = impl Into<glib::SendValue>>,
) -> Self {
let mut iter = iter.into_iter().peekable();
if iter.peek().is_some() {
let iter = iter.map(|item| item.into());
self.property(name, V::from_iter(iter))
} else {
self
}
}
// rustdoc-stripper-ignore-next
/// Build the element with the provided properties.
///

View file

@ -3773,6 +3773,18 @@ impl<'a> StreamsSelectedBuilder<'a> {
}
}
pub fn streams_if_any(
self,
streams: impl IntoIterator<Item = impl std::borrow::Borrow<crate::Stream>>,
) -> Self {
let mut streams = streams.into_iter().peekable();
if streams.peek().is_some() {
self.streams(streams)
} else {
self
}
}
message_builder_generic_impl!(|s: &mut Self, src| {
let msg = ffi::gst_message_new_streams_selected(src, s.collection.to_glib_none().0);
if let Some(ref streams) = s.streams {

View file

@ -145,6 +145,15 @@ impl StreamCollectionBuilder {
}
}
pub fn streams_if_any(self, streams: impl IntoIterator<Item = Stream>) -> Self {
let mut streams = streams.into_iter().peekable();
if streams.peek().is_some() {
self.streams(streams)
} else {
self
}
}
#[must_use = "Building the stream collection without using it has no effect"]
pub fn build(self) -> StreamCollection {
self.0

View file

@ -12,7 +12,7 @@ use std::{
use glib::{
prelude::*,
translate::*,
value::{FromValue, SendValue},
value::{FromValue, SendValue, Value},
IntoGStr,
};
@ -1176,6 +1176,38 @@ impl Builder {
}
}
// rustdoc-stripper-ignore-next
/// Set field `name` using the given `ValueType` `V` built from the `Item`s of `iter`.
#[inline]
pub fn field_from_iter<V: ValueType + FromIterator<SendValue> + Into<Value> + Send>(
self,
name: impl IntoGStr,
iter: impl IntoIterator<Item = impl Into<SendValue>>,
) -> Self {
let iter = iter.into_iter().map(|item| item.into());
self.field(name, V::from_iter(iter))
}
// rustdoc-stripper-ignore-next
/// Set field `name` using the given `ValueType` `V` built from the `Item`s of `iter`
/// if `iter` is not empty.
///
/// This has no effect if `iter` is empty.
#[inline]
pub fn field_if_any<V: ValueType + FromIterator<SendValue> + Into<Value> + Send>(
self,
name: impl IntoGStr,
iter: impl IntoIterator<Item = impl Into<SendValue>>,
) -> Self {
let mut iter = iter.into_iter().peekable();
if iter.peek().is_some() {
let iter = iter.map(|item| item.into());
self.field(name, V::from_iter(iter))
} else {
self
}
}
#[must_use = "Building the structure without using it has no effect"]
pub fn build(self) -> Structure {
self.s
@ -1329,4 +1361,64 @@ mod tests {
assert_eq!(format!("{s:?}"), "Structure(test { f1: (gchararray) \"abc\", f2: (gchararray) \"bcd\", f3: (gint) 123, f4: Structure(nested { badger: (gboolean) TRUE }), f5: Array([(gchararray) \"a\", (gchararray) \"b\", (gchararray) \"c\"]), f6: List([(gchararray) \"d\", (gchararray) \"e\", (gchararray) \"f\"]) })");
}
#[test]
fn builder_field_from_iter() {
crate::init().unwrap();
let s = Structure::builder("test")
.field_from_iter::<crate::Array>("array", [&1, &2, &3])
.field_from_iter::<crate::List>("list", [&4, &5, &6])
.build();
assert!(s
.get::<crate::Array>("array")
.unwrap()
.iter()
.map(|val| val.get::<i32>().unwrap())
.eq([1, 2, 3]));
assert!(s
.get::<crate::List>("list")
.unwrap()
.iter()
.map(|val| val.get::<i32>().unwrap())
.eq([4, 5, 6]));
let array = Vec::<i32>::new();
let s = Structure::builder("test")
.field_from_iter::<crate::Array>("array", &array)
.field_from_iter::<crate::List>("list", &array)
.build();
assert!(s.get::<crate::Array>("array").unwrap().as_ref().is_empty());
assert!(s.get::<crate::List>("list").unwrap().as_ref().is_empty());
}
#[test]
fn builder_field_if_any() {
crate::init().unwrap();
let s = Structure::builder("test")
.field_if_any::<crate::Array>("array", [&1, &2, &3])
.field_if_any::<crate::List>("list", [&4, &5, &6])
.build();
assert!(s
.get::<crate::Array>("array")
.unwrap()
.iter()
.map(|val| val.get::<i32>().unwrap())
.eq([1, 2, 3]));
assert!(s
.get::<crate::List>("list")
.unwrap()
.iter()
.map(|val| val.get::<i32>().unwrap())
.eq([4, 5, 6]));
let array = Vec::<i32>::new();
let s = Structure::builder("test")
.field_if_any::<crate::Array>("array", &array)
.field_if_any::<crate::List>("list", &array)
.build();
assert!(!s.has_field("array"));
assert!(!s.has_field("list"));
}
}

View file

@ -551,7 +551,7 @@ unsafe extern "C" fn element_post_message<T: ElementImpl>(
#[cfg(test)]
mod tests {
use std::sync::atomic;
use std::sync::{atomic, Arc, Mutex, OnceLock};
use super::*;
use crate::ElementFactory;
@ -564,6 +564,7 @@ mod tests {
pub(super) sinkpad: crate::Pad,
pub(super) n_buffers: atomic::AtomicU32,
pub(super) reached_playing: atomic::AtomicBool,
pub(super) array: Arc<Mutex<Vec<String>>>,
}
impl TestElement {
@ -648,6 +649,10 @@ mod tests {
reached_playing: atomic::AtomicBool::new(false),
srcpad,
sinkpad,
array: Arc::new(Mutex::new(vec![
"default0".to_string(),
"default1".to_string(),
])),
}
}
}
@ -660,6 +665,30 @@ mod tests {
element.add_pad(&self.sinkpad).unwrap();
element.add_pad(&self.srcpad).unwrap();
}
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: OnceLock<Vec<glib::ParamSpec>> = OnceLock::new();
PROPERTIES.get_or_init(|| vec![crate::ParamSpecArray::builder("array").build()])
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"array" => {
let value = value.get::<crate::Array>().unwrap();
let mut array = self.array.lock().unwrap();
array.clear();
array.extend(value.iter().map(|v| v.get().unwrap()));
}
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"array" => crate::Array::new(&*self.array.lock().unwrap()).to_value(),
_ => unimplemented!(),
}
}
}
impl GstObjectImpl for TestElement {}
@ -729,6 +758,27 @@ mod tests {
}
}
fn plugin_init(plugin: &crate::Plugin) -> Result<(), glib::BoolError> {
crate::Element::register(
Some(plugin),
"testelement",
crate::Rank::MARGINAL,
TestElement::static_type(),
)
}
crate::plugin_define!(
rssubclasstestelem,
env!("CARGO_PKG_DESCRIPTION"),
plugin_init,
env!("CARGO_PKG_VERSION"),
"MPL-2.0",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_REPOSITORY"),
"1970-01-01"
);
#[test]
fn test_element_subclass() {
crate::init().unwrap();
@ -766,4 +816,48 @@ mod tests {
assert_eq!(imp.n_buffers.load(atomic::Ordering::SeqCst), 100);
assert!(imp.reached_playing.load(atomic::Ordering::SeqCst));
}
#[test]
fn property_from_iter_if_any() {
crate::init().unwrap();
plugin_register_static().unwrap();
let elem = crate::ElementFactory::make("testelement").build().unwrap();
assert!(elem
.property::<crate::Array>("array")
.iter()
.map(|val| val.get::<&str>().unwrap())
.eq(["default0", "default1"]));
let elem = crate::ElementFactory::make("testelement")
.property_from_iter::<crate::Array>("array", ["value0", "value1"])
.build()
.unwrap();
assert!(elem
.property::<crate::Array>("array")
.iter()
.map(|val| val.get::<&str>().unwrap())
.eq(["value0", "value1"]));
let array = Vec::<String>::new();
let elem = crate::ElementFactory::make("testelement")
.property_if_any::<crate::Array>("array", &array)
.build()
.unwrap();
assert!(elem
.property::<crate::Array>("array")
.iter()
.map(|val| val.get::<&str>().unwrap())
.eq(["default0", "default1"]));
let elem = crate::ElementFactory::make("testelement")
.property_if_any::<crate::Array>("array", ["value0", "value1"])
.build()
.unwrap();
assert!(elem
.property::<crate::Array>("array")
.iter()
.map(|val| val.get::<&str>().unwrap())
.eq(["value0", "value1"]));
}
}