diff --git a/gstreamer-pbutils/Gir.toml b/gstreamer-pbutils/Gir.toml index 317030292..11650e4fc 100644 --- a/gstreamer-pbutils/Gir.toml +++ b/gstreamer-pbutils/Gir.toml @@ -393,6 +393,11 @@ status = "generate" [object.function.return] nullable = false + [[object.function]] + name = "get_element_properties" + # Use custom wrapper types + manual = true + [[object.property]] name = "restriction-caps" # encodingprofile is immutable after constructed diff --git a/gstreamer-pbutils/src/auto/encoding_profile.rs b/gstreamer-pbutils/src/auto/encoding_profile.rs index 92447361b..9293d5881 100644 --- a/gstreamer-pbutils/src/auto/encoding_profile.rs +++ b/gstreamer-pbutils/src/auto/encoding_profile.rs @@ -80,12 +80,6 @@ pub trait EncodingProfileExt: 'static { #[doc(alias = "get_description")] fn description(&self) -> Option; - #[cfg(any(feature = "v1_20", feature = "dox"))] - #[cfg_attr(feature = "dox", doc(cfg(feature = "v1_20")))] - #[doc(alias = "gst_encoding_profile_get_element_properties")] - #[doc(alias = "get_element_properties")] - fn element_properties(&self) -> Option; - #[doc(alias = "gst_encoding_profile_get_file_extension")] #[doc(alias = "get_file_extension")] fn file_extension(&self) -> Option; @@ -164,16 +158,6 @@ impl> EncodingProfileExt for O { } } - #[cfg(any(feature = "v1_20", feature = "dox"))] - #[cfg_attr(feature = "dox", doc(cfg(feature = "v1_20")))] - fn element_properties(&self) -> Option { - unsafe { - from_glib_full(ffi::gst_encoding_profile_get_element_properties( - self.as_ref().to_glib_none().0, - )) - } - } - fn file_extension(&self) -> Option { unsafe { from_glib_none(ffi::gst_encoding_profile_get_file_extension( diff --git a/gstreamer-pbutils/src/element_properties.rs b/gstreamer-pbutils/src/element_properties.rs new file mode 100644 index 000000000..dfe82ae43 --- /dev/null +++ b/gstreamer-pbutils/src/element_properties.rs @@ -0,0 +1,238 @@ +use gst::prelude::*; + +use std::ops::Deref; + +// rustdoc-stripper-ignore-next +/// Wrapper around `gst::Structure` for `element-properties` +/// property of `EncodingProfile`. +/// +/// # Examples +/// +/// ```rust +/// # use gstreamer_pbutils::ElementProperties; +/// # gst::init().unwrap(); +/// ElementProperties::builder_general() +/// .field("threads", 16) +/// .build(); +/// ``` +/// +/// ```rust +/// # use gstreamer_pbutils::ElementProperties; +/// # gst::init().unwrap(); +/// ElementProperties::builder_map() +/// .item( +/// gst::Structure::builder("vp8enc") +/// .field("max-quantizer", 17) +/// .field("buffer-size", 20000) +/// .field("threads", 16) +/// .build(), +/// ) +/// .build(); +/// ``` +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ElementProperties(pub(crate) gst::Structure); + +impl Default for ElementProperties { + fn default() -> Self { + Self::builder_general().build() + } +} + +impl Deref for ElementProperties { + type Target = gst::StructureRef; + + fn deref(&self) -> &gst::StructureRef { + self.0.as_ref() + } +} + +impl From for gst::Structure { + fn from(e: ElementProperties) -> Self { + skip_assert_initialized!(); + + e.into_inner() + } +} + +impl ElementProperties { + // rustdoc-stripper-ignore-next + /// Creates an `ElementProperties` builder that build into + /// something similar to the following: + /// + /// [element-properties, boolean-prop=true, string-prop="hi"] + pub fn builder_general() -> ElementPropertiesGeneralBuilder { + assert_initialized_main_thread!(); + + ElementPropertiesGeneralBuilder { + structure: gst::Structure::new_empty("element-properties"), + } + } + + // rustdoc-stripper-ignore-next + /// Creates an `ElementProperties` builder that build into + /// something similar to the following: + /// + /// element-properties-map, map = { + /// [openh264enc, gop-size=32, ], + /// [x264enc, key-int-max=32, tune=zerolatency], + /// } + pub fn builder_map() -> ElementPropertiesMapBuilder { + assert_initialized_main_thread!(); + + ElementPropertiesMapBuilder { map: Vec::new() } + } + + // rustdoc-stripper-ignore-next + /// Returns true if self is built with `ElementPropertiesGeneralBuilder`. + pub fn is_general(&self) -> bool { + let structure_name = self.0.name(); + + if structure_name != "element-properties" { + assert_eq!(structure_name, "element-properties-map"); + return false; + } + + true + } + + // rustdoc-stripper-ignore-next + /// Returns true if self is built with `ElementPropertiesMapBuilder`. + pub fn is_map(&self) -> bool { + !self.is_general() + } + + // rustdoc-stripper-ignore-next + /// Returns the inner list of `gst::Structure` if self is_general() + /// or `None` if self is_map(). + pub fn map(&self) -> Option> { + if !self.is_map() { + return None; + } + + Some( + self.0 + .get::("map") + .unwrap() + .as_slice() + .iter() + .map(|props_map| props_map.get::().unwrap()) + .collect::>(), + ) + } + + pub fn into_inner(self) -> gst::Structure { + self.0 + } +} + +#[must_use = "The builder must be built to be used"] +#[derive(Debug, Clone)] +pub struct ElementPropertiesGeneralBuilder { + structure: gst::Structure, +} + +impl ElementPropertiesGeneralBuilder { + pub fn field(mut self, property_name: &str, value: T) -> Self + where + T: ToSendValue + Sync, + { + self.structure.set(property_name, value); + self + } + + pub fn build(self) -> ElementProperties { + ElementProperties(self.structure) + } +} + +#[must_use = "The builder must be built to be used"] +#[derive(Debug, Clone)] +pub struct ElementPropertiesMapBuilder { + map: Vec, +} + +impl ElementPropertiesMapBuilder { + // rustdoc-stripper-ignore-next + /// Insert a new `element-properties-map` map item. + /// + /// The `structure`s name is the element factory's name + /// and each field corresponds to a property-value pair. + pub fn item(mut self, structure: gst::Structure) -> Self { + self.map.push(structure.to_send_value()); + self + } + + pub fn build(self) -> ElementProperties { + ElementProperties( + gst::Structure::builder("element-properties-map") + .field("map", gst::List::from(self.map)) + .build(), + ) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn element_properties_getters() { + gst::init().unwrap(); + + let elem_props_general = ElementProperties::builder_general() + .field("string-prop", "hi") + .field("boolean-prop", true) + .build(); + assert!(elem_props_general.is_general()); + assert!(!elem_props_general.is_map()); + assert_eq!(elem_props_general.map(), None); + + let elem_factory_props_map = gst::Structure::builder("vp8enc") + .field("cq-level", 13) + .field("resize-allowed", false) + .build(); + let elem_props_map = ElementProperties::builder_map() + .item(elem_factory_props_map.clone()) + .build(); + assert!(elem_props_map.is_map()); + assert!(!elem_props_map.is_general()); + assert_eq!(elem_props_map.map(), Some(vec![elem_factory_props_map])); + } + + #[test] + fn element_properties_general_builder() { + gst::init().unwrap(); + + let elem_props = ElementProperties::builder_general() + .field("string-prop", "hi") + .field("boolean-prop", true) + .build(); + assert_eq!(elem_props.n_fields(), 2); + assert_eq!(elem_props.name(), "element-properties"); + assert_eq!(elem_props.get::("string-prop").unwrap(), "hi"); + assert!(elem_props.get::("boolean-prop").unwrap()); + } + + #[test] + fn element_properties_map_builder() { + gst::init().unwrap(); + + let props_map = gst::Structure::builder("vp8enc") + .field("cq-level", 13) + .field("resize-allowed", false) + .build(); + assert_eq!(props_map.n_fields(), 2); + assert_eq!(props_map.name(), "vp8enc"); + assert_eq!(props_map.get::("cq-level").unwrap(), 13); + assert!(!props_map.get::("resize-allowed").unwrap()); + + let elem_props = ElementProperties::builder_map() + .item(props_map.clone()) + .build(); + assert_eq!(elem_props.n_fields(), 1); + + let list = elem_props.map().unwrap(); + assert_eq!(list.len(), 1); + assert_eq!(list.get(0).unwrap(), &props_map); + } +} diff --git a/gstreamer-pbutils/src/encoding_profile.rs b/gstreamer-pbutils/src/encoding_profile.rs index 0fb921911..bc525d339 100644 --- a/gstreamer-pbutils/src/encoding_profile.rs +++ b/gstreamer-pbutils/src/encoding_profile.rs @@ -8,6 +8,30 @@ use crate::auto::EncodingContainerProfile; use crate::auto::EncodingProfile; use crate::auto::EncodingVideoProfile; +#[cfg(feature = "v1_20")] +use crate::ElementProperties; + +pub trait EncodingProfileExtManual { + #[cfg(any(feature = "v1_20", feature = "dox"))] + #[cfg_attr(feature = "dox", doc(cfg(feature = "v1_20")))] + #[doc(alias = "gst_encoding_profile_get_element_properties")] + #[doc(alias = "get_element_properties")] + fn element_properties(&self) -> Option; +} + +impl> EncodingProfileExtManual for O { + #[cfg(any(feature = "v1_20", feature = "dox"))] + #[cfg_attr(feature = "dox", doc(cfg(feature = "v1_20")))] + fn element_properties(&self) -> Option { + unsafe { + from_glib_full::<_, Option<_>>(ffi::gst_encoding_profile_get_element_properties( + self.as_ref().to_glib_none().0, + )) + .map(ElementProperties) + } + } +} + trait EncodingProfileBuilderCommon { fn set_allow_dynamic_output(&self, allow_dynamic_output: bool); @@ -27,6 +51,9 @@ trait EncodingProfileBuilderCommon { #[cfg(feature = "v1_18")] fn set_single_segment(&self, single_segment: bool); + + #[cfg(feature = "v1_20")] + fn set_element_properties(&self, element_properties: ElementProperties); } impl> EncodingProfileBuilderCommon for O { @@ -115,6 +142,17 @@ impl> EncodingProfileBuilderCommon for O { ); } } + + // checker-ignore-item + #[cfg(feature = "v1_20")] + fn set_element_properties(&self, element_properties: ElementProperties) { + unsafe { + ffi::gst_encoding_profile_set_element_properties( + self.as_ref().to_glib_none().0, + element_properties.into_inner().into_glib_ptr(), + ); + } + } } // Split the trait as only the getter is public @@ -294,6 +332,8 @@ struct EncodingProfileBuilderCommonData<'a> { enabled: bool, #[cfg(feature = "v1_18")] single_segment: bool, + #[cfg(feature = "v1_20")] + element_properties: Option, } impl<'a> EncodingProfileBuilderCommonData<'a> { @@ -310,6 +350,8 @@ impl<'a> EncodingProfileBuilderCommonData<'a> { enabled: true, #[cfg(feature = "v1_18")] single_segment: false, + #[cfg(feature = "v1_20")] + element_properties: None, } } } @@ -340,6 +382,10 @@ pub trait EncodingProfileBuilder<'a>: Sized { #[doc(alias = "gst_encoding_profile_set_single_segment")] #[must_use] fn single_segment(self, single_segment: bool) -> Self; + #[cfg(feature = "v1_20")] + #[doc(alias = "gst_encoding_profile_set_element_properties")] + #[must_use] + fn element_properties(self, element_properties: ElementProperties) -> Self; } macro_rules! declare_encoding_profile_builder_common( @@ -385,6 +431,12 @@ macro_rules! declare_encoding_profile_builder_common( self.base.single_segment = single_segment; self } + + #[cfg(feature = "v1_20")] + fn element_properties(mut self, element_properties: ElementProperties) -> $name<'a> { + self.base.element_properties = Some(element_properties); + self + } } } ); @@ -405,6 +457,12 @@ fn set_common_fields( { profile.set_single_segment(base_data.single_segment); } + #[cfg(feature = "v1_20")] + { + if let Some(ref element_properties) = base_data.element_properties { + profile.set_element_properties(element_properties.clone()); + } + } } #[derive(Debug)] diff --git a/gstreamer-pbutils/src/lib.rs b/gstreamer-pbutils/src/lib.rs index 344553767..7ab801a08 100644 --- a/gstreamer-pbutils/src/lib.rs +++ b/gstreamer-pbutils/src/lib.rs @@ -43,6 +43,11 @@ mod auto; pub use crate::auto::functions::*; pub use crate::auto::*; +#[cfg(feature = "v1_20")] +mod element_properties; +#[cfg(feature = "v1_20")] +pub use crate::element_properties::ElementProperties; + #[cfg(feature = "serde")] mod flag_serde; @@ -70,7 +75,7 @@ pub mod prelude { pub use crate::audio_visualizer::*; pub use crate::auto::traits::*; pub use crate::encoding_profile::{ - EncodingProfileBuilder, EncodingProfileHasRestrictionGetter, + EncodingProfileBuilder, EncodingProfileExtManual, EncodingProfileHasRestrictionGetter, }; pub use crate::functions::CodecTag;