aboutsummaryrefslogtreecommitdiff
path: root/i3pystatus/.config/i3pystatus_modules/now_playing.py
blob: 0be2825242ab464b40c8ea4c7e51e900cdf67983 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import functools
from os.path import basename

import dbus

from i3pystatus import IntervalModule, formatp
from i3pystatus.core.util import TimeWrapper


class NoPlayerException(Exception):
    pass


class NowPlaying(IntervalModule):
    """
    Shows currently playing track information, supports most media players

    * Requires python-dbus available from every distros' package manager.

    Left click on the module play/pauses, right click goes to the next track.

    .. rubric:: Available formatters (uses :ref:`formatp`)

    * `{title}` — (the title of the current song)
    * `{album}` — (the album of the current song, can be an empty string (e.g. for online streams))
    * `{artist}` — (can be empty, too)
    * `{filename}` — (file name with out extension and path; empty unless title is empty)
    * `{song_elapsed}` — (position in the currently playing song, uses :ref:`TimeWrapper`, default is `%m:%S`)
    * `{song_length}` — (length of the current song, same as song_elapsed)
    * `{status}` — (play, pause, stop mapped through the `status` dictionary)
    * `{volume}` — (volume)
    """

    interval = 1

    settings = (
        ("player", "Player name. If not set, compatible players will be \
                    detected automatically."),
        ("status", "Dictionary mapping pause, play and stop to output text"),
        ("format", "formatp string"),
        ("color", "Text color"),
        ("format_no_player", "Text to show if no player is detected"),
        ("color_no_player", "Text color when no player is detected"),
        ("hide_no_player", "Hide output if no player is detected"),
    )

    hide_no_player = True
    format_no_player = "No Player"
    color_no_player = "#ffffff"

    format = "{title} {status}"
    color = "#ffffff"
    status = {
        "pause": "▷",
        "play": "▶",
        "stop": "◾",
    }
    statusmap = {
        "Playing": "playd",
        "Paused": "pause",
        "Stopped": "stop",
    }

    on_leftclick = "playpause"
    on_rightclick = "next_song"

    player = None
    old_player = None

    def find_player(self):
        players = [a for a in dbus.SessionBus().get_object("org.freedesktop.DBus", "/org/freedesktop/DBus").ListNames() if a.startswith("org.mpris.MediaPlayer2.")]
        if self.old_player in players:
            return self.old_player
        if not players:
            raise NoPlayerException()
        self.old_player = players[0]
        return players[0]

    def get_player(self):
        if self.player:
            player = "org.mpris.MediaPlayer2." + self.player
            try:
                return dbus.SessionBus().get_object(player, "/org/mpris/MediaPlayer2")
            except dbus.exceptions.DBusException:
                raise NoPlayerException()
        else:
            player = self.find_player()
            return dbus.SessionBus().get_object(player, "/org/mpris/MediaPlayer2")

    def run(self):
        try:
            player = self.get_player()
            properties = dbus.Interface(player, "org.freedesktop.DBus.Properties")
            get_prop = functools.partial(properties.Get, "org.mpris.MediaPlayer2.Player")
            currentsong = get_prop("Metadata")

            current_status = get_prop("PlaybackStatus")
            fdict = {
                "status": self.status[self.statusmap[current_status]],
                "len": 0,  # TODO: Use optional(!) TrackList interface for this to gain 100 % mpd<->now_playing compat
                "pos": 0,
                "volume": int(get_prop("Volume") * 100),

                "title": currentsong.get("xesam:title", ""),
                "album": currentsong.get("xesam:album", ""),
                "artist": ", ".join(currentsong.get("xesam:artist", "")),
                "song_length": TimeWrapper((currentsong.get("mpris:length") or 0) / 1000 ** 2),
                "song_elapsed": TimeWrapper((get_prop("Position") or 0) / 1000 ** 2),
                "filename": "",
            }

            if not fdict["title"]:
                fdict["filename"] = '.'.join(
                    basename((currentsong.get("xesam:url") or "")).split('.')[:-1])

            if current_status in ["Stopped", "Paused"]:
                self.output = {
                    "full_text": self.status["play"],
                    "color": self.color,
                }
            else:
                self.output = {
                    "full_text": formatp(self.format, **fdict).strip(),
                    "color": self.color,
                }

        except NoPlayerException:
            if self.hide_no_player:
                self.output = None
            else:
                self.output = {
                    "full_text": self.format_no_player,
                    "color": self.color_no_player,
                }
            return

        except dbus.exceptions.DBusException as e:
            if self.hide_no_player:
                self.output = None
            else:
                self.output = {
                    "full_text": "DBus error: " + e.get_dbus_message(),
                    "color": "#ff0000",
                }
            return

    def playpause(self):
        try:
            dbus.Interface(self.get_player(), "org.mpris.MediaPlayer2.Player").PlayPause()
        except NoPlayerException:
            return
        except dbus.exceptions.DBusException:
            return

    def next_song(self):
        try:
            dbus.Interface(self.get_player(), "org.mpris.MediaPlayer2.Player").Next()
        except NoPlayerException:
            return
        except dbus.exceptions.DBusException:
            return