Utilities for aligning GoPro (or other camera) video timestamps with GPX tracks. The primary tool, gpx_splitter.py,
crops a GPX track to match a video clip so you can sync footage and GPS data for mapping or overlays. The repository
also includes a Svelte + Vite frontend in frontend/ and a Python/FastAPI backend (managed with Poetry) in backend/.
- Extracts video start time and duration from EXIF metadata via
exiftool. - Matches GPX track points closest to the video start/end times and writes a cropped GPX.
- Falls back to file modification time when metadata is missing, with clear warnings.
- Works with GPX files that include timezone-aware timestamps (UTC recommended).
- Renders GPX tracks into MP4 map animations with OpenStreetMap tiles.
- Renders batch GPX route animations into a ZIP when paired with video durations; batch renders are all-or-nothing, so a failed pair prevents ZIP creation.
- Supports multi-clip trimming and telemetry overlay rendering (heart rate, speed, elevation).
- Python 3.13+
exiftoolavailable in yourPATH(forgpx_splitter.py)- Backend Python dependencies installed via Poetry (
gpxpy,matplotlib,pillow,numpy,fastapi, etc.) ffmpegavailable in yourPATHwhen exporting video- Node.js 20+ and npm (for the frontend)
Clone or download this repository. Install the system tools, then install project dependencies:
# Core dependency for gpx_splitter.py
brew install exiftool # macOS
sudo apt-get update && sudo apt-get install -y libimage-exiftool-perl # Ubuntu/Debian
# ffmpeg is required to write video output
brew install ffmpeg # macOS
sudo apt-get install -y ffmpeg # Ubuntu/Debian
# Backend dependencies
cd backend
poetry install
# Frontend dependencies
cd ../frontend
npm ciRun the splitter by providing the video file and the GPX file. Optionally set an output path.
python3 backend/src/gpx_helper/gpx_splitter.py /path/to/video.MP4 /path/to/track.gpx \
-o /path/to/track.cropped.gpxIf -o/--output is omitted, the script writes to <input>.cropped.gpx next to the original GPX file.
Run the backend and frontend together from the frontend directory:
cd frontend
npm run devThis starts the FastAPI backend at http://localhost:8000 and the Vite frontend at http://localhost:4173.
Poetry dependencies must already be installed in backend/, and npm dependencies must already be installed in frontend/.
If you only need the frontend server, run npm run dev:frontend from frontend/. If you only need the backend API,
run npm run dev:backend from frontend/.
The backend includes a FastAPI service for GPX trimming, map animation, and telemetry video rendering.
For normal local development, use npm run dev from frontend/ to start the API and frontend together. To run only
the API, use:
cd frontend
npm run dev:backendExample requests:
curl -X POST http://localhost:8000/api/v1/gpx/trim-by-time \
-F gpx_file=@/path/to/track.gpx \
-F start_time=2025-11-02T17:02:23Z \
-F end_time=2025-11-02T17:07:00Z \
--output trimmed.gpxcurl -X POST http://localhost:8000/api/v1/gpx/trim-by-video \
-F gpx_file=@/path/to/track.gpx \
-F start_time=2025-11-02T17:02:23Z \
-F end_time=2025-11-02T17:07:00Z \
-F duration_seconds=277 \
--output trimmed.gpxcurl -X POST http://localhost:8000/api/v1/gpx/trim-by-videos \
-F gpx_file=@/path/to/track.gpx \
-F 'clips_json=[{"start_time":"2025-11-02T17:02:23Z","end_time":"2025-11-02T17:07:00Z","duration_seconds":277}]' \
--output trimmed.zipcurl -X POST http://localhost:8000/api/v1/gpx/map-animate \
-F gpx_file=@/path/to/track.gpx \
-F duration_seconds=45 \
-F resolution=1920x1080 \
--output route.mp4
# Ask the backend for an ETA before rendering (JSON response)
curl -X POST http://localhost:8000/api/v1/gpx/map-animate/estimate \
-F gpx_file=@/path/to/track.gpx \
-F duration_seconds=45 \
-F resolution=1920x1080curl -X POST http://localhost:8000/api/v1/gpx/telemetry-video \
-F gpx_file=@/path/to/track.gpx \
-F duration_seconds=45 \
-F resolution=1080x1080 \
-F telemetry_type=heart_rate \
-F background_color=transparent \
--output telemetry.webm
# Transparent telemetry overlays use WebM with alpha. Use an opaque custom
# background_color such as #000000 when you want an MP4 export.
# Ask for telemetry render ETA (JSON response)
curl -X POST http://localhost:8000/api/v1/gpx/telemetry-video/estimate \
-F gpx_file=@/path/to/track.gpx \
-F duration_seconds=45 \
-F resolution=1080x1080 \
-F telemetry_type=heart_ratemap_animator.py turns a GPX track into an MP4 that draws the route over token-free built-in map layers, including
OpenStreetMap and Satellite (Esri World Imagery). It converts coordinates to Web Mercator (EPSG:3857) so they align
with the basemap and hides chart axes for a clean map view.
Usage:
python3 backend/src/gpx_helper/map_animator.py route.gpx 45 1920x1080 -o route.mp4route.gpx: input GPX track45: duration in seconds1920x1080: output resolution (width x height)-o route.mp4(optional): output file name; defaults tooutput.mp4
The script fetches the selected built-in map tiles via folium and writes MP4 video with ffmpeg.
- Reads creation and duration metadata from the video (UTC) using
exiftool. - Parses the GPX track, finds the points closest to the video start and end times, and keeps only that segment.
- Writes the cropped GPX file to the requested location.
Video start (Local): 2025-11-02T17:02:23+00:00
Video end (Local): 2025-11-02T17:07:00+00:00
Start time HH:MM:SS: 17:02:23
End time HH:MM:SS: 17:07:00
Cropped GPX written to: /path/to/track.cropped.gpx
If GoPro splits a recording into multiple files and their timestamps need correction, update the metadata (UTC) with exiftool:
exiftool -overwrite_original -P -api QuickTimeUTC=0 \
-CreateDate="2025:11:29 18:42:49" \
-ModifyDate="2025:11:29 18:42:49" \
-MediaCreateDate="2025:11:29 18:42:49" \
-MediaModifyDate="2025:11:29 18:42:49" \
-TrackCreateDate="2025:11:29 18:42:49" \
-TrackModifyDate="2025:11:29 18:42:49" \
GX020427.MP4exiftoolnot found: Ensure it is installed and available in yourPATH.- Missing or incomplete metadata: The script will fall back to the file's modification time and print a warning; verify your video metadata when accuracy matters.
- No timestamps in GPX: The crop requires GPX points with valid
<time>elements that include timezone information (e.g., a trailingZfor UTC).
The Svelte-based landing page lives in frontend/. To run it with the backend locally:
cd frontend
npm install
npm run devThen open the local Vite URL: http://localhost:4173.
Feel free to open issues or submit pull requests with improvements or bug fixes. PRs are validated with the
GitHub Actions workflow in .github/workflows/pr-structure-check.yml, which runs backend tests
(poetry run python -m unittest discover -s tests) and frontend checks/tests/build
(npm run check, npm test -- --run, npm run build).
This repository is available for use without limitations.