Table of Contents
Hoy me he encontrado en una situación un tanto molesta. Tras obtener de forma totalmente legal y lícita una gran colección de videos mp4, los cargué en mi disco duro externo, enchufé el disco duro en mi televisor, le di al play al primer video… y el audio no se reconocía. Drama y desesperación, no hay posibilidad de encontrar estos videos en otro formato, solo existen en este, así que me puse manos a la obra.
¿Qué es un encoding de audio?
Si tú, como yo, eres un tanto ignorante en estos aspectos del multimedia, te voy a explicar brevemente por qué mi televisor no sabía reproducir el audio de estos archivos mp4.
Un archivo mp4 se puede explicar como un paquete que comprime por separado el audio y el video. Tanto el video como el audio tienen su propio encoding, que resumidamente es la forma en que se almacena y se transmite sus datos. Por lo tanto, si el reproductor (mi televisor) no tiene los codecs apropiados, no sabrá leer ese formato de encoding. Los codecs son los paquetes de algoritmos que permiten codificar, en este caso decodificar, los datos de video/audio.
Y si mi televisor no tiene los codecs para leer ese encoding de audio concreto, qué hago? Pues cambiar el encoding a los videos. Y aquí entra FFmpeg.
FFmpeg es un proyecto open source para manejar archivos multimedia, te permite ver el detalle de estos con FFprobe, te permite reproducirlos con FFplay, y te permite modificarlos con FFmpeg.
rruiz@rruiz:~/Videos$ ffprobe video1.mp4
ffprobe version 4.2.7-0ubuntu0.1 Copyright (c) 2007-2022 the FFmpeg developers
built with gcc 9 (Ubuntu 9.4.0-1ubuntu1~20.04.1)
configuration: --prefix=/usr --extra-version=0ubuntu0.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-nvenc --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
libavutil 56. 31.100 / 56. 31.100
libavcodec 58. 54.100 / 58. 54.100
libavformat 58. 29.100 / 58. 29.100
libavdevice 58. 8.100 / 58. 8.100
libavfilter 7. 57.100 / 7. 57.100
libavresample 4. 0. 0 / 4. 0. 0
libswscale 5. 5.100 / 5. 5.100
libswresample 3. 5.100 / 3. 5.100
libpostproc 55. 5.100 / 55. 5.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'video1.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
title : video 1 (1080p)
artist : WinX HD Video Converter Deluxe
encoder : Lavf58.29.100
Duration: 00:24:39.56, start: 0.000000, bitrate: 3737 kb/s
Stream #0:0(und): Video: h264 (Main) (avc1 / 0x31637661), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 3455 kb/s, 30 fps, 30 tbr, 15360 tbn, 60 tbc (default)
Metadata:
handler_name : VideoHandler
Stream #0:1(cat): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 276 kb/s (default)
Metadata:
handler_name : SoundHandler
Aquí podemos ver que el stream #0:0(und) contiene la pista de video en encoding h264, que una rápida búsqueda en internet nos dirá que es un standard de video encoding también conocido como AVC (advanced video encoding) o MPEG-4 Part 10. Aunque esta no es la parte que nos interesa.
En el stream #0:1(cat) encontramos la pista de audio, con encoding aac(advanced audio encoding), este es el encoding que, por algún motivo, mi televisor no sabe reproducir.
No es la primera vez que esto me ocurre, generalmente me ocurre con vídeos mkv, formato de video que no tiene un standard de encodings tan definido como el mp4 y que provoca con mayor facilidad que el reproductor no tenga el codec apropiado. Sin embargo por algún motivo, ya sea la versión del aac de estos videos o cualquier otro, el audio de estos videos mp4 no es compatible con mi televisor.
¿Cómo cambio el encoding del video?
Si conFFprobe puedo sacar la información del video, conFFmpeg puedo cambiarlo.
ffmpeg -i video1.mp4 -acodec mp3 -vcodec copy encoded_video1.mp4
La documentación de FFmpeges larga y completa, pero en nuestro caso solo necesitamos conocer un par de opciones:
–i: input, el vídeo que queremos modificar, en este caso video1.mp4. –acodec: el codec de audio que queremos que tenga el archivo de salida, en nuestro caso mp3. –vcodec: el codec de video que queremos que tenga el archivo de salida, como no queremos cambiarlo, ponemos copy. Finalmente ponemos el nombre del archivo de salida, en este caso encoded_video1.mp4.
Automatizarlo
Genial, ya se cambiar el encoding de un archivo, pero tengo muchos videos, no puedo estar escribiendo el comando uno a uno. ¿Qué opciones tenemos?
for loop
for filename in *.mp4; do
ffmpeg -i $filename -acodec mp3 -vcodec copy encoded_$filename;
done
Un for loop sería una primera opción, pero tiene unos cuantos inconvenientes. Procesará los archivos uno a uno y mi PC cuenta con 16 cores y FFmpeg, por defecto usa un solo thread, es poco eficiente y lento para muchos archivos medianamente grandes.
FFmpeg -threads
FFmpeg cuenta con la posibilidad de definir un threadpool para usar en el procesado:
ffmpeg -threads 4 -i video1.mp4 -acodec mp3 -vcodec copy encoded_video1.mp4
-threads 0 implies that FFmpeg will choose the optimal amount of threads, with -threads 1 it will be monothread, and so on, as long as it can parallelize.
Sin embargo la cantidad de threads que vaya a usar realmente depende de la versión de FFmpeg en cuestión, del codec que estés usando y de los cores de tu PC. No es algo muy consistente, y en mi caso aunque le pusiera 4 threads, nunca ha pasado de usar 1.
¿Forks? ¡Parallel!
Si FFmpeg no me permite explotar los 16 cores de mi PC, tendré que crear los threads yo mismo, y justo cuando estaba refrescando el cómo manejar una threadpool en bash para procesar archivos en batches, encuentro parallel.
Paralell es un comando gnu/bash que permite pasarle un listado de N argumentos y un comando. Ejecutará el comando N veces, cada vez con uno de los argumentos, por defecto en paralelo con tantos jobs como cores tenga tu PC.
De esta manera, si tu PC cuenta con 4 cores y tu le pasas 40 argumentos, paralelizará 4 de ellos, procesando el siguiente cuando tenga un core libre, genial, justo lo que necesitaba.
Sin embargo te permite indicar el número máximo de jobs que quieres que use, para no saturar el PC, con –jobs. También recomiendan usar –ungroup para más eficiencia, esto permite pintar los logs asap, resultando en logs de diferentes comandos mezclados.
ls *.mp4 | parallel --ungroup --jobs 6 echo
En este ejemplo los argumentos que le pasamos a parallel son el listado de archivos mp4 de la carpeta actual, y el comando que va a ejecutar en paralelo es… echo. No es útil, pero nos sirve para ver la sintaxis.
Es hora de montar un pequeño script para cambiar el encoding de los videos de una carpeta:
#!/bin/bash
encode() { ffmpeg -i $1 -acodec mp3 -vcodec copy encoded_$1; }
export -f encode
ls *.mp4 | parallel --ungroup --jobs 14 encode
En este sencillo script definimos una función encodeque contiene el uso del comando FFmpeg tal y como lo hemos explicado antes. A continuación exportamos la función para que pueda ser usada porparallel. Finalmente llamamos a parallel con todos los mp4 de la carpeta actual, usando un máximo de 14 jobs (cores) simultáneos.
Genial, ya tenemos un prototipo operativo. Ahora toca mejorarlo para poder usarlo en diferentes carpetas:
#!/bin/bash
DIRECTORY=$1
JOBS=14
encode() { ffmpeg -i $1 -acodec mp3 -vcodec copy encoded_$1; }
export -f encode
cd "$DIRECTORY"
ls *.mp4 | parallel --ungroup --jobs $JOBS encode
Extraemos el directorio origen de los archivos del argumento $1 del script. Sacamos la cantidad de jobs a una variable para que sea más visible y fácil de cambiar.
Un cd al directorio en cuestión, y listos. El motivo por que el cd usa “” es para que funcione también para nombres de directorios que contengan espacios.
Para terminar, mejoramos el logging y el output de videos:
#!/bin/bash
DIRECTORY=$1
JOBS=14
ENCODED_DIRECTORY=encoded
echo "changing audio encode to aac for mp4 files in directory $DIRECTORY, $JOBS files at a time, storing the newly encoded files in $DIRECTORY/$ENCODED_DIRECTORY"
encode() { echo "processing $2..."; ffmpeg -loglevel 0 -nostats -i $2 -acodec mp3 -vcodec copy $1/$2; }
export -f encode
cd "$DIRECTORY"
mkdir -p $ENCODED_DIRECTORY
ls *.mp4 | parallel --ungroup --jobs $JOBS encode $ENCODED_DIRECTORY
Las opciones -loglevel o -nostats de FFmpeg permiten su ejecución con loglevel panic, en caso que algo falle no lo veremos salvo que sea un fatal error, pero la ejecución normal será más rápida y limpia. Siempre podemos quitar estas opciones más adelante para ver qué está fallando, o cambiar el loglevel a fatal(8), error(16), warning(24), etc.
Crearemos un subdirectorio “encoded”, especificado en la variableENCODED_DIRECTORY . Le pasaremos esta variable al comando encode dentro del parallel de manera que la función lo reciba como primer argumento, y lo usaremos en la función para guardar el output de FFmpeg en un subdirectorio, pero conservando el nombre.
Para entenderlo mejor, podemos usar la opción de parallel:
rruiz@rruiz:~/Videos$ ls *.mp4 | parallel --dryrun --jobs 14 encode "subdirectory"
encode subdirectory video1.mp4
encode subdirectory video2.mp4
encode subdirectory video3.mp4
encode subdirectory video4.mp4
Y listos, ahora podemos cambiar el encoding de los archivos mp4 de directorios enteros disparando:
rruiz@rruiz:~/Videos$ ./encode_mp4.sh directorio_1
Author
-
Software developer with over 8 years experience working with different code languages, able to work as a FullStack Developer, with the best skills in the backend side. Passionate about new technologies, and the best software development practices involved in a DevOps culture. Enjoying mentoring teams and junior developers and I feel very comfortable with the stakeholders management-wise.
Ver todas las entradas