Communitygithub.com

Material bruto a Shorts: clips 9:16 con subtítulos

Encuentra los mejores momentos de un video largo (remates, reacciones y pausas con Whisper, o hachazos, golpes y baquetazos con detección de transitorios de audio), comprueba que cada uno sale en cuadro, corta clips independientes, reencuadra de 16:9 a 9:16 con seguimiento de cara o pantalla dividida y quema subtítulos palabra por palabra.

¿Qué es Material bruto a Shorts: clips 9:16 con subtítulos?

Receta Material bruto a Shorts, paso 2 (pásalo a vertical), de louisedesadeleer/clipify, una skill de Claude Code con más de 600 estrellas en GitHub; se instala con npx skills add louisedesadeleer/clipify. Primero eliges modo: el modo diálogo transcribe con Whisper (marcas de tiempo por palabra con tiny.en, base para otros idiomas) y busca remates, giros, pausas incómodas y picos de audio; el modo acción omite Whisper y detecta transitorios por flujo espectral en la banda de 1–6 kHz para cortar en cada impacto; el híbrido intercala ambos. Propone de 3 a 5 candidatos, extrae un fotograma en cada uno para confirmar que la acción es visible, corta los clips, genera un recorte que sigue la cara o una pantalla dividida para 9:16, acelera los paneos para revelaciones más ajustadas y quema subtítulos ASS palabra por palabra estilo Opus con ffmpeg (máster libx264, CRF 20).

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add louisedesadeleer/clipify

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Clipify

Encuentra momentos llamativos en un video, recórtalos como clips independientes, opcionalmente reformatea 16:9 → 9:16 (pan de rostro o pantalla dividida), ajusta la velocidad para revelaciones más precisas y añade subtítulos al estilo Opus palabra por palabra.

Modos

Elige uno antes del Paso 1. La elección cambia cómo encuentras momentos y si se aplican subtítulos.

  • Modo diálogo (predeterminado para podcasts, entrevistas, cabezas parlantes): Whisper transcribe, tú buscas remates / reacciones / pausas incómodas. → Paso 1A. Subtítulos en el Paso 5.
  • Modo acción (cortar leña, deportes, percusión, batería, cualquier cosa con sonidos de impacto repetidos): omite Whisper por completo, ejecuta detect_transients.py para encontrar cada golpe, corta en cada uno. → Paso 1B. Omite el Paso 5; los sonidos de golpe son el ritmo.
  • Híbrido: modo diálogo para secciones habladas, modo acción para el resto, intercala. Usa tanto 1A como 1B.

Si el usuario dice "No estoy hablando en esto", estás en modo acción. Si dice "corta en cada X" (corte, golpe, puñetazo, redoble, balanceo, pisada), estás en modo acción. Por defecto, modo diálogo en caso contrario.

Entradas

  • Una ruta de archivo de video (el usuario la proporcionará; de lo contrario, pregunta)
  • Opcional: formato solicitado (9:16, 16:9, 1:1) — si no se proporciona, pregunta después de seleccionar los candidatos
  • Opcional: preferencia de estilo de subtítulos — si no se proporciona, pregunta antes de subtitular

Herramientas (usa solo la ruta más rápida)

  • Whisper: whisper --model tiny.en --word_timestamps True --output_format json (≈10× más rápido que small.en; calidad suficiente para inglés). Para otros idiomas: --model base (omite --language).
  • ffmpeg: añade -hwaccel videotoolbox para decodificación y -preset ultrafast para renderizados. Usa -c:v libx264 -crf 20 para el master final.
  • Numpy para alineación de audio (correlación cruzada FFT). No se necesita scipy/cv2.
  • Scripts: <skill-dir>/scripts/ (donde <skill-dir> es el directorio que contiene este SKILL.md — típicamente ~/.claude/skills/clipify/)
    • analyze.py — línea de tiempo del hablante a partir de dos archivos de movimiento ROI
    • build_pan.py — expresión x de recorte ffmpeg con cortes duros
    • build_ass.py — subtítulos ASS estilo Opus desde JSON de Whisper
    • audio_align.py — encuentra el desplazamiento de un subclip en una fuente más larga
    • detect_transients.py — encuentra sonidos de impacto agudos (modo acción); consulta --help

