Skip to content

Parse Novatek freeGPS natively so distance sampling can use it - #843

Open
caglarpir wants to merge 2 commits into
mapillary:mainfrom
caglarpir:fix-novatek-freegps-native
Open

caglarpir wants to merge 2 commits into
mapillary:mainfrom
caglarpir:fix-novatek-freegps-native

Conversation

@caglarpir

@caglarpir caglarpir commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Problem

Distance sampling reads GPS only through the native extractors (GoPro, CAMM, BlackVue). Novatek based dashcams store GPS in freeGPS blocks indexed by a moov/gps box, which none of them parse, so distance sampling finds no GPS in these videos:

$ mapillary_tools video_process 20230106_150558_00422F.MP4 out --video_sample_distance 5
WARNING - No GPS data found from the video
ERROR   - MapillaryFileNotFoundError: Import file or directory not found: out

process geotags the same videos fine, because it falls back to ExifTool. Affected: Viofo A129 and A139, Rove R2-4K Pro, AZDome GS63H, Vantrue N4, Anker Roav C1 Pro. With #839, this becomes a hard sampling failure instead of a silent one.

Fix

Add novatek_parser, tried last by NativeVideoExtractor.

ExifTool tells the vendor layouts apart by probing each block in a fixed order (ProcessFreeGPS in QuickTimeStream.pl) and numbers them with its GPSType. The parser probes in the same order and decodes only the three layouts these cameras use:

ExifTool GPSType Layout Cameras
1 XOR encrypted text AZDome GS63H
3 float32 Viofo A129, Viofo A139, Anker Roav C1 Pro
15 float64 Vantrue N4, Rove R2-4K Pro

Positions, headings and GPS times (epoch_time) are the ones the ExifTool fallback produces for the same video: same date handling, same dedupe of repeated fixes, and numbers rounded to the 15 significant digits ExifTool prints.

Point times

Point times are deliberately not ExifTool's. ExifTool's times count from the first fix, which is fine for geotagging the whole video but wrong for distance sampling, which interpolates the track at frame times.

The camera writes one block per second of video, right after the frames of that second, whether it has a fix or not, and in timelapse too. In all 35 videos I have with a gps box, block i follows the frames of video second i: the frame before it is at i + 0.93 to 0.98 s. So the parser uses the position of the block in the index as the point time. Counting from the first fix instead would:

  • shift the track by 17 to 35 seconds on the three clips where the camera got its first fix late (Viofo A139, Rove, Vantrue);
  • on a Rove timelapse (103 s of video, 1515 s of GPS time), put every frame on the first 1.2 km of a 21.8 km track.

Since the timing relies on one block per second, a video whose block count differs from its mvhd duration by more than 1.5 is left to ExifTool.

Left to ExifTool

