Uno de los mejores compañeros de viaje de ImageMagick a la hora de procesar vídeo es ffmpeg.
ffmpeg es un programa de edición de vídeo que permite, desde la línea de comandos crear vídeos, editarlos, extraer el audio, combinar una pista de audio y otra de vídeo generadas individualmente en una mismo fichero... Tiene todas las funcionalidades de cualquier programa de edición de vídeo (la mayoría de los programas de edción de vídeo en modo gráfico se basan en ffmpeg para procesar vídeos y lo que hacen es que visualmente sea un poco más amigable) con la ventaja de ser en modo consola, por lo que permite automatizar procesos y usarlo en scripts, como contrapartida, es un poco farragoso de usar y no tan cómodo de trabajar como Kdenlive, OpenShot, Pitivi u otro programa gráfico.
Pero ahora vamos a ver cómo sacarle mucho partido en combinación con ImageMagick descomponiendo un vídeo en fotogramas para poder procesar individualmente cada uno de ellos con IM, componiendo una serie de fotogramas en vídeo y generando un vídeo al vuelo en un script sin tener que almacenar los fotogramas en el disco y luego generar el vídeo que, como comenté al explicar qué son convert y mogrify, muchas veces nos genera un gran quebradero de cabeza cuando hacemos un vídeo procesando uno a uno los fotogramas con ImageMagick.
Para descomponer en fotogramas un vídeo le tenemos que indicar a ffmpeg el fichero de entrada con -i y los ficheros de salida con, al igual que cuando generamos nuevas imágenes con crop, el formato de salida se lo daremos con un prefijo, el indicador del formato del numeral con % y la extensión del fichero. Algo así como:
ffmpeg -i video.mp4 fotograma%04d.png
Lo que nos generará es una serie de fotogramas que se llamarán fotograma0001.png, fotograma0002.png... ya que le hemos indicado:
Del mismo modo que podemos descomponer en fotogramas un vídeo, podemos componer un vídeo a partir de fotogramas. Para que el vídeo tenga el orden correcto de fotogramas, lo mejor es que sigan el patrón que he comentado antes: el mismo prefijo, números correlativos y con la misma cantidad de dígitos y la misma extensión. Esto no es obligatorio, ya que podríamos indicarle que generase un vídeo, por ejemplo, con todos los pngs de un directorio (*.png), pero molesta mucho ver cómo un vídeo que has tardado horas en renderizar falla el orden de algún fotograma simplemente por no haber nombrado con un patrón claro los fotogramas. Así que haremos las cosas bien y seremos más felices:
ffmpeg -i fotograma%04d.png video.mp4
En general, con este comando genera un vídeo a partir de los fotogramas, aunque, como digo, si le decimos exactamente cómo queremos el vídeo, lo hará exactamente como queremos. Así que le podemos añadir alguna opción para que haga el vídeo a nuestro gusto:
-r $fotogramasporsegundo
: con la opción -r le indicamos el ratio de fotogramas por segundo que queramos que tenga el vídeo.
-s $anchuraX$altura
: con -s indicamos el tamaño del vídeo
-aspect $ancho:$alto
: el aspecto del fotograma, por ejemplo, 16:9. Opción que la inico aquí más a modo de curiosidad que por practicidad, ya que si le indicamos el tamaño, le estamos indicando el aspecto.
-c:v $codecdevideo
: con -c:v le indicamos el códec de vídeo con el que queremos que renderice el vídeo (Nota: -c:a es para audio -c:v es para vídeo)
-pix_fmt $formatodelpixel
: -pix_fmt permite seleccionar el formato de píxel. Podemos ver todos los formatos seleccionados con ffmpeg -pix_fmts que nos muestra el nombre de formato de píxel, los bits por píxel de ese formato y los componentes NB (el número de componentes de una imagen, por ejemplo, los tres canales RGB).
Para componer un vídeo al vuelo a través de fotogramas generados en un script con convert, le debemos indicar a convert en lugar de un nombre de fichero, que los codifique como png:- y a ffmpeg, que recoja los ficheros con image2pipe. Quedaría algo así:
#!/bin/bash
for i in $lista
do
convert imagen.png -$parametro $valor png:-
done | ffmpeg -f image2pipe -i - video.mp4
Aquí también le podemos indicar a ffmpeg los valores que deseemos para ajustar su trabajo a nuestras necesidades.
Nota: Gracias a Gaspar Fernández (https://poesiabinaria.net) por sus indicaciones con image2pipe.
Ya hemos visto que $RANDOM%$limitesuperior no funciona.
Hemos visto que $RANDOM%1 para hacer tiradas "a cara o cruz" no funciona.
Y del mismo modo que deberemos sospechar (y no apostar) si vemos que un trilero siempre saca el mismo resultado lanzando una moneda, cuando lanza un dado puede que haga también trampa. Los dados se pueden cargar y hacer que un lado pese más, por lo que hay más posibilidades de que ese lado sea el que se apoye en la mesa y, por lo tanto, sea el lado apostado el que quede arriba. Pasa lo mismo con $RANDOM:
#!/bin/bash
let nocoincide=0
for i in $(seq 1 1000)
do
for x in $(seq 1 1000)
do
let numero=$((RANDOM%$x))
if [ $numero -lt 10 ]
then
echo $numero >> coincidencias.txt
fi
done
done
for i in $(seq 0 9)
do
echo "Veces que se repite "$i
fgrep -o $i coincidencias.txt | wc -l
done
El resultado que me ha dado es (cada vez que lo probemos saldŕa algo distinto, ya que son números aleatorios):
Veces que se repite 0
7559
Veces que se repite 1
6668
Veces que se repite 2
5941
Veces que se repite 3
5730
Veces que se repite 4
5387
Veces que se repite 5
5219
Veces que se repite 6
5053
Veces que se repite 7
4810
Veces que se repite 8
4769
Veces que se repite 9
4638
Cuanto más pequeño es el número, más veces se repite. Curioso caso de "aleatoriedad". ¿No os resulta sospechoso? A mi, sí. Por lo que voy a compararlo con otros números. Y antes, voy a borrar coincidencias.txt para evitar viciar resultados:
rm coincidencias.txt
Y ahora, el script:
#!/bin/bash
let nocoincide=0
for i in $(seq 1 1000)
do
for x in $(seq 1 1000)
do
let numero=$((RANDOM%$x))
if [ $numero -lt 10 ]
then
echo $numero >> coincidencias.txt
elif [ $numero -eq 100 ]
then
echo $numero >> coincidencias.txt
elif [ $numero -eq 500 ]
then
echo $numero >> coincidencias.txt
elif [ $numero -eq 750 ]
then
echo $numero >> coincidencias.txt
elif [ $numero -eq 999 ]
then
echo $numero >> coincidencias.txt
fi
done
done
for i in $(seq 0 9)
do
echo "Veces que se repite "$i":"
fgrep -o $i coincidencias.txt | wc -l
done
echo "Datos de control"
for i in 100 500 750 999
do
echo "Veces que se repite "$i":"
fgrep -o $i coincidencias.txt | wc -l
done
Y el resultado:
Veces que se repite 0:
13717
Veces que se repite 1:
8694
Veces que se repite 2:
5986
Veces que se repite 3:
5651
Veces que se repite 4:
5389
Veces que se repite 5:
6259
Veces que se repite 6:
5029
Veces que se repite 7:
5339
Veces que se repite 8:
4796
Veces que se repite 9:
4701
Datos de control
Veces que se repite 100:
2296
Veces que se repite 500:
687
Veces que se repite 750:
277
Veces que se repite 999:
0
Ummmm... Como digo, algo que es aleatorio puede cambiar el resultado. Pero si siempre que ejecutamos el script nos da el mismo patrón, que es que cuanto más pequeño sea el número, más veces sale, es que esa aleatoriedad está tan viciada con los dados cargados de un trilero. Y es que $numero en $RANDOM%$numero no indica el límite superior sino el divisor de un dividendo aleatorio en una instrucción en la que le pedimos que nos indique el resto.
Ni más, ni menos.
En otro artículo ya comenté que para hacer una especie de "cara o cruz" en el que de forma aleatoria nos muestre 0 o 1, $RANDOM%1 no funciona, ya que siempre da 0. En un comentario me decían que dividir una cantidad entre 1 no da decimales. Es cierto, nunca podrá haber rendondeo a la alza porque nunca podrá haber redondeo y, por lo tanto, nunca podrá salir la cantidad indicada como límite superior del rango.
Voy a hacer un script en el que saquemos muchos números aleatorios. Por ejemplo, que mil veces haga una tirada aleatoria con resultados del 0 al 1.000.
#!/bin/bash
for i in $(seq 1 1000)
do
for x in $(seq 1 1000)
do
let numero=$((RANDOM%$x))
if [ $numero -eq $x ]
then
echo $x
fi
done
done
Al ejecutarlo no sale nada. Y repito, y repito, y repito la ejecución del script. Siempre sin ningún resultado. ¿Me habré equivocado? Voy a acumular las veces que el resultado no coincide con el límite superior:
#!/bin/bash
let nocoincide=0
for i in $(seq 1 1000)
do
for x in $(seq 1 1000)
do
let numero=$((RANDOM%$x))
if [ $numero -eq $x ]
then
echo $x
else
let nocoincide++
fi
done
done
echo "Han salido "$nocoincide" coincidencias con el límite superior"
Ahora el resultado siempre es el mismo:
Han salido 1000000 coincidencias con el límite superior
Pues igual no he sido yo el que me he equivocado. Aunque es cierto que al ser aleatorio puede que ese número no salga. Pero si varias veces repito un millón de tiradas y nunca coincide, algo raro pasa. Quizá lo raro es que quien afirma que para sacar un número aleatorio entre 0 y $x se puede conseguir con let numero=$((RANDOM%$x))
se equivoca... o que nunca se ha parado a comprobarlo.
Cuando queremos escribir en un fichero de texto desde un script de Bash, tenemos dos opciones:
Que queramos sobreescribir el fichero con el texto que le enviemos
Que queramos anexar el texto a un fichero ya existente
La primera opción la haremos con:
echo $variable > fichero.txt
La segunda opción, con >>:
echo $variable >> fichero.txt
Ejemplos
Si queremos escribir únicamente el último resultado:
#!/bin/bash
for i in $(seq 1 100)
do
echo $i > numeros.txt
done
Si queremos un fichero en el que se almacene una lista de números del uno al cien:
#!/bin/bash
for i in $(seq 1 100)
do
echo $i >> numeros.txt
done
Si queremos un número aleatorio en Bash, tenemos la función de Bash RANDOM. Es una función del intérprete de comandos, por lo que no lo podemos ejecutar como comando, sino que si queremos desde la línea de comandos un número aleatorio, deberemos escribir:
echo $RANDOM
Esto nos dará un número aleatorio entre 0 y 32.768 (2 elevado a 15).
Pero, ¿qué pasa si queremos delimitar el rango en el que queremos el número aleatorio?
Podemos dividirlo por el límite superior y, al devolver el módulo, siempre será, como máximo, el número indicado menos uno. En muchos artículos y tutoriales que dicen que el número que indicamos después del símbolo de porcentaje es el límite superior, pero esto no es así.
Veamos un caso extremo:
#!/bin/bash
contador=0
for i in $(seq 1 100000)
do
let numero=$((RANDOM%1))
if [ $numero -eq 1 ]
then
let contador=$contador+1
fi
done
echo $contador
Le digo que haga un bucle cien mil veces y que calcule un número aleatorio entre el 0 y el 1. Que cuente cuantas veces sale 1 y muestre el número de veces que ha salido 1.
El resultado siempre es 0.
Así pues, cuando queráis un número aleatorio con un rango, sumadle uno al rango superior que le indiquéis a $RANDOM.
Vamos a ver cómo calcular todos los divisores de un número. Esto nos puede servir, por ejemplo para calcular en cuantas piezas iguales podemos dividir un fotograma. Veamos un script que lo hace:
#!/bin/bash
function hallatodoslosdivisores()
{
divisores=""
for i in $(seq 1 $1)
do
let resto=$1%$i
if [ $resto -eq 0 ]
then
divisores=$divisores$i" "
fi
done
echo $divisores
}
let numero=1920
todoslosdivisores=$(hallatodoslosdivisores $numero)
echo "Los divisores de "$numero" son: "$todoslosdivisores
Explicación del script:
En una función llamada hallatodoslosdivisores, le indicamos que cree una variable vacía para poder ir rellenando de las cadenas que le digamos según si cumplen o no el requisito que buscamos. Después le decimos que recorra desde 1 hasta el número que le indicamos y que calcule el módulo, es decir, el resto de la división. Si el resto es 0, es que ese número es divisor del parámetro que le hemos indicado, así que se deberá incluir en el listado. En esa instrucción (divisores=$divisores$i" ") le digo que anexe también un espacio en blanco, así podemos separar fácilmente los divisores. Una vez recorrido desde 1 hasta el número indicado y hallados todos los divisores de ese número, que la función devuelva el resultado para poder trabajar con él.
Le indicamos un número. En este caso, 1920 por ser la anchura en píxeles del formato HD, pero le podemos indicar cualquier otro número. Para enviar un parámetro a una función y luego recibir en una variable el resultado de esa función escribimos:
variable=$(funcion $parametro)
Y, por último, comprobamos que el resultado es correcto.
¿Lo comprobamos? Aquí está:
Los divisores de 1920 son: 1 2 3 4 5 6 8 10 12 15 16 20 24 30 32 40 48 60 64 80 96 120 128 160 192 240 320 384 480 640 960 1920
La verdad es que Bash no es el mejor lenguaje de programación para hacer operaciones matemáticas complejas. En este artículo me voy a centrar en las operaciones matemáticas más sencillas y un par de trucos para exprimirlas un poco más. Si has llegado aquí por casualidad buscando aprender a hacer operaciones más complejas, busca información sobre bc... o estate atento y sigue a Linux Center en las redes sociales, que pronto escribiré sobre bc.
De momento, haré un script con las operaciones básicas:
#!/bin/bash
let operador1=5
let operador2=3
let suma=$operador1+$operador2
let resta=$operador1-$operador2
let multiplicacion=$operador1*$operador2
let division=$operador1/$operador2
let resto=$operador1%$operador2
echo "El resultado de la suma es "$suma
echo "El resultado de la resta es "$resta
echo "El resultado de la multiplicación es "$multiplicacion
echo "El resultado de la división es "$division
echo "El resto de la división es "$resto
Que, al ejecutarlo, me da el siguiente resultado:
El resultado de la suma es 8
El resultado de la resta es 2
El resultado de la multiplicación es 15
El resultado de la división es 1
El resto de la división es 2
Este último operador, el módulo o resto de la división, que, como vemos, no es nada preciso, ya que redondea a un único dígito, siendo el resultado un decimal periódico puro. Sin embargo, puede dar mucho juego:
* Delimitar el rago de aleatoriedad de un número
* Hallar todos los divisores de un número
Trabajar con funciones ahorra bastante tiempo y esfuerzo al incluir en las funciones tareas repetitivas, además de hacer scripts mucho más modulares y manejables, que nos facilita el mantenimiento y evolución de los scripts.
Ya hemos visto qué son las funciones en shell script, qué son los parámetros, tanto en shell script como en las funciones, cómo enviar parámetros a funciones y cómo recibir como variable el resultado de una función.
Ahora vamos a ver un sencillo truco para poder trabajar con funciones u otras porciones de código. Hagamos una función:
function enficheroexterno()
{
echo "Soy una función que está en distinto fichero que el script"
}
Y la guardo en un fichero que se llama funcionexterna (no hace falta que le pongamos extensión, aunque podemos hacerlo).
Y un script:
#!/bin/bash
. funcionexterna
enficheroexterno
Y cuando ejecuto el script, me da el siguiente resultado:
Soy una función que está en distinto fichero que el script
Para incluir la función de un fichero externo, únicamente he puesto un punto antes del nombre del fichero. De esa manera, incluye el texto del fichero en nuestro script.
Las máscaras son imágenes, generalmente en blanco y negro, o escala de grises en el que, al aplicarla a la composición de una imagen a través de dos o más imágenes superpuestas, uno de los dos colores deja ver totalmente la imagen que se encuentra en un segundo plano mientras que la región de la máscara coloreados con el otro color sean opacos y por lo tanto, se vea la imagen superior.
Dicho de otra manera: una máscara lo que hace es que unas partes de una imagen sean opacas y otras transparentes. En las zonas opacas se verá la imagen superior y en las zonas transparentes se verá la imagen inferior.
Y cuando hay más colores que el blanco y el negro, los tonos medios serán más o menos traslúcidos dependiendo si se acercan al color transparente o al color opaco.
Cuando componemos una imagen con composite, el orden de las imágenes es de arriba a abajo, es decir, la primera imagen que le indiquemos será la superior y la segunda imagen,será la capa inferior.
Y cuando usemos una máscara, esta deberá ser la última imagen indicada.
Respecto al color opaco y transparente, el blanco será el color opaco y el negro, el color tranparente.
Como una imagen vale más que mil palabras, vamos a ver cuatro imágenes. Que, por una sencilla regla de tres, valdrá más que cuatro mil palabras:
Hacemos dos imágenes planas para que se vea claro, una roja y otra azul:
convert -size 300x150 xc:red rojo.png
convert -size 300x150 xc:blue azul.png
Y una imagen blanca y negra que será la que usemos como máscara:
convert -size 150x150 xc:white xc:black +append mascara.png
Y ahora vamos a comprobar cómo trabaja composite con máscaras poniendo en primer plano la imagen roja, en segundo plano la azul y usando la máscara:composite rojo.png azul.png mascara.png imagenconmascara.png
A veces nos puede interesar generar una imagen promediada de dos imágenes. Las utilidades son muchas: bien porque queramos hacer una transición en un vídeo, bien porque queramos aplicar un filtro a una imagen y generar el promedio con la imagen original o bien porque queramos hacer un montaje con dos imágenes... Las limitaciones aquí, como en cualquier otro campo, son casi exclusivamente las que la imaginación que el autor tiene.
Para generar una imagen con el promedio de otras dos o más imágenes tenemos el parámetro -average. Digo que se pueden unir dos o más imágenes, pero cuidado con el abuso que unir muchas imágenes que puede generar un pastiche sin sentido... o un maravilloso caos. La instrucción sería:
convert imagen1.png imagen2.png [... imagenN.png ] -average imagenpromediada.png
Veamos varios ejemplos:
Tomaremos como referencia una de las torres que hay a la entrada por Isabel la Católica del Parque Grande José Antonio Labordeta de Zaragoza:
Y le vamos a aplicar un efecto. Por ejemplo, sombrearlo con -shade:convert torre_entrada_parque_grande.jpg -shade 15x45 torre_sombreada.jpg
Los perfiles son los mismos, pero los colores no lo son, así que nos vendrá bien para ver cómo trabaja -average:
convert torre_entrada_parque_grande.jpg torre_sombreada.jpg -average torre_promediada.jpg
Al promediar con una gran parte gris, desatura los colores, pero realza algunas zonas sombreadas.
Podemos hacer un lienzo sólido de un color y luego promediarlo con la imagen original
convert -size 500x375 xc:red lienzorojo.jpg
convert torre_entrada_parque_grande.jpg lienzorojo.jpg -average torreenrojecida.jpg
O promediando el rojo y la sombra:
convert torre_sombreada.jpg lienzorojo.jpg -average sombraenrojecida.jpg
Podemos generar una imagen con el promedio de dos imágenes distintas. Promediaremos esa torre con el Auditorio de Zaragoza:
convert torre_entrada_parque_grande.jpg auditorio.jpg -average auditorio_torre_promedio.jpg
Y como habíamos dicho antes, -average no sólo une dos imágenes, sino que puede unir más. Añadámosle estas columnas del Parque de Bomberos 3 de Zaragoza:convert auditorio.jpg torre_entrada_parque_grande.jpg columnas_bomberos.jpg -average pastiche.jpg
Y podríamos seguir añadiendo imágenes hasta el infinito, pero este efecto queda más limpio cuando las imágenes son más uniformes. Veamos el promedio de un detalle de la Pasarela del Voluntariado y un anochecer con el sol rielando en una acequia en Pastriz:convert anochecer_acequia.jpg detalle_pasarela_del_voluntariado.jpg -average anochecer_voluntariado.jpg