Kompletter Video-Konverter mit Web-UI, GPU-Beschleunigung (Intel VAAPI), Video-Bibliothek mit Serien/Film-Erkennung und TVDB-Integration. Features: - AV1/HEVC/H.264 Encoding (GPU + CPU) - Video-Bibliothek mit ffprobe-Analyse und Filtern - TVDB-Integration mit Review-Modal und Sprachkonfiguration - Film-Scanning und TVDB-Zuordnung - Import- und Clean-Service (Grundgeruest) - WebSocket Live-Updates, Queue-Management - Docker mit GPU/CPU-Profilen Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
156 lines
5.8 KiB
Python
156 lines
5.8 KiB
Python
"""Haupt-Server: HTTP + WebSocket + Templates in einer aiohttp-App"""
|
|
import asyncio
|
|
import logging
|
|
from pathlib import Path
|
|
from aiohttp import web
|
|
import aiohttp_jinja2
|
|
import jinja2
|
|
from app.config import Config
|
|
from app.services.queue import QueueService
|
|
from app.services.scanner import ScannerService
|
|
from app.services.encoder import EncoderService
|
|
from app.routes.ws import WebSocketManager
|
|
from app.services.library import LibraryService
|
|
from app.services.tvdb import TVDBService
|
|
from app.services.cleaner import CleanerService
|
|
from app.services.importer import ImporterService
|
|
from app.routes.api import setup_api_routes
|
|
from app.routes.library_api import setup_library_routes
|
|
from app.routes.pages import setup_page_routes
|
|
|
|
|
|
class VideoKonverterServer:
|
|
"""Haupt-Server - ein Port fuer HTTP, WebSocket und Admin-UI"""
|
|
|
|
def __init__(self):
|
|
self.config = Config()
|
|
self.config.setup_logging()
|
|
|
|
# Services
|
|
self.ws_manager = WebSocketManager()
|
|
self.scanner = ScannerService(self.config)
|
|
self.queue_service = QueueService(self.config, self.ws_manager)
|
|
self.ws_manager.set_queue_service(self.queue_service)
|
|
|
|
# Bibliothek-Services
|
|
self.library_service = LibraryService(self.config, self.ws_manager)
|
|
self.tvdb_service = TVDBService(self.config)
|
|
self.cleaner_service = CleanerService(self.config, self.library_service)
|
|
self.importer_service = ImporterService(
|
|
self.config, self.library_service, self.tvdb_service
|
|
)
|
|
|
|
# aiohttp App (50 GiB Upload-Limit fuer grosse Videodateien)
|
|
self.app = web.Application(client_max_size=50 * 1024 * 1024 * 1024)
|
|
self._setup_app()
|
|
|
|
def _setup_app(self) -> None:
|
|
"""Konfiguriert die aiohttp-Application"""
|
|
# Jinja2 Templates (request_processor macht request in Templates verfuegbar)
|
|
template_dir = Path(__file__).parent / "templates"
|
|
aiohttp_jinja2.setup(
|
|
self.app,
|
|
loader=jinja2.FileSystemLoader(str(template_dir)),
|
|
context_processors=[aiohttp_jinja2.request_processor],
|
|
)
|
|
|
|
# WebSocket Route
|
|
ws_path = self.config.server_config.get("websocket_path", "/ws")
|
|
self.app.router.add_get(ws_path, self.ws_manager.handle_websocket)
|
|
|
|
# API Routes
|
|
setup_api_routes(
|
|
self.app, self.config, self.queue_service, self.scanner
|
|
)
|
|
|
|
# Bibliothek API Routes
|
|
setup_library_routes(
|
|
self.app, self.config, self.library_service,
|
|
self.tvdb_service, self.queue_service,
|
|
self.cleaner_service, self.importer_service,
|
|
)
|
|
|
|
# Seiten Routes
|
|
setup_page_routes(self.app, self.config, self.queue_service)
|
|
|
|
# Statische Dateien
|
|
static_dir = Path(__file__).parent / "static"
|
|
if static_dir.exists():
|
|
self.app.router.add_static(
|
|
"/static/", path=str(static_dir), name="static"
|
|
)
|
|
|
|
# Startup/Shutdown Hooks
|
|
self.app.on_startup.append(self._on_startup)
|
|
self.app.on_shutdown.append(self._on_shutdown)
|
|
|
|
async def _on_startup(self, app: web.Application) -> None:
|
|
"""Server-Start: GPU pruefen, Queue starten"""
|
|
mode = self.config.encoding_mode
|
|
|
|
# Auto-Detection
|
|
if mode == "auto":
|
|
gpu_ok = EncoderService.detect_gpu_available()
|
|
if gpu_ok:
|
|
gpu_ok = await EncoderService.test_gpu_encoding(
|
|
self.config.gpu_device
|
|
)
|
|
if gpu_ok:
|
|
self.config.settings["encoding"]["mode"] = "gpu"
|
|
self.config.settings["encoding"]["default_preset"] = "gpu_av1"
|
|
logging.info(f"GPU erkannt ({self.config.gpu_device}), "
|
|
f"verwende GPU-Encoding")
|
|
else:
|
|
self.config.settings["encoding"]["mode"] = "cpu"
|
|
self.config.settings["encoding"]["default_preset"] = "cpu_av1"
|
|
logging.info("Keine GPU erkannt, verwende CPU-Encoding")
|
|
else:
|
|
logging.info(f"Encoding-Modus: {mode}")
|
|
|
|
# Queue starten
|
|
await self.queue_service.start()
|
|
|
|
# Bibliothek starten
|
|
await self.library_service.start()
|
|
|
|
# DB-Pool mit anderen Services teilen
|
|
if self.library_service._db_pool:
|
|
self.tvdb_service.set_db_pool(self.library_service._db_pool)
|
|
self.importer_service.set_db_pool(self.library_service._db_pool)
|
|
|
|
# Zusaetzliche DB-Tabellen erstellen
|
|
await self.tvdb_service.init_db()
|
|
await self.importer_service.init_db()
|
|
|
|
host = self.config.server_config.get("host", "0.0.0.0")
|
|
port = self.config.server_config.get("port", 8080)
|
|
logging.info(f"Server bereit auf http://{host}:{port}")
|
|
|
|
async def _on_shutdown(self, app: web.Application) -> None:
|
|
"""Server-Stop: Queue und Library stoppen"""
|
|
await self.queue_service.stop()
|
|
await self.library_service.stop()
|
|
logging.info("Server heruntergefahren")
|
|
|
|
async def run(self) -> None:
|
|
"""Startet den Server"""
|
|
host = self.config.server_config.get("host", "0.0.0.0")
|
|
port = self.config.server_config.get("port", 8080)
|
|
|
|
runner = web.AppRunner(self.app)
|
|
await runner.setup()
|
|
site = web.TCPSite(runner, host, port)
|
|
await site.start()
|
|
|
|
logging.info(
|
|
f"VideoKonverter Server laeuft auf http://{host}:{port}\n"
|
|
f" Dashboard: http://{host}:{port}/\n"
|
|
f" Bibliothek: http://{host}:{port}/library\n"
|
|
f" Admin: http://{host}:{port}/admin\n"
|
|
f" Statistik: http://{host}:{port}/statistics\n"
|
|
f" WebSocket: ws://{host}:{port}/ws\n"
|
|
f" API: http://{host}:{port}/api/convert (POST)"
|
|
)
|
|
|
|
# Endlos laufen bis Interrupt
|
|
await asyncio.Event().wait()
|