Added Romanized Lyrics

This commit is contained in:
2026-02-27 19:19:54 +11:00
parent c6e916e6f6
commit 00eaf37dfa
2 changed files with 43 additions and 5 deletions
@@ -14,6 +14,8 @@ declare global {
updateQualityProgressColor?: () => void; updateQualityProgressColor?: () => void;
updateLyricsStyle?: () => void; updateLyricsStyle?: () => void;
updateLyricsStyleSetting?: (value: number) => void; updateLyricsStyleSetting?: (value: number) => void;
updateRomanizeLyrics?: () => void;
updateRomanizeLyricsSetting?: (checked: boolean) => void;
} }
} }
@@ -49,6 +51,7 @@ export const settings = await ReactiveStore.getPluginStorage("RadiantLyrics", {
bubbledLyrics: true, bubbledLyrics: true,
syllableLogging: false, syllableLogging: false,
lyricsFontSize: 100, lyricsFontSize: 100,
romanizeLyrics: false,
}); });
export const Settings = () => { export const Settings = () => {
@@ -146,6 +149,16 @@ export const Settings = () => {
const [qualityProgressColor, setQualityProgressColor] = React.useState( const [qualityProgressColor, setQualityProgressColor] = React.useState(
settings.qualityProgressColor, settings.qualityProgressColor,
); );
const [romanizeLyrics, setRomanizeLyrics] = React.useState(
settings.romanizeLyrics,
);
React.useEffect(() => {
window.updateRomanizeLyricsSetting = (checked: boolean) =>
setRomanizeLyrics(checked);
return () => {
window.updateRomanizeLyricsSetting = undefined;
};
}, []);
// Derive props and override onChange to accept a broader first param type // Derive props and override onChange to accept a broader first param type
type BaseSwitchProps = React.ComponentProps<typeof LunaSwitchSetting>; type BaseSwitchProps = React.ComponentProps<typeof LunaSwitchSetting>;
@@ -267,6 +280,18 @@ export const Settings = () => {
} }
}} }}
/> />
<AnySwitch
title="Romanize Lyrics | Beta"
desc="Display romanized (latin) text for non-latin lyrics (e.g. Korean, Japanese, Chinese)"
checked={romanizeLyrics}
onChange={(_: unknown, checked: boolean) => {
settings.romanizeLyrics = checked;
setRomanizeLyrics(checked);
if (window.updateRomanizeLyrics) {
window.updateRomanizeLyrics();
}
}}
/>
<AnySwitch <AnySwitch
title="Sticky Lyrics" title="Sticky Lyrics"
desc="auto-switches to Play Queue when lyrics aren't available (mirrored in lyrics dropdown)" desc="auto-switches to Play Queue when lyrics aren't available (mirrored in lyrics dropdown)"
+18 -5
View File
@@ -1215,6 +1215,7 @@ interface WordTiming {
time: number; // ms time: number; // ms
duration: number; // ms duration: number; // ms
isBackground: boolean; isBackground: boolean;
romanized?: string;
} }
interface WordLine { interface WordLine {
@@ -1225,6 +1226,7 @@ interface WordLine {
syllabus: WordTiming[]; syllabus: WordTiming[];
element: { key: string; songPart: string; singer: string }; element: { key: string; songPart: string; singer: string };
translation: string | null; translation: string | null;
romanized?: string;
} }
interface WordLyricsResponse { interface WordLyricsResponse {
@@ -1452,7 +1454,7 @@ const fetchWordLyrics = async (
artist: string, artist: string,
isrc?: string, isrc?: string,
): Promise<WordLyricsResponse | null> => { ): Promise<WordLyricsResponse | null> => {
const cacheKey = `${title}\0${artist}\0${isrc ?? ""}`; const cacheKey = `${title}\0${artist}\0${isrc ?? ""}\0${settings.romanizeLyrics ? "r" : ""}`;
if (cachedLyricsKey === cacheKey) { if (cachedLyricsKey === cacheKey) {
sylLog(`[RL-Syllable] Cache hit for "${title}" by "${artist}"`); sylLog(`[RL-Syllable] Cache hit for "${title}" by "${artist}"`);
return cachedLyricsData; return cachedLyricsData;
@@ -1460,6 +1462,7 @@ const fetchWordLyrics = async (
let params = `lyrics?title=${encodeURIComponent(title)}&artist=${encodeURIComponent(artist)}`; let params = `lyrics?title=${encodeURIComponent(title)}&artist=${encodeURIComponent(artist)}`;
if (isrc) params += `&isrc=${encodeURIComponent(isrc)}`; if (isrc) params += `&isrc=${encodeURIComponent(isrc)}`;
if (settings.romanizeLyrics) params += "&romanize=true";
const primaryUrls = [ const primaryUrls = [
`https://rl-api.atomix.one/${params}`, `https://rl-api.atomix.one/${params}`,
@@ -1811,6 +1814,9 @@ const buildWordSpans = (): {
return span; return span;
}; };
const useRomanized = settings.romanizeLyrics;
const sylDisplay = (s: WordTiming) => (useRomanized && s.romanized != null ? s.romanized : s.text);
// Group syllables into words: trailing whitespace in syl.text marks a word boundary // Group syllables into words: trailing whitespace in syl.text marks a word boundary
const wordGroups: number[][] = []; const wordGroups: number[][] = [];
let currentGroup: number[] = []; let currentGroup: number[] = [];
@@ -1829,7 +1835,7 @@ const buildWordSpans = (): {
const bgSyls = splitBg ? syllabus.filter(s => s.isBackground) : []; const bgSyls = splitBg ? syllabus.filter(s => s.isBackground) : [];
if (mainSyls.length > 0) { if (mainSyls.length > 0) {
const text = mainSyls.map(s => s.text).join("").trim(); const text = mainSyls.map(s => sylDisplay(s)).join("").trim();
const first = mainSyls[0]; const first = mainSyls[0];
const last = mainSyls[mainSyls.length - 1]; const last = mainSyls[mainSyls.length - 1];
const span = makeSpan(text, first.time, false); const span = makeSpan(text, first.time, false);
@@ -1837,7 +1843,7 @@ const buildWordSpans = (): {
lineWords.push({ el: span, start: first.time, end: last.time + last.duration, duration: (last.time + last.duration) - first.time }); lineWords.push({ el: span, start: first.time, end: last.time + last.duration, duration: (last.time + last.duration) - first.time });
} }
if (bgSyls.length > 0 && bgContainer) { if (bgSyls.length > 0 && bgContainer) {
const text = bgSyls.map(s => s.text).join("").trim().replace(/[()]/g, ""); const text = bgSyls.map(s => sylDisplay(s)).join("").trim().replace(/[()]/g, "");
const first = bgSyls[0]; const first = bgSyls[0];
const last = bgSyls[bgSyls.length - 1]; const last = bgSyls[bgSyls.length - 1];
const span = makeSpan(text, first.time, true); const span = makeSpan(text, first.time, true);
@@ -1855,7 +1861,7 @@ const buildWordSpans = (): {
const groupSpans: HTMLSpanElement[] = []; const groupSpans: HTMLSpanElement[] = [];
for (const si of group) { for (const si of group) {
const syl = syllabus[si]; const syl = syllabus[si];
const span = makeSpan(syl.text.trimEnd(), wordStartMs, syl.isBackground); const span = makeSpan(sylDisplay(syl).trimEnd(), wordStartMs, syl.isBackground);
span.addEventListener("mouseenter", () => { span.addEventListener("mouseenter", () => {
for (const s of groupSpans) s.classList.add("rl-wbw-word-hover"); for (const s of groupSpans) s.classList.add("rl-wbw-word-hover");
}); });
@@ -1868,7 +1874,7 @@ const buildWordSpans = (): {
targetWords.push(entry); targetWords.push(entry);
} }
} else { } else {
const mergedText = group.map(si => syllabus[si].text.trimEnd()).join(""); const mergedText = group.map(si => sylDisplay(syllabus[si]).trimEnd()).join("");
const first = syllabus[group[0]]; const first = syllabus[group[0]];
const last = syllabus[group[group.length - 1]]; const last = syllabus[group[group.length - 1]];
const start = first.time; const start = first.time;
@@ -2553,6 +2559,13 @@ const updateLyricsStyleFromSettings = (): void => {
}; };
(window as any).updateLyricsStyle = updateLyricsStyleFromSettings; (window as any).updateLyricsStyle = updateLyricsStyleFromSettings;
const updateRomanizeLyricsFromSettings = (): void => {
cachedLyricsKey = null;
cachedLyricsData = null;
toggle();
};
(window as any).updateRomanizeLyrics = updateRomanizeLyricsFromSettings;
// Update lyrics on track change (wipe cache for new song) // Update lyrics on track change (wipe cache for new song)
onGlobalTrackChange(() => { onGlobalTrackChange(() => {
cachedLyricsKey = null; cachedLyricsKey = null;