Communitygithub.com

Tennis-Rally-Cut – Tennis-Rally-Cut

Schneidet lange Tennis-Trainingsvideos (gegen die Wand, mit Ballmaschine oder im Match) automatisch anhand von Schlägergeräuschen in einzelne vollständige Ballwechsel. Entfernt Bälle aufsammeln, Gehen und Pausen.

Was ist Tennis-Rally-Cut – Tennis-Rally-Cut?

Dieses Skill automatisiert die Zerlegung langer Tennis-Trainingsvideos – egal ob gegen die Wand, mit Ballmaschine oder im Match – in einzelne vollständige Ballwechsel. Der Kernansatz ist „Hören nach Schlägergeräuschen, Überprüfen mit Bildmaterial“. Es erkennt jedes Schlaggeräusch und fasst aufeinanderfolgende Schläge zu einem Ballwechsel zusammen (standardmäßig bei einer Pause von mindestens 6 Sekunden). Aufsammeln, Gehen, Pausen und Handy-Checks werden entfernt – genau das, was Nutzer typischerweise mit „Schneide die Ballwechsel raus“ oder „Entferne das Bällesammeln“ meinen. Das Skript (scripts/rally_cut.py) benötigt nur ffmpeg, ffprobe und numpy. Es unterstützt die Subbefehle probe (Überprüfung der Videoqualität), analyze (Analysiere Schlaggeräusche und Ballwechsel), sheets (Erstelle Standbilder für die visuelle Überprüfung), cut (Schneide die finalen Clips) und align (Ausrichten bei hochauflösenden Originalen). Das Tool funktioniert auch für Tischtennis, Badminton, Squash und andere Trainingsvideos mit deutlichen Schlaggeräuschen. Wichtig: Es zerschneidet keine Ballwechsel – ein Ballwechsel mit 10 aufeinanderfolgenden Schlägen bleibt ein einziger Clip.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/layyyback/tennis-rally-cut/tree/HEAD/tennis-rally-cut

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

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.

UnterbefehlFunktion
probeZeigt Auflösung, Bitrate, Ausrichtung; warnt vor Komprimierung durch Chat-Apps
analyzeFindet Schlägergeräusche, gruppiert zu Ballwechseln, schreibt <Name>.segments.json
sheetsErstellt je ein Standbild für Anfang und Ende jedes Ballwechsels zur Überprüfung
cutSchneidet einzelne Ballwechsel als separate mp4-Dateien, schreibt Ausschnittliste.txt
alignBerechnet 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.mp4 Erst nachdem der Benutzer in WeChat „Originalvideo ansehen“ ausgewählt hat, wird es heruntergeladen. Mit mdfind nach Hinzugefügt-Zeitpunkt suchen: mdfind -onlyin ~ 'kMDItemContentTypeTree == "public.movie" && kMDItemDateAdded >= $time.iso(<Datum>)'
  • Nachdem das hochauflösende Original gefunden wurde, mit align prü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:

ParameterStandardBedeutung
--k2.5Faktor, um wie viel lauter als der Hintergrund ein Geräusch als Schlag zählt. Bei lauter Umgebung höher stellen
--gap6Wenn 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-hits6Weniger als 6 Schläge werden als Rauschen verworfen (Schritte, Handy)
--pre / --post1 / 1.5Zusä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 end auf 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 wie VideoA_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_Alt umbenennen (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)

FalleFolgeLösung
ffmpeg in einer Schleife ohne -nostdin aufgerufenffmpeg frisst die restlichen Zeilen der Liste, Schnittpunkte falsch, AbsturzSkript hat -nostdin ergänzt
Zeit als „Bilder × 10 ms“ umgerechnetEin Bild entspricht tatsächlich 220 Samplepunkten – nach 7 Minuten 1 Sekunde DriftSkript rechnet mit Samplepunkten
Bildbewegung zum Erkennen von Spiel genutztBälle aufsammeln hat stärkere Bewegung als SpielenNur Ton verwendet
Komprimiertes Weiterleitungsvideo verwendetErgebnis unscharf, Benutzer denkt an schlechten SchnittSchritt 1: immer vorher probe

Verwandte Skills