From 9453d636310bda350a0cddd89abd5a3ebf740cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Laignel?= Date: Fri, 12 May 2023 10:49:22 +0200 Subject: [PATCH] gst/pad: [Ghost]PadBuilder: inherit name from template or target when possible This commit adds convenience auto naming in the following cases: * When building from a non wildcard-named template, the name of the template is automatically assigned to the Pad. User can override with a specific name by calling `name()` on the `PadBuilder`. * When building with a target and no name was provided via the above, the GhostPad is named after the target. Part-of: --- gstreamer/src/ghost_pad.rs | 596 +++++++++++++++++++++-- gstreamer/src/pad.rs | 234 +++++++-- gstreamer/src/subclass/element.rs | 2 - tutorials/src/bin/playback-tutorial-7.rs | 5 +- 4 files changed, 748 insertions(+), 89 deletions(-) diff --git a/gstreamer/src/ghost_pad.rs b/gstreamer/src/ghost_pad.rs index 6d1a4e204..ac667d0a5 100644 --- a/gstreamer/src/ghost_pad.rs +++ b/gstreamer/src/ghost_pad.rs @@ -54,15 +54,21 @@ impl GhostPad { } // rustdoc-stripper-ignore-next - /// Creates a new [`GhostPad`] object with a default name. + /// Creates a new [`GhostPad`] with an automatically generated name. /// - /// Use [`GhostPad::builder()`] to get a [`PadBuilder`] and then define a specific name. + /// Use [`GhostPad::builder_from_template()`] to get a [`PadBuilder`](crate::PadBuilder) + /// and define options. #[doc(alias = "gst_ghost_pad_new_no_target")] pub fn new(direction: crate::PadDirection) -> Self { skip_assert_initialized!(); Self::builder(direction).build() } + // rustdoc-stripper-ignore-next + /// Creates a [`PadBuilder`](crate::PadBuilder) for a [`PadBuilder`] with an automatically generated name. + /// + /// Use [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name) + /// to specify a different name. #[doc(alias = "gst_ghost_pad_new_no_target")] pub fn builder(direction: crate::PadDirection) -> PadBuilder { skip_assert_initialized!(); @@ -70,15 +76,33 @@ impl GhostPad { } // rustdoc-stripper-ignore-next - /// Creates a new [`GhostPad`] object from the [`StaticPadTemplate`](crate::StaticPadTemplate) with a default name. + /// Creates a new [`GhostPad`] from the [`StaticPadTemplate`](crate::StaticPadTemplate). /// - /// Use [`GhostPad::builder_from_static_template()`] to get a [`PadBuilder`] and then define a specific name. + /// If the [`StaticPadTemplate`](crate::StaticPadTemplate) has a specific `name_template`, + /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, + /// the `GhostPad` will automatically be named after the `name_template`. + /// + /// # Panics + /// + /// Panics if the `name_template` is a wildcard-name. + /// + /// Use [`GhostPad::builder_from_template()`] to get a [`PadBuilder`](crate::PadBuilder) + /// and define options. #[doc(alias = "gst_ghost_pad_new_no_target_from_static_template")] pub fn from_static_template(templ: &StaticPadTemplate) -> Self { skip_assert_initialized!(); Self::builder_from_static_template(templ).build() } + // rustdoc-stripper-ignore-next + /// Creates a new [`PadBuilder`](crate::PadBuilder) for a [`GhostPad`] from the [`StaticPadTemplate`](crate::StaticPadTemplate). + /// + /// If the [`StaticPadTemplate`](crate::StaticPadTemplate) has a specific `name_template`, + /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, + /// the `GhostPad` will automatically be named after the `name_template`. + /// + /// Use [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name) + /// to specify a different name. #[doc(alias = "gst_ghost_pad_new_no_target_from_static_template")] pub fn builder_from_static_template(templ: &StaticPadTemplate) -> PadBuilder { skip_assert_initialized!(); @@ -86,15 +110,33 @@ impl GhostPad { } // rustdoc-stripper-ignore-next - /// Creates a new [`GhostPad`] object from the [`PadTemplate`](crate::PadTemplate) with a default name. + /// Creates a new [`GhostPad`] from the [`PadTemplate`](crate::PadTemplate). /// - /// Use [`GhostPad::builder_from_template()`] to get a [`PadBuilder`] and then define a specific name. + /// If the [`PadTemplate`](crate::PadTemplate) has a specific `name_template`, + /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, + /// the `GhostPad` will automatically be named after the `name_template`. + /// + /// # Panics + /// + /// Panics if the `name_template` is a wildcard-name. + /// + /// Use [`GhostPad::builder_from_template()`] to get a [`PadBuilder`](crate::PadBuilder) + /// and define options. #[doc(alias = "gst_ghost_pad_new_no_target_from_template")] pub fn from_template(templ: &crate::PadTemplate) -> Self { skip_assert_initialized!(); Self::builder_from_template(templ).build() } + // rustdoc-stripper-ignore-next + /// Creates a new [`PadBuilder`](crate::PadBuilder) for a [`GhostPad`] from the [`PadTemplate`](crate::PadTemplate). + /// + /// If the [`PadTemplate`](crate::PadTemplate) has a specific `name_template`, + /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, + /// the `GhostPad` will automatically be named after the `name_template`. + /// + /// Use [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name) + /// to specify a different name. #[doc(alias = "gst_ghost_pad_new_no_target_from_template")] pub fn builder_from_template(templ: &crate::PadTemplate) -> PadBuilder { skip_assert_initialized!(); @@ -102,32 +144,50 @@ impl GhostPad { } // rustdoc-stripper-ignore-next - /// Creates a new [`GhostPad`] object from the specified `target` `Pad` and a default name. + /// Creates a new [`GhostPad`] from the specified `target` `Pad`. /// - /// Use [`GhostPad::builder_with_target()`] to get a [`PadBuilder`] and then define a specific name. + /// The `GhostPad` will automatically be named after the `target` `name`. + /// + /// Use [`GhostPad::builder_with_target()`] to get a [`PadBuilder`](crate::PadBuilder) + /// and define options. #[doc(alias = "gst_ghost_pad_new")] - pub fn with_target>(target: &P) -> Result { + pub fn with_target + IsA>( + target: &P, + ) -> Result { skip_assert_initialized!(); Ok(Self::builder_with_target(target)?.build()) } + // rustdoc-stripper-ignore-next + /// Creates a new [`PadBuilder`](crate::PadBuilder) for a [`GhostPad`] from the specified `target` `Pad`. + /// + /// The `GhostPad` will automatically be named after the `target` `name`. + /// + /// Use [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name) + /// to specify a different name. #[doc(alias = "gst_ghost_pad_new_no_target_from_template")] - pub fn builder_with_target>( + pub fn builder_with_target + IsA>( target: &P, ) -> Result, glib::BoolError> { skip_assert_initialized!(); - Self::builder(target.direction()).with_target(target) + let mut builder = Self::builder(target.direction()); + builder.needs_specific_name = true; + builder.with_target(target) } // rustdoc-stripper-ignore-next - /// Creates a new [`GhostPad`] object from the [`PadTemplate`](crate::PadTemplate) - /// with the specified `target` `Pad` and a default name. + /// Creates a new [`GhostPad`] from the [`PadTemplate`](crate::PadTemplate) + /// with the specified `target` `Pad`. /// - /// Returns `Err(_)` if the `PadTemplate` and the `target` directions differ. + /// If the [`PadTemplate`](crate::PadTemplate) has a specific `name_template`, + /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, + /// the `GhostPad` will automatically be named after the `name_template`. /// - /// Use [`GhostPad::builder_from_template_with_target()`] to get a [`PadBuilder`] and then define a specific name. + /// If the `name_template` is a wildcard-name, then the `target` `name` is used, + /// if it is compatible. Otherwise, a specific name must be provided using + /// [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name). #[doc(alias = "gst_ghost_pad_new_from_template")] - pub fn from_template_with_target>( + pub fn from_template_with_target + IsA>( templ: &crate::PadTemplate, target: &P, ) -> Result { @@ -135,8 +195,19 @@ impl GhostPad { Ok(Self::builder_from_template_with_target(templ, target)?.build()) } + // rustdoc-stripper-ignore-next + /// Creates a new [`PadBuilder`](crate::PadBuilder) for a [`GhostPad`] from the [`PadTemplate`](crate::PadTemplate) + /// with the specified `target` `Pad`. + /// + /// If the [`PadTemplate`](crate::PadTemplate) has a specific `name_template`, + /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, + /// the `GhostPad` will automatically be named after the `name_template`. + /// + /// If the `name_template` is a wildcard-name, then the `target` `name` is used, + /// if it is compatible. Otherwise, a specific name must be provided using + /// [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name). #[doc(alias = "gst_ghost_pad_new_from_template")] - pub fn builder_from_template_with_target>( + pub fn builder_from_template_with_target + IsA>( templ: &crate::PadTemplate, target: &P, ) -> Result, glib::BoolError> { @@ -163,7 +234,7 @@ impl + IsA> PadBuilder { { unsafe { let proxy = self - .0 + .pad .unsafe_cast_ref::() .internal() .unwrap(); @@ -188,7 +259,7 @@ impl + IsA> PadBuilder { { unsafe { let proxy = self - .0 + .pad .unsafe_cast_ref::() .internal() .unwrap(); @@ -212,7 +283,7 @@ impl + IsA> PadBuilder { { unsafe { let proxy = self - .0 + .pad .unsafe_cast_ref::() .internal() .unwrap(); @@ -236,7 +307,7 @@ impl + IsA> PadBuilder { { unsafe { let proxy = self - .0 + .pad .unsafe_cast_ref::() .internal() .unwrap(); @@ -256,7 +327,7 @@ impl + IsA> PadBuilder { { unsafe { let proxy = self - .0 + .pad .unsafe_cast_ref::() .internal() .unwrap(); @@ -280,7 +351,7 @@ impl + IsA> PadBuilder { { unsafe { let proxy = self - .0 + .pad .unsafe_cast_ref::() .internal() .unwrap(); @@ -306,7 +377,7 @@ impl + IsA> PadBuilder { { unsafe { let proxy = self - .0 + .pad .unsafe_cast_ref::() .internal() .unwrap(); @@ -326,7 +397,7 @@ impl + IsA> PadBuilder { { unsafe { let proxy = self - .0 + .pad .unsafe_cast_ref::() .internal() .unwrap(); @@ -350,7 +421,7 @@ impl + IsA> PadBuilder { { unsafe { let proxy = self - .0 + .pad .unsafe_cast_ref::() .internal() .unwrap(); @@ -370,7 +441,7 @@ impl + IsA> PadBuilder { { unsafe { let proxy = self - .0 + .pad .unsafe_cast_ref::() .internal() .unwrap(); @@ -387,7 +458,7 @@ impl + IsA> PadBuilder { { unsafe { let proxy = self - .0 + .pad .unsafe_cast_ref::() .internal() .unwrap(); @@ -400,7 +471,7 @@ impl + IsA> PadBuilder { pub fn proxy_pad_flags(self, flags: PadFlags) -> Self { unsafe { let proxy = self - .0 + .pad .unsafe_cast_ref::() .internal() .unwrap(); @@ -410,10 +481,110 @@ impl + IsA> PadBuilder { self } - pub fn with_target>(self, target: &P) -> Result { - assert_eq!(self.0.direction(), target.direction()); + // rustdoc-stripper-ignore-next + /// Specifies a `target` [`Pad`](crate::Pad) for the [`GhostPad`]. + /// + /// If the [`PadBuilder`](crate::PadBuilder) was created from + /// a [`PadTemplate`](crate::PadTemplate) and the `PadTemplate` has a specific `name_template`, + /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, + /// the `GhostPad` will automatically be named after the `name_template`. + /// + /// If the `name_template` is a wildcard-name, then the `target` `name` is used, + /// if it is compatible. Otherwise, a specific name must be provided using + /// [`PadBuilder::name`](crate::PadBuilder::name) or [`PadBuilder::maybe_name`](crate::PadBuilder::maybe_name). + pub fn with_target + IsA>( + mut self, + target: &P, + ) -> Result { + assert_eq!(self.pad.direction(), target.direction()); - self.0.set_target(Some(target))?; + self.pad.set_target(Some(target))?; + + if self.needs_specific_name { + let mut can_assign_target_name = true; + + if let Some(pad_template) = self.pad.pad_template() { + if pad_template.presence() == crate::PadPresence::Request { + // Check if the target name is compatible with the name template. + use crate::CAT_RUST; + + let target_name = target.name(); + let mut target_parts = target_name.split('_'); + for template_part in pad_template.name_template().split('_') { + let Some(target_part) = target_parts.next() else { + crate::debug!( + CAT_RUST, + "Not using target Pad name '{target_name}': not enough parts compared to template '{}'", + pad_template.name_template(), + ); + can_assign_target_name = false; + break; + }; + + if let Some(conv_spec_start) = template_part.find('%') { + if conv_spec_start > 0 + && !target_part.starts_with(&template_part[..conv_spec_start]) + { + crate::debug!( + CAT_RUST, + "Not using target Pad name '{target_name}': mismatch template '{}' prefix", + pad_template.name_template(), + ); + can_assign_target_name = false; + break; + } + + let conv_spec_pos = conv_spec_start + 1; + match template_part.get(conv_spec_pos..=conv_spec_pos) { + Some("s") => { + // *There can be only one* %s + break; + } + Some("u") => { + if target_part + .get(conv_spec_start..) + .map_or(true, |s| s.parse::().is_err()) + { + crate::debug!( + CAT_RUST, + "Not using target Pad name '{target_name}': can't parse '%u' from '{target_part}' (template '{}')", + pad_template.name_template(), + ); + + can_assign_target_name = false; + break; + } + } + Some("d") => { + if target_part + .get(conv_spec_start..) + .map_or(true, |s| s.parse::().is_err()) + { + crate::debug!( + CAT_RUST, + "Not using target Pad name '{target_name}': can't parse '%i' from '{target_part}' (template '{}')", + pad_template.name_template(), + ); + + can_assign_target_name = false; + break; + } + } + other => unreachable!("Unexpected conversion specifier {other:?}"), + } + } else if target_part != template_part { + can_assign_target_name = false; + break; + } + } + } + } + + if can_assign_target_name { + self.pad.set_property("name", target.name()); + self.needs_specific_name = false; + } + } Ok(self) } @@ -423,10 +594,40 @@ impl + IsA> PadBuilder { mod tests { use super::*; + #[test] + fn no_template_no_target() { + crate::init().unwrap(); + + let ghost_pad = GhostPad::new(crate::PadDirection::Sink); + assert!(ghost_pad.name().starts_with("ghostpad")); + + let ghost_pad = GhostPad::builder(crate::PadDirection::Sink).build(); + assert!(ghost_pad.name().starts_with("ghostpad")); + + let ghost_pad = GhostPad::builder(crate::PadDirection::Sink) + .name("sink") + .build(); + assert_eq!(ghost_pad.name(), "sink"); + } + #[test] fn from_template() { crate::init().unwrap(); + let caps = crate::Caps::new_any(); + let wildcard_templ = crate::PadTemplate::new( + "sink_%u", + crate::PadDirection::Sink, + crate::PadPresence::Request, + &caps, + ) + .unwrap(); + + let ghost_pad = GhostPad::builder_from_template(&wildcard_templ) + .name("my-ghostpad") + .build(); + assert_eq!(ghost_pad.name(), "my-ghostpad"); + let caps = crate::Caps::new_any(); let templ = crate::PadTemplate::new( "sink", @@ -437,13 +638,53 @@ mod tests { .unwrap(); let ghost_pad = GhostPad::from_template(&templ); - assert!(ghost_pad.name().starts_with("ghostpad")); + assert_eq!(ghost_pad.name(), "sink"); let ghost_pad = GhostPad::builder_from_template(&templ).build(); - assert!(ghost_pad.name().starts_with("ghostpad")); + assert!(ghost_pad.name().starts_with("sink")); - let ghost_pad = GhostPad::builder_from_template(&templ).name("sink").build(); - assert_eq!(ghost_pad.name(), "sink"); + let ghost_pad = GhostPad::builder_from_template(&templ) + .name("my-sink") + .build(); + assert_eq!(ghost_pad.name(), "my-sink"); + } + + #[test] + #[should_panic] + fn from_template_missing_name() { + crate::init().unwrap(); + + let caps = crate::Caps::new_any(); + let templ = crate::PadTemplate::new( + "audio_%u", + crate::PadDirection::Sink, + crate::PadPresence::Request, + &caps, + ) + .unwrap(); + + // Panic: attempt to build from a wildcard-named template + // without providing a name. + let _ghost_pad = GhostPad::from_template(&templ); + } + + #[test] + #[should_panic] + fn from_template_builder_missing_name() { + crate::init().unwrap(); + + let caps = crate::Caps::new_any(); + let templ = crate::PadTemplate::new( + "audio_%u", + crate::PadDirection::Sink, + crate::PadPresence::Request, + &caps, + ) + .unwrap(); + + // Panic: attempt to build from a wildcard-named template + // without providing a name. + let _ghost_pad = GhostPad::builder_from_template(&templ).build(); } #[test] @@ -452,27 +693,27 @@ mod tests { let caps = crate::Caps::new_any(); let templ = crate::PadTemplate::new( - "sink", + "test", crate::PadDirection::Sink, crate::PadPresence::Always, &caps, ) .unwrap(); - let target = crate::Pad::from_template(&templ); + let target = crate::Pad::builder_from_template(&templ).build(); let ghost_pad = GhostPad::with_target(&target).unwrap(); - assert!(ghost_pad.name().starts_with("ghostpad")); + assert_eq!(ghost_pad.name(), "test"); let target = crate::Pad::from_template(&templ); let ghost_pad = GhostPad::builder_with_target(&target).unwrap().build(); - assert!(ghost_pad.name().starts_with("ghostpad")); + assert_eq!(ghost_pad.name(), "test"); let target = crate::Pad::from_template(&templ); let ghost_pad = GhostPad::builder_with_target(&target) .unwrap() - .name("sink") + .name("ghost_test") .build(); - assert_eq!(ghost_pad.name(), "sink"); + assert_eq!(ghost_pad.name(), "ghost_test"); } #[test] @@ -480,7 +721,7 @@ mod tests { crate::init().unwrap(); let caps = crate::Caps::new_any(); - let templ = crate::PadTemplate::new( + let sink_templ = crate::PadTemplate::new( "sink", crate::PadDirection::Sink, crate::PadPresence::Always, @@ -488,29 +729,278 @@ mod tests { ) .unwrap(); + // # No conversion specifier, Always template let ghost_templ = crate::PadTemplate::new( - "sink", + "ghost_sink", + crate::PadDirection::Sink, + crate::PadPresence::Always, + &caps, + ) + .unwrap(); + + let target = crate::Pad::from_template(&sink_templ); + let ghost_pad = GhostPad::from_template_with_target(&ghost_templ, &target).unwrap(); + assert_eq!(ghost_pad.name(), "ghost_sink"); + + let target = crate::Pad::from_template(&sink_templ); + let ghost_pad = GhostPad::builder_from_template_with_target(&ghost_templ, &target) + .unwrap() + .build(); + assert_eq!(ghost_pad.name(), "ghost_sink"); + + let target = crate::Pad::from_template(&sink_templ); + let ghost_pad = GhostPad::builder_from_template_with_target(&ghost_templ, &target) + .unwrap() + .name("my-sink") + .build(); + assert_eq!(ghost_pad.name(), "my-sink"); + + // # Request template %u + let wildcard_u_templ = crate::PadTemplate::new( + "sink_%u", crate::PadDirection::Sink, crate::PadPresence::Request, &caps, ) .unwrap(); - let target = crate::Pad::from_template(&templ); - let ghost_pad = GhostPad::from_template_with_target(&ghost_templ, &target).unwrap(); - assert!(ghost_pad.name().starts_with("ghostpad")); + // ## Incompatible target but specific name + let target = crate::Pad::from_template(&sink_templ); + let ghost_pad = GhostPad::builder_from_template_with_target(&wildcard_u_templ, &target) + .unwrap() + .name("sink_0") + .build(); + assert_eq!(ghost_pad.name(), "sink_0"); - let target = crate::Pad::from_template(&templ); - let ghost_pad = GhostPad::builder_from_template_with_target(&ghost_templ, &target) + // ## Compatible target + let sink_0_templ = crate::PadTemplate::new( + "sink_0", + crate::PadDirection::Sink, + crate::PadPresence::Always, + &caps, + ) + .unwrap(); + let target = crate::Pad::from_template(&sink_0_templ); + let ghost_pad = GhostPad::builder_from_template_with_target(&wildcard_u_templ, &target) .unwrap() .build(); - assert!(ghost_pad.name().starts_with("ghostpad")); + assert_eq!(ghost_pad.name(), "sink_0"); + + // # Request template %d_%u + let wildcard_u_templ = crate::PadTemplate::new( + "sink_%d_%u", + crate::PadDirection::Sink, + crate::PadPresence::Request, + &caps, + ) + .unwrap(); + + // ## Incompatible target but specific name + let target = crate::Pad::from_template(&sink_templ); + let ghost_pad = GhostPad::builder_from_template_with_target(&wildcard_u_templ, &target) + .unwrap() + .name("sink_-1_0") + .build(); + assert_eq!(ghost_pad.name(), "sink_-1_0"); + + // ## Compatible target + let sink_m2_0_templ = crate::PadTemplate::new( + "sink_-2_0", + crate::PadDirection::Sink, + crate::PadPresence::Always, + &caps, + ) + .unwrap(); + let target = crate::Pad::from_template(&sink_m2_0_templ); + let ghost_pad = GhostPad::builder_from_template_with_target(&wildcard_u_templ, &target) + .unwrap() + .build(); + assert_eq!(ghost_pad.name(), "sink_-2_0"); + + // # Request template %s + let wildcard_s_templ = crate::PadTemplate::new( + "sink_%s", + crate::PadDirection::Sink, + crate::PadPresence::Request, + &caps, + ) + .unwrap(); + + // ## Incompatible target but specific name + let target = crate::Pad::from_template(&sink_templ); + let ghost_pad = GhostPad::builder_from_template_with_target(&wildcard_s_templ, &target) + .unwrap() + .name("sink_ghost_test") + .build(); + assert_eq!(ghost_pad.name(), "sink_ghost_test"); + + // ## Compatible target + let sink_test_templ = crate::PadTemplate::new( + "sink_test", + crate::PadDirection::Sink, + crate::PadPresence::Always, + &caps, + ) + .unwrap(); + let target = crate::Pad::from_template(&sink_test_templ); + let ghost_pad = GhostPad::builder_from_template_with_target(&wildcard_s_templ, &target) + .unwrap() + .build(); + assert_eq!(ghost_pad.name(), "sink_test"); + } + + #[test] + #[should_panic] + fn from_template_with_target_incompatible_prefix() { + crate::init().unwrap(); + + let caps = crate::Caps::new_any(); + let wildcard_templ = crate::PadTemplate::new( + "sink_%u", + crate::PadDirection::Sink, + crate::PadPresence::Request, + &caps, + ) + .unwrap(); + + let templ = crate::PadTemplate::new( + "audio_0", + crate::PadDirection::Sink, + crate::PadPresence::Always, + &caps, + ) + .unwrap(); let target = crate::Pad::from_template(&templ); - let ghost_pad = GhostPad::builder_from_template_with_target(&ghost_templ, &target) + // Panic: attempt to build from a wildcard-named template + // with a target name with a different prefix + // without providing a name. + let _ghost_pad = GhostPad::builder_from_template_with_target(&wildcard_templ, &target) + .unwrap() + .build(); + } + + #[test] + #[should_panic] + fn from_template_with_target_missing_part() { + crate::init().unwrap(); + + let caps = crate::Caps::new_any(); + let wildcard_templ = crate::PadTemplate::new( + "sink_%u_%s", + crate::PadDirection::Sink, + crate::PadPresence::Request, + &caps, + ) + .unwrap(); + + let templ = crate::PadTemplate::new( + "sink_0", + crate::PadDirection::Sink, + crate::PadPresence::Always, + &caps, + ) + .unwrap(); + + let target = crate::Pad::from_template(&templ); + // Panic: attempt to build from a wildcard-named template + // with a target name missing a part + // without providing a name. + let _ghost_pad = GhostPad::builder_from_template_with_target(&wildcard_templ, &target) + .unwrap() + .build(); + } + + #[test] + #[should_panic] + fn from_template_with_target_incompatible_conversion_unsigned() { + crate::init().unwrap(); + + let caps = crate::Caps::new_any(); + let wildcard_templ = crate::PadTemplate::new( + "sink_%u", + crate::PadDirection::Sink, + crate::PadPresence::Request, + &caps, + ) + .unwrap(); + + let templ = crate::PadTemplate::new( + "sink_-1", + crate::PadDirection::Sink, + crate::PadPresence::Always, + &caps, + ) + .unwrap(); + + let target = crate::Pad::from_template(&templ); + // Panic: attempt to build from a wildcard-named template + // with a target name %d, expecting %u + // without providing a name. + let _ghost_pad = GhostPad::builder_from_template_with_target(&wildcard_templ, &target) + .unwrap() + .build(); + } + + #[test] + #[should_panic] + fn from_template_with_target_incompatible_conversion_decimal() { + crate::init().unwrap(); + + let caps = crate::Caps::new_any(); + let wildcard_templ = crate::PadTemplate::new( + "sink_%u", + crate::PadDirection::Sink, + crate::PadPresence::Request, + &caps, + ) + .unwrap(); + + let templ = crate::PadTemplate::new( + "sink_test", + crate::PadDirection::Sink, + crate::PadPresence::Always, + &caps, + ) + .unwrap(); + + let target = crate::Pad::from_template(&templ); + // Panic: attempt to build from a wildcard-named template + // with a target name with %s, expecting %d + // without providing a name. + let _ghost_pad = GhostPad::builder_from_template_with_target(&wildcard_templ, &target) + .unwrap() + .build(); + } + + #[test] + #[should_panic] + fn from_template_with_target_incompatible_missing_decimal() { + crate::init().unwrap(); + + let caps = crate::Caps::new_any(); + let wildcard_templ = crate::PadTemplate::new( + "sink_%d", + crate::PadDirection::Sink, + crate::PadPresence::Request, + &caps, + ) + .unwrap(); + + let templ = crate::PadTemplate::new( + "sink_", + crate::PadDirection::Sink, + crate::PadPresence::Always, + &caps, + ) + .unwrap(); + + let target = crate::Pad::from_template(&templ); + // Panic: attempt to build from a wildcard-named template + // with a target name missing a number, expecting %d + // without providing a name. + let _ghost_pad = GhostPad::builder_from_template_with_target(&wildcard_templ, &target) .unwrap() - .name("sink") .build(); - assert_eq!(ghost_pad.name(), "sink"); } } diff --git a/gstreamer/src/pad.rs b/gstreamer/src/pad.rs index 8ab8d1ab5..1f898ad6c 100644 --- a/gstreamer/src/pad.rs +++ b/gstreamer/src/pad.rs @@ -1519,15 +1519,21 @@ unsafe extern "C" fn destroy_closure(ptr: gpointer) { impl Pad { // rustdoc-stripper-ignore-next - /// Creates a new [`Pad`] object with a default name. + /// Creates a new [`Pad`] with the specified [`PadDirection`](crate::PadDirection). /// - /// Use [`Pad::builder()`] to get a [`PadBuilder`] and then define a specific name. + /// An automatically generated name will be assigned. + /// + /// Use [`Pad::builder()`] to get a [`PadBuilder`] and define options. #[doc(alias = "gst_pad_new")] pub fn new(direction: crate::PadDirection) -> Self { skip_assert_initialized!(); Self::builder(direction).build() } + // rustdoc-stripper-ignore-next + /// Creates a [`PadBuilder`] with the specified [`PadDirection`](crate::PadDirection). + /// + /// An automatically generated name will be assigned. #[doc(alias = "gst_pad_new")] pub fn builder(direction: crate::PadDirection) -> PadBuilder { skip_assert_initialized!(); @@ -1535,15 +1541,31 @@ impl Pad { } // rustdoc-stripper-ignore-next - /// Creates a new [`Pad`] object from the [`StaticPadTemplate`](crate::StaticPadTemplate) with a default name. + /// Creates a new [`Pad`] from the [`StaticPadTemplate`](crate::StaticPadTemplate). /// - /// Use [`Pad::builder_from_static_template()`] to get a [`PadBuilder`] and then define a specific name. + /// If the [`StaticPadTemplate`](crate::StaticPadTemplate) has a specific `name_template`, + /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, + /// the `Pad` will automatically be named after the `name_template`. + /// + /// # Panics + /// + /// Panics if the `name_template` is a wildcard-name. + /// + /// Use [`Pad::builder_from_static_template()`] to get a [`PadBuilder`] and define options. #[doc(alias = "gst_pad_new_from_static_template")] pub fn from_static_template(templ: &StaticPadTemplate) -> Self { skip_assert_initialized!(); Self::builder_from_static_template(templ).build() } + // rustdoc-stripper-ignore-next + /// Creates a new [`PadBuilder`] from the [`StaticPadTemplate`](crate::StaticPadTemplate). + /// + /// If the [`StaticPadTemplate`](crate::StaticPadTemplate) has a specific `name_template`, + /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, + /// the `Pad` will automatically be named after the `name_template`. + /// + /// Use [`PadBuilder::name`] or [`PadBuilder::maybe_name`] to specify a different name. #[doc(alias = "gst_pad_new_from_static_template")] pub fn builder_from_static_template(templ: &StaticPadTemplate) -> PadBuilder { skip_assert_initialized!(); @@ -1551,15 +1573,31 @@ impl Pad { } // rustdoc-stripper-ignore-next - /// Creates a new [`Pad`] object from the [`PadTemplate`](crate::PadTemplate) with a default name. + /// Creates a new [`Pad`] from the [`PadTemplate`](crate::PadTemplate). /// - /// Use [`Pad::builder_from_template()`] to get a [`PadBuilder`] and then define a specific name. + /// If the [`PadTemplate`](crate::PadTemplate) has a specific `name_template`, + /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, + /// the `Pad` will automatically be named after the `name_template`. + /// + /// # Panics + /// + /// Panics if the `name_template` is a wildcard-name. + /// + /// Use [`Pad::builder_from_template()`] to get a [`PadBuilder`] and define options. #[doc(alias = "gst_pad_new_from_template")] pub fn from_template(templ: &crate::PadTemplate) -> Self { skip_assert_initialized!(); Self::builder_from_template(templ).build() } + // rustdoc-stripper-ignore-next + /// Creates a new [`PadBuilder`] from the [`PadTemplate`](crate::PadTemplate). + /// + /// If the [`PadTemplate`](crate::PadTemplate) has a specific `name_template`, + /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, + /// the `Pad` will automatically be named after the `name_template`. + /// + /// Use [`PadBuilder::name`] or [`PadBuilder::maybe_name`] to specify a different name. #[doc(alias = "gst_pad_new_from_template")] pub fn builder_from_template(templ: &crate::PadTemplate) -> PadBuilder { skip_assert_initialized!(); @@ -1614,9 +1652,17 @@ impl Pad { } #[must_use = "The builder must be built to be used"] -pub struct PadBuilder(pub(crate) T); +pub struct PadBuilder { + pub(crate) pad: T, + pub(crate) needs_specific_name: bool, +} impl + IsA + glib::object::IsClass> PadBuilder { + // rustdoc-stripper-ignore-next + /// Creates a `PadBuilder` with the specified [`PadDirection`](crate::PadDirection). + /// + /// An automatically generated name will be assigned. Use [`PadBuilder::name`] or + /// [`PadBuilder::maybe_name`] to define a specific name. pub fn new(direction: crate::PadDirection) -> Self { assert_initialized_main_thread!(); @@ -1633,9 +1679,20 @@ impl + IsA + glib::object::IsClass> PadBuilder { } } - PadBuilder(pad) + PadBuilder { + pad, + needs_specific_name: false, + } } + // rustdoc-stripper-ignore-next + /// Creates a `PadBuilder` from the specified [`StaticPadTemplate`](crate::StaticPadTemplate). + /// + /// If the [`StaticPadTemplate`](crate::StaticPadTemplate) has a specific `name_template`, + /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, + /// the `Pad` will automatically be named after the `name_template`. + /// + /// Use [`PadBuilder::name`] or [`PadBuilder::maybe_name`] to specify a different name. pub fn from_static_template(templ: &StaticPadTemplate) -> Self { skip_assert_initialized!(); @@ -1643,6 +1700,14 @@ impl + IsA + glib::object::IsClass> PadBuilder { Self::from_template(&templ) } + // rustdoc-stripper-ignore-next + /// Creates a `PadBuilder` from the specified [`PadTemplate`](crate::PadTemplate). + /// + /// If the [`PadTemplate`](crate::PadTemplate) has a specific `name_template`, + /// i.e. if it's not a wildcard-name containing `%u`, `%s` or `%d`, + /// the `Pad` will automatically be named after the `name_template`. + /// + /// Use [`PadBuilder::name`] or [`PadBuilder::maybe_name`] to specify a different name. pub fn from_template(templ: &crate::PadTemplate) -> Self { assert_initialized_main_thread!(); @@ -1682,22 +1747,49 @@ impl + IsA + glib::object::IsClass> PadBuilder { } } - PadBuilder(pad) + let needs_specific_name = if templ.name().find('%').is_some() { + // Pad needs a specific name + true + } else { + pad.set_property("name", templ.name()); + false + }; + + PadBuilder { + pad, + needs_specific_name, + } } - pub fn name(self, name: impl Into) -> Self { - self.0.set_property("name", name.into()); + // rustdoc-stripper-ignore-next + /// Sets the name of the Pad. + pub fn name(mut self, name: impl glib::IntoGStr) -> Self { + name.run_with_gstr(|name| self.pad.set_property("name", name)); + self.needs_specific_name = false; self } + // rustdoc-stripper-ignore-next + /// Optionally sets the name of the Pad. + /// + /// This method is convenient when the `name` is provided as an `Option`. + /// If the `name` is `None`, this has no effect. + pub fn maybe_name(self, name: Option) -> Self { + if let Some(name) = name { + self.name(name) + } else { + self + } + } + #[doc(alias = "gst_pad_set_activate_function")] pub fn activate_function(self, func: F) -> Self where F: Fn(&T, Option<&crate::Object>) -> Result<(), LoggableError> + Send + Sync + 'static, { unsafe { - self.0.set_activate_function(func); + self.pad.set_activate_function(func); } self @@ -1712,7 +1804,7 @@ impl + IsA + glib::object::IsClass> PadBuilder { + 'static, { unsafe { - self.0.set_activatemode_function(func); + self.pad.set_activatemode_function(func); } self @@ -1727,7 +1819,7 @@ impl + IsA + glib::object::IsClass> PadBuilder { + 'static, { unsafe { - self.0.set_chain_function(func); + self.pad.set_chain_function(func); } self @@ -1742,7 +1834,7 @@ impl + IsA + glib::object::IsClass> PadBuilder { + 'static, { unsafe { - self.0.set_chain_list_function(func); + self.pad.set_chain_list_function(func); } self @@ -1754,7 +1846,7 @@ impl + IsA + glib::object::IsClass> PadBuilder { F: Fn(&T, Option<&crate::Object>, crate::Event) -> bool + Send + Sync + 'static, { unsafe { - self.0.set_event_function(func); + self.pad.set_event_function(func); } self @@ -1769,7 +1861,7 @@ impl + IsA + glib::object::IsClass> PadBuilder { + 'static, { unsafe { - self.0.set_event_full_function(func); + self.pad.set_event_full_function(func); } self @@ -1790,7 +1882,7 @@ impl + IsA + glib::object::IsClass> PadBuilder { + 'static, { unsafe { - self.0.set_getrange_function(func); + self.pad.set_getrange_function(func); } self @@ -1802,7 +1894,7 @@ impl + IsA + glib::object::IsClass> PadBuilder { F: Fn(&T, Option<&crate::Object>) -> crate::Iterator + Send + Sync + 'static, { unsafe { - self.0.set_iterate_internal_links_function(func); + self.pad.set_iterate_internal_links_function(func); } self @@ -1821,7 +1913,7 @@ impl + IsA + glib::object::IsClass> PadBuilder { + 'static, { unsafe { - self.0.set_link_function(func); + self.pad.set_link_function(func); } self @@ -1833,7 +1925,7 @@ impl + IsA + glib::object::IsClass> PadBuilder { F: Fn(&T, Option<&crate::Object>, &mut crate::QueryRef) -> bool + Send + Sync + 'static, { unsafe { - self.0.set_query_function(func); + self.pad.set_query_function(func); } self @@ -1845,21 +1937,40 @@ impl + IsA + glib::object::IsClass> PadBuilder { F: Fn(&T, Option<&crate::Object>) + Send + Sync + 'static, { unsafe { - self.0.set_unlink_function(func); + self.pad.set_unlink_function(func); } self } pub fn flags(self, flags: PadFlags) -> Self { - self.0.set_pad_flags(flags); + self.pad.set_pad_flags(flags); self } + // rustdoc-stripper-ignore-next + /// Builds the [`Pad`]. + /// + /// # Panics + /// + /// Panics if the [`Pad`] was built from a [`PadTemplate`](crate::PadTemplate) + /// with a wildcard-name `name_template` (i.e. containing `%u`, `%s` or `%d`) + /// and no specific `name` was provided using [`PadBuilder::name`] + /// or [`PadBuilder::maybe_name`], or for [`GhostPad`s](crate::GhostPad), + /// by defining a `target`. #[must_use = "Building the pad without using it has no effect"] + #[track_caller] pub fn build(self) -> T { - self.0 + if self.needs_specific_name { + panic!(concat!( + "Attempt to build a Pad from a wildcard-name template", + " or with a target Pad with an incompatible name.", + " Make sure to define a specific name using PadBuilder.", + )); + } + + self.pad } } @@ -2274,9 +2385,43 @@ mod tests { } #[test] - fn from_template() { + fn naming() { crate::init().unwrap(); + let pad = crate::Pad::builder(crate::PadDirection::Sink).build(); + assert!(pad.name().starts_with("pad")); + + let pad = crate::Pad::builder(crate::PadDirection::Src).build(); + assert!(pad.name().starts_with("pad")); + + let pad = crate::Pad::builder(crate::PadDirection::Unknown).build(); + assert!(pad.name().starts_with("pad")); + + let pad = crate::Pad::builder(crate::PadDirection::Unknown) + .maybe_name(None::<&str>) + .build(); + assert!(pad.name().starts_with("pad")); + + let pad = crate::Pad::builder(crate::PadDirection::Sink) + .name("sink_0") + .build(); + assert_eq!(pad.name(), "sink_0"); + + let pad = crate::Pad::builder(crate::PadDirection::Src) + .name("src_0") + .build(); + assert_eq!(pad.name(), "src_0"); + + let pad = crate::Pad::builder(crate::PadDirection::Unknown) + .name("test") + .build(); + assert_eq!(pad.name(), "test"); + + let pad = crate::Pad::builder(crate::PadDirection::Unknown) + .maybe_name(Some("test")) + .build(); + assert_eq!(pad.name(), "test"); + let caps = crate::Caps::new_any(); let templ = crate::PadTemplate::new( "sink", @@ -2287,12 +2432,41 @@ mod tests { .unwrap(); let pad = Pad::from_template(&templ); - assert!(pad.name().starts_with("pad")); + assert!(pad.name().starts_with("sink")); - let pad = Pad::builder_from_template(&templ).build(); - assert!(pad.name().starts_with("pad")); + let pad = Pad::builder_from_template(&templ) + .name("audio_sink") + .build(); + assert!(pad.name().starts_with("audio_sink")); - let pad = Pad::builder_from_template(&templ).name("sink").build(); - assert_eq!(pad.name(), "sink"); + let templ = crate::PadTemplate::new( + "audio_%u", + crate::PadDirection::Sink, + crate::PadPresence::Request, + &caps, + ) + .unwrap(); + + let pad = Pad::builder_from_template(&templ).name("audio_0").build(); + assert!(pad.name().starts_with("audio_0")); + } + + #[test] + #[should_panic] + fn missing_name() { + crate::init().unwrap(); + + let caps = crate::Caps::new_any(); + let templ = crate::PadTemplate::new( + "audio_%u", + crate::PadDirection::Sink, + crate::PadPresence::Request, + &caps, + ) + .unwrap(); + + // Panic: attempt to build from a wildcard-named template + // without providing a name. + let _pad = Pad::from_template(&templ); } } diff --git a/gstreamer/src/subclass/element.rs b/gstreamer/src/subclass/element.rs index 91b8b8de9..d28e00151 100644 --- a/gstreamer/src/subclass/element.rs +++ b/gstreamer/src/subclass/element.rs @@ -585,7 +585,6 @@ mod tests { fn with_class(klass: &Self::Class) -> Self { let templ = klass.pad_template("sink").unwrap(); let sinkpad = crate::Pad::builder_from_template(&templ) - .name("sink") .chain_function(|pad, parent, buffer| { TestElement::catch_panic_pad_function( parent, @@ -611,7 +610,6 @@ mod tests { let templ = klass.pad_template("src").unwrap(); let srcpad = crate::Pad::builder_from_template(&templ) - .name("src") .event_function(|pad, parent, event| { TestElement::catch_panic_pad_function( parent, diff --git a/tutorials/src/bin/playback-tutorial-7.rs b/tutorials/src/bin/playback-tutorial-7.rs index 4a6ba992e..51637d1ef 100644 --- a/tutorials/src/bin/playback-tutorial-7.rs +++ b/tutorials/src/bin/playback-tutorial-7.rs @@ -34,10 +34,7 @@ fn tutorial_main() -> Result<(), Error> { let pad = equalizer .static_pad("sink") .expect("Failed to get a static pad from equalizer."); - let ghost_pad = gst::GhostPad::builder_with_target(&pad) - .unwrap() - .name("sink") - .build(); + let ghost_pad = gst::GhostPad::builder_with_target(&pad).unwrap().build(); ghost_pad.set_active(true)?; bin.add_pad(&ghost_pad)?;