–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 fetch_album_art(artist, album):
"""Fetches album art from iTunes Search API."""
if not artist or not album or artist == "Unknown":
return DEFAULT_ART
try:
query = f"{artist} {album}"
url = f"https://itunes.apple.com/search?term={query}&entity=album&limit=1"
r = requests.get(url, timeout=3)
if r.status_code == 200:
data = r.json()
if data.get("resultCount", 0) > 0:
art_100 = data["results"][0].get("artworkUrl100", DEFAULT_ART)
# Upgrade to 600x600 for better quality
return art_100.replace("100x100bb.jpg", "600x600bb.jpg")
except Exception as e:
print(f"Art fetch error: {e}")
return DEFAULT_ART
def update_loop(player):
last_known_title = ""
last_known_status = ""
last_known_album = ""
last_time_str = ""
last_known_shuffle = False
last_known_loop = "None"
offline_logged = False
while True:
try:
# 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
# Only use window title for metadata as time is unreliable here
match = re.search(r"(.*?) \| .* - Winamp", window_title)
if match:
metadata_raw = match.group(1).strip()
# 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 = ""
title_parsed = True
# 2. Poll Web UI (Status and Time source)
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 ""
new_status = "Stopped"
if "Playing" in status_raw: new_status = "Playing"
elif "Paused" in status_raw: new_status = "Paused"
player._status = new_status
# Parse Repeat and Random status
# Repeat is off ...
Random is off ...
repeat_tag = None
for p in p_tags:
if "Repeat is" in p.text:
repeat_tag = p
break
if repeat_tag:
# Extract "on" or "off" status
text = repeat_tag.get_text()
is_repeat = False
is_random = False
m_rep = re.search(r"Repeat is\s*(?:)?(\w+)(?:)?", text, re.I)
if m_rep:
is_repeat = m_rep.group(1).lower() == "on"
m_rand = re.search(r"Random is\s*(?:)?(\w+)(?:)?", text, re.I)
if m_rand:
is_random = m_rand.group(1).lower() == "on"
player._loop_status = "Playlist" if is_repeat else "None"
player._shuffle = is_random
# Always update time from Web UI
match_time = re.search(r"\((\d+:?\d*:\d+) / (\d+:?\d*:\d+)\)", status_raw)
if match_time:
current_time_str = match_time.group(1)
if current_time_str != last_time_str:
player._last_position_us = parse_time_to_us(current_time_str)
player._total_length_us = parse_time_to_us(match_time.group(2))
player._last_update_ts = time.time()
last_time_str = current_time_str
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()
player._album = ""
elif "track" in status_raw:
player._title = status_raw.split('-')[0].replace("Playing track ", "").strip()
player._artist = "Unknown"
player._album = ""
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"
if (player._status != last_known_status or
player._title != last_known_title or
player._album != last_known_album or
player._shuffle != last_known_shuffle or
player._loop_status != last_known_loop):
# Fetch art if album/artist changed
if player._artist != "Unknown" and player._album:
player._art_url = fetch_album_art(player._artist, player._album)
else:
player._art_url = DEFAULT_ART
last_known_status = player._status
last_known_title = player._title
last_known_album = player._album
last_known_shuffle = player._shuffle
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}")
player.PropertiesChanged(
"org.mpris.MediaPlayer2.Player",
{
"PlaybackStatus": player.PlaybackStatus,
"Metadata": player.Metadata,
"Shuffle": player.Shuffle,
"LoopStatus": player.LoopStatus
},
[]
)
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))
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 (Window Title + Web UI + Album Art) ---")
loop = GLib.MainLoop()
try:
loop.run()
except KeyboardInterrupt:
pass