Compare commits

...

3 commits

Author SHA1 Message Date
8db6107dfa Source Size berechnet 2025-04-16 21:35:28 +02:00
bc3ad079d1 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	client/client.js
#	client/index.html
2025-04-16 19:55:15 +02:00
e6022151f6 Stats bearbeitet und für Java Script angepasst 2025-04-16 19:54:47 +02:00
11 changed files with 281 additions and 70 deletions

View file

@ -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}")

View file

@ -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

View file

@ -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()

View file

@ -112,38 +112,3 @@ class Process:
"bitrate": self.bitrate,
"speed": self.speed
}}}
@staticmethod
def size_convert(source: str, target, unit: str, size=0):
list_unit: list = []
if unit == "storage":
list_unit = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"]
elif unit == "data_rate":
list_unit = ["bps", "Kbps", "Mbps", "Gbps", "Tbps", "Pbps"]
elif unit == "binary_data_rate":
list_unit = ["b/s", "Kib/s", "Mib/s", "Gib/s", "Tib/s", "Pib/s"]
factor = 1024 # Binäre Umrechnung
if source not in list_unit:
raise ValueError("Ungültige Quell-Einheit!")
source_index = list_unit.index(source)
if target:
if target not in list_unit:
raise ValueError("Ungültige Ziel-Einheit!")
target_index = list_unit.index(target)
if source_index < target_index:
return size / (factor ** (target_index - source_index)), target
else:
return size * (factor ** (source_index - target_index)), target
# Automatische Umrechnung
while size >= 1000 and source_index < len(list_unit) - 1:
size /= factor
source_index += 1
return [round(size,1), list_unit[source_index]]

View file

@ -11,13 +11,14 @@ class Media:
self.source_file: str = path
self.source_file_name: str = os.path.basename(self.source_file)
self.source_duration: int = self.time_in_sec(streams_video[0]["tags"].get("DURATION" or "duration", "00:00:00"))
self.source_size: int = 0
self.source_size: list = self.size_convert("B", None, "storage", int(streams_format[0].get("size")))
self.source_frame_rate: int = self.frame_rate(streams_video)
self.source_frames_total: int = self.source_frame_rate * self.source_duration
self.source_time: int = 0
# 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,
@ -169,4 +172,39 @@ class Media:
h, m, s = map(float, parts) # In float umwandeln, falls Nachkommastellen im Sekundenwert sind
return int(h * 3600 + m * 60 + s) # Alles in Sekunden umrechnen
raise ValueError(f"Ungültiges Zeitformat: {time_str}")
raise ValueError(f"Ungültiges Zeitformat: {time_str}")
@staticmethod
def size_convert(source: str, target, unit: str, size=0):
list_unit: list = []
if unit == "storage":
list_unit = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"]
elif unit == "data_rate":
list_unit = ["bps", "Kbps", "Mbps", "Gbps", "Tbps", "Pbps"]
elif unit == "binary_data_rate":
list_unit = ["b/s", "Kib/s", "Mib/s", "Gib/s", "Tib/s", "Pib/s"]
factor = 1024 # Binäre Umrechnung
if source not in list_unit:
raise ValueError("Ungültige Quell-Einheit!")
source_index = list_unit.index(source)
if target:
if target not in list_unit:
raise ValueError("Ungültige Ziel-Einheit!")
target_index = list_unit.index(target)
if source_index < target_index:
return size / (factor ** (target_index - source_index)), target
else:
return size * (factor ** (source_index - target_index)), target
# Automatische Umrechnung
while size >= 1000 and source_index < len(list_unit) - 1:
size /= factor
source_index += 1
return [round(size, 1), list_unit[source_index]]

View file

@ -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)
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

View file

@ -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,11 +89,17 @@ 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_static("/client", path="./client", name="client")
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()
site = web.TCPSite(runner, "0.0.0.0", 8080)
@ -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}")

50
client/client.js Normal file → Executable file
View file

@ -6,6 +6,11 @@ async function getServerConfig() {
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;
@ -32,3 +37,48 @@ getServerConfig()
.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);
});

27
client/index.html Normal file → Executable file
View file

@ -3,8 +3,35 @@
<head>
<meta charset="UTF-8">
<title>Websocket Client</title>
<link rel="stylesheet" href="/client/index_style.css">
<link rel="icon" href="/client/icons/favicon.ico" type="image/x-icon">
</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
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
done