Overlays dive-computer CSV telemetry (depth, temperature, pressure, heart rate, dive time) onto video. Supports multiple clips with gaps, per-clip sync points, automatic sync via each MP4's recording time, and combining all of a dive's clips into one video.
- Overlay (default): values burned directly into the video pixels.
- Subtitles: values written as a soft subtitle track (SRT/
mov_text), toggleable on/off in the player. Video/audio are copied losslessly (no re-encode); an.srtsidecar is also written.
- Rust (stable, 2021 edition) via rustup
ffmpegandffprobeon PATH (e.g.winget install Gyan.FFmpegon Windows)
cargo build --release
cargo test --workspaceBinaries land in target/release/dive_overlay_cli(.exe) and target/release/dive_overlay_gui(.exe).
crates/dive_overlay_core— CSV parsing, sample lookup, overlay drawing, ffprobe wrapper, ffmpeg pipeline, multi-clip/auto-synccrates/dive_overlay_cli— CLI binary (clap)crates/dive_overlay_gui— GUI binary (egui/eframe)
Column names are recognized flexibly, e.g. sample time (min), sample depth (m), sample temperature (C), sample pressure (bar), sample heartrate (see dive.csv for a sample file). Use --column-map to override auto-detection.
cargo run --release --bin dive_overlay_guiSelect a CSV , set fields, choose a mode and codec/preset/hardware acceleration, add clips, find each clip's sync point, then start processing.
Sync preview (select a clip → Sync preview) walks the two steps in order:
- The
-1 min … +1 minbuttons scrub the video sync point and re-seek the clip. Use them to land on the frame where the dive computer is readable; the readout shows the position and the clip length, and scrubbing stops at both ends of the video. - Type the dive time the computer reads in that frame into the CSV sync field. The overlay redraws as you type, so you can check it against what's on screen — if the two agree, the clip is synced.
Two checkboxes handle multi-clip dives:
- Combine all clips into one dive video — same behavior as
--merge-outputbelow: clips are sorted by dive time, joined back-to-back, and only the combined file is kept. - Auto-sync clips from their recording timestamps — the GUI equivalent of
--auto-sync: pick a base clip and sync only that one by hand; every other clip's sync is derived from how much later it started recording. Add clips... imports a whole dive's clips at once, in recording order.
cargo run --release --bin dive_overlay_cli -- \
--csv dive.csv --video input.mp4 \
--video-sync-sec 3.2 --csv-sync-mmss 0:10Produces input_overlay.mp4. Add --mode subtitles for the subtitle variant, or --codec hevc --hw-accel for hardware-accelerated encoding.
Each clip gets its own sync point: video_path|video_sync_sec|csv_sync_mmss[|output_path].
cargo run --release --bin dive_overlay_cli -- \
--csv dive.csv --fields time,depth,temp \
--clip "clip1.mp4|2.1|0:10|clip1_overlay.mp4" \
--clip "clip2.mp4|0.8|18:35|clip2_overlay.mp4" \
--clip "clip3.mp4|5.0|31:20"If output_path is omitted, <video_stem>_overlay.mp4 is used.
Instead of syncing every clip manually, sync one base clip and let the rest be derived from how much later each clip started recording:
cargo run --release --bin dive_overlay_cli -- \
--csv dive.csv \
--clip "clip1.mp4|0|10:00" --clip "clip2.mp4|0|0:00" \
--auto-sync --base-clip clip1.mp4 --base-video-sync-sec 0The base clip's own csv_sync_mmss (10:00 above) is the anchor; the other clips' values are ignored and recomputed. video_sync_sec is assumed identical across clips (e.g. "film the dive computer for the first few seconds of every clip") — only csv_sync_sec is shifted per clip.
Recording times come from each clip's start timecode (the tmcd track, which cameras stamp with the time of day at record start, at frame resolution), falling back to the container's creation_time when any clip has no timecode. The timecode is preferred because creation_time is quantized to whole seconds, so it can be most of a second off per clip. Nothing is read from the CSV but its elapsed-time column, so this also works with dive computers that export no wall-clock time at all.
Any clip landing outside the logged dive is reported as a warning — the usual cause is a clip from a different dive having been selected.
A dive usually arrives as several clips from the same CSV. --merge-output combines them into a single video:
cargo run --release --bin dive_overlay_cli -- \
--csv dive.csv \
--clip "clip3.mp4|0|31:20" --clip "clip1.mp4|0|0:10" --clip "clip2.mp4|0|18:35" \
--merge-output dive_full.mp4- Clips are sorted by dive time, not by the order you listed them — the example above still comes out as clip1 → clip2 → clip3.
- They are joined back-to-back with no filler for the surface intervals between them, so the overlay's dive time jumps at each seam (
12:30→18:35). That's an honest rendering of footage that doesn't exist. - Only the combined file is kept. The per-clip results are written to a
<output_stem>_parts/scratch directory and deleted once the merge succeeds; if a clip fails or you cancel, the parts are left behind and the message says where. - Clips that share resolution, frame rate and audio are joined losslessly (stream copy). Mismatched ones are re-encoded into the largest clip's frame, with silence synthesized for any clip without audio.
- In subtitle mode the embedded
mov_texttrack comes along and the.srtsidecar is rebuilt on the merged timeline. Mismatched clips are rejected there rather than re-encoded, since that mode exists precisely to avoid touching pixels.
Combining works with --auto-sync too, which is the usual pairing: sync one base clip by hand and get one finished dive video out.
--video-sync-sec is the point in the video (seconds) where the dive computer is filmed as a reference; --csv-sync-mmss is the dive time shown at that exact moment. E.g. if the computer reads 0:10 at 3.2s into the video: --video-sync-sec 3.2 --csv-sync-mmss 0:10.
| Flag | Description |
|---|---|
--output out.mp4 |
Custom output filename |
--fields time,depth,temp,pressure,hr |
Which values are displayed |
--column-map time=TIME,depth=Depth |
Manual CSV column mapping (keys: time, depth, temp, pressure, hr, plus date/clock for the wall-clock columns) |
--clip "video|video_sync|csv_sync[|out]" |
Repeatable, for multi-clip jobs |
--merge-output dive_full.mp4 |
Combine every clip into this one dive video, see above |
--codec auto|avc1|H264|hevc|H265|mp4v|XVID|MJPG |
Video codec (overlay mode only); auto/H264/avc1 → libx264, hevc/H265 → libx265 |
--preset ultrafast…placebo |
H264/H265 encoder preset (speed vs. compression), default veryfast |
--hw-accel |
Hardware encoding (Intel Quick Sync, NVIDIA NVENC, or AMD AMF) for H264/H265, falling back to software automatically; the actual encoder used is printed/shown |
--show-graph |
Small depth-profile graph (overlay mode only) |
--interpolate |
Smoothly interpolates field values between samples (cubic spline) instead of carrying the last known reading forward |
--mode overlay|subtitles |
See Output modes above |
--auto-sync, --base-clip, --base-video-sync-sec |
Automatic sync, see above |
Allowed fields: time, depth, temp, pressure, hr.
- If no CSV time has been reached yet at the start of the video, only the dive time is shown.
- Missing CSV values (e.g. temperature in individual rows) are skipped automatically.
- By default the last known measurement is carried forward.
- The original audio track is preserved , if present.
