Open source · MIT · runs on your Mac
A 47-minute
tennis recording in.
A 3-minute
highlight reel out.
Tennis Evolution cuts every rally out of a long phone recording, burns the hit count, venue, wall clock and source timestamp onto the picture, and checks the result before handing it over. Your footage never leaves your machine — no upload, no account, no cloud.
01 / Labels
Same footage. The labels do the talking.
Every segment carries the hit count, the venue, the wall clock of that rally and where it came from in the source file — so a clip still makes sense once it's out of context.
Switching keeps the playback position, so you see the same frame with and without the overlay. PNG overlay · Pillow
02 / The reel
Watch it, then run it on your own recording.
Every tennis highlight video here is the tool's real output, not a mock-up. The hero numbers come from one 47-minute phone recording; the player below is a shorter CC0 sample cut by the same pipeline.
Sample footage: CC0 clips run through the same pipeline — source 2:16, four rallies, reel 1:39.
These are lossless stream copies — cut on keyframes, nothing is re-encoded. The demo source is SDR; on a real iPhone HDR recording the 10-bit HLG frames and rotation metadata pass through untouched.
03 / Pipeline
Four moves, and one of them is refusing a shortcut.
The interesting decisions are all at the edges: where a cut is allowed to land, how the segments get joined, and what counts as done.
-
txt · json
Bring a highlight list
Any tool that emits time ranges works: sliceapp's export, your own detector, or a text file you typed. Detection is deliberately out of scope.
-
-c copy
Cut on keyframes, not on milliseconds
Stream copy is bit-exact and keeps HDR, 10-bit and rotation metadata; cuts snap to the nearest keyframe, which is why the pipeline never re-encodes a clip.
-
trim → concat
Assemble in one pass
Rallies are trimmed and concatenated into a single encode. The obvious concat-demuxer shortcut produced a permanent 1.01 s audio offset and broken HEVC reference frames, so this tool refuses it and reports the problem instead.
-
manifest.json
Verify before it counts as done
Every output is checked for clean decode, zero start offset, A/V duration match, non-black first/last frames and non-silent audio. Failing checks make the command exit non-zero.
04 / Output
What you get.
One directory per run. Nothing is hidden in a project file: the reel, the cuts, the label layers and the report sit side by side — a tennis rally cutter that hands you files, not a subscription.
reel_labeled.mp4The reel, labels burned in — the file you upload.reel_plain.mp4Same assembly, clean picture, for your own titles.clips/One lossless cut per rally. Original HDR, rotation burned in.labels/*.pngTransparent overlays, reusable in any editor.highlights.normalized.jsonThe segment list actually used.manifest.jsonEvery parameter and every check result.
The manifest is the contract: every parameter and every check result, machine-readable. A non-zero exit code means a check failed — scripts and agents can trust the run without watching the video.
The label, enlarged
- Hits
- 14The shot count, and the sort key for the reel.
- Venue
- Whatever you pass with
--place— Riverside Tennis Club in this sample. - Wall clock
- Recording start plus the rally offset, in the timezone you choose.
- Source
- 0:02Where the rally sits in the original file.
05 / Install
Install in one line.
Not on PyPI yet, so install straight from the repository. ffmpeg has to be on your PATH — the tool will not quietly bundle 40 MB of it for you.
# ffmpeg first: brew install ffmpeg · sudo apt install ffmpeg fonts-noto-cjk
pipx install git+https://github.com/bobobo2026/tennis-evolution
# a highlight list from sliceapp, your detector, or by hand
tennis-evolution all IMG_5155.MOV --highlights highlights.txt --top 5 --place "Your Club" --timezone Asia/Shanghai --outdir out
Requires Python 3.11+ and ffmpeg/ffprobe. macOS and Linux. Source, issues and the full pipeline notes live on GitHub.
06 / Privacy
The footage never leaves the machine.
A full match contains your friends, your club and your mistakes. Local-first is not a slogan here: there is no server to send it to, and no queue to wait in.
Your footage stays on disk
No account, no queue
Free and MIT licensed
Works on HDR iPhone video
07 / FAQ
Questions that keep coming up.
Short answers below. The longer reasoning, including the ffmpeg arguments and the failures that shaped them, is in the repository.
Do I need a hit detector?
No. Bring any list of time ranges: sliceapp's export, your own detector, or a text file you typed by hand. Detection is deliberately out of scope, because a hit counter tuned to one venue's reverb makes the whole pipeline brittle. If you already export ranges from sliceapp, this doubles as a slice app alternative: the export drops straight in.
Does it work with iPhone HDR video?
Yes. iPhone tennis video highlights keep their HLG tags, 10-bit colour and rotation metadata: the lossless cuts are untouched, and the reel comes out as an HDR HEVC tennis video at the same 10-bit depth. Dolby Vision's dynamic metadata cannot be written back by x265 and is dropped on re-encode; the HDR10/HLG base layer stays intact.
Is it macOS only?
The CLI runs on macOS and Linux with ffmpeg on PATH and Python 3.11 or newer. A packaged Mac app is on the roadmap; star the repository to follow it.
Can I try it without installing?
Every clip on this page is pre-rendered, so you can judge the output first. There is no online renderer on purpose: generating one means uploading an hour of your footage to someone else's server.
How long does it take?
A 47-minute source with five rallies takes about 3 to 5 minutes end to end on an M-series Mac, almost all of it the single x265 encode. The local web UI shows live progress for each stage.
What does it cost?
Nothing. It is MIT licensed and the source is on GitHub: no account, no trial clock, no telemetry.
Something else? Open an issue on GitHub ↗