Вы любите смотреть фильмы? Я — да: «Теория большого взрыва», «The IT Crowd», разное аниме… Все это очень затягивает.
Для просмотра всего этого добра я пользуюсь консольной версией самого всеядного медиаплеера mplayer. (Давайте воздержимся от холивара по поводу красноглазия и GUI vs консоль) Но вот незадача, для каждой новой серии приходится заново набирать длинную команду вроде такой:
Ясное дело, что я не набираю всю эту простыню с нуля, а просто стираю бэкспейсом до номера серии, пишу новый номер и табом дополняю до конца имени файла. Но это ведь долго и неудобно, можно промахнуться и стереть лишнего.
А еще часто смотрится несколько сериалов параллельно, по мере выхода новых серий. И помнить, на какой серии я остановился в прошлый раз, и с какими параметрами и ключами запускал mplayer, становится затруднительно. И поэтому я решил написать себе на шелле простую запускалку плеера, которая будет запоминать параметры, номер последнего просмотренного эпизода и уметь подставлять номер следующего в команду запуска.
Для начала определимся, какой интерфейс должен быть у этой обертки.
Номера сериалов практически всегда двузначные, поэтому я не стал заморачиваться с поддержкой однозначных или трехзначных чисел.
Вот так будет выглядеть первоначальная настройка, которую нужно выполнить только один раз:
А теперь смотрим:
А если у нас имена эпизодов различаются не только номером серии? Тогда используем подстановку shell:
Выставим дефолтные значения, это просто:
Где будем хранить данные о состоянии просмотра? В первую очередь приходит мысль записать это прямо в папку, в которой он лежит, но такой вариант не подойдет, если вы смотрите сериал с компакт-диска, который, как известно, read-only. Также защищенными от записи могут быть сетевые папки (nfs/samba) или просто неправильно настроенные права у торрентокачалки.
Поэтому хранить будем в домашней папке, но идентифицировать сериал будем так же по пути в файловой системе, где эпизоды лежат. Для удобства лучше еще взять от него хэш, чтобы не иметь дела с экранированием всяких спецсимволов, которые могут там встретиться.
Создадим директорию, в которой будем хранить все наши состояния сериалов, если её еще нет:
Определим имя файла, в которое будем записывать:
Вот где-то тут меня посетила шальная мысль, что можно использовать полновесную реляционную базу данных вроде sqlite или еще того тяжелее, mysql, но эту мысль я вовремя отогнал, иначе бы вышел большой монстр вместо простой обертки над мплеером.
Теперь о сериализации: как именно хранить данные? Парсить свежепридуманные форматы на языке bash, который предназначается совершенно для других целей, мне совсем не улыбалось, поэтому я просто решил что там будут переменные окружения в таком же sh-формате.
Давайте загрузим файл, если он уже там есть:
Так, а какие действия мы можем выполнить с сериалом? Я остановился на таких:
Приступим к реализации отдельных действий.
Первым делом стоит проверить, настроили ли мы просмотр этого сериала или нет. Так как это действие выполняется почти в каждой команде, я вынес его в функцию, которая смотрит наличие установленной при загрузке параметров переменной, и в случае отсутствия выводит инструкцию, как же это дело настраивается.
Теперь, собственно, запуск, также отдельной функцией:
Сначала заменим вопросы в названии на текущий номер эпизода:
С подстановкой у меня возникли проблемы: оказывается, в баше это не так-то просто… Перепробовав много вариантов разной степени извращенности, я остановился на таком, хотя если честно, так и не понял как он работает, но побочный эффект его в том, что нужно экранировать пробелы при задании маски:
В комментариях можете предложить варианты получше.
Проверим, что файл с полученным названием существует, а если нет, нужно вывалить сообщение и выйти.
Ах да, вот еще одна вспомогательная функция: выводит сообщение и выходит
Вернемся к реализации функции запуска launch. Сохраним номер последнего проигранного эпизода (а заодно и дату запуска) в файл настроек:
Выведем сообщение о текущем эпизоде:
И наконец запустим наш плеер с параметрами, файлом и дополнительными аргументами, которые может передать пользователь после указания эпизода
Отлично, функция launch готова! Осталось совсем малость: заполнить конструкцию case. Самая простая команда — same:
Для просмотра следующего эпизода нужно инкрементировать переменную, но при этом сохранить отбивку нулями сначала. Для этого подошел awk:
Вот так можно запустить с конкретным эпизодом по номеру:
А теперь нечто совершенно другое — установка переменных:
Наконец, вывод текущего состояния:
Вот здесь вы можете увидеть скрипт в готовом, но очень плохо документированном варианте, которым я активно пользуюсь уже давно bitbucket.org/tsx/env/src/tip/bin/serial
Для просмотра всего этого добра я пользуюсь консольной версией самого всеядного медиаплеера mplayer. (Давайте воздержимся от холивара по поводу красноглазия и GUI vs консоль) Но вот незадача, для каждой новой серии приходится заново набирать длинную команду вроде такой:
$ mplayer -ass -subcp cp1251 имя_сериала_01_еще_какая_то_чушь.mkv
Ясное дело, что я не набираю всю эту простыню с нуля, а просто стираю бэкспейсом до номера серии, пишу новый номер и табом дополняю до конца имени файла. Но это ведь долго и неудобно, можно промахнуться и стереть лишнего.
А еще часто смотрится несколько сериалов параллельно, по мере выхода новых серий. И помнить, на какой серии я остановился в прошлый раз, и с какими параметрами и ключами запускал mplayer, становится затруднительно. И поэтому я решил написать себе на шелле простую запускалку плеера, которая будет запоминать параметры, номер последнего просмотренного эпизода и уметь подставлять номер следующего в команду запуска.
Интерфейс
Для начала определимся, какой интерфейс должен быть у этой обертки.
Номера сериалов практически всегда двузначные, поэтому я не стал заморачиваться с поддержкой однозначных или трехзначных чисел.
Вот так будет выглядеть первоначальная настройка, которую нужно выполнить только один раз:
$ cd ~/имя_сериала # где и что мы хотим смотреть $ ls # смотрим, как называются видео-файлы serial_name_01_bla_bla.avi serial_name_01_bla_bla.srt serial_name_02_bla_bla.avi serial_name_02_bla_bla.srt ... $ serial set mask "serial_name_??_bla_bla.avi" # указываем параметр name - имя видеофайлов с замененным номером серии на "??" # serial set options -subcp cp1251 # опционально указываем параметр options - ключи, с которыми запускать mplayer
А теперь смотрим:
$ serial next # Начнем с начала Playing episode 01... ... $ serial next # Понравилось, давайте следующий Playing episode 02... ... $ serial same # Увидел смешной момент, хочу пересмотреть заново этот же эпизод Playing episode 02... ... $ serial episode 14 # Перепрыгиваем к конкретному эпизоду Playing episode 14... ... $ while true; do; serial n; sleep 1; done; # нон-стоп
А если у нас имена эпизодов различаются не только номером серии? Тогда используем подстановку shell:
$ ls # смотрим, как называются видео-файлы serial_name_01_qwerty.mkv serial_name_02_asdfgh.mkv $ serial set mask "serial_name_??_*.avi" # указываем параметр name c заменой подстановки на звездочку $ serial set glob yes # выполнять подстановку
Пишем
Выставим дефолтные значения, это просто:
player=mplayer options="" episode=00
Где будем хранить данные о состоянии просмотра? В первую очередь приходит мысль записать это прямо в папку, в которой он лежит, но такой вариант не подойдет, если вы смотрите сериал с компакт-диска, который, как известно, read-only. Также защищенными от записи могут быть сетевые папки (nfs/samba) или просто неправильно настроенные права у торрентокачалки.
Поэтому хранить будем в домашней папке, но идентифицировать сериал будем так же по пути в файловой системе, где эпизоды лежат. Для удобства лучше еще взять от него хэш, чтобы не иметь дела с экранированием всяких спецсимволов, которые могут там встретиться.
pwdhash=`pwd|md5sum|awk '{print $1}'`
Создадим директорию, в которой будем хранить все наши состояния сериалов, если её еще нет:
test ! -d ~/.serial && mkdir ~/.serial
Определим имя файла, в которое будем записывать:
savefile=~/.serial/$pwdhash
Вот где-то тут меня посетила шальная мысль, что можно использовать полновесную реляционную базу данных вроде sqlite или еще того тяжелее, mysql, но эту мысль я вовремя отогнал, иначе бы вышел большой монстр вместо простой обертки над мплеером.
Теперь о сериализации: как именно хранить данные? Парсить свежепридуманные форматы на языке bash, который предназначается совершенно для других целей, мне совсем не улыбалось, поэтому я просто решил что там будут переменные окружения в таком же sh-формате.
Давайте загрузим файл, если он уже там есть:
if [ -f $savefile ] then ready="true" . $savefile fi
Так, а какие действия мы можем выполнить с сериалом? Я остановился на таких:
case $1 in # Запускает следующий эпизод n|next) # ... ;; # Запускает только что просмотренный эпизод снова s|same) # ... ;; # Запускает эпизод по его номеру e|ep|episode) # ... ;; # Устанавливает параметры, с которыми просматривать сериал set) # ... ;; # Просмотр текущего состояния status|show) # ... ;; # И краткая справка, если я что-то вдруг забуду. *) echo Unknown command. echo Commands: echo next - plays episode next to what you have played before echo same - plays this episode again echo show - shows current state echo episode NN - plays episode NN echo set var_name value - sets the variable ;; esac
Приступим к реализации отдельных действий.
Первым делом стоит проверить, настроили ли мы просмотр этого сериала или нет. Так как это действие выполняется почти в каждой команде, я вынес его в функцию, которая смотрит наличие установленной при загрузке параметров переменной, и в случае отсутствия выводит инструкцию, как же это дело настраивается.
function check_ready { if [ -z "$ready" ] then echo This directory is not known to have serials. echo Use the following command to setup: echo "$0 set mask \"Movie_name_episode_??_smth.avi\"" exit 1 fi }
Теперь, собственно, запуск, также отдельной функцией:
function launch { # ... }
Сначала заменим вопросы в названии на текущий номер эпизода:
movie="`echo \"$mask\" | sed \"s/??/$episode/g\"`"
С подстановкой у меня возникли проблемы: оказывается, в баше это не так-то просто… Перепробовав много вариантов разной степени извращенности, я остановился на таком, хотя если честно, так и не понял как он работает, но побочный эффект его в том, что нужно экранировать пробелы при задании маски:
if [ "$glob" == "yes" ] then movie="$(eval "echo $movie")" fi
В комментариях можете предложить варианты получше.
Проверим, что файл с полученным названием существует, а если нет, нужно вывалить сообщение и выйти.
test ! -f "$movie" && die Episode $episode not found
Ах да, вот еще одна вспомогательная функция: выводит сообщение и выходит
function die { echo $@ exit 1 }
Вернемся к реализации функции запуска launch. Сохраним номер последнего проигранного эпизода (а заодно и дату запуска) в файл настроек:
echo episode=$episode "#" at `date` >> $savefile
Выведем сообщение о текущем эпизоде:
echo Playing episode $episode...
И наконец запустим наш плеер с параметрами, файлом и дополнительными аргументами, которые может передать пользователь после указания эпизода
$player $options "$movie" "$@"
Отлично, функция launch готова! Осталось совсем малость: заполнить конструкцию case. Самая простая команда — same:
s|same) check_ready shift launch "$@" ;;
Для просмотра следующего эпизода нужно инкрементировать переменную, но при этом сохранить отбивку нулями сначала. Для этого подошел awk:
n|next) check_ready episode=`echo $episode |awk '{printf "%02d",$1+1}'` shift launch "$@" ;;
Вот так можно запустить с конкретным эпизодом по номеру:
e|ep|episode) check_ready test -z "$2" && die No episode specified episode=$2 shift shift launch "$@" ;;
А теперь нечто совершенно другое — установка переменных:
set) # Проверим, что нам передали название переменной test -z "$2" && die Variables: episode mask glob options player var_name="$2" # Проверим, что есть хотя бы что-то в значении test -z "$3" && die No value specified shift shift # И запишем все что есть: echo "$var_name=\"$@\"" "#" at `date` >> $savefile ;;
Наконец, вывод текущего состояния:
status|show) check_ready echo Last played episode $episode echo Options are $options echo Savefile is $savefile echo Mask is \"$mask\" test -z "$glob" || echo Globbing is set ;;
Вот здесь вы можете увидеть скрипт в готовом, но очень плохо документированном варианте, которым я активно пользуюсь уже давно bitbucket.org/tsx/env/src/tip/bin/serial