FrontEnd erweitert, Löschen Funktion für Warteschlange, Kleinigkeiten bereinigt und nach Server start das auslesen der Pfade und den Autostart

This commit is contained in:
Eduard Wisch 2025-05-18 20:45:56 +02:00
parent d3bc4e3979
commit 7147f6385e
11 changed files with 178 additions and 37 deletions

View file

@ -1,9 +1,11 @@
import asyncio import asyncio
import logging import logging
from platform import python_version
from app.main_server import Server from app.main_server import Server
if __name__ == "__main__": if __name__ == "__main__":
print(python_version())
obj_server = Server() obj_server = Server()
try: try:

View file

@ -1,9 +1,13 @@
import logging import logging
import asyncio import asyncio
import time import time
from typing import TYPE_CHECKING
from app.class_file_convert_read_out import Process from app.class_file_convert_read_out import Process
from app.class_media_file_stat import Stat from app.class_media_file_stat import Stat
if TYPE_CHECKING:
from app.class_media_file import Media
class Convert: class Convert:
def __init__(self, websocket, cfg, obj_path): def __init__(self, websocket, cfg, obj_path):
self.yaml = cfg self.yaml = cfg
@ -85,13 +89,13 @@ class Convert:
obj.convert_end = time.time() obj.convert_end = time.time()
obj_stat.save_stat(obj) obj_stat.save_stat(obj)
def convert_cmd(self, obj): def convert_cmd(self, obj):
command_convert = [ command_convert = [
"ffmpeg", "-y", "-i", obj.source_file, "ffmpeg", "-y", "-i", obj.source_file,
# "-init_hw_device", "vaapi=va:/dev/dri/renderD128",
"-map", "0:0", "-map", "0:0",
"-c:v", "libsvtav1", "-c:v", "libsvtav1",
#"-c:v", "av1_qsv",
"-preset", "5", "-preset", "5",
"-crf", "30", "-crf", "30",
"-g", "240", "-g", "240",
@ -119,6 +123,7 @@ class Convert:
]) ])
command_convert.append(obj.target_file) command_convert.append(obj.target_file)
obj.cmd = command_convert
return command_convert return command_convert
def snake_update(self): def snake_update(self):

View file

@ -39,15 +39,15 @@ class Path:
paths_extrat_dict = {"paths": paths} paths_extrat_dict = {"paths": paths}
try: try:
with open(f"app/cfg/{self.yaml["path_file"]}", "w", encoding="utf8") as file: with open(f"app/cfg/{self.yaml['path_file']}", "w", encoding="utf8") as file:
yaml.dump(paths_extrat_dict, file, default_flow_style=False, indent=4) yaml.dump(paths_extrat_dict, file, default_flow_style=False, indent=4)
logging.info(f"{len(self.paths)} paths were saved to file") logging.info(f"{len(self.paths)} paths were saved to file")
return 1 return 1
except FileNotFoundError: except FileNotFoundError:
logging.error(f"File {self.yaml["path_file"]} not found") logging.error(f"File {self.yaml['path_file']} not found")
return 0 return 0
except IOError: except IOError:
logging.critical(f"Error file {self.yaml["path_file"]} maybe damaged") logging.critical(f"Error file {self.yaml['path_file']} maybe damaged")
return 0 return 0
# Achtung Abrufen aus Datei und neu einlesen der ffprobe Daten fehlt noch # Achtung Abrufen aus Datei und neu einlesen der ffprobe Daten fehlt noch
@ -57,18 +57,20 @@ class Path:
:return: True or False :return: True or False
""" """
try: try:
with open(f"app/cfg/{self.yaml["path_file"]}", "r", encoding="utf8") as file: with open(f"app/cfg/{self.yaml['path_file']}", "r", encoding="utf8") as file:
list_paths = yaml.safe_load(file) list_paths = yaml.safe_load(file)
if len(list_paths) > 0: count = sum(1 for v in list_paths.values() if v)
if count > 0:
paths = list_paths paths = list_paths
logging.info(f"{len(paths)} paths were read from file") logging.info(f"{count} paths were read from file")
return 1 return 1
except FileNotFoundError: except FileNotFoundError:
logging.error(f"File {self.yaml["path_file"]} not found") logging.error(f"File {self.yaml['path_file']} not found")
return 0 return 0
except IOError: except IOError:
logging.critical(f"Error file {self.yaml["path_file"]} maybe damaged") logging.critical(f"Error file {self.yaml['path_file']} maybe damaged")
return 0 return 0
def delete_path(self, obj_id:int): def delete_path(self, obj_id:int):
@ -139,6 +141,8 @@ class Path:
elif select == "s": elif select == "s":
return json_data.get("streams", []) return json_data.get("streams", [])
return ""
def get_with_ffprobe(self, path:str): def get_with_ffprobe(self, path:str):
try: try:
path = path.strip() path = path.strip()
@ -189,7 +193,7 @@ class Path:
list_active_paths = {} list_active_paths = {}
for obj in self.paths.values(): for obj in self.paths.values():
if obj.status is None: if obj.status is None or obj.status == 1 or obj.status == 2 or obj.status == 3:
list_active_paths.update({obj.id: obj.to_dict_active_paths()}) list_active_paths.update({obj.id: obj.to_dict_active_paths()})
return {"data_queue": list_active_paths} return {"data_queue": list_active_paths}

View file

@ -1,4 +1,3 @@
import logging
import os import os
import math import math
import time import time
@ -9,6 +8,7 @@ class Media:
def __init__(self, path, streams_video, streams_audio, streams_subtitle, streams_format): def __init__(self, path, streams_video, streams_audio, streams_subtitle, streams_format):
# misc # misc
self.id = int(f"{int(time.time() * 1000)}{str(Media._id_counter).zfill(3)}") self.id = int(f"{int(time.time() * 1000)}{str(Media._id_counter).zfill(3)}")
self.cmd : list = None
# source # source
self.source_file: str = path self.source_file: str = path
self.source_path: str = os.path.dirname(path) self.source_path: str = os.path.dirname(path)
@ -20,7 +20,7 @@ class Media:
self.source_time: int = 0 self.source_time: int = 0
# target # target
self.target_file: str = f"{path.rsplit(".", 1)[0]}.webm" self.target_file: str = f"{path.rsplit('.', 1)[0]}.webm"
self.target_file_name: str = os.path.basename(self.target_file) self.target_file_name: str = os.path.basename(self.target_file)
self.target_size: int = 0 self.target_size: int = 0
@ -54,8 +54,8 @@ class Media:
count = 1 count = 1
string = "" string = ""
for video_stream in stream_list: for video_stream in stream_list:
string += f"{video_stream.get("codec_type").capitalize()} {count}" if video_stream.get( string += f"{video_stream.get('codec_type').capitalize()} {count}" if video_stream.get(
"codec_type") else "Format" 'codec_type') else "Format"
for key, value in video_stream.items(): for key, value in video_stream.items():
string += f" -- {key}: {value}" string += f" -- {key}: {value}"
@ -96,7 +96,10 @@ class Media:
# target # target
"target_file_name": self.target_file_name, "target_file_name": self.target_file_name,
"target_file": self.target_file, "target_file": self.target_file,
"target_size": self.target_size "target_size": self.target_size,
#process
"status": self.status
} }
def to_dict_stat(self): def to_dict_stat(self):
@ -155,7 +158,7 @@ class Media:
days = round(seconds // (24 * 3600)) days = round(seconds // (24 * 3600))
seconds %= (24 * 3600) seconds %= (24 * 3600)
if days and str_day is not None: if days and str_day is not None:
d = (f"{days} {str_day}") d = f"{days} {str_day}"
else: else:
d = "" d = ""

View file

@ -3,6 +3,7 @@ import asyncio
import websockets import websockets
import json import json
import logging import logging
import shlex
from asyncio import CancelledError from asyncio import CancelledError
from websockets import InvalidUpgrade, ConnectionClosed, ConnectionClosedError from websockets import InvalidUpgrade, ConnectionClosed, ConnectionClosedError
@ -26,6 +27,15 @@ class Server:
self.obj_path = Path(self.yaml) self.obj_path = Path(self.yaml)
self.obj_convert = Convert(self, self.yaml, self.obj_path) self.obj_convert = Convert(self, self.yaml, self.obj_path)
async def start_convert(self):
global var_convert_active
if var_convert_active == False and self.yaml['autostart']:
await self.obj_convert.snake_waiting()
var_convert_active = True
else:
self.obj_convert.snake_update()
async def send_websocket(self, message): async def send_websocket(self, message):
message_json = json.dumps(message) message_json = json.dumps(message)
@ -52,15 +62,12 @@ class Server:
if data.get("data_path"): if data.get("data_path"):
self.obj_path.receive_paths(data.get("data_path")) self.obj_path.receive_paths(data.get("data_path"))
await self.send_websocket(self.obj_path.queue_path_to_dict()) await self.send_websocket(self.obj_path.queue_path_to_dict())
await self.start_convert()
if var_convert_active == False and self.yaml['autostart']:
await self.obj_convert.snake_waiting()
var_convert_active = True
else:
self.obj_convert.snake_update()
elif data.get("data_command"): elif data.get("data_command"):
pass if data["data_command"]["cmd"] == "delete":
self.obj_path.delete_path(data["data_command"]["id"])
await self.send_websocket(self.obj_path.queue_path_to_dict())
elif data.get("data_message"): elif data.get("data_message"):
logging.info(f"Server hat Empfangen: {data.get('data_message')}") logging.info(f"Server hat Empfangen: {data.get('data_message')}")
@ -80,6 +87,10 @@ class Server:
var_convert_active = value var_convert_active = value
async def server_websocket(self): async def server_websocket(self):
# Bei Server Start einmal ausführen
self.obj_path.read_paths()
await self.start_convert()
server = await websockets.serve(self.handle_client, self.yaml['server_ip'], self.yaml['websocket_port']) server = await websockets.serve(self.handle_client, self.yaml['server_ip'], self.yaml['websocket_port'])
logging.info(f"Websocket Server läuft auf IP: {self.yaml['server_ip']} Port: {self.yaml['websocket_port']}") logging.info(f"Websocket Server läuft auf IP: {self.yaml['server_ip']} Port: {self.yaml['websocket_port']}")
await server.wait_closed() await server.wait_closed()
@ -106,17 +117,32 @@ class Server:
return web.json_response(obj_stat.read_stat()) return web.json_response(obj_stat.read_stat())
# noinspection PyUnusedLocal
async def handle_cmd(self, request):
command = self.obj_convert.active_tasks # deine oben gepostete Funktion
html = ""
for obj in command:
# ffmpeg-Befehl als String (mit Shell-Escapes, falls Leerzeichen etc.)
cmd_str = " ".join([shlex.quote(arg) for arg in obj.cmd])
html += f"{cmd_str}<br /><br />"
return web.Response(text=html, content_type="text/html")
async def server_http(self): async def server_http(self):
app = web.Application() app = web.Application()
app.router.add_get("/", self.handle_index) app.router.add_get("/", self.handle_index)
app.router.add_get("/api/ip", self.handle_ip) app.router.add_get("/api/ip", self.handle_ip)
app.router.add_get("/api/stats", self.handle_stat) app.router.add_get("/api/stats", self.handle_stat)
app.router.add_get("/api/cmd", self.handle_cmd)
app.router.add_static("/client/", path="./client", name="client") app.router.add_static("/client/", path="./client", name="client")
runner = web.AppRunner(app) runner = web.AppRunner(app)
await runner.setup() await runner.setup()
site = web.TCPSite(runner, "0.0.0.0", self.yaml["webserver_port"]) site = web.TCPSite(runner, "0.0.0.0", self.yaml["webserver_port"])
await site.start() await site.start()
logging.info(f"HTTP Server läuft auf Port {self.yaml["webserver_port"]}") logging.info(f"HTTP Server läuft auf Port {self.yaml['webserver_port']}")
# Start Server ----------------------------------------------------------------------------------------------------- # Start Server -----------------------------------------------------------------------------------------------------

BIN
client/icons/fehler-96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
client/icons/wait-100.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
client/icons/wait.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Webm VCC</title> <title>Webm VC</title>
<link rel="stylesheet" href="/client/index_style.css"> <link rel="stylesheet" href="/client/index_style.css">
<link rel="icon" href="/client/icons/favicon.ico" type="image/x-icon"> <link rel="icon" href="/client/icons/favicon.ico" type="image/x-icon">
</head> </head>
@ -24,7 +24,7 @@
<!-- === Warteschleife === --> <!-- === Warteschleife === -->
<header> <header>
<h1>Warteschleife</h1> <h1>Warteschlange</h1>
</header> </header>
<section id="queue"> <section id="queue">
<!-- Wird dynamisch mit JS gefüllt --> <!-- Wird dynamisch mit JS gefüllt -->

View file

@ -140,10 +140,66 @@ nav button:hover {
gap: 1rem; gap: 1rem;
} }
.queue_wait-card {
display: flex;
flex-direction: column;
height: 80%;
background-color: #1f1f1f;
padding: 1rem;
border-radius: 10px;
color: #eee;
}
.card_wait-inner {
display: flex;
flex-direction: column;
height: 100%;
}
.card-inner {
display: flex;
flex-direction: column;
height: 100%;
}
#menu_wait {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: auto; /* ← Schiebt das Ding ganz nach unten */
}
#queue h2 { #queue h2 {
margin-top: 0; margin-top: 0;
} }
.conversion_wait_icons {
width: 25px;
height: auto;
cursor: pointer;
}
.status_icons {
width: 25px;
height: auto;
}
#menu_wait {
display: flex;
justify-content: space-between; /* Abstand dazwischen */
align-items: center; /* Vertikal zentriert, optional */
margin-top: auto; /* ← Schiebt das Ding ganz nach unten */
}
.links {
width: 25px;
}
.rechts {
width: 25px;
}
/* === Statistik-Bereich === */ /* === Statistik-Bereich === */
#statistics { #statistics {
padding: 1rem; padding: 1rem;
@ -159,6 +215,7 @@ nav button:hover {
height: auto; height: auto;
} }
.conversion_url { .conversion_url {
font-size: 0.7rem; font-size: 0.7rem;
text-align: center; text-align: center;

View file

@ -4,6 +4,7 @@
let videoActive = [] let videoActive = []
let videoQueue = [] let videoQueue = []
let ws = null
async function getServerConfig() { async function getServerConfig() {
const response = await fetch('/api/ip'); const response = await fetch('/api/ip');
@ -20,13 +21,14 @@ getServerConfig()
const externWebsocketUrl = data.extern_websocket_url; const externWebsocketUrl = data.extern_websocket_url;
const websocketHttps = data.websocket_https const websocketHttps = data.websocket_https
let connect;
if (websocketHttps === 1){ if (websocketHttps === 1){
connect = `wss://${externWebsocketUrl}` connect = `wss://${externWebsocketUrl}`
} else { } else {
connect = `ws://${externWebsocketUrl}` connect = `ws://${externWebsocketUrl}`
} }
const ws = new WebSocket(connect); ws = new WebSocket(connect);
ws.onopen = function() { ws.onopen = function() {
console.log("WebSocket ist geöffnet"); console.log("WebSocket ist geöffnet");
@ -41,6 +43,7 @@ getServerConfig()
if (packet.data_flow !== undefined){ if (packet.data_flow !== undefined){
updateVideoElement(packet); updateVideoElement(packet);
} else if(packet.data_convert !== undefined){ } else if(packet.data_convert !== undefined){
deleteVideoElement(packet)
createVideoElement(packet); createVideoElement(packet);
} else if(packet.data_queue !== undefined){ } else if(packet.data_queue !== undefined){
createWaitingSnake(packet); createWaitingSnake(packet);
@ -62,6 +65,29 @@ getServerConfig()
console.error('Error fetching settings:', error); console.error('Error fetching settings:', error);
}); });
function sendCommand(command, id){
if (ws && ws.readyState === WebSocket.OPEN) {
const payload = {"data_command": {"cmd": command, "id": id}};
ws.send(JSON.stringify(payload));
} else {
console.warn("WebSocket ist nicht verbunden")
}
}
function deleteVideoElement(packet) {
for (let key in videoActive){
if (!(key in packet.data_convert)){
const elem = document.getElementById(key);
if(elem){
elem.remove();
}
delete videoActive[key];
}
}
}
function createVideoElement(packet){ function createVideoElement(packet){
const active_Conversions = document.getElementById('active-conversions'); const active_Conversions = document.getElementById('active-conversions');
@ -88,10 +114,7 @@ function createVideoElement(packet){
<div title="Verarbeitungsgeschwindigkeit im Vergleich zur Echtzeit (1.0x = Echtzeit)" class="video-card-values-items"><b>Speed:</b> <span class="speed">0</span></div> <div title="Verarbeitungsgeschwindigkeit im Vergleich zur Echtzeit (1.0x = Echtzeit)" class="video-card-values-items"><b>Speed:</b> <span class="speed">0</span></div>
<div title="Verbleibende Zeit" class="video-card-values-items"><b>Verbleibend:</b> <span class="time_remaining">0</span></div> <div title="Verbleibende Zeit" class="video-card-values-items"><b>Verbleibend:</b> <span class="time_remaining">0</span></div>
<div title="Position im Film" class="video-card-values-items"><b>Zeit:</b> <span class="time">0</span></div> <div title="Position im Film" class="video-card-values-items"><b>Zeit:</b> <span class="time">0</span></div>
<div class="video-card-values-items delete-button"><img src="/client/icons/muell-128.png" class="conversion_icons"></div> <div class="video-card-values-items delete-button"><!--<img src="/client/icons/muell-128.png" class="conversion_icons">--></div>
</div>
<div class="tooltip">
Quelle: ${video.key}<br>
</div>`; </div>`;
@ -101,23 +124,44 @@ function createVideoElement(packet){
}); });
} }
function deepEqual(dict1, dict2) {
return JSON.stringify(dict2) === JSON.stringify(dict2);
}
function createWaitingSnake(packet){ function createWaitingSnake(packet){
const queue = document.getElementById('queue'); const queue = document.getElementById('queue');
Object.keys(packet.data_queue).forEach(key => { Object.keys(packet.data_queue).forEach(key => {
const video = packet.data_queue[key]; const video = packet.data_queue[key];
if(!deepEqual(videoActive[key], video)){
const elem = document.getElementById(key);
if(elem){
elem.remove();
delete videoQueue[key];
}
}
if(!videoQueue[key]){ if(!videoQueue[key]){
const card = document.createElement('div'); const card = document.createElement('div');
card.className = 'queue-card'; card.className = 'queue_wait-card';
card.id = key card.id = key
if(video.status === 1 || video.status === 2){
status_img = `<img src="/client/icons/fehler-96.png" class="status_icons">`
} else if(video.status === 3) {
status_img = `<img src="/client/icons/wait.gif" class="status_icons">`
} else {
status_img = `<img src="/client/icons/wait-100.png" class="status_icons">`
}
card.innerHTML = ` card.innerHTML = `
<div class="card_wait-inner">
<div>
<h3 title="${video.source_path}" align="center">${video.source_file_name}</h3> <h3 title="${video.source_path}" align="center">${video.source_file_name}</h3>
<div class="delete-button"><img src="/client/icons/muell-128.png" class="conversion_icons"></div> <div id="menu_wait">
<div class="tooltip">Quelle: ${video.key}<br></div> <div class="links">${status_img}</div>
<div class="rechts"><img src="/client/icons/muell-128.png" class="conversion_wait_icons" onclick="sendCommand('delete', ${key})"></div>
</div>
</div> </div>
`; `;