Decode the hidden telemetry your dashcam was silently recording.
offline open-source multi-format decoder HUD compositor
klartext (German: plain text; speaking the unvarnished truth) is a command-line tool that decodes the telemetry stream embedded in dashcam footage and renders a clean HUD overlay onto a copy of the video.
The goal is universal: any dashcam that hides or embeds telemetry — protobuf cues, NMEA sentences, GoPro GPMF, manufacturer-proprietary binary, plain-text subtitle GPS — should be readable through klartext.
The reality today is one format: the Audi A5 (2026, E3 1.2 / MIB4) protobuf-in-tx3g payload that the project was originally built around. The architecture treats this as one decoder among many; new formats live behind the same pipeline. See 04 / Supported Cameras for the current matrix, and 09 / Roadmap for what's next.
Everything happens on your machine. No cloud, no accounts, no telemetry of its own.
Audi A5 (2026) — HUD rendered by klartext from the embedded telemetry track
- 01 / Overview
- 02 / Quick Start
- 03 / Output
- 04 / Supported Cameras
- 05 / How It Works
- 06 / Telemetry Field Reference
- 07 / CLI Reference
- 08 / Watermark Policy
- 09 / Roadmap
- 10 / Contributing
- 11 / License
- 12 / Acknowledgements
klartext is two pieces glued together:
- A decoder that reads telemetry from whatever the camera embedded — and normalises it into a universal frame format with timestamp, GPS, speed, heading, odometer, optional altitude, plus a typed bag of vendor-specific extras.
- A renderer that turns those frames into standard outputs (CSV, JSON, GPX) and a clean HUD overlay on a re-encoded copy of the video — Audi-MMI-style by default, configurable.
The decoder is the part that varies between camera vendors. The renderer is the part that doesn't. Everything between the two — the universal KSFFrame / KSFDocument, the writers, the encoder picker, the HUD layout, the watermark, the minimap — is shared across formats.
Today klartext ships with one decoder: the Audi A5 (E3 1.2 / MIB4) protobuf-in-tx3g format. New decoders are added by opening an issue with a sample clip from a format klartext can't read yet — the maintainers handle the decoder work. You don't have to write code; you just have to bring the data. (Writing the decoder yourself is welcome too, but optional.) The workflow is in CONTRIBUTING.md.
| Component | Version | Required? | Notes |
|---|---|---|---|
| Python | ≥ 3.8 | yes | type-hinted dataclasses |
| ffmpeg / ffprobe | ≥ 5.0 | yes | must be on $PATH |
rich |
latest | yes | terminal UI |
Pillow |
≥ 10.0 | optional | enables the graphical HUD with minimap. Without it, klartext falls back to ASS subtitle overlay. |
Hardware-accelerated encoding is auto-detected in this order: NVIDIA NVENC → Intel QSV → AMD/Mesa VAAPI → libx265 software. Override with --encoder if needed.
# recommended (includes Pillow for the graphical HUD):
pip install "klartext[hud]"
# minimal (no graphical HUD, ASS subtitle overlay only):
pip install klartext# or install from source (latest unreleased commit):
git clone https://github.com/OGMatrix/klartext
cd klartext
pip install -e ".[hud]"Pillow is optional but strongly recommended — it enables the full graphical HUD with satellite minimap and brand mark. Without it, klartext falls back to an ASS subtitle overlay only.
klartext drive.mp4
# or equivalently:
python -m klartext drive.mp4That is the entire common case. By default klartext writes its outputs next to the input file.
klartext --versionReports the current version.
For an input drive.mp4, klartext produces:
| File | Format | Purpose |
|---|---|---|
drive.telemetry.csv |
wide CSV | one row per cue, every decoded field |
drive.telemetry.json |
structured JSON | scripting / pipeline use, includes raw fields |
drive.track.gpx |
GPX 1.1 | drop into any mapping tool |
drive.hud.ass |
Advanced SubStation Alpha | the overlay subtitle, re-usable on its own |
drive.hud.mp4 |
HEVC video | re-encoded with the HUD burned in |
drive.map.html |
HTML | self-contained Leaflet.js interactive route viewer (opt-in: --html-map) |
drive.chapters.ffmeta |
FFMETADATA | chapter markers injected during re-encode (opt-in: --chapters) |
drive.thumb.jpg |
JPEG | HUD-composited still thumbnail at a chosen timestamp (opt-in: --thumbnail) |
The CSV columns map directly to the fields in 06 / Telemetry Field Reference.
The GPX file includes klartext-specific extensions for the fields GPX 1.1 doesn't natively carry (cumulative odometer, GPS C/N₀) — readable by any GPX-aware tool, with the extras ignored cleanly.
If Pillow is installed, the burned-in HUD includes a satellite minimap with a heading-aware GPS marker, the date/time + location panel, a centred speed readout, and a small klartext mark in the lower-right corner. Without Pillow, the same telemetry is shown via the ASS subtitle overlay, minus the minimap.
Two axes: the telemetry format (what protocol klartext reads), and the specific hardware known to use that format. Adding a new vehicle to an already-supported format is usually a one-line change; adding a new format is a new decoder module.
Status legend: Working = first-class support; Planned = committed roadmap item with a defined approach; In investigation = format identified, feasibility being assessed.
| Format | Vendors / cameras it covers | Status | Difficulty |
|---|---|---|---|
Audi protobuf-in-tx3g |
Audi A5 (2026), likely Q6 e-tron, A6 e-tron, Porsche Macan EV — all E3 1.2 / MIB4 | Working | reverse-engineered |
GoPro GPMF (gpmd metadata track) |
GoPro Hero 5+, some Insta360, Sony Action Cam | Planned | well-documented public spec |
| DJI SRT subtitle GPS | DJI Mavic / Air / Mini, Osmo Action | Planned | plain text, regex-extractable |
| NMEA 0183 sidecar | Garmin, Viofo, Thinkware, Nextbase, some BlackVue | Planned | standard GPS protocol |
| Novatek embedded | Viofo A119, generic Novatek-chipset cams | In investigation | community-RE'd binary atom |
| BMW integrated dashcam | recent BMW models with built-in cam | In investigation | format undocumented |
| Mercedes integrated dashcam | recent MBUX-based models with built-in cam | In investigation | format undocumented |
| Tesla TeslaCam | Tesla Model S/3/X/Y | In investigation | GPS is in vehicle logs, not the clip — feasibility uncertain |
Specific cameras / vehicles confirmed working through actual sample testing.
| Manufacturer | Model | Year | Format | Status | Verified by |
|---|---|---|---|---|---|
| Audi | A5 | 2026 | Audi protobuf-in-tx3g |
Working | project author |
"Working" means: the telemetry stream is detected, every sample parses cleanly, GPS + speed + odometer round-trip correctly, and the rendered HUD matches the in-car (or expected) playback.
Other vehicles built on the Volkswagen Group E3 1.2 / MIB4 platform with an integrated dashcam are expected to use the same protobuf schema. Candidates, in rough order of confidence:
- Audi Q6 e-tron (2025+)
- Audi A6 e-tron (2025+)
- Audi A6 (current generation)
- Audi Q5 (next generation)
- Porsche Macan EV (2024+)
If you own one of these and have a dashcam export to share, please open an issue — your sample is what moves the row from this list to the verified table.
Both paths are issue-driven and primarily about you bringing the data. You don't need to write code. Open an issue with a sample clip, the ffprobe output, and whatever else you can find (camera model, firmware, sidecar files, manufacturer docs), and the maintainers will handle the decoder work. Writing the decoder yourself is welcome but optional.
- New hardware on an existing format (e.g., another E3 1.2 Audi): see
CONTRIBUTING.md. - New format entirely (e.g., a NMEA-sidecar dashcam, a GoPro export): see
CONTRIBUTING.md.
input.mp4
→ ffprobe (identify streams)
→ auto_detect / --decoder (choose decoder, report confidence %)
→ decoder.decode(...) (per-format)
→ KSFDocument (universal in-memory model)
→ writers (CSV, JSON, GPX, ASS) (format-agnostic)
→ renderer (Pillow HUD, optional) (format-agnostic)
→ ffmpeg burn + re-encode (format-agnostic)
→ input.hud.mp4
Every decoder produces the same KSFDocument (a list of KSFFrame objects plus metadata). Every output writer consumes that shape. New formats only touch the decoder.
ffprobe on a stock Audi A5 dashcam export reveals an extra track alongside the video and audio:
Stream #0:0 Video : hevc (Main 10), 3840×2160, 30 fps
Stream #0:1 Audio : aac, 48000 Hz, stereo
Stream #0:2 Subtitle : mov_text (tx3g) ← telemetry channel
The subtitle stream is the entire telemetry channel. The in-car player reads it; every external player ignores it.
Each cue is a 3GPP timed-text payload of the form:
{\an7}<font size="0">PD94bWwgdmVyc2lvbj0iMS4wIj8+CjwhRG9j…</font>
The <font size="0"> makes the cue invisible if a player ever did try to render it. The base64 blob inside decodes to a binary Protocol Buffers message at roughly 5 Hz.
There is no .proto file. There does not need to be: the firmware writes field names as plaintext strings directly into the wire format alongside their values. A single frame looks roughly like:
id200_01 "2026-05-04T07:14:24+00:00"
id200_02 "52.606677242"
id200_03 "11.849491519"
id200_05 9.138 (speed, m/s)
id200_06 774116.0 (odometer, m)
idOC_02 "Scharnhorststrasse"
idOC_04 "Stendal"
idOC_06 "DE"
idOC_40 "t11_DEU_1p0_2p50_3p0_1s0"
klartext walks the wire bytes, reads each (tag, name, value) triple, and binds the result into a strongly-typed KSFFrame. No protobuf library is used — the decoder is ~80 lines.
When Pillow is installed, klartext renders a graphical HUD that mimics the Audi MMI playback overlay:
- top-left — semi-transparent panel with date/time +
(<country>) <city> – <street> - bottom-left — satellite minimap with a heading-aware GPS marker
- bottom-centre —
‹‹ NN km/h ››speed readout - bottom-right — small klartext brand watermark
The renderer writes each composite as a frame in a transparent overlay video, which is then composited and re-encoded onto the source by ffmpeg.
When Pillow is not installed, klartext falls back to an ASS subtitle overlay covering the same text content (no minimap, no graphical icons).
The minimap composites raster tiles behind a GPS marker:
- ESRI World Imagery — satellite imagery (the default, no token required).
- Mapbox satellite-streets — optional, higher detail at low zooms, requires a free token. klartext tracks your monthly tile usage against Mapbox's 50,000-tile free tier and prints what's left at the end of each run.
When the GPS heading is established (after enough samples to derive a stable direction), the marker rotates to point along the track. Before that, it shows a static disc.
Tiles are cached to ~/.cache/klartext/tiles/ so repeated processing of nearby clips reuses what's already on disk.
This section documents the Audi A5 schema specifically. Other formats will get their own reference sections as their decoders land.
The Audi A5 dashcam writes two related blocks per cue. Field names appear stable across the firmware versions we've seen; semantics are not always known.
Per-cue dynamic measurements. Confirmed through round-trip verification (e.g., the integral of id200_05 matches the delta of id200_06 to within float-rounding).
| Field | Type | Meaning | Status |
|---|---|---|---|
id200_01 |
string | UTC timestamp, ISO 8601 | Confirmed |
id200_02 |
string | Latitude, decimal degrees, WGS-84 | Confirmed |
id200_03 |
string | Longitude, decimal degrees, WGS-84 | Confirmed |
id200_04 |
float | GPS carrier-to-noise ratio (C/N₀), dB-Hz | Confirmed |
id200_05 |
float | Ground speed, m/s — multiply by 3.6 for km/h | Confirmed |
id200_06 |
float | Cumulative odometer, metres | Confirmed |
Static and state fields. Some have plaintext semantic content; many are integer flags whose meaning is not yet known.
Status legend: Confirmed = decoded and verified; Hypothesis = best guess from observed values, needs confirmation; Unknown = field name only, no behaviour observed yet.
| Field | Type | Meaning / hypothesis | Status |
|---|---|---|---|
idOC_02 |
string | Street name (--- when off-road / no match) |
Confirmed |
idOC_04 |
string | City | Confirmed |
idOC_06 |
string | ISO 3166-1 alpha-2 country code | Confirmed |
idOC_08 |
string | region / sub-city district | Hypothesis |
idOC_09 |
int (1) | recording active flag | Hypothesis |
idOC_10 |
int (0) | always 0 in observed data | Unknown |
idOC_11 |
int (0) | always 0 in observed data | Unknown |
idOC_12 |
int (0/1) | right turn indicator — 1 while the right stalk is active | Confirmed |
idOC_13 |
int (0/1) | left turn indicator — 1 while the left stalk is active | Confirmed |
idOC_14 |
int (0) | always 0 in observed data | Unknown |
idOC_15 |
int (0/1) | high-beam headlights active | Confirmed |
idOC_16 |
int (0) | always 0 in observed data | Unknown |
idOC_19 |
int (0) | always 0 in observed data | Unknown |
idOC_21 |
enum | always NONE so far — likely a warning category |
Hypothesis |
idOC_24 |
enum | WarningZero / WarningSeven observed — likely a warning level |
Hypothesis |
idOC_25 |
enum | always TextZero so far — likely a text-overlay slot |
Hypothesis |
idOC_26 |
enum | always RequestZero so far — likely a request-type slot |
Hypothesis |
idOC_27 |
enum | always RequestZero so far — likely a second request slot |
Hypothesis |
idOC_28 |
int (0) | always 0 in observed data | Unknown |
idOC_29 |
int (0) | always 0 in observed data | Unknown |
idOC_30 |
int (0) | always 0 in observed data | Unknown |
idOC_31 |
int (0) | always 0 in observed data | Unknown |
idOC_32 |
int (0) | always 0 in observed data | Unknown |
idOC_40 |
string | head-unit firmware string | Confirmed |
idOC_41 |
string | mostly empty; occasionally a second firmware-shaped string — possibly companion-component firmware | Hypothesis |
idOC_42 |
string | vehicle model identifier | Hypothesis |
idOC_46 |
int (0/1) | warning triangle / hazard indicator — 1 when active | Confirmed |
Computed by klartext, not present in the raw stream:
| Field | Type | Meaning |
|---|---|---|
heading |
float | bearing in degrees from north, clockwise, derived from consecutive GPS positions with EMA smoothing |
speed_kmh |
float | convenience conversion of speed_ms × 3.6 |
The fastest way to upgrade an Unknown row to a Hypothesis, or a Hypothesis to Confirmed, is to capture footage of a known event and look at which fields move:
- a sharp brake → which counter increments?
- a g-sensor event (speed bump, pothole) → which integer becomes non-zero?
- crossing a country border → when does
idOC_06flip? - a dashcam firmware update → does
idOC_41change? doesidOC_42appear? - a lane-assist warning → which enum field changes?
If you find a match, please open a PR against this table. The workflow is in CONTRIBUTING.md.
klartext INPUT.MP4 [options]
| Flag | Default | Description |
|---|---|---|
INPUT.MP4 |
— | the dashcam export to process |
-o, --output-dir DIR |
input dir | destination directory |
--decoder ID |
auto | force a specific decoder by ID; use list to show all (--decoder list) |
--external-telemetry FILE |
— | external .gpx sidecar to fill GPS gaps in the decoded data |
--version |
— | print version and exit |
-v, --verbose |
off | print every detected raw decoder field with its type and first observed value |
--stats-only |
off | decode telemetry, print trip statistics (speed, distance, GPS, countries …), and exit without writing any output files |
| Flag | Default | Description |
|---|---|---|
--preview SECS |
off | process only the first SECS seconds of the clip — useful for fast iteration |
--trim START END |
off | process only the [START, END) window; times as [[HH:]MM:]SS[.ff] or bare seconds (e.g. 30 90 or 0:30 1:30) |
--speed-unit {kmh,mph} |
kmh |
speed display unit used in the HUD, ASS subtitle, the HTML map, and --stats-only output |
| Flag | Default | Description |
|---|---|---|
--no-csv |
off | skip writing the CSV telemetry table |
--no-json |
off | skip writing the JSON telemetry file |
--no-gpx |
off | skip writing the GPX track |
--export-ksf |
off | also write <name>.ksf.json — the full KSF normalised document (useful for scripting) |
--html-map |
off | write a self-contained Leaflet.js interactive route viewer (<name>.map.html) with speed-coloured segments, hover panel, and sidebar statistics; internet connection required only for map tiles |
--chapters |
off | detect chapter events (clip start, stops, country crossings) and write <name>.chapters.ffmeta; automatically injected into the re-encoded video |
--thumbnail [TIMESTAMP] |
off | export a HUD-composited JPEG thumbnail (<name>.thumb.jpg) at TIMESTAMP ([[HH:]MM:]SS[.ff] or bare seconds); omit the value to use the video midpoint |
| Flag | Default | Description |
|---|---|---|
--batch DIR |
off | process every MP4 in DIR with the same options; the positional input argument is ignored; prints a per-file ✓/✗ summary at the end |
| Flag | Default | Description |
|---|---|---|
--no-burn |
off | skip the video re-encode; only export data + .ass |
--no-video |
off | skip HUD rendering and video output entirely; only export telemetry, GPX, and ASS |
--stream-copy |
off | embed the ASS subtitle as a soft track via stream-copy — no re-encode, very fast, but the subtitle is not burned in (requires player support for soft subs) |
--encoder NAME |
auto | force a specific ffmpeg encoder (hevc_nvenc, hevc_qsv, hevc_vaapi, libx265, libx264) |
--crf N |
22 | quality (CRF / CQ / QP depending on encoder) |
--font NAME |
DejaVu Sans | HUD font family |
--whatsapp |
off | encode for WhatsApp compatibility: forces libx264, yuv420p, H.264 Main profile level 4.0 |
--strip-telemetry |
off | drop the hidden telemetry track from the output — recommended when sharing |
| Flag | Default | Description |
|---|---|---|
--no-map |
off | skip the satellite minimap (use ASS-only HUD, or for offline runs) |
--mapbox |
off | use Mapbox satellite-streets tiles instead of ESRI |
--mapbox-token TOKEN |
— | save a Mapbox token to the config file; can be used standalone without a video file |
--map-zoom N |
16 | satellite tile zoom level for the minimap |
--map-size PX |
auto | minimap panel size in pixels (default: 25 % of shorter video dimension, min 180) |
| Path | Purpose |
|---|---|
~/.config/klartext/config.json |
persisted settings (currently: Mapbox token, monthly usage counter) |
~/.cache/klartext/tiles/ |
cached satellite & navigation tiles |
Environment variables MAPBOX_TOKEN or KLARTEXT_MAPBOX_TOKEN are honoured as fallbacks if no token is in the config file.
The hidden telemetry track contains your GPS coordinates, timestamps, street names, and odometer. When you share a dashcam clip with anyone — insurance, police, social media — that track travels with the video unless you strip it. --strip-telemetry produces a clean output that retains the rendered HUD but discards the underlying raw stream.
klartext renders a small klartext mark in the lower-right corner of the output video. It is intentionally small (≈10 % of frame width), low-contrast, and unobtrusive.
It exists for two reasons:
- Attribution. When a clip with a klartext-style HUD shows up online, it should be obvious which tool produced it.
- Provenance. If the underlying telemetry is later disputed, the mark indicates which decoder was used.
Contributors are asked not to remove or disable the watermark in pull requests. Refining its size, placement, contrast, opacity, or styling is welcome; deleting it is not. Forks for personal use are free to do as they wish — that is what open-source means. The request applies to contributions back to the canonical repository.
Highlights, in roughly the order they're expected to land:
Landed since v1.0.0: --preview, --trim, --speed-unit, --html-map, --thumbnail, --stream-copy, --batch, --stats-only, --chapters, PyPI publishing (pip install klartext)
Up next:
- format coverage — decoders for GPMF, DJI SRT, NMEA 0183 sidecars, Novatek embedded
- additional HUD modules (speed history, altitude, compass, trip distance)
- a configurable HUD layout with freely movable components
- a browser-based HUD designer at klartext.dev
- expanded vehicle coverage within existing formats (E3 1.2 siblings)
The full plan with status, prioritisation, and open questions lives in ROADMAP.md.
klartext is open-source and will stay that way. The shape of the project — first-class Audi support today, every dashcam tomorrow — only works through contributions.
Where you can help:
- bug reports & fixes — open an issue or a PR
- security findings — see the dedicated section in
CONTRIBUTING.md - a new decoder format — share a clip from a dashcam klartext can't read yet, plus everything you can find about it; the maintainers handle the decoder code (writing it yourself is welcome too, but optional)
- a new vehicle inside an existing format — confirm support by sharing a sample clip from your car
- reverse-engineering — help fill in the Unknown / Hypothesis rows in the Field Reference
- features — pick a roadmap item or propose your own
- documentation — typo fixes, translations, clearer phrasing
The full process, code style, decoder interface, and the new-vehicle workflow live in CONTRIBUTING.md.
MIT for the source code.
Brand assets in assets/ are © the klartext authors — all rights reserved unless otherwise noted.
The embedded glyph outlines in the brand SVGs derive from DejaVu Sans Bold (OFL-1.1).
- the FFmpeg project, without which none of this exists
- the GoPro GPMF authors for publishing the gpmd specification
- the DejaVu Sans contributors for an OFL grotesque that survived being chopped into a logo
- ESRI for the open World Imagery tile service, and Mapbox for a usable free tier
- everyone who has ever filed an issue with a hex dump attached
klartext.dev