0.12.0-4 DVD Plugin
This commit is contained in:
Generated
+2
-2
@@ -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,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": {
|
||||||
|
|||||||
@@ -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 ─────────────────────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
@@ -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');
|
||||||
|
|||||||
Generated
+2
-2
@@ -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,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": {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2
-2
@@ -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
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user