mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-19 17:06:38 +02:00
189 lines
5.9 KiB
TypeScript
189 lines
5.9 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import isElectron from 'is-electron';
|
|
|
|
import {
|
|
alignFuriganaToWordCues,
|
|
alignRomajiTokensToWordCues,
|
|
LyricTextToken,
|
|
RomajiToken,
|
|
} from '/@/renderer/features/lyrics/api/lyric-conversion';
|
|
import { LyricsResponse, SyncedCueLine, SynchronizedLyrics } from '/@/shared/types/domain-types';
|
|
|
|
const lyricsApi = isElectron() ? window.api.lyrics : null;
|
|
|
|
const convertSyncedLyricsFurigana = async (
|
|
lyrics: SynchronizedLyrics,
|
|
): Promise<SynchronizedLyrics> => {
|
|
if (!lyricsApi) {
|
|
return lyrics;
|
|
}
|
|
|
|
return Promise.all(
|
|
lyrics.map(async (line) => ({
|
|
...line,
|
|
cueLines: line.cueLines
|
|
? await Promise.all(
|
|
line.cueLines.map(async (cueLine) => {
|
|
const tokens = (await lyricsApi.parseLyricsTextTokens(
|
|
cueLine.value,
|
|
)) as LyricTextToken[];
|
|
const alignedWords = cueLine.words.length
|
|
? await alignFuriganaToWordCues(
|
|
cueLine.value,
|
|
cueLine.words,
|
|
tokens,
|
|
(text) => lyricsApi.convertFuriganaFragment(text),
|
|
)
|
|
: cueLine.words;
|
|
return {
|
|
...cueLine,
|
|
value: await lyricsApi.convertFurigana(cueLine.value),
|
|
words: alignedWords ?? cueLine.words,
|
|
};
|
|
}),
|
|
)
|
|
: undefined,
|
|
text: await lyricsApi.convertFurigana(line.text),
|
|
})),
|
|
);
|
|
};
|
|
|
|
const convertSyncedLyricsRomaji = async (
|
|
lyrics: SynchronizedLyrics,
|
|
convert: (text: string) => Promise<string>,
|
|
): Promise<SynchronizedLyrics> =>
|
|
Promise.all(
|
|
lyrics.map(async (line) => ({
|
|
...line,
|
|
cueLines: line.cueLines
|
|
? await Promise.all(
|
|
line.cueLines.map(async (cueLine) => ({
|
|
...cueLine,
|
|
value: await convert(cueLine.value),
|
|
words: await Promise.all(
|
|
cueLine.words.map(async (word) => ({
|
|
...word,
|
|
text: await convert(word.text),
|
|
})),
|
|
),
|
|
})),
|
|
)
|
|
: undefined,
|
|
text: await convert(line.text),
|
|
})),
|
|
);
|
|
|
|
export const useFuriganaLyrics = (lyrics: LyricsResponse | null | undefined, enabled: boolean) => {
|
|
return useQuery({
|
|
enabled: enabled && !!lyrics && !!lyricsApi,
|
|
queryFn: async () => {
|
|
if (!lyrics || !lyricsApi || !enabled) return lyrics;
|
|
|
|
if (typeof lyrics === 'string') {
|
|
return await lyricsApi.convertFurigana(lyrics);
|
|
}
|
|
|
|
if (Array.isArray(lyrics)) {
|
|
return convertSyncedLyricsFurigana(lyrics);
|
|
}
|
|
|
|
return lyrics;
|
|
},
|
|
queryKey: ['furigana', lyrics],
|
|
staleTime: Infinity,
|
|
});
|
|
};
|
|
|
|
export const useRomajiLyrics = (lyrics: LyricsResponse | null | undefined, enabled: boolean) => {
|
|
return useQuery({
|
|
enabled: enabled && !!lyrics && !!lyricsApi,
|
|
queryFn: async () => {
|
|
if (!lyrics || !lyricsApi || !enabled) return lyrics;
|
|
|
|
if (typeof lyrics === 'string') {
|
|
return await lyricsApi.convertRomaji(lyrics);
|
|
}
|
|
|
|
if (Array.isArray(lyrics)) {
|
|
return convertSyncedLyricsRomaji(lyrics, (text) => lyricsApi.convertRomaji(text));
|
|
}
|
|
|
|
return lyrics;
|
|
},
|
|
queryKey: ['romaji', lyrics],
|
|
staleTime: Infinity,
|
|
});
|
|
};
|
|
|
|
export type SyncedRomajiLyrics = ((null | SyncedCueLine)[] | null)[];
|
|
|
|
const buildSyncedRomajiLine = async (
|
|
cueLines: SyncedCueLine[],
|
|
): Promise<(null | SyncedCueLine)[]> => {
|
|
const romajiCueLines: (null | SyncedCueLine)[] = [];
|
|
|
|
for (const cueLine of cueLines) {
|
|
if (!cueLine.words.length) {
|
|
romajiCueLines.push(null);
|
|
continue;
|
|
}
|
|
|
|
if (!lyricsApi) {
|
|
return cueLines.map(() => null);
|
|
}
|
|
|
|
const tokens = (await lyricsApi.convertRomajiTokens(cueLine.value)) as RomajiToken[];
|
|
if (!tokens.length) {
|
|
romajiCueLines.push(null);
|
|
continue;
|
|
}
|
|
|
|
const alignedWords = alignRomajiTokensToWordCues(cueLine.value, cueLine.words, tokens);
|
|
|
|
if (!alignedWords) {
|
|
romajiCueLines.push(null);
|
|
continue;
|
|
}
|
|
|
|
romajiCueLines.push({
|
|
...cueLine,
|
|
words: alignedWords,
|
|
});
|
|
}
|
|
|
|
return romajiCueLines;
|
|
};
|
|
|
|
export const useSyncedRomajiLyrics = (
|
|
lyrics: null | SynchronizedLyrics | undefined,
|
|
enabled: boolean,
|
|
) => {
|
|
return useQuery({
|
|
enabled: enabled && !!lyrics && !!lyricsApi,
|
|
queryFn: async (): Promise<null | SyncedRomajiLyrics> => {
|
|
if (!lyrics || !lyricsApi || !enabled) {
|
|
return null;
|
|
}
|
|
|
|
const result: SyncedRomajiLyrics = [];
|
|
|
|
for (const line of lyrics) {
|
|
if (
|
|
!line.cueLines?.length ||
|
|
!line.cueLines.some((cueLine) => cueLine.words.length)
|
|
) {
|
|
result.push(null);
|
|
continue;
|
|
}
|
|
|
|
const romajiCueLines = await buildSyncedRomajiLine(line.cueLines);
|
|
result.push(romajiCueLines.some((entry) => entry !== null) ? romajiCueLines : null);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
queryKey: ['romaji-synced', lyrics],
|
|
staleTime: Infinity,
|
|
});
|
|
};
|