• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) <2016> Vivia Nikolaidou <vivia@toolsonair.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 
23 #include <stdio.h>
24 #include "gstvideotimecode.h"
25 
26 static void
27 gst_video_time_code_gvalue_to_string (const GValue * tc_val, GValue * str_val);
28 static void
29 gst_video_time_code_gvalue_from_string (const GValue * str_val,
30     GValue * tc_val);
31 static gboolean gst_video_time_code_deserialize (GValue * dest,
32     const gchar * tc_str);
33 static gchar *gst_video_time_code_serialize (const GValue * val);
34 
35 static void
_init(GType type)36 _init (GType type)
37 {
38   static GstValueTable table =
39       { 0, (GstValueCompareFunc) gst_video_time_code_compare,
40     (GstValueSerializeFunc) gst_video_time_code_serialize,
41     (GstValueDeserializeFunc) gst_video_time_code_deserialize
42   };
43 
44   table.type = type;
45   gst_value_register (&table);
46   g_value_register_transform_func (type, G_TYPE_STRING,
47       (GValueTransform) gst_video_time_code_gvalue_to_string);
48   g_value_register_transform_func (G_TYPE_STRING, type,
49       (GValueTransform) gst_video_time_code_gvalue_from_string);
50 }
51 
52 G_DEFINE_BOXED_TYPE_WITH_CODE (GstVideoTimeCode, gst_video_time_code,
53     (GBoxedCopyFunc) gst_video_time_code_copy,
54     (GBoxedFreeFunc) gst_video_time_code_free, _init (g_define_type_id));
55 
56 /**
57  * gst_video_time_code_is_valid:
58  * @tc: #GstVideoTimeCode to check
59  *
60  * Returns: whether @tc is a valid timecode (supported frame rate,
61  * hours/minutes/seconds/frames not overflowing)
62  *
63  * Since: 1.10
64  */
65 gboolean
gst_video_time_code_is_valid(const GstVideoTimeCode * tc)66 gst_video_time_code_is_valid (const GstVideoTimeCode * tc)
67 {
68   guint fr;
69 
70   g_return_val_if_fail (tc != NULL, FALSE);
71 
72   if (tc->config.fps_n == 0 || tc->config.fps_d == 0)
73     return FALSE;
74 
75   if (tc->hours >= 24)
76     return FALSE;
77   if (tc->minutes >= 60)
78     return FALSE;
79   if (tc->seconds >= 60)
80     return FALSE;
81 
82   /* We can't have more frames than rounded up frames per second */
83   fr = (tc->config.fps_n + (tc->config.fps_d >> 1)) / tc->config.fps_d;
84   if (tc->frames >= fr && (tc->config.fps_n != 0 || tc->config.fps_d != 1))
85     return FALSE;
86 
87   /* We either need a specific X/1001 framerate or otherwise an integer
88    * framerate */
89   if (tc->config.fps_d == 1001) {
90     if (tc->config.fps_n != 30000 && tc->config.fps_n != 60000 &&
91         tc->config.fps_n != 24000)
92       return FALSE;
93   } else if (tc->config.fps_n % tc->config.fps_d != 0) {
94     return FALSE;
95   }
96 
97   /* We only support 30000/1001 and 60000/1001 as drop-frame framerates.
98    * 24000/1001 is *not* a drop-frame framerate! */
99   if (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) {
100     if (tc->config.fps_d != 1001 || (tc->config.fps_n != 30000
101             && tc->config.fps_n != 60000))
102       return FALSE;
103   }
104 
105   /* Drop-frame framerates require skipping over the first two
106    * timecodes every minutes except for every tenth minute in case
107    * of 30000/1001 and the first four timecodes for 60000/1001 */
108   if ((tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) &&
109       tc->minutes % 10 && tc->seconds == 0 && tc->frames < fr / 15) {
110     return FALSE;
111   }
112 
113   return TRUE;
114 }
115 
116 /**
117  * gst_video_time_code_to_string:
118  * @tc: A #GstVideoTimeCode to convert
119  *
120  * Returns: the SMPTE ST 2059-1:2015 string representation of @tc. That will
121  * take the form hh:mm:ss:ff. The last separator (between seconds and frames)
122  * may vary:
123  *
124  * ';' for drop-frame, non-interlaced content and for drop-frame interlaced
125  * field 2
126  * ',' for drop-frame interlaced field 1
127  * ':' for non-drop-frame, non-interlaced content and for non-drop-frame
128  * interlaced field 2
129  * '.' for non-drop-frame interlaced field 1
130  *
131  * Since: 1.10
132  */
133 gchar *
gst_video_time_code_to_string(const GstVideoTimeCode * tc)134 gst_video_time_code_to_string (const GstVideoTimeCode * tc)
135 {
136   gchar *ret;
137   gboolean top_dot_present;
138   gchar sep;
139 
140   /* Top dot is present for non-interlaced content, and for field 2 in
141    * interlaced content */
142   top_dot_present =
143       !((tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_INTERLACED) != 0
144       && tc->field_count == 1);
145 
146   if (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME)
147     sep = top_dot_present ? ';' : ',';
148   else
149     sep = top_dot_present ? ':' : '.';
150 
151   ret =
152       g_strdup_printf ("%02d:%02d:%02d%c%02d", tc->hours, tc->minutes,
153       tc->seconds, sep, tc->frames);
154 
155   return ret;
156 }
157 
158 /**
159  * gst_video_time_code_to_date_time:
160  * @tc: A valid #GstVideoTimeCode to convert
161  *
162  * The @tc.config->latest_daily_jam is required to be non-NULL.
163  *
164  * Returns: (nullable): the #GDateTime representation of @tc or %NULL if @tc
165  *   has no daily jam.
166  *
167  * Since: 1.10
168  */
169 GDateTime *
gst_video_time_code_to_date_time(const GstVideoTimeCode * tc)170 gst_video_time_code_to_date_time (const GstVideoTimeCode * tc)
171 {
172   GDateTime *ret;
173   GDateTime *ret2;
174   gdouble add_us;
175 
176   g_return_val_if_fail (gst_video_time_code_is_valid (tc), NULL);
177 
178   if (tc->config.latest_daily_jam == NULL) {
179     gchar *tc_str = gst_video_time_code_to_string (tc);
180     GST_WARNING
181         ("Asked to convert time code %s to GDateTime, but its latest daily jam is NULL",
182         tc_str);
183     g_free (tc_str);
184     return NULL;
185   }
186 
187   ret = g_date_time_ref (tc->config.latest_daily_jam);
188 
189   gst_util_fraction_to_double (tc->frames * tc->config.fps_d, tc->config.fps_n,
190       &add_us);
191   if ((tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_INTERLACED)
192       && tc->field_count == 1) {
193     gdouble sub_us;
194 
195     gst_util_fraction_to_double (tc->config.fps_d, 2 * tc->config.fps_n,
196         &sub_us);
197     add_us -= sub_us;
198   }
199 
200   ret2 = g_date_time_add_seconds (ret, add_us + tc->seconds);
201   g_date_time_unref (ret);
202   ret = g_date_time_add_minutes (ret2, tc->minutes);
203   g_date_time_unref (ret2);
204   ret2 = g_date_time_add_hours (ret, tc->hours);
205   g_date_time_unref (ret);
206 
207   return ret2;
208 }
209 
210 /**
211  * gst_video_time_code_init_from_date_time:
212  * @tc: an uninitialized #GstVideoTimeCode
213  * @fps_n: Numerator of the frame rate
214  * @fps_d: Denominator of the frame rate
215  * @dt: #GDateTime to convert
216  * @flags: #GstVideoTimeCodeFlags
217  * @field_count: Interlaced video field count
218  *
219  * The resulting config->latest_daily_jam is set to midnight, and timecode is
220  * set to the given time.
221  *
222  * Will assert on invalid parameters, use gst_video_time_code_init_from_date_time_full()
223  * for being able to handle invalid parameters.
224  *
225  * Since: 1.12
226  */
227 void
gst_video_time_code_init_from_date_time(GstVideoTimeCode * tc,guint fps_n,guint fps_d,GDateTime * dt,GstVideoTimeCodeFlags flags,guint field_count)228 gst_video_time_code_init_from_date_time (GstVideoTimeCode * tc,
229     guint fps_n, guint fps_d,
230     GDateTime * dt, GstVideoTimeCodeFlags flags, guint field_count)
231 {
232   if (!gst_video_time_code_init_from_date_time_full (tc, fps_n, fps_d, dt,
233           flags, field_count))
234     g_return_if_fail (gst_video_time_code_is_valid (tc));
235 }
236 
237 /**
238  * gst_video_time_code_init_from_date_time_full:
239  * @tc: a #GstVideoTimeCode
240  * @fps_n: Numerator of the frame rate
241  * @fps_d: Denominator of the frame rate
242  * @dt: #GDateTime to convert
243  * @flags: #GstVideoTimeCodeFlags
244  * @field_count: Interlaced video field count
245  *
246  * The resulting config->latest_daily_jam is set to
247  * midnight, and timecode is set to the given time.
248  *
249  * Returns: %TRUE if @tc could be correctly initialized to a valid timecode
250  *
251  * Since: 1.16
252  */
253 gboolean
gst_video_time_code_init_from_date_time_full(GstVideoTimeCode * tc,guint fps_n,guint fps_d,GDateTime * dt,GstVideoTimeCodeFlags flags,guint field_count)254 gst_video_time_code_init_from_date_time_full (GstVideoTimeCode * tc,
255     guint fps_n, guint fps_d,
256     GDateTime * dt, GstVideoTimeCodeFlags flags, guint field_count)
257 {
258   GDateTime *jam;
259   guint64 frames;
260   gboolean add_a_frame = FALSE;
261 
262   g_return_val_if_fail (tc != NULL, FALSE);
263   g_return_val_if_fail (dt != NULL, FALSE);
264   g_return_val_if_fail (fps_n != 0 && fps_d != 0, FALSE);
265 
266   gst_video_time_code_clear (tc);
267 
268   jam = g_date_time_new_local (g_date_time_get_year (dt),
269       g_date_time_get_month (dt), g_date_time_get_day_of_month (dt), 0, 0, 0.0);
270 
271   /* Note: This might be inaccurate for 1 frame
272    * in case we have a drop frame timecode */
273   frames =
274       gst_util_uint64_scale_round (g_date_time_get_microsecond (dt) *
275       G_GINT64_CONSTANT (1000), fps_n, fps_d * GST_SECOND);
276   if (G_UNLIKELY (((frames == fps_n) && (fps_d == 1)) ||
277           ((frames == fps_n / 1000) && (fps_d == 1001)))) {
278     /* Avoid invalid timecodes */
279     frames--;
280     add_a_frame = TRUE;
281   }
282 
283   gst_video_time_code_init (tc, fps_n, fps_d, jam, flags,
284       g_date_time_get_hour (dt), g_date_time_get_minute (dt),
285       g_date_time_get_second (dt), frames, field_count);
286 
287   if (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) {
288     guint df = (tc->config.fps_n + (tc->config.fps_d >> 1)) /
289         (15 * tc->config.fps_d);
290     if (tc->minutes % 10 && tc->seconds == 0 && tc->frames < df) {
291       tc->frames = df;
292     }
293   }
294   if (add_a_frame)
295     gst_video_time_code_increment_frame (tc);
296 
297   g_date_time_unref (jam);
298 
299   return gst_video_time_code_is_valid (tc);
300 }
301 
302 /**
303  * gst_video_time_code_nsec_since_daily_jam:
304  * @tc: a valid #GstVideoTimeCode
305  *
306  * Returns: how many nsec have passed since the daily jam of @tc.
307  *
308  * Since: 1.10
309  */
310 guint64
gst_video_time_code_nsec_since_daily_jam(const GstVideoTimeCode * tc)311 gst_video_time_code_nsec_since_daily_jam (const GstVideoTimeCode * tc)
312 {
313   guint64 frames, nsec;
314 
315   g_return_val_if_fail (gst_video_time_code_is_valid (tc), -1);
316 
317   frames = gst_video_time_code_frames_since_daily_jam (tc);
318   nsec =
319       gst_util_uint64_scale (frames, GST_SECOND * tc->config.fps_d,
320       tc->config.fps_n);
321 
322   return nsec;
323 }
324 
325 /**
326  * gst_video_time_code_frames_since_daily_jam:
327  * @tc: a valid #GstVideoTimeCode
328  *
329  * Returns: how many frames have passed since the daily jam of @tc.
330  *
331  * Since: 1.10
332  */
333 guint64
gst_video_time_code_frames_since_daily_jam(const GstVideoTimeCode * tc)334 gst_video_time_code_frames_since_daily_jam (const GstVideoTimeCode * tc)
335 {
336   guint ff_nom;
337   gdouble ff;
338 
339   g_return_val_if_fail (gst_video_time_code_is_valid (tc), -1);
340 
341   gst_util_fraction_to_double (tc->config.fps_n, tc->config.fps_d, &ff);
342   if (tc->config.fps_d == 1001) {
343     ff_nom = tc->config.fps_n / 1000;
344   } else {
345     ff_nom = ff;
346   }
347   if (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) {
348     /* these need to be truncated to integer: side effect, code looks cleaner
349      * */
350     guint ff_minutes = 60 * ff;
351     guint ff_hours = 3600 * ff;
352     /* for 30000/1001 we drop the first 2 frames per minute, for 60000/1001 we
353      * drop the first 4 : so we use this number */
354     guint dropframe_multiplier;
355 
356     if (tc->config.fps_n == 30000) {
357       dropframe_multiplier = 2;
358     } else if (tc->config.fps_n == 60000) {
359       dropframe_multiplier = 4;
360     } else {
361       /* already checked by gst_video_time_code_is_valid() */
362       g_assert_not_reached ();
363     }
364 
365     return tc->frames + (ff_nom * tc->seconds) +
366         (ff_minutes * tc->minutes) +
367         dropframe_multiplier * ((gint) (tc->minutes / 10)) +
368         (ff_hours * tc->hours);
369   } else {
370     return tc->frames + (ff_nom * (tc->seconds + (60 * (tc->minutes +
371                     (60 * tc->hours)))));
372   }
373 
374 }
375 
376 /**
377  * gst_video_time_code_increment_frame:
378  * @tc: a valid #GstVideoTimeCode
379  *
380  * Adds one frame to @tc.
381  *
382  * Since: 1.10
383  */
384 void
gst_video_time_code_increment_frame(GstVideoTimeCode * tc)385 gst_video_time_code_increment_frame (GstVideoTimeCode * tc)
386 {
387   gst_video_time_code_add_frames (tc, 1);
388 }
389 
390 /**
391  * gst_video_time_code_add_frames:
392  * @tc: a valid #GstVideoTimeCode
393  * @frames: How many frames to add or subtract
394  *
395  * Adds or subtracts @frames amount of frames to @tc. tc needs to
396  * contain valid data, as verified by gst_video_time_code_is_valid().
397  *
398  * Since: 1.10
399  */
400 void
gst_video_time_code_add_frames(GstVideoTimeCode * tc,gint64 frames)401 gst_video_time_code_add_frames (GstVideoTimeCode * tc, gint64 frames)
402 {
403   guint64 framecount;
404   guint64 h_notmod24;
405   guint64 h_new, min_new, sec_new, frames_new;
406   gdouble ff;
407   guint ff_nom;
408   /* This allows for better readability than putting G_GUINT64_CONSTANT(60)
409    * into a long calculation line */
410   const guint64 sixty = 60;
411   /* formulas found in SMPTE ST 2059-1:2015 section 9.4.3
412    * and adapted for 60/1.001 as well as 30/1.001 */
413 
414   g_return_if_fail (gst_video_time_code_is_valid (tc));
415 
416   gst_util_fraction_to_double (tc->config.fps_n, tc->config.fps_d, &ff);
417   if (tc->config.fps_d == 1001) {
418     ff_nom = tc->config.fps_n / 1000;
419   } else {
420     ff_nom = ff;
421   }
422 
423   if (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME) {
424     /* these need to be truncated to integer: side effect, code looks cleaner
425      * */
426     guint ff_minutes = 60 * ff;
427     guint ff_hours = 3600 * ff;
428     /* a bunch of intermediate variables, to avoid monster code with possible
429      * integer overflows */
430     guint64 min_new_tmp1, min_new_tmp2, min_new_tmp3, min_new_denom;
431     /* for 30000/1001 we drop the first 2 frames per minute, for 60000/1001 we
432      * drop the first 4 : so we use this number */
433     guint dropframe_multiplier;
434 
435     if (tc->config.fps_n == 30000) {
436       dropframe_multiplier = 2;
437     } else if (tc->config.fps_n == 60000) {
438       dropframe_multiplier = 4;
439     } else {
440       /* already checked by gst_video_time_code_is_valid() */
441       g_assert_not_reached ();
442     }
443 
444     framecount =
445         frames + tc->frames + (ff_nom * tc->seconds) +
446         (ff_minutes * tc->minutes) +
447         dropframe_multiplier * ((gint) (tc->minutes / 10)) +
448         (ff_hours * tc->hours);
449     h_notmod24 = gst_util_uint64_scale_int (framecount, 1, ff_hours);
450 
451     min_new_denom = sixty * ff_nom;
452     min_new_tmp1 = (framecount - (h_notmod24 * ff_hours)) / min_new_denom;
453     min_new_tmp2 = framecount + dropframe_multiplier * min_new_tmp1;
454     min_new_tmp1 =
455         (framecount - (h_notmod24 * ff_hours)) / (sixty * 10 * ff_nom);
456     min_new_tmp3 =
457         dropframe_multiplier * min_new_tmp1 + (h_notmod24 * ff_hours);
458     min_new =
459         gst_util_uint64_scale_int (min_new_tmp2 - min_new_tmp3, 1,
460         min_new_denom);
461 
462     sec_new =
463         (guint64) ((framecount - (ff_minutes * min_new) -
464             dropframe_multiplier * ((gint) (min_new / 10)) -
465             (ff_hours * h_notmod24)) / ff_nom);
466 
467     frames_new =
468         framecount - (ff_nom * sec_new) - (ff_minutes * min_new) -
469         (dropframe_multiplier * ((gint) (min_new / 10))) -
470         (ff_hours * h_notmod24);
471   } else {
472     framecount =
473         frames + tc->frames + (ff_nom * (tc->seconds + (sixty * (tc->minutes +
474                     (sixty * tc->hours)))));
475     h_notmod24 =
476         gst_util_uint64_scale_int (framecount, 1, ff_nom * sixty * sixty);
477     min_new =
478         gst_util_uint64_scale_int ((framecount -
479             (ff_nom * sixty * sixty * h_notmod24)), 1, (ff_nom * sixty));
480     sec_new =
481         gst_util_uint64_scale_int ((framecount - (ff_nom * sixty * (min_new +
482                     (sixty * h_notmod24)))), 1, ff_nom);
483     frames_new =
484         framecount - (ff_nom * (sec_new + sixty * (min_new +
485                 (sixty * h_notmod24))));
486     if (frames_new > ff_nom)
487       frames_new = 0;
488   }
489 
490   h_new = h_notmod24 % 24;
491 
492   /* The calculations above should always give correct results */
493   g_assert (min_new < 60);
494   g_assert (sec_new < 60);
495   g_assert (frames_new < ff_nom);
496 
497   tc->hours = h_new;
498   tc->minutes = min_new;
499   tc->seconds = sec_new;
500   tc->frames = frames_new;
501 }
502 
503 /**
504  * gst_video_time_code_compare:
505  * @tc1: a valid #GstVideoTimeCode
506  * @tc2: another valid #GstVideoTimeCode
507  *
508  * Compares @tc1 and @tc2. If both have latest daily jam information, it is
509  * taken into account. Otherwise, it is assumed that the daily jam of both
510  * @tc1 and @tc2 was at the same time. Both time codes must be valid.
511  *
512  * Returns: 1 if @tc1 is after @tc2, -1 if @tc1 is before @tc2, 0 otherwise.
513  *
514  * Since: 1.10
515  */
516 gint
gst_video_time_code_compare(const GstVideoTimeCode * tc1,const GstVideoTimeCode * tc2)517 gst_video_time_code_compare (const GstVideoTimeCode * tc1,
518     const GstVideoTimeCode * tc2)
519 {
520   g_return_val_if_fail (gst_video_time_code_is_valid (tc1), -1);
521   g_return_val_if_fail (gst_video_time_code_is_valid (tc2), -1);
522 
523   if (tc1->config.latest_daily_jam == NULL
524       || tc2->config.latest_daily_jam == NULL) {
525     guint64 nsec1, nsec2;
526 #ifndef GST_DISABLE_GST_DEBUG
527     gchar *str1, *str2;
528 
529     str1 = gst_video_time_code_to_string (tc1);
530     str2 = gst_video_time_code_to_string (tc2);
531     GST_INFO
532         ("Comparing time codes %s and %s, but at least one of them has no "
533         "latest daily jam information. Assuming they started together",
534         str1, str2);
535     g_free (str1);
536     g_free (str2);
537 #endif
538     if (tc1->hours > tc2->hours) {
539       return 1;
540     } else if (tc1->hours < tc2->hours) {
541       return -1;
542     }
543     if (tc1->minutes > tc2->minutes) {
544       return 1;
545     } else if (tc1->minutes < tc2->minutes) {
546       return -1;
547     }
548     if (tc1->seconds > tc2->seconds) {
549       return 1;
550     } else if (tc1->seconds < tc2->seconds) {
551       return -1;
552     }
553 
554     nsec1 =
555         gst_util_uint64_scale (GST_SECOND,
556         tc1->frames * tc1->config.fps_n, tc1->config.fps_d);
557     nsec2 =
558         gst_util_uint64_scale (GST_SECOND,
559         tc2->frames * tc2->config.fps_n, tc2->config.fps_d);
560     if (nsec1 > nsec2) {
561       return 1;
562     } else if (nsec1 < nsec2) {
563       return -1;
564     }
565     if (tc1->config.flags & GST_VIDEO_TIME_CODE_FLAGS_INTERLACED) {
566       if (tc1->field_count > tc2->field_count)
567         return 1;
568       else if (tc1->field_count < tc2->field_count)
569         return -1;
570     }
571     return 0;
572   } else {
573     GDateTime *dt1, *dt2;
574     gint ret;
575 
576     dt1 = gst_video_time_code_to_date_time (tc1);
577     dt2 = gst_video_time_code_to_date_time (tc2);
578 
579     ret = g_date_time_compare (dt1, dt2);
580 
581     g_date_time_unref (dt1);
582     g_date_time_unref (dt2);
583 
584     return ret;
585   }
586 }
587 
588 /**
589  * gst_video_time_code_new:
590  * @fps_n: Numerator of the frame rate
591  * @fps_d: Denominator of the frame rate
592  * @latest_daily_jam: The latest daily jam of the #GstVideoTimeCode
593  * @flags: #GstVideoTimeCodeFlags
594  * @hours: the hours field of #GstVideoTimeCode
595  * @minutes: the minutes field of #GstVideoTimeCode
596  * @seconds: the seconds field of #GstVideoTimeCode
597  * @frames: the frames field of #GstVideoTimeCode
598  * @field_count: Interlaced video field count
599  *
600  * @field_count is 0 for progressive, 1 or 2 for interlaced.
601  * @latest_daiy_jam reference is stolen from caller.
602  *
603  * Returns: a new #GstVideoTimeCode with the given values.
604  * The values are not checked for being in a valid range. To see if your
605  * timecode actually has valid content, use gst_video_time_code_is_valid().
606  *
607  * Since: 1.10
608  */
609 GstVideoTimeCode *
gst_video_time_code_new(guint fps_n,guint fps_d,GDateTime * latest_daily_jam,GstVideoTimeCodeFlags flags,guint hours,guint minutes,guint seconds,guint frames,guint field_count)610 gst_video_time_code_new (guint fps_n, guint fps_d, GDateTime * latest_daily_jam,
611     GstVideoTimeCodeFlags flags, guint hours, guint minutes, guint seconds,
612     guint frames, guint field_count)
613 {
614   GstVideoTimeCode *tc;
615 
616   tc = g_new0 (GstVideoTimeCode, 1);
617   gst_video_time_code_init (tc, fps_n, fps_d, latest_daily_jam, flags, hours,
618       minutes, seconds, frames, field_count);
619   return tc;
620 }
621 
622 /**
623  * gst_video_time_code_new_empty:
624  *
625  * Returns: a new empty, invalid #GstVideoTimeCode
626  *
627  * Since: 1.10
628  */
629 GstVideoTimeCode *
gst_video_time_code_new_empty(void)630 gst_video_time_code_new_empty (void)
631 {
632   GstVideoTimeCode *tc;
633 
634   tc = g_new0 (GstVideoTimeCode, 1);
635   gst_video_time_code_clear (tc);
636   return tc;
637 }
638 
639 static void
gst_video_time_code_gvalue_from_string(const GValue * str_val,GValue * tc_val)640 gst_video_time_code_gvalue_from_string (const GValue * str_val, GValue * tc_val)
641 {
642   const gchar *tc_str = g_value_get_string (str_val);
643   GstVideoTimeCode *tc;
644 
645   tc = gst_video_time_code_new_from_string (tc_str);
646   g_value_take_boxed (tc_val, tc);
647 }
648 
649 static void
gst_video_time_code_gvalue_to_string(const GValue * tc_val,GValue * str_val)650 gst_video_time_code_gvalue_to_string (const GValue * tc_val, GValue * str_val)
651 {
652   const GstVideoTimeCode *tc = g_value_get_boxed (tc_val);
653   gchar *tc_str;
654 
655   tc_str = gst_video_time_code_to_string (tc);
656   g_value_take_string (str_val, tc_str);
657 }
658 
659 static gchar *
gst_video_time_code_serialize(const GValue * val)660 gst_video_time_code_serialize (const GValue * val)
661 {
662   GstVideoTimeCode *tc = g_value_get_boxed (val);
663   return gst_video_time_code_to_string (tc);
664 }
665 
666 static gboolean
gst_video_time_code_deserialize(GValue * dest,const gchar * tc_str)667 gst_video_time_code_deserialize (GValue * dest, const gchar * tc_str)
668 {
669   GstVideoTimeCode *tc = gst_video_time_code_new_from_string (tc_str);
670 
671   if (tc == NULL) {
672     return FALSE;
673   }
674 
675   g_value_take_boxed (dest, tc);
676   return TRUE;
677 }
678 
679 /**
680  * gst_video_time_code_new_from_string:
681  * @tc_str: The string that represents the #GstVideoTimeCode
682  *
683  * Returns: (nullable): a new #GstVideoTimeCode from the given string or %NULL
684  *   if the string could not be passed.
685  *
686  * Since: 1.12
687  */
688 GstVideoTimeCode *
gst_video_time_code_new_from_string(const gchar * tc_str)689 gst_video_time_code_new_from_string (const gchar * tc_str)
690 {
691   GstVideoTimeCode *tc;
692   guint hours, minutes, seconds, frames;
693 
694   if (sscanf (tc_str, "%02u:%02u:%02u:%02u", &hours, &minutes, &seconds,
695           &frames)
696       == 4
697       || sscanf (tc_str, "%02u:%02u:%02u.%02u", &hours, &minutes, &seconds,
698           &frames)
699       == 4) {
700     tc = gst_video_time_code_new (0, 1, NULL, GST_VIDEO_TIME_CODE_FLAGS_NONE,
701         hours, minutes, seconds, frames, 0);
702 
703     return tc;
704   } else if (sscanf (tc_str, "%02u:%02u:%02u;%02u", &hours, &minutes, &seconds,
705           &frames)
706       == 4 || sscanf (tc_str, "%02u:%02u:%02u,%02u", &hours, &minutes, &seconds,
707           &frames)
708       == 4) {
709     tc = gst_video_time_code_new (0, 1, NULL,
710         GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME, hours, minutes, seconds, frames,
711         0);
712 
713     return tc;
714   } else {
715     GST_ERROR ("Warning: Could not parse timecode %s. "
716         "Please input a timecode in the form 00:00:00:00", tc_str);
717     return NULL;
718   }
719 }
720 
721 /**
722  * gst_video_time_code_new_from_date_time:
723  * @fps_n: Numerator of the frame rate
724  * @fps_d: Denominator of the frame rate
725  * @dt: #GDateTime to convert
726  * @flags: #GstVideoTimeCodeFlags
727  * @field_count: Interlaced video field count
728  *
729  * The resulting config->latest_daily_jam is set to
730  * midnight, and timecode is set to the given time.
731  *
732  * This might return a completely invalid timecode, use
733  * gst_video_time_code_new_from_date_time_full() to ensure
734  * that you would get %NULL instead in that case.
735  *
736  * Returns: the #GstVideoTimeCode representation of @dt.
737  *
738  * Since: 1.12
739  */
740 GstVideoTimeCode *
gst_video_time_code_new_from_date_time(guint fps_n,guint fps_d,GDateTime * dt,GstVideoTimeCodeFlags flags,guint field_count)741 gst_video_time_code_new_from_date_time (guint fps_n, guint fps_d,
742     GDateTime * dt, GstVideoTimeCodeFlags flags, guint field_count)
743 {
744   GstVideoTimeCode *tc;
745   tc = gst_video_time_code_new_empty ();
746   gst_video_time_code_init_from_date_time_full (tc, fps_n, fps_d, dt, flags,
747       field_count);
748   return tc;
749 }
750 
751 /**
752  * gst_video_time_code_new_from_date_time_full:
753  * @fps_n: Numerator of the frame rate
754  * @fps_d: Denominator of the frame rate
755  * @dt: #GDateTime to convert
756  * @flags: #GstVideoTimeCodeFlags
757  * @field_count: Interlaced video field count
758  *
759  * The resulting config->latest_daily_jam is set to
760  * midnight, and timecode is set to the given time.
761  *
762  * Returns: the #GstVideoTimeCode representation of @dt, or %NULL if
763  *   no valid timecode could be created.
764  *
765  * Since: 1.16
766  */
767 GstVideoTimeCode *
gst_video_time_code_new_from_date_time_full(guint fps_n,guint fps_d,GDateTime * dt,GstVideoTimeCodeFlags flags,guint field_count)768 gst_video_time_code_new_from_date_time_full (guint fps_n, guint fps_d,
769     GDateTime * dt, GstVideoTimeCodeFlags flags, guint field_count)
770 {
771   GstVideoTimeCode *tc;
772   tc = gst_video_time_code_new_empty ();
773   if (!gst_video_time_code_init_from_date_time_full (tc, fps_n, fps_d, dt,
774           flags, field_count)) {
775     gst_video_time_code_free (tc);
776     return NULL;
777   }
778   return tc;
779 }
780 
781 /**
782  * gst_video_time_code_init:
783  * @tc: a #GstVideoTimeCode
784  * @fps_n: Numerator of the frame rate
785  * @fps_d: Denominator of the frame rate
786  * @latest_daily_jam: (allow-none): The latest daily jam of the #GstVideoTimeCode
787  * @flags: #GstVideoTimeCodeFlags
788  * @hours: the hours field of #GstVideoTimeCode
789  * @minutes: the minutes field of #GstVideoTimeCode
790  * @seconds: the seconds field of #GstVideoTimeCode
791  * @frames: the frames field of #GstVideoTimeCode
792  * @field_count: Interlaced video field count
793  *
794  * @field_count is 0 for progressive, 1 or 2 for interlaced.
795  * @latest_daiy_jam reference is stolen from caller.
796  *
797  * Initializes @tc with the given values.
798  * The values are not checked for being in a valid range. To see if your
799  * timecode actually has valid content, use gst_video_time_code_is_valid().
800  *
801  * Since: 1.10
802  */
803 void
gst_video_time_code_init(GstVideoTimeCode * tc,guint fps_n,guint fps_d,GDateTime * latest_daily_jam,GstVideoTimeCodeFlags flags,guint hours,guint minutes,guint seconds,guint frames,guint field_count)804 gst_video_time_code_init (GstVideoTimeCode * tc, guint fps_n, guint fps_d,
805     GDateTime * latest_daily_jam, GstVideoTimeCodeFlags flags, guint hours,
806     guint minutes, guint seconds, guint frames, guint field_count)
807 {
808   tc->hours = hours;
809   tc->minutes = minutes;
810   tc->seconds = seconds;
811   tc->frames = frames;
812   tc->field_count = field_count;
813   tc->config.fps_n = fps_n;
814   tc->config.fps_d = fps_d;
815   if (latest_daily_jam != NULL)
816     tc->config.latest_daily_jam = g_date_time_ref (latest_daily_jam);
817   else
818     tc->config.latest_daily_jam = NULL;
819   tc->config.flags = flags;
820 }
821 
822 /**
823  * gst_video_time_code_clear:
824  * @tc: a #GstVideoTimeCode
825  *
826  * Initializes @tc with empty/zero/NULL values and frees any memory
827  * it might currently use.
828  *
829  * Since: 1.10
830  */
831 void
gst_video_time_code_clear(GstVideoTimeCode * tc)832 gst_video_time_code_clear (GstVideoTimeCode * tc)
833 {
834   tc->hours = 0;
835   tc->minutes = 0;
836   tc->seconds = 0;
837   tc->frames = 0;
838   tc->field_count = 0;
839   tc->config.fps_n = 0;
840   tc->config.fps_d = 1;
841   if (tc->config.latest_daily_jam != NULL)
842     g_date_time_unref (tc->config.latest_daily_jam);
843   tc->config.latest_daily_jam = NULL;
844   tc->config.flags = 0;
845 }
846 
847 /**
848  * gst_video_time_code_copy:
849  * @tc: a #GstVideoTimeCode
850  *
851  * Returns: a new #GstVideoTimeCode with the same values as @tc.
852  *
853  * Since: 1.10
854  */
855 GstVideoTimeCode *
gst_video_time_code_copy(const GstVideoTimeCode * tc)856 gst_video_time_code_copy (const GstVideoTimeCode * tc)
857 {
858   return gst_video_time_code_new (tc->config.fps_n, tc->config.fps_d,
859       tc->config.latest_daily_jam, tc->config.flags, tc->hours, tc->minutes,
860       tc->seconds, tc->frames, tc->field_count);
861 }
862 
863 /**
864  * gst_video_time_code_free:
865  * @tc: a #GstVideoTimeCode
866  *
867  * Frees @tc.
868  *
869  * Since: 1.10
870  */
871 void
gst_video_time_code_free(GstVideoTimeCode * tc)872 gst_video_time_code_free (GstVideoTimeCode * tc)
873 {
874   if (tc->config.latest_daily_jam != NULL)
875     g_date_time_unref (tc->config.latest_daily_jam);
876 
877   g_free (tc);
878 }
879 
880 /**
881  * gst_video_time_code_add_interval:
882  * @tc: The #GstVideoTimeCode where the diff should be added. This
883  * must contain valid timecode values.
884  * @tc_inter: The #GstVideoTimeCodeInterval to add to @tc.
885  * The interval must contain valid values, except that for drop-frame
886  * timecode, it may also contain timecodes which would normally
887  * be dropped. These are then corrected to the next reasonable timecode.
888  *
889  * This makes a component-wise addition of @tc_inter to @tc. For example,
890  * adding ("01:02:03:04", "00:01:00:00") will return "01:03:03:04".
891  * When it comes to drop-frame timecodes,
892  * adding ("00:00:00;00", "00:01:00:00") will return "00:01:00;02"
893  * because of drop-frame oddities. However,
894  * adding ("00:09:00;02", "00:01:00:00") will return "00:10:00;00"
895  * because this time we can have an exact minute.
896  *
897  * Returns: (nullable): A new #GstVideoTimeCode with @tc_inter added or %NULL
898  *   if the interval can't be added.
899  *
900  * Since: 1.12
901  */
902 GstVideoTimeCode *
gst_video_time_code_add_interval(const GstVideoTimeCode * tc,const GstVideoTimeCodeInterval * tc_inter)903 gst_video_time_code_add_interval (const GstVideoTimeCode * tc,
904     const GstVideoTimeCodeInterval * tc_inter)
905 {
906   GstVideoTimeCode *ret;
907   guint frames_to_add;
908   guint df;
909   gboolean needs_correction;
910 
911   g_return_val_if_fail (gst_video_time_code_is_valid (tc), NULL);
912 
913   ret = gst_video_time_code_new (tc->config.fps_n, tc->config.fps_d,
914       tc->config.latest_daily_jam, tc->config.flags, tc_inter->hours,
915       tc_inter->minutes, tc_inter->seconds, tc_inter->frames, 0);
916 
917   df = (tc->config.fps_n + (tc->config.fps_d >> 1)) / (tc->config.fps_d * 15);
918 
919   /* Drop-frame compensation: Create a valid timecode from the
920    * interval */
921   needs_correction = (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME)
922       && ret->minutes % 10 && ret->seconds == 0 && ret->frames < df;
923   if (needs_correction) {
924     ret->minutes--;
925     ret->seconds = 59;
926     ret->frames = df * 14;
927   }
928 
929   if (!gst_video_time_code_is_valid (ret)) {
930     GST_ERROR ("Unsupported time code interval");
931     gst_video_time_code_free (ret);
932     return NULL;
933   }
934 
935   frames_to_add = gst_video_time_code_frames_since_daily_jam (tc);
936 
937   /* Drop-frame compensation: 00:01:00;00 is falsely interpreted as
938    * 00:00:59;28 */
939   if (needs_correction) {
940     /* User wants us to split at invalid timecodes */
941     if (tc->minutes % 10 == 0 && tc->frames <= df) {
942       /* Apply compensation every 10th minute: before adding the frames,
943        * but only if we are before the "invalid frame" mark */
944       frames_to_add += df;
945       needs_correction = FALSE;
946     }
947   }
948   gst_video_time_code_add_frames (ret, frames_to_add);
949   if (needs_correction && ret->minutes % 10 == 0 && tc->frames > df) {
950     gst_video_time_code_add_frames (ret, df);
951   }
952 
953   return ret;
954 }
955 
956 G_DEFINE_BOXED_TYPE (GstVideoTimeCodeInterval, gst_video_time_code_interval,
957     (GBoxedCopyFunc) gst_video_time_code_interval_copy,
958     (GBoxedFreeFunc) gst_video_time_code_interval_free);
959 
960 /**
961  * gst_video_time_code_interval_new:
962  * @hours: the hours field of #GstVideoTimeCodeInterval
963  * @minutes: the minutes field of #GstVideoTimeCodeInterval
964  * @seconds: the seconds field of #GstVideoTimeCodeInterval
965  * @frames: the frames field of #GstVideoTimeCodeInterval
966  *
967  * Returns: a new #GstVideoTimeCodeInterval with the given values.
968  *
969  * Since: 1.12
970  */
971 GstVideoTimeCodeInterval *
gst_video_time_code_interval_new(guint hours,guint minutes,guint seconds,guint frames)972 gst_video_time_code_interval_new (guint hours, guint minutes, guint seconds,
973     guint frames)
974 {
975   GstVideoTimeCodeInterval *tc;
976 
977   tc = g_new0 (GstVideoTimeCodeInterval, 1);
978   gst_video_time_code_interval_init (tc, hours, minutes, seconds, frames);
979   return tc;
980 }
981 
982 /**
983  * gst_video_time_code_interval_new_from_string:
984  * @tc_inter_str: The string that represents the #GstVideoTimeCodeInterval
985  *
986  * @tc_inter_str must only have ":" as separators.
987  *
988  * Returns: (nullable): a new #GstVideoTimeCodeInterval from the given string
989  *   or %NULL if the string could not be passed.
990  *
991  * Since: 1.12
992  */
993 GstVideoTimeCodeInterval *
gst_video_time_code_interval_new_from_string(const gchar * tc_inter_str)994 gst_video_time_code_interval_new_from_string (const gchar * tc_inter_str)
995 {
996   GstVideoTimeCodeInterval *tc;
997   guint hours, minutes, seconds, frames;
998 
999   if (sscanf (tc_inter_str, "%02u:%02u:%02u:%02u", &hours, &minutes, &seconds,
1000           &frames)
1001       == 4
1002       || sscanf (tc_inter_str, "%02u:%02u:%02u;%02u", &hours, &minutes,
1003           &seconds, &frames)
1004       == 4
1005       || sscanf (tc_inter_str, "%02u:%02u:%02u.%02u", &hours, &minutes,
1006           &seconds, &frames)
1007       == 4
1008       || sscanf (tc_inter_str, "%02u:%02u:%02u,%02u", &hours, &minutes,
1009           &seconds, &frames)
1010       == 4) {
1011     tc = gst_video_time_code_interval_new (hours, minutes, seconds, frames);
1012 
1013     return tc;
1014   } else {
1015     GST_ERROR ("Warning: Could not parse timecode %s. "
1016         "Please input a timecode in the form 00:00:00:00", tc_inter_str);
1017     return NULL;
1018   }
1019 
1020 }
1021 
1022 /**
1023  * gst_video_time_code_interval_init:
1024  * @tc: a #GstVideoTimeCodeInterval
1025  * @hours: the hours field of #GstVideoTimeCodeInterval
1026  * @minutes: the minutes field of #GstVideoTimeCodeInterval
1027  * @seconds: the seconds field of #GstVideoTimeCodeInterval
1028  * @frames: the frames field of #GstVideoTimeCodeInterval
1029  *
1030  * Initializes @tc with the given values.
1031  *
1032  * Since: 1.12
1033  */
1034 void
gst_video_time_code_interval_init(GstVideoTimeCodeInterval * tc,guint hours,guint minutes,guint seconds,guint frames)1035 gst_video_time_code_interval_init (GstVideoTimeCodeInterval * tc, guint hours,
1036     guint minutes, guint seconds, guint frames)
1037 {
1038   tc->hours = hours;
1039   tc->minutes = minutes;
1040   tc->seconds = seconds;
1041   tc->frames = frames;
1042 }
1043 
1044 /**
1045  * gst_video_time_code_interval_clear:
1046  * @tc: a #GstVideoTimeCodeInterval
1047  *
1048  * Initializes @tc with empty/zero/NULL values.
1049  *
1050  * Since: 1.12
1051  */
1052 void
gst_video_time_code_interval_clear(GstVideoTimeCodeInterval * tc)1053 gst_video_time_code_interval_clear (GstVideoTimeCodeInterval * tc)
1054 {
1055   tc->hours = 0;
1056   tc->minutes = 0;
1057   tc->seconds = 0;
1058   tc->frames = 0;
1059 }
1060 
1061 /**
1062  * gst_video_time_code_interval_copy:
1063  * @tc: a #GstVideoTimeCodeInterval
1064  *
1065  * Returns: a new #GstVideoTimeCodeInterval with the same values as @tc.
1066  *
1067  * Since: 1.12
1068  */
1069 GstVideoTimeCodeInterval *
gst_video_time_code_interval_copy(const GstVideoTimeCodeInterval * tc)1070 gst_video_time_code_interval_copy (const GstVideoTimeCodeInterval * tc)
1071 {
1072   return gst_video_time_code_interval_new (tc->hours, tc->minutes,
1073       tc->seconds, tc->frames);
1074 }
1075 
1076 /**
1077  * gst_video_time_code_interval_free:
1078  * @tc: a #GstVideoTimeCodeInterval
1079  *
1080  * Frees @tc.
1081  *
1082  * Since: 1.12
1083  */
1084 void
gst_video_time_code_interval_free(GstVideoTimeCodeInterval * tc)1085 gst_video_time_code_interval_free (GstVideoTimeCodeInterval * tc)
1086 {
1087   g_free (tc);
1088 }
1089