Auto-dims your lights when a movie starts playing on Plex
Webhook server for Tautulli. Supports Philips Hue, Govee, and Home Assistant lights. Runs as a background service on macOS.
| Event | Lights |
|---|---|
| Play / Resume | Dim to candlelight (~5%, warm amber) |
| Pause | Brighten slightly (~30%, soft amber) |
| Stop / End | Restore pre-playback state (fallback: normal mode) |
Works with movies and TV episodes. Ignores music, photos, and other media types.
Plex -> Tautulli (webhook) -> plex-lights.py (port 32500) -> Hue bridge / Govee API / Home Assistant API
Tautulli sends play/pause/stop webhooks to plex-lights. The script adjusts your lights based on the event. It snapshots light state at playback start, then restores that state when playback stops. Brightness levels, color temperatures, and RGB values are configurable per mode.
- Plex Media Server
- Tautulli (for webhooks)
- Python 3.8+
- Philips Hue bridge and/or Govee smart lights with a cloud API key
- Optional: Home Assistant with a long-lived access token
git clone https://github.com/liamvibecodes/plex-lights.git
cd plex-lights
# Configure your lights
cp config.json.example config.json
# Edit config.json with your bridge IP, API key, light IDs, etc.
# Or use interactive wizard:
bash install.sh --wizard
# Validate config before running
python3 plex-lights.py --validate-config
# Test it
python3 plex-lights.py
# Or test webhook flow without changing real lights
python3 plex-lights.py --dry-run
# One-shot setup + install as background service (auto-starts on boot)
bash install.sh --setupbash install.sh --setupWhat it does:
- Creates
.venv(local virtual environment) if missing - Installs dependencies from
requirements.txtinto.venv - Validates
config.jsonbefore service install - Installs/starts the launchd service using
.venv/bin/python3
If config.json does not exist, it is created from config.json.example and the installer exits so you can edit values, then rerun bash install.sh --setup.
bash install.sh --wizardWizard prompts for:
- Core settings (port, webhook token, player filter, dry-run default)
- Hue settings (bridge IP, API user, light IDs)
- Govee settings (API key, device, model)
- Home Assistant settings (URL, token, SSL verify, entities/scenes)
Then writes config.json. For full setup in one pass:
bash install.sh --wizard --setupCopy config.json.example to config.json and edit it:
Set "dry_run": true to simulate all light actions (no provider API calls).
{
"hue": {
"enabled": true,
"bridge_ip": "192.168.1.xxx",
"api_user": "your-hue-api-username",
"lights": [1, 2, 3]
}
}Finding your Hue API user: Follow the Hue API getting started guide to create an authorized username. Or if you already use Home Assistant, check your Hue integration for the bridge IP.
Finding light IDs: Open http://<bridge-ip>/api/<username>/lights in a browser. Each light has a numeric ID.
{
"govee": {
"enabled": true,
"api_key": "your-govee-api-key",
"device": "AA:BB:CC:DD:EE:FF:00:11",
"model": "H6076"
}
}Getting a Govee API key: Open the Govee Home app > Profile > About Us > Apply for API Key.
Finding device ID and model: Use the Govee API to list your devices, or check the Govee Home app under device settings.
{
"home_assistant": {
"enabled": true,
"url": "http://homeassistant.local:8123",
"token": "your-long-lived-access-token",
"verify_ssl": true,
"transition_seconds": 1,
"entity_ids": ["light.living_room_lamp"],
"mode_scenes": {
"movie": "",
"pause": "",
"normal": ""
}
}
}Use either approach:
entity_ids: plex-lights callslight.turn_onwith per-mode values.mode_scenes: setmovie,pause, and/ornormalscene entities and scenes are used for those modes.
Long-lived access token in Home Assistant:
- Profile (bottom-left) -> Security
- Long-Lived Access Tokens -> Create Token
By default, plex-lights restores your pre-playback lighting state on stop/end instead of forcing 100% brightness.
{
"state_restore": {
"enabled": true,
"fallback_mode": "normal",
"home_assistant_scene_id": "plex_lights_preplay",
"capture_govee_state": true
}
}enabled: turn snapshot/restore behavior on/off.fallback_mode: mode to apply if restore fails (movie,pause, ornormal).home_assistant_scene_id: scene ID used for Home Assistant snapshot/restore (scene.prefix optional).capture_govee_state: capture/restore Govee power/brightness/color state.
Notes:
- Home Assistant snapshot restore requires
home_assistant.entity_idssoscene.createcan snapshot entities. - If restore is disabled or unavailable, stop/end uses
state_restore.fallback_mode.
By default, plex-lights triggers on ALL players. To limit it to your TV:
- Start a movie and check the log for the player name
- Add it to config.json:
{
"tv_player_name": "Living Room TV"
}Set a shared token and require it on webhook requests:
{
"webhook_token": "change-this-to-a-random-secret"
}When set, plex-lights accepts either:
- Header:
X-Plex-Lights-Token: <your-token> - Query param:
?token=<your-token>
For Tautulli, easiest is adding the query parameter to the webhook URL.
Customize brightness and color for each state:
{
"modes": {
"movie": {
"hue_brightness": 13,
"hue_color_temp": 500,
"govee_brightness": 5,
"govee_color": {"r": 255, "g": 120, "b": 20}
}
}
}| Setting | Range | Notes |
|---|---|---|
hue_brightness |
1-254 | 1 = dimmest, 254 = brightest |
hue_color_temp |
153-500 | 153 = cool daylight, 500 = warm candlelight |
govee_brightness |
0-100 | Percentage |
govee_color |
RGB 0-255 | Only applies to color-capable Govee lights |
ha_brightness_pct |
0-100 | Home Assistant light.turn_on brightness |
ha_color_temp_kelvin |
1500-9000 | Used when ha_rgb_color is [] |
ha_rgb_color |
[] or RGB 0-255 |
[] disables RGB for that mode |
For simple setups without a config file:
export HUE_BRIDGE_IP=192.168.1.xxx
export HUE_API_USER=your-username
export HUE_LIGHTS=1,2,3
export GOVEE_API_KEY=your-key
export GOVEE_DEVICE=AA:BB:CC:DD:EE:FF:00:11
export GOVEE_MODEL=H6076
export HOME_ASSISTANT_URL=http://homeassistant.local:8123
export HOME_ASSISTANT_TOKEN=your-long-lived-access-token
export HOME_ASSISTANT_ENTITY_IDS=light.living_room_lamp,light.tv_bias
export HOME_ASSISTANT_MODE_SCENES=movie:scene.movie_mode,pause:scene.pause_mode
export HOME_ASSISTANT_VERIFY_SSL=true
export TV_PLAYER_NAME="Living Room TV"
export PLEX_LIGHTS_WEBHOOK_TOKEN="change-this-to-a-random-secret"
export PLEX_LIGHTS_DRY_RUN=false
export PLEX_LIGHTS_RESTORE_STATE_ENABLED=true
export PLEX_LIGHTS_RESTORE_FALLBACK_MODE=normal
export PLEX_LIGHTS_HA_SCENE_ID=plex_lights_preplay
export PLEX_LIGHTS_CAPTURE_GOVEE_STATE=true
python3 plex-lights.pyUse dry-run when validating Tautulli webhook payloads and mode mapping:
- CLI override:
python3 plex-lights.py --dry-run - Config:
"dry_run": true - Env:
PLEX_LIGHTS_DRY_RUN=true
In dry-run mode, webhook handling and logs run normally, but no Hue, Govee, or Home Assistant API requests are sent.
python3 plex-lights.py --validate-configUseful before bash install.sh --setup to catch config issues early.
- Open Tautulli > Settings > Notification Agents > Add a new notification agent
- Select Webhook
- Set the webhook URL to
http://localhost:32500?token=change-this-to-a-random-secret - Under Triggers, enable:
- Playback Start
- Playback Stop
- Playback Pause
- Playback Resume
- Under Data, set the JSON body for each trigger:
{
"event": "{action}",
"player": "{player}",
"title": "{title}",
"media_type": "{media_type}"
}- Save and test with "Test Notification"
Health endpoint:
curl http://localhost:32500/healthThe install script creates a launchd job that starts on boot and auto-restarts if it crashes:
# One-shot install (recommended)
bash install.sh --setup
# Install using existing Python environment
bash install.sh
# Restart
launchctl kickstart -k gui/$(id -u)/com.plex-lights
# Stop
launchctl bootout gui/$(id -u)/com.plex-lights
# Refresh .venv dependencies
bash install.sh --setup
# Rebuild config with wizard
bash install.sh --wizard
# Uninstall
bash install.sh --uninstall- mac-media-stack - Docker-based media server for macOS
- mac-media-stack-advanced - Full automated media server with Tautulli included
Built by @liamvibecodes