0.13.1-2 Series RC
This commit is contained in:
@@ -515,6 +515,24 @@ function buildEpisodeReferenceKey(entry = {}) {
|
||||
return normalized.episodeNumber ? `ep:${normalized.episodeNumber}` : null;
|
||||
}
|
||||
|
||||
function collectEpisodeReferenceKeys(entry = {}) {
|
||||
const normalized = normalizeEpisodeReference(entry);
|
||||
if (!normalized) {
|
||||
return [];
|
||||
}
|
||||
const keys = [];
|
||||
if (normalized.episodeId) {
|
||||
keys.push(`id:${normalized.episodeId}`);
|
||||
}
|
||||
if (normalized.seasonNumber && normalized.episodeNumber) {
|
||||
keys.push(`se:${normalized.seasonNumber}:${normalized.episodeNumber}`);
|
||||
}
|
||||
if (normalized.episodeNumber) {
|
||||
keys.push(`ep:${normalized.episodeNumber}`);
|
||||
}
|
||||
return Array.from(new Set(keys));
|
||||
}
|
||||
|
||||
function isSeriesBatchEpisodePlan(plan = null) {
|
||||
const source = plan && typeof plan === 'object' ? plan : {};
|
||||
if (Boolean(source?.seriesBatchChild) || Boolean(source?.seriesBatchVirtualEpisode)) {
|
||||
@@ -1218,8 +1236,11 @@ export default function PipelineStatusCard({
|
||||
}
|
||||
const usedKeySet = new Set(containerUsedEpisodeKeys);
|
||||
return normalizedRows.filter((episode) => {
|
||||
const key = buildEpisodeReferenceKey(episode);
|
||||
return !key || !usedKeySet.has(key);
|
||||
const keys = collectEpisodeReferenceKeys(episode);
|
||||
if (keys.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return keys.every((key) => !usedKeySet.has(key));
|
||||
});
|
||||
}, [selectedMetadata?.episodes, containerSeasonEpisodes, containerUsedEpisodeKeys]);
|
||||
|
||||
@@ -1411,40 +1432,38 @@ export default function PipelineStatusCard({
|
||||
});
|
||||
}, [selectedEncodeTitleId, selectedEncodeTitleIds, mediaInfoReview?.generatedAt]);
|
||||
|
||||
const applyEpisodeFillFrom = (startEpisodeRef = null) => {
|
||||
const resolveEpisodeFillContext = () => {
|
||||
const episodeRows = Array.isArray(selectedMetadataEpisodes) ? selectedMetadataEpisodes : [];
|
||||
if (episodeRows.length === 0) {
|
||||
return;
|
||||
}
|
||||
const reviewTitles = Array.isArray(mediaInfoReview?.titles) ? mediaInfoReview.titles : [];
|
||||
const selectedTitleIds = normalizeTitleIdList([
|
||||
...normalizeTitleIdList(selectedEncodeTitleIds),
|
||||
...(Array.isArray(mediaInfoReview?.selectedTitleIds) ? mediaInfoReview.selectedTitleIds : []),
|
||||
...reviewTitles.filter((title) => Boolean(title?.selectedForEncode)).map((title) => title?.id)
|
||||
]);
|
||||
if (selectedTitleIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
const orderedTitles = (Array.isArray(mediaInfoReview?.titles) ? mediaInfoReview.titles : [])
|
||||
const orderedTitles = reviewTitles
|
||||
.map((title) => ({
|
||||
id: normalizeTitleId(title?.id),
|
||||
title
|
||||
}))
|
||||
.filter((row) => row.id !== null);
|
||||
const orderedSelectedTitleRows = orderedTitles.filter((row) => selectedTitleIds.includes(row.id));
|
||||
const orderedSelectedTitleRows = selectedTitleIds.length > 0
|
||||
? orderedTitles.filter((row) => selectedTitleIds.includes(row.id))
|
||||
: [];
|
||||
const fallbackSelectedTitleRows = orderedSelectedTitleRows.length > 0
|
||||
? orderedSelectedTitleRows
|
||||
: orderedTitles.filter((row) => selectedTitleIds.includes(row.id));
|
||||
if (fallbackSelectedTitleRows.length === 0) {
|
||||
return;
|
||||
}
|
||||
const rawFillRef = String(startEpisodeRef || '').trim();
|
||||
const normalizedRefMatch = rawFillRef.match(/^fill:([^:]+):title:\d+$/i);
|
||||
const normalizedRef = normalizedRefMatch && normalizedRefMatch[1]
|
||||
? String(normalizedRefMatch[1]).trim()
|
||||
: rawFillRef;
|
||||
return {
|
||||
episodeRows,
|
||||
reviewTitles,
|
||||
selectedTitleIds,
|
||||
fallbackSelectedTitleRows
|
||||
};
|
||||
};
|
||||
|
||||
const allReviewTitles = fallbackSelectedTitleRows.map((item) => item?.title).filter(Boolean);
|
||||
const buildGroupedEpisodeUnits = (episodeRows = [], selectedTitleRows = [], reviewTitles = []) => {
|
||||
const rows = Array.isArray(episodeRows) ? episodeRows : [];
|
||||
const selectedRows = Array.isArray(selectedTitleRows) ? selectedTitleRows : [];
|
||||
const allReviewTitles = Array.isArray(reviewTitles) ? reviewTitles : [];
|
||||
const normalizeEpisodeStart = (episode) => normalizePositiveInt(
|
||||
episode?.episodeNumberStart
|
||||
?? episode?.episodeNoStart
|
||||
@@ -1472,10 +1491,10 @@ export default function PipelineStatusCard({
|
||||
return start;
|
||||
};
|
||||
const titleAnchorsByEpisodeIndex = new Map();
|
||||
if (episodeRows.length > 0) {
|
||||
if (rows.length > 0) {
|
||||
let episodeAnchorCursor = 0;
|
||||
for (const selectedTitleRow of fallbackSelectedTitleRows) {
|
||||
if (episodeAnchorCursor >= episodeRows.length) {
|
||||
for (const selectedTitleRow of selectedRows) {
|
||||
if (episodeAnchorCursor >= rows.length) {
|
||||
break;
|
||||
}
|
||||
const explicitSpan = normalizePositiveInt(
|
||||
@@ -1485,7 +1504,7 @@ export default function PipelineStatusCard({
|
||||
);
|
||||
const estimatedSpan = estimateSeriesEpisodeSpanForTitle(selectedTitleRow?.title, allReviewTitles);
|
||||
const span = Math.max(1, explicitSpan || 1, estimatedSpan || 1);
|
||||
const safeSpan = Math.max(1, Math.min(span, episodeRows.length - episodeAnchorCursor));
|
||||
const safeSpan = Math.max(1, Math.min(span, rows.length - episodeAnchorCursor));
|
||||
titleAnchorsByEpisodeIndex.set(episodeAnchorCursor, { span: safeSpan });
|
||||
episodeAnchorCursor += safeSpan;
|
||||
}
|
||||
@@ -1493,8 +1512,8 @@ export default function PipelineStatusCard({
|
||||
|
||||
const groupedEpisodeUnits = [];
|
||||
let sourceEpisodeIndex = 0;
|
||||
while (sourceEpisodeIndex < episodeRows.length) {
|
||||
const startRow = episodeRows[sourceEpisodeIndex];
|
||||
while (sourceEpisodeIndex < rows.length) {
|
||||
const startRow = rows[sourceEpisodeIndex];
|
||||
const rangeStart = normalizeEpisodeStart(startRow)
|
||||
?? normalizePositiveInt(startRow?.episodeNumber)
|
||||
?? null;
|
||||
@@ -1522,8 +1541,8 @@ export default function PipelineStatusCard({
|
||||
const anchor = titleAnchorsByEpisodeIndex.get(sourceEpisodeIndex) || null;
|
||||
const anchorSpan = normalizePositiveInt(anchor?.span) || 1;
|
||||
if (anchorSpan > 1) {
|
||||
const endIndex = Math.min(episodeRows.length - 1, sourceEpisodeIndex + anchorSpan - 1);
|
||||
const endRow = episodeRows[endIndex] || startRow;
|
||||
const endIndex = Math.min(rows.length - 1, sourceEpisodeIndex + anchorSpan - 1);
|
||||
const endRow = rows[endIndex] || startRow;
|
||||
const endRowStart = normalizeEpisodeStart(endRow)
|
||||
?? normalizePositiveInt(endRow?.episodeNumber)
|
||||
?? null;
|
||||
@@ -1555,6 +1574,29 @@ export default function PipelineStatusCard({
|
||||
sourceEpisodeIndex += 1;
|
||||
}
|
||||
|
||||
return groupedEpisodeUnits;
|
||||
};
|
||||
|
||||
const applyEpisodeFillFrom = (startEpisodeRef = null) => {
|
||||
const { episodeRows, reviewTitles, selectedTitleIds, fallbackSelectedTitleRows } = resolveEpisodeFillContext();
|
||||
if (episodeRows.length === 0 || selectedTitleIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (fallbackSelectedTitleRows.length === 0) {
|
||||
return;
|
||||
}
|
||||
const rawFillRef = String(startEpisodeRef || '').trim();
|
||||
const normalizedRefMatch = rawFillRef.match(/^fill:([^:]+):title:\d+$/i);
|
||||
const normalizedRef = normalizedRefMatch && normalizedRefMatch[1]
|
||||
? String(normalizedRefMatch[1]).trim()
|
||||
: rawFillRef;
|
||||
|
||||
const groupedEpisodeUnits = buildGroupedEpisodeUnits(
|
||||
episodeRows,
|
||||
fallbackSelectedTitleRows,
|
||||
reviewTitles
|
||||
);
|
||||
|
||||
let startUnitIndex = 0;
|
||||
if (normalizedRef) {
|
||||
const foundUnitIndex = groupedEpisodeUnits.findIndex((unit) =>
|
||||
@@ -1596,7 +1638,7 @@ export default function PipelineStatusCard({
|
||||
&& rangeEnd >= rangeStart
|
||||
)
|
||||
? Math.max(1, Math.trunc(rangeEnd - rangeStart + 1))
|
||||
: Math.max(1, consumedRows);
|
||||
: Math.max(1, normalizePositiveInt(unit?.consumedRows) || 1);
|
||||
const rangeToken = buildEpisodeRangeToken(
|
||||
rangeStart,
|
||||
rangeEnd
|
||||
@@ -1647,6 +1689,31 @@ export default function PipelineStatusCard({
|
||||
}));
|
||||
};
|
||||
|
||||
const episodeFillOptions = useMemo(() => {
|
||||
const { episodeRows, reviewTitles, fallbackSelectedTitleRows } = resolveEpisodeFillContext();
|
||||
if (!Array.isArray(episodeRows) || episodeRows.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const groupedUnits = buildGroupedEpisodeUnits(episodeRows, fallbackSelectedTitleRows, reviewTitles);
|
||||
return groupedUnits
|
||||
.map((unit) => {
|
||||
const startRow = unit?.startRow || null;
|
||||
const value = startRow?.episodeIdStart
|
||||
|| startRow?.episodeId
|
||||
|| startRow?.episodeNumberStart
|
||||
|| startRow?.episodeNumber
|
||||
|| null;
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
value: String(value),
|
||||
startRef: String(value)
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
}, [selectedMetadataEpisodes, selectedEncodeTitleIds, mediaInfoReview?.titles, mediaInfoReview?.selectedTitleIds]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!Array.isArray(selectedMetadataEpisodes) || selectedMetadataEpisodes.length === 0) {
|
||||
return;
|
||||
@@ -1661,17 +1728,32 @@ export default function PipelineStatusCard({
|
||||
if (hasAnyEpisodeAssignment) {
|
||||
return;
|
||||
}
|
||||
const defaultStartRef = selectedMetadataEpisodes[0]?.episodeIdStart
|
||||
|| selectedMetadataEpisodes[0]?.episodeId
|
||||
|| selectedMetadataEpisodes[0]?.episodeNumberStart
|
||||
|| selectedMetadataEpisodes[0]?.episodeNumber
|
||||
|| null;
|
||||
if (!Array.isArray(episodeFillOptions) || episodeFillOptions.length === 0) {
|
||||
return;
|
||||
}
|
||||
const normalizedSelection = String(selectedEpisodeFillStart || '').trim();
|
||||
const hasValidSelection = Boolean(
|
||||
normalizedSelection
|
||||
&& episodeFillOptions.some((option) => String(option?.value || '').trim() === normalizedSelection)
|
||||
);
|
||||
const defaultStartRef = hasValidSelection
|
||||
? normalizedSelection
|
||||
: (episodeFillOptions[0]?.value || episodeFillOptions[0]?.startRef || null);
|
||||
if (!defaultStartRef) {
|
||||
return;
|
||||
}
|
||||
setSelectedEpisodeFillStart(String(defaultStartRef));
|
||||
if (!hasValidSelection) {
|
||||
setSelectedEpisodeFillStart(String(defaultStartRef));
|
||||
}
|
||||
applyEpisodeFillFrom(defaultStartRef);
|
||||
}, [selectedMetadataEpisodes, selectedEncodeTitleIds, mediaInfoReview?.generatedAt, episodeAssignmentsByTitle]);
|
||||
}, [
|
||||
selectedMetadataEpisodes,
|
||||
selectedEncodeTitleIds,
|
||||
mediaInfoReview?.generatedAt,
|
||||
episodeAssignmentsByTitle,
|
||||
episodeFillOptions,
|
||||
selectedEpisodeFillStart
|
||||
]);
|
||||
|
||||
const reviewPlaylistDecisionRequired = Boolean(mediaInfoReview?.playlistDecisionRequired);
|
||||
const hasSelectedEncodeTitle = normalizeTitleIdList(selectedEncodeTitleIds).length > 0;
|
||||
@@ -2046,8 +2128,8 @@ export default function PipelineStatusCard({
|
||||
|
||||
const refs = collectEpisodeReferencesFromPlan(child?.encodePlan);
|
||||
for (const ref of refs) {
|
||||
const key = buildEpisodeReferenceKey(ref);
|
||||
if (key) {
|
||||
const keys = collectEpisodeReferenceKeys(ref);
|
||||
for (const key of keys) {
|
||||
usedKeys.add(key);
|
||||
}
|
||||
}
|
||||
@@ -2056,12 +2138,12 @@ export default function PipelineStatusCard({
|
||||
? child.handbrakeInfo.seriesBatch.episodes
|
||||
: [];
|
||||
for (const episode of batchEpisodes) {
|
||||
const key = buildEpisodeReferenceKey({
|
||||
const keys = collectEpisodeReferenceKeys({
|
||||
episodeId: episode?.episodeId,
|
||||
episodeNumber: episode?.episodeNumber,
|
||||
seasonNumber: episode?.seasonNumber
|
||||
});
|
||||
if (key) {
|
||||
for (const key of keys) {
|
||||
usedKeys.add(key);
|
||||
}
|
||||
}
|
||||
@@ -2327,15 +2409,17 @@ export default function PipelineStatusCard({
|
||||
.map((row) => String(row?.episodeTitle || '').trim())
|
||||
.filter(Boolean)
|
||||
.join(' + ');
|
||||
const normalizedTitle = normalizeEpisodeTitleForOutput(
|
||||
sourceTitle || String(title?.episodeTitle || '').trim() || '(ohne Titel)',
|
||||
{
|
||||
start: startNumber || 1,
|
||||
end: endNumber || startNumber || 1,
|
||||
span: safeSpan,
|
||||
isMulti
|
||||
}
|
||||
);
|
||||
const normalizedTitle = sourceTitle
|
||||
? normalizeEpisodeTitleForOutput(
|
||||
sourceTitle,
|
||||
{
|
||||
start: startNumber || 1,
|
||||
end: endNumber || startNumber || 1,
|
||||
span: safeSpan,
|
||||
isMulti
|
||||
}
|
||||
)
|
||||
: '';
|
||||
titleAnchorSuggestionById.set(titleId, {
|
||||
suggestedTitle: normalizedTitle || '(ohne Titel)',
|
||||
isMulti
|
||||
@@ -2370,6 +2454,7 @@ export default function PipelineStatusCard({
|
||||
span: effectiveSpan,
|
||||
isMulti: effectiveSpan > 1
|
||||
};
|
||||
const suggestedTitle = String(titleAnchorSuggestionById.get(titleId)?.suggestedTitle || '').trim();
|
||||
return {
|
||||
titleId,
|
||||
episodeTitle: String(
|
||||
@@ -2378,12 +2463,9 @@ export default function PipelineStatusCard({
|
||||
|| title?.fileName
|
||||
|| ''
|
||||
).trim() || '(ohne Titel)',
|
||||
suggestedTitle: (
|
||||
String(titleAnchorSuggestionById.get(titleId)?.suggestedTitle || '').trim()
|
||||
|| normalizeEpisodeTitleForOutput(
|
||||
String(title?.episodeTitle || '').trim() || '(ohne Titel)',
|
||||
suggestedRange
|
||||
)
|
||||
suggestedTitle: suggestedTitle || normalizeEpisodeTitleForOutput(
|
||||
String(assignment?.episodeTitle || title?.episodeTitle || '').trim() || '(ohne Titel)',
|
||||
suggestedRange
|
||||
)
|
||||
};
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user