import { Button } from 'primereact/button'; import { Dropdown } from 'primereact/dropdown'; function formatDuration(minutes) { const value = Number(minutes || 0); if (!Number.isFinite(value)) { return '-'; } return `${value.toFixed(2)} min`; } function formatBytes(bytes) { const value = Number(bytes || 0); if (!Number.isFinite(value) || value <= 0) { return '-'; } const units = ['B', 'KB', 'MB', 'GB', 'TB']; let size = value; let index = 0; while (size >= 1024 && index < units.length - 1) { size /= 1024; index += 1; } return `${size.toFixed(2)} ${units[index]}`; } function normalizeTrackId(value) { const parsed = Number(value); if (!Number.isFinite(parsed) || parsed <= 0) { return null; } return Math.trunc(parsed); } function normalizeTrackIdList(values) { const list = Array.isArray(values) ? values : []; const seen = new Set(); const output = []; for (const value of list) { const normalized = normalizeTrackId(value); if (normalized === null) { continue; } const key = String(normalized); if (seen.has(key)) { continue; } seen.add(key); output.push(normalized); } return output; } function isBurnedSubtitleTrack(track) { const flags = Array.isArray(track?.subtitlePreviewFlags) ? track.subtitlePreviewFlags : (Array.isArray(track?.flags) ? track.flags : []); const hasBurnedFlag = flags.some((flag) => String(flag || '').trim().toLowerCase() === 'burned'); const summary = `${track?.subtitlePreviewSummary || ''} ${track?.subtitleActionSummary || ''}`; return Boolean( track?.subtitlePreviewBurnIn || track?.burnIn || hasBurnedFlag || /burned/i.test(summary) ); } function isDuplicateSubtitleTrack(track) { return Boolean(track?.duplicate); } function normalizeTrackIdSequence(values, options = {}) { const list = Array.isArray(values) ? values : []; const dedupe = options?.dedupe !== false; const seen = new Set(); const output = []; for (const value of list) { const normalized = normalizeTrackId(value); if (normalized === null) { continue; } const key = String(normalized); if (dedupe && seen.has(key)) { continue; } if (dedupe) { seen.add(key); } output.push(normalized); } return output; } 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 normalizeSubtitleLanguageOrder(rawOrder = []) { const list = Array.isArray(rawOrder) ? rawOrder : []; const seen = new Set(); const output = []; for (const item of list) { const language = normalizeSubtitleLanguage(item); if (!language || seen.has(language)) { continue; } seen.add(language); output.push(language); } return output; } function normalizeSubtitleVariantSelectionMap(rawSelection) { const map = {}; if (!rawSelection || typeof rawSelection !== 'object') { return map; } for (const [language, value] of Object.entries(rawSelection)) { const normalizedLanguage = normalizeSubtitleLanguage(language); map[normalizedLanguage] = { full: Boolean(value?.full), forced: Boolean(value?.forced) }; } return map; } function normalizeSubtitleConfidenceValue(value) { const raw = String(value || '').trim().toLowerCase(); if (raw === 'high' || raw === 'medium' || raw === 'low') { return raw; } return 'low'; } function subtitleConfidenceScore(value) { const normalized = normalizeSubtitleConfidenceValue(value); if (normalized === 'high') { return 3; } if (normalized === 'medium') { return 2; } return 1; } 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 compareForcedSubtitleCandidate(a, b) { const defaultDiff = Number(Boolean(b?.defaultFlag)) - Number(Boolean(a?.defaultFlag)); if (defaultDiff !== 0) { return defaultDiff; } const confidenceDiff = subtitleConfidenceScore(b?.confidence) - subtitleConfidenceScore(a?.confidence); if (confidenceDiff !== 0) { return confidenceDiff; } if (a.id !== b.id) { return a.id - b.id; } return a.originalIndex - b.originalIndex; } function compareFullSubtitleCandidate(a, b) { const defaultDiff = Number(Boolean(b?.defaultFlag)) - Number(Boolean(a?.defaultFlag)); if (defaultDiff !== 0) { return defaultDiff; } if (a.id !== b.id) { return a.id - b.id; } return a.originalIndex - b.originalIndex; } function resolveDeterministicSubtitleCliSelectionForPreview({ subtitleTracks, subtitleVariantSelection, subtitleLanguageOrder }) { const tracks = (Array.isArray(subtitleTracks) ? subtitleTracks : []) .map((track, index) => { const id = normalizeTrackId(track?.id ?? track?.sourceTrackId); if (id === null) { return null; } if (isBurnedSubtitleTrack(track) || isDuplicateSubtitleTrack(track)) { return null; } const flags = resolveSubtitleTrackVariantFlags(track); return { id, language: normalizeSubtitleLanguage(track?.language || track?.languageLabel || 'und'), isForcedOnly: flags.isForcedOnly, fullHasForced: flags.fullHasForced, defaultFlag: Boolean(track?.defaultFlag ?? track?.subtitlePreviewDefaultTrack ?? track?.defaultTrack), confidence: normalizeSubtitleConfidenceValue(track?.sourceConfidence || track?.confidence), selectedForEncode: Boolean(track?.selectedForEncode), originalIndex: index }; }) .filter(Boolean); const grouped = new Map(); for (const track of tracks) { if (!grouped.has(track.language)) { grouped.set(track.language, []); } grouped.get(track.language).push(track); } const normalizedVariantSelection = normalizeSubtitleVariantSelectionMap(subtitleVariantSelection || {}); const hasExplicitVariantSelection = Object.keys(normalizedVariantSelection).length > 0; const normalizedLanguageOrder = normalizeSubtitleLanguageOrder(subtitleLanguageOrder || []); const sortedLanguages = Array.from(grouped.entries()) .map(([language, languageTracks]) => ({ language, minId: languageTracks.reduce((minValue, track) => Math.min(minValue, track.id), Number.MAX_SAFE_INTEGER) })) .sort((a, b) => a.minId - b.minId || a.language.localeCompare(b.language)) .map((item) => item.language); const order = []; const seen = new Set(); const pushLanguage = (language) => { if (!grouped.has(language) || seen.has(language)) { return; } seen.add(language); order.push(language); }; normalizedLanguageOrder.forEach(pushLanguage); sortedLanguages.forEach(pushLanguage); const subtitleTrackIds = []; const subtitleForcedTrackIndexes = []; for (const language of order) { const languageTracks = grouped.get(language) || []; const forcedOnly = languageTracks.filter((track) => track.isForcedOnly).sort(compareForcedSubtitleCandidate)[0] || null; const fullTracks = languageTracks.filter((track) => !track.isForcedOnly).sort(compareFullSubtitleCandidate); const bestFull = fullTracks[0] || null; const bestFullHasForced = fullTracks.filter((track) => track.fullHasForced).sort(compareFullSubtitleCandidate)[0] || null; const explicit = normalizedVariantSelection[language] || null; const fallbackFull = languageTracks.some((track) => track.selectedForEncode && !track.isForcedOnly) || Boolean(bestFull); const fallbackForced = languageTracks.some((track) => track.selectedForEncode && track.isForcedOnly) || Boolean(forcedOnly); const requestedFull = explicit ? Boolean(explicit.full) : (hasExplicitVariantSelection ? false : fallbackFull); const requestedForced = explicit ? Boolean(explicit.forced) : (hasExplicitVariantSelection ? false : fallbackForced); if (!requestedFull && !requestedForced) { continue; } if (forcedOnly) { subtitleTrackIds.push(forcedOnly.id); if (requestedFull && bestFull) { subtitleTrackIds.push(bestFull.id); } continue; } if (bestFullHasForced) { if (requestedFull && requestedForced) { subtitleTrackIds.push(bestFullHasForced.id); subtitleTrackIds.push(bestFullHasForced.id); subtitleForcedTrackIndexes.push(subtitleTrackIds.length); } else if (requestedForced) { subtitleTrackIds.push(bestFullHasForced.id); subtitleForcedTrackIndexes.push(subtitleTrackIds.length); } else if (requestedFull) { subtitleTrackIds.push(bestFullHasForced.id); } continue; } if (requestedFull && bestFull) { subtitleTrackIds.push(bestFull.id); } } return { subtitleTrackIds, subtitleForcedTrackIndexes }; } function splitArgs(input) { if (!input || typeof input !== 'string') { return []; } const args = []; let current = ''; let quote = null; let escaping = false; for (const ch of input) { if (escaping) { current += ch; escaping = false; continue; } if (ch === '\\') { escaping = true; continue; } if (quote) { if (ch === quote) { quote = null; } else { current += ch; } continue; } if (ch === '"' || ch === "'") { quote = ch; continue; } if (/\s/.test(ch)) { if (current.length > 0) { args.push(current); current = ''; } continue; } current += ch; } if (current.length > 0) { args.push(current); } return args; } const AUDIO_SELECTION_KEYS_WITH_VALUE = new Set(['-a', '--audio', '--audio-lang-list']); const AUDIO_SELECTION_KEYS_FLAG_ONLY = new Set(['--all-audio', '--first-audio']); const SUBTITLE_SELECTION_KEYS_WITH_VALUE = new Set(['-s', '--subtitle', '--subtitle-lang-list']); const SUBTITLE_SELECTION_KEYS_FLAG_ONLY = new Set(['--all-subtitles', '--first-subtitle']); const SUBTITLE_FLAG_KEYS_WITH_VALUE = new Set(['--subtitle-burned', '--subtitle-default', '--subtitle-forced']); const TITLE_SELECTION_KEYS_WITH_VALUE = new Set(['-t', '--title']); function removeSelectionArgs(extraArgs) { const args = Array.isArray(extraArgs) ? extraArgs : []; const filtered = []; for (let i = 0; i < args.length; i += 1) { const token = String(args[i] || ''); const key = token.includes('=') ? token.slice(0, token.indexOf('=')) : token; const isAudioWithValue = AUDIO_SELECTION_KEYS_WITH_VALUE.has(key); const isAudioFlagOnly = AUDIO_SELECTION_KEYS_FLAG_ONLY.has(key); const isSubtitleWithValue = SUBTITLE_SELECTION_KEYS_WITH_VALUE.has(key) || SUBTITLE_FLAG_KEYS_WITH_VALUE.has(key); const isSubtitleFlagOnly = SUBTITLE_SELECTION_KEYS_FLAG_ONLY.has(key); const isTitleWithValue = TITLE_SELECTION_KEYS_WITH_VALUE.has(key); const skip = isAudioWithValue || isAudioFlagOnly || isSubtitleWithValue || isSubtitleFlagOnly || isTitleWithValue; if (!skip) { filtered.push(token); continue; } if ((isAudioWithValue || isSubtitleWithValue || isTitleWithValue) && !token.includes('=')) { const nextToken = String(args[i + 1] || ''); if (nextToken && !nextToken.startsWith('-')) { i += 1; } } } return filtered; } function shellQuote(value) { const raw = String(value ?? ''); if (raw.length === 0) { return "''"; } if (/^[A-Za-z0-9_./:=,+-]+$/.test(raw)) { return raw; } return `'${raw.replace(/'/g, `'"'"'`)}'`; } function buildHandBrakeCommandPreview({ review, title, selectedAudioTrackIds, selectedSubtitleTrackIds, selectedSubtitleVariantSelection = {}, selectedSubtitleLanguageOrder = [], commandOutputPath = null, presetOverride = null }) { const inputPath = String(title?.filePath || review?.encodeInputPath || '').trim(); const handBrakeCmd = String( review?.selectors?.handbrakeCommand || review?.selectors?.handBrakeCommand || 'HandBrakeCLI' ).trim() || 'HandBrakeCLI'; const preset = presetOverride !== null ? String(presetOverride.handbrakePreset || '').trim() : String(review?.selectors?.preset || '').trim(); const extraArgs = presetOverride !== null ? String(presetOverride.extraArgs || '').trim() : String(review?.selectors?.extraArgs || '').trim(); const rawMappedTitleId = Number(review?.handBrakeTitleId); const mappedTitleId = Number.isFinite(rawMappedTitleId) && rawMappedTitleId > 0 ? Math.trunc(rawMappedTitleId) : null; const resolvedSubtitleSelection = resolveDeterministicSubtitleCliSelectionForPreview({ subtitleTracks: Array.isArray(title?.subtitleTracks) ? title.subtitleTracks : [], subtitleVariantSelection: selectedSubtitleVariantSelection, subtitleLanguageOrder: selectedSubtitleLanguageOrder }); const normalizedSelectedVariantSelection = normalizeSubtitleVariantSelectionMap(selectedSubtitleVariantSelection || {}); const hasExplicitSelectedVariantSelection = Object.keys(normalizedSelectedVariantSelection).length > 0; const subtitleTrackIds = resolvedSubtitleSelection.subtitleTrackIds.length > 0 ? resolvedSubtitleSelection.subtitleTrackIds : (hasExplicitSelectedVariantSelection ? [] : normalizeTrackIdSequence(selectedSubtitleTrackIds, { dedupe: false })); 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); return id !== null && selectedSubtitleSet.has(String(id)); }); const subtitleBurnTrackId = normalizeTrackIdList( selectedSubtitleTracks.filter((track) => Boolean(track?.subtitlePreviewBurnIn || track?.burnIn)).map((track) => track?.id) )[0] || null; const subtitleDefaultTrackId = normalizeTrackIdList( selectedSubtitleTracks.filter((track) => Boolean(track?.subtitlePreviewDefaultTrack || track?.defaultTrack)).map((track) => track?.id) )[0] || null; const baseArgs = [ '-i', inputPath || '', '-o', String(commandOutputPath || '').trim() || '' ]; if (mappedTitleId !== null) { baseArgs.push('-t', String(mappedTitleId)); } if (preset) { baseArgs.push('-Z', preset); } const filteredExtra = removeSelectionArgs(splitArgs(extraArgs)); const overrideArgs = [ '-a', normalizeTrackIdList(selectedAudioTrackIds).join(',') || 'none', '-s', subtitleTrackIds.length > 0 ? subtitleTrackIds.join(',') : 'none' ]; if (subtitleBurnTrackId !== null) { overrideArgs.push(`--subtitle-burned=${subtitleBurnTrackId}`); } if (subtitleDefaultTrackId !== null) { overrideArgs.push(`--subtitle-default=${subtitleDefaultTrackId}`); } if (resolvedSubtitleSelection.subtitleForcedTrackIndexes.length > 0) { overrideArgs.push(`--subtitle-forced=${resolvedSubtitleSelection.subtitleForcedTrackIndexes.join(',')}`); } const finalArgs = [...baseArgs, ...filteredExtra, ...overrideArgs]; return `${handBrakeCmd} ${finalArgs.map((arg) => shellQuote(arg)).join(' ')}`; } function toLang2(value) { const raw = String(value || '').trim().toLowerCase(); if (!raw) { return 'und'; } const map = { en: 'en', eng: 'en', de: 'de', deu: 'de', ger: 'de', tr: 'tr', tur: 'tr', fr: 'fr', fra: 'fr', fre: 'fr', es: 'es', spa: 'es', it: 'it', ita: 'it' }; if (map[raw]) { return map[raw]; } if (raw.length === 2) { return raw; } if (raw.length >= 3) { return raw.slice(0, 2); } return raw; } function simplifyCodec(type, value, hint = null) { const raw = String(value || '').trim(); const hintRaw = String(hint || '').trim(); const lower = raw.toLowerCase(); const merged = `${raw} ${hintRaw}`.toLowerCase(); if (!raw) { return '-'; } if (type === 'subtitle') { if (merged.includes('pgs')) { return 'PGS'; } return raw.toUpperCase(); } if (merged.includes('dts-hd ma') || merged.includes('dts hd ma')) { return 'DTS-HD MA'; } if (merged.includes('dts-hd hra') || merged.includes('dts hd hra')) { return 'DTS-HD HRA'; } if (merged.includes('dts-hd') || merged.includes('dts hd')) { return 'DTS-HD'; } if (merged.includes('dts') || merged.includes('dca')) { return 'DTS'; } if (merged.includes('truehd')) { return 'TRUEHD'; } if (merged.includes('e-ac-3') || merged.includes('eac3') || merged.includes('dd+')) { return 'E-AC-3'; } if (merged.includes('ac-3') || merged.includes('ac3') || merged.includes('dolby digital')) { return 'AC-3'; } const numeric = Number(raw); if (Number.isFinite(numeric)) { if (numeric === 262144) { return 'DTS-HD'; } if (numeric === 131072) { return 'DTS'; } } return raw.toUpperCase(); } function extractAudioVariant(hint) { const raw = String(hint || '').trim(); if (!raw) { return ''; } const paren = raw.match(/\(([^)]+)\)/); if (!paren) { return ''; } const parts = paren[1] .split(',') .map((item) => item.trim()) .filter(Boolean); const extras = parts.filter((item) => { const lower = item.toLowerCase(); if (lower.includes('dts') || lower.includes('ac3') || lower.includes('e-ac3') || lower.includes('eac3')) { return false; } if (/\d+(?:\.\d+)?\s*ch/i.test(item)) { return false; } if (/\d+\s*kbps/i.test(lower)) { return false; } return true; }); return extras.join(', '); } function channelCount(rawValue) { const raw = String(rawValue || '').trim().toLowerCase(); if (!raw) { return null; } if (raw.includes('7.1')) { return 8; } if (raw.includes('5.1')) { return 6; } if (raw.includes('stereo') || raw.includes('2.0') || raw.includes('downmix')) { return 2; } if (raw.includes('mono') || raw.includes('1.0')) { return 1; } const numeric = Number(raw); if (Number.isFinite(numeric) && numeric > 0) { if (Math.abs(numeric - 7.1) < 0.2) { return 8; } if (Math.abs(numeric - 5.1) < 0.2) { return 6; } return Math.trunc(numeric); } const match = raw.match(/(\d+)\s*ch/); if (match) { const value = Number(match[1]); return Number.isFinite(value) && value > 0 ? Math.trunc(value) : null; } return null; } function audioChannelLabel(rawValue) { const raw = String(rawValue || '').trim().toLowerCase(); const count = channelCount(rawValue); if (raw.includes('7.1') || count === 8) { return 'Surround 7.1'; } if (raw.includes('5.1') || count === 6) { return 'Surround 5.1'; } if (raw.includes('stereo') || raw.includes('2.0') || raw.includes('downmix') || count === 2) { return 'Stereo'; } if (count === 1) { return 'Mono'; } return ''; } const DEFAULT_AUDIO_FALLBACK_PREVIEW = 'av_aac'; function mapTrackToCopyCodec(track) { const raw = [ track?.codecToken, track?.format, track?.codecName, track?.description, track?.title ] .map((value) => String(value || '').trim().toLowerCase()) .filter(Boolean) .join(' '); if (!raw) { return null; } if (raw.includes('e-ac-3') || raw.includes('eac3') || raw.includes('dd+')) { return 'eac3'; } if (raw.includes('ac-3') || raw.includes('ac3') || raw.includes('dolby digital')) { return 'ac3'; } if (raw.includes('truehd')) { return 'truehd'; } if (raw.includes('dts-hd') || raw.includes('dtshd')) { return 'dtshd'; } if (raw.includes('dca') || raw.includes('dts')) { return 'dts'; } if (raw.includes('aac')) { return 'aac'; } if (raw.includes('flac')) { return 'flac'; } if (raw.includes('mp3') || raw.includes('mpeg audio')) { return 'mp3'; } if (raw.includes('opus')) { return 'opus'; } if (raw.includes('pcm') || raw.includes('lpcm')) { return 'lpcm'; } return null; } function resolveAudioEncoderPreviewLabel(track, encoderToken, copyMask, fallbackEncoder) { const normalizedToken = String(encoderToken || '').trim().toLowerCase(); if (!normalizedToken || normalizedToken === 'preset-default') { return 'Preset-Default (HandBrake)'; } if (normalizedToken.startsWith('copy')) { const sourceCodec = mapTrackToCopyCodec(track); const explicitCopyCodec = normalizedToken.includes(':') ? normalizedToken.split(':').slice(1).join(':').trim().toLowerCase() : null; const normalizedCopyMask = Array.isArray(copyMask) ? copyMask.map((item) => String(item || '').trim().toLowerCase()).filter(Boolean) : []; let canCopy = false; let effectiveCodec = sourceCodec; if (explicitCopyCodec) { canCopy = Boolean(sourceCodec && sourceCodec === explicitCopyCodec); } else if (sourceCodec && normalizedCopyMask.length > 0) { canCopy = normalizedCopyMask.includes(sourceCodec); // DTS-HD MA contains an embedded DTS core. When dtshd is not in the copy // mask but dts is, HandBrake will extract and copy the DTS core layer. if (!canCopy && sourceCodec === 'dtshd' && normalizedCopyMask.includes('dts')) { canCopy = true; effectiveCodec = 'dts'; } } if (canCopy) { return `Copy (${effectiveCodec || track?.format || 'Quelle'})`; } const fallback = String(fallbackEncoder || DEFAULT_AUDIO_FALLBACK_PREVIEW).trim().toLowerCase() || DEFAULT_AUDIO_FALLBACK_PREVIEW; return `Fallback Transcode (${fallback})`; } return `Transcode (${normalizedToken})`; } function parseAudioSelectorFromArgs(extraArgsString, baseSelector) { const base = baseSelector && typeof baseSelector === 'object' ? baseSelector : {}; const args = String(extraArgsString || '').trim(); if (!args) { return base; } // Tokenize: split on whitespace but respect quoted strings const tokens = []; let current = ''; let inQuote = null; for (let i = 0; i < args.length; i++) { const ch = args[i]; if (inQuote) { if (ch === inQuote) { inQuote = null; } else { current += ch; } } else if (ch === '"' || ch === "'") { inQuote = ch; } else if (ch === ' ' || ch === '\t') { if (current) { tokens.push(current); current = ''; } } else { current += ch; } } if (current) { tokens.push(current); } const result = { ...base }; const getNext = (i) => (i + 1 < tokens.length ? tokens[i + 1] : null); for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; // Support both --flag=value and --flag value const eqIdx = token.indexOf('='); const flag = eqIdx !== -1 ? token.slice(0, eqIdx) : token; const inlineVal = eqIdx !== -1 ? token.slice(eqIdx + 1) : null; const getValue = () => { if (inlineVal !== null) return inlineVal; const next = getNext(i); if (next && !next.startsWith('-')) { i++; return next; } return null; }; if (flag === '--aencoder') { const val = getValue(); if (val) { result.encoders = val.split(',').map((s) => s.trim()).filter(Boolean); result.encoderSource = 'args'; } } else if (flag === '--audio-copy-mask') { const val = getValue(); if (val) { result.copyMask = val.split(',').map((s) => s.trim()).filter(Boolean); } } else if (flag === '--audio-fallback') { const val = getValue(); if (val) { result.fallbackEncoder = val.trim(); } } } return result; } function buildAudioActionPreviewSummary(track, selectedIndex, audioSelector) { const selector = audioSelector && typeof audioSelector === 'object' ? audioSelector : {}; const availableEncoders = Array.isArray(selector.encoders) ? selector.encoders : []; let encoderPlan = []; if (selector.encoderSource === 'args' && availableEncoders.length > 0) { const safeIndex = Number.isFinite(selectedIndex) && selectedIndex >= 0 ? selectedIndex : 0; encoderPlan = [availableEncoders[Math.min(safeIndex, availableEncoders.length - 1)]]; } else if (availableEncoders.length > 0) { encoderPlan = [...availableEncoders]; } else { encoderPlan = ['preset-default']; } const labels = encoderPlan .map((token) => resolveAudioEncoderPreviewLabel(track, token, selector.copyMask, selector.fallbackEncoder)) .filter(Boolean); return labels.join(' + ') || 'Übernehmen'; } function TrackList({ title, tracks, type = 'generic', allowSelection = false, selectedTrackIds = [], selectedSubtitleVariantSelection = {}, selectedSubtitleLanguageOrder = [], onToggleTrack = null, onToggleSubtitleVariant = null, audioSelector = null }) { const orderedTracks = (Array.isArray(tracks) ? [...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 || '').localeCompare(String(b?.language || '')); }); if (type === 'subtitle') { const selectedVariants = normalizeSubtitleVariantSelectionMap(selectedSubtitleVariantSelection || {}); const sortedSubtitleTracks = orderedTracks .filter((track) => !isDuplicateSubtitleTrack(track)); const renderVariantRow = ({ key, checked, disabled, onChange, text, confidence = null, indent = false }) => { const confidenceColor = confidence === 'high' ? '#2e7d32' : confidence === 'medium' ? '#f57c00' : '#c62828'; return (
); }; const resolveSubtitleActionInfo = (track, selected) => { const base = String(track?.subtitlePreviewSummary || track?.subtitleActionSummary || '').trim(); if (allowSelection) { return selected ? 'Übernehmen' : 'Nicht übernommen'; } return base || (selected ? 'Übernehmen' : 'Nicht übernommen'); }; return (

{title}

{!tracks || tracks.length === 0 ? (

Keine Einträge.

) : (
{sortedSubtitleTracks.map((track) => { const burned = isBurnedSubtitleTrack(track); const language = normalizeSubtitleLanguage(track?.language || track?.languageLabel || 'und'); const displayLanguage = toLang2(track?.language || track?.languageLabel || 'und'); const displayCodec = simplifyCodec('subtitle', track?.format, track?.description || track?.title); const flags = resolveSubtitleTrackVariantFlags(track); const confidenceRaw = String(track?.sourceConfidence || '').trim().toLowerCase(); const confidence = confidenceRaw === 'high' || confidenceRaw === 'medium' || confidenceRaw === 'low' ? confidenceRaw : 'low'; const languageSelection = selectedVariants[language] || { full: false, forced: false }; if (flags.isForcedOnly) { const checked = allowSelection ? Boolean(languageSelection.forced) : Boolean(track?.selectedForEncode); const disabled = !allowSelection || burned; const actionInfo = resolveSubtitleActionInfo(track, checked); return (
{renderVariantRow({ key: `${title}-${track.id}-forced-only`, checked, disabled, onChange: (event) => { if (disabled || typeof onToggleSubtitleVariant !== 'function') { return; } onToggleSubtitleVariant(language, 'forced', event.target.checked); }, text: `#${track.id} | ${displayLanguage} | Automatische Übersetzungen (${displayCodec})`, confidence })} Untertitel: {actionInfo}
); } if (flags.fullHasForced) { const checkedFull = allowSelection ? Boolean(languageSelection.full) : Boolean(track?.selectedForEncode); const checkedForced = allowSelection ? Boolean(languageSelection.forced) : false; const disabled = !allowSelection || burned; const actionInfo = resolveSubtitleActionInfo(track, checkedFull || checkedForced); return (
{`#${track.id} | ${displayLanguage}`}
{renderVariantRow({ key: `${title}-${track.id}-combo-full`, checked: checkedFull, disabled, onChange: (event) => { if (disabled || typeof onToggleSubtitleVariant !== 'function') { return; } onToggleSubtitleVariant(language, 'full', event.target.checked); }, text: `Komplette Untertitel (${displayCodec})`, indent: true })} {renderVariantRow({ key: `${title}-${track.id}-combo-forced`, checked: checkedForced, disabled, onChange: (event) => { if (disabled || typeof onToggleSubtitleVariant !== 'function') { return; } onToggleSubtitleVariant(language, 'forced', event.target.checked); }, text: `Automatische Übersetzungen (${displayCodec})`, confidence, indent: true })} Untertitel: {actionInfo}
); } const checked = allowSelection ? Boolean(languageSelection.full) : Boolean(track?.selectedForEncode); const disabled = !allowSelection || burned; const actionInfo = resolveSubtitleActionInfo(track, checked); return (
{renderVariantRow({ key: `${title}-${track.id}-full`, checked, disabled, onChange: (event) => { if (disabled || typeof onToggleSubtitleVariant !== 'function') { return; } onToggleSubtitleVariant(language, 'full', event.target.checked); }, text: `#${track.id} | ${displayLanguage} | Komplette Untertitel (${displayCodec})` })} Untertitel: {actionInfo}
); })}
)}
Automatische Übersetzungen werden nur eingeblendet, wenn im Film eine andere Sprache gesprochen wird. Komplette Untertitel zeigen alle Dialoge an. Blu-ray Untertitel liegen meist als PGS (Bildformat) vor. Confidence-Index (nur für Automatische Übersetzungen): high = explizites Track-Signal erkannt, medium = "forced" im Titel erkannt, low = heuristische Einschätzung.
); } const selectedIds = normalizeTrackIdList(selectedTrackIds); const checkedTrackOrder = orderedTracks .map((track) => normalizeTrackId(track?.id)) .filter((trackId, index) => { if (trackId === null) { return false; } if (allowSelection) { return selectedIds.includes(trackId); } const track = orderedTracks[index]; return Boolean(track?.selectedForEncode); }); return (

{title}

{!tracks || tracks.length === 0 ? (

Keine Einträge.

) : (
{orderedTracks.map((track) => { const trackId = normalizeTrackId(track.id); const checked = allowSelection ? (trackId !== null && selectedIds.includes(trackId)) : Boolean(track.selectedForEncode); const selectedIndex = trackId !== null ? checkedTrackOrder.indexOf(trackId) : -1; const actionInfo = type === 'audio' ? (checked ? (() => { // When encoder comes from user preset extraArgs, always recompute // (server-stored summary reflects a different/default preset) if (audioSelector?.encoderSource === 'args') { return buildAudioActionPreviewSummary(track, selectedIndex, audioSelector); } const base = String(track.encodePreviewSummary || track.encodeActionSummary || '').trim(); const staleUnselectedSummary = /^nicht übernommen$/i.test(base); if (staleUnselectedSummary) { return buildAudioActionPreviewSummary(track, selectedIndex, audioSelector); } return base || buildAudioActionPreviewSummary(track, selectedIndex, audioSelector); })() : 'Nicht übernommen') : null; const displayLanguage = toLang2(track.language || track.languageLabel || 'und'); const displayHint = track.description || track.title; const displayCodec = simplifyCodec(type, track.format, displayHint); const displayChannelCount = channelCount(track.channels); const displayAudioTitle = audioChannelLabel(track.channels); const audioVariant = type === 'audio' ? extractAudioVariant(displayHint) : ''; const disabled = !allowSelection; let displayText = `#${track.id} | ${displayLanguage} | ${displayCodec}`; if (type === 'audio') { if (displayChannelCount !== null) { displayText += ` | ${displayChannelCount}ch`; } if (displayAudioTitle) { displayText += ` | ${displayAudioTitle}`; } if (audioVariant) { displayText += ` | ${audioVariant}`; } } return (
{actionInfo ? Encode: {actionInfo} : null}
); })}
)}
); } function normalizeTitleId(value) { const parsed = Number(value); if (!Number.isFinite(parsed) || parsed <= 0) { return null; } return Math.trunc(parsed); } function normalizeScriptId(value) { const parsed = Number(value); if (!Number.isFinite(parsed) || parsed <= 0) { return null; } return Math.trunc(parsed); } function normalizeChainId(value) { const parsed = Number(value); if (!Number.isFinite(parsed) || parsed <= 0) { return null; } return Math.trunc(parsed); } function normalizeScriptIdList(values) { const list = Array.isArray(values) ? values : []; const seen = new Set(); const output = []; for (const value of list) { const normalized = normalizeScriptId(value); if (normalized === null) { continue; } const key = String(normalized); if (seen.has(key)) { continue; } seen.add(key); output.push(normalized); } return output; } export default function MediaInfoReviewPanel({ review, presetDisplayValue = '', commandOutputPath = null, selectedEncodeTitleId = null, allowTitleSelection = false, onSelectEncodeTitle = null, allowTrackSelection = false, trackSelectionByTitle = {}, onTrackSelectionChange = null, onSubtitleVariantSelectionChange = null, availableScripts = [], availableChains = [], preEncodeItems = [], postEncodeItems = [], allowEncodeItemSelection = false, onAddPreEncodeItem = null, onChangePreEncodeItem = null, onRemovePreEncodeItem = null, onReorderPreEncodeItem = null, onAddPostEncodeItem = null, onChangePostEncodeItem = null, onRemovePostEncodeItem = null, onReorderPostEncodeItem = null, userPresets = [], selectedUserPresetId = null, onUserPresetChange = null, selectedHandBrakePreset = '' }) { if (!review) { return

Keine Mediainfo-Daten vorhanden.

; } const titles = review.titles || []; const currentSelectedId = normalizeTitleId(selectedEncodeTitleId) || normalizeTitleId(review.encodeInputTitleId); const encodeInputTitle = titles.find((item) => item.id === currentSelectedId) || null; const processedFiles = Number(review.processedFiles || titles.length || 0); const totalFiles = Number(review.totalFiles || titles.length || 0); const playlistRecommendation = review.playlistRecommendation || null; const rawPreset = String(review.selectors?.preset || '').trim(); const presetLabel = String(presetDisplayValue || rawPreset).trim() || '(kein Preset)'; // User preset resolution const normalizedUserPresets = Array.isArray(userPresets) ? userPresets : []; const selectedUserPreset = selectedUserPresetId ? normalizedUserPresets.find((p) => Number(p.id) === Number(selectedUserPresetId)) || null : null; const hasUserPresets = normalizedUserPresets.length > 0; const allowUserPresetSelection = hasUserPresets && typeof onUserPresetChange === 'function' && allowEncodeItemSelection; const isUserPresetActive = Boolean(selectedUserPreset); const trimmedHandBrakePreset = String(selectedHandBrakePreset || '').trim(); const isHandBrakePresetActive = !isUserPresetActive && Boolean(trimmedHandBrakePreset); const effectivePresetOverride = isUserPresetActive ? { handbrakePreset: selectedUserPreset.handbrakePreset || '', extraArgs: selectedUserPreset.extraArgs || '' } : (isHandBrakePresetActive ? { handbrakePreset: trimmedHandBrakePreset, extraArgs: '' } : null); const effectiveAudioSelector = effectivePresetOverride?.extraArgs ? parseAudioSelectorFromArgs(effectivePresetOverride.extraArgs, review?.selectors?.audio) : (review?.selectors?.audio || null); const presetSourceLabel = isUserPresetActive ? `User-Preset: ${selectedUserPreset?.name || 'Preset'}` : (isHandBrakePresetActive ? 'HandBrake-Preset' : ''); const scriptCatalog = (Array.isArray(availableScripts) ? availableScripts : []) .map((item) => ({ id: normalizeScriptId(item?.id), name: String(item?.name || '').trim() })) .filter((item) => item.id !== null && item.name.length > 0); const scriptById = new Map(scriptCatalog.map((item) => [item.id, item])); const chainCatalog = (Array.isArray(availableChains) ? availableChains : []) .map((item) => ({ id: normalizeChainId(item?.id), name: String(item?.name || '').trim() })) .filter((item) => item.id !== null && item.name.length > 0); const chainById = new Map(chainCatalog.map((item) => [item.id, item])); const makeHandleDrop = (items, onReorder) => (event, targetIndex) => { if (!allowEncodeItemSelection || typeof onReorder !== 'function' || items.length < 2) { return; } event.preventDefault(); const fromIndex = Number(event.dataTransfer?.getData('text/plain')); if (!Number.isInteger(fromIndex)) { return; } onReorder(fromIndex, targetIndex); }; const handlePreDrop = makeHandleDrop(preEncodeItems, onReorderPreEncodeItem); const handlePostDrop = makeHandleDrop(postEncodeItems, onReorderPostEncodeItem); return (
{allowUserPresetSelection && (
({ label: `${p.name}${p.mediaType !== 'all' ? ` [${p.mediaType === 'bluray' ? 'Blu-ray' : p.mediaType === 'dvd' ? 'DVD' : 'Sonstiges'}]` : ''}`, value: Number(p.id) })) ]} onChange={(e) => onUserPresetChange(e.value)} placeholder="(Einstellungen-Fallback)" style={{ width: '100%' }} /> {selectedUserPreset && (
{selectedUserPreset.handbrakePreset ? -Z {selectedUserPreset.handbrakePreset} : (kein Preset-Name)} {selectedUserPreset.extraArgs && ( Args: {selectedUserPreset.extraArgs} )} {selectedUserPreset.description && ( {selectedUserPreset.description} )}
)}
)}
Preset:{' '} {effectivePresetOverride ? (effectivePresetOverride.handbrakePreset || '(kein Preset)') : presetLabel} {effectivePresetOverride && presetSourceLabel && ( ({presetSourceLabel}) )}
Extra Args:{' '} {effectivePresetOverride ? (effectivePresetOverride.extraArgs || '(keine)') : (review.selectors?.extraArgs || '(keine)')} {effectivePresetOverride && isUserPresetActive && !selectedUserPreset?.extraArgs && ( (aus User-Preset) )}
Preset-Profil: {effectivePresetOverride ? (isUserPresetActive ? 'user-preset' : 'handbrake-preset') : (review.selectors?.presetProfileSource || '-')}
MIN_LENGTH_MINUTES: {review.minLengthMinutes}
Encode Input: {encodeInputTitle?.fileName || '-'}
Audio Auswahl: {effectiveAudioSelector?.mode || '-'}
Audio Encoder: {(effectiveAudioSelector?.encoders || []).join(', ') || 'Preset-Default'}
Audio Copy-Mask: {(effectiveAudioSelector?.copyMask || []).join(', ') || '-'}
Audio Fallback: {effectiveAudioSelector?.fallbackEncoder || '-'}
Subtitle Auswahl: {review.selectors?.subtitle?.mode || '-'}
Subtitle Flags: {review.selectors?.subtitle?.burnBehavior === 'first' ? 'burned(first)' : '-'}
{review.partial ? ( Zwischenstand: {processedFiles}/{totalFiles} Datei(en) analysiert. ) : null} {playlistRecommendation ? (
Empfehlung: {playlistRecommendation.playlistFile || '-'} {playlistRecommendation.reviewTitleId ? ` (Titel #${playlistRecommendation.reviewTitleId})` : ''} {playlistRecommendation.reason ? {playlistRecommendation.reason} : null}
) : null} {Array.isArray(review.notes) && review.notes.length > 0 ? (
{review.notes.map((note, idx) => ( {note} ))}
) : null} {/* Pre-Encode Items (scripts + chains unified) */} {(allowEncodeItemSelection || preEncodeItems.length > 0) ? (

Pre-Encode Ausführungen (optional)

{scriptCatalog.length === 0 && chainCatalog.length === 0 ? ( Keine Skripte oder Ketten konfiguriert. In den Settings anlegen. ) : null} {preEncodeItems.length === 0 ? ( Keine Pre-Encode Ausführungen ausgewählt. ) : null} {preEncodeItems.map((item, rowIndex) => { const isScript = item.type === 'script'; const canDrag = allowEncodeItemSelection && preEncodeItems.length > 1; const scriptObj = isScript ? (scriptById.get(normalizeScriptId(item.id)) || null) : null; const chainObj = !isScript ? (chainById.get(Number(item.id)) || null) : null; const name = isScript ? (scriptObj?.name || `Skript #${item.id}`) : (chainObj?.name || `Kette #${item.id}`); const usedScriptIds = new Set( preEncodeItems .filter((it, i) => it.type === 'script' && i !== rowIndex) .map((it) => normalizeScriptId(it.id)) .filter((id) => id !== null) .map((id) => String(id)) ); const usedChainIds = new Set( preEncodeItems .filter((it, i) => it.type === 'chain' && i !== rowIndex) .map((it) => normalizeChainId(it.id)) .filter((id) => id !== null) .map((id) => String(id)) ); const scriptOptions = scriptCatalog.map((s) => ({ label: s.name, value: s.id, disabled: usedScriptIds.has(String(s.id)) })); const chainOptions = chainCatalog.map((c) => ({ label: c.name, value: c.id, disabled: usedChainIds.has(String(c.id)) })); return (
{ if (!canDrag) return; event.preventDefault(); if (event.dataTransfer) event.dataTransfer.dropEffect = 'move'; }} onDrop={(event) => handlePreDrop(event, rowIndex)} > {allowEncodeItemSelection ? ( <> { if (!canDrag) return; event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData('text/plain', String(rowIndex)); }} /> {isScript ? ( onChangePreEncodeItem?.(rowIndex, 'script', event.value)} className="full-width" /> ) : ( onChangePreEncodeItem?.(rowIndex, 'chain', event.value)} className="full-width" /> )}
); })} {allowEncodeItemSelection ? (
{scriptCatalog.length > preEncodeItems.filter((i) => i.type === 'script').length ? (
) : null} Ausführung vor dem Encoding, strikt nacheinander. Bei Fehler wird der Encode abgebrochen.
) : null} {/* Post-Encode Items (scripts + chains unified) */}

Post-Encode Ausführungen (optional)

{scriptCatalog.length === 0 && chainCatalog.length === 0 ? ( Keine Skripte oder Ketten konfiguriert. In den Settings anlegen. ) : null} {postEncodeItems.length === 0 ? ( Keine Post-Encode Ausführungen ausgewählt. ) : null} {postEncodeItems.map((item, rowIndex) => { const isScript = item.type === 'script'; const canDrag = allowEncodeItemSelection && postEncodeItems.length > 1; const scriptObj = isScript ? (scriptById.get(normalizeScriptId(item.id)) || null) : null; const chainObj = !isScript ? (chainById.get(Number(item.id)) || null) : null; const name = isScript ? (scriptObj?.name || `Skript #${item.id}`) : (chainObj?.name || `Kette #${item.id}`); const usedScriptIds = new Set( postEncodeItems .filter((it, i) => it.type === 'script' && i !== rowIndex) .map((it) => normalizeScriptId(it.id)) .filter((id) => id !== null) .map((id) => String(id)) ); const usedChainIds = new Set( postEncodeItems .filter((it, i) => it.type === 'chain' && i !== rowIndex) .map((it) => normalizeChainId(it.id)) .filter((id) => id !== null) .map((id) => String(id)) ); const scriptOptions = scriptCatalog.map((s) => ({ label: s.name, value: s.id, disabled: usedScriptIds.has(String(s.id)) })); const chainOptions = chainCatalog.map((c) => ({ label: c.name, value: c.id, disabled: usedChainIds.has(String(c.id)) })); return (
{ if (!canDrag) return; event.preventDefault(); if (event.dataTransfer) event.dataTransfer.dropEffect = 'move'; }} onDrop={(event) => handlePostDrop(event, rowIndex)} > {allowEncodeItemSelection ? ( <> { if (!canDrag) return; event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData('text/plain', String(rowIndex)); }} /> {isScript ? ( onChangePostEncodeItem?.(rowIndex, 'script', event.value)} className="full-width" /> ) : ( onChangePostEncodeItem?.(rowIndex, 'chain', event.value)} className="full-width" /> )}
); })} {allowEncodeItemSelection ? (
{scriptCatalog.length > postEncodeItems.filter((i) => i.type === 'script').length ? (
) : null} Ausführung nach erfolgreichem Encode, strikt nacheinander (Drag-and-Drop möglich).

Titel

{titles.length === 0 ? (

Keine Titel analysiert.

) : titles.map((title) => { const titleEligible = title?.eligibleForEncode !== false; const titleChecked = allowTitleSelection ? (currentSelectedId !== null ? currentSelectedId === normalizeTitleId(title.id) : Boolean(title.selectedForEncode)) : Boolean(title.selectedForEncode); const titleSelectionEntry = trackSelectionByTitle?.[title.id] || trackSelectionByTitle?.[String(title.id)] || {}; const subtitleTracks = Array.isArray(title.subtitleTracks) ? title.subtitleTracks : []; const selectableSubtitleTrackIds = subtitleTracks .filter((track) => !isBurnedSubtitleTrack(track) && !isDuplicateSubtitleTrack(track)) .map((track) => normalizeTrackId(track?.id)) .filter((id) => id !== null); const selectableSubtitleTrackIdSet = new Set(selectableSubtitleTrackIds.map((id) => String(id))); const sortedSelectableSubtitleTracks = subtitleTracks .filter((track) => !isBurnedSubtitleTrack(track) && !isDuplicateSubtitleTrack(track)) .slice() .sort((a, b) => (normalizeTrackId(a?.id) || Number.MAX_SAFE_INTEGER) - (normalizeTrackId(b?.id) || Number.MAX_SAFE_INTEGER)); const defaultAudioTrackIds = (Array.isArray(title.audioTracks) ? title.audioTracks : []) .filter((track) => Boolean(track?.selectedByRule)) .map((track) => normalizeTrackId(track?.id)) .filter((id) => id !== null); const defaultSubtitleTrackIds = subtitleTracks .filter((track) => Boolean(track?.selectedByRule) && !isBurnedSubtitleTrack(track) && !isDuplicateSubtitleTrack(track)) .map((track) => normalizeTrackId(track?.id)) .filter((id) => id !== null); const defaultSubtitleVariantSelection = {}; const defaultSubtitleLanguageOrder = []; const subtitleLanguageOrderSeen = new Set(); for (const track of sortedSelectableSubtitleTracks) { const language = normalizeSubtitleLanguage(track?.language || track?.languageLabel || 'und'); if (!subtitleLanguageOrderSeen.has(language)) { subtitleLanguageOrderSeen.add(language); defaultSubtitleLanguageOrder.push(language); } if (!Boolean(track?.selectedByRule)) { continue; } if (!defaultSubtitleVariantSelection[language]) { defaultSubtitleVariantSelection[language] = { full: false, forced: false }; } const flags = resolveSubtitleTrackVariantFlags(track); if (flags.isForcedOnly) { defaultSubtitleVariantSelection[language].forced = true; } else { defaultSubtitleVariantSelection[language].full = true; } } const selectedAudioTrackIds = normalizeTrackIdList( Array.isArray(titleSelectionEntry?.audioTrackIds) ? titleSelectionEntry.audioTrackIds : defaultAudioTrackIds ); const selectedSubtitleTrackIds = normalizeTrackIdList( Array.isArray(titleSelectionEntry?.subtitleTrackIds) ? titleSelectionEntry.subtitleTrackIds : defaultSubtitleTrackIds ).filter((id) => selectableSubtitleTrackIdSet.has(String(id))); const selectedSubtitleVariantSelection = normalizeSubtitleVariantSelectionMap( titleSelectionEntry?.subtitleVariantSelection || defaultSubtitleVariantSelection ); const selectedSubtitleLanguageOrder = normalizeSubtitleLanguageOrder( Array.isArray(titleSelectionEntry?.subtitleLanguageOrder) ? titleSelectionEntry.subtitleLanguageOrder : defaultSubtitleLanguageOrder ); const allowTrackSelectionForTitle = Boolean( allowTrackSelection && allowTitleSelection && titleChecked ); return (
{title.playlistFile || title.playlistEvaluationLabel || title.playlistSegmentCommand ? (
Playlist: {title.playlistFile || '-'} {title.playlistRecommended ? ' | empfohlen' : ''} {title.playlistEvaluationLabel ? ( Bewertung: {title.playlistEvaluationLabel} ) : null} {title.playlistSegmentCommand ? ( Analyse-Command: {title.playlistSegmentCommand} ) : null} {Array.isArray(title.playlistSegmentFiles) && title.playlistSegmentFiles.length > 0 ? (
Segment-Dateien anzeigen ({title.playlistSegmentFiles.length})
{title.playlistSegmentFiles.join('\n')}
) : ( Segment-Ausgabe: keine m2ts-Einträge gefunden. )}
) : null}
{ if (!allowTrackSelectionForTitle || typeof onTrackSelectionChange !== 'function') { return; } onTrackSelectionChange(title.id, 'audio', trackId, checked); }} /> !isBurnedSubtitleTrack(track)) : subtitleTracks} type="subtitle" allowSelection={allowTrackSelectionForTitle} selectedTrackIds={selectedSubtitleTrackIds} selectedSubtitleVariantSelection={selectedSubtitleVariantSelection} selectedSubtitleLanguageOrder={selectedSubtitleLanguageOrder} onToggleSubtitleVariant={(language, variant, checked) => { if (!allowTrackSelectionForTitle || typeof onSubtitleVariantSelectionChange !== 'function') { return; } onSubtitleVariantSelectionChange(title.id, language, variant, checked); }} />
{titleChecked ? (() => { const commandPreview = buildHandBrakeCommandPreview({ review, title, selectedAudioTrackIds, selectedSubtitleTrackIds, selectedSubtitleVariantSelection, selectedSubtitleLanguageOrder, commandOutputPath, presetOverride: effectivePresetOverride }); return (
Finaler HandBrakeCLI-Befehl (Preview):
{commandPreview}
); })() : null}
); })}
); }