|
@@ -0,0 +1,198 @@
|
|
|
+#!/usr/bin/env python3
|
|
|
+
|
|
|
+import sys
|
|
|
+import logging
|
|
|
+import traceback
|
|
|
+from typing import Optional
|
|
|
+from time import sleep
|
|
|
+from datetime import datetime, timezone, timedelta
|
|
|
+import json
|
|
|
+
|
|
|
+import argparse
|
|
|
+
|
|
|
+from mpd import MPDClient
|
|
|
+from mpd import ConnectionError
|
|
|
+
|
|
|
+from aw_core import dirs
|
|
|
+from aw_core.models import Event
|
|
|
+from aw_client.client import ActivityWatchClient
|
|
|
+
|
|
|
+logging.basicConfig(level=logging.WARN)
|
|
|
+
|
|
|
+parser = argparse.ArgumentParser("A watcher for MPD")
|
|
|
+parser.add_argument("--testing", action="store_true",
|
|
|
+ help='run in testing mode')
|
|
|
+args = parser.parse_args()
|
|
|
+
|
|
|
+logger = logging.getLogger("aw-watcher-mpd")
|
|
|
+DEFAULT_CONFIG = """
|
|
|
+[aw-watcher-mpd]
|
|
|
+host = "localhost"
|
|
|
+port = 6600
|
|
|
+poll_time = 3.0"""
|
|
|
+
|
|
|
+def get_current_song(mpdClient) -> Optional[dict]:
|
|
|
+ current_song = mpdClient.currentsong()
|
|
|
+ status = mpdClient.status()
|
|
|
+ if current_song and status["state"] == "play":
|
|
|
+ current_song["elapsed"] = float(status["elapsed"])
|
|
|
+ return current_song
|
|
|
+ return None
|
|
|
+
|
|
|
+def data_from_song(song: dict) -> dict:
|
|
|
+ song_title = song["title"]
|
|
|
+ song_artist = song["artist"]
|
|
|
+ song_album = song["album"]
|
|
|
+
|
|
|
+ data = {}
|
|
|
+ data["title"] = song_title
|
|
|
+ data["artist"] = song_artist
|
|
|
+ data["album"] = song_album
|
|
|
+ data["duration"] = float(song["duration"])
|
|
|
+
|
|
|
+ logging.debug("SONG: {} - {} ({})".format(song_title, song_artist, song_album))
|
|
|
+
|
|
|
+ return data
|
|
|
+
|
|
|
+def mpdConnect(host=None, port=None):
|
|
|
+ mpdClient = MPDClient()
|
|
|
+ mpdClient.timeout = 1
|
|
|
+ mpdClient.idletimeout = None
|
|
|
+ mpdClient.connect(host, int(port))
|
|
|
+ return mpdClient
|
|
|
+
|
|
|
+def load_config():
|
|
|
+ from aw_core.config import load_config_toml as _load_config
|
|
|
+
|
|
|
+ return _load_config("aw-watcher-mpd", DEFAULT_CONFIG)
|
|
|
+
|
|
|
+
|
|
|
+def print_statusline(msg):
|
|
|
+ last_msg_length = (
|
|
|
+ len(print_statusline.last_msg) if hasattr(print_statusline, "last_msg") else 0
|
|
|
+ )
|
|
|
+ print(" " * last_msg_length, end="\r")
|
|
|
+ print(msg, end="\r")
|
|
|
+ print_statusline.last_msg = msg
|
|
|
+
|
|
|
+
|
|
|
+def main():
|
|
|
+ logging.basicConfig(level=logging.INFO)
|
|
|
+
|
|
|
+ config_dir = dirs.get_config_dir("aw-watcher-mpd")
|
|
|
+
|
|
|
+ config = load_config()
|
|
|
+ poll_time = float(config["aw-watcher-mpd"].get("poll_time"))
|
|
|
+ host = config["aw-watcher-mpd"].get("host", None)
|
|
|
+ port = config["aw-watcher-mpd"].get("port", None)
|
|
|
+ if not host or not port:
|
|
|
+ logger.warning(
|
|
|
+ "host or port not specified in config file (in folder {}).".format(
|
|
|
+ config_dir
|
|
|
+ )
|
|
|
+ )
|
|
|
+ sys.exit(1)
|
|
|
+
|
|
|
+ # TODO: Fix --testing flag and set testing as appropriate
|
|
|
+ aw = ActivityWatchClient("aw-watcher-mpd", testing=args.testing)
|
|
|
+ bucketname = "{}_{}".format(aw.client_name, aw.client_hostname)
|
|
|
+ aw.create_bucket(bucketname, "mpd-currently-playing", queued=True)
|
|
|
+ aw.connect()
|
|
|
+
|
|
|
+ mpdClient = None
|
|
|
+ last_song = None
|
|
|
+ song = None
|
|
|
+ last_start_duration = 0
|
|
|
+ last_elapsed = 0
|
|
|
+ while True:
|
|
|
+ try:
|
|
|
+ if not mpdClient:
|
|
|
+ print_statusline("Connecting to mpd...")
|
|
|
+ mpdClient = mpdConnect(host, port)
|
|
|
+ else:
|
|
|
+ try:
|
|
|
+ mpdClient.status()
|
|
|
+ except ConnectionError as e:
|
|
|
+ mpdClient = mpdConnect(host, port)
|
|
|
+ except ConnectionRefusedError as e:
|
|
|
+ print_statusline("Connection to mpd was refused, attempting to reconnect")
|
|
|
+ sleep(poll_time)
|
|
|
+ continue
|
|
|
+
|
|
|
+ try:
|
|
|
+ song = get_current_song(mpdClient)
|
|
|
+ # from pprint import pprint
|
|
|
+ # pprint(track)
|
|
|
+ except Exception as e:
|
|
|
+ logger.error("Unknown Error")
|
|
|
+ logger.error(traceback.format_exc())
|
|
|
+ sleep(0.1)
|
|
|
+ continue
|
|
|
+
|
|
|
+ try:
|
|
|
+ # Outputs a new line when a song ends, giving a short history directly in the log
|
|
|
+ if last_song:
|
|
|
+ last_song_data = data_from_song(last_song)
|
|
|
+ if not song or (
|
|
|
+ song
|
|
|
+ and last_song_data["title"] != data_from_song(song)["title"]
|
|
|
+ ):
|
|
|
+ song_td = timedelta(seconds=last_song["elapsed"])
|
|
|
+ song_time = int(song_td.seconds / 60), int(song_td.seconds % 60)
|
|
|
+ print_statusline(
|
|
|
+ "Song ended ({}:{:02d}): {title} - {artist} ({album})\n".format(
|
|
|
+ *song_time, **last_song_data
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+ last_song_data["startedDuration"] = last_start_duration
|
|
|
+
|
|
|
+ event = Event(timestamp=datetime.now(timezone.utc), data=last_song_data)
|
|
|
+ aw.heartbeat(bucketname, event, pulsetime=poll_time + 1, queued=True)
|
|
|
+ elif song and not (0 < (song["elapsed"] - last_elapsed) < 6):
|
|
|
+ song_td = timedelta(seconds=last_song["elapsed"])
|
|
|
+ song_time = int(song_td.seconds / 60), int(song_td.seconds % 60)
|
|
|
+ print_statusline(
|
|
|
+ "Song seeked ahead/backwards ({}:{:02d}): {title} - {artist} ({album})\n".format(
|
|
|
+ *song_time, **last_song_data
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+ last_song_data["startedDuration"] = last_start_duration
|
|
|
+
|
|
|
+ event = Event(timestamp=datetime.now(timezone.utc), data=last_song_data)
|
|
|
+ aw.heartbeat(bucketname, event, pulsetime=poll_time + 1, queued=True)
|
|
|
+
|
|
|
+ if song:
|
|
|
+ song_data = data_from_song(song)
|
|
|
+
|
|
|
+ if not last_song or (last_song and last_song_data["title"] != song_data["title"]) or not (0 < (song["elapsed"] - last_elapsed) < 6):
|
|
|
+ last_start_duration = song["elapsed"]
|
|
|
+
|
|
|
+ last_elapsed = song["elapsed"]
|
|
|
+
|
|
|
+ song_data["startedDuration"] = last_start_duration
|
|
|
+
|
|
|
+ song_td = timedelta(seconds=song["elapsed"])
|
|
|
+ song_time = int(song_td.seconds / 60), int(song_td.seconds % 60)
|
|
|
+
|
|
|
+ print_statusline(
|
|
|
+ "Current song ({}:{:02d}): {title} - {artist} ({album})".format(
|
|
|
+ *song_time, **song_data
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+ event = Event(timestamp=datetime.now(timezone.utc), data=song_data)
|
|
|
+ aw.heartbeat(bucketname, event, pulsetime=poll_time + 1, queued=True)
|
|
|
+ else:
|
|
|
+ print_statusline("Waiting for song to start playing...")
|
|
|
+
|
|
|
+ last_song = song
|
|
|
+ except Exception as e:
|
|
|
+ print("An exception occurred: {}".format(e))
|
|
|
+ traceback.print_exc()
|
|
|
+ sleep(poll_time)
|
|
|
+
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ main()
|