#!/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()