0.13.1-10 Misc fixes
This commit is contained in:
@@ -669,15 +669,16 @@ function formatTemplateTwoDigit(value) {
|
||||
return String(value || '').trim() || String(numeric);
|
||||
}
|
||||
|
||||
function resolveEpisodeAssignmentRange(assignment = null, title = null, fallbackEpisodeNumber = 1) {
|
||||
function resolveEpisodeAssignmentRange(assignment = null, title = null, fallbackEpisodeNumber = 1, options = {}) {
|
||||
const assignmentSource = assignment && typeof assignment === 'object' ? assignment : {};
|
||||
const titleSource = title && typeof title === 'object' ? title : {};
|
||||
const allowTitleEpisodeNumber = options?.allowTitleEpisodeNumber !== false;
|
||||
const fallbackNumber = normalizePositiveInt(fallbackEpisodeNumber) || 1;
|
||||
const episodeStart = normalizePositiveInt(
|
||||
assignmentSource?.episodeNumberStart
|
||||
?? assignmentSource?.episodeNoStart
|
||||
?? assignmentSource?.episodeNumber
|
||||
?? titleSource?.episodeNumber
|
||||
?? (allowTitleEpisodeNumber ? titleSource?.episodeNumber : null)
|
||||
?? fallbackNumber
|
||||
) || fallbackNumber;
|
||||
const explicitEpisodeEnd = normalizePositiveInt(
|
||||
@@ -939,7 +940,135 @@ function resolveEffectiveEpisodeRangeForTitle(assignment = null, title = null, a
|
||||
};
|
||||
}
|
||||
|
||||
function buildDvdSeriesEpisodeOutputPathPreview(settings, metadata, title, assignment = null, fallbackJobId = null, allTitles = []) {
|
||||
function resolveSeriesTitleOrderForPreview(allTitles = [], selectedTitleIds = [], targetTitleId = null) {
|
||||
const explicitSelectedIds = normalizeTitleIdList(selectedTitleIds);
|
||||
const fallbackSelectedIds = explicitSelectedIds.length > 0
|
||||
? explicitSelectedIds
|
||||
: normalizeTitleIdList(
|
||||
(Array.isArray(allTitles) ? allTitles : [])
|
||||
.filter((item) => Boolean(item?.selectedForEncode || item?.encodeInput))
|
||||
.map((item) => item?.id)
|
||||
);
|
||||
let ordered = fallbackSelectedIds;
|
||||
const normalizedTarget = normalizeTitleId(targetTitleId);
|
||||
if (normalizedTarget && !ordered.some((id) => Number(id) === Number(normalizedTarget))) {
|
||||
ordered = [...ordered, Number(normalizedTarget)];
|
||||
}
|
||||
if (ordered.length === 0 && normalizedTarget) {
|
||||
ordered = [Number(normalizedTarget)];
|
||||
}
|
||||
return ordered;
|
||||
}
|
||||
|
||||
function resolveEpisodeRangeForTitleFromAssignments({
|
||||
assignmentByTitle = {},
|
||||
allTitles = [],
|
||||
selectedTitleIds = [],
|
||||
targetTitle = null,
|
||||
fallbackEpisodeNumber = 1
|
||||
}) {
|
||||
const titles = Array.isArray(allTitles) ? allTitles : [];
|
||||
const titlesById = new Map();
|
||||
for (const item of titles) {
|
||||
const id = normalizeTitleId(item?.id);
|
||||
if (!id) {
|
||||
continue;
|
||||
}
|
||||
titlesById.set(Number(id), item);
|
||||
}
|
||||
const targetTitleId = normalizeTitleId(targetTitle?.id || null);
|
||||
const orderedTitleIds = resolveSeriesTitleOrderForPreview(titles, selectedTitleIds, targetTitleId);
|
||||
const fallbackStart = normalizePositiveInt(fallbackEpisodeNumber) || 1;
|
||||
let cursor = fallbackStart;
|
||||
let targetRange = null;
|
||||
|
||||
for (const rawId of orderedTitleIds) {
|
||||
const currentId = normalizeTitleId(rawId);
|
||||
if (!currentId) {
|
||||
continue;
|
||||
}
|
||||
const assignment = assignmentByTitle?.[currentId] || assignmentByTitle?.[String(currentId)] || null;
|
||||
const currentTitle = titlesById.get(Number(currentId)) || null;
|
||||
const hasExplicitRange = hasExplicitEpisodeAssignmentRange(assignment);
|
||||
const range = resolveEpisodeAssignmentRange(
|
||||
assignment,
|
||||
currentTitle,
|
||||
cursor,
|
||||
{ allowTitleEpisodeNumber: hasExplicitRange }
|
||||
);
|
||||
const normalizedEnd = normalizePositiveInt(range?.end);
|
||||
if (normalizedEnd !== null) {
|
||||
cursor = normalizedEnd + 1;
|
||||
}
|
||||
if (targetTitleId && Number(currentId) === Number(targetTitleId)) {
|
||||
targetRange = range;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetRange) {
|
||||
const assignment = targetTitleId
|
||||
? (assignmentByTitle?.[targetTitleId] || assignmentByTitle?.[String(targetTitleId)] || null)
|
||||
: null;
|
||||
const hasExplicitRange = hasExplicitEpisodeAssignmentRange(assignment);
|
||||
targetRange = resolveEpisodeAssignmentRange(
|
||||
assignment,
|
||||
targetTitle,
|
||||
cursor,
|
||||
{ allowTitleEpisodeNumber: hasExplicitRange }
|
||||
);
|
||||
}
|
||||
|
||||
return targetRange;
|
||||
}
|
||||
|
||||
function templateUsesLeadingZeroForToken(template, token) {
|
||||
const source = String(template || '').trim();
|
||||
const normalizedToken = String(token || '').trim();
|
||||
if (!source || !normalizedToken) {
|
||||
return false;
|
||||
}
|
||||
const pattern = new RegExp(`\\{\\s*0\\s*:\\s*${normalizedToken}\\s*\\}`, 'i');
|
||||
return pattern.test(source);
|
||||
}
|
||||
|
||||
function formatEpisodeNumberByTemplate(rawValue, template, token = 'episodeNr') {
|
||||
const numeric = Number(rawValue);
|
||||
if (!Number.isFinite(numeric) || numeric <= 0) {
|
||||
return String(rawValue || '').trim() || '0';
|
||||
}
|
||||
if (!Number.isInteger(numeric)) {
|
||||
return String(numeric);
|
||||
}
|
||||
const normalized = String(Math.trunc(numeric));
|
||||
if (templateUsesLeadingZeroForToken(template, token)) {
|
||||
return normalized.padStart(2, '0');
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function buildFallbackEpisodeTitleForOutput(episodeRange = null, template = '') {
|
||||
const start = normalizePositiveInt(episodeRange?.start) || 1;
|
||||
const end = normalizePositiveInt(episodeRange?.end) || start;
|
||||
if (end > start) {
|
||||
const startToken = formatEpisodeNumberByTemplate(start, template, 'episodeNr');
|
||||
const endToken = formatEpisodeNumberByTemplate(end, template, 'episodeNr');
|
||||
return `Folge ${startToken}-${endToken}`;
|
||||
}
|
||||
const token = formatEpisodeNumberByTemplate(start, template, 'episodeNr');
|
||||
return `Folge ${token}`;
|
||||
}
|
||||
|
||||
function buildDvdSeriesEpisodeOutputPathPreview(
|
||||
settings,
|
||||
metadata,
|
||||
title,
|
||||
assignment = null,
|
||||
fallbackJobId = null,
|
||||
allTitles = [],
|
||||
assignmentByTitle = {},
|
||||
selectedTitleIds = []
|
||||
) {
|
||||
const seriesRootDir = String(
|
||||
settings?.series_dir_dvd
|
||||
|| settings?.movie_dir_dvd
|
||||
@@ -953,15 +1082,28 @@ function buildDvdSeriesEpisodeOutputPathPreview(settings, metadata, title, assig
|
||||
const seasonNumber = normalizePositiveInt(
|
||||
assignment?.seasonNumber ?? metadata?.seasonNumber ?? title?.seasonNumber ?? 1
|
||||
) || 1;
|
||||
const normalizedSelectedIds = normalizeTitleIdList(selectedTitleIds);
|
||||
const targetTitleId = normalizeTitleId(title?.id);
|
||||
const fallbackEpisodeNumber = targetTitleId && normalizedSelectedIds.length > 0
|
||||
? (normalizedSelectedIds.findIndex((id) => Number(id) === Number(targetTitleId)) + 1)
|
||||
: 1;
|
||||
const inferredRange = resolveEpisodeRangeForTitleFromAssignments({
|
||||
assignmentByTitle,
|
||||
allTitles,
|
||||
selectedTitleIds: normalizedSelectedIds,
|
||||
targetTitle: title,
|
||||
fallbackEpisodeNumber: fallbackEpisodeNumber > 0 ? fallbackEpisodeNumber : 1
|
||||
});
|
||||
const episodeRange = resolveEffectiveEpisodeRangeForTitle(
|
||||
assignment,
|
||||
assignment || {},
|
||||
title,
|
||||
allTitles,
|
||||
title?.id || 1
|
||||
inferredRange?.start || (fallbackEpisodeNumber > 0 ? fallbackEpisodeNumber : 1)
|
||||
);
|
||||
const episodeNumber = episodeRange.start;
|
||||
const episodeRangeToken = buildEpisodeRangeToken(episodeRange.start, episodeRange.end);
|
||||
const partsToken = buildEpisodePartsToken(episodeRange);
|
||||
const effectiveEpisodeRange = episodeRange;
|
||||
const episodeNumber = effectiveEpisodeRange.start;
|
||||
const episodeRangeToken = buildEpisodeRangeToken(effectiveEpisodeRange.start, effectiveEpisodeRange.end);
|
||||
const partsToken = buildEpisodePartsToken(effectiveEpisodeRange);
|
||||
const discNumber = normalizePositiveInt(metadata?.discNumber ?? assignment?.discNumber ?? null);
|
||||
const seriesTitleRaw = String(
|
||||
metadata?.title
|
||||
@@ -969,16 +1111,6 @@ function buildDvdSeriesEpisodeOutputPathPreview(settings, metadata, title, assig
|
||||
|| (fallbackJobId ? `job-${fallbackJobId}` : 'series')
|
||||
).trim();
|
||||
const seriesTitle = seriesTitleRaw || 'series';
|
||||
const episodeTitleRaw = String(
|
||||
assignment?.episodeTitle
|
||||
|| title?.episodeTitle
|
||||
|| title?.fileName
|
||||
|| `Episode ${episodeNumber}`
|
||||
).trim();
|
||||
const episodeTitle = normalizeEpisodeTitleForOutput(
|
||||
episodeTitleRaw || `Episode ${episodeRangeToken}`,
|
||||
episodeRange
|
||||
);
|
||||
const year = String(metadata?.year || new Date().getFullYear()).trim();
|
||||
const language = normalizeSeriesLanguage(settings?.dvd_series_language || metadata?.language || null);
|
||||
|
||||
@@ -992,21 +1124,28 @@ function buildDvdSeriesEpisodeOutputPathPreview(settings, metadata, title, assig
|
||||
).trim();
|
||||
const singleTemplate = singleTemplateRaw || '{seriesTitle}/Staffel {seasonNr}/S{seasonNr}E{episodeNr} - {episodeTitle}';
|
||||
const multiTemplate = multiTemplateRaw || '{seriesTitle}/Staffel {seasonNr}/S{0:seasonNr}E{episodeRange} - {episodeTitle} (Teil {parts})';
|
||||
const template = episodeRange.isMulti ? multiTemplate : singleTemplate;
|
||||
const template = effectiveEpisodeRange.isMulti ? multiTemplate : singleTemplate;
|
||||
const fallbackEpisodeTitle = buildFallbackEpisodeTitleForOutput(effectiveEpisodeRange, template);
|
||||
const episodeTitleRaw = String(
|
||||
assignment?.episodeTitle
|
||||
|| title?.episodeTitle
|
||||
|| fallbackEpisodeTitle
|
||||
).trim();
|
||||
const rendered = renderTemplate(template, {
|
||||
seriesTitle,
|
||||
seasonNr: seasonNumber,
|
||||
seasonNo: String(seasonNumber),
|
||||
episodeNr: episodeNumber,
|
||||
episodeNo: String(episodeNumber),
|
||||
episodeNoStart: episodeRange.start,
|
||||
episodeNoEnd: episodeRange.end,
|
||||
episodeNumberStart: episodeRange.start,
|
||||
episodeNumberEnd: episodeRange.end,
|
||||
episodeNoStart: effectiveEpisodeRange.start,
|
||||
episodeNoEnd: effectiveEpisodeRange.end,
|
||||
episodeNumberStart: effectiveEpisodeRange.start,
|
||||
episodeNumberEnd: effectiveEpisodeRange.end,
|
||||
episodeRange: episodeRangeToken,
|
||||
episodeSpan: episodeRange.span,
|
||||
episodeSpan: effectiveEpisodeRange.span,
|
||||
parts: partsToken,
|
||||
episodeTitle,
|
||||
episodeTitle: normalizeEpisodeTitleForOutput(
|
||||
episodeTitleRaw || fallbackEpisodeTitle,
|
||||
effectiveEpisodeRange
|
||||
) || fallbackEpisodeTitle,
|
||||
discNr: discNumber || '',
|
||||
year,
|
||||
language
|
||||
@@ -2380,14 +2519,24 @@ export default function PipelineStatusCard({
|
||||
title,
|
||||
assignment,
|
||||
retryJobId,
|
||||
Array.isArray(mediaInfoReview?.titles) ? mediaInfoReview.titles : []
|
||||
Array.isArray(mediaInfoReview?.titles) ? mediaInfoReview.titles : [],
|
||||
episodeAssignmentsByTitle,
|
||||
selectedEncodeTitleIds
|
||||
);
|
||||
if (path) {
|
||||
map[titleId] = path;
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}, [isSeriesDvdReview, mediaInfoReview?.titles, episodeAssignmentsByTitle, settingsMap, selectedMetadata, retryJobId]);
|
||||
}, [
|
||||
isSeriesDvdReview,
|
||||
mediaInfoReview?.titles,
|
||||
episodeAssignmentsByTitle,
|
||||
settingsMap,
|
||||
selectedMetadata,
|
||||
retryJobId,
|
||||
selectedEncodeTitleIds
|
||||
]);
|
||||
const multiEpisodeReviewWarnings = useMemo(() => {
|
||||
if (!isSeriesDvdReview) {
|
||||
return [];
|
||||
|
||||
Reference in New Issue
Block a user