mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
rework auto lyrics matcher (#1569)
- remove priority order in favor of best match from all selected providers (lrclib synchronized) - improve % match for title / artist
This commit is contained in:
@@ -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)",
|
||||
|
||||
@@ -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<InternetProviderLyricSearchResponse[] | null>;
|
||||
|
||||
type SongFetcher = (params: LyricSearchQuery) => Promise<InternetProviderLyricResponse | null>;
|
||||
|
||||
const FETCHERS: Record<LyricSource, SongFetcher> = {
|
||||
[LyricSource.GENIUS]: queryGenius,
|
||||
[LyricSource.LRCLIB]: queryLrclib,
|
||||
[LyricSource.NETEASE]: queryNetease,
|
||||
};
|
||||
|
||||
const SEARCH_FETCHERS: Record<LyricSource, SearchFetcher> = {
|
||||
[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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user