0.12.0-4 DVD Plugin

This commit is contained in:
2026-03-21 19:12:58 +00:00
parent e99cdf1895
commit c0350644b9
11 changed files with 446 additions and 192 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "ripster-backend", "name": "ripster-backend",
"version": "0.12.0-3", "version": "0.12.0-4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ripster-backend", "name": "ripster-backend",
"version": "0.12.0-3", "version": "0.12.0-4",
"dependencies": { "dependencies": {
"archiver": "^7.0.1", "archiver": "^7.0.1",
"cors": "^2.8.5", "cors": "^2.8.5",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ripster-backend", "name": "ripster-backend",
"version": "0.12.0-3", "version": "0.12.0-4",
"private": true, "private": true,
"type": "commonjs", "type": "commonjs",
"scripts": { "scripts": {
+6 -6
View File
@@ -1094,20 +1094,20 @@ async function migrateSettingsSchemaMetadata(db) {
{ {
key: 'use_plugin_architecture_dvd_review', key: 'use_plugin_architecture_dvd_review',
label: 'Review / Mediainfo-Prüfung (DVD)', label: 'Review / Mediainfo-Prüfung (DVD)',
description: 'Noch nicht implementiert — kommt in einem späteren Sprint.', description: 'Review-/Track-Analyse läuft über das DVD-Plugin.',
defaultValue: 'false', defaultValue: 'true',
orderIndex: 10013, orderIndex: 10013,
dependsOn: 'use_plugin_architecture_dvd', dependsOn: 'use_plugin_architecture_dvd',
validationJson: '{"readonly":true}' validationJson: '{}'
}, },
{ {
key: 'use_plugin_architecture_dvd_encode', key: 'use_plugin_architecture_dvd_encode',
label: 'Encoding (DVD)', label: 'Encoding (DVD)',
description: 'Noch nicht implementiert — kommt in einem späteren Sprint.', description: 'Encoding läuft über das DVD-Plugin.',
defaultValue: 'false', defaultValue: 'true',
orderIndex: 10014, orderIndex: 10014,
dependsOn: 'use_plugin_architecture_dvd', dependsOn: 'use_plugin_architecture_dvd',
validationJson: '{"readonly":true}' validationJson: '{}'
}, },
// ── Audio-CD ───────────────────────────────────────────────────────────── // ── Audio-CD ─────────────────────────────────────────────────────────────
{ {
+53 -16
View File
@@ -147,12 +147,30 @@ class VideoDiscPlugin extends SourcePlugin {
}); });
const scanLines = []; const scanLines = [];
const runInfo = await _runCommandCaptured({ const runCommandFn = typeof ctx.extra?.runCommand === 'function' ? ctx.extra.runCommand : null;
cmd: scanConfig.cmd, const reviewStage = String(ctx.extra?.reviewStage || 'MEDIAINFO_CHECK').trim().toUpperCase() || 'MEDIAINFO_CHECK';
args: scanConfig.args, const reviewSource = String(ctx.extra?.reviewSource || 'HANDBRAKE_SCAN').trim().toUpperCase() || 'HANDBRAKE_SCAN';
onStdoutLine: (line) => scanLines.push(line), const reviewSilent = Boolean(ctx.extra?.reviewSilent);
context: { jobId: job?.id, source: `${this.id}Plugin.review` } let runInfo;
}); if (runCommandFn) {
runInfo = await runCommandFn({
jobId: job?.id,
stage: reviewStage,
source: reviewSource,
cmd: scanConfig.cmd,
args: scanConfig.args,
collectLines: scanLines,
collectStderrLines: false,
silent: reviewSilent
});
} else {
runInfo = await _runCommandCaptured({
cmd: scanConfig.cmd,
args: scanConfig.args,
onStdoutLine: (line) => scanLines.push(line),
context: { jobId: job?.id, source: `${this.id}Plugin.review` }
});
}
ctx.logger.info(`${this.id}:review:scan-done`, { ctx.logger.info(`${this.id}:review:scan-done`, {
jobId: job?.id, jobId: job?.id,
@@ -315,16 +333,35 @@ class VideoDiscPlugin extends SourcePlugin {
ctx.emitProgress(0, `${this.name}: Encoding läuft …`); ctx.emitProgress(0, `${this.name}: Encoding läuft …`);
const runInfo = await _spawnAndWait({ const runCommandFn = typeof ctx.extra?.runCommand === 'function' ? ctx.extra.runCommand : null;
cmd: handBrakeConfig.cmd, const encodeSource = String(ctx.extra?.encodeSource || 'HANDBRAKE').trim().toUpperCase() || 'HANDBRAKE';
args: handBrakeConfig.args, const encodeStage = String(ctx.extra?.encodeStage || 'ENCODING').trim().toUpperCase() || 'ENCODING';
jobId: job?.id, const encodeParser = typeof ctx.extra?.progressParser === 'function'
progressParser: parseHandBrakeProgress, ? ctx.extra.progressParser
onProgress: (pct, statusText) => ctx.emitProgress(Math.round(pct), statusText || `${this.name}: Encoding ${pct}%`), : parseHandBrakeProgress;
isCancelled,
onProcessHandle, let runInfo;
context: { jobId: job?.id, source: `${this.id}Plugin.encode`, stage: 'ENCODING' } if (runCommandFn) {
}); runInfo = await runCommandFn({
jobId: job?.id,
stage: encodeStage,
source: encodeSource,
cmd: handBrakeConfig.cmd,
args: handBrakeConfig.args,
parser: encodeParser
});
} else {
runInfo = await _spawnAndWait({
cmd: handBrakeConfig.cmd,
args: handBrakeConfig.args,
jobId: job?.id,
progressParser: parseHandBrakeProgress,
onProgress: (pct, statusText) => ctx.emitProgress(Math.round(pct), statusText || `${this.name}: Encoding ${pct}%`),
isCancelled,
onProcessHandle,
context: { jobId: job?.id, source: `${this.id}Plugin.encode`, stage: 'ENCODING' }
});
}
ctx.logger.info(`${this.id}:encode:done`, { ctx.logger.info(`${this.id}:encode:done`, {
jobId: job?.id, jobId: job?.id,
+372 -156
View File
@@ -4296,6 +4296,42 @@ class PipelineService extends EventEmitter {
}); });
} }
async runPluginReviewScan(plugin, job, {
jobId,
rawPath = null,
deviceInfo = null,
reviewMode = 'rip',
source = 'HANDBRAKE_SCAN',
silent = false
} = {}) {
if (!plugin || typeof plugin.review !== 'function') {
const error = new Error('Plugin-Review nicht verfügbar.');
error.statusCode = 500;
throw error;
}
const pluginCtx = await this.buildPluginContext(plugin.id, {
jobId,
rawJobDir: rawPath || null,
deviceInfo: deviceInfo || null,
reviewMode: String(reviewMode || 'rip').trim().toLowerCase() || 'rip',
runCommand: this.runCommand.bind(this),
reviewSource: String(source || 'HANDBRAKE_SCAN').trim().toUpperCase() || 'HANDBRAKE_SCAN',
reviewStage: 'MEDIAINFO_CHECK',
reviewSilent: Boolean(silent)
});
const pluginResult = await plugin.review(job, pluginCtx);
return {
scanLines: Array.isArray(pluginResult?.scanLines) ? pluginResult.scanLines : [],
runInfo: pluginResult?.runInfo && typeof pluginResult.runInfo === 'object'
? pluginResult.runInfo
: null,
sourceArg: String(pluginResult?.sourceArg || '').trim() || null,
pluginExecution: this.sanitizePluginExecutionState(pluginCtx.getPluginExecution())
};
}
isRipSuccessful(job = null) { isRipSuccessful(job = null) {
if (Number(job?.rip_successful || 0) === 1) { if (Number(job?.rip_successful || 0) === 1) {
return true; return true;
@@ -7247,6 +7283,13 @@ class PipelineService extends EventEmitter {
makemkvInfo: mkInfo makemkvInfo: mkInfo
}); });
const settings = await settingsService.getEffectiveSettingsMap(mediaProfile); 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 analyzeContext = mkInfo?.analyzeContext || {};
const playlistAnalysis = analyzeContext.playlistAnalysis || this.snapshot.context?.playlistAnalysis || null; const playlistAnalysis = analyzeContext.playlistAnalysis || this.snapshot.context?.playlistAnalysis || null;
const selectedPlaylistId = normalizePlaylistId( const selectedPlaylistId = normalizePlaylistId(
@@ -7295,24 +7338,44 @@ class PipelineService extends EventEmitter {
}); });
const lines = []; const lines = [];
const scanConfig = await settingsService.buildHandBrakeScanConfig(deviceInfo, { mediaProfile }); let runInfo = null;
logger.info('disc-track-review:command', { let sourceArg = null;
jobId, if (reviewPluginEnabled) {
cmd: scanConfig.cmd, const pluginScan = await this.runPluginReviewScan(reviewPlugin, job, {
args: scanConfig.args, jobId,
sourceArg: scanConfig.sourceArg, deviceInfo,
selectedTitleId: selectedMakemkvTitleId 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
});
const runInfo = await this.runCommand({ runInfo = await this.runCommand({
jobId, jobId,
stage: 'MEDIAINFO_CHECK', stage: 'MEDIAINFO_CHECK',
source: 'HANDBRAKE_SCAN', source: 'HANDBRAKE_SCAN',
cmd: scanConfig.cmd, cmd: scanConfig.cmd,
args: scanConfig.args, args: scanConfig.args,
collectLines: lines, collectLines: lines,
collectStderrLines: false collectStderrLines: false
}); });
sourceArg = scanConfig.sourceArg;
}
const parsed = parseMediainfoJsonOutput(lines.join('\n')); const parsed = parseMediainfoJsonOutput(lines.join('\n'));
if (!parsed) { if (!parsed) {
@@ -7328,7 +7391,7 @@ class PipelineService extends EventEmitter {
selectedPlaylistId, selectedPlaylistId,
selectedMakemkvTitleId, selectedMakemkvTitleId,
mediaProfile, mediaProfile,
sourceArg: scanConfig.sourceArg sourceArg
}); });
if (!Array.isArray(review.titles) || review.titles.length === 0) { if (!Array.isArray(review.titles) || review.titles.length === 0) {
@@ -7337,15 +7400,16 @@ class PipelineService extends EventEmitter {
throw error; throw error;
} }
const persistedMediainfoInfo = this.withPluginExecutionMeta({
generatedAt: nowIso(),
source: 'disc_scan',
runInfo
}, reviewPluginExecution);
await historyService.updateJob(jobId, { await historyService.updateJob(jobId, {
status: 'READY_TO_ENCODE', status: 'READY_TO_ENCODE',
last_state: 'READY_TO_ENCODE', last_state: 'READY_TO_ENCODE',
error_message: null, error_message: null,
mediainfo_info_json: JSON.stringify({ mediainfo_info_json: JSON.stringify(persistedMediainfoInfo),
generatedAt: nowIso(),
source: 'disc_scan',
runInfo
}),
encode_plan_json: JSON.stringify(review), encode_plan_json: JSON.stringify(review),
encode_input_path: review.encodeInputPath || null, encode_input_path: review.encodeInputPath || null,
encode_review_confirmed: 0 encode_review_confirmed: 0
@@ -7373,7 +7437,8 @@ class PipelineService extends EventEmitter {
mode: 'pre_rip', mode: 'pre_rip',
mediaProfile, mediaProfile,
mediaInfoReview: review, mediaInfoReview: review,
selectedMetadata selectedMetadata,
...(reviewPluginExecution ? { pluginExecution: this.mergePluginExecutionState(mkInfo?.pluginExecution, reviewPluginExecution) } : {})
} }
}); });
@@ -7467,6 +7532,13 @@ class PipelineService extends EventEmitter {
makemkvInfo: mkInfo makemkvInfo: mkInfo
}); });
const settings = await settingsService.getEffectiveSettingsMap(mediaProfile); 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 analyzeContext = mkInfo?.analyzeContext || {};
let playlistAnalysis = forceFreshAnalyze let playlistAnalysis = forceFreshAnalyze
? null ? null
@@ -7627,24 +7699,39 @@ class PipelineService extends EventEmitter {
); );
try { try {
const resolveScanLines = []; const resolveScanLines = [];
const resolveScanConfig = await settingsService.buildHandBrakeScanConfigForInput(rawPath, { mediaProfile }); if (reviewPluginEnabled) {
logger.info('backup-track-review:handbrake-predecision-command', { const pluginScan = await this.runPluginReviewScan(reviewPlugin, job, {
jobId, jobId,
cmd: resolveScanConfig.cmd, rawPath,
args: resolveScanConfig.args, reviewMode: 'rip',
sourceArg: resolveScanConfig.sourceArg, source: 'HANDBRAKE_SCAN_PLAYLIST_MAP',
candidatePlaylists: playlistCandidates.map((item) => item.playlistFile || item.playlistId) 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({ await this.runCommand({
jobId, jobId,
stage: 'MEDIAINFO_CHECK', stage: 'MEDIAINFO_CHECK',
source: 'HANDBRAKE_SCAN_PLAYLIST_MAP', source: 'HANDBRAKE_SCAN_PLAYLIST_MAP',
cmd: resolveScanConfig.cmd, cmd: resolveScanConfig.cmd,
args: resolveScanConfig.args, args: resolveScanConfig.args,
collectLines: resolveScanLines, collectLines: resolveScanLines,
collectStderrLines: false collectStderrLines: false
}); });
}
const resolveScanJson = parseMediainfoJsonOutput(resolveScanLines.join('\n')); const resolveScanJson = parseMediainfoJsonOutput(resolveScanLines.join('\n'));
if (resolveScanJson) { if (resolveScanJson) {
@@ -7666,6 +7753,11 @@ class PipelineService extends EventEmitter {
); );
} }
} else { } 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( await historyService.appendLog(
jobId, jobId,
'SYSTEM', 'SYSTEM',
@@ -7682,6 +7774,9 @@ class PipelineService extends EventEmitter {
'SYSTEM', 'SYSTEM',
`HandBrake Voranalyse für Playlist-Auswahl fehlgeschlagen: ${error.message}` `HandBrake Voranalyse für Playlist-Auswahl fehlgeschlagen: ${error.message}`
); );
if (reviewPluginEnabled) {
throw error;
}
} }
} else { } else {
playlistAnalysis = enrichPlaylistAnalysisWithHandBrakeCache(playlistAnalysis, handBrakePlaylistScan); playlistAnalysis = enrichPlaylistAnalysisWithHandBrakeCache(playlistAnalysis, handBrakePlaylistScan);
@@ -7694,7 +7789,7 @@ class PipelineService extends EventEmitter {
} }
} }
const updatedMakemkvInfo = { const updatedMakemkvInfoBase = {
...mkInfo, ...mkInfo,
analyzeContext: { analyzeContext: {
...(mkInfo?.analyzeContext || {}), ...(mkInfo?.analyzeContext || {}),
@@ -7720,6 +7815,10 @@ class PipelineService extends EventEmitter {
error: null error: null
} }
}; };
const buildUpdatedMakemkvInfo = () => this.withPluginExecutionMeta(updatedMakemkvInfoBase, reviewPluginExecution);
const getMergedReviewPluginExecution = () => this.sanitizePluginExecutionState(
buildUpdatedMakemkvInfo()?.pluginExecution
);
if (playlistDecisionRequired && !selectedPlaylistId) { if (playlistDecisionRequired && !selectedPlaylistId) {
const evaluated = Array.isArray(playlistAnalysis?.evaluatedCandidates) const evaluated = Array.isArray(playlistAnalysis?.evaluatedCandidates)
@@ -7727,17 +7826,18 @@ class PipelineService extends EventEmitter {
: []; : [];
const recommendationFile = toPlaylistFile(playlistAnalysis?.recommendation?.playlistId); const recommendationFile = toPlaylistFile(playlistAnalysis?.recommendation?.playlistId);
const decisionContext = describePlaylistManualDecision(playlistAnalysis); const decisionContext = describePlaylistManualDecision(playlistAnalysis);
const waitingMediainfoInfo = this.withPluginExecutionMeta({
generatedAt: nowIso(),
source: 'makemkv_backup_robot',
runInfo: makeMkvAnalyzeRunInfo
}, reviewPluginExecution);
await historyService.updateJob(jobId, { await historyService.updateJob(jobId, {
status: 'WAITING_FOR_USER_DECISION', status: 'WAITING_FOR_USER_DECISION',
last_state: 'WAITING_FOR_USER_DECISION', last_state: 'WAITING_FOR_USER_DECISION',
error_message: null, error_message: null,
makemkv_info_json: JSON.stringify(updatedMakemkvInfo), makemkv_info_json: JSON.stringify(buildUpdatedMakemkvInfo()),
mediainfo_info_json: JSON.stringify({ mediainfo_info_json: JSON.stringify(waitingMediainfoInfo),
generatedAt: nowIso(),
source: 'makemkv_backup_robot',
runInfo: makeMkvAnalyzeRunInfo
}),
encode_plan_json: null, encode_plan_json: null,
encode_input_path: null, encode_input_path: null,
encode_review_confirmed: 0 encode_review_confirmed: 0
@@ -7764,6 +7864,7 @@ class PipelineService extends EventEmitter {
`Status=awaiting_playlist_selection${recommendationFile ? ` | Empfehlung=${recommendationFile}` : ''}` `Status=awaiting_playlist_selection${recommendationFile ? ` | Empfehlung=${recommendationFile}` : ''}`
); );
const mergedPluginExecutionForWaiting = getMergedReviewPluginExecution();
await this.setState('WAITING_FOR_USER_DECISION', { await this.setState('WAITING_FOR_USER_DECISION', {
activeJobId: jobId, activeJobId: jobId,
progress: 0, progress: 0,
@@ -7787,7 +7888,8 @@ class PipelineService extends EventEmitter {
selectedTitleId: null, selectedTitleId: null,
waitingForManualPlaylistSelection: true, waitingForManualPlaylistSelection: true,
manualDecisionState: 'awaiting_playlist_selection', manualDecisionState: 'awaiting_playlist_selection',
mediaInfoReview: null mediaInfoReview: null,
...(mergedPluginExecutionForWaiting ? { pluginExecution: mergedPluginExecutionForWaiting } : {})
} }
}); });
@@ -7849,9 +7951,9 @@ class PipelineService extends EventEmitter {
throw error; throw error;
} }
if (updatedMakemkvInfo && updatedMakemkvInfo.analyzeContext) { if (updatedMakemkvInfoBase && updatedMakemkvInfoBase.analyzeContext) {
updatedMakemkvInfo.analyzeContext.selectedTitleId = selectedTitleForReview; updatedMakemkvInfoBase.analyzeContext.selectedTitleId = selectedTitleForReview;
updatedMakemkvInfo.analyzeContext.selectedPlaylist = resolvedPlaylistId; updatedMakemkvInfoBase.analyzeContext.selectedPlaylist = resolvedPlaylistId;
} }
const cachedHandBrakePlaylistEntry = getCachedHandBrakePlaylistEntry(handBrakePlaylistScan, resolvedPlaylistId); const cachedHandBrakePlaylistEntry = getCachedHandBrakePlaylistEntry(handBrakePlaylistScan, resolvedPlaylistId);
@@ -7922,26 +8024,42 @@ class PipelineService extends EventEmitter {
} else { } else {
try { try {
const resolveScanLines = []; const resolveScanLines = [];
const resolveScanConfig = await settingsService.buildHandBrakeScanConfigForInput(rawPath, { mediaProfile }); if (reviewPluginEnabled) {
logger.info('backup-track-review:handbrake-resolve-command', { const pluginScan = await this.runPluginReviewScan(reviewPlugin, job, {
jobId, jobId,
cmd: resolveScanConfig.cmd, rawPath,
args: resolveScanConfig.args, reviewMode: 'rip',
sourceArg: resolveScanConfig.sourceArg, source: 'HANDBRAKE_SCAN_PLAYLIST_MAP',
selectedPlaylistId: resolvedPlaylistId, silent: !this.isPrimaryJob(jobId)
selectedMakemkvTitleId: selectedTitleForReview });
}); 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({ handBrakeResolveRunInfo = await this.runCommand({
jobId, jobId,
stage: 'MEDIAINFO_CHECK', stage: 'MEDIAINFO_CHECK',
source: 'HANDBRAKE_SCAN_PLAYLIST_MAP', source: 'HANDBRAKE_SCAN_PLAYLIST_MAP',
cmd: resolveScanConfig.cmd, cmd: resolveScanConfig.cmd,
args: resolveScanConfig.args, args: resolveScanConfig.args,
collectLines: resolveScanLines, collectLines: resolveScanLines,
collectStderrLines: false, collectStderrLines: false,
silent: !this.isPrimaryJob(jobId) silent: !this.isPrimaryJob(jobId)
}); });
}
const resolveScanJson = parseMediainfoJsonOutput(resolveScanLines.join('\n')); const resolveScanJson = parseMediainfoJsonOutput(resolveScanLines.join('\n'));
if (!resolveScanJson) { if (!resolveScanJson) {
@@ -8029,8 +8147,8 @@ class PipelineService extends EventEmitter {
} }
} }
if (updatedMakemkvInfo && updatedMakemkvInfo.analyzeContext) { if (updatedMakemkvInfoBase && updatedMakemkvInfoBase.analyzeContext) {
updatedMakemkvInfo.analyzeContext.handBrakePlaylistScan = handBrakePlaylistScan || null; updatedMakemkvInfoBase.analyzeContext.handBrakePlaylistScan = handBrakePlaylistScan || null;
} }
playlistCandidates = buildPlaylistCandidates(playlistAnalysis); playlistCandidates = buildPlaylistCandidates(playlistAnalysis);
@@ -8168,20 +8286,22 @@ class PipelineService extends EventEmitter {
throw error; throw error;
} }
const persistedMediainfoInfo = this.withPluginExecutionMeta({
generatedAt: nowIso(),
source: 'raw_backup_handbrake_playlist_scan',
makemkvAnalyzeRunInfo: makeMkvAnalyzeRunInfo,
makemkvTitleAnalyzeRunInfo: null,
handbrakePlaylistResolveRunInfo: handBrakeResolveRunInfo,
handbrakeTitleRunInfo: handBrakeTitleRunInfo,
handbrakeTitleId: resolvedHandBrakeTitleId || null
}, reviewPluginExecution);
await historyService.updateJob(jobId, { await historyService.updateJob(jobId, {
status: 'READY_TO_ENCODE', status: 'READY_TO_ENCODE',
last_state: 'READY_TO_ENCODE', last_state: 'READY_TO_ENCODE',
error_message: null, error_message: null,
makemkv_info_json: JSON.stringify(updatedMakemkvInfo), makemkv_info_json: JSON.stringify(buildUpdatedMakemkvInfo()),
mediainfo_info_json: JSON.stringify({ mediainfo_info_json: JSON.stringify(persistedMediainfoInfo),
generatedAt: nowIso(),
source: 'raw_backup_handbrake_playlist_scan',
makemkvAnalyzeRunInfo: makeMkvAnalyzeRunInfo,
makemkvTitleAnalyzeRunInfo: null,
handbrakePlaylistResolveRunInfo: handBrakeResolveRunInfo,
handbrakeTitleRunInfo: handBrakeTitleRunInfo,
handbrakeTitleId: resolvedHandBrakeTitleId || null
}),
encode_plan_json: JSON.stringify(review), encode_plan_json: JSON.stringify(review),
encode_input_path: review.encodeInputPath || null, encode_input_path: review.encodeInputPath || null,
encode_review_confirmed: 0 encode_review_confirmed: 0
@@ -8222,6 +8342,7 @@ class PipelineService extends EventEmitter {
const hasEncodableTitle = Boolean(review.encodeInputPath && review.encodeInputTitleId); const hasEncodableTitle = Boolean(review.encodeInputPath && review.encodeInputTitleId);
if (this.isPrimaryJob(jobId)) { if (this.isPrimaryJob(jobId)) {
const mergedPluginExecutionForReady = getMergedReviewPluginExecution();
await this.setState('READY_TO_ENCODE', { await this.setState('READY_TO_ENCODE', {
activeJobId: jobId, activeJobId: jobId,
progress: 0, progress: 0,
@@ -8247,7 +8368,8 @@ class PipelineService extends EventEmitter {
playlistDecisionRequired, playlistDecisionRequired,
playlistCandidates, playlistCandidates,
selectedPlaylist: resolvedPlaylistId || null, selectedPlaylist: resolvedPlaylistId || null,
selectedTitleId: selectedTitleForReview selectedTitleId: selectedTitleForReview,
...(mergedPluginExecutionForReady ? { pluginExecution: mergedPluginExecutionForReady } : {})
} }
}); });
} }
@@ -9744,6 +9866,13 @@ class PipelineService extends EventEmitter {
rawPath rawPath
}); });
const settings = await settingsService.getEffectiveSettingsMap(mediaProfile); 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 analyzeContext = mkInfo?.analyzeContext || {};
const selectedPlaylistId = normalizePlaylistId( const selectedPlaylistId = normalizePlaylistId(
analyzeContext.selectedPlaylist analyzeContext.selectedPlaylist
@@ -9875,26 +10004,43 @@ class PipelineService extends EventEmitter {
await this.updateProgress('MEDIAINFO_CHECK', 10, null, 'HandBrake DVD-Scan läuft', jobId); await this.updateProgress('MEDIAINFO_CHECK', 10, null, 'HandBrake DVD-Scan läuft', jobId);
const dvdScanLines = []; const dvdScanLines = [];
const dvdScanConfig = await settingsService.buildHandBrakeScanConfigForInput(dvdHandBrakeScanInputPath, { let dvdScanRunInfo = null;
mediaProfile, if (reviewPluginEnabled) {
settingsMap: settings const pluginScan = await this.runPluginReviewScan(reviewPlugin, job, {
}); jobId,
logger.info('mediainfo:review:dvd-handbrake-scan:command', { rawPath: dvdHandBrakeScanInputPath,
jobId, reviewMode: 'rip',
dvdHandBrakeScanInputPath, source: 'HANDBRAKE_SCAN_DVD',
cmd: dvdScanConfig.cmd, silent: !this.isPrimaryJob(jobId)
args: dvdScanConfig.args });
}); 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
});
const dvdScanRunInfo = await this.runCommand({ dvdScanRunInfo = await this.runCommand({
jobId, jobId,
stage: 'MEDIAINFO_CHECK', stage: 'MEDIAINFO_CHECK',
source: 'HANDBRAKE_SCAN_DVD', source: 'HANDBRAKE_SCAN_DVD',
cmd: dvdScanConfig.cmd, cmd: dvdScanConfig.cmd,
args: dvdScanConfig.args, args: dvdScanConfig.args,
collectLines: dvdScanLines, collectLines: dvdScanLines,
collectStderrLines: false collectStderrLines: false
}); });
}
dvdHandBrakeScanJson = parseMediainfoJsonOutput(dvdScanLines.join('\n')); dvdHandBrakeScanJson = parseMediainfoJsonOutput(dvdScanLines.join('\n'));
if (dvdHandBrakeScanJson) { if (dvdHandBrakeScanJson) {
@@ -9911,6 +10057,10 @@ class PipelineService extends EventEmitter {
'SYSTEM', 'SYSTEM',
`HandBrake DVD-Scan: Audio=${audioCount}, Subtitle=${subtitleCount}, Dauer=${formatDurationClock(titleInfo.durationSeconds)}` `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).');
error.runInfo = dvdScanRunInfo;
throw error;
} else { } else {
await historyService.appendLog( await historyService.appendLog(
jobId, jobId,
@@ -9919,6 +10069,10 @@ class PipelineService extends EventEmitter {
); );
dvdHandBrakeScanJson = null; dvdHandBrakeScanJson = null;
} }
} else if (reviewPluginEnabled) {
const error = new Error('HandBrake DVD-Scan: Ausgabe nicht lesbar (Plugin-Review aktiv, kein Fallback).');
error.runInfo = dvdScanRunInfo;
throw error;
} else { } else {
await historyService.appendLog( await historyService.appendLog(
jobId, jobId,
@@ -9946,7 +10100,8 @@ class PipelineService extends EventEmitter {
mediaProfile, mediaProfile,
sourceJobId: options.sourceJobId || null, sourceJobId: options.sourceJobId || null,
mediaInfoReview: partialReview, mediaInfoReview: partialReview,
selectedMetadata selectedMetadata,
...(reviewPluginExecution ? { pluginExecution: this.mergePluginExecutionState(mkInfo?.pluginExecution, reviewPluginExecution) } : {})
} }
}); });
} }
@@ -10106,25 +10261,42 @@ class PipelineService extends EventEmitter {
); );
await this.updateProgress('MEDIAINFO_CHECK', 100, null, 'HandBrake DVD Titel-Scan läuft', jobId); await this.updateProgress('MEDIAINFO_CHECK', 100, null, 'HandBrake DVD Titel-Scan läuft', jobId);
const dvdTitleScanLines = []; const dvdTitleScanLines = [];
const dvdTitleScanConfig = await settingsService.buildHandBrakeScanConfigForInput(dvdScanInputPath, { let dvdTitleScanRunInfo = null;
mediaProfile, if (reviewPluginEnabled) {
settingsMap: settings const pluginScan = await this.runPluginReviewScan(reviewPlugin, job, {
}); jobId,
logger.info('mediainfo:review:dvd-title-scan:command', { rawPath: dvdScanInputPath,
jobId, reviewMode: 'rip',
cmd: dvdTitleScanConfig.cmd, source: 'HANDBRAKE_SCAN_DVD_TITLES',
args: dvdTitleScanConfig.args, silent: !this.isPrimaryJob(jobId)
dvdScanInputPath });
}); dvdTitleScanLines.push(...pluginScan.scanLines);
await this.runCommand({ dvdTitleScanRunInfo = pluginScan.runInfo || null;
jobId, reviewPluginExecution = this.mergePluginExecutionState(
stage: 'MEDIAINFO_CHECK', reviewPluginExecution,
source: 'HANDBRAKE_SCAN_DVD_TITLES', pluginScan.pluginExecution
cmd: dvdTitleScanConfig.cmd, );
args: dvdTitleScanConfig.args, } else {
collectLines: dvdTitleScanLines, const dvdTitleScanConfig = await settingsService.buildHandBrakeScanConfigForInput(dvdScanInputPath, {
collectStderrLines: false 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
});
}
dvdTitleScanJson = parseMediainfoJsonOutput(dvdTitleScanLines.join('\n')); dvdTitleScanJson = parseMediainfoJsonOutput(dvdTitleScanLines.join('\n'));
if (dvdTitleScanJson) { if (dvdTitleScanJson) {
const allTitles = parseHandBrakeTitleList(dvdTitleScanJson); const allTitles = parseHandBrakeTitleList(dvdTitleScanJson);
@@ -10163,14 +10335,15 @@ class PipelineService extends EventEmitter {
handBrakeDvdInputPath: dvdScanInputPath, handBrakeDvdInputPath: dvdScanInputPath,
encodeInputPath: null encodeInputPath: null
}; };
const waitingMediainfoInfo = this.withPluginExecutionMeta({
generatedAt: nowIso(),
files: mediaInfoRuns
}, reviewPluginExecution);
await historyService.updateJob(jobId, { await historyService.updateJob(jobId, {
status: 'WAITING_FOR_USER_DECISION', status: 'WAITING_FOR_USER_DECISION',
last_state: 'WAITING_FOR_USER_DECISION', last_state: 'WAITING_FOR_USER_DECISION',
error_message: null, error_message: null,
mediainfo_info_json: JSON.stringify({ mediainfo_info_json: JSON.stringify(waitingMediainfoInfo),
generatedAt: nowIso(),
files: mediaInfoRuns
}),
encode_plan_json: JSON.stringify(enrichedReview), encode_plan_json: JSON.stringify(enrichedReview),
encode_input_path: null, encode_input_path: null,
encode_review_confirmed: 0 encode_review_confirmed: 0
@@ -10197,7 +10370,8 @@ class PipelineService extends EventEmitter {
mediaProfile, mediaProfile,
sourceJobId: options.sourceJobId || null, sourceJobId: options.sourceJobId || null,
mediaInfoReview: enrichedReview, mediaInfoReview: enrichedReview,
selectedMetadata selectedMetadata,
...(reviewPluginExecution ? { pluginExecution: this.mergePluginExecutionState(mkInfo?.pluginExecution, reviewPluginExecution) } : {})
} }
}); });
} }
@@ -10214,6 +10388,10 @@ class PipelineService extends EventEmitter {
); );
enrichedReview = { ...enrichedReview, encodeInputPath: dvdScanInputPath }; enrichedReview = { ...enrichedReview, encodeInputPath: dvdScanInputPath };
} }
} else if (reviewPluginEnabled) {
const error = new Error('HandBrake DVD Titel-Scan: Ausgabe nicht lesbar (Plugin-Review aktiv, kein Fallback).');
error.runInfo = dvdTitleScanRunInfo;
throw error;
} else { } else {
logger.warn('mediainfo:review:dvd-title-scan:parse-failed', { jobId }); logger.warn('mediainfo:review:dvd-title-scan:parse-failed', { jobId });
await historyService.appendLog( await historyService.appendLog(
@@ -10235,14 +10413,16 @@ class PipelineService extends EventEmitter {
]; ];
} }
const persistedMediainfoInfo = this.withPluginExecutionMeta({
generatedAt: nowIso(),
files: mediaInfoRuns
}, reviewPluginExecution);
await historyService.updateJob(jobId, { await historyService.updateJob(jobId, {
status: 'READY_TO_ENCODE', status: 'READY_TO_ENCODE',
last_state: 'READY_TO_ENCODE', last_state: 'READY_TO_ENCODE',
error_message: null, error_message: null,
mediainfo_info_json: JSON.stringify({ mediainfo_info_json: JSON.stringify(persistedMediainfoInfo),
generatedAt: nowIso(),
files: mediaInfoRuns
}),
encode_plan_json: JSON.stringify(enrichedReview), encode_plan_json: JSON.stringify(enrichedReview),
encode_input_path: enrichedReview.encodeInputPath || null, encode_input_path: enrichedReview.encodeInputPath || null,
encode_review_confirmed: 0 encode_review_confirmed: 0
@@ -10284,7 +10464,8 @@ class PipelineService extends EventEmitter {
mediaProfile, mediaProfile,
sourceJobId: options.sourceJobId || null, sourceJobId: options.sourceJobId || null,
mediaInfoReview: enrichedReview, mediaInfoReview: enrichedReview,
selectedMetadata selectedMetadata,
...(reviewPluginExecution ? { pluginExecution: this.mergePluginExecutionState(mkInfo?.pluginExecution, reviewPluginExecution) } : {})
} }
}); });
} }
@@ -10745,6 +10926,13 @@ class PipelineService extends EventEmitter {
rawPath: job.raw_path rawPath: job.raw_path
}); });
const settings = await settingsService.getEffectiveSettingsMap(mediaProfile); 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 resolvedRawPath = this.resolveCurrentRawPathForSettings(settings, mediaProfile, job.raw_path);
const activeRawPath = resolvedRawPath || String(job.raw_path || '').trim() || null; const activeRawPath = resolvedRawPath || String(job.raw_path || '').trim() || null;
if (activeRawPath && normalizeComparablePath(activeRawPath) !== normalizeComparablePath(job.raw_path)) { if (activeRawPath && normalizeComparablePath(activeRawPath) !== normalizeComparablePath(job.raw_path)) {
@@ -11052,13 +11240,11 @@ class PipelineService extends EventEmitter {
); );
} }
} }
const handBrakeConfig = await settingsService.buildHandBrakeConfig(inputPath, incompleteOutputPath, { const effectiveEncodePlan = {
trackSelection, ...(encodePlan && typeof encodePlan === 'object' ? encodePlan : {}),
titleId: handBrakeTitleId, trackSelection: trackSelection || null,
mediaProfile, handBrakeTitleId: handBrakeTitleId || null
settingsMap: settings, };
userPreset: encodePlan?.userPreset || null
});
if (trackSelection) { if (trackSelection) {
await historyService.appendLog( await historyService.appendLog(
jobId, jobId,
@@ -11073,7 +11259,6 @@ class PipelineService extends EventEmitter {
`HandBrake Titel-Selektion aktiv: -t ${handBrakeTitleId}` `HandBrake Titel-Selektion aktiv: -t ${handBrakeTitleId}`
); );
} }
logger.info('encoding:command', { jobId, cmd: handBrakeConfig.cmd, args: handBrakeConfig.args });
const handBrakeProgressParser = encodeScriptProgressTracker.hasScriptSteps const handBrakeProgressParser = encodeScriptProgressTracker.hasScriptSteps
? (line) => { ? (line) => {
const parsed = parseHandBrakeProgress(line); const parsed = parseHandBrakeProgress(line);
@@ -11086,14 +11271,46 @@ class PipelineService extends EventEmitter {
}; };
} }
: parseHandBrakeProgress; : parseHandBrakeProgress;
const handbrakeInfo = await this.runCommand({ let handbrakeInfo = null;
jobId, if (encodePluginEnabled) {
stage: 'ENCODING', const encodeCtx = await this.buildPluginContext(encodePlugin.id, {
source: 'HANDBRAKE', jobId,
cmd: handBrakeConfig.cmd, inputPath,
args: handBrakeConfig.args, outputPath: incompleteOutputPath,
parser: handBrakeProgressParser 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 outputFinalization = finalizeOutputPathForCompletedEncode( const outputFinalization = finalizeOutputPathForCompletedEncode(
incompleteOutputPath, incompleteOutputPath,
preferredFinalOutputPath preferredFinalOutputPath
@@ -11192,11 +11409,11 @@ class PipelineService extends EventEmitter {
} }
} }
const handbrakeInfoWithPostScripts = { const handbrakeInfoWithPostScripts = this.withPluginExecutionMeta({
...handbrakeInfo, ...handbrakeInfo,
preEncodeScripts: preEncodeScriptsSummary, preEncodeScripts: preEncodeScriptsSummary,
postEncodeScripts: postEncodeScriptsSummary postEncodeScripts: postEncodeScriptsSummary
}; }, encodePluginExecution);
await historyService.updateJob(jobId, { await historyService.updateJob(jobId, {
handbrake_info_json: JSON.stringify(handbrakeInfoWithPostScripts), handbrake_info_json: JSON.stringify(handbrakeInfoWithPostScripts),
@@ -11511,12 +11728,11 @@ class PipelineService extends EventEmitter {
} }
const mkInfoBeforeRip = this.safeParseJson(job.makemkv_info_json); const mkInfoBeforeRip = this.safeParseJson(job.makemkv_info_json);
// Nach dem Plugin-Rip hat jobProgress die aktuelle pluginExecution (analyze + rip). // Merge bestehende Analyze-Marker aus der DB mit den Live-Markern aus dem Rip-Lauf,
// Nach Legacy-Rip nehmen wir die analyze-Phase aus der DB. // damit analyze nicht verloren geht, wenn jobProgress nur die neueste Rip-Phase enthält.
const postRipPluginExecution = this.sanitizePluginExecutionState( const postRipPluginExecution = this.mergePluginExecutionState(
mkInfoBeforeRip?.pluginExecution,
this.jobProgress.get(Number(jobId))?.context?.pluginExecution this.jobProgress.get(Number(jobId))?.context?.pluginExecution
?? mkInfoBeforeRip?.pluginExecution
?? null
); );
await historyService.updateJob(jobId, { await historyService.updateJob(jobId, {
makemkv_info_json: JSON.stringify(this.withAnalyzeContextMediaProfile({ makemkv_info_json: JSON.stringify(this.withAnalyzeContextMediaProfile({
+4 -4
View File
@@ -515,12 +515,12 @@ VALUES ('use_plugin_architecture_dvd_rip', 'Erweitert', 'Rip (DVD)', 'boolean',
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('use_plugin_architecture_dvd_rip', 'true'); 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) 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, 'Noch nicht implementiert — kommt in einem späteren Sprint.', 'false', '[]', '{"readonly":true}', 10013, 'use_plugin_architecture_dvd'); 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', 'false'); 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) 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, 'Noch nicht implementiert — kommt in einem späteren Sprint.', 'false', '[]', '{"readonly":true}', 10014, 'use_plugin_architecture_dvd'); 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', 'false'); 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) 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'); 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');
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "ripster-frontend", "name": "ripster-frontend",
"version": "0.12.0-3", "version": "0.12.0-4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ripster-frontend", "name": "ripster-frontend",
"version": "0.12.0-3", "version": "0.12.0-4",
"dependencies": { "dependencies": {
"primeicons": "^7.0.0", "primeicons": "^7.0.0",
"primereact": "^10.9.2", "primereact": "^10.9.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ripster-frontend", "name": "ripster-frontend",
"version": "0.12.0-3", "version": "0.12.0-4",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+2 -1
View File
@@ -111,11 +111,12 @@ export function normalizePluginExecutionState(rawState) {
export function resolveJobPluginExecution(job = null, liveContext = null, options = {}) { export function resolveJobPluginExecution(job = null, liveContext = null, options = {}) {
const liveExecution = normalizePluginExecutionState(liveContext?.pluginExecution); const liveExecution = normalizePluginExecutionState(liveContext?.pluginExecution);
const makemkvExecution = normalizePluginExecutionState(job?.makemkvInfo?.pluginExecution); const makemkvExecution = normalizePluginExecutionState(job?.makemkvInfo?.pluginExecution);
const mediainfoExecution = normalizePluginExecutionState(job?.mediainfoInfo?.pluginExecution);
const handbrakeExecution = normalizePluginExecutionState(job?.handbrakeInfo?.pluginExecution); const handbrakeExecution = normalizePluginExecutionState(job?.handbrakeInfo?.pluginExecution);
const jobExecution = normalizePluginExecutionState(job?.pluginExecution); const jobExecution = normalizePluginExecutionState(job?.pluginExecution);
const expectedStage = inferExpectedPluginStage(options); const expectedStage = inferExpectedPluginStage(options);
const candidates = [liveExecution, makemkvExecution, handbrakeExecution, jobExecution].filter(Boolean); const candidates = [liveExecution, handbrakeExecution, mediainfoExecution, makemkvExecution, jobExecution].filter(Boolean);
if (candidates.length === 0) { if (candidates.length === 0) {
return null; return null;
} }
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "ripster", "name": "ripster",
"version": "0.12.0-3", "version": "0.12.0-4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ripster", "name": "ripster",
"version": "0.12.0-3", "version": "0.12.0-4",
"devDependencies": { "devDependencies": {
"concurrently": "^9.1.2" "concurrently": "^9.1.2"
} }
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "ripster", "name": "ripster",
"private": true, "private": true,
"version": "0.12.0-3", "version": "0.12.0-4",
"scripts": { "scripts": {
"dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"", "dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"",
"dev:backend": "npm run dev --prefix backend", "dev:backend": "npm run dev --prefix backend",