Compare commits
2 Commits
f25b9d88b4
...
d8a071a965
| Author | SHA1 | Date | |
|---|---|---|---|
| d8a071a965 | |||
| 8db9f3bd9f |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
videos/*
|
||||
videocommander.db
|
||||
138
config.json
138
config.json
@@ -1,29 +1,98 @@
|
||||
{
|
||||
"instance_name": "Test",
|
||||
"db_name": "videocommander.db",
|
||||
"display_config": ":1",
|
||||
"disabled_name": "Disable Video",
|
||||
"video_tool": "ffplay",
|
||||
"buttons": [
|
||||
"data": [
|
||||
{
|
||||
"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"
|
||||
],
|
||||
"type": "Button",
|
||||
"name": "Disable Video",
|
||||
"video_url": "",
|
||||
"video_tool_arguments": [],
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"type": "Group",
|
||||
"name": "Animals",
|
||||
"data": [
|
||||
{
|
||||
"type": "Group",
|
||||
"name": "Dogs",
|
||||
"data": [
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "Goofy",
|
||||
"video_url": "videos/goofy.mp4",
|
||||
"video_tool_arguments": [
|
||||
"-fs",
|
||||
"-loop",
|
||||
"0"
|
||||
],
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "Dog of Wisdom",
|
||||
"video_url": "videos/dog.mp4",
|
||||
"video_tool_arguments": [
|
||||
"-fs",
|
||||
"-loop",
|
||||
"0"
|
||||
],
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Group",
|
||||
"name": "Cats",
|
||||
"data": [
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "Nyan Cat",
|
||||
"video_url": "videos/nyan.mp4",
|
||||
"video_tool_arguments": [
|
||||
"-fs",
|
||||
"-loop",
|
||||
"0"
|
||||
],
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name":"Smooth Jazz Nyan Cat",
|
||||
"video_url": "videos/smoothjazznyan.mp4",
|
||||
"video_tool_arguments": [
|
||||
"-fs",
|
||||
"-loop",
|
||||
"0"
|
||||
],
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Group",
|
||||
"name": "Space",
|
||||
"data": [
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "Neutron Stars",
|
||||
"video_url": "videos/neutronstars.mp4",
|
||||
"video_tool_arguments": [
|
||||
"-fs",
|
||||
"-loop",
|
||||
"0"
|
||||
],
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "Gimme The Yeet Boys",
|
||||
"video_url": "videos/yeet.mp4",
|
||||
"video_tool_arguments": [
|
||||
@@ -34,6 +103,7 @@
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "Hey Bender",
|
||||
"video_url": "videos/bender.mp4",
|
||||
"video_tool_arguments": [
|
||||
@@ -42,36 +112,6 @@
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
65
index.html
65
index.html
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html style="background-color: #ebe1c7;">
|
||||
<head>
|
||||
<title>Video Commander</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@@ -7,45 +7,68 @@
|
||||
<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) {
|
||||
async function onclick_processor(name, is_group) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
window.location.origin + "/stream",
|
||||
{
|
||||
method: "POST",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({ "name": name })
|
||||
if (is_group) {
|
||||
window.location.href = window.location.origin + '/?group=' + encodeURIComponent(name)
|
||||
} else {
|
||||
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}`)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Response status: ${response.status}`)
|
||||
const json = await response.json()
|
||||
console.log(json)
|
||||
}
|
||||
|
||||
const json = await response.json()
|
||||
console.log(json)
|
||||
} catch (error) {
|
||||
console.log(error.message)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<body>
|
||||
<body style="background-color: #ebe1c7;">
|
||||
<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>
|
||||
<hr>
|
||||
{% if buttons | length != 0 %}
|
||||
<h3 class="text-center">Current group ({{ current_group }}) streams</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>
|
||||
{% set current_name = buttons[index][0] %}
|
||||
<button class="col-sm-4 btn-default p-3 my-1" onclick="onclick_processor('{{ current_name }}', false)">{{ current_name }}</button>
|
||||
{% if index + 1 == buttons | length or (index + 1) % 3 == 0 %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>There are no stream buttons in this group</p>
|
||||
{% endif %}
|
||||
{% if groups | length != 0 %}
|
||||
<h3 class="text-center">Current group ({{ current_group }}) sub-groups</h3>
|
||||
{% for index in range(groups | length) %}
|
||||
{% if index % 3 == 0 %}
|
||||
<div class="row">
|
||||
{% endif %}
|
||||
{% set current_name = groups[index][0] %}
|
||||
<button class="col-sm-4 btn-default p-3 my-1" onclick="onclick_processor('{{ current_name }}', true)">{{ current_name }}</button>
|
||||
{% if index + 1 == groups | length or (index + 1) % 3 == 0 %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<hr>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -2,70 +2,124 @@ import sqlite3
|
||||
import json
|
||||
import sys
|
||||
|
||||
destroy_table = r'''
|
||||
destroy_button_table = r'''
|
||||
DROP TABLE IF EXISTS buttons
|
||||
'''
|
||||
|
||||
make_table = r'''
|
||||
destroy_group_table = r'''
|
||||
DROP TABLE IF EXISTS groups
|
||||
'''
|
||||
|
||||
make_button_table = r'''
|
||||
CREATE TABLE IF NOT EXISTS buttons
|
||||
(button_name TEXT PRIMARY KEY,
|
||||
(button_name TEXT PRIMARY KEY,
|
||||
page_group TEXT NOT NULL,
|
||||
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
|
||||
(?, ?, ?, ?, ?)
|
||||
make_group_table = r'''
|
||||
CREATE TABLE IF NOT EXISTS groups
|
||||
(group_name TEXT PRIMARY KEY,
|
||||
parent_group TEXT NOT NULL
|
||||
)
|
||||
'''
|
||||
|
||||
insert_button_row = r'''
|
||||
INSERT INTO buttons VALUES
|
||||
(?, ?, ?, ?, ?, ?)
|
||||
'''
|
||||
|
||||
insert_group_row = r'''
|
||||
INSERT INTO groups VALUES
|
||||
(?, ?)
|
||||
'''
|
||||
|
||||
default_count = 0
|
||||
failed_a_test = False
|
||||
|
||||
def recursive_data_check(next_list: list, current_group_name: str):
|
||||
global default_count
|
||||
global failed_a_test
|
||||
|
||||
for element in next_list:
|
||||
if not "type" in element.keys():
|
||||
print(f"An element in {current_group_name} is missing the type key")
|
||||
failed_a_test = True
|
||||
|
||||
if not "name" in element.keys():
|
||||
print(f"An element in {current_group_name} with type {element["type"]} is missing the name key")
|
||||
failed_a_test = True
|
||||
|
||||
if element["type"] == "Group":
|
||||
if not "data" in element.keys():
|
||||
print(f"The element in {current_group_name} with name {element["name"]} is missing the data key")
|
||||
failed_a_test = True
|
||||
|
||||
recursive_data_check(element["data"], element["name"])
|
||||
else:
|
||||
if not "video_url" in element.keys():
|
||||
print(f"The element in {current_group_name} with name {element["name"]} is missing the video_url key")
|
||||
failed_a_test = True
|
||||
|
||||
if not "video_tool_arguments" in element.keys():
|
||||
print(f"The element in {current_group_name} with name {element["name"]} is missing the video_tool_arguments key")
|
||||
failed_a_test = True
|
||||
|
||||
if not "default" in element.keys():
|
||||
print(f"The element in {current_group_name} with name {element["name"]} is missing the default key")
|
||||
failed_a_test = True
|
||||
|
||||
if element["default"]:
|
||||
default_count = default_count + 1
|
||||
|
||||
def recursive_insert(db: sqlite3.Connection, next_list: list, current_group_name: str):
|
||||
for element in next_list:
|
||||
if element["type"] == "Group":
|
||||
db.execute(insert_group_row, [
|
||||
element["name"],
|
||||
current_group_name
|
||||
])
|
||||
db.commit()
|
||||
recursive_insert(db, element["data"], element["name"])
|
||||
else:
|
||||
db.execute(insert_button_row, [
|
||||
element["name"],
|
||||
current_group_name,
|
||||
element["video_url"],
|
||||
" ".join(element["video_tool_arguments"]) if len(element["video_tool_arguments"]) != 0 else "",
|
||||
1 if element["default"] else 0,
|
||||
1 if element["default"] else 0
|
||||
])
|
||||
|
||||
db.commit()
|
||||
|
||||
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"
|
||||
print("The config item instance_name is required")
|
||||
|
||||
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))
|
||||
recursive_data_check(config_json["data"], "Home")
|
||||
|
||||
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 default_count > 1:
|
||||
print(f"There can only be one default button configured, current number configured: {default_count}")
|
||||
failed_a_test = True
|
||||
elif default_count == 0:
|
||||
print("There must be one default button configured, currently there are none")
|
||||
|
||||
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)
|
||||
if failed_a_test:
|
||||
sys.exit(1)
|
||||
|
||||
db = sqlite3.connect(config_json["db_name"])
|
||||
db.execute(destroy_table)
|
||||
db.execute(make_table)
|
||||
db.execute(destroy_button_table)
|
||||
db.execute(destroy_group_table)
|
||||
db.execute(make_button_table)
|
||||
db.execute(make_group_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()
|
||||
recursive_insert(db, config_json["data"], "Home")
|
||||
|
||||
db.close()
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@ import sqlite3
|
||||
import time
|
||||
import json
|
||||
|
||||
get_active_video = r'''
|
||||
SELECT video_url, video_arguments FROM buttons
|
||||
get_active_video_sql = r'''
|
||||
SELECT video_url, video_arguments, button_name FROM buttons
|
||||
WHERE is_active = 1
|
||||
LIMIT 1
|
||||
'''
|
||||
|
||||
def play_video(previous_process, video_player_path, player_arguments, video_url):
|
||||
def play_video(previous_process, video_player_path, player_arguments, video_url, env):
|
||||
if not previous_process is None:
|
||||
os.killpg(os.getpgid(previous_process.pid), signal.SIGTERM)
|
||||
|
||||
@@ -24,42 +24,41 @@ def play_video(previous_process, video_player_path, player_arguments, video_url)
|
||||
stdout = subprocess.PIPE,
|
||||
stderr = subprocess.PIPE,
|
||||
shell = True,
|
||||
preexec_fn = os.setsid
|
||||
preexec_fn = os.setsid,
|
||||
env = env
|
||||
)
|
||||
|
||||
def get_active_video_url(db):
|
||||
cursor = db.execute(get_active_video)
|
||||
def get_active_video(db):
|
||||
cursor = db.execute(get_active_video_sql)
|
||||
return cursor.fetchall()[0]
|
||||
|
||||
with open("config.json", "r") as config_file:
|
||||
config_json = json.loads(config_file.read())
|
||||
|
||||
my_env = os.environ.copy()
|
||||
my_env["DISPLAY"] = config_json["display_config"]
|
||||
|
||||
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)
|
||||
active_video = get_active_video(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]:
|
||||
if current_video is None or current_video[2] != active_video[2]:
|
||||
current_video = active_video
|
||||
|
||||
current_process = play_video(
|
||||
current_process,
|
||||
config_json["video_tool"],
|
||||
current_video[0],
|
||||
current_video[1]
|
||||
)
|
||||
if current_video[2] == config_json["disabled_name"] and not current_process is None:
|
||||
os.killpg(os.getpgid(current_process.pid), signal.SIGTERM)
|
||||
current_process = None
|
||||
else:
|
||||
current_process = play_video(
|
||||
current_process,
|
||||
config_json["video_tool"],
|
||||
current_video[0],
|
||||
current_video[1],
|
||||
my_env
|
||||
)
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
@@ -13,6 +13,16 @@ UPDATE buttons SET is_active = 1
|
||||
WHERE button_name = ?
|
||||
'''
|
||||
|
||||
find_buttons_for_group = r'''
|
||||
SELECT * FROM buttons
|
||||
WHERE page_group = ?
|
||||
'''
|
||||
|
||||
find_groups_for_parent = r'''
|
||||
SELECT * FROM groups
|
||||
WHERE parent_group = ?
|
||||
'''
|
||||
|
||||
with open("config.json", "r") as config_file:
|
||||
config_json = json.loads(config_file.read())
|
||||
|
||||
@@ -36,8 +46,17 @@ def set_active_by_name(name):
|
||||
@app.route("/")
|
||||
def root_route():
|
||||
with open("index.html", "r") as html_file:
|
||||
db = get_db()
|
||||
|
||||
group = request.args.get(key="group", default="Home")
|
||||
|
||||
template = Template(html_file.read())
|
||||
return template.render(config_json), 200
|
||||
return template.render({
|
||||
"instance_name": config_json["instance_name"],
|
||||
"current_group": group,
|
||||
"buttons": db.execute(find_buttons_for_group, [group]).fetchall(),
|
||||
"groups": db.execute(find_groups_for_parent, [group]).fetchall()
|
||||
}), 200
|
||||
|
||||
@app.route("/stream", methods=["POST"])
|
||||
def stream_route():
|
||||
@@ -51,16 +70,6 @@ def stream_route():
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@@ -68,7 +77,7 @@ def stream_route():
|
||||
db.execute(set_active_by_name_sql, [body["name"]])
|
||||
db.commit()
|
||||
|
||||
return jsonify(resource_list[0]), 200
|
||||
return jsonify(body["name"]), 200
|
||||
else:
|
||||
error_response = {
|
||||
'status': 'ERROR',
|
||||
|
||||
Reference in New Issue
Block a user