Copiado. Abre un chat nuevo de Claude y pégalo.
🪜 paso a paso · Editar tus videos con IA

Qué vas a tener cuando termines

Una grabación entra, un video limpio con tu estética sale, sin abrir el editor.

  • Editar me come el tiempotu última grabación sin silencios ni repeticiones, montada con subtítulos y música según tu estética, verificada y lista para subir. Sin haber abierto el editor.
  • Quiero publicar más sin contratar editoruna rutina de un comando y un pedido por video, y de cada grabación hasta cuatro videos publicables (la base y tres variaciones), sin editor.
  • Quiero que todos mis videos se vean igualestu `edit-spec.yaml` terminado (tier, color, formato, subtítulos, música, CTA), tres o cuatro capturas de referencia en `frames/`, y el `CLAUDE.md` que obliga a Claude Code a leerlo antes de tocar cualquier video.

Elige la situación que es la tuya. La página te lleva paso a paso solo por esa.

Editar tus videos con IA

Una grabación entra, un video limpio con tu estética sale, sin abrir el editor.

Tipo: paso a paso · Para: dueños de negocio y marcas personales que graban sus propios videos y no quieren vivir dentro del editor · Tiempo: una tarde la primera vez (instalar el kit y escribir tu estética); después, cada video tarda lo que tarda su transcripción.

Cómo usar esta página

Copia esta página entera (el botón «Copiar todo» de arriba) y pégala en un chat nuevo de Claude. Claude va a leer las instrucciones de abajo y te va a guiar paso a paso, una pregunta a la vez, hasta que lo tengas hecho.

INSTRUCCIONES PARA CLAUDE (si eres Claude y te pegaron esta página, síguelas):

Para quién es, y cuándo no

Ángulo 1 · «Editar me come el tiempo»

Qué vas a tener al final: tu última grabación sin silencios ni repeticiones, montada con subtítulos y música según tu estética, verificada y lista para subir. Sin haber abierto el editor.

  1. Instala el kit una sola vez. Abre Claude Code en una carpeta nueva (por ejemplo mi-kit-de-edicion) y pégale el prompt de instalación de la Pieza B seguido de los archivos que ese prompt pide: el script de la Pieza C, el CLAUDE.md de la Pieza B, el edit-spec.yaml de la Pieza A y las skills de las Piezas D y E (con su variaciones.yaml). Claude Code instala ffmpeg y Whisper, crea las carpetas y guarda cada archivo en su lugar. No avances hasta que te diga «kit listo».
  2. Copia la grabación a entrada/. Al disco, no leída desde una carpeta compartida, de red ni del teléfono: desde ahí es hasta un 45 por ciento más lento.
  3. Decide el cortador. ¿Grabaste leyendo un guion, repitiendo frases? Va el corte por guion (Pieza D): guarda el guion como entrada/mi-grabacion.txt (el mismo nombre que el video), en el orden en que lo grabaste. ¿Grabaste de corrido, sin guion escrito? Va el corta-silencios (Pieza C).
  4. Córrelo. Sin guion: python corta-silencios.py entrada/mi-grabacion.mp4 --salida salida/mi-grabacion.limpia.mp4. Con guion: /corte-por-guion entrada/mi-grabacion.mp4 entrada/mi-grabacion.txt. Los dos dejan salida/mi-grabacion.limpia.mp4 y te dicen cuántos segundos quitaron.
  5. Revisa la lista de cortes, no el video entero. En salida/mi-grabacion.limpia.cortes.json cada corte trae la última palabra antes y la primera después: si en tres cortes al azar las dos son palabras enteras, el cruce funcionó. Si suena atropellado o cortó de más, ajusta con la tabla de parámetros de la Pieza C y vuelve a correr.
  6. Tu estética, versión de hoy. Si todavía no tienes edit-spec.yaml, usa el de la Pieza A tal como viene y cambia solo tier (sobria o llamativa) y paleta_acento (un color, el tuyo). El resto ya está medido. Afinarlo entero es el Ángulo 3.
  7. Pide el montaje. Deja en frames/ tres o cuatro capturas de un video cuya estética quieras (tuyo o de otro; una captura de pantalla desde el celular sirve) y pégale a Claude Code el pedido de edición de la Pieza A. Antes de editar te va a decir en cinco líneas qué decisiones tomó que el spec no cubría.
  8. Verifica antes de subir. Claude Code tiene que reportar el ancho y el alto reales del archivo (1080 por 1920) y que abre sin error. Después míralo tú en el celular, no en el computador: ahí se ve el video.

Cómo sabes que quedó bien: la duración bajó (12 minutos con pausas y repeticiones quedan en unos 4), ninguna palabra quedó cortada, el archivo es 1080 por 1920 también por dentro, abre en el celular, y tienes cinco líneas de decisiones para tu spec.

Ángulo 2 · «Quiero publicar más sin contratar editor»

Qué vas a tener al final: una rutina de un comando y un pedido por video, y de cada grabación hasta cuatro videos publicables (la base y tres variaciones), sin editor.

  1. Ten el kit instalado y el spec escrito. Si no, haz el Ángulo 1 con un solo video: esa es la instalación. Lo que sigue da por hecho que CLAUDE.md (Pieza B), corta-silencios.py (Pieza C), edit-spec.yaml (Pieza A) y las skills (Piezas D y E) ya están en la carpeta.
  2. Graba en tandas, leyendo. Escribe los guiones de la semana, graba uno tras otro en la misma sesión (misma luz, mismo encuadre) y guarda cada video con su guion al lado, con el mismo nombre: entrada/lunes.mp4 con entrada/lunes.txt. Repite cada frase las veces que quieras: la skill se queda con la última toma completa.
  3. Procesa la tanda con un solo pedido. Pégale a Claude Code el pedido por lotes de la Pieza B: por cada video de entrada/, si tiene guion corre la Pieza D, si no corre la Pieza C, y después monta con el spec. Al final te entrega una tabla por video: duración antes y después, frases no encontradas, decisiones no cubiertas.
  4. Lee la tabla, no los videos. Abre solo los que tengan frases no encontradas o una duración rara. Las frases no encontradas se regraban, nunca se inventan con otro corte.
  5. Multiplica con variaciones. Por cada montaje que quieras probar, escribe variaciones.yaml (Pieza E) apuntando a salida/lunes.montaje.mp4, con hasta tres variaciones de una sola variable cada una, y pide /variaciones. Salen tres archivos más junto a la base, uno por variación.
  6. Publica con método. La base y una variación con dos días de diferencia, misma hora, mismo caption. A los tres días compara conversaciones, no vistas. La que gana pasa a ser la base; la semana siguiente pruebas otra variable.
  7. Alimenta el spec. Cada tanda deja decisiones que el spec no cubría: se pegan en edit-spec.yaml. Cuando una tanda entera sale con cero decisiones nuevas, el spec está terminado.

