воскресенье, 2 сентября 2007 г.

Скрипт для получения серии скриншотов к видеофайлу

Недавно я писал, как получить серию скриншотов для видеофайла при помощи программы QFrameCatcher. Там же я жаловался на то, что с ней совершенно невозможно работать из консоли. Т. е., к примеру, если мне необходимо получить кучу скриншотов для большого количества видеофайлов, то придется все это делать вручную при помощи мыши.

Но вчера один мой друг разместил у себя в блоге скрипт, который выполняет ту же задачу при помощи mplayer'а и ImageMagick.

Идея, с помощью которой реализуется данная задача, мне очень понравилась, но реализована она в этом скрипте, на мой взгляд, довольно криво. Вот недостатки, которые я заметил, натравив данный скрипт на несколько файлов:
  • Некорректно работает со многими wmv и HDTVRip видеофайлами (создает серию скриншотов из одного и того же кадра).
  • Иногда захватывает на 2 кадра больше, чем нужно.
  • Не умеет обрабатывать сразу несколько файлов.
  • Выводит кадры так, что в одной строке всегда находятся 4 кадра, и если задать вывод нечетного числа кадров, то в конечном изображении появляются дыры.
  • Нет возможности создавать скриншоты размером с кадр в фильме (на некоторых трекерах предъявляется такое требование).
Поэтому я решил полностью переписать скрипт так, чтобы он удовлетворял всем моим требованиям. При написании скрипта я старался добавлять по-больше комментариев, чтобы помочь людям, которые плохо разбираются в программировании на bash, но хотят что-нибудь исправить в моем скрипте под себя.

Синтаксис запуска скрипта таков:
framegrabber [-n stills] [-s size] files...
где n - количество захватываемых кадров (по умолчанию 20), s - ширина каждого кадра в пикселях (по умолчанию равна ширине кадра в фильме). При формировании конечного изображения количество кадров в строке (2, 3 или 4) выбирается в зависимости от параметра s.

После запуска framegrabber -n8 -s 300 video_file вы получите примерно такой результат:


А вот собственно и сам скрипт:
#!/bin/bash
#***************************************************************************
#*   Copyright (C) 2007, Konishchev Dmitry                                 *
#*   http://konishchevdmitry.blogspot.com/                                 *
#*                                                                         *
#*   Special thanks to Sergey Volkhin                                      *
#*   http://damnsmallblog.blogspot.com/                                    *
#*                                                                         *
#*   This program is free software; you can redistribute it and/or modify  *
#*   it under the terms of the GNU General Public License as published by  *
#*   the Free Software Foundation; either version 3 of the License, or     *
#*   (at your option) any later version.                                   *
#*                                                                         *
#*   This program is distributed in the hope that it will be useful,       *
#*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
#*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
#*   GNU General Public License for more details.                          *
#**************************************************************************/

usage=" framegrabber -h for help"
usage_describe="\n
Usage : framegrabber [-n stills] [-s size] files...\n
\n
Framegrabber is designed to extract stills from a video that is seekable and supported by Mplayer. Framegrabber then combine them in a mosaic image allowing preview of the movie. Number of frames to extract and size of the frames can be modified via options.\n
\n
Framegrabber depends on Mplayer and Imagemagick\n
\n
Options:\n
-h : print this help message\n
-n : number of stills to extract (default : 20)\n
-s : size of each stills (default: same as video frame size)\n
\n
framegrabber -n 20 -s 100 my_video will create a mosaic of 20 stills of 100 pixels each."

# Выводит страницу помощи
print_help()
{
echo -e $usage_describe
}

# Удаляет временные файлы
cleanup()
{
rm $temp_dir/*.jpg 2>/dev/null
rmdir $temp_dir
}

# Получаем параметры, заданные пользователем -->
while getopts ":n:s:h" option
do
case $option in
n ) n=$OPTARG;;
s ) s=$OPTARG;;
h ) print_help; exit 0;;
\? ) echo "Unknown option"; echo $usage; exit 1;;
* ) echo "Unknown option"; echo $usage; exit 1;;
esac
done
# Получаем параметры, заданные пользователем <--

# Значения по умолчанию -->
((!n)) && n=20
((!s)) && s=0
# Значения по умолчанию <--

shift $(($OPTIND - 1))

# Проверяем, передан ли хотя бы один файл -->
if [ "$1" == "" ]
then
echo $usage
exit 1
fi
# Проверяем, передан ли хотя бы один файл <--

# Генерируем скриншоты для каждого переданного файла -->
while [ "$1" != "" ]
do
temp_dir=/tmp/framegrabber_tmp_$$
old_dir=`pwd`
file_path="$1"
frames_num=$n
frame_size=$s
file_name=${file_path##*/}

mkdir $temp_dir || exit 1
cd "$temp_dir" || exit 1

echo "Processing file $file_path..."

# Получаем абсолютный путь -->
echo "$file_path" | grep '^/' > /dev/null
if [ $? -eq 1 ]
then
ab_file_path="$old_dir"/"$file_path"
else
ab_file_path="$file_path"
fi
# Получаем абсолютный путь <--

# Получаем продолжительность видеофайла -->
seconds=`mplayer -ao null -vo null -frames 1 -identify "$ab_file_path" 2>/dev/null | \
grep ID_LENGTH= | cut -d "=" -f 2`
if [ "$seconds" == "" ]
then
echo "mplayer error! May be \"$file_path\" is not a video file?"
cleanup
cd "$old_dir"
shift
continue
fi
seconds=`awk -v var="$seconds" 'BEGIN { rounded = sprintf("%.0f", var); print rounded }'`
# Получаем продолжительность видеофайла <--

# Количество секунд, через которое мы будем перепрыгивать для получения очередного скриншота -->
step=`expr $frames_num + 1`
step=`expr $seconds / $step`
step=`awk -v var=$step 'BEGIN { rounded = sprintf("%.0f", var); print rounded }'`
# Количество секунд, через которое мы будем перепрыгивать для получения очередного скриншота <--

# Генерируем необходимое количество скриншотов -->
is_error=0
cur_sec=$step
max_image_file_name=`expr 100 + $frames_num - 1`
for image_file_name in `seq 100 $max_image_file_name`
do
mplayer -ao null -ss $cur_sec -vo jpeg -frames 30 "$ab_file_path" > /dev/null 2>&1
mv 00000007.jpg $image_file_name.jpg 2>/dev/null
if [ $? -ne 0 ]
then
is_error=1
break
fi
rm 000000??.jpg
cur_sec=`expr $cur_sec + $step`
done

if [ $is_error -ne 0 ]
then
echo "mplayer error! May be \"$file_path\" is not a video file?"
cleanup
cd "$old_dir"
shift
continue
fi
# Генерируем необходимое количество скриншотов <--

cd "$old_dir"

# Подбираем оптимальное количество кадров в строке -->
if [ `expr $frames_num % 4` -eq 0 ]
then
hor_frames_num=4
else
if [ `expr $frames_num % 3` -eq 0 ]
then
hor_frames_num=3
else
if [ `expr $frames_num % 2` -eq 0 ]
then
hor_frames_num=2
else
hor_frames_num=4
fi
fi
fi
# Подбираем оптимальное количество кадров в строке <--

# Упаковываем все захваченные кадры в один файл -->
if [ $frame_size -ne 0 ]
then
resize_option="-resize $frame_size"
else
resize_option=""
fi
montage $resize_option -geometry +2+2 -tile $hor_frames_num -quality 100 $temp_dir/*.jpg "$file_name".jpg
# Упаковываем все захваченные кадры в один файл <--

cleanup

# Прыгаем на следующий в списке файл
shift
done
# Генерируем скриншоты для каждого переданного файла <--

echo "Finished"

10 комментариев:

Андрей Фомин комментирует...

Приветствую. За скрипт спасибо. Есть одна проблема - скриншоты получаются плохого качества, и плохо передают качество исходного видео. Подозреваю, что проблема в битрейте кадров.

Конищев Дмитрий комментирует...

Хм... А что вы подразумеваете под плохим качеством? Если размер каждого скриншота, то его вы можете задать опцией -s. В остальном же полученные скриншоты полностью соответствуют кадрам видео. Почему вы связываете качество скриншотов с битрейтом видео, я, честно говоря, не понял, но на всякий случай нашел у себя видео с очень низким битрейтом - скриншоты получились в точности такие же как и кадры.

Андрей Фомин комментирует...
Этот комментарий был удален автором.
Андрей Фомин комментирует...

В любом видео есть кадры хорошие, есть не очень. Во-первых, брать нужно только i-кадры, но никак не b-кадры и не p-кадры. Во-вторых - было бы неплохо анализировать их качество (идея в том, чтобы брать скриншоты в статических сценах). Потому что в динамических сценах скриншоты не показательны.

Анонимный комментирует...

Хм, скрипт как-то с ходу не заработал. Mplayer выдает значение ID_LENGTH=5304.01, оно заносится в переменную, но скрипт почему-то говорит "mplayer error! May be "Август.avi" is not a video file?" и всё... Хоть ты тресни :(

Анонимный комментирует...

Извиняюсь, был не прав. Собрал mplayer и imagemagick без поддержки jpeg :( Как-то проморгал этот момент :(

Алексей комментирует...

Спасибо, полезный скрипт

Анонимный комментирует...

А какой софт нужно поставить для того что бы он работал нормально?
Например у меня не хватает некой программы montage. Нельзяли сразу указать какие пакеты нужно установить?

Dmitry Konishchev комментирует...

Нужны пакеты imagemagick и mplayer.

Владимир Шамов комментирует...

Скрипт очень поможет, но...
Как убрать из имени получаемого файла расширение видеофайла?
Заранее спасибо.