I used to use This Project but after I stopped using App Daemon and moved to Node-RED I have been meaning to get this up and running again. I came across This on reddit and it gave me the inspiration to get in and try to make this work with Alexa as I no longer use Chromecast for Spotify streaming.
The following is using Node-RED connected to Home Assistant and uses an external Library called color thief to get the prominent colour from a url image.
Steps:
-
SSH into your machine/container running node-red
-
Install color-thief-node
npm i color-thief-node
-
Ensure your nodered/settings.js contains a block to import the new color-thief-node library:
functionGlobalContext: { ColorThief:require('color-thief-node') },
Now, in Node-Red
- Start a flow with the State Changed HA node. Set it to target the Media Player you want to monitor (e.g. media_player.echo_1)
- Connect this node to a Current State node with the same entity_id (this will form part of the loop we create later on)
- Then connects this with a switch node with the following 2 outputs of āplayingā & āpausedā
-
Connect this node to a Function node with the following code replacing the section for imageURL with your own instance details:
//Only run if Music is the active content if (msg.payload == "playing"){ //Import the ColorThief module from the alias we set in Settings.js const { getColorFromURL } = global.get('ColorThief'); //Get the file path to the Alexa's currently playing track out of the msg data from our first node imageURL = "YOURPUBLICIP:YOURPORT" + msg.data.attributes.entity_picture; //get the colour from the image we found. This function is asynchronous, which is why we need the .then function, which acts once the result is available and then forward the data on to the rest of the flow const mainColor = getColorFromURL(imageURL) .then(values => { node.send({payload:values}); }); } //Because this flow is asynchronous, we do not return the message object, we just call return; the message object gets sent by node.send() once the async call completes return;
These next steps are directly from the reddit post and I liked what he did you can either use or not its up to you.
-
You now have the dominant color of the current album art as an array of RGB values. For me though, they werenāt quite rich enough, so I added a few steps before setting the bulb:
-
In Node-Red, go to Menu>Manage Palette>Install and install node-red-contrib-color-convert
-
Add a color convert node to our flow, from RGB to HSV or HSL
-
Follow on with the following code in another function node:
//These 3 lines boost the saturation, relative to how de-saturated a colour is satDiff = 100 - msg.payload[1]; saturation = msg.payload[1] + (satDiff /2); //But don't let the saturation be > 100 else HA with throw an error if (saturation > 100) {saturation=100;} //Construct a payload to send to the light using our values msg.payload = { "data": { "entity_id" : "light.color_bulb", "brightness": "255", "hs_color": [msg.payload[0], saturation] } } return msg;
where light.color_bulb is the entity name of your color lightbulb in home-assistant. You may wish to consider a different brightness. If you want to leave the saturation values alone, ignore the lines before msg.payload, and replace saturation in the hs_color with msg.payload[1].
- Lastly, use a call service node to call light.turn_on. Leave the data field blank, as it will be filled with our payload.
The problem I encountered from here is that the URL will not change between songs as the function as it stands only acts on the state of the media_player is playing. In order to force the URL to update for each song we need to add the following loop into the flow.
Off the āplayingā output of our switch node created earlier we need to create 2 functions one to get the current song length and one to get the current song position.
-
create a function node with the following code to retrieve the total song time in secs:
newmsg = {}; if (msg.payload == "playing") { newmsg.payload = msg.data.attributes.media_duration; } else { newmsg.payload = "False"; } return newmsg;
-
now create a second function node to get the current position in time of the playing track:
newmsg = {}; if (msg.payload == "playing") { newmsg.payload = msg.data.attributes.media_position; } else { newmsg.payload = "False"; } return newmsg;
-
Now we need to create a join node to bring these msg together set the mode - manual to create an Array.
-
next connect to a function node in the function we will subtract the current playing position from the total song time to get when to set our delay to poll for the next artwork URL:
var contA= msg.payload[0]; var contB= msg.payload[1]; msg.payload = (contA - contB); return msg
-
Now we need to put the output into a change node to add the msg.topic of delay so we can variably set the following delay Node to the length of the song remaining ( basically being smart about polling only when a new song starts to get the new URL RGB Colours)
This should all look like this
Lastly you could act on the āpausedā output of the switch node to turn off the lights or set them back to normal settings. I use a time range to determine whether I turn the lights off or set them back to there normal colour.
To use this for Kodi replace with the media_player id of your Kodiand it will essentially do the same thing but for any artwork so you can sync it with TV and Movie Posters also.
Thanks for reading.