Finalize winamp_mpris.py with window title and web UI hybrid logic

This commit is contained in:
2026-03-19 04:00:57 -04:00
parent e59380c929
commit 06f43992d6

View File

@@ -2,6 +2,7 @@
import requests
import time
import re
import subprocess
from threading import Thread
from pydbus import SessionBus
from pydbus.generic import signal
@@ -75,6 +76,7 @@ class WinampMPRIS:
def __init__(self):
self._title = "Winamp"
self._artist = "Unknown"
self._album = ""
self._status = "Stopped"
self._last_position_us = 0
self._total_length_us = 0
@@ -156,19 +158,37 @@ class WinampMPRIS:
'mpris:length': GLib.Variant('x', self._total_length_us),
'xesam:title': GLib.Variant('s', self._title),
'xesam:artist': GLib.Variant('as', [self._artist]),
'xesam:album': GLib.Variant('s', ''),
'xesam:album': GLib.Variant('s', self._album),
'mpris:artUrl': GLib.Variant('s', 'https://webamp.org/favicon.ico')
}
def parse_time_to_us(time_str):
"""Parses MM:SS or H:MM:SS to microseconds."""
parts = list(map(int, time_str.split(':')))
if len(parts) == 2: # MM:SS
return (parts[0] * 60 + parts[1]) * 1_000_000
elif len(parts) == 3: # H:MM:SS
return (parts[0] * 3600 + parts[1] * 60 + parts[2]) * 1_000_000
try:
parts = list(map(int, time_str.split(':')))
if len(parts) == 2: # MM:SS
return (parts[0] * 60 + parts[1]) * 1_000_000
elif len(parts) == 3: # H:MM:SS
return (parts[0] * 3600 + parts[1] * 60 + parts[2]) * 1_000_000
except Exception:
pass
return 0
def get_winamp_window_title():
"""Extracts the Winamp window title using wmctrl."""
try:
output = subprocess.check_output(["wmctrl", "-l"], stderr=subprocess.STDOUT).decode('utf-8')
for line in output.splitlines():
# Match the typical Winamp window title pattern
if " - Winamp" in line:
# wmctrl output: 0x03000001 0 Compy-686 ARTMS<Dall>Unf/Air | 0:06/2:45 - Winamp
parts = line.split(None, 3)
if len(parts) >= 4:
return parts[3]
except Exception:
pass
return None
def update_loop(player):
last_known_title = ""
last_known_status = ""
@@ -176,74 +196,81 @@ def update_loop(player):
while True:
try:
r = requests.get(f"{BASE_URL}/main", auth=AUTH, timeout=2)
if r.status_code == 200:
offline_logged = False
soup = BeautifulSoup(r.text, 'html.parser')
p_tags = soup.find_all('p')
status_raw = p_tags[0].text if p_tags else ""
# 1. Try Window Title (Primary)
window_title = get_winamp_window_title()
title_parsed = False
new_status = "Stopped"
if "Playing" in status_raw: new_status = "Playing"
elif "Paused" in status_raw: new_status = "Paused"
if window_title:
# Pattern: ArtistAlbumTitle | 0:06/2:45 - Winamp
match = re.search(r"(.*?) \| (\d+:?\d*:\d*)/(\d+:?\d*:\d*) - Winamp", window_title)
if match:
metadata_raw = match.group(1).strip()
new_pos_us = parse_time_to_us(match.group(2))
new_len_us = parse_time_to_us(match.group(3))
new_artist = "Unknown"
new_title = "Winamp"
new_pos_us = 0
new_len_us = 0
# Split metadata (ArtistAlbumTitle)
meta_parts = [p.strip() for p in re.split(r'[–—-]', metadata_raw)]
# Metadata match
match_meta = re.search(r"track \d+ - (.*?) - (.*?) -", status_raw)
if match_meta:
new_artist = match_meta.group(1).strip()
new_title = match_meta.group(2).strip()
elif "track" in status_raw:
new_title = status_raw.split('-')[0].replace("Playing track ", "").strip()
if len(meta_parts) >= 3:
player._artist = meta_parts[0]
player._album = meta_parts[1]
player._title = " ".join(meta_parts[2:])
elif len(meta_parts) == 2:
player._artist = meta_parts[0]
player._title = meta_parts[1]
player._album = ""
else:
player._title = metadata_raw
player._artist = "Unknown"
player._album = ""
# Time match: (1:06 / 2:48)
match_time = re.search(r"\((\d+:?\d*:\d+) / (\d+:?\d*:\d+)\)", status_raw)
if match_time:
new_pos_us = parse_time_to_us(match_time.group(1))
new_len_us = parse_time_to_us(match_time.group(2))
player._last_position_us = new_pos_us
player._total_length_us = new_len_us
player._last_update_ts = time.time()
title_parsed = True
# Update internal state
player._status = new_status
player._artist = new_artist
player._title = new_title
player._last_position_us = new_pos_us
player._total_length_us = new_len_us
player._last_update_ts = time.time()
# 2. Poll Web UI (Fallback and Controls/Status)
try:
r = requests.get(f"{BASE_URL}/main", auth=AUTH, timeout=1)
if r.status_code == 200:
offline_logged = False
soup = BeautifulSoup(r.text, 'html.parser')
p_tags = soup.find_all('p')
status_raw = p_tags[0].text if p_tags else ""
if new_status != last_known_status or new_title != last_known_title:
last_known_status = new_status
last_known_title = new_title
new_status = "Stopped"
if "Playing" in status_raw: new_status = "Playing"
elif "Paused" in status_raw: new_status = "Paused"
print(f"UPDATE: [{player._status}] {player._artist} - {player._title} ({match_time.group(1) if match_time else '0:00'})")
player._status = new_status
player.PropertiesChanged(
"org.mpris.MediaPlayer2.Player",
{
"PlaybackStatus": player.PlaybackStatus,
"Metadata": player.Metadata
},
[]
)
if not title_parsed:
# Web UI Fallback for metadata
match_meta = re.search(r"track \d+ - (.*?) - (.*?) -", status_raw)
if match_meta:
player._artist = match_meta.group(1).strip()
player._title = match_meta.group(2).strip()
elif "track" in status_raw:
player._title = status_raw.split('-')[0].replace("Playing track ", "").strip()
except (requests.exceptions.RequestException, Exception) as e:
if not offline_logged:
print(f"Winamp Web Interface offline or unreachable: {e}")
offline_logged = True
match_time = re.search(r"\((\d+:?\d*:\d+) / (\d+:?\d*:\d+)\)", status_raw)
if match_time:
player._last_position_us = parse_time_to_us(match_time.group(1))
player._total_length_us = parse_time_to_us(match_time.group(2))
player._last_update_ts = time.time()
except requests.exceptions.RequestException:
if not window_title:
if not offline_logged:
print("Winamp Web Interface offline and no window found.")
offline_logged = True
player._status = "Stopped"
# Reset state when offline
player._status = "Stopped"
player._artist = "Unknown"
player._title = "Winamp (Offline)"
player._last_position_us = 0
player._total_length_us = 0
player._last_update_ts = time.time()
if player._status != last_known_status or player._title != last_known_title:
last_known_status = player._status
last_known_title = player._title
print(f"UPDATE: [{player._status}] {player._artist} - {player._title}")
if last_known_status != "Stopped":
last_known_status = "Stopped"
player.PropertiesChanged(
"org.mpris.MediaPlayer2.Player",
{
@@ -253,14 +280,16 @@ def update_loop(player):
[]
)
time.sleep(2)
except Exception as e:
print(f"Update error: {e}")
time.sleep(1)
if __name__ == "__main__":
bus = SessionBus()
player_logic = WinampMPRIS()
bus.publish(APP_ID, ("/org/mpris/MediaPlayer2", player_logic))
# Delayed wake-up call
GLib.timeout_add(1500, lambda: player_logic.PropertiesChanged(
"org.mpris.MediaPlayer2.Player",
{
@@ -273,7 +302,7 @@ if __name__ == "__main__":
thread = Thread(target=update_loop, args=(player_logic,), daemon=True)
thread.start()
print(f"--- Winamp Bridge Started (Plasma Detection Fix) ---")
print(f"--- Winamp Bridge Started (Window Title + Web UI) ---")
loop = GLib.MainLoop()
try:
loop.run()