Frontend komplett überarbeitet, Verbleibende Zeit für Ladebalken und Zeit Angabe sowie Lesbare Speicher Größe hinzugefügt

This commit is contained in:
Eduard Wisch 2025-04-20 21:53:50 +02:00
parent f3d9c98b38
commit cd5204fa12
10 changed files with 247 additions and 39 deletions

View file

@ -21,6 +21,7 @@ class Convert:
obj.task = asyncio.create_task(self.convert_video(obj))
self.active_tasks.add(obj)
logging.info(f"Warteschlange started Auftrag - {obj.task}")
obj.status = 3
if len(self.active_tasks) >= self.yaml["task_max"]:
break
@ -46,6 +47,7 @@ class Convert:
result = None
logging.info(f"Starte Konvertierung: {command}")
await self.obj_websocket.send_websocket(self.obj_path.active_path_to_dict())
try:
# Starte den Subprozess asynchron
@ -61,7 +63,7 @@ class Convert:
await obj_process.read_out(obj)
await obj.process.wait()
# Prozess beendet, Status auswerten
# Prself.obj_websocket.send_websocket(self.obj_path.active_path_to_dict())ozess beendet, Status auswerten
if obj.process.returncode == 0:
obj.status = 0
result = "Finished"
@ -76,6 +78,7 @@ class Convert:
logging.info(f"Prozess {result}({obj.process.returncode}): {obj.source_file_name}")
await self.obj_websocket.send_websocket(obj.to_dict())
await self.obj_websocket.send_websocket(self.obj_path.active_path_to_dict())
self.active_process.discard(obj)
self.active_tasks.discard(obj)
self.obj_path.save_paths()

View file

@ -17,6 +17,8 @@ class Process:
self.size: list = [0, "KiB"]
self.time: int = 0
self.time_remaining = 0
self.loading = 0
self.frames: int = 0
@ -30,6 +32,10 @@ class Process:
line_decoded = line.decode()
self.process_line_extract(obj, line_decoded)
#logging.info(line_decoded)
self.time_remaining = obj.format_time(obj.time_remaining())
self.loading = (self.frames / obj.source_frames_total) * 100
await self.obj_websocket.send_websocket(self.to_dict())
@ -47,7 +53,7 @@ class Process:
self.save_stat_value(obj)
elif i == 101 or i == 501:
i = 0
time = obj.format_time(obj.time_remaining())
time = self.time_remaining
if time != " ":
logging.info(f"Time remaining: {time}")
@ -60,25 +66,29 @@ class Process:
# Quantizer
q = re.findall(r"q=\s*(\d+).\d+", line)
#logging.info(f"q: {q}")
self.quantizer = int(q[0]) if q else 0
# Bitrate
bitrate = re.findall(r"bitrate=\s*(\d+)", line)
#logging.info(f"bitrate: {bitrate}")
self.bitrate[0] = int(bitrate[0]) if bitrate else 0
# Speed
speed = re.findall(r"speed=\s*(\d+\.\d+)", line)
#logging.info(f"speed: {speed}")
self.speed = float(speed[0]) if speed else 0.0
# File Size
size = re.findall(r"size=\s*(\d+)", line)
if size and int(size[0]) > self.size[0]:
self.size[0] = int(size[0])
self.size = obj.size_convert("KiB", None, "storage", int(size[0]))
obj.process_size = self.size
# Time
media_time = re.findall(r"time=\s*(\d+:\d+:\d+)", line)
time_v = media_time[0] if media_time else "00:00:00"
#logging.info(media_time)
if self.time < obj.time_in_sec(time_v):
self.time = obj.time_in_sec(time_v)
obj.process_time = self.time
@ -103,12 +113,15 @@ class Process:
obj.stat_speed = [obj.stat_speed[0] + self.speed, obj.stat_speed[1] + 1]
def to_dict(self):
return {"data_flow": {self.id: {
return {"data_flow": {
"id": self.id,
"frames": self.frames,
"fps": self.fps,
"quantizer": self.quantizer,
"size": self.size,
"time": self.time,
"time_remaining": self.time_remaining,
"loading": self.loading,
"bitrate": self.bitrate,
"speed": self.speed
}}}
}}

View file

@ -176,4 +176,13 @@ class Path:
return count
def active_path_to_dict(self):
list_active_paths = {}
for obj in self.paths.values():
if obj.status == 3:
list_active_paths.update({obj.id: obj.to_dict_active_paths()})
return {"data_convert": list_active_paths}

View file

@ -26,7 +26,7 @@ class Media:
self.process_start: int = 0
self.process_end: int = 0
self.process_time: int = 0
self.process_size: int = 0
self.process_size: list = [0, "KiB"]
self.process_frames: int = 0
self.process_time_remaining: int = 0
@ -79,9 +79,21 @@ class Media:
return output_string
@staticmethod
def to_dict():
return "Fertig mit der Welt"
def to_dict_active_paths(self):
return {
"source_file_name": self.source_file_name,
"source_file": self.source_file,
"source_duration": self.source_duration,
"source_size": self.source_size,
"source_frame_rate": self.source_frame_rate,
"source_frames_total": self.source_frames_total,
"source_time": self.source_time,
# target
"target_file_name": self.target_file_name,
"target_file": self.target_file,
"target_size": self.target_size
}
def to_dict_stat(self):
return {self.id: {

View file

@ -3,11 +3,28 @@ import yaml
import traceback
from logging.handlers import TimedRotatingFileHandler, RotatingFileHandler
# === ANSI-Farbcodes für Konsole ===
RESET = "\x1b[0m" # Farbe zurücksetzen
RED = "\x1b[31m" # Fehler: Rot
YELLOW = "\x1b[33m" # Warnung: Gelb
GREEN = "\x1b[32m" # Info: Grün
BLUE = "\x1b[34m" # Debug: Blau
class CustomFormatter(logging.Formatter):
def format(self, record):
if record.levelno >= logging.ERROR and record.exc_info:
record.msg = f"{record.msg}\n{traceback.format_exc()}"
# Farbe nach Level
if record.levelno == logging.DEBUG:
record.msg = f"{BLUE}{record.msg}{RESET}"
elif record.levelno == logging.INFO:
record.msg = f"{GREEN}{record.msg}{RESET}"
elif record.levelno == logging.WARNING:
record.msg = f"{YELLOW}{record.msg}{RESET}"
elif record.levelno >= logging.ERROR:
record.msg = f"{RED}{record.msg}{RESET}"
return super().format(record)
class Settings:

View file

@ -37,9 +37,13 @@ class Server:
async def handle_client(self, websocket):
self.websocket = websocket
self.clients.add(websocket)
global var_convert_active
if websocket not in self.clients:
self.clients.add(websocket)
await self.send_websocket(self.obj_path.active_path_to_dict())
try:
async for message in websocket:
data = json.loads(message)
@ -82,15 +86,27 @@ class Server:
@staticmethod
async def handle_index(request):
query = request.rel_url.query
if "debug" in query:
logging.info("Debug-Modus aktiv beim Seitenaufruf")
return web.FileResponse("./client/index.html")
async def handle_ip(self, request):
query = request.rel_url.query
if "debug" in query:
logging.info("Debug-Modus aktiv beim Seitenaufruf")
ip = self.yaml.get("server_ip", "localhost")
port = self.yaml.get("server_port", 8000)
return web.json_response({"server_ip": ip, "server_port": port})
@staticmethod
async def handle_stat(request):
query = request.rel_url.query
if "debug" in query:
logging.info("Debug-Modus aktiv beim Seitenaufruf")
obj_stat = Stat()
return web.json_response(obj_stat.read_stat())

View file

@ -11,7 +11,7 @@
<header>
<h1>Video Konvertierung</h1>
<nav>
<button onclick="alert('Statistiken bald verfügbar')">Statistiken</button>
<button onclick="toggleStatHidden()">Statistiken</button>
</nav>
</header>
@ -28,10 +28,12 @@
</section>
<!-- === Statistikbereich === -->
<section id="stat">
<section id="stat" hidden=True>
<h2>Allgemeine Statistiken</h2>
<p>Hier könnten Diagramme, Durchschnittswerte etc. angezeigt werden.</p>
</section>
<script src="/client/client.js"></script>
<script src="/client/media_conversion.js"></script>
<script src="/client/media_stat.js"></script>
</body>
</html>

View file

@ -61,6 +61,19 @@ nav button:hover {
margin: 0 0 0.5rem;
}
.video-card-values {
display: grid;
grid-template-columns: 1fr 1fr 1fr auto;
grid-auto-rows: auto;
gap: 10px;
}
.video-card-values-items {
padding: 5px;
text-align: center;
font-size: 0.8rem;
}
.video-card .actions button {
background-color: #444;
color: white;
@ -75,6 +88,14 @@ nav button:hover {
background-color: #666;
}
.delete-button {
grid-column: 4;
grid-row: 1 / span 3; /* oder wie viele Zeilen du hast */
display: flex;
align-items: center;
justify-content: center;
}
.tooltip {
position: absolute;
background-color: #333;
@ -94,6 +115,22 @@ nav button:hover {
display: block;
}
.progress-container {
width: 100%;
background: #333;
border-radius: 5px;
margin-top: 10px;
height: 10px;
overflow: hidden;
}
.progress-bar {
height: 100%;
width: 0%;
background: linear-gradient(90deg, #4caf50, #00c853);
transition: width 0.5s ease-in-out;
}
/* === Bereich: Warteschleife === */
#queue {
padding: 1rem;

121
client/media_conversion.js Normal file
View file

@ -0,0 +1,121 @@
/**
* @returns {Promise<{server_ip: string, server_port: number}>}
*/
let videoActive = []
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) {
try{
console.log(messageEvent.data);
let packet = JSON.parse(messageEvent.data);
if (packet.data_flow !== undefined){
updateVideoElement(packet);
} else if(packet.data_convert !== undefined){
createVideoElement(packet);
}
} catch (e){
console.error("Error parsing data flow");
}
};
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);
});
function createVideoElement(packet){
const active_Conversions = document.getElementById('active-conversions');
Object.keys(packet.data_convert).forEach(key => {
const video = packet.data_convert[key];
if(!videoActive[key]){
const card = document.createElement('div');
card.className = 'video-card';
card.id = key
card.innerHTML = `
<h3 title="${video.source_file_name}" align="center">${video.source_file_name} - ${video.target_file_name}</h3>
<div class="progress-container">
<div class="progress-bar"></div>
</div>
<br/>
<div class="video-card-values">
<div title="Anzahl Frames" class="video-card-values-items"><b>Frames:</b> <span class="frames">0</span> Anz</div>
<div title="Größe" class="video-card-values-items"><b>Größe:</b> <span class="size">0</span> <span class="size_unit"></span></div>
<div title="FPS" class="video-card-values-items"><b>FPS:</b> <span class="fps">0</span></div>
<div title="Quantizer" class="video-card-values-items"><b>Quanitzer:</b> <span class="quantizer">0</span></div>
<div title="Bitrate" class="video-card-values-items"><b>Bitrate:</b> <span class="bitrate">0</span> <span class="bitrate_unit"></span></div>
<div title="Speed" class="video-card-values-items"><b>Speed:</b> <span class="speed">0</span></div>
<div title="Zeit" class="video-card-values-items"><b>Zeit:</b> <span class="time_remaining">0</span></div>
<div class="video-card-values-items delete-button"><button class="delete-button">Löschen</button></div>
</div>
<div class="tooltip">
Quelle: ${video.key}<br>
</div>`;
active_Conversions.appendChild(card)
videoActive[key] = video;
}
});
}
function updateVideoElement(packet){
let video = packet.data_flow;
let container = document.getElementById(video.id);
container.querySelector(".frames").textContent = video.frames || 0;
container.querySelector(".size").textContent = video.size[0] || 0;
container.querySelector(".size_unit").textContent = video.size[1] || "KB";
container.querySelector(".fps").textContent = video.fps || 0;
container.querySelector(".quantizer").textContent = video.quantizer || 0;
container.querySelector(".bitrate").textContent = video.bitrate[0] || 0;
container.querySelector(".bitrate_unit").textContent = video.bitrate[1] || 0;
container.querySelector(".speed").textContent = video.speed || 0;
container.querySelector(".time_remaining").textContent = video.time_remaining || 0;
let progressBar = container.querySelector(".progress-bar");
let progress = video.loading; // Annahme: `loading` kommt als Zahl von 0 bis 100
progressBar.style.width = progress + "%"
if (videoActive[video.id].status == 3) {
progressBar.style.background = "linear-gradient(90deg, #4caf50, #00c853)";
container.querySelector(".finished").textContent = "Läuft";
} else if(videoActive[video.id].status == 0) {
progressBar.style.background = "#4caf50";
container.querySelector(".finished").textContent = "Fertig";
} else if(videoActive[video.id].status == 1) {
progressBar.style.background = "#ff0000";
container.querySelector(".finished").textContent = "Fehler";
}
}

View file

@ -11,32 +11,10 @@ async function getMediaStat() {
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);
});
async function toggleStatHidden() {
const section_stat = document.getElementById("stat")
section_stat.hidden = !section_stat.hidden
}
getMediaStat()
.then(data => {