// Take a look at the license at the top of the repository in the LICENSE file. use glib::translate::*; use gst::prelude::*; use std::cmp; use std::fmt; use std::mem; use std::ptr; use std::str; use crate::VideoTimeCodeFlags; use crate::VideoTimeCodeInterval; #[doc(alias = "GstVideoTimeCode")] #[repr(transparent)] pub struct VideoTimeCode(ffi::GstVideoTimeCode); #[doc(alias = "GstVideoTimeCode")] #[repr(transparent)] pub struct ValidVideoTimeCode(ffi::GstVideoTimeCode); impl VideoTimeCode { pub fn new_empty() -> Self { assert_initialized_main_thread!(); unsafe { let mut v = mem::MaybeUninit::zeroed(); ffi::gst_video_time_code_clear(v.as_mut_ptr()); Self(v.assume_init()) } } #[allow(clippy::too_many_arguments)] pub fn new( fps: gst::Fraction, latest_daily_jam: Option<&glib::DateTime>, flags: VideoTimeCodeFlags, hours: u32, minutes: u32, seconds: u32, frames: u32, field_count: u32, ) -> Self { assert_initialized_main_thread!(); unsafe { let mut v = mem::MaybeUninit::zeroed(); ffi::gst_video_time_code_init( v.as_mut_ptr(), fps.numer() as u32, fps.denom() as u32, latest_daily_jam.to_glib_none().0, flags.into_glib(), hours, minutes, seconds, frames, field_count, ); Self(v.assume_init()) } } #[cfg(any(feature = "v1_16", feature = "dox"))] #[cfg_attr(feature = "dox", doc(cfg(feature = "v1_16")))] #[doc(alias = "gst_video_time_code_init_from_date_time_full")] pub fn from_date_time( fps: gst::Fraction, dt: &glib::DateTime, flags: VideoTimeCodeFlags, field_count: u32, ) -> Result { assert_initialized_main_thread!(); assert!(fps.denom() > 0); unsafe { let mut v = mem::MaybeUninit::zeroed(); let res = ffi::gst_video_time_code_init_from_date_time_full( v.as_mut_ptr(), fps.numer() as u32, fps.denom() as u32, dt.to_glib_none().0, flags.into_glib(), field_count, ); if res == glib::ffi::GFALSE { Err(glib::bool_error!("Failed to init video time code")) } else { Ok(Self(v.assume_init())) } } } #[doc(alias = "gst_video_time_code_is_valid")] pub fn is_valid(&self) -> bool { unsafe { from_glib(ffi::gst_video_time_code_is_valid(self.to_glib_none().0)) } } pub fn set_fps(&mut self, fps: gst::Fraction) { self.0.config.fps_n = fps.numer() as u32; self.0.config.fps_d = fps.denom() as u32; } pub fn set_flags(&mut self, flags: VideoTimeCodeFlags) { self.0.config.flags = flags.into_glib() } pub fn set_hours(&mut self, hours: u32) { self.0.hours = hours } pub fn set_minutes(&mut self, minutes: u32) { assert!(minutes < 60); self.0.minutes = minutes } pub fn set_seconds(&mut self, seconds: u32) { assert!(seconds < 60); self.0.seconds = seconds } pub fn set_frames(&mut self, frames: u32) { self.0.frames = frames } pub fn set_field_count(&mut self, field_count: u32) { assert!(field_count <= 2); self.0.field_count = field_count } } impl TryFrom for ValidVideoTimeCode { type Error = VideoTimeCode; fn try_from(v: VideoTimeCode) -> Result { skip_assert_initialized!(); if v.is_valid() { // Use ManuallyDrop here to prevent the Drop impl of VideoTimeCode // from running as we don't move v.0 out here but copy it. // GstVideoTimeCode implements Copy. let v = mem::ManuallyDrop::new(v); Ok(Self(v.0)) } else { Err(v) } } } impl ValidVideoTimeCode { #[allow(clippy::too_many_arguments)] pub fn new( fps: gst::Fraction, latest_daily_jam: Option<&glib::DateTime>, flags: VideoTimeCodeFlags, hours: u32, minutes: u32, seconds: u32, frames: u32, field_count: u32, ) -> Result { assert_initialized_main_thread!(); let tc = VideoTimeCode::new( fps, latest_daily_jam, flags, hours, minutes, seconds, frames, field_count, ); match tc.try_into() { Ok(v) => Ok(v), Err(_) => Err(glib::bool_error!("Failed to create new ValidVideoTimeCode")), } } // #[cfg_attr(feature = "dox", doc(cfg(feature = "v1_16")))] // pub fn from_date_time( // fps: gst::Fraction, // dt: &glib::DateTime, // flags: VideoTimeCodeFlags, // field_count: u32, // ) -> Option { // let tc = VideoTimeCode::from_date_time(fps, dt, flags, field_count); // tc.and_then(|tc| tc.try_into().ok()) // } #[doc(alias = "gst_video_time_code_add_frames")] pub fn add_frames(&mut self, frames: i64) { skip_assert_initialized!(); unsafe { ffi::gst_video_time_code_add_frames(self.to_glib_none_mut().0, frames); } } #[doc(alias = "gst_video_time_code_add_interval")] pub fn add_interval( &self, tc_inter: &VideoTimeCodeInterval, ) -> Result { unsafe { match from_glib_full(ffi::gst_video_time_code_add_interval( self.to_glib_none().0, tc_inter.to_glib_none().0, )) { Some(i) => Ok(i), None => Err(glib::bool_error!("Failed to add interval")), } } } #[doc(alias = "gst_video_time_code_compare")] fn compare(&self, tc2: &Self) -> i32 { unsafe { ffi::gst_video_time_code_compare(self.to_glib_none().0, tc2.to_glib_none().0) } } #[doc(alias = "gst_video_time_code_frames_since_daily_jam")] pub fn frames_since_daily_jam(&self) -> u64 { unsafe { ffi::gst_video_time_code_frames_since_daily_jam(self.to_glib_none().0) } } #[doc(alias = "gst_video_time_code_increment_frame")] pub fn increment_frame(&mut self) { unsafe { ffi::gst_video_time_code_increment_frame(self.to_glib_none_mut().0); } } #[doc(alias = "gst_video_time_code_nsec_since_daily_jam")] #[doc(alias = "nsec_since_daily_jam")] pub fn time_since_daily_jam(&self) -> gst::ClockTime { gst::ClockTime::from_nseconds(unsafe { ffi::gst_video_time_code_nsec_since_daily_jam(self.to_glib_none().0) }) } #[doc(alias = "gst_video_time_code_to_date_time")] pub fn to_date_time(&self) -> Result { unsafe { match from_glib_full(ffi::gst_video_time_code_to_date_time(self.to_glib_none().0)) { Some(d) => Ok(d), None => Err(glib::bool_error!( "Failed to convert VideoTimeCode to date time" )), } } } } macro_rules! generic_impl { ($name:ident) => { impl $name { pub fn hours(&self) -> u32 { self.0.hours } pub fn minutes(&self) -> u32 { self.0.minutes } pub fn seconds(&self) -> u32 { self.0.seconds } pub fn frames(&self) -> u32 { self.0.frames } pub fn field_count(&self) -> u32 { self.0.field_count } pub fn fps(&self) -> gst::Fraction { (self.0.config.fps_n as i32, self.0.config.fps_d as i32).into() } pub fn flags(&self) -> VideoTimeCodeFlags { unsafe { from_glib(self.0.config.flags) } } pub fn latest_daily_jam(&self) -> Option { unsafe { from_glib_none(self.0.config.latest_daily_jam) } } pub fn set_latest_daily_jam(&mut self, latest_daily_jam: Option<&glib::DateTime>) { unsafe { if !self.0.config.latest_daily_jam.is_null() { glib::ffi::g_date_time_unref(self.0.config.latest_daily_jam); } self.0.config.latest_daily_jam = latest_daily_jam.to_glib_full() } } } impl Clone for $name { fn clone(&self) -> Self { unsafe { let v = self.0; if !v.config.latest_daily_jam.is_null() { glib::ffi::g_date_time_ref(v.config.latest_daily_jam); } $name(v) } } } impl Drop for $name { fn drop(&mut self) { unsafe { if !self.0.config.latest_daily_jam.is_null() { glib::ffi::g_date_time_unref(self.0.config.latest_daily_jam); } } } } impl fmt::Debug for $name { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct(stringify!($name)) .field("fps", &self.fps()) .field("flags", &self.flags()) .field("latest_daily_jam", &self.latest_daily_jam()) .field("hours", &self.hours()) .field("minutes", &self.minutes()) .field("seconds", &self.seconds()) .field("frames", &self.frames()) .field("field_count", &self.field_count()) .finish() } } impl fmt::Display for $name { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let s = unsafe { glib::GString::from_glib_full(ffi::gst_video_time_code_to_string( self.to_glib_none().0, )) }; f.write_str(&s) } } unsafe impl Send for $name {} unsafe impl Sync for $name {} #[doc(hidden)] impl GlibPtrDefault for $name { type GlibType = *mut ffi::GstVideoTimeCode; } #[doc(hidden)] impl<'a> ToGlibPtr<'a, *const ffi::GstVideoTimeCode> for $name { type Storage = &'a Self; #[inline] fn to_glib_none(&'a self) -> Stash<'a, *const ffi::GstVideoTimeCode, Self> { Stash(&self.0 as *const _, self) } #[inline] fn to_glib_full(&self) -> *const ffi::GstVideoTimeCode { unsafe { ffi::gst_video_time_code_copy(&self.0 as *const _) } } } #[doc(hidden)] impl<'a> ToGlibPtrMut<'a, *mut ffi::GstVideoTimeCode> for $name { type Storage = &'a mut Self; #[inline] fn to_glib_none_mut(&'a mut self) -> StashMut<'a, *mut ffi::GstVideoTimeCode, Self> { let ptr = &mut self.0 as *mut _; StashMut(ptr, self) } } #[doc(hidden)] impl FromGlibPtrNone<*mut ffi::GstVideoTimeCode> for $name { #[inline] unsafe fn from_glib_none(ptr: *mut ffi::GstVideoTimeCode) -> Self { assert!(!ptr.is_null()); let v = ptr::read(ptr); if !v.config.latest_daily_jam.is_null() { glib::ffi::g_date_time_ref(v.config.latest_daily_jam); } $name(v) } } #[doc(hidden)] impl FromGlibPtrNone<*const ffi::GstVideoTimeCode> for $name { #[inline] unsafe fn from_glib_none(ptr: *const ffi::GstVideoTimeCode) -> Self { assert!(!ptr.is_null()); let v = ptr::read(ptr); if !v.config.latest_daily_jam.is_null() { glib::ffi::g_date_time_ref(v.config.latest_daily_jam); } $name(v) } } #[doc(hidden)] impl FromGlibPtrFull<*mut ffi::GstVideoTimeCode> for $name { #[inline] unsafe fn from_glib_full(ptr: *mut ffi::GstVideoTimeCode) -> Self { assert!(!ptr.is_null()); let v = ptr::read(ptr); if !v.config.latest_daily_jam.is_null() { glib::ffi::g_date_time_ref(v.config.latest_daily_jam); } ffi::gst_video_time_code_free(ptr); $name(v) } } #[doc(hidden)] impl FromGlibPtrBorrow<*mut ffi::GstVideoTimeCode> for $name { #[inline] unsafe fn from_glib_borrow(ptr: *mut ffi::GstVideoTimeCode) -> Borrowed { assert!(!ptr.is_null()); let v = ptr::read(ptr); Borrowed::new($name(v)) } } impl StaticType for $name { fn static_type() -> glib::Type { unsafe { from_glib(ffi::gst_video_time_code_get_type()) } } } impl glib::value::ValueType for $name { type Type = Self; } impl glib::value::ValueTypeOptional for $name {} #[doc(hidden)] unsafe impl<'a> glib::value::FromValue<'a> for $name { type Checker = glib::value::GenericValueTypeOrNoneChecker; unsafe fn from_value(value: &'a glib::Value) -> Self { skip_assert_initialized!(); from_glib_none(glib::gobject_ffi::g_value_get_boxed(value.to_glib_none().0) as *mut ffi::GstVideoTimeCode) } } #[doc(hidden)] impl glib::value::ToValue for $name { fn to_value(&self) -> glib::Value { let mut value = glib::Value::for_value_type::(); unsafe { glib::gobject_ffi::g_value_set_boxed( value.to_glib_none_mut().0, self.to_glib_none().0 as *mut _, ) } value } fn value_type(&self) -> glib::Type { Self::static_type() } } #[doc(hidden)] impl glib::value::ToValueOptional for $name { fn to_value_optional(s: Option<&Self>) -> glib::Value { skip_assert_initialized!(); let mut value = glib::Value::for_value_type::(); unsafe { glib::gobject_ffi::g_value_set_boxed( value.to_glib_none_mut().0, s.to_glib_none().0 as *mut _, ) } value } } #[doc(hidden)] impl From<$name> for glib::Value { fn from(v: $name) -> glib::Value { skip_assert_initialized!(); glib::value::ToValue::to_value(&v) } } }; } generic_impl!(VideoTimeCode); generic_impl!(ValidVideoTimeCode); impl str::FromStr for VideoTimeCode { type Err = glib::error::BoolError; #[doc(alias = "gst_video_time_code_new_from_string")] fn from_str(s: &str) -> Result { assert_initialized_main_thread!(); unsafe { Option::::from_glib_full(ffi::gst_video_time_code_new_from_string( s.to_glib_none().0, )) .ok_or_else(|| glib::bool_error!("Failed to create VideoTimeCode from string")) } } } impl PartialEq for ValidVideoTimeCode { #[inline] fn eq(&self, other: &Self) -> bool { self.compare(other) == 0 } } impl Eq for ValidVideoTimeCode {} impl PartialOrd for ValidVideoTimeCode { #[inline] fn partial_cmp(&self, other: &Self) -> Option { self.compare(other).partial_cmp(&0) } } impl Ord for ValidVideoTimeCode { #[inline] fn cmp(&self, other: &Self) -> cmp::Ordering { self.compare(other).cmp(&0) } } impl From for VideoTimeCode { fn from(v: ValidVideoTimeCode) -> Self { skip_assert_initialized!(); // Use ManuallyDrop here to prevent the Drop impl of VideoTimeCode // from running as we don't move v.0 out here but copy it. // GstVideoTimeCode implements Copy. let v = mem::ManuallyDrop::new(v); Self(v.0) } } #[repr(transparent)] #[doc(alias = "GstVideoTimeCodeMeta")] pub struct VideoTimeCodeMeta(ffi::GstVideoTimeCodeMeta); unsafe impl Send for VideoTimeCodeMeta {} unsafe impl Sync for VideoTimeCodeMeta {} impl VideoTimeCodeMeta { #[doc(alias = "gst_buffer_add_video_time_code_meta")] pub fn add<'a>( buffer: &'a mut gst::BufferRef, tc: &ValidVideoTimeCode, ) -> gst::MetaRefMut<'a, Self, gst::meta::Standalone> { skip_assert_initialized!(); unsafe { let meta = ffi::gst_buffer_add_video_time_code_meta( buffer.as_mut_ptr(), tc.to_glib_none().0 as *mut _, ); Self::from_mut_ptr(buffer, meta) } } #[doc(alias = "get_tc")] pub fn tc(&self) -> ValidVideoTimeCode { unsafe { ValidVideoTimeCode::from_glib_none(&self.0.tc as *const _) } } pub fn set_tc(&mut self, tc: ValidVideoTimeCode) { #![allow(clippy::cast_ptr_alignment)] unsafe { ffi::gst_video_time_code_clear(&mut self.0.tc); // Use ManuallyDrop here to prevent the Drop impl of VideoTimeCode // from running as we don't move tc.0 out here but copy it. // GstVideoTimeCode implements Copy. let tc = mem::ManuallyDrop::new(tc); self.0.tc = tc.0; } } } unsafe impl MetaAPI for VideoTimeCodeMeta { type GstType = ffi::GstVideoTimeCodeMeta; #[doc(alias = "gst_video_time_code_meta_api_get_type")] fn meta_api() -> glib::Type { unsafe { from_glib(ffi::gst_video_time_code_meta_api_get_type()) } } } impl fmt::Debug for VideoTimeCodeMeta { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("VideoTimeCodeMeta") .field("tc", &self.tc()) .finish() } } #[cfg(feature = "v1_16")] #[cfg(test)] mod tests { #[test] fn test_add_get_set_meta() { gst::init().unwrap(); let mut buffer = gst::Buffer::new(); { let datetime = glib::DateTime::from_utc(2021, 2, 4, 10, 53, 17.0).expect("can't create datetime"); let time_code = crate::VideoTimeCode::from_date_time( gst::Fraction::new(30, 1), &datetime, crate::VideoTimeCodeFlags::empty(), 0, ) .expect("can't create timecode"); drop(datetime); let mut meta = crate::VideoTimeCodeMeta::add( buffer.get_mut().unwrap(), &time_code.try_into().expect("invalid timecode"), ); let datetime = glib::DateTime::from_utc(2021, 2, 4, 10, 53, 17.0).expect("can't create datetime"); let mut time_code_2 = crate::ValidVideoTimeCode::try_from( crate::VideoTimeCode::from_date_time( gst::Fraction::new(30, 1), &datetime, crate::VideoTimeCodeFlags::empty(), 0, ) .expect("can't create timecode"), ) .expect("invalid timecode"); assert_eq!(meta.tc(), time_code_2); time_code_2.increment_frame(); assert_eq!(meta.tc().frames() + 1, time_code_2.frames()); meta.set_tc(time_code_2.clone()); assert_eq!(meta.tc(), time_code_2); } } }