32blogby Studio Mitsu

Conversión de formato y streaming con fqmpeg: GIF, HLS, DASH

Siete verbos de fqmpeg para formato: cambia contenedores, genera GIFs, crea manifiestos HLS/DASH, parte en segmentos y extrae pistas por índice.

by omitsu20 min read
Contenido

El cluster C2 de fqmpeg agrupa siete verbos para todo lo que tenga que ver con "cambiar la forma" o "preparar para entrega": convert cambia el contenedor, gif y gif-to-video hacen el viaje de ida y vuelta entre GIF y MP4, hls y dash generan manifiestos para streaming adaptativo, segment parte un archivo largo en trozos de duración fija, y extract-stream saca una pista de vídeo / audio / subtítulo por índice. Ninguno decide el códec (eso es C1) — todos responden a la pregunta qué forma necesita el archivo a continuación.

Esta guía recorre cada comando — los flags FFmpeg que produce, sus valores por defecto, y la regla de nombres de salida — y luego encadena varios en flujos reales de entrega. Todo está verificado contra el código en src/commands/ de fqmpeg 3.0.1.

Lo que vas a sacar de esta guía

  • Qué verbo elegir para cada caso (matriz de decisión)
  • La invocación exacta de FFmpeg que cada verbo genera (salida --dry-run verificada)
  • Valores por defecto, rangos permitidos y nombres de archivo para cada comando
  • Tres recetas de extremo a extremo: HLS para hosting estático con CDN, vista previa GIF desde una edición larga, y cirugía multipista sobre un MKV

Formato y entrega: qué verbo para qué tarea

El atajo mental para este cluster va por dos ejes — un único archivo vs. salida fragmentada, y cambiar píxeles vs. cambiar solo el envoltorio.

ObjetivoVerboQué haceSalida
Cambiar contenedor sin re-codificarconvert --copyRemux con stream-copy<nombre>.<ext>
Cambiar contenedor con re-codificaciónconvertDecodifica + re-codifica<nombre>.<ext>
Hacer un GIF de vista previagifRecorte + paleta + escalado<nombre>-gif.gif
Subir un GIF a MP4gif-to-videoPad par + yuv420p<nombre>.mp4
Playlist HLShls.m3u8 + segmentos .ts<nombre>.m3u8 + _%03d.ts
Manifiesto MPEG-DASHdash.mpd + segmentos DASH<nombre>.mpd
Cortar en trozos fijossegmentStream-copy -f segmentsegment_%03d.<ext>
Sacar una pista por índiceextract-stream-map 0:<spec> -c copy<nombre>-stream-<spec>.<ext>

Dos cosas que conviene interiorizar antes de seguir:

  1. Stream-copy vs. re-codificar. convert --copy, segment y extract-stream usan -c copy — no tocan los píxeles. Acaban en segundos porque solo reescriben el contenedor. convert (sin --copy), gif, gif-to-video, hls y dash re-codifican y van a la velocidad normal de codificación de FFmpeg. Cuando dudes, prueba primero la forma con stream-copy.
  2. Archivo único vs. salida segmentada. hls, dash y segment generan muchos archivos en el directorio de trabajo: un manifiesto más N segmentos. Ejecútalos dentro de un directorio de salida dedicado, o vas a tener decenas de chunks .ts revueltos junto a la fuente.

Para el lado más amplio de códec / calidad de estas decisiones, la guía de compresión de vídeo es el complemento natural. Para una tubería HLS de extremo a extremo desde fqmpeg hls hasta una CDN, mira la guía de streaming HLS con CDN.

Conversión de contenedor

convert — Cambia el contenedor (mp4 ↔ mov ↔ mkv ↔ webm ↔ mp3 ...)

Renombra el envoltorio. Por defecto FFmpeg elige los códecs que casan con el nuevo contenedor; con --copy hace stream-copy de las pistas tal cual.

  • Código: src/commands/convert.js
  • Por defecto: decodifica + re-codifica con los códecs por defecto del contenedor
  • --copy: añade -c copy para hacer remux sin tocar píxeles (rápido, sin pérdida — pero solo válido si el códec fuente es legal en el contenedor destino)
Argumento / OpciónPor defectoNotas
<format> (posicional)requeridoExtensión destino. El punto inicial se quita (.mov y mov valen igual)
--copyoffModo stream-copy — sin re-codificar
-o, --output <path><nombre-fuente>.<format>Sobrescribe la ruta de salida
bash
$ npx fqmpeg convert input.mp4 mov --dry-run

  ffmpeg -i input.mp4 input.mov

$ npx fqmpeg convert input.mp4 mov --copy --dry-run

  ffmpeg -i input.mp4 -c copy input.mov

Cuando --copy funciona, termina en menos de un segundo en archivos de varios GB porque no hay decodificación. La trampa es la compatibilidad códec/contenedor:

  • MP4 ↔ MOV: normalmente ambos copian bien (H.264 + AAC es legal en los dos)
  • MP4 ↔ MKV: el copy funciona casi siempre (MKV es muy permisivo)
  • WebM: requiere vídeo VP8/VP9/AV1 + audio Vorbis/Opus — copiar H.264 a .webm falla. Usa encode-vp9 de C1

Si convert --copy da el error Could not find tag for codec ... in stream, el códec fuente no es legal en el contenedor destino. Quita --copy para volver a re-codificación.

Ida y vuelta GIF

gif — Hacer un GIF desde un clip de vídeo

Recorta un trozo de tiempo del vídeo y escribe un GIF de alta calidad. El predeterminado de 480 px × 15 fps × 5 s está calibrado para terreno README — pequeño para caber en el presupuesto de 4 MB de un README, pero suficientemente grande para leerse.

  • Código: src/commands/gif.js
  • Truco de calidad: usa la cadena de filtros de dos pasadas palettegen / paletteuse. Esto genera una paleta óptima de 256 colores a partir del clip y la aplica en la segunda pasada. El resultado es muchísimo mejor que la codificación GIF ingenua (sin banding, sin ruido de dithering)
  • Escalador: scale=<width>:-1:flags=lanczos — la altura es automática, lanczos es el remuestreador de alta calidad
  • Bucle: -loop 0 (infinito)
OpciónPor defectoRango / FormatoNotas
-s, --start <sec>0número no negativoDesfase de inicio en segundos
-d, --duration <sec>5número positivoDuración del GIF
--fps <n>15entero positivoMás alto = más fluido pero archivo mayor
--width <px>480entero positivoLa altura se deriva manteniendo aspecto

Nombre por defecto: <nombre-fuente>-gif.gif.

bash
$ npx fqmpeg gif input.mp4 --dry-run

  ffmpeg -ss 0 -t 5 -i input.mp4 -vf fps=15,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse -loop 0 input-gif.gif

# Personalizado: empieza a los 10 s, 3 segundos, 20 fps, 600 px de ancho
$ npx fqmpeg gif input.mp4 --start 10 --duration 3 --fps 20 --width 600 --dry-run

  ffmpeg -ss 10 -t 3 -i input.mp4 -vf fps=20,scale=600:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse -loop 0 input-gif.gif

gif-to-video — Subir un GIF a MP4

La operación inversa. Convierte un GIF a un MP4 H.264 — mucho más pequeño, soporta sonido real (en este caso ninguno), funciona en tags <video> en todos lados.

  • Código: src/commands/gif-to-video.js
  • Formato de píxel: -pix_fmt yuv420p (necesario para compatibilidad amplia con navegadores/códecs — la mayoría de decodificadores rechaza MP4 en RGB de 8 bits)
  • Arreglo de dimensiones pares: -vf scale=trunc(iw/2)*2:trunc(ih/2)*2 — H.264 requiere ancho/alto pares; esto los redondea hacia abajo al par más cercano cuando hace falta
  • Faststart: -movflags faststart para que el archivo empiece a reproducirse antes de descargarse entero
  • Bucle: usa -stream_loop para repetir la fuente N veces (un MP4 estático se reproduce N+1 veces, ya que --loop 0 significa una sola pasada)
OpciónPor defectoRangoNotas
--loop <n>0entero no negativo0 = una pasada. 3 = la fuente suena 4 veces

Nombre por defecto: <nombre-fuente>.mp4.

bash
$ npx fqmpeg gif-to-video input.gif --dry-run

  ffmpeg -i input.gif -movflags faststart -pix_fmt yuv420p -vf scale=trunc(iw/2)*2:trunc(ih/2)*2 input.mp4

# Repetir el GIF 4 veces en el MP4 resultante
$ npx fqmpeg gif-to-video input.gif --loop 3 --dry-run

  ffmpeg -stream_loop 3 -i input.gif -movflags faststart -pix_fmt yuv420p -vf scale=trunc(iw/2)*2:trunc(ih/2)*2 input.mp4

El uso más común es achicar un GIF que ha crecido demasiado — un GIF de tutorial de 12 MB suele convertirse en un MP4 de 600 KB sin pérdida perceptible.

Streaming adaptativo

Estos dos verbos producen el tipo de salida que servirías desde una CDN detrás de un tag <video> con hls.js o dash.js.

hls — Playlist HLS + segmentos

HTTP Live Streaming. El formato adaptativo de Apple, nativo en Safari y iOS, soportado vía hls.js en el resto de navegadores. La salida es una playlist .m3u8 y una secuencia de segmentos MPEG-TS (.ts).

  • Código: src/commands/hls.js
  • Códec de vídeo: libx264 (forzado — el contenedor .ts y los reproductores .m3u8 tienen el soporte H.264 más robusto)
  • Códec de audio: aac
  • -hls_list_size 0: mantiene todos los segmentos en la playlist (modo VOD). Para una ventana móvil en directo, lo bajarías — pero VOD es el caso común
  • Patrón de nombres de segmento: <nombre-playlist>_%03d.ts junto a la playlist
OpciónPor defectoRangoNotas
--segment <sec>6número positivoDuración del segmento. Apple recomienda 6 s para VOD; 2–4 s para baja latencia
-o, --output <path><nombre-fuente>.m3u8rutaDefine la ruta de la playlist y el directorio de los segmentos
bash
$ npx fqmpeg hls input.mp4 --dry-run

  ffmpeg -i input.mp4 -c:v libx264 -c:a aac -hls_time 6 -hls_list_size 0 -hls_segment_filename input_%03d.ts input.m3u8

La salida es un solo peldaño de la escalera de bitrates — hls no genera la playlist maestra multi-bitrate que necesita el streaming adaptativo de verdad. Para eso, codificas la fuente a 3–5 bitrates distintos (usa bitrate de C1 para cada uno) y unes los .m3u8 resultantes en una playlist maestra a mano. La guía de streaming HLS con CDN recorre esa tubería entera incluyendo el despliegue en R2 / S3.

dash — Manifiesto MPEG-DASH

El formato adaptativo estándar W3C. La salida es un manifiesto .mpd y un conjunto de segmentos MP4 fragmentados. DASH se reproduce vía dash.js en la mayoría de navegadores; Safari tira hacia HLS pero acepta DASH a través del modo experimental DASH de hls.js o vía Media Source Extensions.

  • Código: src/commands/dash.js
  • Vídeo / audio: libx264 + aac (mismas defaults que HLS)
  • -use_timeline 1 -use_template 1: genera un manifiesto basado en SegmentTemplate, que es el formato moderno y más pequeño preferido sobre SegmentList para VOD
OpciónPor defectoNotas
--segment <sec>4DASH por defecto es más corto que HLS (4 s vs 6 s) — los reproductores DASH toleran mejor segmentos pequeños
-o, --output <path><nombre-fuente>.mpd
bash
$ npx fqmpeg dash input.mp4 --dry-run

  ffmpeg -i input.mp4 -c:v libx264 -c:a aac -f dash -seg_duration 4 -use_timeline 1 -use_template 1 input.mpd

Misma advertencia que con HLS — salida de una sola rendición. Para escaleras DASH multi-bitrate, lanza bitrate a varios objetivos y combina los manifiestos, o usa el argumento adaptation_sets de FFmpeg directamente vía --dry-run y edición manual.

Para decidir rápido entre los dos: usa HLS si tu audiencia es pesada en iOS o Safari; usa DASH si apuntas a dispositivos de salón, Android TV o cualquier cosa que siga el camino W3C. La mayoría de stacks de producción mandan ambos.

División y cirugía de pistas

