Skip to content

Instantly share code, notes, and snippets.

@cees-elzinga
Created February 11, 2014 20:36
Show Gist options
  • Save cees-elzinga/8943531 to your computer and use it in GitHub Desktop.
Save cees-elzinga/8943531 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
#
# Ambilight for XBMC picture slideshow
#
# proof-of-concept
#
# Depends on:
# - Python Image library
# - XBMC HTTP API enabled in System -> Services -> Webserver
# - Allow Control of XBMC via HTTP enabled
import Image
import requests
import json
import time
import colorsys
# XBMC HTTP API settings
# Copy from System -> Services -> Webserver
HOST = "localhost"
PORT = "8080"
AUTH = ("username", "password")
# Philips Hue Bridge settings
IP = "192.168.1.1"
KEY = "abcdef0123456789abcdef0123456789"
LIGHT = 2
# Global settings
SIZE = (32, 32)
fmtRGBA = True
class Settings():
color_bias = 36
settings = Settings()
# FIXME: Test executeJSONRPC function
# The Python transport can only be used by XBMC addons through the executeJSONRPC
# method provided by the xbmc python library. As it must be available to every addon
# in an XBMC installation it must not be enabled or disabled by the user.
r = requests.get("http://{}:{}/jsonrpc".format(HOST, PORT), auth=AUTH)
if not r.ok: raise Exception("Could not connect")
def rpc(data):
headers = {"Content-Type": "application/json"}
return requests.post(
"http://{}:{}/jsonrpc".format(HOST, PORT),
data=json.dumps(data),
auth=AUTH,
headers=headers,
)
def get_slideshow_player():
r = rpc({"jsonrpc": "2.0", "method": "Player.GetActivePlayers", "id": 1})
if not r.ok:
return False
r_json = json.loads(r.text)
if len(r_json['result']) == 0:
return False
if r_json['result'][0]['type'] != "picture":
return False
return r_json['result'][0]['playerid']
def get_slideshow_picture():
r = rpc({"jsonrpc": "2.0",
"method": "Player.GetItem",
"params": {
"properties": ["file"],
"playerid": player_id
},
"id": "PictureGetItem",
})
r_json = json.loads(r.text)
return r_json['result']['item']['file']
def get_pixels(filename):
# convert PixelArray to flat list of RGBARGBA etc
all_pixels = []
im = Image.open(filename)
pixels = im.load()
im.thumbnail(SIZE, Image.ANTIALIAS)
for x in range(SIZE[0]):
for y in range(SIZE[1]):
# Assume RGB
all_pixels.append(pixels[x, y][0])
all_pixels.append(pixels[x, y][1])
all_pixels.append(pixels[x, y][2])
# requires an alpha value (but it's not used), set is to zero
all_pixels.append(0)
return all_pixels
def set_light(hue, sat, bri, dur=20):
data = json.dumps({
"on": True,
"hue": hue,
"sat": sat,
"bri": bri,
"transitiontime": dur
})
requests.put("http://%s/api/%s/lights/%s/state" % \
(IP, KEY, LIGHT), data=data)
#
#
# Code re-used from script.xbmc.ambilight.hue add-on
#
#
class Screenshot:
def __init__(self, pixels, capture_width, capture_height):
self.pixels = pixels
self.capture_width = capture_width
self.capture_height = capture_height
def most_used_spectrum(self, spectrum, saturation, value, size, overall_value):
# color bias/groups 6 - 36 in steps of 3
colorGroups = settings.color_bias
if colorGroups == 0:
colorGroups = 1
colorHueRatio = 360 / colorGroups
hsvRatios = []
hsvRatiosDict = {}
for i in range(360):
if spectrum.has_key(i):
#shift index to the right so that groups are centered on primary and secondary colors
colorIndex = int(((i+colorHueRatio/2) % 360)/colorHueRatio)
pixelCount = spectrum[i]
if hsvRatiosDict.has_key(colorIndex):
hsvr = hsvRatiosDict[colorIndex]
hsvr.average(i/360.0, saturation[i], value[i])
hsvr.ratio = hsvr.ratio + pixelCount / float(size)
else:
hsvr = HSVRatio(i/360.0, saturation[i], value[i], pixelCount / float(size))
hsvRatiosDict[colorIndex] = hsvr
hsvRatios.append(hsvr)
colorCount = len(hsvRatios)
if colorCount > 1:
# sort colors by popularity
hsvRatios = sorted(hsvRatios, key=lambda hsvratio: hsvratio.ratio, reverse=True)
# logger.debuglog("hsvRatios %s" % hsvRatios)
#return at least 3
if colorCount == 2:
hsvRatios.insert(0, hsvRatios[0])
hsvRatios[0].averageValue(overall_value)
hsvRatios[1].averageValue(overall_value)
hsvRatios[2].averageValue(overall_value)
return hsvRatios
elif colorCount == 1:
hsvRatios[0].averageValue(overall_value)
return [hsvRatios[0]] * 3
else:
return [HSVRatio()] * 3
def spectrum_hsv(self, pixels, width, height):
spectrum = {}
saturation = {}
value = {}
size = int(len(pixels)/4)
pixel = 0
i = 0
s, v = 0, 0
r, g, b = 0, 0, 0
tmph, tmps, tmpv = 0, 0, 0
for i in range(size):
if fmtRGBA:
r = pixels[pixel]
g = pixels[pixel + 1]
b = pixels[pixel + 2]
else: #probably BGRA
b = pixels[pixel]
g = pixels[pixel + 1]
r = pixels[pixel + 2]
pixel += 4
tmph, tmps, tmpv = colorsys.rgb_to_hsv(float(r/255.0), float(g/255.0), float(b/255.0))
s += tmps
v += tmpv
# skip low value and saturation
if tmpv > 0.25:
if tmps > 0.33:
h = int(tmph * 360)
# logger.debuglog("%s \t set pixel r %s \tg %s \tb %s" % (i, r, g, b))
# logger.debuglog("%s \t set pixel h %s \ts %s \tv %s" % (i, tmph*100, tmps*100, tmpv*100))
if spectrum.has_key(h):
spectrum[h] += 1 # tmps * 2 * tmpv
saturation[h] = (saturation[h] + tmps)/2
value[h] = (value[h] + tmpv)/2
else:
spectrum[h] = 1 # tmps * 2 * tmpv
saturation[h] = tmps
value[h] = tmpv
overall_value = v / float(i)
# s_overall = int(s * 100 / i)
return self.most_used_spectrum(spectrum, saturation, value, size, overall_value)
class HSVRatio:
cyan_min = float(4.5/12.0)
cyan_max = float(7.75/12.0)
def __init__(self, hue=0.0, saturation=0.0, value=0.0, ratio=0.0):
self.h = hue
self.s = saturation
self.v = value
self.ratio = ratio
def average(self, h, s, v):
self.h = (self.h + h)/2
self.s = (self.s + s)/2
self.v = (self.v + v)/2
def averageValue(self, overall_value):
if self.ratio > 0.5:
self.v = self.v * self.ratio + overall_value * (1-self.ratio)
else:
self.v = (self.v + overall_value)/2
def hue(self, fullSpectrum):
if fullSpectrum != True:
if self.s > 0.01:
if self.h < 0.5:
#yellow-green correction
self.h = self.h * 1.17
#cyan-green correction
if self.h > self.cyan_min:
self.h = self.cyan_min
else:
#cyan-blue correction
if self.h < self.cyan_max:
self.h = self.cyan_max
h = int(self.h*65535) # on a scale from 0 <-> 65535
s = int(self.s*255)
v = int(self.v*255)
# Disabled for this script
#if v < hue.settings.ambilight_min:
# v = hue.settings.ambilight_min
#if v > hue.settings.ambilight_max:
# v = hue.settings.ambilight_max
return h, s, v
def __repr__(self):
return 'h: %s s: %s v: %s ratio: %s' % (self.h, self.s, self.v, self.ratio)
#
#
# End code re-used from script.xbmc.ambilight.hue add-on
#
#
last = None
while True:
player_id = get_slideshow_player()
if not player_id:
print "[*] No slideshow running, waiting 3s"
time.sleep(3)
continue
time.sleep(1.0/20)
picture = get_slideshow_picture()
if last == picture:
continue
print "[*] New picture"
last = picture
screen = Screenshot(get_pixels(picture), SIZE[0], SIZE[1])
hsvRatios = screen.spectrum_hsv(screen.pixels, screen.capture_width, screen.capture_height)
h, s, v = hsvRatios[0].hue(True)
set_light(h, s, v, 10)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment