diff --git a/web/prisma/migrations/20260714180402_add_library_rg_mbid/migration.sql b/web/prisma/migrations/20260714180402_add_library_rg_mbid/migration.sql new file mode 100644 index 0000000..4243834 --- /dev/null +++ b/web/prisma/migrations/20260714180402_add_library_rg_mbid/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "LibraryItem" ADD COLUMN "rgMbid" TEXT; diff --git a/web/prisma/schema.prisma b/web/prisma/schema.prisma index e17461a..5c1c5e9 100644 --- a/web/prisma/schema.prisma +++ b/web/prisma/schema.prisma @@ -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 diff --git a/web/src/app/api/library/import/route.test.ts b/web/src/app/api/library/import/route.test.ts index a54a8db..271d900 100644 --- a/web/src/app/api/library/import/route.test.ts +++ b/web/src/app/api/library/import/route.test.ts @@ -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"; diff --git a/web/src/app/api/library/import/route.ts b/web/src/app/api/library/import/route.ts index 67656f4..55df0c4 100644 --- a/web/src/app/api/library/import/route.ts +++ b/web/src/app/api/library/import/route.ts @@ -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, }, }); diff --git a/web/src/app/api/library/route.ts b/web/src/app/api/library/route.ts index b8bcf43..fc8be4b 100644 Binary files a/web/src/app/api/library/route.ts and b/web/src/app/api/library/route.ts differ