Communitygithub.com

tennis-rally-cut (recorte de ralis de tênis)

Corta automaticamente vídeos longos de treino de tênis (contra parede, com máquina de bolas ou jogo) em ralis completos, removendo pegadas de bolas, caminhadas e pausas com base na detecção de sons de batida.

O que é tennis-rally-cut (recorte de ralis de tênis)?

O tennis-rally-cut é uma skill que processa vídeos de treino de tênis – como contra parede, máquina de bolas ou jogo – e os divide automaticamente em ralis completos, removendo tempos mortos (pegar bola, andar, pausas). A lógica principal é baseada em áudio: detecta os sons de batida (raquete na bola, bola na parede ou no chão) e agrupa-os em segmentos com um parâmetro de lacuna (gap) configurável. A análise visual é usada apenas para verificação manual, pois movimentos como pegar bola geram mais variação de imagem do que o jogo em si. A skill suporta múltiplos formatos de vídeo (incluindo vídeos comprimidos por apps de mensagem como WeChat) e oferece comandos para inspeção, análise, geração de folhas de contato, corte e alinhamento de fontes em alta definição. É útil para jogadores, treinadores e criadores de conteúdo que desejam extrair apenas os momentos de ação de sessões de treino, e também funciona para esportes similares como tênis de mesa, badminton e squash, desde que os sons de batida sejam claros.

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

Perguntar na sua IA favorita

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

Documentação

Recorte de Ralis de Tênis (tennis-rally-cut)

Criado a partir de uma edição real em 08/11/2026: dois vídeos de treino contra parede, gerando 14 ralis. Ideia central: ouvir o som para encontrar os ralis, ver as imagens para conferir.

O que o usuário quer (regras rígidas)

  • Um rali = da primeira batida até a bola morrer. O rali inteiro em um único segmento.
  • Não cortar um rali em pedaços. Se houve 10 batidas seguidas contra a parede, é um segmento único, não pode ser dividido em vários de 2 ou 3 batidas.
  • Pegar bola, andar, pausas, pegar o celular, guardar o equipamento: tudo removido.
  • O vídeo original não é alterado. Os arquivos finais ficam na pasta Recortes/ no mesmo diretório do vídeo original.

Por que usar som e não imagem

  • Cada batida produz dois sons nítidos: "raquete na bola" e "bola na parede/chão". Pegar bola ou andar não tem esses sons.
  • Movimento na imagem não funciona: pegar bola gera mais variação de imagem do que o jogo.
  • A imagem é usada apenas para verificação pontual.

Ferramentas

Script: scripts/rally_cut.py. Depende apenas de ffmpeg, ffprobe e numpy.

SubcomandoFunção
probeVer resolução, bitrate, orientação; avisa se foi comprimido por app de mensagem
analyzeEncontrar sons de batida, agrupar em ralis, gerar <nome>.segments.json
sheetsCriar uma imagem de quadro para início e fim de cada rali, para verificação
cutCortar cada rali em mp4 independente, gerar Lista de Segmentos.txt
alignQuando trocar para a fonte HD original, calcular o deslocamento temporal entre as versões

Daqui em diante, RC é o caminho absoluto do script. Os arquivos intermediários ficam no diretório temporário da sessão, não no diretório de vídeo do usuário.

Fluxo

1. Primeiro, verifique a qualidade

python3 -I $RC probe <vídeo...>
  • Se aparecer "possivelmente comprimido por app de mensagem", avise o usuário primeiro. O corte em si quase não perde qualidade; se o original estiver borrado, o resultado também estará.
  • Vídeos enviados pelo WeChat geralmente ficam no cache com sufixo _raw: ~/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files/<conta>/msg/video/<ano-mês>/<nome>_raw.mp4 Só baixa quando o usuário clica em "Ver original" no WeChat. Use mdfind para buscar por data de adição: mdfind -onlyin ~ 'kMDItemContentTypeTree == "public.movie" && kMDItemDateAdded >= $time.iso(<data>)'
  • Depois de encontrar o original HD, use align para confirmar deslocamento zero, e então poderá reaproveitar os cortes já calculados.

2. Encontrar ralis

python3 -I $RC analyze <vídeo...> --work <dir_temporario>

Ele imprime uma barra temporal de sons de batida por segundo e a lista de ralis. Parâmetros padrão:

ParâmetroPadrãoSignificado
--k2.5Quantas vezes mais alto que o fundo para ser considerado som. Aumente em ambiente barulhento
--gap6Se dois sons tiverem mais de 6s de intervalo, o rali termina. Essencial para não picotar
--min-hits6Menos de 6 sons consecutivos são descartados como ruído (passos, pegar celular)
--pre / --post1 / 1.5Segundos extras antes/depois do rali para garantir a batida e a queda da bola

Compensação do --gap:

  • 4s dividiria "bola morta e reinício imediato" em dois segmentos.
  • 6s mantém juntos. O usuário pede para não picotar, então padrão é 6.
  • Só mude para 4 se o usuário disser explicitamente "cada reinício deve ser um segmento separado".

3. Verifique visualmente (não pule)

python3 -I $RC sheets <vídeo> --segments <json> --work <dir_temporario>/sheets

Veja cada imagem, focando em:

  • Último rali do vídeo: Pode ter alguém andando em direção à câmera, pegando celular, e o som pode ser interpretado como batida. Se vir, ajuste end para cerca de 1,5s após a última batida real.
  • Ralis curtos com poucas batidas: Podem ser ruído. Se ninguém está batendo na imagem, remova.
  • Lacunas de ~5s entre ralis: Confirme se é fim de rali e reinício, não uma batida perdida.

Edite o array segments no JSON: altere start/end, ou remova o segmento inteiro.

4. Corte

python3 -I $RC cut <vídeo> --segments <json> --out <diretório_do_vídeo>/Recortes --label A
  • Vários vídeos de origem usam --label diferentes (A, B...). Nomes dos arquivos: VideoA_03_2m54s-3m22s.mp4, com tempo no original.
  • Precisa reencodificar (padrão H.264, CRF 17). Quadros-chave do original costumam ter 2~3s de intervalo, copiar direto cortaria errado.
  • Vídeos na vertical: a orientação é tratada automaticamente pelo ffmpeg, o resultado fica correto.
  • Se a pasta de destino já tiver resultados antigos, renomeie para Recortes_antigo como backup, não sobrescreva nem delete.
  • Vídeos 1080p 60fps codificam devagar; execute em segundo plano.

5. Verifique e reporte

  • Use ffprobe para conferir duração, resolução, taxa de quadros de cada resultado.
  • Tire um quadro de um ou dois resultados: orientação correta, imagem nítida.
  • No relatório: número de segmentos, faixa de duração de cada um, diretório dos resultados (com link file:// que o Finder possa abrir; caminhos com caracteres chineses devem ser codificados em percentual).
  • Indique segmentos duvidosos (ex.: ralis curtos com poucas batidas) e deixe o usuário decidir se remove.
  • Deixe claro que apenas conferiu visualmente alguns trechos, não assistiu cada segmento do início ao fim.

Armadilhas já enfrentadas

ArmadilhaConsequênciaSolução
Chamar ffmpeg em loop sem -nostdinffmpeg engolia linhas seguintes do script, causando cortes errados e crashAdicionado ao script
Converter tempo como "quadros × 10ms"Cada quadro na verdade tem 220 amostras; 7 minutos desvia 1 segundoConverter por amostras
Usar movimento da imagem para detectar jogoPegar bola mexia mais que o jogoUsar apenas som
Usar vídeo de encaminhamento comprimidoResultado borrado, usuário achou que foi erro de cortePasso 1: probe primeiro

Habilidades Relacionadas