diff --git a/docs/conf.py b/docs/conf.py index d1948e0..a202929 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -24,7 +24,7 @@ MOCK_MODULES = [ "netifaces", "psutil", "lxml.html", "lxml.cssselect", "lxml", "praw", - "gi.repository", "dbus.mainloop.glib", "dbus", + "gi", "gi.repository", "dbus.mainloop.glib", "dbus", "pywapi", "basiciw", "i3pystatus.pulseaudio.pulse", "notmuch", diff --git a/docs/i3pystatus.rst b/docs/i3pystatus.rst index 183de54..f555a70 100644 --- a/docs/i3pystatus.rst +++ b/docs/i3pystatus.rst @@ -11,7 +11,7 @@ Module reference :Audio: `alsa`_ - `pulseaudio`_ :Hardware: `backlight`_ - `battery`_ - `temp`_ :Network: `net_speed`_ - `network`_ - `online`_ - `openstack_vms`_ - `openvpn`_ -:Music: `cmus`_ - `moc`_ - `mpd`_ - `now_playing`_ - `pianobar`_ - `spotify`_ +:Music: `cmus`_ - `moc`_ - `mpd`_ - `now_playing`_ - `pianobar`_ - `playerctl`_ - `spotify`_ :Websites: `bitcoin`_ - `dota2wins`_ - `github`_ - `modsde`_ - `parcel`_ - `reddit`_ - `weather`_ - `whosonlocation`_ :Other: `anybar`_ - `mail`_ - `pomodoro`_ - `pyload`_ - `text`_ - `updates`_ diff --git a/i3pystatus/playerctl.py b/i3pystatus/playerctl.py new file mode 100644 index 0000000..d0afc41 --- /dev/null +++ b/i3pystatus/playerctl.py @@ -0,0 +1,104 @@ +from i3pystatus import formatp +from i3pystatus import IntervalModule +from i3pystatus.core.util import TimeWrapper + +import gi +gi.require_version('Playerctl', '1.0') # nopep8 +from gi.repository import Playerctl as pctl + + +class Playerctl(IntervalModule): + """ + Gets current music information from a playerctl supported player. + + .. rubric:: Available formatters + + * `{status}` — current status icon (paused/playing/stopped) + * `{length}` — total song duration, uses TimeWrapper formatting, default format is `%E%l:%M:%S` + * `{artist}` — artist + * `{title}` — title + * `{album}` — album + """ + + settings = ( + ('format', 'formatp string'), + ('format_not_running', 'Text to show if player is not running'), + ('color', 'The color of the text'), + ('color_not_running', 'The color of the text, when player is not running'), + ('status', 'Dictionary mapping status to output'), + ('player_name', + 'Name of music player, use `playerctl -l` with player running to get. If None, tries to autodetect.'), + ) + + # default settings + color = '#ffffff' + color_not_running = '#ffffff' + format = '{status} {length} {artist} - {title}' + format_not_running = 'Not running' + status = { + 'paused': '▷', + 'playing': '▶', + 'stopped': '■', + } + player_name = None + + on_leftclick = 'playpause' + on_rightclick = 'next_song' + on_upscroll = 'next_song' + on_downscroll = 'previous_song' + + def _get_length_in_secs(self, metadata): + if not metadata: + return 0 + try: + time = metadata["mpris:length"] / 1.0e6 + seconds = round(time) + return seconds + except KeyError: + return 0 + + def get_formatted_info(self, player): + """Get player track info from playerctl""" + + result = { + "status": "", + "artist": "", + "title": "", + "album": "", + "length": "", + } + + status = player.props.status + if status: + result["status"] = self.status.get(status.lower(), "") + result["artist"] = player.get_artist() + result["title"] = player.get_title() + result["album"] = player.get_album() + length_in_secs = self._get_length_in_secs(player.props.metadata) + result["length"] = TimeWrapper(length_in_secs, "%E%l%M:%S") + + return result + + def run(self): + """Main statement, executes all code every interval""" + + self.player = pctl.Player(player_name=self.player_name) + fdict = self.get_formatted_info(self.player) + if fdict.get("status", ""): + self.output = {"full_text": formatp(self.format, **fdict), + "color": self.color} + else: + self.output = {"full_text": self.format_not_running, + "color": self.color_not_running} + + def playpause(self): + """Pauses and plays player""" + self.player.play_pause() + + def next_song(self): + """skips to the next song""" + self.player.next() + + def previous_song(self): + """Plays the previous song""" + self.player.previous() diff --git a/i3pystatus/spotify.py b/i3pystatus/spotify.py index c12bc36..bd608fe 100644 --- a/i3pystatus/spotify.py +++ b/i3pystatus/spotify.py @@ -1,111 +1,8 @@ -import math -from i3pystatus import formatp -from i3pystatus import IntervalModule -from gi.repository import Playerctl +from i3pystatus.playerctl import Playerctl -class Spotify(IntervalModule): +class Spotify(Playerctl): """ - Gets Spotify info using playerctl - - .. rubric:: Available formatters - - * `{status}` — current status icon (paused/playing) - * `{length}` — total song duration (mm:ss format) - * `{artist}` — artist - * `{title}` — title - * `{album}` — album + Get Spotify info using playerctl. Based on `Playerctl`_ module. """ - - settings = ( - ('format', 'formatp string'), - ('format_not_running', 'Text to show if cmus is not running'), - ('color', 'The color of the text'), - ('color_not_running', 'The color of the text, when cmus is not running'), - ('status', 'Dictionary mapping status to output'), - ) - - # default settings - color = '#ffffff' - color_not_running = '#ffffff' - format = '{status} {length} {artist} - {title}' - format_not_running = 'Not running' - interval = 1 - status = { - 'paused': '▷', - 'playing': '▶', - } - - on_leftclick = 'playpause' - on_rightclick = 'next_song' - on_upscroll = 'next_song' - on_downscroll = 'previous_song' - - def get_info(self, player): - """gets spotify track info from playerctl""" - - artist = player.get_artist() - title = player.get_title() - album = player.get_album() - status = player.props.status - - # gets the length of spotify through the metadata command - length = "" - - # stores the metadata and checks if it is valid - metadata = player.props.metadata - if metadata is not None: - # math to convert the number stored in mpris:length to a human readable format - time = dict(metadata)["mpris:length"] / 60.0e6 - minutes = math.floor(time) - seconds = round(time % 1 * 60) - if seconds < 10: - seconds = "0" + str(seconds) - length = "{}:{}".format(minutes, seconds) - - # sets length to an empty string if it does not exist for whatever reason. This should usually not happen - else: - length = "" - - # returns a dictionary of all spotify data - return {"artist": artist, "title": title, "album": album, "status": status, "length": length} - - def run(self): - """Main statement, executes all code every interval""" - - # tries to create player object and get data from player - try: - self.player = Playerctl.Player(player_name="spotify") - - response = self.get_info(self.player) - - # creates a dictionary of the spotify data captured - fdict = { - 'status': self.status[response['status'].lower()], - 'title': response["title"], - 'album': response.get('album', ''), - 'artist': response.get('artist', ''), - 'length': response.get('length', 0), - } - self.data = fdict - self.output = {"full_text": formatp(self.format, **fdict), - "color": self.color} - - # outputs the not running string if spotify is closed - except: - self.output = {"full_text": self.format_not_running, - "color": self.color_not_running} - if hasattr(self, "data"): - del self.data - - def playpause(self): - """Pauses and plays spotify""" - self.player.play_pause() - - def next_song(self): - """skips to the next song""" - self.player.next() - - def previous_song(self): - """Plays the previous song""" - self.player.previous() + player_name = "spotify"