Rework of MPV Commander into a new project
This commit is contained in:
commit
f5e52da613
77
config.json
Normal file
77
config.json
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"instance_name": "Test",
|
||||||
|
"db_name": "videocommander.db",
|
||||||
|
"video_tool": "ffplay",
|
||||||
|
"buttons": [
|
||||||
|
{
|
||||||
|
"name": "Goofy",
|
||||||
|
"video_url": "videos/goofy.mp4",
|
||||||
|
"video_tool_arguments": [
|
||||||
|
"-fs",
|
||||||
|
"-loop",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Neutron Stars",
|
||||||
|
"video_url": "videos/neutronstars.mp4",
|
||||||
|
"video_tool_arguments": [
|
||||||
|
"-fs",
|
||||||
|
"-loop",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Gimme The Yeet Boys",
|
||||||
|
"video_url": "videos/yeet.mp4",
|
||||||
|
"video_tool_arguments": [
|
||||||
|
"-fs",
|
||||||
|
"-loop",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Hey Bender",
|
||||||
|
"video_url": "videos/bender.mp4",
|
||||||
|
"video_tool_arguments": [
|
||||||
|
"-fs",
|
||||||
|
"-loop",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dog of Wisdom",
|
||||||
|
"video_url": "videos/dog.mp4",
|
||||||
|
"video_tool_arguments": [
|
||||||
|
"-fs",
|
||||||
|
"-loop",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nyan Cat",
|
||||||
|
"video_url": "videos/nyan.mp4",
|
||||||
|
"video_tool_arguments": [
|
||||||
|
"-fs",
|
||||||
|
"-loop",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Smooth Jazz Nyan Cat",
|
||||||
|
"video_url": "videos/smoothjazznyan.mp4",
|
||||||
|
"video_tool_arguments": [
|
||||||
|
"-fs",
|
||||||
|
"-loop",
|
||||||
|
"0"
|
||||||
|
],
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
51
index.html
Normal file
51
index.html
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Video Commander</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<script>
|
||||||
|
async function onclick_processor(name) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
window.location.origin + "/stream",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
"headers": {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ "name": name })
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Response status: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = await response.json()
|
||||||
|
console.log(json)
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="text-center">Video Commander Instance {{ instance_name }}</h1>
|
||||||
|
<h3 class="text-center">Which video stream do you want to show?</h3>
|
||||||
|
{% for index in range(buttons | length) %}
|
||||||
|
{% if index % 3 == 0 %}
|
||||||
|
<div class="row">
|
||||||
|
{% endif %}
|
||||||
|
{% set current_name = buttons[index]["name"] %}
|
||||||
|
<button class="col-sm-4 btn-default p-3 my-1" onclick="onclick_processor('{{ current_name }}')">{{ current_name }}</button>
|
||||||
|
{% if index + 1 == buttons | length or (index + 1) % 3 == 0 %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
73
video_dbbuilder.py
Normal file
73
video_dbbuilder.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
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,
|
||||||
|
video_arguments 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(config_json["db_name"])
|
||||||
|
db.execute(destroy_table)
|
||||||
|
db.execute(make_table)
|
||||||
|
|
||||||
|
for button in config_json["buttons"]:
|
||||||
|
db.execute(insert_row, [
|
||||||
|
button["name"],
|
||||||
|
button["video_url"],
|
||||||
|
" ".join(button["video_tool_arguments"]) if len(button["video_tool_arguments"]) != 0 else "",
|
||||||
|
1 if button["default"] else 0,
|
||||||
|
1 if button["default"] else 0
|
||||||
|
])
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
65
video_player.py
Normal file
65
video_player.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import subprocess
|
||||||
|
import sqlite3
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
|
get_active_video = r'''
|
||||||
|
SELECT video_url, video_arguments FROM buttons
|
||||||
|
WHERE is_active = 1
|
||||||
|
LIMIT 1
|
||||||
|
'''
|
||||||
|
|
||||||
|
def play_video(previous_process, video_player_path, player_arguments, video_url):
|
||||||
|
if not previous_process is None:
|
||||||
|
os.killpg(os.getpgid(previous_process.pid), signal.SIGTERM)
|
||||||
|
|
||||||
|
return subprocess.Popen(
|
||||||
|
"{video_player} {arguments} {video_url}".format(
|
||||||
|
video_player = video_player_path,
|
||||||
|
arguments = player_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]
|
||||||
|
|
||||||
|
with open("config.json", "r") as config_file:
|
||||||
|
config_json = json.loads(config_file.read())
|
||||||
|
|
||||||
|
current_process = None
|
||||||
|
current_video = None
|
||||||
|
db = sqlite3.connect(config_json["db_name"])
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if current_process is None:
|
||||||
|
current_video = get_active_video_url(db)
|
||||||
|
|
||||||
|
current_process = play_video(
|
||||||
|
current_process,
|
||||||
|
config_json["video_tool"],
|
||||||
|
current_video[0],
|
||||||
|
current_video[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
active_video = get_active_video_url(db)
|
||||||
|
|
||||||
|
if current_video[0] != active_video[0]:
|
||||||
|
current_video = active_video
|
||||||
|
|
||||||
|
current_process = play_video(
|
||||||
|
current_process,
|
||||||
|
config_json["video_tool"],
|
||||||
|
current_video[0],
|
||||||
|
current_video[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
90
video_webserver.py
Normal file
90
video_webserver.py
Normal file
@ -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(config_json["db_name"])
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user