Directorio de trabajo: /tmp/clipify/ (crear con mkdir al inicio, dejar artefactos para depuración).


Flujo de trabajo

Paso 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

Lee el JSON resultante (o .txt) y selecciona de 3 a 5 clips candidatos. Señales divertidas a buscar:

  • Remates y reacciones: palabras como "qué", "espera", "no puede ser", risas, "jaja", insultos
  • Momentos de giro: pregunta preparatoria → respuesta inesperada
  • Pausas incómodas: segmento de Whisper con un gran espacio, o muletillas ("eh", "um")
  • Autocrítica / frases citables: oraciones declarativas cortas que se sostienen solas
  • Picos de audio: detecta mediante ffmpeg -af volumedetect o busca intercambios rápidos (segmentos cortos alternados de Whisper)

Para cada candidato, propón: [inicio, fin, por qué es divertido, título sugerido]. Busca clips de 10 a 25 segundos. Muestra la lista y deja que el usuario confirme/seleccione.

Paso 1B — Encontrar cada golpe (modo acción)

Reemplaza Whisper con detección de transitorios de audio. El detector encuentra impactos agudos (hacha en madera, puño en almohadilla, parche de batería, pelota en bate, madera cayendo en una pila) calculando el flujo espectral en la banda de 1–6 kHz — el impulso de banda ancha que crea un impacto y que el ambiente/viento no tiene.

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

Ajuste:

  • --min-flux 30 es el valor predeterminado sensato. Los impactos reales registran 50–300; el ambiente/viento se sitúa en 5–15. Demasiados falsos positivos → sube a 40–50. Muy pocos golpes → baja a 20.
  • --min-gap 0.6 (segundos) es la distancia mínima entre dos golpes. La batería rápida puede necesitar 0.2; cortar leña funciona con 0.6–1.0.
  • --band 1000:6000 cubre impactos de madera/metal/vidrio. Los golpes graves pesados (bombo, golpes al cuerpo) se sitúan más abajo (--band 200:2000); silbidos, tintineos, golpes de caja más arriba (--band 4000:10000).
  • Para fuentes de múltiples clips (una carpeta de clips de teléfono), ejecuta el detector en cada clip y elige un conjunto variado entre ángulos. Busca 50–65 golpes para un montaje de 45–60 segundos a ~0.9 segundos por toma.

Cada "toma" debe comenzar ~0.4 segundos antes del golpe (mostrando el impulso) y terminar ~0.5 segundos después (golpe + breve seguimiento). Eso es ~0.9 segundos por tiempo.

Paso 1.5 — Verificar que cada candidato esté en cuadro

Haz esto siempre antes de renderizar, en cualquier modo. La detección de audio encuentra el sonido de un evento; la cámara podría no haberlo capturado. Saltarse esto es la forma #1 de recibir comentarios del usuario como "cortaste en golpes donde ni siquiera soy visible".

# Toma un fotograma en cada tiempo 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

Lee cada imagen de verificación y descarta el candidato si:

  • El sujeto está agachado, fuera de cuadro o detrás de un obstáculo
  • Hay un dedo en la lente (sí, esto ocurre — y solo lo detectas visualmente)
  • Es claramente una fuente de sonido diferente (portazo, herramienta caída) que activó el detector

Para ~30 candidatos, esto es una llamada rápida a ffmpeg más una lectura de imágenes por lote. Un seguro barato.

Paso 2 — Recortar cada clip elegido

ffmpeg -y -ss "$START" -t "$DURATION" -i "$VIDEO" -c copy /tmp/clipify/clip_$N.mp4

