Nesta postagem, faço referência a um site de vídeo adulto (pornô). Somos todos adultos, por favor, não se ofenda.
Exibindo um imagem de visualização antes de carregar um vídeo é uma prática comum na mídia da Web atualmente; muitas vezes, essa visualização da imagem é o primeiro quadro do vídeo ou um quadro importante do vídeo. Se o senhor visitou um site adulto recentemente (ahem), deve ter notado que, além de obter uma imagem de visualização, ao passar o mouse sobre essa imagem, é exibido um pequeno vídeo de amostra com trechos em diferentes pontos do filme. Naturalmente, fiquei interessado em saber como tudo isso funcionava e concluí o seguinte:
- Eles criam a imagem de visualização usando uma ferramenta como o ffmpeg
- Ao passar o mouse sobre a imagem, o vídeo de visualização é carregado de forma assíncrona em segundo plano
- Depois que a imagem de visualização for carregada, o vídeo será exibido sobre a imagem e reproduzido automaticamente
- Quando o mouse sai, o vídeo fica oculto (caso o usuário passe o mouse sobre a imagem novamente)
Alcançando o <image>
e <video>
A troca de elementos é simples, mas eu queria saber como o PornHub, a maior rede de sites adultos, gerava seus vídeos de visualização. A ideia por trás do vídeo de visualização é simples, é claro, mas eu queria aprender mais sobre ffmpeg e bash scripting. Aqui está a prova:
Visualização de vídeo gerada a partir de um download local de Kung Fury.
Vamos desconstruir meu código para saber como esse vídeo de visualização (muito básico) pode ser criado!
Executando o script
Nosso roteiro, preview.sh
, será chamado da seguinte forma:
# ./preview.sh {input.format} {output.format} $ ./preview.sh kung-fury.mp4 kung-fury-preview.mp4
Em um mundo perfeito, o script aceitaria muito mais argumentos, como o tamanho desejado do vídeo ou qualquer uma das configurações que assumiremos dentro do script, mas eu queria me concentrar em fazer a essência do script funcionar; adicionar argumentos de linha de comando seria um exercício de rotina quando todo o resto estivesse funcionando.
O roteiro: preview.sh
O script começa com uma validação muito básica:
sourcefile=$1 destfile=$2 # Overly simple validation if [ ! -e "$sourcefile" ]; then echo 'Please provide an existing input file.' exit fi if [ "$destfile" == "" ]; then echo 'Please provide an output preview file name.' exit fi
Em seguida, temos uma série de variáveis relacionadas à duração do vídeo, aos pontos desejados no vídeo para cortar as visualizações, ao tamanho do vídeo etc:
# Detect destination file extension extension=${destfile#*.} # Get video length in seconds length=$(ffprobe $sourcefile -show_format 2>&1 | sed -n 's/duration=//p' | awk '{print int($0)}') # Start 20 seconds into the video to avoid opening credits (arbitrary) starttimeseconds=20 # Mini-snippets will be 2 seconds in length snippetlengthinseconds=2 # We'll aim for 5 snippets spread throughout the video desiredsnippets=5 # Ensure the video is long enough to even bother previewing minlength=$(($snippetlengthinseconds*$desiredsnippets)) # Video dimensions (these could probably be command line arguments) dimensions=640:-1 # Temporary directory and text file where we'll store snippets # These will be cleaned up and removed when the preview image is generated tempdir=snippets listfile=list.txt
Se o vídeo for muito curto para gerar uma visualização, basta cancelar:
# Display and check video length echo 'Video length: ' $length if [ "$length" -lt "$minlength" ] then echo 'Video is too short. Exiting.' exit fi
Em seguida, geramos uma série de vídeos “snippet” em momentos calculados dentro do vídeo, salvando esses vídeos snippet em um diretório temporário:
# Loop and generate video snippets mkdir $tempdir interval=$(($length/$desiredsnippets-$starttimeseconds)) for i in $(seq 1 $desiredsnippets) do # Format the second marks into hh:mm:ss format start=$(($(($i*$interval))+$starttimeseconds)) formattedstart=$(printf "%02d:%02d:%02d\n" $(($start/3600)) $(($start%3600/60)) $(($start%60))) echo 'Generating preview part ' $i $formattedstart # Generate the snippet at calculated time ffmpeg -i $sourcefile -vf scale=$dimensions -preset fast -qmin 1 -qmax 1 -ss $formattedstart -t $snippetlengthinseconds $tempdir/$i.$extension &>/dev/null done
Observação: copiar e cortar vídeos com o ffmpeg é difícil se o senhor não for um especialista em codecs e mídia, o que certamente não sou. Essas configurações básicas permitem que o trabalho seja feito, mas talvez não da melhor maneira possível. Comente abaixo se o senhor conhece uma maneira de melhorar o desempenho ou a qualidade do vídeo.
A última etapa é concatenar os vídeos de snippet no vídeo de visualização final:
# Concat videos echo 'Generating final preview file' # Generate a text file with one snippet video location per line # (https://trac.ffmpeg.org/wiki/Concatenate) for f in $tempdir/*; do echo "file '$f'" >> $listfile; done # Concatenate the files based on the generated list ffmpeg -f concat -safe 0 -i $listfile -c copy $destfile &>/dev/null echo 'Done! Check ' $destfile '!' # Cleanup rm -rf $tempdir $listfile
A concatenação de vídeo é fácil nesse caso porque estamos concatenando trechos do mesmo vídeo original. A concatenação seria muito mais difícil se estivéssemos usando vídeos de diferentes taxas de quadros, tamanhos, etc.
O roteiro final em toda a sua glória:
sourcefile=$1 destfile=$2 # Overly simple validation if [ ! -e "$sourcefile" ]; then echo 'Please provide an existing input file.' exit fi if [ "$destfile" == "" ]; then echo 'Please provide an output preview file name.' exit fi # Detect destination file extension extension=${destfile#*.} # Get video length in seconds length=$(ffprobe $sourcefile -show_format 2>&1 | sed -n 's/duration=//p' | awk '{print int($0)}') # Start 20 seconds into the video to avoid opening credits (arbitrary) starttimeseconds=20 # Mini-snippets will be 2 seconds in length snippetlengthinseconds=2 # We'll aim for 5 snippets spread throughout the video desiredsnippets=5 # Ensure the video is long enough to even bother previewing minlength=$(($snippetlengthinseconds*$desiredsnippets)) # Video dimensions (these could probably be command line arguments) dimensions=640:-1 # Temporary directory and text file where we'll store snippets # These will be cleaned up and removed when the preview image is generated tempdir=snippets listfile=list.txt # Display and check video length echo 'Video length: ' $length if [ "$length" -lt "$minlength" ] then echo 'Video is too short. Exiting.' exit fi # Loop and generate video snippets mkdir $tempdir interval=$(($length/$desiredsnippets-$starttimeseconds)) for i in $(seq 1 $desiredsnippets) do # Format the second marks into hh:mm:ss format start=$(($(($i*$interval))+$starttimeseconds)) formattedstart=$(printf "%02d:%02d:%02d\n" $(($start/3600)) $(($start%3600/60)) $(($start%60))) echo 'Generating preview part ' $i $formattedstart # Generate the snippet at calculated time ffmpeg -i $sourcefile -vf scale=$dimensions -preset fast -qmin 1 -qmax 1 -ss $formattedstart -t $snippetlengthinseconds $tempdir/$i.$extension &>/dev/null done # Concat videos echo 'Generating final preview file' # Generate a text file with one snippet video location per line # (https://trac.ffmpeg.org/wiki/Concatenate) for f in $tempdir/*; do echo "file '$f'" >> $listfile; done # Concatenate the files based on the generated list ffmpeg -f concat -safe 0 -i $listfile -c copy $destfile &>/dev/null echo 'Done! Check ' $destfile '!' # Cleanup rm -rf $tempdir $listfile
Melhorias
O senhor pode apostar que o PornHub e todos os outros sites de vídeo que empregam essa técnica têm um script muito mais seguro e avançado do que esse script. Algumas maneiras pelas quais esse script poderia ser aprimorado:
Dedicar algum tempo para montar esse script foi estimulante; uma mistura de manipulação de mídia, script bash e ver o produto final de algo que é usado em dezenas de sites de vídeo é um verdadeiro sentimento de realização. Informe-me como o script acima pode ser melhorado!