1200px-Film_strip.jpg

Seguramente en nuestros scripts, si tratamos con archivos de vídeo, nos interese conocer información sobre el mismo. Tal vez podamos extraer más información de un archivo de vídeo con un software especializado como VLC; pero para hacer unos cálculos rápidos o una conversión de formato nos va a venir muy bien.

Programas como avconv o ffmpeg son capaces de extraer información básica del vídeo. Es más, programas como identify (de imagemagick) entre otros, en realidad llaman por detrás a uno de los dos primeros para realizar la identificación del archivo.

Pero claro, imaginemos que queremos automatizar algunos procesos y hay archivos de vídeo involucrados. ¿Cómo extraemos la información de los mismos? Con herramientas como sed o awk. Al final del post tendréis el código fuente completo del script.

¿avconv o ffmpeg?

avconv es un fork de ffmpeg. Por eso, tienen algunas cosas en común. El caso es que algunas distribuciones incluyen ffmpeg y otras avconv. Por lo que lo primero que tenemos que tener claro es qué programa tenemos nosotros instalado.
Ambos programas sólo tienen que especificar el archivo de entrada para tener como salida información del archivo de vídeo. En este caso el argumento -i nos devuelve algo como:

avconv version 9.18-6:9.18-0ubuntu0.14.04.1, Copyright (c) 2000-2014 the Libav developers
built on Mar 16 2015 13:19:10 with gcc 4.8 (Ubuntu 4.8.2-19ubuntu1)
Guessed Channel Layout for Input Stream #0.1 : stereo
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from ‘/tmp/video.mp4?:
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf54.20.4
Duration: 00:26:25.36, start: 0.000000, bitrate: 1105 kb/s
Stream #0.0(und): Video: h264 (High), yuv420p, 720×576 [PAR 16:15 DAR 4:3], 974 kb/s, 25 fps, 25 tbr, 25 tbn, 50 tbc
Stream #0.1(und): Audio: mp3, 48000 Hz, stereo, s16p, 128 kb/s
At least one output file must be specified

Nos da más información de la que vamos a extraer, por lo que podríamos sacar más cosas en claro, por otro lado, no nos enseña los frames totales del vídeo, y eso puede resultarnos de ayuda ya que esta herramienta expresa el progreso en número de frames procesados, por lo que si sabemos cuántos frames hay en total, podemos saber qué porcentaje de progreso llevamos. Pero lo veremos más adelante.

Desgranando el script

Para eso he utilizado una función que busca los dos ejecutables en el sistema, y coge el que exista.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function checkSoftware()
{
    SW="`which avconv; which ffmpeg`"

    if [ -n "`echo $SW | grep avconv`" ]
    then
    CONVERTSOFTWARE="avconv"
    elif [ -n "`echo $SW | grep ffmpeg`" ]
    then
    CONVERTSOFTWARE="ffmpeg"
    else
    echo "No hay ningún conversor disponible" >&2
        exit 1
    fi
}

Con esto, sólo tendremos que llamar a checkSoftware y a partir de ahí se establecerá la variable CONVERTSOFTWARE con el nombre de nuestro ejecutable.

Tras esto, sólo tendremos que llamar a:

1
$CONVERTSOFTWARE -i "$ARCHIVO"

Esto, yo prefiero meterlo en una función que he llamado getVideoInfo(), por si algún día decido cambiar algo o introducir algo más:

 
1
2
3
4
function getVideoInfo()
{
    $CONVERTSOFTWARE -i "$1" 2>&1
}

Al final, como avconv devuelve los mensajes por la salida de error, redirijo ésta a la salida estándar (2>&1) para no tener problemas.

Obtención de datos

Los datos los obtendremos a partir de la salida de avconv (o ffmpeg), filtrando las zonas de texto. Puede que haya archivos que por su naturaleza no nos devuelvan bien la información (porque tengan varios streams, o porque no tengan los streams bien formados, o no tengan stream de vídeo…), aunque por lo general sí que funcionará bien:

1
2
3
4
5
6
7
8
9
10
11
12
13
function getAllData()
{
    FILE="$1"
    if [ ! -r "$FILE" ]
    then
    panic "File $FILE not found"
    fi
    FILEINFO="`getVideoInfo "$FILE"`"
    DURATION=$(echo "$FILEINFO" | sed -n "s/.* Duration: \([^,]*\), start: .*/\1/p")
    BITRATE=$(echo "$FILEINFO" | sed -n "s/.* bitrate: \([^,]*\) kb\/s/\1/p")
    FPS=$(echo "$FILEINFO" | sed -n "s/.*, \(.*\) fps.*/\1/p")
    FRAMES=$(echo $DURATION | awk -F':' "{ FRAMES=(\$1*3600+\$2*60+\$3)*$FPS; print FRAMES }")
}

