Укрощаем пыл Plex на ARM устройствах

    Всё началось в моего вопроса в 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

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

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

        Хочу попробовать собрать нормальный Docker образ на основе образа phusion
          0
          А… Кажется сразу не разобрался… Вопрос отпал… Каюсь…
            0

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

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

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

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

                  0

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


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


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

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

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

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

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

              0
              Что Plex, что Emby часа через два простоя просто умирают (развернуты на Pi 2)б сама малина включена и пингуется, но медиасервера нет, я не силен в linux'ах (установлен ubuntu-mate) может быть нужно отключить какую-то конфигурацию «спящего режима»?
                0
                У меня на Pi 2 (Raspbian 8 jessie) тоже стоит Plex и он тоже перестаёт отзываться через несколько часов (только не сам Plex, а весь одноплатник). не силён в Linux, поэтому решил проблему запланированным ребутом машины 2 раза в сутки.
                0
                Не знал про Emby Media Server, надо бы попробовать.

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

                    Документация к 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
                    0
                    mediatomb, minidlna по-моему гораздо легче и стабильнее
                      0

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

                        0
                        конечно, они не против этого
                      +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 — скрипт который забирает последние версии и разбрасывает по папкам и пишет необходимые скрипты для работы.

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

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


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

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое