promo-video — Pipeline de video promocional / anuncio de costo cero
Convierte un proyecto web en un video promocional o anuncio social. Idea central: si la aplicación se mueve por sí sola (con cámara virtual), grábala con captura de pantalla; si es un proyecto de imágenes, descarga las seleccionadas y haz un montaje Ken Burns. El resto (narración, verificación de audio, composición, marca y CTA) se comparte entre ambos modos. Probado en la práctica: battlefield-editor/tools/trailer (captura de pantalla) + anuncio de Catime "El gato que lee el mundo" (Ken Burns + noticias → imágenes reveladas) + el juego completo de "Grich y el Sr. Agujero Negro" en siete capítulos (variante con audio de captura de pantalla, 60 minutos, 700 líneas de diálogo automatizadas: https://youtu.be/J9OMebCjr9Y).
Árbol de decisiones: primero elige el modo de captura
- App con movimiento (3D/canvas/cámara automática, con auto-play) → MODO A Captura de pantalla (
capture-app.mjs). - Generación de imágenes / galería / feed de fotos (imágenes AI, galería) → MODO B Montaje Ken Burns (descarga imágenes seleccionadas +
kenburns.sh). - App que reproduce su propio audio (Variante con audio del MODO A; qué reproduce y por cuánto tiempo lo decide la aplicación en tiempo real, no tienes una pista de audio completa) → No uses
recordVideo, en su lugar usaffmpegpara capturar video y audio simultáneamente (ver sección "Cuando la app reproduce su propio audio: grabación real"). Si tienes una pista de audio completa, no uses este camino; mezclar después (mux) da mejor calidad de audio (GOTCHA 24). - Repositorio no visual (CLI / librería / API / backend / pipeline de datos, sin imágenes que grabar) → MODO C Terminal explicativa (
term-player.html+capture-term.mjs): convierte "comando + salida real" en una grabación de terminal con animación de escritura. Para herramientas de desarrollo, haz una "versión narrada educativa", no un "avance rápido con cortes" (ver GOTCHA 22): estructura = hook del problema → pasos (instalar → ejecutar → entender la salida → comandos avanzados) + voz narrada con edge-tts explicando paso a paso + subtítulos de los pasos → CTA. La sincronización de la narración se basa enwindow.__STEPLOGdeterm-player.html(segundo en que aparece la salida de cada paso).composeaplicaadelaya cada frase de narración para alinearla con su paso. En la práctica: video educativo CLI de estado multi-repo (WIP, ~60s, música de fondo al 0.13 para que la narración sea la protagonista). - Video corto de frases destacadas de una transmisión en vivo (9:16 Reels/Shorts) → MODO E Grabación con plantilla (
short-9x16.html+capture-short.mjs): primero extrae frases destacadas de la transcripción y corta el clip 16:9 en un pipeline de curso; aquí lo incrustas en una plantilla HTML 9:16 y lo grabas. ¿Por qué HTML en lugar de filtros ffmpeg? Recortar también se puede con ffmpeg, pero los efectos de palabras clave en CSS son 10 líneas; hacer el mismo efecto con formato de subtítulos ASS es un infierno; ajustar el diseño también es cuestión de cambiar CSS y verlo de golpe, sin tener que modificar ciegamente el filtergraph. Límite: el pipeline del curso corta, promo-video empaqueta. La interfaz es "un mp4 ya cortado + marcas de tiempo de la línea de tiempo del producto final". - Modo híbrido: captura de pantalla como principal, con Ken Burns / terminal como complemento, todo es posible.
Cuatro fases
- Captura — MODO A:
xvfb-runejecuta Chromium con interfaz gráfica (necesario para WebGL), PlaywrightrecordVideograba la parte destacada con reproducción automática; MODO E va headless (ver GOTCHA 47). MODO B: obtén la lista de fuentes, selecciona, descarga. - Narración (opcional) — edge-tts
zh-TW-YunJheNeural --rate=-5%genera narración. - Verificación (opcional) — Gemini Pro escucha y verifica el tono de la narración; Gemini Flash selecciona música de fondo.
- Composición —
brand.pygenera materiales de marca (tarjetas de texto, logotipos, CTA, QR), ffmpeg superpone subtítulos, concatena, mezcla audio, produce múltiples formatos + versión resumida.
Las seis plantillas aquí (configura la parte superior o pasa parámetros para usarlas)
brand.py— PIL: compositor que mantiene la proporción cover/contain, tarjetas de texto, logotipos fijos, tarjetas CTA, mini galería, QR (vía API).kenburns.sh— Ken Burns sin vibración (clip) + texto pegado al borde de la imagen + concat + mezcla de audio (principal para MODO B).capture-app.mjs— Captura de pantalla con Playwright + xvfb (MODO A); incluye servidor HTTP interno, oculta HUD, hace clic en nodos para saltar secciones / maneja juegos con teclado.term-player.html+capture-term.mjs— MODO C Terminal explicativa: reproductor de terminal en HTML (animación de escritura + salida a color) + grabación con xvfb. No necesita asciinema/xterm.short-9x16.html+capture-short.mjs— MODO E: plantilla 9:16 (diapositivas centradas, subtítulos grandes arriba y abajo, palabras clave agrandadas con rebote, logotipo fijo, barra de progreso) + grabación con xvfb + mezcla posterior del audio original. La cuadrícula de diapositivas tiene cámara automática: mide dónde están las palabras en la imagen, cambia la composición cada 2-3 segundos, y cuando se pronuncia una palabra clave, acerca y resalta (GOTCHAS 32-37). Para regrabar, usa--tag v2para cambiar el nombre del archivo, no sobrescribas el producto anterior — el anterior es la línea base de comparación. Después de grabar, imprime automáticamente "número de eventos de brillo/segundo" y "volumen del producto" para control de calidad, no se necesita verificación adicional.relevel.py— Para videos ya lanzados que necesitan ajuste de volumen: solo modifica el audio,-c:v copy. Comprobado que el md5 del flujo de video antes y después es el mismo. Un clip de un segundo. No es necesario para grabaciones nuevas (capture-short.mjsya lo ajusta al hacer el mux).audition.py— Gemini escucha:music "<uso>" *.mp3(selecciona música de fondo con puntuación 0-10),tone "<texto>" v.mp3(verifica el tono de la narración).
Cuando la app reproduce su propio audio: grabación real
GOTCHA 24 habla del caso en que tienes una pista de audio completa (video musical, narración edge-tts): mezcla posterior (mux) con el archivo original, la mejor calidad de audio. Pero hay un caso en que no se puede hacer — qué se reproduce y por cuánto tiempo lo decide la aplicación en tiempo real. En la práctica: un juego completo de novela visual, 700 archivos de voz reproducidos según el orden de la historia, no hay una "pista de audio completa" para sincronizar.
Entonces, abandona completamente recordVideo de Playwright y usa ffmpeg para capturar video y audio simultáneamente:
# 1. Abre un receptor de audio independiente, dirige el audio del navegador allí, sin mezclar otros sonidos del escritorio
MOD=$(pactl load-module module-null-sink sink_name=cap)
# 2. Tu propia pantalla X
Xvfb :77 -screen 0 1280x808x24 -nolisten tcp &
# 3. Graba video + audio juntos, escribe archivos por segmentos (si falla, solo pierdes un segmento)
ffmpeg -f x11grab -draw_mouse 0 -framerate 25 -video_size 1280x808 -i :77.0 \
-f pulse -i cap.monitor \
-vf crop=1280:720:0:88 \
-c:v libx264 -preset veryfast -crf 24 -pix_fmt yuv420p \
-c:a aac -b:a 160k \
-f segment -segment_time 600 -reset_timestamps 1 seg/part-%03d.mp4 &
# 4. Ejecuta el navegador (DISPLAY=:77 PULSE_SINK=cap), al final concatena
Cinco puntos aprendidos en la práctica:
--kioskno funciona con Playwright, gestiona su propia ventana. La barra de pestañas + barra de direcciones suman 88 px fijos. Abre la pantalla 88 px más grande y usacroppara recortarlo. Oculta el cursor con-draw_mouse 0.- No uses un tiempo fijo para pasar páginas. La duración de cada línea de voz varía; esperar demasiado poco corta el final de la voz, esperar demasiado duplica la duración. Intercepta
new Audio(), y cuando termine la reproducción de voz, haz clic en la siguiente diapositiva (esos elementos no están en el DOM,querySelectorno los encuentra). - Es obligatorio tener una verificación de "si el contenido ha avanzado". Sin ella, al encontrar una tarjeta que no se puede hacer clic, se seguirá haciendo clic hasta el límite, y ffmpeg no reportará ningún error: en la práctica, se grabó durante 58 minutos, el archivo parecía normal, pero al final solo se había recorrido una cuarta parte del libro. El criterio es "si el texto en la pantalla ha cambiado", y incluso si el texto está vacío, se considera que no ha avanzado — la pantalla final sin texto, si se omite esta regla, se graban 33 minutos adicionales.
- Escribe archivos por segmentos (
-f segment), si falla a mitad de camino, solo se pierde un segmento, luego concatena conconcat -c copysin pérdida. - Al terminar, verifica cuatro cosas, no solo si el archivo existe: duración total, cuántas diapositivas se recorrieron, desviación estándar de un fotograma de muestra (cerca de 0 = pantalla vacía), volumen promedio con
volumedetect(silencio indica que el null sink no se conectó, y tampoco se reporta error).
En la práctica: glitch-vn/tools/capture/ (record.sh + play_through.mjs). El producto final es el juego completo de "Grich y el Sr. Agujero Negro" en siete capítulos, 60 minutos, 700 líneas de voz, todo ejecutado sin intervención humana: https://youtu.be/J9OMebCjr9Y
GOTCHAS — Esta es la parte más valiosa, revísala cada vez
-
Mantén la proporción: Nunca uses
scale=W:Hen imágenes de proporciones mixtas (se estirarán o deformarán). El fondo usa cover (amplía proporcionalmente y recorta), el sujeto usa contain (reduce proporcionalmente y coloca). Elscalede ffmpeg solo puede ser múltiplos enteros (2x) o conforce_original_aspect_ratio. Antes de terminar, ejecuta una auditoría de "proporción original vs. proporción mostrada" (error <0.1% para aprobar). -
Vibración de Ken Burns: primero amplía la fuente 2x y luego reduce (el salto de píxeles enteros se convierte en subpíxeles) + la escala usa número de fotograma lineal
z='1+K*on/(D-1)', nunca usesz='min(zoom+inc,...)'que acumula redondeo. -
Explosión de fotogramas de zoompan: No uses
-loop 1 -t(cada fotograma de entrada se multiplica por d); para una sola imagen, usa-frames:v D+zoompan d=D. -
Texto pegado al borde de la imagen: primero centra verticalmente la imagen; la posición del texto = centro ± (altura de la imagen × zmax)/2 ± gap, calcula por imagen (PIL genera la altura de cada imagen como sidecar). Así, las imágenes horizontales no quedarán muy separadas ni se superpondrán.
-
Fetch de file:// bloqueado: la aplicación usa
fetch(?pkg=)/carga JSON → iniciapython3 -m http.serverpara servir vía http (o usa el servidor interno decapture-app.mjs). -
WebGL headless pierde Swiftshader/se vuelve completamente negro → usa
xvfb-run -a --server-args="-screen 0 WxHx24"para ejecutar Chromium headed. -
Aplicación modular ES:
gotoPhaseno está enwindow→ haz clic en los nodos de la línea de tiempo del DOM#tlNodes .node[i].click(), no usesevaluate(gotoPhase). -
QR: PEP668 no puede instalar qrcode → usa
api.qrserver.com/v1/create-qr-code/?data=<enlace_público>(solo para enlaces públicos); antes de publicar, escanea con el teléfono para confirmar. -
Caracteres con pronunciación incorrecta en edge-tts: primero evítalos ("没 mò" → cambia la palabra); la verificación del tono se hace con Gemini Pro preguntando por la dirección del tono, Whisper STT no puede verificar el tono (incluso si se pronuncia mal, transcribe el mismo carácter).
-
Música de fondo: Gemini Flash selecciona CC0, que sea adecuada (no pongas música de guerra en un video de gatos); anota la fuente CC0.
-
Múltiples formatos: 16:9 = YouTube, 9:16 = Reels/Shorts/LINE, 1:1 & 4:5 = Facebook/IG feed. MODO A: regraba nativamente cada formato (viewport vertical); MODO B: recompone cada formato.
-
Logotipo fijo (marca de agua): marca + URL en una esquina de la pantalla durante todo el video (no solo al final), superpone una PNG transparente en todo el clip.
-
El anuncio debe impulsar la acción: gancho en los primeros 2 segundos (reproducción automática silenciada, el primer fotograma ya tiene imagen/texto grande), subtítulos quemados (Facebook silenciado), CTA con una razón para "hacerlo ahora".
-
Detalles de la plataforma: en Facebook, publica el video en el feed, pon el enlace en el primer comentario (para evitar que se reduzca el alcance); usa
+movflags +faststart; para insertar en LINE, usa el álbum/botón de video, no "archivo". -
Espera a que la carga se complete y verifica que no esté vacío: MODO A/galería, captura de pantalla por elemento no uses un tiempo fijo (el lento capturará una pantalla vacía de "cargando"). Espera a que el indicador de carga desaparezca antes de capturar; después de capturar, mide la desviación estándar del área objetivo (≈0 = vacío/sin renderizar), detecta automáticamente y vuelve a capturar. En la práctica: roll-formosa Miaoli tardaba 1.8s fijos y capturaba en blanco; se arregló con wait-for-load + escaneo stdev.
-
Amplía para ver, no te fíes solo de las miniaturas: verifica extrayendo fotogramas a resolución completa y examínalos en detalle (recortes, bordes borrosos, cortes que no se ven en miniatura). Al medir coordenadas, dibuja líneas de cuadrícula y/x en la imagen original. En la práctica: el borde inferior de un punto de referencia estaba recortado 55 px, la mitad inferior borrosa; solo se detectó al ampliar y dibujar la cuadrícula.
-
Juego interactivo sin auto-play → envía teclas para jugar solo (submodo de MODO A): Katamari/juegos de acción usan
keyboard.down/upde Playwright para controlar (teclas de dirección para rodar). El juego a menudo comienza ~10s después de la carga (precache/PREPARING): primero espera hasta START (sondea si el botón está listo) → haz clic en START → luego envía teclas. Un bot ciego podría no agarrar nada/no crecer (roll-formosa en horizontal se quedaba en 2cm) — toma la "ventana con más imagen" (bola suficientemente grande/muchos objetos), o cambia al siguiente showcase. -
Gameplay difícil de grabar → usa showcase/galería autónoma como material: muchas aplicaciones tienen páginas de "galería/vista previa de objetos" que se mueven solas (ej.
preview.html?city=X), sin necesidad de jugar, renderizan todo el contenido, lo que es una fuente confiable de montaje (los 20 puntos de referencia de roll-formosa son así). Estos objetos sobre fondo oscuro usa fondo oscuro liso, no fondo borroso (de lo contrario, salen fantasmas borrosos). -
Pantalla de "listo" al final de la grabación:
recordVideograba desde la carga, la pantalla de PREPARANDO→LISTO aparece en la parte final; graba lo suficiente (hasta START + 3s adicionales), recorta desde la parte final la sección lista, no desde el principio (puedes capturar la carga). -
Viewport vertical de teléfono a menudo es mejor: el diseño móvil del juego a veces es más completo, incluso el comportamiento es diferente (roll-formosa en vertical la bola crece, en horizontal se atasca). Al regrabar nativamente cada formato, verifica qué viewport funciona mejor.
-
Texto preciso: la presentación del protagonista/objeto usa personalidad/peculiaridades, no apariencia (reparto de Catime); la línea "noticia → imagen" del anuncio debe derivarse del campo idea/historia de la fuente de datos, no inventarla (si la noticia es incorrecta, queda ridículo).
-
Avance ≠ educativo, no confundas los formatos: los proyectos visuales (juegos/apps) son adecuados para avances rápidos de 15-20s (tease, captar atención); pero las herramientas CLI/dev hechas con avances rápidos son muy aburridas, no educativas — haz un video educativo narrado de ~60-90s: hook del problema →
instalar → ejecutar → entender la salida → avanzadopor pasos + edge-tts explicando paso a paso (lento, suficiente pausa en cada paso) + subtítulos de los pasos → CTA, música de fondo muy baja (~0.13) para que la narración sea la protagonista. En la práctica: la primera versión del avance rápido de WIP fue criticada por aburrida; al cambiarla a la versión educativa narrada, los usuarios sintieron "entiendo, lo usaré". La sincronización de la narración usa__STEPLOGdel reproductor de terminal. El reproductor de terminal debe limpiar la pantalla en cada paso (GOTCHA 16 mismo problema: el contenido acumulado se escribe fuera del viewport, los comandos mencionados se desplazan). -
Primero lee el README completo /
--helppara hacer una lista de funciones, luego escribe el guión: para hacer un tutorial/explicación de una herramienta, no empieces a grabar solo con leer la introducción — te arriesgas a perder funciones estrella (WIP perdiódashboard.pypanel visual,wip done,--root,install-skill, soporte Codex,see:detección de archivos de planificación). Primero enumera todas las funciones, luego selecciona cuáles demostrar. TTS no leas cuentas/handles/ortografías raras (ej.yazelines la romanización del nombre chino "亚泽林", leerlo letra por letra suena extraño) — si la URL ya está en la pantalla, deja que la narración lo omita ("enlace abajo"); evita palabras de inicio que hagan pausa ("先…" → "…muy simple"). edge-tts lee términos técnicos en inglés (cargo/markdown/Claude Code) bien; usa Gemini Flash para escuchar una frase y confirmar. -
MODO D Video musical: canción completa + interacción, no grabes, mezcla después con el archivo original.
recordVideode Playwright no incluye pista de audio ni cursor (cuando no tienes una pista de audio completa, ver sección "Grabación real"), así que: (a) el audio se mezcla después con el mp3 original (mejor calidad, no afectado por el null sink de xvfb); (b) inyecta un cursor falso (pega una imagen que sigue alpointermove, solo para la grabación, no se incluye en el repo), de lo contrario, la "interacción con el ratón" parecerá solo efectos especiales moviéndose solos. La línea de tiempo de la acción debe vincularse aaudio.currentTimecon sondeo (waitForFunction(t => au.currentTime >= X)), no useswaitForTimeoutacumulativo — cuando el grabador pierde fotogramas, el reloj de pared se desvía, el reloj de audio no. -
La sincronización se basa en el reloj en la imagen, no en
Date.now()del nodo. Hay un desfase incierto entre el primer fotograma del webm ynewContext(), y además Playwright agrega un gran bloque de fotogramas congelados al final (en la práctica, 160s de contenido se escriben como un archivo de 239s), la duración deffprobeno se puede usar como longitud. Método: recorta la barra de progreso, mide el deslizador x fotograma por fotograma (toma la columna con más píxeles rosas como pico y luego el centro de gravedad — las partículas que pasan son del mismo color, tomar el promedio se desvía), ajusta linealmentetiempo de imagen → tiempo de audio, resuelve el punto de inicio del audio. Verifica cruzadamente tres estimaciones (ajuste del deslizador, momento de cambio de línea de la letra, reloj de pared al hacer clic) y toma la mediana; la línea de la letra tiene un retraso de ~0.1s debido a la transición CSS, recuerda restarlo. Antes de terminar, usa correlación cruzada para verificar el retraso del audio del producto final contra el archivo original (el priming AAC agrega ~40ms fijos, normal), luego extrae tres fotogramas "lejos del cambio de línea" para confirmar que se ilumina la línea correcta. -
El formato vertical (9:16) no debe confiar en el zoom del navegador para alcanzar la resolución.
recordVideopega la "imagen de tamaño CSS" en el tamaño de fotograma que especifiques: viewport 405×720 conrecordVideo.size1080×1920 da como resultado una imagen de 405×720 pegada en la esquina superior izquierda, el resto relleno de gris; si intentas usardeviceScaleFactorpara aumentar los píxeles del dispositivo, Playwright lo ignora y lo devuelve a 1; si usas--force-device-scale-factor+--window-size, xvfb no tiene gestor de ventanas,--window-size,Browser.setWindowBounds,--kioskno modifican la ventana (midesinnerWidthy se queda en 501).html{zoom:...}tampoco funciona, las media queries leen el ancho original. Solución viable: viewport directamente 1080×1920 nativo (DSF 1), luego inyecta un "estilo ampliado para vertical" (disposición móvil + tamaño de texto/controles ampliados proporcionalmente, con!importantpara sobrescribir la rejilla de escritorio). El estilo solo para la grabación no se incluye en el repo, se inyecta en la misma posición queHIDE. La nitidez de las letras se debe ver ampliada (GOTCHA 16): el borde de las letras al escalar de 405 a 1080 es suave, el renderizado nativo es duro. -
Si es posible, haz que "la imagen hable por sí sola": para grabaciones interactivas, organiza las operaciones a mostrar en una línea de tiempo (clic, arrastrar, pausar), deja intencionadamente algunas pausas sin movimiento para que la interfaz que se oculta automáticamente se cierre — el movimiento continuo distrae de lo importante. Al grabar interacciones, recuerda que
recordVideono incluye cursor, inyecta un cursor falso (pega una imagen que sigue alpointermove), de lo contrario, la audiencia solo ve efectos especiales moviéndose solos. -
El ancla de sincronización debe ser un "destello blanco", no "todo negro". La idea era "primero la página se vuelve negra, cuando el video realmente comienza, se muestra; escanea el primer fotograma iluminado como t=0" — en la práctica no funciona:
recordVideode Playwright no comienza a grabar desde el primer fotograma, el fotograma más oscuro de toda la grabación es 24.4/255, no hay ningún fotograma negro para reconocer. Cambia a: antes de comenzar, muestra 400ms de todo blanco, cuando comience la reproducción, elimínalo, escanea "el fotograma después de que termine el destello blanco". Blanco 250 vs. contenido 24-37, no se puede equivocar. Verificado que el desfase es de ±0.05s en todo el clip, sin deriva. -
metadata=printescribe en stderr de forma predeterminada. La salida deffmpeg -vf "...,metadata=print:key=X"no está en stdout, si el script solo recoge stdout, siempre leerá vacío, y luego interpretará "no leer" como "la imagen está negra". Hay que añadir:file=-. -
El servidor HTTP interno debe leer el archivo antes de enviar las cabeceras. Si
res.writeHead(200)se escribe antes deawait readFile, cuando el archivo no existe, el 200 ya se ha enviado, y al intentar capturar el error para enviar un 404, se choca conERR_HTTP_HEADERS_SENT— la causa real (ruta incorrecta) queda oculta por este falso error. Elcapture-app.mjsde este repo tenía este problema, ya corregido. -
Para verificar la sincronización A/V, usa un método con poder discriminatorio. "Extraer fotogramas del mismo momento y comparar correlación" suena razonable, pero para contenido de diapositivas que no cambia durante decenas de segundos, una prueba negativa (desfase intencional de 6s) también da 0.996, por lo que no sirve. Método viable: coloca una barra de progreso en la plantilla (ancho =
currentTime / duration), mide el ancho de la barra en el producto final para leer el tiempo interno del reproductor y restarlo directamente del tiempo de salida. Esta barra también sirve como elemento visual del producto, no solo para pruebas. -
** Los videos cortos 9:16 con diapositivas estáticas, la imagen está muerta — y se puede medir.** La diferencia de grises fotograma a fotograma en la cuadrícula de diapositivas es de solo 0.072 / 0.092 (congelado de control 0.000), la reacción del usuario fue "demasiado plano, sin punch". No establezcas el umbral con sensaciones; usa tus propios productos 9:16 ya lanzados y mide el rango utilizable con la misma regla: video musical neko-tensei 0.233, avance k-rider 1.045, battlefield-editor 1.293, roll-formosa 1.912, anuncio Catime 3.965, avance Catime 6.168 (active_ratio 0.636 = cortes rápidos). Los videos cortos de frases destacadas no deben ser de cortes rápidos.
Rango objetivo 0.30 a 1.05, active_ratio 0.10 a 0.40— Este rango de umbral está descartado:meanyactive_rationo están normalizados por la tasa de fotogramas, y además definir el umbral con tus propios productos es una circularidad. El criterio actual esmotion.slide.per_s ≥ 5.5enpunch-score.py(cambio de grises por segundo,mean × fps, el único número que no varía con la tasa de fotogramas), sin límite superior. Los "destellos" peligrosos se controlan con el GOTCHA 35 de eventos de brillo. Base: la versión v1, que los usuarios calificaron como "imagen muerta", tenía 1.79 y 2.30; mientras que el producto más silencioso ya lanzado, neko-tensei, tiene 5.82 y debe pasar. -
Para la cámara en diapositivas estáticas, la posición debe medirse desde la imagen misma, no fijarse en proporciones. En diapositivas de 960×720, solo 250 líneas de escaneo tienen texto, el resto está en blanco; los cortes fijos superior/medio/inferior pueden cortar la pequeña línea del encabezado o un área completamente en blanco. Método: canvas extrae un fotograma → cantidad de tinta por fila (diferencia con la mediana del fondo) → conecta en bandas → descarta bandas con menos del 12% de tinta → los extremos izquierdo y derecho de las bandas restantes son el rango de texto SP, la cámara no debe cortar el SP sin importar cómo se mueva. Al medir, no reduzcas la imagen: al reducir 8x (120×90),
drawImageno es un promedio de área, los trazos finos desaparecen (el "的" al final de la línea no se mide, el SP pierde 96 px), y el degradado de fondo se considera como algo que cruza todo el ancho. Ajustar el umbral causa conflictos; usa la resolución original directamente, y la "tinta" puede usar la misma definición que en la verificación (28 por encima de la mediana del fondo). Escanear 960×720 en un Chromium headless toma solo decenas de milisegundos. -
El encuadre debe medir el "fotograma que se está reproduciendo en ese momento", no buscar primero y luego medir. Si quieres medir "la página donde se pronuncia la frase destacada", usa
V.currentTime = mitad de la frasey luegodrawImage—seekedsolo garantiza que la decodificación ha terminado, la imagen aún no ha cambiado, en la práctica, todo el clip quote-06 tomaba el diseño de la página 0 (la anterior): el SP se medía como 596 en lugar de 696, por lo que el marco "que garantiza no cortar palabras" estaba mal desde el principio, y durante los 2.8 segundos de la palabra clave, la mitad de los fotogramas perdían parte del texto. Método confiable: mide durante la reproducción: usa el fotograma del segundo 0 para la apertura (que es lo que está en pantalla en ese momento), luego vuelve a medir cada vez que cambia la cámara. Esto también resuelve el caso de "una frase que cruza dos páginas" — después del cambio de página, la siguiente cámara se actualiza. -
Los eventos de brillo deben tener un límite medible, no solo decir "lo hago lentamente". Mide el YAVG fotograma a fotograma de la pista de diapositivas. El umbral usa 10% relativo más un piso absoluto de 4/255 — el fondo de la diapositiva tiene un Y de aproximadamente 20, si solo usas el valor absoluto como umbral, ni siquiera detectarás un aumento del 9%; si solo usas el valor relativo, el ruido en las zonas oscuras lo saturará. Cuenta "cuántas veces en el segundo más denso", mantén 3 veces (umbral de riesgo de epilepsia fotosensible). En la práctica, el producto final tiene 2 veces/segundo; un control negativo de parpadeo a 5 Hz tiene 26 veces/segundo, una pulsación lenta de 1 Hz ±6% tiene 6 veces/segundo, ambos se bloquean correctamente. Esta verificación ya está escrita en
capture-short.mjs, se imprime automáticamente después de grabar. -
El filtro
eqde ffmpeg, la expresión de brillo solo se calcula una vez por defecto (eval=init). Al hacer el control negativo de "parpadeo intencional", olvidé:eval=frame, el filtro no tuvo efecto, y la medición fue idéntica a la del producto final — por poco declaro un buen indicador como "sin poder discriminatorio". El control negativo también debe verificarse que realmente está roto, de lo contrario solo demostrará que tu herramienta no puede distinguir dos archivos idénticos. -
La diferencia de fotogramas nunca tiene poder discriminatorio para la sincronización. El caso de GOTCHA 31 se verificó de nuevo con otra plantilla: al desfasar
words.json6 segundos intencionalmente, la diferencia de fotogramas de la pista de diapositivas era exactamente la misma 0.718 (no mirawords.jsonen absoluto). Lo que sí cambia es "el momento de cambio de bloque de subtítulos vs. el momento de máxima diferencia de fotogramas en el área de subtítulos" (0.091 → 0.567). Los indicadores dinámicos solo pueden afirmar que hay movimiento, la sincronización debe medirse por separado. -
La división de subtítulos en bloques junta dos "palabras clave adyacentes" en un mismo bloque. La división solo cambia de bloque cuando "no cabe", y al cambiar a otra palabra clave, a menudo todavía cabe — en la práctica, "很大的问号" y "没有 被验证过" no tienen otras palabras entre medias, se unen en un bloque de 11 caracteres, todo el bloque en dorado, dos explosiones se reducen a una (solo 2 bloques iluminados). La solución es añadir una regla: si
k >= 0 y k !== k de la palabra anterior, fuerza un nuevo bloque. Después de la corrección, hay 3 bloques iluminados, el bloque más grande pasa de 11 a 6 caracteres. Nota:punch-score.pytiene una copia de la misma división, debe modificarse junto con esto. -
En tiempo de ejecución, el zoom se multiplica de forma encubierta; al calcular el ancho del encuadre, primero debes restar ese margen. El zoom lento (+3.5%) y el zoom de palabra clave (+5%) suman 8.7%: un encuadre "justo" al ser ampliado expulsa el texto del encuadre, y ocurre justo en los segundos de la palabra clave — el peor momento posible. Hay que hacer dos cosas: al encuadrar,
z = margen de ancho / 1.087, y volver a ajustar cada fotograma (z ≤ margen de ancho,cxajusta para que ambos extremos del SP estén dentro del marco). La invariante debe cumplirse en el momento en que la imagen sale, no solo "cuando calculé el encuadre". -
Una vez que el encabezado y pie de página de la diapositiva se colocan en el centro de la imagen, los lados izquierdo y derecho se cortan, y se cuentan como texto cortado. La pequeña línea del encabezado (x 24 a 932) y la barra de progreso del pie de página (x 0 a 759) sobresalen del rango de texto; cuando la cámara se acerca, cruzan los lados del encuadre. La veración recorta un 10% superior e inferior de la pista de diapositivas como "encabezado/pie de página"; al cambiar la cámara, los elementos decorativos caen dentro de esa línea, por lo que se cuentan como texto cortado. Solución: cualquier fila donde "la tinta sobresalga del SP" se marca como decoración (el criterio no es "cruza todo el ancho" — la barra de progreso del pie de página solo tiene el 79% del ancho bajo el umbral de tinta, usar todo el ancho lo perdería), luego limita el centro de la cámara para que la decoración solo pueda estar en el 10% superior/inferior del encuadre o fuera de él. Deja unos píxeles de margen en el límite: si está justo en el 10%, el redondeo del muestreo hará que las últimas una o dos filas de la decoración caigan en el lado del texto (en la práctica, aún quedaban 15 fotogramas, todos en las filas 81 y 82).
-
Para diagnosticar problemas de encuadre, primero ejecuta el algoritmo fuera de línea de nuevo, no ajustes parámetros a ciegas. Una grabación de 40s, probar umbrales a ciegas te lleva a "cada vez peor": en la práctica, al desactivar el zoom lento y el zoom de palabra clave (pensando que era la causa principal), los recortes de texto pasaron de 102 fotogramas a 189 fotogramas. Cambia a: extrae el mismo fotograma de detección con ffmpeg, ejecuta la misma lógica de bandas/SP en Python, en diez segundos ves que "el SP medido por el navegador es exactamente el mismo que el de la página del segundo 0", y la causa principal aparece de inmediato. Además, imprime las premisas del encuadre en el log (SP, z del margen de ancho, posición de la decoración) — cuando la cámara colapsa, con esos tres números sabes si el ancho está mal medido o el pasillo está atascado.
-
Al reportar los números del producto, separa "los que vienen del contenido" y "los que vienen de la imagen"; estos últimos deben incluir el md5 de la plantilla. Los que vienen del contenido (gancho, coloración, bloques iluminados, bloques de subtítulos, silencio, velocidad de habla) están vinculados a
words.json; si ese md5 no cambia, los números no cambian ni un solo bit. Los que vienen de la imagen (diferencia de fotogramas, recorte de texto) solo son válidos para la versión de la plantilla en el momento de la grabación. Si se mezclan ambos y se reportan juntos, la siguiente persona que siga la documentación y regrabe obtendrá números que no coinciden: en la práctica, para el mismo clip, la versión anterior de la plantilla grababa un cambio por segundo de 14.80, la versión posterior 27.19 — una diferencia de 1.8 veces, mientras que los seis elementos del contenido eran exactamente iguales. La persona perdió un ciclo buscando una regresión inexistente.capture-short.mjsahora imprimePlantilla short-9x16.html md5 …en cada grabación, y se copia al reportar los números. -
La grabación en sí tiene vibración; los criterios deben ser tolerantes a ella. Con la plantilla y la entrada fijadas con md5, grabado cuatro veces seguidas, el cambio por segundo de la pista de diapositivas fue 21.27 / 21.33 / 20.82 / 21.3...