diff --git a/app/class_file_convert.py b/app/class_file_convert.py index 419f3f9..374f2c2 100644 --- a/app/class_file_convert.py +++ b/app/class_file_convert.py @@ -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() diff --git a/app/class_file_convert_read_out.py b/app/class_file_convert_read_out.py index a002c0d..7f06766 100644 --- a/app/class_file_convert_read_out.py +++ b/app/class_file_convert_read_out.py @@ -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 - }}} + }} diff --git a/app/class_file_path.py b/app/class_file_path.py index 6161b2e..12161c0 100644 --- a/app/class_file_path.py +++ b/app/class_file_path.py @@ -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} + diff --git a/app/class_media_file.py b/app/class_media_file.py index 18a16cb..9ffb7ff 100644 --- a/app/class_media_file.py +++ b/app/class_media_file.py @@ -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: { diff --git a/app/class_settings.py b/app/class_settings.py index 1e2a055..b952b7e 100644 --- a/app/class_settings.py +++ b/app/class_settings.py @@ -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: diff --git a/app/main_server.py b/app/main_server.py index d7c74e7..d49e83f 100644 --- a/app/main_server.py +++ b/app/main_server.py @@ -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()) diff --git a/client/index.html b/client/index.html index 54a519b..099c5ca 100755 --- a/client/index.html +++ b/client/index.html @@ -11,7 +11,7 @@

Video Konvertierung

@@ -28,10 +28,12 @@ -
+ - + + + \ No newline at end of file diff --git a/client/index_style.css b/client/index_style.css index 0ee2afd..e3e9343 100755 --- a/client/index_style.css +++ b/client/index_style.css @@ -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; diff --git a/client/media_conversion.js b/client/media_conversion.js new file mode 100644 index 0000000..42bdccd --- /dev/null +++ b/client/media_conversion.js @@ -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 = ` +

${video.source_file_name} - ${video.target_file_name}

+
+
+
+
+
+
Frames: 0 Anz
+
Größe: 0
+
FPS: 0
+
Quanitzer: 0
+
Bitrate: 0
+
Speed: 0
+
Zeit: 0
+
+
+
+ Quelle: ${video.key}
+
`; + + + 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"; + } +} \ No newline at end of file diff --git a/client/client.js b/client/media_stat.js similarity index 71% rename from client/client.js rename to client/media_stat.js index 70c3b93..941bbd8 100755 --- a/client/client.js +++ b/client/media_stat.js @@ -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 => {