Skip to content

Commit a151439

Browse files
committed
refactor(synced-lyrics): extract empty line handling into shared utilities
1 parent c4ddcdc commit a151439

File tree

1 file changed

+120
-0
lines changed
  • src/plugins/synced-lyrics/shared

1 file changed

+120
-0
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
export interface SyncedLineCore {
2+
text: string;
3+
timeInMs: number;
4+
duration: number;
5+
}
6+
7+
export function mergeConsecutiveEmptySyncedLines<T extends SyncedLineCore>(
8+
input: T[],
9+
): T[] {
10+
const merged: T[] = [];
11+
for (const line of input) {
12+
const isEmpty = !line.text || !line.text.trim();
13+
if (isEmpty && merged.length > 0) {
14+
const prev = merged[merged.length - 1];
15+
const prevEmpty = !prev.text || !prev.text.trim();
16+
if (prevEmpty) {
17+
// extend previous duration to cover this line
18+
const prevEnd = prev.timeInMs + prev.duration;
19+
const thisEnd = line.timeInMs + line.duration;
20+
const newEnd = Math.max(prevEnd, thisEnd);
21+
prev.duration = newEnd - prev.timeInMs;
22+
continue; // skip adding this line
23+
}
24+
}
25+
merged.push(line);
26+
}
27+
return merged;
28+
}
29+
30+
// adds a leading empty line if the first line starts after the threshold
31+
// - 'span': spans the initial silence (duration = first.timeInMs)
32+
// - 'zero': creates a zero-duration line at the start
33+
export function ensureLeadingPaddingEmptyLine<T extends SyncedLineCore>(
34+
input: T[],
35+
thresholdMs = 300,
36+
mode: 'span' | 'zero' = 'span',
37+
): T[] {
38+
if (input.length === 0) return input;
39+
const first = input[0];
40+
if (first.timeInMs <= thresholdMs) return input;
41+
42+
const leading: T = Object.assign({}, first, {
43+
timeInMs: 0,
44+
duration: mode === 'span' ? first.timeInMs : 0,
45+
text: '',
46+
});
47+
48+
// update the time string if it exists in the object
49+
if ((leading as unknown as { time?: unknown }).time !== undefined) {
50+
(leading as unknown as { time: string }).time = toLrcTime(leading.timeInMs);
51+
}
52+
53+
return [leading, ...input];
54+
}
55+
56+
// ensures a trailing empty line with two strategies:
57+
// - 'lastEnd': adds a zero-duration line at the last end time
58+
// - 'midpoint': adds a line at the midpoint between the last line and song end
59+
export function ensureTrailingEmptyLine<T extends SyncedLineCore>(
60+
input: T[],
61+
strategy: 'lastEnd' | 'midpoint',
62+
songEndMs?: number,
63+
): T[] {
64+
if (input.length === 0) return input;
65+
const out = input.slice();
66+
const last = out[out.length - 1];
67+
68+
const isLastEmpty = !last.text || !last.text.trim();
69+
if (isLastEmpty) return out; // already has an empty line at the end
70+
71+
const lastEndCandidate = Number.isFinite(last.duration)
72+
? last.timeInMs + last.duration
73+
: last.timeInMs;
74+
75+
if (strategy === 'lastEnd') {
76+
const trailing: T = Object.assign({}, last, {
77+
timeInMs: lastEndCandidate,
78+
duration: 0,
79+
text: '',
80+
});
81+
if ((trailing as unknown as { time?: unknown }).time !== undefined) {
82+
(trailing as unknown as { time: string }).time = toLrcTime(
83+
trailing.timeInMs,
84+
);
85+
}
86+
out.push(trailing);
87+
return out;
88+
}
89+
90+
// handle the midpoint strategy
91+
if (typeof songEndMs !== 'number') return out;
92+
if (lastEndCandidate >= songEndMs) return out;
93+
94+
const midpoint = Math.floor((lastEndCandidate + songEndMs) / 2);
95+
96+
// adjust the last line to end at the calculated midpoint
97+
last.duration = midpoint - last.timeInMs;
98+
99+
const trailing: T = Object.assign({}, last, {
100+
timeInMs: midpoint,
101+
duration: songEndMs - midpoint,
102+
text: '',
103+
});
104+
if ((trailing as unknown as { time?: unknown }).time !== undefined) {
105+
(trailing as unknown as { time: string }).time = toLrcTime(
106+
trailing.timeInMs,
107+
);
108+
}
109+
out.push(trailing);
110+
return out;
111+
}
112+
113+
function toLrcTime(ms: number): string {
114+
const minutes = Math.floor(ms / 60000);
115+
const seconds = Math.floor((ms % 60000) / 1000);
116+
const centiseconds = Math.floor((ms % 1000) / 10);
117+
return `${minutes.toString().padStart(2, '0')}:${seconds
118+
.toString()
119+
.padStart(2, '0')}.${centiseconds.toString().padStart(2, '0')}`;
120+
}

0 commit comments

Comments
 (0)