Skip to content

Instantly share code, notes, and snippets.

@cassidyjames
Last active July 12, 2026 05:32
Show Gist options
  • Select an option

  • Save cassidyjames/f067fdc31ccad560e8997630e25fad8b to your computer and use it in GitHub Desktop.

Select an option

Save cassidyjames/f067fdc31ccad560e8997630e25fad8b to your computer and use it in GitHub Desktop.
Now Playing color blueprint for Home Assistant
blueprint:
name: Now Playing Color
description: >
Extracts the dominant color from a media player's album art whenever the
track changes, boosts it to a minimum brightness so it stays legible on
lights and displays, and stores the result as a hex string (e.g. "#e0803a")
in an input_text helper.
Consume the stored value in other automations, e.g.:
rgb_color: >-
{% set h = states('input_text.your_helper') | replace('#','') %}
[{{ h[0:2]|int(base=16) }}, {{ h[2:4]|int(base=16) }}, {{ h[4:6]|int(base=16) }}]
PREREQUISITES:
1. The `color_extractor` integration must be enabled.
2. The album art host must be allow-listed in configuration.yaml, e.g.
homeassistant:
allowlist_external_urls:
- "https://yt3.googleusercontent.com/"
3. An input_text helper to store the color (max length >= 7).
domain: automation
input:
media_player:
name: Media player
description: The player whose album art is sampled on each track change.
selector:
entity:
filter:
- domain: media_player
color_store:
name: Color storage helper
description: >
The input_text helper that receives the hex color. Its max length must
be at least 7 to fit a value like "#e0803a".
selector:
entity:
filter:
- domain: input_text
brightness_floor:
name: Minimum brightness
description: >
Dark album art produces dim, hard-to-see colors. The extracted color is
pushed up to at least this brightness (HSV value). 1.0 = always full
brightness, 0.0 = never adjust.
default: 0.85
selector:
number:
min: 0.0
max: 1.0
step: 0.05
mode: slider
mode: queued
max: 10
variables:
media_player: !input media_player
brightness_floor: !input brightness_floor
triggers:
- trigger: state
entity_id: !input media_player
attribute: media_title
conditions:
# Skip when there's no artwork to sample (idle player, stream without art).
- condition: template
value_template: "{{ state_attr(media_player, 'entity_picture') not in [none, ''] }}"
actions:
# 1. Pull the dominant color out of the album art.
- action: color_extractor.get_color
data:
color_extract_url: "{{ state_attr(media_player, 'entity_picture') }}"
response_variable: art_color
continue_on_error: true
# 2. Normalize to 0-1 fractions, falling back to white if extraction failed.
- variables:
r_frac: "{{ (art_color.color[0] if art_color is defined else 255) / 255 }}"
g_frac: "{{ (art_color.color[1] if art_color is defined else 255) / 255 }}"
b_frac: "{{ (art_color.color[2] if art_color is defined else 255) / 255 }}"
# 3. RGB -> HSV.
- variables:
cmax: "{{ [r_frac, g_frac, b_frac] | max }}"
cmin: "{{ [r_frac, g_frac, b_frac] | min }}"
- variables:
delta: "{{ cmax - cmin }}"
- variables:
hue: >-
{%- if delta == 0 -%}0{%- elif cmax == r_frac -%}{{ (60 * (((g_frac - b_frac) / delta) % 6)) }}{%- elif cmax == g_frac -%}{{ (60 * (((b_frac - r_frac) / delta) + 2)) }}{%- else -%}{{ (60 * (((r_frac - g_frac) / delta) + 4)) }}{%- endif -%}
sat: "{{ 0 if cmax == 0 else delta / cmax }}"
# The brightness floor: never let value drop below the configured minimum.
val: "{{ [cmax, brightness_floor | float] | max }}"
# 4. HSV -> RGB, now with the boosted value.
- variables:
c: "{{ val | float * sat | float }}"
m: "{{ val | float - (val | float * sat | float) }}"
x: "{{ (val | float * sat | float) * (1 - ((((hue | float) / 60) % 2) - 1) | abs) }}"
- variables:
r1: >-
{%- if hue|float < 60 -%}{{ c }}{%- elif hue|float < 120 -%}{{ x }}{%- elif hue|float < 180 -%}0{%- elif hue|float < 240 -%}0{%- elif hue|float < 300 -%}{{ x }}{%- else -%}{{ c }}{%- endif -%}
g1: >-
{%- if hue|float < 60 -%}{{ x }}{%- elif hue|float < 120 -%}{{ c }}{%- elif hue|float < 180 -%}{{ c }}{%- elif hue|float < 240 -%}{{ x }}{%- elif hue|float < 300 -%}0{%- else -%}0{%- endif -%}
b1: >-
{%- if hue|float < 60 -%}0{%- elif hue|float < 120 -%}0{%- elif hue|float < 180 -%}{{ x }}{%- elif hue|float < 240 -%}{{ c }}{%- elif hue|float < 300 -%}{{ c }}{%- else -%}{{ x }}{%- endif -%}
- variables:
final_r: "{{ (((r1 | float) + (m | float)) * 255) | round(0) | int }}"
final_g: "{{ (((g1 | float) + (m | float)) * 255) | round(0) | int }}"
final_b: "{{ (((b1 | float) + (m | float)) * 255) | round(0) | int }}"
# 5. Store the hex color.
- action: input_text.set_value
target:
entity_id: !input color_store
data:
value: "{{ '#%02x%02x%02x' | format(final_r, final_g, final_b) }}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment