feat(library): edit/fix album metadata
Correct an owned album's artist/album/year from the Library modal ("Edit
metadata"). PATCH /api/library/[id] renames the folder to the canonical
Artist/Album (Year) scheme on the /music mount, updates the LibraryItem,
and re-resolves the MusicBrainz release-group (cover art) when the
artist/album changed (best-effort — keeps the old rgMbid on an MB outage).
- New shared lib/library-path.ts (safeName, albumDir, yearFromPath); the
manual-import route now uses it so edits and imports name folders
identically.
- Library GET falls back to the folder's "(YYYY)" for the year when there's
no matching release, so edited/manual years show in the grid.
- Does NOT rewrite embedded file tags (a worker/mutagen job) — Lyra reads
from the folder + DB. Guards paths inside the library root; 409 on folder
or (artist,album) collision.
web 172 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import Busboy from "busboy";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { isAuthed } from "@/lib/auth";
|
||||
import { searchReleaseGroup } from "@/lib/musicbrainz";
|
||||
import { safeName, albumDir } from "@/lib/library-path";
|
||||
|
||||
// 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
|
||||
@@ -19,12 +20,6 @@ import { searchReleaseGroup } from "@/lib/musicbrainz";
|
||||
|
||||
const AUDIO_EXT = new Set([".flac", ".mp3", ".m4a", ".opus", ".ogg", ".aac", ".wav"]);
|
||||
const LOSSLESS_EXT = new Set([".flac", ".wav"]);
|
||||
const ILLEGAL = /[<>:"/\\|?*]/g;
|
||||
|
||||
function safeName(s: string): string {
|
||||
const cleaned = s.replace(ILLEGAL, "_").trim().replace(/^\.+|\.+$/g, "").trim();
|
||||
return cleaned || "Unknown";
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
// This route is excluded from the auth middleware (to avoid the 10MB body cap), so it must
|
||||
@@ -95,8 +90,7 @@ export async function POST(request: Request) {
|
||||
return Response.json({ error: bad }, { status: 400 });
|
||||
}
|
||||
|
||||
const albumFolder = year ? `${safeName(album)} (${year})` : safeName(album);
|
||||
const finalDir = join(root, safeName(artist), albumFolder);
|
||||
const finalDir = albumDir(root, artist, album, year || undefined);
|
||||
if (existsSync(finalDir) || (await prisma.libraryItem.findFirst({ where: { artist, album } }))) {
|
||||
await rm(staging, { recursive: true, force: true });
|
||||
return Response.json({ error: "this album is already in the library" }, { status: 409 });
|
||||
|
||||
Reference in New Issue
Block a user