commit 7bb45b12cac3b992eb37dc3414adaa8961aa09d6 Author: Bradley Bickford Date: Thu Apr 24 15:28:53 2025 -0400 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eba74f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +venv/ \ No newline at end of file diff --git a/config.json b/config.json new file mode 100644 index 0000000..b803af6 --- /dev/null +++ b/config.json @@ -0,0 +1,44 @@ +{ + "instance_name": "Test", + "mpv_arguments": [ + "--loop", + "--screen=0" + ], + "buttons": [ + { + "name": "Goofy", + "video_url": "videos/goofy.mp4", + "default": true + }, + { + "name": "Neutron Stars", + "video_url": "videos/neutronstars.mp4", + "default": false + }, + { + "name": "Gimme The Bleach Boys", + "video_url": "videos/bleach.mp4", + "default": false + }, + { + "name": "The Cloak", + "video_url": "videos/cloak.mp4", + "default": false + }, + { + "name": "Dog of Wisdom", + "video_url": "videos/dog.mp4", + "default": false + }, + { + "name": "Nyan Cat", + "video_url": "videos/nyan.mp4", + "default": false + }, + { + "name": "Smooth Jazz Nyan Cat", + "video_url": "videos/smoothjazznyan.mp4", + "default": false + } + ] +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..0b06f2e --- /dev/null +++ b/index.html @@ -0,0 +1,51 @@ + + + + MPV Commander + + + + + + +
+

MPV Commander Instance {{ instance_name }}

+

Which video stream do you want to show?

+ {% for index in range(buttons | length) %} + {% if index % 3 == 0 %} +
+ {% endif %} + {% set current_name = buttons[index]["name"] %} + + {% if index + 1 == buttons | length or (index + 1) % 3 == 0 %} +
+ {% endif %} + {% endfor %} +
+ + \ No newline at end of file diff --git a/mpv_commander.py b/mpv_commander.py new file mode 100644 index 0000000..117b9c9 --- /dev/null +++ b/mpv_commander.py @@ -0,0 +1,62 @@ +import os +import signal +import subprocess +import sqlite3 +import time +import json + +get_active_video = r''' +SELECT video_url FROM buttons +WHERE is_active = 1 +LIMIT 1 +''' + +def run_mpv(previous_process, mpv_arguments, video_url): + if not previous_process is None: + os.killpg(os.getpgid(previous_process.pid), signal.SIGTERM) + + return subprocess.Popen( + "/usr/bin/mpv {arguments} {video_url}".format( + arguments = " ".join(mpv_arguments), + video_url = video_url + ), + stdout = subprocess.PIPE, + stderr = subprocess.PIPE, + shell = True, + preexec_fn = os.setsid + ) + +def get_active_video_url(db): + cursor = db.execute(get_active_video) + return cursor.fetchall()[0][0] + +with open("config.json", "r") as config_file: + config_json = json.loads(config_file.read()) + +current_process = None +current_video_url = None +db = sqlite3.connect("mpvcommander.db") + +while True: + if current_process is None: + current_video_url = get_active_video_url(db) + + current_process = run_mpv( + current_process, + config_json["mpv_arguments"], + current_video_url + ) + + active_video_url = get_active_video_url(db) + + if current_video_url != active_video_url: + current_video_url = active_video_url + + current_process = run_mpv( + current_process, + config_json["mpv_arguments"], + current_video_url + ) + + time.sleep(1) + \ No newline at end of file diff --git a/mpv_dbbuilder.py b/mpv_dbbuilder.py new file mode 100644 index 0000000..d4855ef --- /dev/null +++ b/mpv_dbbuilder.py @@ -0,0 +1,71 @@ +import sqlite3 +import json +import sys + +destroy_table = r''' +DROP TABLE IF EXISTS buttons +''' + +make_table = r''' +CREATE TABLE IF NOT EXISTS buttons +(button_name TEXT PRIMARY KEY, +video_url TEXT NOT NULL, +is_default BOOLEAN NOT NULL CHECK (is_default IN (0, 1)), +is_active BOOLEAN NOT NULL CHECK (is_active IN (0, 1))) +''' + +insert_row = r''' +INSERT INTO buttons VALUES +(?, ?, ?, ?) +''' + +with open("config.json", "r") as config_file: + config_json = json.loads(config_file.read()) + +config_issues = "" + +if not "instance_name" in config_json.keys(): + config_issues = config_issues + "The config item instance_name is required\n" + +for i in range(len(config_json["buttons"])): + if not "name" in config_json["buttons"][i].keys(): + config_issues = config_issues + "Config index {index} is missing the required name parameter\n".format(index = (i + 1)) + + if not "video_url" in config_json["buttons"][i].keys(): + config_issues = config_issues + "Config index {index} is missing the required video_url parameter\n".format(index = (i + 1)) + + if not "default" in config_json["buttons"][i].keys(): + config_issues = config_issues + "Config index {index} is missing the required default parameter\n".format(index = (i + 1)) + +all_defaults = [element for element in config_json["buttons"] if element["default"]] + +if len(all_defaults) > 1: + config_issues = config_issues + "More than one button config is set as default, only one can be the default, remove one of these: {button_list}\n".format( + button_list = ", ".join([element["name"] if "name" in element else "NAME MISSING" for element in all_defaults]) + ) +elif len(all_defaults) == 0: + config_issues = config_issues = "At least one button config must be set as the default\n" + +if len(config_issues) != 0: + print("Config issues detected! Please correct these issues before trying again") + print(config_issues) + sys.exit(1) + +db = sqlite3.connect("mpvcommander.db") +db.execute(destroy_table) +db.execute(make_table) + +for button in config_json["buttons"]: + db.execute(insert_row, [ + button["name"], + button["video_url"], + 1 if button["default"] else 0, + 1 if button["default"] else 0 + ]) + + db.commit() + +db.close() + + + diff --git a/mpv_server.py b/mpv_server.py new file mode 100644 index 0000000..6550538 --- /dev/null +++ b/mpv_server.py @@ -0,0 +1,90 @@ +from flask import Flask, request, jsonify, g +from jinja2 import Template +import json +import sys +import sqlite3 + +disable_all_active = r''' +UPDATE buttons SET is_active = 0 +''' + +set_active_by_name_sql = r''' +UPDATE buttons SET is_active = 1 +WHERE button_name = ? +''' + +with open("config.json", "r") as config_file: + config_json = json.loads(config_file.read()) + +app = Flask(__name__) + +def get_db(): + db = getattr(g, "_database", None) + + if db is None: + db = g._database = sqlite3.connect("mpvcommander.db") + + return db + +def set_active_by_name(name): + db = get_db() + + db.execute(disable_all_active) + db.execute(set_active_by_name, name) + db.commit() + +@app.route("/") +def root_route(): + with open("index.html", "r") as html_file: + template = Template(html_file.read()) + return template.render(config_json), 200 + +@app.route("/stream", methods=["POST"]) +def stream_route(): + if request.content_type == 'application/json': + body = request.get_json() + + if not "name" in body.keys(): + error_response = { + 'status': "ERROR", + "reason": "A name must be specified in the body of the request" + } + + return jsonify(error_response), 400 + + resource_list = [element for element in config_json["buttons"] if element["name"] == body["name"]] + + if(len(resource_list) == 0): + error_response = { + 'status': "ERROR", + "reason": "The name {name} does not exist in config['buttons'], check your config".format(name = body["name"]) + } + + return jsonify(error_response), 400 + + db = get_db() + + db.execute(disable_all_active) + db.execute(set_active_by_name_sql, [body["name"]]) + db.commit() + + return jsonify(resource_list[0]), 200 + else: + error_response = { + 'status': 'ERROR', + 'reason': 'Posted body must be of content type application/json' + } + + return jsonify(error_response) + +@app.teardown_appcontext +def close_connection(exception): + db = getattr(g, '_database', None) + + if db is not None: + db.close() + + +if __name__ == '__main__': + app.run(debug=True, host="0.0.0.0", port=1801) + diff --git a/mpvcommander.db b/mpvcommander.db new file mode 100644 index 0000000..f2943c9 Binary files /dev/null and b/mpvcommander.db differ diff --git a/videos/goofy.mp4 b/videos/goofy.mp4 new file mode 100644 index 0000000..9714b67 Binary files /dev/null and b/videos/goofy.mp4 differ diff --git a/videos/neutronstars.mp4 b/videos/neutronstars.mp4 new file mode 100644 index 0000000..ab28073 Binary files /dev/null and b/videos/neutronstars.mp4 differ