mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-13 22:20:13 +02:00
Add support for OpenSubsonic enhanced lyrics (#2208)
This commit is contained in:
@@ -1,8 +1,19 @@
|
||||
import Kuroshiro from 'kuroshiro';
|
||||
import KuromojiAnalyzer from 'kuroshiro-analyzer-kuromoji';
|
||||
import { hasJapanese, kanaToRomaji, patchTokens } from 'kuroshiro/lib/util';
|
||||
|
||||
// doc: https://kuroshiro.org
|
||||
|
||||
export type LyricTextToken = {
|
||||
endChar: number;
|
||||
startChar: number;
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type RomajiToken = LyricTextToken & {
|
||||
romaji: string;
|
||||
};
|
||||
|
||||
let kuroshiroInstance: any = null;
|
||||
let initPromise: null | Promise<void> = null;
|
||||
|
||||
@@ -39,6 +50,20 @@ export const convertFurigana = async (text: string): Promise<string> => {
|
||||
}
|
||||
};
|
||||
|
||||
export const convertFuriganaFragment = async (text: string): Promise<string> => {
|
||||
if (!text || !hasJapanese(text)) {
|
||||
return text;
|
||||
}
|
||||
|
||||
try {
|
||||
const kuroshiro = await getKuroshiro();
|
||||
return await kuroshiro.convert(text, { mode: 'furigana', to: 'hiragana' });
|
||||
} catch (e) {
|
||||
console.error('Furigana fragment conversion error: ', e);
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
export const convertRomaji = async (text: string): Promise<string> => {
|
||||
const KuroshiroClass = (Kuroshiro as any).default || Kuroshiro;
|
||||
|
||||
@@ -52,3 +77,70 @@ export const convertRomaji = async (text: string): Promise<string> => {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export const parseLyricsTextTokens = async (text: string): Promise<LyricTextToken[]> => {
|
||||
if (!text || !hasJapanese(text)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const kuroshiro = await getKuroshiro();
|
||||
const rawTokens = await kuroshiro._analyzer.parse(text);
|
||||
const tokens = patchTokens(rawTokens);
|
||||
|
||||
let cursor = 0;
|
||||
|
||||
return tokens.map((token: { surface_form: string }) => {
|
||||
const surface = token.surface_form;
|
||||
const startChar = cursor;
|
||||
cursor += surface.length;
|
||||
|
||||
return {
|
||||
endChar: cursor,
|
||||
startChar,
|
||||
text: surface,
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Lyrics token parse error: ', e);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const convertRomajiTokens = async (text: string): Promise<RomajiToken[]> => {
|
||||
const KuroshiroClass = (Kuroshiro as any).default || Kuroshiro;
|
||||
|
||||
if (!KuroshiroClass.Util.hasKana(text)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const kuroshiro = await getKuroshiro();
|
||||
const rawTokens = await kuroshiro._analyzer.parse(text);
|
||||
const tokens = patchTokens(rawTokens);
|
||||
|
||||
let cursor = 0;
|
||||
|
||||
return tokens.map(
|
||||
(token: { pronunciation?: string; reading: string; surface_form: string }) => {
|
||||
const surface = token.surface_form;
|
||||
const startChar = cursor;
|
||||
cursor += surface.length;
|
||||
|
||||
const romaji = hasJapanese(surface)
|
||||
? kanaToRomaji(token.pronunciation || token.reading)
|
||||
: surface;
|
||||
|
||||
return {
|
||||
endChar: cursor,
|
||||
romaji,
|
||||
startChar,
|
||||
text: surface,
|
||||
};
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('Romaji token conversion error: ', e);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import { ipcMain } from 'electron';
|
||||
|
||||
import { store } from '../settings';
|
||||
import { convertFurigana, convertRomaji } from './furigana';
|
||||
import {
|
||||
convertFurigana,
|
||||
convertFuriganaFragment,
|
||||
convertRomaji,
|
||||
convertRomajiTokens,
|
||||
parseLyricsTextTokens,
|
||||
} from './furigana';
|
||||
import { getLyricsBySongId as getGenius, getSearchResults as searchGenius } from './genius';
|
||||
import { getLyricsBySongId as getLrcLib, getSearchResults as searchLrcLib } from './lrclib';
|
||||
import { getLyricsBySongId as getNetease, getSearchResults as searchNetease } from './netease';
|
||||
@@ -237,6 +243,18 @@ ipcMain.handle('lyric-convert-furigana', async (_event, text: string) => {
|
||||
return await convertFurigana(text);
|
||||
});
|
||||
|
||||
ipcMain.handle('lyric-convert-furigana-fragment', async (_event, text: string) => {
|
||||
return await convertFuriganaFragment(text);
|
||||
});
|
||||
|
||||
ipcMain.handle('lyric-parse-text-tokens', async (_event, text: string) => {
|
||||
return await parseLyricsTextTokens(text);
|
||||
});
|
||||
|
||||
ipcMain.handle('lyric-convert-romaji', async (_event, text: string) => {
|
||||
return await convertRomaji(text);
|
||||
});
|
||||
|
||||
ipcMain.handle('lyric-convert-romaji-tokens', async (_event, text: string) => {
|
||||
return await convertRomajiTokens(text);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user