#!/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 from python_mpv_jsonipc import MPV import argparse 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 MPV") parser.add_argument("--testing", action="store_true", help='run in testing mode') args = parser.parse_args() logger = logging.getLogger("aw-watcher-mpv") DEFAULT_CONFIG = """ [aw-watcher-mpv] ipc_socket = "/tmp/mpv-socket" poll_time = 3.0""" def get_current_video(mpv) -> Optional[dict]: if (mpv.command("get_property", "duration") == None): return None current_video = {} current_video["duration"] = float(mpv.command("get_property", "duration")) current_video["filename"] = mpv.command("get_property", "filename") current_video["time-pos"] = float(mpv.command("get_property", "time-pos")) current_video["volume"] = float(mpv.command("get_property", "volume")) current_video["mute"] = mpv.command("get_property", "mute") current_video["audio"] = "{} - {}".format( mpv.command("get_property", "current-tracks/audio/id"), mpv.command("get_property", "current-tracks/audio/lang"), ) current_video["sub"] = "{} - {}".format( mpv.command("get_property", "current-tracks/sub/id"), mpv.command("get_property", "current-tracks/sub/lang") ) pause = mpv.command("get_property", "pause") if pause == False: return current_video return None def data_from_video(video: dict) -> dict: data = {} data["filename"] = video["filename"] data["duration"] = video["duration"] data["volume"] = video["volume"] data["mute"] = video["mute"] data["audio"] = video["audio"] data["sub"] = video["sub"] logging.debug("VIDEO: {}".format(data["filename"])) return data def mpvConnect(ipc_socket=None): mpv = MPV(start_mpv=False, ipc_socket=ipc_socket) return mpv def load_config(): from aw_core.config import load_config_toml as _load_config return _load_config("aw-watcher-mpv", 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-mpv") config = load_config() poll_time = float(config["aw-watcher-mpv"].get("poll_time")) ipc_socket = config["aw-watcher-mpv"].get("ipc_socket", None) if not ipc_socket: logger.warning( "ipc_socket 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-mpv", testing=args.testing) bucketname = "{}_{}".format(aw.client_name, aw.client_hostname) aw.create_bucket(bucketname, "mpv-currently-playing", queued=True) aw.connect() mpv = None last_video = None video = None last_start_duration = 0 last_elapsed = 0 while True: try: if not mpv: print_statusline("Connecting to mpv...") mpv = mpvConnect(ipc_socket) else: try: mpv.command("get_property", "duration") except BrokenPipeError as e: mpv = mpvConnect(ipc_socket) except Exception as e: print_statusline("Connection to mpv was refused, attempting to reconnect") sleep(poll_time) continue try: video = get_current_video(mpv) except Exception as e: logger.error("Unknown Error") logger.error(traceback.format_exc()) sleep(0.1) continue try: # Outputs a new line when a video ends, giving a short history directly in the log if last_video: last_video_data = data_from_video(last_video) if not video or ( video and last_video_data["filename"] != data_from_video(video)["filename"] ): video_td = timedelta(seconds=last_video["time-pos"]) video_time = int(video_td.seconds / 60), int(video_td.seconds % 60) print_statusline( "Video ended ({}:{:02d}): {filename}\n".format( *video_time, **last_video_data ) ) last_video_data["startedDuration"] = last_start_duration event = Event(timestamp=datetime.now(timezone.utc), data=last_video_data) aw.heartbeat(bucketname, event, pulsetime=poll_time + 1, queued=True) elif video and not (0 < (video["time-pos"] - last_elapsed) < 6): video_td = timedelta(seconds=last_video["time-pos"]) video_time = int(video_td.seconds / 60), int(video_td.seconds % 60) print_statusline( "Video seeked ahead/backwards ({}:{:02d}): {filename}\n".format( *video_time, **last_video_data ) ) last_video_data["startedDuration"] = last_start_duration event = Event(timestamp=datetime.now(timezone.utc), data=last_video_data) aw.heartbeat(bucketname, event, pulsetime=poll_time + 1, queued=True) if video: video_data = data_from_video(video) if not last_video or (last_video and last_video_data["filename"] != video_data["filename"]) or not (0 < (video["time-pos"] - last_elapsed) < 6): last_start_duration = video["time-pos"] last_elapsed = video["time-pos"] video_data["startedDuration"] = last_start_duration video_td = timedelta(seconds=video["time-pos"]) video_time = int(video_td.seconds / 60), int(video_td.seconds % 60) print_statusline( "Current video ({}:{:02d}): {filename}".format( *video_time, **video_data ) ) event = Event(timestamp=datetime.now(timezone.utc), data=video_data) aw.heartbeat(bucketname, event, pulsetime=poll_time + 1, queued=True) else: print_statusline("Waiting for video to start playing...") last_video = video except Exception as e: print("An exception occurred: {}".format(e)) traceback.print_exc() sleep(poll_time) if __name__ == "__main__": main()