feat(synced-lyrics): romanization (#2790)

* feat(synced-lyrics): init romanization!

* remove debug logs and add TODO

* feat(synced-lyrics/romanization): Mandarin!

* feat(synced-lyrics/romanization): improve japanese detection

* feat(synced-lyrics/romanization): Korean!

* qol(synced-lyrics/romanization): canonicalize punctuation and symbols

* feat(synced-lyrics/romanization): handle japanese+korean and korean+chinese lyrics

* revert formatting on electron.vite.config.mts

* feat(synced-lyrics/romanization): romanize plain lyrics

* apply fix by @kimjammer

* fix lockfile due to rebase

* feat(synced-lyrics): improve lyric processing and formatting;

* feat(synced-lyrics/romanization): add option to enable/disable romanization

* chore: move default value for --lyrics-duration to the declaration

* update lockfile

* fix: improvement

1. improved language detection logic
2. changed code to work in the renderer process

* fix: fix regression (canonicalize)

---------

Co-authored-by: JellyBrick <shlee1503@naver.com>
This commit is contained in:
Angelos Bouklis
2025-03-26 13:29:43 +02:00
committed by GitHub
parent 19fd0d61c6
commit 4b35a96778
19 changed files with 1304 additions and 239 deletions

View File

@ -30,15 +30,16 @@ export class LyricsGenius implements LyricProvider {
title: titleA,
primary_artist: { name: artistA },
},
},
{
}, {
result: {
title: titleB,
primary_artist: { name: artistB },
},
}) => {
const pointsA = (titleA === title ? 1 : 0) + (artistA.includes(artist) ? 1 : 0);
const pointsB = (titleB === title ? 1 : 0) + (artistB.includes(artist) ? 1 : 0);
const pointsA = (titleA === title ? 1 : 0) +
(artistA.includes(artist) ? 1 : 0);
const pointsB = (titleB === title ? 1 : 0) +
(artistB.includes(artist) ? 1 : 0);
return pointsB - pointsA;
},
@ -51,14 +52,21 @@ export class LyricsGenius implements LyricProvider {
const { result: { path } } = closestHit;
const html = await fetch(`${this.baseUrl}${path}`).then((res) => res.text());
const html = await fetch(`${this.baseUrl}${path}`).then((res) =>
res.text()
);
const doc = this.domParser.parseFromString(html, 'text/html');
const preloadedStateScript = Array.prototype.find.call(doc.querySelectorAll('script'), (script: HTMLScriptElement) => {
return script.textContent?.includes('window.__PRELOADED_STATE__');
}) as HTMLScriptElement;
const preloadedStateScript = Array.prototype.find.call(
doc.querySelectorAll('script'),
(script: HTMLScriptElement) => {
return script.textContent?.includes('window.__PRELOADED_STATE__');
},
) as HTMLScriptElement;
const preloadedState = preloadedStateScript.textContent?.match(preloadedStateRegex)?.[1]?.replace(/\\"/g, '"');
const preloadedState = preloadedStateScript.textContent?.match(
preloadedStateRegex,
)?.[1]?.replace(/\\"/g, '"');
const lyricsHtml = preloadedState?.match(preloadHtmlRegex)?.[1]
?.replace(/\\\//g, '/')
@ -67,12 +75,19 @@ export class LyricsGenius implements LyricProvider {
?.replace(/\\'/g, "'")
?.replace(/\\"/g, '"');
if (!lyricsHtml) throw new Error('Failed to extract lyrics from preloaded state.');
const hasUnreleasedPlaceholder = preloadedState &&
/lyricsPlaceholderReason.{1,5}unreleased/.test(preloadedState);
if (!lyricsHtml) {
if (hasUnreleasedPlaceholder) return null;
throw new Error('Failed to extract lyrics from preloaded state.');
}
const lyricsDoc = this.domParser.parseFromString(lyricsHtml, 'text/html');
const lyrics = lyricsDoc.body.innerText;
if (lyrics.trim().toLowerCase().replace(/[[\]]/g, '') === 'instrumental') return null;
if (lyrics.trim().toLowerCase().replace(/[[\]]/g, '') === 'instrumental') {
return null;
}
return {
title: closestHit.result.title,