This commit is contained in:
2026-03-09 13:28:21 +00:00
parent ac29c68de0
commit 8e3c67565d
10 changed files with 536 additions and 78 deletions

View File

@@ -523,6 +523,7 @@ async function openAndPrepareDatabase() {
await seedFromSchemaFile(dbInstance); await seedFromSchemaFile(dbInstance);
await migrateLegacyProfiledToolSettings(dbInstance); await migrateLegacyProfiledToolSettings(dbInstance);
await removeDeprecatedSettings(dbInstance); await removeDeprecatedSettings(dbInstance);
await migrateSettingsSchemaMetadata(dbInstance);
await ensurePipelineStateRow(dbInstance); await ensurePipelineStateRow(dbInstance);
const syncedLogRoot = await configureRuntimeLogRootFromSettings(dbInstance, { ensure: true }); const syncedLogRoot = await configureRuntimeLogRootFromSettings(dbInstance, { ensure: true });
logger.info('log-root:synced', { logger.info('log-root:synced', {
@@ -677,6 +678,41 @@ async function removeDeprecatedSettings(db) {
} }
} }
// Aktualisiert settings_schema-Metadaten (required, description, validation_json)
// für bestehende Einträge, ohne user-konfigurierte Werte in settings_values anzutasten.
const SETTINGS_SCHEMA_METADATA_UPDATES = [
{
key: 'handbrake_preset_bluray',
required: 0,
description: 'Preset Name für -Z (Blu-ray). Leer = kein Preset, nur CLI-Parameter werden verwendet.',
validation_json: '{}'
},
{
key: 'handbrake_preset_dvd',
required: 0,
description: 'Preset Name für -Z (DVD). Leer = kein Preset, nur CLI-Parameter werden verwendet.',
validation_json: '{}'
}
];
async function migrateSettingsSchemaMetadata(db) {
for (const update of SETTINGS_SCHEMA_METADATA_UPDATES) {
const result = await db.run(
`UPDATE settings_schema
SET required = ?, description = ?, validation_json = ?, updated_at = CURRENT_TIMESTAMP
WHERE key = ? AND (required != ? OR description != ? OR validation_json != ?)`,
[
update.required, update.description, update.validation_json,
update.key,
update.required, update.description, update.validation_json
]
);
if (result?.changes > 0) {
logger.info('migrate:settings-schema-metadata', { key: update.key });
}
}
}
async function getDb() { async function getDb() {
return initDatabase(); return initDatabase();
} }

View File

@@ -2633,14 +2633,16 @@ class PipelineService extends EventEmitter {
return String(mkInfo?.status || '').trim().toUpperCase() === 'SUCCESS'; return String(mkInfo?.status || '').trim().toUpperCase() === 'SUCCESS';
} }
resolveCurrentRawPath(rawBaseDir, storedRawPath) { resolveCurrentRawPath(rawBaseDir, storedRawPath, extraBaseDirs = []) {
const stored = String(storedRawPath || '').trim(); const stored = String(storedRawPath || '').trim();
if (!stored) { if (!stored) {
return null; return null;
} }
const folderName = path.basename(stored);
const candidates = [stored]; const candidates = [stored];
if (rawBaseDir) { const allBaseDirs = [rawBaseDir, ...extraBaseDirs].filter(Boolean);
const byFolder = path.join(rawBaseDir, path.basename(stored)); for (const baseDir of allBaseDirs) {
const byFolder = path.join(baseDir, folderName);
if (!candidates.includes(byFolder)) { if (!candidates.includes(byFolder)) {
candidates.push(byFolder); candidates.push(byFolder);
} }
@@ -2660,7 +2662,13 @@ class PipelineService extends EventEmitter {
async migrateRawFolderNamingOnStartup(db) { async migrateRawFolderNamingOnStartup(db) {
const settings = await settingsService.getSettingsMap(); const settings = await settingsService.getSettingsMap();
const rawBaseDir = String(settings?.raw_dir || '').trim(); const rawBaseDir = String(settings?.raw_dir || '').trim();
if (!rawBaseDir || !fs.existsSync(rawBaseDir)) { const rawExtraDirs = [
settings?.raw_dir_bluray,
settings?.raw_dir_dvd,
settings?.raw_dir_other
].map((d) => String(d || '').trim()).filter(Boolean);
const allRawDirs = [rawBaseDir, ...rawExtraDirs].filter((d) => d && fs.existsSync(d));
if (allRawDirs.length === 0) {
return; return;
} }
@@ -2680,40 +2688,42 @@ class PipelineService extends EventEmitter {
let missingCount = 0; let missingCount = 0;
const discoveredByJobId = new Map(); const discoveredByJobId = new Map();
try { for (const scanDir of allRawDirs) {
const dirEntries = fs.readdirSync(rawBaseDir, { withFileTypes: true }); try {
for (const entry of dirEntries) { const dirEntries = fs.readdirSync(scanDir, { withFileTypes: true });
if (!entry.isDirectory()) { for (const entry of dirEntries) {
continue; if (!entry.isDirectory()) {
} continue;
const match = String(entry.name || '').match(/-\s*RAW\s*-\s*job-(\d+)\s*$/i); }
if (!match) { const match = String(entry.name || '').match(/-\s*RAW\s*-\s*job-(\d+)\s*$/i);
continue; if (!match) {
} continue;
const mappedJobId = Number(match[1]); }
if (!Number.isFinite(mappedJobId) || mappedJobId <= 0) { const mappedJobId = Number(match[1]);
continue; if (!Number.isFinite(mappedJobId) || mappedJobId <= 0) {
} continue;
const candidatePath = path.join(rawBaseDir, entry.name); }
let mtimeMs = 0; const candidatePath = path.join(scanDir, entry.name);
try { let mtimeMs = 0;
mtimeMs = Number(fs.statSync(candidatePath).mtimeMs || 0); try {
} catch (_error) { mtimeMs = Number(fs.statSync(candidatePath).mtimeMs || 0);
// ignore fs errors and keep zero mtime } catch (_error) {
} // ignore fs errors and keep zero mtime
const current = discoveredByJobId.get(mappedJobId); }
if (!current || mtimeMs > current.mtimeMs) { const current = discoveredByJobId.get(mappedJobId);
discoveredByJobId.set(mappedJobId, { if (!current || mtimeMs > current.mtimeMs) {
path: candidatePath, discoveredByJobId.set(mappedJobId, {
mtimeMs path: candidatePath,
}); mtimeMs
});
}
} }
} catch (scanError) {
logger.warn('startup:raw-dir-migrate:scan-failed', {
scanDir,
error: errorToMeta(scanError)
});
} }
} catch (scanError) {
logger.warn('startup:raw-dir-migrate:scan-failed', {
rawBaseDir,
error: errorToMeta(scanError)
});
} }
for (const row of rows) { for (const row of rows) {
@@ -2728,7 +2738,7 @@ class PipelineService extends EventEmitter {
ripFlagUpdateCount += 1; ripFlagUpdateCount += 1;
} }
const currentRawPath = this.resolveCurrentRawPath(rawBaseDir, row.raw_path) const currentRawPath = this.resolveCurrentRawPath(rawBaseDir, row.raw_path, rawExtraDirs)
|| discoveredByJobId.get(jobId)?.path || discoveredByJobId.get(jobId)?.path
|| null; || null;
if (!currentRawPath) { if (!currentRawPath) {
@@ -2736,6 +2746,8 @@ class PipelineService extends EventEmitter {
continue; continue;
} }
// Keep renamed folder in the same base dir as the current path
const currentBaseDir = path.dirname(currentRawPath);
const currentFolderName = path.basename(currentRawPath).replace(/^Incomplete_/i, '').trim(); const currentFolderName = path.basename(currentRawPath).replace(/^Incomplete_/i, '').trim();
const folderYearMatch = currentFolderName.match(/\((19|20)\d{2}\)/); const folderYearMatch = currentFolderName.match(/\((19|20)\d{2}\)/);
const fallbackYear = folderYearMatch const fallbackYear = folderYearMatch
@@ -2748,7 +2760,7 @@ class PipelineService extends EventEmitter {
}, jobId); }, jobId);
const shouldBeIncomplete = !isJobFinished(row); const shouldBeIncomplete = !isJobFinished(row);
const desiredRawPath = path.join( const desiredRawPath = path.join(
rawBaseDir, currentBaseDir,
buildRawDirName(metadataBase, jobId, { incomplete: shouldBeIncomplete }) buildRawDirName(metadataBase, jobId, { incomplete: shouldBeIncomplete })
); );
@@ -2791,7 +2803,7 @@ class PipelineService extends EventEmitter {
ripFlagUpdateCount, ripFlagUpdateCount,
conflictCount, conflictCount,
missingCount, missingCount,
rawBaseDir scannedDirs: allRawDirs
}); });
} }
} }
@@ -3814,7 +3826,18 @@ class PipelineService extends EventEmitter {
}; };
} }
if (!job.raw_path || !fs.existsSync(job.raw_path)) { const refreshSettings = await settingsService.getSettingsMap();
const refreshRawBaseDir = String(refreshSettings?.raw_dir || '').trim();
const refreshRawExtraDirs = [
refreshSettings?.raw_dir_bluray,
refreshSettings?.raw_dir_dvd,
refreshSettings?.raw_dir_other
].map((d) => String(d || '').trim()).filter(Boolean);
const resolvedRefreshRawPath = job.raw_path
? this.resolveCurrentRawPath(refreshRawBaseDir, job.raw_path, refreshRawExtraDirs)
: null;
if (!resolvedRefreshRawPath) {
return { return {
triggered: false, triggered: false,
reason: 'raw_path_missing', reason: 'raw_path_missing',
@@ -3824,6 +3847,10 @@ class PipelineService extends EventEmitter {
}; };
} }
if (resolvedRefreshRawPath !== job.raw_path) {
await historyService.updateJob(activeJobId, { raw_path: resolvedRefreshRawPath });
}
const existingPlan = this.safeParseJson(job.encode_plan_json); const existingPlan = this.safeParseJson(job.encode_plan_json);
const mode = existingPlan?.mode || this.snapshot.context?.mode || 'rip'; const mode = existingPlan?.mode || this.snapshot.context?.mode || 'rip';
const sourceJobId = existingPlan?.sourceJobId || this.snapshot.context?.sourceJobId || null; const sourceJobId = existingPlan?.sourceJobId || this.snapshot.context?.sourceJobId || null;
@@ -3834,7 +3861,7 @@ class PipelineService extends EventEmitter {
`Settings gespeichert (${relevantKeys.join(', ')}). Titel-/Spurprüfung wird mit aktueller Konfiguration neu gestartet.` `Settings gespeichert (${relevantKeys.join(', ')}). Titel-/Spurprüfung wird mit aktueller Konfiguration neu gestartet.`
); );
this.runReviewForRawJob(activeJobId, job.raw_path, { mode, sourceJobId }).catch((error) => { this.runReviewForRawJob(activeJobId, resolvedRefreshRawPath, { mode, sourceJobId }).catch((error) => {
logger.error('settings:refresh-review:failed', { logger.error('settings:refresh-review:failed', {
jobId: activeJobId, jobId: activeJobId,
relevantKeys, relevantKeys,
@@ -5659,7 +5686,12 @@ class PipelineService extends EventEmitter {
const reencodeSettings = await settingsService.getSettingsMap(); const reencodeSettings = await settingsService.getSettingsMap();
const reencodeRawBaseDir = String(reencodeSettings?.raw_dir || '').trim(); const reencodeRawBaseDir = String(reencodeSettings?.raw_dir || '').trim();
const resolvedReencodeRawPath = this.resolveCurrentRawPath(reencodeRawBaseDir, sourceJob.raw_path); const reencodeRawExtraDirs = [
reencodeSettings?.raw_dir_bluray,
reencodeSettings?.raw_dir_dvd,
reencodeSettings?.raw_dir_other
].map((d) => String(d || '').trim()).filter(Boolean);
const resolvedReencodeRawPath = this.resolveCurrentRawPath(reencodeRawBaseDir, sourceJob.raw_path, reencodeRawExtraDirs);
if (!resolvedReencodeRawPath) { if (!resolvedReencodeRawPath) {
const error = new Error(`Re-Encode nicht möglich: RAW-Pfad existiert nicht (${sourceJob.raw_path}).`); const error = new Error(`Re-Encode nicht möglich: RAW-Pfad existiert nicht (${sourceJob.raw_path}).`);
error.statusCode = 400; error.statusCode = 400;
@@ -7420,7 +7452,12 @@ class PipelineService extends EventEmitter {
const reviewSettings = await settingsService.getSettingsMap(); const reviewSettings = await settingsService.getSettingsMap();
const reviewRawBaseDir = String(reviewSettings?.raw_dir || '').trim(); const reviewRawBaseDir = String(reviewSettings?.raw_dir || '').trim();
const resolvedReviewRawPath = this.resolveCurrentRawPath(reviewRawBaseDir, sourceJob.raw_path); const reviewRawExtraDirs = [
reviewSettings?.raw_dir_bluray,
reviewSettings?.raw_dir_dvd,
reviewSettings?.raw_dir_other
].map((d) => String(d || '').trim()).filter(Boolean);
const resolvedReviewRawPath = this.resolveCurrentRawPath(reviewRawBaseDir, sourceJob.raw_path, reviewRawExtraDirs);
if (!resolvedReviewRawPath) { if (!resolvedReviewRawPath) {
const error = new Error(`Review-Neustart nicht möglich: RAW-Pfad existiert nicht (${sourceJob.raw_path}).`); const error = new Error(`Review-Neustart nicht möglich: RAW-Pfad existiert nicht (${sourceJob.raw_path}).`);
error.statusCode = 400; error.statusCode = 400;

View File

@@ -847,7 +847,9 @@ class SettingsService {
if (selectedTitleId !== null) { if (selectedTitleId !== null) {
baseArgs.push('-t', String(selectedTitleId)); baseArgs.push('-t', String(selectedTitleId));
} }
baseArgs.push('-Z', map.handbrake_preset); if (map.handbrake_preset) {
baseArgs.push('-Z', map.handbrake_preset);
}
const extra = splitArgs(map.handbrake_extra_args); const extra = splitArgs(map.handbrake_extra_args);
const rawSelection = options?.trackSelection || null; const rawSelection = options?.trackSelection || null;
const hasSelection = rawSelection && typeof rawSelection === 'object'; const hasSelection = rawSelection && typeof rawSelection === 'object';

159
build-handbrake-nvdec.sh Executable file
View File

@@ -0,0 +1,159 @@
#!/usr/bin/env bash
# =============================================================================
# HandBrake mit NVDEC aus Quellcode bauen
# Ubuntu 22.04 / 24.04, Debian 11 / 12
#
# Verwendung:
# sudo bash build-handbrake-nvdec.sh [--version 1.9.0]
#
# NVDEC benötigt zur Laufzeit den NVIDIA-Treiber (libnvcuvid.so).
# =============================================================================
set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
BLUE='\033[0;34m'; BOLD='\033[1m'; RESET='\033[0m'
info() { echo -e "${BLUE}[INFO]${RESET} $*"; }
ok() { echo -e "${GREEN}[OK]${RESET} $*"; }
warn() { echo -e "${YELLOW}[WARN]${RESET} $*"; }
fatal() { echo -e "${RED}[FEHLER]${RESET} $*" >&2; exit 1; }
HANDBRAKE_VERSION="1.9.0"
while [[ $# -gt 0 ]]; do
case "$1" in
--version) HANDBRAKE_VERSION="$2"; shift 2 ;;
-h|--help) echo "Verwendung: sudo bash $0 [--version X.Y.Z]"; exit 0 ;;
*) fatal "Unbekannte Option: $1" ;;
esac
done
[[ $EUID -eq 0 ]] || fatal "Bitte als root ausführen: sudo bash $0"
[[ -f /etc/os-release ]] && . /etc/os-release || fatal "OS nicht erkennbar"
echo -e "\n${BOLD}${BLUE}══════════════════════════════════════════${RESET}"
echo -e "${BOLD} HandBrake ${HANDBRAKE_VERSION} mit NVDEC bauen${RESET}"
echo -e "${BOLD}${BLUE}══════════════════════════════════════════${RESET}\n"
# --------------------------------------------------------------------------
# 1. Build-Abhängigkeiten
# --------------------------------------------------------------------------
info "Installiere Build-Abhängigkeiten..."
apt-get update -qq
apt-get install -y \
autoconf automake build-essential cmake git \
libass-dev libbz2-dev libdvdnav-dev libdvdread-dev \
libfontconfig-dev libfreetype-dev libfribidi-dev libharfbuzz-dev \
libjansson-dev liblzma-dev libmp3lame-dev libnuma-dev libogg-dev \
libopus-dev libsamplerate0-dev libspeex-dev libtheora-dev libtool \
libturbojpeg0-dev libvorbis-dev libvpx-dev libx264-dev libxml2-dev \
m4 meson nasm ninja-build patch pkg-config python3 tar zlib1g-dev \
>/dev/null
ok "Build-Abhängigkeiten installiert"
# --------------------------------------------------------------------------
# 2. CUDA-Header für NVDEC
# --------------------------------------------------------------------------
info "Prüfe CUDA-Header für NVDEC-Support..."
if dpkg -l 2>/dev/null | grep -q '^ii.*nvidia-cuda-toolkit'; then
ok "nvidia-cuda-toolkit bereits installiert"
else
info "Installiere nvidia-cuda-toolkit (für NVDEC-Header)..."
if apt-get install -y nvidia-cuda-toolkit >/dev/null 2>&1; then
ok "nvidia-cuda-toolkit installiert"
else
warn "nvidia-cuda-toolkit nicht in Standard-Repos versuche NVIDIA CUDA Repo..."
local_ver="${VERSION_ID//./}"
cuda_deb="/tmp/cuda-keyring.deb"
if curl -fsSL \
"https://developer.download.nvidia.com/compute/cuda/repos/ubuntu${local_ver}/x86_64/cuda-keyring_1.1-1_all.deb" \
-o "$cuda_deb" 2>/dev/null; then
dpkg -i "$cuda_deb"
apt-get update -qq
# Minimale Header statt vollem Toolkit
apt-get install -y cuda-cudart-dev-12-8 >/dev/null 2>&1 && \
ok "CUDA-Header installiert (cuda-cudart-dev-12-8)" || \
warn "CUDA-Header-Installation fehlgeschlagen NVDEC könnte im Build fehlen."
else
warn "NVIDIA CUDA Repo nicht erreichbar NVDEC könnte im Build fehlen."
fi
fi
fi
# --------------------------------------------------------------------------
# 3. Alte Installation entfernen
# --------------------------------------------------------------------------
if command -v HandBrakeCLI &>/dev/null; then
EXISTING=$(HandBrakeCLI --version 2>&1 | head -1)
warn "Entferne vorhandenes HandBrakeCLI: ${EXISTING}"
apt-get remove -y handbrake-cli 2>/dev/null || true
snap remove handbrake-cli 2>/dev/null || true
rm -f /usr/bin/HandBrakeCLI /usr/local/bin/HandBrakeCLI
fi
# --------------------------------------------------------------------------
# 4. Quellcode herunterladen
# --------------------------------------------------------------------------
TMP_DIR=$(mktemp -d)
trap 'cd /; rm -rf "$TMP_DIR"' EXIT
SRC_URL="https://github.com/HandBrake/HandBrake/releases/download/${HANDBRAKE_VERSION}/HandBrake-${HANDBRAKE_VERSION}-source.tar.bz2"
TARBALL="${TMP_DIR}/handbrake-src.tar.bz2"
info "Lade HandBrake ${HANDBRAKE_VERSION} herunter..."
info "URL: ${SRC_URL}"
curl -fL --progress-bar "$SRC_URL" -o "$TARBALL" || \
wget --progress=bar:force "$SRC_URL" -O "$TARBALL" || \
fatal "Download fehlgeschlagen. Bitte Version prüfen: https://github.com/HandBrake/HandBrake/releases"
info "Entpacke..."
tar xjf "$TARBALL" -C "$TMP_DIR"
SRC_DIR="${TMP_DIR}/HandBrake-${HANDBRAKE_VERSION}"
[[ -d "$SRC_DIR" ]] || SRC_DIR=$(find "$TMP_DIR" -maxdepth 1 -type d -name "HandBrake*" | head -1)
[[ -d "$SRC_DIR" ]] || fatal "Quellverzeichnis nicht gefunden"
# --------------------------------------------------------------------------
# 5. Konfigurieren & Bauen
# --------------------------------------------------------------------------
cd "$SRC_DIR"
info "Konfiguriere HandBrake mit NVDEC (--enable-nvdec)..."
./configure \
--launch-jobs="$(nproc)" \
--enable-nvdec \
--prefix=/usr/local \
2>&1 | tail -15
info "Baue HandBrake mit $(nproc) Threads das dauert 1030 Minuten..."
make --directory=build -j"$(nproc)"
info "Installiere nach /usr/local/bin/..."
make --directory=build install
# --------------------------------------------------------------------------
# 6. Ergebnis prüfen
# --------------------------------------------------------------------------
if command -v HandBrakeCLI &>/dev/null; then
VER=$(HandBrakeCLI --version 2>&1 | head -1)
ok "Erfolgreich installiert: ${VER}"
echo ""
# NVDEC im Binary prüfen
if HandBrakeCLI --help 2>&1 | grep -qi "nvdec"; then
ok "NVDEC: im Binary vorhanden ✓"
else
warn "NVDEC: nicht in --help gefunden (evtl. kein --enable-nvdec oder kein CUDA-Header)"
fi
# Laufzeit-Bibliothek prüfen
if ldconfig -p 2>/dev/null | grep -q libnvcuvid; then
ok "libnvcuvid: gefunden NVDEC zur Laufzeit verfügbar ✓"
else
warn "libnvcuvid: NICHT gefunden"
warn "→ Bitte NVIDIA-Treiber installieren: apt-get install nvidia-driver-XXX"
warn " NVDEC ist im Binary vorhanden, funktioniert aber erst mit dem Treiber."
fi
else
fatal "HandBrakeCLI nach dem Build nicht gefunden Build fehlgeschlagen."
fi

View File

@@ -271,7 +271,7 @@ VALUES ('makemkv_rip_extra_args_bluray', 'Tools', 'MakeMKV Rip Extra Args', 'str
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('makemkv_rip_extra_args_bluray', NULL); INSERT OR IGNORE INTO settings_values (key, value) VALUES ('makemkv_rip_extra_args_bluray', NULL);
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index) INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
VALUES ('handbrake_preset_bluray', 'Tools', 'HandBrake Preset', 'string', 1, 'Preset Name für -Z (Blu-ray).', 'H.264 MKV 1080p30', '[]', '{"minLength":1}', 320); VALUES ('handbrake_preset_bluray', 'Tools', 'HandBrake Preset', 'string', 0, 'Preset Name für -Z (Blu-ray). Leer = kein Preset, nur CLI-Parameter werden verwendet.', 'H.264 MKV 1080p30', '[]', '{}', 320);
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('handbrake_preset_bluray', 'H.264 MKV 1080p30'); INSERT OR IGNORE INTO settings_values (key, value) VALUES ('handbrake_preset_bluray', 'H.264 MKV 1080p30');
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index) INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
@@ -308,7 +308,7 @@ VALUES ('makemkv_rip_extra_args_dvd', 'Tools', 'MakeMKV Rip Extra Args', 'string
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('makemkv_rip_extra_args_dvd', NULL); INSERT OR IGNORE INTO settings_values (key, value) VALUES ('makemkv_rip_extra_args_dvd', NULL);
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index) INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
VALUES ('handbrake_preset_dvd', 'Tools', 'HandBrake Preset', 'string', 1, 'Preset Name für -Z (DVD).', 'H.264 MKV 480p30', '[]', '{"minLength":1}', 520); VALUES ('handbrake_preset_dvd', 'Tools', 'HandBrake Preset', 'string', 0, 'Preset Name für -Z (DVD). Leer = kein Preset, nur CLI-Parameter werden verwendet.', 'H.264 MKV 480p30', '[]', '{}', 520);
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('handbrake_preset_dvd', 'H.264 MKV 480p30'); INSERT OR IGNORE INTO settings_values (key, value) VALUES ('handbrake_preset_dvd', 'H.264 MKV 480p30');
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index) INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)

View File

@@ -709,7 +709,8 @@ export default function MediaInfoReviewPanel({
const processedFiles = Number(review.processedFiles || titles.length || 0); const processedFiles = Number(review.processedFiles || titles.length || 0);
const totalFiles = Number(review.totalFiles || titles.length || 0); const totalFiles = Number(review.totalFiles || titles.length || 0);
const playlistRecommendation = review.playlistRecommendation || null; const playlistRecommendation = review.playlistRecommendation || null;
const presetLabel = String(presetDisplayValue || review.selectors?.preset || '').trim() || '-'; const rawPreset = String(review.selectors?.preset || '').trim();
const presetLabel = String(presetDisplayValue || rawPreset).trim() || '(kein Preset)';
const scriptCatalog = (Array.isArray(availableScripts) ? availableScripts : []) const scriptCatalog = (Array.isArray(availableScripts) ? availableScripts : [])
.map((item) => ({ .map((item) => ({
id: normalizeScriptId(item?.id), id: normalizeScriptId(item?.id),
@@ -974,7 +975,9 @@ export default function MediaInfoReviewPanel({
) : titles.map((title) => { ) : titles.map((title) => {
const titleEligible = title?.eligibleForEncode !== false; const titleEligible = title?.eligibleForEncode !== false;
const titleChecked = allowTitleSelection const titleChecked = allowTitleSelection
? currentSelectedId === normalizeTitleId(title.id) ? (currentSelectedId !== null
? currentSelectedId === normalizeTitleId(title.id)
: Boolean(title.selectedForEncode))
: Boolean(title.selectedForEncode); : Boolean(title.selectedForEncode);
const titleSelectionEntry = trackSelectionByTitle?.[title.id] || trackSelectionByTitle?.[String(title.id)] || {}; const titleSelectionEntry = trackSelectionByTitle?.[title.id] || trackSelectionByTitle?.[String(title.id)] || {};
const subtitleTracks = Array.isArray(title.subtitleTracks) ? title.subtitleTracks : []; const subtitleTracks = Array.isArray(title.subtitleTracks) ? title.subtitleTracks : [];

View File

@@ -427,7 +427,11 @@ export default function PipelineStatusCard({
return presetDisplayMap[preset] || preset; return presetDisplayMap[preset] || preset;
}, [mediaInfoReview?.selectors?.preset, presetDisplayMap]); }, [mediaInfoReview?.selectors?.preset, presetDisplayMap]);
const buildSelectedTrackSelectionForCurrentTitle = () => { const buildSelectedTrackSelectionForCurrentTitle = () => {
const encodeTitleId = normalizeTitleId(selectedEncodeTitleId); const encodeTitleId = normalizeTitleId(selectedEncodeTitleId)
|| normalizeTitleId(mediaInfoReview?.encodeInputTitleId)
|| normalizeTitleId(
(Array.isArray(mediaInfoReview?.titles) ? mediaInfoReview.titles : []).find((t) => t?.selectedForEncode)?.id
);
const selectionEntry = encodeTitleId const selectionEntry = encodeTitleId
? (trackSelectionByTitle?.[encodeTitleId] || trackSelectionByTitle?.[String(encodeTitleId)] || null) ? (trackSelectionByTitle?.[encodeTitleId] || trackSelectionByTitle?.[String(encodeTitleId)] || null)
: null; : null;

View File

@@ -93,6 +93,10 @@ function injectHandBrakePresetOptions(categories, presetPayload) {
}); });
}; };
// "(kein Preset)" immer als erste Option — ermöglicht reinen CLI-Betrieb
normalizedOptions.push({ label: '(kein Preset nur CLI-Parameter)', value: '', disabled: false });
seenValues.add('');
for (const option of sourceOptions) { for (const option of sourceOptions) {
if (option?.disabled) { if (option?.disabled) {
addGroupOption(option); addGroupOption(option);
@@ -103,7 +107,7 @@ function injectHandBrakePresetOptions(categories, presetPayload) {
addSelectableOption(setting?.value); addSelectableOption(setting?.value);
addSelectableOption(setting?.defaultValue); addSelectableOption(setting?.defaultValue);
if (normalizedOptions.length === 0) { if (normalizedOptions.length <= 1) {
return setting; return setting;
} }

View File

@@ -15,6 +15,8 @@
# --host <hostname> Hostname/IP für die Weboberfläche (Standard: Maschinen-IP) # --host <hostname> Hostname/IP für die Weboberfläche (Standard: Maschinen-IP)
# --no-makemkv MakeMKV-Installation überspringen # --no-makemkv MakeMKV-Installation überspringen
# --no-handbrake HandBrake-Installation überspringen # --no-handbrake HandBrake-Installation überspringen
# --build-handbrake HandBrake aus Quellcode mit NVDEC-Unterstützung bauen
# --handbrake-version HandBrake-Version für Source-Build (Standard: 1.9.0)
# --no-nginx Nginx-Einrichtung überspringen (Frontend läuft dann auf Port 5173) # --no-nginx Nginx-Einrichtung überspringen (Frontend läuft dann auf Port 5173)
# --reinstall Vorhandene Installation ersetzen (Daten bleiben erhalten) # --reinstall Vorhandene Installation ersetzen (Daten bleiben erhalten)
# -h, --help Diese Hilfe anzeigen # -h, --help Diese Hilfe anzeigen
@@ -41,6 +43,8 @@ BACKEND_PORT="3001"
FRONTEND_HOST="" # wird automatisch ermittelt, wenn leer FRONTEND_HOST="" # wird automatisch ermittelt, wenn leer
SKIP_MAKEMKV=false SKIP_MAKEMKV=false
SKIP_HANDBRAKE=false SKIP_HANDBRAKE=false
BUILD_HANDBRAKE_NVDEC=false
HANDBRAKE_VERSION="1.9.0"
SKIP_NGINX=false SKIP_NGINX=false
REINSTALL=false REINSTALL=false
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -48,14 +52,16 @@ SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# --- Argumente parsen --------------------------------------------------------- # --- Argumente parsen ---------------------------------------------------------
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--dir) INSTALL_DIR="$2"; shift 2 ;; --dir) INSTALL_DIR="$2"; shift 2 ;;
--user) SERVICE_USER="$2"; shift 2 ;; --user) SERVICE_USER="$2"; shift 2 ;;
--port) BACKEND_PORT="$2"; shift 2 ;; --port) BACKEND_PORT="$2"; shift 2 ;;
--host) FRONTEND_HOST="$2"; shift 2 ;; --host) FRONTEND_HOST="$2"; shift 2 ;;
--no-makemkv) SKIP_MAKEMKV=true; shift ;; --no-makemkv) SKIP_MAKEMKV=true; shift ;;
--no-handbrake) SKIP_HANDBRAKE=true; shift ;; --no-handbrake) SKIP_HANDBRAKE=true; shift ;;
--no-nginx) SKIP_NGINX=true; shift ;; --build-handbrake) BUILD_HANDBRAKE_NVDEC=true; shift ;;
--reinstall) REINSTALL=true; shift ;; --handbrake-version) HANDBRAKE_VERSION="$2"; shift 2 ;;
--no-nginx) SKIP_NGINX=true; shift ;;
--reinstall) REINSTALL=true; shift ;;
-h|--help) -h|--help)
sed -n '/^# Verwendung/,/^# ====/p' "$0" | head -n -1 | sed 's/^# \?//' sed -n '/^# Verwendung/,/^# ====/p' "$0" | head -n -1 | sed 's/^# \?//'
exit 0 ;; exit 0 ;;
@@ -177,11 +183,107 @@ install_makemkv() {
warn "Beta-Key: https://www.makemkv.com/forum/viewtopic.php?t=1053" warn "Beta-Key: https://www.makemkv.com/forum/viewtopic.php?t=1053"
} }
build_handbrake_nvdec() {
header "HandBrake ${HANDBRAKE_VERSION} mit NVDEC aus Quellcode bauen"
local tmp_dir
tmp_dir=$(mktemp -d)
local src_url="https://github.com/HandBrake/HandBrake/releases/download/${HANDBRAKE_VERSION}/HandBrake-${HANDBRAKE_VERSION}-source.tar.bz2"
local tarball="${tmp_dir}/handbrake-src.tar.bz2"
# Build-Abhängigkeiten
info "Installiere Build-Abhängigkeiten..."
apt-get install -y \
autoconf automake build-essential cmake git \
libass-dev libbz2-dev libdvdnav-dev libdvdread-dev \
libfontconfig-dev libfreetype-dev libfribidi-dev libharfbuzz-dev \
libjansson-dev liblzma-dev libmp3lame-dev libnuma-dev libogg-dev \
libopus-dev libsamplerate0-dev libspeex-dev libtheora-dev libtool \
libturbojpeg0-dev libvorbis-dev libvpx-dev libx264-dev libxml2-dev \
m4 meson nasm ninja-build patch pkg-config python3 tar zlib1g-dev \
>/dev/null
# CUDA Toolkit für NVDEC-Header
info "Installiere CUDA Toolkit (für NVDEC-Header)..."
if ! dpkg -l 2>/dev/null | grep -q '^ii.*nvidia-cuda-toolkit'; then
apt-get install -y nvidia-cuda-toolkit >/dev/null 2>&1 || {
warn "nvidia-cuda-toolkit nicht verfügbar versuche Fallback-Header..."
local cuda_keyring="/tmp/cuda-keyring.deb"
local ubuntu_ver="${VERSION_ID//./}"
curl -fsSL "https://developer.download.nvidia.com/compute/cuda/repos/ubuntu${ubuntu_ver}/x86_64/cuda-keyring_1.1-1_all.deb" \
-o "$cuda_keyring" 2>/dev/null && \
dpkg -i "$cuda_keyring" 2>/dev/null && \
apt-get update -qq && \
apt-get install -y cuda-cudart-dev-12-8 >/dev/null 2>&1 || \
warn "CUDA-Header konnten nicht installiert werden NVDEC wird möglicherweise nicht verfügbar sein."
}
fi
ok "Build-Abhängigkeiten installiert"
# Alte Installation entfernen
if command_exists HandBrakeCLI; then
warn "Entferne vorhandenes HandBrakeCLI..."
apt-get remove -y handbrake-cli 2>/dev/null || true
snap remove handbrake-cli 2>/dev/null || true
rm -f /usr/bin/HandBrakeCLI /usr/local/bin/HandBrakeCLI
fi
# Quellcode herunterladen
info "Lade HandBrake ${HANDBRAKE_VERSION} herunter..."
curl -fsSL "$src_url" -o "$tarball" 2>/dev/null || \
wget -q "$src_url" -O "$tarball" || \
fatal "HandBrake-Quellcode konnte nicht heruntergeladen werden (${src_url})"
info "Entpacke Quellcode..."
tar xjf "$tarball" -C "$tmp_dir"
local src_dir="${tmp_dir}/HandBrake-${HANDBRAKE_VERSION}"
[[ -d "$src_dir" ]] || src_dir=$(find "$tmp_dir" -maxdepth 1 -type d -name "HandBrake*" | head -1)
[[ -d "$src_dir" ]] || fatal "HandBrake-Quellverzeichnis nicht gefunden in $tmp_dir"
cd "$src_dir"
info "Konfiguriere HandBrake mit NVDEC..."
./configure --launch-jobs="$(nproc)" --enable-nvdec --prefix=/usr/local 2>&1 | tail -10
info "Baue HandBrake ($(nproc) Threads bitte warten)..."
make --directory=build -j"$(nproc)"
info "Installiere HandBrake nach /usr/local/bin..."
make --directory=build install
cd /
rm -rf "$tmp_dir"
if command_exists HandBrakeCLI; then
local ver
ver=$(HandBrakeCLI --version 2>&1 | head -1)
ok "HandBrakeCLI mit NVDEC installiert: ${ver}"
if ldconfig -p 2>/dev/null | grep -q libnvcuvid; then
ok "libnvcuvid gefunden NVDEC ist zur Laufzeit verfügbar."
else
warn "libnvcuvid NICHT gefunden. NVDEC benötigt den installierten NVIDIA-Treiber."
fi
else
fatal "HandBrakeCLI nach dem Build nicht gefunden Build fehlgeschlagen."
fi
}
install_handbrake() { install_handbrake() {
header "HandBrake CLI installieren" header "HandBrake CLI installieren"
if [[ "$BUILD_HANDBRAKE_NVDEC" == true ]]; then
build_handbrake_nvdec
return
fi
if command_exists HandBrakeCLI; then if command_exists HandBrakeCLI; then
ok "HandBrakeCLI bereits installiert" local ver
ver=$(HandBrakeCLI --version 2>&1 | head -1)
ok "HandBrakeCLI bereits installiert: ${ver}"
if ! HandBrakeCLI --help 2>&1 | grep -qi "nvdec"; then
warn "Das installierte HandBrakeCLI unterstützt kein NVDEC."
warn "Für NVDEC neu bauen: sudo bash install-dev.sh --no-makemkv --no-nginx --build-handbrake"
fi
return return
fi fi
@@ -189,13 +291,14 @@ install_handbrake() {
info "Versuche HandBrake CLI aus den Standard-Repos..." info "Versuche HandBrake CLI aus den Standard-Repos..."
if apt-get install -y handbrake-cli 2>/dev/null; then if apt-get install -y handbrake-cli 2>/dev/null; then
ok "HandBrakeCLI installiert (Standard-Repos)" ok "HandBrakeCLI installiert (Standard-Repos)"
if ! HandBrakeCLI --help 2>&1 | grep -qi "nvdec"; then
warn "Dieses HandBrakeCLI hat kein NVDEC. Für NVDEC: sudo bash install-dev.sh --no-makemkv --no-nginx --build-handbrake"
fi
return return
fi fi
case "$ID" in case "$ID" in
ubuntu) ubuntu)
# Strategie 2 (Ubuntu < 24.04): PPA manuell per Key + Sources-Datei eintragen,
# ohne add-apt-repository (schlägt auf Noble mit 401 fehl).
local codename="${VERSION_CODENAME:-jammy}" local codename="${VERSION_CODENAME:-jammy}"
local ppa_sources="/etc/apt/sources.list.d/handbrake.list" local ppa_sources="/etc/apt/sources.list.d/handbrake.list"
local ppa_key="/etc/apt/keyrings/handbrake.gpg" local ppa_key="/etc/apt/keyrings/handbrake.gpg"
@@ -217,7 +320,6 @@ EOF
warn "PPA-Key konnte nicht geladen werden." warn "PPA-Key konnte nicht geladen werden."
fi fi
# Strategie 3 (Ubuntu): snap
if command_exists snap; then if command_exists snap; then
info "Versuche HandBrake via snap..." info "Versuche HandBrake via snap..."
if snap install handbrake-cli 2>/dev/null; then if snap install handbrake-cli 2>/dev/null; then
@@ -228,7 +330,6 @@ EOF
;; ;;
debian) debian)
# Strategie 2 (Debian): Backports
info "Versuche HandBrake CLI über Debian Backports..." info "Versuche HandBrake CLI über Debian Backports..."
if ! find /etc/apt/sources.list.d/ -name "*.list" -exec grep -l "backports" {} \; 2>/dev/null | grep -q .; then if ! find /etc/apt/sources.list.d/ -name "*.list" -exec grep -l "backports" {} \; 2>/dev/null | grep -q .; then
echo "deb http://deb.debian.org/debian ${VERSION_CODENAME}-backports main" \ echo "deb http://deb.debian.org/debian ${VERSION_CODENAME}-backports main" \
@@ -241,7 +342,8 @@ EOF
esac esac
warn "HandBrake CLI konnte nicht automatisch installiert werden." warn "HandBrake CLI konnte nicht automatisch installiert werden."
warn "Bitte manuell installieren: https://handbrake.fr/downloads2.php" warn "Für einen NVDEC-Build: sudo bash install-dev.sh --no-makemkv --no-nginx --build-handbrake"
warn "Oder manuell: https://handbrake.fr/downloads2.php"
} }
# --- apt-Hilfsfunktionen ------------------------------------------------------ # --- apt-Hilfsfunktionen ------------------------------------------------------

