From e6022151f663fe8f7fe9618dfedb9b7ad6262484 Mon Sep 17 00:00:00 2001 From: data Date: Thu, 10 Apr 2025 21:49:10 +0200 Subject: [PATCH] =?UTF-8?q?Stats=20bearbeitet=20und=20f=C3=BCr=20Java=20Sc?= =?UTF-8?q?ript=20angepasst?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __main__.py | 4 +- app/cfg/settings.yaml | 2 +- app/class_file_convert.py | 4 +- app/class_media_file.py | 3 + app/class_media_file_stat.py | 30 +++-- app/main_server.py | 35 ++++-- client | 1 - client/client.js | 84 +++++++++++++ client/index.html | 36 ++++++ client/index_style.css | 114 ++++++++++++++++++ .../share/kio/servicemenus/send.path.sh | 6 +- 11 files changed, 286 insertions(+), 33 deletions(-) delete mode 160000 client create mode 100755 client/client.js create mode 100755 client/index.html create mode 100755 client/index_style.css diff --git a/__main__.py b/__main__.py index 348693e..96f0475 100644 --- a/__main__.py +++ b/__main__.py @@ -8,7 +8,7 @@ if __name__ == "__main__": try: asyncio.run(obj_server.start_server()) - except asyncio.exceptions.CancelledError: - pass + except KeyboardInterrupt: + logging.warning("Server wurde manuell beendet") except Exception as e: logging.critical(f"Global error: {e}") \ No newline at end of file diff --git a/app/cfg/settings.yaml b/app/cfg/settings.yaml index 8b55122..c56e9f5 100644 --- a/app/cfg/settings.yaml +++ b/app/cfg/settings.yaml @@ -2,7 +2,7 @@ log_file: "server.log" log_level: DEBUG log_rotation: time path_file: "media_path.yaml" -server_ip: "127.0.0.1" +server_ip: "0.0.0.0" server_port: 8000 task_max: 1 autostart: true diff --git a/app/class_file_convert.py b/app/class_file_convert.py index 9d5c7d3..419f3f9 100644 --- a/app/class_file_convert.py +++ b/app/class_file_convert.py @@ -39,7 +39,7 @@ class Convert: """Startet die Videokonvertierung asynchron.""" obj_process = Process(self.obj_websocket) - obj_stat = Stat(obj) + obj_stat = Stat() obj.convert_start = time.time() command = self.convert_cmd(obj) @@ -79,7 +79,7 @@ class Convert: self.active_process.discard(obj) self.active_tasks.discard(obj) self.obj_path.save_paths() - obj_stat.save_stat() + obj_stat.save_stat(obj) obj.convert_end = time.time() diff --git a/app/class_media_file.py b/app/class_media_file.py index e9c423e..d245e16 100644 --- a/app/class_media_file.py +++ b/app/class_media_file.py @@ -18,6 +18,7 @@ class Media: # target 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 # process @@ -85,6 +86,7 @@ class Media: def to_dict_stat(self): return {self.id: { # source + "source_file_name": self.source_file_name, "source_file": self.source_file, "source_duration": self.source_duration, "source_size": self.source_size, @@ -93,6 +95,7 @@ class Media: "source_time": self.source_time, # target + "target_file_name": self.target_file_name, "target_file": self.target_file, "target_size": self.target_size, diff --git a/app/class_media_file_stat.py b/app/class_media_file_stat.py index cd71d11..d45c8cb 100644 --- a/app/class_media_file_stat.py +++ b/app/class_media_file_stat.py @@ -2,25 +2,29 @@ import yaml import os class Stat: - def __init__(self, obj): - self.obj = obj + def __init__(self): + self.path = "app/cfg/statistic.yaml" - def save_stat(self): - pfad = "app/cfg/statistic.yaml" + def save_stat(self, obj): - # Bestehende Daten laden - if os.path.exists(pfad): - with open(pfad, "r", encoding="utf8") as file: - daten = yaml.safe_load(file) or {} - else: - daten = {} + daten = self.read_stat() # Videosammlung initialisieren, falls nötig daten.setdefault("videos", {}) # Neuen Eintrag hinzufügen - daten["videos"].update(self.obj.to_dict_stat()) + daten["videos"].update(obj.to_dict_stat()) # Datei mit aktualisierten Daten speichern - with open(pfad, "w", encoding="utf8") as file: - yaml.dump(daten, file, default_flow_style=False, indent=4, allow_unicode=True) \ No newline at end of file + with open(self.path, "w", encoding="utf8") as file: + 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 \ No newline at end of file diff --git a/app/main_server.py b/app/main_server.py index 14b01d8..d21cc47 100644 --- a/app/main_server.py +++ b/app/main_server.py @@ -4,12 +4,14 @@ import websockets import json import logging +from asyncio import CancelledError from websockets import InvalidUpgrade, ConnectionClosed, ConnectionClosedError from aiohttp import web from app.class_settings import Settings from app.class_file_path import Path from app.class_file_convert import Convert +from app.class_media_file_stat import Stat var_convert_active = False @@ -56,14 +58,14 @@ class Server: elif data.get("data_message"): logging.info(f"Server hat Empfangen: {data.get('data_message')}") - except (ConnectionClosedError, InvalidUpgrade): - pass - except json.JSONDecodeError as e: - logging.error(f"JSON Fehler: {e}") - except ConnectionClosed: - logging.error("Server sagt: Client getrennt") + except (ConnectionClosedError, ConnectionClosed): + logging.warning("Client Verbindung geschlossen") + except InvalidUpgrade: + logging.warning("Ungültiger Websocket Upgrade versuch") + except Exception as e: + logging.error(f"Unerwarteter Fehler {e}") finally: - self.clients.remove(websocket) + self.clients.discard(websocket) @staticmethod def set_var_convert_active(value: bool): @@ -87,10 +89,16 @@ class Server: port = self.yaml.get("server_port", 8000) 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): 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_static("/client", path="./client", name="client") runner = web.AppRunner(app) await runner.setup() @@ -101,7 +109,12 @@ class Server: # Start Server ----------------------------------------------------------------------------------------------------- async def start_server(self): - await asyncio.gather( - self.server_websocket(), - self.server_http() - ) + try: + await asyncio.gather( + self.server_websocket(), + 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}") diff --git a/client b/client deleted file mode 160000 index 9cbce74..0000000 --- a/client +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9cbce7415d2429d2132afb3b1d985c09f6ddd7f2 diff --git a/client/client.js b/client/client.js new file mode 100755 index 0000000..70c3b93 --- /dev/null +++ b/client/client.js @@ -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 = ` +

${video.source_file_name}

+
+ +
+
+ Quelle: ${video.source_file}
+ Dauer: ${video.source_duration}
+ Größe: ${video.source_size}
+ FPS: ${video.source_frame_rate}
+ Frames: ${video.source_frames_total}
+ Start: ${video.process_start}
+ Ende: ${video.process_end}
+ Status: ${video.status}
+ Zeit: ${video.process_time}
+ Größe (Ziel): ${video.target_size}
+ FPS (aktuell): ${video.stat_fps}
+ Bitrate: ${video.stat_bitrate}
+ Speed: ${video.stat_speed} +
+ `; + + stat_Container.appendChild(card) + }); + }) + .catch(error => { + console.error('Fehler beim Abrufen der Medienstatistik:', error); + }); \ No newline at end of file diff --git a/client/index.html b/client/index.html new file mode 100755 index 0000000..a5d1e40 --- /dev/null +++ b/client/index.html @@ -0,0 +1,36 @@ + + + + + Websocket Client + + + + +
+

Video Konvertierung

+ +
+ + +
+

Aktive Konvertierungen

+ +
+ + +
+

Warteschleife

+ +
+ + +
+

Allgemeine Statistiken

+

Hier könnten Diagramme, Durchschnittswerte etc. angezeigt werden.

+
+ + + \ No newline at end of file diff --git a/client/index_style.css b/client/index_style.css new file mode 100755 index 0000000..0ee2afd --- /dev/null +++ b/client/index_style.css @@ -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; +} \ No newline at end of file diff --git a/kde_context_plugin/share/kio/servicemenus/send.path.sh b/kde_context_plugin/share/kio/servicemenus/send.path.sh index 9ecb617..196f0d7 100755 --- a/kde_context_plugin/share/kio/servicemenus/send.path.sh +++ b/kde_context_plugin/share/kio/servicemenus/send.path.sh @@ -1,9 +1,9 @@ #!/bin/bash -SERVER="ws://localhost:8000/" +SERVER="ws://192.168.155.110:8000/" for FILE in "$@"; do - JSON=$(printf '{"data_path: "%s"}' "$FILE") + JSON=$(printf '{"data_path": "%s"}' "$FILE") echo "$JSON" | websocat "$SERVER" -done \ No newline at end of file +done