mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 10:31:47 +00:00
synced-lyrics: make the lyrics search more reliable (#2343)
* fix: comparing multiple artists on a single track * fix: get song info on startup * chore: also split on commas * chore: re-apply .toLowerCase() on the artist names * chore: remove redundant code * chore: attempt at improving the initial videodata * oops * eureka! * stuff
This commit is contained in:
@ -17,7 +17,6 @@ export const [hadSecondAttempt, setHadSecondAttempt] = createSignal(false);
|
|||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
export const [differentDuration, setDifferentDuration] = createSignal(false);
|
export const [differentDuration, setDifferentDuration] = createSignal(false);
|
||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
export let foundPlainTextLyrics = false;
|
|
||||||
|
|
||||||
export const extractTimeAndText = (
|
export const extractTimeAndText = (
|
||||||
line: string,
|
line: string,
|
||||||
@ -47,7 +46,9 @@ export const extractTimeAndText = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const makeLyricsRequest = async (extractedSongInfo: SongInfo) => {
|
export const makeLyricsRequest = async (extractedSongInfo: SongInfo) => {
|
||||||
|
setIsFetching(true);
|
||||||
setLineLyrics([]);
|
setLineLyrics([]);
|
||||||
|
|
||||||
const songData: Parameters<typeof getLyricsList>[0] = {
|
const songData: Parameters<typeof getLyricsList>[0] = {
|
||||||
title: `${extractedSongInfo.title}`,
|
title: `${extractedSongInfo.title}`,
|
||||||
artist: `${extractedSongInfo.artist}`,
|
artist: `${extractedSongInfo.artist}`,
|
||||||
@ -55,14 +56,18 @@ export const makeLyricsRequest = async (extractedSongInfo: SongInfo) => {
|
|||||||
songDuration: extractedSongInfo.songDuration,
|
songDuration: extractedSongInfo.songDuration,
|
||||||
};
|
};
|
||||||
|
|
||||||
const lyrics = await getLyricsList(songData);
|
let lyrics;
|
||||||
|
try {
|
||||||
|
lyrics = await getLyricsList(songData);
|
||||||
|
} catch {}
|
||||||
|
|
||||||
setLineLyrics(lyrics ?? []);
|
setLineLyrics(lyrics ?? []);
|
||||||
|
setIsFetching(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getLyricsList = async (
|
export const getLyricsList = async (
|
||||||
songData: Pick<SongInfo, 'title' | 'artist' | 'album' | 'songDuration'>,
|
songData: Pick<SongInfo, 'title' | 'artist' | 'album' | 'songDuration'>,
|
||||||
): Promise<LineLyrics[] | null> => {
|
): Promise<LineLyrics[] | null> => {
|
||||||
setIsFetching(true);
|
|
||||||
setIsInstrumental(false);
|
setIsInstrumental(false);
|
||||||
setHadSecondAttempt(false);
|
setHadSecondAttempt(false);
|
||||||
setDifferentDuration(false);
|
setDifferentDuration(false);
|
||||||
@ -73,26 +78,21 @@ export const getLyricsList = async (
|
|||||||
track_name: songData.title,
|
track_name: songData.title,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (songData.album) {
|
query.set('album_name', songData.album!);
|
||||||
query.set('album_name', songData.album);
|
if (query.get('album_name') === 'undefined') {
|
||||||
|
query.delete('album_name');
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = `https://lrclib.net/api/search?${query.toString()}`;
|
let url = `https://lrclib.net/api/search?${query.toString()}`;
|
||||||
let response = await fetch(url);
|
let response = await fetch(url);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
setIsFetching(false);
|
|
||||||
setDebugInfo('Got non-OK response from server.');
|
setDebugInfo('Got non-OK response from server.');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = (await response.json().catch((e: Error) => {
|
let data = await response.json() as LRCLIBSearchResponse;
|
||||||
setDebugInfo(`Error: ${e.message}\n\n${e.stack}`);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
})) as LRCLIBSearchResponse | null;
|
|
||||||
if (!data || !Array.isArray(data)) {
|
if (!data || !Array.isArray(data)) {
|
||||||
setIsFetching(false);
|
|
||||||
setDebugInfo('Unexpected server response.');
|
setDebugInfo('Unexpected server response.');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -108,14 +108,12 @@ export const getLyricsList = async (
|
|||||||
|
|
||||||
response = await fetch(url);
|
response = await fetch(url);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
setIsFetching(false);
|
|
||||||
setDebugInfo('Got non-OK response from server. (2)');
|
setDebugInfo('Got non-OK response from server. (2)');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
data = (await response.json()) as LRCLIBSearchResponse;
|
data = (await response.json()) as LRCLIBSearchResponse;
|
||||||
if (!Array.isArray(data)) {
|
if (!Array.isArray(data)) {
|
||||||
setIsFetching(false);
|
|
||||||
setDebugInfo('Unexpected server response. (2)');
|
setDebugInfo('Unexpected server response. (2)');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -125,12 +123,26 @@ export const getLyricsList = async (
|
|||||||
|
|
||||||
const filteredResults = [];
|
const filteredResults = [];
|
||||||
for (const item of data) {
|
for (const item of data) {
|
||||||
if (!item.syncedLyrics) continue;
|
|
||||||
|
|
||||||
const { artist } = songData;
|
const { artist } = songData;
|
||||||
const { artistName } = item;
|
const { artistName } = item;
|
||||||
|
|
||||||
const ratio = jaroWinkler(artist.toLowerCase(), artistName.toLowerCase());
|
const artists = artist.split(/&,/).map((i) => i.trim());
|
||||||
|
const itemArtists = artistName.split(/&,/).map((i) => i.trim());
|
||||||
|
|
||||||
|
const permutations = [];
|
||||||
|
for (const artistA of artists) {
|
||||||
|
for (const artistB of itemArtists) {
|
||||||
|
permutations.push([artistA.toLowerCase(), artistB.toLowerCase()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const artistA of itemArtists) {
|
||||||
|
for (const artistB of artists) {
|
||||||
|
permutations.push([artistA.toLowerCase(), artistB.toLowerCase()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ratio = Math.max(...permutations.map(([x, y]) => jaroWinkler(x, y)));
|
||||||
|
|
||||||
if (ratio <= 0.9) continue;
|
if (ratio <= 0.9) continue;
|
||||||
filteredResults.push(item);
|
filteredResults.push(item);
|
||||||
@ -146,23 +158,31 @@ export const getLyricsList = async (
|
|||||||
|
|
||||||
const closestResult = filteredResults[0];
|
const closestResult = filteredResults[0];
|
||||||
if (!closestResult) {
|
if (!closestResult) {
|
||||||
setIsFetching(false);
|
|
||||||
setDebugInfo('No search result matched the criteria.');
|
setDebugInfo('No search result matched the criteria.');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// setDebugInfo(JSON.stringify(closestResult, null, 4));
|
setDebugInfo(JSON.stringify(closestResult, null, 4));
|
||||||
|
|
||||||
|
if (Math.abs(closestResult.duration - duration) > 15) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (Math.abs(closestResult.duration - duration) > 15) return null;
|
|
||||||
if (Math.abs(closestResult.duration - duration) > 5) {
|
if (Math.abs(closestResult.duration - duration) > 5) {
|
||||||
// show message that the timings may be wrong
|
// show message that the timings may be wrong
|
||||||
setDifferentDuration(true);
|
setDifferentDuration(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsInstrumental(closestResult.instrumental);
|
setIsInstrumental(closestResult.instrumental);
|
||||||
|
if (closestResult.instrumental) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Separate the lyrics into lines
|
// Separate the lyrics into lines
|
||||||
const raw = closestResult.syncedLyrics.split('\n');
|
const raw = closestResult.syncedLyrics?.split('\n') ?? [];
|
||||||
|
if (!raw.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Add a blank line at the beginning
|
// Add a blank line at the beginning
|
||||||
raw.unshift('[0:0.0] ');
|
raw.unshift('[0:0.0] ');
|
||||||
@ -186,6 +206,5 @@ export const getLyricsList = async (
|
|||||||
line.duration = next.timeInMs - line.timeInMs;
|
line.duration = next.timeInMs - line.timeInMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsFetching(false);
|
|
||||||
return syncedLyricList;
|
return syncedLyricList;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -200,6 +200,26 @@ export default (api: YoutubePlayer) => {
|
|||||||
for (const status of ['playing', 'pause'] as const) {
|
for (const status of ['playing', 'pause'] as const) {
|
||||||
video.addEventListener(status, playPausedHandlers[status]);
|
video.addEventListener(status, playPausedHandlers[status]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isNaN(video.duration)) {
|
||||||
|
const {
|
||||||
|
title, author,
|
||||||
|
video_id: videoId,
|
||||||
|
list: playlistId
|
||||||
|
} = api.getVideoData();
|
||||||
|
|
||||||
|
const { playerOverlays } = api.getWatchNextResponse();
|
||||||
|
|
||||||
|
sendSongInfo(<VideoDataChangeValue>{
|
||||||
|
title, author, videoId, playlistId,
|
||||||
|
|
||||||
|
isUpcoming: false,
|
||||||
|
lengthSeconds: video.duration,
|
||||||
|
loading: true,
|
||||||
|
|
||||||
|
uhhh: { playerOverlays }
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendSongInfo(videoData: VideoDataChangeValue) {
|
function sendSongInfo(videoData: VideoDataChangeValue) {
|
||||||
|
|||||||
@ -5,3 +5,17 @@ export interface QueueResponse {
|
|||||||
autoPlaying?: boolean;
|
autoPlaying?: boolean;
|
||||||
continuation?: string;
|
continuation?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WatchNextResponse {
|
||||||
|
playerOverlays: {
|
||||||
|
playerOverlayRenderer: {
|
||||||
|
browserMediaSession: {
|
||||||
|
browserMediaSessionRenderer: {
|
||||||
|
album: {
|
||||||
|
runs: { text: string; }[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
import { VideoDetails } from './video-details';
|
import { VideoDetails } from './video-details';
|
||||||
import { GetPlayerResponse } from './get-player-response';
|
import { GetPlayerResponse } from './get-player-response';
|
||||||
import { PlayerAPIEvents } from './player-api-events';
|
import { PlayerAPIEvents } from './player-api-events';
|
||||||
|
import { WatchNextResponse } from '@/types/youtube-music-desktop-internal';
|
||||||
|
|
||||||
export interface YoutubePlayer {
|
export interface YoutubePlayer {
|
||||||
getInternalApiInterface: <Parameters extends unknown[], Return>(
|
getInternalApiInterface: <Parameters extends unknown[], Return>(
|
||||||
@ -427,4 +428,6 @@ export interface YoutubePlayer {
|
|||||||
addEmbedsConversionTrackingParams: <Parameters extends unknown[], Return>(
|
addEmbedsConversionTrackingParams: <Parameters extends unknown[], Return>(
|
||||||
...params: Parameters
|
...params: Parameters
|
||||||
) => Return;
|
) => Return;
|
||||||
|
|
||||||
|
getWatchNextResponse(): WatchNextResponse;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user