diff --git a/Dockerfile b/Dockerfile index d8b575c..adf197a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,8 +42,8 @@ COPY video-konverter/app/ ./app/ RUN cp -r /opt/video-konverter/app/cfg /opt/video-konverter/cfg_defaults # Daten- und Log-Verzeichnisse + HLS-Streaming (beschreibbar fuer UID 1000) -RUN mkdir -p /opt/video-konverter/data /opt/video-konverter/logs /tmp/hls \ - && chmod 777 /opt/video-konverter/data /opt/video-konverter/logs /tmp/hls +RUN mkdir -p /opt/video-konverter/data /opt/video-konverter/logs /tmp/hls /tmp/jinja2_cache \ + && chmod 777 /opt/video-konverter/data /opt/video-konverter/logs /tmp/hls /tmp/jinja2_cache # Entrypoint (kopiert Defaults in gemountete Volumes) COPY entrypoint.sh . diff --git a/video-konverter/app/routes/tv_api.py b/video-konverter/app/routes/tv_api.py index 162897a..70e225f 100644 --- a/video-konverter/app/routes/tv_api.py +++ b/video-konverter/app/routes/tv_api.py @@ -1451,7 +1451,7 @@ def setup_tv_routes(app: web.Application, config: Config, seg_path, headers={ "Content-Type": content_type, - "Cache-Control": "public, max-age=3600", + "Cache-Control": "public, max-age=86400, immutable", "Access-Control-Allow-Origin": "*", }) diff --git a/video-konverter/app/routes/ws.py b/video-konverter/app/routes/ws.py index 9852003..c8ba2a0 100644 --- a/video-konverter/app/routes/ws.py +++ b/video-konverter/app/routes/ws.py @@ -21,7 +21,7 @@ class WebSocketManager: async def handle_websocket(self, request: web.Request) -> web.WebSocketResponse: """WebSocket-Endpoint Handler""" - ws = web.WebSocketResponse() + ws = web.WebSocketResponse(heartbeat=30.0) await ws.prepare(request) self.clients.add(ws) diff --git a/video-konverter/app/server.py b/video-konverter/app/server.py index b095522..bda95da 100644 --- a/video-konverter/app/server.py +++ b/video-konverter/app/server.py @@ -1,6 +1,7 @@ """Haupt-Server: HTTP + WebSocket + Templates in einer aiohttp-App""" import asyncio import logging +import os import time from pathlib import Path from aiohttp import web @@ -83,6 +84,12 @@ class VideoKonverterServer: self.app, loader=jinja2.FileSystemLoader(str(template_dir)), context_processors=[aiohttp_jinja2.request_processor], + bytecode_cache=jinja2.FileSystemBytecodeCache( + directory="/tmp/jinja2_cache", + pattern="__jinja2_%s.cache", + ), + auto_reload=os.environ.get("VK_DEV", "").lower() == "true", + enable_async=True, ) # i18n: Uebersetzungen laden und Jinja2-Filter registrieren diff --git a/video-konverter/app/services/hls.py b/video-konverter/app/services/hls.py index a41a65a..51f7aec 100644 --- a/video-konverter/app/services/hls.py +++ b/video-konverter/app/services/hls.py @@ -324,7 +324,8 @@ class HLSSessionManager: vf_parts.insert(0, f"scale=-2:{target_h}") vf_parts.append("hwupload") cmd += ["-vf", ",".join(vf_parts), - "-c:v", "h264_vaapi", "-qp", crf] + "-c:v", "h264_vaapi", "-qp", crf, + "-low_power", "1"] else: # CPU Software-Encoding vf_parts = [] diff --git a/video-konverter/app/services/library.py b/video-konverter/app/services/library.py index c43f1b2..c22d5a8 100644 --- a/video-konverter/app/services/library.py +++ b/video-konverter/app/services/library.py @@ -81,9 +81,10 @@ class LibraryService: db=db_cfg.get("database", "video_converter"), charset="utf8mb4", autocommit=True, - minsize=1, - maxsize=5, + minsize=2, + maxsize=10, connect_timeout=10, + pool_recycle=300, ) return self._db_pool except Exception as e: diff --git a/video-konverter/app/services/queue.py b/video-konverter/app/services/queue.py index 6d525f2..5eb44b6 100644 --- a/video-konverter/app/services/queue.py +++ b/video-konverter/app/services/queue.py @@ -500,9 +500,10 @@ class QueueService: db=db_cfg["db"], charset="utf8mb4", autocommit=True, - minsize=1, - maxsize=5, + minsize=2, + maxsize=10, connect_timeout=10, + pool_recycle=300, ) return self._db_pool diff --git a/video-konverter/app/static/tv/css/tv.css b/video-konverter/app/static/tv/css/tv.css index 34caca1..ea2f245 100644 --- a/video-konverter/app/static/tv/css/tv.css +++ b/video-konverter/app/static/tv/css/tv.css @@ -134,9 +134,9 @@ a { color: var(--accent); text-decoration: none; } .tv-row .tv-card { scroll-snap-align: start; flex-shrink: 0; - width: 90px; + width: 126px; } -.tv-row .tv-card-wide { width: 132px; } +.tv-row .tv-card-wide { width: 185px; } /* === Poster-Grid === */ .tv-grid { @@ -1244,8 +1244,8 @@ a { color: var(--accent); text-decoration: none; } .tv-nav-item { padding: 0.4rem 0.6rem; font-size: 0.85rem; } .tv-main { padding: 1rem; } .tv-grid { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 8px; } - .tv-row .tv-card { width: 72px; } - .tv-row .tv-card-wide { width: 108px; } + .tv-row .tv-card { width: 101px; } + .tv-row .tv-card-wide { width: 151px; } .tv-detail-header { flex-direction: column; } .tv-detail-poster { width: 150px; } .tv-page-title { font-size: 1.3rem; } @@ -1263,7 +1263,7 @@ a { color: var(--accent); text-decoration: none; } .tv-nav-links { gap: 0; } .tv-nav-item { padding: 0.3rem 0.5rem; font-size: 0.8rem; } .tv-grid { grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); } - .tv-row .tv-card { width: 60px; } + .tv-row .tv-card { width: 84px; } .tv-detail-poster { width: 120px; } /* Episoden-Karten: kompakt auf Handy */ .tv-ep-thumb { width: 100px; } @@ -1282,8 +1282,8 @@ a { color: var(--accent); text-decoration: none; } /* TV/Desktop (grosse Bildschirme) */ @media (min-width: 1280px) { .tv-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; } - .tv-row .tv-card { width: 102px; } - .tv-row .tv-card-wide { width: 156px; } + .tv-row .tv-card { width: 143px; } + .tv-row .tv-card-wide { width: 218px; } .tv-play-btn { padding: 1rem 3rem; font-size: 1.3rem; } /* Episoden-Karten: groesser auf TV */ .tv-ep-thumb { width: 260px; }