@applemusic-like-lyrics/lyric 0.4.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1984 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let pako = require("pako");
3
+ let _applemusic_like_lyrics_ttml = require("@applemusic-like-lyrics/ttml");
4
+ //#region src/utils.ts
5
+ const createLine = (line) => ({
6
+ words: [],
7
+ translatedLyric: "",
8
+ romanLyric: "",
9
+ isBG: false,
10
+ isDuet: false,
11
+ startTime: 0,
12
+ endTime: 0,
13
+ ...line
14
+ });
15
+ const createWord = (word) => ({
16
+ startTime: 0,
17
+ endTime: 0,
18
+ word: "",
19
+ ...word
20
+ });
21
+ const parseTime = (time) => Math.round(time.split(":").map(Number).reverse().reduce((acc, cur, idx) => acc + cur * 60 ** idx, 0) * 1e3);
22
+ const formatTime = (ms) => {
23
+ return `${Math.floor(ms / 6e4).toString().padStart(2, "0")}:${Math.floor(ms % 6e4 / 1e3).toString().padStart(2, "0")}.${Math.round(ms % 1e3).toString().padStart(3, "0")}`;
24
+ };
25
+ const normalizeTimestamp = (ms) => {
26
+ if (!Number.isFinite(ms) || ms < 0) return 0;
27
+ return ms;
28
+ };
29
+ const normalizeDuration = (duration) => {
30
+ if (!Number.isFinite(duration) || duration < 0) return 0;
31
+ return duration;
32
+ };
33
+ const MAX_LRC_TIMESTAMP = 60039999;
34
+ const clampTimestamp = (ms, max = MAX_LRC_TIMESTAMP) => Math.min(max, normalizeTimestamp(ms));
35
+ /**
36
+ * Returns consecutive pairs from the given iterable.
37
+ *
38
+ * Example: `0, 1, 2, 3` -> `[0, 1], [1, 2], [2, 3]`
39
+ */
40
+ function* pairwise(iterable) {
41
+ let prev;
42
+ let hasPrev = false;
43
+ for (const curr of iterable) {
44
+ if (hasPrev) yield [prev, curr];
45
+ prev = curr;
46
+ hasPrev = true;
47
+ }
48
+ }
49
+ //#endregion
50
+ //#region src/formats/ass.ts
51
+ function writeASSTimestamp(ms) {
52
+ const normalized = normalizeTimestamp(ms);
53
+ const milli = Math.round(normalized) % 1e3;
54
+ const secTotal = Math.floor(Math.round(normalized) / 1e3);
55
+ const sec = secTotal % 60;
56
+ const minTotal = Math.floor(secTotal / 60);
57
+ return `${Math.floor(minTotal / 60)}:${String(minTotal % 60).padStart(2, "0")}:${String(sec).padStart(2, "0")}.${String(Math.floor(milli / 10)).padStart(2, "0")}`;
58
+ }
59
+ function getSpeakerName(line) {
60
+ let name = line.isDuet ? "v2" : "v1";
61
+ if (line.isBG) name += "-bg";
62
+ return name;
63
+ }
64
+ function writeLyricDialogue(result, startTime, endTime, name, text) {
65
+ result.push(`Dialogue: 0,${writeASSTimestamp(startTime)}, ${writeASSTimestamp(endTime)}, Default, ${name},0,0,0,,${text}`);
66
+ }
67
+ /**
68
+ * 将歌词数组转换为 ASS 字幕格式字符串
69
+ * @param lines 歌词数组
70
+ * @returns ASS 字幕格式字符串
71
+ */
72
+ function stringifyAss(lines) {
73
+ const result = [
74
+ "[Script Info]",
75
+ "[Events]",
76
+ "Formats: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"
77
+ ];
78
+ for (const line of lines) {
79
+ const timedWords = line.words.map((w) => ({
80
+ ...w,
81
+ startTime: normalizeTimestamp(w.startTime),
82
+ endTime: normalizeTimestamp(w.endTime)
83
+ })).filter((w) => w.endTime > w.startTime);
84
+ const startTime = Math.min(...timedWords.map((w) => w.startTime));
85
+ const endTime = Math.max(...timedWords.map((w) => w.endTime));
86
+ if (!Number.isFinite(startTime) || !Number.isFinite(endTime)) continue;
87
+ let lyricText = "";
88
+ let previousWordEndTime = startTime;
89
+ for (const word of line.words) {
90
+ const wordStart = normalizeTimestamp(word.startTime);
91
+ const wordEnd = normalizeTimestamp(word.endTime);
92
+ if (wordStart >= wordEnd) {
93
+ lyricText += word.word;
94
+ continue;
95
+ }
96
+ if (wordStart > previousWordEndTime) {
97
+ const gapDurationCS = Math.floor((wordStart - previousWordEndTime + 5) / 10);
98
+ if (gapDurationCS > 0) lyricText += `{\\k${gapDurationCS}}`;
99
+ }
100
+ const wordDurationCS = Math.floor((wordEnd - wordStart + 5) / 10);
101
+ if (wordDurationCS > 0) lyricText += `{\\k${wordDurationCS}}`;
102
+ lyricText += word.word;
103
+ previousWordEndTime = wordEnd;
104
+ }
105
+ const speaker = getSpeakerName(line);
106
+ writeLyricDialogue(result, startTime, endTime, speaker, lyricText);
107
+ if (line.translatedLyric) writeLyricDialogue(result, startTime, endTime, `${speaker}-trans`, line.translatedLyric);
108
+ if (line.romanLyric) writeLyricDialogue(result, startTime, endTime, `${speaker}-roman`, line.romanLyric);
109
+ }
110
+ return `${result.join("\n")}\n`;
111
+ }
112
+ //#endregion
113
+ //#region src/formats/eqrc/constants.ts
114
+ /**
115
+ * @internal
116
+ * @module constants
117
+ * @description
118
+ * 包含了所有 `custom_des` 模块所需的常量数据。
119
+ */
120
+ const KEY_1 = new TextEncoder().encode("!@#)(*$%");
121
+ const KEY_2 = new TextEncoder().encode("123ZXC!@");
122
+ const KEY_3 = new TextEncoder().encode("!@#)(NHL");
123
+ const S_BOXES = [
124
+ [
125
+ 14,
126
+ 4,
127
+ 13,
128
+ 1,
129
+ 2,
130
+ 15,
131
+ 11,
132
+ 8,
133
+ 3,
134
+ 10,
135
+ 6,
136
+ 12,
137
+ 5,
138
+ 9,
139
+ 0,
140
+ 7,
141
+ 0,
142
+ 15,
143
+ 7,
144
+ 4,
145
+ 14,
146
+ 2,
147
+ 13,
148
+ 1,
149
+ 10,
150
+ 6,
151
+ 12,
152
+ 11,
153
+ 9,
154
+ 5,
155
+ 3,
156
+ 8,
157
+ 4,
158
+ 1,
159
+ 14,
160
+ 8,
161
+ 13,
162
+ 6,
163
+ 2,
164
+ 11,
165
+ 15,
166
+ 12,
167
+ 9,
168
+ 7,
169
+ 3,
170
+ 10,
171
+ 5,
172
+ 0,
173
+ 15,
174
+ 12,
175
+ 8,
176
+ 2,
177
+ 4,
178
+ 9,
179
+ 1,
180
+ 7,
181
+ 5,
182
+ 11,
183
+ 3,
184
+ 14,
185
+ 10,
186
+ 0,
187
+ 6,
188
+ 13
189
+ ],
190
+ [
191
+ 15,
192
+ 1,
193
+ 8,
194
+ 14,
195
+ 6,
196
+ 11,
197
+ 3,
198
+ 4,
199
+ 9,
200
+ 7,
201
+ 2,
202
+ 13,
203
+ 12,
204
+ 0,
205
+ 5,
206
+ 10,
207
+ 3,
208
+ 13,
209
+ 4,
210
+ 7,
211
+ 15,
212
+ 2,
213
+ 8,
214
+ 15,
215
+ 12,
216
+ 0,
217
+ 1,
218
+ 10,
219
+ 6,
220
+ 9,
221
+ 11,
222
+ 5,
223
+ 0,
224
+ 14,
225
+ 7,
226
+ 11,
227
+ 10,
228
+ 4,
229
+ 13,
230
+ 1,
231
+ 5,
232
+ 8,
233
+ 12,
234
+ 6,
235
+ 9,
236
+ 3,
237
+ 2,
238
+ 15,
239
+ 13,
240
+ 8,
241
+ 10,
242
+ 1,
243
+ 3,
244
+ 15,
245
+ 4,
246
+ 2,
247
+ 11,
248
+ 6,
249
+ 7,
250
+ 12,
251
+ 0,
252
+ 5,
253
+ 14,
254
+ 9
255
+ ],
256
+ [
257
+ 10,
258
+ 0,
259
+ 9,
260
+ 14,
261
+ 6,
262
+ 3,
263
+ 15,
264
+ 5,
265
+ 1,
266
+ 13,
267
+ 12,
268
+ 7,
269
+ 11,
270
+ 4,
271
+ 2,
272
+ 8,
273
+ 13,
274
+ 7,
275
+ 0,
276
+ 9,
277
+ 3,
278
+ 4,
279
+ 6,
280
+ 10,
281
+ 2,
282
+ 8,
283
+ 5,
284
+ 14,
285
+ 12,
286
+ 11,
287
+ 15,
288
+ 1,
289
+ 13,
290
+ 6,
291
+ 4,
292
+ 9,
293
+ 8,
294
+ 15,
295
+ 3,
296
+ 0,
297
+ 11,
298
+ 1,
299
+ 2,
300
+ 12,
301
+ 5,
302
+ 10,
303
+ 14,
304
+ 7,
305
+ 1,
306
+ 10,
307
+ 13,
308
+ 0,
309
+ 6,
310
+ 9,
311
+ 8,
312
+ 7,
313
+ 4,
314
+ 15,
315
+ 14,
316
+ 3,
317
+ 11,
318
+ 5,
319
+ 2,
320
+ 12
321
+ ],
322
+ [
323
+ 7,
324
+ 13,
325
+ 14,
326
+ 3,
327
+ 0,
328
+ 6,
329
+ 9,
330
+ 10,
331
+ 1,
332
+ 2,
333
+ 8,
334
+ 5,
335
+ 11,
336
+ 12,
337
+ 4,
338
+ 15,
339
+ 13,
340
+ 8,
341
+ 11,
342
+ 5,
343
+ 6,
344
+ 15,
345
+ 0,
346
+ 3,
347
+ 4,
348
+ 7,
349
+ 2,
350
+ 12,
351
+ 1,
352
+ 10,
353
+ 14,
354
+ 9,
355
+ 10,
356
+ 6,
357
+ 9,
358
+ 0,
359
+ 12,
360
+ 11,
361
+ 7,
362
+ 13,
363
+ 15,
364
+ 1,
365
+ 3,
366
+ 14,
367
+ 5,
368
+ 2,
369
+ 8,
370
+ 4,
371
+ 3,
372
+ 15,
373
+ 0,
374
+ 6,
375
+ 10,
376
+ 10,
377
+ 13,
378
+ 8,
379
+ 9,
380
+ 4,
381
+ 5,
382
+ 11,
383
+ 12,
384
+ 7,
385
+ 2,
386
+ 14
387
+ ],
388
+ [
389
+ 2,
390
+ 12,
391
+ 4,
392
+ 1,
393
+ 7,
394
+ 10,
395
+ 11,
396
+ 6,
397
+ 8,
398
+ 5,
399
+ 3,
400
+ 15,
401
+ 13,
402
+ 0,
403
+ 14,
404
+ 9,
405
+ 14,
406
+ 11,
407
+ 2,
408
+ 12,
409
+ 4,
410
+ 7,
411
+ 13,
412
+ 1,
413
+ 5,
414
+ 0,
415
+ 15,
416
+ 10,
417
+ 3,
418
+ 9,
419
+ 8,
420
+ 6,
421
+ 4,
422
+ 2,
423
+ 1,
424
+ 11,
425
+ 10,
426
+ 13,
427
+ 7,
428
+ 8,
429
+ 15,
430
+ 9,
431
+ 12,
432
+ 5,
433
+ 6,
434
+ 3,
435
+ 0,
436
+ 14,
437
+ 11,
438
+ 8,
439
+ 12,
440
+ 7,
441
+ 1,
442
+ 14,
443
+ 2,
444
+ 13,
445
+ 6,
446
+ 15,
447
+ 0,
448
+ 9,
449
+ 10,
450
+ 4,
451
+ 5,
452
+ 3
453
+ ],
454
+ [
455
+ 12,
456
+ 1,
457
+ 10,
458
+ 15,
459
+ 9,
460
+ 2,
461
+ 6,
462
+ 8,
463
+ 0,
464
+ 13,
465
+ 3,
466
+ 4,
467
+ 14,
468
+ 7,
469
+ 5,
470
+ 11,
471
+ 10,
472
+ 15,
473
+ 4,
474
+ 2,
475
+ 7,
476
+ 12,
477
+ 9,
478
+ 5,
479
+ 6,
480
+ 1,
481
+ 13,
482
+ 14,
483
+ 0,
484
+ 11,
485
+ 3,
486
+ 8,
487
+ 9,
488
+ 14,
489
+ 15,
490
+ 5,
491
+ 2,
492
+ 8,
493
+ 12,
494
+ 3,
495
+ 7,
496
+ 0,
497
+ 4,
498
+ 10,
499
+ 1,
500
+ 13,
501
+ 11,
502
+ 6,
503
+ 4,
504
+ 3,
505
+ 2,
506
+ 12,
507
+ 9,
508
+ 5,
509
+ 15,
510
+ 10,
511
+ 11,
512
+ 14,
513
+ 1,
514
+ 7,
515
+ 6,
516
+ 0,
517
+ 8,
518
+ 13
519
+ ],
520
+ [
521
+ 4,
522
+ 11,
523
+ 2,
524
+ 14,
525
+ 15,
526
+ 0,
527
+ 8,
528
+ 13,
529
+ 3,
530
+ 12,
531
+ 9,
532
+ 7,
533
+ 5,
534
+ 10,
535
+ 6,
536
+ 1,
537
+ 13,
538
+ 0,
539
+ 11,
540
+ 7,
541
+ 4,
542
+ 9,
543
+ 1,
544
+ 10,
545
+ 14,
546
+ 3,
547
+ 5,
548
+ 12,
549
+ 2,
550
+ 15,
551
+ 8,
552
+ 6,
553
+ 1,
554
+ 4,
555
+ 11,
556
+ 13,
557
+ 12,
558
+ 3,
559
+ 7,
560
+ 14,
561
+ 10,
562
+ 15,
563
+ 6,
564
+ 8,
565
+ 0,
566
+ 5,
567
+ 9,
568
+ 2,
569
+ 6,
570
+ 11,
571
+ 13,
572
+ 8,
573
+ 1,
574
+ 4,
575
+ 10,
576
+ 7,
577
+ 9,
578
+ 5,
579
+ 0,
580
+ 15,
581
+ 14,
582
+ 2,
583
+ 3,
584
+ 12
585
+ ],
586
+ [
587
+ 13,
588
+ 2,
589
+ 8,
590
+ 4,
591
+ 6,
592
+ 15,
593
+ 11,
594
+ 1,
595
+ 10,
596
+ 9,
597
+ 3,
598
+ 14,
599
+ 5,
600
+ 0,
601
+ 12,
602
+ 7,
603
+ 1,
604
+ 15,
605
+ 13,
606
+ 8,
607
+ 10,
608
+ 3,
609
+ 7,
610
+ 4,
611
+ 12,
612
+ 5,
613
+ 6,
614
+ 11,
615
+ 0,
616
+ 14,
617
+ 9,
618
+ 2,
619
+ 7,
620
+ 11,
621
+ 4,
622
+ 1,
623
+ 9,
624
+ 12,
625
+ 14,
626
+ 2,
627
+ 0,
628
+ 6,
629
+ 10,
630
+ 13,
631
+ 15,
632
+ 3,
633
+ 5,
634
+ 8,
635
+ 2,
636
+ 1,
637
+ 14,
638
+ 7,
639
+ 4,
640
+ 10,
641
+ 8,
642
+ 13,
643
+ 15,
644
+ 12,
645
+ 9,
646
+ 0,
647
+ 3,
648
+ 5,
649
+ 6,
650
+ 11
651
+ ]
652
+ ];
653
+ const P_BOX = [
654
+ 16,
655
+ 7,
656
+ 20,
657
+ 21,
658
+ 29,
659
+ 12,
660
+ 28,
661
+ 17,
662
+ 1,
663
+ 15,
664
+ 23,
665
+ 26,
666
+ 5,
667
+ 18,
668
+ 31,
669
+ 10,
670
+ 2,
671
+ 8,
672
+ 24,
673
+ 14,
674
+ 32,
675
+ 27,
676
+ 3,
677
+ 9,
678
+ 19,
679
+ 13,
680
+ 30,
681
+ 6,
682
+ 22,
683
+ 11,
684
+ 4,
685
+ 25
686
+ ];
687
+ const E_BOX_TABLE = [
688
+ 32,
689
+ 1,
690
+ 2,
691
+ 3,
692
+ 4,
693
+ 5,
694
+ 4,
695
+ 5,
696
+ 6,
697
+ 7,
698
+ 8,
699
+ 9,
700
+ 8,
701
+ 9,
702
+ 10,
703
+ 11,
704
+ 12,
705
+ 13,
706
+ 12,
707
+ 13,
708
+ 14,
709
+ 15,
710
+ 16,
711
+ 17,
712
+ 16,
713
+ 17,
714
+ 18,
715
+ 19,
716
+ 20,
717
+ 21,
718
+ 20,
719
+ 21,
720
+ 22,
721
+ 23,
722
+ 24,
723
+ 25,
724
+ 24,
725
+ 25,
726
+ 26,
727
+ 27,
728
+ 28,
729
+ 29,
730
+ 28,
731
+ 29,
732
+ 30,
733
+ 31,
734
+ 32,
735
+ 1
736
+ ];
737
+ const KEY_RND_SHIFT = [
738
+ 1,
739
+ 1,
740
+ 2,
741
+ 2,
742
+ 2,
743
+ 2,
744
+ 2,
745
+ 2,
746
+ 1,
747
+ 2,
748
+ 2,
749
+ 2,
750
+ 2,
751
+ 2,
752
+ 2,
753
+ 1
754
+ ];
755
+ const KEY_PERM_C = [
756
+ 56,
757
+ 48,
758
+ 40,
759
+ 32,
760
+ 24,
761
+ 16,
762
+ 8,
763
+ 0,
764
+ 57,
765
+ 49,
766
+ 41,
767
+ 33,
768
+ 25,
769
+ 17,
770
+ 9,
771
+ 1,
772
+ 58,
773
+ 50,
774
+ 42,
775
+ 34,
776
+ 26,
777
+ 18,
778
+ 10,
779
+ 2,
780
+ 59,
781
+ 51,
782
+ 43,
783
+ 35
784
+ ];
785
+ const KEY_PERM_D = [
786
+ 62,
787
+ 54,
788
+ 46,
789
+ 38,
790
+ 30,
791
+ 22,
792
+ 14,
793
+ 6,
794
+ 61,
795
+ 53,
796
+ 45,
797
+ 37,
798
+ 29,
799
+ 21,
800
+ 13,
801
+ 5,
802
+ 60,
803
+ 52,
804
+ 44,
805
+ 36,
806
+ 28,
807
+ 20,
808
+ 12,
809
+ 4,
810
+ 27,
811
+ 19,
812
+ 11,
813
+ 3
814
+ ];
815
+ const KEY_COMPRESSION = [
816
+ 13,
817
+ 16,
818
+ 10,
819
+ 23,
820
+ 0,
821
+ 4,
822
+ 2,
823
+ 27,
824
+ 14,
825
+ 5,
826
+ 20,
827
+ 9,
828
+ 22,
829
+ 18,
830
+ 11,
831
+ 3,
832
+ 25,
833
+ 7,
834
+ 15,
835
+ 6,
836
+ 26,
837
+ 19,
838
+ 12,
839
+ 1,
840
+ 40,
841
+ 51,
842
+ 30,
843
+ 36,
844
+ 46,
845
+ 54,
846
+ 29,
847
+ 39,
848
+ 50,
849
+ 44,
850
+ 32,
851
+ 47,
852
+ 43,
853
+ 48,
854
+ 38,
855
+ 55,
856
+ 33,
857
+ 52,
858
+ 45,
859
+ 41,
860
+ 49,
861
+ 35,
862
+ 28,
863
+ 31
864
+ ];
865
+ //#endregion
866
+ //#region src/formats/eqrc/custom-des.ts
867
+ /**
868
+ * @internal
869
+ * @module custom_des
870
+ * @description
871
+ * 本模块包含了为解密 QRC 歌词而移植的、非标准的类 DES 算法的底层实现。
872
+ *
873
+ * <h2>
874
+ * <strong>警告:该 DES 实现并非标准实现!</strong>
875
+ * </h2>
876
+ *
877
+ * 它是结构类似DES的、但完全私有的分组密码算法。
878
+ * 本实现仅用于 QRC 歌词解密,不应用于实际安全目的。
879
+ */
880
+ /**
881
+ * 从8字节密钥中根据置换表提取位,生成一个 BigInt。
882
+ *
883
+ * 这个函数对应原始C代码中的天书BITNUM宏,模拟 QQ 音乐特有的非标准的字节序处理方式。
884
+ * 其将 8 字节密钥视为两个独立的、小端序的32位整数拼接而成。
885
+ *
886
+ * 例如,要读取第0位(MSB),它实际访问的是 `key[3]` 的最高位。
887
+ * 要读取第31位,它访问的是 `key[0]` 的最低位。
888
+ *
889
+ * @param key 8字节的密钥 Uint8Array
890
+ * @param table 0-based 的位索引置换表
891
+ */
892
+ function permuteFromKeyBytes(key, table) {
893
+ let output = 0n;
894
+ let currentBitMask = 1n << BigInt(table.length - 1);
895
+ for (let i = 0; i < table.length; i++) {
896
+ const pos = table[i];
897
+ const wordIndex = pos >> 5;
898
+ const bitInWord = pos & 31;
899
+ const byteInWord = bitInWord >> 3;
900
+ const bitInByte = bitInWord & 7;
901
+ if (key[wordIndex * 4 + 3 - byteInWord] >> 7 - bitInByte & 1) output |= currentBitMask;
902
+ currentBitMask >>= 1n;
903
+ }
904
+ return output;
905
+ }
906
+ /**
907
+ * 对一个存储在 BigInt 中的28位密钥部分进行循环左移。
908
+ * @param value 包含28位数据的高位的 BigInt
909
+ * @param amount 左移的位数
910
+ */
911
+ function rotateLeft28Bit(value, amount) {
912
+ const BITS_28_MASK = 4294967280n;
913
+ const val = value & BITS_28_MASK;
914
+ return (val << BigInt(amount) | val >> BigInt(28 - amount)) & BITS_28_MASK;
915
+ }
916
+ /**
917
+ * DES 密钥调度算法。
918
+ * 从一个64位的主密钥(实际使用56位,每字节的最低位是奇偶校验位,被忽略)
919
+ * 生成16个48位的轮密钥。
920
+ *
921
+ * @param key 8字节的DES密钥
922
+ * @param mode 加密或解密模式
923
+ */
924
+ function keySchedule(key, mode) {
925
+ const schedule = new Int32Array(32);
926
+ const c0 = permuteFromKeyBytes(key, KEY_PERM_C);
927
+ const d0 = permuteFromKeyBytes(key, KEY_PERM_D);
928
+ let c = c0 << 4n;
929
+ let d = d0 << 4n;
930
+ for (let i = 0; i < 16; i++) {
931
+ const shift = KEY_RND_SHIFT[i];
932
+ c = rotateLeft28Bit(c, shift);
933
+ d = rotateLeft28Bit(d, shift);
934
+ const toGen = mode === 1 ? 15 - i : i;
935
+ let subkey48bit = 0n;
936
+ for (let k = 0; k < KEY_COMPRESSION.length; k++) {
937
+ const pos = KEY_COMPRESSION[k];
938
+ if ((pos < 28 ? c >> BigInt(31 - pos) & 1n : d >> BigInt(31 - (pos - 27)) & 1n) === 1n) subkey48bit |= 1n << BigInt(47 - k);
939
+ }
940
+ const b5 = Number(subkey48bit >> 40n & 255n);
941
+ const b4 = Number(subkey48bit >> 32n & 255n);
942
+ const b3 = Number(subkey48bit >> 24n & 255n);
943
+ const high24 = b5 << 16 | b4 << 8 | b3;
944
+ const b2 = Number(subkey48bit >> 16n & 255n);
945
+ const b1 = Number(subkey48bit >> 8n & 255n);
946
+ const b0 = Number(subkey48bit & 255n);
947
+ const low24 = b2 << 16 | b1 << 8 | b0;
948
+ schedule[toGen * 2] = high24;
949
+ schedule[toGen * 2 + 1] = low24;
950
+ }
951
+ return schedule;
952
+ }
953
+ const IP_RULE = [
954
+ 34,
955
+ 42,
956
+ 50,
957
+ 58,
958
+ 2,
959
+ 10,
960
+ 18,
961
+ 26,
962
+ 36,
963
+ 44,
964
+ 52,
965
+ 60,
966
+ 4,
967
+ 12,
968
+ 20,
969
+ 28,
970
+ 38,
971
+ 46,
972
+ 54,
973
+ 62,
974
+ 6,
975
+ 14,
976
+ 22,
977
+ 30,
978
+ 40,
979
+ 48,
980
+ 56,
981
+ 64,
982
+ 8,
983
+ 16,
984
+ 24,
985
+ 32,
986
+ 33,
987
+ 41,
988
+ 49,
989
+ 57,
990
+ 1,
991
+ 9,
992
+ 17,
993
+ 25,
994
+ 35,
995
+ 43,
996
+ 51,
997
+ 59,
998
+ 3,
999
+ 11,
1000
+ 19,
1001
+ 27,
1002
+ 37,
1003
+ 45,
1004
+ 53,
1005
+ 61,
1006
+ 5,
1007
+ 13,
1008
+ 21,
1009
+ 29,
1010
+ 39,
1011
+ 47,
1012
+ 55,
1013
+ 63,
1014
+ 7,
1015
+ 15,
1016
+ 23,
1017
+ 31
1018
+ ];
1019
+ const INV_IP_RULE = [
1020
+ 37,
1021
+ 5,
1022
+ 45,
1023
+ 13,
1024
+ 53,
1025
+ 21,
1026
+ 61,
1027
+ 29,
1028
+ 38,
1029
+ 6,
1030
+ 46,
1031
+ 14,
1032
+ 54,
1033
+ 22,
1034
+ 62,
1035
+ 30,
1036
+ 39,
1037
+ 7,
1038
+ 47,
1039
+ 15,
1040
+ 55,
1041
+ 23,
1042
+ 63,
1043
+ 31,
1044
+ 40,
1045
+ 8,
1046
+ 48,
1047
+ 16,
1048
+ 56,
1049
+ 24,
1050
+ 64,
1051
+ 32,
1052
+ 33,
1053
+ 1,
1054
+ 41,
1055
+ 9,
1056
+ 49,
1057
+ 17,
1058
+ 57,
1059
+ 25,
1060
+ 34,
1061
+ 2,
1062
+ 42,
1063
+ 10,
1064
+ 50,
1065
+ 18,
1066
+ 58,
1067
+ 26,
1068
+ 35,
1069
+ 3,
1070
+ 43,
1071
+ 11,
1072
+ 51,
1073
+ 19,
1074
+ 59,
1075
+ 27,
1076
+ 36,
1077
+ 4,
1078
+ 44,
1079
+ 12,
1080
+ 52,
1081
+ 20,
1082
+ 60,
1083
+ 28
1084
+ ];
1085
+ const IP_LEFT_TABLE = new Int32Array(2048);
1086
+ const IP_RIGHT_TABLE = new Int32Array(2048);
1087
+ const INV_IP_LEFT_TABLE = new Int32Array(2048);
1088
+ const INV_IP_RIGHT_TABLE = new Int32Array(2048);
1089
+ function generatePermutationTables() {
1090
+ const applyPermutation = (input, rule) => {
1091
+ let output = 0n;
1092
+ for (let i = 0; i < 64; i++) {
1093
+ const srcBit1Based = rule[i];
1094
+ if (input >> BigInt(64 - srcBit1Based) & 1n) output |= 1n << BigInt(63 - i);
1095
+ }
1096
+ return output;
1097
+ };
1098
+ for (let bytePos = 0; bytePos < 8; bytePos++) for (let byteVal = 0; byteVal < 256; byteVal++) {
1099
+ const permuted = applyPermutation(BigInt(byteVal) << BigInt(56 - bytePos * 8), IP_RULE);
1100
+ const idx = bytePos << 8 | byteVal;
1101
+ IP_LEFT_TABLE[idx] = Number(permuted >> 32n & 4294967295n);
1102
+ IP_RIGHT_TABLE[idx] = Number(permuted & 4294967295n);
1103
+ }
1104
+ for (let blockPos = 0; blockPos < 8; blockPos++) for (let blockVal = 0; blockVal < 256; blockVal++) {
1105
+ const permuted = applyPermutation(BigInt(blockVal) << BigInt(56 - blockPos * 8), INV_IP_RULE);
1106
+ const idx = blockPos << 8 | blockVal;
1107
+ INV_IP_LEFT_TABLE[idx] = Number(permuted >> 32n & 4294967295n);
1108
+ INV_IP_RIGHT_TABLE[idx] = Number(permuted & 4294967295n);
1109
+ }
1110
+ }
1111
+ generatePermutationTables();
1112
+ /**
1113
+ * 计算 DES S-盒的查找索引。
1114
+ * @param a 一个包含6位数据的 u8
1115
+ */
1116
+ function calculateSboxIndex(a) {
1117
+ return a & 32 | (a & 31) >> 1 | (a & 1) << 4;
1118
+ }
1119
+ /**
1120
+ * 对一个 32 位整数应用非标准的 P 盒置换规则。
1121
+ * @param input S-盒代换后的 32 位中间结果
1122
+ */
1123
+ function applyQqPboxPermutation(input) {
1124
+ let output = 0;
1125
+ for (let i = 0; i < 32; i++) {
1126
+ const sourceBit1Based = P_BOX[i];
1127
+ const destBitMask = 1 << 31 - i;
1128
+ if ((input & 1 << 32 - sourceBit1Based) !== 0) output |= destBitMask;
1129
+ }
1130
+ return output;
1131
+ }
1132
+ const SP_TABLE = new Int32Array(512);
1133
+ /**
1134
+ * 生成 S-P 盒合并查找表以提高性能。
1135
+ */
1136
+ function generateSpTables() {
1137
+ for (let sBoxIdx = 0; sBoxIdx < 8; sBoxIdx++) for (let sBoxInput = 0; sBoxInput < 64; sBoxInput++) {
1138
+ const sBoxIndex = calculateSboxIndex(sBoxInput);
1139
+ const prePBoxVal = S_BOXES[sBoxIdx][sBoxIndex] << 28 - sBoxIdx * 4;
1140
+ SP_TABLE[sBoxIdx << 6 | sBoxInput] = applyQqPboxPermutation(prePBoxVal);
1141
+ }
1142
+ }
1143
+ generateSpTables();
1144
+ const EBOX_HIGH_TABLE = new Int32Array(1024);
1145
+ const EBOX_LOW_TABLE = new Int32Array(1024);
1146
+ function generateEBoxTables() {
1147
+ for (let chunkIdx = 0; chunkIdx < 4; chunkIdx++) {
1148
+ const shiftIn32 = (3 - chunkIdx) * 8;
1149
+ for (let byteVal = 0; byteVal < 256; byteVal++) {
1150
+ let high24 = 0;
1151
+ let low24 = 0;
1152
+ const input = byteVal << shiftIn32;
1153
+ for (let i = 0; i < 24; i++) if (input >>> 32 - E_BOX_TABLE[i] & 1) high24 |= 1 << 23 - i;
1154
+ for (let i = 24; i < 48; i++) if (input >>> 32 - E_BOX_TABLE[i] & 1) low24 |= 1 << 47 - i;
1155
+ const tableIdx = chunkIdx << 8 | byteVal;
1156
+ EBOX_HIGH_TABLE[tableIdx] = high24;
1157
+ EBOX_LOW_TABLE[tableIdx] = low24;
1158
+ }
1159
+ }
1160
+ }
1161
+ generateEBoxTables();
1162
+ /**
1163
+ * DES 的 F 函数。
1164
+ */
1165
+ function fFunction(state, keyHigh24, keyLow24) {
1166
+ const b0 = state >>> 24 & 255;
1167
+ const b1 = state >>> 16 & 255;
1168
+ const b2 = state >>> 8 & 255;
1169
+ const b3 = state & 255;
1170
+ const eboxHigh24 = EBOX_HIGH_TABLE[b0] | EBOX_HIGH_TABLE[256 | b1] | EBOX_HIGH_TABLE[512 | b2] | EBOX_HIGH_TABLE[768 | b3];
1171
+ const eboxLow24 = EBOX_LOW_TABLE[b0] | EBOX_LOW_TABLE[256 | b1] | EBOX_LOW_TABLE[512 | b2] | EBOX_LOW_TABLE[768 | b3];
1172
+ const xorHigh24 = eboxHigh24 ^ keyHigh24;
1173
+ const xorLow24 = eboxLow24 ^ keyLow24;
1174
+ return SP_TABLE[xorHigh24 >>> 18 & 63] | SP_TABLE[64 | xorHigh24 >>> 12 & 63] | SP_TABLE[128 | xorHigh24 >>> 6 & 63] | SP_TABLE[192 | xorHigh24 & 63] | SP_TABLE[256 | xorLow24 >>> 18 & 63] | SP_TABLE[320 | xorLow24 >>> 12 & 63] | SP_TABLE[384 | xorLow24 >>> 6 & 63] | SP_TABLE[448 | xorLow24 & 63];
1175
+ }
1176
+ /**
1177
+ * DES 加密/解密单个64位数据块。
1178
+ *
1179
+ * @param input 8字节的输入数据块 (明文或密文)。
1180
+ * @param output 8字节的可变切片,用于存储输出数据块 (密文或明文)。
1181
+ * @param keySchedule 一个包含16个轮密钥的向量的引用,每个轮密钥是6字节。
1182
+ */
1183
+ function desCrypt(input, output, keySchedule) {
1184
+ let left = 0;
1185
+ let right = 0;
1186
+ for (let i = 0; i < 8; i++) {
1187
+ const idx = i << 8 | input[i];
1188
+ left |= IP_LEFT_TABLE[idx];
1189
+ right |= IP_RIGHT_TABLE[idx];
1190
+ }
1191
+ for (let i = 0; i < 15; i++) {
1192
+ const temp = right;
1193
+ right = (left ^ fFunction(right, keySchedule[i * 2], keySchedule[i * 2 + 1])) >>> 0;
1194
+ left = temp;
1195
+ }
1196
+ left = (left ^ fFunction(right, keySchedule[30], keySchedule[31])) >>> 0;
1197
+ let outLeft = 0;
1198
+ let outRight = 0;
1199
+ for (let i = 0; i < 4; i++) {
1200
+ const idxL = i << 8 | left >>> 24 - i * 8 & 255;
1201
+ outLeft |= INV_IP_LEFT_TABLE[idxL];
1202
+ outRight |= INV_IP_RIGHT_TABLE[idxL];
1203
+ const idxR = i + 4 << 8 | right >>> 24 - i * 8 & 255;
1204
+ outLeft |= INV_IP_LEFT_TABLE[idxR];
1205
+ outRight |= INV_IP_RIGHT_TABLE[idxR];
1206
+ }
1207
+ output[0] = outLeft >>> 24 & 255;
1208
+ output[1] = outLeft >>> 16 & 255;
1209
+ output[2] = outLeft >>> 8 & 255;
1210
+ output[3] = outLeft & 255;
1211
+ output[4] = outRight >>> 24 & 255;
1212
+ output[5] = outRight >>> 16 & 255;
1213
+ output[6] = outRight >>> 8 & 255;
1214
+ output[7] = outRight & 255;
1215
+ }
1216
+ //#endregion
1217
+ //#region src/formats/eqrc/utils.ts
1218
+ /**
1219
+ * @module utils
1220
+ * @description
1221
+ *
1222
+ * 包含一些工具函数。
1223
+ */
1224
+ /**
1225
+ * 将十六进制字符串转换为 Uint8Array。
1226
+ */
1227
+ function hexToUint8Array(hex) {
1228
+ if (typeof Buffer !== "undefined") return Buffer.from(hex, "hex");
1229
+ if (hex.length % 2 !== 0) throw new Error("无效的十六进制字符串: 长度必须是偶数");
1230
+ const bytes = new Uint8Array(hex.length / 2);
1231
+ for (let i = 0; i < hex.length; i += 2) bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
1232
+ return bytes;
1233
+ }
1234
+ /**
1235
+ * 将 Uint8Array 转换为十六进制字符串。
1236
+ */
1237
+ function uint8ArrayToHex(bytes) {
1238
+ if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("hex");
1239
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1240
+ }
1241
+ //#endregion
1242
+ //#region src/formats/eqrc/index.ts
1243
+ /**
1244
+ * @module qrc-codec
1245
+ * @description
1246
+ * 此模块是加密与解密 QRC 歌词的核心。
1247
+ * 提供了两个主要的公共函数:`decryptQrc` 和 `encryptQrc`。
1248
+ *
1249
+ * 非标准 3DES 算法实现由 `custom_des` 模块提供。
1250
+ *
1251
+ * 迁移自 https://github.com/apoint123/qrc-decoder
1252
+ */
1253
+ const DES_BLOCK_SIZE = 8;
1254
+ /**
1255
+ * 非标准 3DES 编解码器
1256
+ */
1257
+ var QqMusicCodec = class {
1258
+ encryptSchedule;
1259
+ decryptSchedule;
1260
+ constructor() {
1261
+ this.decryptSchedule = [
1262
+ keySchedule(KEY_3, 1),
1263
+ keySchedule(KEY_2, 0),
1264
+ keySchedule(KEY_1, 1)
1265
+ ];
1266
+ this.encryptSchedule = [
1267
+ keySchedule(KEY_1, 0),
1268
+ keySchedule(KEY_2, 1),
1269
+ keySchedule(KEY_3, 0)
1270
+ ];
1271
+ }
1272
+ /**
1273
+ * 解密一个8字节的数据块。
1274
+ */
1275
+ decryptBlock(input, output) {
1276
+ const temp1 = new Uint8Array(8);
1277
+ const temp2 = new Uint8Array(8);
1278
+ desCrypt(input, temp1, this.decryptSchedule[0]);
1279
+ desCrypt(temp1, temp2, this.decryptSchedule[1]);
1280
+ desCrypt(temp2, output, this.decryptSchedule[2]);
1281
+ }
1282
+ /**
1283
+ * 加密一个8字节的数据块。
1284
+ */
1285
+ encryptBlock(input, output) {
1286
+ const temp1 = new Uint8Array(8);
1287
+ const temp2 = new Uint8Array(8);
1288
+ desCrypt(input, temp1, this.encryptSchedule[0]);
1289
+ desCrypt(temp1, temp2, this.encryptSchedule[1]);
1290
+ desCrypt(temp2, output, this.encryptSchedule[2]);
1291
+ }
1292
+ };
1293
+ const CODEC = new QqMusicCodec();
1294
+ /**
1295
+ * 使用零字节对数据进行填充。
1296
+ *
1297
+ * QQ音乐使用的填充方案是零填充。
1298
+ * @param data 需要填充的字节数据
1299
+ * @param blockSize 块大小,对于DES来说是8
1300
+ */
1301
+ function zeroPad(data, blockSize) {
1302
+ const paddingLen = (blockSize - data.length % blockSize) % blockSize;
1303
+ if (paddingLen === 0) return data;
1304
+ const paddedData = new Uint8Array(data.length + paddingLen);
1305
+ paddedData.set(data, 0);
1306
+ return paddedData;
1307
+ }
1308
+ /**
1309
+ * 使用 Zlib 解压缩字节数据。
1310
+ * 同时会尝试移除头部的 UTF-8 BOM (0xEF 0xBB 0xBF)。
1311
+ */
1312
+ function decompress(data) {
1313
+ const decompressed = (0, pako.inflate)(data);
1314
+ if (decompressed.length >= 3 && decompressed[0] === 239 && decompressed[1] === 187 && decompressed[2] === 191) return decompressed.slice(3);
1315
+ return decompressed;
1316
+ }
1317
+ /**
1318
+ * 解密十六进制字符串格式的 Qrc 歌词数据
1319
+ * 解密后可去头尾 XML 数据后通过调用 `parseQrc` 解析歌词行
1320
+ * @param encryptedHexString 十六进制格式的字符串,代表被加密的歌词数据
1321
+ * @returns 被解密出来的歌词字符串,是前后有 XML 混合的 QRC 歌词
1322
+ */
1323
+ function decryptQrcHex(encryptedHexString) {
1324
+ const encryptedBytes = hexToUint8Array(encryptedHexString);
1325
+ if (encryptedBytes.length % DES_BLOCK_SIZE !== 0) throw new Error(`加密数据长度不是${DES_BLOCK_SIZE}的倍数`);
1326
+ const decryptedData = new Uint8Array(encryptedBytes.length);
1327
+ for (let i = 0; i < encryptedBytes.length; i += DES_BLOCK_SIZE) {
1328
+ const chunk = encryptedBytes.subarray(i, i + DES_BLOCK_SIZE);
1329
+ const outChunk = decryptedData.subarray(i, i + DES_BLOCK_SIZE);
1330
+ CODEC.decryptBlock(chunk, outChunk);
1331
+ }
1332
+ const decompressedBytes = decompress(decryptedData);
1333
+ return new TextDecoder("utf-8").decode(decompressedBytes);
1334
+ }
1335
+ /**
1336
+ * 对明文执行加密操作。
1337
+ * @param plaintext 明文字符串
1338
+ * @returns 十六进制格式的字符串,代表被加密的歌词数据
1339
+ */
1340
+ function encryptQrcHex(plaintext) {
1341
+ const paddedData = zeroPad((0, pako.deflate)(new TextEncoder().encode(plaintext)), DES_BLOCK_SIZE);
1342
+ const encryptedData = new Uint8Array(paddedData.length);
1343
+ for (let i = 0; i < paddedData.length; i += DES_BLOCK_SIZE) {
1344
+ const chunk = paddedData.subarray(i, i + DES_BLOCK_SIZE);
1345
+ const outChunk = encryptedData.subarray(i, i + DES_BLOCK_SIZE);
1346
+ CODEC.encryptBlock(chunk, outChunk);
1347
+ }
1348
+ return uint8ArrayToHex(encryptedData);
1349
+ }
1350
+ //#endregion
1351
+ //#region src/formats/eslrc.ts
1352
+ const TIME_REGEX = /^\[((?:\d+:)*\d+(?:\.\d+)?)\]/;
1353
+ function parseTimestampPrefix(src) {
1354
+ const match = src.match(TIME_REGEX);
1355
+ if (!match) return null;
1356
+ const [raw, timeStr] = match;
1357
+ return {
1358
+ time: parseTime(timeStr),
1359
+ length: raw.length
1360
+ };
1361
+ }
1362
+ function parseEslrcLine(rawLine) {
1363
+ let src = rawLine.trim();
1364
+ const first = parseTimestampPrefix(src);
1365
+ if (!first) return null;
1366
+ src = src.slice(first.length);
1367
+ let startTime = first.time;
1368
+ if (!src.trim()) return null;
1369
+ const words = [];
1370
+ while (src.trim().length > 0) {
1371
+ const nextTimePos = src.indexOf("[");
1372
+ if (nextTimePos <= 0) return null;
1373
+ const word = src.slice(0, nextTimePos);
1374
+ const nextTime = parseTimestampPrefix(src.slice(nextTimePos));
1375
+ if (!nextTime) return null;
1376
+ words.push(createWord({
1377
+ word,
1378
+ startTime,
1379
+ endTime: nextTime.time
1380
+ }));
1381
+ src = src.slice(nextTimePos + nextTime.length);
1382
+ startTime = nextTime.time;
1383
+ }
1384
+ return createLine({ words });
1385
+ }
1386
+ /**
1387
+ * 解析 ESLyric 逐词歌词格式字符串
1388
+ * @param eslrc 歌词字符串
1389
+ * @returns 成功解析出来的歌词
1390
+ */
1391
+ function parseEslrc(eslrc) {
1392
+ const result = [];
1393
+ for (const rawLine of eslrc.split(/\r?\n/)) {
1394
+ const line = parseEslrcLine(rawLine);
1395
+ if (line) result.push(line);
1396
+ }
1397
+ result.sort((a, b) => (a.words[0]?.startTime ?? Number.MAX_SAFE_INTEGER) - (b.words[0]?.startTime ?? Number.MAX_SAFE_INTEGER));
1398
+ for (const line of result) {
1399
+ for (const word of line.words) {
1400
+ word.startTime = clampTimestamp(word.startTime);
1401
+ word.endTime = clampTimestamp(word.endTime);
1402
+ }
1403
+ line.startTime = clampTimestamp(line.words[0]?.startTime ?? 0);
1404
+ line.endTime = clampTimestamp(line.words[line.words.length - 1]?.endTime ?? 0);
1405
+ }
1406
+ return result;
1407
+ }
1408
+ /**
1409
+ * 将歌词数组转换为 ESLyric 逐词歌词格式字符串
1410
+ * @param lines 歌词数组
1411
+ * @returns ESLyric 逐词歌词格式字符串
1412
+ */
1413
+ function stringifyEslrc(lines) {
1414
+ return lines.map((line) => {
1415
+ if (!line.words.length) return "";
1416
+ return `[${formatTime(clampTimestamp(line.words[0].startTime))}]${line.words.map((word) => `${word.word}[${formatTime(clampTimestamp(word.endTime))}]`).join("")}`;
1417
+ }).filter(Boolean).join("\n");
1418
+ }
1419
+ //#endregion
1420
+ //#region src/formats/lys.ts
1421
+ /**
1422
+ * 解析 LYS 格式中的属性值
1423
+ * @param prop 属性值
1424
+ * @returns 对唱与背景标志位
1425
+ */
1426
+ function parseProp(prop) {
1427
+ if (prop < 0 || prop > 8) prop = 0;
1428
+ return {
1429
+ isDuet: prop % 3 === 0 ? void 0 : prop % 3 === 2,
1430
+ isBG: prop <= 2 ? void 0 : prop >= 6
1431
+ };
1432
+ }
1433
+ /**
1434
+ * 解析 LYS 格式的歌词字符串
1435
+ * @param lys 歌词字符串
1436
+ * @returns 成功解析出来的歌词
1437
+ */
1438
+ function parseLys(lys) {
1439
+ const lines = lys.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
1440
+ const lyricLines = [];
1441
+ const propRegex = /^\[(\d+)\]/;
1442
+ const wordRegex = /(.*?)\((\d+),(\d+)\)/g;
1443
+ for (const lineStr of lines) {
1444
+ const propMatch = lineStr.match(propRegex);
1445
+ if (!propMatch) continue;
1446
+ const [, propStr] = propMatch;
1447
+ const content = lineStr.slice(propMatch[0].length);
1448
+ const words = [];
1449
+ const props = parseProp(Number(propStr));
1450
+ for (const match of content.matchAll(wordRegex)) {
1451
+ const [, rawWord, startStr, durStr] = match;
1452
+ const startTime = Number(startStr);
1453
+ const endTime = startTime + Number(durStr);
1454
+ const wordText = rawWord;
1455
+ words.push(createWord({
1456
+ word: wordText,
1457
+ startTime,
1458
+ endTime
1459
+ }));
1460
+ }
1461
+ const lineStartTime = words[0]?.startTime ?? 0;
1462
+ const lineEndTime = words[words.length - 1]?.endTime ?? 0;
1463
+ if (!words.length) continue;
1464
+ if (props.isBG === void 0) props.isBG = words.length > 0 && /^[((]/.test(words[0].word) && /[))]$/.test(words[words.length - 1].word);
1465
+ if (props.isBG && words.length) {
1466
+ words[0].word = words[0].word.replace(/^[((]/, "");
1467
+ words[words.length - 1].word = words[words.length - 1].word.replace(/[))]$/, "");
1468
+ }
1469
+ lyricLines.push(createLine({
1470
+ startTime: lineStartTime,
1471
+ endTime: lineEndTime,
1472
+ isDuet: !!props.isDuet,
1473
+ isBG: props.isBG,
1474
+ words
1475
+ }));
1476
+ }
1477
+ return lyricLines;
1478
+ }
1479
+ function makeProp(line) {
1480
+ let prop = 0;
1481
+ prop += line.isDuet ? 2 : 1;
1482
+ prop += line.isBG ? 6 : 3;
1483
+ return prop;
1484
+ }
1485
+ /**
1486
+ * 将歌词数组转换为 LYS 格式的字符串
1487
+ * @param lines 歌词数组
1488
+ * @returns LYS 格式的字符串
1489
+ */
1490
+ function stringifyLys(lines) {
1491
+ return lines.map((line) => {
1492
+ const prop = makeProp(line);
1493
+ const printWords = [];
1494
+ line.words.forEach((w) => {
1495
+ if (w.word.trim() || !printWords.length) printWords.push({
1496
+ word: w.word,
1497
+ startTime: normalizeTimestamp(w.startTime),
1498
+ duration: normalizeDuration(normalizeTimestamp(w.endTime) - normalizeTimestamp(w.startTime))
1499
+ });
1500
+ else printWords[printWords.length - 1].word += w.word;
1501
+ });
1502
+ return `[${prop}]${printWords.map((w) => `${w.word}(${w.startTime},${w.duration})`).join("")}`;
1503
+ }).join("\n");
1504
+ }
1505
+ //#endregion
1506
+ //#region src/formats/lqe.ts
1507
+ function parseAttr(attr, headerMatches, rawLines, lines) {
1508
+ const headerIndex = headerMatches.findIndex((item) => {
1509
+ if (attr === "translatedLyric") return item.type === "translation";
1510
+ return item.type === "romanization";
1511
+ });
1512
+ if (headerIndex === -1) return;
1513
+ const timeRegex = /^\[((?:\d+:)*\d+(?:\.\d+)?)\](.*)$/;
1514
+ const attrLines = rawLines.slice(headerMatches[headerIndex].index + 1, headerMatches[headerIndex + 1].index).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => {
1515
+ const match = line.match(timeRegex);
1516
+ if (!match) return null;
1517
+ const [, timeStr, text] = match;
1518
+ const time = parseTime(timeStr);
1519
+ if (Number.isNaN(time)) return null;
1520
+ return {
1521
+ time,
1522
+ text
1523
+ };
1524
+ }).filter((item) => item !== null);
1525
+ let attrLineIndex = 0;
1526
+ for (const line of lines) {
1527
+ if (attrLines[attrLineIndex]?.time !== line.startTime) continue;
1528
+ line[attr] = attrLines[attrLineIndex].text;
1529
+ attrLineIndex++;
1530
+ }
1531
+ }
1532
+ /**
1533
+ * 解析 LQE 格式的歌词字符串
1534
+ * @param lqe 歌词字符串
1535
+ * @returns 成功解析出来的歌词
1536
+ */
1537
+ function parseLqe(lqe) {
1538
+ const lines = lqe.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
1539
+ const headerRegex = /^\[([a-zA-Z]+):.+\]$/;
1540
+ const headerMatches = [];
1541
+ lines.forEach((line, index) => {
1542
+ const match = line.match(headerRegex);
1543
+ if (!match) return;
1544
+ const [, type] = match;
1545
+ if (type === "lyrics") headerMatches.push({
1546
+ index,
1547
+ type: "lyric"
1548
+ });
1549
+ else if (type === "translation") headerMatches.push({
1550
+ index,
1551
+ type: "translation"
1552
+ });
1553
+ else if (type === "pronunciation") headerMatches.push({
1554
+ index,
1555
+ type: "romanization"
1556
+ });
1557
+ else headerMatches.push({
1558
+ index,
1559
+ type: "unknown"
1560
+ });
1561
+ });
1562
+ headerMatches.push({
1563
+ index: lines.length,
1564
+ type: "unknown"
1565
+ });
1566
+ const lyricHeaderIndex = headerMatches.findIndex((item) => item.type === "lyric");
1567
+ if (lyricHeaderIndex === -1) return [];
1568
+ const parsedLines = parseLys(lines.slice(headerMatches[lyricHeaderIndex].index + 1, headerMatches[lyricHeaderIndex + 1].index).join("\n"));
1569
+ parseAttr("translatedLyric", headerMatches, lines, parsedLines);
1570
+ parseAttr("romanLyric", headerMatches, lines, parsedLines);
1571
+ return parsedLines;
1572
+ }
1573
+ function stringifyAttr(lines, attr) {
1574
+ const header = attr === "translatedLyric" ? "[translation: format@LRC]" : "[pronunciation: format@LRC, language@romaji]";
1575
+ const contentLines = lines.map((line) => {
1576
+ const value = line[attr];
1577
+ if (!value) return null;
1578
+ return `[${formatTime(line.startTime)}]${value}`;
1579
+ }).filter((line) => line !== null);
1580
+ if (contentLines.length === 0) return null;
1581
+ return [header, ...contentLines].join("\n");
1582
+ }
1583
+ /**
1584
+ * 将歌词数组转换为 LQE 格式的字符串
1585
+ * @param lines 歌词数组
1586
+ * @returns LQE 格式的字符串
1587
+ */
1588
+ function stringifyLqe(lines) {
1589
+ return ["[Lyricify Quick Export]\n[version:1.0]", [
1590
+ `[lyrics: format@Lyricify Syllable]\n${stringifyLys(lines)}`,
1591
+ stringifyAttr(lines, "translatedLyric"),
1592
+ stringifyAttr(lines, "romanLyric")
1593
+ ].filter((section) => section !== null).join("\n\n\n")].join("\n\n");
1594
+ }
1595
+ //#endregion
1596
+ //#region src/formats/lrc.ts
1597
+ /**
1598
+ * 解析 LyRiC 格式的歌词字符串
1599
+ * @param lrc 歌词字符串
1600
+ * @returns 成功解析出来的歌词
1601
+ */
1602
+ function parseLrc(lrc) {
1603
+ const tagRegex = /^\[([a-z]+):([^\]]+)\]$/;
1604
+ const timeRegex = /^\[((?:\d+:)*\d+(?:\.\d+)?)\](.*)$/;
1605
+ const bgRegex = /^[((](.+)[))]$/;
1606
+ const lines = lrc.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
1607
+ const lyricLines = [];
1608
+ for (let lineStr of lines) {
1609
+ if (tagRegex.test(lineStr)) continue;
1610
+ const timeStamps = [];
1611
+ while (true) {
1612
+ const match = lineStr.match(timeRegex);
1613
+ if (!match) break;
1614
+ const [, timeStr, text] = match;
1615
+ const timeStamp = parseTime(timeStr);
1616
+ if (Number.isNaN(timeStamp)) break;
1617
+ timeStamps.push(timeStamp);
1618
+ lineStr = text;
1619
+ }
1620
+ if (timeStamps.length === 0) continue;
1621
+ lineStr = lineStr.trim();
1622
+ const backgroundMatch = lineStr.match(bgRegex);
1623
+ const isBG = Boolean(backgroundMatch);
1624
+ if (backgroundMatch) lineStr = backgroundMatch[1];
1625
+ for (const t of timeStamps) lyricLines.push(createLine({
1626
+ startTime: t,
1627
+ endTime: MAX_LRC_TIMESTAMP,
1628
+ words: [createWord({
1629
+ word: lineStr,
1630
+ startTime: t,
1631
+ endTime: t
1632
+ })],
1633
+ isBG
1634
+ }));
1635
+ }
1636
+ lyricLines.sort((a, b) => a.startTime - b.startTime);
1637
+ for (const [prev, curr] of pairwise(lyricLines)) prev.endTime = prev.words[0].endTime = curr.startTime;
1638
+ return lyricLines.filter((line) => line.words[0].word);
1639
+ }
1640
+ /**
1641
+ * 将歌词数组转换为 LyRiC 格式的字符串
1642
+ * @param lines 歌词数组
1643
+ * @returns LyRiC 格式的字符串
1644
+ */
1645
+ function stringifyLrc(lines) {
1646
+ return lines.map((line) => {
1647
+ const text = line.words.map((w) => w.word).join("");
1648
+ const printText = line.isBG ? `(${text})` : text;
1649
+ return `[${formatTime(normalizeTimestamp(line.startTime))}]${printText}`;
1650
+ }).join("\n");
1651
+ }
1652
+ //#endregion
1653
+ //#region src/formats/lrca2.ts
1654
+ /**
1655
+ * 解析 LRC A2 格式的歌词字符串
1656
+ * @param lrc 歌词字符串
1657
+ * @returns 成功解析出来的歌词
1658
+ */
1659
+ function parseLrcA2(lrc) {
1660
+ const lines = lrc.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
1661
+ const lyricLines = [];
1662
+ const lineTimeStampRegex = /^\[((?:\d+:)*\d+(?:\.\d+)?)\]/;
1663
+ const wordTimestampRegex = /<((?:\d+:)*\d+(?:\.\d+)?)>/;
1664
+ const wordTimestampPrefixRegex = /^<((?:\d+:)*\d+(?:\.\d+)?)>/;
1665
+ for (let lineStr of lines) {
1666
+ if (lineStr.match(/^\[([a-z]):(.+)\]$/i)) continue;
1667
+ const lineTimeStampmatch = lineStr.match(lineTimeStampRegex);
1668
+ if (!lineTimeStampmatch) continue;
1669
+ const [lineTimeStamp, lineTimeStr] = lineTimeStampmatch;
1670
+ const lineStartTime = parseTime(lineTimeStr);
1671
+ if (Number.isNaN(lineStartTime)) continue;
1672
+ lineStr = lineStr.slice(lineTimeStamp.length).trim();
1673
+ if (!lineStr) continue;
1674
+ const lineItems = [];
1675
+ while (lineStr.length) {
1676
+ const prefixedTimeStampMatch = lineStr.match(wordTimestampPrefixRegex);
1677
+ if (prefixedTimeStampMatch) {
1678
+ const [wordTimeStamp, wordTimeStr] = prefixedTimeStampMatch;
1679
+ const parsedWordTime = parseTime(wordTimeStr);
1680
+ if (!Number.isNaN(parsedWordTime)) lineItems.push(parsedWordTime);
1681
+ lineStr = lineStr.slice(wordTimeStamp.length);
1682
+ continue;
1683
+ }
1684
+ const nextWordTimeStampIndex = lineStr.search(wordTimestampRegex);
1685
+ const text = nextWordTimeStampIndex === -1 ? lineStr : lineStr.slice(0, nextWordTimeStampIndex);
1686
+ lineItems.push(text);
1687
+ lineStr = lineStr.slice(text.length);
1688
+ }
1689
+ const words = [];
1690
+ lineItems.forEach((item, index) => {
1691
+ if (typeof item === "number") return;
1692
+ const startTime = lineItems[index - 1] ?? lineStartTime;
1693
+ const endTime = lineItems[index + 1] ?? startTime;
1694
+ if (typeof startTime !== "number" || typeof endTime !== "number") return;
1695
+ if (item.startsWith(" ") && words[words.length - 1]?.word.trim()) words.push(createWord({ word: " " }));
1696
+ words.push(createWord({
1697
+ word: item.trim(),
1698
+ startTime,
1699
+ endTime
1700
+ }));
1701
+ if (item.endsWith(" ")) words.push(createWord({ word: " " }));
1702
+ });
1703
+ const lineEndTime = words[words.length - 1]?.endTime ?? lineStartTime;
1704
+ lyricLines.push(createLine({
1705
+ startTime: lineStartTime,
1706
+ endTime: lineEndTime,
1707
+ words
1708
+ }));
1709
+ }
1710
+ return lyricLines;
1711
+ }
1712
+ /**
1713
+ * 将歌词数组转换为 LRC A2 格式的字符串
1714
+ * @param lines 歌词数组
1715
+ * @returns LRC A2 格式的字符串
1716
+ */
1717
+ function stringifylrcA2(lines) {
1718
+ return lines.map((line) => {
1719
+ const normalizedLineStartTime = normalizeTimestamp(line.startTime);
1720
+ if (line.words.length === 0) return `[${formatTime(normalizedLineStartTime)}]`;
1721
+ const normalizedWords = [];
1722
+ line.words.forEach((w) => {
1723
+ if (!w.word.trim() && normalizedWords.length) {
1724
+ normalizedWords[normalizedWords.length - 1].word += w.word;
1725
+ return;
1726
+ }
1727
+ normalizedWords.push({
1728
+ word: w.word,
1729
+ startTime: normalizeTimestamp(w.startTime),
1730
+ endTime: normalizeTimestamp(w.endTime)
1731
+ });
1732
+ });
1733
+ const lineItems = normalizedWords.flatMap((w) => [w.startTime, w.word]);
1734
+ lineItems.push(normalizedWords[normalizedWords.length - 1].endTime);
1735
+ return `[${formatTime(normalizedLineStartTime)}]` + lineItems.map((item) => typeof item === "number" ? `<${formatTime(item)}>` : item).join("");
1736
+ }).join("\n");
1737
+ }
1738
+ //#endregion
1739
+ //#region src/formats/lyl.ts
1740
+ /**
1741
+ * 解析 LYL 格式的歌词字符串
1742
+ * @param lyl 歌词字符串
1743
+ * @returns 成功解析出来的歌词
1744
+ */
1745
+ function parseLyl(lyl) {
1746
+ const lines = lyl.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
1747
+ const lyricLines = [];
1748
+ const timeRegex = /^\[(\d+),(\d+)\](.*)$/;
1749
+ const bgRegex = /^[((](.+)[))]$/;
1750
+ for (const lineStr of lines) {
1751
+ if (lineStr === "[type:LyricifyLines]") continue;
1752
+ const timeMatch = lineStr.match(timeRegex);
1753
+ if (!timeMatch) continue;
1754
+ const [, startStr, endStr, text] = timeMatch;
1755
+ const startTime = Number(startStr);
1756
+ const endTime = Number(endStr);
1757
+ const backgroundMatch = text.match(bgRegex);
1758
+ const isBG = Boolean(backgroundMatch);
1759
+ const textContent = (backgroundMatch ? backgroundMatch[1] : text).trim();
1760
+ if (!textContent) continue;
1761
+ lyricLines.push(createLine({
1762
+ startTime,
1763
+ endTime,
1764
+ isBG,
1765
+ words: [createWord({
1766
+ word: textContent,
1767
+ startTime,
1768
+ endTime
1769
+ })]
1770
+ }));
1771
+ }
1772
+ return lyricLines;
1773
+ }
1774
+ /**
1775
+ * 将歌词数组转换为 LYL 格式的字符串
1776
+ * @param lines 歌词数组
1777
+ * @returns LYL 格式的字符串
1778
+ */
1779
+ function stringifyLyl(lines) {
1780
+ return ["[type:LyricifyLines]", ...lines.map((line) => {
1781
+ const text = line.words.map((w) => w.word).join("");
1782
+ const printText = line.isBG ? `(${text})` : text;
1783
+ return `[${normalizeTimestamp(line.startTime)},${normalizeTimestamp(line.endTime)}]${printText}`;
1784
+ })].join("\n");
1785
+ }
1786
+ //#endregion
1787
+ //#region src/formats/qrc.ts
1788
+ /**
1789
+ * 解析 QRC 格式的歌词字符串
1790
+ * @param qrc 歌词字符串
1791
+ * @returns 成功解析出来的歌词
1792
+ */
1793
+ function parseQrc(qrc) {
1794
+ const wordPattern = /(.*?)\((\d+),(\d+)\)/g;
1795
+ const linePattern = /^\[(\d+),(\d+)\]/;
1796
+ return qrc.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0).map((lineStr) => {
1797
+ const lineMatch = lineStr.match(linePattern);
1798
+ if (!lineMatch) return null;
1799
+ const [linePrefix, lineStartStr, lineDurStr] = lineMatch;
1800
+ const lineStart = Number(lineStartStr);
1801
+ const lineDuration = Number(lineDurStr);
1802
+ const words = [];
1803
+ const lineContent = lineStr.slice(linePrefix.length).trim();
1804
+ if (!lineContent) return null;
1805
+ for (const wordMatch of lineContent.matchAll(wordPattern)) {
1806
+ const [, wordText, wordStartStr, wordDurStr] = wordMatch;
1807
+ const wordStart = Number(wordStartStr);
1808
+ const wordDur = Number(wordDurStr);
1809
+ words.push(createWord({
1810
+ word: wordText,
1811
+ startTime: wordStart,
1812
+ endTime: wordStart + wordDur
1813
+ }));
1814
+ }
1815
+ const isBG = words.length > 0 && /^[((]/.test(words[0].word) && /[))]$/.test(words[words.length - 1].word);
1816
+ if (isBG) {
1817
+ words[0].word = words[0].word.replace(/^[((]/, "");
1818
+ words[words.length - 1].word = words[words.length - 1].word.replace(/[))]$/, "");
1819
+ }
1820
+ return createLine({
1821
+ startTime: lineStart,
1822
+ endTime: lineStart + lineDuration,
1823
+ words,
1824
+ isBG
1825
+ });
1826
+ }).filter((line) => line !== null);
1827
+ }
1828
+ /**
1829
+ * 将歌词数组转换为 QRC 格式的字符串
1830
+ * @param lines 歌词数组
1831
+ * @returns QRC 格式的字符串
1832
+ */
1833
+ function stringifyQrc(lines) {
1834
+ return lines.map((line) => {
1835
+ const lineStart = normalizeTimestamp(line.startTime);
1836
+ const lineDuration = normalizeDuration(normalizeTimestamp(line.endTime) - lineStart);
1837
+ const lineWords = [];
1838
+ for (const [index, { word, startTime, endTime }] of line.words.entries()) {
1839
+ if (!word.trim() && lineWords.length) {
1840
+ lineWords[lineWords.length - 1] += word;
1841
+ continue;
1842
+ }
1843
+ let printedWord = word;
1844
+ if (line.isBG) {
1845
+ if (index === 0) printedWord = `(${printedWord}`;
1846
+ if (index === line.words.length - 1) printedWord += ")";
1847
+ }
1848
+ const normalizedWordStart = normalizeTimestamp(startTime);
1849
+ const wordDuration = normalizeDuration(normalizeTimestamp(endTime) - normalizedWordStart);
1850
+ lineWords.push(`${printedWord}(${normalizedWordStart},${wordDuration})`);
1851
+ }
1852
+ return `[${lineStart},${lineDuration}]${lineWords.join("")}`;
1853
+ }).join("\n");
1854
+ }
1855
+ //#endregion
1856
+ //#region src/formats/ttml.ts
1857
+ /**
1858
+ * 解析 TTML 格式(包含 AMLL 特有属性信息)的歌词字符串
1859
+ * @param ttmlText 歌词字符串
1860
+ * @returns 成功解析出来的 TTML 歌词对象
1861
+ */
1862
+ function parseTTML(ttmlText) {
1863
+ return (0, _applemusic_like_lyrics_ttml.parseTTML)(ttmlText);
1864
+ }
1865
+ /**
1866
+ * 将歌词数组转换为 TTML 格式(包含 AMLL 特有属性信息)的歌词字符串
1867
+ * @param ttmlLyric TTML 歌词对象
1868
+ */
1869
+ function stringifyTTML(ttmlLyric) {
1870
+ return (0, _applemusic_like_lyrics_ttml.exportTTML)(ttmlLyric);
1871
+ }
1872
+ //#endregion
1873
+ //#region src/formats/yrc.ts
1874
+ const beginParenPattern = /^[((]/;
1875
+ const endParenPattern = /[))]$/;
1876
+ function checkIsBG(words) {
1877
+ return words.length > 0 && beginParenPattern.test(words[0].word) && endParenPattern.test(words[words.length - 1].word);
1878
+ }
1879
+ function trimBGParentheses(words) {
1880
+ words[0].word = words[0].word.slice(1);
1881
+ words[words.length - 1].word = words[words.length - 1].word.slice(0, -1);
1882
+ }
1883
+ /**
1884
+ * 解析 YRC 格式的歌词字符串
1885
+ * @param yrc 歌词字符串
1886
+ * @returns 成功解析出来的歌词
1887
+ */
1888
+ function parseYrc(yrc) {
1889
+ const wordPattern = /^(.*?)\((\d+),(\d+),0\)/;
1890
+ const linePattern = /^\[(\d+),(\d+)\]/;
1891
+ return yrc.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0).map((lineStr) => {
1892
+ const lineMatch = lineStr.match(linePattern);
1893
+ if (!lineMatch) return null;
1894
+ const [linePrefix, lineStartStr, lineDurStr] = lineMatch;
1895
+ const lineStart = Number(lineStartStr);
1896
+ const lineDuration = Number(lineDurStr);
1897
+ const words = [];
1898
+ let lineContent = lineStr.slice(linePrefix.length).trim();
1899
+ if (!lineContent) return null;
1900
+ let lastStart = -1;
1901
+ let lastEnd = -1;
1902
+ while (true) {
1903
+ const wordMatch = lineContent.match(wordPattern);
1904
+ if (!wordMatch) break;
1905
+ const [fullMatch, lastText, wordStartStr, wordDurStr] = wordMatch;
1906
+ if (lastText && lastStart !== -1) words.push(createWord({
1907
+ word: lastText,
1908
+ startTime: lastStart,
1909
+ endTime: lastEnd
1910
+ }));
1911
+ const wordStart = Number(wordStartStr);
1912
+ const wordEnd = wordStart + Number(wordDurStr);
1913
+ [lastStart, lastEnd] = [wordStart, wordEnd];
1914
+ lineContent = lineContent.slice(fullMatch.length);
1915
+ }
1916
+ if (lastStart !== -1 && lineContent) words.push(createWord({
1917
+ word: lineContent,
1918
+ startTime: lastStart,
1919
+ endTime: lastEnd
1920
+ }));
1921
+ const isBG = checkIsBG(words);
1922
+ if (isBG) trimBGParentheses(words);
1923
+ return createLine({
1924
+ startTime: lineStart,
1925
+ endTime: lineStart + lineDuration,
1926
+ words,
1927
+ isBG
1928
+ });
1929
+ }).filter((line) => line !== null);
1930
+ }
1931
+ function makeParenthesesFull(text) {
1932
+ return text.replace(/\(/g, "(").replace(/\)/g, ")");
1933
+ }
1934
+ /**
1935
+ * 将歌词数组转换为 YRC 格式的字符串
1936
+ * @param lines 歌词数组
1937
+ * @returns YRC 格式的字符串
1938
+ */
1939
+ function stringifyYrc(lines) {
1940
+ return lines.map((line) => {
1941
+ const lineStart = normalizeTimestamp(line.startTime);
1942
+ const lineDuration = normalizeDuration(normalizeTimestamp(line.endTime) - lineStart);
1943
+ const lineWords = [];
1944
+ for (const [index, { word, startTime, endTime }] of line.words.entries()) {
1945
+ if (!word.trim() && lineWords.length) {
1946
+ lineWords[lineWords.length - 1] += word;
1947
+ continue;
1948
+ }
1949
+ let printedWord = makeParenthesesFull(word);
1950
+ if (line.isBG) {
1951
+ if (index === 0) printedWord = `(${printedWord}`;
1952
+ if (index === line.words.length - 1) printedWord += ")";
1953
+ }
1954
+ const normalizedWordStart = normalizeTimestamp(startTime);
1955
+ const wordDuration = normalizeDuration(normalizeTimestamp(endTime) - normalizedWordStart);
1956
+ lineWords.push(`(${normalizedWordStart},${wordDuration},0)${printedWord}`);
1957
+ }
1958
+ return `[${lineStart},${lineDuration}]${lineWords.join("")}`;
1959
+ }).join("\n");
1960
+ }
1961
+ //#endregion
1962
+ exports.decryptQrcHex = decryptQrcHex;
1963
+ exports.encryptQrcHex = encryptQrcHex;
1964
+ exports.parseEslrc = parseEslrc;
1965
+ exports.parseLqe = parseLqe;
1966
+ exports.parseLrc = parseLrc;
1967
+ exports.parseLrcA2 = parseLrcA2;
1968
+ exports.parseLyl = parseLyl;
1969
+ exports.parseLys = parseLys;
1970
+ exports.parseQrc = parseQrc;
1971
+ exports.parseTTML = parseTTML;
1972
+ exports.parseYrc = parseYrc;
1973
+ exports.stringifyAss = stringifyAss;
1974
+ exports.stringifyEslrc = stringifyEslrc;
1975
+ exports.stringifyLqe = stringifyLqe;
1976
+ exports.stringifyLrc = stringifyLrc;
1977
+ exports.stringifyLyl = stringifyLyl;
1978
+ exports.stringifyLys = stringifyLys;
1979
+ exports.stringifyQrc = stringifyQrc;
1980
+ exports.stringifyTTML = stringifyTTML;
1981
+ exports.stringifyYrc = stringifyYrc;
1982
+ exports.stringifylrcA2 = stringifylrcA2;
1983
+
1984
+ //# sourceMappingURL=amll-lyric.cjs.map