Rating Plex song from within Home Assistant

I tried to build a script that would allow me to rate (stars) a song in Plex while it’s playing on a Home Assistant media player (eg chromecast etc)
I came up with below script using a rest command to the Plex Server. This is working but requires me to configure my Plex host and token separately. And I need to go into Plex to see what’s the current song rating, I didn’t found the song rating value yet within Home Assistant.
All ideas for improvement are welcome.

To understand below script, I have these helper variables defined:

  • input_select.media_player: a drop down to select the name of any of my media players (without the prefix media_player to make it readable in the frontend)
  • input_text.plex_token: token of my Plex media server (can be found in the url when using Plex website)
  • input_text.plex_host: hostname of my Plex media server
  • input_select.plex_rating: dropdown with possible rating 1 - 5 stars, or rating can be passed as a service_data: rating: element (see dashboard example below)
alias: Rate Plex song
sequence:
  - condition: template
    value_template: >-
      {{state_attr('media_player.' ~
      states('input_select.media_player'),'media_content_id') != None}}
  - variables:
      rating: "{{rating | default('')}}"
      rating_to_set: >-
        {% if rating == '' %}{{states('input_select.plex_rating')|int * 2}}{%
        else %}{{rating|int * 2}}{% endif %}
      media_player_entity: media_player.{{states('input_select.media_player') }}
      media_player_content_id: "{{state_attr(media_player_entity,'media_content_id')}}"
      media_player_content_id_part: "{{media_player_content_id.split('/')[-1]}}"
  - service: notify.persistent_notification
    continue_on_error: true
    data:
      title: Rating Plex song
      message: >-
        Rating Plex song: {{state_attr(media_player_entity,'media_artist')}}:
        {{state_attr(media_player_entity,'media_title')}}
        ({{media_player_content_id_part}}) with
        {{rating_to_set}} stars while playing on
        {{states('input_select.media_player') }}
  - service: rest_command.plex_rating
    continue_on_error: true
    data:
      plex_host: "{{states('input_text.plex_host') }}"
      item_id: "{{media_player_content_id_part }}"
      rating: "{{rating_to_set}}"
      plex_token: "{{states('input_text.plex_token') }}"
    enabled: true
mode: single
icon: mdi:plex

Configuration.yaml:

input_text:
    plex_host:
      initial: !secret plex_host
      mode: password
    plex_token:
      initial: !secret plex_token
      mode: password
rest:
  plex_rating:
      url: 'https://{{plex_host}}:32400/:/rate?key={{item_id}}&identifier=com.plexapp.plugins.library&rating={{rating}}&X-Plex-Token={{plex_token}}&X-Plex-Language=en'
      method: PUT
      content_type: "application/x-www-form-urlencoded"

Lovelace dashboard buttons to apply rating:

  - type: horizontal-stack
    cards:
      - show_name: true
        show_icon: false
        type: button
        tap_action:
          action: call-service
          service: script.rate_plex_song
          service_data:
            rating: 1
        entity: script.rate_plex_song
        name: ⭐
      - show_name: true
        show_icon: false
        type: button
        tap_action:
          action: call-service
          service: script.rate_plex_song
          service_data:
            rating: 2
        entity: script.rate_plex_song
        name: ⭐⭐
      - show_name: true
        show_icon: false
        type: button
        tap_action:
          action: call-service
          service: script.rate_plex_song
          service_data:
            rating: 3
        entity: script.rate_plex_song
        name: ⭐⭐⭐
      - show_name: true
        show_icon: false
        type: button
        tap_action:
          action: call-service
          service: script.rate_plex_song
          service_data:
            rating: 4
        entity: script.rate_plex_song
        name: ⭐⭐ ⭐⭐
      - show_name: true
        show_icon: false
        type: button
        tap_action:
          action: call-service
          service: script.rate_plex_song
          service_data:
            rating: 5
        entity: script.rate_plex_song
        name: ⭐⭐ ⭐⭐⭐

image

I achieved now, with similar approach, to add the Plex item (eg song) that is playing into a predefined playlist. Using the media_content_id and an id for each specific playlist in Plex.

Using below rest call in configuration.yaml:

rest:
  plex_playlist:
      url: 'https://{{plex_host}}:32400/playlists/{{plex_playlist_mapper}}/items?uri={{plex_server_uri}}%2Fcom.plexapp.plugins.library%2Flibrary%2Fmetadata%2F{{item_id}}&X-Plex-Token={{plex_token}}&X-Plex-Language=en'
      method: PUT
      content_type: "application/x-www-form-urlencoded"

The plex_playlist_mapper is a based on a template sensor that will denote the predefined playlist id, based on selected playlist name in an input_select or passed as a parameter playlist to the script. The playlist id can be retrieved out of the url on the Plex website by navigating to each playlist. If new playlists are created, this mapper template will need to maintained accordingly manually.
Example Plex playlist url: http://[...]playlist?key=%2Fplaylists%2F16075&context=source[...] → playlist id to set in the mapper: 16075

sensor:
  - platform: template
    sensors:
      plex_playlist_mapper:
        value_template: >
          {% set mapper =
            { 'playlist1':'16075',
              'playlist2':'12345',
              'playlist3':'23456'} %}
          {% set state = states('input_select.plexplaylist') %}
          {% set id = mapper[state] if state in mapper %}
          {{ id }}

The below service is used to call the rest command and it’s just mapping the right values by retrieving the media_content_id of a Plex object while playing on a device :

alias: Playlist Plex song
sequence:
  - condition: template
    value_template: >-
      {{state_attr('media_player.' ~
      states('input_select.media_player'),'media_content_id') != None}}
  - variables:
      playlist: "{{playlist | default('') }}"
      mapper:
        'playlist1':'16075',
        'playlist2':'12345',
        'playlist3':'23456'
      playlist_to_set: >-
        {% if playlist == '' %}{{mapper[states('input_select.plexplaylist')] if
        states('input_select.plexplaylist') in mapper}}{% else %}{{mapper[playlist]
        if playlist in mapper}}{% endif %}
      media_player_entity: media_player.{{states('input_select.media_player') }}
      media_player_content_id: "{{state_attr(media_player_entity,'media_content_id')}}"
      media_player_content_id_part: "{{media_player_content_id.split('/')[-1]}}"
  - service: notify.persistent_notification
    continue_on_error: true
    data:
      title: Playlist Plex song
      message: >-
        Playlist Plex song: {{state_attr(media_player_entity,'media_artist')}}:
        {{state_attr(media_player_entity,'media_title')}}
        ({{media_player_content_id_part}}) to playlist
        {{states('input_select.plexplaylist')}} while playing on
        {{states('input_select.media_player') }}
  - service: rest_command.plex_playlist
    continue_on_error: true
    data:
      plex_host: "{{states('input_text.plex_host') }}"
      item_id: "{{media_player_content_id_part }}"
      plex_playlist_mapper: "{{playlist_to_set}}"
      plex_token: "{{states('input_text.plex_token') }}"
      plex_server_uri: "{{states('input_text.plex_server_uri') }}"
    enabled: true
  - service: plex.refresh_library
    continue_on_error: true
    data:
      library_name: Music
    enabled: false
mode: single
icon: mdi:plex

I only needed an extra plex_server_uri static field to be able to build the correct plex rest call. this plex_server_uri can be retrieve out of a request using the Plex website. For me it looks like below vlaue in the request: server%3A%2F%2F12334567890abdef. You just need to store it once within secrets.yaml as it won’t change (I expect) for your Plex server.

configuration.yaml:

input_text:
    plex_host:
      initial: !secret plex_host
      mode: password
    plex_token:
      initial: !secret plex_token
      mode: password
    plex_server_uri:
      initial: !secret plex_server_uri
      mode: password

Lovelace dashboard buttons to assign the song to the playlist:

  - type: horizontal-stack
    cards:
      - show_name: true
        show_icon: false
        type: button
        tap_action:
          action: call-service
          service: script.playlist_plex_song
          service_data:
            playlist: playlist1
        name: playlist1
        entity: script.playlist_plex_song
      - show_name: true
        show_icon: false
        type: button
        tap_action:
          action: call-service
          service: script.playlist_plex_song
          service_data:
            playlist: playlist2
        name: playlist2
        entity: script.playlist_plex_song

Update 06/01/2024: instead of using sensor plex_playlist_mapper, the name of the playlist can now also be passed as a parameter to the script and the mapping of the playlist name to the id is performed in the script itself.

Set Plex song Artist & Title:

service: rest_command.plex_title_artist
continue_on_error: true
data:
  plex_host: "{{states('input_text.plex_host') }}"
  item_id: "{{media_player_content_id_part }}"
  plex_set_artist: "{{plex_set_artist}}"
  plex_set_title: "{{plex_set_title}}"
  plex_token: "{{states('input_text.plex_token') }}"
  plex_server_uri: "{{states('input_text.plex_server_uri') }}"
enabled: true
rest:
   plex_title_artist:
      url: 'https://{{plex_host}}:32400/library/sections/1/all?type=10&id={{item_id}}&includeExternalMedia=1&title.value={{plex_set_title}}&title.locked=1&originalTitle.value={{plex_set_artist}}&originalTitle.locked=1&X-Plex-Token={{plex_token}}&X-Plex-Language=en'
      method: PUT
      content_type: "application/x-www-form-urlencoded"

Further extended to get Plex song information (incl. user ratings stored in Plex):

rest:
  get_plex_song_details:
      url: 'https://{{plex_host}}:32400{{item_id}}?Accept-Language=en&includeMarkers=1&includeExternalMedia=1&asyncAugmentMetadata=1&includeRelated=1&checkFiles=1&X-Plex-Text-Format=json&X-Plex-Token={{plex_token}}&X-Plex-Language=en'
      method: GET
      headers:
        accept: "application/json, text/html"
      content_type: "application/x-www-form-urlencoded"

script:

alias: Plex get song info
sequence:
  - variables:
      media_player_entity: media_player.{{states('input_select.media_player') }}
      media_player_content_id: "{{state_attr(media_player_entity,'media_content_id')}}"
      media_player_content_id_part: "{{media_player_content_id.split('/')[-1]}}"
  - service: rest_command.get_plex_song_details
    continue_on_error: true
    data:
      plex_host: "{{states('input_text.plex_host') }}"
      item_id: "{{media_player_content_id }}"
      plex_token: "{{states('input_text.plex_token') }}"
      plex_server_uri: "{{states('input_text.plex_server_uri') }}"
    enabled: true
    response_variable: plex_response
  - variables:
      songinfo:
        value: |
          {{plex_response['content']['MediaContainer']['Metadata'][0]}}
  - stop: All done
    response_variable: songinfo
alias: Get artist song
sequence:
  - variables:
      media_player_entity: media_player.{{states('input_select.media_player') }}
  - choose:
      - conditions:
          - condition: template
            value_template: "{{is_state_attr(media_player_entity,'app_name','Plex')}}"
        sequence:
          - service: script.plex_get_song_info
            metadata: {}
            data: {}
            response_variable: plex_song_info
          - variables:
              artist: >-
                {% if plex_song_info.value['originalTitle'] and
                plex_song_info.value['originalTitle'] != ''
                %}{{plex_song_info.value['originalTitle']}}{% else
                %}{{plex_song_info.value['grandparentTitle']}}{% endif %}
              title: "{{plex_song_info.value['title']}}"
              rating: "{{plex_song_info.value['userRating']/2}}"
          - service: input_text.set_value
            metadata: {}
            data:
              value: "{{artist}}"
            target:
              entity_id: input_text.plex_set_artist
          - service: input_text.set_value
            metadata: {}
            data:
              value: "{{title}}"
            target:
              entity_id: input_text.plex_set_title
          - service: input_select.select_option
            metadata: {}
            data:
              option: "{{rating}}"
            target:
              entity_id: input_select.plex_rating