Edición de video (grabación de pantalla / OBS / charla)
Kit de scripts: scripts/ en esta skill (prep.sh, transcript_txt.py, ocr_scan.py,
cam_flags.py, df_enhance.py, build_edl.py, render.py, verify_output.py,
chapters.py, overview.py, job.example.json, selftest.sh). Ejecútalos desde una copia en
<workdir>/scripts/ para que un trabajo nunca dependa del directorio de la skill. scripts/selftest.sh demuestra
una instalación completa en un par de minutos.
Entradas
<src>: la grabación, normalmente el más reciente~/Desktop/*.mov(nomenclatura de OBSYYYY-MM-DD HH-MM-SS.mov). Si hay varias grabaciones recientes, confirma cuál ANTES de iniciar el largo pipeline; editar el video equivocado desperdicia una hora.<target_minutes>: pregunta si no se proporciona; normalmente 20-30 a partir de una fuente de 45-90 min.<workdir>: directorio temporal nuevo, ej./tmp/<session>/edit.- Cadena de herramientas:
ffmpeg6+,python3,mlx_whisper, DeepFilterNet3, Pillow/numpy/soundfile, Apple Vision (pyobjc-framework-Vision/-Quartz).
Pasos
- Reconocimiento, nunca tocar la fuente.
ffprobe <src>(resolución, fps, audio). Trabaja solo con copias/extracciones en<workdir>. - Preparación.
prep.sh "<src>" $W:orig48.wav,mono16.wav,frames/(1 cada 2 s),camall/(recorte de webcam a 1 fps, geometría derivada automáticamente de la resolución),sheet_overview.jpg,silences_raw.txt(solo diagnóstico), ytranscript.json(mlx_whisper large-v3-turbo, marcas de tiempo por palabra, ~4 min). Whisper sigue ejecutándose en segundo plano después de que prep regresa; iniciadf_enhance.py orig48.wav df_enhanced.wav(DeepFilterNet3, ~6 min CPU) AHORA, es independiente. - Lee toda la transcripción (3 fragmentos mediante
transcript_txt.py ... --from/--to). Escribe los rangos a conservar (segundos de la fuente) enjob.json -> keep. El recorte de silencios es menor (~2 de 47 min); los minutos reales provienen de decisiones de contenido. Whisper se informa a sí mismo: una frase repetida 10+ veces o segmentos vacíos = tiempo muerto, córtalo. - Paso de estática visual. "No pasa nada" a menudo es estático visual, NO silencio. Diferencia de fotogramas en los frames a 0.5 fps: <1% de píxeles cambiados en una ventana de ~10 s = inactivo; fusiona ejecuciones separadas por destellos <=1 s; requiere >=4 s para cortar; 0.5 s de manejo. Cursores parpadeantes/spinners simulan actividad: enmascara o eleva el umbral; verifica fotogramas intermedios en busca de ráfagas de cambio-y-reversión. La escritura es ruido, no silencio: las compuertas usan RMS en la pista mejorada (
pause.loud_db), nuncasilencedetecten bruto. - Apertura. Corta todo antes del primer contenido real. Si el inicio es lento/aburrido, comienza con la demo más interesante; opcionalmente abre con 1-2 clips emocionantes y luego acelera la introducción. Nunca abras con fondo de pantalla/negro: unos primeros 30 s entrecortados se leen como basura.
- Escaneo de filtraciones (se ejecuta mientras lees).
ocr_scan.py scandespués derunenframes/: IPv4/v6,*.ts.net, correos electrónicos, claves API, material SSH. Pase denso a 2 fps en cada rango de terminal/configuración densa (ocr_scan.py dense src.mov dense 600:60 ...). Texto estático ->boxes(relleno sólido); salida de terminal con desplazamiento -> desenfoca todo el panel (blurventana +blur_regions, en píxeles de origen) para la ventana; las cajas por línea pierden texto entre muestras.127.0.0.1/localhostestán bien. Registra cada ventana enjob.json. - Cortes de privacidad (webcam).
cam_flags.py(mano cerca de la boca mediante Vision) +cam_flags.py --sheet S:DURhojas ampliadas a 2 fps de cada grupo marcado. Corta los momentos que el usuario no publicaría: mano en la cara durante una pausa, salir del escritorio, otra persona en el encuadre, segmentos de rostro a pantalla completa. Las poses de pensamiento normales y los gestos pequeños están bien. NUNCA cortes a mitad de una frase por un gesto; salta al límite de oración más cercano y anota lo que se perdió. - Los cortes basados en marcas de tiempo del usuario son literales. "corta 0000 - 01:12" -> ponlo en
job.cuts(okeep), aplícalo exactamente, luego vuelve a verificar el ritmo alrededor de la unión. - Maestría de audio. Sanidad: correlación mejorado-vs-original > 0.95 (
df_enhance.pylo imprime). Laaudio_chainpredeterminada es paso alto 75, cortes/refuerzos alrededor de 220 Hz / 3.2 kHz / 9 kHz, de-esser, compresor 3:1; elimina cualquier tono de ventilador persistente añadiendo un términoequalizer;render.pyhace la normalización de dos pasos loudnorm a -16 LUFS / -1.5 dBTP. El micrófono sin procesar se mantiene como pista de audio 2. - EDL + renderizado.
build_edl.py->segments.json(pausas >0.7 s dentro de los keep acortadas a 0.4 s; ajustadas a la velocidad de fotogramas probada; <0.3 s eliminadas).render.py: codificacionesh264_videotoolboxparalelas agrupadas, concat-c copy, multiplexa ambas pistas. Es reiniciable por grupo: si un grupo falla, arréglalo y vuelve a ejecutar, los grupos no modificados se saltan. Entrega 1080p a menos que se pida otra cosa (4K duplica el tiempo de renderizado para nada). - Verificar antes de entregar.
verify_output.pysale con código distinto de cero a menos que el recuento de fotogramas coincida con el EDL, ambas pistas de audio coincidan con la duración del video, y el OCR de la salida a 4 fps en cada ventana de redacción encuentre cero filtraciones. También extrae fotogramas de salida en cada corte de privacidad y en 0:00-1:00 y MÍRALOS. Usa--fullpara un barrido completo de filtraciones cuando el material sea sensible. - Vuelve a transcribir el nuevo corte. La transcripción, los subtítulos y los capítulos deben coincidir con la línea de tiempo de SALIDA, no con la fuente: vuelve a ejecutar whisper en el corte (o
chapters.py, que reasigna a través del EDL) antes de escribir SRT/capítulos. Nunca envíes tiempos de SRT tomados de la transcripción fuente. - Metadatos + entrega. Sin que te lo pidan dos veces: marcas de tiempo de capítulos (
chapters.py, >=3, primero en 0:00), 2-3 opciones de título, ~30 etiquetas con menos de 500 caracteres (+2 variantes de etiqueta/descripción si se solicita), descripción, SRT por idioma (~42 caracteres/línea; multilingüe si se solicita), todo como texto plano. Miniatura mediante youtube-pack: lee las últimas 10 miniaturas del usuario para conocer el estilo; fondo blanco/crema por defecto + texto negro, sin rostro a menos que se solicite, logotipos/texto grandes y sin saturar. Luego mueve<output>.mp4(y SRTs/metadatos) a~/Descargasyopenel archivo / abre la carpeta en la aplicación predeterminada.
Salidas
<Topic> - YYYY-MM-DD - edit.mp4(1080p, 2 pistas de audio) en~/Descargas, abierto en la aplicación predeterminada.<workdir>:job.json,segments.json,group_bounds.json,vf_<n>.txt, registros de redacción/privacidad.~/Descargas: SRTs,metadata.txt(títulos, descripción, etiquetas, capítulos).- Opcional si se solicita: subvideos temáticos divididos de la misma fuente, cada uno con sus propios capítulos.
Se compone con
whisper/DFN3 (en kit) -> video-edit (mp4 verificado en ~/Descargas) -> youtube-pack (thumbnail.png) -> subida al navegador a YouTube Studio configurado como los otros videos del usuario.
Reglas
- Nunca modifiques la grabación fuente.
- La duración objetivo es un objetivo; si el contenido no se reduce sin desvirtuar la charla, entrega ligeramente por encima y nombra los dos siguientes cortes.
- Corta digresiones completas antes de recortar más pausas; mantén la charla técnica, las demos en vivo, las salvedades honestas, el resumen.
- Termina siempre con: mover a ~/Descargas + abrir.
- Informa fielmente: no puedes oír el resultado, dilo y señala la pista 2.
- El audio grabado por separado (Discord/Craig) normalmente no existe; revisa Escritorio/Descargas/iCloud/Notas de Voz, si no está, usa el micrófono de OBS y dilo.
- Cuando te pidan liberar espacio: elimina borradores/variantes antiguos no aceptados, conserva la fuente y el final.
- Podcasts con varias personas: encuentra las pistas de audio CORRECTAS (normalmente 2 de muchas), asigna el audio al orador correcto, alterna el video con la persona que habla, transiciones limpias.
- Nunca afirmes que la edición está completa porque el renderizado terminó; solo
verify_output.py+ fotogramas revisados visualmente lo confirman.
Verificación
verify_output.pyimprime PASS (recuento de fotogramas, dos pistas de audio, sin filtraciones en ventanas) y sale con 0.- Fotogramas revisados visualmente en 0:00-1:00 y en cada corte de privacidad (
verify_output.py --sheet 0:60). - Verificación puntual de la re-transcripción SRT contra el corte.
ffprobedel archivo entregado: duración, resolución, fps, 2 flujos de audio.
Fallos conocidos
- Video equivocado editado -> confirma que el más reciente/tema coincida antes del pipeline.
- Subtítulos/capítulos en la línea de tiempo fuente -> vuelve a transcribir después del corte (paso 12).
- Nombre de host visible en la salida -> texto desplazándose entre muestras de OCR; desenfoca todo el panel, amplía la ventana, luego OCR del lado de la salida.
- Silencedetect corta la escritura/reproducción de video -> compuerta basada en RMS de audio mejorado (
pause.loud_db), no solo silencio. - Apertura negra entrecortada -> verifica fotogramas de salida 0:00-1:00, abre con contenido real.
cp -isolicita sobrescritura / el sandbox TCC bloquea~/Descargas-> usa/bin/cpcon sandbox desactivado.- El filtro ffmpeg muere ("Cannot allocate memory") pasadas ~100 cláusulas
between()-> menosrender_groups. - El cursor parpadeante cuenta como actividad en la diferencia de fotogramas -> enmascara/ajusta el umbral, verifica fotogramas intermedios.
render.pydesajuste de fotogramas -> se niega a concatenar; leerender_<n>.log(-ssantes de un keyframe, decodificador roto).- Whisper aún se ejecuta cuando lees
transcript.json->transcript_txt.pyreporta segmentos vacíos; espera a quewhisper.logtermine.
Ejemplos
- Charla del 2026-09-02, 47 min -> 26 min: 32 rangos keep, 2 cortes de privacidad (9:52-9:58, 26:08-26:14), 2 ventanas blur + 1 box para un nombre de host
*.ts.net, renderizado ~2 h de reloj, filtración detectada solo por OCR del lado de la salida. - Sesión de enseñanza OBS del 2026-09-24: el usuario rechazó el primer corte ("ruido de fondo, escritura, comienzo muy aburrido") -> se eliminó el ruido de escritura, se eligió una mejor apertura, se rehicieron las transcripciones, luego corte literal de cabeza
0:00-1:12, escaneo de filtraciones, miniatura blanca/crema con logotipos más grandes, 2 variantes de metadatos, se movió a Descargas. - Podcast (2026-09-18): 1h40m de video + 5 archivos de audio candidatos -> se identificaron las 2 pistas correctas por orador, se mapeó la voz al video, 1080p, recorte de cabeza, SRT + traducciones ES/AR/HI, marcas de tiempo de capítulos, miniatura con ambos oradores, texto en la parte superior, sin fondo negro.