aboutsummaryrefslogtreecommitdiffstats
path: root/deckit/modules/music.py
blob: af13688842c7275e8c8090dcb1da68466d447f39 (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
"""Now playing and volume, via playerctl and wpctl."""

import subprocess

from ..module import Module

SEP = "\x1f"

BASE_ACTIONS = {
    "voldown": ["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", "5%-"],
    "mute": ["wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@", "toggle"],
    "volup": ["wpctl", "set-volume", "-l", "1.0", "@DEFAULT_AUDIO_SINK@", "5%+"],
    "prev": ["playerctl", "previous"],
    "play": ["playerctl", "play-pause"],
    "next": ["playerctl", "next"],
}


def _run(cmd, timeout=2):
    try:
        return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout).stdout.strip()
    except (OSError, subprocess.TimeoutExpired):
        return ""


def _popen(cmd):
    subprocess.Popen(cmd, start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


def make(cfg: dict) -> Module:
    run = cfg.get("run", _run)

    def state():
        fmt = SEP.join(["{{status}}", "{{title}}", "{{artist}}", "{{album}}", "{{mpris:length}}",
                        "{{position}}", "{{mpris:artUrl}}", "{{playerName}}"])
        out = run(["playerctl", "metadata", "--format", fmt])
        vol = run(["wpctl", "get-volume", "@DEFAULT_AUDIO_SINK@"])
        volume = {"level": round(float(vol.split()[1]) * 100) if len(vol.split()) > 1 else 0,
                  "muted": "MUTED" in vol}
        if not out:
            return {"status": "Stopped", "volume": volume}
        status, title, artist, album, length, pos, art, name = (out.split(SEP) + [""] * 8)[:8]
        art_src = art if art.startswith(("http://", "https://")) else None
        return {"status": status, "title": title, "artist": artist, "album": album,
                "length": int(length or 0) / 1e6, "position": int(pos or 0) / 1e6,
                "art": art_src, "player": name.split(".")[0], "volume": volume}

    actions = {name: (lambda cmd: (lambda body: _popen(cmd)))(cmd) for name, cmd in BASE_ACTIONS.items()}
    return Module(name="music", title="Music", state=state, actions=actions)