Cómo sabes que quedó bien: de una tarde de grabación salen los videos de la semana y cada uno te costó un comando y un pedido. Si un video te cuesta más que su transcripción, algo está mal: ve a «Errores conocidos».

Ángulo 3 · «Quiero que todos mis videos se vean iguales»

Qué vas a tener al final: tu edit-spec.yaml terminado (tier, color, formato, subtítulos, música, CTA), tres o cuatro capturas de referencia en frames/, y el CLAUDE.md que obliga a Claude Code a leerlo antes de tocar cualquier video.

  1. Elige el tier y no lo mezcles. Sobria o llamativa (la tabla está en la Pieza A). No es gusto: la estética decide a quién atraes.
  2. Elige el formato. En la Pieza F hay seis specs, uno por tipo de video. Si dudas entre dos, pídele a Claude Code un preview de diez segundos de tu video con cada uno y míralos en el celular.
  3. Arma el archivo. Si no tienes el kit, el paso 1 del Ángulo 1 lo instala. Copia el edit-spec.yaml base (Pieza A) a la raíz de tu carpeta y el archivo del formato elegido a specs/. En la base cambia tier, paleta_acento (un color, el mismo en todos tus videos) y ambiente. En el del formato, colores y tipografía.
  4. Deja capturas. Tres o cuatro fotogramas de videos cuya estética quieres, en frames/, y sus rutas en referencia_visual. El spec describe; la captura muestra.
  5. No toques las cinco reglas que ya vienen puestas (están en la Pieza A, debajo del archivo).
  6. Edita un video real y cierra el spec usándolo. Pide el montaje (el pedido de la Pieza A, con el CLAUDE.md de la Pieza B ya en la carpeta) y pega en el archivo las decisiones que Claude Code tomó solo. Repite con un segundo video. Cuando el reporte diga cero decisiones no cubiertas, el spec está terminado.
  7. Cuando quieras cambiar algo de la estética, no lo cambies a mano en el próximo video: pruébalo como variación (Pieza E), una sola variable, contra la base. Si gana, entra al spec para todos.

Cómo sabes que quedó bien: dos videos tuyos editados con una semana de diferencia se ven de la misma marca; una persona nueva produce igual desde el primer día con solo la carpeta; y edit-spec.yaml tiene al menos cinco líneas que no estaban en la plantilla, escritas por ti.

Las piezas

Pieza A · edit-spec.yaml y el pedido de edición (plantilla + prompt)

Tu estética en parámetros: el archivo que hace que la IA edite tu video con tu estilo y no con el suyo. Se escribe una vez, se le pasa a Claude Code con cada video, y crece con las decisiones que la IA tomó sola. La usan los tres ángulos.

Primero, el tier. Sobria o llamativa. No hay tercera y no se combinan.

Tier A quién atrae Cómo se ve
sobria Dueños de negocio, B2B, capacidad de pago alta Luz natural, grading neutro, un color de acento
llamativa Principiantes, B2C, capacidad de pago baja Saturación, texto grande, muchos efectos

Los valores que vienen puestos no son preferencias: salen de mirar cientos de videos que funcionaron, cuadro por cuadro. Cambia lo que no sea tuyo y deja el resto. El color de acento viene con uno de ejemplo: ponle el tuyo. Guárdalo como edit-spec.yaml (si el bloc de notas te lo guarda como .txt, elige «Todos los archivos» en el tipo antes de guardar).

# edit-spec.yaml : tu estética, en parámetros
# Llénalo una vez. Pásaselo a Claude Code junto con el video crudo y, si puedes,
# una carpeta frames/ con 3 o 4 capturas de referencia.

edit_spec:
  version: 1

  duracion_objetivo_s: 60
  aspect: "9:16"
  fps: 30                    # 24 si la máquina no da. 60 no
  resolucion: [1080, 1920]

  estetica:
    tier: sobria             # sobria (B2B) | llamativa (B2C). NO se mezclan
    ambiente: interior_luz_natural
    grading: neutro
    paleta_acento: "#C9A840" # de ejemplo. UN color, el tuyo, el mismo en TODOS tus videos
    referencia_visual: []    # rutas a frames/ : el spec describe, la captura muestra

  subtitulos:
    corrido:
      fuente: sans
      peso: bold
      color: "#FFFFFF"
      contorno: fino             # none | fino | negro_grueso
      caja: none
      posicion: tercio_inferior
      palabras_por_frase: [2, 4]
      max_lineas: 2
      achicar_si_desborda: false # parte en 2 líneas, NUNCA achica
    titular:
      activo: false
      fijo_todo_el_video: false  # en listados: true (captura al que entra a mitad)
      color_texto: "#FFFFFF"
      peso: bold
      posicion: tercio_superior
      palabra_en_acento: true    # UNA sola palabra por titular
    cta:
      doble_capa: true
      solo_una_vez: true         # REGLA DURA
      mayusculas: true
      escala_vs_corrido: 3.0

  layout:
    modo: talking_head    # talking_head | split_horizontal | pip | mosaico | texto_sobre_video | toma_unica
    cortes: auto
    zooms: 0              # el zoom corporal le gana al digital
    pip_ancho: 0.22       # con la cara al 20-25%, tu fondo deja de importar

  screen_recordings:
    acelerar: false           # el ritmo real se lee como real
    limpiar_escritorio: false # el desorden compra credibilidad

  audio:
    musica:
      arco: [vacio, construccion, llegada]
      llegada_en_s: null      # EXACTO sobre la frase de impacto (~70% del video)
      nivel: bajo_la_voz
      prohibido:
        - "upbeat corporate"
        - "inspiring background"
    silencio_antes_del_cta_s: 0.6   # crescendo invertido
    cortar_pausas: true
    foley_accion_continua: true

  cta:
    tipo: pregunta_cuantificable   # convierte mucho Y devuelve un dato por persona
    texto: null
    entrega: inmediata
    destino: dm_automatico

Las cinco reglas que ya vienen puestas (no se tocan):

  1. La doble capa de subtítulo se usa una sola vez por video, sobre el CTA. Si todo está destacado, nada lo está.
  2. La música llega exacto sobre la frase de impacto. Ni antes ni después: es lo que convierte un momento en el momento.
  3. Silencio en seco justo antes del CTA. Crescendo invertido: lo que viene después del silencio pega tres veces más fuerte.
  4. El subtítulo nunca se achica. Si no entra, parte en dos líneas.
  5. Las grabaciones de pantalla no se aceleran ni se limpian. El desorden real compra credibilidad; un escritorio impecable se lee como demo.

El anti-patrón con nombre: música tipo «upbeat corporate» o «inspiring background» (ukelele, pianito de cuatro notas en loop): suena a sala de espera de dentista y el sistema nervioso la manda al fondo en tres segundos. Por eso está en la lista de prohibidos.

El pedido de edición (se pega en Claude Code con el video limpio ya en salida/; cambia mi-grabacion por el nombre de tu video):

Edita salida/mi-grabacion.limpia.mp4 siguiendo edit-spec.yaml al pie de la letra (y
specs/<formato>.yaml si existe, que hereda de la base). Las capturas de referencia están
en frames/. Deja el resultado como salida/mi-grabacion.montaje.mp4.

Antes de editar, dime en cinco líneas qué decisiones tomaste que el spec NO cubría,
para que yo las agregue al archivo.

Si algo del spec choca con el material, dímelo en vez de resolverlo por tu cuenta.

Al terminar, reporta: duración final, ancho y alto reales del archivo según ffprobe,
y confirma que abre. No me digas "listo" sin esa verificación.

Pieza B · Kit de edición: prompt de instalación, CLAUDE.md y pedido por lotes (paso a paso)

No hay nada que descargar: con este prompt Claude Code instala lo que falta, crea las carpetas y guarda cada pieza donde va. El CLAUDE.md son las reglas para que use el kit sin que se lo expliques cada vez. Todo corre en tu computador; el video no sale de ahí. La usan los tres ángulos.

El prompt de instalación (pégalo en Claude Code, abierto en una carpeta nueva, seguido de los archivos que nombra el paso 5: Piezas C, B, A, D y E):

Estamos armando mi kit de edición de video en esta carpeta. Hazlo en este orden y no avances
hasta que cada paso funcione de verdad:

1. Comprueba si ffmpeg y ffprobe están instalados (ffmpeg -version). Si no, instálalos con el
   gestor de paquetes de mi sistema (winget en Windows, brew en Mac, apt en Linux) y vuelve a
   comprobar. Si en Windows no quedan en el PATH, agrégalos y abre una terminal nueva.
2. Comprueba Python 3.10 o superior. Si no está, instálalo igual.
3. Instala Whisper: pip install -U openai-whisper. Si falla por torch, instala torch primero
   (pip install torch) y repite.
4. Crea las carpetas: entrada/, salida/, frames/, specs/, skills/corte-por-guion/,
   skills/variaciones/.
5. Guarda como corta-silencios.py, en la raíz, el script que te pego abajo, sin cambiarle nada.
   Guarda CLAUDE.md, edit-spec.yaml, variaciones.yaml y los dos SKILL.md, cada uno en su ruta.
6. Prueba con el video que dejé en entrada/: python corta-silencios.py entrada/<video> --solo-lista
   La primera vez descarga el modelo de Whisper; espera, es una sola vez. Dime cuántos
   silencios detectó, cuántas palabras transcribió y cuántos segundos quitaría.

Cuando todo eso funcione, escribe "kit listo" y dime el comando exacto que corro para cada
video nuevo, con y sin guion.

CLAUDE.md (va en la raíz de la carpeta; Claude Code lo lee solo):

# Kit de edición · reglas para Claude Code

Esta carpeta edita videos. Antes de tocar cualquier video lee edit-spec.yaml: ahí está la
estética. Lo que el spec no diga, lo preguntas o lo anotas como "decisión no cubierta";
nunca lo decides en silencio. Si un spec de specs/ tiene la línea "hereda:", carga primero
ese archivo base y aplica encima lo del formato (lo del formato manda).

## El flujo para cada video (entrada/<nombre>.mp4)
1. Si el video viene de una carpeta de red, compartida o del teléfono, cópialo a entrada/
   antes de procesarlo. Leer desde ahí es hasta un 45 % más lento.
2. Si existe entrada/<nombre>.txt, corre la skill corte-por-guion. Si no, corre
   python corta-silencios.py entrada/<nombre>.mp4 --salida salida/<nombre>.limpia.mp4
   Nunca los dos sobre el mismo archivo. Los dos dejan salida/<nombre>.limpia.mp4.
