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