Anything the parser is not certain about returns None and falls through to ExifTool exactly as before:

  • any other layout, including 70mai and Abask (ExifTool can't read these either);
  • blocks that also parse as valid Nextbase records (ExifTool's catch-all type 20);
  • videos mixing layouts, whose directions _aggregate_gps_track would align by position;
  • type 3 timestamps that ExifTool converts from the time zone of the machine it runs on;
  • non-finite numbers;
  • a block count that doesn't match the video duration.

Failure raises MapillaryVideoGPSNotFoundError, which the geotag factory treats as reprocessable, so process still falls back to ExifTool.

Behaviour changes worth flagging

  • process point times. For these videos, process now takes the points from the native extractor instead of ExifTool. The descriptions are byte-identical before and after for 31 of the 35 videos with a gps box. The other 4 differ only in point times: the three late-first-fix clips (+17, +22 and +35 s) and the Rove timelapse. Positions, headings and all other fields are unchanged.
  • Make, model and camera UUID. A Novatek camera that also writes these tags would lose them, since ExifTool is no longer consulted. None of the 35 videos carry them.
  • GPX sync. With --video_geotag_source gpx, native GPS used to be missing for these videos, so sync aligned the GPX start with the video start and strict_sync failed. Both now sync by GPS time. That is exact when the first fix is in the first second of the video. When the first fix comes later, the track ends up early by that many seconds, because GPXVideoExtractor._gpx_offset assumes the first native point is at video time 0. Fix CAMM GPS epoch follow-ups from #828 #842 rewrites _gpx_offset, so I left gpx.py alone here. The fix is to anchor on video_gps_points[0].get_unix_time() - video_gps_points[0].time.

Verification

Checked against ExifTool 13.40 on 669 dashcam and action camera videos:

  • Parity: all 27 Novatek videos ExifTool can read now extract natively, with the same points as ExifTool: positions, headings and GPS times bit-exact, times as described above.
  • No other changes: every other video gives the same native extractor result as before (GoPro 73, CAMM 25, BlackVue 433, and all the failures).
  • process: as described above: 31 identical, 4 differing only in point times.

Distance sampling on one real clip per camera, which previously produced no frames:

Camera GPS points Distance Frames
Viofo A129 600 5 m 3093
Viofo A139 277 5 m 400
Rove R2-4K Pro 365 5 m 640
Rove R2-4K Pro, timelapse 102 3 m 4847
AZDome GS63H 300 5 m 368
Vantrue N4 60 5 m 135

The 21 new unit tests build synthetic videos for each layout and cover late first fix, timelapse, skipped blocks, the fall-through cases, the block count check and GPX sync. Their expected positions and GPS times are what ExifTool 13.40 extracts from the same videos.

773 unit + integration tests pass on Python 3.9 and 3.14. ruff check, ruff format --check, usort diff and mypy are clean on 3.9.

Scope

Independent of #839 and #841: branches off main, with no file overlap. #842 touches native.py (CAMM part only) and gpx.py; see GPX sync above.

Distance sampling reads GPS only through the native extractors (GoPro,
CAMM, BlackVue). Novatek based dashcams -- Viofo A129/A139, Rove R2-4K
Pro, AZDome GS63H, Vantrue N4, Anker Roav C1 Pro -- store GPS in
"freeGPS " blocks indexed by a moov/"gps " box, which none of them
parse. So `video_process --video_sample_distance` finds no GPS in these
videos, even though `process` geotags them fine through the ExifTool
fallback.

Add novatek_parser, tried last by NativeVideoExtractor. ExifTool tells
the vendor layouts apart by probing each block in a fixed order
(ProcessFreeGPS). The parser probes in the same order and decodes only
the three layouts these cameras use, ExifTool GPSType 1, 3 and 15. The
points are the ones the ExifTool fallback produces: same date handling,
same dedupe/sort/rebase as _aggregate_gps_track, and numbers rounded to
the 15 significant digits ExifTool prints.

Anything the parser is not certain about returns None and still falls
through to ExifTool as before: other layouts, blocks that also parse as
Nextbase records, videos mixing layouts, type 3 timestamps that ExifTool
converts from the time zone of the machine it runs on, and non-finite
numbers. Failure raises MapillaryVideoGPSNotFoundError, which the geotag
factory treats as reprocessable, so the ExifTool fallback in `process`
is unchanged.

Checked against ExifTool 13.40 on 669 dashcam and action camera videos:
the 27 Novatek videos ExifTool reads now extract natively with points
identical to ExifTool's, no other video changes, and `process` writes
identical descriptions for all 35 videos that have a "gps " box.
@meta-cla meta-cla Bot added the cla signed label Sep 23, 2026
The first version rebased point times on the first fix, as the ExifTool
fallback does. Distance sampling interpolates the track at frame times,
so that shifts the track whenever the camera gets its first fix after
it starts recording. In timelapse videos, where GPS time runs faster
than the video, it also leaves most of the track past the end of the
video.

The camera writes one freeGPS block per second of video, right after
the frames of that second, whether it has a fix or not. So the position
of the block in the index is its time in the video. Use that as the
point time, and keep the GPS time in epoch_time as before. Since the
timing relies on one block per second, leave the video to ExifTool if
the block count doesn't match the duration in mvhd.

Positions and GPS times still match ExifTool's on all 27 Novatek videos
it reads. Times change on the four of them that get their first fix
17 to 35 seconds in, or are timelapse.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant