Clipify
Encontre os momentos mais engraçados de um vídeo, corte-os como clipes independentes, opcionalmente reformate 16:9 → 9:16 (panorâmica de rosto ou tela dividida) e adicione legendas no estilo Opus palavra por palavra.
Entradas
- Um caminho de arquivo de vídeo (o usuário fornecerá; caso contrário, pergunte)
- Opcional: formato solicitado (9:16, 16:9, 1:1) — se não fornecido, pergunte após os candidatos serem escolhidos
- Opcional: preferência de estilo de legenda — se não fornecido, pergunte antes de adicionar legendas
Ferramentas (use apenas o caminho mais rápido)
- Whisper:
whisper --model tiny.en --word_timestamps True --output_format json(≈10× mais rápido quesmall.en; qualidade suficiente para inglês). Para não inglês:--model base(remova--language). - ffmpeg: a decodificação por hardware é opcional e específica da plataforma — use
-hwaccel autoou omita (macOS:videotoolbox; Linux:vaapi/cuda/nenhum). Adicione-preset ultrafastpara renderizações. Use-c:v libx264 -crf 20para o mestre final. - Numpy para alinhamento de áudio (correlação cruzada FFT). Sem necessidade de scipy/cv2.
- Scripts:
<skill-dir>/scripts/(onde<skill-dir>é o diretório que contém este SKILL.md — tipicamente~/.claude/skills/clipify/)analyze.py— linha do tempo do locutor a partir de dois arquivos de movimento ROIbuild_pan.py— expressão x de corte ffmpeg com cortes secosbuild_ass.py— legendas ASS no estilo opus a partir do JSON do whisperaudio_align.py— encontra o deslocamento de um subclipe em uma fonte mais longa
Diretório de trabalho: /tmp/clipify/ (crie no início, deixe artefatos para depuração).
Fluxo de Trabalho
Etapa 1 — Encontre as partes mais engraçadas
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
Leia o JSON resultante (ou .txt) e escolha 3–5 clipes candidatos. Sinais engraçados a procurar:
- Punchlines e reações: palavras como "what", "wait", "no way", risadas, "haha", xingamentos
- Momentos de virada: pergunta de configuração → resposta inesperada
- Pausas estranhas: segmento do Whisper com longo intervalo, ou preenchimento ("uh", "um")
- Autodepreciação / frases citáveis: frases declarativas curtas que se sustentam sozinhas
- Picos de áudio: detecte via
ffmpeg -af volumedetectou procure por idas e vindas rápidas (segmentos curtos alternados do Whisper)
Para cada candidato, proponha: [início, fim, por que é engraçado, título sugerido]. Mire em clipes de 10–25s. Mostre a lista e deixe o usuário confirmar/escolher.
Etapa 2 — Corte cada clipe escolhido
ffmpeg -y -ss "$START" -t "$DURATION" -i "$VIDEO" -c copy /tmp/clipify/clip_$N.mp4
(Use -c copy para corte instantâneo. Re-codifique apenas se os cortes precisarem ser precisos por quadro.)
Etapa 3 — Decida o formato de saída
Pergunte ao usuário (pule se já especificado): "9:16 (TikTok / Reels), 16:9 (YouTube), ou 1:1 (feed do Insta)?"
Etapa 4 — Se 16:9 → 9:16: panorâmica entre rostos vs tela dividida
Detecte a proporção de origem com ffprobe. Se a origem for 16:9 e o destino for 9:16, pergunte:
"Duas opções: (a) panorâmica de corte seco que segue quem está falando (um rosto na tela por vez), ou (b) tela dividida empilhada com ambos os rostos visíveis. Qual você quer?"
Pule a pergunta se houver apenas um rosto (clipe de um único locutor). Para um único locutor, basta cortar centralizado.
Etapa 4a — Panorâmica entre rostos (recomendado para diálogo de cabeça falante com cortes rápidos)
-
Localize as duas ROIs de rosto. Amostre um quadro:
ffmpeg -ss <meio> -i <clipe> -frames:v 1 /tmp/clipify/probe.jpg. Leia-o. Determine a área da boca+queixo de cada rosto comox,y,w,hno espaço de pixels da origem. (Não é necessário cv2 — a câmera é estática dentro de um clipe; um quadro é suficiente.) Verifique desenhando caixas: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.jpgItere no máximo duas vezes. As caixas devem cobrir boca + queixo e evitar mãos/microfones. Não ajuste demais — a diferença de quadros é tolerante.
-
Extraia a energia de movimento por quadro em cada 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 - -
Construa a linha do tempo do locutor (permanência mínima de 1.0s — interjeições curtas são mescladas ao locutor anterior):
python3 <skill-dir>/scripts/analyze.py /tmp/clipify/L.txt /tmp/clipify/R.txt 1.0 > /tmp/clipify/segments.json -
Escolha coordenadas x da panorâmica para uma faixa vertical 9:16 a partir da origem. Com origem W=1920 e destino W=1080, largura da faixa de corte = 608.
- LEFT_X =
face_left_center_x - 304(limite ≥ 0) - RIGHT_X =
face_right_center_x - 304(limite ≤ source_W - 608)
- LEFT_X =
-
Gere a expressão x de corte seco e renderize:
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.mp4Origem 1920×1080 presumida; para origem 4K, reduza a resolução primeiro ou dobre todas as coordenadas.
Etapa 4b — Tela dividida (ambos os rostos sempre visíveis)
Duas colunas empilhadas, 1080×960 cada. O rosto do locutor ativo fica em cima — a sobreposição alterna nas mudanças de locutor.
[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]
Construa <RIGHT_SPEAKER_ENABLE> a partir de segments.json como between(t,a,b)+between(t,a,b)+... sobre os segmentos do locutor direito. Os cortes das colunas devem segmentar ~720×640 ao redor de cada rosto (1.125:1 para corresponder a 1080×960).
Etapa 5 — Adicione legendas
Pergunte uma vez (apenas se o usuário não especificou um estilo):
"Três estilos de legenda: opus (branco grande e negrito, destaque amarelo na palavra ativa), karaokê (blocos de 4 palavras, destaque verde), minimal (Helvetica limpa, sem destaque). Ou cole um exemplo que você goste."
Se colarem uma imagem de referência/exemplo: corresponda a fonte, tamanho, peso, cor, posição e animação o mais próximo possível — escreva um ASS personalizado manualmente ou estenda build_ass.py.
Caso contrário, use o predefinido:
# Re-execute whisper no clipe cortado para timestamps precisos relativos ao início do clipe
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
Grave as legendas:
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"
Etapa 6 — Entregue
- Salve cada saída em
<source_dir>/clipify_out/(crie o diretório se não existir) - Imprima uma linha por clipe: nome, duração, o que era engraçado, caminho da saída
- Imprima o primeiro caminho de saída (ou abra-o — Linux
xdg-open <caminho>, macOSopen <caminho>) para que o usuário possa verificá-lo - Ofereça-se para iterar (estilo diferente, ROI diferente, trocar para tela dividida, reajustar tempo das legendas)
Armadilhas (lições de execuções anteriores — não repita)
- Não ajuste demais as ROIs. No máximo duas iterações. A diferença de movimento é tolerante — ROIs mais largas cobrindo boca+queixo funcionam bem mesmo se não perfeitamente centradas na boca.
- Cuidado com cortes de cena dentro de um clipe. Execute
ffmpeg -filter:v "select='gt(scene,0.3)',showinfo" -f null -para contar os cortes. Se um clipe 16:9→9:16 tiver muitos cortes, as ROIs fixas de rosto funcionam apenas para a cena dominante; avise o usuário e ofereça-se para escolher um clipe de uma única tomada ou aceitar enquadramento descentralizado durante os cortes. - A resolução da origem importa. Se a origem for 4K, reduza a resolução para 1920×1080 primeiro (mais rápido, bom para saída 9:16) ou multiplique todas as coordenadas ROI/panorâmica por 2.
- Legendas queimadas na origem. Alguns clipes "brutos" ainda têm legendas. Nesse caso, encontre o mestre sem legendas via correlação cruzada de áudio (
audio_align.py) e corte a partir dele. - Não execute whisper na fonte completa se um clipe curto for suficiente. Execute whisper no clipe cortado após a Etapa 2; execute whisper na fonte completa na Etapa 1 apenas se precisar de uma transcrição para encontrar momentos engraçados.
- Declare o plano em uma linha, depois aja. Não narre cada iteração.