|
@@ -0,0 +1,203 @@
|
|
|
+#!/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()
|