Communitygithub.com

Bruto para Shorts: cortes 9:16 com legendas

Encontra os melhores momentos de um vídeo longo (piadas, reações e pausas via Whisper, ou machadadas, golpes e batidas via detecção de transientes de áudio), confirma que cada um aparece no quadro, corta clipes independentes, reenquadra 16:9 para 9:16 com pan seguindo o rosto ou tela dividida e grava legendas palavra por palavra.

O que é Bruto para Shorts: cortes 9:16 com legendas?

Receita Bruto para Shorts, passo 2 (vire vertical), de louisedesadeleer/clipify, uma skill do Claude Code com mais de 600 estrelas no GitHub; instale com npx skills add louisedesadeleer/clipify. Primeiro escolha o modo: o modo diálogo transcreve com Whisper (timestamps por palavra com tiny.en, base para outros idiomas) e procura piadas, viradas, pausas constrangedoras e picos de áudio; o modo ação pula o Whisper e detecta transientes por fluxo espectral na faixa de 1–6 kHz para cortar em cada impacto; o híbrido intercala os dois. Ele propõe de 3 a 5 candidatos, extrai um quadro em cada um para confirmar que a ação está visível, corta os clipes, gera um recorte que segue o rosto ou tela dividida em 9:16, acelera os pans para revelações mais enxutas e grava legendas ASS palavra por palavra no estilo Opus com ffmpeg (master libx264, CRF 20).

Funciona com✓Claude Code~Codex CLI~Cursor
npx skills add louisedesadeleer/clipify

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Clipify

Encontre momentos marcantes em um vídeo, corte-os como clipes independentes, opcionalmente reformate 16:9 → 9:16 (panorâmica facial ou tela dividida), faça movimentos de câmera temporais para revelações mais rápidas e queime legendas no estilo opus, palavra por palavra.

Modos

Escolha um antes do Passo 1. A escolha muda como você encontra os momentos e se as legendas se aplicam.

  • Modo Diálogo (padrão para podcasts, entrevistas, cabeças falantes): Whisper transcreve, você procura por punchlines / reações / pausas estranhas. → Passo 1A. Legendas no Passo 5.
  • Modo Ação (cortar madeira, esportes, percussão, bateria, qualquer coisa com sons de impacto repetidos): pule o Whisper completamente, execute detect_transients.py para encontrar cada golpe, corte em cada um. → Passo 1B. Pule o Passo 5; os sons de golpe são o ritmo.
  • Híbrido: modo diálogo para seções de fala, modo ação para o resto, intercale. Use ambos 1A e 1B.

Se o usuário disser "Não estou falando nisso", você está no modo ação. Se eles disserem "corte em cada X" (corte, golpe, murro, batida de bateria, balanço, pisada), você está no modo ação. Padrão para modo diálogo caso contrário.

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 for fornecido, pergunte após os candidatos serem selecionados
  • Opcional: preferência de estilo de legenda — se não for fornecida, pergunte antes de legendagem

Ferramentas (use apenas o caminho mais rápido)

  • Whisper: whisper --model tiny.en --word_timestamps True --output_format json (≈10× mais rápido que small.en; qualidade suficiente para inglês). Para não-inglês: --model base (remova --language).
  • ffmpeg: adicione -hwaccel videotoolbox para decodificação e -preset ultrafast para renders. Use -c:v libx264 -crf 20 para o master final.
  • Numpy para alinhamento de áudio (correlação cruzada FFT). Sem scipy/cv2 necessário.
  • 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 falante a partir de dois arquivos de movimento ROI
    • build_pan.py — expressão de corte x do ffmpeg com cortes secos
    • build_ass.py — legendas ASS estilo opus a partir de JSON do Whisper
    • audio_align.py — encontrar deslocamento de um sub-clipe em uma fonte mais longa
    • detect_transients.py — encontrar sons de impacto agudos (modo ação); veja --help

Diretório de trabalho: /tmp/clipify/ (crie o diretório no início, deixe artefatos para depuração).


Fluxo de Trabalho

Passo 1A — Encontrar momentos de diálogo (modo diálogo)

mkdir -p /tmp/clipify
ffmpeg -y -hwaccel videotoolbox -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 para procurar:

  • Punchlines e reações: palavras como "o quê", "espera", "não acredito", risadas, "haha", palavrões
  • Momentos de reviravolta: pergunta de configuração → resposta inesperada
  • Pausas estranhas: segmento Whisper com longo intervalo, ou preenchimentos ("uh", "um")
  • Auto-depreciação / frases citáveis: frases declarativas curtas que se sustentam sozinhas
  • Picos de áudio: detecte via ffmpeg -af volumedetect ou 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.

