mirror of
https://github.com/meowarex/TidaLuna-Plugins.git
synced 2026-10-15 07:53:13 +11:00
commit
e3d71806b1
3 files changed
+36
-8
No files matched your search
@@ -211,9 +211,10 @@ export interface ArtworkLookup {
|
|||||||
artwork: AnimatedArtwork | null;
|
artwork: AnimatedArtwork | null;
|
||||||
/**
|
/**
|
||||||
* Scope the result is valid for, as a dedupe key: the album key only when
|
* Scope the result is valid for, as a dedupe key: the album key only when
|
||||||
* the answer was matched by album. A title-only fallback (or a miss) is
|
* the answer was matched by album (by name or via the track's ISRC, which
|
||||||
* specific to this track, so its key scopes to the title — the album's
|
* the server resolves to the same album). A title-only fallback (or a
|
||||||
* other tracks must not inherit it.
|
* miss) is specific to this track, so its key scopes to the title — the
|
||||||
|
* album's other tracks must not inherit it.
|
||||||
*/
|
*/
|
||||||
key: string;
|
key: string;
|
||||||
}
|
}
|
||||||
@@ -222,6 +223,13 @@ export interface ArtworkLookup {
|
|||||||
export const artworkTitleKey = (artist: string, title: string): string =>
|
export const artworkTitleKey = (artist: string, title: string): string =>
|
||||||
`${artist}\u0000\u0000${title}`;
|
`${artist}\u0000\u0000${title}`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ISRC lookup: the server resolves the exact song by ISRC and answers
|
||||||
|
* from its album, immune to title/album name mismatches.
|
||||||
|
*/
|
||||||
|
const artworkIsrcUrl = (isrc: string): string =>
|
||||||
|
`https://ama.trainswift.net/api/v1/artwork/search?isrc=${encodeURIComponent(isrc)}`;
|
||||||
|
|
||||||
function artworkSearchUrl(
|
function artworkSearchUrl(
|
||||||
title: string,
|
title: string,
|
||||||
artist: string,
|
artist: string,
|
||||||
@@ -234,8 +242,9 @@ function artworkSearchUrl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cached including misses; most tracks have none and the answer never changes.
|
// Cached including misses; most tracks have none and the answer never changes.
|
||||||
// Only album-matched results go under the album key — a title-only fallback
|
// Only album-matched results (by name or ISRC) go under the album key — a
|
||||||
// must not stand in for the album's other tracks (see ArtworkLookup).
|
// title-only fallback must not stand in for the album's other tracks (see
|
||||||
|
// ArtworkLookup).
|
||||||
const animatedArtworkCache = new Map<string, AnimatedArtwork | null>();
|
const animatedArtworkCache = new Map<string, AnimatedArtwork | null>();
|
||||||
const ARTWORK_CACHE_MAX = 200;
|
const ARTWORK_CACHE_MAX = 200;
|
||||||
|
|
||||||
@@ -243,10 +252,12 @@ export async function fetchAnimatedArtwork(
|
|||||||
title: string,
|
title: string,
|
||||||
artist: string,
|
artist: string,
|
||||||
album?: string,
|
album?: string,
|
||||||
|
isrc?: string,
|
||||||
signal?: AbortSignal,
|
signal?: AbortSignal,
|
||||||
): Promise<ArtworkLookup> {
|
): Promise<ArtworkLookup> {
|
||||||
const albumName = album?.trim() || "";
|
const albumName = album?.trim() || "";
|
||||||
const albumPresent = albumName !== "";
|
const albumPresent = albumName !== "";
|
||||||
|
const isrcTrimmed = isrc?.trim() || "";
|
||||||
// Album key, matching artworkKey() in index.ts; without an album the
|
// Album key, matching artworkKey() in index.ts; without an album the
|
||||||
// answer is only ever matched by title, so key by title directly.
|
// answer is only ever matched by title, so key by title directly.
|
||||||
const cacheKey = albumPresent
|
const cacheKey = albumPresent
|
||||||
@@ -301,7 +312,15 @@ export async function fetchAnimatedArtwork(
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Exact album first; on a miss retry once without the album (server-side disambiguation is imperfect)
|
// ISRC first: the exact match. A hit is resolved through the track's
|
||||||
|
// album, so it is album-scoped (cacheKey); a miss says nothing about
|
||||||
|
// the album (the ISRC may simply be unknown to the source), so fall
|
||||||
|
// through to the name flow without caching it.
|
||||||
|
if (isrcTrimmed) {
|
||||||
|
const byIsrc = await tryFetch(artworkIsrcUrl(isrcTrimmed));
|
||||||
|
if (byIsrc) return { artwork: remember(byIsrc), key: cacheKey };
|
||||||
|
}
|
||||||
|
// Fallback: exact album first; on a miss retry once without the album (server-side disambiguation is imperfect)
|
||||||
const withAlbum = await tryFetch(
|
const withAlbum = await tryFetch(
|
||||||
artworkSearchUrl(title, artist, album),
|
artworkSearchUrl(title, artist, album),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ export class AnimatedArtworkLayer {
|
|||||||
private artwork: { url: string; url_tall: string } | null = null;
|
private artwork: { url: string; url_tall: string } | null = null;
|
||||||
/**
|
/**
|
||||||
* Scope key of the displayed artwork (see fetchAnimatedArtwork): the album
|
* Scope key of the displayed artwork (see fetchAnimatedArtwork): the album
|
||||||
* key only when the answer was matched by album, else the title key.
|
* key only when the answer was matched by album (name or ISRC), else the
|
||||||
|
* title key.
|
||||||
*/
|
*/
|
||||||
private artworkKey: string | null = null;
|
private artworkKey: string | null = null;
|
||||||
private liveSrc: string | null = null;
|
private liveSrc: string | null = null;
|
||||||
@@ -121,6 +122,7 @@ export class AnimatedArtworkLayer {
|
|||||||
title: string,
|
title: string,
|
||||||
artist: string,
|
artist: string,
|
||||||
album: string | undefined,
|
album: string | undefined,
|
||||||
|
isrc?: string,
|
||||||
): Promise<{ done: boolean; key: string }> {
|
): Promise<{ done: boolean; key: string }> {
|
||||||
// Already showing artwork that covers this track (album-matched, or the
|
// Already showing artwork that covers this track (album-matched, or the
|
||||||
// same track again) — keep playing, touch nothing.
|
// same track again) — keep playing, touch nothing.
|
||||||
@@ -143,7 +145,13 @@ export class AnimatedArtworkLayer {
|
|||||||
if (!document.querySelector<HTMLElement>(ART_TILE_SELECTOR))
|
if (!document.querySelector<HTMLElement>(ART_TILE_SELECTOR))
|
||||||
return { done: false, key };
|
return { done: false, key };
|
||||||
|
|
||||||
const lookup = await fetchAnimatedArtwork(title, artist, album, signal);
|
const lookup = await fetchAnimatedArtwork(
|
||||||
|
title,
|
||||||
|
artist,
|
||||||
|
album,
|
||||||
|
isrc,
|
||||||
|
signal,
|
||||||
|
);
|
||||||
if (token !== this.loadToken) return { done: true, key: lookup.key };
|
if (token !== this.loadToken) return { done: true, key: lookup.key };
|
||||||
if (!this.tileAlive()) return { done: false, key: lookup.key };
|
if (!this.tileAlive()) return { done: false, key: lookup.key };
|
||||||
|
|
||||||
|
|||||||
@@ -1152,6 +1152,7 @@ const updateAnimatedArtwork = async (
|
|||||||
target.title,
|
target.title,
|
||||||
target.artist,
|
target.artist,
|
||||||
target.album,
|
target.album,
|
||||||
|
target.isrc,
|
||||||
);
|
);
|
||||||
// Adopt the effective scope — a title-matched result (or a miss) must not
|
// Adopt the effective scope — a title-matched result (or a miss) must not
|
||||||
// stand in for the album's other tracks — unless a newer trigger claimed.
|
// stand in for the album's other tracks — unless a newer trigger claimed.
|
||||||
|
|||||||
Reference in new issue
Block a user