mirror of
https://github.com/meowarex/TidaLuna-Plugins.git
synced 2026-06-18 03:43:10 +10:00
@@ -1324,7 +1324,7 @@ function setupStickyLyricsObserver(): void {
|
|||||||
// Apply word lyrics when lyrics container appears or reappears
|
// Apply word lyrics when lyrics container appears or reappears
|
||||||
observe<HTMLElement>(unloads, '[data-test="lyrics-lines"]', () => {
|
observe<HTMLElement>(unloads, '[data-test="lyrics-lines"]', () => {
|
||||||
if (lyricsMode === "line-tidal") {
|
if (lyricsMode === "line-tidal") {
|
||||||
reapplyTidalLineLyrics();
|
void reapplyTidalLineLyrics();
|
||||||
} else if (lyricsData) {
|
} else if (lyricsData) {
|
||||||
reapplyWordLyrics();
|
reapplyWordLyrics();
|
||||||
} else {
|
} else {
|
||||||
@@ -1671,6 +1671,8 @@ const getTrackInfo = async (): Promise<{
|
|||||||
// fetch syllables from the API (wiped on track change)
|
// fetch syllables from the API (wiped on track change)
|
||||||
let cachedLyricsKey: string | null = null;
|
let cachedLyricsKey: string | null = null;
|
||||||
let cachedLyricsData: LyricsApiResponse | null = null;
|
let cachedLyricsData: LyricsApiResponse | null = null;
|
||||||
|
let cachedTidalRomanizeKey: string | null = null;
|
||||||
|
let cachedTidalRomanizedLines: string[] | null = null;
|
||||||
const fetchLyrics = async (
|
const fetchLyrics = async (
|
||||||
title: string,
|
title: string,
|
||||||
artist: string,
|
artist: string,
|
||||||
@@ -1808,6 +1810,91 @@ const normalizeLineLyricsData = (data: ApiLine[]): WordLine[] => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Scrapes Tidal Line Texts (For Romanization)
|
||||||
|
const getTidalLineTexts = (): string[] => {
|
||||||
|
const lyricsContainer = document.querySelector(
|
||||||
|
'[data-test="lyrics-lines"]',
|
||||||
|
) as HTMLElement;
|
||||||
|
if (!lyricsContainer) return [];
|
||||||
|
const innerDiv = lyricsContainer.querySelector(":scope > div") as HTMLElement;
|
||||||
|
if (!innerDiv) return [];
|
||||||
|
|
||||||
|
const spans = Array.from(
|
||||||
|
innerDiv.querySelectorAll('span[data-test="lyrics-line"]'),
|
||||||
|
) as HTMLElement[];
|
||||||
|
return spans
|
||||||
|
.map((s) => s.textContent ?? "")
|
||||||
|
.filter((text) => text.trim().length > 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const romanizeLinePayload = async (
|
||||||
|
lineTexts: string[],
|
||||||
|
): Promise<string[] | null> => {
|
||||||
|
if (!settings.romanizeLyrics || lineTexts.length === 0) return null;
|
||||||
|
|
||||||
|
const cacheKey = `${lineTexts.join("\n")}\0r`;
|
||||||
|
if (cachedTidalRomanizeKey === cacheKey && cachedTidalRomanizedLines) {
|
||||||
|
return cachedTidalRomanizedLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
type: "Line" as const,
|
||||||
|
data: lineTexts.map((text, idx) => ({
|
||||||
|
text,
|
||||||
|
startTime: idx,
|
||||||
|
duration: 0,
|
||||||
|
endTime: idx,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
const urls = [
|
||||||
|
"https://rl-api.atomix.one/romanize",
|
||||||
|
"https://lyricsplus-api.atomix.one/romanize",
|
||||||
|
"https://rl-api.kineticsand.net/romanize",
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const url of urls) {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||||
|
try {
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "content-type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
signal: controller.signal,
|
||||||
|
});
|
||||||
|
clearTimeout(timeout);
|
||||||
|
if (!res.ok) {
|
||||||
|
trace.log(`Romanize: request failed ${res.status} from ${url}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await res.json()) as {
|
||||||
|
type?: string;
|
||||||
|
data?: Array<{ text?: string; romanized?: string }>;
|
||||||
|
};
|
||||||
|
if (!Array.isArray(data?.data)) continue;
|
||||||
|
|
||||||
|
const romanized = lineTexts.map((original, idx) => {
|
||||||
|
const item = data.data?.[idx];
|
||||||
|
return item?.romanized ?? item?.text ?? original;
|
||||||
|
});
|
||||||
|
cachedTidalRomanizeKey = cacheKey;
|
||||||
|
cachedTidalRomanizedLines = romanized;
|
||||||
|
return romanized;
|
||||||
|
} catch (err) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
if (err instanceof DOMException && err.name === "AbortError") {
|
||||||
|
trace.log(`Romanize: request timed out from ${url}`);
|
||||||
|
} else {
|
||||||
|
trace.log(`Romanize: request error from ${url}: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
// strip tidal css classes (prevent conflict)
|
// strip tidal css classes (prevent conflict)
|
||||||
const hideTidalLyrics = (): boolean => {
|
const hideTidalLyrics = (): boolean => {
|
||||||
const lyricsContainer = document.querySelector(
|
const lyricsContainer = document.querySelector(
|
||||||
@@ -2274,7 +2361,9 @@ const buildWordSpans = (): {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Scrapes & Builds Tidal Line Spans (no lines found in API)
|
// Scrapes & Builds Tidal Line Spans (no lines found in API)
|
||||||
const buildTidalLineSpans = (): { lines: LineEntry[] } => {
|
const buildTidalLineSpans = (
|
||||||
|
romanizedLines: string[] | null = null,
|
||||||
|
): { lines: LineEntry[] } => {
|
||||||
const lines: LineEntry[] = [];
|
const lines: LineEntry[] = [];
|
||||||
const lyricsContainer = document.querySelector(
|
const lyricsContainer = document.querySelector(
|
||||||
'[data-test="lyrics-lines"]',
|
'[data-test="lyrics-lines"]',
|
||||||
@@ -2314,9 +2403,15 @@ const buildTidalLineSpans = (): { lines: LineEntry[] } => {
|
|||||||
const tidalSpans = Array.from(
|
const tidalSpans = Array.from(
|
||||||
innerDiv.querySelectorAll('span[data-test="lyrics-line"]'),
|
innerDiv.querySelectorAll('span[data-test="lyrics-line"]'),
|
||||||
) as HTMLElement[];
|
) as HTMLElement[];
|
||||||
|
let textIdx = 0;
|
||||||
for (const tidalSpan of tidalSpans) {
|
for (const tidalSpan of tidalSpans) {
|
||||||
const text = tidalSpan.textContent ?? "";
|
const rawText = tidalSpan.textContent ?? "";
|
||||||
if (text.trim().length === 0) {
|
const text =
|
||||||
|
settings.romanizeLyrics && romanizedLines?.[textIdx]
|
||||||
|
? romanizedLines[textIdx]
|
||||||
|
: rawText;
|
||||||
|
if (rawText.trim().length > 0) textIdx++;
|
||||||
|
if (rawText.trim().length === 0) {
|
||||||
const spacer = document.createElement("div");
|
const spacer = document.createElement("div");
|
||||||
spacer.className = "rl-wbw-line rl-wbw-spacer";
|
spacer.className = "rl-wbw-line rl-wbw-spacer";
|
||||||
forceStyle(spacer, {
|
forceStyle(spacer, {
|
||||||
@@ -2513,7 +2608,7 @@ const watchForRerender = (): void => {
|
|||||||
sylTrace("Lyrics overlay: re-applying after Tidal re-render");
|
sylTrace("Lyrics overlay: re-applying after Tidal re-render");
|
||||||
hideTidalLyrics();
|
hideTidalLyrics();
|
||||||
if (lyricsMode === "line-tidal") {
|
if (lyricsMode === "line-tidal") {
|
||||||
const result = buildTidalLineSpans();
|
const result = buildTidalLineSpans(cachedTidalRomanizedLines);
|
||||||
lines = result.lines;
|
lines = result.lines;
|
||||||
startTidalFollowLoop();
|
startTidalFollowLoop();
|
||||||
} else if (lyricsData) {
|
} else if (lyricsData) {
|
||||||
@@ -3097,10 +3192,19 @@ const onTrackChange = async (): Promise<void> => {
|
|||||||
if (token !== trackChangeToken) return;
|
if (token !== trackChangeToken) return;
|
||||||
if (!response) {
|
if (!response) {
|
||||||
trace.log("RL API: no API lyrics available, falling back to TIDAL lines");
|
trace.log("RL API: no API lyrics available, falling back to TIDAL lines");
|
||||||
|
const tidalTexts = getTidalLineTexts();
|
||||||
|
const romanized = settings.romanizeLyrics
|
||||||
|
? await romanizeLinePayload(tidalTexts)
|
||||||
|
: null;
|
||||||
|
if (token !== trackChangeToken) return;
|
||||||
|
cachedTidalRomanizedLines = romanized;
|
||||||
|
cachedTidalRomanizeKey = settings.romanizeLyrics
|
||||||
|
? `${tidalTexts.join("\n")}\0r`
|
||||||
|
: null;
|
||||||
isActive = true;
|
isActive = true;
|
||||||
lyricsMode = "line-tidal";
|
lyricsMode = "line-tidal";
|
||||||
hideTidalLyrics();
|
hideTidalLyrics();
|
||||||
const tidalResult = buildTidalLineSpans();
|
const tidalResult = buildTidalLineSpans(romanized);
|
||||||
lines = tidalResult.lines;
|
lines = tidalResult.lines;
|
||||||
if (lines.length === 0) {
|
if (lines.length === 0) {
|
||||||
trace.log("No TIDAL lines available yet");
|
trace.log("No TIDAL lines available yet");
|
||||||
@@ -3171,7 +3275,7 @@ const reapplyWordLyrics = (): void => {
|
|||||||
sylLog("[RL-Syllable] Reapplied word/syllable lyrics (cached)");
|
sylLog("[RL-Syllable] Reapplied word/syllable lyrics (cached)");
|
||||||
};
|
};
|
||||||
|
|
||||||
const reapplyTidalLineLyrics = (): void => {
|
const reapplyTidalLineLyrics = async (): Promise<void> => {
|
||||||
clearTickLoop();
|
clearTickLoop();
|
||||||
stopTidalFollowLoop();
|
stopTidalFollowLoop();
|
||||||
clearScrollAnim();
|
clearScrollAnim();
|
||||||
@@ -3186,8 +3290,12 @@ const reapplyTidalLineLyrics = (): void => {
|
|||||||
|
|
||||||
isActive = true;
|
isActive = true;
|
||||||
lyricsMode = "line-tidal";
|
lyricsMode = "line-tidal";
|
||||||
|
const tidalTexts = getTidalLineTexts();
|
||||||
|
const romanized = settings.romanizeLyrics
|
||||||
|
? await romanizeLinePayload(tidalTexts)
|
||||||
|
: null;
|
||||||
hideTidalLyrics();
|
hideTidalLyrics();
|
||||||
const result = buildTidalLineSpans();
|
const result = buildTidalLineSpans(romanized);
|
||||||
lines = result.lines;
|
lines = result.lines;
|
||||||
if (lines.length === 0) return;
|
if (lines.length === 0) return;
|
||||||
watchForRerender();
|
watchForRerender();
|
||||||
@@ -3214,6 +3322,8 @@ const updateLyricsStyleFromSettings = (): void => {
|
|||||||
const updateRomanizeLyricsFromSettings = (): void => {
|
const updateRomanizeLyricsFromSettings = (): void => {
|
||||||
cachedLyricsKey = null;
|
cachedLyricsKey = null;
|
||||||
cachedLyricsData = null;
|
cachedLyricsData = null;
|
||||||
|
cachedTidalRomanizeKey = null;
|
||||||
|
cachedTidalRomanizedLines = null;
|
||||||
toggle();
|
toggle();
|
||||||
};
|
};
|
||||||
(window as any).updateRomanizeLyrics = updateRomanizeLyricsFromSettings;
|
(window as any).updateRomanizeLyrics = updateRomanizeLyricsFromSettings;
|
||||||
@@ -3222,6 +3332,8 @@ const updateRomanizeLyricsFromSettings = (): void => {
|
|||||||
onGlobalTrackChange(() => {
|
onGlobalTrackChange(() => {
|
||||||
cachedLyricsKey = null;
|
cachedLyricsKey = null;
|
||||||
cachedLyricsData = null;
|
cachedLyricsData = null;
|
||||||
|
cachedTidalRomanizeKey = null;
|
||||||
|
cachedTidalRomanizedLines = null;
|
||||||
onTrackChange();
|
onTrackChange();
|
||||||
});
|
});
|
||||||
unloads.add(() => teardown());
|
unloads.add(() => teardown());
|
||||||
|
|||||||
Reference in New Issue
Block a user