fix: CD metadata dialog search/images/Weiter button

- Remove auto MusicBrainz search from analyzeCd; user triggers search manually
- Dialog uses single results state (replaced per search, not appended)
- Add cover-art-archive to MB search includes so cover images load
- Weiter button only blocked by track selection when tracks actually exist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 08:32:23 +00:00
parent 15f1a49378
commit 882dad57e2
3 changed files with 13 additions and 33 deletions

View File

@@ -92,7 +92,7 @@ class MusicBrainzService {
url.searchParams.set('query', q); url.searchParams.set('query', q);
url.searchParams.set('fmt', 'json'); url.searchParams.set('fmt', 'json');
url.searchParams.set('limit', '10'); url.searchParams.set('limit', '10');
url.searchParams.set('inc', 'artist-credits+labels+recordings'); url.searchParams.set('inc', 'artist-credits+labels+recordings+cover-art-archive');
try { try {
const data = await mbFetch(url.toString()); const data = await mbFetch(url.toString());

View File

@@ -9794,11 +9794,6 @@ class PipelineService extends EventEmitter {
const tracks = await cdRipService.readToc(devicePath, cdparanoiaCmd); const tracks = await cdRipService.readToc(devicePath, cdparanoiaCmd);
logger.info('cd:analyze:toc', { jobId: job.id, trackCount: tracks.length }); logger.info('cd:analyze:toc', { jobId: job.id, trackCount: tracks.length });
// Search MusicBrainz
const mbCandidates = await musicBrainzService
.searchByDiscLabel(detectedTitle)
.catch(() => []);
const cdInfo = { const cdInfo = {
phase: 'PREPARE', phase: 'PREPARE',
mediaProfile: 'cd', mediaProfile: 'cd',
@@ -9816,7 +9811,7 @@ class PipelineService extends EventEmitter {
await historyService.appendLog( await historyService.appendLog(
job.id, job.id,
'SYSTEM', 'SYSTEM',
`CD analysiert: ${tracks.length} Track(s) gefunden. MusicBrainz: ${mbCandidates.length} Treffer.` `CD analysiert: ${tracks.length} Track(s) gefunden.`
); );
const runningJobs = await historyService.getRunningJobs(); const runningJobs = await historyService.getRunningJobs();
@@ -9832,13 +9827,12 @@ class PipelineService extends EventEmitter {
device, device,
mediaProfile: 'cd', mediaProfile: 'cd',
detectedTitle, detectedTitle,
tracks, tracks
mbCandidates
} }
}); });
} }
return { jobId: job.id, detectedTitle, tracks, mbCandidates }; return { jobId: job.id, detectedTitle, tracks };
} catch (error) { } catch (error) {
logger.error('cd:analyze:failed', { jobId: job.id, error: errorToMeta(error) }); logger.error('cd:analyze:failed', { jobId: job.id, error: errorToMeta(error) });
await this.failJob(job.id, 'CD_ANALYZING', error); await this.failJob(job.id, 'CD_ANALYZING', error);

View File

@@ -24,7 +24,7 @@ export default function CdMetadataDialog({
}) { }) {
const [selected, setSelected] = useState(null); const [selected, setSelected] = useState(null);
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const [extraResults, setExtraResults] = useState([]); const [results, setResults] = useState([]);
// Manual metadata inputs // Manual metadata inputs
const [manualTitle, setManualTitle] = useState(''); const [manualTitle, setManualTitle] = useState('');
@@ -46,7 +46,7 @@ export default function CdMetadataDialog({
setManualTitle(context?.detectedTitle || ''); setManualTitle(context?.detectedTitle || '');
setManualArtist(''); setManualArtist('');
setManualYear(null); setManualYear(null);
setExtraResults([]); setResults([]);
const titles = {}; const titles = {};
const positions = new Set(); const positions = new Set();
@@ -84,27 +84,13 @@ export default function CdMetadataDialog({
} }
}, [selected]); }, [selected]);
const allMbRows = [
...(Array.isArray(context?.mbCandidates) ? context.mbCandidates : []),
...extraResults
].filter(Boolean);
// Deduplicate by mbId
const mbRows = [];
const seen = new Set();
for (const r of allMbRows) {
if (r.mbId && !seen.has(r.mbId)) {
seen.add(r.mbId);
mbRows.push(r);
}
}
const handleSearch = async () => { const handleSearch = async () => {
if (!query.trim()) { if (!query.trim()) {
return; return;
} }
const results = await onSearch(query.trim()); const searchResults = await onSearch(query.trim());
setExtraResults(results || []); setResults(searchResults || []);
setSelected(null);
}; };
const handleToggleTrack = (position) => { const handleToggleTrack = (position) => {
@@ -163,7 +149,7 @@ export default function CdMetadataDialog({
); );
const allSelected = tocTracks.length > 0 && selectedTrackPositions.size === tocTracks.length; const allSelected = tocTracks.length > 0 && selectedTrackPositions.size === tocTracks.length;
const noneSelected = selectedTrackPositions.size === 0; const tracksBlocking = tocTracks.length > 0 && selectedTrackPositions.size === 0;
return ( return (
<Dialog <Dialog
@@ -186,10 +172,10 @@ export default function CdMetadataDialog({
<Button label="MusicBrainz Suche" icon="pi pi-search" onClick={handleSearch} loading={busy} /> <Button label="MusicBrainz Suche" icon="pi pi-search" onClick={handleSearch} loading={busy} />
</div> </div>
{mbRows.length > 0 ? ( {results.length > 0 ? (
<div className="table-scroll-wrap table-scroll-medium"> <div className="table-scroll-wrap table-scroll-medium">
<DataTable <DataTable
value={mbRows} value={results}
selectionMode="single" selectionMode="single"
selection={selected} selection={selected}
onSelectionChange={(e) => setSelected(e.value)} onSelectionChange={(e) => setSelected(e.value)}
@@ -274,7 +260,7 @@ export default function CdMetadataDialog({
icon="pi pi-arrow-right" icon="pi pi-arrow-right"
onClick={handleSubmit} onClick={handleSubmit}
loading={busy} loading={busy}
disabled={noneSelected || (!manualTitle.trim() && !context?.detectedTitle)} disabled={tracksBlocking || (!manualTitle.trim() && !context?.detectedTitle)}
/> />
</div> </div>
</Dialog> </Dialog>