diff --git a/app/__pycache__/main.cpython-313.pyc b/app/__pycache__/main.cpython-313.pyc deleted file mode 100644 index cdefe65..0000000 Binary files a/app/__pycache__/main.cpython-313.pyc and /dev/null differ diff --git a/app/__pycache__/video_class.cpython-313.pyc b/app/__pycache__/video_class.cpython-313.pyc deleted file mode 100644 index 6f2c0c5..0000000 Binary files a/app/__pycache__/video_class.cpython-313.pyc and /dev/null differ diff --git a/app/main.py b/app/main.py index ed3fed8..3aa62b2 100755 --- a/app/main.py +++ b/app/main.py @@ -8,17 +8,19 @@ import threading import time from datetime import date +import uvicorn from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect from fastapi.staticfiles import StaticFiles from fastapi.responses import HTMLResponse from fastapi.templating import Jinja2Templates +from starlette.websockets import WebSocketState import app.video_class as vc # Settings language = ["ger", "eng"] subtitle_codec_blacklist = ["hdmv_pgs_subtitle", "dvd_subtitle"] -max_tasks = 2 +max_tasks = 1 # Globale Variablen queue = asyncio.Queue() @@ -27,7 +29,7 @@ video_files = {} active_process = set() active_tasks = set() connected_clients = set() -semaphore = threading.Semaphore(1) +semaphore = threading.Semaphore(max_tasks) date = date.today() if not os.path.exists("./logs"): @@ -105,7 +107,7 @@ def get_ffprobe(select, source_file): # Convert Process ------------------------------------------------------------------------------------------------------ def queue_video(): for key, obj in video_files.items(): - with semaphore: + if obj.finished == 0: obj.task = threading.Thread(target=video_convert, args=(obj,)) obj.task.start() active_tasks.add(obj) @@ -114,6 +116,8 @@ def queue_video(): def video_convert(obj): global active_process + semaphore.acquire() + obj.convert_start = time.time() # Erstelle und setze einen Event-Loop für diesen Thread loop = asyncio.new_event_loop() @@ -123,7 +127,7 @@ def video_convert(obj): "ffmpeg", "-y", "-i", obj.source_file, "-map", "0:0", "-c:v", "libsvtav1", - "-preset", "8", + "-preset", "5", "-crf", "30", "-g", "240", "-pix_fmt", "yuv420p10le", @@ -150,6 +154,7 @@ def video_convert(obj): ]) command.append(obj.output_file) logging.info(f"{command}") + loop.run_until_complete(queue.put(video_list())) # Prozess try: @@ -179,9 +184,11 @@ def video_convert(obj): active_process.discard(obj) active_tasks.discard(obj) obj.convert_end = time.time() + semaphore.release() except Exception as e: obj.finished = 2 + semaphore.release() logging.error(f"Convert Process Failure: {e}") #UviCorn WebServer Teil @@ -194,11 +201,9 @@ def read_output(qu): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - print(active_process) - while True: if not len(active_process): - time.sleep(30) + time.sleep(5) continue for obj in list(active_process): @@ -213,6 +218,13 @@ def read_output(qu): logging.info(f"Data Packet created: {obj.to_dict()}") loop.run_until_complete(qu.put(obj.to_dict())) +def video_list(): + vlist = [] + for video in video_files.values(): + vlist.append(video.get_vars()) + + return json.dumps({"video_list": vlist}) + @app.post("/") async def receive_video_file(data: dict): @@ -242,20 +254,30 @@ async def websocket_v(websocket: WebSocket): read_output_task = threading.Thread(target=read_output, args=(queue,)) read_output_task.start() - # await queue.put(obj_list()) - try: + var_first_sending = 0 while True: + if websocket not in connected_clients: + break + message = await queue.get() # Warten auf neue Nachricht aus der Queue await websocket.send_text(message) + if not var_first_sending: + await queue.put(video_list()) + var_first_sending = 1 + except WebSocketDisconnect: logging.info("WebSocket disconnected") except Exception as e: logging.error(f"WebSocket error: {e}") finally: connected_clients.discard(websocket) - await websocket.close() + if websocket.client_state == WebSocketState.CONNECTED: + await websocket.close() @app.get("/clients") async def get_clients_count(): return {"active_clients": len(connected_clients), "active_processes": len(active_process), "active_tasks": len(active_tasks)} + +if __name__ == "__main__": + uvicorn.run("app.main:app", host="127.0.0.1", port=8000, reload=False) \ No newline at end of file diff --git a/app/templates/webs-ui.html b/app/templates/webs-ui.html index 0a51bc8..f235964 100644 --- a/app/templates/webs-ui.html +++ b/app/templates/webs-ui.html @@ -8,7 +8,9 @@

Video-Konvertierung Fortschritt

-
+

+

Warteliste


+

Fertig

diff --git a/app/video_class.py b/app/video_class.py index 1d73674..2d75223 100644 --- a/app/video_class.py +++ b/app/video_class.py @@ -9,12 +9,16 @@ from collections import deque class Video: def __init__(self, path, video_streams, video_format, audio_streams, subtitle_streams): self.id = id(self) + + #Source self.source_file = path self.file_name = os.path.basename(self.source_file) self.duration = self.time_in_sec(video_streams[0]["tags"].get("DURATION" or "duration", "00:00:00")) self.frame_rate = self.frame_rate(video_streams) self.frames_max = self.frame_rate * self.duration + self.source_file_size = video_format.get("size", 0) + # Target self.output_file = f"{path.rsplit(".", 1)[0]}.webm" self.convert_start = 0 self.convert_end = 0 @@ -30,15 +34,14 @@ class Video: self.format = [video_format] # Datenpaket - self.frame = 0 - self.fps = 0 - self.q = 0 - self.size = 0 - self.time = 0 - self.bitrate = 0 - self.speed = 0 - self.loading = 0 - self.count_empty_data = 0 + self.frame: int = 0 + self.fps: int = 0 + self.q: int = 0 + self.size: int = 0 + self.time: int = 0 + self.bitrate: int = 0 + self.speed: int = 0 + self.loading: int = 0 # Process self.task = None @@ -109,7 +112,16 @@ class Video: return json.dumps({"data_flow":data_packet}) def get_vars(self): - return json.dumps({"obj_list":vars(self)}) + obj_vars = { + "id": self.id, + "file_name": self.file_name, + "duration": self.format_time(self.duration), + "source_file_size": self.convert_kb_mb(self.source_file_size), + "finished": self.finished + + } + + return obj_vars def extract_convert_data(self, line_decoded): frame = re.findall(r"frame=\s*(\d+)", line_decoded) @@ -178,23 +190,22 @@ class Video: @staticmethod def format_time(seconds): - # Berechne die Anzahl der Tage, Stunden, Minuten und Sekunden - days = round(seconds // (24 * 3600)) # 1 Tag = 24 Stunden * 3600 Sekunden - seconds %= (24 * 3600) # Restliche Sekunden nach Tagen + days = round(seconds // (24 * 3600)) + seconds %= (24 * 3600) if days: d = f"{days} Tage" else: d = "" - hours = round(seconds // 3600) # 1 Stunde = 3600 Sekunden - seconds %= 3600 # Restliche Sekunden nach Stunden + hours = round(seconds // 3600) + seconds %= 3600 if hours: h = f"{hours} Std" else: h = "" - minutes = math.ceil(seconds // 60) # 1 Minute = 60 Sekunden - seconds %= 60 # Restliche Sekunden nach Minuten + minutes = math.ceil(seconds // 60) + seconds %= 60 if minutes: m = f"{minutes} Min" else: diff --git a/app/webs/favicon.ico b/app/webs/favicon.ico new file mode 100644 index 0000000..a8550c8 Binary files /dev/null and b/app/webs/favicon.ico differ diff --git a/app/webs/mkv.png b/app/webs/mkv.png new file mode 100644 index 0000000..98a8233 Binary files /dev/null and b/app/webs/mkv.png differ diff --git a/app/webs/webs.css b/app/webs/webs.css index cdcd712..d5b564c 100644 --- a/app/webs/webs.css +++ b/app/webs/webs.css @@ -56,16 +56,10 @@ h1 { } .table_video_info{ - border: None; + border: 1px solid black; width: 100%; } -th, td { - width: 16%; /* Jede Zelle nimmt 33% der Breite der Tabelle ein */ - padding: 16px; - text-align: left; /* Optional: Text ausrichten */ -} - .icons { width: 25px; float: right; @@ -73,7 +67,8 @@ th, td { .label { text-align: left; - vertical-align: middle; + vertical-align: top; + display: inline-block; } .loader { diff --git a/app/webs/webs.js b/app/webs/webs.js index cf030be..e81db52 100644 --- a/app/webs/webs.js +++ b/app/webs/webs.js @@ -1,6 +1,8 @@ let ws = new WebSocket("ws://127.0.0.1:8000/ws"); let videoQueue = {}; // Hier speichern wir alle laufenden Videos +console.log("Start Script"); + ws.onmessage = function(event) { try { let packet = JSON.parse(event.data); @@ -22,8 +24,22 @@ ws.onmessage = function(event) { } else { console.error("Fehlende Felder in der Nachricht", message); } - } else { - console.error("Kein data_flow in der empfangenen Nachricht", packet); + } + + if (packet.video_list !== undefined) { + let video_list = packet.video_list; + + createVideoList(video_list); + createVideoListFinished(video_list); + + video_list.forEach(list => { + if(list.finished === 1) { + let container = document.getElementById(list.id); // Holt das Element mit der id + if (container) { + container.remove(); // Entfernt das Element aus dem DOM + } + } + }); } } catch (e) { console.error("Fehler beim Parsen der WebSocket-Nachricht:", e, event.data); @@ -34,12 +50,66 @@ ws.onerror = function(event) { console.error("WebSocket Fehler:", event); }; -function sekundenInStundenMinuten(sekunden) { - const stunden = Math.floor(sekunden / 3600); // 1 Stunde = 3600 Sekunden - const minuten = Math.floor((sekunden % 3600) / 60); // Restsekunden in Minuten umrechnen - const verbleibendeSekunden = sekunden % 60; // Übrige Sekunden +function createVideoList(video) { + let container = document.createElement("div"); + container.className = "video_list"; + container.id = "remaining" - return `${stunden} Stunden, ${minuten} Minuten, ${verbleibendeSekunden} Sekunden`; + let html = ``; + + video.forEach(list => { + if(list.finished === 0) { + html += ` + + ${list.file_name} + ${list.duration} + ${list.source_file_size} + `; + } + }); + + container.innerHTML = ` +
+
+ + ${html} +
+
+
`; + + document.getElementById("video_list").appendChild(container); + return container; +} + +function createVideoListFinished(video) { + let container = document.createElement("div"); + container.className = "video_list"; + container.id = "finished" + + let html = ``; + + video.forEach(list => { + if(list.finished === 1) { + html += ` + + ${list.file_name} + ${list.duration} + ${list.source_file_size} + `; + } + }); + + container.innerHTML = ` +
+
+ + ${html} +
+
+
`; + + document.getElementById("video_finished").appendChild(container); + return container; } function createVideoElement(id, source, target, path) { @@ -54,21 +124,21 @@ function createVideoElement(id, source, target, path) {
${path}
- +
- - - + + + - - - + + + - - - + + +
0 Anz
0 fps
0 Q
0 Anz
0 fps
0 Q
0 MB
0 Mb/s
0 x
0 MB
0 Mb/s
0 x
0
0
@@ -94,14 +164,15 @@ function updateVideoElement(id, data) { let progress = data.loading; // Annahme: `loading` kommt als Zahl von 0 bis 100 progressBar.style.width = progress + "%" - if (data.finished == 0) { + if (data.finished == 3) { progressBar.style.background = "linear-gradient(90deg, #4caf50, #00c853)"; container.querySelector(".finished").textContent = "Läuft"; } else if(data.finished == 1) { progressBar.style.background = "#4caf50"; container.querySelector(".finished").textContent = "Fertig"; - } else { + } else if(data.finished == 2) { progressBar.style.background = "#ff0000"; container.querySelector(".finished").textContent = "Fehler"; } } +console.log("End Script");