#!/usr/bin/env python3 import requests import time import re from threading import Thread from pydbus import SessionBus from pydbus.generic import signal from gi.repository import GLib from bs4 import BeautifulSoup # --- CONFIGURATION --- BASE_URL = "http://localhost:5666" AUTH = ('winamp', 'llama') APP_ID = "org.mpris.MediaPlayer2.winamp" class WinampMPRIS: """ MPRIS2 specification implementation for Winamp Web Interface. Optimized for Plasma 6 property change detection. """ dbus = f""" """ # Signal for org.freedesktop.DBus.Properties.PropertiesChanged PropertiesChanged = signal() def __init__(self): self._title = "Winamp" self._artist = "Unknown" self._status = "Stopped" def _request(self, endpoint): try: requests.get(f"{BASE_URL}/{endpoint}", auth=AUTH, timeout=2) except Exception as e: print(f"Request failed: {e}") # MPRIS Methods def Next(self): self._request("next") def Previous(self): self._request("prev") def Pause(self): self._request("pause") def PlayPause(self): self._request("pause") def Play(self): self._request("play") def Stop(self): self._request("stop") def Seek(self, offset): pass def SetPosition(self, track_id, position): pass def OpenUri(self, uri): pass # --- Root Properties --- @property def CanQuit(self): return False @property def CanRaise(self): return False @property def HasTrackList(self): return False @property def Identity(self): return "Winamp Web Bridge" @property def DesktopEntry(self): return "" @property def SupportedUriSchemes(self): return [] @property def SupportedMimeTypes(self): return [] # --- Player Properties --- @property def PlaybackStatus(self): return self._status @property def LoopStatus(self): return "None" @property def Rate(self): return 1.0 @property def Shuffle(self): return False @property def CanGoNext(self): return True @property def CanGoPrevious(self): return True @property def CanPlay(self): return True @property def CanPause(self): return True @property def CanControl(self): return True @property def CanSeek(self): return False @property def Position(self): return 0 @property def Volume(self): return 1.0 @property def Metadata(self): # Metadata is a{sv}, so values MUST be wrapped in GLib.Variant return { 'mpris:trackid': GLib.Variant('o', '/org/mpris/MediaPlayer2/winamp/track/0'), 'mpris:length': GLib.Variant('x', 0), 'xesam:title': GLib.Variant('s', self._title), 'xesam:artist': GLib.Variant('as', [self._artist]), 'xesam:album': GLib.Variant('s', ''), 'mpris:artUrl': GLib.Variant('s', 'https://webamp.org/favicon.ico') } def update_loop(player): last_known_title = "" last_known_status = "" while True: try: r = requests.get(f"{BASE_URL}/main", auth=AUTH, timeout=2) if r.status_code == 200: soup = BeautifulSoup(r.text, 'html.parser') p_tags = soup.find_all('p') status_raw = p_tags[0].text if p_tags else "" new_status = "Stopped" if "Playing" in status_raw: new_status = "Playing" elif "Paused" in status_raw: new_status = "Paused" new_artist = "Unknown" new_title = "Winamp" match = re.search(r"track \d+ - (.*?) - (.*?) -", status_raw) if match: new_artist = match.group(1).strip() new_title = match.group(2).strip() elif "track" in status_raw: new_title = status_raw.split('-')[0].replace("Playing track ", "").strip() if new_status != last_known_status or new_title != last_known_title: player._status = new_status player._artist = new_artist player._title = new_title last_known_status = new_status last_known_title = new_title print(f"UPDATE: [{player._status}] {player._artist} - {player._title}") # pydbus PropertiesChanged helper will wrap these in Variants # based on the introspection XML. # Metadata is a{sv}, so its value (the dict) will be wrapped in Variant('a{sv}', ...) # For that to work, the dict's values must already be Variants. player.PropertiesChanged( "org.mpris.MediaPlayer2.Player", { "PlaybackStatus": player.PlaybackStatus, "Metadata": player.Metadata }, [] ) except Exception as e: print(f"Update error: {e}") import traceback traceback.print_exc() time.sleep(2) 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", { "PlaybackStatus": player_logic.PlaybackStatus, "Metadata": player_logic.Metadata }, [] )) thread = Thread(target=update_loop, args=(player_logic,), daemon=True) thread.start() print(f"--- Winamp Bridge Started (Plasma Detection Fix) ---") loop = GLib.MainLoop() try: loop.run() except KeyboardInterrupt: pass