diff --git a/backend/package-lock.json b/backend/package-lock.json index 0068c77..f0e2c00 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-backend", - "version": "0.12.0-6", + "version": "0.12.0-7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-backend", - "version": "0.12.0-6", + "version": "0.12.0-7", "dependencies": { "archiver": "^7.0.1", "cors": "^2.8.5", diff --git a/backend/package.json b/backend/package.json index b089855..4079106 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-backend", - "version": "0.12.0-6", + "version": "0.12.0-7", "private": true, "type": "commonjs", "scripts": { diff --git a/backend/src/db/database.js b/backend/src/db/database.js index ab2a052..d9c1784 100644 --- a/backend/src/db/database.js +++ b/backend/src/db/database.js @@ -790,7 +790,24 @@ async function removeDeprecatedSettings(db) { 'filename_template_dvd', 'output_folder_template_bluray', 'output_folder_template_dvd', - 'output_extension_audiobook' + 'output_extension_audiobook', + 'use_plugin_architecture', + 'use_plugin_architecture_bluray', + 'use_plugin_architecture_bluray_analyze', + 'use_plugin_architecture_bluray_rip', + 'use_plugin_architecture_bluray_review', + 'use_plugin_architecture_bluray_encode', + 'use_plugin_architecture_dvd', + 'use_plugin_architecture_dvd_analyze', + 'use_plugin_architecture_dvd_rip', + 'use_plugin_architecture_dvd_review', + 'use_plugin_architecture_dvd_encode', + 'use_plugin_architecture_cd', + 'use_plugin_architecture_cd_analyze', + 'use_plugin_architecture_cd_rip', + 'use_plugin_architecture_audiobook', + 'use_plugin_architecture_audiobook_analyze', + 'use_plugin_architecture_audiobook_encode' ]; for (const key of deprecatedKeys) { const schemaResult = await db.run('DELETE FROM settings_schema WHERE key = ?', [key]); @@ -996,202 +1013,6 @@ async function migrateSettingsSchemaMetadata(db) { } } - // Plugin-Architektur Toggle (Phase 2) — hierarchisch mit depends_on - // Struktur: global → medium (depends_on: global) → phase (depends_on: medium) - // - // Implementierungsstand: - // ✅ = plugin-pfad aktiv ⏳ = noch nicht implementiert (readonly, immer aus) - // - // BluRay / DVD: Analyse ✅ Rip ✅ Review ⏳ Encode ⏳ - // CD: Analyse ✅ Rip ✅ - // Audiobook: Analyse ✅ Encode ⏳ - - const pluginArchitectureSettings = [ - // ── Global ────────────────────────────────────────────────────────────── - { - key: 'use_plugin_architecture', - label: 'Neue Plugin-Architektur (Beta)', - description: 'Masterschalter: Aktiviert die neue modulare Plugin-Architektur (Phase 2). Alle Medium-Schalter darunter sind nur wirksam wenn dieser aktiv ist.', - defaultValue: 'false', - orderIndex: 9999, - dependsOn: null, - validationJson: '{}' - }, - // ── Blu-ray ────────────────────────────────────────────────────────────── - { - key: 'use_plugin_architecture_bluray', - label: 'Blu-ray Plugin', - description: 'Aktiviert das Blu-ray Plugin. Deaktiviert = Legacy-Pfad für alle Blu-ray-Phasen.', - defaultValue: 'true', - orderIndex: 10000, - dependsOn: 'use_plugin_architecture', - validationJson: '{}' - }, - { - key: 'use_plugin_architecture_bluray_analyze', - label: 'Analyse (Blu-ray)', - description: 'Disc-Erkennung und OMDB-Suche laufen über das Plugin.', - defaultValue: 'true', - orderIndex: 10001, - dependsOn: 'use_plugin_architecture_bluray', - validationJson: '{}' - }, - { - key: 'use_plugin_architecture_bluray_rip', - label: 'Rip (Blu-ray)', - description: 'MakeMKV-Rip läuft über das Plugin.', - defaultValue: 'true', - orderIndex: 10002, - dependsOn: 'use_plugin_architecture_bluray', - validationJson: '{}' - }, - { - key: 'use_plugin_architecture_bluray_review', - label: 'Review / Mediainfo-Prüfung (Blu-ray)', - description: 'HandBrake-Scan läuft über das Blu-ray-Plugin.', - defaultValue: 'true', - orderIndex: 10003, - dependsOn: 'use_plugin_architecture_bluray', - validationJson: '{}' - }, - { - key: 'use_plugin_architecture_bluray_encode', - label: 'Encoding (Blu-ray)', - description: 'Encoding läuft über das Blu-ray-Plugin.', - defaultValue: 'true', - orderIndex: 10004, - dependsOn: 'use_plugin_architecture_bluray', - validationJson: '{}' - }, - // ── DVD ────────────────────────────────────────────────────────────────── - { - key: 'use_plugin_architecture_dvd', - label: 'DVD Plugin', - description: 'Aktiviert das DVD Plugin. Deaktiviert = Legacy-Pfad für alle DVD-Phasen.', - defaultValue: 'true', - orderIndex: 10010, - dependsOn: 'use_plugin_architecture', - validationJson: '{}' - }, - { - key: 'use_plugin_architecture_dvd_analyze', - label: 'Analyse (DVD)', - description: 'Disc-Erkennung und OMDB-Suche laufen über das Plugin.', - defaultValue: 'true', - orderIndex: 10011, - dependsOn: 'use_plugin_architecture_dvd', - validationJson: '{}' - }, - { - key: 'use_plugin_architecture_dvd_rip', - label: 'Rip (DVD)', - description: 'MakeMKV-Rip läuft über das Plugin.', - defaultValue: 'true', - orderIndex: 10012, - dependsOn: 'use_plugin_architecture_dvd', - validationJson: '{}' - }, - { - key: 'use_plugin_architecture_dvd_review', - label: 'Review / Mediainfo-Prüfung (DVD)', - description: 'Review-/Track-Analyse läuft über das DVD-Plugin.', - defaultValue: 'true', - orderIndex: 10013, - dependsOn: 'use_plugin_architecture_dvd', - validationJson: '{}' - }, - { - key: 'use_plugin_architecture_dvd_encode', - label: 'Encoding (DVD)', - description: 'Encoding läuft über das DVD-Plugin.', - defaultValue: 'true', - orderIndex: 10014, - dependsOn: 'use_plugin_architecture_dvd', - validationJson: '{}' - }, - // ── Audio-CD ───────────────────────────────────────────────────────────── - { - key: 'use_plugin_architecture_cd', - label: 'Audio-CD Plugin', - description: 'Aktiviert das Audio-CD Plugin. Deaktiviert = Legacy-Pfad für alle CD-Phasen.', - defaultValue: 'true', - orderIndex: 10020, - dependsOn: 'use_plugin_architecture', - validationJson: '{}' - }, - { - key: 'use_plugin_architecture_cd_analyze', - label: 'Analyse (Audio-CD)', - description: 'TOC-Auslesung läuft über das Plugin.', - defaultValue: 'true', - orderIndex: 10021, - dependsOn: 'use_plugin_architecture_cd', - validationJson: '{}' - }, - { - key: 'use_plugin_architecture_cd_rip', - label: 'Rip (Audio-CD)', - description: 'CD-Rip/RAW-Rip/Encode-aus-RAW läuft über das Plugin.', - defaultValue: 'true', - orderIndex: 10022, - dependsOn: 'use_plugin_architecture_cd', - validationJson: '{}' - }, - // ── Audiobook ──────────────────────────────────────────────────────────── - { - key: 'use_plugin_architecture_audiobook', - label: 'Audiobook Plugin', - description: 'Aktiviert das Audiobook Plugin. Deaktiviert = Legacy-Pfad für alle Audiobook-Phasen.', - defaultValue: 'true', - orderIndex: 10030, - dependsOn: 'use_plugin_architecture', - validationJson: '{}' - }, - { - key: 'use_plugin_architecture_audiobook_analyze', - label: 'Analyse (Audiobook)', - description: 'ffprobe-Metadatenanalyse läuft über das Plugin.', - defaultValue: 'true', - orderIndex: 10031, - dependsOn: 'use_plugin_architecture_audiobook', - validationJson: '{}' - }, - { - key: 'use_plugin_architecture_audiobook_encode', - label: 'Encoding (Audiobook)', - description: 'Encoding läuft über das Audiobook-Plugin.', - defaultValue: 'true', - orderIndex: 10032, - dependsOn: 'use_plugin_architecture_audiobook', - validationJson: '{}' - } - ]; - for (const setting of pluginArchitectureSettings) { - await db.run( - `INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) - VALUES (?, 'Erweitert', ?, 'boolean', 1, ?, ?, '[]', ?, ?, ?)`, - [setting.key, setting.label, setting.description, setting.defaultValue, setting.validationJson, setting.orderIndex, setting.dependsOn ?? null] - ); - // depends_on und order_index für bereits existierende Zeilen aktualisieren - await db.run( - `UPDATE settings_schema SET depends_on = ?, order_index = ?, validation_json = ?, updated_at = CURRENT_TIMESTAMP - WHERE key = ? AND (depends_on IS NOT ? OR order_index != ? OR validation_json != ?)`, - [setting.dependsOn ?? null, setting.orderIndex, setting.validationJson, setting.key, setting.dependsOn ?? null, setting.orderIndex, setting.validationJson] - ); - await db.run( - `UPDATE settings_schema - SET label = ?, description = ?, default_value = ?, updated_at = CURRENT_TIMESTAMP - WHERE key = ? - AND (label != ? OR description != ? OR COALESCE(default_value, '') != COALESCE(?, ''))`, - [setting.label, setting.description, setting.defaultValue, setting.key, setting.label, setting.description, setting.defaultValue] - ); - await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES (?, ?)`, [setting.key, setting.defaultValue]); - // Readonly-Toggles immer auf 'false' zurücksetzen (nie aktivierbar) - if (setting.validationJson === '{"readonly":true}') { - await db.run(`UPDATE settings_values SET value = 'false' WHERE key = ?`, [setting.key]); - } - } - await db.run(` CREATE TABLE IF NOT EXISTS user_prefs ( key TEXT PRIMARY KEY, diff --git a/backend/src/services/pipelineService.js b/backend/src/services/pipelineService.js index 96ad58d..1c6d639 100644 --- a/backend/src/services/pipelineService.js +++ b/backend/src/services/pipelineService.js @@ -4007,93 +4007,12 @@ class PipelineService extends EventEmitter { } } - async isPluginArchitectureEnabled() { - return this.isPluginArchitectureEnabledForPlugin(null); - } - - getPluginArchitectureSettingKey(pluginId = null) { - const normalizedPluginId = String(pluginId || '').trim().toLowerCase(); - if (!normalizedPluginId) { - return null; - } - const byPluginId = { - bluray: 'use_plugin_architecture_bluray', - dvd: 'use_plugin_architecture_dvd', - cd: 'use_plugin_architecture_cd', - audiobook: 'use_plugin_architecture_audiobook' - }; - return byPluginId[normalizedPluginId] || null; - } - - isPluginArchitectureEnabledForSettings(settingsMap = null, pluginId = null, phase = null) { - const settings = settingsMap && typeof settingsMap === 'object' ? settingsMap : {}; - const globalEnabled = this.normalizeBooleanSetting(settings.use_plugin_architecture); - if (!globalEnabled) { - return false; - } - - const pluginSettingKey = this.getPluginArchitectureSettingKey(pluginId); - if (!pluginSettingKey) { - return globalEnabled; - } - - if (!(pluginSettingKey in settings)) { - return true; - } - - if (!this.normalizeBooleanSetting(settings[pluginSettingKey])) { - return false; - } - - // Phasen-spezifischer Toggle: use_plugin_architecture_{id}_{phase} - if (phase) { - const normalizedId = String(pluginId || '').trim().toLowerCase(); - const phaseKey = `use_plugin_architecture_${normalizedId}_${phase}`; - if (phaseKey in settings && !this.normalizeBooleanSetting(settings[phaseKey])) { - return false; - } - } - - return true; - } - - async isPluginArchitectureEnabledForPlugin(pluginId = null, phase = null) { - try { - const settingsMap = await settingsService.getSettingsMap(); - return this.isPluginArchitectureEnabledForSettings(settingsMap, pluginId, phase); - } catch (error) { - logger.warn('plugin-architecture:settings-read-failed', { - error: errorToMeta(error), - pluginId: pluginId || null, - phase: phase || null - }); - return false; - } - } - - async resolveAnalyzePlugin(discInfo = null, phase = 'analyze') { - const enabled = await this.isPluginArchitectureEnabledForPlugin(null); - if (!enabled) { - return null; - } + async resolveAnalyzePlugin(discInfo = null) { const registry = this.ensureSourcePluginRegistry(); if (!registry) { return null; } - const plugin = registry.findPlugin(discInfo); - if (!plugin?.id) { - return null; - } - const pluginEnabled = await this.isPluginArchitectureEnabledForPlugin(plugin.id, phase); - if (!pluginEnabled) { - logger.info('plugin-architecture:plugin-phase-disabled', { - pluginId: plugin.id, - phase, - mediaProfile: discInfo?.mediaProfile || null - }); - return null; - } - return plugin; + return registry.findPlugin(discInfo) || null; } sanitizePluginExecutionState(rawState = null) { @@ -7284,11 +7203,6 @@ class PipelineService extends EventEmitter { }); const settings = await settingsService.getEffectiveSettingsMap(mediaProfile); const reviewPlugin = await this.resolveAnalyzePlugin({ mediaProfile }, 'review').catch(() => null); - const reviewPluginEnabled = Boolean( - reviewPlugin - && reviewPlugin.id === mediaProfile - && typeof reviewPlugin.review === 'function' - ); let reviewPluginExecution = null; const analyzeContext = mkInfo?.analyzeContext || {}; const playlistAnalysis = analyzeContext.playlistAnalysis || this.snapshot.context?.playlistAnalysis || null; @@ -7340,42 +7254,20 @@ class PipelineService extends EventEmitter { const lines = []; let runInfo = null; let sourceArg = null; - if (reviewPluginEnabled) { - const pluginScan = await this.runPluginReviewScan(reviewPlugin, job, { - jobId, - deviceInfo, - reviewMode: 'pre_rip', - source: 'HANDBRAKE_SCAN', - silent: !this.isPrimaryJob(jobId) - }); - lines.push(...pluginScan.scanLines); - runInfo = pluginScan.runInfo || null; - sourceArg = pluginScan.sourceArg || null; - reviewPluginExecution = this.mergePluginExecutionState( - reviewPluginExecution, - pluginScan.pluginExecution - ); - } else { - const scanConfig = await settingsService.buildHandBrakeScanConfig(deviceInfo, { mediaProfile }); - logger.info('disc-track-review:command', { - jobId, - cmd: scanConfig.cmd, - args: scanConfig.args, - sourceArg: scanConfig.sourceArg, - selectedTitleId: selectedMakemkvTitleId - }); - - runInfo = await this.runCommand({ - jobId, - stage: 'MEDIAINFO_CHECK', - source: 'HANDBRAKE_SCAN', - cmd: scanConfig.cmd, - args: scanConfig.args, - collectLines: lines, - collectStderrLines: false - }); - sourceArg = scanConfig.sourceArg; - } + const pluginScan = await this.runPluginReviewScan(reviewPlugin, job, { + jobId, + deviceInfo, + reviewMode: 'pre_rip', + source: 'HANDBRAKE_SCAN', + silent: !this.isPrimaryJob(jobId) + }); + lines.push(...pluginScan.scanLines); + runInfo = pluginScan.runInfo || null; + sourceArg = pluginScan.sourceArg || null; + reviewPluginExecution = this.mergePluginExecutionState( + reviewPluginExecution, + pluginScan.pluginExecution + ); const parsed = parseMediainfoJsonOutput(lines.join('\n')); if (!parsed) { @@ -7533,11 +7425,6 @@ class PipelineService extends EventEmitter { }); const settings = await settingsService.getEffectiveSettingsMap(mediaProfile); const reviewPlugin = await this.resolveAnalyzePlugin({ mediaProfile }, 'review').catch(() => null); - const reviewPluginEnabled = Boolean( - reviewPlugin - && reviewPlugin.id === mediaProfile - && typeof reviewPlugin.review === 'function' - ); let reviewPluginExecution = null; const analyzeContext = mkInfo?.analyzeContext || {}; let playlistAnalysis = forceFreshAnalyze @@ -7699,39 +7586,18 @@ class PipelineService extends EventEmitter { ); try { const resolveScanLines = []; - if (reviewPluginEnabled) { - const pluginScan = await this.runPluginReviewScan(reviewPlugin, job, { - jobId, - rawPath, - reviewMode: 'rip', - source: 'HANDBRAKE_SCAN_PLAYLIST_MAP', - silent: !this.isPrimaryJob(jobId) - }); - resolveScanLines.push(...pluginScan.scanLines); - reviewPluginExecution = this.mergePluginExecutionState( - reviewPluginExecution, - pluginScan.pluginExecution - ); - } else { - const resolveScanConfig = await settingsService.buildHandBrakeScanConfigForInput(rawPath, { mediaProfile }); - logger.info('backup-track-review:handbrake-predecision-command', { - jobId, - cmd: resolveScanConfig.cmd, - args: resolveScanConfig.args, - sourceArg: resolveScanConfig.sourceArg, - candidatePlaylists: playlistCandidates.map((item) => item.playlistFile || item.playlistId) - }); - - await this.runCommand({ - jobId, - stage: 'MEDIAINFO_CHECK', - source: 'HANDBRAKE_SCAN_PLAYLIST_MAP', - cmd: resolveScanConfig.cmd, - args: resolveScanConfig.args, - collectLines: resolveScanLines, - collectStderrLines: false - }); - } + const pluginScan = await this.runPluginReviewScan(reviewPlugin, job, { + jobId, + rawPath, + reviewMode: 'rip', + source: 'HANDBRAKE_SCAN_PLAYLIST_MAP', + silent: !this.isPrimaryJob(jobId) + }); + resolveScanLines.push(...pluginScan.scanLines); + reviewPluginExecution = this.mergePluginExecutionState( + reviewPluginExecution, + pluginScan.pluginExecution + ); const resolveScanJson = parseMediainfoJsonOutput(resolveScanLines.join('\n')); if (resolveScanJson) { @@ -7753,16 +7619,9 @@ class PipelineService extends EventEmitter { ); } } else { - if (reviewPluginEnabled) { - const error = new Error('HandBrake Playlist-Trackdaten konnten nicht geparst werden (Plugin-Review aktiv, kein Fallback).'); - error.runInfo = null; - throw error; - } - await historyService.appendLog( - jobId, - 'SYSTEM', - 'HandBrake Playlist-Trackdaten konnten nicht geparst werden (Warteansicht ohne Audiodetails).' - ); + const error = new Error('HandBrake Playlist-Trackdaten konnten nicht geparst werden.'); + error.runInfo = null; + throw error; } } catch (error) { logger.warn('backup-track-review:handbrake-predecision-failed', { @@ -7774,9 +7633,7 @@ class PipelineService extends EventEmitter { 'SYSTEM', `HandBrake Voranalyse für Playlist-Auswahl fehlgeschlagen: ${error.message}` ); - if (reviewPluginEnabled) { - throw error; - } + throw error; } } else { playlistAnalysis = enrichPlaylistAnalysisWithHandBrakeCache(playlistAnalysis, handBrakePlaylistScan); @@ -8024,42 +7881,19 @@ class PipelineService extends EventEmitter { } else { try { const resolveScanLines = []; - if (reviewPluginEnabled) { - const pluginScan = await this.runPluginReviewScan(reviewPlugin, job, { - jobId, - rawPath, - reviewMode: 'rip', - source: 'HANDBRAKE_SCAN_PLAYLIST_MAP', - silent: !this.isPrimaryJob(jobId) - }); - resolveScanLines.push(...pluginScan.scanLines); - handBrakeResolveRunInfo = pluginScan.runInfo || null; - reviewPluginExecution = this.mergePluginExecutionState( - reviewPluginExecution, - pluginScan.pluginExecution - ); - } else { - const resolveScanConfig = await settingsService.buildHandBrakeScanConfigForInput(rawPath, { mediaProfile }); - logger.info('backup-track-review:handbrake-resolve-command', { - jobId, - cmd: resolveScanConfig.cmd, - args: resolveScanConfig.args, - sourceArg: resolveScanConfig.sourceArg, - selectedPlaylistId: resolvedPlaylistId, - selectedMakemkvTitleId: selectedTitleForReview - }); - - handBrakeResolveRunInfo = await this.runCommand({ - jobId, - stage: 'MEDIAINFO_CHECK', - source: 'HANDBRAKE_SCAN_PLAYLIST_MAP', - cmd: resolveScanConfig.cmd, - args: resolveScanConfig.args, - collectLines: resolveScanLines, - collectStderrLines: false, - silent: !this.isPrimaryJob(jobId) - }); - } + const pluginScan = await this.runPluginReviewScan(reviewPlugin, job, { + jobId, + rawPath, + reviewMode: 'rip', + source: 'HANDBRAKE_SCAN_PLAYLIST_MAP', + silent: !this.isPrimaryJob(jobId) + }); + resolveScanLines.push(...pluginScan.scanLines); + handBrakeResolveRunInfo = pluginScan.runInfo || null; + reviewPluginExecution = this.mergePluginExecutionState( + reviewPluginExecution, + pluginScan.pluginExecution + ); const resolveScanJson = parseMediainfoJsonOutput(resolveScanLines.join('\n')); if (!resolveScanJson) { @@ -9887,11 +9721,6 @@ class PipelineService extends EventEmitter { }); const settings = await settingsService.getEffectiveSettingsMap(mediaProfile); const reviewPlugin = await this.resolveAnalyzePlugin({ mediaProfile }, 'review').catch(() => null); - const reviewPluginEnabled = Boolean( - reviewPlugin - && reviewPlugin.id === mediaProfile - && typeof reviewPlugin.review === 'function' - ); let reviewPluginExecution = null; const analyzeContext = mkInfo?.analyzeContext || {}; const selectedPlaylistId = normalizePlaylistId( @@ -10025,42 +9854,19 @@ class PipelineService extends EventEmitter { const dvdScanLines = []; let dvdScanRunInfo = null; - if (reviewPluginEnabled) { - const pluginScan = await this.runPluginReviewScan(reviewPlugin, job, { - jobId, - rawPath: dvdHandBrakeScanInputPath, - reviewMode: 'rip', - source: 'HANDBRAKE_SCAN_DVD', - silent: !this.isPrimaryJob(jobId) - }); - dvdScanLines.push(...pluginScan.scanLines); - dvdScanRunInfo = pluginScan.runInfo || null; - reviewPluginExecution = this.mergePluginExecutionState( - reviewPluginExecution, - pluginScan.pluginExecution - ); - } else { - const dvdScanConfig = await settingsService.buildHandBrakeScanConfigForInput(dvdHandBrakeScanInputPath, { - mediaProfile, - settingsMap: settings - }); - logger.info('mediainfo:review:dvd-handbrake-scan:command', { - jobId, - dvdHandBrakeScanInputPath, - cmd: dvdScanConfig.cmd, - args: dvdScanConfig.args - }); - - dvdScanRunInfo = await this.runCommand({ - jobId, - stage: 'MEDIAINFO_CHECK', - source: 'HANDBRAKE_SCAN_DVD', - cmd: dvdScanConfig.cmd, - args: dvdScanConfig.args, - collectLines: dvdScanLines, - collectStderrLines: false - }); - } + const pluginScan = await this.runPluginReviewScan(reviewPlugin, job, { + jobId, + rawPath: dvdHandBrakeScanInputPath, + reviewMode: 'rip', + source: 'HANDBRAKE_SCAN_DVD', + silent: !this.isPrimaryJob(jobId) + }); + dvdScanLines.push(...pluginScan.scanLines); + dvdScanRunInfo = pluginScan.runInfo || null; + reviewPluginExecution = this.mergePluginExecutionState( + reviewPluginExecution, + pluginScan.pluginExecution + ); dvdHandBrakeScanJson = parseMediainfoJsonOutput(dvdScanLines.join('\n')); if (dvdHandBrakeScanJson) { @@ -10077,28 +9883,15 @@ class PipelineService extends EventEmitter { 'SYSTEM', `HandBrake DVD-Scan: Audio=${audioCount}, Subtitle=${subtitleCount}, Dauer=${formatDurationClock(titleInfo.durationSeconds)}` ); - } else if (reviewPluginEnabled) { - const error = new Error('HandBrake DVD-Scan: Kein Titel erkannt (Plugin-Review aktiv, kein Fallback).'); + } else { + const error = new Error('HandBrake DVD-Scan: Kein Titel erkannt.'); error.runInfo = dvdScanRunInfo; throw error; - } else { - await historyService.appendLog( - jobId, - 'SYSTEM', - 'HandBrake DVD-Scan: Kein Titel erkannt, falle auf MediaInfo zurück.' - ); - dvdHandBrakeScanJson = null; } - } else if (reviewPluginEnabled) { - const error = new Error('HandBrake DVD-Scan: Ausgabe nicht lesbar (Plugin-Review aktiv, kein Fallback).'); + } else { + const error = new Error('HandBrake DVD-Scan: Ausgabe nicht lesbar.'); error.runInfo = dvdScanRunInfo; throw error; - } else { - await historyService.appendLog( - jobId, - 'SYSTEM', - 'HandBrake DVD-Scan: Ausgabe nicht lesbar, falle auf MediaInfo zurück.' - ); } mediaInfoRuns.push({ filePath: dvdHandBrakeScanInputPath, runInfo: dvdScanRunInfo, fallbackRunInfo: null }); @@ -10282,41 +10075,19 @@ class PipelineService extends EventEmitter { await this.updateProgress('MEDIAINFO_CHECK', 100, null, 'HandBrake DVD Titel-Scan läuft', jobId); const dvdTitleScanLines = []; let dvdTitleScanRunInfo = null; - if (reviewPluginEnabled) { - const pluginScan = await this.runPluginReviewScan(reviewPlugin, job, { - jobId, - rawPath: dvdScanInputPath, - reviewMode: 'rip', - source: 'HANDBRAKE_SCAN_DVD_TITLES', - silent: !this.isPrimaryJob(jobId) - }); - dvdTitleScanLines.push(...pluginScan.scanLines); - dvdTitleScanRunInfo = pluginScan.runInfo || null; - reviewPluginExecution = this.mergePluginExecutionState( - reviewPluginExecution, - pluginScan.pluginExecution - ); - } else { - const dvdTitleScanConfig = await settingsService.buildHandBrakeScanConfigForInput(dvdScanInputPath, { - mediaProfile, - settingsMap: settings - }); - logger.info('mediainfo:review:dvd-title-scan:command', { - jobId, - cmd: dvdTitleScanConfig.cmd, - args: dvdTitleScanConfig.args, - dvdScanInputPath - }); - dvdTitleScanRunInfo = await this.runCommand({ - jobId, - stage: 'MEDIAINFO_CHECK', - source: 'HANDBRAKE_SCAN_DVD_TITLES', - cmd: dvdTitleScanConfig.cmd, - args: dvdTitleScanConfig.args, - collectLines: dvdTitleScanLines, - collectStderrLines: false - }); - } + const pluginScan = await this.runPluginReviewScan(reviewPlugin, job, { + jobId, + rawPath: dvdScanInputPath, + reviewMode: 'rip', + source: 'HANDBRAKE_SCAN_DVD_TITLES', + silent: !this.isPrimaryJob(jobId) + }); + dvdTitleScanLines.push(...pluginScan.scanLines); + dvdTitleScanRunInfo = pluginScan.runInfo || null; + reviewPluginExecution = this.mergePluginExecutionState( + reviewPluginExecution, + pluginScan.pluginExecution + ); dvdTitleScanJson = parseMediainfoJsonOutput(dvdTitleScanLines.join('\n')); if (dvdTitleScanJson) { const allTitles = parseHandBrakeTitleList(dvdTitleScanJson); @@ -10408,18 +10179,10 @@ class PipelineService extends EventEmitter { ); enrichedReview = { ...enrichedReview, encodeInputPath: dvdScanInputPath }; } - } else if (reviewPluginEnabled) { - const error = new Error('HandBrake DVD Titel-Scan: Ausgabe nicht lesbar (Plugin-Review aktiv, kein Fallback).'); + } else { + const error = new Error('HandBrake DVD Titel-Scan: Ausgabe nicht lesbar.'); error.runInfo = dvdTitleScanRunInfo; throw error; - } else { - logger.warn('mediainfo:review:dvd-title-scan:parse-failed', { jobId }); - await historyService.appendLog( - jobId, - 'SYSTEM', - 'HandBrake DVD Titel-Scan: Ausgabe nicht lesbar. Ohne Titel-ID fortgefahren.' - ); - enrichedReview = { ...enrichedReview, encodeInputPath: dvdScanInputPath }; } } } @@ -10947,11 +10710,6 @@ class PipelineService extends EventEmitter { }); const settings = await settingsService.getEffectiveSettingsMap(mediaProfile); const encodePlugin = await this.resolveAnalyzePlugin({ mediaProfile }, 'encode').catch(() => null); - const encodePluginEnabled = Boolean( - encodePlugin - && encodePlugin.id === mediaProfile - && typeof encodePlugin.encode === 'function' - ); let encodePluginExecution = null; const resolvedRawPath = this.resolveCurrentRawPathForSettings(settings, mediaProfile, job.raw_path); const activeRawPath = resolvedRawPath || String(job.raw_path || '').trim() || null; @@ -11292,45 +11050,26 @@ class PipelineService extends EventEmitter { } : parseHandBrakeProgress; let handbrakeInfo = null; - if (encodePluginEnabled) { - const encodeCtx = await this.buildPluginContext(encodePlugin.id, { - jobId, - inputPath, - outputPath: incompleteOutputPath, - encodePlan: effectiveEncodePlan, - runCommand: this.runCommand.bind(this), - progressParser: handBrakeProgressParser, - encodeSource: 'HANDBRAKE', - encodeStage: 'ENCODING' - }); - handbrakeInfo = await encodePlugin.encode(job, encodeCtx); - encodePluginExecution = this.mergePluginExecutionState( - encodePluginExecution, - this.sanitizePluginExecutionState(encodeCtx.getPluginExecution()) - ); - logger.info('plugin:encode:used', { - jobId, - pluginId: encodePlugin.id, - mediaProfile - }); - } else { - const handBrakeConfig = await settingsService.buildHandBrakeConfig(inputPath, incompleteOutputPath, { - trackSelection, - titleId: handBrakeTitleId, - mediaProfile, - settingsMap: settings, - userPreset: encodePlan?.userPreset || null - }); - logger.info('encoding:command', { jobId, cmd: handBrakeConfig.cmd, args: handBrakeConfig.args }); - handbrakeInfo = await this.runCommand({ - jobId, - stage: 'ENCODING', - source: 'HANDBRAKE', - cmd: handBrakeConfig.cmd, - args: handBrakeConfig.args, - parser: handBrakeProgressParser - }); - } + const encodeCtx = await this.buildPluginContext(encodePlugin.id, { + jobId, + inputPath, + outputPath: incompleteOutputPath, + encodePlan: effectiveEncodePlan, + runCommand: this.runCommand.bind(this), + progressParser: handBrakeProgressParser, + encodeSource: 'HANDBRAKE', + encodeStage: 'ENCODING' + }); + handbrakeInfo = await encodePlugin.encode(job, encodeCtx); + encodePluginExecution = this.mergePluginExecutionState( + encodePluginExecution, + this.sanitizePluginExecutionState(encodeCtx.getPluginExecution()) + ); + logger.info('plugin:encode:used', { + jobId, + pluginId: encodePlugin.id, + mediaProfile + }); const outputFinalization = finalizeOutputPathForCompletedEncode( incompleteOutputPath, preferredFinalOutputPath @@ -11650,20 +11389,6 @@ class PipelineService extends EventEmitter { try { await this.ensureMakeMKVRegistration(jobId, 'RIPPING'); - const ripConfig = await settingsService.buildMakeMKVRipConfig(rawJobDir, device, { - selectedTitleId: effectiveSelectedTitleId, - mediaProfile, - settingsMap: settings, - backupOutputBase - }); - logger.info('rip:command', { - jobId, - cmd: ripConfig.cmd, - args: ripConfig.args, - ripMode, - selectedPlaylist: effectiveSelectedPlaylistFile, - selectedTitleId: effectiveSelectedTitleId - }); if (ripMode === 'backup') { await historyService.appendLog( jobId, @@ -11702,28 +11427,16 @@ class PipelineService extends EventEmitter { } let ripCommandSucceeded = false; try { - if (ripPlugin) { - // Plugin-Pfad: VideoDiscPlugin.rip() baut eigene ripConfig + ruft ctx.extra.runCommand() - const ripCtx = await this.buildPluginContext(ripPlugin.id, { - jobId, - rawJobDir, - deviceInfo: device, - selectedTitleId: effectiveSelectedTitleId, - backupOutputBase, - runCommand: this.runCommand.bind(this) - }); - makemkvInfo = await ripPlugin.rip(job, ripCtx); - } else { - // Legacy-Pfad - makemkvInfo = await this.runCommand({ - jobId, - stage: 'RIPPING', - source: 'MAKEMKV_RIP', - cmd: ripConfig.cmd, - args: ripConfig.args, - parser: parseMakeMkvProgress - }); - } + // Plugin-Pfad: VideoDiscPlugin.rip() baut eigene ripConfig + ruft ctx.extra.runCommand() + const ripCtx = await this.buildPluginContext(ripPlugin.id, { + jobId, + rawJobDir, + deviceInfo: device, + selectedTitleId: effectiveSelectedTitleId, + backupOutputBase, + runCommand: this.runCommand.bind(this) + }); + makemkvInfo = await ripPlugin.rip(job, ripCtx); ripCommandSucceeded = true; } finally { if (devicePath && !ripCommandSucceeded) { @@ -15292,11 +15005,11 @@ class PipelineService extends EventEmitter { activeJobId, 'SYSTEM', skipRip - ? `CD-Encode aus RAW gestartet (${cdRipPlugin ? 'Plugin' : 'Legacy'}): Format=${format}, Tracks=${effectiveSelectedTrackPositions.join(',') || 'alle'}` + ? `CD-Encode aus RAW gestartet: Format=${format}, Tracks=${effectiveSelectedTrackPositions.join(',') || 'alle'}` : ( skipEncode - ? `CD-RAW-Rip gestartet (${cdRipPlugin ? 'Plugin' : 'Legacy'}): Tracks=${effectiveSelectedTrackPositions.join(',') || 'alle'}` - : `CD-Rip gestartet (${cdRipPlugin ? 'Plugin' : 'Legacy'}): Format=${format}, Tracks=${effectiveSelectedTrackPositions.join(',') || 'alle'}` + ? `CD-RAW-Rip gestartet: Tracks=${effectiveSelectedTrackPositions.join(',') || 'alle'}` + : `CD-Rip gestartet: Format=${format}, Tracks=${effectiveSelectedTrackPositions.join(',') || 'alle'}` ) ); if ( diff --git a/db/schema.sql b/db/schema.sql index c8db68e..7f0b9c1 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -478,74 +478,6 @@ INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, des VALUES ('ui_expert_mode', 'Benachrichtigungen', 'Expertenmodus', 'boolean', 1, 'Schaltet erweiterte Einstellungen in der UI ein.', 'false', '[]', '{}', 495); INSERT OR IGNORE INTO settings_values (key, value) VALUES ('ui_expert_mode', 'false'); -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture', 'Erweitert', 'Neue Plugin-Architektur (Beta)', 'boolean', 1, 'Masterschalter: Aktiviert die neue modulare Plugin-Architektur (Phase 2). Alle Medium-Schalter darunter sind nur wirksam wenn dieser aktiv ist.', 'false', '[]', '{}', 9999, NULL); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture', 'false'); - -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture_bluray', 'Erweitert', 'Blu-ray Plugin', 'boolean', 1, 'Aktiviert das Blu-ray Plugin. Deaktiviert = Legacy-Pfad für alle Blu-ray-Phasen.', 'true', '[]', '{}', 10000, 'use_plugin_architecture'); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_bluray', 'true'); - -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture_bluray_analyze', 'Erweitert', 'Analyse (Blu-ray)', 'boolean', 1, 'Disc-Erkennung und OMDB-Suche laufen über das Plugin.', 'true', '[]', '{}', 10001, 'use_plugin_architecture_bluray'); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_bluray_analyze', 'true'); - -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture_bluray_rip', 'Erweitert', 'Rip (Blu-ray)', 'boolean', 1, 'MakeMKV-Rip läuft über das Plugin.', 'true', '[]', '{}', 10002, 'use_plugin_architecture_bluray'); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_bluray_rip', 'true'); - -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture_bluray_review', 'Erweitert', 'Review / Mediainfo-Prüfung (Blu-ray)', 'boolean', 1, 'Noch nicht implementiert — kommt in einem späteren Sprint.', 'false', '[]', '{"readonly":true}', 10003, 'use_plugin_architecture_bluray'); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_bluray_review', 'false'); - -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture_bluray_encode', 'Erweitert', 'Encoding (Blu-ray)', 'boolean', 1, 'Noch nicht implementiert — kommt in einem späteren Sprint.', 'false', '[]', '{"readonly":true}', 10004, 'use_plugin_architecture_bluray'); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_bluray_encode', 'false'); - -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture_dvd', 'Erweitert', 'DVD Plugin', 'boolean', 1, 'Aktiviert das DVD Plugin. Deaktiviert = Legacy-Pfad für alle DVD-Phasen.', 'true', '[]', '{}', 10010, 'use_plugin_architecture'); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_dvd', 'true'); - -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture_dvd_analyze', 'Erweitert', 'Analyse (DVD)', 'boolean', 1, 'Disc-Erkennung und OMDB-Suche laufen über das Plugin.', 'true', '[]', '{}', 10011, 'use_plugin_architecture_dvd'); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_dvd_analyze', 'true'); - -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture_dvd_rip', 'Erweitert', 'Rip (DVD)', 'boolean', 1, 'MakeMKV-Rip läuft über das Plugin.', 'true', '[]', '{}', 10012, 'use_plugin_architecture_dvd'); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_dvd_rip', 'true'); - -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture_dvd_review', 'Erweitert', 'Review / Mediainfo-Prüfung (DVD)', 'boolean', 1, 'Review-/Track-Analyse läuft über das DVD-Plugin.', 'true', '[]', '{}', 10013, 'use_plugin_architecture_dvd'); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_dvd_review', 'true'); - -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture_dvd_encode', 'Erweitert', 'Encoding (DVD)', 'boolean', 1, 'Encoding läuft über das DVD-Plugin.', 'true', '[]', '{}', 10014, 'use_plugin_architecture_dvd'); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_dvd_encode', 'true'); - -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture_cd', 'Erweitert', 'Audio-CD Plugin', 'boolean', 1, 'Aktiviert das Audio-CD Plugin. Deaktiviert = Legacy-Pfad für alle CD-Phasen.', 'true', '[]', '{}', 10020, 'use_plugin_architecture'); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_cd', 'true'); - -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture_cd_analyze', 'Erweitert', 'Analyse (Audio-CD)', 'boolean', 1, 'TOC-Auslesung läuft über das Plugin.', 'true', '[]', '{}', 10021, 'use_plugin_architecture_cd'); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_cd_analyze', 'true'); - -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture_cd_rip', 'Erweitert', 'Rip (Audio-CD)', 'boolean', 1, 'CD-Rip/RAW-Rip/Encode-aus-RAW läuft über das Plugin.', 'true', '[]', '{}', 10022, 'use_plugin_architecture_cd'); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_cd_rip', 'true'); - -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture_audiobook', 'Erweitert', 'Audiobook Plugin', 'boolean', 1, 'Aktiviert das Audiobook Plugin. Deaktiviert = Legacy-Pfad für alle Audiobook-Phasen.', 'true', '[]', '{}', 10030, 'use_plugin_architecture'); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_audiobook', 'true'); - -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture_audiobook_analyze', 'Erweitert', 'Analyse (Audiobook)', 'boolean', 1, 'ffprobe-Metadatenanalyse läuft über das Plugin.', 'true', '[]', '{}', 10031, 'use_plugin_architecture_audiobook'); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_audiobook_analyze', 'true'); - -INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index, depends_on) -VALUES ('use_plugin_architecture_audiobook_encode', 'Erweitert', 'Encoding (Audiobook)', 'boolean', 1, 'Noch nicht implementiert — Encoding läuft noch über Legacy.', 'false', '[]', '{"readonly":true}', 10032, 'use_plugin_architecture_audiobook'); -INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_audiobook_encode', 'false'); - INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index) VALUES ('pushover_enabled', 'Benachrichtigungen', 'PushOver aktiviert', 'boolean', 1, 'Master-Schalter für PushOver Versand.', 'false', '[]', '{}', 500); INSERT OR IGNORE INTO settings_values (key, value) VALUES ('pushover_enabled', 'false'); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7bc6149..e2c2f5b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-frontend", - "version": "0.12.0-6", + "version": "0.12.0-7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-frontend", - "version": "0.12.0-6", + "version": "0.12.0-7", "dependencies": { "primeicons": "^7.0.0", "primereact": "^10.9.2", diff --git a/frontend/package.json b/frontend/package.json index 0d9cd08..b55f379 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-frontend", - "version": "0.12.0-6", + "version": "0.12.0-7", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/components/JobDetailDialog.jsx b/frontend/src/components/JobDetailDialog.jsx index 0b8b288..0cb735b 100644 --- a/frontend/src/components/JobDetailDialog.jsx +++ b/frontend/src/components/JobDetailDialog.jsx @@ -5,7 +5,6 @@ import { Tag } from 'primereact/tag'; import blurayIndicatorIcon from '../assets/media-bluray.svg'; import discIndicatorIcon from '../assets/media-disc.svg'; import otherIndicatorIcon from '../assets/media-other.svg'; -import { resolveJobPluginExecution } from '../utils/pluginExecution'; import { getProcessStatusLabel, getStatusLabel } from '../utils/statusPresentation'; const CD_FORMAT_LABELS = { @@ -725,9 +724,6 @@ export default function JobDetailDialog({ const mediaTypeAlt = mediaTypeLabel; const statusMeta = statusBadgeMeta(job?.status, queueLocked); const omdbInfo = job?.omdbInfo && typeof job.omdbInfo === 'object' ? job.omdbInfo : {}; - const pluginExecution = resolveJobPluginExecution(job, null, { - currentStatus: job?.status - }); const configuredSelection = buildConfiguredScriptAndChainSelection(job); const hasConfiguredSelection = configuredSelection.preScriptIds.length > 0 || configuredSelection.postScriptIds.length > 0 @@ -928,12 +924,6 @@ export default function JobDetailDialog({