0.10.2-10 DVD Title Scan

This commit is contained in:
2026-03-16 11:05:38 +00:00
parent 38a82706ce
commit 901b8a0b61
14 changed files with 482 additions and 99 deletions
@@ -547,6 +547,23 @@ export default function JobDetailDialog({
: null;
const encodePlanUserPresetId = Number(encodePlanUserPreset?.id);
const reviewUserPresets = encodePlanUserPreset ? [encodePlanUserPreset] : [];
const encodePlanManualTrackSelection = job?.encodePlan?.manualTrackSelection
&& typeof job.encodePlan.manualTrackSelection === 'object'
? job.encodePlan.manualTrackSelection
: null;
const reviewTrackSelectionByTitle = encodePlanManualTrackSelection?.titleId != null
? {
[encodePlanManualTrackSelection.titleId]: {
audioTrackIds: Array.isArray(encodePlanManualTrackSelection.audioTrackIds)
? encodePlanManualTrackSelection.audioTrackIds
: [],
subtitleTrackIds: Array.isArray(encodePlanManualTrackSelection.subtitleTrackIds)
? encodePlanManualTrackSelection.subtitleTrackIds
: []
}
}
: {};
const reviewSelectedEncodeTitleId = job?.encodePlan?.encodeInputTitleId ?? null;
const executedHandBrakeCommand = buildExecutedHandBrakeCommand(job?.handbrakeInfo);
const canDownloadRaw = Boolean(job?.raw_path && job?.rawStatus?.exists && typeof onDownloadArchive === 'function');
const canDownloadOutput = Boolean(job?.output_path && job?.outputStatus?.exists && typeof onDownloadArchive === 'function');
@@ -846,6 +863,8 @@ export default function JobDetailDialog({
<MediaInfoReviewPanel
review={job.encodePlan}
commandOutputPath={job.output_path || null}
selectedEncodeTitleId={reviewSelectedEncodeTitleId}
trackSelectionByTitle={reviewTrackSelectionByTitle}
availableScripts={configuredSelection.scriptCatalog}
availableChains={configuredSelection.chainCatalog}
preEncodeItems={reviewPreEncodeItems}
+89 -1
View File
@@ -310,6 +310,7 @@ export default function PipelineStatusCard({
onRestartReview,
onConfirmReview,
onSelectPlaylist,
onSelectHandBrakeTitle,
onCancel,
onRetry,
isQueued = false,
@@ -331,6 +332,7 @@ export default function PipelineStatusCard({
const jobMediaProfile = resolvePipelineMediaProfile(pipeline, mediaInfoReview);
const [selectedEncodeTitleId, setSelectedEncodeTitleId] = useState(null);
const [selectedPlaylistId, setSelectedPlaylistId] = useState(null);
const [selectedHandBrakeTitleIdState, setSelectedHandBrakeTitleIdState] = useState(null);
const [trackSelectionByTitle, setTrackSelectionByTitle] = useState({});
const [settingsMap, setSettingsMap] = useState({});
const [presetDisplayMap, setPresetDisplayMap] = useState({});
@@ -527,6 +529,44 @@ export default function PipelineStatusCard({
return dedup;
}, [playlistAnalysis, pipeline?.context?.playlistCandidates]);
const handBrakeTitleDecisionRequired = state === 'WAITING_FOR_USER_DECISION'
&& Boolean(pipeline?.context?.handBrakeTitleDecisionRequired);
const waitingHandBrakeTitleRows = useMemo(() => {
const raw = Array.isArray(pipeline?.context?.handBrakeTitleCandidates)
? pipeline.context.handBrakeTitleCandidates
: [];
return raw
.map((item) => {
const titleId = Number(item?.handBrakeTitleId);
if (!Number.isFinite(titleId) || titleId <= 0) return null;
const durationSeconds = Number(item?.durationSeconds || 0);
const minutes = Math.floor(durationSeconds / 60);
const seconds = durationSeconds % 60;
const durationLabel = `${minutes}:${String(seconds).padStart(2, '0')} min`;
return {
handBrakeTitleId: Math.trunc(titleId),
durationSeconds,
durationLabel,
durationMinutes: Number(item?.durationMinutes || 0),
audioTrackCount: Number(item?.audioTrackCount || 0),
subtitleTrackCount: Number(item?.subtitleTrackCount || 0),
sizeBytes: Number(item?.sizeBytes || 0)
};
})
.filter(Boolean)
.sort((a, b) => b.durationSeconds - a.durationSeconds);
}, [pipeline?.context?.handBrakeTitleCandidates]);
useEffect(() => {
if (!handBrakeTitleDecisionRequired) {
setSelectedHandBrakeTitleIdState(null);
return;
}
const best = waitingHandBrakeTitleRows[0]?.handBrakeTitleId || null;
setSelectedHandBrakeTitleIdState(best);
}, [handBrakeTitleDecisionRequired, retryJobId, waitingHandBrakeTitleRows]);
useEffect(() => {
if (state !== 'WAITING_FOR_USER_DECISION') {
setSelectedPlaylistId(null);
@@ -551,7 +591,7 @@ export default function PipelineStatusCard({
pipeline?.context?.selectedPlaylist
]);
const playlistDecisionRequiredBeforeStart = state === 'WAITING_FOR_USER_DECISION';
const playlistDecisionRequiredBeforeStart = state === 'WAITING_FOR_USER_DECISION' && !handBrakeTitleDecisionRequired;
const commandOutputPath = useMemo(
() => buildOutputPathPreview(settingsMap, jobMediaProfile, selectedMetadata, retryJobId),
[settingsMap, jobMediaProfile, selectedMetadata, retryJobId]
@@ -696,6 +736,18 @@ export default function PipelineStatusCard({
/>
)}
{handBrakeTitleDecisionRequired && retryJobId && (
<Button
label="DVD Titel bestätigen"
icon="pi pi-check"
severity="warning"
outlined
onClick={() => onSelectHandBrakeTitle?.(retryJobId, selectedHandBrakeTitleIdState)}
loading={busy}
disabled={!selectedHandBrakeTitleIdState}
/>
)}
{state === 'READY_TO_ENCODE' && retryJobId ? (
<Button
label={isPreRipReview ? 'Backup + Encoding starten' : 'Encoding starten'}
@@ -851,6 +903,42 @@ export default function PipelineStatusCard({
</div>
) : null}
{handBrakeTitleDecisionRequired && !queueLocked ? (
<div className="playlist-decision-block">
<h3>DVD Titel-Auswahl erforderlich</h3>
<small>
Mehrere DVD-Titel gefunden, die die Mindestlänge erfüllen. Bitte den gewünschten Titel auswählen.
</small>
{waitingHandBrakeTitleRows.length > 0 ? (
<div className="playlist-decision-list">
{waitingHandBrakeTitleRows.map((row) => (
<div key={row.handBrakeTitleId} className="playlist-decision-item">
<label className="readonly-check-row">
<input
type="checkbox"
checked={selectedHandBrakeTitleIdState === row.handBrakeTitleId}
disabled={queueLocked}
onChange={() => {
const next = selectedHandBrakeTitleIdState === row.handBrakeTitleId ? null : row.handBrakeTitleId;
setSelectedHandBrakeTitleIdState(next);
}}
/>
<span>
{`Titel -t ${row.handBrakeTitleId}`}
{row.durationLabel ? ` | ${row.durationLabel}` : ''}
{row.audioTrackCount > 0 ? ` | Audio: ${row.audioTrackCount}` : ''}
{row.subtitleTrackCount > 0 ? ` | Untertitel: ${row.subtitleTrackCount}` : ''}
</span>
</label>
</div>
))}
</div>
) : (
<small>Keine Kandidaten verfügbar. Bitte Analyse erneut ausführen.</small>
)}
</div>
) : null}
{selectedMetadata ? (
<div className="pipeline-meta-inline">
{selectedMetadata.poster ? (