Как стать автором
Обновить

Парсер RSS на bash для LostFilm.TV: Transmission + SQLite + mkvtools

Время на прочтение6 мин
Количество просмотров1.8K
Здравия желаю, Хабр!

В ответ на предыдущий топик про парсер RSS-ленты LostFilm.TV хочу выложить свой вариант работающий уже около 4х месяцев без каких-либо ошибок.
Суть идеи состоит в том, что сервер качает сериалы и раскладывает по папкам, оформляя при этом файлы с обложкой и нормальным заголовком.
Однако, в работе всей системы участвует не один скрипт, а целый набор скриптов. В такой системе скрипты разделены на pre-обработку и post-обработку.
И, конечно же, для эстетов: файлы каждой серии должны выглядеть красиво и быть разложены по папкам.


pre-обработчик


Алгоритм работы парсера

• Прочитать RSS-ленту
• Выполнить разбор на отдельные элементы
• Отсеять старые элементы
• Добавить новые элементы в базу и в очередь загрузки transmission

Код парсера

#!/bin/bash

export PRFX="/var/lib/transmission-daemon"
export SELF=$(basename $(readlink -f $0))

# Загрузка конфигурации
. $PRFX/.$SELF/config

# Загрузка необходимых функций
. $PRFX/.funcs/sqlite
. $PRFX/.funcs/transmission

# Проверка и запись значения последней даты
check_last() {
        if [ ! -f $LFW_RSS_LAST ] || [ $1 -gt $(cat $LFW_RSS_LAST) ]; then
                printf "$1" > $LFW_RSS_LAST
                return 0
        else
                return 1
        fi
}

# Функция разбора заголовка
parse_title() {
        echo "$1" | sed -r 's/^([^(]+)[. ]+\((.+)\)[. ]+([^(]+)[. ]+\((.+)\)[. ]*(\[720p\]){0,1}[. ]*\(S0*([0-9]+)E0*([0-9]+).*\)$/\1|\2|\3|\4|\5|\6|\7/'
}

logger -t $SELF -- 'Запрос данных RSS/Atom'
# Запрос RSS-ленты
rsstail -1NHlp -n 30 -u "$LFW_RSS_URL" |
# Конвертация потока в совместимый со скриптом формат
iconv -f cp1251 | sed -r '/^\s*$/d; s/^\s+//; s/\s+$//' | sed -r '$!N; s/\n/|/; $!N; s/\n/|/' |
# Конвертация даты в UNIXTIME и сортировка по убыванию даты
(IFS='|'; while read item_title item_link item_date; do
        printf '%s|%s|%s\n' $(date -d "$item_date" +%s) "$item_title" "$item_link"
done) | sort |
# Отсеивание новых элементов
(IFS='|'; while read item_date item_title item_link; do
        if check_last $item_date; then
                printf '%s|%s|%s\n' $item_date "$(parse_title "$item_title")" "$item_link"
        fi
done) |
# Обработка новых элементов
(IFS='|'; while read date name_ru name_en title_ru title_en hd season episode link; do
        # получение идентификатора сериала из базы
        id=$(printf 'SELECT id FROM series WHERE title_en = "%s";' "$name_en" | db_query $LFW_DB)
        # если есть то обрабатываем, иначе уходим
        if [ -n "$id" ]; then
                # загрузка торрент-файла в во временное расположение
                tr_file="/tmp/lostfilm_$(uuidgen).torrent"
                if wget -nv -q --header "$LFW_WGET_AUTH" "$link" -O "$tr_file"; then
                        # проверяем добавляли ли ранее сведения о серии в базу, если нет то добавить
                        if [ -z $(printf 'SELECT id FROM episodes WHERE series = %d AND season = %d AND episode = %d;' $id $season $episode | db_query $LFW_DB) ]; then
                                printf 'INSERT INTO episodes (series, season, episode, title_en, title_ru) VALUES (%d, %d, %d, "%s", "%s");' \
                                        $id $season $episode $title_en $title_ru | db_query $LFW_DB
                        fi
                        # здесь идет разделение для HD и SD вариантов серий
                        if [ -z "$hd" ]; then
                                file=$(transmission-show "$tr_file" | sed -r '/^Name:/!d; s/^Name:\s*(.+)\s*$/\1/') #'
                                printf 'INSERT INTO files (id, date, filename) VALUES ((SELECT id FROM episodes WHERE series = %d AND season = %d AND episode = %d), %d, "%s");' \
                                        $id $season $episode $date $file | db_query $LFW_DB
                                if [ $(printf 'SELECT tracked FROM series WHERE id =  %d;' $id | db_query $LFW_DB) -ne 0 ]; then
                                        transmission --add "$tr_file" --start > /dev/null
                                fi
                                logger -t $SELF -- $(printf 'Добавлена новая SD серия «%s» (сезон %s, серия %s) — «%s»' "$name_ru" "$season" "$episode" "$title_ru") #'
                        else
                                file=$(transmission-show "$tr_file" | sed -r '/^Name:/!d; s/^Name:\s*(.+)\s*$/\1/') #'
                                printf 'INSERT INTO files_hd (id, date, filename) VALUES ((SELECT id FROM episodes WHERE series = %d AND season = %d AND episode = %d), %d, "%s");' \
                                        $id $season $episode $date $file | db_query $LFW_DB
                                if [ $(printf 'SELECT tracked_hd FROM series WHERE id =  %d;' $id | db_query $LFW_DB) -ne 0 ]; then
                                        transmission --add "$tr_file" --start > /dev/null
                                fi
                                logger -t $SELF -- $(printf 'Добавлена новая HD серия «%s» (сезон %s, серия %s) — «%s»' "$name_ru" "$season" "$episode" "$title_ru") #'
                        fi
                fi
                [ -f "$tr_file" ] && rm -f "$tr_file"
        fi
done)
logger -t $SELF -- 'Завершено'


