0.10.2-24 Laout
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ripster-frontend",
|
||||
"version": "0.10.2-23",
|
||||
"version": "0.10.2-24",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ripster-frontend",
|
||||
"version": "0.10.2-23",
|
||||
"version": "0.10.2-24",
|
||||
"dependencies": {
|
||||
"primeicons": "^7.0.0",
|
||||
"primereact": "^10.9.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripster-frontend",
|
||||
"version": "0.10.2-23",
|
||||
"version": "0.10.2-24",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -866,11 +866,15 @@ export default function HistoryPage({ refreshToken = 0 }) {
|
||||
|
||||
const renderPoster = (row, className = 'history-dv-poster') => {
|
||||
const mediaMeta = resolveMediaTypeMeta(row);
|
||||
const isAudioSquare = ['cd', 'audiobook'].includes(mediaMeta.mediaType);
|
||||
const imgClass = className === 'history-dv-poster-grid' && isAudioSquare
|
||||
? 'history-dv-poster-grid-audio'
|
||||
: className;
|
||||
const title = row?.title || row?.detected_title || 'Poster';
|
||||
if (row?.poster_url && row.poster_url !== 'N/A') {
|
||||
return <img src={row.poster_url} alt={title} className={className} loading="lazy" />;
|
||||
return <img src={row.poster_url} alt={title} className={imgClass} loading="lazy" />;
|
||||
}
|
||||
return <div className="history-dv-poster-fallback">{['cd', 'audiobook'].includes(mediaMeta.mediaType) ? 'Kein Cover' : 'Kein Poster'}</div>;
|
||||
return <div className="history-dv-poster-fallback">{isAudioSquare ? 'Kein Cover' : 'Kein Poster'}</div>;
|
||||
};
|
||||
|
||||
const renderPresenceChip = (label, available) => (
|
||||
|
||||
@@ -2334,10 +2334,10 @@ body {
|
||||
}
|
||||
|
||||
.history-dv-poster,
|
||||
.history-dv-poster-grid {
|
||||
.history-dv-poster-grid,
|
||||
.history-dv-poster-grid-audio {
|
||||
display: block;
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 0.35rem;
|
||||
border: 1px solid #cba266;
|
||||
background: #f6ebd6;
|
||||
@@ -2345,10 +2345,27 @@ body {
|
||||
|
||||
.history-dv-poster {
|
||||
height: 88px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.history-dv-poster-grid {
|
||||
height: 164px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.history-dv-poster-grid-audio {
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.history-dv-grid-poster-wrap {
|
||||
width: min(120px, 100%);
|
||||
height: 164px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.history-dv-poster-fallback {
|
||||
@@ -2380,11 +2397,6 @@ body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.history-dv-grid-poster-wrap {
|
||||
width: min(120px, 100%);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.history-dv-grid-status-overlay {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
@@ -3023,6 +3035,68 @@ body {
|
||||
color: var(--rip-muted);
|
||||
}
|
||||
|
||||
.spurauswahl-block {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.spurauswahl-label {
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
color: var(--rip-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.track-title-block {
|
||||
border: 1px solid var(--rip-border);
|
||||
border-radius: 0.45rem;
|
||||
padding: 0.55rem 0.65rem;
|
||||
background: var(--rip-panel-soft);
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.track-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.track-input-badge {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
padding: 0.1rem 0.4rem;
|
||||
border-radius: 0.3rem;
|
||||
background: var(--rip-accent-soft, #e8f4ea);
|
||||
color: var(--rip-success, #2d7a3a);
|
||||
border: 1px solid var(--rip-success, #2d7a3a);
|
||||
}
|
||||
|
||||
.track-group {
|
||||
display: grid;
|
||||
gap: 0.25rem;
|
||||
padding-left: 0.5rem;
|
||||
border-left: 2px solid var(--rip-border);
|
||||
}
|
||||
|
||||
.track-group-label {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
color: var(--rip-muted);
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
||||
|
||||
.track-item {
|
||||
display: grid;
|
||||
gap: 0.1rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.media-title-block {
|
||||
border: 1px solid var(--rip-border);
|
||||
border-radius: 0.45rem;
|
||||
|
||||
Reference in New Issue
Block a user