0.16.1-7 Release-Bugfixes
Deploy Docs to GitHub Pages / Deploy to GitHub Pages (push) Has been cancelled
Deploy Docs to GitHub Pages / Build Documentation (push) Has been cancelled

This commit is contained in:
2026-05-08 08:10:34 +02:00
parent 024a6305e2
commit 9b437fe5e5
52 changed files with 5815 additions and 2600 deletions
+81 -28
View File
@@ -154,6 +154,59 @@ function deriveAudioTracks(plan) {
return [];
}
function normalizePositiveInt(value) {
const parsed = Number(value);
if (!Number.isFinite(parsed) || parsed <= 0) {
return null;
}
return Math.trunc(parsed);
}
function buildVideoMetadataPayload(sourceMetadata, fallbackTitle = '') {
const source = sourceMetadata && typeof sourceMetadata === 'object' ? sourceMetadata : {};
const normalized = {
title: String(source?.title || fallbackTitle || '').trim() || null,
year: Number.isFinite(Number(source?.year)) ? Math.trunc(Number(source.year)) : null,
imdbId: String(source?.imdbId || '').trim() || null,
poster: String(source?.poster || '').trim() || null
};
const metadataProvider = String(source?.metadataProvider || '').trim() || null;
const workflowKind = String(source?.workflowKind || '').trim() || null;
const providerId = String(source?.providerId || '').trim() || null;
const metadataKind = String(source?.metadataKind || '').trim() || null;
const seasonName = String(source?.seasonName || '').trim() || null;
const imdbRating = String(source?.imdbRating || '').trim() || null;
const tmdbId = normalizePositiveInt(source?.tmdbId);
const seasonNumber = normalizePositiveInt(source?.seasonNumber);
const discNumber = normalizePositiveInt(source?.discNumber);
const episodeCount = Number(source?.episodeCount || 0);
const voteAverageRaw = Number(source?.voteAverage || 0);
const voteAverage = Number.isFinite(voteAverageRaw) && voteAverageRaw > 0
? Number(voteAverageRaw.toFixed(1))
: null;
const episodes = Array.isArray(source?.episodes) ? source.episodes : [];
const tmdbDetails = source?.tmdbDetails && typeof source.tmdbDetails === 'object' && !Array.isArray(source.tmdbDetails)
? source.tmdbDetails
: null;
if (metadataProvider) normalized.metadataProvider = metadataProvider;
if (workflowKind) normalized.workflowKind = workflowKind;
if (providerId) normalized.providerId = providerId;
if (metadataKind) normalized.metadataKind = metadataKind;
if (tmdbId) normalized.tmdbId = tmdbId;
if (seasonNumber) normalized.seasonNumber = seasonNumber;
if (seasonName) normalized.seasonName = seasonName;
if (discNumber) normalized.discNumber = discNumber;
if (Number.isFinite(episodeCount) && episodeCount > 0) normalized.episodeCount = Math.trunc(episodeCount);
if (episodes.length > 0) normalized.episodes = episodes;
if (voteAverage !== null) normalized.voteAverage = voteAverage;
if (imdbRating) normalized.imdbRating = imdbRating;
if (tmdbDetails) normalized.tmdbDetails = tmdbDetails;
return normalized;
}
// ── Status helpers ────────────────────────────────────────────────────────────
function mediaTypeBadge(type) {
@@ -260,10 +313,10 @@ function InlineConfig({ job, plan, onStarted, onDeleted, onCancelled, onInputsCh
// ── Video metadata ──────────────────────────────────────────────────────────
const [videoMetadata, setVideoMetadata] = useState(() => ({
title: String(planMetadata?.title || job.title || job.detected_title || '').trim(),
year: Number.isFinite(Number(planMetadata?.year)) ? Math.trunc(Number(planMetadata.year)) : null,
imdbId: String(planMetadata?.imdbId || '').trim() || null,
poster: String(planMetadata?.poster || '').trim() || null
...buildVideoMetadataPayload(
planMetadata,
String(job.title || job.detected_title || '').trim()
)
}));
// ── Script / Chain catalog ──────────────────────────────────────────────────
@@ -378,10 +431,10 @@ function InlineConfig({ job, plan, onStarted, onDeleted, onCancelled, onInputsCh
coverUrl: coverUrl || null
}
: {
title: String(videoMetadata?.title || job.title || job.detected_title || '').trim() || null,
year: Number.isFinite(Number(videoMetadata?.year)) ? Math.trunc(Number(videoMetadata.year)) : null,
imdbId: String(videoMetadata?.imdbId || '').trim() || null,
poster: String(videoMetadata?.poster || '').trim() || null
...buildVideoMetadataPayload(
videoMetadata,
String(job.title || job.detected_title || '').trim()
)
};
return {
@@ -436,12 +489,13 @@ function InlineConfig({ job, plan, onStarted, onDeleted, onCancelled, onInputsCh
jobId: job.id,
detectedTitle: videoMetadata?.title || job.title || job.detected_title || '',
selectedMetadata: {
...videoMetadata,
title: videoMetadata?.title || job.title || job.detected_title || '',
year: videoMetadata?.year || null,
imdbId: videoMetadata?.imdbId || null,
poster: videoMetadata?.poster || null
},
omdbCandidates: []
metadataCandidates: []
}), [job.id, job.title, job.detected_title, videoMetadata]);
const cdMetadataDialogContext = useMemo(() => ({
@@ -454,31 +508,28 @@ function InlineConfig({ job, plan, onStarted, onDeleted, onCancelled, onInputsCh
}), [job.id, audioTracks, trackFields]);
// ── Dialog handlers ─────────────────────────────────────────────────────────
const handleOmdbSearch = async (query) => {
const handleMetadataSearch = async (query) => {
try {
const response = await api.searchOmdb(query);
const response = await api.searchTmdbMovie(query);
return Array.isArray(response?.results) ? response.results : [];
} catch { return []; }
};
const handleOmdbSubmit = async (payload) => {
const handleMetadataSubmit = async (payload) => {
setSearchDialogBusy(true);
try {
setVideoMetadata({
title: String(payload?.title || job.title || job.detected_title || '').trim(),
year: Number.isFinite(Number(payload?.year)) ? Math.trunc(Number(payload.year)) : null,
imdbId: String(payload?.imdbId || '').trim() || null,
poster: String(payload?.poster || '').trim() || null
});
setVideoMetadata(buildVideoMetadataPayload(payload, String(job.title || job.detected_title || '').trim()));
setMetadataDialogVisible(false);
} finally {
setSearchDialogBusy(false);
}
};
const handleMusicBrainzSearchDialog = async (query) => {
const handleMusicBrainzSearchDialog = async (query, options = {}) => {
try {
const response = await api.searchMusicBrainz(query);
const response = await api.searchMusicBrainz(query, {
trackCount: Number(options?.trackCount || 0) > 0 ? Math.trunc(Number(options.trackCount)) : null
});
return Array.isArray(response?.results) ? response.results : [];
} catch { return []; }
};
@@ -642,10 +693,10 @@ function InlineConfig({ job, plan, onStarted, onDeleted, onCancelled, onInputsCh
coverUrl: coverUrl || null
}
: {
title: String(videoMetadata?.title || job.title || job.detected_title || '').trim() || null,
year: Number.isFinite(Number(videoMetadata?.year)) ? Math.trunc(Number(videoMetadata.year)) : null,
imdbId: String(videoMetadata?.imdbId || '').trim() || null,
poster: String(videoMetadata?.poster || '').trim() || null
...buildVideoMetadataPayload(
videoMetadata,
String(job.title || job.detected_title || '').trim()
)
};
await api.startConverterJob(job.id, {
@@ -880,8 +931,10 @@ function InlineConfig({ job, plan, onStarted, onDeleted, onCancelled, onInputsCh
)}
{/* Pre / Post Ausführungen */}
{renderEncodeSection('pre', preItems)}
{renderEncodeSection('post', postItems)}
<div className="encode-automation-grid">
{renderEncodeSection('pre', preItems)}
{renderEncodeSection('post', postItems)}
</div>
{/* Aktionen */}
<div className="actions-row" style={{ marginTop: '1rem' }}>
@@ -1025,8 +1078,8 @@ function InlineConfig({ job, plan, onStarted, onDeleted, onCancelled, onInputsCh
visible={metadataDialogVisible}
context={metadataDialogContext}
onHide={() => setMetadataDialogVisible(false)}
onSubmit={handleOmdbSubmit}
onSearch={handleOmdbSearch}
onSubmit={handleMetadataSubmit}
onSearch={handleMetadataSearch}
busy={searchDialogBusy}
/>
</div>