Compare commits

2 Commits
main ... groups

6 changed files with 273 additions and 147 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
videos/*
videocommander.db

View File

@@ -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
}
]
}
}

View File

@@ -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>

View File

@@ -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()

View File

@@ -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)

View File

@@ -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',