0.12.0-16 misc fixes
This commit is contained in:
@@ -15,6 +15,14 @@ function normalizeTitleId(value) {
|
||||
return Math.trunc(parsed);
|
||||
}
|
||||
|
||||
function normalizeJobId(value) {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||
return null;
|
||||
}
|
||||
return Math.trunc(parsed);
|
||||
}
|
||||
|
||||
function normalizePlaylistId(value) {
|
||||
const raw = String(value || '').trim().toLowerCase();
|
||||
if (!raw) {
|
||||
@@ -155,10 +163,119 @@ function isBurnedSubtitleTrack(track) {
|
||||
);
|
||||
}
|
||||
|
||||
function isDuplicateSubtitleTrack(track) {
|
||||
return Boolean(track?.duplicate);
|
||||
}
|
||||
|
||||
function normalizeSubtitleLanguage(value) {
|
||||
const raw = String(value || '').trim().toLowerCase();
|
||||
if (!raw) {
|
||||
return 'und';
|
||||
}
|
||||
if (raw.length >= 3) {
|
||||
return raw.slice(0, 3);
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
function normalizeSubtitleVariantSelectionMap(rawSelection) {
|
||||
const map = {};
|
||||
if (!rawSelection || typeof rawSelection !== 'object') {
|
||||
return map;
|
||||
}
|
||||
for (const [language, value] of Object.entries(rawSelection)) {
|
||||
const normalizedLanguage = normalizeSubtitleLanguage(language);
|
||||
const full = Boolean(value?.full);
|
||||
const forced = Boolean(value?.forced);
|
||||
map[normalizedLanguage] = { full, forced };
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
function normalizeSubtitleLanguageOrder(rawOrder = []) {
|
||||
const list = Array.isArray(rawOrder) ? rawOrder : [];
|
||||
const seen = new Set();
|
||||
const output = [];
|
||||
for (const value of list) {
|
||||
const language = normalizeSubtitleLanguage(value);
|
||||
if (!language || seen.has(language)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(language);
|
||||
output.push(language);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function resolveSubtitleTrackVariantFlags(track) {
|
||||
const subtitleType = String(track?.subtitleType || '').trim().toLowerCase();
|
||||
const isForcedOnly = Boolean(
|
||||
track?.isForcedOnly
|
||||
?? track?.subtitlePreviewForcedOnly
|
||||
?? track?.forcedOnly
|
||||
?? subtitleType === 'forced'
|
||||
);
|
||||
const fullHasForced = !isForcedOnly && Boolean(
|
||||
track?.fullHasForced
|
||||
?? track?.subtitleFullHasForced
|
||||
?? track?.hasForcedVariant
|
||||
);
|
||||
return { isForcedOnly, fullHasForced };
|
||||
}
|
||||
|
||||
function buildDefaultSubtitleVariantSelection(subtitleTracks, selectedTracks) {
|
||||
const tracks = Array.isArray(subtitleTracks) ? subtitleTracks : [];
|
||||
const selected = Array.isArray(selectedTracks) ? selectedTracks : [];
|
||||
const orderedTracks = [...tracks].sort((a, b) => {
|
||||
const aId = normalizeTrackId(a?.id) ?? Number.MAX_SAFE_INTEGER;
|
||||
const bId = normalizeTrackId(b?.id) ?? Number.MAX_SAFE_INTEGER;
|
||||
if (aId !== bId) {
|
||||
return aId - bId;
|
||||
}
|
||||
return String(a?.language || a?.languageLabel || '').localeCompare(String(b?.language || b?.languageLabel || ''));
|
||||
});
|
||||
|
||||
const order = [];
|
||||
const orderSeen = new Set();
|
||||
for (const track of orderedTracks) {
|
||||
const language = normalizeSubtitleLanguage(track?.language || track?.languageLabel || 'und');
|
||||
if (!orderSeen.has(language)) {
|
||||
orderSeen.add(language);
|
||||
order.push(language);
|
||||
}
|
||||
}
|
||||
|
||||
const selectionMap = {};
|
||||
for (const track of selected) {
|
||||
if (isBurnedSubtitleTrack(track) || isDuplicateSubtitleTrack(track)) {
|
||||
continue;
|
||||
}
|
||||
const language = normalizeSubtitleLanguage(track?.language || track?.languageLabel || 'und');
|
||||
if (!selectionMap[language]) {
|
||||
selectionMap[language] = { full: false, forced: false };
|
||||
}
|
||||
const flags = resolveSubtitleTrackVariantFlags(track);
|
||||
if (flags.isForcedOnly) {
|
||||
selectionMap[language].forced = true;
|
||||
} else {
|
||||
selectionMap[language].full = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subtitleVariantSelection: selectionMap,
|
||||
subtitleLanguageOrder: order
|
||||
};
|
||||
}
|
||||
|
||||
function buildDefaultTrackSelection(review) {
|
||||
const titles = Array.isArray(review?.titles) ? review.titles : [];
|
||||
const selection = {};
|
||||
const reviewEncodeInputTitleId = normalizeTitleId(review?.encodeInputTitleId);
|
||||
const manualSelection = review?.manualTrackSelection && typeof review.manualTrackSelection === 'object'
|
||||
? review.manualTrackSelection
|
||||
: null;
|
||||
const manualTitleId = normalizeTitleId(manualSelection?.titleId);
|
||||
|
||||
for (const title of titles) {
|
||||
const titleId = normalizeTitleId(title?.id);
|
||||
@@ -179,16 +296,49 @@ function buildDefaultTrackSelection(review) {
|
||||
const subtitleSelectionSource = isEncodeInputTitle
|
||||
? subtitleTracks.filter((track) => Boolean(track?.selectedForEncode))
|
||||
: subtitleTracks.filter((track) => Boolean(track?.selectedByRule));
|
||||
const defaultSubtitleVariantSelection = buildDefaultSubtitleVariantSelection(
|
||||
subtitleTracks,
|
||||
subtitleSelectionSource
|
||||
);
|
||||
const manualSelectionMatchesTitle = Boolean(
|
||||
isEncodeInputTitle
|
||||
&& manualSelection
|
||||
&& (!manualTitleId || manualTitleId === titleId)
|
||||
);
|
||||
const manualAudioTrackIds = manualSelectionMatchesTitle
|
||||
? normalizeTrackIdList(manualSelection?.audioTrackIds || [])
|
||||
: [];
|
||||
const manualSubtitleTrackIds = manualSelectionMatchesTitle
|
||||
? normalizeTrackIdList(
|
||||
Array.isArray(manualSelection?.subtitleTrackIdsOrdered)
|
||||
? manualSelection.subtitleTrackIdsOrdered
|
||||
: manualSelection?.subtitleTrackIds || []
|
||||
)
|
||||
: [];
|
||||
const manualSubtitleVariantSelection = manualSelectionMatchesTitle
|
||||
? normalizeSubtitleVariantSelectionMap(manualSelection?.subtitleVariantSelection || {})
|
||||
: {};
|
||||
const manualSubtitleLanguageOrder = manualSelectionMatchesTitle
|
||||
? normalizeSubtitleLanguageOrder(manualSelection?.subtitleLanguageOrder || [])
|
||||
: [];
|
||||
|
||||
selection[titleId] = {
|
||||
audioTrackIds: normalizeTrackIdList(
|
||||
audioSelectionSource.map((track) => track?.id)
|
||||
),
|
||||
subtitleTrackIds: normalizeTrackIdList(
|
||||
subtitleSelectionSource
|
||||
.filter((track) => !isBurnedSubtitleTrack(track))
|
||||
.map((track) => track?.id)
|
||||
)
|
||||
audioTrackIds: manualAudioTrackIds.length > 0
|
||||
? manualAudioTrackIds
|
||||
: normalizeTrackIdList(audioSelectionSource.map((track) => track?.id)),
|
||||
subtitleTrackIds: manualSubtitleTrackIds.length > 0
|
||||
? manualSubtitleTrackIds
|
||||
: normalizeTrackIdList(
|
||||
subtitleSelectionSource
|
||||
.filter((track) => !isBurnedSubtitleTrack(track) && !isDuplicateSubtitleTrack(track))
|
||||
.map((track) => track?.id)
|
||||
),
|
||||
subtitleVariantSelection: Object.keys(manualSubtitleVariantSelection).length > 0
|
||||
? manualSubtitleVariantSelection
|
||||
: defaultSubtitleVariantSelection.subtitleVariantSelection,
|
||||
subtitleLanguageOrder: manualSubtitleLanguageOrder.length > 0
|
||||
? manualSubtitleLanguageOrder
|
||||
: defaultSubtitleVariantSelection.subtitleLanguageOrder
|
||||
};
|
||||
}
|
||||
|
||||
@@ -197,7 +347,12 @@ function buildDefaultTrackSelection(review) {
|
||||
|
||||
function defaultTrackSelectionForTitle(review, titleId) {
|
||||
const defaults = buildDefaultTrackSelection(review);
|
||||
return defaults[titleId] || defaults[String(titleId)] || { audioTrackIds: [], subtitleTrackIds: [] };
|
||||
return defaults[titleId] || defaults[String(titleId)] || {
|
||||
audioTrackIds: [],
|
||||
subtitleTrackIds: [],
|
||||
subtitleVariantSelection: {},
|
||||
subtitleLanguageOrder: []
|
||||
};
|
||||
}
|
||||
|
||||
function buildSettingsMap(categories) {
|
||||
@@ -299,6 +454,7 @@ function buildOutputPathPreview(settings, mediaProfile, metadata, fallbackJobId
|
||||
}
|
||||
|
||||
export default function PipelineStatusCard({
|
||||
jobId = null,
|
||||
pipeline,
|
||||
onAnalyze,
|
||||
onReanalyze,
|
||||
@@ -321,7 +477,9 @@ export default function PipelineStatusCard({
|
||||
const stateLabel = getStatusLabel(state);
|
||||
const progress = Number(pipeline?.progress || 0);
|
||||
const running = state === 'ANALYZING' || state === 'RIPPING' || state === 'ENCODING' || state === 'MEDIAINFO_CHECK';
|
||||
const retryJobId = pipeline?.context?.jobId;
|
||||
const explicitJobId = normalizeJobId(jobId);
|
||||
const contextJobId = normalizeJobId(pipeline?.context?.jobId);
|
||||
const retryJobId = explicitJobId || contextJobId;
|
||||
const queueLocked = Boolean(isQueued && retryJobId);
|
||||
const selectedMetadata = pipeline?.context?.selectedMetadata || null;
|
||||
const mediaInfoReview = pipeline?.context?.mediaInfoReview || null;
|
||||
@@ -435,7 +593,12 @@ export default function PipelineStatusCard({
|
||||
}
|
||||
|
||||
const defaults = buildDefaultTrackSelection(mediaInfoReview);
|
||||
const fallback = defaults[currentTitleId] || { audioTrackIds: [], subtitleTrackIds: [] };
|
||||
const fallback = defaults[currentTitleId] || {
|
||||
audioTrackIds: [],
|
||||
subtitleTrackIds: [],
|
||||
subtitleVariantSelection: {},
|
||||
subtitleLanguageOrder: []
|
||||
};
|
||||
return {
|
||||
...prev,
|
||||
[currentTitleId]: fallback
|
||||
@@ -639,7 +802,12 @@ export default function PipelineStatusCard({
|
||||
: null;
|
||||
const fallbackSelection = encodeTitleId
|
||||
? defaultTrackSelectionForTitle(mediaInfoReview, encodeTitleId)
|
||||
: { audioTrackIds: [], subtitleTrackIds: [] };
|
||||
: {
|
||||
audioTrackIds: [],
|
||||
subtitleTrackIds: [],
|
||||
subtitleVariantSelection: {},
|
||||
subtitleLanguageOrder: []
|
||||
};
|
||||
const effectiveSelection = selectionEntry || fallbackSelection;
|
||||
const encodeTitle = encodeTitleId
|
||||
? (Array.isArray(mediaInfoReview?.titles)
|
||||
@@ -648,17 +816,39 @@ export default function PipelineStatusCard({
|
||||
: null;
|
||||
const blockedSubtitleTrackIds = new Set(
|
||||
(Array.isArray(encodeTitle?.subtitleTracks) ? encodeTitle.subtitleTracks : [])
|
||||
.filter((track) => isBurnedSubtitleTrack(track))
|
||||
.filter((track) => isBurnedSubtitleTrack(track) || isDuplicateSubtitleTrack(track))
|
||||
.map((track) => normalizeTrackId(track?.id))
|
||||
.filter((id) => id !== null)
|
||||
.map((id) => String(id))
|
||||
);
|
||||
const availableSubtitleLanguages = new Set(
|
||||
(Array.isArray(encodeTitle?.subtitleTracks) ? encodeTitle.subtitleTracks : [])
|
||||
.filter((track) => !isBurnedSubtitleTrack(track) && !isDuplicateSubtitleTrack(track))
|
||||
.map((track) => normalizeSubtitleLanguage(track?.language || track?.languageLabel || 'und'))
|
||||
);
|
||||
const normalizedVariantSelection = normalizeSubtitleVariantSelectionMap(effectiveSelection?.subtitleVariantSelection || {});
|
||||
const filteredVariantSelection = {};
|
||||
for (const [language, value] of Object.entries(normalizedVariantSelection)) {
|
||||
if (!availableSubtitleLanguages.has(language)) {
|
||||
continue;
|
||||
}
|
||||
filteredVariantSelection[language] = {
|
||||
full: Boolean(value?.full),
|
||||
forced: Boolean(value?.forced)
|
||||
};
|
||||
}
|
||||
const normalizedLanguageOrder = normalizeSubtitleLanguageOrder(effectiveSelection?.subtitleLanguageOrder || [])
|
||||
.filter((language) => availableSubtitleLanguages.has(language));
|
||||
const hasExplicitVariantSelection = Object.keys(filteredVariantSelection).length > 0;
|
||||
const filteredSubtitleTrackIds = normalizeTrackIdList(effectiveSelection?.subtitleTrackIds || [])
|
||||
.filter((id) => !blockedSubtitleTrackIds.has(String(id)));
|
||||
const selectedTrackSelection = encodeTitleId
|
||||
? {
|
||||
[encodeTitleId]: {
|
||||
audioTrackIds: normalizeTrackIdList(effectiveSelection?.audioTrackIds || []),
|
||||
subtitleTrackIds: normalizeTrackIdList(effectiveSelection?.subtitleTrackIds || [])
|
||||
.filter((id) => !blockedSubtitleTrackIds.has(String(id)))
|
||||
subtitleTrackIds: hasExplicitVariantSelection ? [] : filteredSubtitleTrackIds,
|
||||
subtitleVariantSelection: filteredVariantSelection,
|
||||
subtitleLanguageOrder: normalizedLanguageOrder
|
||||
}
|
||||
}
|
||||
: null;
|
||||
@@ -1032,7 +1222,9 @@ export default function PipelineStatusCard({
|
||||
setTrackSelectionByTitle((prev) => {
|
||||
const current = prev?.[normalizedTitleId] || prev?.[String(normalizedTitleId)] || {
|
||||
audioTrackIds: [],
|
||||
subtitleTrackIds: []
|
||||
subtitleTrackIds: [],
|
||||
subtitleVariantSelection: {},
|
||||
subtitleLanguageOrder: []
|
||||
};
|
||||
const key = trackType === 'subtitle' ? 'subtitleTrackIds' : 'audioTrackIds';
|
||||
const existing = normalizeTrackIdList(current?.[key] || []);
|
||||
@@ -1049,6 +1241,53 @@ export default function PipelineStatusCard({
|
||||
};
|
||||
});
|
||||
}}
|
||||
onSubtitleVariantSelectionChange={(titleId, language, variant, checked) => {
|
||||
const normalizedTitleId = normalizeTitleId(titleId);
|
||||
const normalizedLanguage = normalizeSubtitleLanguage(language);
|
||||
const normalizedVariant = String(variant || '').trim().toLowerCase() === 'forced' ? 'forced' : 'full';
|
||||
if (!normalizedTitleId || !normalizedLanguage) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTrackSelectionByTitle((prev) => {
|
||||
const current = prev?.[normalizedTitleId] || prev?.[String(normalizedTitleId)] || {
|
||||
audioTrackIds: [],
|
||||
subtitleTrackIds: [],
|
||||
subtitleVariantSelection: {},
|
||||
subtitleLanguageOrder: []
|
||||
};
|
||||
const currentMap = normalizeSubtitleVariantSelectionMap(current?.subtitleVariantSelection || {});
|
||||
const nextMap = { ...currentMap };
|
||||
const currentEntry = nextMap[normalizedLanguage] || { full: false, forced: false };
|
||||
nextMap[normalizedLanguage] = {
|
||||
...currentEntry,
|
||||
[normalizedVariant]: Boolean(checked)
|
||||
};
|
||||
const existingOrder = normalizeSubtitleLanguageOrder(current?.subtitleLanguageOrder || []);
|
||||
const fallbackOrder = normalizeSubtitleLanguageOrder(
|
||||
defaultTrackSelectionForTitle(mediaInfoReview, normalizedTitleId)?.subtitleLanguageOrder || []
|
||||
);
|
||||
let nextOrder = existingOrder.length > 0
|
||||
? existingOrder
|
||||
: fallbackOrder;
|
||||
if (nextOrder.length === 0) {
|
||||
nextOrder = [normalizedLanguage];
|
||||
} else if (!nextOrder.includes(normalizedLanguage) && fallbackOrder.includes(normalizedLanguage)) {
|
||||
nextOrder = fallbackOrder;
|
||||
} else if (!nextOrder.includes(normalizedLanguage)) {
|
||||
nextOrder = [...nextOrder, normalizedLanguage];
|
||||
}
|
||||
|
||||
return {
|
||||
...prev,
|
||||
[normalizedTitleId]: {
|
||||
...current,
|
||||
subtitleVariantSelection: nextMap,
|
||||
subtitleLanguageOrder: nextOrder
|
||||
}
|
||||
};
|
||||
});
|
||||
}}
|
||||
availableScripts={scriptCatalog}
|
||||
availableChains={chainCatalog}
|
||||
preEncodeItems={preEncodeItems}
|
||||
|
||||
Reference in New Issue
Block a user