Finalize winamp_mpris.py with window title and web UI hybrid logic
This commit is contained in:
129
winamp_mpris.py
129
winamp_mpris.py
@@ -2,6 +2,7 @@
|
|||||||
import requests
|
import requests
|
||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from pydbus import SessionBus
|
from pydbus import SessionBus
|
||||||
from pydbus.generic import signal
|
from pydbus.generic import signal
|
||||||
@@ -75,6 +76,7 @@ class WinampMPRIS:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._title = "Winamp"
|
self._title = "Winamp"
|
||||||
self._artist = "Unknown"
|
self._artist = "Unknown"
|
||||||
|
self._album = ""
|
||||||
self._status = "Stopped"
|
self._status = "Stopped"
|
||||||
self._last_position_us = 0
|
self._last_position_us = 0
|
||||||
self._total_length_us = 0
|
self._total_length_us = 0
|
||||||
@@ -156,19 +158,37 @@ class WinampMPRIS:
|
|||||||
'mpris:length': GLib.Variant('x', self._total_length_us),
|
'mpris:length': GLib.Variant('x', self._total_length_us),
|
||||||
'xesam:title': GLib.Variant('s', self._title),
|
'xesam:title': GLib.Variant('s', self._title),
|
||||||
'xesam:artist': GLib.Variant('as', [self._artist]),
|
'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')
|
'mpris:artUrl': GLib.Variant('s', 'https://webamp.org/favicon.ico')
|
||||||
}
|
}
|
||||||
|
|
||||||
def parse_time_to_us(time_str):
|
def parse_time_to_us(time_str):
|
||||||
"""Parses MM:SS or H:MM:SS to microseconds."""
|
"""Parses MM:SS or H:MM:SS to microseconds."""
|
||||||
|
try:
|
||||||
parts = list(map(int, time_str.split(':')))
|
parts = list(map(int, time_str.split(':')))
|
||||||
if len(parts) == 2: # MM:SS
|
if len(parts) == 2: # MM:SS
|
||||||
return (parts[0] * 60 + parts[1]) * 1_000_000
|
return (parts[0] * 60 + parts[1]) * 1_000_000
|
||||||
elif len(parts) == 3: # H:MM:SS
|
elif len(parts) == 3: # H:MM:SS
|
||||||
return (parts[0] * 3600 + parts[1] * 60 + parts[2]) * 1_000_000
|
return (parts[0] * 3600 + parts[1] * 60 + parts[2]) * 1_000_000
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
return 0
|
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):
|
def update_loop(player):
|
||||||
last_known_title = ""
|
last_known_title = ""
|
||||||
last_known_status = ""
|
last_known_status = ""
|
||||||
@@ -176,7 +196,42 @@ def update_loop(player):
|
|||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
r = requests.get(f"{BASE_URL}/main", auth=AUTH, timeout=2)
|
# 1. Try Window Title (Primary)
|
||||||
|
window_title = get_winamp_window_title()
|
||||||
|
title_parsed = False
|
||||||
|
|
||||||
|
if window_title:
|
||||||
|
# Pattern: Artist–Album–Title | 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))
|
||||||
|
|
||||||
|
# Split metadata (Artist–Album–Title)
|
||||||
|
meta_parts = [p.strip() for p in re.split(r'[–—-]', metadata_raw)]
|
||||||
|
|
||||||
|
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 = ""
|
||||||
|
|
||||||
|
player._last_position_us = new_pos_us
|
||||||
|
player._total_length_us = new_len_us
|
||||||
|
player._last_update_ts = time.time()
|
||||||
|
title_parsed = True
|
||||||
|
|
||||||
|
# 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:
|
if r.status_code == 200:
|
||||||
offline_logged = False
|
offline_logged = False
|
||||||
soup = BeautifulSoup(r.text, 'html.parser')
|
soup = BeautifulSoup(r.text, 'html.parser')
|
||||||
@@ -187,63 +242,35 @@ def update_loop(player):
|
|||||||
if "Playing" in status_raw: new_status = "Playing"
|
if "Playing" in status_raw: new_status = "Playing"
|
||||||
elif "Paused" in status_raw: new_status = "Paused"
|
elif "Paused" in status_raw: new_status = "Paused"
|
||||||
|
|
||||||
new_artist = "Unknown"
|
player._status = new_status
|
||||||
new_title = "Winamp"
|
|
||||||
new_pos_us = 0
|
|
||||||
new_len_us = 0
|
|
||||||
|
|
||||||
# Metadata match
|
if not title_parsed:
|
||||||
|
# Web UI Fallback for metadata
|
||||||
match_meta = re.search(r"track \d+ - (.*?) - (.*?) -", status_raw)
|
match_meta = re.search(r"track \d+ - (.*?) - (.*?) -", status_raw)
|
||||||
if match_meta:
|
if match_meta:
|
||||||
new_artist = match_meta.group(1).strip()
|
player._artist = match_meta.group(1).strip()
|
||||||
new_title = match_meta.group(2).strip()
|
player._title = match_meta.group(2).strip()
|
||||||
elif "track" in status_raw:
|
elif "track" in status_raw:
|
||||||
new_title = status_raw.split('-')[0].replace("Playing track ", "").strip()
|
player._title = status_raw.split('-')[0].replace("Playing track ", "").strip()
|
||||||
|
|
||||||
# Time match: (1:06 / 2:48)
|
|
||||||
match_time = re.search(r"\((\d+:?\d*:\d+) / (\d+:?\d*:\d+)\)", status_raw)
|
match_time = re.search(r"\((\d+:?\d*:\d+) / (\d+:?\d*:\d+)\)", status_raw)
|
||||||
if match_time:
|
if match_time:
|
||||||
new_pos_us = parse_time_to_us(match_time.group(1))
|
player._last_position_us = parse_time_to_us(match_time.group(1))
|
||||||
new_len_us = parse_time_to_us(match_time.group(2))
|
player._total_length_us = parse_time_to_us(match_time.group(2))
|
||||||
|
|
||||||
# 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()
|
player._last_update_ts = time.time()
|
||||||
|
except requests.exceptions.RequestException:
|
||||||
if new_status != last_known_status or new_title != last_known_title:
|
if not window_title:
|
||||||
last_known_status = new_status
|
|
||||||
last_known_title = new_title
|
|
||||||
|
|
||||||
print(f"UPDATE: [{player._status}] {player._artist} - {player._title} ({match_time.group(1) if match_time else '0:00'})")
|
|
||||||
|
|
||||||
player.PropertiesChanged(
|
|
||||||
"org.mpris.MediaPlayer2.Player",
|
|
||||||
{
|
|
||||||
"PlaybackStatus": player.PlaybackStatus,
|
|
||||||
"Metadata": player.Metadata
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
except (requests.exceptions.RequestException, Exception) as e:
|
|
||||||
if not offline_logged:
|
if not offline_logged:
|
||||||
print(f"Winamp Web Interface offline or unreachable: {e}")
|
print("Winamp Web Interface offline and no window found.")
|
||||||
offline_logged = True
|
offline_logged = True
|
||||||
|
|
||||||
# Reset state when offline
|
|
||||||
player._status = "Stopped"
|
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 last_known_status != "Stopped":
|
if player._status != last_known_status or player._title != last_known_title:
|
||||||
last_known_status = "Stopped"
|
last_known_status = player._status
|
||||||
|
last_known_title = player._title
|
||||||
|
|
||||||
|
print(f"UPDATE: [{player._status}] {player._artist} - {player._title}")
|
||||||
|
|
||||||
player.PropertiesChanged(
|
player.PropertiesChanged(
|
||||||
"org.mpris.MediaPlayer2.Player",
|
"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__":
|
if __name__ == "__main__":
|
||||||
bus = SessionBus()
|
bus = SessionBus()
|
||||||
player_logic = WinampMPRIS()
|
player_logic = WinampMPRIS()
|
||||||
bus.publish(APP_ID, ("/org/mpris/MediaPlayer2", player_logic))
|
bus.publish(APP_ID, ("/org/mpris/MediaPlayer2", player_logic))
|
||||||
|
|
||||||
# Delayed wake-up call
|
|
||||||
GLib.timeout_add(1500, lambda: player_logic.PropertiesChanged(
|
GLib.timeout_add(1500, lambda: player_logic.PropertiesChanged(
|
||||||
"org.mpris.MediaPlayer2.Player",
|
"org.mpris.MediaPlayer2.Player",
|
||||||
{
|
{
|
||||||
@@ -273,7 +302,7 @@ if __name__ == "__main__":
|
|||||||
thread = Thread(target=update_loop, args=(player_logic,), daemon=True)
|
thread = Thread(target=update_loop, args=(player_logic,), daemon=True)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
print(f"--- Winamp Bridge Started (Plasma Detection Fix) ---")
|
print(f"--- Winamp Bridge Started (Window Title + Web UI) ---")
|
||||||
loop = GLib.MainLoop()
|
loop = GLib.MainLoop()
|
||||||
try:
|
try:
|
||||||
loop.run()
|
loop.run()
|
||||||
|
|||||||
Reference in New Issue
Block a user