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: ManuallyDrop = 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(val: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: 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: 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 v_boxed: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(tc2: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(tc2: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: ManuallyDrop = 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(val: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&mut DebugStruct<'_, '_>.debug_struct("VideoTimeCodeMeta")
545 .field(name:"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}
600