Всё началось в моего вопроса в Toster. И вот уже полгода как я использую медиа сервер Plex. Для тех, кто о нём не слышал, поясню: это ПО, которое анализирует и структурирует вашу медиатеку, и предоставляет к ней доступ через web и не только, эдакий персональный Netflix без регистрации и SMS. Я использую Plex для просмотра фильмов и сериалов через браузер на ноутбуке или Chromebook.
Раньше мне приходилось настраивать NFS или Samba share, колдовать с automount(8), мириться с отваливанием share после suspend-resume, или просто копировать файлы по sftp/scp, но теперь я использую Тайд Plex. К сожалению, с ним тоже не всё просто.
Роль моего домашнего сервера выполняет Cubietruck с процессором ARM Cortex-A7 1GHz и дистрибутивом Armbian (Vanilla kernel для поддержки Docker и namespaces(7)). Его вполне хватает для повседневных нужд (хранение бэкапов, VPN сервер), но он очевидно не предназначен для более ресурсоёмких вещей.
Plex является freeware software. Он бесплатный, но не свободный, что налагает определенные ограничения. Например нет исходных кодов и официальных deb пакетов под любую архитектуру процессора. Ну и само собой многим параноикам-киноманам не захочется устанавливать кота в мешке на свою систему.
Существует opensource проект Emby Media Server, аналог Plex, написанный на Mono. К сожалению, у него есть проблемы с воспроизведением файлов в браузерах. Многие из видео форматов Emby транскодирует полностью, даже если изначально используется кодек h264.
На сегодняшний день Plex позволяет воспроизвести больше форматов видео без полной перекодировки, чем Emby Media Server. Возможно кто-то из вас поможет исправить эту ситуацию. А пока можно поколдовать над файлом browserdeviceprofile.js, который отвечает за профили браузеров.
Первую проблему мы решим с помощью официально распространяемых пакетов для NAS устройств, а вторую частично с помощью Docker.
За основу возьмем пакет для NAS QNAP с архитектурой ARMv7-X31+ (данный билд поддерживает расширение Neon, которое поддерживается Cubietruck'ом, проверить можно командой cat /proc/cpuinfo | grep neon
):
$ curl -s https://plex.tv/api/downloads/1.json | python -mjson.tool | grep x31plus
"url": "https://downloads.plex.tv/plex-media-server/1.0.3.2461-35f0caa/PlexMediaServer_1.0.3.2461-35f0caa_arm-x31plus.qpkg
Файл qpkg является симбиозом shell скрипта и нескольких архивов. Распаковать его в директорию plex_media_server
мы можем с помощью команды:
$ mkdir plex_media_server
$ wget https://downloads.plex.tv/plex-media-server/1.0.3.2461-35f0caa/PlexMediaServer_1.0.3.2461-35f0caa_arm-x31plus.qpkg
$ dd if=PlexMediaServer_1.0.3.2461-35f0caa_arm-x31plus.qpkg bs=22954 skip=1 status=none | tar -xzf - -C plex_media_server
Полученные файлы можно поместить в Docker контейнер, но об этом чуть позже. Предположим, что мы запустили Plex и собираемся посмотреть в браузере фильм, который уже закодирован в h264 со звуковой AC3 5.1. Что сделает Plex? Он начнет перекодировать дорожку AC3 5.1 в AAC 5.1. А для просмотра видео на ноутбуке нам нет необходимости слушать видео с шестью каналами, да и небыстрый процессор даёт о себе знать с периодическими паузами при просмотре.
К счастью у Plex есть конфигурационные профили, которые можно редактировать. Например профиль для браузеров Resources/Profiles/Web.xml
.
<?xml version="1.0" encoding="utf-8"?>
<Client name="Web">
<!-- Author: Plex Inc. -->
<TranscodeTargets>
<VideoProfile protocol="hls" container="mpegts" codec="h264" audioCodec="aac,mp3" context="streaming" />
<VideoProfile protocol="dash" container="mp4" codec="h264" audioCodec="aac" context="streaming" />
<VideoProfile protocol="http" container="mkv" codec="h264" audioCodec="aac,mp3" context="streaming" />
<MusicProfile container="mp3" codec="mp3" />
<PhotoProfile container="jpeg" />
<SubtitleProfile container="ass" codec="ass" context="all" />
</TranscodeTargets>
<CodecProfiles>
<VideoCodec name="*">
<Limitations>
<UpperBound name="video.bitDepth" value="8" />
</Limitations>
</VideoCodec>
<VideoAudioCodec name="*">
<Limitations>
<UpperBound name="audio.channels" value="6" />
</Limitations>
</VideoAudioCodec>
</CodecProfiles>
</Client>
В нём мы видим параметр <UpperBound name="audio.channels" value="6" />
, который говорит о том, что максимальное количество каналов для аудио не должно превышать шесть. А при перекодировании аудио дорожки это означает, что если мы преобразуем AC3 6 каналов в AAC, то результирующий AAC тоже будет иметь 6 каналов, т.е. мы декодируем 6 каналов AC3 и кодируем их в 6 каналов AAC, лишний раз используя ресурсы CPU. При просмотре видео это вызывает периодические подвисания.
Чтобы включить так называемый downmix, нужно параметр 6 заменить на 2 и получим <UpperBound name="audio.channels" value="2" />
. Тогда файлы с шестиканальной звуковой дорожкой будут преобразовываться в стерео.
Для большинства пользователей этот вариант будет приемлемым. Но не для тех, у кого есть файлы с шестиканальной дорожкой AAC. В данном случае шестиканальный AAC будет преобразоываваться в stereo AAC. А это опять трата ресурсов процессора и периодические зависания при просмотре видео. Я полагал, что колдование с профилями может решить проблему, но, к сожалению, в текущей версии Plex такие исключения не возможны. На форуме Plex уже две недели без ответа висит запрос о добавлении подобной опции.
Единственный вариант для решения этой проблемы я увидел в подмене бинарника Plex Transcoder
на скрипт, который будет формировать необходимые параметры при наличии AAC дорожки в видео файле.
#!/bin/bash
# This script disables transcode for videos which already have aac audio
magic=0
i=0
input=false
for arg in "$@"; do
((i++))
next=$((i+1))
if [[ "$arg" == "-i" ]]; then
input=true
fi
if [[ "$arg" =~ -codec:[0-9] && "${@:$next:1}" == "aac" && $magic == 0 && $input == false ]]; then
((magic++))
continue
fi
if [[ "$arg" == "aac" && $magic == 1 ]]; then
((magic++))
continue
fi
if [[ "$arg" == "-codec:1" && $magic == 2 ]]; then
((magic++))
fi
if [[ "$arg" == "aac" && $magic == 3 ]]; then
args[$i]="copy"
((magic++))
continue
fi
if [[ "$arg" == "-ar:1" && $magic == 4 ]]; then
args[$i]="-copypriorss:1"
((magic++))
continue
fi
if [[ "$arg" == "48000" && $magic == 5 ]]; then
args[$i]="0"
((magic++))
continue
fi
if [[ "$arg" == "-channel_layout:1" && $magic == 6 ]]; then
((magic++))
continue
fi
if [[ "$arg" == "stereo" && $magic == 7 ]]; then
((magic++))
continue
fi
if [[ "$arg" == "-b:1" && $magic == 8 ]]; then
((magic++))
continue
fi
if [[ "$arg" == "256k" && $magic == 9 ]]; then
((magic++))
continue
fi
args[$i]=$(printf "%q" "$arg")
done
set -- "${args[@]}"
eval "/opt/plex/Application/Resources/Plex Transcoder_ $@"
ссылка на github: https://github.com/kayrus/plex/blob/master/magic.sh
При тестировании выяснилось, что данный хак неплохо работает с видео файлами из моей медиатеки.
Docker
Теперь посмотрим как всё это обернуть в образ Docker. Как минимум должны выполняться следующие условия:
- Доступ из контейнера возможен только к определенным директориям.
- Plex не должен запускаться из под root, даже внутри контейнера.
- Почему бы не использовать systemd для запуска контейнера с Plex?
Далее приведу выдержки из Dockefile
, который я использую.
Копируем и распаковываем скачанный архив (можно получить напрямую через wget, но в используемой мной конфигурации это запрещено). Использую COPY
вместо ADD
чтобы избежать автоматической распаковки архива, в данном случае в этом нет необходимости. Выражение || true
позволяет проигнорировать сообщение gzip о мусоре после конца архива.
COPY PlexMediaServer_1.0.3.2461-35f0caa_arm-x31plus.qpkg /tmp/plex_media_server.tar
RUN { dd if=/tmp/plex_media_server.tar bs=22954 skip=1 status=none | tar -xzf - -C /opt/plex/Application || true; } && rm -f /tmp/plex_media_server.tar
Добавляем в контейнер непривилегированного системного пользователя plex
.
RUN useradd -r -d /var/lib/plex -s /sbin/nologin plex
Активизируем downmix:
RUN sed -i 's/name="audio.channels" value="6"/name="audio.channels" value="2"/' /opt/plex/Application/Resources/Profiles/Web.xml
Все дальнейшие действия в контейнере будут выполняться от пользователя plex
.
USER plex
Помечаем пути /var/lib/plex
(для сохранения состояния базы медиа файлов) и /media
(путь для медиа файлов) как внешние тома:
VOLUME ["/var/lib/plex","/media"]
Запуск контейнера
В команде ниже мы транслируем стандартный порт 32400 в 80-й http порт, монтируем путь /home/plex
в /var/lib/plex
внутри контейнера и /home/user/media
в /media
.
$ docker run --name plex --hostname plex --rm -p 80:32400 -v /home/plex:/var/lib/plex -v /home/user/media:/media pleximage
systemd
Unit файл, который я использую для запуска контейнера plex.
[Unit]
Description=Plex Media Server
After=docker.service
Requires=docker.service
[Service]
Environment=MEDIA_LIB=/home/user/media
Environment=CONFIG_DIR=/var/lib/plex
Environment=DOCKER_IMAGE=kayrus/plex
Environment=PLEX_INT_PORT=32400
Environment=PLEX_EXT_PORT=32400
# Remove old Plex container
ExecStarPre=-/usr/bin/docker rm plex
ExecStart=/usr/bin/docker run --name plex --hostname plex --rm -p ${PLEX_EXT_PORT}:${PLEX_INT_PORT} -v ${CONFIG_DIR}:/var/lib/plex -v ${MEDIA_LIB}:/media ${DOCKER_IMAGE}
# Fix foreign network which requires Plex login/signup
ExecStartPost=/sbin/iptables -t nat -I POSTROUTING -o docker0 -p tcp -m tcp --dport ${PLEX_INT_PORT} -j MASQUERADE
ExecStopPost=-/sbin/iptables -t nat -D POSTROUTING -o docker0 -p tcp -m tcp --dport ${PLEX_INT_PORT} -j MASQUERADE
ExecStop=/usr/bin/docker stop plex
# Remove pidfile after stop which prevents Plex server start
ExecStopPost=/bin/rm -f ${CONFIG_DIR}/Library/Applicationx20Support/Plexx20Mediax20Server/plexmediaserver.pid
[Install]
WantedBy=multi-user.target
https и nginx
Чтобы иметь доступ к Plex из интернета, рекомендуется использовать HTTPS соединение. Если не хочется регистрироваться и платить деньги за дополнительные возможности Plex, то сертификат можно настроить самостоятельно. Можно использовать самоподписанный сертификат, можно использовать сертификат от Let's Encrypt. Но в конечном итоге конфигурационный файл nginx будет выглядеть приблизительно так:
# Реализуем автоматический редирект на Plex dashboard, изначально в бесплатной версии это не предусмотрено.
map $request_method$request_uri$http_referer $do_redirect {
"GET/" 1;
default 0;
}
server {
# Listen only HTTPS socket
listen [::]:443;
# Enter your domain here
server_name plex.example.com;
# Configure your SSL certificates here
ssl on;
include ssl.conf;
ssl_trusted_certificate ssl/ca-certs.pem;
ssl_certificate ssl/plex.example.com.pem;
ssl_certificate_key ssl/plex.example.com-key.pem;
# Protect Plex by basic auth
auth_basic "denied";
auth_basic_user_file .htpasswd;
# Redirect to the Plex dashboard
if ($do_redirect = 1) {
return 302 https://$host/web;
}
# Default location
location / {
# Не будем передавать в Plex наши пароли
proxy_set_header Authorization "";
proxy_buffering off;
proxy_pass http://localhost:32400;
}
# Websockets location
location /:/websockets/ {
# Не будем передавать в Plex наши пароли
proxy_set_header Authorization "";
proxy_buffering off;
proxy_pass http://localhost:32400;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Ссылки:
- Репозиторий с информацией о том как запустить Plex в Docker контейнере под архитектурой ARM: https://github.com/kayrus/plex
- Репозиторий с информацией о том как запустить Emby Media Server в Docker контейнере под архитектурой ARM: https://github.com/kayrus/emby
Автор: kay