mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-16 20:52:06 +00:00
feat(Synced-Lyrics): Also search for lyrics with the original title language (#3206)
* Add tags array for song info * Implement original title and original artist search for LRCBLib * comment cleanup * Check if microformat.tags is an array
This commit is contained in:
@ -11,10 +11,13 @@ export class LRCLib implements LyricProvider {
|
|||||||
|
|
||||||
async search({
|
async search({
|
||||||
title,
|
title,
|
||||||
|
alternativeTitle,
|
||||||
artist,
|
artist,
|
||||||
album,
|
album,
|
||||||
songDuration,
|
songDuration,
|
||||||
|
tags,
|
||||||
}: SearchSongInfo): Promise<LyricResult | null> {
|
}: SearchSongInfo): Promise<LyricResult | null> {
|
||||||
|
|
||||||
let query = new URLSearchParams({
|
let query = new URLSearchParams({
|
||||||
artist_name: artist,
|
artist_name: artist,
|
||||||
track_name: title,
|
track_name: title,
|
||||||
@ -42,7 +45,9 @@ export class LRCLib implements LyricProvider {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
query = new URLSearchParams({ q: title });
|
// Try to search with the alternative title (original language)
|
||||||
|
const trackName = alternativeTitle || title;
|
||||||
|
query = new URLSearchParams({ q: `${trackName}` });
|
||||||
url = `${this.baseUrl}/api/search?${query.toString()}`;
|
url = `${this.baseUrl}/api/search?${query.toString()}`;
|
||||||
|
|
||||||
response = await fetch(url);
|
response = await fetch(url);
|
||||||
@ -54,6 +59,22 @@ export class LRCLib implements LyricProvider {
|
|||||||
if (!Array.isArray(data)) {
|
if (!Array.isArray(data)) {
|
||||||
throw new Error(`Expected an array, instead got ${typeof data}`);
|
throw new Error(`Expected an array, instead got ${typeof data}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If still no results, try with the original title as fallback
|
||||||
|
if (data.length === 0 && alternativeTitle) {
|
||||||
|
query = new URLSearchParams({ q: title });
|
||||||
|
url = `${this.baseUrl}/api/search?${query.toString()}`;
|
||||||
|
|
||||||
|
response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`bad HTTPStatus(${response.statusText})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
data = (await response.json()) as LRCLIBSearchResponse;
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
throw new Error(`Expected an array, instead got ${typeof data}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredResults = [];
|
const filteredResults = [];
|
||||||
@ -63,6 +84,7 @@ export class LRCLib implements LyricProvider {
|
|||||||
const artists = artist.split(/[&,]/g).map((i) => i.trim());
|
const artists = artist.split(/[&,]/g).map((i) => i.trim());
|
||||||
const itemArtists = artistName.split(/[&,]/g).map((i) => i.trim());
|
const itemArtists = artistName.split(/[&,]/g).map((i) => i.trim());
|
||||||
|
|
||||||
|
// Try to match using artist name first
|
||||||
const permutations = [];
|
const permutations = [];
|
||||||
for (const artistA of artists) {
|
for (const artistA of artists) {
|
||||||
for (const artistB of itemArtists) {
|
for (const artistB of itemArtists) {
|
||||||
@ -76,10 +98,40 @@ export class LRCLib implements LyricProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ratio = Math.max(
|
let ratio = Math.max(
|
||||||
...permutations.map(([x, y]) => jaroWinkler(x, y)),
|
...permutations.map(([x, y]) => jaroWinkler(x, y)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// If direct artist match is below threshold and we have tags, try matching with tags
|
||||||
|
if (ratio <= 0.9 && tags && tags.length > 0) {
|
||||||
|
// Filter out the artist from tags to avoid duplicate comparisons
|
||||||
|
const filteredTags = tags.filter(tag => tag.toLowerCase() !== artist.toLowerCase());
|
||||||
|
|
||||||
|
const tagPermutations = [];
|
||||||
|
// Compare each tag with each item artist
|
||||||
|
for (const tag of filteredTags) {
|
||||||
|
for (const itemArtist of itemArtists) {
|
||||||
|
tagPermutations.push([tag.toLowerCase(), itemArtist.toLowerCase()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare each item artist with each tag
|
||||||
|
for (const itemArtist of itemArtists) {
|
||||||
|
for (const tag of filteredTags) {
|
||||||
|
tagPermutations.push([itemArtist.toLowerCase(), tag.toLowerCase()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tagPermutations.length > 0) {
|
||||||
|
const tagRatio = Math.max(
|
||||||
|
...tagPermutations.map(([x, y]) => jaroWinkler(x, y)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use the best match ratio between direct artist match and tag match
|
||||||
|
ratio = Math.max(ratio, tagRatio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ratio <= 0.9) continue;
|
if (ratio <= 0.9) continue;
|
||||||
filteredResults.push(item);
|
filteredResults.push(item);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export interface LyricResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
export type SearchSongInfo = Pick<SongInfo, 'title' | 'artist' | 'album' | 'songDuration' | 'videoId'>;
|
export type SearchSongInfo = Pick<SongInfo, 'title' | 'alternativeTitle' | 'artist' | 'album' | 'songDuration' | 'videoId' | 'tags'>;
|
||||||
|
|
||||||
export interface LyricProvider {
|
export interface LyricProvider {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@ -42,6 +42,7 @@ export interface SongInfo {
|
|||||||
videoId: string;
|
videoId: string;
|
||||||
playlistId?: string;
|
playlistId?: string;
|
||||||
mediaType: MediaType;
|
mediaType: MediaType;
|
||||||
|
tags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grab the native image using the src
|
// Grab the native image using the src
|
||||||
@ -83,6 +84,7 @@ const handleData = async (
|
|||||||
videoId: '',
|
videoId: '',
|
||||||
playlistId: '',
|
playlistId: '',
|
||||||
mediaType: MediaType.Audio,
|
mediaType: MediaType.Audio,
|
||||||
|
tags: [],
|
||||||
} satisfies SongInfo;
|
} satisfies SongInfo;
|
||||||
|
|
||||||
const microformat = data.microformat?.microformatDataRenderer;
|
const microformat = data.microformat?.microformatDataRenderer;
|
||||||
@ -96,6 +98,7 @@ const handleData = async (
|
|||||||
songInfo.alternativeTitle = microformat.linkAlternates.find(
|
songInfo.alternativeTitle = microformat.linkAlternates.find(
|
||||||
(link) => link.title,
|
(link) => link.title,
|
||||||
)?.title;
|
)?.title;
|
||||||
|
songInfo.tags = Array.isArray(microformat.tags) ? microformat.tags : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const { videoDetails } = data;
|
const { videoDetails } = data;
|
||||||
|
|||||||
Reference in New Issue
Block a user