1 | // Take a look at the license at the top of the repository in the LICENSE file. |
2 | |
3 | use std::{cmp, fmt, mem, str}; |
4 | |
5 | use glib::translate::*; |
6 | use gst::prelude::*; |
7 | |
8 | use crate::{VideoTimeCodeFlags, VideoTimeCodeInterval}; |
9 | |
10 | glib::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 | |
33 | glib::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 | |
55 | impl 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 | |
177 | impl 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 | |
194 | impl 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 | pub fn add_interval( |
243 | &self, |
244 | tc_inter: &VideoTimeCodeInterval, |
245 | ) -> Result<Self, glib::error::BoolError> { |
246 | unsafe { |
247 | match from_glib_full(ffi::gst_video_time_code_add_interval( |
248 | self.to_glib_none().0, |
249 | tc_inter.to_glib_none().0, |
250 | )) { |
251 | Some(i) => Ok(i), |
252 | None => Err(glib::bool_error!("Failed to add interval" )), |
253 | } |
254 | } |
255 | } |
256 | |
257 | #[doc (alias = "gst_video_time_code_compare" )] |
258 | fn compare(&self, tc2: &Self) -> i32 { |
259 | unsafe { ffi::gst_video_time_code_compare(self.to_glib_none().0, tc2.to_glib_none().0) } |
260 | } |
261 | |
262 | #[doc (alias = "gst_video_time_code_frames_since_daily_jam" )] |
263 | pub fn frames_since_daily_jam(&self) -> u64 { |
264 | unsafe { ffi::gst_video_time_code_frames_since_daily_jam(self.to_glib_none().0) } |
265 | } |
266 | |
267 | #[doc (alias = "gst_video_time_code_increment_frame" )] |
268 | pub fn increment_frame(&mut self) { |
269 | unsafe { |
270 | ffi::gst_video_time_code_increment_frame(self.to_glib_none_mut().0); |
271 | } |
272 | } |
273 | |
274 | #[doc (alias = "gst_video_time_code_nsec_since_daily_jam" )] |
275 | #[doc (alias = "nsec_since_daily_jam" )] |
276 | pub fn time_since_daily_jam(&self) -> gst::ClockTime { |
277 | gst::ClockTime::from_nseconds(unsafe { |
278 | ffi::gst_video_time_code_nsec_since_daily_jam(self.to_glib_none().0) |
279 | }) |
280 | } |
281 | |
282 | #[doc (alias = "gst_video_time_code_to_date_time" )] |
283 | pub fn to_date_time(&self) -> Result<glib::DateTime, glib::error::BoolError> { |
284 | unsafe { |
285 | match from_glib_full(ffi::gst_video_time_code_to_date_time(self.to_glib_none().0)) { |
286 | Some(d) => Ok(d), |
287 | None => Err(glib::bool_error!( |
288 | "Failed to convert VideoTimeCode to date time" |
289 | )), |
290 | } |
291 | } |
292 | } |
293 | } |
294 | |
295 | macro_rules! generic_impl { |
296 | ($name:ident) => { |
297 | impl $name { |
298 | #[inline] |
299 | pub fn hours(&self) -> u32 { |
300 | self.inner.hours |
301 | } |
302 | |
303 | #[inline] |
304 | pub fn minutes(&self) -> u32 { |
305 | self.inner.minutes |
306 | } |
307 | |
308 | #[inline] |
309 | pub fn seconds(&self) -> u32 { |
310 | self.inner.seconds |
311 | } |
312 | |
313 | #[inline] |
314 | pub fn frames(&self) -> u32 { |
315 | self.inner.frames |
316 | } |
317 | |
318 | #[inline] |
319 | pub fn field_count(&self) -> u32 { |
320 | self.inner.field_count |
321 | } |
322 | |
323 | #[inline] |
324 | pub fn fps(&self) -> gst::Fraction { |
325 | ( |
326 | self.inner.config.fps_n as i32, |
327 | self.inner.config.fps_d as i32, |
328 | ) |
329 | .into() |
330 | } |
331 | |
332 | #[inline] |
333 | pub fn flags(&self) -> VideoTimeCodeFlags { |
334 | unsafe { from_glib(self.inner.config.flags) } |
335 | } |
336 | |
337 | #[inline] |
338 | pub fn latest_daily_jam(&self) -> Option<glib::DateTime> { |
339 | unsafe { from_glib_none(self.inner.config.latest_daily_jam) } |
340 | } |
341 | |
342 | #[inline] |
343 | pub fn set_latest_daily_jam(&mut self, latest_daily_jam: Option<glib::DateTime>) { |
344 | unsafe { |
345 | if !self.inner.config.latest_daily_jam.is_null() { |
346 | glib::ffi::g_date_time_unref(self.inner.config.latest_daily_jam); |
347 | } |
348 | |
349 | self.inner.config.latest_daily_jam = latest_daily_jam.into_glib_ptr(); |
350 | } |
351 | } |
352 | } |
353 | |
354 | impl fmt::Debug for $name { |
355 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
356 | f.debug_struct(stringify!($name)) |
357 | .field("fps" , &self.fps()) |
358 | .field("flags" , &self.flags()) |
359 | .field("latest_daily_jam" , &self.latest_daily_jam()) |
360 | .field("hours" , &self.hours()) |
361 | .field("minutes" , &self.minutes()) |
362 | .field("seconds" , &self.seconds()) |
363 | .field("frames" , &self.frames()) |
364 | .field("field_count" , &self.field_count()) |
365 | .finish() |
366 | } |
367 | } |
368 | |
369 | impl fmt::Display for $name { |
370 | #[inline] |
371 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
372 | let s = unsafe { |
373 | glib::GString::from_glib_full(ffi::gst_video_time_code_to_string( |
374 | self.to_glib_none().0, |
375 | )) |
376 | }; |
377 | f.write_str(&s) |
378 | } |
379 | } |
380 | |
381 | unsafe impl Send for $name {} |
382 | unsafe impl Sync for $name {} |
383 | }; |
384 | } |
385 | |
386 | generic_impl!(VideoTimeCode); |
387 | generic_impl!(ValidVideoTimeCode); |
388 | |
389 | impl StaticType for ValidVideoTimeCode { |
390 | #[inline ] |
391 | fn static_type() -> glib::Type { |
392 | unsafe { from_glib(val:ffi::gst_video_time_code_get_type()) } |
393 | } |
394 | } |
395 | |
396 | #[doc (hidden)] |
397 | impl glib::value::ToValue for ValidVideoTimeCode { |
398 | fn to_value(&self) -> glib::Value { |
399 | let mut value: Value = glib::Value::for_value_type::<VideoTimeCode>(); |
400 | unsafe { |
401 | glib::gobject_ffi::g_value_set_boxed( |
402 | value:value.to_glib_none_mut().0, |
403 | self.to_glib_none().0 as *mut _, |
404 | ) |
405 | } |
406 | value |
407 | } |
408 | |
409 | fn value_type(&self) -> glib::Type { |
410 | Self::static_type() |
411 | } |
412 | } |
413 | |
414 | #[doc (hidden)] |
415 | impl glib::value::ToValueOptional for ValidVideoTimeCode { |
416 | fn to_value_optional(s: Option<&Self>) -> glib::Value { |
417 | skip_assert_initialized!(); |
418 | let mut value: Value = glib::Value::for_value_type::<VideoTimeCode>(); |
419 | unsafe { |
420 | glib::gobject_ffi::g_value_set_boxed( |
421 | value:value.to_glib_none_mut().0, |
422 | v_boxed:s.to_glib_none().0 as *mut _, |
423 | ) |
424 | } |
425 | value |
426 | } |
427 | } |
428 | |
429 | #[doc (hidden)] |
430 | impl From<ValidVideoTimeCode> for glib::Value { |
431 | fn from(v: ValidVideoTimeCode) -> glib::Value { |
432 | skip_assert_initialized!(); |
433 | glib::value::ToValue::to_value(&v) |
434 | } |
435 | } |
436 | |
437 | impl str::FromStr for VideoTimeCode { |
438 | type Err = glib::error::BoolError; |
439 | |
440 | #[doc (alias = "gst_video_time_code_new_from_string" )] |
441 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
442 | assert_initialized_main_thread!(); |
443 | unsafe { |
444 | Option::<Self>::from_glib_full(ffi::gst_video_time_code_new_from_string( |
445 | s.to_glib_none().0, |
446 | )) |
447 | .ok_or_else(|| glib::bool_error!("Failed to create VideoTimeCode from string" )) |
448 | } |
449 | } |
450 | } |
451 | |
452 | impl PartialEq for ValidVideoTimeCode { |
453 | #[inline ] |
454 | fn eq(&self, other: &Self) -> bool { |
455 | self.compare(tc2:other) == 0 |
456 | } |
457 | } |
458 | |
459 | impl Eq for ValidVideoTimeCode {} |
460 | |
461 | impl PartialOrd for ValidVideoTimeCode { |
462 | #[inline ] |
463 | fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> { |
464 | Some(self.cmp(other)) |
465 | } |
466 | } |
467 | |
468 | impl Ord for ValidVideoTimeCode { |
469 | #[inline ] |
470 | fn cmp(&self, other: &Self) -> cmp::Ordering { |
471 | self.compare(tc2:other).cmp(&0) |
472 | } |
473 | } |
474 | |
475 | impl From<ValidVideoTimeCode> for VideoTimeCode { |
476 | #[inline ] |
477 | fn from(v: ValidVideoTimeCode) -> Self { |
478 | skip_assert_initialized!(); |
479 | // Use ManuallyDrop here to prevent the Drop impl of VideoTimeCode |
480 | // from running as we don't move v.0 out here but copy it. |
481 | // GstVideoTimeCode implements Copy. |
482 | let v: ManuallyDrop = mem::ManuallyDrop::new(v); |
483 | Self { inner: v.inner } |
484 | } |
485 | } |
486 | |
487 | #[repr (transparent)] |
488 | #[doc (alias = "GstVideoTimeCodeMeta" )] |
489 | pub struct VideoTimeCodeMeta(ffi::GstVideoTimeCodeMeta); |
490 | |
491 | unsafe impl Send for VideoTimeCodeMeta {} |
492 | unsafe impl Sync for VideoTimeCodeMeta {} |
493 | |
494 | impl VideoTimeCodeMeta { |
495 | #[doc (alias = "gst_buffer_add_video_time_code_meta" )] |
496 | pub fn add<'a>( |
497 | buffer: &'a mut gst::BufferRef, |
498 | tc: &ValidVideoTimeCode, |
499 | ) -> gst::MetaRefMut<'a, Self, gst::meta::Standalone> { |
500 | skip_assert_initialized!(); |
501 | unsafe { |
502 | let meta = ffi::gst_buffer_add_video_time_code_meta( |
503 | buffer.as_mut_ptr(), |
504 | tc.to_glib_none().0 as *mut _, |
505 | ); |
506 | |
507 | Self::from_mut_ptr(buffer, meta) |
508 | } |
509 | } |
510 | |
511 | #[doc (alias = "get_tc" )] |
512 | #[inline ] |
513 | pub fn tc(&self) -> ValidVideoTimeCode { |
514 | unsafe { ValidVideoTimeCode::from_glib_none(&self.0.tc as *const _) } |
515 | } |
516 | |
517 | #[inline ] |
518 | pub fn set_tc(&mut self, tc: ValidVideoTimeCode) { |
519 | #![allow (clippy::cast_ptr_alignment)] |
520 | unsafe { |
521 | ffi::gst_video_time_code_clear(&mut self.0.tc); |
522 | // Use ManuallyDrop here to prevent the Drop impl of VideoTimeCode |
523 | // from running as we don't move tc.0 out here but copy it. |
524 | // GstVideoTimeCode implements Copy. |
525 | let tc = mem::ManuallyDrop::new(tc); |
526 | self.0.tc = tc.inner; |
527 | } |
528 | } |
529 | } |
530 | |
531 | unsafe impl MetaAPI for VideoTimeCodeMeta { |
532 | type GstType = ffi::GstVideoTimeCodeMeta; |
533 | |
534 | #[doc (alias = "gst_video_time_code_meta_api_get_type" )] |
535 | #[inline ] |
536 | fn meta_api() -> glib::Type { |
537 | unsafe { from_glib(val:ffi::gst_video_time_code_meta_api_get_type()) } |
538 | } |
539 | } |
540 | |
541 | impl fmt::Debug for VideoTimeCodeMeta { |
542 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
543 | f&mut DebugStruct<'_, '_>.debug_struct("VideoTimeCodeMeta" ) |
544 | .field(name:"tc" , &self.tc()) |
545 | .finish() |
546 | } |
547 | } |
548 | |
549 | #[cfg (feature = "v1_16" )] |
550 | #[cfg (test)] |
551 | mod tests { |
552 | #[test ] |
553 | fn test_add_get_set_meta() { |
554 | gst::init().unwrap(); |
555 | |
556 | let mut buffer = gst::Buffer::new(); |
557 | { |
558 | let datetime = |
559 | glib::DateTime::from_utc(2021, 2, 4, 10, 53, 17.0).expect("can't create datetime" ); |
560 | let time_code = crate::VideoTimeCode::from_date_time( |
561 | gst::Fraction::new(30, 1), |
562 | &datetime, |
563 | crate::VideoTimeCodeFlags::empty(), |
564 | 0, |
565 | ) |
566 | .expect("can't create timecode" ); |
567 | drop(datetime); |
568 | |
569 | let mut meta = crate::VideoTimeCodeMeta::add( |
570 | buffer.get_mut().unwrap(), |
571 | &time_code.try_into().expect("invalid timecode" ), |
572 | ); |
573 | |
574 | let datetime = |
575 | glib::DateTime::from_utc(2021, 2, 4, 10, 53, 17.0).expect("can't create datetime" ); |
576 | let mut time_code_2 = crate::ValidVideoTimeCode::try_from( |
577 | crate::VideoTimeCode::from_date_time( |
578 | gst::Fraction::new(30, 1), |
579 | &datetime, |
580 | crate::VideoTimeCodeFlags::empty(), |
581 | 0, |
582 | ) |
583 | .expect("can't create timecode" ), |
584 | ) |
585 | .expect("invalid timecode" ); |
586 | |
587 | assert_eq!(meta.tc(), time_code_2); |
588 | |
589 | time_code_2.increment_frame(); |
590 | |
591 | assert_eq!(meta.tc().frames() + 1, time_code_2.frames()); |
592 | |
593 | meta.set_tc(time_code_2.clone()); |
594 | |
595 | assert_eq!(meta.tc(), time_code_2); |
596 | } |
597 | } |
598 | } |
599 | |