diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 4e4f3254f..6c9ebb9f2 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -848,7 +848,7 @@ "lastfmApiKey": "{{lastfm}} API key", "lyricFetch_description": "fetch lyrics from various internet sources", "lyricFetch": "fetch lyrics from the internet", - "lyricFetchProvider_description": "select the providers to fetch lyrics from. the order of the providers is the order in which they will be queried", + "lyricFetchProvider_description": "select the providers to fetch lyrics from", "lyricFetchProvider": "providers to fetch lyrics from", "lyricOffset_description": "offset the lyric by the specified amount of milliseconds", "lyricOffset": "lyric offset (ms)", diff --git a/src/main/features/core/lyrics/index.ts b/src/main/features/core/lyrics/index.ts index ab38c8b2d..4d6c1c614 100644 --- a/src/main/features/core/lyrics/index.ts +++ b/src/main/features/core/lyrics/index.ts @@ -1,21 +1,10 @@ import { ipcMain } from 'electron'; import { store } from '../settings'; -import { - getLyricsBySongId as getGenius, - query as queryGenius, - getSearchResults as searchGenius, -} from './genius'; -import { - getLyricsBySongId as getLrcLib, - query as queryLrclib, - getSearchResults as searchLrcLib, -} from './lrclib'; -import { - getLyricsBySongId as getNetease, - query as queryNetease, - getSearchResults as searchNetease, -} from './netease'; +import { getLyricsBySongId as getGenius, getSearchResults as searchGenius } from './genius'; +import { getLyricsBySongId as getLrcLib, getSearchResults as searchLrcLib } from './lrclib'; +import { getLyricsBySongId as getNetease, getSearchResults as searchNetease } from './netease'; +import { orderSearchResults } from './shared'; import { Song } from '/@/shared/types/domain-types'; @@ -42,6 +31,7 @@ export type InternetProviderLyricResponse = { export type InternetProviderLyricSearchResponse = { artist: string; id: string; + isSync: boolean | null; name: string; score?: number; source: LyricSource; @@ -72,14 +62,6 @@ type SearchFetcher = ( params: LyricSearchQuery, ) => Promise; -type SongFetcher = (params: LyricSearchQuery) => Promise; - -const FETCHERS: Record = { - [LyricSource.GENIUS]: queryGenius, - [LyricSource.LRCLIB]: queryLrclib, - [LyricSource.NETEASE]: queryNetease, -}; - const SEARCH_FETCHERS: Record = { [LyricSource.GENIUS]: searchGenius, [LyricSource.LRCLIB]: searchLrcLib, @@ -108,39 +90,84 @@ const getRemoteLyrics = async (song: Song) => { } } - let lyricsFromSource: InternetProviderLyricResponse | null = null; + const params: LyricSearchQuery = { + album: song.album || song.name, + artist: song.artists[0].name, + duration: song.duration / 1000.0, + name: song.name, + }; + + const allSearchResults: InternetProviderLyricSearchResponse[] = []; for (const source of sources) { - const params = { - album: song.album || song.name, - artist: song.artists[0].name, - duration: song.duration / 1000.0, - name: song.name, - }; - const response = await FETCHERS[source](params as unknown as LyricSearchQuery); - - if (response) { - const newResult = cached - ? { - ...cached, - [source]: response, - } - : ({ [source]: response } as CachedLyrics); - - if (lyricCache.size === MAX_CACHED_ITEMS && cached === undefined) { - const toRemove = lyricCache.keys().next().value; - if (toRemove) { - lyricCache.delete(toRemove); - } + try { + const searchResults = await SEARCH_FETCHERS[source](params); + if (searchResults) { + allSearchResults.push(...searchResults); } - - lyricCache.set(song.id.toString(), newResult); - - lyricsFromSource = response; - break; + } catch (error) { + console.error(`Error searching ${source} for lyrics:`, error); } } + if (allSearchResults.length === 0) { + return null; + } + + const rankedResults = orderSearchResults({ + params, + results: allSearchResults, + }); + + const bestMatch = rankedResults[0]; + + if (!bestMatch) { + return null; + } + + // Score is 0-1 where 0 = perfect match, 1 = worst match + const matchThreshold = 0.55; + const matchScore = bestMatch.score ?? 1; + + if (matchScore > matchThreshold) { + return null; + } + + let lyricsFromSource: InternetProviderLyricResponse | null = null; + + try { + const lyrics = await GET_FETCHERS[bestMatch.source](bestMatch.id); + if (lyrics) { + lyricsFromSource = { + artist: bestMatch.artist, + id: bestMatch.id, + lyrics, + name: bestMatch.name, + source: bestMatch.source, + }; + } + } catch (error) { + console.error(`Error fetching lyrics from ${bestMatch.source}:`, error); + } + + if (lyricsFromSource) { + const newResult = cached + ? { + ...cached, + [lyricsFromSource.source]: lyricsFromSource, + } + : ({ [lyricsFromSource.source]: lyricsFromSource } as CachedLyrics); + + if (lyricCache.size === MAX_CACHED_ITEMS && cached === undefined) { + const toRemove = lyricCache.keys().next().value; + if (toRemove) { + lyricCache.delete(toRemove); + } + } + + lyricCache.set(song.id.toString(), newResult); + } + return lyricsFromSource; };