(Usa -c copy para un recorte instantáneo. Re-codifica solo si los cortes deben ser precisos al fotograma).

Paso 3 — Decidir el formato de salida

Pregunta al usuario (omite si ya lo especificaron): "¿9:16 (TikTok / Reels), 16:9 (YouTube) o 1:1 (feed de Insta)?"

Paso 4 — Si 16:9 → 9:16: paneo entre rostros vs pantalla dividida

Detecta la relación de aspecto de origen con ffprobe. Si la fuente es 16:9 y el destino es 9:16, pregunta:

"Dos opciones: (a) paneo de corte duro que sigue a quien habla (un solo rostro en pantalla a la vez), o (b) pantalla dividida apilada con ambos rostros visibles. ¿Cuál prefieres?"

Omite la pregunta si solo hay un rostro (clip de un solo hablante). Para un solo hablante, simplemente recorta centrado.

Paso 4a — Paneo entre rostros (recomendado para diálogo rápido de cabezas parlantes)

  1. Ubica las dos regiones de interés (ROI) de los rostros. Toma un fotograma: ffmpeg -ss <medio> -i <clip> -frames:v 1 /tmp/clipify/probe.jpg. Léelo. Estima el área de la boca+mentón de cada rostro como x,y,w,h en el espacio de píxeles de la fuente. (No se necesita cv2 — la cámara es estática dentro de un clip; un fotograma es suficiente). Verifica dibujando cuadros:

    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
    

    Itera como máximo dos veces. Los cuadros deben cubrir boca + mentón y evitar manos/micrófonos. No afines en exceso — la diferencia de fotogramas es tolerante.

  2. Extrae la energía de movimiento por fotograma en 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. Construye la línea de tiempo del hablante (permanencia mínima de 1.0 s — las interjecciones cortas se fusionan con el hablante anterior):

    python3 <skill-dir>/scripts/analyze.py /tmp/clipify/L.txt /tmp/clipify/R.txt 1.0 > /tmp/clipify/segments.json
    
  4. Elige las coordenadas x del paneo para una franja vertical 9:16 desde la fuente. Con fuente W=1920 y destino W=1080, ancho de franja recortada = 608.

    • LEFT_X = face_left_center_x - 304 (limitar ≥ 0)
    • RIGHT_X = face_right_center_x - 304 (limitar ≤ source_W - 608)
  5. Genera la expresión x de corte duro y renderiza:

    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
    

    Se asume fuente 1920×1080; para fuente 4K, reduce la escala primero o duplica todas las coordenadas.

Paso 4b — Pantalla dividida (ambos rostros siempre visibles)

Dos mosaicos apilados, 1080×960 cada uno. El mosaico del hablante activo está arriba — la superposición cambia en los cambios de hablante.

[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]

Construye <RIGHT_SPEAKER_ENABLE> desde segments.json como between(t,a,b)+between(t,a,b)+... sobre los segmentos del hablante derecho. Los recortes de los mosaicos deben apuntar a ~720×640 alrededor de cada rostro (1.125:1 para coincidir con 1080×960).

Paso 4c — Revelaciones con ajuste de velocidad (opcional)

Cuando el usuario quiere reducir un plano lento / panorámico / de revelación sin perder el arco, usa aceleración en lugar de recorte. Recortar te obliga a descartar parte del arco; la aceleración preserva todos los tiempos a un tempo mayor.

# Velocidad 2x: arco de 12s → salida de 6s, tono de audio preservado mediante 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

Reglas generales:

  • 1.5x — movimiento humano que debe sentirse "ligeramente rápido" sin parecer acelerado
  • 2x — revelación más contundente; aún se lee como natural
  • 3x–4x — ambiente de lapso de tiempo (encadena atempo=2.0,atempo=2.0 para 4x; atempo solo acepta 0.5–2.0 por instancia de filtro)
  • Omite -af atempo y silencia el segmento si el audio es solo ambiente/viento y el efecto de tono agudo sería molesto

