Clipify
Trouvez les moments les plus drôles d'une vidéo, découpez-les en clips autonomes, reformatez éventuellement du 16:9 au 9:16 (face-pan ou écran partagé) et gravez des sous-titres mot par mot de type opus.
Entrées
- Un chemin de fichier vidéo (l'utilisateur le fournira ; sinon demandez-le)
- Optionnel : format demandé (9:16, 16:9, 1:1) — s'il n'est pas donné, demandez après que les candidats soient sélectionnés
- Optionnel : préférence de style de sous-titres — s'il n'est pas donné, demandez avant le sous-titrage
Outils (utilisez uniquement le chemin le plus rapide)
- Whisper :
whisper --model tiny.en --word_timestamps True --output_format json(≈10 × plus rapide quesmall.en; qualité suffisante pour l'anglais). Pour le non-anglais :--model base(omettre--language). - ffmpeg : le décodage matériel est optionnel et spécifique à la plateforme — utilisez
-hwaccel auto, ou omettez-le (macOS :videotoolbox; Linux :vaapi/cuda/aucun). Ajoutez-preset ultrafastpour les rendus. Utilisez-c:v libx264 -crf 20pour le master final. - Numpy pour l'alignement audio (corrélation croisée FFT). Pas besoin de scipy/cv2.
- Scripts :
<skill-dir>/scripts/(où<skill-dir>est le répertoire contenant ce SKILL.md — généralement~/.claude/skills/clipify/)analyze.py— ligne du temps des locuteurs à partir de deux fichiers de mouvement ROIbuild_pan.py— expression x de crop ffmpeg avec coupures franchesbuild_ass.py— sous-titres ASS de style opus à partir du JSON de Whisperaudio_align.py— trouver le décalage d'un sous-clip dans une source plus longue
Répertoire de travail : /tmp/clipify/ (le créer au début, laisser les artefacts pour le débogage).
Flux de travail
Étape 1 — Trouver les parties les plus drôles
mkdir -p /tmp/clipify
ffmpeg -y -i "$VIDEO" -vn -ac 1 -ar 16000 /tmp/clipify/audio.wav
whisper /tmp/clipify/audio.wav --model tiny.en --word_timestamps True --output_format json --output_dir /tmp/clipify --language en
Lisez le JSON résultant (ou .txt) et sélectionnez 3 à 5 clips candidats. Signaux amusants à rechercher :
- Chutes et réactions : mots comme "quoi", "attends", "pas possible", rire, "haha", jurons
- Moments de renversement : question de mise en place → réponse inattendue
- Pauses gênantes : segment Whisper avec un long écart, ou remplissage ("euh", "hum")
- Auto-dérision / répliques citables : phrases déclaratives courtes qui se suffisent à elles-mêmes
- Pics audio : détecter via
ffmpeg -af volumedetectou chercher un va-et-vient rapide (segments Whisper courts alternés)
Pour chaque candidat, proposez : [start, end, pourquoi-c'est-drôle, titre suggéré]. Visez des clips de 10 à 25 s. Affichez la liste et laissez l'utilisateur confirmer/sélectionner.
Étape 2 — Découper chaque clip choisi
ffmpeg -y -ss "$START" -t "$DURATION" -i "$VIDEO" -c copy /tmp/clipify/clip_$N.mp4
(Utilisez -c copy pour un découpage instantané. Ne ré-encodez que si les coupes doivent être précises à l'image.)
Étape 3 — Décider du format de sortie
Demandez à l'utilisateur (passez s'il a déjà spécifié) : "9:16 (TikTok / Reels), 16:9 (YouTube) ou 1:1 (fil Instagram) ?"
Étape 4 — Si 16:9 → 9:16 : panoramique entre les visages vs écran partagé
Détectez le format source avec ffprobe. Si la source est en 16:9 et la cible en 9:16, demandez :
"Deux options : (a) panoramique à coupures franches qui suit la personne qui parle (un seul visage à l'écran à la fois), ou (b) écran partagé superposé avec les deux visages visibles. Laquelle préférez-vous ?"
Ignorez la question s'il n'y a qu'un seul visage (clip avec un seul locuteur). Pour un seul locuteur, centrez le recadrage.
Étape 4a — Panoramique entre les visages (recommandé pour un dialogue rapide à têtes parlantes)
-
Localisez les deux ROI des visages. Échantillonnez une image :
ffmpeg -ss <milieu> -i <clip> -frames:v 1 /tmp/clipify/probe.jpg. Lisez-la. Estimez à l'œil la zone bouche+menton de chaque visage sous forme dex,y,w,hdans l'espace pixel de la source. (Pas besoin de cv2 — la caméra est statique dans un clip ; une seule image suffit.) Vérifiez en dessinant des boîtes :ffmpeg -i probe.jpg -vf "drawbox=x=$LX:y=$LY:w=$LW:h=$LH:[email protected]:t=4,drawbox=x=$RX:y=$RY:w=$RW:h=$RH:[email protected]:t=4" verify.jpgItérez au plus deux fois. Les boîtes doivent couvrir la bouche + le menton et éviter les mains/micros. Ne sur-ajustez pas — la différence d'image est tolérante.
-
Extrayez l'énergie de mouvement par image dans chaque ROI :
ffmpeg -y -i clip.mp4 -filter_complex " [0:v]split=2[a][b]; [a]crop=$LW:$LH:$LX:$LY,format=gray,tblend=all_mode=difference,signalstats,metadata=mode=print:key=lavfi.signalstats.YAVG:file=/tmp/clipify/L.txt[la]; [b]crop=$RW:$RH:$RX:$RY,format=gray,tblend=all_mode=difference,signalstats,metadata=mode=print:key=lavfi.signalstats.YAVG:file=/tmp/clipify/R.txt[ra] " -map "[la]" -f null - -map "[ra]" -f null - -
Construisez la ligne du temps des locuteurs (temps de séjour minimum 1.0 s — les courtes interjections fusionnent avec le locuteur précédent) :
python3 <skill-dir>/scripts/analyze.py /tmp/clipify/L.txt /tmp/clipify/R.txt 1.0 > /tmp/clipify/segments.json -
Choisissez les coordonnées x du panoramique pour une bande verticale 9:16 à partir de la source. Avec une source W=1920 et une cible W=1080, largeur de la bande de recadrage = 608.
- GAUCHE_X =
face_left_center_x - 304(clamp ≥ 0) - DROITE_X =
face_right_center_x - 304(clamp ≤ source_W - 608)
- GAUCHE_X =
-
Générez l'expression x de coupure franche et effectuez le rendu :
EXPR=$(python3 <skill-dir>/scripts/build_pan.py /tmp/clipify/segments.json $LEFT_X $RIGHT_X) ffmpeg -y -i clip.mp4 -filter_complex \ "[0:v]crop=608:1080:x='$EXPR':y=0,scale=1080:1920:flags=lanczos[v]" \ -map "[v]" -map 0:a -c:v libx264 -preset fast -crf 20 -pix_fmt yuv420p \ -c:a aac -b:a 192k /tmp/clipify/clip_panned.mp4Source 1920×1080 supposée ; pour une source 4K, réduisez d'abord ou doublez toutes les coordonnées.
Étape 4b — Écran partagé (les deux visages toujours visibles)
Deux tuiles empilées, 1080×960 chacune. La tuile du locuteur actif est en haut — la superposition bascule lors des changements de locuteur.
[0:v]split=2[a0][a1];
[a0]crop=Wcrop:Hcrop:LX_tile:LY_tile,scale=1080:960,split=2[lt0][lt1];
[a1]crop=Wcrop:Hcrop:RX_tile:RY_tile,scale=1080:960,split=2[rt0][rt1];
[lt0][rt0]vstack[layoutL];
[rt1][lt1]vstack[layoutR];
[layoutL][layoutR]overlay=0:0:enable='<RIGHT_SPEAKER_ENABLE>'[v]
Construisez <RIGHT_SPEAKER_ENABLE> à partir de segments.json sous la forme between(t,a,b)+between(t,a,b)+... sur les segments du locuteur de droite. Les recadrages des tuiles doivent cibler environ 720×640 autour de chaque visage (1.125:1 pour correspondre à 1080×960).
Étape 5 — Ajouter des sous-titres
Demandez une fois (uniquement si l'utilisateur n'a pas déjà spécifié un style) :
"Trois styles de sous-titres : opus (gras blanc grand, surlignage jaune du mot actif), karaoké (groupes de 4 mots, surlignage vert), minimal (Helvetica propre, pas de surlignage). Ou collez un exemple que vous aimez."
S'ils collent une image de référence/un exemple : adaptez la police, la taille, le poids, la couleur, la position et l'animation aussi fidèlement que possible — écrivez un ASS personnalisé à la main ou étendez build_ass.py.
Sinon, utilisez le préréglage :
# Réexécutez whisper sur le clip découpé pour des horodatages précis par rapport au début du clip
whisper /tmp/clipify/clip_panned.mp4 --model tiny.en --word_timestamps True --output_format json --output_dir /tmp/clipify --language en
python3 <skill-dir>/scripts/build_ass.py /tmp/clipify/clip_panned.json /tmp/clipify/captions.ass opus
Gravez les sous-titres :
ffmpeg -y -i /tmp/clipify/clip_panned.mp4 -vf "subtitles=/tmp/clipify/captions.ass" \
-c:v libx264 -preset fast -crf 20 -c:a copy "$OUTPUT.mp4"
Étape 6 — Livrer
- Sauvegardez chaque sortie dans
<source_dir>/clipify_out/(créez le répertoire s'il manque) - Affichez une ligne par clip : nom, durée, ce qui était drôle, chemin de sortie
- Affichez le premier chemin de sortie (ou ouvrez-le — Linux
xdg-open <chemin>, macOSopen <chemin>) pour que l'utilisateur puisse le vérifier - Proposez d'itérer (style différent, ROI différent, passer à l'écran partagé, retimbrer les sous-titres)
Pièges (leçons des exécutions précédentes — ne les répétez pas)
- Ne sur-ajustez pas les ROI. Deux itérations maximum. La différence de mouvement est tolérante — des ROI plus larges couvrant la bouche+menton fonctionnent bien même s'ils ne sont pas parfaitement centrés sur la bouche.
- Attention aux changements de plan à l'intérieur d'un clip. Exécutez
ffmpeg -filter:v "select='gt(scene,0.3)',showinfo" -f null -pour compter les coupures. Si un clip 16:9→9:16 comporte de nombreuses coupures, les ROI de visage fixes ne fonctionnent que pour la scène dominante ; prévenez l'utilisateur et proposez soit de choisir un clip en plan unique, soit d'accepter un cadrage décentré pendant les coupures. - La résolution source compte. Si la source est en 4K, réduisez-la d'abord en 1920×1080 (plus rapide, bien pour une sortie 9:16) ou multipliez toutes les coordonnées ROI/panoramique par 2.
- Sous-titres incrustés dans la source. Certains clips "bruts" ont encore des sous-titres. Si c'est le cas, trouvez le master sans sous-titres par corrélation croisée audio (
audio_align.py) et découpez à partir de là. - N'exécutez pas whisper sur la source complète de long métrage si un court clip suffit. Whisper le clip découpé après l'étape 2 ; ne whisper la source complète qu'à l'étape 1 si vous avez besoin d'une transcription pour trouver les moments drôles.
- Énoncez le plan en une ligne, puis agissez. Ne racontez pas chaque itération.