Conversation
… duration A video's start time decides where its sampled frames land on a GPS track. We derived it as creation_time - duration, which pushed every video one full duration into the past unless its camera happened to stamp the creation time at the end of the recording. That subtraction came from 644701c (2019), whose removed comment says why: "Blackvue actually reports endtime in the created_at exif field". The workaround was later generalised to every video. ISO/IEC 14496-12 defines creation_time as the creation time of the presentation, which for a recording is the moment it started, and that is what cameras and ffmpeg write. Take the start time from the video's own telemetry when it has some. That clock is absolute UTC, so it is right for BlackVue, whose container time is the end of the recording, and for GoPro, whose container time is in local time (mapillary#819). Fall back to creation_time for the plain MP4s that get geotagged from a GPX, which are exactly the videos that have no telemetry to sync against. Reported for a 10:49 dashcam video paired with a GPX starting at the video's creation time: every sampled frame came out 648.88s early, so the documented GPX workflow failed with MapillaryOutsideGPXTrackError on all of them. Where the track does reach back far enough -- a GPX covering a recording split into several files -- the frames were silently placed up to a kilometre off instead. With this change all 289 sampled frames land on the track exactly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
A community report (Mapillary forum + FB group): a dashcam MP4 recorded at 4.91 fps, paired
with a GPX of the same drive, comes out with its frames on the wrong part of the route.
The same file + GPX works in Google Street View.
The reporter described it as the frame rate being misread. It isn't — the container is parsed
correctly (
mdhdtimescale 15712,stts3186 × 3200 → exactly 4.91 fps, andmp4_sample_parserreproduces every composition time to the microsecond). What is wrong isthe video's start time, and since the frames are located along the GPX by their absolute
timestamps, getting that wrong moves all of them together.
Probe.extract_stream_start_time()returnedcreation_time - duration. For the reported file(
creation_time2026-08-08T08:47:32Z, duration 648.879837s) that is 08:36:43.120Z, while theGPX starts at 08:47:32Z — the creation time, to the second. Every sampled frame came out one
full video duration early, and the workflow the README documents fails outright:
The hard failure is the lucky case. When the track does reach back far enough — a GPX covering
a drive that the camera split across several files, which is how a ~5 GB-per-file dashcam
records — the frames from every file after the first are silently placed one file-length back
along the route. On this track that is up to ~1 km, and the images end up bunched onto road
they were not shot on, which is what the report describes.
Where it came from
The subtraction was introduced in 644701c (Feb 2019). The comment it deleted says what it was
for:
# Blackvue actually reports endtime in the created_at exif fieldIt was a BlackVue workaround that later got generalised to every video. ISO/IEC 14496-12
defines
creation_timeas the creation time of the presentation, which for a recording is themoment it started, and that is what cameras and ffmpeg actually write.
The fix
Take the start time from the video's own telemetry when it has any, and fall back to
creation_timeotherwise:--video_start_time, if given (unchanged)creation_timeStep 2 is what keeps the cameras the old workaround was written for correct, without guessing:
their GPS clock is absolute UTC, so it is right for BlackVue, whose container time really is
the end of the recording, and for GoPro, whose container time is in local time (#819). Step 3
then applies to exactly the videos that have no telemetry to sync against — the plain MP4s
people geotag from a GPX.
Distance-based sampling already timestamps frames from that same GPS clock, so this also makes
the two sampling paths agree instead of differing by 8 hours on a GoPro.
Behaviour changes
creation_timeinstead of
creation_time - duration, i.e. one duration later than before.For the
hero8.mp4fixture that moves the first sample from2019_11_18_15_41_12_354(thecamera's local RTC, minus the duration, labelled UTC) to
2019_11_18_23_42_08_645(real UTC,matching the GPS positions written into the same frames). This is Video Timestamping Bugs with GoPro MAX #819 items 2 and 4.
used them.
--video_start_time.Verification
On the reporter's files (10:49 video, 1630-point GPX),
video_process --geotag_source gpx --video_sample_distance -1 --video_sample_interval 2now yields 289 geotagged frames spanning08:47:32 → 08:58:18, and every one sits 0.0000 m from the GPX position at its true video
time. Before the change: 0 frames, all rejected as outside the track.
pytest tests/unit(690 passed),pytest tests/integration(52 passed),ruff check,ruff format --checkandmypyall pass.Not addressed here
--video_geotag_source gpxstill rebases the GPX onto video time 0, so a GPX covering severalvideo files is injected whole into each of them. That is the documented limitation under
"Limitations of
--video_geotag_source" and is a separate change.