From e9b8029685241576e8883ea6fb173ed56e52cac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Laignel?= Date: Sat, 27 Apr 2024 15:38:59 +0200 Subject: [PATCH] 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::("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::("audio-codecs", &args.audio_codecs) .build() .unwrap(); ```` The above will override the default value if `args.audio_codecs` is empty. --- gstreamer-audio/src/caps.rs | 95 ++++++++++++++++++++++++++++- gstreamer-video/src/caps.rs | 70 +++++++++++++++++++++- gstreamer/src/element_factory.rs | 36 +++++++++++ gstreamer/src/message.rs | 12 ++++ gstreamer/src/stream_collection.rs | 9 +++ gstreamer/src/structure.rs | 94 ++++++++++++++++++++++++++++- gstreamer/src/subclass/element.rs | 96 +++++++++++++++++++++++++++++- 7 files changed, 405 insertions(+), 7 deletions(-) diff --git a/gstreamer-audio/src/caps.rs b/gstreamer-audio/src/caps.rs index 715209ba5..6ad0098ff 100644 --- a/gstreamer-audio/src/caps.rs +++ b/gstreamer-audio/src/caps.rs @@ -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 AudioCapsBuilder { } } + pub fn format_list_if_any(self, formats: impl IntoIterator) -> 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 AudioCapsBuilder { } } + pub fn rate_list_if_any(self, rates: impl IntoIterator) -> 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 AudioCapsBuilder { } } + pub fn channels_list_if_any(self, channels: impl IntoIterator) -> 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 AudioCapsBuilder { } } + pub fn layout_list_if_any(self, layouts: impl IntoIterator) -> 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 AudioCapsBuilder { } } - pub fn field(self, name: &str, value: impl Into + Send) -> Self { + pub fn field(self, name: &str, value: impl Into + Send) -> Self { Self { builder: self.builder.field(name, value), } } - pub fn field_if_some(self, name: &str, value: Option + Send>) -> Self { + pub fn field_if_some(self, name: &str, value: Option + Send>) -> Self { if let Some(value) = value { self.field(name, value) } else { @@ -289,6 +326,20 @@ impl AudioCapsBuilder { } } + pub fn field_if_any + Into + Send>( + self, + name: &str, + iter: impl IntoIterator>, + ) -> 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::("format") + .unwrap() + .iter() + .map(|f| f.get::().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::>::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::::new()) + .build(); + assert!(!caps.structure(0).unwrap().has_field("format")); + } } diff --git a/gstreamer-video/src/caps.rs b/gstreamer-video/src/caps.rs index e5fca200e..881a9b254 100644 --- a/gstreamer-video/src/caps.rs +++ b/gstreamer-video/src/caps.rs @@ -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 VideoCapsBuilder { } } + pub fn format_list_if_any(self, formats: impl IntoIterator) -> 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 VideoCapsBuilder { } } + pub fn width_list_if_any(self, widths: impl IntoIterator) -> 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 VideoCapsBuilder { } } + pub fn height_list_if_any(self, heights: impl IntoIterator) -> 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 VideoCapsBuilder { } } + pub fn framerate_list_if_any( + self, + framerates: impl IntoIterator, + ) -> 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 VideoCapsBuilder { } } - pub fn field(self, name: &str, value: impl Into + Send) -> Self { + pub fn pixel_aspect_ratio_list_if_any( + self, + pixel_aspect_ratios: impl IntoIterator, + ) -> 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 + Send) -> Self { Self { builder: self.builder.field(name, value), } } - pub fn field_if_some(self, name: &str, value: Option + Send>) -> Self { + pub fn field_if_some(self, name: &str, value: Option + Send>) -> Self { if let Some(value) = value { self.field(name, value) } else { @@ -350,6 +402,20 @@ impl VideoCapsBuilder { } } + pub fn field_if_any + Into + Send>( + self, + name: &str, + iter: impl IntoIterator>, + ) -> 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() diff --git a/gstreamer/src/element_factory.rs b/gstreamer/src/element_factory.rs index 7565c0d90..86dd70282 100644 --- a/gstreamer/src/element_factory.rs +++ b/gstreamer/src/element_factory.rs @@ -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 + Into + 'a, + >( + self, + name: &'a str, + iter: impl IntoIterator>, + ) -> 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 + Into + 'a, + >( + self, + name: &'a str, + iter: impl IntoIterator>, + ) -> 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. /// diff --git a/gstreamer/src/message.rs b/gstreamer/src/message.rs index 639f4288b..05d626775 100644 --- a/gstreamer/src/message.rs +++ b/gstreamer/src/message.rs @@ -3773,6 +3773,18 @@ impl<'a> StreamsSelectedBuilder<'a> { } } + pub fn streams_if_any( + self, + streams: impl IntoIterator>, + ) -> 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 { diff --git a/gstreamer/src/stream_collection.rs b/gstreamer/src/stream_collection.rs index d2ba5e495..f0c41a5f1 100644 --- a/gstreamer/src/stream_collection.rs +++ b/gstreamer/src/stream_collection.rs @@ -145,6 +145,15 @@ impl StreamCollectionBuilder { } } + pub fn streams_if_any(self, streams: impl IntoIterator) -> 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 diff --git a/gstreamer/src/structure.rs b/gstreamer/src/structure.rs index 416fa950c..b5471b7ce 100644 --- a/gstreamer/src/structure.rs +++ b/gstreamer/src/structure.rs @@ -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 + Into + Send>( + self, + name: impl IntoGStr, + iter: impl IntoIterator>, + ) -> 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 + Into + Send>( + self, + name: impl IntoGStr, + iter: impl IntoIterator>, + ) -> 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::("array", [&1, &2, &3]) + .field_from_iter::("list", [&4, &5, &6]) + .build(); + assert!(s + .get::("array") + .unwrap() + .iter() + .map(|val| val.get::().unwrap()) + .eq([1, 2, 3])); + assert!(s + .get::("list") + .unwrap() + .iter() + .map(|val| val.get::().unwrap()) + .eq([4, 5, 6])); + + let array = Vec::::new(); + let s = Structure::builder("test") + .field_from_iter::("array", &array) + .field_from_iter::("list", &array) + .build(); + assert!(s.get::("array").unwrap().as_ref().is_empty()); + assert!(s.get::("list").unwrap().as_ref().is_empty()); + } + + #[test] + fn builder_field_if_any() { + crate::init().unwrap(); + + let s = Structure::builder("test") + .field_if_any::("array", [&1, &2, &3]) + .field_if_any::("list", [&4, &5, &6]) + .build(); + assert!(s + .get::("array") + .unwrap() + .iter() + .map(|val| val.get::().unwrap()) + .eq([1, 2, 3])); + assert!(s + .get::("list") + .unwrap() + .iter() + .map(|val| val.get::().unwrap()) + .eq([4, 5, 6])); + + let array = Vec::::new(); + let s = Structure::builder("test") + .field_if_any::("array", &array) + .field_if_any::("list", &array) + .build(); + assert!(!s.has_field("array")); + assert!(!s.has_field("list")); + } } diff --git a/gstreamer/src/subclass/element.rs b/gstreamer/src/subclass/element.rs index 02c3e7c4b..936d2fd66 100644 --- a/gstreamer/src/subclass/element.rs +++ b/gstreamer/src/subclass/element.rs @@ -551,7 +551,7 @@ unsafe extern "C" fn element_post_message( #[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>>, } 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> = 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::().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::("array") + .iter() + .map(|val| val.get::<&str>().unwrap()) + .eq(["default0", "default1"])); + + let elem = crate::ElementFactory::make("testelement") + .property_from_iter::("array", ["value0", "value1"]) + .build() + .unwrap(); + assert!(elem + .property::("array") + .iter() + .map(|val| val.get::<&str>().unwrap()) + .eq(["value0", "value1"])); + + let array = Vec::::new(); + let elem = crate::ElementFactory::make("testelement") + .property_if_any::("array", &array) + .build() + .unwrap(); + assert!(elem + .property::("array") + .iter() + .map(|val| val.get::<&str>().unwrap()) + .eq(["default0", "default1"])); + + let elem = crate::ElementFactory::make("testelement") + .property_if_any::("array", ["value0", "value1"]) + .build() + .unwrap(); + assert!(elem + .property::("array") + .iter() + .map(|val| val.get::<&str>().unwrap()) + .eq(["value0", "value1"])); + } }