3. Montaje según edit-spec.yaml, con ffmpeg, desde salida/<nombre>.limpia.mp4 hacia
   salida/<nombre>.montaje.mp4:
   - Subtítulos a partir de entrada/<nombre>.palabras.json (la transcripción con tiempos que
     deja corta-silencios.py): agrupa según palabras_por_frase, máximo
     max_lineas; si una frase desborda se parte en dos líneas, nunca se achica la fuente.
   - Música: arco vacío / construcción / llegada, con la llegada exacta sobre la frase de
     impacto (si llegada_en_s es null, pregunta cuál es la frase de impacto antes de
     renderizar; suele estar cerca del 70 % del video). Nivel bajo la voz. Silencio en
     seco de silencio_antes_del_cta_s antes del CTA.
   - CTA: doble capa una sola vez, sobre el CTA, en mayúsculas, a escala_vs_corrido.
   - Cortes secos en el mismo cuadro. Sin fundidos ni deslizamientos que muestren la toma
     de atrás. Sonido de transición solo donde se ve un cambio.
   - Deja al lado salida/<nombre>.montaje.sin-subs.mp4 y salida/<nombre>.voz.wav: la skill
     variaciones parte de ahí.
4. Verifica la salida con ffprobe antes de decir "listo": ancho y alto reales iguales a
   resolucion del spec, duración esperada, y que abre. Un MP4 puede declarar 1080x1920 y
   por dentro ser un 540 estirado; y uno a medio escribir pesa bien y no abre.
5. Reporta en una tabla: video, duración antes y después, cortes aplicados, frases no
   encontradas (si hubo guion), y las decisiones que tomaste que el spec no cubría.

## Reglas duras
- Nunca cortar a mitad de palabra: los límites salen de los tiempos por palabra.
- No aplicar filtros de ruido ni compuertas: rompen la voz. Si sobra ruido, sobra tiempo:
  corta más.
- No acelerar ni limpiar grabaciones de pantalla.
- No renderizar para averiguar: decide con un preview corto (10 s alrededor de la frase de
  impacto, a la mitad de la resolución) y renderiza entero una sola vez, al final, siempre
  desde el original o desde el corte limpio, nunca desde un archivo recodificado dos veces.
- Música prohibida: "upbeat corporate", "inspiring background". La música se elige por la
  emoción del video, no por la que había a mano.
- Si algo del spec choca con el material, decirlo, no resolverlo por tu cuenta.
- Hablar en español neutro (tú). Sin rayas largas.

El pedido por lotes (para procesar toda una tanda de grabaciones de una vez):

Procesa todo lo que hay en entrada/ siguiendo CLAUDE.md:
- Por cada video, si existe un .txt con el mismo nombre, usa corte-por-guion con ese guion;
  si no, usa corta-silencios.py.
- Después monta cada uno con edit-spec.yaml (y specs/<formato>.yaml si existe) y déjalo
  como salida/<nombre>.montaje.mp4.
- No me preguntes video por video: junta las preguntas y hazlas todas al final.
- Al terminar dame la tabla de CLAUDE.md (video, duración antes y después, cortes, frases no
  encontradas, decisiones no cubiertas) y verifica cada archivo con ffprobe antes de incluirlo.

Pieza C · corta-silencios.py (script)

Detecta los silencios con ffmpeg (tramos por debajo de -35 dB que duren más de 0,35 s), transcribe con Whisper con tiempos por palabra y cruza las dos listas: un silencio se corta solo si cae completo entre el fin de una palabra y el inicio de la siguiente (un «eh» bajito adentro lo salva), con 0,12 s de margen a cada lado para que no suene apretado. Corte seco, misma resolución que la entrada. Lo usan los Ángulos 1 y 2.

#!/usr/bin/env python3
# corta-silencios.py : entra un video, sale sin silencios, más la lista de cortes. Solo corta
# un silencio si cae completo entre el fin de una palabra y el inicio de la siguiente.
# Uso:
#   python corta-silencios.py entrada.mp4 --salida limpia.mp4 --umbral -35 --minimo 0.35 --margen 0.12
#   python corta-silencios.py entrada.mp4 --solo-lista     (escribe lista y transcripción, no renderiza)
# Deja: limpia.mp4, limpia.cortes.json (cada corte con la última palabra antes y la primera
# después) y entrada.palabras.json (la transcripción con tiempos, que reutilizan las skills).

import argparse, json, os, re, shutil, subprocess, sys, tempfile

def correr(cmd):
    return subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace")

def duracion(ruta):
    r = correr(["ffprobe", "-v", "error", "-show_entries", "format=duration",
                "-of", "default=noprint_wrappers=1:nokey=1", ruta])
    return float(r.stdout.strip())

def resolucion(ruta):
    r = correr(["ffprobe", "-v", "error", "-select_streams", "v:0",
                "-show_entries", "stream=width,height,codec_name", "-of", "csv=p=0", ruta])
    return r.stdout.strip()

def detectar_silencios(ruta, umbral, minimo):
    r = correr(["ffmpeg", "-hide_banner", "-i", ruta,
                "-af", f"silencedetect=noise={umbral}dB:d={minimo}", "-f", "null", "-"])
    inicios = [float(x) for x in re.findall(r"silence_start:\s*([0-9.]+)", r.stderr)]
    fines = [float(x) for x in re.findall(r"silence_end:\s*([0-9.]+)", r.stderr)]
    silencios = list(zip(inicios, fines))
    if len(inicios) > len(fines):            # silencio abierto hasta el final del archivo
        silencios.append((inicios[-1], duracion(ruta)))
    return silencios

def transcribir(ruta, modelo, idioma):
    import whisper
    res = whisper.load_model(modelo).transcribe(ruta, language=idioma, word_timestamps=True, verbose=False)
    return [{"texto": w["word"].strip(), "inicio": round(float(w["start"]), 3), "fin": round(float(w["end"]), 3)}
            for seg in res["segments"] for w in seg.get("words", [])]

