Getested und ein Berechnungsfehler beseitigt in Time to sec
This commit is contained in:
parent
c68aff4e87
commit
3bcf8b1bc3
6 changed files with 116 additions and 105 deletions
|
|
@ -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:
|
||||
|
|
|
|||
118
app/main.py
118
app/main.py
|
|
@ -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}")
|
||||
|
||||
queue.put(obj.to_dict())
|
||||
logging.info(f"Prozess {result}({obj.process.returncode}): {obj.file_name}")
|
||||
|
||||
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 = queue.get() # Warten auf neue Nachricht aus der Queue
|
||||
message = await queue.get()
|
||||
|
||||
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")
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 = ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -87,4 +100,4 @@ h1 {
|
|||
|
||||
.path {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue