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-commande | Rôle |
|---|---|
probe | Voir résolution, débit, orientation ; indique si compressé par messagerie |
analyze | Détecter les frappes, les assembler en rallyes, écrire <nom>.segments.json |
sheets | Générer une vignette de début et de fin pour chaque rallye, pour vérification |
cut | Découper chaque rallye en MP4 indépendant, écrire 片段清单.txt |
align | Quand 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.mp4Il est téléchargé seulement après que l'utilisateur a tapé « Voir la vidéo originale ». On peut chercher avecmdfindpar date d'ajout :mdfind -onlyin ~ 'kMDItemContentTypeTree == "public.movie" && kMDItemDateAdded >= $time.iso(<date>)' - Une fois la source HD trouvée, utiliser
alignpour 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ètre | Défaut | Signification |
|---|---|---|
--k | 2.5 | Seuil 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 |
--gap | 6 | Si deux frappes sont espacées de plus de gap secondes, le rallye est considéré terminé. Clé pour ne pas couper les rallyes |
--min-hits | 6 | Les séquences de moins de 6 frappes sont ignorées (bruits de pas, téléphone) |
--pre / --post | 1 / 1.5 | Secondes 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
endpour 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
--labeldifférents (A, B…). Les noms de fichiers auront la formevidé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ège | Conséquence | Correction |
|---|---|---|
Appel à ffmpeg dans une boucle sans -nostdin | ffmpeg dévore la suite de la liste, points de coupe décalés, plantage | Ajouté 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 minutes | Conversion basée sur le nombre d'échantillons |
| Utilisation des mouvements visuels pour détecter les frappes | Les ramassages créent plus de mouvement que les frappes | Utilisation exclusive du son |
| Utilisation d'une vidéo compressée par messagerie | Résultat flou, l'utilisateur pense que le découpage a mal fonctionné | Étape 1 : probe |