def cruzar(silencios, palabras, margen):
    """Un silencio se corta solo si, ya con margen, no tiene ninguna palabra adentro."""
    cortes = []
    for ini, fin in silencios:
        a, b = ini + margen, fin - margen
        if b <= a or any(p["fin"] > a and p["inicio"] < b for p in palabras):
            continue                          # hay una palabra (o un "eh") adentro: no se toca
        antes = [p for p in palabras if p["fin"] <= a]
        despues = [p for p in palabras if p["inicio"] >= b]
        cortes.append({"inicio": round(a, 3), "fin": round(b, 3), "dura": round(b - a, 3),
                       "ultima_palabra_antes": antes[-1]["texto"] if antes else "",
                       "primera_palabra_despues": despues[0]["texto"] if despues else ""})
    return cortes

def tramos_a_conservar(cortes, total):
    tramos, t = [], 0.0
    for c in cortes:
        if c["inicio"] > t:
            tramos.append((t, c["inicio"]))
        t = max(t, c["fin"])
    if t < total:
        tramos.append((t, total))
    return tramos

def cortar(entrada, salida, tramos):
    # Corte seco: select/aselect con los tramos a conservar, un solo render desde el original.
    sel = "+".join(f"between(t,{a:.3f},{b:.3f})" for a, b in tramos)
    carpeta = tempfile.mkdtemp()
    rv, ra = os.path.join(carpeta, "v.txt"), os.path.join(carpeta, "a.txt")
    open(rv, "w", encoding="utf-8").write(f"select='{sel}',setpts=N/FRAME_RATE/TB")
    open(ra, "w", encoding="utf-8").write(f"aselect='{sel}',asetpts=N/SR/TB")
    r = correr(["ffmpeg", "-hide_banner", "-y", "-i", entrada,
                "-filter_script:v", rv, "-filter_script:a", ra,
                "-c:v", "libx264", "-preset", "medium", "-crf", "18", "-pix_fmt", "yuv420p",
                "-c:a", "aac", "-b:a", "192k", "-movflags", "+faststart", salida])
    shutil.rmtree(carpeta, ignore_errors=True)
    if r.returncode != 0:
        print(r.stderr[-3000:])
        sys.exit("ffmpeg falló al cortar; el mensaje de arriba dice por qué")

def main():
    ap = argparse.ArgumentParser(description="Corta silencios sin cortar palabras.")
    ap.add_argument("entrada")
    ap.add_argument("--salida", default=None)
    ap.add_argument("--umbral", type=float, default=-35, help="dB; súbelo si hay ruido de fondo")
    ap.add_argument("--minimo", type=float, default=0.35, help="s; súbelo si corta pausas expresivas")
    ap.add_argument("--margen", type=float, default=0.12, help="s; súbelo si suena atropellado")
    ap.add_argument("--modelo", default="small", help="whisper: tiny | base | small | medium")
    ap.add_argument("--idioma", default="es")
    ap.add_argument("--solo-lista", action="store_true", help="escribe cortes y palabras, no renderiza")
    a = ap.parse_args()

    for h in ("ffmpeg", "ffprobe"):
        if not shutil.which(h):
            sys.exit(f"falta {h} en el PATH: instálalo y abre una terminal nueva")
    ent = a.entrada
    salida = a.salida or os.path.splitext(ent)[0] + ".limpia.mp4"
    os.makedirs(os.path.dirname(os.path.abspath(salida)), exist_ok=True)

    total = duracion(ent)
    print(f"entrada: {ent} · {total:.1f} s · {resolucion(ent)}")
    sil = detectar_silencios(ent, a.umbral, a.minimo)
    print(f"silencios detectados (>{a.minimo} s bajo {a.umbral} dB): {len(sil)}")

    ruta_palabras = os.path.splitext(ent)[0] + ".palabras.json"
    if os.path.exists(ruta_palabras):
        pal = json.load(open(ruta_palabras, encoding="utf-8"))
    else:
        pal = transcribir(ent, a.modelo, a.idioma)
        json.dump(pal, open(ruta_palabras, "w", encoding="utf-8"), ensure_ascii=False, indent=1)
    print(f"palabras con tiempo: {len(pal)}")

    cortes = cruzar(sil, pal, a.margen)
    quitado = sum(c["dura"] for c in cortes)
    lista = os.path.splitext(salida)[0] + ".cortes.json"
    json.dump({"entrada": ent, "duracion_original_s": round(total, 3), "quitado_s": round(quitado, 3),
               "duracion_final_s": round(total - quitado, 3), "umbral_db": a.umbral,
               "minimo_s": a.minimo, "margen_s": a.margen, "cortes": cortes},
              open(lista, "w", encoding="utf-8"), ensure_ascii=False, indent=2)
    print(f"cortes que se aplican: {len(cortes)} · se quitan {quitado:.1f} s · lista: {lista}")

    if a.solo_lista:
        return
    if not cortes:
        print("no hay nada que cortar con estos parámetros; prueba --umbral -30 o --minimo 0.25")
        return
    cortar(ent, salida, tramos_a_conservar(cortes, total))
    print(f"salida: {salida} · {duracion(salida):.1f} s · {resolucion(salida)}")
    print("comprueba que ancho y alto de la salida sean los mismos que los de la entrada")

if __name__ == "__main__":
    main()

Los parámetros que cambian el resultado:

Parámetro Súbelo si… Bájalo si…
Umbral (dB, por defecto -35) Hay ruido de fondo y no detecta silencios Corta respiraciones que quieres dejar
Mínimo (s, por defecto 0,35) Corta pausas expresivas Quedan huecos largos
Margen (s, por defecto 0,12) Suena atropellado Quedan colas de aire

Lo que NO resuelve: no elige entre tomas repetidas (para eso está la Pieza D) ni agrega subtítulos, música o b-roll (eso es el montaje con el spec, Pieza A).

Pieza D · corte-por-guion (skill)

