From 9543d65e077779c136d3c426288461893b57dd53 Mon Sep 17 00:00:00 2001 From: ltadeu6 Date: Wed, 15 Apr 2026 16:25:17 -0300 Subject: [PATCH] first commit --- luz-musica.py | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 luz-musica.py diff --git a/luz-musica.py b/luz-musica.py new file mode 100644 index 0000000..1c25d9b --- /dev/null +++ b/luz-musica.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import colorsys +import subprocess +import time +from io import BytesIO +from typing import Optional + +import requests +from PIL import Image + +HA_URL = "http://localhost:8123" +HA_TOKEN = "COLE_SEU_TOKEN_AQUI" +LIGHT_ENTITY = "light.luz_sala" + +# Ajustes visuais +BRIGHTNESS_PCT = 70 +SATURATION_BOOST = 1.15 +MIN_SATURATION = 0.20 +MIN_VALUE = 0.18 +REQUEST_TIMEOUT = 10 + + +def run_playerctl(field: str) -> str: + result = subprocess.run( + ["playerctl", "metadata", field], + capture_output=True, + text=True, + ) + if result.returncode != 0: + return "" + return result.stdout.strip() + + +def get_current_track_id() -> str: + title = run_playerctl("title") + artist = run_playerctl("artist") + art = run_playerctl("mpris:artUrl") + return f"{artist}::{title}::{art}" + + +def get_album_art_url() -> str: + return run_playerctl("mpris:artUrl") + + +def load_image_from_url(url: str) -> Image.Image: + if url.startswith("file://"): + local_path = url[7:] + return Image.open(local_path).convert("RGB") + + resp = requests.get(url, timeout=REQUEST_TIMEOUT) + resp.raise_for_status() + return Image.open(BytesIO(resp.content)).convert("RGB") + + +def good_pixels(img: Image.Image) -> list[tuple[int, int, int]]: + img = img.resize((80, 80)) + pixels = list(img.getdata()) + + filtered: list[tuple[int, int, int]] = [] + for r, g, b in pixels: + rf, gf, bf = r / 255.0, g / 255.0, b / 255.0 + h, s, v = colorsys.rgb_to_hsv(rf, gf, bf) + + # ignora pixels muito escuros e quase cinza + if v < MIN_VALUE: + continue + if s < MIN_SATURATION: + continue + + filtered.append((r, g, b)) + + return filtered + + +def dominant_color(img: Image.Image) -> tuple[int, int, int]: + pixels = good_pixels(img) + if not pixels: + # fallback simples + small = img.resize((50, 50)) + colors = small.getcolors(50 * 50) + if not colors: + return (255, 255, 255) + colors.sort(reverse=True) + return colors[0][1] + + # Quantiza para agrupar tons parecidos + buckets: dict[tuple[int, int, int], int] = {} + for r, g, b in pixels: + key = (r // 16 * 16, g // 16 * 16, b // 16 * 16) + buckets[key] = buckets.get(key, 0) + 1 + + best = max(buckets.items(), key=lambda x: x[1])[0] + return boost_color(best) + + +def boost_color(rgb: tuple[int, int, int]) -> tuple[int, int, int]: + r, g, b = rgb + rf, gf, bf = r / 255.0, g / 255.0, b / 255.0 + h, s, v = colorsys.rgb_to_hsv(rf, gf, bf) + + s = min(1.0, s * SATURATION_BOOST) + v = max(v, 0.45) + + nr, ng, nb = colorsys.hsv_to_rgb(h, s, v) + return (int(nr * 255), int(ng * 255), int(nb * 255)) + + +def set_light_color(rgb: tuple[int, int, int]) -> None: + response = requests.post( + f"{HA_URL}/api/services/light/turn_on", + headers={ + "Authorization": f"Bearer {HA_TOKEN}", + "Content-Type": "application/json", + }, + json={ + "entity_id": LIGHT_ENTITY, + "rgb_color": list(rgb), + "brightness_pct": BRIGHTNESS_PCT, + }, + timeout=REQUEST_TIMEOUT, + ) + response.raise_for_status() + + +def main() -> None: + print("Monitorando música atual. Ctrl+C para sair.") + last_track = "" + + while True: + try: + track_id = get_current_track_id() + + if not track_id or track_id == "::": + time.sleep(2) + continue + + if track_id != last_track: + art_url = get_album_art_url() + if not art_url: + print("Sem capa disponível para a faixa atual.") + last_track = track_id + time.sleep(2) + continue + + print(f"Nova faixa detectada: {track_id}") + img = load_image_from_url(art_url) + rgb = dominant_color(img) + print(f"Aplicando cor: {rgb}") + set_light_color(rgb) + last_track = track_id + + time.sleep(2) + + except KeyboardInterrupt: + print("\nEncerrado.") + break + except Exception as exc: + print(f"Erro: {exc}") + time.sleep(3) + + +if __name__ == "__main__": + main()