videotimecode: Fix conversion of timecode to datetime with drop-frame timecodes

gst_video_time_code_to_date_time() simply calculated the date time based on
adding the hours/minutes/seconds to the daily jam. This causes a gap every full
minute (except for every 10th minute) with drop-frame timecodes as the first 2
(29.97fps) or 4 (59.94fps) timecodes are skipped (not frames!), e.g. with
29.97fps:

timecode: 12:00:59;28  12:00:59;29  12:01:00;02  12:01:00;03
time    : 12:00:59.950 12:00:59.983 12:01:00.017 12:01:00.050

and not

time    : 12:00:59.934 12:00:59.968 12:01:00.067 12:01:00.100
                        |-- gap of 2 frames --|

The correct calculation would be to use gst_video_time_code_nsec_since_daily_jam()
and add that to the daily jam.

Also add a test for this.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/8649>
This commit is contained in:
Sebastian Dröge 2025-03-15 11:00:52 +02:00
parent 0623581850
commit 18798440e3
2 changed files with 76 additions and 9 deletions
subprojects/gst-plugins-base
gst-libs/gst/video
tests/check/libs

@ -183,7 +183,9 @@ gst_video_time_code_to_date_time (const GstVideoTimeCode * tc)
{
GDateTime *ret;
GDateTime *ret2;
gdouble add_us;
guint64 nseconds;
gdouble seconds;
gint hours, minutes;
g_return_val_if_fail (gst_video_time_code_is_valid (tc), NULL);
@ -198,22 +200,30 @@ gst_video_time_code_to_date_time (const GstVideoTimeCode * tc)
ret = g_date_time_ref (tc->config.latest_daily_jam);
gst_util_fraction_to_double (tc->frames * tc->config.fps_d, tc->config.fps_n,
&add_us);
nseconds = gst_video_time_code_nsec_since_daily_jam (tc);
hours = nseconds / GST_SECOND / 60 / 60;
nseconds -= hours * GST_SECOND * 60 * 60;
minutes = nseconds / GST_SECOND / 60;
nseconds -= minutes * GST_SECOND * 60;
seconds = nseconds / 1000000000.0;
if ((tc->config.flags & GST_VIDEO_TIME_CODE_FLAGS_INTERLACED)
&& tc->field_count == 1) {
gdouble sub_us;
gdouble sub_s;
gst_util_fraction_to_double (tc->config.fps_d, 2 * tc->config.fps_n,
&sub_us);
add_us -= sub_us;
&sub_s);
seconds -= sub_s;
}
ret2 = g_date_time_add_seconds (ret, add_us + tc->seconds);
ret2 = g_date_time_add_seconds (ret, seconds);
g_date_time_unref (ret);
ret = g_date_time_add_minutes (ret2, tc->minutes);
ret = g_date_time_add_minutes (ret2, minutes);
g_date_time_unref (ret2);
ret2 = g_date_time_add_hours (ret, tc->hours);
ret2 = g_date_time_add_hours (ret, hours);
g_date_time_unref (ret);
return ret2;

@ -337,6 +337,63 @@ GST_START_TEST (videotimecode_dailyjam_todatetime)
gst_video_time_code_free (tc1);
g_date_time_unref (dt2);
g_date_time_unref (dt1);
dt1 = g_date_time_new_utc (2016, 7, 29, 0, 0, 0);
tc1 =
gst_video_time_code_new (30000, 1001, dt1,
GST_VIDEO_TIME_CODE_FLAGS_DROP_FRAME, 1, 4, 0, 2, 0);
/* 1 hour, 4 minutes, 0 seconds, and 2 frames
*
* Note: timecodes with 0 and 1 frames do not exist every full minute
* except for every 10th minute!
*/
fail_unless (gst_video_time_code_is_valid (tc1));
fail_unless (tc1->hours == 1);
fail_unless (tc1->minutes == 4);
fail_unless (tc1->seconds == 0);
fail_unless (tc1->frames == 2);
fail_unless_equals_uint64 (gst_video_time_code_frames_since_daily_jam (tc1),
115086);
fail_unless_equals_uint64 (gst_video_time_code_nsec_since_daily_jam (tc1),
3840036200000);
dt2 = gst_video_time_code_to_date_time (tc1);
fail_unless (g_date_time_get_year (dt2) == 2016);
fail_unless (g_date_time_get_month (dt2) == 7);
fail_unless (g_date_time_get_day_of_month (dt2) == 29);
fail_unless (g_date_time_get_hour (dt2) == 1);
fail_unless (g_date_time_get_minute (dt2) == 4);
fail_unless_equals_float (g_date_time_get_seconds (dt2), 0.0362);
g_date_time_unref (dt2);
/* 1 hour, 3 minutes, 59 seconds, and 29 frames
*
* Note: timecodes with 0 and 1 frames do not exist every full minute
* except for every 10th minute!
*/
gst_video_time_code_add_frames (tc1, -1);
fail_unless (gst_video_time_code_is_valid (tc1));
fail_unless (tc1->hours == 1);
fail_unless (tc1->minutes == 3);
fail_unless (tc1->seconds == 59);
fail_unless (tc1->frames == 29);
fail_unless_equals_uint64 (gst_video_time_code_frames_since_daily_jam (tc1),
115085);
fail_unless_equals_uint64 (gst_video_time_code_nsec_since_daily_jam (tc1),
3840002833333);
dt2 = gst_video_time_code_to_date_time (tc1);
fail_unless (g_date_time_get_year (dt2) == 2016);
fail_unless (g_date_time_get_month (dt2) == 7);
fail_unless (g_date_time_get_day_of_month (dt2) == 29);
fail_unless (g_date_time_get_hour (dt2) == 1);
fail_unless (g_date_time_get_minute (dt2) == 4);
fail_unless_equals_float (g_date_time_get_seconds (dt2), 0.002833);
gst_video_time_code_free (tc1);
g_date_time_unref (dt2);
g_date_time_unref (dt1);
}
GST_END_TEST;