#!/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")