2 * Copyright (C) <2016> Vivia Nikolaidou <vivia@toolsonair.com>
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.
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.
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.
24 #include "gstvideotimecode.h"
27 gst_video_time_code_gvalue_to_string (const GValue * tc_val, GValue * str_val);
29 gst_video_time_code_gvalue_from_string (const GValue * str_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);
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
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);
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));
57 * gst_video_time_code_is_valid:
58 * @tc: #GstVideoTimeCode to check
60 * Returns: whether @tc is a valid timecode (supported frame rate,
61 * hours/minutes/seconds/frames not overflowing)
66 gst_video_time_code_is_valid (const GstVideoTimeCode * tc)
70 g_return_val_if_fail (tc != NULL, FALSE);
72 if (tc->config.fps_n == 0 || tc->config.fps_d == 0)
77 if (tc->minutes >= 60)
79 if (tc->seconds >= 60)
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) {
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)
94 if (tc->frames >= fr && (tc->config.fps_n != 0 || tc->config.fps_d != 1))
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)
104 } else if (tc->config.fps_n >= tc->config.fps_d
105 && tc->config.fps_n % tc->config.fps_d != 0) {
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)
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) {
129 * gst_video_time_code_to_string:
130 * @tc: A #GstVideoTimeCode to convert
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)
136 * ';' for drop-frame, non-interlaced content and for drop-frame interlaced
138 * ',' for drop-frame interlaced field 1
139 * ':' for non-drop-frame, non-interlaced content and for non-drop-frame
141 * '.' for non-drop-frame interlaced field 1
146 gst_video_time_code_to_string (const GstVideoTimeCode * tc)
149 gboolean top_dot_present;
152 /* Top dot is present for non-interlaced content, and for field 2 in
153 * interlaced content */
155 !((tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_INTERLACED) != 0
156 && tc->field_count == 1);
158 if (tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME)
159 sep = top_dot_present ? ';' : ',';
161 sep = top_dot_present ? ':' : '.';
164 g_strdup_printf ("%02d:%02d:%02d%c%02d", tc->hours, tc->minutes,
165 tc->seconds, sep, tc->frames);
171 * gst_video_time_code_to_date_time:
172 * @tc: A valid #GstVideoTimeCode to convert
174 * The @tc.config->latest_daily_jam is required to be non-NULL.
176 * Returns: (nullable): the #GDateTime representation of @tc or %NULL if @tc
182 gst_video_time_code_to_date_time (const GstVideoTimeCode * tc)
188 g_return_val_if_fail (gst_video_time_code_is_valid (tc), NULL);
190 if (tc->config.latest_daily_jam == NULL) {
191 gchar *tc_str = gst_video_time_code_to_string (tc);
193 ("Asked to convert time code %s to GDateTime, but its latest daily jam is NULL",
199 ret = g_date_time_ref (tc->config.latest_daily_jam);
201 gst_util_fraction_to_double (tc->frames * tc->config.fps_d, tc->config.fps_n,
203 if ((tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_INTERLACED)
204 && tc->field_count == 1) {
207 gst_util_fraction_to_double (tc->config.fps_d, 2 * tc->config.fps_n,
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);
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
231 * The resulting config->latest_daily_jam is set to midnight, and timecode is
232 * set to the given time.
234 * Will assert on invalid parameters, use gst_video_time_code_init_from_date_time_full()
235 * for being able to handle invalid parameters.
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)
244 if (!gst_video_time_code_init_from_date_time_full (tc, fps_n, fps_d, dt,
246 g_return_if_fail (gst_video_time_code_is_valid (tc));
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
258 * The resulting config->latest_daily_jam is set to
259 * midnight, and timecode is set to the given time.
261 * Returns: %TRUE if @tc could be correctly initialized to a valid timecode
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)
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);
276 gst_video_time_code_clear (tc);
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);
282 guint64 hour, min, 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;
294 gst_video_time_code_init (tc, fps_n, fps_d, jam, flags,
295 hour, min, sec, 0, field_count);
298 gboolean add_a_frame = FALSE;
300 /* Note: This might be inaccurate for 1 frame
301 * in case we have a drop frame timecode */
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 */
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);
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) {
324 gst_video_time_code_increment_frame (tc);
327 g_date_time_unref (jam);
329 return gst_video_time_code_is_valid (tc);
333 * gst_video_time_code_nsec_since_daily_jam:
334 * @tc: a valid #GstVideoTimeCode
336 * Returns: how many nsec have passed since the daily jam of @tc.
341 gst_video_time_code_nsec_since_daily_jam (const GstVideoTimeCode * tc)
343 guint64 frames, nsec;
345 g_return_val_if_fail (gst_video_time_code_is_valid (tc), -1);
347 frames = gst_video_time_code_frames_since_daily_jam (tc);
349 gst_util_uint64_scale (frames, GST_SECOND * tc->config.fps_d,
356 * gst_video_time_code_frames_since_daily_jam:
357 * @tc: a valid #GstVideoTimeCode
359 * Returns: how many frames have passed since the daily jam of @tc.
364 gst_video_time_code_frames_since_daily_jam (const GstVideoTimeCode * tc)
369 g_return_val_if_fail (gst_video_time_code_is_valid (tc), -1);
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;
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
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;
386 if (tc->config.fps_n == 30000) {
387 dropframe_multiplier = 2;
388 } else if (tc->config.fps_n == 60000) {
389 dropframe_multiplier = 4;
391 /* already checked by gst_video_time_code_is_valid() */
392 g_assert_not_reached ();
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);
403 return tc->frames + (ff_nom * (tc->seconds + (60 * (tc->minutes +
404 (60 * tc->hours)))));
410 * gst_video_time_code_increment_frame:
411 * @tc: a valid #GstVideoTimeCode
413 * Adds one frame to @tc.
418 gst_video_time_code_increment_frame (GstVideoTimeCode * tc)
420 gst_video_time_code_add_frames (tc, 1);
424 * gst_video_time_code_add_frames:
425 * @tc: a valid #GstVideoTimeCode
426 * @frames: How many frames to add or subtract
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().
434 gst_video_time_code_add_frames (GstVideoTimeCode * tc, gint64 frames)
438 guint64 h_new, min_new, sec_new, frames_new;
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 */
447 g_return_if_fail (gst_video_time_code_is_valid (tc));
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;
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
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;
468 if (tc->config.fps_n == 30000) {
469 dropframe_multiplier = 2;
470 } else if (tc->config.fps_n == 60000) {
471 dropframe_multiplier = 4;
473 /* already checked by gst_video_time_code_is_valid() */
474 g_assert_not_reached ();
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);
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;
488 (framecount - (h_notmod24 * ff_hours)) / (sixty * 10 * ff_nom);
490 dropframe_multiplier * min_new_tmp1 + (h_notmod24 * ff_hours);
492 gst_util_uint64_scale_int (min_new_tmp2 - min_new_tmp3, 1,
496 (guint64) ((framecount - (ff_minutes * min_new) -
497 dropframe_multiplier * ((gint) (min_new / 10)) -
498 (ff_hours * h_notmod24)) / ff_nom);
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) {
506 frames + gst_util_uint64_scale (tc->seconds + (60 * (tc->minutes +
507 (60 * tc->hours))), tc->config.fps_n, tc->config.fps_d);
509 gst_util_uint64_scale (frames_new, tc->config.fps_d, tc->config.fps_n);
511 min_new = sec_new / 60;
512 sec_new = sec_new % 60;
513 h_notmod24 = min_new / 60;
514 min_new = min_new % 60;
517 frames + tc->frames + (ff_nom * (tc->seconds + (sixty * (tc->minutes +
518 (sixty * tc->hours)))));
520 gst_util_uint64_scale_int (framecount, 1, ff_nom * sixty * sixty);
522 gst_util_uint64_scale_int ((framecount -
523 (ff_nom * sixty * sixty * h_notmod24)), 1, (ff_nom * sixty));
525 gst_util_uint64_scale_int ((framecount - (ff_nom * sixty * (min_new +
526 (sixty * h_notmod24)))), 1, ff_nom);
528 framecount - (ff_nom * (sec_new + sixty * (min_new +
529 (sixty * h_notmod24))));
530 if (frames_new > ff_nom)
534 h_new = h_notmod24 % 24;
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));
542 tc->minutes = min_new;
543 tc->seconds = sec_new;
544 tc->frames = frames_new;
548 * gst_video_time_code_compare:
549 * @tc1: a valid #GstVideoTimeCode
550 * @tc2: another valid #GstVideoTimeCode
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.
556 * Returns: 1 if @tc1 is after @tc2, -1 if @tc1 is before @tc2, 0 otherwise.
561 gst_video_time_code_compare (const GstVideoTimeCode * tc1,
562 const GstVideoTimeCode * tc2)
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);
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
573 str1 = gst_video_time_code_to_string (tc1);
574 str2 = gst_video_time_code_to_string (tc2);
576 ("Comparing time codes %s and %s, but at least one of them has no "
577 "latest daily jam information. Assuming they started together",
582 if (tc1->hours > tc2->hours) {
584 } else if (tc1->hours < tc2->hours) {
587 if (tc1->minutes > tc2->minutes) {
589 } else if (tc1->minutes < tc2->minutes) {
592 if (tc1->seconds > tc2->seconds) {
594 } else if (tc1->seconds < tc2->seconds) {
599 gst_util_uint64_scale (GST_SECOND,
600 tc1->frames * tc1->config.fps_n, tc1->config.fps_d);
602 gst_util_uint64_scale (GST_SECOND,
603 tc2->frames * tc2->config.fps_n, tc2->config.fps_d);
606 } else if (nsec1 < nsec2) {
609 if (tc1->config.flags & GST_VIDEO_TIME_CODE_FLAGS_INTERLACED) {
610 if (tc1->field_count > tc2->field_count)
612 else if (tc1->field_count < tc2->field_count)
617 GDateTime *dt1, *dt2;
620 dt1 = gst_video_time_code_to_date_time (tc1);
621 dt2 = gst_video_time_code_to_date_time (tc2);
623 ret = g_date_time_compare (dt1, dt2);
625 g_date_time_unref (dt1);
626 g_date_time_unref (dt2);
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
644 * @field_count is 0 for progressive, 1 or 2 for interlaced.
645 * @latest_daiy_jam reference is stolen from caller.
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().
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)
658 GstVideoTimeCode *tc;
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);
667 * gst_video_time_code_new_empty:
669 * Returns: a new empty, invalid #GstVideoTimeCode
674 gst_video_time_code_new_empty (void)
676 GstVideoTimeCode *tc;
678 tc = g_new0 (GstVideoTimeCode, 1);
679 gst_video_time_code_clear (tc);
684 gst_video_time_code_gvalue_from_string (const GValue * str_val, GValue * tc_val)
686 const gchar *tc_str = g_value_get_string (str_val);
687 GstVideoTimeCode *tc;
689 tc = gst_video_time_code_new_from_string (tc_str);
690 g_value_take_boxed (tc_val, tc);
694 gst_video_time_code_gvalue_to_string (const GValue * tc_val, GValue * str_val)
696 const GstVideoTimeCode *tc = g_value_get_boxed (tc_val);
699 tc_str = gst_video_time_code_to_string (tc);
700 g_value_take_string (str_val, tc_str);
704 gst_video_time_code_serialize (const GValue * val)
706 GstVideoTimeCode *tc = g_value_get_boxed (val);
707 return gst_video_time_code_to_string (tc);
711 gst_video_time_code_deserialize (GValue * dest, const gchar * tc_str)
713 GstVideoTimeCode *tc = gst_video_time_code_new_from_string (tc_str);
719 g_value_take_boxed (dest, tc);
724 * gst_video_time_code_new_from_string:
725 * @tc_str: The string that represents the #GstVideoTimeCode
727 * Returns: (nullable): a new #GstVideoTimeCode from the given string or %NULL
728 * if the string could not be passed.
733 gst_video_time_code_new_from_string (const gchar * tc_str)
735 GstVideoTimeCode *tc;
736 guint hours, minutes, seconds, frames;
738 if (sscanf (tc_str, "%02u:%02u:%02u:%02u", &hours, &minutes, &seconds,
741 || sscanf (tc_str, "%02u:%02u:%02u.%02u", &hours, &minutes, &seconds,
744 tc = gst_video_time_code_new (0, 1, NULL, GST_VIDEO_TIME_CODE_FLAGS_NONE,
745 hours, minutes, seconds, frames, 0);
748 } else if (sscanf (tc_str, "%02u:%02u:%02u;%02u", &hours, &minutes, &seconds,
750 == 4 || sscanf (tc_str, "%02u:%02u:%02u,%02u", &hours, &minutes, &seconds,
753 tc = gst_video_time_code_new (0, 1, NULL,
754 GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME, hours, minutes, seconds, frames,
759 GST_ERROR ("Warning: Could not parse timecode %s. "
760 "Please input a timecode in the form 00:00:00:00", tc_str);
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
773 * The resulting config->latest_daily_jam is set to
774 * midnight, and timecode is set to the given time.
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.
780 * Returns: the #GstVideoTimeCode representation of @dt.
785 gst_video_time_code_new_from_date_time (guint fps_n, guint fps_d,
786 GDateTime * dt, GstVideoTimeCodeFlags flags, guint field_count)
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,
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
803 * The resulting config->latest_daily_jam is set to
804 * midnight, and timecode is set to the given time.
806 * Returns: (nullable): the #GstVideoTimeCode representation of @dt, or %NULL if
807 * no valid timecode could be created.
812 gst_video_time_code_new_from_date_time_full (guint fps_n, guint fps_d,
813 GDateTime * dt, GstVideoTimeCodeFlags flags, guint field_count)
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);
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
838 * @field_count is 0 for progressive, 1 or 2 for interlaced.
839 * @latest_daiy_jam reference is stolen from caller.
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().
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)
853 tc->minutes = minutes;
854 tc->seconds = seconds;
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);
862 tc->config.latest_daily_jam = NULL;
863 tc->config.flags = flags;
867 * gst_video_time_code_clear:
868 * @tc: a #GstVideoTimeCode
870 * Initializes @tc with empty/zero/NULL values and frees any memory
871 * it might currently use.
876 gst_video_time_code_clear (GstVideoTimeCode * tc)
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;
892 * gst_video_time_code_copy:
893 * @tc: a #GstVideoTimeCode
895 * Returns: a new #GstVideoTimeCode with the same values as @tc.
900 gst_video_time_code_copy (const GstVideoTimeCode * tc)
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);
908 * gst_video_time_code_free:
909 * @tc: a #GstVideoTimeCode
916 gst_video_time_code_free (GstVideoTimeCode * tc)
918 if (tc->config.latest_daily_jam != NULL)
919 g_date_time_unref (tc->config.latest_daily_jam);
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.
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.
941 * Returns: (nullable): A new #GstVideoTimeCode with @tc_inter added or %NULL
942 * if the interval can't be added.
947 gst_video_time_code_add_interval (const GstVideoTimeCode * tc,
948 const GstVideoTimeCodeInterval * tc_inter)
950 GstVideoTimeCode *ret;
953 gboolean needs_correction;
955 g_return_val_if_fail (gst_video_time_code_is_valid (tc), NULL);
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);
961 df = (tc->config.fps_n + (tc->config.fps_d >> 1)) / (tc->config.fps_d * 15);
963 /* Drop-frame compensation: Create a valid timecode from the
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) {
970 ret->frames = df * 14;
973 if (!gst_video_time_code_is_valid (ret)) {
974 GST_ERROR ("Unsupported time code interval");
975 gst_video_time_code_free (ret);
979 frames_to_add = gst_video_time_code_frames_since_daily_jam (tc);
981 /* Drop-frame compensation: 00:01:00;00 is falsely interpreted as
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 */
989 needs_correction = FALSE;
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);
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);
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
1011 * Returns: a new #GstVideoTimeCodeInterval with the given values.
1015 GstVideoTimeCodeInterval *
1016 gst_video_time_code_interval_new (guint hours, guint minutes, guint seconds,
1019 GstVideoTimeCodeInterval *tc;
1021 tc = g_new0 (GstVideoTimeCodeInterval, 1);
1022 gst_video_time_code_interval_init (tc, hours, minutes, seconds, frames);
1027 * gst_video_time_code_interval_new_from_string:
1028 * @tc_inter_str: The string that represents the #GstVideoTimeCodeInterval
1030 * @tc_inter_str must only have ":" as separators.
1032 * Returns: (nullable): a new #GstVideoTimeCodeInterval from the given string
1033 * or %NULL if the string could not be passed.
1037 GstVideoTimeCodeInterval *
1038 gst_video_time_code_interval_new_from_string (const gchar * tc_inter_str)
1040 GstVideoTimeCodeInterval *tc;
1041 guint hours, minutes, seconds, frames;
1043 if (sscanf (tc_inter_str, "%02u:%02u:%02u:%02u", &hours, &minutes, &seconds,
1046 || sscanf (tc_inter_str, "%02u:%02u:%02u;%02u", &hours, &minutes,
1049 || sscanf (tc_inter_str, "%02u:%02u:%02u.%02u", &hours, &minutes,
1052 || sscanf (tc_inter_str, "%02u:%02u:%02u,%02u", &hours, &minutes,
1055 tc = gst_video_time_code_interval_new (hours, minutes, seconds, frames);
1059 GST_ERROR ("Warning: Could not parse timecode %s. "
1060 "Please input a timecode in the form 00:00:00:00", tc_inter_str);
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
1074 * Initializes @tc with the given values.
1079 gst_video_time_code_interval_init (GstVideoTimeCodeInterval * tc, guint hours,
1080 guint minutes, guint seconds, guint frames)
1083 tc->minutes = minutes;
1084 tc->seconds = seconds;
1085 tc->frames = frames;
1089 * gst_video_time_code_interval_clear:
1090 * @tc: a #GstVideoTimeCodeInterval
1092 * Initializes @tc with empty/zero/NULL values.
1097 gst_video_time_code_interval_clear (GstVideoTimeCodeInterval * tc)
1106 * gst_video_time_code_interval_copy:
1107 * @tc: a #GstVideoTimeCodeInterval
1109 * Returns: a new #GstVideoTimeCodeInterval with the same values as @tc.
1113 GstVideoTimeCodeInterval *
1114 gst_video_time_code_interval_copy (const GstVideoTimeCodeInterval * tc)
1116 return gst_video_time_code_interval_new (tc->hours, tc->minutes,
1117 tc->seconds, tc->frames);
1121 * gst_video_time_code_interval_free:
1122 * @tc: a #GstVideoTimeCodeInterval
1129 gst_video_time_code_interval_free (GstVideoTimeCodeInterval * tc)