Implement XDG compliance, logging, and improved 401 error handling

- Install to ~/.local/bin/winamp-mpris
- Use ~/.local/state/winamp-mpris/bridge.log for logging
- Use $XDG_RUNTIME_DIR/winamp-mpris.pid for PID management
- Add detailed user notification for 401 Unauthorized errors
- Add install.sh for automated, standard-compliant setup
- Include Winamp Web Interface source code and installer in repository
This commit is contained in:
2026-04-08 18:29:40 -04:00
parent 1e9257a27f
commit 22492dbee9
120 changed files with 9092 additions and 10 deletions

View File

@@ -3,18 +3,49 @@ import requests
import time
import re
import subprocess
import os
import logging
from threading import Thread
from pydbus import SessionBus
from pydbus.generic import signal
from gi.repository import GLib
from bs4 import BeautifulSoup
# --- CONFIGURATION ---
# --- CONFIGURATION & XDG PATHS ---
BASE_URL = "http://localhost:5666"
AUTH = ('winamp', 'llama')
APP_ID = "org.mpris.MediaPlayer2.winamp"
DEFAULT_ART = "https://webamp.org/favicon.ico"
# XDG Standard Paths
XDG_STATE_HOME = os.environ.get("XDG_STATE_HOME", os.path.expanduser("~/.local/state"))
XDG_RUNTIME_DIR = os.environ.get("XDG_RUNTIME_DIR", f"/run/user/{os.getuid()}")
LOG_DIR = os.path.join(XDG_STATE_HOME, "winamp-mpris")
LOG_FILE = os.path.join(LOG_DIR, "bridge.log")
PID_FILE = os.path.join(XDG_RUNTIME_DIR, "winamp-mpris.pid")
# Ensure log directory exists
os.makedirs(LOG_DIR, exist_ok=True)
# Configure Logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
handlers=[
logging.FileHandler(LOG_FILE),
logging.StreamHandler()
]
)
logger = logging.getLogger("winamp-mpris")
def write_pid_file():
try:
with open(PID_FILE, "w") as f:
f.write(str(os.getpid()))
except Exception as e:
logger.error(f"Failed to write PID file: {e}")
class WinampMPRIS:
"""
MPRIS2 specification implementation for Winamp Web Interface.
@@ -94,16 +125,20 @@ class WinampMPRIS:
self._volume = 1.0
def _request(self, endpoint):
print(f"COMMAND RECEIVED: {endpoint}")
logger.info(f"COMMAND RECEIVED: {endpoint}")
try:
r = requests.get(f"{BASE_URL}/{endpoint}", auth=AUTH, timeout=2)
if r.status_code != 200:
if r.status_code == 401:
msg = "401 Unauthorized: Check gen_httpsrv.dll plugin config. Ensure user 'winamp' has correct password and 'Play' permissions in Users tab."
logger.warning(f"ERROR: {msg}")
subprocess.run(["notify-send", "-u", "critical", "-t", "10000", "Winamp Bridge Auth Error", msg])
elif r.status_code != 200:
msg = f"Failed to send '{endpoint}' to Winamp (Status {r.status_code})."
print(f"ERROR: {msg}")
logger.error(f"ERROR: {msg}")
subprocess.run(["notify-send", "-u", "critical", "-t", "3000", "Winamp Bridge Error", msg])
except Exception as e:
msg = f"Connection error while sending '{endpoint}': {e}"
print(f"ERROR: {msg}")
logger.warning(f"ERROR: {msg}")
subprocess.run(["notify-send", "-u", "critical", "-t", "3000", "Winamp Bridge Offline", msg])
# MPRIS Methods
@@ -264,6 +299,7 @@ def update_loop(player):
last_known_shuffle = False
last_known_loop = "None"
offline_logged = False
auth_error_logged = False
while True:
try:
@@ -301,6 +337,7 @@ def update_loop(player):
r = requests.get(f"{BASE_URL}/main", auth=AUTH, timeout=1)
if r.status_code == 200:
offline_logged = False
auth_error_logged = False
soup = BeautifulSoup(r.text, 'html.parser')
p_tags = soup.find_all('p')
status_raw = p_tags[0].text if p_tags else ""
@@ -357,10 +394,17 @@ def update_loop(player):
player._title = status_raw.split('-')[0].replace("Playing track ", "").strip()
player._artist = "Unknown"
player._album = ""
elif r.status_code == 401:
if not auth_error_logged:
msg = "401 Unauthorized: Check gen_httpsrv.dll plugin config. Ensure user 'winamp' has correct password and 'Play' permissions in Users tab."
logger.warning(f"ERROR: {msg}")
subprocess.run(["notify-send", "-u", "critical", "-t", "10000", "Winamp Bridge Auth Error", msg])
auth_error_logged = True
player._status = "Stopped"
except requests.exceptions.RequestException:
if not window_title:
if not offline_logged:
print("Winamp Web Interface offline and no window found.")
logger.info("Winamp Web Interface offline and no window found.")
offline_logged = True
player._status = "Stopped"
@@ -383,7 +427,7 @@ def update_loop(player):
last_known_loop = player._loop_status
album_str = f" [{player._album}]" if player._album else ""
print(f"UPDATE: [{player._status}] Shuffle: {player._shuffle}, Loop: {player._loop_status}, {player._artist}{album_str} - {player._title}")
logger.info(f"UPDATE: [{player._status}] Shuffle: {player._shuffle}, Loop: {player._loop_status}, {player._artist}{album_str} - {player._title}")
player.PropertiesChanged(
"org.mpris.MediaPlayer2.Player",
@@ -398,12 +442,13 @@ def update_loop(player):
)
except Exception as e:
print(f"Update error: {e}")
logger.error(f"Update error: {e}")
time.sleep(1)
if __name__ == "__main__":
write_pid_file()
bus = SessionBus()
player_logic = WinampMPRIS()
bus.publish(APP_ID, ("/org/mpris/MediaPlayer2", player_logic))
@@ -420,7 +465,7 @@ if __name__ == "__main__":
thread = Thread(target=update_loop, args=(player_logic,), daemon=True)
thread.start()
print(f"--- Winamp Bridge Started (Window Title + Web UI + Album Art) ---")
logger.info(f"--- Winamp Bridge Started (Window Title + Web UI + Album Art) ---")
loop = GLib.MainLoop()
try:
loop.run()