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