A dashcam GPS visualization tool for Viofo cameras. Upload an MP4 directly from your SD card — or point it at a footage directory and let DashTrack auto-index everything. GPS data is extracted from the file's embedded binary blocks and displayed on a satellite map, synced frame-accurately to video playback. No external tools, no OCR, no GPS app required. Built on a pluggable provider architecture — Viofo today, open to more brands (see Extending to other dashcams).
Viofo dashcams (Novatek NT96660 chip) embed GPS as freeGPS binary blocks directly inside the MP4 file's mdat section — one block per second. These are invisible to standard tools like ffprobe, but DashTrack reads them natively.
Each block contains: GPS fix status, latitude, longitude, speed, bearing, and altitude — all encoded as raw binary floats in NMEA format.
Offset Field
0 'GPS ' magic
32 Active flag ('A' = fix, 'V' = no fix)
36 Latitude (float32 LE, NMEA DDMM.MMMM)
40 Longitude (float32 LE, NMEA DDDMM.MMMM)
44 Speed (knots)
48 Bearing (degrees)
52 Altitude (metres)
The extracted data is output as a GPX file with a custom <video_sec> extension tag per waypoint, which is what enables the frame-accurate map sync during playback.
- Binary GPS extraction — reads
freeGPSblocks directly from Viofo MP4 files, no FFmpeg needed - Auto-indexing library — mount a footage directory and DashTrack indexes all MP4s on startup, watching for new files in real time
- Library browser — calendar date picker, date presets, day grouping, channel filtering (front/interior/rear)
- Multi-channel video — synchronized front / interior / rear playback (up to 3 channels) in side-by-side or picture-in-picture layout, click a PiP thumbnail to focus it
- Multi-segment route builder — select clips from different days, trim start/end, reorder, and compose into a single continuous route
- Frame-accurate map sync — the car marker on the map moves in sync with video playback
- Satellite map — Mapbox GL JS v3, switchable between satellite+streets and dark vector
- Follow car mode — map viewport auto-pans to keep the car marker centered
- Waypoint timeline — scrollable list of GPS waypoints with timestamps, speed, and bearing; click to seek
- Trip stats — total distance, max speed, average speed, GPS fix rate
- Swappable layout — swap the map and video between the main panel and sidebar
- Keyboard shortcuts — Space (play/pause), ←/→ (±10s), Shift+←/→ (±30s), M (mute)
- WebSocket progress — real-time extraction progress streamed during upload
- HTTP 206 range requests — video files are streamed with range support for proper seeking
- Docker (for production)
- Node.js 18+ and Python 3.12+ (for development)
- A Mapbox access token (free at account.mapbox.com)
- A Viofo dashcam MP4 file with GPS data
docker build -t dashtrack .
docker run -p 8080:8000 -v /path/to/your/footage:/footage dashtrackOpen http://localhost:8080.
DashTrack will auto-index all MP4 files found in /footage on startup and watch for new ones. Use the library icon to browse indexed clips.
# Terminal 1 — backend (hot reload)
pip install -r requirements.txt
uvicorn main:app --reload --port 8080
# Terminal 2 — frontend (hot reload)
npm install
npm run dev
# → http://localhost:5173- Mount your footage directory when running Docker (
-v /path/to/footage:/footage) - Open the app — clips are indexed automatically in the background
- Click the library icon to open the browser
- Filter by date, channel, or use the calendar picker to find a recording
- Load a single clip, a full session (front / interior / rear), or select multiple clips to build a multi-segment route
- Switch to the Upload tab in the library panel
- Drag and drop (or click to select) a Viofo
.MP4file - DashTrack uploads the file, extracts GPS, and streams progress via WebSocket
- Once done, load the same
.MP4into the video player and press play
Note: The video is played locally in your browser via
createObjectURL— it is never stored server-side in upload mode.
| Layer | Technology |
|---|---|
| Frontend | React 18 + TypeScript |
| Build tool | Vite 5 |
| State management | Zustand 4 |
| Map | Mapbox GL JS v3 |
| Calendar | react-day-picker 9 |
| Backend | FastAPI (Python 3.12) |
| Database | SQLite via SQLModel |
| File watcher | watchfiles |
| Server | Uvicorn |
| Container | Docker (multi-stage build) |
POST /api/extract/start Upload MP4 → { job_id, file_size }
WS /api/ws/extract/{job_id} Progress stream:
{ type:'progress', points:N }
{ type:'done', gpx:'...', stats:{...} }
{ type:'error', message:'...' }
GET /api/library List indexed clips (pagination + date filter)
GET /api/library/days Distinct recording days with clip counts
POST /api/library/batch Batch fetch metadata + GPX for multiple clips
GET /api/library/session/{id} All clips in a session (front/interior/rear) with GPX
GET /api/library/{clip_id} Single clip metadata + GPX
GET /api/footage/{clip_id} Stream MP4 with HTTP 206 range request support
GET /api/health { status:'ok' }
GET /api/docs Swagger UI
dashtrack-single/
├── Dockerfile # Multi-stage: node build → python serve
├── requirements.txt
├── package.json
├── main.py # FastAPI app: SPA serving + API routes + lifespan
├── extractor.py # Viofo freeGPS binary decoder
├── db.py # SQLModel models + SQLite setup
├── scanner.py # Footage directory watcher + auto-indexer (provider-driven)
├── providers/ # Camera provider abstraction (add new brands here)
│ ├── base.py # Provider interface + ClipMeta
│ └── viofo.py # Viofo freeGPS provider (reference implementation)
└── routers/
│ └── library.py # Library + footage streaming API routes
└── src/
├── App.tsx # Root layout, keyboard shortcuts, mode routing
├── channels.ts # Channel model (front / interior / rear helpers)
├── store/index.ts # Zustand global state
├── hooks/
│ ├── useGPX.ts # GPX parser, haversine distance, helpers
│ └── useViewportWidth.ts
├── api/
│ └── library.ts # API client functions
└── components/
├── LibraryModal.tsx # Full library browser (calendar, filters, days)
├── SessionBuilder.tsx # Multi-segment session composer
├── MultiVideoPlayer.tsx # Multi-channel video (side-by-side / PiP)
├── VideoChannel.tsx # Single <video> element wrapper
├── VideoPlayer.tsx # Legacy single-channel player
├── UploadZone.tsx # Drag & drop upload + WS progress
├── MapView.tsx # Mapbox GL map, route, car marker
└── Timeline.tsx # Waypoints list + trip stats
DashTrack currently supports Viofo cameras (Novatek freeGPS format), but it's built to grow. Everything camera-specific — recognizing a file, parsing its channel/session naming, and decoding its embedded GPS — lives behind a small provider interface (providers/base.py). Everything downstream (the SQLite index, the REST API, and the entire UI) is brand-agnostic.
Adding support for another manufacturer (BlackVue, Garmin, Thinkware, 70mai, …) means implementing one Provider subclass and registering it in providers/__init__.py — no database, API, or frontend changes required. See providers/viofo.py for a reference implementation.
I'd love help here. If your dashcam isn't supported and you're willing to share a sample clip or help work out its GPS format:
- Open a camera compatibility issue with your camera model and a sample file, or
- Send a PR adding a provider.
Contributions, GPS-format notes, and test footage are all very welcome.
- Altitude is always 0.0 — the A229 Plus firmware doesn't write altitude data
- No authentication — designed for local use only
- Viofo only (for now) — only the Viofo
freeGPS/ Novatek format is implemented; other brands need a provider (contributions welcome) - No seamless clip transitions — multi-segment playback swaps
video.srcat clip boundaries rather than using MSE