Communitygithub.com

tennis-rally-cut – Découpage automatique de rallyes de tennis

Découpe automatiquement une longue vidéo d'entraînement de tennis (mur, lance-balles, échange) en rallyes complets en détectant les bruits de frappe, en supprimant les ramassages, déplacements et pauses.

Qu'est-ce que tennis-rally-cut – Découpage automatique de rallyes de tennis ?

Cette compétence permet de découper automatiquement des vidéos d'entraînement de tennis (ou sports similaires comme le ping-pong, le badminton, le squash) en rallyes individuels complets, en se basant sur l'analyse audio des bruits de frappe plutôt que sur les changements visuels. L'utilisateur peut simplement demander « découpe ma vidéo d'entraînement en segments » ou fournir un dossier de vidéos. L'outil utilise un script Python (`scripts/rally_cut.py`) s'appuyant sur ffmpeg, ffprobe et numpy, avec plusieurs sous-commandes : `probe` pour vérifier la qualité, `analyze` pour détecter les frappes et assembler les rallyes, `sheets` pour générer des vignettes de contrôle, `cut` pour découper les vidéos et `align` pour recaler sur une version HD. La règle impérative est qu'un rallye doit rester entier (aucun découpage interne), les ramassages, déplacements et pauses étant supprimés. L'utilisateur est invité à vérifier visuellement les transitions via des vignettes avant le découpage final. Le résultat est placé dans un dossier `剪辑/` adjacent aux vidéos originales, avec des noms de fichiers incluant la source et les timestamps.

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

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Découpage de rallyes de tennis (tennis-rally-cut)

Issu d'un montage réel du 8 novembre 2026 : deux vidéos d'entraînement contre un mur, découpées en 14 rallyes. Idée principale : Écouter pour trouver les rallyes, regarder pour vérifier.

Ce que l'utilisateur attend (règles strictes)

  • Un rallye = de la première frappe jusqu'à la mort de la balle. Le rallye entier dans un seul segment.
  • Interdiction de couper un rallye. Si 10 frappes consécutives contre le mur, un seul segment, pas de sous-découpage.
  • Ramassages, déplacements, pauses, utilisation du téléphone, rangement du matériel : tout supprimé.
  • Vidéo originale inchangée. Le résultat est placé dans un dossier 剪辑/ au même endroit que la vidéo originale.

Pourquoi utiliser le son et non l'image

  • Chaque frappe produit deux bruits nets : « raquette qui frappe » et « balle qui touche le mur/sol ». Pas de tels bruits pendant le ramassage ou la marche.
  • L'image ne suffit pas : les déplacements et ramassages modifient plus la scène que les frappes, impossible à distinguer.
  • L'image sert uniquement à vérifier ponctuellement.

Outils

Script : scripts/rally_cut.py. Dépend uniquement de ffmpeg, ffprobe et numpy.

Sous-commandeRôle
probeVoir résolution, débit, orientation ; indique si compressé par messagerie
analyzeDétecter les frappes, les assembler en rallyes, écrire <nom>.segments.json
sheetsGénérer une vignette de début et de fin pour chaque rallye, pour vérification
cutDécouper chaque rallye en MP4 indépendant, écrire 片段清单.txt
alignQuand on remplace par une source HD, calculer le décalage temporel entre ancienne et nouvelle version

Ci-dessous RC désigne le chemin absolu du script. Les fichiers intermédiaires sont placés dans le répertoire temporaire de la session, pas dans le dossier vidéo de l'utilisateur.

Procédure

1. Vérifier la qualité

python3 -I $RC probe <vidéo...>
  • Si un message « probablement compressé par messagerie » apparaît, en informer d'abord l'utilisateur. Le découpage lui-même ne dégrade quasiment pas la qualité ; si l'original est flou, le résultat le sera.
  • Pour les vidéos envoyées via WeChat, l'original se trouve souvent dans le cache WeChat, avec le suffixe _raw : ~/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/<compte>/msg/video/<année-mois>/<nom>_raw.mp4 Il est téléchargé seulement après que l'utilisateur a tapé « Voir la vidéo originale ». On peut chercher avec mdfind par date d'ajout : mdfind -onlyin ~ 'kMDItemContentTypeTree == "public.movie" && kMDItemDateAdded >= $time.iso(<date>)'
  • Une fois la source HD trouvée, utiliser align pour confirmer que le décalage est 0, puis réutiliser les points de coupe déjà calculés.

