0.10.2-25 Fixes
This commit is contained in:
@@ -774,42 +774,43 @@ export default function JobDetailDialog({
|
||||
</div>
|
||||
|
||||
<section className="job-meta-block job-meta-block-full">
|
||||
<h4>Job-Infos</h4>
|
||||
<div className="job-meta-grid job-meta-grid-compact">
|
||||
<div>
|
||||
<strong>Status:</strong>{' '}
|
||||
<span
|
||||
className={`job-status-icon tone-${statusMeta.tone}`}
|
||||
title={statusMeta.label}
|
||||
aria-label={statusMeta.label}
|
||||
>
|
||||
<i className={`pi ${statusMeta.icon}`} aria-hidden="true" />
|
||||
<div className="job-infos-header">
|
||||
<h4>Job-Infos</h4>
|
||||
<div className="job-infos-badges">
|
||||
<span>
|
||||
<strong>Status:</strong>{' '}
|
||||
<span
|
||||
className={`job-status-icon tone-${statusMeta.tone}`}
|
||||
title={statusMeta.label}
|
||||
aria-label={statusMeta.label}
|
||||
>
|
||||
<i className={`pi ${statusMeta.icon}`} aria-hidden="true" />
|
||||
</span>
|
||||
</span>
|
||||
{' '}<span className="job-meta-subtle">{statusMeta.label}</span>
|
||||
<span className="job-infos-sep">|</span>
|
||||
{isCd ? (
|
||||
<>
|
||||
<span><strong>Rip:</strong> <BoolState value={job?.ripSuccessful} /></span>
|
||||
<span className="job-infos-sep">|</span>
|
||||
<span><strong>Backup:</strong> <BoolState value={job?.backupSuccess} /></span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span><strong>{isAudiobook ? 'Import:' : 'Backup:'}</strong> <BoolState value={job?.backupSuccess} /></span>
|
||||
<span className="job-infos-sep">|</span>
|
||||
<span><strong>Encode:</strong> <BoolState value={job?.encodeSuccess} /></span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Start:</strong> {job.start_time || '-'}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Ende:</strong> {job.end_time || '-'}
|
||||
</div>
|
||||
{isCd ? (
|
||||
<div>
|
||||
<strong>Rip:</strong> <BoolState value={job?.ripSuccessful} />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div>
|
||||
<strong>{isAudiobook ? 'Import:' : 'Backup:'}</strong> <BoolState value={job?.backupSuccess} />
|
||||
</div>
|
||||
<div>
|
||||
<strong>Encode:</strong> <BoolState value={job?.encodeSuccess} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="job-infos-layout">
|
||||
{/* Zeile 1: Start | Ende */}
|
||||
<div><strong>Start:</strong> {job.start_time || '-'}</div>
|
||||
<div><strong>Ende:</strong> {job.end_time || '-'}</div>
|
||||
{/* Zeile 3+4: Pfade */}
|
||||
<PathField
|
||||
label={isCd ? 'WAV:' : 'RAW:'}
|
||||
value={job.raw_path}
|
||||
value={isCd ? (job.raw_path || job.output_path) : job.raw_path}
|
||||
onDownload={canDownloadRaw ? () => onDownloadArchive?.(job, 'raw') : null}
|
||||
downloadDisabled={!canDownloadRaw}
|
||||
downloadLoading={downloadBusyTarget === 'raw'}
|
||||
@@ -822,12 +823,10 @@ export default function JobDetailDialog({
|
||||
downloadLoading={downloadBusyTarget === 'output'}
|
||||
/>
|
||||
{job.error_message ? (
|
||||
<div className="job-meta-col-span-2">
|
||||
<strong>Fehler:</strong> {job.error_message}
|
||||
</div>
|
||||
<div><strong>Fehler:</strong> {job.error_message}</div>
|
||||
) : null}
|
||||
{expertMode ? (
|
||||
<div className="job-meta-col-span-2">
|
||||
<div>
|
||||
<Link to={`/database?table=jobs&id=${job.id}`} className="job-db-link" onClick={onHide}>
|
||||
<i className="pi pi-database" /> DB-Eintrag ansehen
|
||||
</Link>
|
||||
@@ -885,50 +884,51 @@ export default function JobDetailDialog({
|
||||
return (
|
||||
<div key={title.id} className="track-title-block">
|
||||
<div className="track-title-row">
|
||||
<span>#{title.id} | {title.fileName} | {title.durationMinutes != null ? `${Number(title.durationMinutes).toFixed(2)} min` : '-'} | {trackSizeFormat(title.sizeBytes)}</span>
|
||||
{title.encodeInput ? <span className="track-input-badge">Encode-Input</span> : null}
|
||||
<span>#{title.id} | {title.fileName} | {title.durationMinutes != null ? `${Number(title.durationMinutes).toFixed(2)} min` : '-'}</span>
|
||||
</div>
|
||||
<div className="track-groups-row">
|
||||
{audioTracks.length > 0 ? (
|
||||
<div className="track-group">
|
||||
<div className="track-group-label">Tonspuren (Titel #{title.id})</div>
|
||||
{audioTracks.map((track) => {
|
||||
const lang = trackLang(track.language || track.languageLabel);
|
||||
const codec = trackCodec('audio', track.format, track.description || track.title);
|
||||
const chLayout = trackChLayout(track.channels);
|
||||
let displayText = `#${track.id} | ${lang} | ${codec}`;
|
||||
if (chLayout) displayText += ` | ${chLayout}`;
|
||||
const actionInfo = track.selectedForEncode
|
||||
? (String(track.encodePreviewSummary || track.encodeActionSummary || '').trim() || 'Copy')
|
||||
: 'Nicht übernommen';
|
||||
return (
|
||||
<div key={track.id} className="track-item">
|
||||
<span>{displayText}</span>
|
||||
<small className="track-action-note">Encode: {actionInfo}</small>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
{subtitleTracks.length > 0 ? (
|
||||
<div className="track-group">
|
||||
<div className="track-group-label">Subtitles (Titel #{title.id})</div>
|
||||
{subtitleTracks.map((track) => {
|
||||
const lang = trackLang(track.language || track.languageLabel);
|
||||
const codec = trackCodec('subtitle', track.format);
|
||||
const displayText = `#${track.id} | ${lang} | ${codec}`;
|
||||
const rawAction = String(track.subtitlePreviewSummary || track.subtitleActionSummary || '').trim();
|
||||
const actionInfo = track.selectedForEncode
|
||||
? (/^nicht übernommen$/i.test(rawAction) ? 'Übernehmen' : (rawAction || 'Übernehmen'))
|
||||
: 'Nicht übernommen';
|
||||
return (
|
||||
<div key={track.id} className="track-item">
|
||||
<span>{displayText}</span>
|
||||
<small className="track-action-note">Encode: {actionInfo}</small>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{audioTracks.length > 0 ? (
|
||||
<div className="track-group">
|
||||
<div className="track-group-label">Tonspuren (Titel #{title.id})</div>
|
||||
{audioTracks.map((track) => {
|
||||
const lang = trackLang(track.language || track.languageLabel);
|
||||
const codec = trackCodec('audio', track.format, track.description || track.title);
|
||||
const chLayout = trackChLayout(track.channels);
|
||||
let displayText = `#${track.id} | ${lang} | ${codec}`;
|
||||
if (chLayout) displayText += ` | ${chLayout}`;
|
||||
const actionInfo = track.selectedForEncode
|
||||
? (String(track.encodePreviewSummary || track.encodeActionSummary || '').trim() || 'Copy')
|
||||
: 'Nicht übernommen';
|
||||
return (
|
||||
<div key={track.id} className="track-item">
|
||||
<span>{displayText}</span>
|
||||
<small className="track-action-note">Encode: {actionInfo}</small>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
{subtitleTracks.length > 0 ? (
|
||||
<div className="track-group">
|
||||
<div className="track-group-label">Subtitles (Titel #{title.id})</div>
|
||||
{subtitleTracks.map((track) => {
|
||||
const lang = trackLang(track.language || track.languageLabel);
|
||||
const codec = trackCodec('subtitle', track.format);
|
||||
const displayText = `#${track.id} | ${lang} | ${codec}`;
|
||||
const rawAction = String(track.subtitlePreviewSummary || track.subtitleActionSummary || '').trim();
|
||||
const actionInfo = track.selectedForEncode
|
||||
? (/^nicht übernommen$/i.test(rawAction) ? 'Übernehmen' : (rawAction || 'Übernehmen'))
|
||||
: 'Nicht übernommen';
|
||||
return (
|
||||
<div key={track.id} className="track-item">
|
||||
<span>{displayText}</span>
|
||||
<small className="track-action-note">Encode: {actionInfo}</small>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
@@ -938,14 +938,99 @@ export default function JobDetailDialog({
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{(isCd || isAudiobook) && job.encodePlan ? (
|
||||
<section className="job-meta-block job-meta-block-full">
|
||||
<h4>{isCd ? 'Titelauswahl' : 'Kapitelauswahl'}</h4>
|
||||
{isCd ? (() => {
|
||||
const tracksRaw = Array.isArray(job.makemkvInfo?.tracks) && job.makemkvInfo.tracks.length > 0
|
||||
? job.makemkvInfo.tracks
|
||||
: (Array.isArray(job.encodePlan?.tracks) ? job.encodePlan.tracks : []);
|
||||
const selectedSet = Array.isArray(job.encodePlan?.selectedTracks) && job.encodePlan.selectedTracks.length > 0
|
||||
? new Set(job.encodePlan.selectedTracks.map(Number))
|
||||
: null;
|
||||
if (tracksRaw.length === 0) return <p className="job-meta-subtle">Keine Tracks vorhanden.</p>;
|
||||
return (
|
||||
<div className="track-group">
|
||||
{tracksRaw.map((track, i) => {
|
||||
const pos = Number(track.position ?? (i + 1));
|
||||
const label = String(track.title || track.name || `Track ${pos}`).trim();
|
||||
const artist = String(track.artist || '').trim();
|
||||
const durationSec = Number(track.durationMs || 0) > 0
|
||||
? Number(track.durationMs) / 1000
|
||||
: Number(track.durationSec || 0);
|
||||
const durLabel = durationSec > 0
|
||||
? `${Math.floor(durationSec / 60)}:${String(Math.floor(durationSec % 60)).padStart(2, '0')} min`
|
||||
: '-';
|
||||
const selected = selectedSet ? selectedSet.has(pos) : track.selected !== false;
|
||||
const ripDone = job?.ripSuccessful != null;
|
||||
const encodeLabel = !selected ? 'Nicht übernommen'
|
||||
: ripDone ? (job.ripSuccessful ? 'Erfolgreich' : 'Fehler')
|
||||
: 'Übernommen';
|
||||
const encodeClass = !selected ? '' : ripDone ? (job.ripSuccessful ? 'tone-ok' : 'tone-no') : '';
|
||||
return (
|
||||
<div key={pos} className="track-item">
|
||||
<span>#{pos} | {label}{artist ? ` | ${artist}` : ''} | {durLabel}</span>
|
||||
<small className={`track-action-note ${encodeClass}`}>Encode: {encodeLabel}</small>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})() : (() => {
|
||||
const chapters = Array.isArray(job.handbrakeInfo?.metadata?.chapters) && job.handbrakeInfo.metadata.chapters.length > 0
|
||||
? job.handbrakeInfo.metadata.chapters
|
||||
: (Array.isArray(job.makemkvInfo?.chapters) && job.makemkvInfo.chapters.length > 0
|
||||
? job.makemkvInfo.chapters
|
||||
: (Array.isArray(job.encodePlan?.metadata?.chapters) ? job.encodePlan.metadata.chapters : []));
|
||||
const stepsByIndex = new Map(
|
||||
(Array.isArray(job.handbrakeInfo?.steps) ? job.handbrakeInfo.steps : [])
|
||||
.map((s) => [Number(s.chapterIndex), s])
|
||||
);
|
||||
if (chapters.length === 0) return <p className="job-meta-subtle">Keine Kapitelinformationen vorhanden.</p>;
|
||||
return (
|
||||
<div className="track-group">
|
||||
{chapters.map((chapter, i) => {
|
||||
const id = Number(chapter.id ?? chapter.index ?? chapter.position ?? (i + 1));
|
||||
const label = String(chapter.title || chapter.name || `Kapitel ${id}`).trim();
|
||||
const durationSec = Number(chapter.durationMs || 0) > 0
|
||||
? Number(chapter.durationMs) / 1000
|
||||
: Number(chapter.durationSec || 0);
|
||||
const durLabel = durationSec > 0
|
||||
? `${Math.floor(durationSec / 60)}:${String(Math.floor(durationSec % 60)).padStart(2, '0')} min`
|
||||
: '-';
|
||||
const step = stepsByIndex.get(id);
|
||||
const stepStatus = step ? String(step.status || '').toUpperCase() : null;
|
||||
const encodeLabel = stepStatus === 'SUCCESS' ? 'Erfolgreich'
|
||||
: stepStatus === 'ERROR' ? 'Fehler'
|
||||
: stepStatus === 'CANCELLED' ? 'Abgebrochen'
|
||||
: stepStatus ? stepStatus
|
||||
: 'Übernommen';
|
||||
const encodeClass = stepStatus === 'SUCCESS' ? 'tone-ok'
|
||||
: stepStatus === 'ERROR' ? 'tone-no'
|
||||
: '';
|
||||
return (
|
||||
<div key={id} className="track-item">
|
||||
<span>#{id} | {label} | {durLabel}</span>
|
||||
<small className={`track-action-note ${encodeClass}`}>Encode: {encodeLabel}</small>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
<section className="job-meta-block job-meta-block-full">
|
||||
<h4>Rohdaten</h4>
|
||||
<h4>Logs</h4>
|
||||
<div className="job-json-grid">
|
||||
{!isCd && !isAudiobook ? <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} />
|
||||
{!isCd && !isAudiobook ? <JsonView title="Mediainfo Info" value={job.mediainfoInfo} /> : null}
|
||||
<JsonView title={isCd ? 'Rip-Plan' : 'Encode Plan'} value={job.encodePlan} />
|
||||
{!isCd ? <JsonView title={isAudiobook ? 'FFmpeg Info' : 'HandBrake Info'} value={job.handbrakeInfo} /> : null}
|
||||
<JsonView title={isCd ? 'Rip-Info' : (isAudiobook ? 'FFmpeg Info' : 'HandBrake Info')} value={job.handbrakeInfo} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user