0.10.2-10 DVD Title Scan
This commit is contained in:
@@ -6959,9 +6959,9 @@ class PipelineService extends EventEmitter {
|
||||
return this.runMediainfoReviewForJob(jobId, rawPath, options);
|
||||
}
|
||||
|
||||
async selectMetadata({ jobId, title, year, imdbId, poster, fromOmdb = null, selectedPlaylist = null }) {
|
||||
async selectMetadata({ jobId, title, year, imdbId, poster, fromOmdb = null, selectedPlaylist = null, selectedHandBrakeTitleId = null }) {
|
||||
this.ensureNotBusy('selectMetadata', jobId);
|
||||
logger.info('metadata:selected', { jobId, title, year, imdbId, poster, fromOmdb, selectedPlaylist });
|
||||
logger.info('metadata:selected', { jobId, title, year, imdbId, poster, fromOmdb, selectedPlaylist, selectedHandBrakeTitleId });
|
||||
|
||||
const job = await historyService.getJobById(jobId);
|
||||
if (!job) {
|
||||
@@ -6973,6 +6973,7 @@ class PipelineService extends EventEmitter {
|
||||
const mediaProfile = this.resolveMediaProfileForJob(job, { makemkvInfo: mkInfo });
|
||||
|
||||
const normalizedSelectedPlaylist = normalizePlaylistId(selectedPlaylist);
|
||||
const normalizedSelectedHandBrakeTitleId = normalizeReviewTitleId(selectedHandBrakeTitleId);
|
||||
const waitingForPlaylistSelection = (
|
||||
job.status === 'WAITING_FOR_USER_DECISION'
|
||||
|| job.last_state === 'WAITING_FOR_USER_DECISION'
|
||||
@@ -6984,6 +6985,52 @@ class PipelineService extends EventEmitter {
|
||||
|| poster !== undefined
|
||||
|| (fromOmdb !== null && fromOmdb !== undefined)
|
||||
);
|
||||
|
||||
if (normalizedSelectedHandBrakeTitleId && waitingForPlaylistSelection && job.raw_path && !hasExplicitMetadataPayload && !normalizedSelectedPlaylist) {
|
||||
const currentMkInfo = mkInfo;
|
||||
const currentAnalyzeContext = currentMkInfo?.analyzeContext || {};
|
||||
const updatedMkInfo = this.withAnalyzeContextMediaProfile({
|
||||
...currentMkInfo,
|
||||
analyzeContext: {
|
||||
...currentAnalyzeContext,
|
||||
selectedHandBrakeTitleId: normalizedSelectedHandBrakeTitleId
|
||||
}
|
||||
}, mediaProfile);
|
||||
|
||||
await historyService.updateJob(jobId, {
|
||||
status: 'MEDIAINFO_CHECK',
|
||||
last_state: 'MEDIAINFO_CHECK',
|
||||
error_message: null,
|
||||
makemkv_info_json: JSON.stringify(updatedMkInfo),
|
||||
mediainfo_info_json: null,
|
||||
encode_plan_json: null,
|
||||
encode_input_path: null,
|
||||
encode_review_confirmed: 0
|
||||
});
|
||||
await historyService.appendLog(
|
||||
jobId,
|
||||
'USER_ACTION',
|
||||
`DVD Titel-Auswahl gesetzt: -t ${normalizedSelectedHandBrakeTitleId}.`
|
||||
);
|
||||
|
||||
try {
|
||||
await this.runMediainfoReviewForJob(jobId, job.raw_path, {
|
||||
mode: 'rip',
|
||||
mediaProfile,
|
||||
selectedHandBrakeTitleId: normalizedSelectedHandBrakeTitleId
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('metadata:handbrake-title-selection:review-failed', {
|
||||
jobId,
|
||||
selectedHandBrakeTitleId: normalizedSelectedHandBrakeTitleId,
|
||||
error: errorToMeta(error)
|
||||
});
|
||||
await this.failJob(jobId, 'MEDIAINFO_CHECK', error);
|
||||
throw error;
|
||||
}
|
||||
return historyService.getJobById(jobId);
|
||||
}
|
||||
|
||||
if (normalizedSelectedPlaylist && waitingForPlaylistSelection && job.raw_path && !hasExplicitMetadataPayload) {
|
||||
const currentMkInfo = mkInfo;
|
||||
const currentAnalyzeContext = currentMkInfo?.analyzeContext || {};
|
||||
@@ -8222,9 +8269,156 @@ class PipelineService extends EventEmitter {
|
||||
: null
|
||||
);
|
||||
enrichedReview = reviewPrefillResult.plan;
|
||||
|
||||
// HandBrake title scan for DVD structures (VIDEO_TS directories / ISO images)
|
||||
const needsHandBrakeTitleScan = rawMedia.source === 'dvd'
|
||||
|| rawMedia.source === 'dvd_image'
|
||||
|| rawMedia.source === 'single_extensionless';
|
||||
if (needsHandBrakeTitleScan) {
|
||||
const dvdScanInputPath = rawMedia.source === 'dvd' ? rawPath : mediaFiles[0].path;
|
||||
const preSelectedHandBrakeTitleId = normalizeReviewTitleId(options?.selectedHandBrakeTitleId);
|
||||
if (preSelectedHandBrakeTitleId) {
|
||||
enrichedReview = {
|
||||
...enrichedReview,
|
||||
handBrakeTitleId: preSelectedHandBrakeTitleId,
|
||||
encodeInputPath: dvdScanInputPath
|
||||
};
|
||||
await historyService.appendLog(
|
||||
jobId,
|
||||
'SYSTEM',
|
||||
`HandBrake Titel-Auswahl (manuell) übernommen: -t ${preSelectedHandBrakeTitleId}`
|
||||
);
|
||||
} else {
|
||||
await historyService.appendLog(
|
||||
jobId,
|
||||
'SYSTEM',
|
||||
'HandBrake Titel-Scan für DVD-Struktur wird gestartet...'
|
||||
);
|
||||
await this.updateProgress('MEDIAINFO_CHECK', 100, null, 'HandBrake DVD Titel-Scan läuft', jobId);
|
||||
const dvdTitleScanLines = [];
|
||||
const dvdTitleScanConfig = await settingsService.buildHandBrakeScanConfigForInput(dvdScanInputPath, {
|
||||
mediaProfile,
|
||||
settingsMap: settings
|
||||
});
|
||||
logger.info('mediainfo:review:dvd-title-scan:command', {
|
||||
jobId,
|
||||
cmd: dvdTitleScanConfig.cmd,
|
||||
args: dvdTitleScanConfig.args,
|
||||
dvdScanInputPath
|
||||
});
|
||||
await this.runCommand({
|
||||
jobId,
|
||||
stage: 'MEDIAINFO_CHECK',
|
||||
source: 'HANDBRAKE_SCAN_DVD_TITLES',
|
||||
cmd: dvdTitleScanConfig.cmd,
|
||||
args: dvdTitleScanConfig.args,
|
||||
collectLines: dvdTitleScanLines,
|
||||
collectStderrLines: false
|
||||
});
|
||||
const dvdTitleScanJson = parseMediainfoJsonOutput(dvdTitleScanLines.join('\n'));
|
||||
if (dvdTitleScanJson) {
|
||||
const allTitles = parseHandBrakeTitleList(dvdTitleScanJson);
|
||||
const minLengthMinutes = Number(settings?.makemkv_min_length_minutes || 0);
|
||||
const minLengthSeconds = Math.max(0, Math.round(minLengthMinutes * 60));
|
||||
const titleCandidates = allTitles.filter((t) => t.durationSeconds >= minLengthSeconds);
|
||||
await historyService.appendLog(
|
||||
jobId,
|
||||
'SYSTEM',
|
||||
`HandBrake DVD Titel-Scan: ${allTitles.length} gesamt, ${titleCandidates.length} ≥ ${minLengthMinutes} min.`
|
||||
);
|
||||
if (titleCandidates.length === 1) {
|
||||
enrichedReview = {
|
||||
...enrichedReview,
|
||||
handBrakeTitleId: titleCandidates[0].handBrakeTitleId,
|
||||
encodeInputPath: dvdScanInputPath
|
||||
};
|
||||
await historyService.appendLog(
|
||||
jobId,
|
||||
'SYSTEM',
|
||||
`HandBrake DVD Titel automatisch gewählt: -t ${titleCandidates[0].handBrakeTitleId}`
|
||||
);
|
||||
} else if (titleCandidates.length > 1) {
|
||||
const candidatesData = titleCandidates.map((t) => ({
|
||||
handBrakeTitleId: t.handBrakeTitleId,
|
||||
durationSeconds: t.durationSeconds,
|
||||
durationMinutes: Number((t.durationSeconds / 60).toFixed(1)),
|
||||
audioTrackCount: t.audioTrackCount,
|
||||
subtitleTrackCount: t.subtitleTrackCount,
|
||||
sizeBytes: t.sizeBytes || 0
|
||||
}));
|
||||
enrichedReview = {
|
||||
...enrichedReview,
|
||||
handBrakeTitleDecisionRequired: true,
|
||||
handBrakeTitleCandidates: candidatesData,
|
||||
handBrakeDvdInputPath: dvdScanInputPath,
|
||||
encodeInputPath: null
|
||||
};
|
||||
await historyService.updateJob(jobId, {
|
||||
status: 'WAITING_FOR_USER_DECISION',
|
||||
last_state: 'WAITING_FOR_USER_DECISION',
|
||||
error_message: null,
|
||||
mediainfo_info_json: JSON.stringify({
|
||||
generatedAt: nowIso(),
|
||||
files: mediaInfoRuns
|
||||
}),
|
||||
encode_plan_json: JSON.stringify(enrichedReview),
|
||||
encode_input_path: null,
|
||||
encode_review_confirmed: 0
|
||||
});
|
||||
await historyService.appendLog(
|
||||
jobId,
|
||||
'SYSTEM',
|
||||
`HandBrake DVD Titelauswahl erforderlich: ${titleCandidates.length} Kandidaten. Nutzer muss Titel wählen.`
|
||||
);
|
||||
if (this.isPrimaryJob(jobId)) {
|
||||
await this.setState('WAITING_FOR_USER_DECISION', {
|
||||
activeJobId: jobId,
|
||||
progress: 0,
|
||||
eta: null,
|
||||
statusText: `DVD Titelauswahl: ${titleCandidates.length} Kandidaten gefunden`,
|
||||
context: {
|
||||
jobId,
|
||||
rawPath,
|
||||
handBrakeTitleDecisionRequired: true,
|
||||
handBrakeTitleCandidates: candidatesData,
|
||||
handBrakeDvdInputPath: dvdScanInputPath,
|
||||
reviewConfirmed: false,
|
||||
mode: options.mode || 'rip',
|
||||
mediaProfile,
|
||||
sourceJobId: options.sourceJobId || null,
|
||||
mediaInfoReview: enrichedReview,
|
||||
selectedMetadata
|
||||
}
|
||||
});
|
||||
}
|
||||
void this.notifyPushover('metadata_ready', {
|
||||
title: 'Ripster - DVD Titelauswahl',
|
||||
message: `Job #${jobId}: ${titleCandidates.length} DVD-Titel gefunden, manuelle Auswahl erforderlich`
|
||||
});
|
||||
return enrichedReview;
|
||||
} else {
|
||||
await historyService.appendLog(
|
||||
jobId,
|
||||
'SYSTEM',
|
||||
`HandBrake DVD Titel-Scan: Kein Titel ≥ ${minLengthMinutes} min. Ohne Titel-ID fortgefahren.`
|
||||
);
|
||||
enrichedReview = { ...enrichedReview, encodeInputPath: dvdScanInputPath };
|
||||
}
|
||||
} else {
|
||||
logger.warn('mediainfo:review:dvd-title-scan:parse-failed', { jobId });
|
||||
await historyService.appendLog(
|
||||
jobId,
|
||||
'SYSTEM',
|
||||
'HandBrake DVD Titel-Scan: Ausgabe nicht lesbar. Ohne Titel-ID fortgefahren.'
|
||||
);
|
||||
enrichedReview = { ...enrichedReview, encodeInputPath: dvdScanInputPath };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hasEncodableTitle = Boolean(enrichedReview.encodeInputPath);
|
||||
const titleSelectionRequired = Boolean(enrichedReview.titleSelectionRequired);
|
||||
if (!hasEncodableTitle && !titleSelectionRequired) {
|
||||
if (!hasEncodableTitle && !titleSelectionRequired && !enrichedReview.handBrakeTitleDecisionRequired) {
|
||||
enrichedReview.notes = [
|
||||
...(Array.isArray(enrichedReview.notes) ? enrichedReview.notes : []),
|
||||
'Kein Titel erfüllt aktuell MIN_LENGTH_MINUTES. Bitte Konfiguration prüfen.'
|
||||
@@ -8991,7 +9185,61 @@ class PipelineService extends EventEmitter {
|
||||
`HandBrake Titel-Mapping: ${selectedPlaylistId}.mpls -> -t ${handBrakeTitleId}`
|
||||
);
|
||||
} else if (!handBrakeTitleId) {
|
||||
handBrakeTitleId = normalizeReviewTitleId(encodePlan?.handBrakeTitleId ?? encodePlan?.encodeInputTitleId);
|
||||
const dvdResolveScanLines = [];
|
||||
const dvdResolveScanConfig = await settingsService.buildHandBrakeScanConfigForInput(inputPath, {
|
||||
mediaProfile,
|
||||
settingsMap: settings
|
||||
});
|
||||
await historyService.appendLog(
|
||||
jobId,
|
||||
'SYSTEM',
|
||||
'HandBrake Titel-Scan für Verzeichnis ohne Playlist wird gestartet...'
|
||||
);
|
||||
logger.info('encoding:dvd-title-resolve-scan:command', {
|
||||
jobId,
|
||||
cmd: dvdResolveScanConfig.cmd,
|
||||
args: dvdResolveScanConfig.args
|
||||
});
|
||||
const dvdResolveRunInfo = await this.runCommand({
|
||||
jobId,
|
||||
stage: 'ENCODING',
|
||||
source: 'HANDBRAKE_SCAN_TITLE_RESOLVE',
|
||||
cmd: dvdResolveScanConfig.cmd,
|
||||
args: dvdResolveScanConfig.args,
|
||||
collectLines: dvdResolveScanLines,
|
||||
collectStderrLines: false
|
||||
});
|
||||
const dvdResolveParsed = parseMediainfoJsonOutput(dvdResolveScanLines.join('\n'));
|
||||
if (dvdResolveParsed) {
|
||||
const dvdTitleInfo = parseHandBrakeSelectedTitleInfo(dvdResolveParsed, {
|
||||
handBrakeTitleId: null,
|
||||
playlistId: null
|
||||
});
|
||||
if (dvdTitleInfo?.handBrakeTitleId) {
|
||||
handBrakeTitleId = dvdTitleInfo.handBrakeTitleId;
|
||||
await historyService.appendLog(
|
||||
jobId,
|
||||
'SYSTEM',
|
||||
`HandBrake Titel-Scan: längster Titel gefunden -> -t ${handBrakeTitleId}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
logger.warn('encoding:dvd-title-resolve-scan:parse-failed', {
|
||||
jobId,
|
||||
runInfo: dvdResolveRunInfo
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!handBrakeTitleId) {
|
||||
const fallbackTitleId = normalizeReviewTitleId(encodePlan?.handBrakeTitleId);
|
||||
if (fallbackTitleId) {
|
||||
handBrakeTitleId = fallbackTitleId;
|
||||
await historyService.appendLog(
|
||||
jobId,
|
||||
'SYSTEM',
|
||||
`HandBrake Titel-ID aus Encode-Plan übernommen: -t ${handBrakeTitleId}`
|
||||
);
|
||||
}
|
||||
}
|
||||
const handBrakeConfig = await settingsService.buildHandBrakeConfig(inputPath, incompleteOutputPath, {
|
||||
|
||||
Reference in New Issue
Block a user