feat(art): default to host-served cover art (scales to large libraries)
CI / publish (push) Has been skipped
CI / exporter (push) Successful in 12s
CI / build (push) Successful in 20s

Adds art.mode "host" (now the default): emit each cover's local file PATH
and let the host serve the bytes via its art proxy, instead of inlining a
data URL. The reconcile payload carries no image data, so a large Playnite
library no longer exceeds the host's request-body limit. "dataurl" (inline,
small libraries) and "off" remain. Requires a host with the provider-art
proxy (punktfunk main: feat/library-local-art).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-19 00:56:15 +02:00
parent f77790db05
commit 9988729aab
6 changed files with 69 additions and 27 deletions
+32 -6
View File
@@ -58,25 +58,51 @@ describe("buildArtwork", () => {
buildArtwork(game({ cover }), { ...DEFAULT_ART, mode: "off" }),
).toBeUndefined();
});
test("dataurl mode embeds the cover as portrait", () => {
test("host mode (default) emits the raw cover path — no bytes", () => {
const cover = write("c2.png", Buffer.from([1, 2]));
const art = buildArtwork(game({ cover }), DEFAULT_ART);
expect(art?.portrait).toBe(cover);
expect(art?.hero).toBeUndefined();
});
test("host mode includeBackground adds the hero path", () => {
const cover = write("c2b.png", Buffer.from([1]));
const background = write("b2b.jpg", Buffer.from([2]));
const art = buildArtwork(game({ cover, background }), {
...DEFAULT_ART,
includeBackground: true,
});
expect(art?.portrait).toBe(cover);
expect(art?.hero).toBe(background);
});
test("host mode with no cover yields undefined", () => {
expect(buildArtwork(game({ cover: null }), DEFAULT_ART)).toBeUndefined();
});
test("dataurl mode embeds the cover as a portrait data URL", () => {
const cover = write("c3.png", Buffer.from([1, 2]));
const art = buildArtwork(game({ cover }), {
...DEFAULT_ART,
mode: "dataurl",
});
expect(art?.portrait?.startsWith("data:image/png;base64,")).toBe(true);
expect(art?.hero).toBeUndefined();
});
test("includeBackground adds hero", () => {
const cover = write("c3.png", Buffer.from([1]));
const background = write("b3.jpg", Buffer.from([2]));
test("dataurl includeBackground adds a hero data URL", () => {
const cover = write("c4.png", Buffer.from([1]));
const background = write("b4.jpg", Buffer.from([2]));
const art = buildArtwork(game({ cover, background }), {
...DEFAULT_ART,
mode: "dataurl",
includeBackground: true,
});
expect(art?.portrait).toBeTruthy();
expect(art?.hero?.startsWith("data:image/jpeg;base64,")).toBe(true);
});
test("no readable art yields undefined", () => {
test("dataurl mode with no readable art yields undefined", () => {
expect(
buildArtwork(game({ cover: "/does/not/exist.png" }), DEFAULT_ART),
buildArtwork(game({ cover: "/does/not/exist.png" }), {
...DEFAULT_ART,
mode: "dataurl",
}),
).toBeUndefined();
});
});