feat(web): MB-resolve manual imports for instant cover art
A manually-imported album had no release-group MBID, so it showed the ◉ placeholder until a Scan matched it. Now the import resolves the release group (searchReleaseGroup, via the shared MB cache) and stores it on the new LibraryItem.rgMbid column (migration add_library_rg_mbid). The library route prefers LibraryItem.rgMbid, falling back to the MonitoredRelease join for scanned items — so a manual album gets cover art immediately. Best-effort: a no-match / MB outage just leaves rgMbid null. web 164 tests, tsc + build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "LibraryItem" ADD COLUMN "rgMbid" TEXT;
|
||||
@@ -82,6 +82,9 @@ model LibraryItem {
|
||||
format String
|
||||
qualityClass Int
|
||||
importedAt DateTime @default(now())
|
||||
// MusicBrainz release-group MBID when known (resolved at manual import) — gives the album
|
||||
// cover art without depending on a matching MonitoredRelease row.
|
||||
rgMbid String?
|
||||
// The album's on-disk audio filenames ("## Title.ext"), captured at import + scan, so the
|
||||
// Library modal can show the REAL tracks on disk (revealing incomplete/edition mismatches)
|
||||
// instead of MusicBrainz's canonical listing. Empty for items imported before this existed
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
vi.mock("@/lib/musicbrainz", () => ({ searchReleaseGroup: vi.fn(async () => null) }));
|
||||
import { mkdtempSync, existsSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
@@ -7,6 +7,7 @@ import { randomUUID } from "node:crypto";
|
||||
import Busboy from "busboy";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { isAuthed } from "@/lib/auth";
|
||||
import { searchReleaseGroup } from "@/lib/musicbrainz";
|
||||
|
||||
// Manually import an album (a ripped CD / files already on the PC): upload the audio (+ an
|
||||
// optional cover) with artist/album/year, and Lyra writes it into the library. The web
|
||||
@@ -104,6 +105,15 @@ export async function POST(request: Request) {
|
||||
await mkdir(dirname(finalDir), { recursive: true });
|
||||
await rename(staging, finalDir);
|
||||
|
||||
// Resolve the release group so the album gets cover art immediately (best-effort — a
|
||||
// no-match just leaves rgMbid null; a later Scan can still match it). Uses the shared MB cache.
|
||||
let rgMbid: string | null = null;
|
||||
try {
|
||||
rgMbid = (await searchReleaseGroup(artist, album))?.rgMbid ?? null;
|
||||
} catch {
|
||||
/* MB unavailable — carry on without cover art */
|
||||
}
|
||||
|
||||
const firstExt = extname(audio[0]).toLowerCase();
|
||||
await prisma.libraryItem.create({
|
||||
data: {
|
||||
@@ -115,6 +125,7 @@ export async function POST(request: Request) {
|
||||
// rough class from the extension (2 = CD-lossless, 1 = lossy); a later scan re-probes and
|
||||
// can upgrade a hi-res file to class 3.
|
||||
qualityClass: LOSSLESS_EXT.has(firstExt) ? 2 : 1,
|
||||
rgMbid,
|
||||
trackNames: audio,
|
||||
},
|
||||
});
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user