mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-03-09 21:03:55 +00:00
feat(synced-lyrics): Add Simplified/Traditional Chinese converter and improve Romanization to display tone (#4111)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Angelos Bouklis <me@arjix.dev> Co-authored-by: JellyBrick <shlee1503@naver.com>
This commit is contained in:
committed by
GitHub
parent
e2f1ff50dd
commit
208e57bdd3
@ -89,6 +89,7 @@
|
|||||||
"bgutils-js": "3.2.0",
|
"bgutils-js": "3.2.0",
|
||||||
"butterchurn": "3.0.0-beta.5",
|
"butterchurn": "3.0.0-beta.5",
|
||||||
"butterchurn-presets": "3.0.0-beta.4",
|
"butterchurn-presets": "3.0.0-beta.4",
|
||||||
|
"chinese-conv": "^4.0.0",
|
||||||
"color": "5.0.3",
|
"color": "5.0.3",
|
||||||
"conf": "15.0.2",
|
"conf": "15.0.2",
|
||||||
"custom-electron-prompt": "1.6.1",
|
"custom-electron-prompt": "1.6.1",
|
||||||
@ -121,6 +122,7 @@
|
|||||||
"node-html-parser": "7.0.2",
|
"node-html-parser": "7.0.2",
|
||||||
"node-id3": "0.2.9",
|
"node-id3": "0.2.9",
|
||||||
"peerjs": "1.5.5",
|
"peerjs": "1.5.5",
|
||||||
|
"pinyin-pro": "^3.27.0",
|
||||||
"semver": "7.7.3",
|
"semver": "7.7.3",
|
||||||
"serve": "14.2.5",
|
"serve": "14.2.5",
|
||||||
"socks": "2.8.7",
|
"socks": "2.8.7",
|
||||||
@ -129,7 +131,6 @@
|
|||||||
"solid-js": "1.9.11",
|
"solid-js": "1.9.11",
|
||||||
"solid-styled-components": "0.28.5",
|
"solid-styled-components": "0.28.5",
|
||||||
"solid-transition-group": "0.3.0",
|
"solid-transition-group": "0.3.0",
|
||||||
"tiny-pinyin": "1.3.2",
|
|
||||||
"tinyld": "1.3.4",
|
"tinyld": "1.3.4",
|
||||||
"virtua": "0.48.5",
|
"virtua": "0.48.5",
|
||||||
"vudio": "2.1.1",
|
"vudio": "2.1.1",
|
||||||
|
|||||||
31
pnpm-lock.yaml
generated
31
pnpm-lock.yaml
generated
@ -114,6 +114,9 @@ importers:
|
|||||||
butterchurn-presets:
|
butterchurn-presets:
|
||||||
specifier: 3.0.0-beta.4
|
specifier: 3.0.0-beta.4
|
||||||
version: 3.0.0-beta.4
|
version: 3.0.0-beta.4
|
||||||
|
chinese-conv:
|
||||||
|
specifier: ^4.0.0
|
||||||
|
version: 4.0.0
|
||||||
color:
|
color:
|
||||||
specifier: 5.0.3
|
specifier: 5.0.3
|
||||||
version: 5.0.3
|
version: 5.0.3
|
||||||
@ -210,6 +213,9 @@ importers:
|
|||||||
peerjs:
|
peerjs:
|
||||||
specifier: 1.5.5
|
specifier: 1.5.5
|
||||||
version: 1.5.5
|
version: 1.5.5
|
||||||
|
pinyin-pro:
|
||||||
|
specifier: ^3.27.0
|
||||||
|
version: 3.27.0
|
||||||
semver:
|
semver:
|
||||||
specifier: 7.7.3
|
specifier: 7.7.3
|
||||||
version: 7.7.3
|
version: 7.7.3
|
||||||
@ -234,9 +240,6 @@ importers:
|
|||||||
solid-transition-group:
|
solid-transition-group:
|
||||||
specifier: 0.3.0
|
specifier: 0.3.0
|
||||||
version: 0.3.0(solid-js@1.9.11)
|
version: 0.3.0(solid-js@1.9.11)
|
||||||
tiny-pinyin:
|
|
||||||
specifier: 1.3.2
|
|
||||||
version: 1.3.2
|
|
||||||
tinyld:
|
tinyld:
|
||||||
specifier: 1.3.4
|
specifier: 1.3.4
|
||||||
version: 1.3.4
|
version: 1.3.4
|
||||||
@ -1830,6 +1833,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==}
|
resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==}
|
||||||
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
||||||
|
|
||||||
|
chinese-conv@4.0.0:
|
||||||
|
resolution: {integrity: sha512-PVBMzvv6CtX1cubaDXfxYscIdbOAHPuY/E2vnfJIzOACX+xIW4NRKRlNsZVI2p5KxGsXyUp7tVHfvQlqZ4yx/w==}
|
||||||
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
|
|
||||||
|
chownr@2.0.0:
|
||||||
|
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
chownr@3.0.0:
|
chownr@3.0.0:
|
||||||
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
|
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@ -3740,6 +3751,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
pinyin-pro@3.27.0:
|
||||||
|
resolution: {integrity: sha512-Osdgjwe7Rm17N2paDMM47yW+jUIUH3+0RGo8QP39ZTLpTaJVDK0T58hOLaMQJbcMmAebVuK2ePunTEVEx1clNQ==}
|
||||||
|
|
||||||
pixelmatch@5.3.0:
|
pixelmatch@5.3.0:
|
||||||
resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==}
|
resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -4308,9 +4322,6 @@ packages:
|
|||||||
tiny-async-pool@1.3.0:
|
tiny-async-pool@1.3.0:
|
||||||
resolution: {integrity: sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==}
|
resolution: {integrity: sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==}
|
||||||
|
|
||||||
tiny-pinyin@1.3.2:
|
|
||||||
resolution: {integrity: sha512-uHNGu4evFt/8eNLldazeAM1M8JrMc1jshhJJfVRARTN3yT8HEEibofeQ7QETWQ5ISBjd6fKtTVBCC/+mGS6FpA==}
|
|
||||||
|
|
||||||
tiny-typed-emitter@2.1.0:
|
tiny-typed-emitter@2.1.0:
|
||||||
resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==}
|
resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==}
|
||||||
|
|
||||||
@ -6359,6 +6370,10 @@ snapshots:
|
|||||||
|
|
||||||
chalk@5.0.1: {}
|
chalk@5.0.1: {}
|
||||||
|
|
||||||
|
chinese-conv@4.0.0: {}
|
||||||
|
|
||||||
|
chownr@2.0.0: {}
|
||||||
|
|
||||||
chownr@3.0.0: {}
|
chownr@3.0.0: {}
|
||||||
|
|
||||||
chromium-pickle-js@0.2.0: {}
|
chromium-pickle-js@0.2.0: {}
|
||||||
@ -8458,6 +8473,8 @@ snapshots:
|
|||||||
|
|
||||||
picomatch@4.0.3: {}
|
picomatch@4.0.3: {}
|
||||||
|
|
||||||
|
pinyin-pro@3.27.0: {}
|
||||||
|
|
||||||
pixelmatch@5.3.0:
|
pixelmatch@5.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
pngjs: 6.0.0
|
pngjs: 6.0.0
|
||||||
@ -9070,8 +9087,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
semver: 5.7.2
|
semver: 5.7.2
|
||||||
|
|
||||||
tiny-pinyin@1.3.2: {}
|
|
||||||
|
|
||||||
tiny-typed-emitter@2.1.0: {}
|
tiny-typed-emitter@2.1.0: {}
|
||||||
|
|
||||||
tinycolor2@1.6.0: {}
|
tinycolor2@1.6.0: {}
|
||||||
|
|||||||
@ -891,6 +891,24 @@
|
|||||||
"show-time-codes": {
|
"show-time-codes": {
|
||||||
"label": "Show time codes",
|
"label": "Show time codes",
|
||||||
"tooltip": "Show the time codes next to the lyrics"
|
"tooltip": "Show the time codes next to the lyrics"
|
||||||
|
},
|
||||||
|
"convert-chinese-character": {
|
||||||
|
"label": "Convert Chinese character",
|
||||||
|
"submenu": {
|
||||||
|
"disabled": {
|
||||||
|
"label": "Disabled",
|
||||||
|
"tooltip": "Disable Chinese character conversion"
|
||||||
|
},
|
||||||
|
"simplified-to-traditional": {
|
||||||
|
"label": "Simplified to Traditional",
|
||||||
|
"tooltip": "Convert Simplified Chinese to Traditional Chinese"
|
||||||
|
},
|
||||||
|
"traditional-to-simplified": {
|
||||||
|
"label": "Traditional to Simplified",
|
||||||
|
"tooltip": "Convert Traditional Chinese to Simplified Chinese"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tooltip": "Convert Chinese character to Traditional or Simplified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "Synced Lyrics",
|
"name": "Synced Lyrics",
|
||||||
|
|||||||
@ -153,6 +153,62 @@ export const menu = async (
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t('plugins.synced-lyrics.menu.convert-chinese-character.label'),
|
||||||
|
toolTip: t(
|
||||||
|
'plugins.synced-lyrics.menu.convert-chinese-character.tooltip',
|
||||||
|
),
|
||||||
|
type: 'submenu',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: t(
|
||||||
|
'plugins.synced-lyrics.menu.convert-chinese-character.submenu.disabled.label',
|
||||||
|
),
|
||||||
|
toolTip: t(
|
||||||
|
'plugins.synced-lyrics.menu.convert-chinese-character.submenu.disabled.tooltip',
|
||||||
|
),
|
||||||
|
type: 'radio',
|
||||||
|
checked:
|
||||||
|
config.convertChineseCharacter === 'disabled' ||
|
||||||
|
config.convertChineseCharacter === undefined,
|
||||||
|
click() {
|
||||||
|
ctx.setConfig({
|
||||||
|
convertChineseCharacter: 'disabled',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t(
|
||||||
|
'plugins.synced-lyrics.menu.convert-chinese-character.submenu.simplified-to-traditional.label',
|
||||||
|
),
|
||||||
|
toolTip: t(
|
||||||
|
'plugins.synced-lyrics.menu.convert-chinese-character.submenu.simplified-to-traditional.tooltip',
|
||||||
|
),
|
||||||
|
type: 'radio',
|
||||||
|
checked: config.convertChineseCharacter === 'simplifiedToTraditional',
|
||||||
|
click() {
|
||||||
|
ctx.setConfig({
|
||||||
|
convertChineseCharacter: 'simplifiedToTraditional',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t(
|
||||||
|
'plugins.synced-lyrics.menu.convert-chinese-character.submenu.traditional-to-simplified.label',
|
||||||
|
),
|
||||||
|
toolTip: t(
|
||||||
|
'plugins.synced-lyrics.menu.convert-chinese-character.submenu.traditional-to-simplified.tooltip',
|
||||||
|
),
|
||||||
|
type: 'radio',
|
||||||
|
checked: config.convertChineseCharacter === 'traditionalToSimplified',
|
||||||
|
click() {
|
||||||
|
ctx.setConfig({
|
||||||
|
convertChineseCharacter: 'traditionalToSimplified',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t('plugins.synced-lyrics.menu.show-time-codes.label'),
|
label: t('plugins.synced-lyrics.menu.show-time-codes.label'),
|
||||||
toolTip: t('plugins.synced-lyrics.menu.show-time-codes.tooltip'),
|
toolTip: t('plugins.synced-lyrics.menu.show-time-codes.tooltip'),
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import { createEffect, createSignal, Show } from 'solid-js';
|
import { createEffect, createMemo, createSignal, Show } from 'solid-js';
|
||||||
|
|
||||||
import { canonicalize, romanize, simplifyUnicode } from '../utils';
|
import {
|
||||||
|
canonicalize,
|
||||||
|
convertChineseCharacter,
|
||||||
|
romanize,
|
||||||
|
simplifyUnicode,
|
||||||
|
} from '../utils';
|
||||||
import { config } from '../renderer';
|
import { config } from '../renderer';
|
||||||
|
|
||||||
interface PlainLyricsProps {
|
interface PlainLyricsProps {
|
||||||
@ -9,11 +14,19 @@ interface PlainLyricsProps {
|
|||||||
|
|
||||||
export const PlainLyrics = (props: PlainLyricsProps) => {
|
export const PlainLyrics = (props: PlainLyricsProps) => {
|
||||||
const [romanization, setRomanization] = createSignal('');
|
const [romanization, setRomanization] = createSignal('');
|
||||||
|
const text = createMemo(() => {
|
||||||
|
let line = props.line;
|
||||||
|
const convertChineseText = config()?.convertChineseCharacter;
|
||||||
|
if (convertChineseText && convertChineseText !== 'disabled') {
|
||||||
|
line = convertChineseCharacter(line, convertChineseText);
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
});
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!config()?.romanization) return;
|
if (!config()?.romanization) return;
|
||||||
|
|
||||||
const input = canonicalize(props.line);
|
const input = canonicalize(text());
|
||||||
romanize(input).then((result) => {
|
romanize(input).then((result) => {
|
||||||
setRomanization(canonicalize(result));
|
setRomanization(canonicalize(result));
|
||||||
});
|
});
|
||||||
@ -31,13 +44,13 @@ export const PlainLyrics = (props: PlainLyricsProps) => {
|
|||||||
>
|
>
|
||||||
<yt-formatted-string
|
<yt-formatted-string
|
||||||
text={{
|
text={{
|
||||||
runs: [{ text: props.line }],
|
runs: [{ text: text() }],
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Show
|
<Show
|
||||||
when={
|
when={
|
||||||
config()?.romanization &&
|
config()?.romanization &&
|
||||||
simplifyUnicode(props.line) !== simplifyUnicode(romanization())
|
simplifyUnicode(text()) !== simplifyUnicode(romanization())
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<yt-formatted-string
|
<yt-formatted-string
|
||||||
|
|||||||
@ -7,7 +7,12 @@ import { type LineLyrics } from '@/plugins/synced-lyrics/types';
|
|||||||
import { config, currentTime } from '../renderer';
|
import { config, currentTime } from '../renderer';
|
||||||
import { _ytAPI } from '..';
|
import { _ytAPI } from '..';
|
||||||
|
|
||||||
import { canonicalize, romanize, simplifyUnicode } from '../utils';
|
import {
|
||||||
|
canonicalize,
|
||||||
|
convertChineseCharacter,
|
||||||
|
romanize,
|
||||||
|
simplifyUnicode,
|
||||||
|
} from '../utils';
|
||||||
|
|
||||||
interface SyncedLineProps {
|
interface SyncedLineProps {
|
||||||
scroller: VirtualizerHandle;
|
scroller: VirtualizerHandle;
|
||||||
@ -81,7 +86,14 @@ const EmptyLine = (props: SyncedLineProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SyncedLine = (props: SyncedLineProps) => {
|
export const SyncedLine = (props: SyncedLineProps) => {
|
||||||
const text = createMemo(() => props.line.text.trim());
|
const text = createMemo(() => {
|
||||||
|
let line = props.line.text;
|
||||||
|
const convertChineseText = config()?.convertChineseCharacter;
|
||||||
|
if (convertChineseText && convertChineseText !== 'disabled') {
|
||||||
|
line = convertChineseCharacter(line, convertChineseText);
|
||||||
|
}
|
||||||
|
return line.trim();
|
||||||
|
});
|
||||||
|
|
||||||
const [romanization, setRomanization] = createSignal('');
|
const [romanization, setRomanization] = createSignal('');
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
|||||||
@ -3,10 +3,11 @@ import KuromojiAnalyzer from 'kuroshiro-analyzer-kuromoji';
|
|||||||
import Kuroshiro from 'kuroshiro';
|
import Kuroshiro from 'kuroshiro';
|
||||||
import { romanize as esHangulRomanize } from 'es-hangul';
|
import { romanize as esHangulRomanize } from 'es-hangul';
|
||||||
import hanja from 'hanja';
|
import hanja from 'hanja';
|
||||||
import * as pinyin from 'tiny-pinyin';
|
import { pinyin } from 'pinyin-pro';
|
||||||
import { romanize as romanizeThaiFrag } from '@dehoist/romanize-thai';
|
import { romanize as romanizeThaiFrag } from '@dehoist/romanize-thai';
|
||||||
import { lazy } from 'lazy-var';
|
import { lazy } from 'lazy-var';
|
||||||
import { detect } from 'tinyld';
|
import { detect } from 'tinyld';
|
||||||
|
import { sify, tify } from 'chinese-conv';
|
||||||
import Sanscript from '@indic-transliteration/sanscript';
|
import Sanscript from '@indic-transliteration/sanscript';
|
||||||
|
|
||||||
import { waitForElement } from '@/utils/wait-for-element';
|
import { waitForElement } from '@/utils/wait-for-element';
|
||||||
@ -85,6 +86,20 @@ export const canonicalize = (text: string) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const convertChineseCharacter = (
|
||||||
|
text: string,
|
||||||
|
mode: 'simplifiedToTraditional' | 'traditionalToSimplified',
|
||||||
|
) => {
|
||||||
|
if (!hasChinese([text])) return text;
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case 'simplifiedToTraditional':
|
||||||
|
return tify(text);
|
||||||
|
case 'traditionalToSimplified':
|
||||||
|
return sify(text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const simplifyUnicode = (text?: string) =>
|
export const simplifyUnicode = (text?: string) =>
|
||||||
text
|
text
|
||||||
? text
|
? text
|
||||||
@ -172,9 +187,9 @@ export const romanizeHangul = (line: string) =>
|
|||||||
esHangulRomanize(hanja.translate(line, 'SUBSTITUTION'));
|
esHangulRomanize(hanja.translate(line, 'SUBSTITUTION'));
|
||||||
|
|
||||||
export const romanizeChinese = (line: string) => {
|
export const romanizeChinese = (line: string) => {
|
||||||
return line.replaceAll(/[\u4E00-\u9FFF]+/g, (match) =>
|
return line.replaceAll(/[\u4E00-\u9FFF]+/g, (match) => {
|
||||||
pinyin.convertToPinyin(match, ' ', true),
|
return pinyin(match, { separator: ' ' });
|
||||||
);
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const thaiSegmenter = Intl.Segmenter.supportedLocalesOf('th').includes('th')
|
const thaiSegmenter = Intl.Segmenter.supportedLocalesOf('th').includes('th')
|
||||||
|
|||||||
@ -10,6 +10,10 @@ export type SyncedLyricsPluginConfig = {
|
|||||||
showLyricsEvenIfInexact: boolean;
|
showLyricsEvenIfInexact: boolean;
|
||||||
lineEffect: LineEffect;
|
lineEffect: LineEffect;
|
||||||
romanization: boolean;
|
romanization: boolean;
|
||||||
|
convertChineseCharacter?:
|
||||||
|
| 'simplifiedToTraditional'
|
||||||
|
| 'traditionalToSimplified'
|
||||||
|
| 'disabled';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LineLyricsStatus = 'previous' | 'current' | 'upcoming';
|
export type LineLyricsStatus = 'previous' | 'current' | 'upcoming';
|
||||||
|
|||||||
Reference in New Issue
Block a user