Improve active player artwork detection
This commit is contained in:
parent
9b07740b53
commit
d7af92f6b1
3 changed files with 146 additions and 14 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -21,6 +21,7 @@ result-*
|
||||||
# IDE / OS
|
# IDE / OS
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.codex
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
|
|
||||||
27
flake.lock
generated
Normal file
27
flake.lock
generated
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1776169885,
|
||||||
|
"narHash": "sha256-l/iNYDZ4bGOAFQY2q8y5OAfBBtrDAaPuRQqWaFHVRXM=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "4bd9165a9165d7b5e33ae57f3eecbcb28fb231c9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
132
luz-musica.py
132
luz-musica.py
|
|
@ -5,14 +5,14 @@ import colorsys
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Optional
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
HA_URL = "http://192.168.1.105:8123"
|
HA_URL = "http://192.168.1.105:8123"
|
||||||
HA_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkZjM1MmRhNjg1ZWM0MGJiYTc4YTZlNWY3ZmQ0YTA1YSIsImlhdCI6MTc3NjI4MTI0MiwiZXhwIjoyMDkxNjQxMjQyfQ.rR0Kfwk5Smr06_9KJSDn3YPXiRlokEZZFiAa_naNs78"
|
HA_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkZjM1MmRhNjg1ZWM0MGJiYTc4YTZlNWY3ZmQ0YTA1YSIsImlhdCI6MTc3NjI4MTI0MiwiZXhwIjoyMDkxNjQxMjQyfQ.rR0Kfwk5Smr06_9KJSDn3YPXiRlokEZZFiAa_naNs78"
|
||||||
LIGHT_ENTITY = "light.luz_sala"
|
LIGHT_ENTITY = "light.luzsala_luz_sala"
|
||||||
|
|
||||||
# Ajustes visuais
|
# Ajustes visuais
|
||||||
BRIGHTNESS_PCT = 70
|
BRIGHTNESS_PCT = 70
|
||||||
|
|
@ -22,9 +22,14 @@ MIN_VALUE = 0.18
|
||||||
REQUEST_TIMEOUT = 10
|
REQUEST_TIMEOUT = 10
|
||||||
|
|
||||||
|
|
||||||
def run_playerctl(field: str) -> str:
|
def run_playerctl(field: str, player: str | None = None) -> str:
|
||||||
|
command = ["playerctl"]
|
||||||
|
if player:
|
||||||
|
command.extend(["--player", player])
|
||||||
|
command.extend(["metadata", field])
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["playerctl", "metadata", field],
|
command,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
)
|
)
|
||||||
|
|
@ -33,15 +38,103 @@ def run_playerctl(field: str) -> str:
|
||||||
return result.stdout.strip()
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
def get_current_track_id() -> str:
|
def list_players() -> list[str]:
|
||||||
title = run_playerctl("title")
|
result = subprocess.run(
|
||||||
artist = run_playerctl("artist")
|
["playerctl", "-l"],
|
||||||
art = run_playerctl("mpris:artUrl")
|
capture_output=True,
|
||||||
return f"{artist}::{title}::{art}"
|
text=True,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
return []
|
||||||
|
return [line.strip() for line in result.stdout.splitlines() if line.strip()]
|
||||||
|
|
||||||
|
|
||||||
def get_album_art_url() -> str:
|
def player_status(player: str) -> str:
|
||||||
return run_playerctl("mpris:artUrl")
|
result = subprocess.run(
|
||||||
|
["playerctl", "--player", player, "status"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
return ""
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def choose_active_player() -> str:
|
||||||
|
for player in list_players():
|
||||||
|
if player_status(player) == "Playing":
|
||||||
|
return player
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def get_track_url(player: str) -> str:
|
||||||
|
return run_playerctl("xesam:url", player)
|
||||||
|
|
||||||
|
|
||||||
|
def get_player_name(player: str) -> str:
|
||||||
|
result = subprocess.run(
|
||||||
|
["playerctl", "--player", player, "metadata", "--format", "{{playerName}}"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
return player
|
||||||
|
return result.stdout.strip() or player
|
||||||
|
|
||||||
|
|
||||||
|
def youtube_video_id(url: str) -> str:
|
||||||
|
if not url:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
parsed = urlparse(url)
|
||||||
|
host = parsed.netloc.lower()
|
||||||
|
path = parsed.path.strip("/")
|
||||||
|
|
||||||
|
if host in {"youtu.be", "www.youtu.be"}:
|
||||||
|
return path.split("/", 1)[0]
|
||||||
|
|
||||||
|
if "youtube.com" not in host:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if path == "watch":
|
||||||
|
return parse_qs(parsed.query).get("v", [""])[0]
|
||||||
|
|
||||||
|
parts = path.split("/")
|
||||||
|
if len(parts) >= 2 and parts[0] in {"shorts", "live", "embed", "v"}:
|
||||||
|
return parts[1]
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def youtube_thumbnail_url(player: str) -> str:
|
||||||
|
video_id = youtube_video_id(get_track_url(player))
|
||||||
|
if not video_id:
|
||||||
|
return ""
|
||||||
|
return f"https://i.ytimg.com/vi/{video_id}/hqdefault.jpg"
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_track_id(player: str) -> str:
|
||||||
|
title = run_playerctl("title", player)
|
||||||
|
artist = run_playerctl("artist", player)
|
||||||
|
art = run_playerctl("mpris:artUrl", player)
|
||||||
|
url = get_track_url(player)
|
||||||
|
player_name = get_player_name(player)
|
||||||
|
return f"{player_name}::{artist}::{title}::{art or url}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_album_art_url(player: str) -> str:
|
||||||
|
art_url = run_playerctl("mpris:artUrl", player)
|
||||||
|
if art_url:
|
||||||
|
return art_url
|
||||||
|
|
||||||
|
track_url = get_track_url(player)
|
||||||
|
if youtube_video_id(track_url):
|
||||||
|
thumb_url = youtube_thumbnail_url(player)
|
||||||
|
if thumb_url:
|
||||||
|
print(f"Usando thumbnail do YouTube: {thumb_url}")
|
||||||
|
return thumb_url
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def load_image_from_url(url: str) -> Image.Image:
|
def load_image_from_url(url: str) -> Image.Image:
|
||||||
|
|
@ -54,9 +147,15 @@ def load_image_from_url(url: str) -> Image.Image:
|
||||||
return Image.open(BytesIO(resp.content)).convert("RGB")
|
return Image.open(BytesIO(resp.content)).convert("RGB")
|
||||||
|
|
||||||
|
|
||||||
|
def image_pixels(img: Image.Image) -> list[tuple[int, int, int]]:
|
||||||
|
if hasattr(img, "get_flattened_data"):
|
||||||
|
return list(img.get_flattened_data())
|
||||||
|
return list(img.getdata())
|
||||||
|
|
||||||
|
|
||||||
def good_pixels(img: Image.Image) -> list[tuple[int, int, int]]:
|
def good_pixels(img: Image.Image) -> list[tuple[int, int, int]]:
|
||||||
img = img.resize((80, 80))
|
img = img.resize((80, 80))
|
||||||
pixels = list(img.getdata())
|
pixels = image_pixels(img)
|
||||||
|
|
||||||
filtered: list[tuple[int, int, int]] = []
|
filtered: list[tuple[int, int, int]] = []
|
||||||
for r, g, b in pixels:
|
for r, g, b in pixels:
|
||||||
|
|
@ -130,14 +229,19 @@ def main() -> None:
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
track_id = get_current_track_id()
|
player = choose_active_player()
|
||||||
|
if not player:
|
||||||
|
time.sleep(2)
|
||||||
|
continue
|
||||||
|
|
||||||
|
track_id = get_current_track_id(player)
|
||||||
|
|
||||||
if not track_id or track_id == "::":
|
if not track_id or track_id == "::":
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if track_id != last_track:
|
if track_id != last_track:
|
||||||
art_url = get_album_art_url()
|
art_url = get_album_art_url(player)
|
||||||
if not art_url:
|
if not art_url:
|
||||||
print("Sem capa disponível para a faixa atual.")
|
print("Sem capa disponível para a faixa atual.")
|
||||||
last_track = track_id
|
last_track = track_id
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue