mirror of
https://github.com/meowarex/TidaLuna-Plugins.git
synced 2026-06-18 03:43:10 +10:00
@@ -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)"
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user