Tennis Evolution open source · v0.1

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.

The reference run: a 47 minute 29 second iPhone recording in, one 3 minute 49 second reel of the five longest rallies out.

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.

demo-reel.mp4

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.

reel_labeled.mp4 · h.264 · 1280×720 · 1:39 · labels burned in

Sample footage: CC0 clips run through the same pipeline — source 2:16, four rallies, reel 1:39.

Lossless cut · 010:28

clip-01.mp4

Bit-exact stream copy, labels burned in.

Lossless cut · 020:19

clip-02.mp4

Keyframe-aligned, no reference frames lost.

Lossless cut · 030:23

clip-03.mp4

No re-encode, so no generation loss.

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.

  1. 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.

    txt · json
  2. 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.

    -c copy
  3. 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.

    trim → concat
  4. 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.

    manifest.json

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.
decode clean start offset a/v duration match expected duration frame not black audio not silent

The label, enlarged

A tennis rally frame with the corner label enlarged: hit count, venue, wall clock and the source timestamp of that rally.
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.

Terminal — install + run
# 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 ↗