0.10.2-24 Laout

This commit is contained in:
2026-03-17 15:11:22 +00:00
parent f029d64e78
commit d7772df75d
9 changed files with 206 additions and 64 deletions
+110 -46
View File
@@ -2,7 +2,6 @@ import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { Dialog } from 'primereact/dialog';
import { Button } from 'primereact/button';
import MediaInfoReviewPanel from './MediaInfoReviewPanel';
import blurayIndicatorIcon from '../assets/media-bluray.svg';
import discIndicatorIcon from '../assets/media-disc.svg';
import otherIndicatorIcon from '../assets/media-other.svg';
@@ -118,6 +117,55 @@ function shellQuote(value) {
return `'${raw.replace(/'/g, `'"'"'`)}'`;
}
function trackLang(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];
return raw.length >= 2 ? raw.slice(0, 2) : raw;
}
function trackCodec(type, value, hint = null) {
const raw = String(value || '').trim();
const merged = `${raw} ${String(hint || '')}`.toLowerCase();
if (!raw) return '-';
if (type === 'subtitle') return merged.includes('pgs') ? 'PGS' : raw.toUpperCase();
if (merged.includes('dts-hd ma') || merged.includes('dts hd ma')) return 'DTS-HD MA';
if (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';
return raw.toUpperCase();
}
function trackChLayout(rawValue) {
const raw = String(rawValue || '').trim().toLowerCase();
if (!raw) return null;
if (raw.includes('7.1')) return '8ch | Surround 7.1';
if (raw.includes('5.1')) return '6ch | Surround 5.1';
if (raw.includes('stereo') || raw.includes('2.0') || raw.includes('downmix')) return '2ch | Stereo';
if (raw.includes('mono') || raw.includes('1.0')) return '1ch | Mono';
const nMatch = raw.match(/^([\d.]+)$/);
if (nMatch) {
const n = Number(nMatch[1]);
if (Number.isFinite(n) && n > 0) return `${Math.trunc(n)}ch`;
}
const chMatch = raw.match(/(\d+)\s*ch/);
if (chMatch) return `${chMatch[1]}ch`;
return null;
}
function trackSizeFormat(bytes) {
const value = Number(bytes || 0);
if (!Number.isFinite(value) || value <= 0) return '-';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let size = value;
let i = 0;
while (size >= 1024 && i < units.length - 1) { size /= 1024; i++; }
return `${size.toFixed(2)} ${units[i]}`;
}
function buildExecutedHandBrakeCommand(handbrakeInfo) {
const cmd = String(handbrakeInfo?.cmd || '').trim();
const args = Array.isArray(handbrakeInfo?.args) ? handbrakeInfo.args : [];
@@ -546,35 +594,9 @@ export default function JobDetailDialog({
|| configuredSelection.postScriptIds.length > 0
|| configuredSelection.preChainIds.length > 0
|| configuredSelection.postChainIds.length > 0;
const reviewPreEncodeItems = [
...configuredSelection.preScriptIds.map((id) => ({ type: 'script', id })),
...configuredSelection.preChainIds.map((id) => ({ type: 'chain', id }))
];
const reviewPostEncodeItems = [
...configuredSelection.postScriptIds.map((id) => ({ type: 'script', id })),
...configuredSelection.postChainIds.map((id) => ({ type: 'chain', id }))
];
const encodePlanUserPreset = job?.encodePlan?.userPreset && typeof job.encodePlan.userPreset === 'object'
? job.encodePlan.userPreset
: 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');
@@ -851,25 +873,67 @@ export default function JobDetailDialog({
</div>
</div>
) : null}
{/* Titel-Liste */}
{!isCd && !isAudiobook && job.encodePlan ? (
<section className="job-meta-block job-meta-block-full">
<h4>Spurauswahl</h4>
<MediaInfoReviewPanel
review={job.encodePlan}
commandOutputPath={job.output_path || null}
selectedEncodeTitleId={reviewSelectedEncodeTitleId}
trackSelectionByTitle={reviewTrackSelectionByTitle}
availableScripts={configuredSelection.scriptCatalog}
availableChains={configuredSelection.chainCatalog}
preEncodeItems={reviewPreEncodeItems}
postEncodeItems={reviewPostEncodeItems}
userPresets={reviewUserPresets}
selectedUserPresetId={Number.isFinite(encodePlanUserPresetId) && encodePlanUserPresetId > 0
? Math.trunc(encodePlanUserPresetId)
: null}
/>
</section>
{/* Spurauswahl read-only */}
{job.encodePlan && Array.isArray(job.encodePlan.titles) && job.encodePlan.titles.length > 0 ? (
<div className="spurauswahl-block">
<div className="spurauswahl-label">Spurauswahl</div>
{job.encodePlan.titles
.filter((t) => t.selectedForEncode || t.encodeInput || String(t.id) === String(reviewSelectedEncodeTitleId))
.map((title) => {
const audioTracks = Array.isArray(title.audioTracks) ? title.audioTracks : [];
const subtitleTracks = Array.isArray(title.subtitleTracks) ? title.subtitleTracks : [];
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}
</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>
);
})
}
</div>
) : null}
</section>
) : null}