diff --git a/__main__.py b/__main__.py index 96f0475..46721f8 100644 --- a/__main__.py +++ b/__main__.py @@ -1,9 +1,11 @@ import asyncio import logging +from platform import python_version from app.main_server import Server if __name__ == "__main__": + print(python_version()) obj_server = Server() try: diff --git a/app/class_file_convert.py b/app/class_file_convert.py index 910b3a6..46acf92 100755 --- a/app/class_file_convert.py +++ b/app/class_file_convert.py @@ -1,9 +1,13 @@ import logging import asyncio import time +from typing import TYPE_CHECKING from app.class_file_convert_read_out import Process from app.class_media_file_stat import Stat +if TYPE_CHECKING: + from app.class_media_file import Media + class Convert: def __init__(self, websocket, cfg, obj_path): self.yaml = cfg @@ -85,13 +89,13 @@ class Convert: obj.convert_end = time.time() obj_stat.save_stat(obj) - - def convert_cmd(self, obj): command_convert = [ "ffmpeg", "-y", "-i", obj.source_file, + # "-init_hw_device", "vaapi=va:/dev/dri/renderD128", "-map", "0:0", "-c:v", "libsvtav1", + #"-c:v", "av1_qsv", "-preset", "5", "-crf", "30", "-g", "240", @@ -119,6 +123,7 @@ class Convert: ]) command_convert.append(obj.target_file) + obj.cmd = command_convert return command_convert def snake_update(self): diff --git a/app/class_file_path.py b/app/class_file_path.py index 3789b2a..0e990b9 100755 --- a/app/class_file_path.py +++ b/app/class_file_path.py @@ -39,15 +39,15 @@ class Path: paths_extrat_dict = {"paths": paths} try: - with open(f"app/cfg/{self.yaml["path_file"]}", "w", encoding="utf8") as file: + with open(f"app/cfg/{self.yaml['path_file']}", "w", encoding="utf8") as file: yaml.dump(paths_extrat_dict, file, default_flow_style=False, indent=4) logging.info(f"{len(self.paths)} paths were saved to file") return 1 except FileNotFoundError: - logging.error(f"File {self.yaml["path_file"]} not found") + logging.error(f"File {self.yaml['path_file']} not found") return 0 except IOError: - logging.critical(f"Error file {self.yaml["path_file"]} maybe damaged") + logging.critical(f"Error file {self.yaml['path_file']} maybe damaged") return 0 # Achtung Abrufen aus Datei und neu einlesen der ffprobe Daten fehlt noch @@ -57,18 +57,20 @@ class Path: :return: True or False """ try: - with open(f"app/cfg/{self.yaml["path_file"]}", "r", encoding="utf8") as file: + with open(f"app/cfg/{self.yaml['path_file']}", "r", encoding="utf8") as file: list_paths = yaml.safe_load(file) - if len(list_paths) > 0: + count = sum(1 for v in list_paths.values() if v) + + if count > 0: paths = list_paths - logging.info(f"{len(paths)} paths were read from file") + logging.info(f"{count} paths were read from file") return 1 except FileNotFoundError: - logging.error(f"File {self.yaml["path_file"]} not found") + logging.error(f"File {self.yaml['path_file']} not found") return 0 except IOError: - logging.critical(f"Error file {self.yaml["path_file"]} maybe damaged") + logging.critical(f"Error file {self.yaml['path_file']} maybe damaged") return 0 def delete_path(self, obj_id:int): @@ -139,6 +141,8 @@ class Path: elif select == "s": return json_data.get("streams", []) + return "" + def get_with_ffprobe(self, path:str): try: path = path.strip() @@ -189,7 +193,7 @@ class Path: list_active_paths = {} for obj in self.paths.values(): - if obj.status is None: + if obj.status is None or obj.status == 1 or obj.status == 2 or obj.status == 3: list_active_paths.update({obj.id: obj.to_dict_active_paths()}) return {"data_queue": list_active_paths} diff --git a/app/class_media_file.py b/app/class_media_file.py index 132a9f2..5f18bed 100755 --- a/app/class_media_file.py +++ b/app/class_media_file.py @@ -1,4 +1,3 @@ -import logging import os import math import time @@ -9,6 +8,7 @@ class Media: def __init__(self, path, streams_video, streams_audio, streams_subtitle, streams_format): # misc self.id = int(f"{int(time.time() * 1000)}{str(Media._id_counter).zfill(3)}") + self.cmd : list = None # source self.source_file: str = path self.source_path: str = os.path.dirname(path) @@ -20,7 +20,7 @@ class Media: self.source_time: int = 0 # target - self.target_file: str = f"{path.rsplit(".", 1)[0]}.webm" + self.target_file: str = f"{path.rsplit('.', 1)[0]}.webm" self.target_file_name: str = os.path.basename(self.target_file) self.target_size: int = 0 @@ -54,8 +54,8 @@ class Media: count = 1 string = "" for video_stream in stream_list: - string += f"{video_stream.get("codec_type").capitalize()} {count}" if video_stream.get( - "codec_type") else "Format" + string += f"{video_stream.get('codec_type').capitalize()} {count}" if video_stream.get( + 'codec_type') else "Format" for key, value in video_stream.items(): string += f" -- {key}: {value}" @@ -96,7 +96,10 @@ class Media: # target "target_file_name": self.target_file_name, "target_file": self.target_file, - "target_size": self.target_size + "target_size": self.target_size, + + #process + "status": self.status } def to_dict_stat(self): @@ -155,7 +158,7 @@ class Media: days = round(seconds // (24 * 3600)) seconds %= (24 * 3600) if days and str_day is not None: - d = (f"{days} {str_day}") + d = f"{days} {str_day}" else: d = "" diff --git a/app/main_server.py b/app/main_server.py index c0755c8..f3058bf 100755 --- a/app/main_server.py +++ b/app/main_server.py @@ -3,6 +3,7 @@ import asyncio import websockets import json import logging +import shlex from asyncio import CancelledError from websockets import InvalidUpgrade, ConnectionClosed, ConnectionClosedError @@ -26,6 +27,15 @@ class Server: self.obj_path = Path(self.yaml) self.obj_convert = Convert(self, self.yaml, self.obj_path) + async def start_convert(self): + global var_convert_active + + if var_convert_active == False and self.yaml['autostart']: + await self.obj_convert.snake_waiting() + var_convert_active = True + else: + self.obj_convert.snake_update() + async def send_websocket(self, message): message_json = json.dumps(message) @@ -52,15 +62,12 @@ class Server: if data.get("data_path"): self.obj_path.receive_paths(data.get("data_path")) await self.send_websocket(self.obj_path.queue_path_to_dict()) - - if var_convert_active == False and self.yaml['autostart']: - await self.obj_convert.snake_waiting() - var_convert_active = True - else: - self.obj_convert.snake_update() + await self.start_convert() elif data.get("data_command"): - pass + if data["data_command"]["cmd"] == "delete": + self.obj_path.delete_path(data["data_command"]["id"]) + await self.send_websocket(self.obj_path.queue_path_to_dict()) elif data.get("data_message"): logging.info(f"Server hat Empfangen: {data.get('data_message')}") @@ -80,6 +87,10 @@ class Server: var_convert_active = value async def server_websocket(self): + # Bei Server Start einmal ausführen + self.obj_path.read_paths() + await self.start_convert() + server = await websockets.serve(self.handle_client, self.yaml['server_ip'], self.yaml['websocket_port']) logging.info(f"Websocket Server läuft auf IP: {self.yaml['server_ip']} Port: {self.yaml['websocket_port']}") await server.wait_closed() @@ -106,17 +117,32 @@ class Server: return web.json_response(obj_stat.read_stat()) + # noinspection PyUnusedLocal + async def handle_cmd(self, request): + command = self.obj_convert.active_tasks # deine oben gepostete Funktion + + html = "" + + for obj in command: + # ffmpeg-Befehl als String (mit Shell-Escapes, falls Leerzeichen etc.) + cmd_str = " ".join([shlex.quote(arg) for arg in obj.cmd]) + + html += f"{cmd_str}

" + + return web.Response(text=html, content_type="text/html") + async def server_http(self): app = web.Application() app.router.add_get("/", self.handle_index) app.router.add_get("/api/ip", self.handle_ip) app.router.add_get("/api/stats", self.handle_stat) + app.router.add_get("/api/cmd", self.handle_cmd) app.router.add_static("/client/", path="./client", name="client") runner = web.AppRunner(app) await runner.setup() site = web.TCPSite(runner, "0.0.0.0", self.yaml["webserver_port"]) await site.start() - logging.info(f"HTTP Server läuft auf Port {self.yaml["webserver_port"]}") + logging.info(f"HTTP Server läuft auf Port {self.yaml['webserver_port']}") # Start Server ----------------------------------------------------------------------------------------------------- diff --git a/client/icons/fehler-96.png b/client/icons/fehler-96.png new file mode 100644 index 0000000..10968a6 Binary files /dev/null and b/client/icons/fehler-96.png differ diff --git a/client/icons/wait-100.png b/client/icons/wait-100.png new file mode 100644 index 0000000..b2143c9 Binary files /dev/null and b/client/icons/wait-100.png differ diff --git a/client/icons/wait.gif b/client/icons/wait.gif new file mode 100644 index 0000000..f38345a Binary files /dev/null and b/client/icons/wait.gif differ diff --git a/client/index.html b/client/index.html index 1f5f893..02f4298 100755 --- a/client/index.html +++ b/client/index.html @@ -3,7 +3,7 @@ - Webm VCC + Webm VC @@ -24,7 +24,7 @@
-

Warteschleife

+

Warteschlange

diff --git a/client/index_style.css b/client/index_style.css index 82cb1a4..69f4ddc 100755 --- a/client/index_style.css +++ b/client/index_style.css @@ -140,10 +140,66 @@ nav button:hover { gap: 1rem; } +.queue_wait-card { + display: flex; + flex-direction: column; + height: 80%; + background-color: #1f1f1f; + padding: 1rem; + border-radius: 10px; + color: #eee; +} + +.card_wait-inner { + display: flex; + flex-direction: column; + height: 100%; +} + +.card-inner { + display: flex; + flex-direction: column; + height: 100%; +} + +#menu_wait { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: auto; /* ← Schiebt das Ding ganz nach unten */ +} + + #queue h2 { margin-top: 0; } +.conversion_wait_icons { + width: 25px; + height: auto; + cursor: pointer; +} + +.status_icons { + width: 25px; + height: auto; +} + +#menu_wait { + display: flex; + justify-content: space-between; /* Abstand dazwischen */ + align-items: center; /* Vertikal zentriert, optional */ + margin-top: auto; /* ← Schiebt das Ding ganz nach unten */ +} + +.links { + width: 25px; +} + +.rechts { + width: 25px; +} + /* === Statistik-Bereich === */ #statistics { padding: 1rem; @@ -159,6 +215,7 @@ nav button:hover { height: auto; } + .conversion_url { font-size: 0.7rem; text-align: center; diff --git a/client/media_conversion.js b/client/media_conversion.js index 24cd49b..06f2caa 100755 --- a/client/media_conversion.js +++ b/client/media_conversion.js @@ -4,6 +4,7 @@ let videoActive = [] let videoQueue = [] +let ws = null async function getServerConfig() { const response = await fetch('/api/ip'); @@ -20,13 +21,14 @@ getServerConfig() const externWebsocketUrl = data.extern_websocket_url; const websocketHttps = data.websocket_https + let connect; if (websocketHttps === 1){ connect = `wss://${externWebsocketUrl}` } else { connect = `ws://${externWebsocketUrl}` } - const ws = new WebSocket(connect); + ws = new WebSocket(connect); ws.onopen = function() { console.log("WebSocket ist geöffnet"); @@ -41,6 +43,7 @@ getServerConfig() if (packet.data_flow !== undefined){ updateVideoElement(packet); } else if(packet.data_convert !== undefined){ + deleteVideoElement(packet) createVideoElement(packet); } else if(packet.data_queue !== undefined){ createWaitingSnake(packet); @@ -62,6 +65,29 @@ getServerConfig() console.error('Error fetching settings:', error); }); +function sendCommand(command, id){ + if (ws && ws.readyState === WebSocket.OPEN) { + const payload = {"data_command": {"cmd": command, "id": id}}; + + ws.send(JSON.stringify(payload)); + } else { + console.warn("WebSocket ist nicht verbunden") + } +} + +function deleteVideoElement(packet) { + for (let key in videoActive){ + if (!(key in packet.data_convert)){ + const elem = document.getElementById(key); + if(elem){ + elem.remove(); + } + + delete videoActive[key]; + } + } + } + function createVideoElement(packet){ const active_Conversions = document.getElementById('active-conversions'); @@ -88,10 +114,7 @@ function createVideoElement(packet){
Speed: 0
Verbleibend: 0
Zeit: 0
-
- -
- Quelle: ${video.key}
+
`; @@ -101,23 +124,44 @@ function createVideoElement(packet){ }); } +function deepEqual(dict1, dict2) { + return JSON.stringify(dict2) === JSON.stringify(dict2); +} + function createWaitingSnake(packet){ const queue = document.getElementById('queue'); Object.keys(packet.data_queue).forEach(key => { const video = packet.data_queue[key]; + if(!deepEqual(videoActive[key], video)){ + const elem = document.getElementById(key); + if(elem){ + elem.remove(); + delete videoQueue[key]; + } + } + if(!videoQueue[key]){ const card = document.createElement('div'); - card.className = 'queue-card'; + card.className = 'queue_wait-card'; card.id = key + if(video.status === 1 || video.status === 2){ + status_img = `` + } else if(video.status === 3) { + status_img = `` + } else { + status_img = `` + } + card.innerHTML = ` - -
+

${video.source_file_name}

-
-
Quelle: ${video.key}
+
`;