0.16.1-6 Detection Fix
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "0.16.1-5",
|
"version": "0.16.1-6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "0.16.1-5",
|
"version": "0.16.1-6",
|
||||||
"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.16.1-5",
|
"version": "0.16.1-6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1049,7 +1049,14 @@ function resolveEffectiveStoragePathsForJob(settings = null, job = {}, parsed =
|
|||||||
const metadataKind = String(selectedMetadata?.metadataKind || '').trim().toLowerCase();
|
const metadataKind = String(selectedMetadata?.metadataKind || '').trim().toLowerCase();
|
||||||
const normalizedJobKind = String(job?.job_kind || '').trim().toLowerCase();
|
const normalizedJobKind = String(job?.job_kind || '').trim().toLowerCase();
|
||||||
const hasSeriesJobKind = normalizedJobKind === 'dvd_series_child' || normalizedJobKind === 'dvd_series_container';
|
const hasSeriesJobKind = normalizedJobKind === 'dvd_series_child' || normalizedJobKind === 'dvd_series_container';
|
||||||
const hasSeriesParent = normalizeJobIdValue(job?.parent_job_id) !== null;
|
const hasParentJob = normalizeJobIdValue(job?.parent_job_id) !== null;
|
||||||
|
const isMultipartJobKind = (
|
||||||
|
normalizedJobKind === 'multipart_movie_container'
|
||||||
|
|| normalizedJobKind === 'multipart_movie_child'
|
||||||
|
|| normalizedJobKind === 'multipart_movie_merge'
|
||||||
|
);
|
||||||
|
// Parent-ID alone is not a reliable series signal: multipart movie jobs also use parent_job_id.
|
||||||
|
const hasSeriesParent = hasParentJob && !isMultipartJobKind;
|
||||||
const hasSeriesRawPathHint = isLikelySeriesRawPath(job?.raw_path, settings || {});
|
const hasSeriesRawPathHint = isLikelySeriesRawPath(job?.raw_path, settings || {});
|
||||||
const hasSeriesMetadataHint = hasSeriesMetadataSignals(
|
const hasSeriesMetadataHint = hasSeriesMetadataSignals(
|
||||||
selectedMetadata,
|
selectedMetadata,
|
||||||
|
|||||||
@@ -4023,12 +4023,45 @@ function pickScanMainFeatureTitleId(scanJson) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizePlaylistAliasIds(values, excludePlaylistId = null) {
|
||||||
|
const exclude = normalizePlaylistId(excludePlaylistId);
|
||||||
|
const rows = Array.isArray(values) ? values : [];
|
||||||
|
const seen = new Set();
|
||||||
|
const aliases = [];
|
||||||
|
for (const value of rows) {
|
||||||
|
const playlistId = normalizePlaylistId(value);
|
||||||
|
if (!playlistId || playlistId === exclude || seen.has(playlistId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.add(playlistId);
|
||||||
|
aliases.push(playlistId);
|
||||||
|
}
|
||||||
|
return aliases;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlaylistAliasesForPlaylist(playlistAnalysis, playlistIdRaw) {
|
||||||
|
const playlistId = normalizePlaylistId(playlistIdRaw);
|
||||||
|
if (!playlistId || !playlistAnalysis || typeof playlistAnalysis !== 'object') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const aliasMap = playlistAnalysis?.playlistAliasMap
|
||||||
|
&& typeof playlistAnalysis.playlistAliasMap === 'object'
|
||||||
|
? playlistAnalysis.playlistAliasMap
|
||||||
|
: null;
|
||||||
|
if (!aliasMap) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return normalizePlaylistAliasIds(aliasMap[playlistId], playlistId);
|
||||||
|
}
|
||||||
|
|
||||||
function resolvePlaylistInfoFromAnalysis(playlistAnalysis, playlistIdRaw) {
|
function resolvePlaylistInfoFromAnalysis(playlistAnalysis, playlistIdRaw) {
|
||||||
const playlistId = normalizePlaylistId(playlistIdRaw);
|
const playlistId = normalizePlaylistId(playlistIdRaw);
|
||||||
|
const playlistAliases = getPlaylistAliasesForPlaylist(playlistAnalysis, playlistId);
|
||||||
if (!playlistId || !playlistAnalysis) {
|
if (!playlistId || !playlistAnalysis) {
|
||||||
return {
|
return {
|
||||||
playlistId: playlistId || null,
|
playlistId: playlistId || null,
|
||||||
playlistFile: playlistId ? `${playlistId}.mpls` : null,
|
playlistFile: playlistId ? `${playlistId}.mpls` : null,
|
||||||
|
playlistAliases,
|
||||||
recommended: false,
|
recommended: false,
|
||||||
evaluationLabel: null,
|
evaluationLabel: null,
|
||||||
segmentCommand: playlistId ? `strings BDMV/PLAYLIST/${playlistId}.mpls | grep m2ts` : null,
|
segmentCommand: playlistId ? `strings BDMV/PLAYLIST/${playlistId}.mpls | grep m2ts` : null,
|
||||||
@@ -4050,6 +4083,7 @@ function resolvePlaylistInfoFromAnalysis(playlistAnalysis, playlistIdRaw) {
|
|||||||
return {
|
return {
|
||||||
playlistId,
|
playlistId,
|
||||||
playlistFile: `${playlistId}.mpls`,
|
playlistFile: `${playlistId}.mpls`,
|
||||||
|
playlistAliases,
|
||||||
recommended,
|
recommended,
|
||||||
evaluationLabel: evaluated?.evaluationLabel || (recommended ? 'wahrscheinlich korrekt (Heuristik)' : null),
|
evaluationLabel: evaluated?.evaluationLabel || (recommended ? 'wahrscheinlich korrekt (Heuristik)' : null),
|
||||||
segmentCommand: segmentEntry?.segmentCommand || `strings BDMV/PLAYLIST/${playlistId}.mpls | grep m2ts`,
|
segmentCommand: segmentEntry?.segmentCommand || `strings BDMV/PLAYLIST/${playlistId}.mpls | grep m2ts`,
|
||||||
@@ -4322,8 +4356,13 @@ function resolveHandBrakeTitleIdForPlaylist(scanJson, playlistIdRaw, options = {
|
|||||||
? Math.trunc(durationToleranceRaw)
|
? Math.trunc(durationToleranceRaw)
|
||||||
: 5;
|
: 5;
|
||||||
|
|
||||||
|
const playlistAliases = normalizePlaylistAliasIds(options?.playlistAliases, playlistId);
|
||||||
|
const candidatePlaylistIds = [playlistId, ...playlistAliases];
|
||||||
|
const candidatePlaylistSet = new Set(candidatePlaylistIds);
|
||||||
|
|
||||||
const rows = buildHandBrakeScanTitleRows(scanJson);
|
const rows = buildHandBrakeScanTitleRows(scanJson);
|
||||||
const matches = rows.filter((item) => item.playlist === playlistId);
|
const directMatches = rows.filter((item) => item.playlist === playlistId);
|
||||||
|
const matches = rows.filter((item) => candidatePlaylistSet.has(item.playlist));
|
||||||
|
|
||||||
const scoreForExpected = (row) => {
|
const scoreForExpected = (row) => {
|
||||||
const durationDelta = expectedDurationSeconds !== null
|
const durationDelta = expectedDurationSeconds !== null
|
||||||
@@ -4352,15 +4391,36 @@ function resolveHandBrakeTitleIdForPlaylist(scanJson, playlistIdRaw, options = {
|
|||||||
if (matches.length > 0) {
|
if (matches.length > 0) {
|
||||||
if (expectedDurationSeconds !== null || expectedSizeBytes !== null) {
|
if (expectedDurationSeconds !== null || expectedSizeBytes !== null) {
|
||||||
const scored = matches.map(scoreForExpected).sort(sortByExpectedScore);
|
const scored = matches.map(scoreForExpected).sort(sortByExpectedScore);
|
||||||
|
const scoredDirect = directMatches.map(scoreForExpected).sort(sortByExpectedScore);
|
||||||
if (expectedDurationSeconds !== null) {
|
if (expectedDurationSeconds !== null) {
|
||||||
const withinTolerance = scored.filter((item) => item.durationDelta <= durationToleranceSeconds);
|
const directWithinTolerance = scoredDirect.filter((item) => item.durationDelta <= durationToleranceSeconds);
|
||||||
if (withinTolerance.length > 0) {
|
if (directWithinTolerance.length > 0) {
|
||||||
return withinTolerance[0].row.handBrakeTitleId;
|
return directWithinTolerance[0].row.handBrakeTitleId;
|
||||||
}
|
}
|
||||||
|
const aliasWithinTolerance = scored.filter((item) =>
|
||||||
|
item.durationDelta <= durationToleranceSeconds
|
||||||
|
&& normalizePlaylistId(item?.row?.playlist) !== playlistId
|
||||||
|
);
|
||||||
|
if (aliasWithinTolerance.length > 0) {
|
||||||
|
return aliasWithinTolerance[0].row.handBrakeTitleId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (scoredDirect.length > 0) {
|
||||||
|
return scoredDirect[0].row.handBrakeTitleId;
|
||||||
}
|
}
|
||||||
return scored[0].row.handBrakeTitleId;
|
return scored[0].row.handBrakeTitleId;
|
||||||
}
|
}
|
||||||
const best = matches.sort((a, b) =>
|
const directBest = directMatches
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) =>
|
||||||
|
b.durationSeconds - a.durationSeconds
|
||||||
|
|| b.sizeBytes - a.sizeBytes
|
||||||
|
|| a.handBrakeTitleId - b.handBrakeTitleId
|
||||||
|
)[0];
|
||||||
|
if (directBest) {
|
||||||
|
return directBest.handBrakeTitleId;
|
||||||
|
}
|
||||||
|
const best = matches.slice().sort((a, b) =>
|
||||||
b.durationSeconds - a.durationSeconds
|
b.durationSeconds - a.durationSeconds
|
||||||
|| b.sizeBytes - a.sizeBytes
|
|| b.sizeBytes - a.sizeBytes
|
||||||
|| a.handBrakeTitleId - b.handBrakeTitleId
|
|| a.handBrakeTitleId - b.handBrakeTitleId
|
||||||
@@ -4416,8 +4476,12 @@ function isHandBrakePlaylistCacheEntryCompatible(entry, playlistIdRaw, options =
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allowedPlaylistIds = new Set([
|
||||||
|
playlistId,
|
||||||
|
...normalizePlaylistAliasIds(options?.playlistAliases, playlistId)
|
||||||
|
]);
|
||||||
const cachedPlaylistId = normalizePlaylistId(titleInfo?.playlistId || null);
|
const cachedPlaylistId = normalizePlaylistId(titleInfo?.playlistId || null);
|
||||||
if (cachedPlaylistId && cachedPlaylistId !== playlistId) {
|
if (cachedPlaylistId && !allowedPlaylistIds.has(cachedPlaylistId)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6001,6 +6065,7 @@ function buildDiscScanReview({
|
|||||||
eligibleForEncode: title.selectedByMinLength,
|
eligibleForEncode: title.selectedByMinLength,
|
||||||
playlistId: title.playlistMatch?.playlistId || null,
|
playlistId: title.playlistMatch?.playlistId || null,
|
||||||
playlistFile: title.playlistMatch?.playlistFile || null,
|
playlistFile: title.playlistMatch?.playlistFile || null,
|
||||||
|
playlistAliases: normalizePlaylistAliasIds(title.playlistMatch?.playlistAliases, title.playlistMatch?.playlistId),
|
||||||
playlistRecommended: Boolean(title.playlistMatch?.recommended),
|
playlistRecommended: Boolean(title.playlistMatch?.recommended),
|
||||||
playlistEvaluationLabel: title.playlistMatch?.evaluationLabel || null,
|
playlistEvaluationLabel: title.playlistMatch?.evaluationLabel || null,
|
||||||
playlistSegmentCommand: title.playlistMatch?.segmentCommand || null,
|
playlistSegmentCommand: title.playlistMatch?.segmentCommand || null,
|
||||||
@@ -6529,6 +6594,9 @@ function buildPlaylistCandidates(playlistAnalysis) {
|
|||||||
const segmentMap = playlistAnalysis?.playlistSegments && typeof playlistAnalysis.playlistSegments === 'object'
|
const segmentMap = playlistAnalysis?.playlistSegments && typeof playlistAnalysis.playlistSegments === 'object'
|
||||||
? playlistAnalysis.playlistSegments
|
? playlistAnalysis.playlistSegments
|
||||||
: {};
|
: {};
|
||||||
|
const aliasMap = playlistAnalysis?.playlistAliasMap && typeof playlistAnalysis.playlistAliasMap === 'object'
|
||||||
|
? playlistAnalysis.playlistAliasMap
|
||||||
|
: {};
|
||||||
|
|
||||||
return rawList
|
return rawList
|
||||||
.map((playlistId) => normalizePlaylistId(playlistId))
|
.map((playlistId) => normalizePlaylistId(playlistId))
|
||||||
@@ -6536,6 +6604,12 @@ function buildPlaylistCandidates(playlistAnalysis) {
|
|||||||
.map((playlistId) => {
|
.map((playlistId) => {
|
||||||
const source = sourceRows.find((item) => normalizePlaylistId(item?.playlistId || item?.playlistFile) === playlistId) || null;
|
const source = sourceRows.find((item) => normalizePlaylistId(item?.playlistId || item?.playlistFile) === playlistId) || null;
|
||||||
const segmentEntry = segmentMap[playlistId] || segmentMap[`${playlistId}.mpls`] || null;
|
const segmentEntry = segmentMap[playlistId] || segmentMap[`${playlistId}.mpls`] || null;
|
||||||
|
const playlistAliases = normalizePlaylistAliasIds(
|
||||||
|
Array.isArray(source?.playlistAliases) && source.playlistAliases.length > 0
|
||||||
|
? source.playlistAliases
|
||||||
|
: aliasMap[playlistId],
|
||||||
|
playlistId
|
||||||
|
);
|
||||||
const score = Number(source?.score);
|
const score = Number(source?.score);
|
||||||
const sequenceCoherence = Number(source?.structuralMetrics?.sequenceCoherence);
|
const sequenceCoherence = Number(source?.structuralMetrics?.sequenceCoherence);
|
||||||
const titleId = Number(source?.titleId ?? source?.id);
|
const titleId = Number(source?.titleId ?? source?.id);
|
||||||
@@ -6584,6 +6658,7 @@ function buildPlaylistCandidates(playlistAnalysis) {
|
|||||||
return {
|
return {
|
||||||
playlistId,
|
playlistId,
|
||||||
playlistFile: toPlaylistFile(playlistId),
|
playlistFile: toPlaylistFile(playlistId),
|
||||||
|
playlistAliases,
|
||||||
titleId: Number.isFinite(titleId) ? Math.trunc(titleId) : null,
|
titleId: Number.isFinite(titleId) ? Math.trunc(titleId) : null,
|
||||||
durationSeconds,
|
durationSeconds,
|
||||||
durationLabel: durationLabel || null,
|
durationLabel: durationLabel || null,
|
||||||
@@ -6747,7 +6822,8 @@ function buildHandBrakePlaylistScanCache(scanJson, playlistCandidates = [], rawP
|
|||||||
candidateMetaByPlaylist.set(playlistId, {
|
candidateMetaByPlaylist.set(playlistId, {
|
||||||
expectedMakemkvTitleId: normalizeNonNegativeInteger(row?.titleId),
|
expectedMakemkvTitleId: normalizeNonNegativeInteger(row?.titleId),
|
||||||
expectedDurationSeconds: Number(row?.durationSeconds || 0) || null,
|
expectedDurationSeconds: Number(row?.durationSeconds || 0) || null,
|
||||||
expectedSizeBytes: Number(row?.sizeBytes || 0) || null
|
expectedSizeBytes: Number(row?.sizeBytes || 0) || null,
|
||||||
|
playlistAliases: normalizePlaylistAliasIds(row?.playlistAliases, playlistId)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11238,7 +11314,10 @@ class PipelineService extends EventEmitter {
|
|||||||
const queueIds = new Set(normalizedIds.map((value) => String(value)));
|
const queueIds = new Set(normalizedIds.map((value) => String(value)));
|
||||||
const previousQueueLength = this.queueEntries.length;
|
const previousQueueLength = this.queueEntries.length;
|
||||||
this.queueEntries = this.queueEntries.filter((entry) => !queueIds.has(String(Number(entry?.jobId || 0))));
|
this.queueEntries = this.queueEntries.filter((entry) => !queueIds.has(String(Number(entry?.jobId || 0))));
|
||||||
const removedQueueEntries = previousQueueLength - this.queueEntries.length;
|
let removedQueueEntries = Math.max(0, previousQueueLength - this.queueEntries.length);
|
||||||
|
for (const deletedJobId of normalizedIds) {
|
||||||
|
removedQueueEntries += this.removeDetachedQueueAutomationEntriesForJob(deletedJobId);
|
||||||
|
}
|
||||||
|
|
||||||
for (const jobId of normalizedIds) {
|
for (const jobId of normalizedIds) {
|
||||||
this.cancelRequestedByJob.add(jobId);
|
this.cancelRequestedByJob.add(jobId);
|
||||||
@@ -14484,6 +14563,41 @@ class PipelineService extends EventEmitter {
|
|||||||
return this.queueEntries.findIndex((entry) => Number(entry?.jobId) === Number(jobId));
|
return this.queueEntries.findIndex((entry) => Number(entry?.jobId) === Number(jobId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeDetachedQueueAutomationEntriesForJob(jobId, options = {}) {
|
||||||
|
const normalizedJobId = this.normalizeQueueJobId(jobId);
|
||||||
|
if (!normalizedJobId) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowedPhases = Array.isArray(options?.phases)
|
||||||
|
? new Set(
|
||||||
|
options.phases
|
||||||
|
.map((value) => String(value || '').trim().toLowerCase())
|
||||||
|
.filter((value) => value === 'pre_encode' || value === 'post_encode')
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
const hasPhaseFilter = Boolean(allowedPhases && allowedPhases.size > 0);
|
||||||
|
|
||||||
|
const queueLengthBefore = this.queueEntries.length;
|
||||||
|
this.queueEntries = this.queueEntries.filter((entry) => {
|
||||||
|
const entryType = String(entry?.type || '').trim().toLowerCase();
|
||||||
|
if (entryType !== 'script' && entryType !== 'chain') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const sourceJobId = this.normalizeQueueJobId(entry?.sourceJobId);
|
||||||
|
if (sourceJobId !== normalizedJobId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!hasPhaseFilter) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const sourcePhase = String(entry?.sourcePhase || '').trim().toLowerCase();
|
||||||
|
return !allowedPhases.has(sourcePhase);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Math.max(0, queueLengthBefore - this.queueEntries.length);
|
||||||
|
}
|
||||||
|
|
||||||
normalizeQueueChainIdList(rawList) {
|
normalizeQueueChainIdList(rawList) {
|
||||||
const list = Array.isArray(rawList) ? rawList : [];
|
const list = Array.isArray(rawList) ? rawList : [];
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
@@ -15607,6 +15721,15 @@ class PipelineService extends EventEmitter {
|
|||||||
'SYSTEM',
|
'SYSTEM',
|
||||||
`Queue-Start fehlgeschlagen (${QUEUE_ACTION_LABELS[entry.action] || entry.action}): ${error.message}`
|
`Queue-Start fehlgeschlagen (${QUEUE_ACTION_LABELS[entry.action] || entry.action}): ${error.message}`
|
||||||
);
|
);
|
||||||
|
const removedDetachedEntries = this.removeDetachedQueueAutomationEntriesForJob(entry.jobId);
|
||||||
|
if (removedDetachedEntries > 0) {
|
||||||
|
await historyService.appendLog(
|
||||||
|
entry.jobId,
|
||||||
|
'SYSTEM',
|
||||||
|
`Queue-Cleanup: ${removedDetachedEntries} verknüpfte Skript/Ketten-Eintrag${removedDetachedEntries === 1 ? '' : 'e'} entfernt.`
|
||||||
|
);
|
||||||
|
await this.emitQueueChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18655,13 +18778,21 @@ class PipelineService extends EventEmitter {
|
|||||||
const cachedHandBrakePlaylistEntry = getCachedHandBrakePlaylistEntry(handBrakePlaylistScan, resolvedPlaylistId);
|
const cachedHandBrakePlaylistEntry = getCachedHandBrakePlaylistEntry(handBrakePlaylistScan, resolvedPlaylistId);
|
||||||
const expectedDurationForCache = Number(selectedTitleFromAnalysis?.durationSeconds || 0) || null;
|
const expectedDurationForCache = Number(selectedTitleFromAnalysis?.durationSeconds || 0) || null;
|
||||||
const expectedSizeForCache = Number(selectedTitleFromAnalysis?.sizeBytes || 0) || null;
|
const expectedSizeForCache = Number(selectedTitleFromAnalysis?.sizeBytes || 0) || null;
|
||||||
|
const playlistAliasesForResolve = normalizePlaylistAliasIds(
|
||||||
|
[
|
||||||
|
...(Array.isArray(selectedTitleFromAnalysis?.playlistAliases) ? selectedTitleFromAnalysis.playlistAliases : []),
|
||||||
|
...getPlaylistAliasesForPlaylist(playlistAnalysis, resolvedPlaylistId)
|
||||||
|
],
|
||||||
|
resolvedPlaylistId
|
||||||
|
);
|
||||||
const hasCachedHandBrakeEntry = Boolean(
|
const hasCachedHandBrakeEntry = Boolean(
|
||||||
isHandBrakePlaylistCacheEntryCompatible(
|
isHandBrakePlaylistCacheEntryCompatible(
|
||||||
cachedHandBrakePlaylistEntry,
|
cachedHandBrakePlaylistEntry,
|
||||||
resolvedPlaylistId,
|
resolvedPlaylistId,
|
||||||
{
|
{
|
||||||
expectedDurationSeconds: expectedDurationForCache,
|
expectedDurationSeconds: expectedDurationForCache,
|
||||||
expectedSizeBytes: expectedSizeForCache
|
expectedSizeBytes: expectedSizeForCache,
|
||||||
|
playlistAliases: playlistAliasesForResolve
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -18744,7 +18875,8 @@ class PipelineService extends EventEmitter {
|
|||||||
resolvedHandBrakeTitleId = resolveHandBrakeTitleIdForPlaylist(resolveScanJson, resolvedPlaylistId, {
|
resolvedHandBrakeTitleId = resolveHandBrakeTitleIdForPlaylist(resolveScanJson, resolvedPlaylistId, {
|
||||||
expectedMakemkvTitleId: selectedTitleForReview,
|
expectedMakemkvTitleId: selectedTitleForReview,
|
||||||
expectedDurationSeconds: expectedDurationForCache,
|
expectedDurationSeconds: expectedDurationForCache,
|
||||||
expectedSizeBytes: expectedSizeForCache
|
expectedSizeBytes: expectedSizeForCache,
|
||||||
|
playlistAliases: playlistAliasesForResolve
|
||||||
});
|
});
|
||||||
if (!resolvedHandBrakeTitleId) {
|
if (!resolvedHandBrakeTitleId) {
|
||||||
const knownPlaylists = listAvailableHandBrakePlaylists(resolveScanJson);
|
const knownPlaylists = listAvailableHandBrakePlaylists(resolveScanJson);
|
||||||
@@ -18776,7 +18908,8 @@ class PipelineService extends EventEmitter {
|
|||||||
titleInfo: reviewTitleInfo
|
titleInfo: reviewTitleInfo
|
||||||
}, resolvedPlaylistId, {
|
}, resolvedPlaylistId, {
|
||||||
expectedDurationSeconds: expectedDurationForCache,
|
expectedDurationSeconds: expectedDurationForCache,
|
||||||
expectedSizeBytes: expectedSizeForCache
|
expectedSizeBytes: expectedSizeForCache,
|
||||||
|
playlistAliases: playlistAliasesForResolve
|
||||||
})) {
|
})) {
|
||||||
const error = new Error(
|
const error = new Error(
|
||||||
`HandBrake Titel-Mapping inkonsistent für ${toPlaylistFile(resolvedPlaylistId)} (-t ${resolvedHandBrakeTitleId}).`
|
`HandBrake Titel-Mapping inkonsistent für ${toPlaylistFile(resolvedPlaylistId)} (-t ${resolvedHandBrakeTitleId}).`
|
||||||
@@ -18880,6 +19013,13 @@ class PipelineService extends EventEmitter {
|
|||||||
const normalizedTitles = (Array.isArray(review.titles) ? review.titles : [])
|
const normalizedTitles = (Array.isArray(review.titles) ? review.titles : [])
|
||||||
.slice(0, 1)
|
.slice(0, 1)
|
||||||
.map((title) => {
|
.map((title) => {
|
||||||
|
const normalizedHandBrakeTitleId = normalizeReviewTitleId(
|
||||||
|
resolvedHandBrakeTitleId
|
||||||
|
?? title?.handBrakeTitleId
|
||||||
|
?? title?.titleIndex
|
||||||
|
?? title?.id
|
||||||
|
?? selectedTitleForReview
|
||||||
|
);
|
||||||
const durationSeconds = Number(reviewTitleInfo?.durationSeconds || title?.durationSeconds || 0);
|
const durationSeconds = Number(reviewTitleInfo?.durationSeconds || title?.durationSeconds || 0);
|
||||||
const eligibleForEncode = durationSeconds >= minLengthSecondsForReview;
|
const eligibleForEncode = durationSeconds >= minLengthSecondsForReview;
|
||||||
const subtitleTracks = (Array.isArray(title?.subtitleTracks) ? title.subtitleTracks : []).map((track) => {
|
const subtitleTracks = (Array.isArray(title?.subtitleTracks) ? title.subtitleTracks : []).map((track) => {
|
||||||
@@ -18995,6 +19135,8 @@ class PipelineService extends EventEmitter {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...title,
|
...title,
|
||||||
|
id: normalizedHandBrakeTitleId || title?.id || null,
|
||||||
|
handBrakeTitleId: normalizedHandBrakeTitleId || null,
|
||||||
filePath: rawPath,
|
filePath: rawPath,
|
||||||
fileName: reviewTitleInfo?.fileName || title?.fileName || `Title #${selectedTitleForReview}`,
|
fileName: reviewTitleInfo?.fileName || title?.fileName || `Title #${selectedTitleForReview}`,
|
||||||
durationSeconds,
|
durationSeconds,
|
||||||
@@ -25141,6 +25283,25 @@ class PipelineService extends EventEmitter {
|
|||||||
);
|
);
|
||||||
const expectedDurationSecondsForResolve = Number(selectedEncodeTitle?.durationSeconds || 0) || null;
|
const expectedDurationSecondsForResolve = Number(selectedEncodeTitle?.durationSeconds || 0) || null;
|
||||||
const expectedSizeBytesForResolve = Number(selectedEncodeTitle?.sizeBytes || 0) || null;
|
const expectedSizeBytesForResolve = Number(selectedEncodeTitle?.sizeBytes || 0) || null;
|
||||||
|
const encodePlanPlaylistAnalysis = encodePlan?.playlistAnalysis && typeof encodePlan.playlistAnalysis === 'object'
|
||||||
|
? encodePlan.playlistAnalysis
|
||||||
|
: null;
|
||||||
|
const selectedPlaylistAliasesForResolve = selectedPlaylistId
|
||||||
|
? normalizePlaylistAliasIds(
|
||||||
|
[
|
||||||
|
...(Array.isArray(selectedEncodeTitle?.playlistAliases) ? selectedEncodeTitle.playlistAliases : []),
|
||||||
|
...getPlaylistAliasesForPlaylist(
|
||||||
|
encodePlanPlaylistAnalysis
|
||||||
|
|| playlistDecision?.playlistAnalysis
|
||||||
|
|| this.snapshot.context?.playlistAnalysis
|
||||||
|
|| this.safeParseJson(job?.makemkv_info_json)?.analyzeContext?.playlistAnalysis
|
||||||
|
|| null,
|
||||||
|
selectedPlaylistId
|
||||||
|
)
|
||||||
|
],
|
||||||
|
selectedPlaylistId
|
||||||
|
)
|
||||||
|
: [];
|
||||||
if (!handBrakeTitleId && selectedPlaylistId) {
|
if (!handBrakeTitleId && selectedPlaylistId) {
|
||||||
const titleResolveScanLines = [];
|
const titleResolveScanLines = [];
|
||||||
const titleResolveScanConfig = await settingsService.buildHandBrakeScanConfigForInput(inputPath, {
|
const titleResolveScanConfig = await settingsService.buildHandBrakeScanConfigForInput(inputPath, {
|
||||||
@@ -25172,7 +25333,8 @@ class PipelineService extends EventEmitter {
|
|||||||
handBrakeTitleId = resolveHandBrakeTitleIdForPlaylist(titleResolveParsed, selectedPlaylistId, {
|
handBrakeTitleId = resolveHandBrakeTitleIdForPlaylist(titleResolveParsed, selectedPlaylistId, {
|
||||||
expectedMakemkvTitleId: expectedMakemkvTitleIdForResolve,
|
expectedMakemkvTitleId: expectedMakemkvTitleIdForResolve,
|
||||||
expectedDurationSeconds: expectedDurationSecondsForResolve,
|
expectedDurationSeconds: expectedDurationSecondsForResolve,
|
||||||
expectedSizeBytes: expectedSizeBytesForResolve
|
expectedSizeBytes: expectedSizeBytesForResolve,
|
||||||
|
playlistAliases: selectedPlaylistAliasesForResolve
|
||||||
});
|
});
|
||||||
if (!handBrakeTitleId) {
|
if (!handBrakeTitleId) {
|
||||||
const knownPlaylists = listAvailableHandBrakePlaylists(titleResolveParsed);
|
const knownPlaylists = listAvailableHandBrakePlaylists(titleResolveParsed);
|
||||||
@@ -25235,6 +25397,29 @@ class PipelineService extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!handBrakeTitleId) {
|
||||||
|
const primaryPlanTitleId = normalizeReviewTitleId(encodePlan?.encodeInputTitleId);
|
||||||
|
const primaryPlanTitle = Array.isArray(encodePlan?.titles)
|
||||||
|
? (
|
||||||
|
encodePlan.titles.find((title) => normalizeReviewTitleId(title?.id) === primaryPlanTitleId)
|
||||||
|
|| encodePlan.titles.find((title) => Boolean(title?.selectedForEncode || title?.encodeInput))
|
||||||
|
|| null
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
const primaryTitleMappedHandBrakeId = normalizeReviewTitleId(
|
||||||
|
primaryPlanTitle?.handBrakeTitleId
|
||||||
|
?? primaryPlanTitle?.titleIndex
|
||||||
|
?? null
|
||||||
|
);
|
||||||
|
if (primaryTitleMappedHandBrakeId) {
|
||||||
|
handBrakeTitleId = primaryTitleMappedHandBrakeId;
|
||||||
|
await historyService.appendLog(
|
||||||
|
jobId,
|
||||||
|
'SYSTEM',
|
||||||
|
`HandBrake Titel-ID aus Primär-Titel übernommen: -t ${handBrakeTitleId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!handBrakeTitleId) {
|
if (!handBrakeTitleId) {
|
||||||
const fallbackTitleId = normalizeReviewTitleId(encodePlan?.handBrakeTitleId);
|
const fallbackTitleId = normalizeReviewTitleId(encodePlan?.handBrakeTitleId);
|
||||||
if (fallbackTitleId) {
|
if (fallbackTitleId) {
|
||||||
@@ -27403,7 +27588,9 @@ class PipelineService extends EventEmitter {
|
|||||||
const queueEntriesBefore = this.queueEntries.length;
|
const queueEntriesBefore = this.queueEntries.length;
|
||||||
this.queueEntries = this.queueEntries.filter((entry) => {
|
this.queueEntries = this.queueEntries.filter((entry) => {
|
||||||
const action = String(entry?.action || '').trim().toUpperCase();
|
const action = String(entry?.action || '').trim().toUpperCase();
|
||||||
|
const entryType = String(entry?.type || 'job').trim().toLowerCase();
|
||||||
const entryJobId = Number(entry?.jobId);
|
const entryJobId = Number(entry?.jobId);
|
||||||
|
const entrySourceJobId = this.normalizeQueueJobId(entry?.sourceJobId);
|
||||||
const isLegacyParentEntry = (
|
const isLegacyParentEntry = (
|
||||||
entryJobId === Number(normalizedParentJobId)
|
entryJobId === Number(normalizedParentJobId)
|
||||||
&& action === QUEUE_ACTIONS.START_SERIES_EPISODE
|
&& action === QUEUE_ACTIONS.START_SERIES_EPISODE
|
||||||
@@ -27414,7 +27601,15 @@ class PipelineService extends EventEmitter {
|
|||||||
&& entryJobId > 0
|
&& entryJobId > 0
|
||||||
&& seriesChildJobIdSet.has(entryJobId)
|
&& seriesChildJobIdSet.has(entryJobId)
|
||||||
);
|
);
|
||||||
return !(isLegacyParentEntry || isSeriesChildJobEntry);
|
const isDetachedSeriesAutomationEntry = (
|
||||||
|
(entryType === 'script' || entryType === 'chain')
|
||||||
|
&& entrySourceJobId
|
||||||
|
&& (
|
||||||
|
Number(entrySourceJobId) === Number(normalizedParentJobId)
|
||||||
|
|| seriesChildJobIdSet.has(Number(entrySourceJobId))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return !(isLegacyParentEntry || isSeriesChildJobEntry || isDetachedSeriesAutomationEntry);
|
||||||
});
|
});
|
||||||
const removedQueueEntries = Math.max(0, queueEntriesBefore - this.queueEntries.length);
|
const removedQueueEntries = Math.max(0, queueEntriesBefore - this.queueEntries.length);
|
||||||
|
|
||||||
@@ -27598,10 +27793,14 @@ class PipelineService extends EventEmitter {
|
|||||||
const queuedIndex = this.findQueueEntryIndexByJobId(normalizedJobId);
|
const queuedIndex = this.findQueueEntryIndexByJobId(normalizedJobId);
|
||||||
if (queuedIndex >= 0) {
|
if (queuedIndex >= 0) {
|
||||||
const [removed] = this.queueEntries.splice(queuedIndex, 1);
|
const [removed] = this.queueEntries.splice(queuedIndex, 1);
|
||||||
|
const removedDetachedEntries = this.removeDetachedQueueAutomationEntriesForJob(normalizedJobId);
|
||||||
await historyService.appendLog(
|
await historyService.appendLog(
|
||||||
normalizedJobId,
|
normalizedJobId,
|
||||||
'USER_ACTION',
|
'USER_ACTION',
|
||||||
`Aus Queue entfernt: ${QUEUE_ACTION_LABELS[removed?.action] || removed?.action || 'Aktion'}`
|
`Aus Queue entfernt: ${QUEUE_ACTION_LABELS[removed?.action] || removed?.action || 'Aktion'}`
|
||||||
|
+ (removedDetachedEntries > 0
|
||||||
|
? ` (inkl. ${removedDetachedEntries} verknüpften Skript/Ketten-Eintrag${removedDetachedEntries === 1 ? '' : 'en'}).`
|
||||||
|
: '')
|
||||||
);
|
);
|
||||||
await this.emitQueueChanged();
|
await this.emitQueueChanged();
|
||||||
return {
|
return {
|
||||||
@@ -28058,6 +28257,15 @@ class PipelineService extends EventEmitter {
|
|||||||
const job = await historyService.getJobById(jobId);
|
const job = await historyService.getJobById(jobId);
|
||||||
const title = job?.title || job?.detected_title || `Job #${jobId}`;
|
const title = job?.title || job?.detected_title || `Job #${jobId}`;
|
||||||
const finalState = isCancelled ? 'CANCELLED' : 'ERROR';
|
const finalState = isCancelled ? 'CANCELLED' : 'ERROR';
|
||||||
|
const removedDetachedQueueEntries = this.removeDetachedQueueAutomationEntriesForJob(jobId);
|
||||||
|
if (removedDetachedQueueEntries > 0) {
|
||||||
|
await historyService.appendLog(
|
||||||
|
jobId,
|
||||||
|
'SYSTEM',
|
||||||
|
`Queue-Cleanup: ${removedDetachedQueueEntries} verknüpfte Skript/Ketten-Eintrag${removedDetachedQueueEntries === 1 ? '' : 'e'} entfernt.`
|
||||||
|
);
|
||||||
|
await this.emitQueueChanged();
|
||||||
|
}
|
||||||
logger[isCancelled ? 'warn' : 'error']('job:failed', { jobId, stage, error: errorToMeta(error) });
|
logger[isCancelled ? 'warn' : 'error']('job:failed', { jobId, stage, error: errorToMeta(error) });
|
||||||
const makemkvInfo = this.safeParseJson(job?.makemkv_info_json);
|
const makemkvInfo = this.safeParseJson(job?.makemkv_info_json);
|
||||||
const encodePlan = this.safeParseJson(job?.encode_plan_json);
|
const encodePlan = this.safeParseJson(job?.encode_plan_json);
|
||||||
|
|||||||
@@ -135,6 +135,83 @@ function extractPlaylistMapping(line) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractPlaylistEqualityMapping(line) {
|
||||||
|
const raw = String(line || '');
|
||||||
|
if (!raw) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Robot output often ends with explicit args:
|
||||||
|
// ..., "00100.mpls","00091.mpls"
|
||||||
|
if (/^MSG:3309,/i.test(raw)) {
|
||||||
|
const quoted = [];
|
||||||
|
const regex = /"([^"]*)"/g;
|
||||||
|
let match = regex.exec(raw);
|
||||||
|
while (match) {
|
||||||
|
quoted.push(String(match[1] || '').trim());
|
||||||
|
match = regex.exec(raw);
|
||||||
|
}
|
||||||
|
if (quoted.length >= 2) {
|
||||||
|
const aliasId = normalizePlaylistId(quoted[quoted.length - 2]);
|
||||||
|
const canonicalId = normalizePlaylistId(quoted[quoted.length - 1]);
|
||||||
|
if (aliasId && canonicalId && aliasId !== canonicalId) {
|
||||||
|
return {
|
||||||
|
aliasId,
|
||||||
|
canonicalId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text fallback:
|
||||||
|
// "Title 00100.mpls is equal to title 00091.mpls and was skipped"
|
||||||
|
const textMatch = raw.match(/title\s+(\d{5}\.mpls)\s+is equal to title\s+(\d{5}\.mpls)/i);
|
||||||
|
if (textMatch) {
|
||||||
|
const aliasId = normalizePlaylistId(textMatch[1]);
|
||||||
|
const canonicalId = normalizePlaylistId(textMatch[2]);
|
||||||
|
if (aliasId && canonicalId && aliasId !== canonicalId) {
|
||||||
|
return {
|
||||||
|
aliasId,
|
||||||
|
canonicalId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildPlaylistAliasMap(lines) {
|
||||||
|
const aliasMap = new Map();
|
||||||
|
for (const line of lines || []) {
|
||||||
|
const mapping = extractPlaylistEqualityMapping(line);
|
||||||
|
if (!mapping) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const canonicalId = normalizePlaylistId(mapping.canonicalId);
|
||||||
|
const aliasId = normalizePlaylistId(mapping.aliasId);
|
||||||
|
if (!canonicalId || !aliasId || canonicalId === aliasId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!aliasMap.has(canonicalId)) {
|
||||||
|
aliasMap.set(canonicalId, new Set());
|
||||||
|
}
|
||||||
|
aliasMap.get(canonicalId).add(aliasId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = {};
|
||||||
|
for (const [canonicalId, aliasSet] of aliasMap.entries()) {
|
||||||
|
const aliases = Array.from(aliasSet)
|
||||||
|
.map((value) => normalizePlaylistId(value))
|
||||||
|
.filter(Boolean)
|
||||||
|
.filter((value) => value !== canonicalId)
|
||||||
|
.sort();
|
||||||
|
if (aliases.length > 0) {
|
||||||
|
normalized[canonicalId] = aliases;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
function parseAnalyzeTitles(lines) {
|
function parseAnalyzeTitles(lines) {
|
||||||
const titleMap = new Map();
|
const titleMap = new Map();
|
||||||
|
|
||||||
@@ -696,6 +773,18 @@ function extractPlaylistMismatchWarnings(titles) {
|
|||||||
|
|
||||||
function analyzePlaylistObfuscation(lines, minLengthMinutes = 60, options = {}) {
|
function analyzePlaylistObfuscation(lines, minLengthMinutes = 60, options = {}) {
|
||||||
const parsedTitles = parseAnalyzeTitles(lines);
|
const parsedTitles = parseAnalyzeTitles(lines);
|
||||||
|
const playlistAliasMap = buildPlaylistAliasMap(lines);
|
||||||
|
const parsedTitlesWithAliases = parsedTitles.map((item) => {
|
||||||
|
const playlistId = normalizePlaylistId(item?.playlistId);
|
||||||
|
const aliasesRaw = playlistId && Array.isArray(playlistAliasMap?.[playlistId])
|
||||||
|
? playlistAliasMap[playlistId]
|
||||||
|
: [];
|
||||||
|
const playlistAliases = uniqueOrdered(aliasesRaw.map((value) => normalizePlaylistId(value)).filter(Boolean));
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
playlistAliases
|
||||||
|
};
|
||||||
|
});
|
||||||
const reportedTitleCount = parseReportedTitleCount(lines);
|
const reportedTitleCount = parseReportedTitleCount(lines);
|
||||||
const minSeconds = Math.max(0, Math.round(Number(minLengthMinutes || 0) * 60));
|
const minSeconds = Math.max(0, Math.round(Number(minLengthMinutes || 0) * 60));
|
||||||
const durationSimilaritySeconds = Math.max(
|
const durationSimilaritySeconds = Math.max(
|
||||||
@@ -703,7 +792,7 @@ function analyzePlaylistObfuscation(lines, minLengthMinutes = 60, options = {})
|
|||||||
Math.round(Number(options.durationSimilaritySeconds || DEFAULT_DURATION_SIMILARITY_SECONDS))
|
Math.round(Number(options.durationSimilaritySeconds || DEFAULT_DURATION_SIMILARITY_SECONDS))
|
||||||
);
|
);
|
||||||
|
|
||||||
const candidatesRaw = parsedTitles
|
const candidatesRaw = parsedTitlesWithAliases
|
||||||
.filter((item) => Number(item.durationSeconds || 0) >= minSeconds)
|
.filter((item) => Number(item.durationSeconds || 0) >= minSeconds)
|
||||||
.sort((a, b) => b.durationSeconds - a.durationSeconds || b.sizeBytes - a.sizeBytes || a.titleId - b.titleId);
|
.sort((a, b) => b.durationSeconds - a.durationSeconds || b.sizeBytes - a.sizeBytes || a.titleId - b.titleId);
|
||||||
const candidates = suppressRawMirrorCandidates(candidatesRaw)
|
const candidates = suppressRawMirrorCandidates(candidatesRaw)
|
||||||
@@ -724,7 +813,7 @@ function analyzePlaylistObfuscation(lines, minLengthMinutes = 60, options = {})
|
|||||||
const recommendation = evaluatedCandidates[0] || null;
|
const recommendation = evaluatedCandidates[0] || null;
|
||||||
const candidatePlaylists = manualDecisionRequired ? candidatePlaylistsAll : [];
|
const candidatePlaylists = manualDecisionRequired ? candidatePlaylistsAll : [];
|
||||||
const playlistSegments = buildPlaylistSegmentMap(decisionPool);
|
const playlistSegments = buildPlaylistSegmentMap(decisionPool);
|
||||||
const playlistToTitleId = buildPlaylistToTitleIdMap(parsedTitles);
|
const playlistToTitleId = buildPlaylistToTitleIdMap(parsedTitlesWithAliases);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
generatedAt: new Date().toISOString(),
|
generatedAt: new Date().toISOString(),
|
||||||
@@ -732,7 +821,7 @@ function analyzePlaylistObfuscation(lines, minLengthMinutes = 60, options = {})
|
|||||||
minLengthMinutes: Number(minLengthMinutes || 0),
|
minLengthMinutes: Number(minLengthMinutes || 0),
|
||||||
minLengthSeconds: minSeconds,
|
minLengthSeconds: minSeconds,
|
||||||
durationSimilaritySeconds,
|
durationSimilaritySeconds,
|
||||||
titles: parsedTitles,
|
titles: parsedTitlesWithAliases,
|
||||||
candidates,
|
candidates,
|
||||||
duplicateDurationGroups: similarityGroups,
|
duplicateDurationGroups: similarityGroups,
|
||||||
obfuscationDetected,
|
obfuscationDetected,
|
||||||
@@ -743,6 +832,7 @@ function analyzePlaylistObfuscation(lines, minLengthMinutes = 60, options = {})
|
|||||||
candidatePlaylists,
|
candidatePlaylists,
|
||||||
candidatePlaylistFiles: candidatePlaylists.map((item) => `${item}.mpls`),
|
candidatePlaylistFiles: candidatePlaylists.map((item) => `${item}.mpls`),
|
||||||
playlistToTitleId,
|
playlistToTitleId,
|
||||||
|
playlistAliasMap,
|
||||||
recommendation: recommendation
|
recommendation: recommendation
|
||||||
? {
|
? {
|
||||||
titleId: recommendation.titleId,
|
titleId: recommendation.titleId,
|
||||||
@@ -765,7 +855,7 @@ function analyzePlaylistObfuscation(lines, minLengthMinutes = 60, options = {})
|
|||||||
...(reportedTitleCount !== null && reportedTitleCount !== parsedTitles.length
|
...(reportedTitleCount !== null && reportedTitleCount !== parsedTitles.length
|
||||||
? [`Titel-Anzahl abweichend: TCOUNT=${reportedTitleCount}, geparst=${parsedTitles.length}`]
|
? [`Titel-Anzahl abweichend: TCOUNT=${reportedTitleCount}, geparst=${parsedTitles.length}`]
|
||||||
: []),
|
: []),
|
||||||
...extractPlaylistMismatchWarnings(parsedTitles)
|
...extractPlaylistMismatchWarnings(parsedTitlesWithAliases)
|
||||||
].slice(0, 60)
|
].slice(0, 60)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "0.16.1-5",
|
"version": "0.16.1-6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "0.16.1-5",
|
"version": "0.16.1-6",
|
||||||
"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.16.1-5",
|
"version": "0.16.1-6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -2527,6 +2527,7 @@ export default function MediaInfoReviewPanel({
|
|||||||
<p>Keine Titel analysiert.</p>
|
<p>Keine Titel analysiert.</p>
|
||||||
) : displayTitles.map((title) => {
|
) : displayTitles.map((title) => {
|
||||||
const normalizedTitleId = normalizeTitleId(title.id);
|
const normalizedTitleId = normalizeTitleId(title.id);
|
||||||
|
const displayTitleId = normalizeTitleId(title?.handBrakeTitleId) || normalizedTitleId;
|
||||||
const titleChecked = allowTitleSelection
|
const titleChecked = allowTitleSelection
|
||||||
? (selectedTitleIdSet.size > 0
|
? (selectedTitleIdSet.size > 0
|
||||||
? selectedTitleIdSet.has(String(normalizedTitleId))
|
? selectedTitleIdSet.has(String(normalizedTitleId))
|
||||||
@@ -2634,7 +2635,7 @@ export default function MediaInfoReviewPanel({
|
|||||||
disabled={!allowTitleSelection}
|
disabled={!allowTitleSelection}
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
#{title.id} | {title.fileName} | {formatDuration(title.durationMinutes)}
|
#{displayTitleId ?? '-'} | {title.fileName} | {formatDuration(title.durationMinutes)}
|
||||||
{audioCount > 0 ? ` | Audio: ${audioCount}` : ''}
|
{audioCount > 0 ? ` | Audio: ${audioCount}` : ''}
|
||||||
{subtitleCount > 0 ? ` | Untertitel: ${subtitleCount}` : ''}
|
{subtitleCount > 0 ? ` | Untertitel: ${subtitleCount}` : ''}
|
||||||
{title.encodeInput ? ' | Encode-Input' : ''}
|
{title.encodeInput ? ' | Encode-Input' : ''}
|
||||||
@@ -2692,7 +2693,7 @@ export default function MediaInfoReviewPanel({
|
|||||||
<summary>Tonspuren und Untertitel</summary>
|
<summary>Tonspuren und Untertitel</summary>
|
||||||
<div className="media-track-grid">
|
<div className="media-track-grid">
|
||||||
<TrackList
|
<TrackList
|
||||||
title={`Tonspuren (Titel #${title.id})`}
|
title={`Tonspuren (Titel #${displayTitleId ?? '-'})`}
|
||||||
tracks={title.audioTracks || []}
|
tracks={title.audioTracks || []}
|
||||||
type="audio"
|
type="audio"
|
||||||
allowSelection={allowTrackSelectionForTitle}
|
allowSelection={allowTrackSelectionForTitle}
|
||||||
@@ -2706,7 +2707,7 @@ export default function MediaInfoReviewPanel({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TrackList
|
<TrackList
|
||||||
title={`Subtitles (Titel #${title.id})`}
|
title={`Subtitles (Titel #${displayTitleId ?? '-'})`}
|
||||||
tracks={allowTrackSelectionForTitle
|
tracks={allowTrackSelectionForTitle
|
||||||
? subtitleTracks.filter((track) => !isBurnedSubtitleTrack(track))
|
? subtitleTracks.filter((track) => !isBurnedSubtitleTrack(track))
|
||||||
: subtitleTracks}
|
: subtitleTracks}
|
||||||
@@ -2727,7 +2728,7 @@ export default function MediaInfoReviewPanel({
|
|||||||
) : (!compactTitleSelection ? (
|
) : (!compactTitleSelection ? (
|
||||||
<div className="media-track-grid">
|
<div className="media-track-grid">
|
||||||
<TrackList
|
<TrackList
|
||||||
title={`Tonspuren (Titel #${title.id})`}
|
title={`Tonspuren (Titel #${displayTitleId ?? '-'})`}
|
||||||
tracks={title.audioTracks || []}
|
tracks={title.audioTracks || []}
|
||||||
type="audio"
|
type="audio"
|
||||||
allowSelection={allowTrackSelectionForTitle}
|
allowSelection={allowTrackSelectionForTitle}
|
||||||
@@ -2741,7 +2742,7 @@ export default function MediaInfoReviewPanel({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TrackList
|
<TrackList
|
||||||
title={`Subtitles (Titel #${title.id})`}
|
title={`Subtitles (Titel #${displayTitleId ?? '-'})`}
|
||||||
tracks={allowTrackSelectionForTitle
|
tracks={allowTrackSelectionForTitle
|
||||||
? subtitleTracks.filter((track) => !isBurnedSubtitleTrack(track))
|
? subtitleTracks.filter((track) => !isBurnedSubtitleTrack(track))
|
||||||
: subtitleTracks}
|
: subtitleTracks}
|
||||||
@@ -2817,6 +2818,7 @@ export default function MediaInfoReviewPanel({
|
|||||||
<small>Keine Titel in der Referenz-Disc gefunden.</small>
|
<small>Keine Titel in der Referenz-Disc gefunden.</small>
|
||||||
) : comparisonTitles.map((title) => {
|
) : comparisonTitles.map((title) => {
|
||||||
const normalizedTitleId = normalizeTitleId(title?.id);
|
const normalizedTitleId = normalizeTitleId(title?.id);
|
||||||
|
const displayTitleId = normalizeTitleId(title?.handBrakeTitleId) || normalizedTitleId;
|
||||||
const titleChecked = normalizedTitleId !== null
|
const titleChecked = normalizedTitleId !== null
|
||||||
&& comparisonSelectedTitleSet.has(String(normalizedTitleId));
|
&& comparisonSelectedTitleSet.has(String(normalizedTitleId));
|
||||||
const titleIsPrimary = normalizedTitleId !== null
|
const titleIsPrimary = normalizedTitleId !== null
|
||||||
@@ -2839,7 +2841,7 @@ export default function MediaInfoReviewPanel({
|
|||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
<span>
|
<span>
|
||||||
#{title.id} | {title.fileName} | {formatDuration(title.durationMinutes)}
|
#{displayTitleId ?? '-'} | {title.fileName} | {formatDuration(title.durationMinutes)}
|
||||||
{audioCount > 0 ? ` | Audio: ${audioCount}` : ''}
|
{audioCount > 0 ? ` | Audio: ${audioCount}` : ''}
|
||||||
{subtitleCount > 0 ? ` | Untertitel: ${subtitleCount}` : ''}
|
{subtitleCount > 0 ? ` | Untertitel: ${subtitleCount}` : ''}
|
||||||
{title.encodeInput ? ' | Encode-Input' : ''}
|
{title.encodeInput ? ' | Encode-Input' : ''}
|
||||||
@@ -2872,13 +2874,13 @@ export default function MediaInfoReviewPanel({
|
|||||||
|
|
||||||
<div className="media-track-grid">
|
<div className="media-track-grid">
|
||||||
<TrackList
|
<TrackList
|
||||||
title={`Tonspuren (Titel #${title.id})`}
|
title={`Tonspuren (Titel #${displayTitleId ?? '-'})`}
|
||||||
tracks={title.audioTracks || []}
|
tracks={title.audioTracks || []}
|
||||||
type="audio"
|
type="audio"
|
||||||
allowSelection={false}
|
allowSelection={false}
|
||||||
/>
|
/>
|
||||||
<TrackList
|
<TrackList
|
||||||
title={`Subtitles (Titel #${title.id})`}
|
title={`Subtitles (Titel #${displayTitleId ?? '-'})`}
|
||||||
tracks={subtitleTracks}
|
tracks={subtitleTracks}
|
||||||
type="subtitle"
|
type="subtitle"
|
||||||
allowSelection={false}
|
allowSelection={false}
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"version": "0.16.1-5",
|
"version": "0.16.1-6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"version": "0.16.1-5",
|
"version": "0.16.1-6",
|
||||||
"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.16.1-5",
|
"version": "0.16.1-6",
|
||||||
"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