docker.videokonverter/app/templates/library.html
data 08dcf34f5d VideoKonverter v2.2.0 - Initial Commit
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>
2026-02-21 20:09:11 +01:00

392 lines
18 KiB
HTML

{% extends "base.html" %}
{% block title %}Bibliothek - VideoKonverter{% endblock %}
{% block content %}
<section class="library-section">
<div class="library-header">
<h2>Video-Bibliothek</h2>
<div class="library-actions">
<button class="btn-primary" onclick="startScan()">Scan starten</button>
<button class="btn-secondary" onclick="openPathsModal()">Pfade verwalten</button>
<button class="btn-secondary" onclick="openCleanModal()">Aufraeumen</button>
<button class="btn-secondary" onclick="openImportModal()">Importieren</button>
<button class="btn-secondary" onclick="showDuplicates()">Duplikate</button>
<button class="btn-secondary" onclick="startAutoMatch()">TVDB Auto-Match</button>
</div>
</div>
<!-- Scan-Progress -->
<div id="scan-progress" class="scan-progress" style="display:none">
<div class="progress-container">
<div class="progress-bar" id="scan-bar"></div>
</div>
<span class="scan-status" id="scan-status">Scanne...</span>
</div>
<!-- Auto-Match Progress -->
<div id="auto-match-progress" class="scan-progress" style="display:none">
<div class="progress-container">
<div class="progress-bar" id="auto-match-bar"></div>
</div>
<span class="scan-status" id="auto-match-status">TVDB Auto-Match...</span>
</div>
<!-- Statistik-Leiste -->
<div class="library-stats" id="library-stats">
<div class="lib-stat"><span class="lib-stat-value" id="stat-videos">-</span><span class="lib-stat-label">Videos</span></div>
<div class="lib-stat"><span class="lib-stat-value" id="stat-series">-</span><span class="lib-stat-label">Serien</span></div>
<div class="lib-stat"><span class="lib-stat-value" id="stat-size">-</span><span class="lib-stat-label">Gesamt</span></div>
<div class="lib-stat"><span class="lib-stat-value" id="stat-duration">-</span><span class="lib-stat-label">Spielzeit</span></div>
</div>
<div class="library-layout">
<!-- Pfad-Navigation -->
<nav class="library-nav" id="library-nav">
<h3>Bibliotheken</h3>
<div id="nav-paths-list">
<div class="loading-msg" style="padding:0.5rem;font-size:0.75rem">Lade...</div>
</div>
</nav>
<!-- Filter-Sidebar -->
<aside class="library-filters" id="filters">
<h3>Filter</h3>
<div class="filter-group">
<label>Suche</label>
<input type="text" id="filter-search" placeholder="Dateiname..." oninput="debounceFilter()">
</div>
<div class="filter-group">
<label>Aufloesung</label>
<select id="filter-resolution" onchange="applyFilters()">
<option value="">Alle</option>
<option value="3840">4K (3840+)</option>
<option value="1920">1080p (1920+)</option>
<option value="1280">720p (1280+)</option>
<option value="720">SD (720+)</option>
</select>
</div>
<div class="filter-group">
<label>Video-Codec</label>
<select id="filter-codec" onchange="applyFilters()">
<option value="">Alle</option>
<option value="hevc">HEVC/H.265</option>
<option value="h264">H.264</option>
<option value="av1">AV1</option>
<option value="mpeg4">MPEG-4</option>
<option value="mpeg2video">MPEG-2</option>
</select>
</div>
<div class="filter-group">
<label>Container</label>
<select id="filter-container" onchange="applyFilters()">
<option value="">Alle</option>
<option value="mkv">MKV</option>
<option value="mp4">MP4</option>
<option value="avi">AVI</option>
<option value="webm">WebM</option>
<option value="ts">TS</option>
<option value="wmv">WMV</option>
</select>
</div>
<div class="filter-group">
<label>Audio-Sprache</label>
<select id="filter-audio-lang" onchange="applyFilters()">
<option value="">Alle</option>
<option value="ger">Deutsch</option>
<option value="eng">Englisch</option>
</select>
</div>
<div class="filter-group">
<label>Audio-Kanaele</label>
<select id="filter-audio-ch" onchange="applyFilters()">
<option value="">Alle</option>
<option value="2">Stereo (2.0)</option>
<option value="6">5.1 Surround</option>
<option value="8">7.1 Surround</option>
</select>
</div>
<div class="filter-group">
<label><input type="checkbox" id="filter-10bit" onchange="applyFilters()"> Nur 10-Bit</label>
</div>
<div class="filter-group">
<label>Sortierung</label>
<select id="filter-sort" onchange="applyFilters()">
<option value="file_name">Name</option>
<option value="file_size">Groesse</option>
<option value="width">Aufloesung</option>
<option value="duration_sec">Dauer</option>
<option value="video_codec">Codec</option>
<option value="scanned_at">Scan-Datum</option>
</select>
<select id="filter-order" onchange="applyFilters()">
<option value="asc">Aufsteigend</option>
<option value="desc">Absteigend</option>
</select>
</div>
</aside>
<!-- Hauptbereich: Dynamische Bereiche pro Library-Pfad -->
<div class="library-content" id="library-content">
<div class="loading-msg">Lade Bibliothek...</div>
</div>
</div>
</section>
<!-- === MODALS === -->
<!-- Pfade-Verwaltung Modal -->
<div id="paths-modal" class="modal-overlay" style="display:none">
<div class="modal">
<div class="modal-header">
<h2>Scan-Pfade verwalten</h2>
<button class="btn-close" onclick="closePathsModal()">&times;</button>
</div>
<div class="modal-body" style="padding:1rem">
<div id="paths-list"></div>
<hr style="border-color:#333; margin:1rem 0">
<h3 style="font-size:0.9rem; margin-bottom:0.5rem">Neuen Pfad hinzufuegen</h3>
<div class="form-grid">
<div class="form-group">
<label>Name</label>
<input type="text" id="new-path-name" placeholder="z.B. Serien">
</div>
<div class="form-group">
<label>Pfad</label>
<input type="text" id="new-path-path" placeholder="/mnt/30 - Media/10 - Serien">
</div>
<div class="form-group">
<label>Typ</label>
<select id="new-path-type">
<option value="series">Serien</option>
<option value="movie">Filme</option>
</select>
</div>
</div>
<div class="form-actions">
<button class="btn-primary" onclick="addPath()">Hinzufuegen</button>
</div>
</div>
</div>
</div>
<!-- TVDB Such-Modal -->
<div id="tvdb-modal" class="modal-overlay" style="display:none">
<div class="modal modal-small">
<div class="modal-header">
<h2>TVDB zuordnen</h2>
<button class="btn-close" onclick="closeTvdbModal()">&times;</button>
</div>
<div class="modal-body" style="padding:1rem">
<input type="hidden" id="tvdb-series-id">
<div class="form-group">
<label>Serie suchen</label>
<input type="text" id="tvdb-search-input" placeholder="Serienname..."
oninput="debounceTvdbSearch()">
</div>
<div id="tvdb-results" class="tvdb-results"></div>
</div>
</div>
</div>
<!-- Duplikate-Modal -->
<div id="duplicates-modal" class="modal-overlay" style="display:none">
<div class="modal">
<div class="modal-header">
<h2>Duplikate</h2>
<button class="btn-close" onclick="closeDuplicatesModal()">&times;</button>
</div>
<div class="modal-body" style="padding:1rem">
<div id="duplicates-list" class="duplicates-list">
<div class="loading-msg">Suche Duplikate...</div>
</div>
</div>
</div>
</div>
<!-- Serien-Detail-Modal -->
<div id="series-modal" class="modal-overlay" style="display:none">
<div class="modal" style="max-width:1000px">
<div class="modal-header">
<div style="flex:1">
<h2 id="series-modal-title">Serie</h2>
<span id="series-modal-genres" class="series-genres-line"></span>
</div>
<div class="modal-header-actions">
<button class="btn-small btn-secondary" id="btn-tvdb-refresh" onclick="tvdbRefresh()" style="display:none">TVDB aktualisieren</button>
<button class="btn-small btn-secondary" id="btn-tvdb-unlink" onclick="tvdbUnlink()" style="display:none">TVDB loesen</button>
<button class="btn-small btn-secondary" id="btn-metadata-dl" onclick="downloadMetadata()" style="display:none">Metadaten laden</button>
<button class="btn-small btn-secondary" id="btn-series-delete-db" onclick="deleteSeries(false)">Aus DB loeschen</button>
<button class="btn-small btn-danger" id="btn-series-delete-all" onclick="deleteSeries(true)">Komplett loeschen</button>
<button class="btn-close" onclick="closeSeriesModal()">&times;</button>
</div>
</div>
<div class="modal-body" style="padding:0">
<!-- Detail-Tabs -->
<div class="detail-tabs">
<button class="detail-tab active" onclick="switchDetailTab('episodes')">Episoden</button>
<button class="detail-tab" onclick="switchDetailTab('cast')">Darsteller</button>
<button class="detail-tab" onclick="switchDetailTab('artworks')">Bilder</button>
</div>
<div id="series-modal-body" style="padding:1rem">
</div>
</div>
</div>
</div>
<!-- Film-Detail-Modal -->
<div id="movie-modal" class="modal-overlay" style="display:none">
<div class="modal" style="max-width:900px">
<div class="modal-header">
<div style="flex:1">
<h2 id="movie-modal-title">Film</h2>
<span id="movie-modal-genres" class="series-genres-line"></span>
</div>
<div class="modal-header-actions">
<button class="btn-small btn-secondary" id="btn-movie-tvdb-unlink" onclick="movieTvdbUnlink()" style="display:none">TVDB loesen</button>
<button class="btn-small btn-secondary" onclick="deleteMovie(false)">Aus DB loeschen</button>
<button class="btn-small btn-danger" onclick="deleteMovie(true)">Komplett loeschen</button>
<button class="btn-close" onclick="closeMovieModal()">&times;</button>
</div>
</div>
<div class="modal-body" style="padding:1rem">
<div id="movie-modal-body"></div>
</div>
</div>
</div>
<!-- Film-TVDB Such-Modal -->
<div id="movie-tvdb-modal" class="modal-overlay" style="display:none">
<div class="modal modal-small">
<div class="modal-header">
<h2>Film TVDB zuordnen</h2>
<button class="btn-close" onclick="closeMovieTvdbModal()">&times;</button>
</div>
<div class="modal-body" style="padding:1rem">
<input type="hidden" id="movie-tvdb-id">
<div class="form-group">
<label>Film suchen</label>
<input type="text" id="movie-tvdb-search-input" placeholder="Filmname..."
oninput="debounceMovieTvdbSearch()">
</div>
<div id="movie-tvdb-results" class="tvdb-results"></div>
</div>
</div>
</div>
<!-- Clean-Modal -->
<div id="clean-modal" class="modal-overlay" style="display:none">
<div class="modal" style="max-width:1000px">
<div class="modal-header">
<h2>Bibliothek aufraeumen</h2>
<button class="btn-close" onclick="closeCleanModal()">&times;</button>
</div>
<div class="modal-body" style="padding:1rem">
<div class="clean-actions" style="margin-bottom:1rem; display:flex; gap:0.5rem; align-items:center;">
<button class="btn-primary" onclick="scanForJunk()">Junk scannen</button>
<button class="btn-secondary" onclick="deleteSelectedJunk()">Ausgewaehlte loeschen</button>
<button class="btn-secondary" onclick="deleteEmptyDirs()">Leere Ordner loeschen</button>
<span id="clean-info" class="text-muted" style="margin-left:auto"></span>
</div>
<div class="clean-filter" style="margin-bottom:0.5rem">
<label style="font-size:0.8rem; color:#aaa;">Filter Extension:</label>
<select id="clean-ext-filter" onchange="filterCleanList()" style="background:#252525;color:#ddd;border:1px solid #333;border-radius:4px;padding:0.2rem;font-size:0.8rem;">
<option value="">Alle</option>
</select>
<label style="margin-left:0.5rem; font-size:0.8rem; cursor:pointer; color:#ccc;">
<input type="checkbox" id="clean-select-all" onchange="toggleCleanSelectAll()"> Alle auswaehlen
</label>
</div>
<div id="clean-list" class="clean-list">
<div class="loading-msg">Klicke "Junk scannen" um zu starten</div>
</div>
</div>
</div>
</div>
<!-- Import-Modal -->
<div id="import-modal" class="modal-overlay" style="display:none">
<div class="modal" style="max-width:1100px">
<div class="modal-header">
<h2>Videos importieren</h2>
<button class="btn-close" onclick="closeImportModal()">&times;</button>
</div>
<div class="modal-body" style="padding:0">
<!-- Schritt 1: Ordner waehlen -->
<div id="import-setup">
<!-- Filebrowser -->
<div class="import-browser-bar">
<input type="text" id="import-source" placeholder="/mnt/..." oninput="debounceImportPath()"
style="flex:1;background:#252525;color:#ddd;border:1px solid #333;border-radius:5px;padding:0.4rem 0.6rem;font-size:0.85rem">
<button class="btn-small btn-secondary" onclick="importBrowse(document.getElementById('import-source').value || '/mnt')">Oeffnen</button>
</div>
<div id="import-browser" class="import-browser"></div>
<!-- Einstellungen + Analysieren -->
<div class="import-setup-footer">
<div class="import-setup-opts">
<label>Ziel:</label>
<select id="import-target"></select>
<label>Modus:</label>
<select id="import-mode">
<option value="copy">Kopieren</option>
<option value="move">Verschieben</option>
</select>
</div>
<div>
<span id="import-folder-info" class="text-muted"></span>
<button class="btn-primary" id="btn-analyze-import" onclick="createImportJob()" disabled>Analysieren</button>
</div>
</div>
</div>
<!-- Schritt 2: Vorschau -->
<div id="import-preview" style="display:none">
<div class="import-actions" style="padding:0.6rem 1rem; display:flex; gap:0.5rem; align-items:center; border-bottom:1px solid #2a2a2a;">
<button class="btn-primary" id="btn-start-import" onclick="executeImport()">Import starten</button>
<button class="btn-secondary" onclick="resetImport()">Zurueck</button>
<span id="import-info" class="text-muted" style="margin-left:auto"></span>
</div>
<div id="import-items-list" class="import-items-list"></div>
</div>
<!-- Schritt 3: Fortschritt -->
<div id="import-progress" style="display:none; padding:1rem;">
<div class="progress-container">
<div class="progress-bar" id="import-bar"></div>
</div>
<span class="text-muted" id="import-status-text">Importiere...</span>
</div>
</div>
</div>
</div>
<!-- TVDB Review-Modal -->
<div id="tvdb-review-modal" class="modal-overlay" style="display:none">
<div class="modal" style="max-width:1100px">
<div class="modal-header">
<div style="flex:1">
<h2>TVDB Vorschlaege pruefen</h2>
<span id="tvdb-review-info" class="text-muted" style="font-size:0.8rem"></span>
</div>
<div class="modal-header-actions">
<button class="btn-small btn-secondary" id="btn-review-skip-all" onclick="skipAllReviewItems()">Alle ueberspringen</button>
<button class="btn-close" onclick="closeTvdbReviewModal()">&times;</button>
</div>
</div>
<div class="modal-body" style="padding:0">
<div id="tvdb-review-list" class="tvdb-review-list">
<div class="loading-msg">Keine Vorschlaege</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="/static/js/library.js"></script>
{% endblock %}