From c602f592cd8a0ffd2d95f73fb18e755cd13d4817 Mon Sep 17 00:00:00 2001 From: Caglar Pir Date: Mon, 19 Oct 2026 13:33:57 +0200 Subject: [PATCH] Sync a GPX track to video time 0, not to the first video GPS point GPXVideoExtractor computed the GPX offset against the first GPS point of the video, and _rebase_times() then put that instant at video time 0. When the video's GPS starts later than the video, the GPX track landed early by that point's time. GoPro drops noisy fixes at the start of a recording, so the first point it keeps can be minutes into the video: on a HERO7 capture whose first kept fix is 221.6 s in, a GPX exported from the video's own GPS was synced 222.8 s off, and is now within 2 s. Subtract the video time of that point, so the offset is to video time 0. --- .../geotag/video_extractors/gpx.py | 4 +++- tests/unit/test_gps_epoch.py | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/mapillary_tools/geotag/video_extractors/gpx.py b/mapillary_tools/geotag/video_extractors/gpx.py index 00722bd1..74eb0552 100644 --- a/mapillary_tools/geotag/video_extractors/gpx.py +++ b/mapillary_tools/geotag/video_extractors/gpx.py @@ -128,6 +128,8 @@ def _gpx_offset( video_unix_time = video_gps_points[0].get_unix_time() if video_unix_time is not None: - offset = gpx_points[0].time - video_unix_time + # Sync to video time 0, not to the first video GPS point, which can + # come later (GoPro drops noisy fixes at the start of a recording) + offset = gpx_points[0].time - (video_unix_time - video_gps_points[0].time) return offset diff --git a/tests/unit/test_gps_epoch.py b/tests/unit/test_gps_epoch.py index 87627b70..2a99fe7f 100644 --- a/tests/unit/test_gps_epoch.py +++ b/tests/unit/test_gps_epoch.py @@ -152,6 +152,28 @@ def test_missing_video_timestamp_yields_no_offset(self): video_points = [_camm_point(time=0.0, epoch_time=0.0)] assert GPXVideoExtractor._gpx_offset(gpx_points, video_points) == 0.0 + @pytest.mark.parametrize("video_point", [_camm_point, _gps_point]) + def test_video_gps_starting_late_syncs_to_video_time(self, video_point): + """ + The offset is to video time 0, not to the first video GPS point, or + the GPX track would land early by that point's time. + """ + gpx_points = [ + _camm_point(time=A_UNIX_TIME + t, epoch_time=A_UNIX_TIME + t) + for t in range(21) + ] + # The video's GPS starts 5s into the video + video_points = [ + video_point(time=5.0 + t, epoch_time=A_UNIX_TIME + 5 + t) for t in range(10) + ] + + offset = GPXVideoExtractor._gpx_offset(gpx_points, video_points) + GPXVideoExtractor._rebase_times(gpx_points, offset=offset) + + # Recorded at the same instant as the first video GPS point, so it + # lands at the same video time + assert gpx_points[5].time == 5.0 + class TestEditListOverflow: """An oversized initial gap must not abort the upload."""