Tennis-Ballwechsel-Schnitt (tennis-rally-cut)
Abgeleitet von einem echten Schnitt am 08.11.2026: Zwei Wandtraining-Videos, 14 Ballwechsel extrahiert. Kernansatz: Höre nach Schlägergeräuschen, überprüfe mit Bildmaterial.
Was der Benutzer will (harte Regeln)
- Ein Ballwechsel = Vom ersten Schlag bis der Ball tot ist. Der gesamte Ballwechsel in einem Clip.
- Ballwechsel dürfen nicht zerschnitten werden. 10 aufeinanderfolgende Schläge gegen die Wand = ein Clip, nicht aufgeteilt in 2er- oder 3er-Blöcke.
- Bälle aufsammeln, Gehen, Pausen, Handy-Checks, Geräte-Aufräumen werden alle entfernt.
- Originalvideo bleibt unverändert. Das fertige Material wird im Ordner
Schnitt/im selben Verzeichnis wie das Original abgelegt.
Warum Ton und nicht Bild
- Jeder Schlag erzeugt zwei scharfe Geräusche: Schläger trifft Ball und Ball trifft Wand/Boden. Beim Aufsammeln oder Gehen gibt es diese nicht.
- Bildbewegung funktioniert nicht: Beim Aufsammeln/Gehen ist die Bildänderung größer als beim Spielen – nicht unterscheidbar.
- Bildmaterial wird nur zur Stichproben-Prüfung verwendet.
Werkzeuge
Skript: scripts/rally_cut.py. Benötigt nur ffmpeg, ffprobe und numpy.
| Unterbefehl | Funktion |
|---|---|
probe | Zeigt Auflösung, Bitrate, Ausrichtung; warnt vor Komprimierung durch Chat-Apps |
analyze | Findet Schlägergeräusche, gruppiert zu Ballwechseln, schreibt <Name>.segments.json |
sheets | Erstellt je ein Standbild für Anfang und Ende jedes Ballwechsels zur Überprüfung |
cut | Schneidet einzelne Ballwechsel als separate mp4-Dateien, schreibt Ausschnittliste.txt |
align | Berechnet Zeitversatz zwischen neuem hochauflösenden Original und alter Version |
Im Folgenden bezeichnet RC den absoluten Pfad des Skripts. Zwischendateien werden in einem temporären Arbeitsverzeichnis abgelegt, nicht im Benutzervideoverzeichnis.
Ablauf
1. Zuerst die Qualität prüfen
python3 -I $RC probe <Video...>
- Bei „Verdacht auf Komprimierung durch Chat-App“ den Benutzer zuerst informieren. Der Schnitt selbst verschlechtert die Qualität kaum – wenn das Original unscharf ist, bleibt das Ergebnis unscharf.
- Von WeChat übertragene Videos: Das Original liegt oft im WeChat-Cache mit dem gleichen Dateinamen plus
_raw-Suffix:~/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/<Account>/msg/video/<Jahr-Monat>/<Name>_raw.mp4Erst nachdem der Benutzer in WeChat „Originalvideo ansehen“ ausgewählt hat, wird es heruntergeladen. Mitmdfindnach Hinzugefügt-Zeitpunkt suchen:mdfind -onlyin ~ 'kMDItemContentTypeTree == "public.movie" && kMDItemDateAdded >= $time.iso(<Datum>)' - Nachdem das hochauflösende Original gefunden wurde, mit
alignprüfen, ob der Versatz 0 ist – dann können die bereits berechneten Schnittpunkte direkt verwendet werden.
2. Ballwechsel finden
python3 -I $RC analyze <Video...> --work <temporäres_Verzeichnis>
Es gibt einen Zeitbalken der Schlaggeräusch-Häufigkeit pro Sekunde und eine Liste der Ballwechsel aus. Standardparameter:
| Parameter | Standard | Bedeutung |
|---|---|---|
--k | 2.5 | Faktor, um wie viel lauter als der Hintergrund ein Geräusch als Schlag zählt. Bei lauter Umgebung höher stellen |
--gap | 6 | Wenn der Abstand zwischen zwei Geräuschen mehr als 6 Sekunden beträgt, gilt der Ballwechsel als beendet. Das ist der Schlüssel, um Ballwechsel nicht zu zerschneiden |
--min-hits | 6 | Weniger als 6 Schläge werden als Rauschen verworfen (Schritte, Handy) |
--pre / --post | 1 / 1.5 | Zusätzliche Sekunden vor/nach einem Ballwechsel, um Schwünge und Landepunkte vollständig zu erfassen |
Abwägung bei --gap:
- 4 Sekunden würde „wenige Sekunden nach dem Tod des Balls sofort neu aufschlagen“ in zwei Abschnitte teilen.
- 6 Sekunden hält sie zusammen. Da der Benutzer keine Zerstückelung wünscht, ist der Standard 6.
- Nur wenn der Benutzer explizit sagt „Jeder Neustart soll ein eigener Abschnitt sein“, auf 4 ändern.
3. Visuelle Überprüfung (nicht überspringbar)
python3 -I $RC sheets <Video> --segments <json> --work <temp>/sheets
Jedes Bild einzeln betrachten, Schwerpunkte:
- Letzter Ballwechsel des Videos: Oft geht am Ende jemand auf die Kamera zu, nimmt das Handy – die Geräusche werden als Schlag fehlinterpretiert. Dann
endauf etwa 1,5 Sekunden nach dem letzten echten Schlag setzen. - Kurze Ballwechsel mit nur einstelligen Schlägen: Könnten Rauschen sein. Löschen, wenn niemand im Bild schwingt.
- Lücken von etwa 5 Sekunden zwischen Ballwechseln: Prüfen, ob es sich um Balltod und Neustart handelt und nicht um einen übersehenen Schlag.
Im segments.json direkt das segments-Array bearbeiten: start/end ändern oder ganze Einträge löschen.
4. Schneiden
python3 -I $RC cut <Video> --segments <json> --out <Video-Verzeichnis>/Schnitt --label A
- Mehrere Quellvideos mit unterschiedlichen
--label(A, B, …), Dateinamen wieVideoA_03_2m54s-3m22s.mp4– Zeiten beziehen sich auf das Originalvideo. - Muss neu encodiert werden (Standard H.264, CRF 17). Originalvideos haben oft Schlüsselbilder alle 2–3 Sekunden; direktes Kopieren würde falsch schneiden.
- Bei Hochformat-Videos verarbeitet ffmpeg die Ausrichtungsinformation automatisch – das Ergebnis ist korrekt ausgerichtet.
- Falls im Zielverzeichnis bereits alte Ergebnisse existieren, diese in
Schnitt_Altumbenennen (Sicherung) – nicht einfach überschreiben oder löschen. - 1080p 60fps-Material braucht lange zum Enkodieren – im Hintergrund laufen lassen.
5. Abnahme und Bericht
- Mit ffprobe bei jedem fertigen Clip Dauer, Auflösung und Bildrate prüfen.
- Von ein oder zwei Clips ein Einzelbild auslesen: Ausrichtung stimmt, Bild klar.
- Bericht erstellen: Anzahl der Clips, Längenbereich, Ausgabeverzeichnis (mit
file://-Link, den Finder öffnen kann – bei chinesischen Pfaden Prozentkodierung verwenden). - Unsichere Clips (z. B. sehr kurze Ballwechsel mit wenigen Schlägen) explizit auflisten – Benutzer entscheidet selbst über Löschung.
- Anmerken, dass nur Stichproben visuell geprüft wurden, nicht jeder Clip komplett.
Fallstricke (gelernt)
| Falle | Folge | Lösung |
|---|---|---|
ffmpeg in einer Schleife ohne -nostdin aufgerufen | ffmpeg frisst die restlichen Zeilen der Liste, Schnittpunkte falsch, Absturz | Skript hat -nostdin ergänzt |
| Zeit als „Bilder × 10 ms“ umgerechnet | Ein Bild entspricht tatsächlich 220 Samplepunkten – nach 7 Minuten 1 Sekunde Drift | Skript rechnet mit Samplepunkten |
| Bildbewegung zum Erkennen von Spiel genutzt | Bälle aufsammeln hat stärkere Bewegung als Spielen | Nur Ton verwendet |
| Komprimiertes Weiterleitungsvideo verwendet | Ergebnis unscharf, Benutzer denkt an schlechten Schnitt | Schritt 1: immer vorher probe |