1use 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")]
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 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 #[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 #[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 #[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 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 #[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 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 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}