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:
parent
d3bc4e3979
commit
7147f6385e
11 changed files with 178 additions and 37 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 = ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
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
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
BIN
client/icons/wait.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
`;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue