Convertendo vídeos pelo Nautilus, mas tem alguns problemas…

Eis que eu me aventuro a criar scripts bash para o Nautilus (“file manager” do Linux). O primeiro teste, é claro, é meu script de conversão de vídeos. Quais são os requisitos? Dado um ou mais arquivos selecionados, preciso obter o número total de frames de cada um e convertê-los ao mesmo tempo que mostro o progresso da conversão. Isso parece ser um trabalho para o ‘ffmpeg’ e para o ‘zenity’.

O problema do frame count:

Começo bem: O ‘ffmpeg’ não me dá, facilmente, a quantidade total de frames (frame count) de um vídeo. Vi outros scripts que pegam essa informação através de um utilitário chamado ‘tcprobe’ (do pacote ‘transcode’), mas ele falha miseravelmente com arquivos ‘.mpg’ ou ‘.mpeg’. Existe ainda um outro problema para esses tipos de arquivo, que mostrarei daqui à pouco. Outro utilitário que pode ser usado é o ‘mediainfo’ (do pacote do mesmo nome):

$ mediainfo myvideo.avi
General
Complete name                            : myvideo.avi
Format                                   : AVI
Format/Info                              : Audio Video Interleave
File size                                : 13.5 MiB
Duration                                 : 2mn 41s
Overall bit rate                         : 701 Kbps
Writing application                      : Lavf53.32.100

Video
ID                                       : 0
Format                                   : MPEG-4 Visual
Format profile                           : Simple@L1
Format settings, BVOP                    : No
Format settings, QPel                    : No
Format settings, GMC                     : No warppoints
Format settings, Matrix                  : Default (H.263)
Codec ID                                 : FMP4
Duration                                 : 2mn 41s
Bit rate                                 : 561 Kbps
Width                                    : 1 280 pixels
Height                                   : 720 pixels
Display aspect ratio                     : 16:9
Frame rate                               : 24.000 fps
Color space                              : YUV
Chroma subsampling                       : 4:2:0
Bit depth                                : 8 bits
Scan type                                : Progressive
Compression mode                         : Lossy
Bits/(Pixel*Frame)                       : 0.025
Stream size                              : 10.8 MiB (80%)
Writing library                          : Lavc53.61.100

Audio
ID                                       : 1
Format                                   : MPEG Audio
Format version                           : Version 1
Format profile                           : Layer 3
Mode                                     : Joint stereo
Mode extension                           : MS Stereo
Codec ID                                 : 55
Codec ID/Hint                            : MP3
Duration                                 : 2mn 41s
Bit rate mode                            : Constant
Bit rate                                 : 128 Kbps
Channel(s)                               : 2 channels
Sampling rate                            : 44.1 KHz
Compression mode                         : Lossy
Stream size                              : 2.47 MiB (18%)
Alignment                                : Aligned on interleaves
Interleave, duration                     : 26 ms (0.63 video frame)
Writing library                          : LAME3.99.3

Onde é que está o frame count? Você pode pensar que se pegarmos o “Frame rate” e multiplicarmos pelo “Duration” teremos a resposta. Acontece que “Frame Rate” nem sempre é exato (ou, melhor, inteiro!). O ‘frame rate’ pode ter uma parte  fracionária (por exemplo: 23.98). Pode-se supor que isso nos daria uma quantidade menor de frames do que o arquivo realmente tem, mas veremos que isso também pode estar errado…

Felizmente o ‘mediainfo’ possui um macete:

$ mediainfo '--Inform=Video;%FrameCount%' myvideo.avi
3882

Note que se multiplicassmos o ‘Duration’ − (2*60+41), em segundos − com o ‘Frame Rate’ − 24 fps − obteríamos 3888 frames. Ou seja, 6 frames a mais do que o vídeo tem, de fato.

Usando zenity com ffmpeg

Agora que obtive o frame count, para o cálculo do percentual de progresso, basta ver como chamar o ‘ffmpeg’, passando o número do frame atual para o zenity.

O ‘ffmpeg’ me mostra a quanidade de frames já processado, atualizado na última linha:

frame= 1234 fps=659 q=31.0 Lsize=   13576kB time=00:01:32.70 bitrate= 687.7kbits/s

Tenho que arrumar um jeito de formatar essa informação e repassar para o zenity. Além disso: é preciso calcular a porcentagem do progresso com a fórmula:

percentual = (frame / frame_count) * 100

Depois disso é montar a string para ser apresentada no ‘zenity’ e passar também a porcentagem. É preciso obter o número depois de “frame=”. A ferramenta ideal para extrair essa informação, fazer os cálculos e preparar a saída é o ‘awk’ (poderia usar um script em perl ou python. Perl seria melhor, já que ele acompanha o Linux, por default):

FFMPEG_OPTIONS="-c:v libx264 -sameq -c:a ac3 -b:a 192k -ac 2 -map_metadata -1"

