Compare commits

..

No commits in common. "8db6107dfa7a1f93496dde352de08aba2c2b27ce" and "bb058bb98965ff09e01a619ba5ee0929c8dea444" have entirely different histories.

11 changed files with 70 additions and 281 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 KeyboardInterrupt: except asyncio.exceptions.CancelledError:
logging.warning("Server wurde manuell beendet") pass
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: "0.0.0.0" server_ip: "127.0.0.1"
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_stat = Stat(obj)
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) obj_stat.save_stat()
obj.convert_end = time.time() obj.convert_end = time.time()

View file

@ -112,3 +112,38 @@ class Process:
"bitrate": self.bitrate, "bitrate": self.bitrate,
"speed": self.speed "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,14 +11,13 @@ class Media:
self.source_file: str = path self.source_file: str = path
self.source_file_name: str = os.path.basename(self.source_file) 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_duration: int = self.time_in_sec(streams_video[0]["tags"].get("DURATION" or "duration", "00:00:00"))
self.source_size: list = self.size_convert("B", None, "storage", int(streams_format[0].get("size"))) self.source_size: int = 0
self.source_frame_rate: int = self.frame_rate(streams_video) self.source_frame_rate: int = self.frame_rate(streams_video)
self.source_frames_total: int = self.source_frame_rate * self.source_duration self.source_frames_total: int = self.source_frame_rate * self.source_duration
self.source_time: int = 0 self.source_time: int = 0
# 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
@ -86,7 +85,6 @@ 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,
@ -95,7 +93,6 @@ 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,
@ -172,39 +169,4 @@ class Media:
h, m, s = map(float, parts) # In float umwandeln, falls Nachkommastellen im Sekundenwert sind 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 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,29 +2,25 @@ import yaml
import os import os
class Stat: class Stat:
def __init__(self): def __init__(self, obj):
self.path = "app/cfg/statistic.yaml" self.obj = obj
def save_stat(self, obj): def save_stat(self):
pfad = "app/cfg/statistic.yaml"
daten = self.read_stat() # Bestehende Daten laden
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(obj.to_dict_stat()) daten["videos"].update(self.obj.to_dict_stat())
# Datei mit aktualisierten Daten speichern # Datei mit aktualisierten Daten speichern
with open(self.path, "w", encoding="utf8") as file: with open(pfad, "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,14 +4,12 @@ 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
@ -58,14 +56,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, ConnectionClosed): except (ConnectionClosedError, InvalidUpgrade):
logging.warning("Client Verbindung geschlossen") pass
except InvalidUpgrade: except json.JSONDecodeError as e:
logging.warning("Ungültiger Websocket Upgrade versuch") logging.error(f"JSON Fehler: {e}")
except Exception as e: except ConnectionClosed:
logging.error(f"Unerwarteter Fehler {e}") logging.error("Server sagt: Client getrennt")
finally: finally:
self.clients.discard(websocket) self.clients.remove(websocket)
@staticmethod @staticmethod
def set_var_convert_active(value: bool): def set_var_convert_active(value: bool):
@ -89,17 +87,11 @@ 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()
site = web.TCPSite(runner, "0.0.0.0", 8080) site = web.TCPSite(runner, "0.0.0.0", 8080)
@ -109,12 +101,7 @@ 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}")

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

@ -6,11 +6,6 @@ async function getServerConfig() {
return await response.json(); return await response.json();
} }
async function getMediaStat() {
const response = await fetch('/api/stats');
return await response.json();
}
getServerConfig() getServerConfig()
.then(data => { .then(data => {
const websocketIp = data.server_ip; const websocketIp = data.server_ip;
@ -37,48 +32,3 @@ getServerConfig()
.catch(error => { .catch(error => {
console.error('Error fetching settings:', 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 Executable file → Normal file
View file

@ -3,35 +3,8 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Websocket Client</title> <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> </head>
<body> <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> <script src="/client/client.js"></script>
</body> </body>
</html> </html>

View file

@ -1,114 +0,0 @@
/* === 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://192.168.155.110:8000/" SERVER="ws://localhost: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