0.15.0-3 Misc Fixes
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ripster-frontend",
|
||||
"version": "0.15.0-2",
|
||||
"version": "0.15.0-3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ripster-frontend",
|
||||
"version": "0.15.0-2",
|
||||
"version": "0.15.0-3",
|
||||
"dependencies": {
|
||||
"primeicons": "^7.0.0",
|
||||
"primereact": "^10.9.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripster-frontend",
|
||||
"version": "0.15.0-2",
|
||||
"version": "0.15.0-3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -184,6 +184,78 @@ function trackSizeFormat(bytes) {
|
||||
return `${size.toFixed(2)} ${units[i]}`;
|
||||
}
|
||||
|
||||
function resolveManualTrackSelectionForTitle(encodePlan, titleId) {
|
||||
const plan = encodePlan && typeof encodePlan === 'object' ? encodePlan : {};
|
||||
const normalizedTitleId = normalizePositiveInteger(titleId);
|
||||
if (!normalizedTitleId) {
|
||||
return null;
|
||||
}
|
||||
const byTitle = plan?.manualTrackSelectionByTitle && typeof plan.manualTrackSelectionByTitle === 'object'
|
||||
? plan.manualTrackSelectionByTitle
|
||||
: {};
|
||||
const directSelection = byTitle[normalizedTitleId] || byTitle[String(normalizedTitleId)] || null;
|
||||
if (directSelection && typeof directSelection === 'object') {
|
||||
return directSelection;
|
||||
}
|
||||
const fallbackSelection = plan?.manualTrackSelection && typeof plan.manualTrackSelection === 'object'
|
||||
? plan.manualTrackSelection
|
||||
: null;
|
||||
if (!fallbackSelection) {
|
||||
return null;
|
||||
}
|
||||
const fallbackTitleId = normalizePositiveInteger(fallbackSelection?.titleId);
|
||||
return fallbackTitleId === normalizedTitleId ? fallbackSelection : null;
|
||||
}
|
||||
|
||||
function buildEffectiveTitleTrackSelection(encodePlan, title) {
|
||||
const manualSelection = resolveManualTrackSelectionForTitle(encodePlan, title?.id);
|
||||
const hasManualAudio = Array.isArray(manualSelection?.audioTrackIds);
|
||||
const hasManualSubtitle = Array.isArray(manualSelection?.subtitleTrackIdsOrdered) || Array.isArray(manualSelection?.subtitleTrackIds);
|
||||
const selectedAudioIds = hasManualAudio
|
||||
? normalizeIdList(manualSelection.audioTrackIds)
|
||||
: normalizeIdList((Array.isArray(title?.audioTracks) ? title.audioTracks : [])
|
||||
.filter((track) => Boolean(track?.selectedForEncode))
|
||||
.map((track) => track?.id));
|
||||
const selectedSubtitleIds = hasManualSubtitle
|
||||
? normalizeIdList(
|
||||
Array.isArray(manualSelection?.subtitleTrackIdsOrdered) && manualSelection.subtitleTrackIdsOrdered.length > 0
|
||||
? manualSelection.subtitleTrackIdsOrdered
|
||||
: manualSelection?.subtitleTrackIds
|
||||
)
|
||||
: normalizeIdList((Array.isArray(title?.subtitleTracks) ? title.subtitleTracks : [])
|
||||
.filter((track) => Boolean(track?.selectedForEncode))
|
||||
.map((track) => track?.id));
|
||||
|
||||
return {
|
||||
selectedAudioSet: new Set(selectedAudioIds.map((id) => String(id))),
|
||||
selectedSubtitleSet: new Set(selectedSubtitleIds.map((id) => String(id))),
|
||||
hasManualAudio,
|
||||
hasManualSubtitle
|
||||
};
|
||||
}
|
||||
|
||||
function getTrackActionLabel({
|
||||
selected = false,
|
||||
summary = '',
|
||||
manualSelection = false,
|
||||
fallback = 'Übernehmen'
|
||||
}) {
|
||||
if (!selected) {
|
||||
return 'Nicht übernommen';
|
||||
}
|
||||
const rawSummary = String(summary || '').trim();
|
||||
if (!rawSummary) {
|
||||
return manualSelection ? `${fallback} (manuell)` : fallback;
|
||||
}
|
||||
if (/^nicht übernommen/i.test(rawSummary)) {
|
||||
return manualSelection ? `${fallback} (manuell)` : fallback;
|
||||
}
|
||||
if (/^preset-default\b/i.test(rawSummary) || /Preset-Default \(HandBrake\)/i.test(rawSummary)) {
|
||||
return manualSelection ? `${fallback} (manuell)` : 'Übernehmen';
|
||||
}
|
||||
return rawSummary;
|
||||
}
|
||||
|
||||
function buildExecutedHandBrakeCommand(handbrakeInfo) {
|
||||
const cmd = String(handbrakeInfo?.cmd || '').trim();
|
||||
const args = Array.isArray(handbrakeInfo?.args) ? handbrakeInfo.args : [];
|
||||
@@ -1378,6 +1450,7 @@ export default function JobDetailDialog({
|
||||
const selectedEncodeTitles = encodePlanTitles.filter(
|
||||
(title) => title.selectedForEncode || title.encodeInput || String(title.id) === String(reviewSelectedEncodeTitleId)
|
||||
);
|
||||
const trackDisplayTitles = selectedEncodeTitles.length > 0 ? selectedEncodeTitles : encodePlanTitles;
|
||||
const executedHandBrakeCommand = buildExecutedHandBrakeCommand(job?.handbrakeInfo);
|
||||
const posterUrl = String(
|
||||
job?.poster_url
|
||||
@@ -1390,7 +1463,41 @@ export default function JobDetailDialog({
|
||||
const resolvedRawPath = job?.rawStatus?.path || job?.raw_path || null;
|
||||
const canDownloadRaw = Boolean(resolvedRawPath && job?.rawStatus?.exists && typeof onDownloadArchive === 'function');
|
||||
const canDownloadOutput = Boolean(job?.output_path && job?.outputStatus?.exists && typeof onDownloadArchive === 'function');
|
||||
const outputFolders = Array.isArray(job?.outputFolders) ? job.outputFolders : [];
|
||||
const outputFolders = (() => {
|
||||
const baseFolders = Array.isArray(job?.outputFolders) ? job.outputFolders : [];
|
||||
if (!isDiskContainer) {
|
||||
return baseFolders;
|
||||
}
|
||||
const merged = [];
|
||||
const seen = new Set();
|
||||
const addFolder = (folder, fallbackId = null) => {
|
||||
const outputPath = String(folder?.output_path || '').trim();
|
||||
if (!outputPath || seen.has(outputPath)) {
|
||||
return;
|
||||
}
|
||||
seen.add(outputPath);
|
||||
merged.push({
|
||||
id: folder?.id ?? fallbackId ?? outputPath,
|
||||
output_path: outputPath
|
||||
});
|
||||
};
|
||||
for (const folder of baseFolders) {
|
||||
addFolder(folder);
|
||||
}
|
||||
for (const child of childJobs) {
|
||||
const childOutputFolders = Array.isArray(child?.outputFolders) ? child.outputFolders : [];
|
||||
for (const childFolder of childOutputFolders) {
|
||||
addFolder(childFolder, `child-folder-${child?.id || 'x'}-${String(childFolder?.output_path || '')}`);
|
||||
}
|
||||
if (child?.output_path) {
|
||||
addFolder(
|
||||
{ output_path: child.output_path },
|
||||
`child-output-${child?.id || 'x'}-${String(child.output_path || '')}`
|
||||
);
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
})();
|
||||
const mergeOutputPathCandidates = new Set(
|
||||
[
|
||||
...(isMultipartMergeJob ? [job?.output_path] : []),
|
||||
@@ -2083,7 +2190,7 @@ export default function JobDetailDialog({
|
||||
</div>
|
||||
) : null}
|
||||
{/* Spurauswahl – read-only */}
|
||||
{selectedEncodeTitles.length > 0 ? (
|
||||
{trackDisplayTitles.length > 0 ? (
|
||||
isDvdSeries ? (
|
||||
<details className="episode-track-accordion">
|
||||
<summary className="series-batch-episode-head">
|
||||
@@ -2091,9 +2198,10 @@ export default function JobDetailDialog({
|
||||
</summary>
|
||||
<div className="spurauswahl-block">
|
||||
<div className="spurauswahl-label">Spurauswahl</div>
|
||||
{selectedEncodeTitles.map((title) => {
|
||||
{trackDisplayTitles.map((title) => {
|
||||
const audioTracks = Array.isArray(title.audioTracks) ? title.audioTracks : [];
|
||||
const subtitleTracks = Array.isArray(title.subtitleTracks) ? title.subtitleTracks : [];
|
||||
const trackSelection = buildEffectiveTitleTrackSelection(job?.encodePlan, title);
|
||||
return (
|
||||
<div key={title.id} className="track-title-block">
|
||||
<div className="track-title-row">
|
||||
@@ -2109,9 +2217,16 @@ export default function JobDetailDialog({
|
||||
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';
|
||||
const normalizedTrackId = normalizePositiveInteger(track?.id);
|
||||
const selected = normalizedTrackId
|
||||
? trackSelection.selectedAudioSet.has(String(normalizedTrackId))
|
||||
: Boolean(track?.selectedForEncode);
|
||||
const actionInfo = getTrackActionLabel({
|
||||
selected,
|
||||
summary: track.encodeActionSummary || track.encodePreviewSummary,
|
||||
manualSelection: trackSelection.hasManualAudio,
|
||||
fallback: 'Übernehmen'
|
||||
});
|
||||
return (
|
||||
<div key={track.id} className="track-item">
|
||||
<span>{displayText}</span>
|
||||
@@ -2128,10 +2243,16 @@ export default function JobDetailDialog({
|
||||
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';
|
||||
const normalizedTrackId = normalizePositiveInteger(track?.id);
|
||||
const selected = normalizedTrackId
|
||||
? trackSelection.selectedSubtitleSet.has(String(normalizedTrackId))
|
||||
: Boolean(track?.selectedForEncode);
|
||||
const actionInfo = getTrackActionLabel({
|
||||
selected,
|
||||
summary: track.subtitleActionSummary || track.subtitlePreviewSummary,
|
||||
manualSelection: trackSelection.hasManualSubtitle,
|
||||
fallback: 'Übernehmen'
|
||||
});
|
||||
return (
|
||||
<div key={track.id} className="track-item">
|
||||
<span>{displayText}</span>
|
||||
@@ -2150,9 +2271,10 @@ export default function JobDetailDialog({
|
||||
) : (
|
||||
<div className="spurauswahl-block">
|
||||
<div className="spurauswahl-label">Spurauswahl</div>
|
||||
{selectedEncodeTitles.map((title) => {
|
||||
{trackDisplayTitles.map((title) => {
|
||||
const audioTracks = Array.isArray(title.audioTracks) ? title.audioTracks : [];
|
||||
const subtitleTracks = Array.isArray(title.subtitleTracks) ? title.subtitleTracks : [];
|
||||
const trackSelection = buildEffectiveTitleTrackSelection(job?.encodePlan, title);
|
||||
return (
|
||||
<div key={title.id} className="track-title-block">
|
||||
<div className="track-title-row">
|
||||
@@ -2168,9 +2290,16 @@ export default function JobDetailDialog({
|
||||
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';
|
||||
const normalizedTrackId = normalizePositiveInteger(track?.id);
|
||||
const selected = normalizedTrackId
|
||||
? trackSelection.selectedAudioSet.has(String(normalizedTrackId))
|
||||
: Boolean(track?.selectedForEncode);
|
||||
const actionInfo = getTrackActionLabel({
|
||||
selected,
|
||||
summary: track.encodeActionSummary || track.encodePreviewSummary,
|
||||
manualSelection: trackSelection.hasManualAudio,
|
||||
fallback: 'Übernehmen'
|
||||
});
|
||||
return (
|
||||
<div key={track.id} className="track-item">
|
||||
<span>{displayText}</span>
|
||||
@@ -2187,10 +2316,16 @@ export default function JobDetailDialog({
|
||||
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';
|
||||
const normalizedTrackId = normalizePositiveInteger(track?.id);
|
||||
const selected = normalizedTrackId
|
||||
? trackSelection.selectedSubtitleSet.has(String(normalizedTrackId))
|
||||
: Boolean(track?.selectedForEncode);
|
||||
const actionInfo = getTrackActionLabel({
|
||||
selected,
|
||||
summary: track.subtitleActionSummary || track.subtitlePreviewSummary,
|
||||
manualSelection: trackSelection.hasManualSubtitle,
|
||||
fallback: 'Übernehmen'
|
||||
});
|
||||
return (
|
||||
<div key={track.id} className="track-item">
|
||||
<span>{displayText}</span>
|
||||
|
||||
@@ -179,6 +179,85 @@ function resolvePipelineMediaProfile(pipeline, mediaInfoReview) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function normalizeMakeMkvRipIssueMessage(value) {
|
||||
return String(value || '').replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function parseMakeMkvRipIssueLine(rawLine) {
|
||||
const line = String(rawLine || '').trim();
|
||||
if (!line) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sourceMatch = line.match(/^\[[^\]]+\]\s+\[([A-Z0-9_]+)\]\s+/i);
|
||||
const source = String(sourceMatch?.[1] || '').trim().toUpperCase();
|
||||
if (source && source !== 'MAKEMKV_RIP') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const msgCodeMatch = line.match(/\bMSG:(\d+),/i);
|
||||
const parsedCode = msgCodeMatch ? Number(msgCodeMatch[1]) : null;
|
||||
const code = Number.isFinite(parsedCode) ? Math.trunc(parsedCode) : null;
|
||||
const quotedMessageMatch = line.match(/\bMSG:\d+,[^,]*,[^,]*,"((?:[^"\\]|\\.)*)"/i);
|
||||
const fallback = line
|
||||
.replace(/^\[[^\]]+\]\s+\[[^\]]+\]\s*/i, '')
|
||||
.trim();
|
||||
const rawMessage = quotedMessageMatch?.[1]
|
||||
? quotedMessageMatch[1].replace(/\\"/g, '"')
|
||||
: (fallback || line);
|
||||
const message = normalizeMakeMkvRipIssueMessage(rawMessage);
|
||||
if (!message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isErrorLine = /\berror\b|\bfail(?:ed|ure)?\b|\bhash check\b|\buncorrectable\b/i.test(message);
|
||||
if (!isErrorLine) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return { code, message };
|
||||
}
|
||||
|
||||
function extractMakeMkvRipIssues(makemkvInfo) {
|
||||
if (!makemkvInfo || typeof makemkvInfo !== 'object') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const sourceLines = [];
|
||||
if (Array.isArray(makemkvInfo?.highlights)) {
|
||||
sourceLines.push(...makemkvInfo.highlights);
|
||||
}
|
||||
|
||||
const stdoutTail = String(makemkvInfo?.stdoutTail || '').trim();
|
||||
if (stdoutTail) {
|
||||
sourceLines.push(...stdoutTail.split('\n'));
|
||||
}
|
||||
|
||||
const stderrTail = String(makemkvInfo?.stderrTail || '').trim();
|
||||
if (stderrTail) {
|
||||
sourceLines.push(...stderrTail.split('\n'));
|
||||
}
|
||||
|
||||
const seen = new Set();
|
||||
const issues = [];
|
||||
for (const line of sourceLines) {
|
||||
const parsed = parseMakeMkvRipIssueLine(line);
|
||||
if (!parsed) {
|
||||
continue;
|
||||
}
|
||||
const key = `${parsed.code ?? '-'}|${parsed.message.toLowerCase()}`;
|
||||
if (seen.has(key)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(key);
|
||||
issues.push(parsed);
|
||||
if (issues.length >= 12) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
|
||||
function isBurnedSubtitleTrack(track) {
|
||||
const flags = Array.isArray(track?.subtitlePreviewFlags)
|
||||
? track.subtitlePreviewFlags
|
||||
@@ -2880,6 +2959,33 @@ export default function PipelineStatusCard({
|
||||
+ `${runningSeriesEpisode?.title ? ` - ${runningSeriesEpisode.title}` : ''}`
|
||||
)
|
||||
: (pipeline?.statusText || 'Bereit');
|
||||
const ripIssueEntries = useMemo(
|
||||
() => extractMakeMkvRipIssues(jobRow?.makemkvInfo),
|
||||
[jobRow?.makemkvInfo]
|
||||
);
|
||||
const ripIssueVisibleEntries = ripIssueEntries.slice(0, 8);
|
||||
const hiddenRipIssueCount = Math.max(0, ripIssueEntries.length - ripIssueVisibleEntries.length);
|
||||
const ripIssueSummaryLabel = ripIssueEntries.length === 1
|
||||
? '1 Rip-Fehler erkannt'
|
||||
: `${ripIssueEntries.length} Rip-Fehler erkannt`;
|
||||
const showRipIssueNotice = Boolean(
|
||||
(jobMediaProfile === 'dvd' || jobMediaProfile === 'bluray')
|
||||
&& ripCompleted
|
||||
&& ripIssueEntries.length > 0
|
||||
&& !running
|
||||
&& (
|
||||
state === 'WAITING_FOR_USER_DECISION'
|
||||
|| state === 'READY_TO_ENCODE'
|
||||
|| state === 'MEDIAINFO_CHECK'
|
||||
|| Boolean(mediaInfoReview)
|
||||
)
|
||||
);
|
||||
const canTriggerRipRetry = Boolean(
|
||||
showRipIssueNotice
|
||||
&& retryJobId
|
||||
&& typeof onRetry === 'function'
|
||||
&& !queueLocked
|
||||
);
|
||||
const isSeriesBatchParentReview = Boolean(
|
||||
mediaInfoReview?.seriesBatchParent
|
||||
|| pipeline?.context?.mediaInfoReview?.seriesBatchParent
|
||||
@@ -3672,6 +3778,42 @@ export default function PipelineStatusCard({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showRipIssueNotice ? (
|
||||
<div className="rip-error-notice" role="alert" aria-live="polite">
|
||||
<div className="rip-error-notice-head">
|
||||
<i className="pi pi-exclamation-triangle" aria-hidden="true" />
|
||||
<strong>Hinweis: Beim Rip sind Fehler aufgetreten.</strong>
|
||||
</div>
|
||||
<small>{ripIssueSummaryLabel}. Bitte vor der Bestätigung von Playlist-/Spurauswahl prüfen.</small>
|
||||
<div className="rip-error-notice-list">
|
||||
{ripIssueVisibleEntries.map((entry, index) => (
|
||||
<div key={`rip-issue-${entry.code ?? 'none'}-${index}`} className="rip-error-notice-item">
|
||||
<span className="rip-error-notice-code">{entry.code !== null ? `MSG:${entry.code}` : 'Fehler'}</span>
|
||||
<span className="rip-error-notice-message">{entry.message}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<small className="rip-error-notice-more">
|
||||
{hiddenRipIssueCount > 0
|
||||
? `+${hiddenRipIssueCount} weitere Meldung(en) im Job-Log.`
|
||||
: 'Vollständige Details stehen im Job-Log.'}
|
||||
</small>
|
||||
{canTriggerRipRetry ? (
|
||||
<div className="rip-error-notice-actions">
|
||||
<Button
|
||||
label="Retry Rippen"
|
||||
icon="pi pi-refresh"
|
||||
severity="warning"
|
||||
outlined
|
||||
onClick={() => onRetry?.(retryJobId)}
|
||||
loading={busy}
|
||||
disabled={busy || queueLocked || !retryJobId}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{rawDecisionRequiredBeforeStart && !queueLocked ? (
|
||||
<div className="playlist-decision-block">
|
||||
<h3>RAW Entscheidung erforderlich</h3>
|
||||
|
||||
@@ -1581,8 +1581,8 @@ export default function HistoryPage({ refreshToken = 0 }) {
|
||||
) {
|
||||
toastRef.current?.show({
|
||||
severity: 'warn',
|
||||
summary: `${deleteEntryOutputShortLabel}-Ordner auswählen`,
|
||||
detail: `Bitte mindestens einen ${deleteEntryOutputShortLabel}-Ordner zum Löschen auswählen.`,
|
||||
summary: `${deleteEntryOutputShortLabel}-Dateien auswählen`,
|
||||
detail: `Bitte mindestens eine ${deleteEntryOutputShortLabel}-Datei zum Löschen auswählen.`,
|
||||
life: 3500
|
||||
});
|
||||
return;
|
||||
@@ -2471,7 +2471,7 @@ export default function HistoryPage({ refreshToken = 0 }) {
|
||||
) : null}
|
||||
|
||||
<div>
|
||||
<h4>{deleteEntryOutputShortLabel}</h4>
|
||||
<h4>{deleteEntryOutputShortLabel} (Dateien)</h4>
|
||||
{previewMoviePaths.length > 0 ? (() => {
|
||||
return (
|
||||
<ul className="history-delete-preview-list">
|
||||
@@ -2502,7 +2502,7 @@ export default function HistoryPage({ refreshToken = 0 }) {
|
||||
</ul>
|
||||
);
|
||||
})() : (
|
||||
<small className="history-dv-subtle">Keine Movie-Pfade.</small>
|
||||
<small className="history-dv-subtle">Keine Output-Dateien.</small>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1079,7 +1079,7 @@ function buildPipelineFromJob(job, currentPipeline, currentPipelineJobId) {
|
||||
tracks: (Array.isArray(existingContext.tracks) && existingContext.tracks.length > 0)
|
||||
? existingContext.tracks
|
||||
: computedContext.tracks,
|
||||
selectedMetadata: existingContext.selectedMetadata || computedContext.selectedMetadata,
|
||||
selectedMetadata: computedContext.selectedMetadata || existingContext.selectedMetadata,
|
||||
canRestartEncodeFromLastSettings:
|
||||
existingContext.canRestartEncodeFromLastSettings ?? computedContext.canRestartEncodeFromLastSettings,
|
||||
canRestartReviewFromRaw:
|
||||
@@ -1116,7 +1116,7 @@ function buildPipelineFromJob(job, currentPipeline, currentPipelineJobId) {
|
||||
tracks: (Array.isArray(liveContext.tracks) && liveContext.tracks.length > 0)
|
||||
? liveContext.tracks
|
||||
: computedContext.tracks,
|
||||
selectedMetadata: liveContext.selectedMetadata || computedContext.selectedMetadata,
|
||||
selectedMetadata: computedContext.selectedMetadata || liveContext.selectedMetadata,
|
||||
cdRipConfig: liveContext.cdRipConfig || computedContext.cdRipConfig
|
||||
}
|
||||
: computedContext;
|
||||
|
||||
@@ -3705,6 +3705,74 @@ body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.rip-error-notice {
|
||||
margin-top: 1rem;
|
||||
border: 1px solid #d48c7c;
|
||||
border-left: 4px solid #b6452c;
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
background: #fff1ec;
|
||||
color: #4e2418;
|
||||
}
|
||||
|
||||
.rip-error-notice-head {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.rip-error-notice-head .pi {
|
||||
color: #b6452c;
|
||||
}
|
||||
|
||||
.rip-error-notice-list {
|
||||
display: grid;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.rip-error-notice-item {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
gap: 0.5rem;
|
||||
align-items: flex-start;
|
||||
padding: 0.35rem 0.45rem;
|
||||
border: 1px dashed #d8a79b;
|
||||
border-radius: 0.4rem;
|
||||
background: #fff8f6;
|
||||
}
|
||||
|
||||
.rip-error-notice-code {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 4.9rem;
|
||||
padding: 0.1rem 0.4rem;
|
||||
border-radius: 0.3rem;
|
||||
background: #b6452c;
|
||||
color: #fff;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.rip-error-notice-message {
|
||||
font-size: 0.83rem;
|
||||
line-height: 1.3;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.rip-error-notice-more {
|
||||
color: #7a3a2a;
|
||||
}
|
||||
|
||||
.rip-error-notice-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.playlist-decision-list {
|
||||
display: grid;
|
||||
gap: 0.75rem;
|
||||
|
||||
Reference in New Issue
Block a user