Corta el video usando el guion como fuente de verdad: busca cada frase adentro de la grabación, se queda con la toma que la dice entera y mejor, y descarta repeticiones y arranques en falso. Se guarda como skills/corte-por-guion/SKILL.md. La usan los Ángulos 1 y 2.

---
name: corte-por-guion
description: Corta una grabación usando el guion como fuente de verdad. Busca cada frase del guion dentro de la transcripción con tiempos por palabra, se queda con la toma que la dice entera y mejor, descarta repeticiones y arranques en falso, y monta las tomas en el orden del guion con corte seco. Usar cuando el usuario diga "corte por guion", "quédate con la toma buena", "grabé leyendo el guion", escriba /corte-por-guion, o pase un video junto con un guion en .txt.
---

# Corte por guion

Entrada: un video (entrada/grabacion.mp4) y el guion en texto plano con el mismo nombre
(entrada/grabacion.txt), en el orden en que SE GRABÓ, no el orden original si se cambió al
grabar.
Salida: salida/grabacion.limpia.mp4, salida/grabacion.limpia.cortes.json y
salida/grabacion.limpia.informe.md.

## Pasos

1. Tiempos por palabra. Si existe entrada/grabacion.palabras.json (lo deja
   corta-silencios.py), úsalo. Si no, genéralo:
   python corta-silencios.py entrada/grabacion.mp4 --solo-lista
2. Frases del guion. Parte el .txt en frases: una frase termina en punto, signo de
   pregunta o de exclamación. Ignora líneas vacías, títulos y acotaciones entre corchetes.
   Numera las frases en orden. Si el guion viene como JSON con cada frase y su segundo
   previsto, úsalo tal cual: la alineación mejora.
3. Normaliza los dos lados para comparar: minúsculas, sin tildes, sin puntuación, y los
   números escritos como palabras si el guion los trae escritos.
4. Busca cada frase en la transcripción: ventana deslizante sobre las palabras, de largo
   entre el 70 % y el 130 % del largo de la frase. Una aparición es una ventana con
   similitud >= 0,75 (difflib.SequenceMatcher sobre las listas de palabras normalizadas).
   Junta las ventanas solapadas en una sola toma y guarda el inicio de su primera palabra y
   el fin de su última.
5. Elige la toma, en este orden y sin saltarte ninguno:
   a. La dijo entera: similitud >= 0,9 y sin que falten palabras del final de la frase.
   b. Sin muletillas ni corrección a mitad: descarta la toma si adentro aparece "eh", "em",
      "o sea", "no no", "perdón", "de nuevo", "otra vez", o una palabra repetida dos veces
      seguidas que en el guion va una sola vez.
   c. La última de las que quedaron: casi siempre es la mejor, porque ya la ensayó.
   Si ninguna pasa (a), toma la de mayor similitud y márcala "menos mala" en el informe.
   Si ninguna llega a 0,6, la frase queda NO ENCONTRADA: deja un hueco marcado en el
   informe y NO inventes el corte con otra frase parecida.
6. Márgenes: 0,12 s antes de la primera palabra y 0,12 s después de la última, sin invadir
   la palabra vecina. Nunca cortes a mitad de palabra: los límites salen de los tiempos por
   palabra, no de los silencios.
7. Montaje en el orden del guion, con ffmpeg y corte seco en el mismo cuadro: select y
   aselect con between(t,a,b) por toma, setpts=N/FRAME_RATE/TB y asetpts=N/SR/TB, misma
   resolución que la entrada, libx264 crf 18, aac 192k, faststart. Si el filtro es largo,
   escríbelo en un archivo y pásalo con -filter_script. Si entre dos tomas cambia el
   encuadre (el usuario lo dice o se ve en una captura), respeta el cambio: sigue siendo
   corte seco, sin fundidos ni deslizamientos que muestren la toma de atrás.
8. Verifica con ffprobe: ancho y alto iguales a la entrada, y duración igual a la suma de las
   tomas. Si no coincide, no digas listo: revisa el filtro.

## Formato de salida (el informe)

Una tabla: nº | frase del guion | toma elegida (inicio-fin) | de cuántas tomas | criterio
(entera / última completa / menos mala / NO ENCONTRADA).
Debajo: duración final, la lista de frases no encontradas, y si hay alguna esta línea:
"Mejor graba estas frases que inventar el corte".

## Reglas duras

- Nunca a mitad de palabra.
- No agregues b-roll ni música: eso viene después, con edit-spec.yaml.
- No mejores una toma mala: si las tres veces salieron mal, elige la menos mala y dilo.
- Si el usuario improvisó largo fuera del guion, dilo: esta skill no sabe qué guardar de
  eso. Ofrécele corta-silencios.py para ese tramo.
- Dos personas hablando: no aplica. Dilo y para.
- Un solo hablante, a cámara, guion respetado en el orden grabado: ahí funciona bien.

Pieza E · variaciones (skill + plantilla)

Renderiza el mismo montaje en varias versiones cambiando una sola cosa por versión (tipografía, música o saturación), para probar qué estética rinde sin volver a grabar ni a editar. La plantilla se guarda como variaciones.yaml en la raíz y la skill como skills/variaciones/SKILL.md. La usan los Ángulos 2 y 3.

# variaciones.yaml : un montaje, N versiones, UNA variable por versión
base:
  spec: edit-spec.yaml                       # tu estética por defecto
  entrada: salida/mi-grabacion.montaje.mp4   # el montaje ya editado con el spec
  ya_funciono_organicamente: false           # si es true, la skill no varía: este video ya es la base

variaciones:
  - nombre: tipo-b
    cambia: tipografia
    valor: "Space Grotesk"
  - nombre: musica-b
    cambia: musica
    valor: "cama-2.mp3"           # elegida por la emoción del video, no por la que tenías a mano
  - nombre: saturacion-baja
    cambia: saturacion
    valor: 0.85

