#!/usr/bin/env python3 import gi import os import sys import json import time import requests try: gi.require_version("Playerctl", "2.0") except: sys.exit(1) from gi.repository import Playerctl, GLib from gi.events import GLibEventLoopPolicy import asyncio asyncio.set_event_loop_policy(GLibEventLoopPolicy()) title_maxlen = 40 title_end_at = ["(", "-"] artist_maxlen = 40 album_maxlen = 30 # Split on these characters, take the first part, and strip whitespace # from the result. This can be used to remove subtitles (like " - Remastered" or " (feat. Artist)"). album_end_at = ["(", "-"] class Status: def __init__(self): self.running = False self.playing = False self.title = None self.artist = None self.album = None self.album_artist = None self.album_art = None def _dict_dump(self): return { "running": self.running, "playing": self.playing, "title": self.title, "artist": self.artist, "album": self.album, "album_artist": self.album_artist, "album_art": self.album_art, } def __str__(self): return json.dumps(self._dict_dump()) class StatusDisplay: def __init__(self): self._player = None self.last_album_art = None def show(self): self._init_player() loop = asyncio.get_event_loop() loop.run_forever() def _get_status(self, playing=None): if self._player: status = Status() status.running = self._player.props.playback_status != 2 if playing == None: status.playing = self._player.props.playback_status == 0 else: status.playing = playing status.title = self._player.get_title() or "" status.artist = self._player.get_artist() or "" status.album = self._player.get_album() or "" album_art = ( dict(self._player.get_property("metadata")).get("mpris:artUrl") or "" ) if album_art != self.last_album_art: # Need to download the new album art self.last_album_art = album_art if album_art.startswith("file://"): album_art = album_art[7:] if os.path.exists(album_art): status.album_art = album_art else: try: r = requests.get(album_art, timeout=1) # Write album art to tmp file if r.status_code == 200: with open("/tmp/album_art", "wb") as f: f.write(r.content) status.album_art = "/tmp/album_art" except: pass for end in title_end_at: if end in status.title: status.title = status.title.split(end)[0].strip() if len(status.title) > title_maxlen: status.title = status.title[: title_maxlen - 1] + "…" if len(status.artist) > artist_maxlen: status.artist = status.artist[: artist_maxlen - 1] + "…" for end in album_end_at: if end in status.album: status.album = status.album.split(end)[0].strip() if len(status.album) > album_maxlen: status.album = status.album[: album_maxlen - 1] + "…" return status else: return Status() def _print_status(self, status): print(status, flush=True) def _init_player(self): success = False while not success: try: self._player = Playerctl.Player() self._player.connect("metadata", self._on_update) self._player.connect("play", self._on_play) self._player.connect("pause", self._on_pause) self._player.connect("exit", self._on_exit) self._print_status(self._get_status()) success = True except Exception as e: # print(e, flush=True, file=sys.stderr) self._print_status(Status()) time.sleep(2) def _on_update(self, *args): self._print_status(self._get_status()) def _on_play(self, *args): self._print_status(self._get_status(playing=True)) def _on_pause(self, *args): self._print_status(self._get_status(playing=False)) def _on_exit(self, player): del self._player self._player = None self._init_player() StatusDisplay().show()