Stats bearbeitet und für Java Script angepasst

This commit is contained in:
Eduard Wisch 2025-04-10 21:49:10 +02:00
parent b3a5da292e
commit e6022151f6
11 changed files with 286 additions and 33 deletions

View file

@ -8,7 +8,7 @@ if __name__ == "__main__":
try: try:
asyncio.run(obj_server.start_server()) asyncio.run(obj_server.start_server())
except asyncio.exceptions.CancelledError: except KeyboardInterrupt:
pass logging.warning("Server wurde manuell beendet")
except Exception as e: except Exception as e:
logging.critical(f"Global error: {e}") logging.critical(f"Global error: {e}")

View file

@ -2,7 +2,7 @@ log_file: "server.log"
log_level: DEBUG log_level: DEBUG
log_rotation: time log_rotation: time
path_file: "media_path.yaml" path_file: "media_path.yaml"
server_ip: "127.0.0.1" server_ip: "0.0.0.0"
server_port: 8000 server_port: 8000
task_max: 1 task_max: 1
autostart: true autostart: true

View file

@ -39,7 +39,7 @@ class Convert:
"""Startet die Videokonvertierung asynchron.""" """Startet die Videokonvertierung asynchron."""
obj_process = Process(self.obj_websocket) obj_process = Process(self.obj_websocket)
obj_stat = Stat(obj) obj_stat = Stat()
obj.convert_start = time.time() obj.convert_start = time.time()
command = self.convert_cmd(obj) command = self.convert_cmd(obj)
@ -79,7 +79,7 @@ class Convert:
self.active_process.discard(obj) self.active_process.discard(obj)
self.active_tasks.discard(obj) self.active_tasks.discard(obj)
self.obj_path.save_paths() self.obj_path.save_paths()
obj_stat.save_stat() obj_stat.save_stat(obj)
obj.convert_end = time.time() obj.convert_end = time.time()

View file

@ -18,6 +18,7 @@ class Media:
# 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_size: int = 0 self.target_size: int = 0
# process # process
@ -85,6 +86,7 @@ class Media:
def to_dict_stat(self): def to_dict_stat(self):
return {self.id: { return {self.id: {
# source # source
"source_file_name": self.source_file_name,
"source_file": self.source_file, "source_file": self.source_file,
"source_duration": self.source_duration, "source_duration": self.source_duration,
"source_size": self.source_size, "source_size": self.source_size,
@ -93,6 +95,7 @@ class Media:
"source_time": self.source_time, "source_time": self.source_time,
# target # target
"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,

View file

@ -2,25 +2,29 @@ import yaml
import os import os
class Stat: class Stat:
def __init__(self, obj): def __init__(self):
self.obj = obj self.path = "app/cfg/statistic.yaml"
def save_stat(self): def save_stat(self, obj):
pfad = "app/cfg/statistic.yaml"
# Bestehende Daten laden daten = self.read_stat()
if os.path.exists(pfad):
with open(pfad, "r", encoding="utf8") as file:
daten = yaml.safe_load(file) or {}
else:
daten = {}
# Videosammlung initialisieren, falls nötig # Videosammlung initialisieren, falls nötig
daten.setdefault("videos", {}) daten.setdefault("videos", {})
# Neuen Eintrag hinzufügen # Neuen Eintrag hinzufügen
daten["videos"].update(self.obj.to_dict_stat()) daten["videos"].update(obj.to_dict_stat())
# Datei mit aktualisierten Daten speichern # Datei mit aktualisierten Daten speichern
with open(pfad, "w", encoding="utf8") as file: with open(self.path, "w", encoding="utf8") as file:
yaml.dump(daten, file, default_flow_style=False, indent=4, allow_unicode=True) yaml.dump(daten, file, default_flow_style=False, indent=4, allow_unicode=True)
def read_stat(self):
# Bestehende Daten laden
if os.path.exists(self.path):
with open(self.path, "r", encoding="utf8") as file:
daten = yaml.safe_load(file) or {}
else:
daten = {}
return daten

View file

@ -4,12 +4,14 @@ import websockets
import json import json
import logging import logging
from asyncio import CancelledError
from websockets import InvalidUpgrade, ConnectionClosed, ConnectionClosedError from websockets import InvalidUpgrade, ConnectionClosed, ConnectionClosedError
from aiohttp import web from aiohttp import web
from app.class_settings import Settings from app.class_settings import Settings
from app.class_file_path import Path from app.class_file_path import Path
from app.class_file_convert import Convert from app.class_file_convert import Convert
from app.class_media_file_stat import Stat
var_convert_active = False var_convert_active = False
@ -56,14 +58,14 @@ class Server:
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')}")
except (ConnectionClosedError, InvalidUpgrade): except (ConnectionClosedError, ConnectionClosed):
pass logging.warning("Client Verbindung geschlossen")
except json.JSONDecodeError as e: except InvalidUpgrade:
logging.error(f"JSON Fehler: {e}") logging.warning("Ungültiger Websocket Upgrade versuch")
except ConnectionClosed: except Exception as e:
logging.error("Server sagt: Client getrennt") logging.error(f"Unerwarteter Fehler {e}")
finally: finally:
self.clients.remove(websocket) self.clients.discard(websocket)
@staticmethod @staticmethod
def set_var_convert_active(value: bool): def set_var_convert_active(value: bool):
@ -87,10 +89,16 @@ class Server:
port = self.yaml.get("server_port", 8000) port = self.yaml.get("server_port", 8000)
return web.json_response({"server_ip": ip, "server_port": port}) return web.json_response({"server_ip": ip, "server_port": port})
@staticmethod
async def handle_stat(request):
obj_stat = Stat()
return web.json_response(obj_stat.read_stat())
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_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()
@ -101,7 +109,12 @@ class Server:
# Start Server ----------------------------------------------------------------------------------------------------- # Start Server -----------------------------------------------------------------------------------------------------
async def start_server(self): async def start_server(self):
try:
await asyncio.gather( await asyncio.gather(
self.server_websocket(), self.server_websocket(),
self.server_http() self.server_http()
) )
except CancelledError:
logging.warning("Server wurde durch Keyboard Interrupt gestoppt.")
except Exception as e:
logging.error(f"Unerwarteter Fehler beim Starten des Servers {e}")

1
client

@ -1 +0,0 @@
Subproject commit 9cbce7415d2429d2132afb3b1d985c09f6ddd7f2

84
client/client.js Executable file
View file

@ -0,0 +1,84 @@
/**
* @returns {Promise<{server_ip: string, server_port: number}>}
*/
async function getServerConfig() {
const response = await fetch('/api/ip');
return await response.json();
}
async function getMediaStat() {
const response = await fetch('/api/stats');
return await response.json();
}
getServerConfig()
.then(data => {
const websocketIp = data.server_ip;
const websocketPort = data.server_port;
const ws = new WebSocket(`ws://${websocketIp}:${websocketPort}`);
ws.onopen = function() {
console.log("WebSocket ist geöffnet");
ws.send(JSON.stringify({"data_message": "Server Adresse: " + websocketIp + ":" + websocketPort}));
};
ws.onmessage = function(messageEvent) {
console.log(messageEvent.data);
};
ws.onclose = function() {
console.log("WebSocket wurde geschlossen");
};
ws.onerror = function(errorEvent) {
console.error("WebSocket-Fehler: ", errorEvent);
};
})
.catch(error => {
console.error('Error fetching settings:', error);
});
getMediaStat()
.then(data => {
console.log("Antwort von /api/stats:", data); // Debug-Ausgabe
if (!data || typeof data !== 'object' || !data.videos || typeof data.videos !== 'object') {
throw new Error("Ungültiges Antwortformat oder fehlende 'videos'-Eigenschaft");
}
const stat_Container = document.getElementById('stat');
Object.keys(data.videos).forEach(key => {
const video = data.videos[key];
const card = document.createElement('div');
card.className = 'video-card';
card.id = key
card.innerHTML = `
<h3 title="${video.source_file}">${video.source_file_name}</h3>
<div class="actions">
<button>Löschen</button>
</div>
<div class="tooltip">
Quelle: ${video.source_file}<br>
Dauer: ${video.source_duration}<br>
Größe: ${video.source_size}<br>
FPS: ${video.source_frame_rate}<br>
Frames: ${video.source_frames_total}<br>
Start: ${video.process_start}<br>
Ende: ${video.process_end}<br>
Status: ${video.status}<br>
Zeit: ${video.process_time}<br>
Größe (Ziel): ${video.target_size}<br>
FPS (aktuell): ${video.stat_fps}<br>
Bitrate: ${video.stat_bitrate}<br>
Speed: ${video.stat_speed}
</div>
`;
stat_Container.appendChild(card)
});
})
.catch(error => {
console.error('Fehler beim Abrufen der Medienstatistik:', error);
});

36
client/index.html Executable file
View file

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Websocket Client</title>
<link rel="stylesheet" href="/client/index_style.css">
</head>
<body>
<!-- === Menü === -->
<header>
<h1>Video Konvertierung</h1>
<nav>
<button onclick="alert('Statistiken bald verfügbar')">Statistiken</button>
</nav>
</header>
<!-- === Aktive Konvertierungen === -->
<section id="active-conversions">
<h2>Aktive Konvertierungen</h2>
<!-- Wird dynamisch mit JS gefüllt -->
</section>
<!-- === Warteschleife === -->
<section id="queue">
<h2>Warteschleife</h2>
<!-- Wird dynamisch mit JS gefüllt -->
</section>
<!-- === Statistikbereich === -->
<section id="stat">
<h2>Allgemeine Statistiken</h2>
<p>Hier könnten Diagramme, Durchschnittswerte etc. angezeigt werden.</p>
</section>
<script src="/client/client.js"></script>
</body>
</html>

114
client/index_style.css Executable file
View file

@ -0,0 +1,114 @@
/* === Allgemeines Design === */
body {
margin: 0;
font-family: Arial, sans-serif;
background-color: #121212;
color: #eee;
}
a {
color: #90caf9;
}
/* === Menü oben === */
header {
background-color: #1e1e1e;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #333;
}
header h1 {
margin: 0;
font-size: 1.5rem;
}
nav button {
background-color: #333;
color: #fff;
border: none;
padding: 0.5rem 1rem;
margin-left: 0.5rem;
cursor: pointer;
border-radius: 5px;
}
nav button:hover {
background-color: #444;
}
/* === Bereich: Aktive Konvertierungen === */
#active-conversions {
padding: 1rem;
border-bottom: 1px solid #333;
}
#active-conversions h2 {
margin-top: 0;
}
.video-card {
background-color: #1f1f1f;
padding: 1rem;
margin-bottom: 1rem;
border-radius: 8px;
position: relative;
}
.video-card h3 {
margin: 0 0 0.5rem;
}
.video-card .actions button {
background-color: #444;
color: white;
border: none;
padding: 0.4rem 0.8rem;
margin-right: 0.5rem;
cursor: pointer;
border-radius: 4px;
}
.video-card .actions button:hover {
background-color: #666;
}
.tooltip {
position: absolute;
background-color: #333;
color: #eee;
padding: 0.5rem;
border-radius: 6px;
top: 100%;
left: 0;
z-index: 1;
display: none;
width: max-content;
max-width: 300px;
font-size: 0.7rem;
}
.video-card:hover .tooltip {
display: block;
}
/* === Bereich: Warteschleife === */
#queue {
padding: 1rem;
}
#queue h2 {
margin-top: 0;
}
/* === Statistik-Bereich === */
#statistics {
padding: 1rem;
border-top: 1px solid #333;
}
#statistics h2 {
margin-top: 0;
}

View file

@ -1,9 +1,9 @@
#!/bin/bash #!/bin/bash
SERVER="ws://localhost:8000/" SERVER="ws://192.168.155.110:8000/"
for FILE in "$@"; do for FILE in "$@"; do
JSON=$(printf '{"data_path: "%s"}' "$FILE") JSON=$(printf '{"data_path": "%s"}' "$FILE")
echo "$JSON" | websocat "$SERVER" echo "$JSON" | websocat "$SERVER"
done done