Когда я в очередной раз столкнулся с этой проблемой, то пошел на домашнюю страницу MPlayer, на которой нашел два скрипта для ее решения: mplayer-resume и MPlayer Tools.
mplayer-resume у меня отказался запоминать позиции в файлах и к тому же подавлял весь вывод mplayer'a, что довольно неаккуратно с его стороны, так что я сразу же отказался от него, а MPlayer Tools показался мне слишком неудобным в использовании. Поэтому я решил изобрести собственный велосипед. :)
В итоге на свет появился относительно небольшой скрипт, представленный ниже. Скрипт полностью сохраняет вывод MPlayer'a и может принимать все аргументы, которые принимает MPlayer. В том числе ему можно передавать одновременно несколько файлов для воспроизведения - каждый из них он воспроизведет с того места, на котором было остановлено воспроизведение в прошлый раз.
Краткое описание можно прочитать в комментариях, располагающихся в начале самого скрипта.
Удачи, надеюсь, скрипт окажется вам полезен и сэкономит хотя бы немного вашего времени и нервов. :)
mplayer.ext:
#!/bin/bash
#***************************************************************************
#* Copyright (C) 2008, Konishchev Dmitry *
#* http://konishchevdmitry.blogspot.com/ *
#* *
#* Project homepage: *
#* http://sourceforge.net/projects/mplayerext/ *
#* *
#* 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. *
#**************************************************************************/
# mplayer.ext - скрипт-оболочка для mplayer.
#
# Предназначен для продолжения прослушивания/просмотра аудио и видео
# файлов с той позиции, на которой просмотр/прослушивание завершился в
# прошлый раз при закрытии mplayer'a.
#
# Использование:
# Если вы хотите пользоваться возможностями скрипта, вам необходимо
# всегда, когда вы хотите проиграть аудио/видео файл, вызывать этот скрипт
# вместо mplayer'a.
#
# Как работает скрипт:
# Если завершение работы mplayer'a происходит во время просмотра фильма,
# то в файл, путь к которому задан переменной $resume_info_file, заносится
# информация о времени, на котором произошло прерывание просмотра. Время
# привязывается к имени файла (имени, а не пути!), таким образом, если файл
# будет перемещен в другую дирректорию, то скрипт все равно его "узнает".
# В следующий раз, когда пользователь запросит проигрывание этого фильма,
# скрипт просмотрит файл, заданный переменной $resume_info_file и
# продолжит воспроизведение фильма с того момента, на котором завершилось
# воспроизведение в прошлый раз.
#
# Максимальное количество файлов, информация о которых может храниться в
# $resume_info_file задается переменной $max_resume_info_length.
#
# Ограничения скрипта:
# * Скрипт не обрабатывает файлы DVD вида VTS_*_*.VOB, т. к. mplayer не
# позволяет начинать воспроизведение таких файлов с произвольного места.
# * Т. к. mplayer позволяет начинать воспроизведение фильма только с
# ключевого кадра, то, если воспроизведение фильма в прошлый раз
# прервалось не на ключевом кадре, при попытке воспроизвести фильм с
# того же места произойдет перемотка вперед до следующего ключевого
# кадра, т. е. часть фильма останется непросмотренной. В связи с этим
# скрипт производит "отматывание" на $keyint секунд назад (по умолчанию
# - 10), т. к., при кодировании большинства MPEG-4 файлов данная
# величина используется в качестве максимального расстояния между
# ключевыми кадрами. Если в ваших видеофайлах интервал между ключевыми
# кадрами больше этого значения, то измените значение переменной $keyint.
# Настройки -->
# Максимальный интервал между ключевыми кадрами
keyint=10
# Файл, в котором будет храниться информация о недосмотренных файлах
resume_info_file=~/.mplayer/resume_info
# Максимальное количесво файлов, информация о которых будет храниться в $resume_info_file
max_resume_info_length=100
# Настройки <--
mplayer_ext_echo()
{
echo "mplayer.ext: $@"
}
mplayer_ext_warning()
{
mplayer_ext_echo "$@" >&2
}
cleanup()
{
rm -f $tmp_file
}
die()
{
mplayer_ext_warning "$@"
cleanup
exit 1
}
# Возвращает идентификатор видео по имени файла
get_video_name_by_file_name()
{
local video_name=$(basename "$1")
if [[ "$video_name" == "" ]]
then
return 1
fi
# Не обрабатываем файлы DVD, т. к. в них невозможно осуществлять воспроизведение
# с произвольного места
if echo -n "$video_name" | egrep -i '^vts_[[:digit:]]+_[[:digit:]]+.vob$' > /dev/null
then
return 1
fi
echo -n "$video_name"
}
# Если $2 == 0, то файл помечается как просмотренный
set_resume_pos()
{
declare -a resume_info_array
local resume_info resume_info_time resume_info_time i
# Устанавливаем разделитель слов равным \n
local IFS=$'\n'
i=0
for resume_info in `cat "$resume_info_file" | tail --lines $((max_resume_info_length - 1))`
do
resume_info_time=`echo -n "$resume_info" | egrep '^.+:[[:digit:]]+$' | sed -r 's/^.+://' | egrep '^[[:digit:]]+$'`
resume_info_name=`echo -n "$resume_info" | sed "s/:${resume_info_time}\$//"`
# Пропускаем неверно сформированные записи
if [[ "$resume_info_time" == "" || "$resume_info_name" == "" ]]
then
mplayer_ext_warning "Bad resume info string: '$resume_info'."
continue
fi
# Если это тот файл, который мы ищем
if [[ "$resume_info_name" == "$1" ]]
then
# Пропускаем старую запись
continue
# Остальные файлы - оставляем без изменений
else
resume_info_array[$((i++))]="$resume_info"
fi
done
# Если видео не досмотрели до конца
if [[ "$2" != "0" ]]
then
mplayer_ext_echo "Writing resume time information: '$1': $2."
resume_info_array[$i]="$1:$2"
else
mplayer_ext_echo "Writing resume time information: '$1': viewed."
fi
# Заносим изменения в файл
echo "${resume_info_array[*]}" > "$resume_info_file" || die
}
# Получает строку времени, на котором было приостановлено воспроизведение файла.
# Преобразует строки вида:
# A: 308.4 V: 308.4 A-V: -0.006 ct: -0.041 7395/7395 4% 0% 5.5% 0 0
# A: 308.4 V: 308.4 A-V: 0.006 ct: 0.041 7395/7395 4% 0% 5.5% 0 0
# A: 2.0 V: 2.0 A-V: -0.006 ct: 0.007 50/ 50 6% 3% 3.9% 0
# A: 87.6 (01:27.5) of 228.0 (03:48.0) 4.4%
# V: 1.8 45/ 45 15% 3% 0.0% 0 0
# в строку вида:
# [AV]:308
# в зависимости от наличия в файле аудио/видео дорожек
get_cur_pos_info()
{
local pos_info=`cat $tmp_file | head --lines $end_line | tail --lines $((end_line - start_line + 1)) | tr '\33\15' '\n' \
| egrep '^[AV]:[[:space:]]*[[:digit:]]+\.[[:digit:]]+[[:space:]]+' \
| tail --lines 1 \
| sed -r 's/:\s+/:/g' | sed -r 's/\s+/ /g'`
if [[ $pos_info == "" ]]
then
return 1
fi
# Видео со звуком
if echo "$pos_info" | egrep -o '^A:[[:digit:]]+\.[[:digit:]]+ V:[[:digit:]]+\.[[:digit:]]' > /dev/null
then
pos_info=`echo -n "$pos_info" | awk '{ print $2 }' | awk -F '.' '{ print $1 }'`
# Видео без звука
elif echo "$pos_info" | egrep -o '^V:[[:digit:]]+\.[[:digit:]]' > /dev/null
then
pos_info=`echo -n "$pos_info" | awk -F '.' '{ print $1 }'`
# Аудио без видео
elif echo "$pos_info" | egrep -o '^A:[[:digit:]]+\.[[:digit:]]' > /dev/null
then
pos_info=`echo -n "$pos_info" | awk -F '.' '{ print $1 }'`
# Логическая ошибка
else
die "Logical error! :)"
fi
if [[ $pos_info == "" ]]
then
die "Logical error! :)"
fi
echo -n "$pos_info"
}
get_resume_pos()
{
local resume_info resume_info_time resume_info_time
# Устанавливаем разделитель слов равным \n
local IFS=$'\n'
for resume_info in $(< "$resume_info_file")
do
resume_info_time=`echo "$resume_info" | egrep '^.+:[[:digit:]]+$' | sed -r 's/^.+://' | egrep '^[[:digit:]]+$'`
resume_info_name=`echo "$resume_info" | sed "s/:${resume_info_time}\$//"`
# Пропускаем неверно сформированные записи
if [[ "$resume_info_time" == "" || "$resume_info_name" == "" ]]
then
# Предупреждение не выводим, т. к. оно будет выведено при записи в файл.
continue
fi
# Если это тот файл, который мы ищем
if [[ "$resume_info_name" == "$1" ]]
then
echo $resume_info_time
return 0
fi
done
return 1
}
if ! tmp_file=`mktemp`
then
die "Can't create temp file."
fi
if ! which mplayer > /dev/null
then
die "Error! Mplayer not installed."
fi
if [[ ! -e "$resume_info_file" ]]
then
touch "$resume_info_file" || die
fi
# Изменяем агрументы, переданные mplayer'у так, чтобы выбранные видеофайлы
# воспроизводились с того момента, где в прошлый раз было прервано
# воспроизведение.
i=0
for option
do
options[$((i++))]="$option"
if [[ ${option:0:1} != '-' ]]
then
# Если значение параметра похоже на имя файла, то считаем, что
# требуется проиграть этот файл. Если это просто значение опции, то
# скрипт все равно сработает нормально, разве что добавится лишний
# ключ -ss в случае когда значение параметра будет совпадать с
# именем какого-либо файла в системе.
if [[ -e "$option" ]]
then
if video_name=`get_video_name_by_file_name "$option"`
then
# Если воспроизведение этого видео файла было прервано ранее
if video_resume_pos=`get_resume_pos "$video_name"`
then
options[$((i++))]='-ss'
options[$((i++))]="$video_resume_pos"
fi
fi
fi
fi
done
# Запускаем mplayer с измененными параметрами командной строки
mplayer_ext_echo "Starting mplayer: mplayer ${options[@]}"
mplayer "${options[@]}" | tee "$tmp_file"
# Получаем все файлы, которые проигрывал mplayer -->
files_in_output="`egrep --line-number 'Playing[[:space:]]+.+\.' $tmp_file`"
for line in `seq \`echo "$files_in_output" | wc --lines\``
do
file_in_output=`echo "$files_in_output" | head --lines $line | tail --lines 1`
files_lines[$((line-1))]=`echo "$file_in_output" | awk -F ':' '{ print $1 }'`
files_paths[$((line-1))]=`echo "$file_in_output" | sed -r 's/^[[:digit:]]+:Playing[[:space:]]+//' | sed 's/\.$//g'`
done
# Получаем все файлы, которые проигрывал mplayer <--
# Получаем всю необходимую информацию о каждом проигранном файле -->
for num in `seq 1 ${#files_lines[*]}`
do
i=$((num-1))
start_line=${files_lines[$i]}
# Генерируем имя видео по имени файла
if ! video_name=`get_video_name_by_file_name "${files_paths[$i]}"`
then
mplayer_ext_echo "Skiping file '${files_paths[$i]}'"
continue
fi
# Если файл последний
if [[ $num -eq ${#files_lines[*]} ]]
then
end_line=$((`cat $tmp_file | wc --lines` + 1))
# Получаем строку со временем, на котором остановилось воспроизведение
if ! video_resume_string=`get_cur_pos_info`
then
# Получить строку не удалось - это может произойти по разным причинам,
# например, если не удалось открыть файл.
# Т. к. файл не проигрывался, то не запоминаем его позицию.
mplayer_ext_echo "Skiping file '${files_paths[$i]}'"
continue
fi
# Файл проигрался до конца
if [[ `cat $tmp_file | tail --lines 1` == 'Exiting... (End of file)' ]]
then
set_resume_pos "$video_name" 0
# Проигрывание файла было прервано
else
video_resume_time=`echo -n "$video_resume_string" | awk -F ':' '{ print $2 }'`
# Видео (для аудио отматывать не надо)
if [[ `echo -n "$video_resume_string" | awk -F ':' '{ print $1 }'` == 'V' ]]
then
# "Отматываем" видео назад (приблизительно) до предыдущего ключевого кадра
if [[ $((video_resume_time - keyint)) -lt 0 ]]
then
video_resume_time=0
else
video_resume_time=$((video_resume_time - keyint))
fi
fi
set_resume_pos "$video_name" "$video_resume_time"
fi
# Файл не последний
else
end_line=${files_lines[$((i+1))]}
# Получаем строку со временем, на котором остановилось воспроизведение
if ! video_resume_string=`get_cur_pos_info`
then
# Получить строку не удалось - это может произойти по разным причинам,
# например, если не удалось открыть файл.
# Т. к. файл не проигрывался, то не запоминаем его позицию.
mplayer_ext_echo "Skiping file '${files_names[$i]}'"
continue
fi
set_resume_pos "$video_name" 0
fi
done
# Получаем всю необходимую информацию о каждом проигранном файле <--
cleanupТакже скачать скрипт можно здесь.
21 комментарий:
А нет ли решения для подобного всего, но для VLC?
весьма полезная вещь.
обязательно опробую.
ansate, VLC "менее консольный". С ним без правки исходного кода такое вряд ли получится. Мой скрипт читает вывод MPlayer, в котором содержится текущая позиция проигрываемого файла, а потом при запуске MPlayer'a меняет аргументы, переданные пользователем MPlayer'у, так, чтобы он начал воспроизведение с того момента, на котором в прошлый раз остановился. VLC же ничего не выводит на консоль и не принимает никаких аргументов, которыми можно было бы менять начальную точку воспроизведения.
Супер, сам порывался написать такое пару раз. Обломался после парсера вывода.
Причем, код внятный и гладкий.
Спасибо за скрипт :))
Спасибо за отзывы. Очень рад, что результаты моих трудов оказываются востребованными.
Большое спасибо за скрипт!
Против деб-пакета в локальном репозитории не будет возражений? Онлайнового репозитория у меня пока нет.
И большое спасибо!
andfom, да нет, конечно, какие могут быть возражения. :)
Спасибо, помогло!
Большое спасибо! Но, однако, стоило бы выложить скриптик отдельным файлом - у меня после копипаста он упорно не хотел работать, спасло perl -pi.bk -e 's/\r//' mplayer.ext
stanislav, ок, спасибо, добавил ссылку на скачивание.
smplayer все запоминает.
> smplayer все запоминает.
Да, но я предпочитаю оригинальный MPlayer. :)
Никак не могу добиться работы скрипта.При выходе пишет:
Выходим... (Выход)
mplayer.ext: Skiping file ''
Естественно ничего не запоминает.
Анонимный, мне довольно сложно судить о том, что у вас происходит, если вы не предоставите какие-либо данные. Какие аргументы вы передаете скрипту? Также я бы посмотрел на вывод "bash -x script ...".
Вот здесь http://dpaste.com/254045/ запуск и остановка.
Здесь http://dpaste.com/254050/ вывод Вами указанной команды
Вторая паста видимо неверная,вот:
http://dpaste.com/254055/
Анонимный, ну ничего себе. :) А что это у вас за дистрибутив такой, что mplayer в нем говорит по-русски? Первый раз такое вижу. Мой скрипт грепает вывод mplayer'а по английским фразам, который тот выдает в stdout. Попробуйте запустить его как "LC_ALL=C script ..." - в таком случае mplayer должен переключиться в режим вывода всех сообщений на английском языке.
Дистрибутив Gentoo. Вообщем разобрался.Система собрана с LINGUAS="ru en". Mplayer при компиляции берёт первое значение переменной,остальное его не интересует.Запуск с подстановкой значений переменных ничего не даёт.Пересобрал mplayer с LINGUAS="en" и только тогда он заговорил по английски.Скрипт заработал.
Большое спасибо за прекрасный скрипт и помощь!
Спасибо за скрипт))) mplayer2 в арче, кстати, тоже по-русски разговаривает, поставил первый, все заработало нормально =)) А то smplayer юзать под опенбоксом из за функции запоминания воспроизведения мне совсем не нравилось....
Отправить комментарий