From 38a82706ceb672d8314cad1e7654c980bf65e4c2 Mon Sep 17 00:00:00 2001 From: mboehmlaender Date: Mon, 16 Mar 2026 10:02:41 +0000 Subject: [PATCH] 0.10.2-9 Layout --- backend/package-lock.json | 4 +- backend/package.json | 2 +- backend/src/services/audnexService.js | 40 +++++++++ backend/src/services/pipelineService.js | 36 ++++++-- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- frontend/src/assets/media-audiobook.svg | 4 + frontend/src/assets/media-disc.svg | 2 +- .../src/components/AudiobookConfigPanel.jsx | 2 - frontend/src/pages/DashboardPage.jsx | 9 +- frontend/src/pages/HistoryPage.jsx | 4 +- frontend/src/styles/app.css | 87 ++++++++++++++----- package-lock.json | 4 +- package.json | 2 +- 14 files changed, 159 insertions(+), 43 deletions(-) create mode 100644 frontend/src/assets/media-audiobook.svg diff --git a/backend/package-lock.json b/backend/package-lock.json index 958e829..cb984ef 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-backend", - "version": "0.10.2-8", + "version": "0.10.2-9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-backend", - "version": "0.10.2-8", + "version": "0.10.2-9", "dependencies": { "archiver": "^7.0.1", "cors": "^2.8.5", diff --git a/backend/package.json b/backend/package.json index 1a741f0..0ed7dc5 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-backend", - "version": "0.10.2-8", + "version": "0.10.2-9", "private": true, "type": "commonjs", "scripts": { diff --git a/backend/src/services/audnexService.js b/backend/src/services/audnexService.js index 78bc53a..f361eee 100644 --- a/backend/src/services/audnexService.js +++ b/backend/src/services/audnexService.js @@ -131,6 +131,45 @@ function normalizeAudnexChapter(entry, index) { }; } +function normalizeNameList(items) { + if (!Array.isArray(items) || items.length === 0) { + return null; + } + const names = items + .map((item) => String(item?.name || '').trim()) + .filter(Boolean); + return names.length > 0 ? names.join(', ') : null; +} + +async function fetchBookByAsin(asin, region = 'de') { + const normalizedAsin = normalizeAsin(asin); + if (!normalizedAsin) { + return null; + } + + const url = new URL(`${AUDNEX_BASE_URL}/books/${normalizedAsin}`); + url.searchParams.set('region', String(region || 'de').trim() || 'de'); + logger.info('book:fetch:start', { asin: normalizedAsin, url: url.toString() }); + + const payload = await audnexFetch(url.toString()); + if (!payload || typeof payload !== 'object') { + return null; + } + + const narrator = normalizeNameList(payload.narrators); + const author = normalizeNameList(payload.authors); + const title = String(payload.title || '').trim() || null; + const series = String(payload.seriesName || payload.series || '').trim() || null; + const part = String(payload.seriesPart || payload.part || '').trim() || null; + const description = String(payload.summary || payload.description || '').trim() || null; + const year = payload.releaseDate + ? String(payload.releaseDate).slice(0, 4) + : null; + + logger.info('book:fetch:done', { asin: normalizedAsin, narrator, author, title }); + return { narrator, author, title, series, part, description, year }; +} + async function fetchChaptersByAsin(asin, region = 'de') { const normalizedAsin = normalizeAsin(asin); if (!normalizedAsin) { @@ -152,5 +191,6 @@ async function fetchChaptersByAsin(asin, region = 'de') { module.exports = { extractAsinFromAaxFile, + fetchBookByAsin, fetchChaptersByAsin }; diff --git a/backend/src/services/pipelineService.js b/backend/src/services/pipelineService.js index c9bb8f8..6db6513 100644 --- a/backend/src/services/pipelineService.js +++ b/backend/src/services/pipelineService.js @@ -11081,21 +11081,37 @@ class PipelineService extends EventEmitter { let detectedAsin = null; let audnexChapters = []; + let audnexBook = null; try { detectedAsin = await audnexService.extractAsinFromAaxFile(storagePaths.rawFilePath); if (detectedAsin) { await historyService.appendLog(job.id, 'SYSTEM', `ASIN erkannt: ${detectedAsin}`); - audnexChapters = await audnexService.fetchChaptersByAsin(detectedAsin, 'de'); - if (audnexChapters.length > 0) { - await historyService.appendLog(job.id, 'SYSTEM', `Audnex-Kapitel geladen: ${audnexChapters.length}`); + const [chapters, book] = await Promise.allSettled([ + audnexService.fetchChaptersByAsin(detectedAsin, 'de'), + audnexService.fetchBookByAsin(detectedAsin, 'de') + ]); + if (chapters.status === 'fulfilled') { + audnexChapters = chapters.value; + if (audnexChapters.length > 0) { + await historyService.appendLog(job.id, 'SYSTEM', `Audnex-Kapitel geladen: ${audnexChapters.length}`); + } else { + await historyService.appendLog(job.id, 'SYSTEM', `Keine Audnex-Kapitel fuer ASIN ${detectedAsin} gefunden.`); + } } else { - await historyService.appendLog(job.id, 'SYSTEM', `Keine Audnex-Kapitel fuer ASIN ${detectedAsin} gefunden.`); + logger.warn('audiobook:upload:audnex-chapters-failed', { jobId: job.id, asin: detectedAsin, error: errorToMeta(chapters.reason) }); + await historyService.appendLog(job.id, 'SYSTEM', `Audnex-Kapitel konnten nicht geladen werden: ${chapters.reason?.message || 'unknown'}`).catch(() => {}); + } + if (book.status === 'fulfilled' && book.value) { + audnexBook = book.value; + await historyService.appendLog(job.id, 'SYSTEM', `Audnex-Buchmetadaten geladen: ${audnexBook.narrator ? `Sprecher: ${audnexBook.narrator}` : 'kein Sprecher'}`); + } else if (book.status === 'rejected') { + logger.warn('audiobook:upload:audnex-book-failed', { jobId: job.id, asin: detectedAsin, error: errorToMeta(book.reason) }); } } else { await historyService.appendLog(job.id, 'SYSTEM', 'Keine ASIN in der AAX-Datei gefunden, verwende eingebettete Kapitel.'); } } catch (audnexError) { - logger.warn('audiobook:upload:audnex-chapters-failed', { + logger.warn('audiobook:upload:audnex-failed', { jobId: job.id, stagedRawFilePath: storagePaths.rawFilePath, asin: detectedAsin, @@ -11104,7 +11120,7 @@ class PipelineService extends EventEmitter { await historyService.appendLog( job.id, 'SYSTEM', - `Audnex-Kapitel konnten nicht geladen werden: ${audnexError?.message || 'unknown'}` + `Audnex-Daten konnten nicht geladen werden: ${audnexError?.message || 'unknown'}` ).catch(() => {}); } @@ -11140,6 +11156,14 @@ class PipelineService extends EventEmitter { const resolvedMetadata = { ...metadata, + // Audnex book metadata takes precedence over embedded tags where available + ...(audnexBook?.narrator ? { narrator: audnexBook.narrator } : {}), + ...(audnexBook?.author ? { author: audnexBook.author, artist: audnexBook.author } : {}), + ...(audnexBook?.title ? { title: audnexBook.title, album: audnexBook.title } : {}), + ...(audnexBook?.description ? { description: audnexBook.description } : {}), + ...(audnexBook?.series ? { series: audnexBook.series } : {}), + ...(audnexBook?.part ? { part: audnexBook.part } : {}), + ...(audnexBook?.year ? { year: audnexBook.year } : {}), asin: detectedAsin || null, chapterSource: audnexChapters.length > 0 ? 'audnex' : 'probe', chapters: audnexChapters.length > 0 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 016bacf..e3dacee 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-frontend", - "version": "0.10.2-8", + "version": "0.10.2-9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-frontend", - "version": "0.10.2-8", + "version": "0.10.2-9", "dependencies": { "primeicons": "^7.0.0", "primereact": "^10.9.2", diff --git a/frontend/package.json b/frontend/package.json index 5754c45..9cb0b37 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-frontend", - "version": "0.10.2-8", + "version": "0.10.2-9", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/assets/media-audiobook.svg b/frontend/src/assets/media-audiobook.svg new file mode 100644 index 0000000..44890da --- /dev/null +++ b/frontend/src/assets/media-audiobook.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/media-disc.svg b/frontend/src/assets/media-disc.svg index 5a9b384..45609d6 100644 --- a/frontend/src/assets/media-disc.svg +++ b/frontend/src/assets/media-disc.svg @@ -9,5 +9,5 @@ - CD + DVD diff --git a/frontend/src/components/AudiobookConfigPanel.jsx b/frontend/src/components/AudiobookConfigPanel.jsx index 59b345d..fc05150 100644 --- a/frontend/src/components/AudiobookConfigPanel.jsx +++ b/frontend/src/components/AudiobookConfigPanel.jsx @@ -190,8 +190,6 @@ export default function AudiobookConfigPanel({
Titel: {metadata?.title || '-'}
Autor: {metadata?.author || '-'}
Sprecher: {metadata?.narrator || '-'}
-
Serie: {metadata?.series || '-'}
-
Teil: {metadata?.part || '-'}
Jahr: {metadata?.year || '-'}
Kapitel: {editableChapters.length || '-'}
{descriptionPreview ? ( diff --git a/frontend/src/pages/DashboardPage.jsx b/frontend/src/pages/DashboardPage.jsx index 59bce91..8a7acb9 100644 --- a/frontend/src/pages/DashboardPage.jsx +++ b/frontend/src/pages/DashboardPage.jsx @@ -19,6 +19,7 @@ import AudiobookConfigPanel from '../components/AudiobookConfigPanel'; import blurayIndicatorIcon from '../assets/media-bluray.svg'; import discIndicatorIcon from '../assets/media-disc.svg'; import otherIndicatorIcon from '../assets/media-other.svg'; +import audiobookIndicatorIcon from '../assets/media-audiobook.svg'; import { getStatusLabel, getStatusSeverity, normalizeStatus } from '../utils/statusPresentation'; const processingStates = ['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING', 'CD_ANALYZING', 'CD_RIPPING', 'CD_ENCODING']; @@ -475,7 +476,7 @@ function mediaIndicatorMeta(job) { return { mediaType, src: otherIndicatorIcon, alt: 'Audio CD', title: 'Audio CD' }; } if (mediaType === 'audiobook') { - return { mediaType, src: otherIndicatorIcon, alt: 'Audiobook', title: 'Audiobook' }; + return { mediaType, src: audiobookIndicatorIcon, alt: 'Audiobook', title: 'Audiobook' }; } return { mediaType, src: otherIndicatorIcon, alt: 'Sonstiges Medium', title: 'Sonstiges Medium' }; } @@ -2524,7 +2525,7 @@ export default function DashboardPage({
- + - +
@@ -2755,7 +2756,7 @@ export default function DashboardPage({
- +
0 ? 'warning' : 'success'} /> diff --git a/frontend/src/pages/HistoryPage.jsx b/frontend/src/pages/HistoryPage.jsx index f92e5ed..1698658 100644 --- a/frontend/src/pages/HistoryPage.jsx +++ b/frontend/src/pages/HistoryPage.jsx @@ -1058,12 +1058,14 @@ export default function HistoryPage({ refreshToken = 0 }) { >
{renderPoster(row, 'history-dv-poster-grid')} +
+ {renderStatusTag(row)} +
{row?.title || row?.detected_title || '-'} - {renderStatusTag(row)}
{subtitle} diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index 14f8f81..56eca8e 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -419,54 +419,62 @@ body { .hardware-cpu-summary { display: flex; - flex-wrap: wrap; - gap: 0.45rem; - align-items: stretch; + flex-wrap: nowrap; + gap: 0.35rem; + align-items: center; + min-width: 0; + overflow: hidden; } .hardware-cpu-chip { display: inline-flex; align-items: center; - gap: 0.35rem; + gap: 0.28rem; border: 1px solid var(--rip-border); border-radius: 999px; background: var(--rip-panel); - padding: 0.22rem 0.55rem; - font-size: 0.8rem; + padding: 0.18rem 0.42rem; + font-size: 0.75rem; font-weight: 600; - min-height: 2rem; - height: 2rem; white-space: nowrap; + min-width: 0; + flex-shrink: 1; } .hardware-cpu-chip span { line-height: 1; + overflow: hidden; + text-overflow: ellipsis; } .hardware-cpu-chip .pi { - font-size: 0.82rem; + font-size: 0.75rem; color: var(--rip-brown-700); + flex-shrink: 0; } .hardware-cpu-load-group { display: inline-flex; align-items: center; - gap: 0.25rem; + gap: 0.2rem; + min-width: 0; + flex-shrink: 1; } .hardware-cpu-core-toggle-btn { border: 1px solid var(--rip-border); background: var(--rip-panel); color: var(--rip-brown-700); - width: 2rem; - min-width: 2rem; - height: 2rem; + width: 1.6rem; + min-width: 1.6rem; + height: 1.6rem; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; padding: 0; + flex-shrink: 0; } .hardware-cpu-core-toggle-btn:hover { @@ -507,12 +515,13 @@ body { } .hardware-core-item.compact { - grid-template-columns: auto auto auto; + display: flex; + flex-wrap: wrap; align-items: center; - justify-content: start; - gap: 0.35rem; + gap: 0.2rem 0.3rem; padding: 0.3rem 0.45rem; text-align: left; + overflow: hidden; } .hardware-core-title { @@ -1112,6 +1121,21 @@ body { white-space: normal; } +.p-fileupload-buttonbar { + padding: 0.4rem 0.6rem !important; + gap: 0.35rem !important; +} + +.p-fileupload-buttonbar .p-button.p-button-icon-only { + width: 2rem !important; + height: 2rem !important; + padding: 0 !important; +} + +.p-fileupload-buttonbar .p-button.p-button-icon-only .p-button-icon { + font-size: 0.85rem !important; +} + .aax-drop-zone { display: flex; flex-direction: column; @@ -1507,7 +1531,7 @@ body { .settings-grid { display: grid; - gap: 1rem; + gap: 0.5rem; } .settings-tabview { @@ -1528,7 +1552,7 @@ body { .settings-sections { display: grid; - gap: 0.85rem; + gap: 0.6rem; } .settings-section.grouped { @@ -1556,8 +1580,8 @@ body { .setting-row { display: grid; - gap: 0.35rem; - padding: 0.75rem; + gap: 0.25rem; + padding: 0.45rem 0.6rem; border: 1px solid var(--rip-border); border-radius: 0.5rem; background: var(--rip-panel-soft); @@ -1569,6 +1593,9 @@ body { .setting-description { white-space: pre-wrap; + font-size: 0.78rem; + color: var(--rip-muted); + line-height: 1.35; } .notification-toggle-grid { @@ -2075,6 +2102,8 @@ body { .history-dataview .p-dataview-content .p-grid.grid > .xl-col-4 { width: 100%; padding: 0.35rem; + display: flex; + flex-direction: column; } @media (min-width: 900px) { @@ -2094,6 +2123,9 @@ body { border-radius: 0.55rem; background: var(--rip-panel-soft); box-shadow: 0 2px 7px rgba(58, 29, 18, 0.06); + display: flex; + flex-direction: column; + height: 100%; } .history-dv-item:focus-visible { @@ -2230,13 +2262,28 @@ body { gap: 0.5rem; padding: 0.65rem; grid-template-rows: auto 1fr auto; + flex: 1; } .history-dv-grid-poster-wrap { + position: relative; width: min(120px, 100%); margin: 0 auto; } +.history-dv-grid-status-overlay { + position: absolute; + top: 0.4rem; + left: 0; + z-index: 1; + pointer-events: none; +} + +.history-dv-grid-status-overlay .p-tag { + pointer-events: auto; + box-shadow: 0 1px 4px rgba(0,0,0,0.25); +} + .history-dv-grid-main { display: grid; gap: 0.35rem; diff --git a/package-lock.json b/package-lock.json index 4f4b291..28b8f4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster", - "version": "0.10.2-8", + "version": "0.10.2-9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster", - "version": "0.10.2-8", + "version": "0.10.2-9", "devDependencies": { "concurrently": "^9.1.2" } diff --git a/package.json b/package.json index 3d33a35..4609eba 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ripster", "private": true, - "version": "0.10.2-8", + "version": "0.10.2-9", "scripts": { "dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"", "dev:backend": "npm run dev --prefix backend",