0.10.2-13 DVD Media Info Fix

This commit is contained in:
2026-03-16 13:40:01 +00:00
parent affabd3720
commit 2cbbbd9a86
7 changed files with 206 additions and 94 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "ripster-backend", "name": "ripster-backend",
"version": "0.10.2-12", "version": "0.10.2-13",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ripster-backend", "name": "ripster-backend",
"version": "0.10.2-12", "version": "0.10.2-13",
"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.10.2-12", "version": "0.10.2-13",
"private": true, "private": true,
"type": "commonjs", "type": "commonjs",
"scripts": { "scripts": {
+197 -85
View File
@@ -8154,8 +8154,10 @@ class PipelineService extends EventEmitter {
const mediaInfoByPath = {}; const mediaInfoByPath = {};
const mediaInfoRuns = []; const mediaInfoRuns = [];
let effectiveMediaFiles = mediaFiles;
const buildReviewSnapshot = (processedCount) => { const buildReviewSnapshot = (processedCount) => {
const processedFiles = mediaFiles const processedFiles = effectiveMediaFiles
.slice(0, processedCount) .slice(0, processedCount)
.filter((item) => Boolean(mediaInfoByPath[item.path])); .filter((item) => Boolean(mediaInfoByPath[item.path]));
@@ -8178,73 +8180,95 @@ class PipelineService extends EventEmitter {
mediaProfile, mediaProfile,
sourceJobId: options.sourceJobId || null, sourceJobId: options.sourceJobId || null,
reviewConfirmed: false, reviewConfirmed: false,
partial: processedFiles.length < mediaFiles.length, partial: processedFiles.length < effectiveMediaFiles.length,
processedFiles: processedFiles.length, processedFiles: processedFiles.length,
totalFiles: mediaFiles.length totalFiles: effectiveMediaFiles.length
}; };
}; };
for (let i = 0; i < mediaFiles.length; i += 1) { // For DVD sources, use HandBrake scan directly instead of mediainfo (mediainfo
const file = mediaFiles[i]; // is unreliable on VOB files and often returns incomplete track information).
const percent = Number((((i + 1) / mediaFiles.length) * 100).toFixed(2)); const isDvdSource = rawMedia.source === 'dvd'
await this.updateProgress('MEDIAINFO_CHECK', percent, null, `Mediainfo ${i + 1}/${mediaFiles.length}: ${path.basename(file.path)}`, jobId); || rawMedia.source === 'dvd_image'
|| rawMedia.source === 'single_extensionless';
let dvdHandBrakeScanJson = null;
let dvdHandBrakeScanInputPath = null;
const result = await this.runMediainfoForFile(jobId, file.path, { if (isDvdSource && mediaFiles.length > 0) {
// For 'dvd': scan the folder that contains VIDEO_TS (parent of the VIDEO_TS dir).
// For image/extensionless: scan the file directly.
dvdHandBrakeScanInputPath = rawMedia.source === 'dvd'
? path.dirname(path.dirname(mediaFiles[0].path))
: mediaFiles[0].path;
await historyService.appendLog(
jobId,
'SYSTEM',
`DVD-Analyse via HandBrake Scan: ${path.basename(dvdHandBrakeScanInputPath)}`
);
await this.updateProgress('MEDIAINFO_CHECK', 10, null, 'HandBrake DVD-Scan läuft', jobId);
const dvdScanLines = [];
const dvdScanConfig = await settingsService.buildHandBrakeScanConfigForInput(dvdHandBrakeScanInputPath, {
mediaProfile, mediaProfile,
settingsMap: settings settingsMap: settings
}); });
let parsedMediaInfo = result.parsed; logger.info('mediainfo:review:dvd-handbrake-scan:command', {
let fallbackRunInfo = null; jobId,
if (shouldRunDvdTrackFallback(parsedMediaInfo, mediaProfile, file.path)) { dvdHandBrakeScanInputPath,
try { cmd: dvdScanConfig.cmd,
const fallback = await this.runDvdTrackFallbackForFile(jobId, file.path, { args: dvdScanConfig.args
mediaProfile,
settingsMap: settings
});
if (fallback?.parsedMediaInfo) {
parsedMediaInfo = fallback.parsedMediaInfo;
fallbackRunInfo = fallback.runInfo || null;
const audioCount = Array.isArray(fallback?.titleInfo?.audioTracks)
? fallback.titleInfo.audioTracks.length
: 0;
const subtitleCount = Array.isArray(fallback?.titleInfo?.subtitleTracks)
? fallback.titleInfo.subtitleTracks.length
: 0;
await historyService.appendLog(
jobId,
'SYSTEM',
`DVD Track-Fallback aktiv (${path.basename(file.path)}): Audio=${audioCount}, Subtitle=${subtitleCount}.`
);
} else {
await historyService.appendLog(
jobId,
'SYSTEM',
`DVD Track-Fallback ohne Ergebnis (${path.basename(file.path)}).`
);
}
} catch (error) {
logger.warn('mediainfo:track-fallback:failed', {
jobId,
inputPath: file.path,
error: errorToMeta(error)
});
}
}
mediaInfoByPath[file.path] = parsedMediaInfo;
mediaInfoRuns.push({
filePath: file.path,
runInfo: result.runInfo,
fallbackRunInfo
}); });
const partialReview = buildReviewSnapshot(i + 1); const dvdScanRunInfo = await this.runCommand({
jobId,
stage: 'MEDIAINFO_CHECK',
source: 'HANDBRAKE_SCAN_DVD',
cmd: dvdScanConfig.cmd,
args: dvdScanConfig.args,
collectLines: dvdScanLines,
collectStderrLines: false
});
dvdHandBrakeScanJson = parseMediainfoJsonOutput(dvdScanLines.join('\n'));
if (dvdHandBrakeScanJson) {
const titleInfo = parseHandBrakeSelectedTitleInfo(dvdHandBrakeScanJson);
if (titleInfo) {
const syntheticMediaInfo = buildSyntheticMediaInfoFromMakeMkvTitle(titleInfo);
mediaInfoByPath[dvdHandBrakeScanInputPath] = syntheticMediaInfo;
effectiveMediaFiles = [{ path: dvdHandBrakeScanInputPath, size: 0 }];
const audioCount = Array.isArray(titleInfo.audioTracks) ? titleInfo.audioTracks.length : 0;
const subtitleCount = Array.isArray(titleInfo.subtitleTracks) ? titleInfo.subtitleTracks.length : 0;
await historyService.appendLog(
jobId,
'SYSTEM',
`HandBrake DVD-Scan: Audio=${audioCount}, Subtitle=${subtitleCount}, Dauer=${formatDurationClock(titleInfo.durationSeconds)}`
);
} else {
await historyService.appendLog(
jobId,
'SYSTEM',
'HandBrake DVD-Scan: Kein Titel erkannt, falle auf MediaInfo zurück.'
);
dvdHandBrakeScanJson = null;
}
} else {
await historyService.appendLog(
jobId,
'SYSTEM',
'HandBrake DVD-Scan: Ausgabe nicht lesbar, falle auf MediaInfo zurück.'
);
}
mediaInfoRuns.push({ filePath: dvdHandBrakeScanInputPath, runInfo: dvdScanRunInfo, fallbackRunInfo: null });
const partialReview = buildReviewSnapshot(1);
if (this.isPrimaryJob(jobId)) { if (this.isPrimaryJob(jobId)) {
await this.setState('MEDIAINFO_CHECK', { await this.setState('MEDIAINFO_CHECK', {
activeJobId: jobId, activeJobId: jobId,
progress: percent, progress: 50,
eta: null, eta: null,
statusText: `Mediainfo ${i + 1}/${mediaFiles.length} analysiert: ${path.basename(file.path)}`, statusText: `HandBrake DVD-Scan abgeschlossen: ${path.basename(dvdHandBrakeScanInputPath)}`,
context: { context: {
jobId, jobId,
rawPath, rawPath,
@@ -8261,8 +8285,88 @@ class PipelineService extends EventEmitter {
} }
} }
// For non-DVD sources (or when HandBrake scan failed), run mediainfo on each file.
if (!isDvdSource || !dvdHandBrakeScanJson) {
for (let i = 0; i < mediaFiles.length; i += 1) {
const file = mediaFiles[i];
const percent = Number((((i + 1) / mediaFiles.length) * 100).toFixed(2));
await this.updateProgress('MEDIAINFO_CHECK', percent, null, `Mediainfo ${i + 1}/${mediaFiles.length}: ${path.basename(file.path)}`, jobId);
const result = await this.runMediainfoForFile(jobId, file.path, {
mediaProfile,
settingsMap: settings
});
let parsedMediaInfo = result.parsed;
let fallbackRunInfo = null;
if (shouldRunDvdTrackFallback(parsedMediaInfo, mediaProfile, file.path)) {
try {
const fallback = await this.runDvdTrackFallbackForFile(jobId, file.path, {
mediaProfile,
settingsMap: settings
});
if (fallback?.parsedMediaInfo) {
parsedMediaInfo = fallback.parsedMediaInfo;
fallbackRunInfo = fallback.runInfo || null;
const audioCount = Array.isArray(fallback?.titleInfo?.audioTracks)
? fallback.titleInfo.audioTracks.length
: 0;
const subtitleCount = Array.isArray(fallback?.titleInfo?.subtitleTracks)
? fallback.titleInfo.subtitleTracks.length
: 0;
await historyService.appendLog(
jobId,
'SYSTEM',
`DVD Track-Fallback aktiv (${path.basename(file.path)}): Audio=${audioCount}, Subtitle=${subtitleCount}.`
);
} else {
await historyService.appendLog(
jobId,
'SYSTEM',
`DVD Track-Fallback ohne Ergebnis (${path.basename(file.path)}).`
);
}
} catch (error) {
logger.warn('mediainfo:track-fallback:failed', {
jobId,
inputPath: file.path,
error: errorToMeta(error)
});
}
}
mediaInfoByPath[file.path] = parsedMediaInfo;
mediaInfoRuns.push({
filePath: file.path,
runInfo: result.runInfo,
fallbackRunInfo
});
const partialReview = buildReviewSnapshot(i + 1);
if (this.isPrimaryJob(jobId)) {
await this.setState('MEDIAINFO_CHECK', {
activeJobId: jobId,
progress: percent,
eta: null,
statusText: `Mediainfo ${i + 1}/${mediaFiles.length} analysiert: ${path.basename(file.path)}`,
context: {
jobId,
rawPath,
inputPath: partialReview?.encodeInputPath || null,
hasEncodableTitle: Boolean(partialReview?.encodeInputPath),
reviewConfirmed: false,
mode: options.mode || 'rip',
mediaProfile,
sourceJobId: options.sourceJobId || null,
mediaInfoReview: partialReview,
selectedMetadata
}
});
}
}
}
const review = buildMediainfoReview({ const review = buildMediainfoReview({
mediaFiles, mediaFiles: effectiveMediaFiles,
mediaInfoByPath, mediaInfoByPath,
settings, settings,
presetProfile, presetProfile,
@@ -8279,8 +8383,8 @@ class PipelineService extends EventEmitter {
sourceJobId: options.sourceJobId || null, sourceJobId: options.sourceJobId || null,
reviewConfirmed: false, reviewConfirmed: false,
partial: false, partial: false,
processedFiles: mediaFiles.length, processedFiles: effectiveMediaFiles.length,
totalFiles: mediaFiles.length totalFiles: effectiveMediaFiles.length
}; };
const reviewPrefillResult = applyPreviousSelectionDefaultsToReviewPlan( const reviewPrefillResult = applyPreviousSelectionDefaultsToReviewPlan(
enrichedReview, enrichedReview,
@@ -8295,7 +8399,10 @@ class PipelineService extends EventEmitter {
|| rawMedia.source === 'dvd_image' || rawMedia.source === 'dvd_image'
|| rawMedia.source === 'single_extensionless'; || rawMedia.source === 'single_extensionless';
if (needsHandBrakeTitleScan) { if (needsHandBrakeTitleScan) {
const dvdScanInputPath = rawMedia.source === 'dvd' ? rawPath : mediaFiles[0].path; // Use the same path that was used for the DVD HandBrake scan above, or fall back
// to the existing logic for cases where the scan was not yet performed.
const dvdScanInputPath = dvdHandBrakeScanInputPath
|| (rawMedia.source === 'dvd' ? rawPath : mediaFiles[0].path);
const preSelectedHandBrakeTitleId = normalizeReviewTitleId(options?.selectedHandBrakeTitleId); const preSelectedHandBrakeTitleId = normalizeReviewTitleId(options?.selectedHandBrakeTitleId);
if (preSelectedHandBrakeTitleId) { if (preSelectedHandBrakeTitleId) {
enrichedReview = { enrichedReview = {
@@ -8309,33 +8416,38 @@ class PipelineService extends EventEmitter {
`HandBrake Titel-Auswahl (manuell) übernommen: -t ${preSelectedHandBrakeTitleId}` `HandBrake Titel-Auswahl (manuell) übernommen: -t ${preSelectedHandBrakeTitleId}`
); );
} else { } else {
await historyService.appendLog( // Reuse the HandBrake scan result from the DVD analysis step when available,
jobId, // otherwise run a fresh scan for title selection.
'SYSTEM', let dvdTitleScanJson = dvdHandBrakeScanJson || null;
'HandBrake Titel-Scan für DVD-Struktur wird gestartet...' if (!dvdTitleScanJson) {
); await historyService.appendLog(
await this.updateProgress('MEDIAINFO_CHECK', 100, null, 'HandBrake DVD Titel-Scan läuft', jobId); jobId,
const dvdTitleScanLines = []; 'SYSTEM',
const dvdTitleScanConfig = await settingsService.buildHandBrakeScanConfigForInput(dvdScanInputPath, { 'HandBrake Titel-Scan für DVD-Struktur wird gestartet...'
mediaProfile, );
settingsMap: settings await this.updateProgress('MEDIAINFO_CHECK', 100, null, 'HandBrake DVD Titel-Scan läuft', jobId);
}); const dvdTitleScanLines = [];
logger.info('mediainfo:review:dvd-title-scan:command', { const dvdTitleScanConfig = await settingsService.buildHandBrakeScanConfigForInput(dvdScanInputPath, {
jobId, mediaProfile,
cmd: dvdTitleScanConfig.cmd, settingsMap: settings
args: dvdTitleScanConfig.args, });
dvdScanInputPath logger.info('mediainfo:review:dvd-title-scan:command', {
}); jobId,
await this.runCommand({ cmd: dvdTitleScanConfig.cmd,
jobId, args: dvdTitleScanConfig.args,
stage: 'MEDIAINFO_CHECK', dvdScanInputPath
source: 'HANDBRAKE_SCAN_DVD_TITLES', });
cmd: dvdTitleScanConfig.cmd, await this.runCommand({
args: dvdTitleScanConfig.args, jobId,
collectLines: dvdTitleScanLines, stage: 'MEDIAINFO_CHECK',
collectStderrLines: false source: 'HANDBRAKE_SCAN_DVD_TITLES',
}); cmd: dvdTitleScanConfig.cmd,
const dvdTitleScanJson = parseMediainfoJsonOutput(dvdTitleScanLines.join('\n')); args: dvdTitleScanConfig.args,
collectLines: dvdTitleScanLines,
collectStderrLines: false
});
dvdTitleScanJson = parseMediainfoJsonOutput(dvdTitleScanLines.join('\n'));
}
if (dvdTitleScanJson) { if (dvdTitleScanJson) {
const allTitles = parseHandBrakeTitleList(dvdTitleScanJson); const allTitles = parseHandBrakeTitleList(dvdTitleScanJson);
const minLengthMinutes = Number(settings?.makemkv_min_length_minutes || 0); const minLengthMinutes = Number(settings?.makemkv_min_length_minutes || 0);
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "ripster-frontend", "name": "ripster-frontend",
"version": "0.10.2-12", "version": "0.10.2-13",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ripster-frontend", "name": "ripster-frontend",
"version": "0.10.2-12", "version": "0.10.2-13",
"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.10.2-12", "version": "0.10.2-13",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "ripster", "name": "ripster",
"version": "0.10.2-12", "version": "0.10.2-13",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ripster", "name": "ripster",
"version": "0.10.2-12", "version": "0.10.2-13",
"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.10.2-12", "version": "0.10.2-13",
"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",