Tres variaciones, tres videos junto a la base: salida/mi-grabacion.montaje.tipo-b.mp4 y los otros dos con su nombre.

---
name: variaciones
description: Renderiza el mismo montaje en N versiones cambiando UNA sola cosa por versión (tipografía, música o saturación) a partir de variaciones.yaml. Usar cuando el usuario diga "variaciones", "versiones", "prueba A/B de estética", escriba /variaciones o pase un variaciones.yaml.
---

# Variaciones

Entrada: variaciones.yaml (base + lista). Salida: un mp4 por variación, junto a la base y
con su nombre más el de la variación: salida/<nombre>.montaje.<variacion>.mp4.

## Antes de renderizar
1. Lee variaciones.yaml. Si una variación tiene más de un campo en "cambia", para y dilo:
   una variable por versión, sin excepción. No lo arregles tú: que el usuario elija cuál.
2. Si base.ya_funciono_organicamente es true, no varíes nada: ese video ya es la base. Dile
   al usuario que las variaciones se prueban en el siguiente video y para.
3. Comprueba que cada cambio se vea en el celular, porque ahí se ve el video:
   - tipografía: la familia nueva tiene que cambiar la forma de las letras a simple vista
     (una sans por una serif, una grotesk por una redondeada). Dos sans parecidas no son una
     variación: dilo y pide otra.
   - saturación: valores entre 0,7 y 1,3; menos de 0,1 de diferencia con la base no se nota.
   - música: se elige por la emoción del video. Si el usuario pasó una cama "porque la
     tenía a mano", pregúntale qué emoción tiene el video y si esa cama calza. Una cama de
     otro cliente suena chillona en el suyo.
4. Preview antes de renderizar: 10 s alrededor de la frase de impacto, a la mitad de la
   resolución, por cada variación. El usuario los mira en el celular y aprueba. Recién ahí
   renderizas entero. No renderices para averiguar.

## Cómo se aplica cada cambio
Siempre sobre el montaje sin subtítulos y la pista de voz limpia (<nombre>.montaje.sin-subs.mp4
y <nombre>.voz.wav, que el montaje con el spec deja en salida/); nunca sobre un mp4 ya
subtitulado.
- tipografia: vuelve a quemar los subtítulos con la familia nueva y todo lo demás igual al
  spec (tamaño, posición, contorno, nunca achicar, partir en 2 líneas).
- musica: mezcla la voz con la cama nueva respetando audio.musica del spec: arco vacío /
  construcción / llegada, llegada exacta sobre la frase de impacto, nivel bajo la voz, y el
  silencio de silencio_antes_del_cta_s antes del CTA.
- saturacion: filtro eq=saturation=<valor> sobre el video. Nada más.
Todo lo que no cambia se copia idéntico. Misma resolución que la base, libx264 crf 18,
aac 192k, faststart. Verifica cada archivo con ffprobe antes de decir listo.

## Salida
Una tabla: versión | qué cambia | valor | archivo | duración · y debajo la línea
"una variable por versión: cumplido".

## Cómo se prueba (díselo al usuario al terminar)
1. Publica la base y UNA variación con dos días de diferencia, misma hora, mismo caption.
2. A los 3 días compara el número que importa: conversaciones, no vistas.
3. La que gana pasa a ser la base. La siguiente semana, otra variable.
Lo que esta skill NO hace: cambiar el guion ni el gancho. Eso es otro video, no una variación.

Pieza F · Biblioteca de specs: seis formatos (plantilla)

Seis archivos de spec, uno por formato de video: talking head (tú a cámara), pantalla dividida (tú y una pantalla, o un antes y después), mosaico (varios clips a la vez), imagen sobre imagen (tú en chico sobre una pantalla grande), texto sobre video (frases sobre b-roll, sin cara) y toma única (un plano sin cortes). Cada archivo hereda la base de la Pieza A y escribe solo lo que cambia; Claude Code fusiona los dos (lo del formato pisa a la base). Se guardan en specs/. La usa el Ángulo 3. Los colores y la tipografía que vienen puestos son de ejemplo: cambia esos dos por los tuyos y deja el ritmo quieto la primera semana. Los seis van en un solo bloque; cada # specs/... es un archivo aparte.

# specs/talking-head.yaml · tú a cámara explicando
hereda: edit-spec.yaml
colores: { fondo: "#0A0B0D", acento: "#C9A840", texto: "#F4EFE4" }   # de ejemplo: pon los tuyos
tipografia: { familia: "Space Grotesk", peso: 600 }
layout:
  modo: talking_head
  cortes: { cada_s: 4 }         # medido; no lo cambies la primera semana
  zooms: 0                      # el zoom corporal le gana al digital; si no, 1 suave en la frase de impacto
subtitulos:
  corrido: { tamano_px: 64, estilo: palabra_a_palabra, posicion: tercio_inferior, resaltar: acento }
audio:
  musica: { cama: electronica_suave, nivel_db_bajo_la_voz: -18 }
transiciones: { entre_tomas: seca, entre_bloques: flash_blanco_2_cuadros }

# specs/pantalla-dividida.yaml · tú y una pantalla, o un antes y después
hereda: edit-spec.yaml
layout:
  modo: split_horizontal
  split: { arriba: camara, abajo: pantalla, proporcion: [0.4, 0.6], audio_de: camara }
  cortes: { cada_s: 4 }
subtitulos:
  corrido: { posicion: franja_central, tamano_px: 56 }   # sobre la unión, sin tapar la pantalla

# specs/mosaico.yaml · varios clips cortos a la vez
hereda: edit-spec.yaml
layout:
  modo: mosaico
  mosaico: { celdas: 4, dura_cada_una_s: 3, orden: [1, 2, 3, 4], audio_de: 1 }   # orden: izquierda a derecha, arriba a abajo
subtitulos:
  titular: { activo: true, fijo_todo_el_video: true, posicion: tercio_superior }
  corrido: { tamano_px: 56 }
