Test
This commit is contained in:
parent
7ea9407d41
commit
f44c6d0af1
9 changed files with 156 additions and 55 deletions
Binary file not shown.
Binary file not shown.
42
app/main.py
42
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)
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
BIN
app/webs/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
app/webs/mkv.png
Normal file
BIN
app/webs/mkv.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
|
|
@ -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 {
|
||||
|
|
|
|||
109
app/webs/webs.js
109
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 += `<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");
|
||||
|
|
|
|||
Loading…
Reference in a new issue