post-обработчик


Далее, когда файл загружен его нужно привести в эстетический вид (моя жена предпочитает видеть все сериалы, разложенными по папкам с именем сериала и, имеющими имя файла, содержащее номер сезона/эпизода и название серии, а также с красивой обложкой-иконкой сериала).
После загрузки сериала срабатывает цепочка скриптов, направленных на обработку различного рода файлов, среди которых есть и lostfilm.tv.

Алгоритм работы скрипта пост-обработки

• Выполнить проверку наличия в БД имени файла полученного от демона, иначе завершить работу
• Выгрузить обложку из базы
• Применить mkvtools (фильтр англ дорожки, вставка обложки, запись заголовка)
• Положить в папку по имени сериала (создать, если нет)
• Уведомить подписанных по почте и через SMS (мне на телефон, жене на почту)

Скрипт post-обработки

#!/bin/bash

export SELF="lostfilm-rss"

# Загрузка конфигурации
. $PRFX/.$SELF/config

# Загрузка необходимых функций
. $PRFX/.funcs/sqlite
. $PRFX/.funcs/transmission
. $PRFX/.funcs/mkv_tools
. $PRFX/.funcs/mail_notify
. $PRFX/.funcs/utils
. $PRFX/.funcs/sms_notify

MAIL_LIST_HD="me@a***n.ru"

