0.10.0 Audbile Prototype

This commit is contained in:
2026-03-14 13:35:23 +00:00
parent 5d79a34905
commit e56cff43a9
22 changed files with 1667 additions and 148 deletions

View File

@@ -24,6 +24,7 @@ const MEDIA_FILTER_OPTIONS = [
{ label: 'Blu-ray', value: 'bluray' },
{ label: 'DVD', value: 'dvd' },
{ label: 'Audio CD', value: 'cd' },
{ label: 'Audiobook', value: 'audiobook' },
{ label: 'Sonstiges', value: 'other' }
];
@@ -80,6 +81,9 @@ function resolveMediaType(row) {
if (['cd', 'audio_cd', 'audio cd'].includes(raw)) {
return 'cd';
}
if (['audiobook', 'audio_book', 'audio book', 'book'].includes(raw)) {
return 'audiobook';
}
}
const statusCandidates = [
row?.status,
@@ -100,6 +104,12 @@ function resolveMediaType(row) {
if (Array.isArray(row?.makemkvInfo?.tracks) && row.makemkvInfo.tracks.length > 0) {
return 'cd';
}
if (String(row?.handbrakeInfo?.mode || '').trim().toLowerCase() === 'audiobook_encode') {
return 'audiobook';
}
if (String(encodePlan?.mode || '').trim().toLowerCase() === 'audiobook') {
return 'audiobook';
}
return 'other';
}
@@ -129,6 +139,14 @@ function resolveMediaTypeMeta(row) {
alt: 'Audio CD'
};
}
if (mediaType === 'audiobook') {
return {
mediaType,
icon: otherIndicatorIcon,
label: 'Audiobook',
alt: 'Audiobook'
};
}
return {
mediaType,
icon: otherIndicatorIcon,
@@ -216,12 +234,47 @@ function resolveCdDetails(row) {
};
}
function resolveAudiobookDetails(row) {
const encodePlan = row?.encodePlan && typeof row.encodePlan === 'object' ? row.encodePlan : {};
const selectedMetadata = row?.makemkvInfo?.selectedMetadata && typeof row.makemkvInfo.selectedMetadata === 'object'
? row.makemkvInfo.selectedMetadata
: (encodePlan?.metadata && typeof encodePlan.metadata === 'object' ? encodePlan.metadata : {});
const chapters = Array.isArray(selectedMetadata?.chapters)
? selectedMetadata.chapters
: (Array.isArray(row?.makemkvInfo?.chapters) ? row.makemkvInfo.chapters : []);
const format = String(
row?.handbrakeInfo?.format
|| encodePlan?.format
|| ''
).trim().toLowerCase() || null;
return {
author: String(selectedMetadata?.author || selectedMetadata?.artist || '').trim() || null,
narrator: String(selectedMetadata?.narrator || '').trim() || null,
chapterCount: chapters.length,
formatLabel: format ? format.toUpperCase() : null
};
}
function getOutputLabelForRow(row) {
return resolveMediaType(row) === 'cd' ? 'Audio-Dateien' : 'Movie-Datei(en)';
const mediaType = resolveMediaType(row);
if (mediaType === 'cd') {
return 'Audio-Dateien';
}
if (mediaType === 'audiobook') {
return 'Audiobook-Datei(en)';
}
return 'Movie-Datei(en)';
}
function getOutputShortLabelForRow(row) {
return resolveMediaType(row) === 'cd' ? 'Audio' : 'Movie';
const mediaType = resolveMediaType(row);
if (mediaType === 'cd') {
return 'Audio';
}
if (mediaType === 'audiobook') {
return 'Audiobook';
}
return 'Movie';
}
function normalizeJobId(value) {
@@ -760,7 +813,7 @@ export default function HistoryPage() {
if (row?.poster_url && row.poster_url !== 'N/A') {
return <img src={row.poster_url} alt={title} className={className} loading="lazy" />;
}
return <div className="history-dv-poster-fallback">{mediaMeta.mediaType === 'cd' ? 'Kein Cover' : 'Kein Poster'}</div>;
return <div className="history-dv-poster-fallback">{['cd', 'audiobook'].includes(mediaMeta.mediaType) ? 'Kein Cover' : 'Kein Poster'}</div>;
};
const renderPresenceChip = (label, available) => (
@@ -803,6 +856,32 @@ export default function HistoryPage() {
));
}
if (resolveMediaType(row) === 'audiobook') {
const audiobookDetails = resolveAudiobookDetails(row);
const infoItems = [];
if (audiobookDetails.author) {
infoItems.push({ key: 'author', label: 'Autor', value: audiobookDetails.author });
}
if (audiobookDetails.narrator) {
infoItems.push({ key: 'narrator', label: 'Sprecher', value: audiobookDetails.narrator });
}
if (audiobookDetails.chapterCount > 0) {
infoItems.push({ key: 'chapters', label: 'Kapitel', value: String(audiobookDetails.chapterCount) });
}
if (audiobookDetails.formatLabel) {
infoItems.push({ key: 'format', label: 'Format', value: audiobookDetails.formatLabel });
}
if (infoItems.length === 0) {
return <span className="history-dv-subtle">Keine Audiobook-Details</span>;
}
return infoItems.map((item) => (
<span key={`${row?.id}-${item.key}`} className="history-dv-rating-chip">
<strong>{item.label}</strong>
<span>{item.value}</span>
</span>
));
}
const ratings = resolveRatings(row);
if (ratings.length === 0) {
return <span className="history-dv-subtle">Keine Ratings</span>;