Esto creará algunas variables:

  • FILEINFO: Contendrá la información en bruto devuelta por avconv
  • DURATION: Busco la palabra Duration y start y extraigo el valor que hay entre ellos, que es la duración en horas:minutos:segundos (segundos puede tener decimales)
  • BITRATE: Busco la palabra bitrate y kb/s y extraigo el número entre ellos, así consigo los kilobits por segundo.
  • FPS: Los fotogramas por segundo son un número seguido de la palabra fps
  • FRAMES: Los fotogramas, ya que no vienen en esta salida los calculo extrayendo el número total de segundos de duración y multiplicándolos por los FPS. Es decir: (horas*3600 + minutos*60 + segundos)*fps. En este caso, awk tomará como separador ‘:’ y cogerá el elemento $1, como hora; $2, como minutos; $3, como segundos. La barra delante del $ es un escapado, ya que hemos de diferenciar entre una variable de awk (\$1) y una variable de bash ($FPS).

El script completo

Aquí lo tenéis para copiar y pegar:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#!/bin/bash
#
# Author: Gaspar Fernandez (Esta dirección de correo electrónico está siendo protegida contra los robots de spam. Necesita tener JavaScript habilitado para poder verlo.)
# Extracted from http://totaki.com/poesiabinaria
#
# Do whatever you want with this code.

function panic()
{
    echo "$1" 1>&2
    exit 1
}

function checkSoftware()
{
    SW="`which avconv; which ffmpeg`"

    if [ -n "`echo $SW | grep avconv`" ]
    then
    CONVERTSOFTWARE="avconv"
    elif [ -n "`echo $SW | grep ffmpeg`" ]
    then
    CONVERTSOFTWARE="ffmpeg"
    else
    panic "Failed to search conversion software in remote server"
    fi
}

function getVideoInfo()
{
    $CONVERTSOFTWARE -i "$1" 2>&1
}

function getAllData()
{
    FILE="$1"
    if [ ! -r "$FILE" ]
    then
    panic "File $FILE not found"
    fi
    FILEINFO="`getVideoInfo "$FILE"`"
    DURATION=$(echo "$FILEINFO" | sed -n "s/.* Duration: \([^,]*\), start: .*/\1/p")
    BITRATE=$(echo "$FILEINFO" | sed -n "s/.* bitrate: \([^,]*\) kb\/s/\1/p")
    FPS=$(echo "$FILEINFO" | sed -n "s/.*, \(.*\) fps.*/\1/p")
    FRAMES=$(echo $DURATION | awk -F':' "{ FRAMES=(\$1*3600+\$2*60+\$3)*$FPS; print FRAMES }")
}

checkSoftware

if [ -z "$1" ]
then
    panic "No input file"
fi

if [ -z "$2" ]
then
    FILE="$1"
    DATA="all"
else
    FILE="$2"
    DATA="$1"
fi

case "$DATA" in
    "all")
    getAllData "$FILE"
    echo "Duration: $DURATION"
    echo "FPS: $FPS"
    echo "Bitrate: $BITRATE kb/s"
    echo "Total frames: $FRAMES"
    ;;
    "duration")
    getAllData "$FILE"
    echo "$DURATION"
    ;;
    "fps")
    getAllData "$FILE"
    echo "$FPS"
    ;;
    "bitrate")
    getAllData "$FILE"
    echo "$BITRATE"
    ;;
    "frames")
    getAllData "$FILE"
    echo "$FRAMES"
    ;;
    *)
    panic "Element to extract not recognized: $DATA"
esac

El ejecutable tiene algunas opciones, si lo llamamos vinfo y ejecutamos:

$ vinfo mivideo.avi

Nos devolverá información general sobre el vídeo.

Duration: 00:26:25.36
FPS: 25
Bitrate: 1105 kb/s
Total frames: 39634

En cambio si especificamos una opción de las posibles (duration, fps, bitrate, frames) sólo devolverá el contenido del dato, sin ningún texto introductorio, para que podamos usar ese dato directamente en scripts.

$ vinfo frames mivideo.avi
39634

 

Foto: “Film strip” by Bart from New Orleans, Louisiana, USA – Strip. Licensed under CC BY 2.0 via Commons

Fuente: poesiabinaria

¿Quién está en línea?

Hay 1422 invitados y ningún miembro en línea