#!/usr/bin/env python3 from textwrap import shorten from typing import Optional from xml.sax.saxutils import escape from dbus.exceptions import DBusException from mpris2 import Player, get_players_uri from mpris2.types import Metadata_Map from mpris2.utils import get_player_id_from_uri class MagicMetadata: _metadata: Metadata_Map def __init__(self, metadata: Metadata_Map): self._metadata = metadata def __getattr__(self, attr: str): """lookup attributes via pre-defined constants in Metadata_Map""" return self._metadata.get(getattr(self._metadata, attr.upper())) def format_duration(duration: Optional[int]): if duration is None: return "?" hours, rem = divmod(duration // 1000 // 1000, 3600) minutes, seconds = divmod(rem, 60) if hours: return f"{hours:02d}:{minutes:02d}:{seconds:02d}" else: return f"{minutes:02d}:{seconds:02d}" def format_player(player: Player, short: bool = False) -> str: metadata = MagicMetadata(player.Metadata) try: position = player.Position.real except DBusException: position = None if position is None and metadata.length is None: position_and_length = "" else: position_and_length = f" {format_duration(position) }/{ format_duration(metadata.length) }" if short: status = "" else: status = { "Playing": "▶", "Paused": "⏸️", "Stopped": "⏹️", }.get(player.PlaybackStatus, "❓") + " " artist = escape(",".join(metadata.artist)) if metadata.artist else "" album = escape(metadata.album) if metadata.album else "" title = escape(metadata.title) if metadata.title else "" if short: # TODO: could be a bit more clever here to use the whole space artist = shorten(artist, 40, placeholder="…") album = shorten(album, 40, placeholder="…") title = shorten(title, 60, placeholder="…") if album: album = f" / { album }" return f"{status}{ artist }{ album } | { title }{ position_and_length }" def status_key(player: Player) -> int: return { "Playing": 0, "Paused": 1, "Stopped": 2, }.get(player.PlaybackStatus, 100) def status_icon(player: Player) -> str: return { "Playing": "exaile-play", "Paused": "exaile-pause", "Stopped": "media-player-banshee-stopped", }.get(player.PlaybackStatus, "unknown") players = [Player(dbus_interface_info={"dbus_uri": uri}) for uri in get_players_uri()] players.sort(key=status_key) if len(players) > 0: active_player_id = get_player_id_from_uri(players[0]._dbus_interface_info.uri) print( "" + (f"[{ len(players) }] " if len(players) > 1 else "") + format_player(players[0], short=True) + "" ) print(f"playerctl --player={ active_player_id } play-pause") print(f"{ status_icon(players[0]) }") print("" + "\n".join(format_player(player) for player in players) + "") else: print("No media players")