segment — Cortar un archivo en trozos de duración fija

Divide la entrada en N piezas de duración fija cada una, con stream-copy para que sea casi instantáneo. Los nombres de archivo siguen un patrón numerado al estilo printf.

  • Código: src/commands/segment.js
  • Modo: -f segment -c copy (sin re-codificación)
  • -reset_timestamps 1: los PTS de cada segmento se resetean a 0 para que cada segmento se reproduzca independiente
  • Contenedor: preserva la extensión de entrada (los segmentos de input.mp4 son .mp4; los de input.mkv son .mkv)
Argumento / OpciónPor defectoNotas
<duration> (posicional)requeridoDuración del segmento en segundos
-o, --output <pattern>segment_%03d.<ext-fuente>Patrón printf%03d se vuelve 001, 002, …
bash
$ npx fqmpeg segment input.mp4 10 --dry-run

  ffmpeg -i input.mp4 -c copy -f segment -segment_time 10 -reset_timestamps 1 segment_%03d.mp4

Algunas notas prácticas:

  • Corta en keyframes, no en segundos exactos. El modo stream-copy solo puede dividir en límites GOP existentes. Si tu fuente tiene un intervalo de keyframes de 5 segundos, un segmento de 10 segundos puede acabar a 9.7 s o 10.3 s. Para cortes precisos al frame, re-codifica (quita -c copy del comando impreso) o acorta el GOP de la fuente primero
  • No es lo mismo que segmentos HLS. HLS produce una playlist junto a los chunks; segment solo produce chunks. Para HLS, usa hls
  • El patrón de salida por defecto vuelca archivos en CWD, no junto a la entrada. Ejecuta dentro de un directorio de salida o pasa -o /ruta/a/dir/seg_%03d.mp4

extract-stream — Sacar una pista por índice

Extrae una sola pista de vídeo, audio o subtítulo de un archivo multi-pista. Solo stream-copy — rápido y sin pérdida. El formato del argumento sigue la sintaxis de stream specifier de FFmpeg: v:0 (primer vídeo), a:1 (segundo audio), s:0 (primer subtítulo).

  • Código: src/commands/extract-stream.js
  • Mapeo: -map 0:<spec> -c copy
  • Extensión de salida: elegida por tipo de pista — v:*.mp4, a:*.aac, s:*.srt. Si el códec real no coincide (p. ej. el audio es Opus, no AAC), pasa --output para sobrescribir
ArgumentoNotas
<input>Archivo de entrada
<stream>Stream specifier: v:N, a:N o s:N

Nombres por defecto:

  • v:0<nombre-fuente>-stream-v0.mp4
  • a:1<nombre-fuente>-stream-a1.aac
  • s:0<nombre-fuente>-stream-s0.srt
bash
$ npx fqmpeg extract-stream input.mkv v:0 --dry-run

  ffmpeg -i input.mkv -map 0:v:0 -c copy input-stream-v0.mp4

$ npx fqmpeg extract-stream input.mkv a:1 --dry-run

  ffmpeg -i input.mkv -map 0:a:1 -c copy input-stream-a1.aac

$ npx fqmpeg extract-stream input.mkv s:0 --dry-run

  ffmpeg -i input.mkv -map 0:s:0 -c copy input-stream-s0.srt

Para saber qué pistas hay en un archivo, usa info (uno de los cuatro verbos de inspección del artículo hub) o ffprobe -v error -show_streams input.mkv directamente. La salida te da el mapa de índices: v:0, v:1, a:0, a:1, etc.

Recetas reales

Cada receta encadena varios verbos (a veces de varios clusters) en un flujo real.

Receta 1: Tubería HLS para hosting estático

Quieres entregar un vídeo de 30 minutos desde un host estático (Cloudflare R2, S3 + CloudFront, Vercel) y reproducirlo con hls.js dentro de un tag <video>. Una sola rendición sirve para la mayoría de casos.

bash
# Paso 1: comprimir a un bitrate de entrega razonable
npx fqmpeg compress source.mov --crf 22 --preset slow
# → source-compressed.mp4

# Paso 2: empaquetar como HLS en una carpeta de salida limpia
mkdir hls-out
npx fqmpeg hls source-compressed.mp4 -o hls-out/stream.m3u8
# → hls-out/stream.m3u8 + hls-out/stream_001.ts ... stream_NNN.ts

Sube hls-out/ al host estático con lectura pública. En tu página:

html
<video controls></video>
<script type="module">
  import Hls from "https://cdn.jsdelivr.net/npm/hls.js@latest/+esm";
  const hls = new Hls();
  hls.loadSource("https://cdn.example.com/hls-out/stream.m3u8");
  hls.attachMedia(document.querySelector("video"));
</script>

Para una escalera multi-bitrate y los ajustes de caché del CDN, la guía de streaming HLS con CDN cubre la configuración de producción incluyendo peticiones byte-range y cabeceras de caché de segmento.

Receta 2: Vista previa GIF desde una edición larga

Has exportado un screencast de 4 minutos como MP4 y necesitas un GIF para el hero del README. Tiene que ser ~2 MB máximo y ~600 px de ancho.

bash
# Paso 1: elige una ventana representativa de 5 segundos
# Visualmente busca un momento cerca del 1:30 de la edición
# Paso 2: extrae esa ventana como GIF
npx fqmpeg gif screencast.mp4 --start 90 --duration 5 --width 600 --fps 12
# → screencast-gif.gif

¿Por qué --fps 12 en lugar del 15 por defecto? Porque para screencasts de cabeza parlante o movimiento de cursor, la fluidez percibida con 12 fps es suficiente y el archivo pesa un 20 % menos. Si el GIF sigue siendo demasiado grande, baja --fps a 10 o --width a 480.

Si el .gif resultante se pasa de presupuesto, la respuesta rara vez es "ajustar más los settings de GIF". Convierte a MP4 con compress y sirve un <video autoplay loop muted playsinline> — mismo aspecto, ~10× más pequeño, más nítido.

Receta 3: Cirugía sobre un MKV multipista

Tienes un .mkv de un rip Blu-ray con tres pistas de audio (japonés, inglés, comentario en inglés) y dos pistas de subtítulos (inglés, inglés SDH). Quieres un MP4 limpio con solo el audio japonés y los subtítulos en inglés como .srt sidecar.

bash
# Paso 1: inspecciona las pistas (con el verbo info del hub, o ffprobe directo)
ffprobe -v error -show_streams source.mkv | grep -E "index=|codec_type=|TAG:language"
# Supón: v:0 H.264, a:0 jpn, a:1 eng, a:2 eng-comm, s:0 eng, s:1 eng-sdh

# Paso 2: extraer el audio japonés
npx fqmpeg extract-stream source.mkv a:0 -o ja.aac

# Paso 3: extraer el subtítulo inglés
npx fqmpeg extract-stream source.mkv s:0 -o eng.srt

# Paso 4: extraer el vídeo
npx fqmpeg extract-stream source.mkv v:0 -o video.mp4

# Paso 5: unir vídeo + audio japonés en un MP4 final
# (esta parte queda fuera de C2 — usa ffmpeg directo)
ffmpeg -i video.mp4 -i ja.aac -c copy -map 0:v -map 1:a final.mp4

El patrón "MKV → vídeo + audio elegido + subtítulo sidecar" es el núcleo de "haz que este rip Blu-ray se vea en mi móvil". extract-stream desensambla sin re-codificar, así que un archivo de 4 GB se procesa en segundos. La unión final es una invocación normal de FFmpeg; puedes previsualizar la forma fusionada vía --dry-run con el verbo fqmpeg más cercano a lo que necesitas (p. ej. convert con flags personalizados) o escribir el FFmpeg a mano.

Preguntas Frecuentes

¿Por qué convert --copy a veces falla con "Could not find tag for codec"?

Porque el códec fuente no es legal en el contenedor destino. WebM solo acepta vídeo VP8/VP9/AV1 y audio Vorbis/Opus — copiar H.264 a .webm lo rechaza. MP4 acepta vídeo H.264/H.265/AV1 pero no VP9. Quita --copy para volver a re-codificación, o elige un contenedor más permisivo como .mkv.

¿hls produce una escalera adaptativa multi-bitrate?

