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