0.12.0-18 Tests and Converter Fixes
This commit is contained in:
@@ -1,12 +1,19 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Card } from 'primereact/card';
|
||||
import { Tag } from 'primereact/tag';
|
||||
import { ProgressBar } from 'primereact/progressbar';
|
||||
import { Button } from 'primereact/button';
|
||||
import { Dropdown } from 'primereact/dropdown';
|
||||
import MediaInfoReviewPanel from './MediaInfoReviewPanel';
|
||||
import { api } from '../api/client';
|
||||
import { getStatusLabel, getStatusSeverity } from '../utils/statusPresentation';
|
||||
|
||||
const CONVERTER_VIDEO_OUTPUT_FORMATS = [
|
||||
{ label: 'MKV', value: 'mkv' },
|
||||
{ label: 'MP4', value: 'mp4' },
|
||||
{ label: 'M4V', value: 'm4v' }
|
||||
];
|
||||
|
||||
function normalizeTitleId(value) {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||
@@ -120,6 +127,9 @@ function normalizeMediaProfile(value) {
|
||||
if (['audiobook', 'audio_book', 'audio book', 'book'].includes(raw)) {
|
||||
return 'audiobook';
|
||||
}
|
||||
if (raw === 'converter') {
|
||||
return 'converter';
|
||||
}
|
||||
if (['other', 'sonstiges', 'unknown'].includes(raw)) {
|
||||
return 'other';
|
||||
}
|
||||
@@ -453,6 +463,37 @@ function buildOutputPathPreview(settings, mediaProfile, metadata, fallbackJobId
|
||||
return `${root}/${baseName}.${ext}`;
|
||||
}
|
||||
|
||||
function buildConverterOutputPathPreview(settings, review, metadata, fallbackJobId = null) {
|
||||
const explicitPath = String(review?.outputPath || review?.outputDir || '').trim();
|
||||
if (explicitPath) {
|
||||
return explicitPath;
|
||||
}
|
||||
|
||||
const movieDir = String(settings?.converter_movie_dir || '').trim();
|
||||
if (!movieDir) {
|
||||
return null;
|
||||
}
|
||||
const title = metadata?.title || (fallbackJobId ? `job-${fallbackJobId}` : 'job');
|
||||
const year = metadata?.year || new Date().getFullYear();
|
||||
const template = String(settings?.converter_output_template_video || '{title}').trim() || '{title}';
|
||||
const rendered = renderTemplate(template, { title, year });
|
||||
const segments = rendered
|
||||
.replace(/\\/g, '/')
|
||||
.replace(/\/+/g, '/')
|
||||
.replace(/^\/+|\/+$/g, '')
|
||||
.split('/')
|
||||
.map((seg) => sanitizeFileName(seg))
|
||||
.filter(Boolean);
|
||||
const baseName = segments.length > 0 ? segments[segments.length - 1] : 'untitled';
|
||||
const folderParts = segments.slice(0, -1);
|
||||
const ext = String(review?.outputFormat || 'mkv').trim() || 'mkv';
|
||||
const root = movieDir.replace(/\/+$/g, '');
|
||||
if (folderParts.length > 0) {
|
||||
return `${root}/${folderParts.join('/')}/${baseName}.${ext}`;
|
||||
}
|
||||
return `${root}/${baseName}.${ext}`;
|
||||
}
|
||||
|
||||
export default function PipelineStatusCard({
|
||||
jobId = null,
|
||||
pipeline,
|
||||
@@ -470,6 +511,7 @@ export default function PipelineStatusCard({
|
||||
onCancel,
|
||||
onRetry,
|
||||
onDeleteJob,
|
||||
onInputsChanged,
|
||||
isQueued = false,
|
||||
busy
|
||||
}) {
|
||||
@@ -502,6 +544,13 @@ export default function PipelineStatusCard({
|
||||
const [postEncodeItems, setPostEncodeItems] = useState([]);
|
||||
const [userPresets, setUserPresets] = useState([]);
|
||||
const [selectedUserPresetId, setSelectedUserPresetId] = useState(null);
|
||||
const [handBrakePresetOptions, setHandBrakePresetOptions] = useState([]);
|
||||
const [selectedHandBrakePreset, setSelectedHandBrakePreset] = useState('');
|
||||
const [selectedOutputFormat, setSelectedOutputFormat] = useState('mkv');
|
||||
const [converterConfigSaving, setConverterConfigSaving] = useState(false);
|
||||
const [converterConfigError, setConverterConfigError] = useState(null);
|
||||
const converterConfigLatestRef = useRef(null);
|
||||
const converterConfigSaveTimeoutRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
@@ -512,7 +561,7 @@ export default function PipelineStatusCard({
|
||||
api.getHandBrakePresets(),
|
||||
api.getScripts(),
|
||||
api.getScriptChains(),
|
||||
api.getUserPresets()
|
||||
api.getUserPresets(null, { forceRefresh: true })
|
||||
]);
|
||||
if (!cancelled) {
|
||||
const categories = settingsResponse.status === 'fulfilled'
|
||||
@@ -523,6 +572,7 @@ export default function PipelineStatusCard({
|
||||
? (presetsResponse.value?.options || [])
|
||||
: [];
|
||||
setPresetDisplayMap(buildPresetDisplayMap(presetOptions));
|
||||
setHandBrakePresetOptions(Array.isArray(presetOptions) ? presetOptions : []);
|
||||
const scripts = scriptsResponse.status === 'fulfilled'
|
||||
? (Array.isArray(scriptsResponse.value?.scripts) ? scriptsResponse.value.scripts : [])
|
||||
: [];
|
||||
@@ -548,6 +598,7 @@ export default function PipelineStatusCard({
|
||||
setScriptCatalog([]);
|
||||
setChainCatalog([]);
|
||||
setUserPresets([]);
|
||||
setHandBrakePresetOptions([]);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -570,14 +621,25 @@ export default function PipelineStatusCard({
|
||||
...normalizeScriptIdList(mediaInfoReview?.postEncodeScriptIds || []).map((id) => ({ type: 'script', id })),
|
||||
...normChain(mediaInfoReview?.postEncodeChainIds).map((id) => ({ type: 'chain', id }))
|
||||
]);
|
||||
const nextOutputFormat = String(mediaInfoReview?.outputFormat || 'mkv').trim().toLowerCase() || 'mkv';
|
||||
setSelectedOutputFormat(nextOutputFormat);
|
||||
const userPresetId = Number(mediaInfoReview?.userPreset?.id);
|
||||
setSelectedUserPresetId(Number.isFinite(userPresetId) && userPresetId > 0 ? Math.trunc(userPresetId) : null);
|
||||
if (Number.isFinite(userPresetId) && userPresetId > 0) {
|
||||
setSelectedUserPresetId(Math.trunc(userPresetId));
|
||||
setSelectedHandBrakePreset('');
|
||||
} else {
|
||||
setSelectedUserPresetId(null);
|
||||
const hbPresetName = String(mediaInfoReview?.userPreset?.handbrakePreset || '').trim();
|
||||
setSelectedHandBrakePreset(hbPresetName);
|
||||
}
|
||||
}, [
|
||||
mediaInfoReview?.encodeInputTitleId,
|
||||
mediaInfoReview?.generatedAt,
|
||||
mediaInfoReview?.reviewConfirmedAt,
|
||||
mediaInfoReview?.prefilledFromPreviousRunAt,
|
||||
mediaInfoReview?.outputFormat,
|
||||
mediaInfoReview?.userPreset?.id,
|
||||
mediaInfoReview?.userPreset?.handbrakePreset,
|
||||
retryJobId
|
||||
]);
|
||||
|
||||
@@ -609,6 +671,11 @@ export default function PipelineStatusCard({
|
||||
const reviewPlaylistDecisionRequired = Boolean(mediaInfoReview?.playlistDecisionRequired);
|
||||
const hasSelectedEncodeTitle = Boolean(normalizeTitleId(selectedEncodeTitleId));
|
||||
const canConfirmReview = !reviewPlaylistDecisionRequired || hasSelectedEncodeTitle;
|
||||
const trimmedHandBrakePreset = String(selectedHandBrakePreset || '').trim();
|
||||
const hasSelectedPreset = Boolean(selectedUserPresetId) || Boolean(trimmedHandBrakePreset);
|
||||
const converterMediaType = String(mediaInfoReview?.converterMediaType || 'video').trim().toLowerCase() || 'video';
|
||||
const showConverterConfig = jobMediaProfile === 'converter' && converterMediaType !== 'audio';
|
||||
const requiresPresetSelection = showConverterConfig;
|
||||
|
||||
// Filter user presets by job media profile ('all' presets always shown)
|
||||
const filteredUserPresets = (Array.isArray(userPresets) ? userPresets : []).filter((p) => {
|
||||
@@ -616,11 +683,17 @@ export default function PipelineStatusCard({
|
||||
if (!jobMediaProfile) {
|
||||
return true;
|
||||
}
|
||||
// Converter jobs may reuse any user preset; do not restrict by preset media type here.
|
||||
if (jobMediaProfile === 'converter') {
|
||||
return true;
|
||||
}
|
||||
return presetMediaType === 'all' || presetMediaType === jobMediaProfile;
|
||||
});
|
||||
const canStartReadyJob = isPreRipReview
|
||||
const effectiveUserPresets = filteredUserPresets;
|
||||
const canStartReadyJob = (isPreRipReview
|
||||
? Boolean(retryJobId)
|
||||
: Boolean(retryJobId && encodeInputPath);
|
||||
: Boolean(retryJobId && encodeInputPath))
|
||||
&& (!requiresPresetSelection || hasSelectedPreset);
|
||||
const canRestartEncodeFromLastSettings = Boolean(
|
||||
(state === 'ERROR' || state === 'CANCELLED')
|
||||
&& retryJobId
|
||||
@@ -631,6 +704,87 @@ export default function PipelineStatusCard({
|
||||
&& !running
|
||||
&& (pipeline?.context?.canRestartReviewFromRaw || pipeline?.context?.rawPath)
|
||||
);
|
||||
const allowConverterConfigEdit = showConverterConfig && !queueLocked && state === 'READY_TO_ENCODE';
|
||||
|
||||
const converterConfigPayload = useMemo(() => {
|
||||
if (!showConverterConfig) {
|
||||
return null;
|
||||
}
|
||||
let userPreset = null;
|
||||
if (selectedUserPresetId) {
|
||||
const preset = (Array.isArray(userPresets) ? userPresets : [])
|
||||
.find((p) => Number(p?.id) === Number(selectedUserPresetId));
|
||||
userPreset = preset
|
||||
? { id: preset.id, handbrakePreset: preset.handbrake_preset || null, extraArgs: preset.extra_args || '' }
|
||||
: { id: selectedUserPresetId };
|
||||
} else if (trimmedHandBrakePreset) {
|
||||
userPreset = { handbrakePreset: trimmedHandBrakePreset, extraArgs: '' };
|
||||
}
|
||||
return {
|
||||
converterMediaType,
|
||||
outputFormat: selectedOutputFormat,
|
||||
userPreset
|
||||
};
|
||||
}, [
|
||||
showConverterConfig,
|
||||
converterMediaType,
|
||||
selectedOutputFormat,
|
||||
selectedUserPresetId,
|
||||
trimmedHandBrakePreset,
|
||||
userPresets
|
||||
]);
|
||||
|
||||
const converterConfigSerialized = useMemo(
|
||||
() => (converterConfigPayload ? JSON.stringify(converterConfigPayload) : null),
|
||||
[converterConfigPayload]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
converterConfigLatestRef.current = null;
|
||||
setConverterConfigError(null);
|
||||
setConverterConfigSaving(false);
|
||||
}, [retryJobId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!allowConverterConfigEdit || !converterConfigPayload || !retryJobId) {
|
||||
return;
|
||||
}
|
||||
if (converterConfigLatestRef.current === null) {
|
||||
converterConfigLatestRef.current = converterConfigSerialized;
|
||||
return;
|
||||
}
|
||||
if (converterConfigSerialized === converterConfigLatestRef.current) {
|
||||
return;
|
||||
}
|
||||
if (converterConfigSaveTimeoutRef.current) {
|
||||
clearTimeout(converterConfigSaveTimeoutRef.current);
|
||||
}
|
||||
converterConfigSaveTimeoutRef.current = setTimeout(async () => {
|
||||
setConverterConfigSaving(true);
|
||||
setConverterConfigError(null);
|
||||
try {
|
||||
await api.converterUpdateJobConfig(retryJobId, converterConfigPayload);
|
||||
converterConfigLatestRef.current = converterConfigSerialized;
|
||||
onInputsChanged?.(retryJobId, converterConfigPayload);
|
||||
} catch (error) {
|
||||
setConverterConfigError(error?.message || 'Konfiguration konnte nicht gespeichert werden.');
|
||||
} finally {
|
||||
setConverterConfigSaving(false);
|
||||
}
|
||||
}, 650);
|
||||
return () => {
|
||||
if (converterConfigSaveTimeoutRef.current) {
|
||||
clearTimeout(converterConfigSaveTimeoutRef.current);
|
||||
converterConfigSaveTimeoutRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [
|
||||
allowConverterConfigEdit,
|
||||
converterConfigPayload,
|
||||
converterConfigSerialized,
|
||||
retryJobId,
|
||||
onInputsChanged
|
||||
]);
|
||||
|
||||
const waitingPlaylistRows = useMemo(() => {
|
||||
const evaluated = Array.isArray(playlistAnalysis?.evaluatedCandidates)
|
||||
@@ -780,10 +934,15 @@ export default function PipelineStatusCard({
|
||||
}, [running, retryJobId]);
|
||||
|
||||
const playlistDecisionRequiredBeforeStart = state === 'WAITING_FOR_USER_DECISION' && !handBrakeTitleDecisionRequired;
|
||||
const commandOutputPath = useMemo(
|
||||
() => buildOutputPathPreview(settingsMap, jobMediaProfile, selectedMetadata, retryJobId),
|
||||
[settingsMap, jobMediaProfile, selectedMetadata, retryJobId]
|
||||
);
|
||||
const commandOutputPath = useMemo(() => {
|
||||
if (jobMediaProfile === 'converter') {
|
||||
const reviewWithOutput = mediaInfoReview
|
||||
? { ...mediaInfoReview, outputFormat: selectedOutputFormat }
|
||||
: { outputFormat: selectedOutputFormat };
|
||||
return buildConverterOutputPathPreview(settingsMap, reviewWithOutput, selectedMetadata, retryJobId);
|
||||
}
|
||||
return buildOutputPathPreview(settingsMap, jobMediaProfile, selectedMetadata, retryJobId);
|
||||
}, [settingsMap, jobMediaProfile, mediaInfoReview, selectedMetadata, retryJobId, selectedOutputFormat]);
|
||||
const presetDisplayValue = useMemo(() => {
|
||||
const preset = String(mediaInfoReview?.selectors?.preset || '').trim();
|
||||
if (!preset) {
|
||||
@@ -791,6 +950,52 @@ export default function PipelineStatusCard({
|
||||
}
|
||||
return presetDisplayMap[preset] || preset;
|
||||
}, [mediaInfoReview?.selectors?.preset, presetDisplayMap]);
|
||||
const converterUserPresetOptions = useMemo(() => {
|
||||
if (!showConverterConfig) {
|
||||
return [];
|
||||
}
|
||||
const userOptions = (Array.isArray(effectiveUserPresets) ? effectiveUserPresets : [])
|
||||
.map((preset) => ({
|
||||
label: String(preset?.name || '').trim(),
|
||||
value: Number(preset?.id)
|
||||
}))
|
||||
.filter((option) => option.label && Number.isFinite(option.value));
|
||||
return [{ label: 'Kein User-Preset', value: null }, ...userOptions];
|
||||
}, [showConverterConfig, effectiveUserPresets]);
|
||||
const converterHandBrakePresetOptions = useMemo(() => {
|
||||
if (!showConverterConfig) {
|
||||
return [];
|
||||
}
|
||||
const list = Array.isArray(handBrakePresetOptions) ? handBrakePresetOptions : [];
|
||||
const hbOptions = list
|
||||
.map((option) => {
|
||||
const label = String(option?.label || '').trim();
|
||||
if (!label) {
|
||||
return null;
|
||||
}
|
||||
const disabled = Boolean(option?.disabled);
|
||||
const lowerLabel = label.toLowerCase();
|
||||
if (lowerLabel.includes('handbrake cli') || lowerLabel.includes('cli default')) {
|
||||
return null;
|
||||
}
|
||||
if (!disabled && (lowerLabel.includes('kein preset') || lowerLabel.includes('cli-parameter'))) {
|
||||
return null;
|
||||
}
|
||||
const value = disabled
|
||||
? String(option?.value || label)
|
||||
: String(option?.value || '').trim();
|
||||
if (!disabled && !value) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
label,
|
||||
value,
|
||||
disabled
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
return [{ label: 'Kein HandBrake-Preset', value: '' }, ...hbOptions];
|
||||
}, [showConverterConfig, handBrakePresetOptions]);
|
||||
const buildSelectedTrackSelectionForCurrentTitle = () => {
|
||||
const encodeTitleId = normalizeTitleId(selectedEncodeTitleId)
|
||||
|| normalizeTitleId(mediaInfoReview?.encodeInputTitleId)
|
||||
@@ -862,7 +1067,8 @@ export default function PipelineStatusCard({
|
||||
selectedPostScriptIds,
|
||||
selectedPreScriptIds,
|
||||
selectedPostChainIds,
|
||||
selectedPreChainIds
|
||||
selectedPreChainIds,
|
||||
selectedHandBrakePreset: trimmedHandBrakePreset
|
||||
};
|
||||
};
|
||||
|
||||
@@ -908,7 +1114,10 @@ export default function PipelineStatusCard({
|
||||
/>
|
||||
)}
|
||||
|
||||
{(state === 'METADATA_SELECTION' || state === 'WAITING_FOR_USER_DECISION') && retryJobId && typeof onOpenMetadata === 'function' ? (
|
||||
{(state === 'METADATA_SELECTION' || state === 'WAITING_FOR_USER_DECISION')
|
||||
&& retryJobId
|
||||
&& typeof onOpenMetadata === 'function'
|
||||
&& !(jobMediaProfile === 'converter' && !selectedMetadata?.imdbId) ? (
|
||||
<Button
|
||||
label="Metadaten öffnen"
|
||||
icon="pi pi-list"
|
||||
@@ -918,7 +1127,24 @@ export default function PipelineStatusCard({
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{!running && state !== 'METADATA_SELECTION' && state !== 'WAITING_FOR_USER_DECISION' && state !== 'IDLE' && state !== 'DISC_DETECTED' && retryJobId && typeof onReassignOmdb === 'function' ? (
|
||||
{state === 'METADATA_SELECTION' && jobMediaProfile === 'converter' && retryJobId ? (
|
||||
<Button
|
||||
label="Review starten"
|
||||
icon="pi pi-play"
|
||||
severity="info"
|
||||
onClick={() => onStart(retryJobId)}
|
||||
loading={busy}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{!running
|
||||
&& state !== 'METADATA_SELECTION'
|
||||
&& state !== 'WAITING_FOR_USER_DECISION'
|
||||
&& state !== 'IDLE'
|
||||
&& state !== 'DISC_DETECTED'
|
||||
&& retryJobId
|
||||
&& typeof onReassignOmdb === 'function'
|
||||
&& !(jobMediaProfile === 'converter' && state === 'READY_TO_START' && !selectedMetadata?.imdbId) ? (
|
||||
<Button
|
||||
label="OMDb neu zuordnen"
|
||||
icon="pi pi-search"
|
||||
@@ -931,9 +1157,9 @@ export default function PipelineStatusCard({
|
||||
|
||||
{state === 'READY_TO_START' && retryJobId ? (
|
||||
<Button
|
||||
label="Job starten"
|
||||
label={jobMediaProfile === 'converter' ? 'Review starten' : 'Job starten'}
|
||||
icon="pi pi-play"
|
||||
severity="success"
|
||||
severity={jobMediaProfile === 'converter' ? 'info' : 'success'}
|
||||
onClick={() => onStart(retryJobId)}
|
||||
loading={busy}
|
||||
/>
|
||||
@@ -975,9 +1201,10 @@ export default function PipelineStatusCard({
|
||||
selectedPostScriptIds,
|
||||
selectedPreScriptIds,
|
||||
selectedPostChainIds,
|
||||
selectedPreChainIds
|
||||
selectedPreChainIds,
|
||||
selectedHandBrakePreset
|
||||
} = buildSelectedTrackSelectionForCurrentTitle();
|
||||
await onStart(retryJobId, {
|
||||
const startPayload = {
|
||||
ensureConfirmed: true,
|
||||
selectedEncodeTitleId: encodeTitleId,
|
||||
selectedTrackSelection,
|
||||
@@ -985,8 +1212,10 @@ export default function PipelineStatusCard({
|
||||
selectedPreEncodeScriptIds: selectedPreScriptIds,
|
||||
selectedPostEncodeChainIds: selectedPostChainIds,
|
||||
selectedPreEncodeChainIds: selectedPreChainIds,
|
||||
selectedUserPresetId: selectedUserPresetId || null
|
||||
});
|
||||
selectedHandBrakePreset
|
||||
};
|
||||
startPayload.selectedUserPresetId = selectedUserPresetId || null;
|
||||
await onStart(retryJobId, startPayload);
|
||||
}}
|
||||
loading={busy}
|
||||
disabled={!canStartReadyJob || !canConfirmReview}
|
||||
@@ -1203,6 +1432,91 @@ export default function PipelineStatusCard({
|
||||
{reviewPlaylistDecisionRequired ? ' Bitte den korrekten Titel per Checkbox auswählen.' : ''}
|
||||
</small>
|
||||
) : null}
|
||||
{showConverterConfig ? (
|
||||
<div style={{ marginTop: '0.75rem', marginBottom: '0.75rem', padding: '0.75rem', border: '1px solid var(--surface-border, #e0e0e0)', borderRadius: '6px', background: 'var(--surface-ground, #f8f8f8)' }}>
|
||||
<div style={{ display: 'grid', gap: '0.75rem', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))' }}>
|
||||
<div>
|
||||
<label style={{ display: 'block', marginBottom: '0.35rem', fontWeight: 600 }}>Ausgabeformat</label>
|
||||
<Dropdown
|
||||
value={selectedOutputFormat}
|
||||
options={CONVERTER_VIDEO_OUTPUT_FORMATS}
|
||||
onChange={(e) => setSelectedOutputFormat(e.value)}
|
||||
style={{ width: '100%' }}
|
||||
disabled={!allowConverterConfigEdit}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ display: 'block', marginBottom: '0.35rem', fontWeight: 600 }}>User-Preset</label>
|
||||
<Dropdown
|
||||
value={selectedUserPresetId}
|
||||
options={converterUserPresetOptions}
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
onChange={(e) => {
|
||||
const rawValue = e?.value && typeof e.value === 'object' ? e.value.value : e?.value;
|
||||
const nextValue = rawValue === null || rawValue === undefined || String(rawValue).trim() === ''
|
||||
? null
|
||||
: Number(rawValue);
|
||||
const normalized = Number.isFinite(nextValue) && nextValue > 0 ? Math.trunc(nextValue) : null;
|
||||
setSelectedUserPresetId(normalized);
|
||||
if (normalized) {
|
||||
setSelectedHandBrakePreset('');
|
||||
}
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
placeholder="User-Preset auswählen …"
|
||||
showClear
|
||||
disabled={!allowConverterConfigEdit || Boolean(trimmedHandBrakePreset) || converterUserPresetOptions.length <= 1}
|
||||
/>
|
||||
{converterUserPresetOptions.length <= 1 ? (
|
||||
<small style={{ color: 'var(--text-color-secondary)' }}>Keine User-Presets vorhanden.</small>
|
||||
) : null}
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ display: 'block', marginBottom: '0.35rem', fontWeight: 600 }}>HandBrake-Preset</label>
|
||||
<Dropdown
|
||||
value={trimmedHandBrakePreset}
|
||||
options={converterHandBrakePresetOptions}
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
onChange={(e) => {
|
||||
const rawValue = e?.value && typeof e.value === 'object' ? e.value.value : e?.value;
|
||||
const next = String(rawValue || '').trim();
|
||||
setSelectedHandBrakePreset(next);
|
||||
if (next) {
|
||||
setSelectedUserPresetId(null);
|
||||
}
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
placeholder="HandBrake-Preset auswählen …"
|
||||
filter
|
||||
optionDisabled="disabled"
|
||||
showClear
|
||||
disabled={!allowConverterConfigEdit || Boolean(selectedUserPresetId) || converterHandBrakePresetOptions.length <= 1}
|
||||
/>
|
||||
{converterHandBrakePresetOptions.length <= 1 ? (
|
||||
<small style={{ color: 'var(--text-color-secondary)' }}>Keine HandBrake-Presets geladen.</small>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{allowConverterConfigEdit && !hasSelectedPreset ? (
|
||||
<small style={{ display: 'block', marginTop: '0.5rem', color: 'var(--red-600)' }}>
|
||||
Bitte wähle ein User- oder HandBrake-Preset aus, bevor das Encoding starten kann.
|
||||
</small>
|
||||
) : null}
|
||||
{allowConverterConfigEdit ? (
|
||||
converterConfigError ? (
|
||||
<small style={{ display: 'block', marginTop: '0.5rem', color: 'var(--red-600)' }}>
|
||||
{converterConfigError}
|
||||
</small>
|
||||
) : (
|
||||
<small style={{ display: 'block', marginTop: '0.5rem', color: 'var(--text-color-secondary)' }}>
|
||||
{converterConfigSaving ? 'Speichere Konfiguration …' : 'Konfiguration gespeichert'}
|
||||
</small>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<MediaInfoReviewPanel
|
||||
review={mediaInfoReview}
|
||||
presetDisplayValue={presetDisplayValue}
|
||||
@@ -1292,9 +1606,10 @@ export default function PipelineStatusCard({
|
||||
availableChains={chainCatalog}
|
||||
preEncodeItems={preEncodeItems}
|
||||
postEncodeItems={postEncodeItems}
|
||||
userPresets={filteredUserPresets}
|
||||
userPresets={effectiveUserPresets}
|
||||
selectedUserPresetId={selectedUserPresetId}
|
||||
onUserPresetChange={(presetId) => setSelectedUserPresetId(presetId)}
|
||||
onUserPresetChange={showConverterConfig ? null : (presetId) => setSelectedUserPresetId(presetId)}
|
||||
selectedHandBrakePreset={selectedHandBrakePreset}
|
||||
allowEncodeItemSelection={state === 'READY_TO_ENCODE' && !queueLocked}
|
||||
onAddPreEncodeItem={(itemType) => {
|
||||
setPreEncodeItems((prev) => {
|
||||
|
||||
Reference in New Issue
Block a user