ffvideo () {
  # Pega o número total de frames do arquivo de entrada.
  local TOTAL_FRAMES=$(mediainfo '--Inform=Video;%FrameCount%' "$1")

  ffmpeg -y -i "$1" $FFMPEG_OPTIONS "$2" 2>&1 | \
      awk -vRS="\r" \
        '$1 ~ /^frame/ \
         { \
           gsub(/frame=/,""); \
           percent=int(($1 / '$TOTAL_FRAMES')*100); \
           print "#Converting '"'$1'"'.\\nTotal Frames: '$TOTAL_FRAMES'\\t\\tCurrent Frame:" $1 "\\n\\nPercent complete: " percent " %"; \
           print percent; \
           fflush(); \
         }' | \
      zenity --progress \
             --auto-close \
             --title="Converting..." \
             --width=500 \
             --percentage=0
}

O script do awk ficou um pouco confuso, não? É que estou misturando as variáveis dos campos do awk ($1) com as variáveis dos parâmetros da função ($1 e $2) e a variável local $TOTAL_FRAMES. Repare que a string com o script awk está entre aspas simples (‘), para que o bash não faça substituições escalares. Assim, o $1 do awk fica intacto… Quando quero usar o $1 do bash fecho a string usando aspas simples (‘), coloco o $1 (entre aspas duplas − ” − quando necesário) e reabro a string com (‘).

O script awk verifica se a linha (“record”, na nomenclatura do awk) começa com “frame”, retira a string “frame=” do caminho e coloca o valor do frame no primeiro “field” ($1). A partir daí é só fazer o cálculo e imprimir a mensagem formatada para o zenity. Atenção para o fato de que o zenity exige que as sequẽncias de escape (‘\t’ e ‘\n’) tenham duas barras (‘\\r’ e ‘\\n’). E o caracter ‘#’ parece ser o que diferencia a mensagem apresentada na janela do valor a ser usado na barra de progresso (por isso a porcentagem é impressa uma segunda vez).

A saída do awk é então repassada para o zenity.

Os problemas

Lembra-se que falei que arquivos ‘.mpg’ ou ‘.mpeg’ podem causar mais um problema além do frame_count não ser mostrado pelo ‘tcprobe’? Este é um problema sério: Quando tenho que “juntar” dois ou mais vídeos, converto-os para ‘.mpg’ e depois uso o comando ‘cat’ para juntá-los. No exemplo abaixo tenho 3 vídeos chamados ‘video1.avi’, ‘video2.avi’ e ‘video3.avi’ e quero juntá-los num ‘myvideo.mpg’:

$ for i in {1..3}; do ffmpeg -i video$i.avi -sameq $i.mpg; done
$ cat 1.mpg 2.mpg 3.mpg > myvideo.mpg

O problema é que ‘mediainfo’ e o próprio ‘ffmpeg’ acham que o vídeo resultante têm o tamanho do primeiro vídeo (video1.mpg)… O ffmpeg consegue converter o video inteiro (myvideo.mpg) para qualquer outro formato, mas já que o mediainfo me dá o tamanho errado e o zenity vai tentar fechar quando chegar a 100%, você já viu onde isso vai dar, né?

Outros problemas: O script ‘awk’, acima, não é perfeito… Ele não checa por problemas.

Quanto ao zenity, é possível adicionar a opção ‘–auto-kill’, mas isso o fará enviar um sinal SIGKILL para o processo pai (o script bash). Por padrão, os sinais SIGKILL e SIGSTOP não podem ser tratados por quaisquer aplicações. Assim, mesmo que tenhamos pegado a quantidade total de frames com ‘mediainfo’, esse valor não é garantido que esteja correto. Se ele for menor que o valor real, o script morrerá antes da hora e o ffmpeg junto com ele, corrompendo o arquivo final.

Podemos ainda usar a opção –no-close, no zenity, mas isso não impede que o usuário feche a janela.

E quanto ao Nautilus?

Não há complicação alguma em fazer scripts para o Nautilus. Cada arquivo (ou diretório) selecionado é passado na linha de comando do script. Assim, para converter o vídeo basta fazer:

# Se não tem parâmetros, não tem arquivos selecionados!
if [ $# -lt 1 ]; then
  zenity --error --text="No files selected"
  exit
fi

# Enquanto existirem arquivos nos parâmetros...
while [ ! -z "$1" ]; do
  # Se for um arquivo...
  if [ -f "$1" ]; then
    # Converte.
    TMPFILE="$(mktemp $(dirname "$1")/tmp_XXXXXXXXX.mp4)"
    ffvideo "$1" "$TMPFILE"
    if [ $? -eq 0 ]; then
      rm "$1" 
      mv "$TMPFILE" "${1%.*}.mp4";)
    fi

    # Pega o próximo parâmetro via "shift".
    shift
  fi
done

Claro que podemos dar umas incrementadas: Verificar se o arquivo é um dos permitidos (.mp4, .avi, .asf, .wmv, .mpg, .mpeg, .m4v etc); Calcular, aproximadamente, o tamanho do arquivo (de forma empirica!), se for um .mpg ou .mpeg, e verificar se é maior do que deveria ser (pro caso da concatenação acima). Só que isso é uma gambiarra e pode não funcionar muito bem.

Você pode também tentar modificar o script awk para mostrar, além do nome do arquivo, dos frames e da porcentagem, o frame rate, o bit rate, o tamanho do arquivo e a marca de tempo [do arquivo] convertido.

Mas acho que você já pegou a idéia.

Instalando o script no Nautilus

Basta copiar o script para o diretório ‘~/.gnome2/nautilus-scripts/’ e voilà!

Anúncios

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s