View File

@@ -20,6 +20,8 @@
# --host <hostname> Hostname/IP für die Weboberfläche (Standard: Maschinen-IP) # --host <hostname> Hostname/IP für die Weboberfläche (Standard: Maschinen-IP)
# --no-makemkv MakeMKV-Installation überspringen # --no-makemkv MakeMKV-Installation überspringen
# --no-handbrake HandBrake-Installation überspringen # --no-handbrake HandBrake-Installation überspringen
# --build-handbrake HandBrake aus Quellcode mit NVDEC-Unterstützung bauen
# --handbrake-version HandBrake-Version für Source-Build (Standard: 1.9.0)
# --no-nginx Nginx-Einrichtung überspringen # --no-nginx Nginx-Einrichtung überspringen
# --reinstall Vorhandene Installation aktualisieren (Daten bleiben erhalten) # --reinstall Vorhandene Installation aktualisieren (Daten bleiben erhalten)
# -h, --help Diese Hilfe anzeigen # -h, --help Diese Hilfe anzeigen
@@ -49,21 +51,25 @@ BACKEND_PORT="3001"
FRONTEND_HOST="" FRONTEND_HOST=""
SKIP_MAKEMKV=false SKIP_MAKEMKV=false
SKIP_HANDBRAKE=false SKIP_HANDBRAKE=false
BUILD_HANDBRAKE_NVDEC=false
HANDBRAKE_VERSION="1.9.0"
SKIP_NGINX=false SKIP_NGINX=false
REINSTALL=false REINSTALL=false
# --- Argumente parsen --------------------------------------------------------- # --- Argumente parsen ---------------------------------------------------------
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--branch) GIT_BRANCH="$2"; shift 2 ;; --branch) GIT_BRANCH="$2"; shift 2 ;;
--dir) INSTALL_DIR="$2"; shift 2 ;; --dir) INSTALL_DIR="$2"; shift 2 ;;
--user) SERVICE_USER="$2"; shift 2 ;; --user) SERVICE_USER="$2"; shift 2 ;;
--port) BACKEND_PORT="$2"; shift 2 ;; --port) BACKEND_PORT="$2"; shift 2 ;;
--host) FRONTEND_HOST="$2"; shift 2 ;; --host) FRONTEND_HOST="$2"; shift 2 ;;
--no-makemkv) SKIP_MAKEMKV=true; shift ;; --no-makemkv) SKIP_MAKEMKV=true; shift ;;
--no-handbrake) SKIP_HANDBRAKE=true; shift ;; --no-handbrake) SKIP_HANDBRAKE=true; shift ;;
--no-nginx) SKIP_NGINX=true; shift ;; --build-handbrake) BUILD_HANDBRAKE_NVDEC=true; shift ;;
--reinstall) REINSTALL=true; shift ;; --handbrake-version) HANDBRAKE_VERSION="$2"; shift 2 ;;
--no-nginx) SKIP_NGINX=true; shift ;;
--reinstall) REINSTALL=true; shift ;;
-h|--help) -h|--help)
sed -n '/^# Verwendung/,/^# ====/p' "$0" | head -n -1 | sed 's/^# \?//' sed -n '/^# Verwendung/,/^# ====/p' "$0" | head -n -1 | sed 's/^# \?//'
exit 0 ;; exit 0 ;;
@@ -182,11 +188,115 @@ install_makemkv() {
warn "Beta-Key: https://www.makemkv.com/forum/viewtopic.php?t=1053" warn "Beta-Key: https://www.makemkv.com/forum/viewtopic.php?t=1053"
} }
build_handbrake_nvdec() {
header "HandBrake ${HANDBRAKE_VERSION} mit NVDEC aus Quellcode bauen"
local tmp_dir
tmp_dir=$(mktemp -d)
local src_url="https://github.com/HandBrake/HandBrake/releases/download/${HANDBRAKE_VERSION}/HandBrake-${HANDBRAKE_VERSION}-source.tar.bz2"
local tarball="${tmp_dir}/handbrake-src.tar.bz2"
# Build-Abhängigkeiten
info "Installiere Build-Abhängigkeiten..."
apt-get install -y \
autoconf automake build-essential cmake git \
libass-dev libbz2-dev libdvdnav-dev libdvdread-dev \
libfontconfig-dev libfreetype-dev libfribidi-dev libharfbuzz-dev \
libjansson-dev liblzma-dev libmp3lame-dev libnuma-dev libogg-dev \
libopus-dev libsamplerate0-dev libspeex-dev libtheora-dev libtool \
libturbojpeg0-dev libvorbis-dev libvpx-dev libx264-dev libxml2-dev \
m4 meson nasm ninja-build patch pkg-config python3 tar zlib1g-dev \
>/dev/null
# CUDA Toolkit für NVDEC-Header
info "Installiere CUDA Toolkit (für NVDEC-Header)..."
if ! dpkg -l 2>/dev/null | grep -q '^ii.*nvidia-cuda-toolkit'; then
apt-get install -y nvidia-cuda-toolkit >/dev/null 2>&1 || {
warn "nvidia-cuda-toolkit nicht verfügbar versuche Fallback-Header..."
# Fallback: nur die minimalen Header aus dem NVIDIA-CUDA-Repo
local cuda_keyring="/tmp/cuda-keyring.deb"
local ubuntu_ver="${VERSION_ID//./}"
curl -fsSL "https://developer.download.nvidia.com/compute/cuda/repos/ubuntu${ubuntu_ver}/x86_64/cuda-keyring_1.1-1_all.deb" \
-o "$cuda_keyring" 2>/dev/null && \
dpkg -i "$cuda_keyring" 2>/dev/null && \
apt-get update -qq && \
apt-get install -y cuda-cudart-dev-12-8 >/dev/null 2>&1 || \
warn "CUDA-Header konnten nicht installiert werden NVDEC wird möglicherweise nicht verfügbar sein."
}
fi
ok "Build-Abhängigkeiten installiert"
# Alte Installation entfernen
if command_exists HandBrakeCLI; then
warn "Entferne vorhandenes HandBrakeCLI..."
apt-get remove -y handbrake-cli 2>/dev/null || true
snap remove handbrake-cli 2>/dev/null || true
rm -f /usr/bin/HandBrakeCLI /usr/local/bin/HandBrakeCLI
fi
# Quellcode herunterladen
info "Lade HandBrake ${HANDBRAKE_VERSION} herunter..."
curl -fsSL "$src_url" -o "$tarball" 2>/dev/null || \
wget -q "$src_url" -O "$tarball" || \
fatal "HandBrake-Quellcode konnte nicht heruntergeladen werden (${src_url})"
info "Entpacke Quellcode..."
tar xjf "$tarball" -C "$tmp_dir"
local src_dir="${tmp_dir}/HandBrake-${HANDBRAKE_VERSION}"
[[ -d "$src_dir" ]] || src_dir=$(find "$tmp_dir" -maxdepth 1 -type d -name "HandBrake*" | head -1)
[[ -d "$src_dir" ]] || fatal "HandBrake-Quellverzeichnis nicht gefunden in $tmp_dir"
cd "$src_dir"
# Konfigurieren mit NVDEC
info "Konfiguriere HandBrake mit NVDEC..."
./configure --launch-jobs="$(nproc)" --enable-nvdec --prefix=/usr/local 2>&1 | tail -10
# Bauen (dauert je nach Hardware 1030 Min)
info "Baue HandBrake ($(nproc) Threads bitte warten)..."
make --directory=build -j"$(nproc)"
info "Installiere HandBrake nach /usr/local/bin..."
make --directory=build install
cd /
rm -rf "$tmp_dir"
if command_exists HandBrakeCLI; then
local ver
ver=$(HandBrakeCLI --version 2>&1 | head -1)
ok "HandBrakeCLI mit NVDEC installiert: ${ver}"
# NVDEC-Verfügbarkeit zur Laufzeit hängt vom NVIDIA-Treiber ab.
# Prüfe ob libnvcuvid vorhanden:
if ldconfig -p 2>/dev/null | grep -q libnvcuvid; then
ok "libnvcuvid gefunden NVDEC ist zur Laufzeit verfügbar."
else
warn "libnvcuvid NICHT gefunden. NVDEC benötigt den installierten NVIDIA-Treiber (nvidia-driver-XXX)."
warn "Stelle sicher, dass der NVIDIA-Treiber auf dem System installiert ist."
fi
else
fatal "HandBrakeCLI nach dem Build nicht gefunden Build fehlgeschlagen."
fi
}
install_handbrake() { install_handbrake() {
header "HandBrake CLI installieren" header "HandBrake CLI installieren"
# --build-handbrake: immer aus Quellcode mit NVDEC bauen
if [[ "$BUILD_HANDBRAKE_NVDEC" == true ]]; then
build_handbrake_nvdec
return
fi
# Bereits installiert?
if command_exists HandBrakeCLI; then if command_exists HandBrakeCLI; then
ok "HandBrakeCLI bereits installiert" local ver
ver=$(HandBrakeCLI --version 2>&1 | head -1)
ok "HandBrakeCLI bereits installiert: ${ver}"
if ! HandBrakeCLI --help 2>&1 | grep -qi "nvdec"; then
warn "Das installierte HandBrakeCLI unterstützt kein NVDEC."
warn "Für NVDEC neu bauen: sudo bash install.sh --no-makemkv --no-nginx --build-handbrake"
fi
return return
fi fi
@@ -194,13 +304,14 @@ install_handbrake() {
info "Versuche HandBrake CLI aus den Standard-Repos..." info "Versuche HandBrake CLI aus den Standard-Repos..."
if apt-get install -y handbrake-cli 2>/dev/null; then if apt-get install -y handbrake-cli 2>/dev/null; then
ok "HandBrakeCLI installiert (Standard-Repos)" ok "HandBrakeCLI installiert (Standard-Repos)"
if ! HandBrakeCLI --help 2>&1 | grep -qi "nvdec"; then
warn "Dieses HandBrakeCLI hat kein NVDEC. Für NVDEC: sudo bash install.sh --no-makemkv --no-nginx --build-handbrake"
fi
return return
fi fi
case "$ID" in case "$ID" in
ubuntu) ubuntu)
# Strategie 2 (Ubuntu < 24.04): PPA manuell per Key + Sources-Datei eintragen,
# ohne add-apt-repository (schlägt auf Noble mit 401 fehl).
local codename="${VERSION_CODENAME:-jammy}" local codename="${VERSION_CODENAME:-jammy}"
local ppa_sources="/etc/apt/sources.list.d/handbrake.list" local ppa_sources="/etc/apt/sources.list.d/handbrake.list"
local ppa_key="/etc/apt/keyrings/handbrake.gpg" local ppa_key="/etc/apt/keyrings/handbrake.gpg"
@@ -233,7 +344,6 @@ EOF
;; ;;
debian) debian)
# Strategie 2 (Debian): Backports
info "Versuche HandBrake CLI über Debian Backports..." info "Versuche HandBrake CLI über Debian Backports..."
if ! find /etc/apt/sources.list.d/ -name "*.list" -exec grep -l "backports" {} \; 2>/dev/null | grep -q .; then if ! find /etc/apt/sources.list.d/ -name "*.list" -exec grep -l "backports" {} \; 2>/dev/null | grep -q .; then
echo "deb http://deb.debian.org/debian ${VERSION_CODENAME}-backports main" \ echo "deb http://deb.debian.org/debian ${VERSION_CODENAME}-backports main" \
@@ -246,7 +356,8 @@ EOF
esac esac
warn "HandBrake CLI konnte nicht automatisch installiert werden." warn "HandBrake CLI konnte nicht automatisch installiert werden."
warn "Bitte manuell installieren: https://handbrake.fr/downloads2.php" warn "Für einen NVDEC-Build: sudo bash install.sh --no-makemkv --no-nginx --build-handbrake"
warn "Oder manuell: https://handbrake.fr/downloads2.php"
} }
# --- apt-Hilfsfunktionen ------------------------------------------------------ # --- apt-Hilfsfunktionen ------------------------------------------------------