mirror of
https://github.com/antebudimir/feishin.git
synced 2025-12-31 18:13:31 +00:00
lyrics: add translation lyrics for netease.ts (#951)
* lyrics: add translation lyrics for netease.ts
This commit is contained in:
parent
e3751229b6
commit
ae41fe99bb
5 changed files with 90 additions and 2 deletions
|
|
@ -609,6 +609,8 @@
|
||||||
"mpvExtraParameters_help": "one per line",
|
"mpvExtraParameters_help": "one per line",
|
||||||
"musicbrainz": "show musicbrainz links",
|
"musicbrainz": "show musicbrainz links",
|
||||||
"musicbrainz_description": "show links to musicbrainz on artist/album pages, where mbid exists",
|
"musicbrainz_description": "show links to musicbrainz on artist/album pages, where mbid exists",
|
||||||
|
"neteaseTranslation": "Enable NetEase translations",
|
||||||
|
"neteaseTranslation_description": "When enabled, fetches and displays translated lyrics from NetEase if available.",
|
||||||
"passwordStore": "passwords/secret store",
|
"passwordStore": "passwords/secret store",
|
||||||
"passwordStore_description": "what password/secret store to use. change this if you are having issues storing passwords.",
|
"passwordStore_description": "what password/secret store to use. change this if you are having issues storing passwords.",
|
||||||
"playbackStyle": "playback style",
|
"playbackStyle": "playback style",
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,8 @@
|
||||||
"followLyric_description": "滚动歌词到当前播放位置",
|
"followLyric_description": "滚动歌词到当前播放位置",
|
||||||
"audioExclusiveMode": "音频独占模式",
|
"audioExclusiveMode": "音频独占模式",
|
||||||
"font": "字体",
|
"font": "字体",
|
||||||
|
"neteaseTranslation": "启用网易云歌词翻译",
|
||||||
|
"neteaseTranslation_description": "启用后,在获取歌词时将包含并显示网易云音乐提供的翻译(如果存在)。",
|
||||||
"crossfadeDuration_description": "设置淡入淡出持续时间",
|
"crossfadeDuration_description": "设置淡入淡出持续时间",
|
||||||
"audioDevice": "音频设备",
|
"audioDevice": "音频设备",
|
||||||
"enableRemote": "启用远程控制服务器",
|
"enableRemote": "启用远程控制服务器",
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import {
|
||||||
LyricSearchQuery,
|
LyricSearchQuery,
|
||||||
LyricSource,
|
LyricSource,
|
||||||
} from '.';
|
} from '.';
|
||||||
|
import { store } from '../settings';
|
||||||
import { orderSearchResults } from './shared';
|
import { orderSearchResults } from './shared';
|
||||||
|
|
||||||
const SEARCH_URL = 'https://music.163.com/api/search/get';
|
const SEARCH_URL = 'https://music.163.com/api/search/get';
|
||||||
|
|
@ -76,14 +77,20 @@ export async function getLyricsBySongId(songId: string): Promise<null | string>
|
||||||
id: songId,
|
id: songId,
|
||||||
kv: '-1',
|
kv: '-1',
|
||||||
lv: '-1',
|
lv: '-1',
|
||||||
|
tv: '-1',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('NetEase lyrics request got an error!', e);
|
console.error('NetEase lyrics request got an error!', e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const enableTranslation = store.get('enableNeteaseTranslation', true) as boolean;
|
||||||
return result.data.klyric?.lyric || result.data.lrc?.lyric;
|
const originalLrc = result.data.lrc?.lyric;
|
||||||
|
if (!enableTranslation) {
|
||||||
|
return originalLrc || null;
|
||||||
|
}
|
||||||
|
const translatedLrc = result.data.tlyric?.lyric;
|
||||||
|
return mergeLyrics(originalLrc, translatedLrc);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSearchResults(
|
export async function getSearchResults(
|
||||||
|
|
@ -166,3 +173,54 @@ async function getMatchedLyrics(
|
||||||
|
|
||||||
return firstMatch;
|
return firstMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mergeLyrics(original: string | undefined, translated: string | undefined): null | string {
|
||||||
|
if (!original) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!translated) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lrcLineRegex = /\[(\d{2}:\d{2}\.\d{2,3})\](.*)/;
|
||||||
|
const translatedMap = new Map<string, string>();
|
||||||
|
|
||||||
|
// Parse the translated LRC and store it in a Map for efficient timestamp-based lookups.
|
||||||
|
translated.split('\n').forEach((line) => {
|
||||||
|
const match = line.match(lrcLineRegex);
|
||||||
|
if (match) {
|
||||||
|
const timestamp = match[1];
|
||||||
|
const text = match[2].trim();
|
||||||
|
if (text) {
|
||||||
|
translatedMap.set(timestamp, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (translatedMap.size === 0) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through each line of the original LRC. If a translation exists for
|
||||||
|
// the same timestamp, insert it as a new, fully-formatted LRC line.
|
||||||
|
const finalLines = original.split('\n').flatMap((line) => {
|
||||||
|
const match = line.match(lrcLineRegex);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
const timestamp = match[1];
|
||||||
|
const translatedText = translatedMap.get(timestamp);
|
||||||
|
|
||||||
|
if (translatedText) {
|
||||||
|
// Return an array containing both the original line and the new translated line.
|
||||||
|
// flatMap will flatten this into the final array of lines.
|
||||||
|
const translatedLine = `[${timestamp}]${translatedText}`;
|
||||||
|
return [line, translatedLine];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no match or no translation is found, return only the original line.
|
||||||
|
return [line];
|
||||||
|
});
|
||||||
|
|
||||||
|
return finalLines.join('\n');
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,30 @@ export const LyricSettings = () => {
|
||||||
isHidden: !isElectron(),
|
isHidden: !isElectron(),
|
||||||
title: t('setting.lyricFetchProvider', { postProcess: 'sentenceCase' }),
|
title: t('setting.lyricFetchProvider', { postProcess: 'sentenceCase' }),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Switch
|
||||||
|
aria-label="Enable NetEase translations"
|
||||||
|
defaultChecked={settings.enableNeteaseTranslation}
|
||||||
|
onChange={(e) => {
|
||||||
|
const isChecked = e.currentTarget.checked;
|
||||||
|
setSettings({
|
||||||
|
lyrics: {
|
||||||
|
...settings,
|
||||||
|
enableNeteaseTranslation: e.currentTarget.checked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
localSettings?.set('enableNeteaseTranslation', isChecked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description: t('setting.neteaseTranslation', {
|
||||||
|
context: 'description',
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
}),
|
||||||
|
isHidden: !isElectron(),
|
||||||
|
title: t('setting.neteaseTranslation', { postProcess: 'sentenceCase' }),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
control: (
|
control: (
|
||||||
<NumberInput
|
<NumberInput
|
||||||
|
|
|
||||||
|
|
@ -263,6 +263,7 @@ export interface SettingsState {
|
||||||
lyrics: {
|
lyrics: {
|
||||||
alignment: 'center' | 'left' | 'right';
|
alignment: 'center' | 'left' | 'right';
|
||||||
delayMs: number;
|
delayMs: number;
|
||||||
|
enableNeteaseTranslation: boolean;
|
||||||
fetch: boolean;
|
fetch: boolean;
|
||||||
follow: boolean;
|
follow: boolean;
|
||||||
fontSize: number;
|
fontSize: number;
|
||||||
|
|
@ -445,6 +446,7 @@ const initialState: SettingsState = {
|
||||||
lyrics: {
|
lyrics: {
|
||||||
alignment: 'center',
|
alignment: 'center',
|
||||||
delayMs: 0,
|
delayMs: 0,
|
||||||
|
enableNeteaseTranslation: false,
|
||||||
fetch: false,
|
fetch: false,
|
||||||
follow: true,
|
follow: true,
|
||||||
fontSize: 46,
|
fontSize: 46,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue