Getested und ein Berechnungsfehler beseitigt in Time to sec

This commit is contained in:
Eduard Wisch 2025-03-12 06:39:25 +01:00
parent c68aff4e87
commit 3bcf8b1bc3
6 changed files with 116 additions and 105 deletions

View file

@ -1,12 +1,9 @@
from app.main import video_convert
class Ffmpeg:
def __init__(config):
class FFCmd:
def __init__(self, config):
self.config = config
@staticmethod
def video_info():
def video_info(select, source_file):
command_info = [
"ffprobe", "-v",
"error",
@ -23,8 +20,7 @@ class Ffmpeg:
return command_info
@staticmethod
def video_convert():
def video_convert(self, obj):
command_convert = [
"ffmpeg", "-y", "-i", obj.source_file,
"-map", "0:0",
@ -33,12 +29,12 @@ class Ffmpeg:
"-crf", "30",
"-g", "240",
"-pix_fmt", "yuv420p10le",
"-svtav1-params", "tune=0:film-grain=8",
"-svtav1-params", "tune=0:film-grain=8:threads=16",
]
if len(obj.streams_audio):
for audio_stream in obj.streams_audio:
if audio_stream.get("tags", {}).get("language", None) in config["convert"]["language"]:
if audio_stream.get("tags", {}).get("language", None) in self.config["convert"]["language"]:
command_convert.extend([
"-map", f"0:{audio_stream['index']}",
f"-c:a", "libopus",
@ -49,8 +45,8 @@ class Ffmpeg:
# Subtitle-Streams einbinden
if len(obj.streams_subtitle):
for subtitle_stream in obj.streams_subtitle:
if subtitle_stream.get("codec_name") not in config["subtitle"]["blacklist"]:
if subtitle_stream.get("tags", {}).get("language", None) in config["convert"]["language"]:
if subtitle_stream.get("codec_name") not in self.config["subtitle"]["blacklist"]:
if subtitle_stream.get("tags", {}).get("language", None) in self.config["convert"]["language"]:
command_convert.extend([
"-map", f"0:{subtitle_stream['index']}",
])
@ -59,7 +55,7 @@ class Ffmpeg:
return command_convert
@staticmethod
def command_as_string(self, cmd):
def command_as_string(cmd):
command_as_string: str = ""
for para in cmd:
if "/" not in para:

View file

@ -1,11 +1,10 @@
import asyncio
import os.path
import re
import subprocess
import json
import logging
import threading
import time
import queue
from datetime import date
import uvicorn
@ -15,16 +14,16 @@ from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from starlette.websockets import WebSocketState
import app.video_class as vc
from app.ffmpeg_class import Ffmpeg as Fc
from app.config_class import Config as Cfg
import video_class as vc
from ffmpeg_class import FFCmd as Fc
from config_class import Config as Cfg
# Globale Variablen
config = Cfg().config
ffmpeg = Fc(config)
queue: queue = queue.Queue()
semaphore = threading.Semaphore(config["convert"]["max_process"])
queue = asyncio.Queue()
semaphore = asyncio.Semaphore(config["convert"]["max_process"])
thread_output = None
video_files = {}
@ -96,87 +95,81 @@ def get_ffprobe(select, source_file):
return json_data.get("streams", [])
# Convert Process ------------------------------------------------------------------------------------------------------
def queue_video():
for key, obj in video_files.items():
if obj.finished == 0:
obj.task = threading.Thread(target=video_convert, args=(obj,))
obj.task.start()
active_tasks.add(obj)
logging.info(f"Warteschlange started Auftrag - {obj.task}")
async def queue_video():
async with semaphore:
for key, obj in video_files.items():
if obj.finished == 0:
obj.task = await asyncio.create_task(video_convert(obj))
active_tasks.add(obj)
logging.info(f"Warteschlange started Auftrag - {obj.task}")
def video_convert(obj):
global active_process
async def video_convert(obj):
"""Startet die Videokonvertierung asynchron."""
global active_process, active_tasks
semaphore.acquire()
result: str = ""
obj.convert_start = time.time()
command = ffmpeg.video_convert(obj)
command = ffmpeg.video_convert(config, obj)
logging.info(f"Starte Konvertierung: {command}")
logging.info(f"{command}")
# Prozess
try:
obj.process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
# Starte den Subprozess asynchron
obj.process = await asyncio.create_subprocess_exec(
*command,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
active_process.add(obj)
obj.finished = 3
queue.put(video_list())
await queue.put(await video_list())
while obj.process.poll() is None:
logging.info(f"{obj.file_name} ... Running")
time.sleep(10)
while obj.process.returncode is None:
logging.info(f"{obj.file_name} ... läuft noch")
await asyncio.sleep(10)
if obj.process.poll() == 0:
# Prozess beendet, Status auswerten
if obj.process.returncode == 0:
obj.finished = 1
result = "Finished"
elif obj.process.poll() != 0:
else:
obj.finished = 2
result = "Failure"
logging.info(f"Process {result}({obj.process.returncode}): {obj.file_name}")
logging.info(f"Prozess {result}({obj.process.returncode}): {obj.file_name}")
queue.put(obj.to_dict())
await queue.put(obj.to_dict())
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}")
logging.error(f"Fehler in video_convert(): {e}")
#UviCorn WebServer Teil
#----------------------------------------------------------------------------------------------------------------------
def read_output():
async def read_output():
"""Thread-Funktion, die Prozessausgaben in die Queue legt."""
global active_process
while True:
if not len(active_process):
time.sleep(30)
queue.put(video_list())
await asyncio.sleep(30)
await queue.put(video_list())
continue
for obj in list(active_process):
line_error = obj.process.stderr.read(1024).decode()
line = await obj.process.stderr.read(1024)
line_error = line.decode()
logging.info(f"Data ffmpeg: {line_error}")
obj.extract_convert_data(line_error)
# Verwende den Event-Loop, um die Daten in die asyncio Queue zu legen
logging.info(f"Data Packet created: {obj.to_dict()}")
queue.put(obj.to_dict())
await queue.put(obj.to_dict())
await asyncio.sleep(3)
def video_list():
async def video_list():
vlist = []
for video in video_files.values():
vlist.append(video.get_vars())
@ -187,18 +180,20 @@ def video_list():
@app.post("/")
async def receive_video_file(data: dict):
if get_video_information(data):
queue_video()
await queue_video()
await queue.put(await video_list())
else:
logging.error(f"Videos konnten nicht verarbeitet werden! Warteschleife wurde nicht gestarted")
@app.get("/command", response_class=HTMLResponse)
async def display_paths(request: Request):
list_command: list = []
for video in video_files.values():
if video.finished == 3:
ffmpeg.
list_command.append(ffmpeg.command_as_string(ffmpeg.video_convert(video)))
return templates.TemplateResponse("command.html", {"request": request, "videos": video_files, "command": list_command})
return templates.TemplateResponse("command.html", {"request": request, "videos": video_files, "commands": list_command})
@app.get("/webs-ui", response_class=HTMLResponse)
async def display_paths(request: Request):
@ -214,23 +209,20 @@ async def websocket_v(websocket: WebSocket):
if thread_output is None:
# Startet den Thread zum Verarbeiten der Prozessausgaben
thread_output = threading.Thread(target=read_output)
thread_output.start()
thread_output = asyncio.create_task(read_output())
try:
var_first_sending = 0
while True:
if not var_first_sending:
queue.put(video_list())
var_first_sending = 1
await queue.put(await video_list())
var_first_sending = True
if websocket not in clients:
break
message = await queue.get()
message = queue.get() # Warten auf neue Nachricht aus der Queue
for client in clients:
# Alle Clients benachrichtigen
for client in list(clients):
await client.send_text(message)
@ -240,7 +232,7 @@ async def websocket_v(websocket: WebSocket):
logging.error(f"WebSocket error: {e}")
finally:
clients.discard(websocket)
if websocket.client_state == WebSocketState.CONNECTED:
if websocket.client_state == WebSocketState.DISCONNECTED:
await websocket.close()
@app.get("/clients")
@ -248,4 +240,4 @@ async def get_clients_count():
return {"active_clients": len(clients), "active_processes": len(active_process), "active_tasks": len(active_tasks)}
if __name__ == "__main__":
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=False)
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=False, log_level="debug")

View file

@ -5,10 +5,10 @@
<title>Video Liste</title>
</head>
<body>
<h1>Vorhandene Videos</h1>
<h1>FFmpeg Kommandos der Laufenden Video Konvertierungen</h1>
<ul>
{% for key, video in videos.items() %}
<li>{{ video.source_file }} <b>{{video.finished}}</b></li>
{% for cmd in commands %}
<li>{{ cmd }}</li><br /><br />
{% endfor %}
</ul>
</body>

View file

@ -35,7 +35,7 @@ class Video:
# Datenpaket
self.frame: int = 0
self.fps: int = 0
self.fps: float = 0
self.q: int = 0
self.size: int = 0
self.time: int = 0
@ -130,8 +130,8 @@ class Video:
self.frame = int(frame[0])
# FPS
fps = re.findall(r"fps=\s*(\d+)", line_decoded)
self.fps = int(fps[0]) if fps else 0
fps = re.findall(r"fps=\s*(\d+.\d*)", line_decoded)
self.fps = float(fps[0]) if fps else 0
# Quantizer
q = re.findall(r"q=\s*(\d+).\d+", line_decoded)
@ -200,21 +200,21 @@ class Video:
days = round(seconds // (24 * 3600))
seconds %= (24 * 3600)
if days:
d = f"{days} Tage"
d = f"{days} T"
else:
d = ""
hours = round(seconds // 3600)
seconds %= 3600
if hours:
h = f"{hours} Std"
h = f"{hours} S"
else:
h = ""
minutes = math.ceil(seconds // 60)
seconds %= 60
if minutes:
m = f"{minutes} Min"
m = f"{minutes} M"
else:
m = ""

View file

@ -55,20 +55,33 @@ h1 {
font-weight: bold;
}
.table_video_info{
border: 1px solid black;
width: 100%;
}
.icons {
width: 25px;
float: right;
}
.video_info {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
width: 100%
}
.table_video_info {
border-spacing: 15px;
border: 1;
}
.label {
text-align: left;
text-align: right;
vertical-align: top;
display: inline-block;
width: 100px;
}
.img {
align-items: right;
width: 100px;
}
.loader {

View file

@ -122,26 +122,36 @@ function createVideoElement(id, source, target, path) {
<div class="progress-container">
<div class="progress-bar"></div>
</div>
<div class="video-info">
<div><span class="path">${path}</span></div>
<table border="0" width="100%" style="border-spacing: 20px;">
<div><span class="path">${path}</span></div><br />
<div class="video_info">
<table class="table_video_info" border="0">
<tr>
<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>
<td class="img"><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 class="img"><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 class="img"><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>
<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>
<td class="img"><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 class="img"><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 class="img"><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>
<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>
<td class="img"><img src="/webs/timer-50.png" class="icons"></td>
<td class="label"><div title="Verbleibend"><span class="time_remaining">0 </span></div></td>
<td class="img"></td>
<td class="label"></td>
<td class="img"></td>
<td align="right"><div class="loader"><span class="finished"></span></div></td>
</tr>
</table>
</div>
`;
document.getElementById("videos").appendChild(container);