Всё началось в моего вопроса в Toster. И вот уже полгода как я использую медиа сервер Plex. Для тех, кто о нём не слышал, поясню: это ПО, которое анализирует и структурирует вашу медиатеку, и предоставляет к ней доступ через web и не только, эдакий персональный Netflix без регистрации и SMS. Я использую Plex для просмотра фильмов и сериалов через браузер на ноутбуке или Chromebook.


image


Раньше мне приходилось настраивать 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 пакетов под любую архитектуру процессора. Ну и само собой многим параноикам-киноманам не захочется устанавливать кота в мешке на свою систему.


Emby Media Server

Существует 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.


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 дорожки в видео файле.


magic.sh
#!/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/Application\x20Support/Plex\x20Media\x20Server/plexmediaserver.pid

[Install]
WantedBy=multi-user.target

https и nginx


Чтобы иметь доступ к Plex из интернета, рекомендуется использовать HTTPS соединение. Если не хочется регистрироваться и платить деньги за дополнительные возможности Plex, то сертификат можно настроить самостоятельно. Можно использовать самоподписанный сертификат, можно использовать сертификат от Let's Encrypt. Но в конечном итоге конфигурационный файл nginx будет выглядеть приблизительно так:


nginx_plex.conf
# Реализуем автоматический редирект на 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
Поделиться с друзьями
-->

Комментарии (21)


  1. kvaps
    02.08.2016 10:02
    +1

    Давайте вместе пилить Emby! — спасибо что упомянули, надо бы статью про него написать что ли :)


    1. vektory79
      03.08.2016 23:34

      А можно как-то Emby запустить в foreground режиме?

      Хочу попробовать собрать нормальный Docker образ на основе образа phusion


      1. vektory79
        03.08.2016 23:46

        А… Кажется сразу не разобрался… Вопрос отпал… Каюсь…


      1. kvaps
        04.08.2016 22:19

        Вообще у emby есть свой официальный docker-образ и надо признать он достаточно качественно собран.


        1. vektory79
          04.08.2016 23:26

          Спасибо, но я уже успел его найти и изучить. Просто у меня минисервер на Intel Atom (x86). По этой причине приходится пересобирать образа под себя самостоятельно. Ну а уж коли пересобираю, то за одно вношу коррективы для облегчения себе жизни.

          А что до вашего образа, то он довольно сильно расходится с моими представлениями о правильности и качестве. Уж извините…


        1. kay
          05.08.2016 09:35
          +1

          К сожалению он только под архитектуру x86*


          1. werwolfby
            05.08.2016 14:42

            В статье про ARM :)


            Я очень долго собирал emby образ под ARM и всегда он работал с косяками в результате я пока забил на emby. Да и здоровый он непомерно получается.


            Вроде бы emby собираются перевести на .NET Core, тогда я думаю всё станет лучше.


  1. vektory79
    02.08.2016 11:46

    А чем для вас запуск через systemd оказался предпочтительнее простой опции --restart=unless-stopped у docker run?


    1. kay
      02.08.2016 12:32
      +1

      1) Удобно модифицировать параметры запуска через переменные окружения
      2) После запуска автоматически добавляется правило iptables masquerading (plex видит родную подсеть докера 172.16.0.0/16, а домашнюю сеть 192.168.0.0/16 воспринимает как внешнюю и не позволяет зайти в dashboard).
      3) Логи автоматически пишутся в journald


      1. vektory79
        03.08.2016 01:17
        +1

        1) Может я чего-то не понимаю, но сам Docker так же через переменные окружения рулится…
        2) Тем самым вы отрезаете себя от протокола DLNA, которому нуден UDP. Хотя тут я тоже могу ошибаться…
        3) --log-driver=journald вроде тоже самое делает…


        1. kay
          03.08.2016 08:48

          1) Ключевое слово "удобно". Точно также можно и shell скрипт запилить, но если всё уже придумано, то почему не бы и не использовать?
          2) В моём случае в DLNA нет необхоимости. Контейнер можно запустить и с параметром --net host, тогда никакие правила iptables не понадобятся. Но я всётаки решил изолировать plex и рулить доступом к сети с помощью iptables. Например в моей конфигурации plex не может использовать ipv6 и знать реальный IP сервера, трафик перенаправляется исключительно через tor.
          3) См. пункт 1. Если уже есть система инициализации, то зачем использовать зоопарк из других решений?


  1. Samogonshik
    02.08.2016 12:26

    Что Plex, что Emby часа через два простоя просто умирают (развернуты на Pi 2)б сама малина включена и пингуется, но медиасервера нет, я не силен в linux'ах (установлен ubuntu-mate) может быть нужно отключить какую-то конфигурацию «спящего режима»?


    1. GritsanY
      02.08.2016 14:50

      У меня на Pi 2 (Raspbian 8 jessie) тоже стоит Plex и он тоже перестаёт отзываться через несколько часов (только не сам Plex, а весь одноплатник). не силён в Linux, поэтому решил проблему запланированным ребутом машины 2 раза в сутки.


  1. Vallefor
    02.08.2016 12:26

    Не знал про Emby Media Server, надо бы попробовать.

    Использую Plex уже давно, но раньше, когда не было Хромкаста (а на моем ТВ нет Plex приложения), для просмотра аниме с сабами использовал PS3 Media Server.


  1. VecH
    02.08.2016 15:05

    По поводу докера, что за COPY, RUN и т.д.
    вот тут поподробнее можно? в armbian это уже стоит из коробки в образе?


    1. kay
      02.08.2016 15:10

      Документация к RUN, к COPY. Отличие ADD от COPY в том, что ADD работает и с URL, и автоматически распаковывает архив, если файл является таковым.


      Чтобы Docker заработал в Armbian, требуется использовать Vanilla kernel, и установить Docker из debian backports:


      $ echo "deb http://ftp.debian.org/debian jessie-backports main" > /etc/apt/sources.list.d/backports.list
      $ apt-get update
      $ apt-get install docker.io


  1. realwk
    02.08.2016 15:38

    mediatomb, minidlna по-моему гораздо легче и стабильнее


    1. kay
      02.08.2016 15:39

      А они позволяют использовать браузер для просмотра видео без перекодировки?


      1. realwk
        02.08.2016 16:37

        конечно, они не против этого


  1. oleg_krv
    02.08.2016 15:51
    +1

    Есть сборка под арм, становится на дебиан арм (армбиан и расбиан):

    sudo echo "deb https://dev2day.de/pms/ jessie main" > /etc/apt/sources.list.d/pms-plex.list
    wget -O - https://dev2day.de/pms/dev2day-pms.gpg.key | sudo apt-key add -
    sudo apt-get update
    

    с поддержкой неон
    sudo apt-get install plexmediaserver-installer
    

    без поддержки
    sudo apt-get install 	plexmediaserver-neonless-installer
    


    взято из Регулярно человек обрабатывает обновления и как ни странно на Lamobo R1 работает стабильно (смотрю из смарта Самсунг и андроид прог).

    Сам пакет plexmediaserver-installer — скрипт который забирает последние версии и разбрасывает по папкам и пишет необходимые скрипты для работы.

    Но по поводу веб-версии спасибо.


    1. kay
      02.08.2016 16:00

      Посмотрел скрипты в пакете plexmediaserver-installer_1.0.3.2461-35f0caa-1~jessie_armhf.deb. Вот так и обнаруживаешь, что другой человек почти тоже самое делает. Даже фиксы с downmix такие же.


      Ну хоть bash скрипт с подменой параметров не зря писал.