0.13.0 DVD Series

This commit is contained in:
2026-04-10 18:44:41 +00:00
parent 43702b0138
commit 974b7bfad3
36 changed files with 9666 additions and 739 deletions
+117
View File
@@ -0,0 +1,117 @@
'use strict';
const { SourcePlugin } = require('./PluginBase');
const dvdSeriesScanService = require('../services/dvdSeriesScanService');
const tmdbService = require('../services/tmdbService');
/**
* Companion-Plugin für Serien-DVDs.
*
* Dieses Plugin ersetzt den bestehenden DVDPlugin-Flow nicht. Es wird in einem
* späteren Schritt explizit aus dem DVD-Flow aufgerufen, sobald ein Prescan eine
* serientypische Titelstruktur vermuten lässt.
*/
class DVDSeriesPlugin extends SourcePlugin {
get id() {
return 'dvd_series';
}
get name() {
return 'DVD Series';
}
detect() {
return false;
}
async analyze(_devicePath, job, ctx) {
ctx.markExecution('analyze', {
pluginId: this.id,
pluginName: this.name,
pluginFile: 'DVDSeriesPlugin.js'
});
const scanText = String(ctx.extra?.scanText || '').trim();
if (!scanText) {
return {
parsedScan: null,
seriesAnalysis: null,
providerConfigured: await tmdbService.isConfigured()
};
}
const result = dvdSeriesScanService.analyzeHandBrakeScan(scanText, ctx.extra?.seriesOptions || {});
const seriesLookupHint = dvdSeriesScanService.deriveSeriesLookupHint([
{
value: result?.parsed?.discTitle || '',
source: 'handbrake_disc_title'
},
{
value: ctx.extra?.detectedTitle || '',
source: 'detected_title'
}
]);
return {
parsedScan: result.parsed,
seriesAnalysis: result.analysis,
seriesLookupHint,
providerConfigured: await tmdbService.isConfigured()
};
}
async review(_job, ctx) {
ctx.markExecution('review', {
pluginId: this.id,
pluginName: this.name,
pluginFile: 'DVDSeriesPlugin.js'
});
return null;
}
async rip() {
throw new Error('DVDSeriesPlugin.rip() ist ein Companion-Plugin und wird nicht direkt ausgeführt.');
}
async encode() {
throw new Error('DVDSeriesPlugin.encode() ist ein Companion-Plugin und wird nicht direkt ausgeführt.');
}
async searchSeries(query, options = {}) {
return tmdbService.searchSeries(query, options);
}
async searchSeriesWithSeason(query, seasonNumber, options = {}) {
return tmdbService.searchSeriesWithSeason(query, seasonNumber, options);
}
getSettingsSchema() {
return [
{
key: 'dvd_series_plugin_enabled',
category: 'Features',
label: 'DVD Serien-Plugin aktiviert',
type: 'boolean',
required: 1,
description: 'Aktiviert die additive Serienerkennung für DVDs.',
default_value: 'false',
options_json: '[]',
validation_json: '{}',
order_index: 145
},
{
key: 'tmdb_api_read_access_token',
category: 'Metadaten',
label: 'TMDb Read Access Token',
type: 'string',
required: 0,
description: 'Read Access Token für Serien-Metadaten über TheMovieDB (TMDb).',
default_value: null,
options_json: '[]',
validation_json: '{}',
order_index: 401
}
];
}
}
module.exports = { DVDSeriesPlugin };
+16 -5
View File
@@ -161,7 +161,9 @@ class VideoDiscPlugin extends SourcePlugin {
cmd: scanConfig.cmd,
args: scanConfig.args,
collectLines: scanLines,
collectStderrLines: false,
// HandBrake scan details (duration/chapters/audio/subtitles) are often
// emitted on stderr. Collect both streams for reliable disc heuristics.
collectStderrLines: true,
silent: reviewSilent
});
} catch (cmdError) {
@@ -179,6 +181,7 @@ class VideoDiscPlugin extends SourcePlugin {
cmd: scanConfig.cmd,
args: scanConfig.args,
onStdoutLine: (line) => scanLines.push(line),
onStderrLine: (line) => scanLines.push(line),
context: { jobId: job?.id, source: `${this.id}Plugin.review` }
});
}
@@ -219,6 +222,7 @@ class VideoDiscPlugin extends SourcePlugin {
deviceInfo,
selectedTitleId = null,
backupOutputBase = null,
disableMinLengthFilter = false,
isCancelled,
onProcessHandle
} = ctx.extra || {};
@@ -237,7 +241,8 @@ class VideoDiscPlugin extends SourcePlugin {
selectedTitleId,
mediaProfile: this.mediaProfile,
settingsMap: settings,
backupOutputBase
backupOutputBase,
disableMinLengthFilter
});
const ripMode = String(ripConfig.ripMode || settings.makemkv_rip_mode || 'mkv')
@@ -249,7 +254,8 @@ class VideoDiscPlugin extends SourcePlugin {
args: ripConfig.args,
ripMode,
rawJobDir,
selectedTitleId
selectedTitleId,
disableMinLengthFilter
});
ctx.emitProgress(0, ripMode === 'backup'
@@ -443,7 +449,7 @@ function _assertNotCancelled(isCancelled) {
}
}
async function _runCommandCaptured({ cmd, args, onStdoutLine, context }) {
async function _runCommandCaptured({ cmd, args, onStdoutLine, onStderrLine, context }) {
const lines = [];
const handle = spawnTrackedProcess({
cmd,
@@ -454,7 +460,12 @@ async function _runCommandCaptured({ cmd, args, onStdoutLine, context }) {
onStdoutLine(line);
}
},
onStderrLine: () => {},
onStderrLine: (line) => {
lines.push(line);
if (typeof onStderrLine === 'function') {
onStderrLine(line);
}
},
context: context || {}
});