Passo 1B — Encontrar cada golpe (modo ação)

Substitua o Whisper pela detecção de transitórios de áudio. O detector encontra impactos agudos (machado na madeira, punho no saco de pancadas, pele de bateria, bola no taco, madeira caindo em uma pilha) computando fluxo espectral na banda de 1–6 kHz — o impulso de banda larga que um impacto cria e que o ambiente/vento não cria.

ffmpeg -y -hwaccel videotoolbox -i "$VIDEO" -vn -ac 1 -ar 16000 /tmp/clipify/audio.wav
python3 <skill-dir>/scripts/detect_transients.py /tmp/clipify/audio.wav \
  --min-flux 30 --min-gap 0.6 > /tmp/clipify/strikes.json

Ajustes:

  • --min-flux 30 é o padrão são. Impactos reais registram 50–300; ambiente/vento fica em 5–15. Muitos falsos positivos → aumente para 40–50. Poucos acertos → diminua para 20.
  • --min-gap 0.6 (segundos) é a distância mínima entre dois golpes. Bateria rápida pode precisar de 0.2; cortar madeira é suficiente em 0.6–1.0.
  • --band 1000:6000 cobre impactos em madeira/metal/vidro. Batidas pesadas (bumbo, golpes no corpo) ficam mais abaixo (--band 200:2000); assobios, tinidos, estalos de caixa ficam mais acima (--band 4000:10000).
  • Para fontes com múltiplos clipes (uma pasta de clipes de celular), execute o detector em cada clipe e escolha um conjunto variado entre ângulos. Mire em 50–65 golpes para uma montagem de 45–60s a ~0.9s por cena.

Cada "cena" deve começar ~0.4s antes do golpe (mostrando a preparação) e terminar ~0.5s após (golpe + breve acompanhamento). Isso dá ~0.9s por batida.

Passo 1.5 — Verificar se cada candidato está em quadro

Sempre faça isso antes de renderizar, em qualquer modo. A detecção de áudio encontra o som de um evento; a câmera pode não tê-lo capturado. Pular isso é a forma número 1 de receber feedback do usuário como "você cortou em cortes onde nem estou visível".

# Amostre um quadro em cada tempo de golpe candidato T:
for T in $STRIKE_TIMES; do
  ffmpeg -y -ss "$T" -i "$VIDEO" -frames:v 1 -vf scale=320:-1 \
    /tmp/clipify/verify_${T}.jpg
done

Leia cada imagem de verificação e descarte o candidato se:

  • O sujeito está abaixado, fora do quadro, ou atrás de um obstáculo
  • Há um polegar na lente (sim, isso acontece — e você só pega visualmente)
  • É claramente uma fonte de som diferente (batida de porta, ferramenta caída) que acionou o detector

Para ~30 candidatos, isso é uma chamada rápida de ffmpeg cada mais uma única leitura de lote de imagens. Um seguro barato.

Passo 2 — Cortar 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.)

Passo 3 — Decidir o formato de saída

Pergunte ao usuário (pule se já especificaram): "9:16 (TikTok / Reels), 16:9 (YouTube), ou 1:1 (feed do Insta)?"

Passo 4 — Se 16:9 → 9:16: panorama entre rostos vs tela dividida

Detecte a proporção de aspecto da fonte com ffprobe. Se a fonte for 16:9 e o destino for 9:16, pergunte:

"Duas opções: (a) panorama com 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ê prefere?"

Pule a pergunta se houver apenas um rosto (clipe de um único falante). Para um único falante, apenas centralize o corte.

Passo 4a — Panorama entre rostos (recomendado para diálogo rápido de cabeças falantes)

  1. Localize as duas ROIs de rosto. Amostre um quadro: ffmpeg -ss <meio> -i <clipe> -frames:v 1 /tmp/clipify/probe.jpg. Leia-o. Observe a área da boca+queixo de cada rosto como x,y,w,h no espaço de pixels da fonte. (Sem necessidade de 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.jpg
    

    Itere 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.

  2. Extraia 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 -
    
  3. Construa a linha do tempo do falante (tempo mínimo de permanência de 1.0s — interjeições curtas se fundem ao falante anterior):

    python3 <skill-dir>/scripts/analyze.py /tmp/clipify/L.txt /tmp/clipify/R.txt 1.0 > /tmp/clipify/segments.json
    
  4. Escolha coordenadas x do panorama para uma faixa vertical 9:16 da fonte. Com fonte W=1920 e destino W=1080, largura da faixa de corte = 608.

    • LEFT_X = centro_rosto_esquerdo_x - 304 (limitar ≥ 0)
    • RIGHT_X = centro_rosto_direito_x - 304 (limitar ≤ source_W - 608)
  5. 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 -hwaccel videotoolbox -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.mp4
    

    Fonte 1920×1080 assumida; para fonte 4K, reduza a resolução primeiro ou dobre todas as coordenadas.

Passo 4b — Tela dividida (ambos os rostos sempre visíveis)

Dois blocos empilhados, 1080×960 cada. O bloco do falante ativo fica no topo — a sobreposição alterna nas mudanças de falante.

[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 falante direito. Os cortes dos blocos devem mirar ~720×640 ao redor de cada rosto (1.125:1 para corresponder a 1080×960).

Passo 4c — Revelações com variação temporal (opcional)

Quando o usuário quer um plano lento / panorâmico / revelação encurtado ("corte isso pela metade") sem perder o arco, use rampa de velocidade em vez de cortar. Cortar força você a descartar parte do arco; a rampa de velocidade preserva todas as batidas em um andamento mais alto.

# Velocidade 2x: arco de 12s → saída de 6s, tom de áudio preservado via atempo
ffmpeg -y -ss "$START" -i "$CLIP" -t "$ORIG_DUR" \
  -vf "setpts=PTS/2,scale=1080:1920:flags=lanczos,fps=30" \
  -af "atempo=2.0" \
  -c:v libx264 -preset fast -crf 21 -pix_fmt yuv420p \
  -c:a aac -b:a 128k /tmp/clipify/clip_2x.mp4

Regras práticas:

  • 1.5x — movimento humano que deve parecer "ligeiramente rápido" sem parecer acelerado
  • 2x — revelação mais contundente; ainda parece natural
  • 3x–4x — vibe de time-lapse (encadeie atempo=2.0,atempo=2.0 para 4x; atempo aceita apenas 0.5–2.0 por instância de filtro)
  • Remova -af atempo e silencie o segmento se o áudio for apenas ambiente/vento e o efeito de esquilo for perturbador

Útil quando o resto do corte é rítmico (montagem de cortes, bate-papo de ida e volta) e a revelação pareceria um ponto morto.

Passo 5 — Adicionar legendas

Pule esta etapa no modo ação — os sons dos golpes são o ritmo e as legendas apenas poluem o visual.

Pergunte uma vez (apenas se o usuário ainda não especificou um estilo):

"Três estilos de legenda: opus (branco grande e negrito, destaque de palavra ativa amarelo), karaokê (blocos de 4 palavras, destaque verde), mínimo (Helvetica limpo, sem destaque). Ou cole um exemplo que você goste."

Se eles colarem uma imagem/exemplo de referência: 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.

Senão, 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

Queime 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"

Passo 6 — Entregar

  • 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 de saída
  • Abra a primeira saída com open <caminho> para que o usuário possa verificar
  • Ofereça-se para iterar (estilo diferente, ROI diferente, trocar para tela dividida, reajustar legendas)

Armadilhas (lições de execuções anteriores — não repita)

  • Detecção de áudio ≠ evento visível. Sempre execute o Passo 1.5 (verificar cada quadro candidato) antes de renderizar. O detector encontra o som de um corte, não se o cortador está em quadro. Golpes onde o sujeito está curvado, fora da câmera, ou onde um polegar está na lente ainda acionam o detector de áudio. Pegue-os antes de renderizar.
  • Banda de fluxo espectral importa. Padrão --band 1000:6000 cobre impactos em madeira/metal/vidro. Batidas pesadas graves (bumbo, golpes no corpo) precisam de --band 200:2000. Assobios, tinidos, estalos de caixa precisam de --band 4000:10000. Se --min-flux 30 não retornar nada, tente baixar para 15 primeiro; se retornar milhares, tente uma banda diferente antes de aumentar o limite.
  • Áudio de rampa de velocidade em pares. atempo aceita apenas 0.5–2.0 em um único filtro; para 4x, encadeie atempo=2.0,atempo=2.0. Sem atempo, setpts sozinho dá áudio de esquilo.
  • Não ajuste demais as ROIs. Máximo de 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 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 selecionar um clipe de tomada única ou aceitar enquadramento descentralizado durante os cortes.
  • Resolução da fonte importa. Se a fonte for 4K, reduza a resolução para 1920×1080 primeiro (mais rápido, suficiente para saída 9:16) ou multiplique todas as coordenadas ROI/panorâmica por 2.
  • Legendas queimadas na fonte. 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 de longa-metragem se um clipe curto for suficiente. Execute whisper no clipe cortado após o Passo 2; execute whisper na fonte completa apenas no Passo 1 se você precisar de uma transcrição para encontrar momentos engraçados.
  • Declare o plano em uma linha, depois aja. Não narre cada iteração.

Habilidades Relacionadas