oriVideoGen turns orienteering action-cam footage into YouTube-ready videos with synchronized Garmin telemetry and a moving Livelox-style map inset — without recording your screen for the entire race.
Typical manual workflow:
- Film with a DJI Osmo Action (or similar)
- Export a Garmin
.fitactivity - Open Livelox and screen-record the replay in real time (a 60-minute race ≈ 60 minutes of recording)
- Place HR, speed, elevation, compass, and map overlays by hand in a video editor
oriVideoGen automates steps 3–4:
oriVideoGen \
--video run.mp4 \
--fit activity.fit \
--livelox "https://www.livelox.com/Viewer/...?classId=123456" \
--output youtube_ready.mp4- Garmin FIT telemetry overlays — elevation, gradient, distance, heart rate, speed gauge, heading/compass
- Livelox map inset (required) — circular runner-centered map with course, controls, and purple route trail
- Two map modes
render(default/fast): download map + georef + route once, draw each frame programmaticallycapture(fallback): headless Chromium seeks the Livelox timeline and screenshots the map (~10–15 min for a 1 h race instead of watching it live)
- Time sync — auto-align via video
creation_timevs FIT start, or set--offsetmanually - Preview —
--preview 30to validate sync before a full encode - Hardware encode — optional NVIDIA NVENC via
--hw-encode - NLE export —
--overlay-onlyfor a transparent overlay clip (Premiere / Resolve)
| Dependency | Notes |
|---|---|
| Python 3.11+ | Developed/tested on 3.11–3.14 |
| FFmpeg | Must be on PATH (ffmpeg and ffprobe) |
| Chromium (optional) | Only for --map-mode capture (playwright install chromium) |
Windows (recommended):
winget install Gyan.FFmpegRestart the terminal so PATH updates.
macOS: brew install ffmpeg
Linux: use your package manager (apt install ffmpeg, etc.)
git clone https://github.com/TunaSen/oriVideoGen.git
cd oriVideoGen
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate
pip install -e .
# Optional: only needed for --map-mode capture
playwright install chromiumVerify:
oriVideoGen --help
ffmpeg -version-
Upload / open your route in Livelox and copy the viewer URL. It must include
classId, for example:https://www.livelox.com/Viewer/My-Event/H21?classId=382140 -
Export the Garmin activity as
.fit(Garmin Connect / watch export). -
Have your DJI (or other) MP4 ready.
-
Run a short preview first:
oriVideoGen \
--video "C:\Videos\DJI_run.mp4" \
--fit "C:\Garmin\activity.fit" \
--livelox "https://www.livelox.com/Viewer/My-Event/H21?classId=382140" \
--output "C:\Videos\preview.mp4" \
--preview 30- If overlays look early/late, adjust
--offset(see Time sync), then render the full video without--preview.
oriVideoGen [OPTIONS]
| Option | Required | Description |
|---|---|---|
--video PATH |
yes | Source POV / action-cam MP4 |
--fit PATH |
yes | Garmin FIT activity file |
--livelox URL |
yes | Livelox viewer URL containing classId |
--output PATH |
yes | Output MP4 (or directory for --map-only) |
--offset FLOAT |
no | Manual sync offset in seconds |
--map-mode auto|render|capture |
no | Default auto |
--participant TEXT |
no | Prefer a Livelox route matching this name |
--preview FLOAT |
no | Only encode the first N seconds |
--overlay-only |
no | Export transparent overlay MOV (qtrle) |
--map-only |
no | Export circular map inset PNG sequence |
--hw-encode |
no | Prefer h264_nvenc when available |
--layout PATH |
no | YAML layout overrides |
--cache-dir PATH |
no | Override Livelox cache directory |
--force-fetch |
no | Ignore cached Livelox downloads |
--capture-fps FLOAT |
no | Screenshot FPS for capture mode (default 1.0) |
-v / --verbose |
no | Debug logging |
Default layout matches a common YouTube orienteering POV style:
| Position | Content | Source |
|---|---|---|
| Top-left | Elevation, gradient, total distance | Garmin FIT |
| Top-right | Heart rate (BPM) | Garmin FIT |
| Bottom-right | Circular orienteering map | Livelox |
| Bottom-right (below map) | Speed gauge (km/h) | Garmin FIT |
| Bottom-center | Heading / compass (°) | Garmin FIT |
Customize sizes and margins with a YAML file (see examples/sample_config.yaml):
oriVideoGen ... --layout examples/sample_config.yamlThe map overlay is required. oriVideoGen never skips it; it only speeds up how the data is acquired.
POST https://www.livelox.com/Data/ClassInfowith theclassIdfrom your URL- Download the class blob (map metadata)
- Save map PNG, projection / world-file matrix, controls, course line, and GPS route
- For each video frame, crop the map around the runner and draw the purple trail + controls
Results are cached under:
~/.cache/oriVideoGen/livelox/<classId>/
Re-runs of the same class are much faster unless you pass --force-fetch.
If scraping fails or you want the original Livelox rendering:
oriVideoGen ... --map-mode capturePlaywright opens the viewer headlessly, seeks the replay timeline (no real-time playback), and screenshots the map region. For a one-hour race at 1 FPS this is typically on the order of 10–15 minutes, still far less than a live screen recording.
Try programmatic fetch/render; if route/controls are missing or fetch fails, fall back to capture.
Offset meaning:
video_time = fit_activity_time + offset
- Positive offset — FIT activity starts later than the video (skip the beginning of the video to reach FIT start)
- Negative offset — FIT started before the video
Auto methods:
--offsetif provided (always wins)- Else video container
creation_timevs FIT session/record start - Else assume both start together (
offset = 0) and print a hint
Practical tip: always validate with --preview 30 before a full-length encode.
oriVideoGen \
--video run.mp4 \
--fit activity.fit \
--livelox "https://www.livelox.com/Viewer/Event/Class?classId=123456" \
--output youtube_ready.mp4 \
--hw-encodeoriVideoGen \
--video run.mp4 \
--fit activity.fit \
--livelox "https://www.livelox.com/Viewer/Event/Class?classId=123456" \
--output youtube_ready.mp4 \
--map-mode capture \
--capture-fps 1oriVideoGen \
--video run.mp4 \
--fit activity.fit \
--livelox "https://www.livelox.com/Viewer/Event/Class?classId=123456" \
--output overlay.mov \
--overlay-onlyoriVideoGen \
--video run.mp4 \
--fit activity.fit \
--livelox "https://www.livelox.com/Viewer/Event/Class?classId=123456" \
--output map_frames \
--map-only \
--preview 60oriVideoGen/
├── oriVideoGen/
│ ├── cli.py # Click CLI entry point
│ ├── config/layout.py # Overlay layout defaults + YAML loader
│ ├── telemetry/ # FIT parse + per-frame interpolation
│ ├── sync/ # ffprobe + creation_time / offset aligner
│ ├── livelox/ # ClassInfo fetch, route decode, Playwright capture
│ └── render/ # Widgets, overlay canvas, FFmpeg compositor
├── examples/sample_config.yaml
├── tests/
├── pyproject.toml
└── README.md
pip install -e ".[dev]"
pytestTests include FIT/interpolation unit tests, Livelox URL/georef/route decoder tests, and an FFmpeg smoke compose (synthetic clip, no network).
- Livelox’s official public API does not expose maps/routes for copyright reasons. This tool uses the same class viewer data endpoints that the web UI loads for a class you can already open, plus an optional headless capture of that viewer.
- Map images remain under the event organizer’s copyright. Use this with your own footage and routes; do not redistribute maps you are not allowed to share.
- Auto sync depends on trustworthy video timestamps. DJI metadata is usually good; if clocks disagree, use
--offset. - Full-length 4K encodes can take tens of minutes; use
--previewand--hw-encodewhen available.
MIT. See repository license metadata. Orienteering maps shown in your outputs remain subject to the rights of the map authors / organizers.