This commit is contained in:
Eduard Wisch 2025-03-03 19:27:42 +01:00
parent 7ea9407d41
commit f44c6d0af1
9 changed files with 156 additions and 55 deletions

Binary file not shown.

View file

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

View file

@ -8,7 +8,9 @@
</head>
<body>
<h1>Video-Konvertierung Fortschritt</h1>
<div id="videos"></div>
<div id="videos"></div><br />
<div id="video_list" class="video"><div class="video-header"><h1>Warteliste</h1></div></div><br />
<div id="video_finished" class="video"><div class="video-header"><h1>Fertig</h1></div></div>
<script src="/webs/webs.js"></script>
</body>
</html>

View file

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

BIN
app/webs/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
app/webs/mkv.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

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

View file

@ -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 += `<tr>
<td width="5%" align="right"><img src="/webs/mkv.png" width="20px"></td>
<td width="65%" align="left"><span>${list.file_name}</span></td>
<td width="15%" align="right"><span>${list.duration}</span></td>
<td width="15%" align="right"><span>${list.source_file_size}</span></td>
</tr>`;
}
});
container.innerHTML = `
<div class="video-info">
<div>
<table class="table_video_wait" id="remaining_list" border="0" width="100%">
${html}
</table>
</div>
</div>`;
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 += `<tr>
<td width="5%" align="right"><img src="/webs/mkv.png" width="20px"></td>
<td width="65%" align="left"><span>${list.file_name}</span></td>
<td width="15%" align="right"><span>${list.duration}</span></td>
<td width="15%" align="right"><span>${list.source_file_size}</span></td>
</tr>`;
}
});
container.innerHTML = `
<div class="video-info">
<div>
<table class="table_video_wait" id="remaining_list" border="0" width="100%">
${html}
</table>
</div>
</div>`;
document.getElementById("video_finished").appendChild(container);
return container;
}
function createVideoElement(id, source, target, path) {
@ -54,21 +124,21 @@ function createVideoElement(id, source, target, path) {
</div>
<div class="video-info">
<div><span class="path">${path}</span></div>
<table class="table_video_info">
<table border="0" width="100%" style="border-spacing: 20px;">
<tr>
<th><img src="/webs/animation-32.png" class="icons"></th><th class="label"><div title="Anzahl Frames"><span class="frame">0</span> Anz</div></th>
<th><img src="/webs/fps-32.png" class="icons"></th><th class="label"><div title="Frames / Sekunde"><span class="fps">0</span> fps</div></th>
<th><img src="/webs/q-24.png" class="icons"></th><th class="label"><div title="Quantizer"><span class="q">0 </span> Q</div></th>
<td><img src="/webs/animation-32.png" class="icons"></td><td class="label"><div title="Anzahl Frames"><span class="frame">0</span> Anz</div></td>
<td><img src="/webs/fps-32.png" class="icons"></td><td class="label"><div title="Frames / Sekunde"><span class="fps">0</span> fps</div></td>
<td><img src="/webs/q-24.png" class="icons"></td><td class="label"><div title="Quantizer"><span class="q">0 </span> Q</div></td>
</tr>
<tr>
<th><img src="/webs/ssd-30.png" class="icons"></th><th class="label"><div title="Dateigröße"><span class="size">0 </span> MB</div></th>
<th><img src="/webs/bitrate-30.png" class="icons"></th><th class="label"><div title="Bitrate"><span class="bitrate">0 </span> Mb/s</div></th>
<th><img src="/webs/speed-32.png" class="icons"></th><th class="label"><div title="Speed"><span class="speed">0</span> x</div></th>
<td><img src="/webs/ssd-30.png" class="icons"></td><td class="label"><div title="Dateigröße"><span class="size">0 </span> MB</div></td>
<td><img src="/webs/bitrate-30.png" class="icons"></td><td class="label"><div title="Bitrate"><span class="bitrate">0 </span> Mb/s</div></td>
<td><img src="/webs/speed-32.png" class="icons"></td><td class="label"><div title="Speed"><span class="speed">0</span> x</div></td>
</tr>
<tr>
<th><img src="/webs/timer-50.png" class="icons"></th><th class="label" colspan="2"><div title="Verbleibend"><span class="time_remaining">0 </span></div></th>
<th></th><th></th>
<th></th><th><div class="loader"><span class="finished"></span></div></th>
<td valign="top"><img src="/webs/timer-50.png" class="icons"></td><td class="label"><div title="Verbleibend"><span class="time_remaining">0 </span></div></td>
<td></td><td></td>
<td></td><td align="right"><div class="loader"><span class="finished"></span></div></td>
</tr>
</table>
</div>
@ -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");