-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathplexlifx.py
299 lines (236 loc) · 8.05 KB
/
plexlifx.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#
# Author: Bailey Belvis (https://github.com/philosowaffle)
#
# Webhook to modify the state of your lifx lights through Plex.
# https://support.plex.tv/hc/en-us/articles/115002267687-Webhooks
#
import os
import sys
import json
import logging
import hashlib
import shutil
import numpy
from flask import Flask, abort, request
from random import shuffle
from pifx import PIFX
from colorthief import ColorThief
import config_helper as config
##############################
# Logging Setup
##############################
if config.ConfigSectionMap("LOGGER")['logfile'] is None:
logger.error("Please specify a path for the logfile.")
sys.exit(1)
logger = logging.getLogger('plex_lifx_webhook')
logger.setLevel(logging.DEBUG)
# Formatter
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s: %(message)s')
# File Handler
file_handler = logging.FileHandler(config.ConfigSectionMap("LOGGER")['logfile'])
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
# Console Handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.addHandler(console_handler)
logger.debug("Starting Plex+Lifx Webhook :)")
##############################
# Flask Setup
##############################
flask_port = int(config.ConfigSectionMap("SERVER")['flaskport'])
upload_folder = os.getcwd() + '/tmp'
if flask_port is None:
logger.info("Using default Falsk Port: 5000")
flask_port = 5000
flask_debug = False
##############################
# Plex Setup
##############################
plex_config = config.ConfigSectionMap("PLEX")
filtered_players = [] if plex_config['ignoreplayeruuids'] == "none" else plex_config['ignoreplayeruuids'].split(',')
logger.debug("Filtered Players: " + filtered_players.__str__())
local_players_only = True if plex_config['localplayersonly'] is None else plex_config['localplayersonly']
events = [
'media.play',
'media.pause',
'media.resume',
'media.stop'
]
##############################
# LIFX Setup
##############################
lifx_config = config.ConfigSectionMap("LIFX")
brightness = float(lifx_config['brightness']) if float(lifx_config['brightness']) else .35
duration = float(lifx_config['duration']) if float(lifx_config['duration']) else 2.0
num_colors = int(lifx_config['numcolors']) if int(lifx_config['numcolors']) else 4
color_quality = int(lifx_config['colorquality']) if int(lifx_config['colorquality']) else 1
if not lifx_config['apikey']:
logger.error("Missing LIFX API Key")
exit(1)
else:
lifx_api_key = lifx_config['apikey']
logger.debug("LIFX API Key: " + lifx_api_key)
pifx = PIFX(lifx_api_key)
lights = []
if lifx_config['lights']:
lights_use_name = True
lights = lifx_config['lights'].split(',')
tmp = []
for light in lights:
tmp.append(light.strip())
lights = tmp
else:
lights_detail = pifx.list_lights()
for light in lights_detail:
lights.append(light['id'])
shuffle(lights)
scenes_details = pifx.list_scenes()
scenes = dict()
for scene in scenes_details:
scenes[scene['name']] = scene['uuid']
logger.debug(scenes)
logger.debug(lights)
default_pause_theme = lifx_config['defaultpausetheme']
default_play_theme = lifx_config['defaultplaytheme']
default_pause_uuid = scenes[default_pause_theme]
default_play_uuid = scenes[default_play_theme]
number_of_lights = len(lights)
if number_of_lights < num_colors:
num_colors = number_of_lights
light_groups = numpy.array_split(numpy.array(lights), num_colors)
logger.debug("Number of Lights: " + color_quality.__str__())
logger.debug("Number of Colors: " + num_colors.__str__())
logger.debug("Color Quality: " + color_quality.__str__())
##############################
# Helper Methods
##############################
##############################
# Server
##############################
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = upload_folder
@app.route("/", methods=['POST'])
def inbound_request():
# read the json webhook
data = request.form
try:
webhook = json.loads(data['payload'])
except:
logger.error("No payload found")
abort(400)
logger.debug(webhook)
# Extract the event
try:
event = webhook['event']
logger.info("Event: " + event)
except KeyError:
logger.error("No event found in the json")
return "No event found in the json"
# Only perform action for event play/pause/resume/stop for TV and Movies
if not event in events:
# Take no action
return 'ok'
# Extract the media type
try:
media_type = webhook['Metadata']['type']
logger.debug("Media Type: " + media_type)
except KeyError:
logger.error("No media type found in the json")
return "No media type found in the json"
if (media_type != "movie") and (media_type != "episode"):
logger.debug("Media type was not movie or episode, ignoring.")
return 'ok'
# Unless we explicitly said we want to enable remote players,
# Let's filter events
if local_players_only:
is_player_local = True # Let's assume it's true
try:
is_player_local = webhook['Player']['local']
logger.debug("Local Player: " + is_player_local.__str__())
except Exception as e:
logger.info("Not sure if this player is local or not :(")
logger.debug("Failed to parse [Player][local] - " + e.__str__())
if not is_player_local:
logger.info("Not allowed. This player is not local.")
return 'ok'
try:
player_uuid = webhook['Player']['uuid'].__str__()
logger.debug("Player UUID: " + player_uuid)
except:
logger.error("No player uuid found")
return 'ok'
# If we configured only specific players to be able to play with the lights
if filtered_players:
try:
if player_uuid in filtered_players:
logger.info(player_uuid + " player is not able to play with the lights")
return 'ok'
except Exception as e:
logger.error("Failed to check uuid - " + e.__str__())
# Extract media guid
try:
media_guid = webhook['Metadata']['guid']
logger.debug("Media Guid: " + media_guid)
# Clean guid
media_guid = hashlib.sha224(media_guid.encode('utf-8')).hexdigest()
logger.debug("Clean Media Guid: " + media_guid)
except KeyError:
logger.error("No media guid found")
return "No media guid found"
# Get Thumbnail if any
thumb_folder = os.path.join(upload_folder, media_guid)
thumb_path = os.path.join(thumb_folder, "thumb.jpg")
if event == 'media.stop':
if os.path.isdir(thumb_folder):
logger.debug("Removing Directory: " + thumb_folder)
shutil.rmtree(thumb_folder)
pifx.activate_scene(default_pause_uuid)
return 'ok'
if event == 'media.pause':
pifx.activate_scene(default_pause_uuid)
return 'ok'
if event == 'media.resume' or event == "media.play":
# If the file already exists then we don't need to re-upload the image
if not os.path.exists(thumb_folder):
# Get Thumb
if 'thumb' not in request.files:
logger.info("No file found in request")
pifx.activate_scene(default_play_uuid)
return 'ok'
try:
file = request.files['thumb']
logger.debug("Making Directory: " + thumb_folder)
os.makedirs(thumb_folder)
file.save(thumb_path)
except Exception as e:
logger.error(e)
# Determine Color Palette for Lights
color_thief = ColorThief(thumb_path)
palette = color_thief.get_palette(color_count=num_colors, quality=color_quality)
logger.debug("Color Palette: " + palette.__str__())
# Set Color Palette
pifx.set_state(selector='all', power="off")
for index in range(len(light_groups)):
try:
color = palette[index]
light_group = light_groups[index]
logger.debug(light_group)
logger.debug(color)
color_rgb = ', '.join(str(c) for c in color)
color_rgb = "rgb:" + color_rgb
color_rgb = color_rgb.replace(" ", "")
for light_id in light_group:
if lights_use_name:
selector = "label:" + light_id
else:
selector = light_id
logger.debug("Setting light: " + selector + " to color: " + color_rgb)
pifx.set_state(selector=selector, power="on", color=color_rgb, brightness=brightness, duration=duration)
except Exception as e:
logger.error(e)
return 'ok'
if __name__ == "__main__":
app.run(host='0.0.0.0', port=flask_port, debug=flask_debug)