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 logging
from platform import python_version
from app.main_server import Server
if __name__ == "__main__":
print(python_version())
obj_server = Server()
try:

View file

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

View file

@ -39,15 +39,15 @@ class Path:
paths_extrat_dict = {"paths": paths}
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)
logging.info(f"{len(self.paths)} paths were saved to file")
return 1
except FileNotFoundError:
logging.error(f"File {self.yaml["path_file"]} not found")
logging.error(f"File {self.yaml['path_file']} not found")
return 0
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
# Achtung Abrufen aus Datei und neu einlesen der ffprobe Daten fehlt noch
@ -57,18 +57,20 @@ class Path:
:return: True or False
"""
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)
if len(list_paths) > 0:
count = sum(1 for v in list_paths.values() if v)
if count > 0:
paths = list_paths
logging.info(f"{len(paths)} paths were read from file")
logging.info(f"{count} paths were read from file")
return 1
except FileNotFoundError:
logging.error(f"File {self.yaml["path_file"]} not found")
logging.error(f"File {self.yaml['path_file']} not found")
return 0
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
def delete_path(self, obj_id:int):
@ -139,6 +141,8 @@ class Path:
elif select == "s":
return json_data.get("streams", [])
return ""
def get_with_ffprobe(self, path:str):
try:
path = path.strip()
@ -189,7 +193,7 @@ class Path:
list_active_paths = {}
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()})
return {"data_queue": list_active_paths}

View file

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

View file

@ -3,6 +3,7 @@ import asyncio
import websockets
import json
import logging
import shlex
from asyncio import CancelledError
from websockets import InvalidUpgrade, ConnectionClosed, ConnectionClosedError
@ -26,6 +27,15 @@ class Server:
self.obj_path = Path(self.yaml)
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):
message_json = json.dumps(message)
@ -52,15 +62,12 @@ class Server:
if data.get("data_path"):
self.obj_path.receive_paths(data.get("data_path"))
await self.send_websocket(self.obj_path.queue_path_to_dict())
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()
await self.start_convert()
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"):
logging.info(f"Server hat Empfangen: {data.get('data_message')}")
@ -80,6 +87,10 @@ class Server:
var_convert_active = value
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'])
logging.info(f"Websocket Server läuft auf IP: {self.yaml['server_ip']} Port: {self.yaml['websocket_port']}")
await server.wait_closed()
@ -106,17 +117,32 @@ class Server:
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):
app = web.Application()
app.router.add_get("/", self.handle_index)
app.router.add_get("/api/ip", self.handle_ip)
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")
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, "0.0.0.0", self.yaml["webserver_port"])
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 -----------------------------------------------------------------------------------------------------

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>
<meta charset="UTF-8">
<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="icon" href="/client/icons/favicon.ico" type="image/x-icon">
</head>
@ -24,7 +24,7 @@
<!-- === Warteschleife === -->
<header>
<h1>Warteschleife</h1>
<h1>Warteschlange</h1>
</header>
<section id="queue">
<!-- Wird dynamisch mit JS gefüllt -->

View file

@ -140,10 +140,66 @@ nav button:hover {
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 {
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 === */
#statistics {
padding: 1rem;
@ -159,6 +215,7 @@ nav button:hover {
height: auto;
}
.conversion_url {
font-size: 0.7rem;
text-align: center;

View file

@ -4,6 +4,7 @@
let videoActive = []
let videoQueue = []
let ws = null
async function getServerConfig() {
const response = await fetch('/api/ip');
@ -20,13 +21,14 @@ getServerConfig()
const externWebsocketUrl = data.extern_websocket_url;
const websocketHttps = data.websocket_https
let connect;
if (websocketHttps === 1){
connect = `wss://${externWebsocketUrl}`
} else {
connect = `ws://${externWebsocketUrl}`
}
const ws = new WebSocket(connect);
ws = new WebSocket(connect);
ws.onopen = function() {
console.log("WebSocket ist geöffnet");
@ -41,6 +43,7 @@ getServerConfig()
if (packet.data_flow !== undefined){
updateVideoElement(packet);
} else if(packet.data_convert !== undefined){
deleteVideoElement(packet)
createVideoElement(packet);
} else if(packet.data_queue !== undefined){
createWaitingSnake(packet);
@ -62,6 +65,29 @@ getServerConfig()
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){
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="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 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 class="video-card-values-items delete-button"><!--<img src="/client/icons/muell-128.png" class="conversion_icons">--></div>
</div>`;
@ -101,23 +124,44 @@ function createVideoElement(packet){
});
}
function deepEqual(dict1, dict2) {
return JSON.stringify(dict2) === JSON.stringify(dict2);
}
function createWaitingSnake(packet){
const queue = document.getElementById('queue');
Object.keys(packet.data_queue).forEach(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]){
const card = document.createElement('div');
card.className = 'queue-card';
card.className = 'queue_wait-card';
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 = `
<div>
<div class="card_wait-inner">
<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 class="tooltip">Quelle: ${video.key}<br></div>
<div id="menu_wait">
<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>
`;