Skip to content

Commit

Permalink
Add option use_saturated_colors (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
ericmatte authored Aug 25, 2020
1 parent b7bad18 commit a4dba64
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 51 deletions.
6 changes: 4 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{
"editor.formatOnSave": true
}
"editor.formatOnSave": true,
"python.formatting.provider": "autopep8",
"python.formatting.autopep8Args": ["--max-line-length=120"]
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ media_lights_sync:
| `media_player` | False | string | | The entity_id of the media player to sync from. |
| `photo_attribute` | True | string | `entity_picture` | The state attribute containing the url to the thumbnail. Examples: `entity_picture`, `entity_picture_local`. |
| `ha_url` | True | string | `None` | The URL to your Home Assistant. Only useful if `photo_attribute` is a relative URL<sup id="ha-url">[1](#ha-url-note)</sup>. Examples: `https://my-ha.duckdns.org`, `http://192.168.1.123:8123`. |
| `use_saturated_colors` | True | string | `False` | Increase the saturation and brightness of the colors. |
| `use_current_brightness` | True | string | `False` | Do not change lights brightness. If `False`, it will always sets all lights to maximum brightness. |
| `condition` | True | object | | Sync lights only if the state of the condition entity is valid. |
| `condition.entity` | False | string | | The entity_id of the condition. |
Expand Down
107 changes: 58 additions & 49 deletions apps/media_lights_sync/media_lights_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,71 @@
import sys
import threading
import io
import colorsys

from PIL import Image
from urllib.parse import urljoin
from urllib.request import urlopen

if sys.version_info < (3, 0):
from urllib2 import urlopen
from urlparse import urljoin
else:
from urllib.parse import urljoin
from urllib.request import urlopen

class MediaLightsSync(hass.Hass):
"""MediaLightsSync class."""

def initialize(self):
"""Initialize the app and listen for media_player photo_attribute changes."""
self.ha_url = self.args.get("ha_url", None)
self.use_current_brightness = self.args.get("use_current_brightness", False)
self.condition = self.args.get("condition")
self.lights = self.args["lights"]
self.listen_state(self.change_lights_color, self.args["media_player"], attribute = self.args.get("photo_attribute", "entity_picture"))
"""MediaLightsSync class."""

def initialize(self):
"""Initialize the app and listen for media_player photo_attribute changes."""
self.ha_url = self.args.get("ha_url", None)
self.use_current_brightness = self.args.get("use_current_brightness", False)
self.condition = self.args.get("condition")
self.lights = self.args["lights"]
self.use_saturated_colors = self.args.get("use_saturated_colors", False)

photo_attribute = self.args.get("photo_attribute", "entity_picture")
self.listen_state(self.change_lights_color, self.args["media_player"], attribute=photo_attribute)

def change_lights_color(self, entity, attribute, oldUrl, newUrl, kwargs):
"""Callback when the photo_attribute has changed."""
if newUrl != oldUrl and newUrl is not None and self.can_change_colors():
rgb_colors = self.get_colors(self.format_ha_url(newUrl))
for i in range(len(self.lights)):
threading.Thread(target=self.set_light_rgb, args=(self.lights[i], rgb_colors[i])).start()

def change_lights_color(self, entity, attribute, oldUrl, newUrl, kwargs):
"""Callback when the photo_attribute has changed."""
if newUrl != oldUrl and newUrl is not None and self.can_change_colors():
rgb_colors = self.get_colors(self.format_ha_url(newUrl))
for i in range(len(self.lights)):
threading.Thread(target=self.set_light_rgb, args=(self.lights[i], rgb_colors[i])).start()
def can_change_colors(self):
"""Validate that light should be sync if a condition is set."""
return self.condition is None or self.get_state(self.condition["entity"]) == self.condition["state"]

def can_change_colors(self):
"""Validate that light should be sync if a condition is set."""
return self.condition is None or self.get_state(self.condition["entity"]) == self.condition["state"]
def set_light_rgb(self, light, color):
"""Change a light color."""
light_kwargs = {"rgb_color": color}
if self.use_saturated_colors:
light_kwargs["rgb_color"] = self.get_saturated_color(color)
if not self.use_current_brightness:
light_kwargs["brightness"] = 255
self.turn_on(light, **light_kwargs)

def set_light_rgb(self, light, color):
"""Change a light color."""
light_kwargs = { "rgb_color": color }
if not self.use_current_brightness:
light_kwargs["brightness"] = 255
self.turn_on(light, **light_kwargs)
def get_saturated_color(self, color):
"""Increase the saturation of the current color."""
hls = colorsys.rgb_to_hls(color[0] / 255, color[1] / 255, color[2] / 255)
rgb_saturated = colorsys.hls_to_rgb(hls[0], 0.5, 0.5)
return [int(rgb_saturated[0] * 255), int(rgb_saturated[1] * 255), int(rgb_saturated[2] * 255)]

def get_colors(self, url):
"""Get the palette of colors from url."""
fd = urlopen(url)
f = io.BytesIO(fd.read())
im = Image.open(f)
palette = im.quantize(colors=len(self.lights)).getpalette()
return self.extract_colors(palette, len(self.lights))
def get_colors(self, url):
"""Get the palette of colors from url."""
fd = urlopen(url)
f = io.BytesIO(fd.read())
im = Image.open(f)
palette = im.quantize(colors=len(self.lights)).getpalette()
return self.extract_colors(palette, len(self.lights))

def extract_colors(self, palette, colors):
"""Extract an amount of colors corresponding to the amount of lights in the configuration."""
return [palette[i:i + 3] for i in range(0, colors * 3, 3)]
def extract_colors(self, palette, colors):
"""Extract an amount of colors corresponding to the amount of lights in the configuration."""
return [palette[i:i + 3] for i in range(0, colors * 3, 3)]

def format_ha_url(self, url):
"""Append ha_url if this is a relative url"""
is_relative = not url.startswith("http")
if not is_relative:
return url
elif is_relative and self.ha_url is None:
raise ValueError("ha_url must be specified when using relative url for photo_attribute.")
else:
return urljoin(self.ha_url, url)
def format_ha_url(self, url):
"""Append ha_url if this is a relative url"""
is_relative = not url.startswith("http")
if not is_relative:
return url
elif is_relative and self.ha_url is None:
raise ValueError("ha_url must be specified when using relative url for photo_attribute.")
else:
return urljoin(self.ha_url, url)
1 change: 1 addition & 0 deletions info.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ media_lights_sync:
| `media_player` | False | string | | The entity_id of the media player to sync from. |
| `photo_attribute` | True | string | `entity_picture` | The state attribute containing the url to the thumbnail. Examples: `entity_picture`, `entity_picture_local`. |
| `ha_url` | True | string | `None` | The URL to your Home Assistant. Only useful if `photo_attribute` is a relative URL<sup id="ha-url">[1](#ha-url-note)</sup>. Examples: `https://my-ha.duckdns.org`, `http://192.168.1.123:8123`. |
| `use_saturated_colors` | True | string | `False` | Increase the saturation and brightness of the colors. |
| `use_current_brightness` | True | string | `False` | Do not change lights brightness. If `False`, it will always sets all lights to maximum brightness. |
| `condition` | True | object | | Sync lights only if the state of the condition entity is valid. |
| `condition.entity` | False | string | | The entity_id of the condition. |
Expand Down

0 comments on commit a4dba64

Please sign in to comment.