0.12.0-16 misc fixes
This commit is contained in:
@@ -300,6 +300,18 @@ function resolveMediaType(job) {
|
||||
if (['audiobook', 'audio_book', 'audio book', 'book'].includes(raw)) {
|
||||
return 'audiobook';
|
||||
}
|
||||
if (raw === 'converter') {
|
||||
return 'converter';
|
||||
}
|
||||
}
|
||||
if (String(encodePlan?.mediaProfile || '').trim().toLowerCase() === 'converter') {
|
||||
return 'converter';
|
||||
}
|
||||
if (String(job?.media_type || '').trim().toLowerCase() === 'converter') {
|
||||
return 'converter';
|
||||
}
|
||||
if (resolveConverterMediaType(job)) {
|
||||
return 'converter';
|
||||
}
|
||||
const statusCandidates = [job?.status, job?.last_state, job?.makemkvInfo?.lastState];
|
||||
if (statusCandidates.some((v) => String(v || '').trim().toUpperCase().startsWith('CD_'))) {
|
||||
@@ -325,6 +337,26 @@ function resolveMediaType(job) {
|
||||
return 'other';
|
||||
}
|
||||
|
||||
function resolveConverterMediaType(job) {
|
||||
const encodePlan = job?.encodePlan && typeof job.encodePlan === 'object' ? job.encodePlan : null;
|
||||
const candidates = [
|
||||
encodePlan?.converterMediaType,
|
||||
job?.converterMediaType,
|
||||
job?.makemkvInfo?.converterMediaType,
|
||||
job?.mediainfoInfo?.converterMediaType
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
const raw = String(candidate || '').trim().toLowerCase();
|
||||
if (!raw) {
|
||||
continue;
|
||||
}
|
||||
if (raw === 'audio' || raw === 'video' || raw === 'iso') {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveCdDetails(job) {
|
||||
const encodePlan = job?.encodePlan && typeof job.encodePlan === 'object' ? job.encodePlan : {};
|
||||
const makemkvInfo = job?.makemkvInfo && typeof job.makemkvInfo === 'object' ? job.makemkvInfo : {};
|
||||
@@ -595,10 +627,12 @@ export default function JobDetailDialog({
|
||||
onDownloadArchive,
|
||||
onDownloadOutputFolder,
|
||||
onRemoveFromQueue,
|
||||
onCancel,
|
||||
isQueued = false,
|
||||
omdbAssignBusy = false,
|
||||
cdMetadataAssignBusy = false,
|
||||
actionBusy = false,
|
||||
cancelBusy = false,
|
||||
reencodeBusy = false,
|
||||
deleteEntryBusy = false,
|
||||
downloadBusyTarget = null,
|
||||
@@ -609,11 +643,19 @@ export default function JobDetailDialog({
|
||||
const isUnencodedOrphanImport = Boolean(
|
||||
job?.makemkvInfo?.source === 'orphan_raw_import' && !job?.handbrakeInfo
|
||||
);
|
||||
const running = ['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING', 'CD_RIPPING', 'CD_ENCODING'].includes(job?.status);
|
||||
const statusUpper = String(job?.status || '').trim().toUpperCase();
|
||||
const running = ['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING', 'CD_RIPPING', 'CD_ENCODING'].includes(statusUpper);
|
||||
const softCancelable = ['READY_TO_START', 'READY_TO_ENCODE', 'METADATA_SELECTION', 'WAITING_FOR_USER_DECISION', 'CD_METADATA_SELECTION', 'CD_READY_TO_RIP'].includes(statusUpper);
|
||||
const showCancelAction = (running || softCancelable) && typeof onCancel === 'function';
|
||||
const showFinalLog = !running;
|
||||
const mediaType = resolveMediaType(job);
|
||||
const isCd = mediaType === 'cd';
|
||||
const isAudiobook = mediaType === 'audiobook';
|
||||
const isConverter = mediaType === 'converter';
|
||||
const converterMediaType = isConverter ? (resolveConverterMediaType(job) || 'video') : null;
|
||||
const converterMediaTypeLabel = converterMediaType === 'audio'
|
||||
? 'Audio'
|
||||
: (converterMediaType === 'iso' ? 'ISO (Video)' : 'Video');
|
||||
const cdRawPathCandidates = [
|
||||
job?.raw_path,
|
||||
job?.makemkvInfo?.rawPath,
|
||||
@@ -704,6 +746,62 @@ export default function JobDetailDialog({
|
||||
&& mediaType !== 'audiobook'
|
||||
&& typeof onRestartReview === 'function'
|
||||
);
|
||||
const converterPlan = isConverter && job?.encodePlan && typeof job.encodePlan === 'object'
|
||||
? job.encodePlan
|
||||
: {};
|
||||
const converterMetadata = isConverter && converterPlan?.metadata && typeof converterPlan.metadata === 'object'
|
||||
? converterPlan.metadata
|
||||
: {};
|
||||
const converterInputPaths = isConverter
|
||||
? (
|
||||
Array.isArray(converterPlan?.inputPaths) && converterPlan.inputPaths.length > 0
|
||||
? converterPlan.inputPaths
|
||||
: [converterPlan?.inputPath || job?.raw_path]
|
||||
)
|
||||
.map((value) => String(value || '').trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
const converterTrackList = isConverter && Array.isArray(converterPlan?.tracks)
|
||||
? converterPlan.tracks
|
||||
: [];
|
||||
const converterOutputFormat = String(
|
||||
converterPlan?.outputFormat
|
||||
|| job?.handbrakeInfo?.format
|
||||
|| converterPlan?.format
|
||||
|| ''
|
||||
).trim().toLowerCase();
|
||||
const converterPresetLabel = String(
|
||||
converterPlan?.userPreset?.name
|
||||
|| converterPlan?.userPreset?.handbrakePreset
|
||||
|| ''
|
||||
).trim() || null;
|
||||
const converterAudioQualityLabel = isConverter && converterMediaType === 'audio'
|
||||
? (() => {
|
||||
const opts = converterPlan?.audioFormatOptions && typeof converterPlan.audioFormatOptions === 'object'
|
||||
? converterPlan.audioFormatOptions
|
||||
: {};
|
||||
if (converterOutputFormat === 'flac') {
|
||||
return `Kompression ${Number(opts?.flacCompression ?? 5)}`;
|
||||
}
|
||||
if (converterOutputFormat === 'mp3') {
|
||||
const mode = String(opts?.mp3Mode || 'cbr').trim().toLowerCase();
|
||||
if (mode === 'vbr') {
|
||||
return `VBR V${Number(opts?.mp3Quality ?? 4)}`;
|
||||
}
|
||||
return `CBR ${Number(opts?.mp3Bitrate ?? 192)} kbps`;
|
||||
}
|
||||
if (converterOutputFormat === 'aac') {
|
||||
return `${Number(opts?.aacBitrate ?? 256)} kbps`;
|
||||
}
|
||||
if (converterOutputFormat === 'opus') {
|
||||
return `${Number(opts?.opusBitrate ?? 160)} kbps`;
|
||||
}
|
||||
if (converterOutputFormat === 'ogg') {
|
||||
return `Qualität ${Number(opts?.oggQuality ?? 6)}`;
|
||||
}
|
||||
return converterOutputFormat ? converterOutputFormat.toUpperCase() : '-';
|
||||
})()
|
||||
: null;
|
||||
const canDeleteEntry = !running && typeof onDeleteEntry === 'function';
|
||||
const queueLocked = Boolean(isQueued && job?.id);
|
||||
const logCount = Number(job?.log_count || 0);
|
||||
@@ -719,7 +817,7 @@ export default function JobDetailDialog({
|
||||
? 'DVD'
|
||||
: isCd
|
||||
? 'Audio CD'
|
||||
: (isAudiobook ? 'Audiobook' : 'Sonstiges Medium');
|
||||
: (isAudiobook ? 'Audiobook' : (isConverter ? `Converter ${converterMediaTypeLabel}` : 'Sonstiges Medium'));
|
||||
const mediaTypeIcon = mediaType === 'bluray'
|
||||
? blurayIndicatorIcon
|
||||
: mediaType === 'dvd'
|
||||
@@ -742,6 +840,11 @@ export default function JobDetailDialog({
|
||||
const canDownloadOutput = Boolean(job?.output_path && job?.outputStatus?.exists && typeof onDownloadArchive === 'function');
|
||||
const outputFolders = Array.isArray(job?.outputFolders) ? job.outputFolders : [];
|
||||
const hasAnyOutputFolder = outputFolders.length > 0 || Boolean(job?.outputStatus?.exists);
|
||||
const canShowGeneralEncodeActions = canResumeReady
|
||||
|| typeof onRestartEncode === 'function'
|
||||
|| typeof onRestartReview === 'function'
|
||||
|| typeof onReencode === 'function';
|
||||
const useAudioPosterLayout = isCd || isAudiobook || (isConverter && converterMediaType === 'audio');
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@@ -759,9 +862,9 @@ export default function JobDetailDialog({
|
||||
|
||||
<div className="job-head-row">
|
||||
{job.poster_url && job.poster_url !== 'N/A' ? (
|
||||
<img src={job.poster_url} alt={job.title || 'Poster'} className={isCd || isAudiobook ? 'poster-large-audio' : 'poster-large'} />
|
||||
<img src={job.poster_url} alt={job.title || 'Poster'} className={useAudioPosterLayout ? 'poster-large-audio' : 'poster-large'} />
|
||||
) : (
|
||||
<div className={`${isCd || isAudiobook ? 'poster-large-audio' : 'poster-large'} poster-fallback`}>{isCd || isAudiobook ? 'Kein Cover' : 'Kein Poster'}</div>
|
||||
<div className={`${useAudioPosterLayout ? 'poster-large-audio' : 'poster-large'} poster-fallback`}>{useAudioPosterLayout ? 'Kein Cover' : 'Kein Poster'}</div>
|
||||
)}
|
||||
|
||||
<div className="job-film-info-grid">
|
||||
@@ -812,6 +915,70 @@ export default function JobDetailDialog({
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
) : isConverter ? (
|
||||
<section className="job-meta-block job-meta-block-film">
|
||||
<h4>{converterMediaType === 'audio' ? 'Converter Audio' : 'Converter Video'}</h4>
|
||||
<div className="job-meta-list">
|
||||
<div className="job-meta-item">
|
||||
<strong>Titel:</strong>
|
||||
<span>{job.title || converterMetadata?.albumTitle || (job?.id ? `Job #${job.id}` : '-')}</span>
|
||||
</div>
|
||||
<div className="job-meta-item">
|
||||
<strong>Typ:</strong>
|
||||
<span>{converterMediaTypeLabel}</span>
|
||||
</div>
|
||||
<div className="job-meta-item">
|
||||
<strong>Eingaben:</strong>
|
||||
<span>{converterInputPaths.length > 0 ? `${converterInputPaths.length} Datei${converterInputPaths.length !== 1 ? 'en' : ''}` : '-'}</span>
|
||||
</div>
|
||||
<div className="job-meta-item">
|
||||
<strong>Format:</strong>
|
||||
<span>{converterOutputFormat ? converterOutputFormat.toUpperCase() : '-'}</span>
|
||||
</div>
|
||||
{converterMediaType === 'audio' ? (
|
||||
<>
|
||||
<div className="job-meta-item">
|
||||
<strong>Album:</strong>
|
||||
<span>{converterMetadata?.albumTitle || job?.title || (job?.id ? `Job #${job.id}` : '-')}</span>
|
||||
</div>
|
||||
<div className="job-meta-item">
|
||||
<strong>Interpret:</strong>
|
||||
<span>{converterMetadata?.albumArtist || '-'}</span>
|
||||
</div>
|
||||
<div className="job-meta-item">
|
||||
<strong>Jahr:</strong>
|
||||
<span>{converterMetadata?.albumYear || job?.year || '-'}</span>
|
||||
</div>
|
||||
<div className="job-meta-item">
|
||||
<strong>Tracks:</strong>
|
||||
<span>{converterTrackList.length > 0 ? converterTrackList.length : converterInputPaths.length || '-'}</span>
|
||||
</div>
|
||||
<div className="job-meta-item">
|
||||
<strong>Qualität:</strong>
|
||||
<span>{converterAudioQualityLabel || '-'}</span>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="job-meta-item">
|
||||
<strong>Preset:</strong>
|
||||
<span>{converterPresetLabel || '-'}</span>
|
||||
</div>
|
||||
<div className="job-meta-item">
|
||||
<strong>HandBrake Titel:</strong>
|
||||
<span>{converterPlan?.handBrakeTitleId || '-'}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="job-meta-item">
|
||||
<strong>Medium:</strong>
|
||||
<span className="job-step-cell">
|
||||
<img src={mediaTypeIcon} alt={mediaTypeAlt} title={mediaTypeLabel} className="media-indicator-icon" />
|
||||
<span>{mediaTypeLabel}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<>
|
||||
<section className="job-meta-block job-meta-block-film">
|
||||
@@ -937,7 +1104,7 @@ export default function JobDetailDialog({
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span><strong>{isAudiobook ? 'Import:' : 'Backup:'}</strong> <BoolState value={job?.backupSuccess} /></span>
|
||||
<span><strong>{isAudiobook || isConverter ? 'Import:' : 'Backup:'}</strong> <BoolState value={job?.backupSuccess} /></span>
|
||||
<span className="job-infos-sep">|</span>
|
||||
<span><strong>Encode:</strong> <BoolState value={job?.encodeSuccess} /></span>
|
||||
</>
|
||||
@@ -950,7 +1117,7 @@ export default function JobDetailDialog({
|
||||
<div><strong>Ende:</strong> {job.end_time || '-'}</div>
|
||||
{/* Zeile 3+4: Pfade */}
|
||||
<PathField
|
||||
label={isCd ? 'WAV:' : 'RAW:'}
|
||||
label={isCd ? 'WAV:' : (isConverter ? 'Input:' : 'RAW:')}
|
||||
value={isCd ? (job.raw_path || job.output_path) : job.raw_path}
|
||||
onDownload={canDownloadRaw ? () => onDownloadArchive?.(job, 'raw') : null}
|
||||
downloadDisabled={!canDownloadRaw}
|
||||
@@ -990,7 +1157,72 @@ export default function JobDetailDialog({
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{!isCd && !isAudiobook && (hasConfiguredSelection || encodePlanUserPreset || job.encodePlan?.minLengthMinutes != null || Array.isArray(job.encodePlan?.titles)) ? (
|
||||
{isConverter ? (
|
||||
<section className="job-meta-block job-meta-block-full">
|
||||
<h4>Encode-Konfiguration</h4>
|
||||
<div className="job-meta-grid job-meta-grid-compact">
|
||||
<div>
|
||||
<strong>Typ:</strong> {converterMediaTypeLabel}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Format:</strong> {converterOutputFormat ? converterOutputFormat.toUpperCase() : '-'}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Eingabe-Modus:</strong> {converterPlan?.isFolder ? 'Ordner' : (converterPlan?.isSharedAudio ? 'Gemeinsam' : 'Einzeln')}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Eingabe-Dateien:</strong> {converterInputPaths.length > 0 ? converterInputPaths.length : '-'}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Preset:</strong> {converterPresetLabel || '-'}
|
||||
</div>
|
||||
<div>
|
||||
<strong>HandBrake Titel:</strong> {converterPlan?.handBrakeTitleId || '-'}
|
||||
</div>
|
||||
</div>
|
||||
{converterMediaType === 'audio' ? (
|
||||
<div className="job-meta-grid job-meta-grid-compact">
|
||||
<div>
|
||||
<strong>Album:</strong> {converterMetadata?.albumTitle || job?.title || (job?.id ? `Job #${job.id}` : '-')}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Interpret:</strong> {converterMetadata?.albumArtist || '-'}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Jahr:</strong> {converterMetadata?.albumYear || job?.year || '-'}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Qualität:</strong> {converterAudioQualityLabel || '-'}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{converterInputPaths.length > 0 ? (
|
||||
<div className="track-group">
|
||||
{converterInputPaths.map((inputPath, index) => (
|
||||
<div key={`${inputPath}-${index}`} className="track-item">
|
||||
<span>#{index + 1} | {inputPath}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{converterMediaType === 'audio' && converterTrackList.length > 0 ? (
|
||||
<div className="track-group">
|
||||
{converterTrackList.map((track, index) => {
|
||||
const position = Number(track?.position) > 0 ? Math.trunc(Number(track.position)) : (index + 1);
|
||||
const title = String(track?.title || '').trim() || `Track ${position}`;
|
||||
const artist = String(track?.artist || '').trim();
|
||||
return (
|
||||
<div key={`${position}-${title}`} className="track-item">
|
||||
<span>#{position} | {artist ? `${artist} - ` : ''}{title}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{!isCd && !isAudiobook && !isConverter && (hasConfiguredSelection || encodePlanUserPreset || job.encodePlan?.minLengthMinutes != null || Array.isArray(job.encodePlan?.titles)) ? (
|
||||
<section className="job-meta-block job-meta-block-full">
|
||||
<h4>Encode-Konfiguration</h4>
|
||||
{/* Zeile 1: Preset + Mindestlaufzeit */}
|
||||
@@ -1193,13 +1425,13 @@ export default function JobDetailDialog({
|
||||
<section className="job-meta-block job-meta-block-full">
|
||||
<h4>Logs</h4>
|
||||
<div className="job-json-grid">
|
||||
{!isCd && !isAudiobook ? <JsonView title="OMDb Info" value={job.omdbInfo} /> : null}
|
||||
{!isCd && !isAudiobook && !isConverter ? <JsonView title="OMDb Info" value={job.omdbInfo} /> : null}
|
||||
{isCd ? <JsonView title="MusicBrainz" value={job.makemkvInfo?.selectedMetadata ?? null} /> : null}
|
||||
{isAudiobook ? <JsonView title="Metadaten" value={job.handbrakeInfo?.metadata ?? job.makemkvInfo?.selectedMetadata ?? null} /> : null}
|
||||
<JsonView title={isCd ? 'cdparanoia Info' : (isAudiobook ? 'Audiobook Info' : 'MakeMKV Info')} value={job.makemkvInfo} />
|
||||
<JsonView title={isCd ? 'cdparanoia Info' : (isAudiobook ? 'Audiobook Info' : (isConverter ? 'Converter Analyze Info' : 'MakeMKV Info'))} value={job.makemkvInfo} />
|
||||
{!isCd && !isAudiobook ? <JsonView title="Mediainfo Info" value={job.mediainfoInfo} /> : null}
|
||||
<JsonView title={isCd ? 'Rip-Plan' : 'Encode Plan'} value={job.encodePlan} />
|
||||
<JsonView title={isCd ? 'Rip-Info' : (isAudiobook ? 'FFmpeg Info' : 'HandBrake Info')} value={job.handbrakeInfo} />
|
||||
<JsonView title={isCd ? 'Rip-Plan' : (isConverter ? 'Converter Plan' : 'Encode Plan')} value={job.encodePlan} />
|
||||
<JsonView title={isCd ? 'Rip-Info' : (isAudiobook ? 'FFmpeg Info' : (isConverter ? 'Converter Encode Info' : 'HandBrake Info'))} value={job.handbrakeInfo} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1257,6 +1489,27 @@ export default function JobDetailDialog({
|
||||
</div>
|
||||
) : (
|
||||
<div className="actions-section">
|
||||
{showCancelAction ? (
|
||||
<div className="actions-group">
|
||||
<div className="actions-group-label"><i className="pi pi-ban" /> {running ? 'Laufender Job' : 'Wartender Job'}</div>
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label="Job abbrechen"
|
||||
icon="pi pi-times"
|
||||
severity="warning"
|
||||
size="small"
|
||||
onClick={() => onCancel?.(job)}
|
||||
loading={cancelBusy}
|
||||
/>
|
||||
<span className="action-desc">
|
||||
{running
|
||||
? 'Bricht den aktuell laufenden Job sofort ab.'
|
||||
: `Bricht den wartenden Job im Status ${statusUpper || '-'} ab.`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{isCd ? (
|
||||
<div className="actions-group">
|
||||
<div className="actions-group-label"><i className="pi pi-play-circle" /> Audio CD</div>
|
||||
@@ -1304,73 +1557,77 @@ export default function JobDetailDialog({
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="actions-group">
|
||||
<div className="actions-group-label"><i className="pi pi-play-circle" /> Kodierung</div>
|
||||
{canResumeReady ? (
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label="Im Dashboard öffnen"
|
||||
icon="pi pi-window-maximize"
|
||||
severity="info"
|
||||
outlined
|
||||
size="small"
|
||||
onClick={() => onResumeReady?.(job)}
|
||||
loading={actionBusy}
|
||||
/>
|
||||
<span className="action-desc">Öffnet den wartenden Job im Dashboard zur Weiterverarbeitung.</span>
|
||||
</div>
|
||||
) : null}
|
||||
{typeof onRestartEncode === 'function' ? (
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label="Encode neu starten"
|
||||
icon="pi pi-play"
|
||||
severity="success"
|
||||
size="small"
|
||||
onClick={() => onRestartEncode?.(job)}
|
||||
loading={actionBusy}
|
||||
disabled={!canRestartEncode}
|
||||
/>
|
||||
<span className="action-desc">Startet {isAudiobook ? 'FFmpeg' : 'HandBrake'} mit den zuletzt gespeicherten Einstellungen direkt neu — ohne Review.</span>
|
||||
</div>
|
||||
) : null}
|
||||
{typeof onRestartReview === 'function' ? (
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label={isAudiobook ? 'Kapitel-Auswahl öffnen' : 'Spur-Auswahl öffnen'}
|
||||
icon="pi pi-list"
|
||||
severity="info"
|
||||
outlined
|
||||
size="small"
|
||||
onClick={() => onRestartReview?.(job)}
|
||||
loading={actionBusy}
|
||||
disabled={!canRestartReview || isUnencodedOrphanImport}
|
||||
/>
|
||||
<span className="action-desc">{isAudiobook
|
||||
? 'Öffnet die Kapitel-Auswahl erneut, um Kapitel anzupassen bevor der Encode startet.'
|
||||
: 'Öffnet die Spur- und Preset-Auswahl erneut, um Einstellungen vor dem Encode zu ändern.'
|
||||
}</span>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label="Neustart"
|
||||
icon="pi pi-sync"
|
||||
severity="info"
|
||||
size="small"
|
||||
onClick={() => isUnencodedOrphanImport ? onRestartReview?.(job) : onReencode?.(job)}
|
||||
loading={isUnencodedOrphanImport ? actionBusy : reencodeBusy}
|
||||
disabled={isUnencodedOrphanImport ? !canRestartReview : (!canReencode || typeof onReencode !== 'function')}
|
||||
/>
|
||||
<span className="action-desc">{isAudiobook
|
||||
? 'Liest die AAX-Datei neu ein und öffnet danach die Kapitel-Auswahl. Sinnvoll wenn sich die Quelldatei geändert hat.'
|
||||
: 'Analysiert die Quelldatei neu (MediaInfo, Playlist-Check) und öffnet dann die Spur-Auswahl. Unterschied zu „Spur-Auswahl öffnen": der Analyse-Schritt wird komplett wiederholt.'
|
||||
}</span>
|
||||
canShowGeneralEncodeActions ? (
|
||||
<div className="actions-group">
|
||||
<div className="actions-group-label"><i className="pi pi-play-circle" /> Kodierung</div>
|
||||
{canResumeReady ? (
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label="Im Dashboard öffnen"
|
||||
icon="pi pi-window-maximize"
|
||||
severity="info"
|
||||
outlined
|
||||
size="small"
|
||||
onClick={() => onResumeReady?.(job)}
|
||||
loading={actionBusy}
|
||||
/>
|
||||
<span className="action-desc">Öffnet den wartenden Job im Dashboard zur Weiterverarbeitung.</span>
|
||||
</div>
|
||||
) : null}
|
||||
{typeof onRestartEncode === 'function' ? (
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label="Encode neu starten"
|
||||
icon="pi pi-play"
|
||||
severity="success"
|
||||
size="small"
|
||||
onClick={() => onRestartEncode?.(job)}
|
||||
loading={actionBusy}
|
||||
disabled={!canRestartEncode}
|
||||
/>
|
||||
<span className="action-desc">Startet {isAudiobook ? 'FFmpeg' : 'HandBrake'} mit den zuletzt gespeicherten Einstellungen direkt neu — ohne Review.</span>
|
||||
</div>
|
||||
) : null}
|
||||
{typeof onRestartReview === 'function' ? (
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label={isAudiobook ? 'Kapitel-Auswahl öffnen' : 'Spur-Auswahl öffnen'}
|
||||
icon="pi pi-list"
|
||||
severity="info"
|
||||
outlined
|
||||
size="small"
|
||||
onClick={() => onRestartReview?.(job)}
|
||||
loading={actionBusy}
|
||||
disabled={!canRestartReview || isUnencodedOrphanImport}
|
||||
/>
|
||||
<span className="action-desc">{isAudiobook
|
||||
? 'Öffnet die Kapitel-Auswahl erneut, um Kapitel anzupassen bevor der Encode startet.'
|
||||
: 'Öffnet die Spur- und Preset-Auswahl erneut, um Einstellungen vor dem Encode zu ändern.'
|
||||
}</span>
|
||||
</div>
|
||||
) : null}
|
||||
{typeof onReencode === 'function' ? (
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label="Neustart"
|
||||
icon="pi pi-sync"
|
||||
severity="info"
|
||||
size="small"
|
||||
onClick={() => isUnencodedOrphanImport ? onRestartReview?.(job) : onReencode?.(job)}
|
||||
loading={isUnencodedOrphanImport ? actionBusy : reencodeBusy}
|
||||
disabled={isUnencodedOrphanImport ? !canRestartReview : !canReencode}
|
||||
/>
|
||||
<span className="action-desc">{isAudiobook
|
||||
? 'Liest die AAX-Datei neu ein und öffnet danach die Kapitel-Auswahl. Sinnvoll wenn sich die Quelldatei geändert hat.'
|
||||
: 'Analysiert die Quelldatei neu (MediaInfo, Playlist-Check) und öffnet dann die Spur-Auswahl. Unterschied zu „Spur-Auswahl öffnen": der Analyse-Schritt wird komplett wiederholt.'
|
||||
}</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
)}
|
||||
|
||||
{!isCd && !isAudiobook && typeof onAssignOmdb === 'function' ? (
|
||||
{!isCd && !isAudiobook && !isConverter && typeof onAssignOmdb === 'function' ? (
|
||||
<div className="actions-group">
|
||||
<div className="actions-group-label"><i className="pi pi-search" /> Metadaten</div>
|
||||
<div className="action-item">
|
||||
@@ -1389,47 +1646,49 @@ export default function JobDetailDialog({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="actions-group">
|
||||
<div className="actions-group-label"><i className="pi pi-trash" /> Dateien löschen</div>
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label="RAW löschen"
|
||||
icon="pi pi-trash"
|
||||
severity="warning"
|
||||
outlined
|
||||
size="small"
|
||||
onClick={() => onDeleteFiles?.(job, 'raw')}
|
||||
loading={actionBusy}
|
||||
disabled={!job.rawStatus?.exists || typeof onDeleteFiles !== 'function'}
|
||||
/>
|
||||
<span className="action-desc">Löscht die Quelldateien nach dem Rip ({isCd ? 'Audio-Rohdaten' : isAudiobook ? 'AAX/MP3-Quelldatei' : 'MKV-Datei von MakeMKV'}).</span>
|
||||
{typeof onDeleteFiles === 'function' ? (
|
||||
<div className="actions-group">
|
||||
<div className="actions-group-label"><i className="pi pi-trash" /> Dateien löschen</div>
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label="RAW löschen"
|
||||
icon="pi pi-trash"
|
||||
severity="warning"
|
||||
outlined
|
||||
size="small"
|
||||
onClick={() => onDeleteFiles?.(job, 'raw')}
|
||||
loading={actionBusy}
|
||||
disabled={!job.rawStatus?.exists}
|
||||
/>
|
||||
<span className="action-desc">Löscht die Quelldateien nach dem Rip ({isCd ? 'Audio-Rohdaten' : isAudiobook ? 'AAX/MP3-Quelldatei' : isConverter ? 'Quelldatei aus dem Converter-Import' : 'MKV-Datei von MakeMKV'}).</span>
|
||||
</div>
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label={isCd ? 'Audio löschen' : (isAudiobook ? 'Ausgabe löschen' : (isConverter ? 'Output löschen' : 'Movie löschen'))}
|
||||
icon="pi pi-trash"
|
||||
severity="warning"
|
||||
outlined
|
||||
size="small"
|
||||
onClick={() => onDeleteFiles?.(job, 'movie')}
|
||||
loading={actionBusy}
|
||||
disabled={!hasAnyOutputFolder}
|
||||
/>
|
||||
<span className="action-desc">Löscht {isCd ? 'die fertig gerippten Audiodateien' : isAudiobook ? 'die fertig konvertierte Audiobook-Ausgabe' : isConverter ? 'die fertig konvertierte Ausgabe' : 'die fertig encodierte Filmdatei'}.</span>
|
||||
</div>
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label="Beides löschen"
|
||||
icon="pi pi-times"
|
||||
severity="danger"
|
||||
size="small"
|
||||
onClick={() => onDeleteFiles?.(job, 'both')}
|
||||
loading={actionBusy}
|
||||
disabled={!job.rawStatus?.exists && !job.outputStatus?.exists}
|
||||
/>
|
||||
<span className="action-desc">Löscht RAW-Quelldateien und Ausgabe gemeinsam.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label={isCd ? 'Audio löschen' : (isAudiobook ? 'Ausgabe löschen' : 'Movie löschen')}
|
||||
icon="pi pi-trash"
|
||||
severity="warning"
|
||||
outlined
|
||||
size="small"
|
||||
onClick={() => onDeleteFiles?.(job, 'movie')}
|
||||
loading={actionBusy}
|
||||
disabled={!hasAnyOutputFolder || typeof onDeleteFiles !== 'function'}
|
||||
/>
|
||||
<span className="action-desc">Löscht {isCd ? 'die fertig gerippten Audiodateien' : isAudiobook ? 'die fertig konvertierte Audiobook-Ausgabe' : 'die fertig encodierte Filmdatei'}.</span>
|
||||
</div>
|
||||
<div className="action-item">
|
||||
<Button
|
||||
label="Beides löschen"
|
||||
icon="pi pi-times"
|
||||
severity="danger"
|
||||
size="small"
|
||||
onClick={() => onDeleteFiles?.(job, 'both')}
|
||||
loading={actionBusy}
|
||||
disabled={(!job.rawStatus?.exists && !job.outputStatus?.exists) || typeof onDeleteFiles !== 'function'}
|
||||
/>
|
||||
<span className="action-desc">Löscht RAW-Quelldateien und Ausgabe gemeinsam.</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user