El cluster C12 de fqmpeg cubre el ida y vuelta entre video y fotogramas individuales — miniaturas únicas, capturas periódicas, contact sheets, filmstrips, divisiones por detección de escena, slideshows desde imágenes, comparaciones lado a lado, y conteo de fotogramas con ffprobe. Doce verbos en total, todos trabajando a través de ffmpeg (o ffprobe en un caso) pero exponiendo argumentos mucho más simples que las cadenas de filtro subyacentes.
Esta guía recorre cada verbo contra su código fuente en src/commands/ de fqmpeg 3.0.3 — el filtro o flag FFmpeg subyacente, los defaults, el nombre del archivo de salida y las trampas que no se ven solo desde --help (thumbnail-grid y tile usan el mismo muestreo pero distintos nombres de archivo por defecto; snapshot y video-to-frames ambos extraen imágenes periódicas pero a directorios por defecto distintos; frames-to-video y slideshow ambos construyen video desde imágenes pero usan mecanismos completamente distintos).
Lo que sacarás de esta guía
- Una matriz de decisión de los 12 verbos por tarea (imagen única / múltiples imágenes / contact sheets / reconstrucción de video / análisis)
- La invocación FFmpeg exacta que genera cada verbo (salida
--dry-runverificada) - Defaults, unidades, nombres de archivo de salida — y qué verbos se solapan (
thumbnail-gridvstile,snapshotvsvideo-to-frames) - Tres recetas — flujo de miniatura YouTube, timelapse desde dump de cámara de seguridad, comparación de filtro antes/después
Los 12 Verbos de un Vistazo
El cluster se divide en cuatro grupos de tareas. Elige el grupo, luego el verbo.
| Grupo | Verbos | Qué hacen |
|---|---|---|
| Imágenes únicas y periódicas | thumbnail, snapshot, video-to-frames, count-frames | Extraer una imagen en un timestamp, o muchas a intervalos / por fotograma |
| Contact sheets y filmstrips | thumbnail-grid, thumbnail-strip, tile | Componer múltiples fotogramas en una sola imagen (grilla o fila) |
| Fotogramas ↔ reconstrucción de video | frames-to-video, slideshow | Construir video desde un patrón numerado o lista de imágenes |
| Análisis y composición | scenes, preview, compare | Detectar cortes, generar reels de momentos, antes/después lado a lado |
Cinco cosas a saber antes de seguir leyendo:
thumbnail-gridytileconstruyen contact sheets con el mismo muestreo basado en tiempo. Ambos usanselect='isnan(prev_selected_t)+gte(t-prev_selected_t, 1)'— un fotograma por segundo — que se comporta de forma predecible en videos largos y cortos. Los dos verbos se mantienen como alias por descubribilidad (busca "grid" o "tile" y cualquiera de los dos te trae aquí); la única diferencia es el nombre de archivo de salida por defecto (-grid.jpgvs-tile<C>x<R>.jpg).snapshotproduce salida junto al input;video-to-framesproduce salida al directorio de trabajo actual. Ambos extraen imágenes periódicas, pero sus patrones de salida por defecto difieren —snapshotpone archivos en la misma carpeta que el input (<input-dir>/<stem>-snap-%04d.jpg), mientrasvideo-to-framesescribe a./frame_%04d.pngdesdecwd. Si quieres paths predecibles, pasa siempre-o.count-frameses el único verbo en C12 que usaffprobe, noffmpeg. Ejecutaffprobe -count_frames -select_streams v:0e imprime un único entero a stdout. No se escribe ningún archivo.frames-to-videousa-framerate(tasa de input), no-r(tasa de output). Esto importa porque-frameratedice a FFmpeg cómo interpretar la secuencia de imágenes (cuántas alimentar por segundo), mientras que-rre-temporizaría el stream de salida. Para image-to-video directo a tasa consistente,-frameratees el flag correcto y es lo que fqmpeg usa.scenesdivide un video en cortes detectados usando el segmenter. Cada escena se vuelve su propio archivo (<stem>-scene000.mp4,<stem>-scene001.mp4, ...) — útil para descomponer grabaciones largas en clips automáticamente. El umbral es0.0–1.0; menor = más sensible = más cortes.
Imágenes Únicas y Periódicas
thumbnail — Un fotograma como imagen única
El verbo más simple del cluster: busca a un timestamp y escribe un JPEG. Usado para miniaturas de video (subidas a YouTube, portadas de galería, tags de imagen OG).
- Fuente:
src/commands/thumbnail.js - Flags:
-ss <sec> -i <input> -frames:v 1 -q:v 2 - Salida:
<input-stem>-thumb.jpg
| Argumento / Opción | Default | Notas |
|---|---|---|
<input> | requerido | Archivo de video de entrada |
-s, --start <sec> | 1 | Timestamp en segundos (decimal permitido) |
-o, --output <path> | <input-stem>-thumb.jpg | Sobrescribir salida |
$ npx fqmpeg thumbnail input.mp4 --dry-run
ffmpeg -ss 1 -i input.mp4 -frames:v 1 -q:v 2 input-thumb.jpg
$ npx fqmpeg thumbnail input.mp4 -s 45.5 -o cover.jpg --dry-run
ffmpeg -ss 45.5 -i input.mp4 -frames:v 1 -q:v 2 cover.jpg
-ss antes de -i es la forma fast-seek. FFmpeg usa seek alineado a keyframe cuando -ss precede a -i, que es dramáticamente más rápido en archivos largos pero aterriza en el keyframe más cercano en lugar del timestamp exacto solicitado. Para miniatura de contenido, está bien — los keyframes suelen estar a medio segundo de distancia como máximo. Para extracción precisa por fotograma, pondrías -ss después de -i, pero eso decodifica desde el inicio y es mucho más lento (fqmpeg no expone ese modo aquí).
-q:v 2: escala de calidad JPEG donde 2 es casi sin pérdidas (la escala es 1–31, menor es mejor). Esta es la calidad JPEG más alta razonable para una miniatura — archivo grande, salida nítida. Para miniaturas más pequeñas (tiles de galería), genera a calidad máxima aquí y reduce escala por separado con resize.
snapshot — Fotogramas a intervalos regulares
Extrae un fotograma cada N segundos a lo largo del video completo. La salida es una secuencia numerada (<stem>-snap-0001.jpg, 0002.jpg, ...), guardada junto al archivo de entrada.
- Fuente:
src/commands/snapshot.js - Filtro:
fps=<1/interval> - Salida:
<input-dir>/<stem>-snap-%04d.<format>
| Argumento / Opción | Default | Permitido | Notas |
|---|---|---|---|
<input> | requerido | — | Archivo de video de entrada |
--interval <seconds> | 1 | número positivo | Intervalo de captura (--interval 5 = un fotograma cada 5 s) |
--format <fmt> | jpg | jpg, png | Formato de imagen |
-o, --output <pattern> | <input-dir>/<stem>-snap-%04d.<format> | patrón printf | Sobrescribir; debe contener %d o %0Nd |
$ npx fqmpeg snapshot lecture.mp4 --dry-run
ffmpeg -i lecture.mp4 -vf fps=1 -q:v 2 lecture-snap-%04d.jpg
$ npx fqmpeg snapshot lecture.mp4 --interval 30 --format png --dry-run
ffmpeg -i lecture.mp4 -vf fps=0.03333333333333333 -q:v 2 lecture-snap-%04d.png
Por qué fps= en lugar de select=: el filtro fps es la forma más simple de forzar una tasa de salida constante — FFmpeg descarta o duplica fotogramas según sea necesario para alcanzar el objetivo. fps=0.0333... significa "un fotograma cada 30 segundos", y FFmpeg elige el fotograma más cercano a cada tick de 30 segundos.
-q:v 2 se aplica también a PNG, pero se ignora — la calidad PNG se determina por el nivel de compresión, no por la escala q. JPEG es donde importa.
Contando salidas: para un video de 60 minutos a --interval 30, obtendrás 120 archivos (-snap-0001.jpg a -snap-0120.jpg). Para --interval 1 en el mismo archivo, obtendrías 3600 archivos. Planifica el espacio en disco.
video-to-frames — Cada fotograma (o limitado por fps) como imágenes
Extrae fotogramas a la tasa fuente por defecto, o a una tasa limitada si se suministra --fps. Usa esto cuando necesitas cada fotograma (retoque fotograma a fotograma, datasets de entrenamiento ML) o una tasa de muestreo por segundo conocida.
- Fuente:
src/commands/video-to-frames.js - Flag:
-i <input>(+ opcional-vf fps=<n>) - Salida:
./frame_%04d.<format>(en el directorio de trabajo actual)
| Argumento / Opción | Default | Permitido | Notas |
|---|---|---|---|
<input> | requerido | — | Video de entrada |
--fps <n> | tasa fuente (sin filtro) | número positivo | Limitar a esta cantidad de fotogramas por segundo |
--format <fmt> | png | png, jpg | Formato de imagen. PNG default (sin pérdidas) para ML / edición |
-o, --output <pattern> | ./frame_%04d.<format> | patrón printf | Sobrescribir |
$ npx fqmpeg video-to-frames input.mp4 --dry-run
ffmpeg -i input.mp4 frame_%04d.png
$ npx fqmpeg video-to-frames input.mp4 --fps 5 --format jpg --dry-run
ffmpeg -i input.mp4 -vf fps=5 frame_%04d.jpg
El default es cada fotograma. A 30 fps durante 60 s, eso son 1800 PNGs — fácilmente varios cientos de megabytes. Si solo quieres muestras periódicas, usa snapshot (que tiene un default más sensato) o pasa --fps.
La salida va al CWD, no al directorio del input. Esto es intencional — los dumps de fotogramas suelen ser datos de trabajo que quieres en una carpeta conocida, no contaminando el directorio donde vive el video. Pero es una inconsistencia con snapshot, así que si los quieres junto al input, pasa -o input/dir/frame_%04d.png.
PNG vs JPG: PNG es el default seguro para procesamiento de imágenes posterior (sin pérdidas, preserva alfa). JPG es 5–10× más pequeño pero con pérdidas — bien para vista previa o para entrenamiento ML donde el modelo de todos modos hará downsample. El flag de formato controla la extensión; FFmpeg escoge el codec a partir de eso.
count-frames — Conteo exacto de fotogramas vía ffprobe
Devuelve el número exacto de fotogramas de video en el stream. El único verbo de C12 que no usa ffmpeg — ejecuta ffprobe -count_frames y escribe un único entero a stdout, sin archivo de salida.
- Fuente:
src/commands/count-frames.js - Binario:
ffprobe(noffmpeg) - Salida: entero a stdout
| Argumento / Opción | Notas |
|---|---|
<input> | Video de entrada |
$ npx fqmpeg count-frames input.mp4 --dry-run
ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of csv=p=0 input.mp4
$ npx fqmpeg count-frames input.mp4
1798
-count_frames decodifica el stream entero. Es preciso pero lento — para un archivo largo, count-frames lee cada byte del video. Para una estimación más rápida, consulta duration × frame rate desde el encabezado (sin decodificación):
ffprobe -v error -select_streams v:0 -show_entries stream=duration,r_frame_rate -of csv=p=0 input.mp4
Eso devuelve duration y r_frame_rate (ej. 60.5,30000/1001), y multiplicas (60.5 × 29.97 ≈ 1814 fotogramas) — cerca pero no exacto para archivos de tasa variable.
Cuándo importan los conteos exactos: para automatización de edición de video (dividir en N chunks iguales de fotogramas), workflows de pose-estimation que necesitan indexación por fotograma, o análisis anti-cheat / forense donde el conteo exacto es una huella digital. Para la mayoría de las necesidades, la estimación desde el encabezado está bien.
Contact Sheets y Filmstrips
thumbnail-grid — Contact sheet (alias de tile)
Una composición de miniaturas multi-fotograma dispuesta como grilla. El look clásico "selección de capítulos DVD". Mismo muestreo basado en tiempo que tile (un fotograma por segundo); se mantiene bajo este nombre para usuarios que buscan "grid".
- Fuente:
src/commands/thumbnail-grid.js - Filtro:
select='isnan(prev_selected_t)+gte(t-prev_selected_t, 1)', scale=<W>:-1, tile=<C>x<R> - Salida:
<input-stem>-grid.jpg
| Argumento / Opción | Default | Notas |
|---|---|---|
<input> | requerido | Video de entrada |
--cols <n> | 4 | Columnas de la grilla |
--rows <n> | 4 | Filas de la grilla |
--width <n> | 320 | Ancho de cada tile en píxeles |
-o, --output <path> | <input-stem>-grid.jpg | Sobrescribir |
$ npx fqmpeg thumbnail-grid input.mp4 --dry-run
ffmpeg -i input.mp4 -frames:v 1 -vf select='isnan(prev_selected_t)+gte(t-prev_selected_t\,1)',scale=320:-1,tile=4x4 -q:v 2 input-grid.jpg
Un fotograma por segundo, tope en cols × rows. Con los defaults de 4×4 = 16 tiles, la salida cubre los primeros 16 segundos del video. Para videos más largos, aumenta --cols/--rows para que cols × rows ≥ duración_en_segundos, o usa el verbo tile (mismo comportamiento) y pasa -o para controlar el nombre. Ver la sección de tile abajo para los detalles de prev_selected_t.
Mismo algoritmo que tile, distinto nombre de salida por defecto. Usa thumbnail-grid cuando se te venga "grid" a la mente; usa tile cuando pienses en "tile" o "contact sheet". El nombre por defecto de tile lleva las dimensiones embebidas (-tile4x4.jpg), útil al re-correr con distintos --cols/--rows.
thumbnail-strip — Filmstrip horizontal (muestreo por tiempo)
Variante de una fila de thumbnail-grid. Mismo muestreo basado en tiempo, pero rows=1 y un parámetro de altura por fotograma (en lugar de ancho).
- Fuente:
src/commands/thumbnail-strip.js - Filtro:
select='isnan(prev_selected_t)+gte(t-prev_selected_t, 1)', scale=-1:<H>, tile=<N>x1 - Salida:
<input-stem>-strip.jpg
| Argumento / Opción | Default | Notas |
|---|---|---|
<input> | requerido | Video de entrada |
--frames <n> | 10 | Número de fotogramas en el strip |
--height <n> | 120 | Altura de cada fotograma en píxeles |
-o, --output <path> | <input-stem>-strip.jpg | Sobrescribir |
$ npx fqmpeg thumbnail-strip input.mp4 --dry-run
ffmpeg -i input.mp4 -frames:v 1 -vf select='isnan(prev_selected_t)+gte(t-prev_selected_t\,1)',scale=-1:120,tile=10x1 -q:v 2 input-strip.jpg
Un fotograma por segundo, tope en --frames. Con el default --frames 10, el strip cubre los primeros 10 segundos del video. Para un strip que abarque un runtime más largo, aumenta --frames hasta meet o superar duración_en_segundos, o construye el strip desde la salida de snapshot y únelos con tile/montage (ImageMagick) en timestamps proporcionales.
Caso de uso del filmstrip: vistas previas del scrubber en editores de video, vistas previas hover del reproductor de video (el strip WebVTT de miniaturas de YouTube), arte estilo "timeline scrolling" para redes sociales.
tile — Contact sheet (muestreo por tiempo)
Muestrea un fotograma por segundo (basado en timestamp vía prev_selected_t), así que la grilla abarca la porción inicial del video de manera uniforme independientemente del frame rate de la fuente. Algoritmo idéntico a thumbnail-grid; este verbo es el que tiene el nombre por defecto (-tile<C>x<R>.jpg) que codifica las dimensiones.
- Fuente:
src/commands/tile.js - Filtro:
select='isnan(prev_selected_t)+gte(t-prev_selected_t, 1)', scale=<W>:-1, tile=<C>x<R> - Salida:
<input-stem>-tile<C>x<R>.jpg
| Argumento / Opción | Default | Notas |
|---|---|---|
<input> | requerido | Video de entrada |
--cols <n> | 4 | Columnas de la grilla |
--rows <n> | 4 | Filas de la grilla |
--width <n> | 320 | Ancho de cada tile en píxeles |
-o, --output <path> | <input-stem>-tile<C>x<R>.jpg | Sobrescribir (nota las dimensiones en el nombre por defecto) |
$ npx fqmpeg tile input.mp4 --dry-run
ffmpeg -i input.mp4 -frames:v 1 -vf select='isnan(prev_selected_t)+gte(t-prev_selected_t\,1)',scale=320:-1,tile=4x4 -q:v 2 input-tile4x4.jpg
El predicado prev_selected_t:
isnan(prev_selected_t)es verdadero para el primer fotograma (aún no hay selección previa —prev_selected_tno definido → NaN).gte(t - prev_selected_t, 1)es verdadero cuando ha pasado al menos 1 segundo desde el último fotograma seleccionado.
Su suma activa el filtro select, produciendo una selección por segundo. Para los defaults 4×4 = 16 tiles, la salida cubre los primeros 16 segundos. Para videos más largos, el tope tile=4x4 descarta cualquier cosa más allá de la 16ª selección, dándote los primeros 16 segundos. Si quieres una contact sheet que abarque el runtime completo, aumenta --cols/--rows para que cols × rows ≥ duración_en_segundos, o baja a FFmpeg directo con select='gte(t, X)' en timestamps proporcionales.
Por qué dos verbos (tile y thumbnail-grid): descubribilidad — los usuarios buscan "grid" o "tile" y cualquiera de los dos los trae aquí. El filtro y el muestreo son idénticos; solo difiere el nombre de archivo por defecto (-tile4x4.jpg para tile, -grid.jpg para thumbnail-grid). Elige el que se te venga a la mente y sobrescribe con -o si necesitas un nombre estable entre corridas.
Fotogramas ↔ Reconstrucción de Video
frames-to-video — Secuencia de imágenes → video
La inversa de video-to-frames. Toma una secuencia numerada estilo printf (o patrón glob) de imágenes y produce un único archivo de video.
- Fuente:
src/commands/frames-to-video.js - Flags:
-framerate <fps> -i <pattern> -c:v <codec> -pix_fmt yuv420p - Salida:
<pattern-base>-video.mp4
| Argumento / Opción | Default | Notas |
|---|---|---|
<pattern> | requerido | Patrón printf (frame_%04d.png) o glob shell (img_*.jpg) |
--fps <n> | 30 | Fotogramas por segundo |
--codec <name> | libx264 | Codec de video |
-o, --output <path> | <pattern-base>-video.mp4 | Sobrescribir (default quita %d y extensión del patrón) |
$ npx fqmpeg frames-to-video frame_%04d.png --dry-run
ffmpeg -framerate 30 -i frame_%04d.png -c:v libx264 -pix_fmt yuv420p frame-video.mp4
$ npx fqmpeg frames-to-video 'img_*.jpg' --fps 60 --dry-run
ffmpeg -framerate 60 -i img_*.jpg -c:v libx264 -pix_fmt yuv420p img_*-video.mp4
-framerate vs -r: -framerate establece la tasa de input de la secuencia de imágenes (cuántas consumir por segundo), no la tasa de output. Para la mayoría de casos image-to-video esto es lo que quieres — la tasa de output coincide por default. Si quisieras una tasa de output distinta (interpolar o duplicar fotogramas), añadirías -r después del input.
yuv420p para compatibilidad: archivos de imagen raw (especialmente PNG) decodifican a formatos yuva420p o rgb24 que algunos reproductores rechazan en contenedores MP4. Forzar yuv420p garantiza reproducción en QuickTime, navegadores móviles y reproductores embebidos. Para output archival de mayor calidad (10 bits, RGB completo), pasa --codec libx264rgb o baja a FFmpeg directo.
Coincidencia de patrones: los patrones printf (frame_%04d.png) requieren numeración secuencial empezando en 00001 (o 00000 con -start_number 0). Los patrones glob (img_*.jpg) funcionan pero requieren que el shell los expanda — quótalos en scripts para evitar expansión temprana. Globs con extensiones mixtas (.jpg y .png juntas) no funcionan porque el demuxer image2 de FFmpeg espera formato uniforme.
slideshow — Múltiples imágenes → video con duraciones por imagen
Construye un video desde una lista de imágenes donde cada una se mantiene en pantalla durante una duración configurable. Usa el demuxer concat de FFmpeg con un listfile auto-generado (limpiado al salir).
- Fuente:
src/commands/slideshow.js - Flags:
-f concat -safe 0 -i <listfile> -vf fps=<n>,format=yuv420p -c:v libx264 -pix_fmt yuv420p - Salida:
<input-dir>/slideshow.mp4
| Argumento / Opción | Default | Notas |
|---|---|---|
<images...> | requerido (≥2) | Dos o más rutas de imagen en orden |
--duration <sec> | 3 | Segundos por imagen (uniforme para todas) |
--fps <n> | 30 | Tasa de fotogramas de output |
-o, --output <path> | <input-dir>/slideshow.mp4 | Sobrescribir |
$ npx fqmpeg slideshow img1.jpg img2.jpg img3.jpg --dry-run
# Image list (auto-generated):
# file '/abs/path/img1.jpg'
# duration 3
# file '/abs/path/img2.jpg'
# duration 3
# file '/abs/path/img3.jpg'
# duration 3
# file '/abs/path/img3.jpg'
ffmpeg -f concat -safe 0 -i imagelist.txt -vf fps=30,format=yuv420p -c:v libx264 -pix_fmt yuv420p slideshow.mp4
Demuxer concat con duration por entrada: la sintaxis del listfile file '<path>' \n duration <sec> permite a FFmpeg coser imágenes secuencialmente con tiempos exactos. La limpieza es automática (fqmpeg usa process.on("exit") para borrar el listfile con timestamp).
Por qué la última imagen aparece dos veces: en el demuxer concat, la línea duration especifica el tiempo de transición al siguiente archivo, así que sin esto la imagen final se mostraría durante cero segundos. fqmpeg repite la última imagen como entrada trailing (sin línea duration) para que efectivamente se mantenga --duration segundos y la salida resulte de imágenes × duration segundos, coincidiendo con lo que promete la opción.
Rutas absolutas en el listfile: fqmpeg resuelve cada imagen a una ruta absoluta antes de escribir, así que el concat funciona sin importar desde dónde invocas. Con comillas simples y comillas internas escapadas ('\\''), así que rutas con apóstrofes (ej. O'Brien.jpg) también funcionan.
Caso límite de una sola imagen: el comando requiere ≥ 2 imágenes y devuelve error con una. Para "una imagen estática como video" de longitud arbitraria, usa FFmpeg directo: ffmpeg -loop 1 -i img.jpg -t 10 -c:v libx264 -pix_fmt yuv420p out.mp4.
Sin transiciones integradas — solo cortes duros. slideshow usa el demuxer concat de FFmpeg, que cose las imágenes una tras otra sin crossfades. Para fades entre imágenes, baja a FFmpeg directo con el filtro xfade (filter_complex encadenando cada par con xfade=transition=fade:duration=1:offset=…), o ejecuta slideshow y post-procesa con un pase de fade aparte. Agregar cadenas xfade como built-in reemplazaría el flujo simple de concat con plumbing de segmentos por imagen — fuera del alcance de la superficie "quick".
Análisis y Composición
scenes — Dividir un video en cortes de escena detectados
Detecta cambios de escena vía la variable de metadata scene (un score de diferencia por fotograma, 0–1) y usa el segmenter para escribir cada escena a su propio archivo.
- Fuente:
src/commands/scenes.js - Filtro:
select='gt(scene,<threshold>)',setpts=N/FRAME_RATE/TB+-f segment -reset_timestamps 1 - Salida:
<input-dir>/<stem>-scene%03d<ext>
| Argumento / Opción | Default | Rango | Notas |
|---|---|---|---|
<input> | requerido | — | Video de entrada |
--threshold <n> | 0.3 | 0.0–1.0 | Menor = más sensible = más cortes |
-o, --output <pattern> | <input-dir>/<stem>-scene%03d<ext> | patrón printf | Sobrescribir |
$ npx fqmpeg scenes movie.mp4 --dry-run
ffmpeg -i movie.mp4 -filter_complex select='gt(scene,0.3)',setpts=N/FRAME_RATE/TB -f segment -reset_timestamps 1 movie-scene%03d.mp4
Variable de metadata scene: FFmpeg calcula un score 0–1 por fotograma representando cuánto difiere del previo (diferencia de histograma de color). Valores por encima del umbral se tratan como cortes. Valores típicos:
0.1–0.2: agresivo (atrapa disoluciones y crossfades, frecuentemente falsos positivos por movimiento)0.3: balanceado (el default — la mayoría de cortes duros en video narrativo)0.4–0.5: conservador (solo transiciones nítidas; pierde algunos cortes legítimos)0.7+: solo atrapa los cortes más duros (límites de capítulo en contenido guionizado)
-reset_timestamps 1: cada segmento de salida empieza en PTS 0 en lugar de continuar la línea de tiempo fuente. Esto es lo que hace que los segmentos sean reproducibles individualmente.
No es posible stream-copy. Como el filtro toca cada fotograma, la salida se re-encodea. Para una lista de cortes rápida sin re-encode, usa FFmpeg directo con -ss/-to después de la detección — primero corre scenes --dry-run para leer el umbral, luego usa ffprobe para extraer timestamps, luego segmenta vía ffmpeg -ss <t1> -to <t2> -c copy.
preview — Generar un highlight reel corto
Muestrea N clips cortos distribuidos uniformemente a lo largo de la fuente y los concatena en un video preview corto — el estilo "trailer para redes sociales".
- Fuente:
src/commands/preview.js - Filtro: selecciona N clips de
<clip-duration>segundos en posiciones uniformemente espaciadas, concatena con-t <clips × clip-duration> - Salida:
<input-stem>-preview.<ext>
| Argumento / Opción | Default | Notas |
|---|---|---|
<input> | requerido | Video de entrada |
--clips <n> | 5 | Número de clips de muestra |
--clip-duration <sec> | 2 | Duración de cada clip en segundos |
-o, --output <path> | <input-stem>-preview.<ext> | Sobrescribir |
$ npx fqmpeg preview input.mp4 --dry-run
# Note: could not probe input.mp4 for duration. Using placeholder total=60s.
# Run on a real file (or after creating input.mp4) to get exact clip offsets.
ffmpeg -i input.mp4 -vf select='between(t,0,2)+between(t,12,14)+between(t,24,26)+between(t,36,38)+between(t,48,50)',setpts=N/FRAME_RATE/TB -af aselect='between(t,0,2)+between(t,12,14)+between(t,24,26)+between(t,36,38)+between(t,48,50)',asetpts=N/SR/TB -t 10 input-preview.mp4
La duración de salida es determinista: clips × clip-duration segundos, independientemente de la duración fuente. Los defaults (5 × 2 = 10 s) producen un highlight de 10 segundos — corto para Twitter / Instagram, lo suficientemente largo para transmitir la esencia de una charla de 30 minutos.
Distribución uniforme a lo largo de la fuente: preview primero ejecuta ffprobe para leer la duración de la fuente T, luego selecciona --clips segmentos en offsets uniformemente espaciados — clip 1 empieza en 0 × T/clips, clip 2 en 1 × T/clips, ..., clip N en (N−1) × T/clips. Cada uno corre durante clip-duration segundos. Para un video de 60 minutos con los defaults, eso son clips en los minutos 0, 12, 24, 36, 48 — 2 segundos cada uno. La salida del dry-run arriba usa T = 60 (placeholder) porque input.mp4 no existe en disco; los offsets reales se calculan desde el archivo real en tiempo de ejecución.
Sin fade de audio entre clips. La salida corta duro entre los clips muestreados, así que los pops de audio son probables en contenido pesado en música. Para previews de calidad de redes sociales, querrías crossfades entre clips y una cama musical; para eso es mejor scriptear el workflow con trim + crossfade + audio-fade manualmente.
compare — Antes/después lado a lado
Apila dos videos horizontalmente (default) o verticalmente en una sola salida para comparación visual. El demo canónico "mira cuánto mejor está este filtro".
- Fuente:
src/commands/compare.js - Filtro (horizontal):
[0:v]scale=iw/2:ih[left];[1:v]scale=iw/2:ih[right];[left][right]hstack - Filtro (vertical):
[0:v]scale=iw:ih/2[top];[1:v]scale=iw:ih/2[bottom];[top][bottom]vstack - Salida:
<input1-stem>-compare.<ext>
| Argumento / Opción | Default | Permitido | Notas |
|---|---|---|---|
<input1> | requerido | — | Primer video (izquierda / arriba) |
<input2> | requerido | — | Segundo video (derecha / abajo) |
--direction <dir> | horizontal | horizontal, vertical | Layout |
-o, --output <path> | <input1-stem>-compare.<ext> | — | Sobrescribir |
$ npx fqmpeg compare before.mp4 after.mp4 --dry-run
ffmpeg -i before.mp4 -i after.mp4 -filter_complex [0:v]scale=iw/2:ih[left];[1:v]scale=iw/2:ih[right];[left][right]hstack -c:a copy before-compare.mp4
$ npx fqmpeg compare original.mp4 stabilized.mp4 --direction vertical --dry-run
ffmpeg -i original.mp4 -i stabilized.mp4 -filter_complex [0:v]scale=iw:ih/2[top];[1:v]scale=iw:ih/2[bottom];[top][bottom]vstack -c:a copy original-compare.mp4
Cada input se divide a la mitad antes de apilar: el canvas de salida mantiene el ancho original (horizontal) o alto (vertical), con cada input escalado a la mitad. Esto preserva la relación de aspecto general. Si los dos inputs difieren en dimensiones, el paso scale los normaliza al mismo tamaño-mitad — pero puede resultar distorsión de contenido. Para mejor salida, los dos inputs deberían ya coincidir en resolución y duración.
El audio se copia desde el input 1. -c:a copy toma la pista de audio del primer input con stream-copy; el audio del input 2 se descarta. Esto coincide con el encuadre antes/después usual (comparar visuales, mantener un audio).
Discordancia de duración: si los dos videos difieren en longitud, la salida termina cuando el más corto termina (el default de hstack/vstack de FFmpeg), con los fotogramas restantes del más largo truncados. Recorta ambos a longitudes coincidentes primero si necesitas alineación exacta.
Sin etiquetas integradas. Para overlays de texto "Left"/"Right" o "Before"/"After", primero haz dry-run del filtro, luego baja a FFmpeg directo y empalma drawtext en cada paso de scale — ver la Receta 2 abajo para una plantilla concreta. Añadir una opción --label forzaría una dependencia de fontfile (drawtext necesita libfreetype + ruta a la fuente) fuera del alcance de la superficie quick.
Casos de Uso Reales
Receta 1: Flujo de miniatura YouTube
Tienes un video terminado de 12 minutos y necesitas una miniatura personalizada. Objetivo: elegir el mejor fotograma, escalar a la spec de YouTube (1280×720), y revisar la contact sheet para confirmar la elección.
# Paso 1: contact sheet para elegir el mejor momento (muestreo por tiempo)
npx fqmpeg tile video.mp4 --cols 6 --rows 8 --width 480
# → video-tile6x8.jpg, 48 fotogramas abarcando los primeros 48 segundos
# Para cobertura más larga de un video de 12 minutos (720 segundos), necesitarías cols×rows ≥ 720,
# así que una grilla 30×24 (720 tiles). A ancho 200 eso es una hoja de 6000×~3375 px.
npx fqmpeg tile video.mp4 --cols 30 --rows 24 --width 200
# Paso 2: extraer el fotograma elegido en su timestamp exacto
npx fqmpeg thumbnail video.mp4 -s 374 -o thumbnail-raw.jpg
# Paso 3: redimensionar al spec de miniatura recomendado de YouTube
npx fqmpeg resize thumbnail-raw.jpg 1280x720 -o thumbnail-final.jpg
El paso tile es la parte lenta — tiene que decodificar a través de la fuente. Una vez elegido tu timestamp, thumbnail es casi instantáneo gracias al -ss alineado a keyframe.
Receta 2: Timelapse desde dump de cámara de seguridad
Tienes una carpeta de 86,400 JPEGs desde una cámara de seguridad (uno por segundo, 24 horas de metraje). Quieres un video timelapse de 60 segundos a 30 fps.
# 86400 fotogramas de input a 30 fps de output = 86400/30 = 2880 segundos = 48 minutos.
# Para comprimir a 60 segundos a 30 fps, necesitamos 1800 fotogramas de output.
# Muestrear 1 fotograma de input cada 48 fotogramas: 86400 / 1800 = 48.
# Paso 1: seleccionar cada 48º fotograma a una secuencia renombrada
i=1
ls *.jpg | awk 'NR%48==1' | while read f; do
printf -v new "frame_%04d.jpg" "$i"
ln -s "$(realpath "$f")" "$new"
((i++))
done
# Paso 2: coser en un timelapse a 30 fps
npx fqmpeg frames-to-video frame_%04d.jpg --fps 30
# → frame-video.mp4
Alternativa sin symlinks: usa slideshow con --duration 0.0333 (1/30 s por imagen) — pero slideshow re-encodea a través del demuxer concat, que es más lento que el -i pattern directo de frames-to-video. Para datasets de este tamaño, frames-to-video es la herramienta correcta; slideshow es para ≤ decenas de imágenes con duraciones por imagen.
Receta 3: Comparación de filtro antes/después para portfolio
Estás documentando el efecto de stabilize en un clip de drone tembloroso. Construye un video de comparación lado a lado con etiquetas para tu portfolio:
# Paso 1: estabilizar el original
npx fqmpeg stabilize drone-raw.mp4 -o drone-stable.mp4
# Paso 2: comparación lado a lado
npx fqmpeg compare drone-raw.mp4 drone-stable.mp4 \
-o drone-comparison.mp4
# Paso 3: extraer una miniatura de fotograma único para la imagen de portada del case study
npx fqmpeg thumbnail drone-comparison.mp4 -s 3 -o drone-cover.jpg
compare en sí no añade etiquetas — para un case study pulido, haz dry-run del filtro y re-renderiza con FFmpeg directo, empalmando drawtext en cada paso de scale:
ffmpeg -i drone-raw.mp4 -i drone-stable.mp4 -filter_complex \
"[0:v]scale=iw/2:ih,drawtext=text='Original':x=20:y=20:fontsize=36:fontcolor=white:box=1:boxcolor=black@0.5[left];[1:v]scale=iw/2:ih,drawtext=text='Stabilized':x=20:y=20:fontsize=36:fontcolor=white:box=1:boxcolor=black@0.5[right];[left][right]hstack" \
-c:a copy drone-portfolio.mp4
Preguntas Frecuentes
¿Debería usar thumbnail-grid o tile?
Cualquiera — son alias. Ambos usan el mismo muestreo basado en tiempo (select='isnan(prev_selected_t)+gte(t-prev_selected_t, 1)') y producen el mismo contact sheet para unos --cols/--rows/--width dados. La única diferencia es el nombre de archivo por defecto: thumbnail-grid escribe <stem>-grid.jpg, tile escribe <stem>-tile<C>x<R>.jpg. Elige tile cuando quieras que el nombre registre el tamaño de la grilla; elige thumbnail-grid cuando un nombre fijo -grid.jpg sea más conveniente. Como sea, pasa -o para sobrescribir.
¿Cuál es la diferencia entre snapshot y video-to-frames?
Ambos extraen imágenes periódicas, pero con defaults y ubicaciones de salida distintas:
snapshotpor default un fotograma por segundo, formato JPEG, salida junto al input (<input-dir>/<stem>-snap-%04d.jpg).video-to-framespor default cada fotograma a la tasa fuente, formato PNG, salida al directorio de trabajo actual (./frame_%04d.png).
Si quieres "imágenes de referencia ocasionales junto al video", snapshot es la herramienta correcta. Si quieres "cada fotograma para ML o edición", video-to-frames es la herramienta correcta. Pasa -o si quieres que cualquiera escriba a otro lado.
¿Por qué count-frames es lento en videos largos?
Porque -count_frames decodifica el stream de video entero. El flag le dice a ffprobe que efectivamente camine cada paquete y cuente los fotogramas decodificados exitosamente — necesario para precisión exacta (especialmente en archivos de tasa de fotogramas variable), pero cuesta un paso de decodificación completo. Para una fuente 4K de 4 horas, eso puede tardar minutos. Si una estimación está bien, consulta duration y r_frame_rate desde el header y multiplica — sin decodificación requerida:
ffprobe -v error -select_streams v:0 -show_entries stream=duration,r_frame_rate -of csv=p=0 input.mp4
¿Puedo usar frames-to-video con un glob como img_*.jpg?
Sí, pero quótalo. El demuxer image2 de FFmpeg acepta tanto patrones printf (frame_%04d.png) como globs shell ('img_*.jpg'). Los globs sin comillas son expandidos por el shell antes de que FFmpeg los vea, lo que usualmente rompe el comando — quota con comillas simples para que el patrón literal alcance a FFmpeg. El modo glob requiere extensión uniforme a través de todos los archivos (sin .jpg + .png mezclados).
¿Por qué tile solo cubre los primeros 16 segundos de mi video largo?
Porque el default de 4×4 = 16 tiles, combinado con el muestreo basado en tiempo de 1 segundo, naturalmente cubre exactamente 16 segundos. Para abarcar un video más largo, aumenta --cols y --rows para que cols × rows ≥ duración_en_segundos. Para un video de 5 minutos (300 segundos) muestreado uniformemente, necesitarías una grilla aproximadamente 17×18 (306 tiles). Para muestreo más grueso en videos largos, baja a FFmpeg directo y ajusta el predicado select — ej. select='gte(t - prev_selected_t, 60)' para un fotograma por minuto.
¿Cómo es frames-to-video distinto de slideshow?
frames-to-video consume una secuencia de imágenes numerada o globbeada (frame_%04d.png) y produce un video a una sola tasa de fotogramas — cada imagen de input se convierte en un fotograma, uniformemente. slideshow consume una lista explícita de imágenes (img1.jpg img2.jpg img3.jpg) y te deja establecer una duración por imagen (cada imagen mantenida durante N segundos), con cortes duros entre imágenes (sin crossfade integrado — añade fades vía el xfade de FFmpeg directo, ver la sección de slideshow). Usa frames-to-video para timelapses, reconstrucción de output de ML, y secuencias de imágenes desde renderizado. Usa slideshow para presentaciones fotográficas donde cada imagen necesita mantenerse unos segundos.
¿Qué umbral debería usar con scenes para un video narrativo típico?
Empieza con el default 0.3. Si obtienes muy pocos cortes (perdiendo transiciones duras), baja a 0.2 o 0.15. Si obtienes demasiados cortes (falsos positivos por movimiento de cámara, disoluciones), sube a 0.4 o 0.5. La sensibilidad del umbral depende del contenido — los videos musicales con movimiento rápido a menudo necesitan umbrales más altos (0.5+) para ignorar el movimiento dentro del shot, mientras que las entrevistas de talking-head pueden usar más bajo (0.2) porque los únicos cambios son cortes reales.
¿Puede compare manejar audio de ambos inputs simultáneamente?
No. La implementación usa -c:a copy que toma solo el stream de audio del input 1. El audio del input 2 se descarta. Esto coincide con el workflow usual (comparación visual antes/después con una pista de audio común). Para comparación de audio dual (ej. comparando dos mezclas de audio lado a lado), usa FFmpeg directo con amerge o amix:
ffmpeg -i a.mp4 -i b.mp4 -filter_complex \
"[0:v]scale=iw/2:ih[L];[1:v]scale=iw/2:ih[R];[L][R]hstack;[0:a][1:a]amerge=inputs=2[a]" \
-map "[a]" -ac 2 compare-dual-audio.mp4
Cerrando
Los doce verbos de C12 cubren el viaje de ida y vuelta entre video y fotogramas individuales:
thumbnail,snapshot,video-to-frames,count-framespara imágenes únicas o periódicas (-q:v 2es el default de calidad JPEG;count-frameses el único verboffprobedel cluster)thumbnail-grid,thumbnail-strip,tilepara contact sheets y filmstrips (los tres usan muestreo basado en tiempo — un fotograma por segundo — con--cols × --rows(grid/tile) o--frames(strip) como tope)frames-to-video,slideshowpara reconstruir video desde imágenes (frames-to-videopara secuencias a tasa uniforme,slideshowpara duraciones por imagen con cortes duros — añade fades conxfadede FFmpeg directo si los necesitas)scenes,preview,comparepara análisis y composición (scenessegmenta en cortes detectados;previewconstruye un highlight reel uniformemente muestreado;compareapila dos videos para demos antes/después)
Cada verbo imprime su invocación FFmpeg subyacente bajo --dry-run, así que cuando la superficie simplificada no es suficiente (muestreo personalizado de contact-sheet, comparación de audio dual, seeks precisos por fotograma en lugar de alineados a keyframe), copia el filtro, edita y llama a FFmpeg directo. Para el mapa fqmpeg más amplio, mira la fqmpeg complete guide.