Home Assitant для управления Plex

от автора

В этой статье я расскажу про написание скрипта HA, который продолжает воспроизведение видео запущенного с Plex на другом устройстве: выключает источник, включает устройство назначения, включает Plex, включает текущее видео, перематывает видео в нужную позицию, устанавливает необходимую громкость.

Написание скрипта и тестирование заняло больше времени, чем сэкономит его использование, тем более Plex и так сохраняет текущую позицию, но скрипт писался скорее в исследовательских целях. Возможно вы сможете подчерпнуть полезные решения, которые были использованы в нем, а может подскажете мне в комментариях как что-то можно было сделать проще.

В статье будет рассмотрено использование переменных (variables), триггеров (wait triggers), циклов (loops), условий (conditions), шаблонов значений (value templates).

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

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

А теперь давайте посмотрим каждое действие по отдельности.

1. Определение переменных target, source, plextarget и volume

Эти переменные будут использоваться в скрипте:

target — телевизор назначение

source — телевизор источник

plextarget — медиаплеер Plex на ТВ нахначении. Каждый клиент plex регистрируется в Home Assistant как отдельный device — media_player. Управление контентом и позицией возможно только через него, так как сам телевизор знает только какой запущен источник (hdmi или приложение) и статус (idle, playing, paused) и не понимает, что конкретно на нем проигрывается и какая текущая позиция.

volume — текущая громкость на источнике. Так как в фильмах используется разный уровень аудиодорожек, то не хочется руками регулировать громкость на ТВ назначении, а тем более чтобы он заорал на всю квартиру, если там был установлен высокий уровень.

Сначала нужно определить откуда и куда необходимо перенести просмотр фильма. Так как я автоматизировал только для сценария перехода между двумя комнатами (кухней и спальней), то предполагаем что один ТВ в момент запуска скрипта включен, другой выключен (можно реализовать и более сложные проверки, например, что включен источник plex, телевизор в статусе playing и т.д.).

Соответственно если ТВ спальня включен, то target = ТВ кухня, иначе target = ТВ спальня. Аналогично поступаем с source, plextarget и volume.

Для получения значения entity необходимо воспользоваться states, параметром в который передается entity, а states возвращает его значение (статус).

Код
variables:   target: >-     {{ 'media_player.lg_webos_kitchen' if     (states('media_player.lg_webos_bedroom') == 'on') else     'media_player.lg_webos_bedroom' }}   source: >-     {{ 'media_player.lg_webos_bedroom' if     (states('media_player.lg_webos_bedroom') == 'on') else     'media_player.lg_webos_kitchen' }}   plextarget: >-     {{ 'media_player.plex_plex_for_lg_lg_65up751c0zf' if     (states('media_player.lg_webos_bedroom') == 'on') else     'media_player.plex_plex_for_lg_lg_oled65c1rla' }}   volume: >-     {{ state_attr('media_player.lg_webos_bedroom', 'volume_level') if     (states('media_player.lg_webos_bedroom') == 'on') else     state_attr('media_player.lg_webos_kitchen', 'volume_level') }}

2. Поставить Plex на паузу

Придется немного забежать вперед, чтобы объяснить зачем ставить Plex на паузу на источнике. Как выяснилось, Plex обновляет атрибут media_position с большим интервалом, а значит узнать точную текущую позицию нельзя, но оказалось, что позиция обновляется при постановке на паузу.

Код
action: media_player.media_pause metadata: {} data: {} target:   entity_id: "{{ source }}" alias: Поставить Plex на паузу, чтобы обновилась media_position

3. Дождаться пока Plex станет на паузу

Поскольку в wait trigger нельзя использовать template entity, то я не смог использовать переменную source. А так как один из медиаплееров все равно выключен и находится в статусе unavailable, то просто добавляем оба в entity_id (далее по этой причине в wait trigger везде будут добавляться и источник и назначение) и при изменении статуса любого на paused продолжаем выполнение скрипта.

Код
wait_for_trigger:   - trigger: state     entity_id:       - media_player.plex_plex_for_lg_lg_65up751c0zf       - media_player.plex_plex_for_lg_lg_oled65c1rla     to: paused     for:       hours: 0       minutes: 0       seconds: 1 timeout:   hours: 0   minutes: 0   seconds: 10   milliseconds: 0

4. Определить переменные media_content_id и media_position

В интерфейсе HA идем в Developer tools\States, находим media_player Plex и смотрим какие там есть атрибуты:

Entity

State

Attributes

media_player.plex_plex_for_lg_lg_oled65c1rla

Plex (Plex for LG — LG OLED65C1RLA)

playing

friendly_name: Plex (Plex for LG — LG OLED65C1RLA)
supported_features: 152127
media_content_id: 1658
media_content_type: movie
media_duration: 8337
media_position: 666
media_position_updated_at: 2025-01-06T10:15:29.607078+00:00
media_title: Анора (2024)
media_content_rating: R
media_library_title: Фильмы
player_source: PMS
media_summary: Молодая стриптизёрша из Бруклина внезапно выходит замуж за сына русского олигарха. Узнав об этом, родители парня тотчас вылетают из Москвы в Нью-Йорк. Они готовы на всё, лишь бы расстроить брак. username: tmv002
entity_picture: /api/media_player_proxy/media_player.plex_plex_for_lg_lg_oled65c1rla?token=ae6b1a44a1007aed0da3e35a96895486bcd4da6b4f49a58b13c02ac788dba228&cache=8fbb4c41247a4811 volume_level: 1
is_volume_muted: false

Из полезного видим media_content_id и media_position. Проверяем и убеждаемся, что в этих атрибутах содержится id видео и текущая позиция в секундах.

Сохраняем эти значения в соответствующие переменные, они нам еще пригодятся. Для этого нам понадобится if, который мы использовали ранее и state_attr. Если у entity, кроме state еще есть атрибуты, то их значения можно получить с помощью state_attr, первым параметром в него передается entity, вторым имя атрибута.

Код
variables:   media_content_id: >-     {{ state_attr('media_player.plex_plex_for_lg_lg_oled65c1rla',     'media_content_id') if (states('media_player.lg_webos_bedroom') == 'on')     else state_attr('media_player.plex_plex_for_lg_lg_65up751c0zf',     'media_content_id') }}   media_position: >-     {{ state_attr('media_player.plex_plex_for_lg_lg_oled65c1rla',     'media_position') if (states('media_player.lg_webos_bedroom') == 'on') else     state_attr('media_player.plex_plex_for_lg_lg_65up751c0zf', 'media_position')     }}

5. Выключить ТВ источник

Код
action: media_player.turn_off metadata: {} data: {} target:   entity_id: "{{ source }}" alias: Выключить ТВ источник

6. Включить ТВ назначение

Код
action: media_player.turn_on metadata: {} data: {} target:   entity_id: "{{ target }}" alias: Включить ТВ назначение

7. Ждать пока ТВ не включится

Код
wait_for_trigger:   - trigger: state     entity_id:       - media_player.lg_webos_kitchen       - media_player.lg_webos_bedroom     from: "off"     to: "on"     for:       hours: 0       minutes: 0       seconds: 2 timeout:   hours: 0   minutes: 1   seconds: 0   milliseconds: 0 alias: Ждать пока ТВ не включится

8. Убавить звук до 0

Код
action: media_player.volume_set metadata: {} data:   volume_level: 0 target:   entity_id: "{{ target }}" alias: Убавить громкость до 0

9. Запустить приложение Plex на ТВ

Код
action: media_player.select_source metadata: {} data:   source: Plex target:   entity_id: "{{ target }}" alias: Включить источник Plex

10. Определить переменную timestamp

Получаем ее с помощью now().timestamp() и преобразовываем в int. Объясню ее назначение в следующем пункте.

Код
variables:   timestamp: "{{ (now().timestamp() | int) }}"

11. Сканировать клиентов Plex

Как выяснилось, media_player в HA остается очень долгое время в статусе unavailable после запуска Plex на ТВ. В документации на интеграцию не нашел никакой информации, что с этим можно было бы сделать. Но если нет документации, то идем методом научного тыка. Интеграция Plex кроме медиаплееров создает еще одно устройство с несколькими сенсорами и одной кнопкой «Scan clients». Пробуем ее нажимать и видим, что это существенно ускоряет переход клиента в статус idle (запущен, но ничего не проигрывается). В этом статусе можно управлять клиентом.

Действие выглядит предельно простым: нажимать scan clients каждые 5 секунд, пока один из медиаплееров (кухня, спальня) не перейдет в состояние idle. Но чтобы скрипт не завис, хочется ожидание статуса idle ограничить одной минутой. Иначе я устану ждать включу фильм сам и вдруг в какой-то момент скрипт начнет управлять медиаплеером, когда это уже не нужно.

Казалось бы элементарная задача, объявить переменную, после каждой итерации делать инкремент и на 12 раз (цикл длится 5 секунд) остановить цикл, однако в HA область действия переменных не позволяет это сделать. Можно почитать подробнее здесь.

Поэтому воспользуемся созданной в предыдущем пункте переменной timestamp и будем ждать пока текущее время в секундах не станет больше timestamp на 60 или пока один из медиаплееров не перейдет в статус idle.

Код
alias: Сканировать клиентов Plex каждые 5 секунд, пока не найдется клиент repeat:   sequence:     - delay:         hours: 0         minutes: 0         seconds: 5         milliseconds: 0     - action: button.press       metadata: {}       data: {}       target:         entity_id: button.media_scan_clients   while:     - condition: and       conditions:         - condition: template           value_template: "{{ (now().timestamp() | int) - timestamp < 60 }}"         - condition: not           conditions:             - condition: or               conditions:                 - condition: state                   entity_id: media_player.plex_plex_for_lg_lg_oled65c1rla                   state: idle                 - condition: state                   entity_id: media_player.plex_plex_for_lg_lg_65up751c0zf                   state: idle enabled: true

12. Включить фильм на Plex

Для этого воспользуемся переменной media_content_id

Код
action: media_player.play_media metadata: {} data:   media_content_id: "{{ media_content_id }}"   media_content_type: movie target:   entity_id: "{{ plextarget }}" alias: Включить фильм на Plex

13. Ждать пока статус Plex не станет Playing

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

Код
alias: Ждать пока статус Plex не станет Playing wait_for_trigger:   - trigger: state     entity_id:       - media_player.plex_plex_for_lg_lg_oled65c1rla       - media_player.plex_plex_for_lg_lg_65up751c0zf     to: playing     for:       hours: 0       minutes: 0       seconds: 1 timeout:   hours: 0   minutes: 1   seconds: 0   milliseconds: 0

14. Поставить на паузу

Код
action: media_player.media_pause metadata: {} data: {} target:   entity_id: "{{ target }}" alias: Поставить на паузу

15. Ждать пока не станет на паузу

Код
wait_for_trigger:   - trigger: state     entity_id:       - media_player.lg_webos_kitchen       - media_player.lg_webos_bedroom     to: paused     for:       hours: 0       minutes: 0       seconds: 1 timeout:   hours: 0   minutes: 0   seconds: 10   milliseconds: 0 alias: Ждать пока не станет на паузу

16. Перемотать фильм

Воспользуемся переменной media_position чтобы перемотать фильм в нужное место

Код
action: media_player.media_seek metadata: {} data:   seek_position: "{{ media_position }}" target:   entity_id: "{{ plextarget }}" alias: Перемотать фильм в нужное место

17. Установить уровень громкости

Воспользуемся переменной volume, чтобы установить уровень громкости.

Код
action: media_player.volume_set metadata: {} data:   volume_level: "{{ volume }}" target:   entity_id: "{{ target }}" alias: Установить уровень громкости как на источнике

Ну и на последок скрипт полностью.

Скрипт
alias: plex_transfer_movie_position sequence:   - variables:       target: >-         {{ 'media_player.lg_webos_kitchen' if         (states('media_player.lg_webos_bedroom') == 'on') else         'media_player.lg_webos_bedroom' }}       source: >-         {{ 'media_player.lg_webos_bedroom' if         (states('media_player.lg_webos_bedroom') == 'on') else         'media_player.lg_webos_kitchen' }}       plextarget: >-         {{ 'media_player.plex_plex_for_lg_lg_65up751c0zf' if         (states('media_player.lg_webos_bedroom') == 'on') else         'media_player.plex_plex_for_lg_lg_oled65c1rla' }}       volume: >-         {{ state_attr('media_player.lg_webos_bedroom', 'volume_level') if         (states('media_player.lg_webos_bedroom') == 'on') else         state_attr('media_player.lg_webos_kitchen', 'volume_level') }}   - action: media_player.media_pause     metadata: {}     data: {}     target:       entity_id: "{{ source }}"     alias: Поставить Plex на паузу, чтобы обновилась media_position   - wait_for_trigger:       - trigger: state         entity_id:           - media_player.plex_plex_for_lg_lg_65up751c0zf           - media_player.plex_plex_for_lg_lg_oled65c1rla         to: paused         for:           hours: 0           minutes: 0           seconds: 1     timeout:       hours: 0       minutes: 0       seconds: 10       milliseconds: 0   - variables:       media_content_id: >-         {{ state_attr('media_player.plex_plex_for_lg_lg_oled65c1rla',         'media_content_id') if (states('media_player.lg_webos_bedroom') == 'on')         else state_attr('media_player.plex_plex_for_lg_lg_65up751c0zf',         'media_content_id') }}       media_position: >-         {{ state_attr('media_player.plex_plex_for_lg_lg_oled65c1rla',         'media_position') if (states('media_player.lg_webos_bedroom') == 'on')         else state_attr('media_player.plex_plex_for_lg_lg_65up751c0zf',         'media_position') }}   - action: media_player.turn_off     metadata: {}     data: {}     target:       entity_id: "{{ source }}"     alias: Выключить ТВ источник   - action: media_player.turn_on     metadata: {}     data: {}     target:       entity_id: "{{ target }}"     alias: Включить ТВ назначение   - wait_for_trigger:       - trigger: state         entity_id:           - media_player.lg_webos_kitchen           - media_player.lg_webos_bedroom         from: "off"         to: "on"         for:           hours: 0           minutes: 0           seconds: 2     timeout:       hours: 0       minutes: 1       seconds: 0       milliseconds: 0     alias: Ждать пока ТВ не включится   - action: media_player.volume_set     metadata: {}     data:       volume_level: 0     target:       entity_id: "{{ target }}"     alias: Убавить громкость до 0   - action: media_player.select_source     metadata: {}     data:       source: Plex     target:       entity_id: "{{ target }}"     alias: Включить источник Plex   - variables:       timestamp: "{{ (now().timestamp() | int) }}"   - alias: Сканировать клиентов Plex каждые 5 секунд, пока не найдется клиент     repeat:       sequence:         - delay:             hours: 0             minutes: 0             seconds: 5             milliseconds: 0         - action: button.press           metadata: {}           data: {}           target:             entity_id: button.media_scan_clients       while:         - condition: and           conditions:             - condition: template               value_template: "{{ (now().timestamp() | int) - timestamp < 60 }}"             - condition: not               conditions:                 - condition: or                   conditions:                     - condition: state                       entity_id: media_player.plex_plex_for_lg_lg_oled65c1rla                       state: idle                     - condition: state                       entity_id: media_player.plex_plex_for_lg_lg_65up751c0zf                       state: idle     enabled: true   - action: telegram_bot.send_message     metadata: {}     data:       message: |-         media_content: {{ media_content_id }}           media_position: {{ media_position }}       target: -1001108604669     enabled: false   - action: media_player.play_media     metadata: {}     data:       media_content_id: "{{ media_content_id }}"       media_content_type: movie     target:       entity_id: "{{ plextarget }}"     alias: Включить фильм на Plex   - alias: Ждать пока статус Plex не станет Playing     wait_for_trigger:       - trigger: state         entity_id:           - media_player.plex_plex_for_lg_lg_oled65c1rla           - media_player.plex_plex_for_lg_lg_65up751c0zf         to: playing         for:           hours: 0           minutes: 0           seconds: 1     timeout:       hours: 0       minutes: 1       seconds: 0       milliseconds: 0   - action: media_player.media_pause     metadata: {}     data: {}     target:       entity_id: "{{ target }}"     alias: Поставить на паузу   - wait_for_trigger:       - trigger: state         entity_id:           - media_player.lg_webos_kitchen           - media_player.lg_webos_bedroom         to: paused         for:           hours: 0           minutes: 0           seconds: 1     timeout:       hours: 0       minutes: 0       seconds: 10       milliseconds: 0     alias: Ждать пока не станет на паузу   - action: media_player.media_seek     metadata: {}     data:       seek_position: "{{ media_position }}"     target:       entity_id: "{{ plextarget }}"     alias: Перемотать фильм в нужное место   - action: media_player.volume_set     metadata: {}     data:       volume_level: "{{ volume }}"     target:       entity_id: "{{ target }}"     alias: Установить уровень громкости как на источнике description: "" icon: mdi:movie

Буду рад вашим комментариям и советам по улучшению. Спасибо.


ссылка на оригинал статьи https://habr.com/ru/articles/871862/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *