From 559720693d2dbf6191d96ec438af5019b33a5da5 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Thu, 15 Jun 2023 18:13:23 -0400 Subject: [PATCH] ges: Allow subclassing GESFormatter Part-of: --- gstreamer-editing-services/src/formatter.rs | 73 ++++ gstreamer-editing-services/src/lib.rs | 5 + .../src/subclass/formatter.rs | 363 ++++++++++++++++++ .../src/subclass/mod.rs | 13 + 4 files changed, 454 insertions(+) create mode 100644 gstreamer-editing-services/src/formatter.rs create mode 100644 gstreamer-editing-services/src/subclass/formatter.rs create mode 100644 gstreamer-editing-services/src/subclass/mod.rs diff --git a/gstreamer-editing-services/src/formatter.rs b/gstreamer-editing-services/src/formatter.rs new file mode 100644 index 000000000..4f53f1ed5 --- /dev/null +++ b/gstreamer-editing-services/src/formatter.rs @@ -0,0 +1,73 @@ +// Take a look at the license at the top of the repository in the LICENSE file. +use crate::prelude::*; +use gst::glib::translate::*; + +pub trait FormatterExtManual: 'static { + fn can_load_uri(&self, uri: &str) -> Result<(), glib::Error>; + #[doc(alias = "ges_formatter_class_register_metas")] + fn register( + type_: glib::types::Type, + name: &str, + description: Option<&str>, + extensions: Option<&str>, + caps: Option<&str>, + version: f64, + rank: gst::Rank, + ); +} + +impl> FormatterExtManual for O { + fn can_load_uri(&self, uri: &str) -> Result<(), glib::Error> { + unsafe { + let klass = self.class_of::().unwrap(); + + let f = klass.as_ref().can_load_uri.ok_or_else(|| { + glib::Error::new(gst::CoreError::Failed, "No `can_load_uri` method defined") + })?; + + let mut err = std::ptr::null_mut(); + let res = f( + self.as_ref().to_glib_none().0, + uri.to_glib_none().0, + &mut err, + ); + + if res == glib::ffi::GTRUE { + Ok(()) + } else { + Err(from_glib_full(err)) + } + } + } + + #[doc(alias = "ges_formatter_class_register_metas")] + fn register( + type_: glib::types::Type, + name: &str, + description: Option<&str>, + extensions: Option<&str>, + caps: Option<&str>, + version: f64, + rank: gst::Rank, + ) { + skip_assert_initialized!(); + + unsafe { + let klass = mut_override( + gst::glib::Class::::from_type(type_) + .unwrap() + .as_ref(), + ); + + ffi::ges_formatter_class_register_metas( + klass, + name.to_glib_none().0, + description.to_glib_none().0, + extensions.to_glib_none().0, + caps.to_glib_none().0, + version, + rank.into_glib(), + ); + } + } +} diff --git a/gstreamer-editing-services/src/lib.rs b/gstreamer-editing-services/src/lib.rs index cfa3fc474..5df602c0a 100644 --- a/gstreamer-editing-services/src/lib.rs +++ b/gstreamer-editing-services/src/lib.rs @@ -54,7 +54,9 @@ macro_rules! skip_assert_initialized { #[allow(deprecated)] #[allow(unused_imports)] mod auto; +mod formatter; pub use crate::auto::*; +pub mod subclass; #[cfg(feature = "serde")] mod flag_serde; @@ -65,9 +67,12 @@ pub mod prelude { #[doc(hidden)] pub use gio::prelude::*; #[doc(hidden)] + pub use glib::prelude::*; + #[doc(hidden)] pub use gst_base::prelude::*; #[doc(hidden)] pub use gst_pbutils::prelude::*; pub use crate::auto::traits::*; + pub use crate::formatter::FormatterExtManual; } diff --git a/gstreamer-editing-services/src/subclass/formatter.rs b/gstreamer-editing-services/src/subclass/formatter.rs new file mode 100644 index 000000000..744867e79 --- /dev/null +++ b/gstreamer-editing-services/src/subclass/formatter.rs @@ -0,0 +1,363 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::prelude::*; +use crate::Formatter; +use glib::{subclass::prelude::*, translate::*}; + +pub trait FormatterImpl: FormatterImplExt + ObjectImpl + Send + Sync { + fn can_load_uri(&self, uri: &str) -> Result<(), glib::Error> { + self.parent_can_load_uri(uri) + } + + fn load_from_uri(&self, timeline: &crate::Timeline, uri: &str) -> Result<(), glib::Error> { + self.parent_load_from_uri(timeline, uri) + } + + fn save_to_uri( + &self, + timeline: &crate::Timeline, + uri: &str, + overwrite: bool, + ) -> Result<(), glib::Error> { + self.parent_save_to_uri(timeline, uri, overwrite) + } +} + +pub trait FormatterImplExt: ObjectSubclass { + fn parent_can_load_uri(&self, uri: &str) -> Result<(), glib::Error>; + + fn parent_load_from_uri( + &self, + timeline: &crate::Timeline, + uri: &str, + ) -> Result<(), glib::Error>; + + fn parent_save_to_uri( + &self, + timeline: &crate::Timeline, + uri: &str, + overwrite: bool, + ) -> Result<(), glib::Error>; +} + +impl FormatterImplExt for T { + fn parent_can_load_uri(&self, uri: &str) -> Result<(), glib::Error> { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *mut ffi::GESFormatterClass; + + let f = (*parent_class) + .can_load_uri + .expect("Missing parent function `can_load_uri`"); + + let mut error = std::ptr::null_mut(); + let res = f( + self.obj() + .unsafe_cast_ref::() + .to_glib_none() + .0, + uri.to_glib_none().0, + &mut error, + ); + + if res == glib::ffi::GFALSE { + if error.is_null() { + Err(glib::Error::new( + gst::CoreError::Failed, + "Can load uri failed", + )) + } else { + Err(from_glib_full(error)) + } + } else { + Ok(()) + } + } + } + + fn parent_load_from_uri( + &self, + timeline: &crate::Timeline, + uri: &str, + ) -> Result<(), glib::Error> { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *mut ffi::GESFormatterClass; + + let f = (*parent_class) + .load_from_uri + .expect("Missing parent function `load_from_uri`"); + + let mut error = std::ptr::null_mut(); + let res = f( + self.obj() + .unsafe_cast_ref::() + .to_glib_none() + .0, + timeline + .unsafe_cast_ref::() + .to_glib_none() + .0, + uri.to_glib_none().0, + &mut error, + ); + + if res == glib::ffi::GFALSE { + if error.is_null() { + Err(glib::Error::new( + gst::CoreError::Failed, + "Load from uri failed", + )) + } else { + Err(from_glib_full(error)) + } + } else { + Ok(()) + } + } + } + fn parent_save_to_uri( + &self, + timeline: &crate::Timeline, + uri: &str, + overwrite: bool, + ) -> Result<(), glib::Error> { + unsafe { + let data = Self::type_data(); + let parent_class = data.as_ref().parent_class() as *mut ffi::GESFormatterClass; + + let f = (*parent_class) + .save_to_uri + .expect("Missing parent function `save_to_uri`"); + + let mut error = std::ptr::null_mut(); + let res = f( + self.obj() + .unsafe_cast_ref::() + .to_glib_none() + .0, + timeline + .unsafe_cast_ref::() + .to_glib_none() + .0, + uri.to_glib_none().0, + overwrite.into_glib(), + &mut error, + ); + + if res == glib::ffi::GFALSE { + if error.is_null() { + Err(glib::Error::new( + gst::CoreError::Failed, + "Save to uri failed", + )) + } else { + Err(from_glib_full(error)) + } + } else { + Ok(()) + } + } + } +} + +unsafe impl IsSubclassable for Formatter { + fn class_init(klass: &mut glib::Class) { + Self::parent_class_init::(klass); + let klass = klass.as_mut(); + klass.can_load_uri = Some(formatter_can_load_uri::); + klass.load_from_uri = Some(formatter_load_from_uri::); + klass.save_to_uri = Some(formatter_save_to_uri::); + } +} + +unsafe extern "C" fn formatter_can_load_uri( + ptr: *mut ffi::GESFormatter, + uri: *const libc::c_char, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(ptr as *mut T::Instance); + let imp = instance.imp(); + + match imp.can_load_uri(glib::GString::from_glib_borrow(uri).as_str()) { + Err(err) => { + if !error.is_null() { + *error = err.into_glib_ptr(); + } + + glib::ffi::GFALSE + } + Ok(_) => glib::ffi::GTRUE, + } +} + +unsafe extern "C" fn formatter_load_from_uri( + ptr: *mut ffi::GESFormatter, + timeline: *mut ffi::GESTimeline, + uri: *const libc::c_char, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(ptr as *mut T::Instance); + let imp = instance.imp(); + let timeline = from_glib_borrow(timeline); + + match imp.load_from_uri(&timeline, glib::GString::from_glib_borrow(uri).as_str()) { + Err(err) => { + if !error.is_null() { + *error = err.into_glib_ptr(); + } + + glib::ffi::GFALSE + } + Ok(_) => glib::ffi::GTRUE, + } +} + +unsafe extern "C" fn formatter_save_to_uri( + ptr: *mut ffi::GESFormatter, + timeline: *mut ffi::GESTimeline, + uri: *const libc::c_char, + overwrite: glib::ffi::gboolean, + error: *mut *mut glib::ffi::GError, +) -> glib::ffi::gboolean { + let instance = &*(ptr as *mut T::Instance); + let imp = instance.imp(); + let timeline = from_glib_borrow(timeline); + + match imp.save_to_uri( + &timeline, + glib::GString::from_glib_borrow(uri).as_str(), + from_glib(overwrite), + ) { + Err(err) => { + if !error.is_null() { + *error = err.into_glib_ptr(); + } + + glib::ffi::GFALSE + } + Ok(_) => glib::ffi::GTRUE, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Formatter; + + pub mod imp { + use super::*; + + #[derive(Default)] + pub struct SimpleFormatter; + + #[glib::object_subclass] + impl ObjectSubclass for SimpleFormatter { + const NAME: &'static str = "SimpleFormatter"; + type Type = super::SimpleFormatter; + type ParentType = Formatter; + } + impl ObjectImpl for SimpleFormatter {} + impl FormatterImpl for SimpleFormatter { + fn can_load_uri(&self, uri: &str) -> Result<(), glib::Error> { + if uri.starts_with("ges:test") { + Ok(()) + } else { + self.parent_can_load_uri(uri) + } + } + + fn load_from_uri( + &self, + timeline: &crate::Timeline, + _uri: &str, + ) -> Result<(), glib::Error> { + timeline.append_layer(); + + Ok(()) + } + + fn save_to_uri( + &self, + timeline: &crate::Timeline, + uri: &str, + _overwrite: bool, + ) -> Result<(), glib::Error> { + unsafe { timeline.set_data("saved", uri.to_string()) }; + + Ok(()) + } + } + } + + glib::wrapper! { + pub struct SimpleFormatter(ObjectSubclass) @extends Formatter, gst::Object; + } + + impl SimpleFormatter { + pub fn new() -> Self { + glib::Object::builder().build() + } + } + + impl Default for SimpleFormatter { + fn default() -> Self { + Self::new() + } + } + + #[test] + fn test_formatter_subclass() { + crate::init().unwrap(); + + let formatter = SimpleFormatter::new(); + formatter + .can_load_uri("ges:test:") + .expect("We can load anything..."); + + assert!(formatter.can_load_uri("nottest").is_err()); + + let timeline = crate::Timeline::new(); + assert_eq!(timeline.layers().len(), 0); + #[allow(deprecated)] + formatter + .load_from_uri(&timeline, "test") + .expect("We can load anything..."); + assert_eq!(timeline.layers().len(), 1); + + unsafe { + assert_eq!(timeline.data::>("saved"), None); + } + #[allow(deprecated)] + formatter + .save_to_uri(&timeline, "test", false) + .expect("We can save anything..."); + unsafe { + assert_eq!( + timeline.data::("saved").unwrap().as_ref(), + &"test".to_string() + ); + } + + Formatter::register( + SimpleFormatter::static_type(), + "SimpleFormatter", + None, + None, + None, + 1.0, + gst::Rank::Primary, + ); + + let proj = crate::Project::new(Some("ges:test:")); + let timeline = proj + .extract() + .unwrap() + .downcast::() + .unwrap(); + assert_eq!(timeline.layers().len(), 1); + + let proj = crate::Project::new(Some("ges:notest:")); + assert!(proj.extract().is_err()); + } +} diff --git a/gstreamer-editing-services/src/subclass/mod.rs b/gstreamer-editing-services/src/subclass/mod.rs new file mode 100644 index 000000000..8c644bed6 --- /dev/null +++ b/gstreamer-editing-services/src/subclass/mod.rs @@ -0,0 +1,13 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#![allow(clippy::cast_ptr_alignment)] + +mod formatter; + +pub mod prelude { + #[doc(hidden)] + pub use glib::subclass::prelude::*; + pub use gst::subclass::prelude::*; + + pub use super::formatter::{FormatterImpl, FormatterImplExt}; +}