diff --git a/modules/contrib/mpd.py b/modules/contrib/mpd.py new file mode 100644 index 0000000..8b2650d --- /dev/null +++ b/modules/contrib/mpd.py @@ -0,0 +1,181 @@ +# pylint: disable=C0111,R0903 +# -*- coding: utf-8 -*- + +"""Displays information about the current song in mpd. + +Requires the following executable: + * mpc + +Parameters: + * mpd.format: Format string for the song information. + Supported tags (see `man mpc` for additional information) + * {name} + * {artist} + * {album} + * {albumartist} + * {comment} + * {composer} + * {date} + * {originaldate} + * {disc} + * {genre} + * {performer} + * {title} + * {track} + * {time} + * {file} + * {id} + * {prio} + * {mtime} + * {mdate} + Additional tags: + * {position} - position of currently playing song + not to be confused with %position% mpc tag + * {duration} - duration of currently playing song + * {file1} - song file name without path prefix + if {file} = '/foo/bar.baz', then {file1} = 'bar.baz' + * {file2} - song file name without path prefix and extension suffix + if {file} = '/foo/bar.baz', then {file2} = 'bar' + * mpd.host: MPD host to connect to. (mpc behaviour by default) + * mpd.layout: Space-separated list of widgets to add. Possible widgets are the buttons/toggles mpd.prev, mpd.next, mpd.shuffle and mpd.repeat, and the main display with play/pause function mpd.main. +""" + +from collections import defaultdict + +import string +import os + +import bumblebee.util +import bumblebee.input +import bumblebee.output +import bumblebee.engine + +from bumblebee.output import scrollable + +class Module(bumblebee.engine.Module): + def __init__(self, engine, config): + super(Module, self).__init__(engine, config, []) + + self._layout = self.parameter("layout", "mpd.prev mpd.main mpd.next mpd.shuffle mpd.repeat") + + self._fmt = self.parameter("format", "{artist} - {title} {position}/{duration}") + self._status = None + self._shuffle = False + self._repeat = False + self._tags = defaultdict(lambda: '') + + if not self.parameter("host"): + self._hostcmd = "" + else: + self._hostcmd = " -h " + self.parameter("host") + + # Create widgets + widget_list = [] + widget_map = {} + for widget_name in self._layout.split(): + widget = bumblebee.output.Widget(name=widget_name) + widget_list.append(widget) + + if widget_name == "mpd.prev": + widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc prev" + self._hostcmd} + elif widget_name == "mpd.main": + widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc toggle" + self._hostcmd} + widget.full_text(self.description) + elif widget_name == "mpd.next": + widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc next" + self._hostcmd} + elif widget_name == "mpd.shuffle": + widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc random" + self._hostcmd} + elif widget_name == "mpd.repeat": + widget_map[widget] = {"button": bumblebee.input.LEFT_MOUSE, "cmd": "mpc repeat" + self._hostcmd} + else: + raise KeyError("The mpd module does not support a {widget_name!r} widget".format(widget_name=widget_name)) + self.widgets(widget_list) + + # Register input callbacks + for widget, callback_options in widget_map.items(): + engine.input.register_callback(widget, **callback_options) + + def hidden(self): + return self._status is None + + @scrollable + def description(self, widget): + return string.Formatter().vformat(self._fmt, (), self._tags) + + def update(self, widgets): + self._load_song() + + def state(self, widget): + if widget.name == "mpd.shuffle": + return "shuffle-on" if self._shuffle else "shuffle-off" + if widget.name == "mpd.repeat": + return "repeat-on" if self._repeat else "repeat-off" + if widget.name == "mpd.prev": + return "prev" + if widget.name == "mpd.next": + return "next" + return self._status + + def _load_song(self): + info = "" + try: + tags = ['name', + 'artist', + 'album', + 'albumartist', + 'comment', + 'composer', + 'date', + 'originaldate', + 'disc', + 'genre', + 'performer', + 'title', + 'track', + 'time', + 'file', + 'id', + 'prio', + 'mtime', + 'mdate'] + joinedtags = "\n".join(["tag {0} %{0}%".format(tag) for tag in tags]) + info = bumblebee.util.execute('mpc -f ' + '"' + joinedtags + '"' + self._hostcmd) + except RuntimeError: + pass + self._tags = defaultdict(lambda: '') + self._status = None + for line in info.split("\n"): + if line.startswith("[playing]"): + self._status = "playing" + elif line.startswith("[paused]"): + self._status = "paused" + + if line.startswith("["): + timer = line.split()[2] + position = timer.split("/")[0] + dur = timer.split("/")[1] + duration = dur.split(" ")[0] + self._tags.update({'position': position}) + self._tags.update({'duration': duration}) + + if line.startswith("volume"): + value = line.split(" ", 2)[1:] + for option in value: + if option.startswith("repeat: on"): + self._repeat = True + elif option.startswith("repeat: off"): + self._repeat = False + elif option.startswith("random: on"): + self._shuffle = True + elif option.startswith("random: off"): + self._shuffle = False + if line.startswith("tag"): + key, value = line.split(" ", 2)[1:] + self._tags.update({key: value}) + if key == "file": + self._tags.update({"file1": os.path.basename(value)}) + self._tags.update( + {"file2": + os.path.splitext(os.path.basename(value))[0]}) + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4