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