# выполняем запрос к БД о наличии файла
data="$(printf 'SELECT s.title_ru, e.season, e.episode, e.title_ru, s.id FROM episodes e, series s, files_hd f WHERE e.series = s.id AND e.id = f.id AND f.filename = "%s";' "$TR_TORRENT_NAME" | db_query $LFW_DB)"
# если есть то обрабатываем, иначе отбой
if [ -n "$data" ]; then
        # разбор результата запроса
        name=$(echo $data | cut -d'|' -f1)
        s=$(echo $data | cut -d'|' -f2)
        e=$(echo $data | cut -d'|' -f3)
        part=$(echo $data | cut -d'|' -f4)
        id=$(echo $data | cut -d'|' -f5)
        # эстетическое имя файла
        mkv_file=$(printf '/mnt/videos/Series.HD/%s [%s.%s] — %s.mkv' "$name" "$s" "$e" "$part")
        # форматирование заголовка видео-файла
        mkv_title=$(printf '«%s» • Сезон %s, Серия %s • «%s»' "$name" "$s" "$e" "$part")
        # выгрузка обложки из базы
        mkv_poster="/tmp/mkv_poster_$(uuidgen).jpg"
        printf 'SELECT data FROM posters WHERE series = %d ORDER BY date DESC LIMIT 1;' $id | db_query $LFW_DB | base64 -d - > $mkv_poster
        logger -t $SELF -- $(printf 'Загружена HD серия «%s» (сезон %s, серия %s) — «%s»' "$name" "$s" "$e" "$part") #'
        # ремукс видео-файла с заданными параметрами
        if to_mkv "/mnt/torrent/$TR_TORRENT_NAME" "$mkv_file" "$mkv_title" "$mkv_poster"; then
                logger -t $SELF -- $(printf 'Обработана HD серия «%s» (сезон %s, серия %s) — «%s»' "$name" "$s" "$e" "$part") #'
                # я не раздаю файлы, поскольку подсчет рейтинга сломан уже два года, он у меня не меняется, удаляем торрент вместе с исходником
                transmission -t $TR_TORRENT_ID --remove-and-delete > /dev/null
                logger -t $SELF -- $(printf 'Удалена из торрента HD серия «%s» (сезон %s, серия %s) — «%s»' "$name" "$s" "$e" "$part") #'
               # уведомляем по почте
               mail_notify "$MAIL_LIST_HD" "$(printf 'Доступна новая серия «%s» (сезон %s, серия %s) — «%s»' "$name" "$s" "$e" "$part")" \
                   "$(printf '<h3>«%s» (сезон %s, серия %s) — «%s»</h3><br/><i>Качество: HD</i>' "$name" "$s" "$e" "$part")"
                # отправляем смс
                sms_notify a***n "$(printf 'Новая серия «%s» (сезон %s, серия %s) — «%s» \n[%s]' "$name" "$s" "$e" "$part" "$(disk_info)")"
        else
                logger -t $SELF -- $(printf 'Произошла ошибка при обработке HD серии «%s» (сезон %s, серия %s) — «%s»' "$name" "$s" "$e" "$part") #'
        fi
        rm -f $mkv_poster
fi


База данных


Из скриптов видно, что работа идет с базой данных, в моем варианте используется SQLite3

Структура базы данных

CREATE TABLE episodes (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        series INTEGER REFERENCES series(id) ON UPDATE CASCADE ON DELETE CASCADE,
        season INTEGER NOT NULL,
        episode INTEGER NOT NULL,
        title_en TEXT NOT NULL,
        title_ru TEXT NOT NULL,
        UNIQUE(series, season, episode)
);
CREATE TABLE files (
        id INTEGER PRIMARY KEY REFERENCES episodes(id) ON UPDATE CASCADE ON DELETE CASCADE,
        date INTEGER NOT NULL,
        filename TEXT NOT NULL
);
CREATE TABLE files_hd (
        id INTEGER PRIMARY KEY REFERENCES episodes(id) ON UPDATE CASCADE ON DELETE CASCADE,
        date INTEGER NOT NULL,
        filename TEXT NOT NULL
);
CREATE TABLE posters (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        series INTEGER REFERENCES series(id) ON UPDATE CASCADE ON DELETE CASCADE,
        date INTEGER NOT NULL,
        data TEXT NOT NULL
);
CREATE TABLE series (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title_en TEXT NOT NULL,
        title_ru TEXT NOT NULL,
        tracked INTEGER DEFAULT 0
, tracked_hd integer default 1);


Обложки хранятся внутри БД в виде base64, понятно, что от этого база станет жирнее, но все же я предпочел хранить все о сериалах внутри одной базы.

TODO и PS


Осталось сделать те вещи, которые уже не так значимы для меня и может быть будут когда-то сделаны.
1. Перевести базу на MySQL/PostgreSQL
2. Сделать веб-интерфейс к базе
3. Сделать автоматическое добавление нового сериала в базу (с обложкой)
n. Будущие исправления в распознователь RSS-ленты

Остальное публике должно быть понятно. В коде есть комментарии.

Скрипт работает уже 4 месяца без сбоев и ошибок (хотя сегодня вот была Эврика с сезоном номер 0, но это был косяк на самом сайте). Сам я военнослужащий и дома бываю примерно раз в 1-2 недели, соответственно работает все в автоматическом режиме с редким контролем, я лишь приезжаю и сливаю новые серии на винт, чтобы что-то посмотреть в части.

Спасибо за внимание!
Теги:
Хабы:
Всего голосов 7: ↑6 и ↓1+5
Комментарии14

Публикации

Истории

Ближайшие события