Test
This commit is contained in:
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
159
build-handbrake-nvdec.sh
Executable 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 10–30 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
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 : [];
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
130
install-dev.sh
130
install-dev.sh
@@ -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 ------------------------------------------------------
|
||||||
|
|||||||
139
install.sh
139
install.sh
@@ -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 10–30 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 ------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user