diff --git a/backend/src/services/pipelineService.js b/backend/src/services/pipelineService.js index d13e58c..ed558a6 100644 --- a/backend/src/services/pipelineService.js +++ b/backend/src/services/pipelineService.js @@ -22793,6 +22793,12 @@ class PipelineService extends EventEmitter { throw error; } + const readyMediaProfile = this.resolveMediaProfileForJob(job, { + encodePlan: planForConfirm + }); + const confirmSettings = await settingsService.getEffectiveSettingsMap(readyMediaProfile); + const settingsExtraArgsForProfile = String(confirmSettings?.handbrake_extra_args || '').trim(); + // Resolve user preset: explicit payload wins, otherwise preserve currently selected preset from encode plan. const hasUserPresetIdField = Object.prototype.hasOwnProperty.call(options || {}, 'selectedUserPresetId'); const hasHandBrakePresetField = Object.prototype.hasOwnProperty.call(options || {}, 'selectedHandBrakePreset'); @@ -22822,10 +22828,17 @@ class PipelineService extends EventEmitter { ? { name: `HandBrake: ${rawHandBrakePreset}`, handbrakePreset: rawHandBrakePreset, - extraArgs: '' + extraArgs: settingsExtraArgsForProfile } : null; } + if (!resolvedUserPreset && explicitPresetClearRequested && settingsExtraArgsForProfile) { + resolvedUserPreset = { + name: 'Settings CLI-Parameter', + handbrakePreset: null, + extraArgs: settingsExtraArgsForProfile + }; + } if (!resolvedUserPreset && !explicitPresetClearRequested) { resolvedUserPreset = normalizeUserPresetForPlan( planForConfirm?.userPreset || encodePlan?.userPreset || null @@ -22850,10 +22863,6 @@ class PipelineService extends EventEmitter { reviewConfirmedAt: nowIso(), userPreset: normalizeUserPresetForPlan(resolvedUserPreset) }; - const readyMediaProfile = this.resolveMediaProfileForJob(job, { - encodePlan: confirmedPlan - }); - const confirmSettings = await settingsService.getEffectiveSettingsMap(readyMediaProfile); const confirmedConverterMediaType = String( confirmedPlan?.converterMediaType || encodePlan?.converterMediaType || '' ).trim().toLowerCase(); @@ -22878,19 +22887,27 @@ class PipelineService extends EventEmitter { || encodePlan?.selectors?.extraArgs || '' ).trim(); - const effectivePresetName = String( - resolvedUserPreset?.handbrakePreset - || rawHandBrakePreset - || planPresetName - || confirmSettings?.handbrake_preset - || '' - ).trim(); - const effectiveExtraArgs = String( - resolvedUserPreset?.extraArgs - || planExtraArgs - || confirmSettings?.handbrake_extra_args - || '' - ).trim(); + const effectivePresetName = explicitPresetClearRequested + ? '' + : String( + resolvedUserPreset?.handbrakePreset + || rawHandBrakePreset + || planPresetName + || confirmSettings?.handbrake_preset + || '' + ).trim(); + const effectiveExtraArgs = explicitPresetClearRequested + ? String( + resolvedUserPreset?.extraArgs + || settingsExtraArgsForProfile + || '' + ).trim() + : String( + resolvedUserPreset?.extraArgs + || planExtraArgs + || settingsExtraArgsForProfile + || '' + ).trim(); if (requiresExplicitPreset && !effectivePresetName && !effectiveExtraArgs) { const error = new Error('Bestätigung nicht möglich: Kein Preset oder Extra-Args gesetzt. Bitte User-/HandBrake-Preset wählen oder passende Settings hinterlegen.'); error.statusCode = 400; diff --git a/frontend/src/components/MediaInfoReviewPanel.jsx b/frontend/src/components/MediaInfoReviewPanel.jsx index 9b4a984..ae1f5dc 100644 --- a/frontend/src/components/MediaInfoReviewPanel.jsx +++ b/frontend/src/components/MediaInfoReviewPanel.jsx @@ -739,11 +739,15 @@ function buildHandBrakeCommandPreview({ hasExplicitVariantSelection: normalizedSubtitleSelectionMode === 'variants' && hasExplicitSelectedVariantSelection }); - const subtitleTrackIds = resolvedSubtitleSelection.subtitleTrackIds.length > 0 - ? resolvedSubtitleSelection.subtitleTrackIds - : (hasExplicitSelectedVariantSelection - ? [] - : normalizedSelectedSubtitleTrackIds); + const subtitleTrackIds = normalizedSubtitleSelectionMode === 'track_ids' + ? normalizedSelectedSubtitleTrackIds + : ( + resolvedSubtitleSelection.subtitleTrackIds.length > 0 + ? resolvedSubtitleSelection.subtitleTrackIds + : (hasExplicitSelectedVariantSelection + ? [] + : normalizedSelectedSubtitleTrackIds) + ); const selectedSubtitleSet = new Set(normalizeTrackIdList(subtitleTrackIds).map((id) => String(id))); const selectedSubtitleTracks = (Array.isArray(title?.subtitleTracks) ? title.subtitleTracks : []).filter((track) => { const id = normalizeTrackId(track?.id); @@ -787,7 +791,7 @@ function buildHandBrakeCommandPreview({ if (subtitleDefaultTrackId !== null) { overrideArgs.push(`--subtitle-default=${subtitleDefaultTrackId}`); } - if (resolvedSubtitleSelection.subtitleForcedTrackIndexes.length > 0) { + if (normalizedSubtitleSelectionMode !== 'track_ids' && resolvedSubtitleSelection.subtitleForcedTrackIndexes.length > 0) { overrideArgs.push(`--subtitle-forced=${resolvedSubtitleSelection.subtitleForcedTrackIndexes.join(',')}`); } @@ -1617,6 +1621,8 @@ export default function MediaInfoReviewPanel({ onUserPresetChange = null, selectedHandBrakePreset = '', onHandBrakePresetChange = null, + presetSelectionExplicitlyCleared = false, + onPresetTokenChange = null, handBrakePresetOptions = [], comparisonReviewItems = [], topContent = null @@ -1922,24 +1928,31 @@ export default function MediaInfoReviewPanel({ const isHandBrakePresetActive = !isUserPresetActive && Boolean(trimmedHandBrakePreset); const settingsPresetFallback = String(review?.selectors?.preset || '').trim(); const settingsExtraArgsFallback = String(review?.selectors?.extraArgs || '').trim(); - const usesSettingsFallbackOnly = !isUserPresetActive && !isHandBrakePresetActive; - const hasSettingsPresetFallback = Boolean(settingsPresetFallback); + const hasNoExplicitPresetSelected = !isUserPresetActive && !isHandBrakePresetActive; + const effectiveSettingsPresetFallback = presetSelectionExplicitlyCleared ? '' : settingsPresetFallback; + const hasSettingsPresetFallback = Boolean(effectiveSettingsPresetFallback); const hasSettingsExtraArgsFallback = Boolean(settingsExtraArgsFallback); - const hasInvalidPresetConfig = usesSettingsFallbackOnly + const hasInvalidPresetConfig = hasNoExplicitPresetSelected && !hasSettingsPresetFallback && !hasSettingsExtraArgsFallback; - const hasSettingsArgsOnlyConfig = usesSettingsFallbackOnly + const hasSettingsArgsOnlyConfig = hasNoExplicitPresetSelected && !hasSettingsPresetFallback && hasSettingsExtraArgsFallback; const effectivePresetOverride = isUserPresetActive ? { handbrakePreset: selectedUserPreset.handbrakePreset || '', extraArgs: selectedUserPreset.extraArgs || '' } - : (isHandBrakePresetActive ? { handbrakePreset: trimmedHandBrakePreset, extraArgs: '' } : null); + : (isHandBrakePresetActive + ? { handbrakePreset: trimmedHandBrakePreset, extraArgs: settingsExtraArgsFallback } + : (presetSelectionExplicitlyCleared + ? { handbrakePreset: '', extraArgs: settingsExtraArgsFallback } + : null)); const effectiveAudioSelector = effectivePresetOverride?.extraArgs ? parseAudioSelectorFromArgs(effectivePresetOverride.extraArgs, review?.selectors?.audio) : (review?.selectors?.audio || null); const activePresetDisplayLabel = isUserPresetActive ? 'User-Preset' - : ((isHandBrakePresetActive || String(presetLabel || '').trim()) ? 'HandBrake-Preset' : '(kein Preset)'); + : (isHandBrakePresetActive + ? 'HandBrake-Preset' + : (presetSelectionExplicitlyCleared ? 'Kein Preset' : (String(presetLabel || '').trim() ? 'HandBrake-Preset' : '(kein Preset)'))); const minLengthMinutes = Number(review?.minLengthMinutes); const minLengthLabel = Number.isFinite(minLengthMinutes) ? (minLengthMinutes <= 0 @@ -2240,6 +2253,9 @@ export default function MediaInfoReviewPanel({ optionDisabled="disabled" onChange={(e) => { const value = String(e?.value || '').trim(); + if (typeof onPresetTokenChange === 'function') { + onPresetTokenChange(value); + } if (value.startsWith('user:')) { const presetId = Number(value.slice(5)); if (typeof onUserPresetChange === 'function') { diff --git a/frontend/src/components/PipelineStatusCard.jsx b/frontend/src/components/PipelineStatusCard.jsx index d5eceab..524ccd4 100644 --- a/frontend/src/components/PipelineStatusCard.jsx +++ b/frontend/src/components/PipelineStatusCard.jsx @@ -1679,6 +1679,7 @@ export default function PipelineStatusCard({ const [selectedUserPresetId, setSelectedUserPresetId] = useState(null); const [handBrakePresetOptions, setHandBrakePresetOptions] = useState([]); const [selectedHandBrakePreset, setSelectedHandBrakePreset] = useState(''); + const [presetSelectionExplicitlyCleared, setPresetSelectionExplicitlyCleared] = useState(false); const [selectedOutputFormat, setSelectedOutputFormat] = useState('mkv'); const [converterConfigSaving, setConverterConfigSaving] = useState(false); const [converterConfigError, setConverterConfigError] = useState(null); @@ -1886,6 +1887,7 @@ export default function PipelineStatusCard({ setSelectedOutputFormat(nextOutputFormat); const userPresetId = Number(mediaInfoReview?.userPreset?.id); const settingsPresetName = String(mediaInfoReview?.selectors?.preset || '').trim(); + setPresetSelectionExplicitlyCleared(false); if (Number.isFinite(userPresetId) && userPresetId > 0) { setSelectedUserPresetId(Math.trunc(userPresetId)); setSelectedHandBrakePreset(''); @@ -1928,6 +1930,7 @@ export default function PipelineStatusCard({ setPostEncodeItems([]); setSelectedUserPresetId(null); setSelectedHandBrakePreset(''); + setPresetSelectionExplicitlyCleared(false); }, [mediaInfoReview, retryJobId]); const resolveEffectiveSelectedTitleIds = (reviewTitles = []) => { @@ -2344,8 +2347,9 @@ export default function PipelineStatusCard({ const settingsPresetFallback = String(mediaInfoReview?.selectors?.preset || '').trim(); const settingsExtraArgsFallback = String(mediaInfoReview?.selectors?.extraArgs || '').trim(); const hasSelectedPreset = Boolean(selectedUserPresetId) || Boolean(trimmedHandBrakePreset); + const hasSettingsPresetForValidation = !presetSelectionExplicitlyCleared && Boolean(settingsPresetFallback); const hasEffectiveSelectedPreset = hasSelectedPreset - || Boolean(settingsPresetFallback) + || hasSettingsPresetForValidation || Boolean(settingsExtraArgsFallback); const converterMediaType = String(mediaInfoReview?.converterMediaType || 'video').trim().toLowerCase() || 'video'; const showConverterConfig = jobMediaProfile === 'converter' && converterMediaType !== 'audio'; @@ -4342,7 +4346,7 @@ export default function PipelineStatusCard({