transiciones: { entre_tomas: seca, entre_bloques: flash_blanco_2_cuadros }

# specs/imagen-sobre-imagen.yaml · tú en chico sobre una pantalla grande
hereda: edit-spec.yaml
layout:
  modo: pip
  pip: { ancho: 0.22, esquina: inferior_derecha, borde_px: 0, sombra: suave }   # cara al 20-25 %: el fondo deja de importar
subtitulos:
  corrido: { posicion: tercio_inferior, tamano_px: 56 }   # nunca encima del recuadro

# specs/texto-sobre-video.yaml · frases sobre b-roll, sin cara
hereda: edit-spec.yaml
layout:
  modo: texto_sobre_video
  texto: { fuente: sans, peso: bold, tamano_px: 96, animacion_entrada: aparece_en_seco,
           dura_cada_frase_s: 3, max_lineas: 3, posicion: centro }
  broll_desde: frames/broll/      # tus clips; se cortan cada dura_cada_frase_s
subtitulos:
  corrido: { activo: false }       # el texto grande ES el subtítulo
audio:
  musica: { nivel_db_bajo_la_voz: -12 }   # sin voz encima, la música puede subir un poco

# specs/toma-unica.yaml · un plano sin cortes
hereda: edit-spec.yaml
layout: { modo: toma_unica, cortes: none, zooms: 0 }
estetica: { grading: neutro }      # solo color, subtítulos y música; nada más se toca
subtitulos:
  corrido: { tamano_px: 64, posicion: tercio_inferior }
audio:
  cortar_pausas: false             # en toma única las pausas son parte del plano
transiciones: { entre_tomas: none, entre_bloques: none }

Lo que NO resuelve: no edita, es el archivo que lee el editor.

Errores conocidos

Lo que ves Por qué pasa Qué haces
La salida pesa lo que debe pero no abre, o abre negro MP4 a medio escribir: ffmpeg se cortó antes de cerrar el archivo ffprobe sobre el archivo: si da error, vuelve a renderizar desde el original, una sola vez, y espera a que termine
Dice 1080 por 1920 y se ve borroso en el celular Por dentro es un 540 estirado: se reescaló en algún paso Claude Code reporta width,height reales con ffprobe y vuelve a codificar desde el original sin ningún scale
El corte tarda muchísimo Lees el video desde una carpeta de red, compartida o del teléfono: hasta un 45 por ciento más lento Copia el archivo a entrada/ en el disco y vuelve a correr
Cortó a mitad de una palabra No se usó el cruce con los tiempos por palabra Mira salida/mi-grabacion.limpia.cortes.json: si los cortes no traen última palabra antes y primera después, el script no corrió entero; vuelve a correrlo
Suena atropellado, quedan colas de aire, no detecta silencios o corta pausas que querías dejar Umbral, mínimo o margen fuera de lugar para tu grabación Tabla de la Pieza C: --margen de 0,12 a 0,20 si atropella; --umbral de -35 a -30 si no detecta; de -35 a -40 o --minimo más alto si corta de más
Sobra ruido de fondo después de cortar Casi siempre sobra tiempo, no falta filtro Corta más. Nunca filtros de ruido ni compuertas: rompen la voz
La primera corrida se queda quieta varios minutos Whisper descarga el modelo la primera vez Espera. Es una sola vez y no sube tu video a ningún lado
Corte por guion: frase NO ENCONTRADA o «menos mala» No la dijiste entera ninguna vez, o las tres tomas salieron mal: la skill no mejora tomas ni inventa cortes Regraba esa frase (diez segundos) y vuelve a correr
Corte por guion: tomas raras o el orden quedó mal Pasaste el guion original y al grabar cambiaste el orden Pásale el guion en el orden en que SE GRABÓ
Corte por guion: se perdió un tramo bueno Improvisaste largo fuera del guion: la skill no sabe qué guardar de eso Ese tramo va por corta-silencios, o lo escribes en el guion y regrabas
Claude Code editó «a su gusto» y no como tu spec No leyó edit-spec.yaml (no está en la raíz o quedó .yaml.txt), o el pedido no lo nombró Comprueba el nombre exacto y que CLAUDE.md esté en la raíz; usa el pedido de la Pieza A tal cual
Muchas «decisiones no cubiertas» Normal las dos primeras veces: el spec se termina usándolo Pégalas en edit-spec.yaml. Con una tanda a cero decisiones nuevas, está listo
La música suena a sala de espera, o chillona Cama «upbeat corporate» o «inspiring background», o elegida porque estaba a mano Elígela por la emoción del video
El video se ve «de principiante» aunque vendes caro Tier llamativa, o mezcla de tiers, en una oferta de ticket alto tier: sobria, sin mezclar
Todo destacado y nada pega, o el subtítulo se achica Doble capa en varias frases, o se achicó la fuente para que entrara Doble capa una sola vez, sobre el CTA; achicar_si_desborda: false, se parte en dos líneas
Una variación «no se nota», o no sabes cuál ganó Tipografía parecida a la base, saturación con menos de 0,1 de diferencia, o dos cambios en la misma versión Hazla visible en el celular, una variable por versión, y rehaz la prueba

Cuando ya lo tienes hecho

Con los videos saliendo solos, el cuello de botella pasa a ser lo que llega después de publicar: quién responde los mensajes, cómo se separa al que puede pagar del que no, y cómo se agenda sin que tú estés. Eso ya no es un archivo: es el setter de IA que atiende Instagram y WhatsApp, el siguiente paso natural después de este.

Si quieres que lo montemos en tu negocio, escríbeme por WhatsApp y te digo por dónde partir.

Si quieres que lo montemos en tu negocio

Esto lo puedes hacer tú con Claude siguiendo la página. Si prefieres que lo dejemos funcionando contigo, escríbeme y te digo por dónde partir.

Escribirle a Ignacio por WhatsApp