gstreamer_video/
video_time_code.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{cmp, fmt, mem, str};
4
5use glib::translate::*;
6use gst::prelude::*;
7
8use crate::{ffi, VideoTimeCodeFlags, VideoTimeCodeInterval};
9
10glib::wrapper! {
11    /// `field_count` must be 0 for progressive video and 1 or 2 for interlaced.
12    ///
13    /// A representation of a SMPTE time code.
14    ///
15    /// `hours` must be positive and less than 24. Will wrap around otherwise.
16    /// `minutes` and `seconds` must be positive and less than 60.
17    /// `frames` must be less than or equal to `config` / `config`
18    /// These values are *NOT* automatically normalized.
19    #[doc(alias = "GstVideoTimeCode")]
20    pub struct VideoTimeCode(BoxedInline<ffi::GstVideoTimeCode>);
21
22    match fn {
23        copy => |ptr| ffi::gst_video_time_code_copy(ptr),
24        free => |ptr| ffi::gst_video_time_code_free(ptr),
25        init => |_ptr| (),
26        copy_into => |dest, src| {
27            *dest = *src;
28            if !(*dest).config.latest_daily_jam.is_null() {
29                glib::ffi::g_date_time_ref((*dest).config.latest_daily_jam);
30            }
31        },
32        clear => |ptr| {
33            if !(*ptr).config.latest_daily_jam.is_null() {
34                glib::ffi::g_date_time_unref((*ptr).config.latest_daily_jam);
35            }
36        },
37        type_ => || ffi::gst_video_time_code_get_type(),
38    }
39}
40
41glib::wrapper! {
42    #[doc(alias = "GstVideoTimeCode")]
43    pub struct ValidVideoTimeCode(BoxedInline<ffi::GstVideoTimeCode>);
44
45    match fn {
46        copy => |ptr| ffi::gst_video_time_code_copy(ptr),
47        free => |ptr| ffi::gst_video_time_code_free(ptr),
48        init => |_ptr| (),
49        copy_into => |dest, src| {
50            *dest = *src;
51            if !(*dest).config.latest_daily_jam.is_null() {
52                glib::ffi::g_date_time_ref((*dest).config.latest_daily_jam);
53            }
54        },
55        clear => |ptr| {
56            if !(*ptr).config.latest_daily_jam.is_null() {
57                glib::ffi::g_date_time_unref((*ptr).config.latest_daily_jam);
58            }
59        },
60    }
61}
62
63impl VideoTimeCode {
64    ///
65    /// # Returns
66    ///
67    /// a new empty, invalid [`VideoTimeCode`][crate::VideoTimeCode]
68    pub fn new_empty() -> Self {
69        assert_initialized_main_thread!();
70        unsafe {
71            let mut v = mem::MaybeUninit::zeroed();
72            ffi::gst_video_time_code_clear(v.as_mut_ptr());
73            Self {
74                inner: v.assume_init(),
75            }
76        }
77    }
78
79    /// `field_count` is 0 for progressive, 1 or 2 for interlaced.
80    /// `latest_daiy_jam` reference is stolen from caller.
81    /// ## `fps_n`
82    /// Numerator of the frame rate
83    /// ## `fps_d`
84    /// Denominator of the frame rate
85    /// ## `latest_daily_jam`
86    /// The latest daily jam of the [`VideoTimeCode`][crate::VideoTimeCode]
87    /// ## `flags`
88    /// [`VideoTimeCodeFlags`][crate::VideoTimeCodeFlags]
89    /// ## `hours`
90    /// the hours field of [`VideoTimeCode`][crate::VideoTimeCode]
91    /// ## `minutes`
92    /// the minutes field of [`VideoTimeCode`][crate::VideoTimeCode]
93    /// ## `seconds`
94    /// the seconds field of [`VideoTimeCode`][crate::VideoTimeCode]
95    /// ## `frames`
96    /// the frames field of [`VideoTimeCode`][crate::VideoTimeCode]
97    /// ## `field_count`
98    /// Interlaced video field count
99    ///
100    /// # Returns
101    ///
102    /// a new [`VideoTimeCode`][crate::VideoTimeCode] with the given values.
103    /// The values are not checked for being in a valid range. To see if your
104    /// timecode actually has valid content, use [`is_valid()`][Self::is_valid()].
105    #[allow(clippy::too_many_arguments)]
106    pub fn new(
107        fps: gst::Fraction,
108        latest_daily_jam: Option<&glib::DateTime>,
109        flags: VideoTimeCodeFlags,
110        hours: u32,
111        minutes: u32,
112        seconds: u32,
113        frames: u32,
114        field_count: u32,
115    ) -> Self {
116        assert_initialized_main_thread!();
117        unsafe {
118            let mut v = mem::MaybeUninit::uninit();
119            ffi::gst_video_time_code_init(
120                v.as_mut_ptr(),
121                fps.numer() as u32,
122                fps.denom() as u32,
123                latest_daily_jam.to_glib_none().0,
124                flags.into_glib(),
125                hours,
126                minutes,
127                seconds,
128                frames,
129                field_count,
130            );
131
132            Self {
133                inner: v.assume_init(),
134            }
135        }
136    }
137
138    /// The resulting config->latest_daily_jam is set to
139    /// midnight, and timecode is set to the given time.
140    ///
141    /// This might return a completely invalid timecode, use
142    /// [`from_date_time_full()`][Self::from_date_time_full()] to ensure
143    /// that you would get [`None`] instead in that case.
144    /// ## `fps_n`
145    /// Numerator of the frame rate
146    /// ## `fps_d`
147    /// Denominator of the frame rate
148    /// ## `dt`
149    /// [`glib::DateTime`][crate::glib::DateTime] to convert
150    /// ## `flags`
151    /// [`VideoTimeCodeFlags`][crate::VideoTimeCodeFlags]
152    /// ## `field_count`
153    /// Interlaced video field count
154    ///
155    /// # Returns
156    ///
157    /// the [`VideoTimeCode`][crate::VideoTimeCode] representation of `dt`.
158    #[cfg(feature = "v1_16")]
159    #[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
160    #[doc(alias = "gst_video_time_code_init_from_date_time_full")]
161    pub fn from_date_time(
162        fps: gst::Fraction,
163        dt: &glib::DateTime,
164        flags: VideoTimeCodeFlags,
165        field_count: u32,
166    ) -> Result<Self, glib::error::BoolError> {
167        assert_initialized_main_thread!();
168        assert!(fps.denom() > 0);
169        unsafe {
170            let mut v = mem::MaybeUninit::zeroed();
171            let res = ffi::gst_video_time_code_init_from_date_time_full(
172                v.as_mut_ptr(),
173                fps.numer() as u32,
174                fps.denom() as u32,
175                dt.to_glib_none().0,
176                flags.into_glib(),
177                field_count,
178            );
179
180            if res == glib::ffi::GFALSE {
181                Err(glib::bool_error!("Failed to init video time code"))
182            } else {
183                Ok(Self {
184                    inner: v.assume_init(),
185                })
186            }
187        }
188    }
189
190    ///
191    /// # Returns
192    ///
193    /// whether `self` is a valid timecode (supported frame rate,
194    /// hours/minutes/seconds/frames not overflowing)
195    #[doc(alias = "gst_video_time_code_is_valid")]
196    pub fn is_valid(&self) -> bool {
197        unsafe { from_glib(ffi::gst_video_time_code_is_valid(self.to_glib_none().0)) }
198    }
199
200    #[inline]
201    pub fn set_fps(&mut self, fps: gst::Fraction) {
202        self.inner.config.fps_n = fps.numer() as u32;
203        self.inner.config.fps_d = fps.denom() as u32;
204    }
205
206    #[inline]
207    pub fn set_flags(&mut self, flags: VideoTimeCodeFlags) {
208        self.inner.config.flags = flags.into_glib()
209    }
210
211    #[inline]
212    pub fn set_hours(&mut self, hours: u32) {
213        self.inner.hours = hours
214    }
215
216    #[inline]
217    pub fn set_minutes(&mut self, minutes: u32) {
218        assert!(minutes < 60);
219        self.inner.minutes = minutes
220    }
221
222    #[inline]
223    pub fn set_seconds(&mut self, seconds: u32) {
224        assert!(seconds < 60);
225        self.inner.seconds = seconds
226    }
227
228    #[inline]
229    pub fn set_frames(&mut self, frames: u32) {
230        self.inner.frames = frames
231    }
232
233    #[inline]
234    pub fn set_field_count(&mut self, field_count: u32) {
235        assert!(field_count <= 2);
236        self.inner.field_count = field_count
237    }
238}
239
240impl TryFrom<VideoTimeCode> for ValidVideoTimeCode {
241    type Error = VideoTimeCode;
242
243    fn try_from(v: VideoTimeCode) -> Result<Self, VideoTimeCode> {
244        skip_assert_initialized!();
245        if v.is_valid() {
246            // Use ManuallyDrop here to prevent the Drop impl of VideoTimeCode
247            // from running as we don't move v.0 out here but copy it.
248            // GstVideoTimeCode implements Copy.
249            let v = mem::ManuallyDrop::new(v);
250            Ok(Self { inner: v.inner })
251        } else {
252            Err(v)
253        }
254    }
255}
256
257impl ValidVideoTimeCode {
258    #[allow(clippy::too_many_arguments)]
259    pub fn new(
260        fps: gst::Fraction,
261        latest_daily_jam: Option<&glib::DateTime>,
262        flags: VideoTimeCodeFlags,
263        hours: u32,
264        minutes: u32,
265        seconds: u32,
266        frames: u32,
267        field_count: u32,
268    ) -> Result<Self, glib::error::BoolError> {
269        skip_assert_initialized!();
270        let tc = VideoTimeCode::new(
271            fps,
272            latest_daily_jam,
273            flags,
274            hours,
275            minutes,
276            seconds,
277            frames,
278            field_count,
279        );
280        match tc.try_into() {
281            Ok(v) => Ok(v),
282            Err(_) => Err(glib::bool_error!("Failed to create new ValidVideoTimeCode")),
283        }
284    }
285
286    //    #[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
287    //    pub fn from_date_time(
288    //        fps: gst::Fraction,
289    //        dt: &glib::DateTime,
290    //        flags: VideoTimeCodeFlags,
291    //        field_count: u32,
292    //    ) -> Option<VideoTimeCode> {
293    //        let tc = VideoTimeCode::from_date_time(fps, dt, flags, field_count);
294    //        tc.and_then(|tc| tc.try_into().ok())
295    //    }
296
297    #[doc(alias = "gst_video_time_code_add_frames")]
298    pub fn add_frames(&mut self, frames: i64) {
299        unsafe {
300            ffi::gst_video_time_code_add_frames(self.to_glib_none_mut().0, frames);
301        }
302    }
303
304    #[doc(alias = "gst_video_time_code_add_interval")]
305    #[must_use = "this returns the result of the operation, without modifying the original"]
306    pub fn add_interval(
307        &self,
308        tc_inter: &VideoTimeCodeInterval,
309    ) -> Result<Self, glib::error::BoolError> {
310        unsafe {
311            match from_glib_full(ffi::gst_video_time_code_add_interval(
312                self.to_glib_none().0,
313                tc_inter.to_glib_none().0,
314            )) {
315                Some(i) => Ok(i),
316                None => Err(glib::bool_error!("Failed to add interval")),
317            }
318        }
319    }
320
321    #[doc(alias = "gst_video_time_code_compare")]
322    fn compare(&self, tc2: &Self) -> i32 {
323        unsafe { ffi::gst_video_time_code_compare(self.to_glib_none().0, tc2.to_glib_none().0) }
324    }
325
326    #[doc(alias = "gst_video_time_code_frames_since_daily_jam")]
327    pub fn frames_since_daily_jam(&self) -> u64 {
328        unsafe { ffi::gst_video_time_code_frames_since_daily_jam(self.to_glib_none().0) }
329    }
330
331    #[doc(alias = "gst_video_time_code_increment_frame")]
332    pub fn increment_frame(&mut self) {
333        unsafe {
334            ffi::gst_video_time_code_increment_frame(self.to_glib_none_mut().0);
335        }
336    }
337
338    #[doc(alias = "gst_video_time_code_nsec_since_daily_jam")]
339    #[doc(alias = "nsec_since_daily_jam")]
340    pub fn time_since_daily_jam(&self) -> gst::ClockTime {
341        gst::ClockTime::from_nseconds(unsafe {
342            ffi::gst_video_time_code_nsec_since_daily_jam(self.to_glib_none().0)
343        })
344    }
345
346    #[doc(alias = "gst_video_time_code_to_date_time")]
347    pub fn to_date_time(&self) -> Result<glib::DateTime, glib::error::BoolError> {
348        unsafe {
349            match from_glib_full(ffi::gst_video_time_code_to_date_time(self.to_glib_none().0)) {
350                Some(d) => Ok(d),
351                None => Err(glib::bool_error!(
352                    "Failed to convert VideoTimeCode to date time"
353                )),
354            }
355        }
356    }
357}
358
359macro_rules! generic_impl {
360    ($name:ident) => {
361        impl $name {
362            #[inline]
363            pub fn hours(&self) -> u32 {
364                self.inner.hours
365            }
366
367            #[inline]
368            pub fn minutes(&self) -> u32 {
369                self.inner.minutes
370            }
371
372            #[inline]
373            pub fn seconds(&self) -> u32 {
374                self.inner.seconds
375            }
376
377            #[inline]
378            pub fn frames(&self) -> u32 {
379                self.inner.frames
380            }
381
382            #[inline]
383            pub fn field_count(&self) -> u32 {
384                self.inner.field_count
385            }
386
387            #[inline]
388            pub fn fps(&self) -> gst::Fraction {
389                (
390                    self.inner.config.fps_n as i32,
391                    self.inner.config.fps_d as i32,
392                )
393                    .into()
394            }
395
396            #[inline]
397            pub fn flags(&self) -> VideoTimeCodeFlags {
398                unsafe { from_glib(self.inner.config.flags) }
399            }
400
401            #[inline]
402            pub fn latest_daily_jam(&self) -> Option<glib::DateTime> {
403                unsafe { from_glib_none(self.inner.config.latest_daily_jam) }
404            }
405
406            #[inline]
407            pub fn set_latest_daily_jam(&mut self, latest_daily_jam: Option<glib::DateTime>) {
408                unsafe {
409                    if !self.inner.config.latest_daily_jam.is_null() {
410                        glib::ffi::g_date_time_unref(self.inner.config.latest_daily_jam);
411                    }
412
413                    self.inner.config.latest_daily_jam = latest_daily_jam.into_glib_ptr();
414                }
415            }
416        }
417
418        impl fmt::Debug for $name {
419            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
420                f.debug_struct(stringify!($name))
421                    .field("fps", &self.fps())
422                    .field("flags", &self.flags())
423                    .field("latest_daily_jam", &self.latest_daily_jam())
424                    .field("hours", &self.hours())
425                    .field("minutes", &self.minutes())
426                    .field("seconds", &self.seconds())
427                    .field("frames", &self.frames())
428                    .field("field_count", &self.field_count())
429                    .finish()
430            }
431        }
432
433        impl fmt::Display for $name {
434            #[inline]
435            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
436                let s = unsafe {
437                    glib::GString::from_glib_full(ffi::gst_video_time_code_to_string(
438                        self.to_glib_none().0,
439                    ))
440                };
441                f.write_str(&s)
442            }
443        }
444
445        unsafe impl Send for $name {}
446        unsafe impl Sync for $name {}
447    };
448}
449
450generic_impl!(VideoTimeCode);
451generic_impl!(ValidVideoTimeCode);
452
453impl StaticType for ValidVideoTimeCode {
454    #[inline]
455    fn static_type() -> glib::Type {
456        unsafe { from_glib(ffi::gst_video_time_code_get_type()) }
457    }
458}
459
460#[doc(hidden)]
461impl glib::value::ToValue for ValidVideoTimeCode {
462    fn to_value(&self) -> glib::Value {
463        let mut value = glib::Value::for_value_type::<VideoTimeCode>();
464        unsafe {
465            glib::gobject_ffi::g_value_set_boxed(
466                value.to_glib_none_mut().0,
467                self.to_glib_none().0 as *mut _,
468            )
469        }
470        value
471    }
472
473    fn value_type(&self) -> glib::Type {
474        Self::static_type()
475    }
476}
477
478#[doc(hidden)]
479impl glib::value::ToValueOptional for ValidVideoTimeCode {
480    fn to_value_optional(s: Option<&Self>) -> glib::Value {
481        skip_assert_initialized!();
482        let mut value = glib::Value::for_value_type::<VideoTimeCode>();
483        unsafe {
484            glib::gobject_ffi::g_value_set_boxed(
485                value.to_glib_none_mut().0,
486                s.to_glib_none().0 as *mut _,
487            )
488        }
489        value
490    }
491}
492
493#[doc(hidden)]
494impl From<ValidVideoTimeCode> for glib::Value {
495    fn from(v: ValidVideoTimeCode) -> glib::Value {
496        skip_assert_initialized!();
497        glib::value::ToValue::to_value(&v)
498    }
499}
500
501impl str::FromStr for VideoTimeCode {
502    type Err = glib::error::BoolError;
503
504    #[doc(alias = "gst_video_time_code_new_from_string")]
505    fn from_str(s: &str) -> Result<Self, Self::Err> {
506        assert_initialized_main_thread!();
507        unsafe {
508            Option::<Self>::from_glib_full(ffi::gst_video_time_code_new_from_string(
509                s.to_glib_none().0,
510            ))
511            .ok_or_else(|| glib::bool_error!("Failed to create VideoTimeCode from string"))
512        }
513    }
514}
515
516impl PartialEq for ValidVideoTimeCode {
517    #[inline]
518    fn eq(&self, other: &Self) -> bool {
519        self.compare(other) == 0
520    }
521}
522
523impl Eq for ValidVideoTimeCode {}
524
525impl PartialOrd for ValidVideoTimeCode {
526    #[inline]
527    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
528        Some(self.cmp(other))
529    }
530}
531
532impl Ord for ValidVideoTimeCode {
533    #[inline]
534    fn cmp(&self, other: &Self) -> cmp::Ordering {
535        self.compare(other).cmp(&0)
536    }
537}
538
539impl From<ValidVideoTimeCode> for VideoTimeCode {
540    #[inline]
541    fn from(v: ValidVideoTimeCode) -> Self {
542        skip_assert_initialized!();
543        // Use ManuallyDrop here to prevent the Drop impl of VideoTimeCode
544        // from running as we don't move v.0 out here but copy it.
545        // GstVideoTimeCode implements Copy.
546        let v = mem::ManuallyDrop::new(v);
547        Self { inner: v.inner }
548    }
549}
550
551#[repr(transparent)]
552#[doc(alias = "GstVideoTimeCodeMeta")]
553pub struct VideoTimeCodeMeta(ffi::GstVideoTimeCodeMeta);
554
555unsafe impl Send for VideoTimeCodeMeta {}
556unsafe impl Sync for VideoTimeCodeMeta {}
557
558impl VideoTimeCodeMeta {
559    #[doc(alias = "gst_buffer_add_video_time_code_meta")]
560    pub fn add<'a>(
561        buffer: &'a mut gst::BufferRef,
562        tc: &ValidVideoTimeCode,
563    ) -> gst::MetaRefMut<'a, Self, gst::meta::Standalone> {
564        skip_assert_initialized!();
565        unsafe {
566            let meta = ffi::gst_buffer_add_video_time_code_meta(
567                buffer.as_mut_ptr(),
568                tc.to_glib_none().0 as *mut _,
569            );
570
571            Self::from_mut_ptr(buffer, meta)
572        }
573    }
574
575    #[doc(alias = "get_tc")]
576    #[inline]
577    pub fn tc(&self) -> ValidVideoTimeCode {
578        unsafe { ValidVideoTimeCode::from_glib_none(&self.0.tc as *const _) }
579    }
580
581    #[inline]
582    pub fn set_tc(&mut self, tc: ValidVideoTimeCode) {
583        #![allow(clippy::cast_ptr_alignment)]
584        unsafe {
585            ffi::gst_video_time_code_clear(&mut self.0.tc);
586            // Use ManuallyDrop here to prevent the Drop impl of VideoTimeCode
587            // from running as we don't move tc.0 out here but copy it.
588            // GstVideoTimeCode implements Copy.
589            let tc = mem::ManuallyDrop::new(tc);
590            self.0.tc = tc.inner;
591        }
592    }
593}
594
595unsafe impl MetaAPI for VideoTimeCodeMeta {
596    type GstType = ffi::GstVideoTimeCodeMeta;
597
598    #[doc(alias = "gst_video_time_code_meta_api_get_type")]
599    #[inline]
600    fn meta_api() -> glib::Type {
601        unsafe { from_glib(ffi::gst_video_time_code_meta_api_get_type()) }
602    }
603}
604
605impl fmt::Debug for VideoTimeCodeMeta {
606    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
607        f.debug_struct("VideoTimeCodeMeta")
608            .field("tc", &self.tc())
609            .finish()
610    }
611}
612
613#[cfg(feature = "v1_16")]
614#[cfg(test)]
615mod tests {
616    #[test]
617    fn test_add_get_set_meta() {
618        gst::init().unwrap();
619
620        let mut buffer = gst::Buffer::new();
621        {
622            let datetime =
623                glib::DateTime::from_utc(2021, 2, 4, 10, 53, 17.0).expect("can't create datetime");
624            let time_code = crate::VideoTimeCode::from_date_time(
625                gst::Fraction::new(30, 1),
626                &datetime,
627                crate::VideoTimeCodeFlags::empty(),
628                0,
629            )
630            .expect("can't create timecode");
631            drop(datetime);
632
633            let mut meta = crate::VideoTimeCodeMeta::add(
634                buffer.get_mut().unwrap(),
635                &time_code.try_into().expect("invalid timecode"),
636            );
637
638            let datetime =
639                glib::DateTime::from_utc(2021, 2, 4, 10, 53, 17.0).expect("can't create datetime");
640            let mut time_code_2 = crate::ValidVideoTimeCode::try_from(
641                crate::VideoTimeCode::from_date_time(
642                    gst::Fraction::new(30, 1),
643                    &datetime,
644                    crate::VideoTimeCodeFlags::empty(),
645                    0,
646                )
647                .expect("can't create timecode"),
648            )
649            .expect("invalid timecode");
650
651            assert_eq!(meta.tc(), time_code_2);
652
653            time_code_2.increment_frame();
654
655            assert_eq!(meta.tc().frames() + 1, time_code_2.frames());
656
657            meta.set_tc(time_code_2.clone());
658
659            assert_eq!(meta.tc(), time_code_2);
660        }
661    }
662}