Skip to content

Commit 533b96d

Browse files
feat(synced-lyrics): multiple lyric sources (#2383)
Co-authored-by: JellyBrick <[email protected]>
1 parent 5c9ded8 commit 533b96d

File tree

28 files changed

+1527
-447
lines changed

28 files changed

+1527
-447
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@
195195
"start": "electron-vite preview",
196196
"start:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm start",
197197
"dev": "cross-env NODE_OPTIONS=--enable-source-maps electron-vite dev --watch",
198+
"dev:renderer": "cross-env NODE_OPTIONS=--enable-source-maps electron-vite dev",
198199
"dev:debug": "cross-env ELECTRON_ENABLE_LOGGING=1 pnpm dev",
199200
"clean": "del-cli dist && del-cli pack && del-cli .vite-inspect",
200201
"dist": "pnpm clean && pnpm build && pnpm electron-builder --win --mac --linux -p never",

src/i18n/resources/en.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -714,8 +714,8 @@
714714
"synced-lyrics": {
715715
"description": "Provides synced lyrics to songs, using providers like LRClib.",
716716
"errors": {
717-
"fetch": "⚠️ - An error occurred while fetching the lyrics. Please try again later.",
718-
"not-found": "⚠️ - No lyrics found for this song."
717+
"fetch": "⚠️\tAn error occurred while fetching the lyrics.\n\tPlease try again later.",
718+
"not-found": "⚠️ No lyrics found for this song."
719719
},
720720
"menu": {
721721
"default-text-string": {

src/index.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ if (config.get('options.disableHardwareAcceleration')) {
132132

133133
if (is.linux()) {
134134
// Overrides WM_CLASS for X11 to correspond to icon filename
135-
app.setName("com.github.th_ch.youtube_music");
135+
app.setName('com.github.th_ch.youtube_music');
136136

137137
// Workaround for issue #2248
138138
if (
@@ -904,9 +904,19 @@ function removeContentSecurityPolicy(
904904
betterSession.webRequest.onHeadersReceived((details, callback) => {
905905
details.responseHeaders ??= {};
906906

907-
// Remove the content security policy
908-
delete details.responseHeaders['content-security-policy-report-only'];
909-
delete details.responseHeaders['content-security-policy'];
907+
// prettier-ignore
908+
if (new URL(details.url).protocol === 'https:') {
909+
// Remove the content security policy
910+
delete details.responseHeaders['content-security-policy-report-only'];
911+
delete details.responseHeaders['Content-Security-Policy-Report-Only'];
912+
delete details.responseHeaders['content-security-policy'];
913+
delete details.responseHeaders['Content-Security-Policy'];
914+
915+
// Only allow cross-origin requests from music.youtube.com
916+
delete details.responseHeaders['access-control-allow-origin'];
917+
delete details.responseHeaders['Access-Control-Allow-Origin'];
918+
details.responseHeaders['access-control-allow-origin'] = ['https://music.youtube.com'];
919+
}
910920

911921
callback({ cancel: false, responseHeaders: details.responseHeaders });
912922
});

src/plugins/lyrics-genius/types.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ export interface Section {
1818

1919
export interface Hit {
2020
highlights: Highlight[];
21-
index: Index;
22-
type: Index;
21+
index: ResultType;
22+
type: ResultType;
2323
result: Result;
2424
}
2525

@@ -35,14 +35,10 @@ export interface Range {
3535
end: number;
3636
}
3737

38-
export enum Index {
39-
Album = 'album',
40-
Lyric = 'lyric',
41-
Song = 'song',
42-
}
38+
export type ResultType = 'song' | 'album' | 'lyric';
4339

4440
export interface Result {
45-
_type: Index;
41+
_type: ResultType;
4642
annotation_count?: number;
4743
api_path: string;
4844
artist_names?: string;

src/plugins/synced-lyrics/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default createPlugin({
2020
showTimeCodes: false,
2121
defaultTextString: '♪',
2222
lineEffect: 'scale',
23-
} satisfies SyncedLyricsPluginConfig,
23+
} as SyncedLyricsPluginConfig,
2424

2525
menu,
2626
renderer,

src/plugins/synced-lyrics/menu.ts

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,10 @@ import { t } from '@/i18n';
55
import type { MenuContext } from '@/types/contexts';
66
import type { SyncedLyricsPluginConfig } from './types';
77

8-
export const menu = async ({
9-
getConfig,
10-
setConfig,
11-
}: MenuContext<SyncedLyricsPluginConfig>): Promise<
8+
export const menu = async (ctx: MenuContext<SyncedLyricsPluginConfig>): Promise<
129
MenuItemConstructorOptions[]
1310
> => {
14-
const config = await getConfig();
11+
const config = await ctx.getConfig();
1512

1613
return [
1714
{
@@ -20,7 +17,7 @@ export const menu = async ({
2017
type: 'checkbox',
2118
checked: config.preciseTiming,
2219
click(item) {
23-
setConfig({
20+
ctx.setConfig({
2421
preciseTiming: item.checked,
2522
});
2623
},
@@ -40,7 +37,7 @@ export const menu = async ({
4037
type: 'radio',
4138
checked: config.lineEffect === 'scale',
4239
click() {
43-
setConfig({
40+
ctx.setConfig({
4441
lineEffect: 'scale',
4542
});
4643
},
@@ -55,7 +52,7 @@ export const menu = async ({
5552
type: 'radio',
5653
checked: config.lineEffect === 'offset',
5754
click() {
58-
setConfig({
55+
ctx.setConfig({
5956
lineEffect: 'offset',
6057
});
6158
},
@@ -70,7 +67,7 @@ export const menu = async ({
7067
type: 'radio',
7168
checked: config.lineEffect === 'focus',
7269
click() {
73-
setConfig({
70+
ctx.setConfig({
7471
lineEffect: 'focus',
7572
});
7673
},
@@ -87,7 +84,7 @@ export const menu = async ({
8784
type: 'radio',
8885
checked: config.defaultTextString === '♪',
8986
click() {
90-
setConfig({
87+
ctx.setConfig({
9188
defaultTextString: '♪',
9289
});
9390
},
@@ -97,7 +94,7 @@ export const menu = async ({
9794
type: 'radio',
9895
checked: config.defaultTextString === ' ',
9996
click() {
100-
setConfig({
97+
ctx.setConfig({
10198
defaultTextString: ' ',
10299
});
103100
},
@@ -107,7 +104,7 @@ export const menu = async ({
107104
type: 'radio',
108105
checked: config.defaultTextString === '...',
109106
click() {
110-
setConfig({
107+
ctx.setConfig({
111108
defaultTextString: '...',
112109
});
113110
},
@@ -117,7 +114,7 @@ export const menu = async ({
117114
type: 'radio',
118115
checked: config.defaultTextString === '———',
119116
click() {
120-
setConfig({
117+
ctx.setConfig({
121118
defaultTextString: '———',
122119
});
123120
},
@@ -130,7 +127,7 @@ export const menu = async ({
130127
type: 'checkbox',
131128
checked: config.showTimeCodes,
132129
click(item) {
133-
setConfig({
130+
ctx.setConfig({
134131
showTimeCodes: item.checked,
135132
});
136133
},
@@ -143,7 +140,7 @@ export const menu = async ({
143140
type: 'checkbox',
144141
checked: config.showLyricsEvenIfInexact,
145142
click(item) {
146-
setConfig({
143+
ctx.setConfig({
147144
showLyricsEvenIfInexact: item.checked,
148145
});
149146
},
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
interface LRCTag {
2+
tag: string;
3+
value: string;
4+
}
5+
6+
interface LRCLine {
7+
time: string;
8+
timeInMs: number;
9+
duration: number;
10+
text: string;
11+
}
12+
13+
interface LRC {
14+
tags: LRCTag[];
15+
lines: LRCLine[];
16+
}
17+
18+
const tagRegex = /^\[(?<tag>\w+):\s*(?<value>.+?)\s*\]$/;
19+
// prettier-ignore
20+
const lyricRegex = /^\[(?<minutes>\d+):(?<seconds>\d+)\.(?<milliseconds>\d+)\](?<text>.+)$/;
21+
22+
export const LRC = {
23+
parse: (text: string): LRC => {
24+
const lrc: LRC = {
25+
tags: [],
26+
lines: [],
27+
};
28+
29+
let offset = 0;
30+
let previousLine: LRCLine | null = null;
31+
32+
for (const line of text.split('\n')) {
33+
if (!line.trim().startsWith('[')) continue;
34+
35+
const lyric = line.match(lyricRegex)?.groups;
36+
if (!lyric) {
37+
const tag = line.match(tagRegex)?.groups;
38+
if (tag) {
39+
if (tag.tag === 'offset') {
40+
offset = parseInt(tag.value);
41+
continue;
42+
}
43+
44+
lrc.tags.push({
45+
tag: tag.tag,
46+
value: tag.value,
47+
});
48+
}
49+
continue;
50+
}
51+
52+
const { minutes, seconds, milliseconds, text } = lyric;
53+
const timeInMs =
54+
parseInt(minutes) * 60 * 1000 +
55+
parseInt(seconds) * 1000 +
56+
parseInt(milliseconds);
57+
58+
const currentLine: LRCLine = {
59+
time: `${minutes}:${seconds}:${milliseconds}`,
60+
timeInMs,
61+
text: text.trim(),
62+
duration: Infinity,
63+
};
64+
65+
if (previousLine) {
66+
previousLine.duration = timeInMs - previousLine.timeInMs;
67+
}
68+
69+
previousLine = currentLine;
70+
lrc.lines.push(currentLine);
71+
}
72+
73+
for (const line of lrc.lines) {
74+
line.timeInMs += offset;
75+
}
76+
77+
const first = lrc.lines.at(0);
78+
if (first && first.timeInMs > 300) {
79+
lrc.lines.unshift({
80+
time: '0:0:0',
81+
timeInMs: 0,
82+
duration: first.timeInMs,
83+
text: '',
84+
});
85+
}
86+
87+
return lrc;
88+
},
89+
};

0 commit comments

Comments
 (0)