No — produce una sola rendición (.m3u8 + chunks). El streaming adaptativo multi-bitrate requiere codificar la fuente a 2–5 bitrates distintos (usa bitrate del cluster de compresión para cada uno) y luego escribir una playlist maestra que referencie el .m3u8 de cada rendición. La guía de streaming HLS con CDN muestra el formato de la playlist maestra.

¿Debo entregar HLS o DASH?

HLS si tu audiencia tira a iOS / Safari / Apple TV — el ecosistema Apple trata HLS como nativo y DASH como ciudadano de segunda. DASH si apuntas a Android TV, smart TVs con reproductores W3C estándar, o tuberías estrictamente conformes a W3C. La mayoría de servicios grandes entregan ambos, multiplexando en tiempo de petición. Para proyectos pequeños, solo HLS cubre ~95 % de los espectadores gracias a hls.js.

¿Por qué segment corta en límites ligeramente desplazados del segundo exacto?

segment corre en modo stream-copy, que solo puede partir en keyframes existentes (límites GOP). Si tu fuente tiene un GOP de 5 segundos, un segmento de 10 segundos caerá entre 5 s y 15 s — lo más cerca posible de 10 s. Para cortes precisos al frame, re-codifica editando el comando impreso por --dry-run para quitar -c copy, o acorta el intervalo de keyframes de la fuente primero con -g 30 -keyint_min 30 en el codificador.

¿Por qué el GIF por defecto se ve mucho mejor que ffmpeg -i in.mp4 out.gif?

La línea ingenua usa la paleta nativa de GIF (256 colores fijos no derivados de tu clip), que produce banding y ruido de dithering. gif usa el filtro de dos pasadas palettegen / paletteuse de FFmpeg para calcular una paleta óptima a partir de los frames reales de tu clip. El mismo truco está enterrado en docenas de posts de Stack Overflow sobre "GIF de alta calidad en FFmpeg"; gif te ahorra teclearlo.

¿Puedo hacer que extract-stream escriba a una extensión personalizada?

Sí — pasa -o output.<ext>. La extensión por defecto asume AAC/SRT/MP4, lo cual es correcto para la mayoría de archivos pero incorrecto para, digamos, audio Opus en un MKV (extract-stream input.mkv a:0 -o audio.opus). El flag -c copy significa que los bytes que estaban dentro de la pista fuente se escriben tal cual al nuevo archivo, así que la extensión solo necesita coincidir con el códec real.

¿Puedo segmentar en lote una carpeta de archivos?

Sí — fqmpeg es shell-friendly. El patrón estándar:

bash
for f in *.mp4; do
  mkdir -p "segments/${f%.mp4}"
  ( cd "segments/${f%.mp4}" && npx fqmpeg segment "../../$f" 60 )
done

Cada invocación de segment corre en su propio subdirectorio para que los chunks no choquen. La subshell exterior ( ... ) evita que el cd se filtre a la siguiente iteración.

¿Hay soporte para HLS de Baja Latencia (LL-HLS)?

No vía un flag dedicado — hls produce HLS estándar. Para LL-HLS querrás --segment 1 o 2, más los flags de FFmpeg -hls_flags +program_date_time -hls_segment_type fmp4 y opciones de segmentos parciales. Lanza --dry-run, copia el comando impreso y añade los flags LL-HLS a mano. La especificación HTTP Live Streaming de Apple es la referencia autoritativa para los campos de segmento parcial y rendition report.

Conclusión

Los siete verbos de C2 cubren las decisiones de "forma del archivo" que vas a tomar una vez decidido el códec:

  • convert para "mismo contenido, otro envoltorio" (con --copy para remux instantáneo)
  • gif / gif-to-video para el viaje de ida y vuelta del GIF
  • hls / dash para manifiestos de streaming adaptativo
  • segment para trocear a duración fija
  • extract-stream para extraer pistas quirúrgicamente

Cada verbo imprime su invocación FFmpeg subyacente con --dry-run, así puedes copiarlo tal cual, adaptarlo (LL-HLS, patrones de segmento personalizados, swap a NVENC) o aprender la sintaxis detrás del verbo. Para el mapa más amplio de fqmpeg, vuelve a la guía completa de fqmpeg. Para decisiones del lado del códec, mira la inmersión profunda en compresión y codificación.