2. Trouver les rallyes

python3 -I $RC analyze <vidéo...> --work <répertoire_temporaire>

Cela affiche une timeline du nombre de frappes par seconde et une liste de rallyes. Paramètres par défaut :

ParamètreDéfautSignification
--k2.5Seuil de détection : un bruit est considéré comme une frappe s'il est k fois plus fort que le bruit de fond. Augmenter si environnement bruyant
--gap6Si deux frappes sont espacées de plus de gap secondes, le rallye est considéré terminé. Clé pour ne pas couper les rallyes
--min-hits6Les séquences de moins de 6 frappes sont ignorées (bruits de pas, téléphone)
--pre / --post1 / 1.5Secondes supplémentaires ajoutées avant/après chaque rallye pour garantir que le geste et le point de chute sont complets

Compromis sur --gap :

  • 4 secondes couperait un rallye si le joueur recommence à jouer quelques secondes après la mort de la balle.
  • 6 secondes les laisse ensemble. Par défaut 6, car l'utilisateur ne veut pas couper les rallyes.
  • Ne passer à 4 que si l'utilisateur dit explicitement « chaque reprise doit être un segment séparé ».

3. Vérifier visuellement (ne pas sauter)

python3 -I $RC sheets <vidéo> --segments <json> --work <répertoire_temporaire>/sheets

Examiner chaque vignette, vérifier en priorité :

  • Le dernier rallye de la vidéo : souvent, à la fin, quelqu'un s'approche de la caméra ou prend le téléphone, le bruit peut être interprété comme une frappe. Si c'est le cas, modifier end pour le placer environ 1,5 seconde après la dernière frappe réelle.
  • Les rallyes courts avec peu de frappes : peuvent être du bruit. Si personne ne frappe sur l'image, supprimer.
  • Les trous d'environ 5 secondes entre rallyes : confirmer qu'il s'agit d'une reprise de jeu après mort de la balle, et non d'une frappe non détectée.

Modifier directement le tableau segments dans segments.json : changer start/end, ou supprimer une entrée.

4. Découper

python3 -I $RC cut <vidéo> --segments <json> --out <dossier_vidéo>/剪辑 --label A
  • Pour plusieurs sources, utiliser des --label différents (A, B…). Les noms de fichiers auront la forme vidéoA_03_2m54s-3m22s.mp4, avec les timestamps dans la source originale.
  • Re-encodage obligatoire (par défaut H.264, CRF 17). Les keyframes de la source sont souvent espacées de 2-3 secondes ; une copie directe décalerait les coupes.
  • Pour les vidéos en portrait, ffmpeg gère automatiquement l'orientation, le résultat sera correct.
  • Si le dossier de destination contient déjà des fichiers, les renommer en 剪辑_旧版 (ancienne version) avant de les remplacer, ne pas écraser ni supprimer directement.
  • Les vidéos 1080p 60 images/s sont plus lentes à encoder ; les lancer en arrière-plan.

5. Valider puis rapporter

  • Utiliser ffprobe pour vérifier la durée, la résolution et le framerate de chaque segment produit.
  • Extraire une image de un ou deux segments pour contrôler l'orientation et la netteté.
  • Dans le rapport, indiquer : nombre de segments, plage de durée de chaque segment, chemin du dossier de sortie (avec un lien file:// cliquable dans le Finder, les chemins en chinois doivent être encodés en pourcentage).
  • Mentionner les segments douteux (par exemple, rallyes très courts avec peu de frappes) et laisser l'utilisateur décider de les conserver ou non.
  • Préciser que seuls des contrôles ponctuels ont été effectués, pas une visualisation complète de chaque segment.

Pièges rencontrés

PiègeConséquenceCorrection
Appel à ffmpeg dans une boucle sans -nostdinffmpeg dévore la suite de la liste, points de coupe décalés, plantageAjouté dans le script
Conversion temporelle « nombre d'images × 10 ms »Une image correspond en réalité à 220 échantillons ; dérive de 1 seconde après 7 minutesConversion basée sur le nombre d'échantillons
Utilisation des mouvements visuels pour détecter les frappesLes ramassages créent plus de mouvement que les frappesUtilisation exclusive du son
Utilisation d'une vidéo compressée par messagerieRésultat flou, l'utilisateur pense que le découpage a mal fonctionnéÉtape 1 : probe

Skills associés