Útil cuando el resto del corte es rítmico (montaje de golpes, ida y vuelta de charla) y la revelación se sentiría como un punto muerto.

Paso 5 — Añadir subtítulos

Omite este paso en modo acción — los sonidos de golpe son el ritmo y los subtítulos solo saturan lo visual.

Pregunta una vez (solo si el usuario aún no ha especificado un estilo):

"Tres estilos de subtítulos: opus (blanco grande y negrita, resaltado de palabra activa en amarillo), karaoke (grupos de 4 palabras, resaltado en verde), minimal (Helvetica limpio, sin resaltado). O pega un ejemplo que te guste."

Si pegan una imagen/ejemplo de referencia: adapta la fuente, tamaño, peso, color, posición y animación lo más posible — escribe un ASS personalizado a mano o extiende build_ass.py.

De lo contrario, usa el preestablecido:

# Vuelve a ejecutar whisper en el clip recortado para marcas de tiempo precisas relativas al inicio del 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

Incrusta subtítulos:

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"

Paso 6 — Entregar

  • Guarda cada salida en <source_dir>/clipify_out/ (crea la carpeta si no existe)
  • Imprime una línea por clip: nombre, duración, qué fue divertido, ruta de salida
  • Abre la primera salida con open <path> para que el usuario pueda verificarla
  • Ofrece iterar (diferente estilo, diferente ROI, cambiar a pantalla dividida, reajustar tiempos de subtítulos)

Errores comunes (lecciones de ejecuciones anteriores — no los repitas)

  • La detección de audio ≠ evento visible. Siempre ejecuta el Paso 1.5 (verificar cada fotograma candidato) antes de renderizar. El detector encuentra el sonido de un golpe, no si el que golpea está en cuadro. Golpes donde el sujeto está agachado, fuera de cámara, o donde hay un dedo en la lente aún activan el detector de audio. Captúralos antes de renderizar.
  • La banda de flujo espectral importa. El valor predeterminado --band 1000:6000 cubre impactos de madera/metal/vidrio. Los golpes graves pesados (bombo, golpes al cuerpo) necesitan --band 200:2000. Silbidos, tintineos, golpes de caja necesitan --band 4000:10000. Si --min-flux 30 no devuelve nada, prueba bajando a 15 primero; si devuelve miles, prueba una banda diferente antes de subir el umbral.
  • Aceleración de audio en pares. atempo solo acepta 0.5–2.0 en un solo filtro; para 4x encadena atempo=2.0,atempo=2.0. Sin atempo, setpts solo da audio de tono agudo.
  • No afines demasiado las ROI. Máximo dos iteraciones. La diferencia de movimiento es tolerante — ROI más amplias que cubran boca+mentón funcionan bien incluso si no están perfectamente centradas en la boca.
  • Ten cuidado con los cortes de escena dentro de un clip. Ejecuta ffmpeg -filter:v "select='gt(scene,0.3)',showinfo" -f null - para contar cortes. Si un clip de 16:9→9:16 tiene muchos cortes, las ROI de rostro fijo solo funcionan para la escena dominante; advierte al usuario y ofrece elegir un clip de una sola toma o aceptar un encuadre descentrado durante los cortes.
  • La resolución de fuente importa. Si la fuente es 4K, reduce la escala a 1920×1080 primero (más rápido, suficiente para salida 9:16) o multiplica todas las coordenadas de ROI/paneo por 2.
  • Subtítulos incrustados en la fuente. Algunos clips "crudos" aún tienen subtítulos. Si es así, encuentra el master sin subtítulos mediante correlación cruzada de audio (audio_align.py) y recorta desde allí.
  • No ejecutes whisper en la fuente completa de larga duración si un clip corto es suficiente. Whisper el clip recortado después del Paso 2; solo susurra la fuente completa en el Paso 1 si necesitas una transcripción para encontrar momentos divertidos.
  • Expresa el plan en una línea, luego actúa. No narres cada iteración.

Skills relacionados