@gondola-lyrics/kit 0.2.0 → 0.3.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.
- package/dist/formats.d.ts +40 -14
- package/dist/formats.js +915 -333
- package/dist/formats.js.map +1 -1
- package/dist/generator.d.ts +42 -2
- package/dist/generator.js +1 -1
- package/dist/index.d.ts +42 -6
- package/dist/index.js +1 -1
- package/dist/parser.d.ts +3 -5
- package/package.json +1 -1
package/dist/formats.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { AgentType, CreditRole, LineType, LyricStatus, Timing, WordType, canonicalizeLyric, deriveLanguageUsages, getWordsText, makeAgent, makeLineAnnotation, makeLineAnnotationRoman, makeLineAnnotationTranslation, makeLineBackground, makeLineContentText, makeLineContentWords, makeLineNormal, makeLyric, makeMeta, makeMetaCredit, makeText, makeTimeRange, makeUnknownEntry, makeWordAnnotation, makeWordAnnotationRuby, makeWordAnnotationToken, makeWordNormal, makeWordSpace, validateLyric } from "@gondola-lyrics/model/runtime";
|
|
1
|
+
import { AgentType, CreditRole, LineType, LyricStatus, Timing, WordType, canonicalizeLyric, deriveLanguageUsages, getWordsText, makeAgent, makeLineAnnotation, makeLineAnnotationRoman, makeLineAnnotationTranslation, makeLineBackground, makeLineContentText, makeLineContentWords, makeLineNormal, makeLyric, makeMeta, makeMetaCredit, makeText, makeTimeRange, makeUnknownEntry, makeWordAnnotation, makeWordAnnotationRuby, makeWordAnnotationToken, makeWordNormal, makeWordSpace, removeOffset, resolveAnnotationItemTime, resolveAnnotationTokenTime, validateLyric } from "@gondola-lyrics/model/runtime";
|
|
2
2
|
//#region src/utils/annotation.ts
|
|
3
3
|
/**
|
|
4
4
|
* An annotation line carries its text in either field, depending on whether the track used word tags.
|
|
5
5
|
*/
|
|
6
|
-
var readText = (line) => {
|
|
6
|
+
var readText$1 = (line) => {
|
|
7
7
|
return (line.content?.text ?? getWordsText(line.content?.words ?? [])).trim();
|
|
8
8
|
};
|
|
9
9
|
var isReadable = (line) => {
|
|
10
|
-
return line.time?.start !== void 0 && readText(line) !== "";
|
|
10
|
+
return line.time?.start !== void 0 && readText$1(line) !== "";
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
13
13
|
* Assigns each annotation line to a lyric line index.
|
|
@@ -79,11 +79,11 @@ var attachLineAnnotations = (lines, sources, options = {}) => {
|
|
|
79
79
|
...line,
|
|
80
80
|
annotation: makeLineAnnotation({
|
|
81
81
|
romans: [...line.annotation?.romans ?? [], ...roman === void 0 ? [] : [makeLineAnnotationRoman({
|
|
82
|
-
text: readText(roman),
|
|
82
|
+
text: readText$1(roman),
|
|
83
83
|
language: roman.languages[0]
|
|
84
84
|
})]],
|
|
85
85
|
translations: [...line.annotation?.translations ?? [], ...translation === void 0 ? [] : [makeLineAnnotationTranslation({
|
|
86
|
-
text: readText(translation),
|
|
86
|
+
text: readText$1(translation),
|
|
87
87
|
language: translation.languages[0]
|
|
88
88
|
})]]
|
|
89
89
|
})
|
|
@@ -233,6 +233,32 @@ var createXmlAttribute = (qualifiedName, value) => {
|
|
|
233
233
|
return attribute;
|
|
234
234
|
};
|
|
235
235
|
/**
|
|
236
|
+
* Creates an element from its full name, with attributes and children in the order given.
|
|
237
|
+
*/
|
|
238
|
+
var createXmlElement = (qualifiedName, attributes = [], children = []) => {
|
|
239
|
+
const element = {
|
|
240
|
+
nodeType: XmlNodeType.element,
|
|
241
|
+
qualifiedName,
|
|
242
|
+
namespacePrefix: "",
|
|
243
|
+
localName: qualifiedName,
|
|
244
|
+
attributes: attributes.map(([name, value]) => {
|
|
245
|
+
return createXmlAttribute(name, value);
|
|
246
|
+
}),
|
|
247
|
+
children: [...children]
|
|
248
|
+
};
|
|
249
|
+
applyXmlNameParts(qualifiedName, element);
|
|
250
|
+
return element;
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Creates a text node, whose value the writer escapes.
|
|
254
|
+
*/
|
|
255
|
+
var createXmlText = (text) => {
|
|
256
|
+
return {
|
|
257
|
+
nodeType: XmlNodeType.text,
|
|
258
|
+
text
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
236
262
|
* Returns the value of one attribute, matched by its local name so the prefix never has to be known.
|
|
237
263
|
*/
|
|
238
264
|
var getXmlAttribute = (element, localName) => {
|
|
@@ -291,27 +317,13 @@ var findXmlElement = (element, localName) => {
|
|
|
291
317
|
}
|
|
292
318
|
};
|
|
293
319
|
/**
|
|
294
|
-
* Returns every element carrying one local name, the element itself included, in document order.
|
|
295
|
-
*/
|
|
296
|
-
var findXmlElements = (element, localName) => {
|
|
297
|
-
const result = [];
|
|
298
|
-
const stack = [element];
|
|
299
|
-
while (stack.length > 0) {
|
|
300
|
-
const node = stack.pop();
|
|
301
|
-
if (node === void 0 || node.nodeType !== XmlNodeType.element) continue;
|
|
302
|
-
if (node.localName === localName) result.push(node);
|
|
303
|
-
for (let index = node.children.length - 1; index >= 0; index -= 1) {
|
|
304
|
-
const child = node.children[index];
|
|
305
|
-
if (child !== void 0) stack.push(child);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
return result;
|
|
309
|
-
};
|
|
310
|
-
/**
|
|
311
320
|
* Joins the text and CDATA content of a node and its descendants, in document order.
|
|
312
321
|
*/
|
|
313
322
|
var getXmlTextContent = (node) => {
|
|
314
323
|
if (node.nodeType !== XmlNodeType.element) return node.text;
|
|
324
|
+
if (node.children.length === 0) return "";
|
|
325
|
+
const child = node.children[0];
|
|
326
|
+
if (node.children.length === 1 && child !== void 0 && child.nodeType !== XmlNodeType.element) return child.text;
|
|
315
327
|
const parts = [];
|
|
316
328
|
const stack = [node];
|
|
317
329
|
while (stack.length > 0) {
|
|
@@ -330,21 +342,21 @@ var getXmlTextContent = (node) => {
|
|
|
330
342
|
};
|
|
331
343
|
//#endregion
|
|
332
344
|
//#region src/utils/xml/reader.ts
|
|
333
|
-
var LESS_THAN = 60;
|
|
334
|
-
var GREATER_THAN = 62;
|
|
345
|
+
var LESS_THAN$1 = 60;
|
|
346
|
+
var GREATER_THAN$1 = 62;
|
|
335
347
|
var SLASH = 47;
|
|
336
348
|
var EQUALS = 61;
|
|
337
|
-
var DOUBLE_QUOTE = 34;
|
|
349
|
+
var DOUBLE_QUOTE$1 = 34;
|
|
338
350
|
var SINGLE_QUOTE = 39;
|
|
339
351
|
var EXCLAMATION = 33;
|
|
340
352
|
var QUESTION = 63;
|
|
341
|
-
var AMPERSAND = 38;
|
|
353
|
+
var AMPERSAND$1 = 38;
|
|
342
354
|
var HASH = 35;
|
|
343
355
|
var HYPHEN = 45;
|
|
344
356
|
var LOWER_X = 120;
|
|
345
357
|
var UPPER_X = 88;
|
|
346
358
|
var BYTE_ORDER_MARK = 65279;
|
|
347
|
-
var ROOT_NAME = "#document";
|
|
359
|
+
var ROOT_NAME$1 = "#document";
|
|
348
360
|
var DECIMAL_DIGITS = /^\d+$/;
|
|
349
361
|
var HEX_DIGITS = /^[0-9a-fA-F]+$/;
|
|
350
362
|
/**
|
|
@@ -375,7 +387,7 @@ var decodeText = (text) => {
|
|
|
375
387
|
let start = 0;
|
|
376
388
|
let cursor = 0;
|
|
377
389
|
while (cursor < length) {
|
|
378
|
-
if (text.charCodeAt(cursor) !== AMPERSAND) {
|
|
390
|
+
if (text.charCodeAt(cursor) !== AMPERSAND$1) {
|
|
379
391
|
cursor += 1;
|
|
380
392
|
continue;
|
|
381
393
|
}
|
|
@@ -402,9 +414,9 @@ var parseXml = (source) => {
|
|
|
402
414
|
const stack = [];
|
|
403
415
|
const root = {
|
|
404
416
|
nodeType: XmlNodeType.element,
|
|
405
|
-
qualifiedName: ROOT_NAME,
|
|
417
|
+
qualifiedName: ROOT_NAME$1,
|
|
406
418
|
namespacePrefix: "",
|
|
407
|
-
localName: ROOT_NAME,
|
|
419
|
+
localName: ROOT_NAME$1,
|
|
408
420
|
attributes: [],
|
|
409
421
|
children: []
|
|
410
422
|
};
|
|
@@ -432,12 +444,12 @@ var parseXml = (source) => {
|
|
|
432
444
|
*/
|
|
433
445
|
const readQuotedValue = () => {
|
|
434
446
|
const quote = source.charCodeAt(position);
|
|
435
|
-
if (quote !== DOUBLE_QUOTE && quote !== SINGLE_QUOTE) {
|
|
447
|
+
if (quote !== DOUBLE_QUOTE$1 && quote !== SINGLE_QUOTE) {
|
|
436
448
|
valid = false;
|
|
437
449
|
return "";
|
|
438
450
|
}
|
|
439
451
|
position += 1;
|
|
440
|
-
const end = source.indexOf(quote === DOUBLE_QUOTE ? "\"" : "'", position);
|
|
452
|
+
const end = source.indexOf(quote === DOUBLE_QUOTE$1 ? "\"" : "'", position);
|
|
441
453
|
if (end === -1) {
|
|
442
454
|
valid = false;
|
|
443
455
|
return "";
|
|
@@ -454,7 +466,7 @@ var parseXml = (source) => {
|
|
|
454
466
|
const nameStart = cursor;
|
|
455
467
|
while (cursor < length) {
|
|
456
468
|
const code = source.charCodeAt(cursor);
|
|
457
|
-
if (code <= 32 || code === GREATER_THAN || code === SLASH || code === EQUALS) break;
|
|
469
|
+
if (code <= 32 || code === GREATER_THAN$1 || code === SLASH || code === EQUALS) break;
|
|
458
470
|
cursor += 1;
|
|
459
471
|
}
|
|
460
472
|
if (cursor === nameStart) {
|
|
@@ -482,14 +494,14 @@ var parseXml = (source) => {
|
|
|
482
494
|
code = source.charCodeAt(cursor);
|
|
483
495
|
}
|
|
484
496
|
if (cursor >= length) break;
|
|
485
|
-
if (code === GREATER_THAN) {
|
|
497
|
+
if (code === GREATER_THAN$1) {
|
|
486
498
|
cursor += 1;
|
|
487
499
|
closed = true;
|
|
488
500
|
break;
|
|
489
501
|
}
|
|
490
502
|
if (code === SLASH) {
|
|
491
503
|
cursor += 1;
|
|
492
|
-
if (cursor < length && source.charCodeAt(cursor) === GREATER_THAN) {
|
|
504
|
+
if (cursor < length && source.charCodeAt(cursor) === GREATER_THAN$1) {
|
|
493
505
|
cursor += 1;
|
|
494
506
|
closed = true;
|
|
495
507
|
selfClosing = true;
|
|
@@ -503,7 +515,7 @@ var parseXml = (source) => {
|
|
|
503
515
|
cursor += 1;
|
|
504
516
|
if (cursor >= length) break;
|
|
505
517
|
code = source.charCodeAt(cursor);
|
|
506
|
-
} while (code > 32 && code !== GREATER_THAN && code !== SLASH && code !== EQUALS);
|
|
518
|
+
} while (code > 32 && code !== GREATER_THAN$1 && code !== SLASH && code !== EQUALS);
|
|
507
519
|
if (cursor === attributeStart) {
|
|
508
520
|
valid = false;
|
|
509
521
|
return;
|
|
@@ -547,7 +559,7 @@ var parseXml = (source) => {
|
|
|
547
559
|
const nameStart = cursor;
|
|
548
560
|
while (cursor < length) {
|
|
549
561
|
const code = source.charCodeAt(cursor);
|
|
550
|
-
if (code <= 32 || code === GREATER_THAN) break;
|
|
562
|
+
if (code <= 32 || code === GREATER_THAN$1) break;
|
|
551
563
|
cursor += 1;
|
|
552
564
|
}
|
|
553
565
|
const name = source.slice(nameStart, cursor);
|
|
@@ -604,8 +616,8 @@ var parseXml = (source) => {
|
|
|
604
616
|
let depth = 1;
|
|
605
617
|
while (cursor < length && depth > 0) {
|
|
606
618
|
const code = source.charCodeAt(cursor);
|
|
607
|
-
if (code === LESS_THAN) depth += 1;
|
|
608
|
-
else if (code === GREATER_THAN) depth -= 1;
|
|
619
|
+
if (code === LESS_THAN$1) depth += 1;
|
|
620
|
+
else if (code === GREATER_THAN$1) depth -= 1;
|
|
609
621
|
cursor += 1;
|
|
610
622
|
}
|
|
611
623
|
if (depth > 0) {
|
|
@@ -654,6 +666,159 @@ var parseXml = (source) => {
|
|
|
654
666
|
return null;
|
|
655
667
|
};
|
|
656
668
|
//#endregion
|
|
669
|
+
//#region src/utils/xml/writer.ts
|
|
670
|
+
var AMPERSAND = 38;
|
|
671
|
+
var LESS_THAN = 60;
|
|
672
|
+
var GREATER_THAN = 62;
|
|
673
|
+
var DOUBLE_QUOTE = 34;
|
|
674
|
+
var TAB = 9;
|
|
675
|
+
var LINE_FEED = 10;
|
|
676
|
+
var CARRIAGE_RETURN = 13;
|
|
677
|
+
var ROOT_NAME = "#document";
|
|
678
|
+
var REPLACEMENT_CHARACTER = "�";
|
|
679
|
+
var XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
|
|
680
|
+
var DEFAULT_INDENT = " ";
|
|
681
|
+
/**
|
|
682
|
+
* Reports whether a code point may appear in an XML 1.0 document.
|
|
683
|
+
*/
|
|
684
|
+
var isAllowedCodePoint = (code) => {
|
|
685
|
+
return code === TAB || code === LINE_FEED || code === CARRIAGE_RETURN || code >= 32 && code <= 55295 || code >= 57344 && code <= 65533 || code >= 65536 && code <= 1114111;
|
|
686
|
+
};
|
|
687
|
+
/**
|
|
688
|
+
* Escapes text content, replacing characters XML cannot carry so corruption stays visible.
|
|
689
|
+
*/
|
|
690
|
+
var escapeXmlText = (text, chunks) => {
|
|
691
|
+
const length = text.length;
|
|
692
|
+
let start = 0;
|
|
693
|
+
for (let index = 0; index < length; index += 1) {
|
|
694
|
+
const code = text.charCodeAt(index);
|
|
695
|
+
let escaped = "";
|
|
696
|
+
if (code === AMPERSAND) escaped = "&";
|
|
697
|
+
else if (code === LESS_THAN) escaped = "<";
|
|
698
|
+
else if (code === GREATER_THAN) escaped = ">";
|
|
699
|
+
else if (code === CARRIAGE_RETURN) escaped = " ";
|
|
700
|
+
else if (code >= 55296 && code <= 56319) {
|
|
701
|
+
const low = index + 1 < length ? text.charCodeAt(index + 1) : 0;
|
|
702
|
+
if (low < 56320 || low > 57343) escaped = REPLACEMENT_CHARACTER;
|
|
703
|
+
else {
|
|
704
|
+
index += 1;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
} else if (code >= 56320 && code <= 57343) escaped = REPLACEMENT_CHARACTER;
|
|
708
|
+
else if (!isAllowedCodePoint(code)) escaped = REPLACEMENT_CHARACTER;
|
|
709
|
+
if (escaped === "") continue;
|
|
710
|
+
if (start < index) chunks.push(text.slice(start, index));
|
|
711
|
+
chunks.push(escaped);
|
|
712
|
+
start = index + 1;
|
|
713
|
+
}
|
|
714
|
+
if (start === 0) {
|
|
715
|
+
chunks.push(text);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (start < length) chunks.push(text.slice(start));
|
|
719
|
+
};
|
|
720
|
+
/**
|
|
721
|
+
* Escapes an attribute value, where whitespace also has to survive normalization.
|
|
722
|
+
*/
|
|
723
|
+
var escapeXmlAttribute = (value, chunks) => {
|
|
724
|
+
const length = value.length;
|
|
725
|
+
let start = 0;
|
|
726
|
+
for (let index = 0; index < length; index += 1) {
|
|
727
|
+
const code = value.charCodeAt(index);
|
|
728
|
+
let escaped = "";
|
|
729
|
+
if (code === AMPERSAND) escaped = "&";
|
|
730
|
+
else if (code === LESS_THAN) escaped = "<";
|
|
731
|
+
else if (code === GREATER_THAN) escaped = ">";
|
|
732
|
+
else if (code === DOUBLE_QUOTE) escaped = """;
|
|
733
|
+
else if (code === TAB) escaped = "	";
|
|
734
|
+
else if (code === LINE_FEED) escaped = " ";
|
|
735
|
+
else if (code === CARRIAGE_RETURN) escaped = " ";
|
|
736
|
+
else if (code >= 55296 && code <= 56319) {
|
|
737
|
+
const low = index + 1 < length ? value.charCodeAt(index + 1) : 0;
|
|
738
|
+
if (low < 56320 || low > 57343) escaped = REPLACEMENT_CHARACTER;
|
|
739
|
+
else {
|
|
740
|
+
index += 1;
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
} else if (code >= 56320 && code <= 57343) escaped = REPLACEMENT_CHARACTER;
|
|
744
|
+
else if (!isAllowedCodePoint(code)) escaped = REPLACEMENT_CHARACTER;
|
|
745
|
+
if (escaped === "") continue;
|
|
746
|
+
if (start < index) chunks.push(value.slice(start, index));
|
|
747
|
+
chunks.push(escaped);
|
|
748
|
+
start = index + 1;
|
|
749
|
+
}
|
|
750
|
+
if (start === 0) {
|
|
751
|
+
chunks.push(value);
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (start < length) chunks.push(value.slice(start));
|
|
755
|
+
};
|
|
756
|
+
/**
|
|
757
|
+
* Reports whether every child is an element, which is the only case indentation can add whitespace to.
|
|
758
|
+
*
|
|
759
|
+
* An element holding any character data is mixed content, where the inserted whitespace would join the text.
|
|
760
|
+
*/
|
|
761
|
+
var hasOnlyElements = (element) => {
|
|
762
|
+
const children = element.children;
|
|
763
|
+
for (let index = 0; index < children.length; index += 1) {
|
|
764
|
+
const child = children[index];
|
|
765
|
+
if (child !== void 0 && child.nodeType !== XmlNodeType.element) return false;
|
|
766
|
+
}
|
|
767
|
+
return true;
|
|
768
|
+
};
|
|
769
|
+
/**
|
|
770
|
+
* Serializes one node, appending its pieces to `chunks`.
|
|
771
|
+
*/
|
|
772
|
+
var writeXmlNode = (node, chunks, depth, indent) => {
|
|
773
|
+
if (node.nodeType === XmlNodeType.text) {
|
|
774
|
+
escapeXmlText(node.text, chunks);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
if (node.nodeType === XmlNodeType.cdata) {
|
|
778
|
+
chunks.push("<![CDATA[", node.text.replaceAll("]]>", "]]]]><![CDATA[>"), "]]>");
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
if (indent !== void 0 && depth > 0) chunks.push("\n", indent.repeat(depth));
|
|
782
|
+
chunks.push("<", node.qualifiedName);
|
|
783
|
+
for (let index = 0; index < node.attributes.length; index += 1) {
|
|
784
|
+
const attribute = node.attributes[index];
|
|
785
|
+
if (attribute === void 0) continue;
|
|
786
|
+
chunks.push(" ", attribute.qualifiedName, "=\"");
|
|
787
|
+
escapeXmlAttribute(attribute.value, chunks);
|
|
788
|
+
chunks.push("\"");
|
|
789
|
+
}
|
|
790
|
+
const children = node.children;
|
|
791
|
+
if (children.length === 0) {
|
|
792
|
+
chunks.push("/>");
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
chunks.push(">");
|
|
796
|
+
const childIndent = indent !== void 0 && hasOnlyElements(node) ? indent : void 0;
|
|
797
|
+
for (let index = 0; index < children.length; index += 1) {
|
|
798
|
+
const child = children[index];
|
|
799
|
+
if (child !== void 0) writeXmlNode(child, chunks, depth + 1, childIndent);
|
|
800
|
+
}
|
|
801
|
+
if (childIndent !== void 0) chunks.push("\n", childIndent.repeat(depth));
|
|
802
|
+
chunks.push("</", node.qualifiedName, ">");
|
|
803
|
+
};
|
|
804
|
+
/**
|
|
805
|
+
* Writes a tree back to text.
|
|
806
|
+
*
|
|
807
|
+
* A `#document` root, as `parseXml` returns, writes its children; any other root writes itself.
|
|
808
|
+
*/
|
|
809
|
+
var serializeXml = (root, options = {}) => {
|
|
810
|
+
const chunks = [];
|
|
811
|
+
if (options.declaration ?? true) chunks.push(XML_DECLARATION);
|
|
812
|
+
const indent = options.indent === true ? options.indentText ?? DEFAULT_INDENT : void 0;
|
|
813
|
+
if (indent !== void 0) chunks.push("\n");
|
|
814
|
+
if (root.qualifiedName === ROOT_NAME) for (let index = 0; index < root.children.length; index += 1) {
|
|
815
|
+
const child = root.children[index];
|
|
816
|
+
if (child !== void 0) writeXmlNode(child, chunks, 0, indent);
|
|
817
|
+
}
|
|
818
|
+
else writeXmlNode(root, chunks, 0, indent);
|
|
819
|
+
return chunks.join("");
|
|
820
|
+
};
|
|
821
|
+
//#endregion
|
|
657
822
|
//#region src/formats/lrc/common.ts
|
|
658
823
|
var readLrcContent = (input) => {
|
|
659
824
|
if (typeof input === "string") return { original: input };
|
|
@@ -747,7 +912,7 @@ var detectLrc = (input) => {
|
|
|
747
912
|
};
|
|
748
913
|
//#endregion
|
|
749
914
|
//#region src/formats/lrc/generator.ts
|
|
750
|
-
var resolveOptions = (options = {}) => {
|
|
915
|
+
var resolveOptions$1 = (options = {}) => {
|
|
751
916
|
return {
|
|
752
917
|
wordTiming: options.wordTiming ?? "angle",
|
|
753
918
|
timeFormat: options.timeFormat ?? "mm:ss.SSS",
|
|
@@ -830,7 +995,7 @@ var joinBlocks = (header, body) => {
|
|
|
830
995
|
* Word tags appear only for a word-timed lyric when `wordTiming` is not `none`; otherwise every line stays whole.
|
|
831
996
|
*/
|
|
832
997
|
var generateLrc = (lyric, options = {}) => {
|
|
833
|
-
const resolved = resolveOptions(options);
|
|
998
|
+
const resolved = resolveOptions$1(options);
|
|
834
999
|
const header = resolved.includeMeta && lyric.meta ? renderMetaRows(lyric.meta, resolved) : [];
|
|
835
1000
|
const wordLevel = lyric.lines.some((line) => (line.content?.words.length ?? 0) > 0) && resolved.wordTiming !== "none";
|
|
836
1001
|
const result = { original: joinBlocks(header, lyric.lines.map((line) => {
|
|
@@ -1109,15 +1274,12 @@ var parseLrc = (input, options = {}) => {
|
|
|
1109
1274
|
var FORMAT = "ttml";
|
|
1110
1275
|
var AMLL_HINT_PATTERN = /xmlns:amll|amll:meta/iu;
|
|
1111
1276
|
var ITUNES_HINT_PATTERN = /xmlns:itunes|iTunesMetadata|itunes:timing=/iu;
|
|
1277
|
+
/**
|
|
1278
|
+
* Accepts the text input shared by TTML detection and parsing.
|
|
1279
|
+
*/
|
|
1112
1280
|
var readTtmlContent = (input) => {
|
|
1113
|
-
if (typeof input
|
|
1114
|
-
|
|
1115
|
-
const source = input;
|
|
1116
|
-
if (typeof source.original !== "string") return null;
|
|
1117
|
-
return { original: source.original };
|
|
1118
|
-
};
|
|
1119
|
-
var readTtmlProfile = (source) => {
|
|
1120
|
-
return AMLL_HINT_PATTERN.test(source) ? "amll" : "itunes";
|
|
1281
|
+
if (typeof input !== "string") return null;
|
|
1282
|
+
return input;
|
|
1121
1283
|
};
|
|
1122
1284
|
//#endregion
|
|
1123
1285
|
//#region src/formats/ttml/detect.ts
|
|
@@ -1130,35 +1292,344 @@ var readTtmlProfile = (source) => {
|
|
|
1130
1292
|
var detectTtml = (input) => {
|
|
1131
1293
|
const content = readTtmlContent(input);
|
|
1132
1294
|
if (!content) return false;
|
|
1133
|
-
if (AMLL_HINT_PATTERN.test(content
|
|
1134
|
-
const root = parseXml(content
|
|
1295
|
+
if (AMLL_HINT_PATTERN.test(content) || ITUNES_HINT_PATTERN.test(content)) return true;
|
|
1296
|
+
const root = parseXml(content);
|
|
1135
1297
|
if (!root) return false;
|
|
1136
1298
|
for (const child of root.children) if (child.nodeType === XmlNodeType.element && child.localName === "tt") return true;
|
|
1137
1299
|
return false;
|
|
1138
1300
|
};
|
|
1301
|
+
//#endregion
|
|
1302
|
+
//#region src/formats/ttml/utils.ts
|
|
1303
|
+
var ROLE_BACKGROUND = "x-bg";
|
|
1304
|
+
var ROLE_TRANSLATION = "x-translation";
|
|
1305
|
+
var ROLE_ROMAN = "x-roman";
|
|
1306
|
+
var RUBY_CONTAINER = "container";
|
|
1307
|
+
var RUBY_BASE = "base";
|
|
1308
|
+
var RUBY_TEXT_CONTAINER = "textContainer";
|
|
1309
|
+
var RUBY_TEXT = "text";
|
|
1139
1310
|
var TIMING_WORD = "Word";
|
|
1140
1311
|
var TIMING_LINE = "Line";
|
|
1141
1312
|
//#endregion
|
|
1142
|
-
//#region src/formats/ttml/
|
|
1313
|
+
//#region src/formats/ttml/generator.ts
|
|
1314
|
+
var NAMESPACE_TTML = "http://www.w3.org/ns/ttml";
|
|
1315
|
+
var NAMESPACE_TTML_METADATA = "http://www.w3.org/ns/ttml#metadata";
|
|
1316
|
+
var NAMESPACE_TTML_STYLING = "http://www.w3.org/ns/ttml#styling";
|
|
1317
|
+
var NAMESPACE_ITUNES = "http://itunes.apple.com/lyric-ttml-extensions";
|
|
1318
|
+
var NAMESPACE_ITUNES_METADATA = "http://music.apple.com/lyric-ttml-internal";
|
|
1319
|
+
var NAMESPACE_AMLL = "http://www.example.com/ns/amll";
|
|
1320
|
+
var ANNOTATION_KINDS = ["translations", "romans"];
|
|
1321
|
+
var XML_ID_PATTERN = /^[A-Za-z_][A-Za-z0-9_.-]*$/;
|
|
1322
|
+
var resolveOptions = (lyric, options) => {
|
|
1323
|
+
const profile = options.profile ?? (lyric.format === "ttml" && lyric.extra.profile === "itunes" ? "itunes" : "amll");
|
|
1324
|
+
if (profile !== "amll" && profile !== "itunes") throw new Error("Unsupported TTML profile");
|
|
1325
|
+
return {
|
|
1326
|
+
profile,
|
|
1327
|
+
includeMeta: options.includeMeta ?? true,
|
|
1328
|
+
indent: options.indent ?? false
|
|
1329
|
+
};
|
|
1330
|
+
};
|
|
1143
1331
|
/**
|
|
1144
|
-
*
|
|
1332
|
+
* Reserves source ids before allocating keys, so unnamed entries cannot take later source ids.
|
|
1145
1333
|
*/
|
|
1146
|
-
var
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
const
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1334
|
+
var makeIdAllocator = (ids) => {
|
|
1335
|
+
const reserved = new Set(ids.filter((id) => {
|
|
1336
|
+
return XML_ID_PATTERN.test(id);
|
|
1337
|
+
}));
|
|
1338
|
+
const used = /* @__PURE__ */ new Set();
|
|
1339
|
+
const counters = /* @__PURE__ */ new Map();
|
|
1340
|
+
return (preferred, prefix) => {
|
|
1341
|
+
if (XML_ID_PATTERN.test(preferred) && !used.has(preferred)) {
|
|
1342
|
+
used.add(preferred);
|
|
1343
|
+
return preferred;
|
|
1344
|
+
}
|
|
1345
|
+
let count = counters.get(prefix) ?? 0;
|
|
1346
|
+
let id;
|
|
1347
|
+
do {
|
|
1348
|
+
count += 1;
|
|
1349
|
+
id = `${prefix}${count}`;
|
|
1350
|
+
} while (reserved.has(id) || used.has(id));
|
|
1351
|
+
counters.set(prefix, count);
|
|
1352
|
+
used.add(id);
|
|
1353
|
+
return id;
|
|
1354
|
+
};
|
|
1355
|
+
};
|
|
1356
|
+
/**
|
|
1357
|
+
* Shares one mapping between declarations, body lines, and annotation references.
|
|
1358
|
+
*/
|
|
1359
|
+
var makeContext = (lyric, lines, profile) => {
|
|
1360
|
+
const ids = lyric.agents.map((agent) => {
|
|
1361
|
+
return agent.id;
|
|
1362
|
+
});
|
|
1363
|
+
for (const line of lines) {
|
|
1364
|
+
ids.push(line.id);
|
|
1365
|
+
for (const background of line.backgrounds) ids.push(background.id);
|
|
1366
|
+
}
|
|
1367
|
+
const allocate = makeIdAllocator(ids);
|
|
1368
|
+
const agents = /* @__PURE__ */ new Map();
|
|
1369
|
+
const keys = /* @__PURE__ */ new Map();
|
|
1370
|
+
for (const agent of lyric.agents) {
|
|
1371
|
+
if (agents.has(agent.id)) throw new Error("TTML agent declarations require unique ids");
|
|
1372
|
+
agents.set(agent.id, allocate(agent.id, "v"));
|
|
1373
|
+
}
|
|
1374
|
+
for (const line of lines) {
|
|
1375
|
+
keys.set(line, allocate(line.id, "L"));
|
|
1376
|
+
for (const background of line.backgrounds) keys.set(background, allocate(background.id, "B"));
|
|
1377
|
+
}
|
|
1153
1378
|
return {
|
|
1154
|
-
|
|
1155
|
-
|
|
1379
|
+
profile,
|
|
1380
|
+
offset: lyric.meta?.offset ?? 0,
|
|
1381
|
+
keys,
|
|
1382
|
+
agents
|
|
1156
1383
|
};
|
|
1157
1384
|
};
|
|
1158
1385
|
/**
|
|
1159
|
-
*
|
|
1386
|
+
* Bakes the playback offset into TTML timestamps without clamping negative results.
|
|
1160
1387
|
*/
|
|
1161
|
-
var
|
|
1388
|
+
var renderTime = (time, offset) => {
|
|
1389
|
+
const shifted = removeOffset(time, offset);
|
|
1390
|
+
if (!Number.isInteger(shifted) || shifted < 0) throw new RangeError("TTML timestamps must be non-negative integer milliseconds after applying the offset");
|
|
1391
|
+
const seconds = Math.floor(shifted / 1e3);
|
|
1392
|
+
const minutes = Math.floor(seconds / 60);
|
|
1393
|
+
const hours = String(Math.floor(minutes / 60)).padStart(2, "0");
|
|
1394
|
+
const milliseconds = String(shifted % 1e3).padStart(3, "0");
|
|
1395
|
+
return `${hours}:${String(minutes % 60).padStart(2, "0")}:${String(seconds % 60).padStart(2, "0")}.${milliseconds}`;
|
|
1396
|
+
};
|
|
1397
|
+
/**
|
|
1398
|
+
* Writes known bounds without inventing an end.
|
|
1399
|
+
*/
|
|
1400
|
+
var renderTimeAttributes = (time, offset) => {
|
|
1401
|
+
if (time === void 0) throw new Error("TTML sung lines, words, and ruby tokens require a time range");
|
|
1402
|
+
const attributes = [["begin", renderTime(time.start, offset)]];
|
|
1403
|
+
if (time.end !== void 0) attributes.push(["end", renderTime(time.end, offset)]);
|
|
1404
|
+
return attributes;
|
|
1405
|
+
};
|
|
1406
|
+
/**
|
|
1407
|
+
* Clears inherited language when the model leaves an item's language unspecified.
|
|
1408
|
+
*/
|
|
1409
|
+
var renderLanguage = (language, inherited) => {
|
|
1410
|
+
if (language !== void 0) return [["xml:lang", language]];
|
|
1411
|
+
return inherited === void 0 ? [] : [["xml:lang", ""]];
|
|
1412
|
+
};
|
|
1413
|
+
var getLineLanguage = (line) => {
|
|
1414
|
+
return line.languages.length === 1 ? line.languages[0] : void 0;
|
|
1415
|
+
};
|
|
1416
|
+
/**
|
|
1417
|
+
* Writes separators as text and resolves inherited ruby timing through the model.
|
|
1418
|
+
*/
|
|
1419
|
+
var renderWord = (word, context, language) => {
|
|
1420
|
+
if (word.type === WordType.SPACE) return createXmlText(word.text);
|
|
1421
|
+
const attributes = [...renderTimeAttributes(word.time, context.offset), ...renderLanguage(word.language, language)];
|
|
1422
|
+
const ruby = context.profile === "amll" ? word.annotation?.rubies[0] : void 0;
|
|
1423
|
+
if (ruby === void 0) return createXmlElement("span", attributes, [createXmlText(word.text)]);
|
|
1424
|
+
const tokens = ruby.tokens.map((token) => {
|
|
1425
|
+
return createXmlElement("span", [["tts:ruby", RUBY_TEXT], ...renderTimeAttributes(resolveAnnotationTokenTime(token, ruby, word), context.offset)], [createXmlText(token.text)]);
|
|
1426
|
+
});
|
|
1427
|
+
return createXmlElement("span", [["tts:ruby", RUBY_CONTAINER], ...attributes], [createXmlElement("span", [["tts:ruby", RUBY_BASE]], [createXmlText(word.text)]), createXmlElement("span", [
|
|
1428
|
+
["tts:ruby", RUBY_TEXT_CONTAINER],
|
|
1429
|
+
...renderTimeAttributes(resolveAnnotationItemTime(ruby, word), context.offset),
|
|
1430
|
+
...renderLanguage(ruby.language, word.language)
|
|
1431
|
+
], tokens)]);
|
|
1432
|
+
};
|
|
1433
|
+
/**
|
|
1434
|
+
* Retains the model's choice between whole text and timed words.
|
|
1435
|
+
*/
|
|
1436
|
+
var renderContent = (line, context) => {
|
|
1437
|
+
const content = line.content;
|
|
1438
|
+
if (content === void 0) throw new Error("TTML sung lines require content");
|
|
1439
|
+
if (content.text !== void 0) return [createXmlText(content.text)];
|
|
1440
|
+
return content.words.map((word) => {
|
|
1441
|
+
return renderWord(word, context, getLineLanguage(line));
|
|
1442
|
+
});
|
|
1443
|
+
};
|
|
1444
|
+
/**
|
|
1445
|
+
* Keeps annotations outside the sung word stream.
|
|
1446
|
+
*/
|
|
1447
|
+
var renderInlineAnnotations = (annotation, language) => {
|
|
1448
|
+
const nodes = [];
|
|
1449
|
+
for (const item of annotation?.translations ?? []) nodes.push(createXmlElement("span", [["ttm:role", ROLE_TRANSLATION], ...renderLanguage(item.language, language)], [createXmlText(item.text)]));
|
|
1450
|
+
for (const item of annotation?.romans ?? []) nodes.push(createXmlElement("span", [["ttm:role", ROLE_ROMAN], ...renderLanguage(item.language, language)], [createXmlText(item.text)]));
|
|
1451
|
+
return nodes;
|
|
1452
|
+
};
|
|
1453
|
+
/**
|
|
1454
|
+
* Uses the same allocated keys and agent ids as the document's declarations.
|
|
1455
|
+
*/
|
|
1456
|
+
var renderLineAttributes = (line, context) => {
|
|
1457
|
+
const attributes = [
|
|
1458
|
+
...renderTimeAttributes(line.time, context.offset),
|
|
1459
|
+
["itunes:key", context.keys.get(line)],
|
|
1460
|
+
["xml:space", "preserve"],
|
|
1461
|
+
["xml:lang", getLineLanguage(line) ?? ""]
|
|
1462
|
+
];
|
|
1463
|
+
const lead = line.agents[0];
|
|
1464
|
+
if (lead !== void 0) {
|
|
1465
|
+
const id = context.agents.get(lead);
|
|
1466
|
+
if (id === void 0) throw new Error("TTML performing agents must have a declaration");
|
|
1467
|
+
attributes.push(["ttm:agent", id]);
|
|
1468
|
+
}
|
|
1469
|
+
return attributes;
|
|
1470
|
+
};
|
|
1471
|
+
/**
|
|
1472
|
+
* An empty text node keeps the XML writer's indentation outside lyric content.
|
|
1473
|
+
*/
|
|
1474
|
+
var createLyricElement = (name, attributes, children) => {
|
|
1475
|
+
return createXmlElement(name, attributes, [createXmlText(""), ...children]);
|
|
1476
|
+
};
|
|
1477
|
+
var renderBackground = (line, context) => {
|
|
1478
|
+
const children = renderContent(line, context);
|
|
1479
|
+
if (context.profile === "amll") children.push(...renderInlineAnnotations(line.annotation, getLineLanguage(line)));
|
|
1480
|
+
return createXmlElement("span", [["ttm:role", ROLE_BACKGROUND], ...renderLineAttributes(line, context)], children);
|
|
1481
|
+
};
|
|
1482
|
+
var renderLine = (line, context) => {
|
|
1483
|
+
const before = [];
|
|
1484
|
+
const after = [];
|
|
1485
|
+
for (const background of line.backgrounds) {
|
|
1486
|
+
const node = renderBackground(background, context);
|
|
1487
|
+
if (background.time !== void 0 && line.time !== void 0 && background.time.start < line.time.start) before.push(node);
|
|
1488
|
+
else after.push(node);
|
|
1489
|
+
}
|
|
1490
|
+
const children = [
|
|
1491
|
+
...before,
|
|
1492
|
+
...renderContent(line, context),
|
|
1493
|
+
...after
|
|
1494
|
+
];
|
|
1495
|
+
if (context.profile === "amll") children.push(...renderInlineAnnotations(line.annotation, getLineLanguage(line)));
|
|
1496
|
+
return createLyricElement("p", renderLineAttributes(line, context), children);
|
|
1497
|
+
};
|
|
1498
|
+
var renderAgents = (lyric, context) => {
|
|
1499
|
+
return lyric.agents.map((agent) => {
|
|
1500
|
+
const type = agent.type === AgentType.PERSON ? "person" : agent.type === AgentType.GROUP ? "group" : agent.raw ?? "other";
|
|
1501
|
+
const names = agent.names.map((name) => {
|
|
1502
|
+
return createXmlElement("ttm:name", [["type", "full"], ...renderLanguage(name.language)], [createXmlText(name.text)]);
|
|
1503
|
+
});
|
|
1504
|
+
return createXmlElement("ttm:agent", [["type", type], ["xml:id", context.agents.get(agent.id)]], names);
|
|
1505
|
+
});
|
|
1506
|
+
};
|
|
1507
|
+
var renderAmllMeta = (meta) => {
|
|
1508
|
+
const nodes = [];
|
|
1509
|
+
const groups = [
|
|
1510
|
+
["musicName", meta.titles],
|
|
1511
|
+
["artists", meta.artists],
|
|
1512
|
+
["album", meta.albums],
|
|
1513
|
+
["ttmlAuthorGithubLogin", meta.authors]
|
|
1514
|
+
];
|
|
1515
|
+
for (const [key, items] of groups) for (const item of items) nodes.push(createXmlElement("amll:meta", [
|
|
1516
|
+
["key", key],
|
|
1517
|
+
["value", item.text],
|
|
1518
|
+
...renderLanguage(item.language)
|
|
1519
|
+
]));
|
|
1520
|
+
for (const value of meta.isrcs) nodes.push(createXmlElement("amll:meta", [["key", "isrc"], ["value", value]]));
|
|
1521
|
+
for (const item of meta.unknowns) nodes.push(createXmlElement("amll:meta", [["key", item.key], ["value", item.value]]));
|
|
1522
|
+
return nodes;
|
|
1523
|
+
};
|
|
1524
|
+
var renderSongwriters = (meta) => {
|
|
1525
|
+
const names = [];
|
|
1526
|
+
for (const credit of meta.credits) {
|
|
1527
|
+
if (credit.role !== CreditRole.LYRICIST) continue;
|
|
1528
|
+
for (const name of credit.names) names.push(createXmlElement("songwriter", renderLanguage(name.language), [createXmlText(name.text)]));
|
|
1529
|
+
}
|
|
1530
|
+
return names.length === 0 ? [] : [createXmlElement("songwriters", [], names)];
|
|
1531
|
+
};
|
|
1532
|
+
var renderAnnotationTracks = (tracks, kind, context) => {
|
|
1533
|
+
if (tracks.size === 0) return [];
|
|
1534
|
+
const translated = kind === "translations";
|
|
1535
|
+
const blocks = Array.from(tracks, ([language, track]) => {
|
|
1536
|
+
const attributes = translated ? [["type", "subtitle"]] : [];
|
|
1537
|
+
attributes.push(...renderLanguage(language));
|
|
1538
|
+
const texts = Array.from(track, ([line, children]) => {
|
|
1539
|
+
return createLyricElement("text", [["for", context.keys.get(line)], ["xml:space", "preserve"]], children);
|
|
1540
|
+
});
|
|
1541
|
+
return createXmlElement(translated ? "translation" : "transliteration", attributes, texts);
|
|
1542
|
+
});
|
|
1543
|
+
return [createXmlElement(translated ? "translations" : "transliterations", [], blocks)];
|
|
1544
|
+
};
|
|
1545
|
+
/**
|
|
1546
|
+
* A background-only annotation still references its parent paragraph, with a second reference naming the background.
|
|
1547
|
+
*/
|
|
1548
|
+
var renderHeadAnnotations = (lines, context) => {
|
|
1549
|
+
const tracks = {
|
|
1550
|
+
translations: /* @__PURE__ */ new Map(),
|
|
1551
|
+
romans: /* @__PURE__ */ new Map()
|
|
1552
|
+
};
|
|
1553
|
+
const append = (annotation, line, background) => {
|
|
1554
|
+
if (annotation === void 0) return;
|
|
1555
|
+
for (const kind of ANNOTATION_KINDS) for (const item of annotation[kind]) {
|
|
1556
|
+
const language = item.language?.toLowerCase();
|
|
1557
|
+
let track = tracks[kind].get(language);
|
|
1558
|
+
if (track === void 0) {
|
|
1559
|
+
track = /* @__PURE__ */ new Map();
|
|
1560
|
+
tracks[kind].set(language, track);
|
|
1561
|
+
}
|
|
1562
|
+
let nodes = track.get(line);
|
|
1563
|
+
if (nodes === void 0) {
|
|
1564
|
+
nodes = [];
|
|
1565
|
+
track.set(line, nodes);
|
|
1566
|
+
}
|
|
1567
|
+
nodes.push(background === void 0 ? createXmlText(item.text) : createXmlElement("span", [["ttm:role", ROLE_BACKGROUND], ["for", context.keys.get(background)]], [createXmlText(item.text)]));
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1570
|
+
for (const line of lines) {
|
|
1571
|
+
append(line.annotation, line);
|
|
1572
|
+
for (const background of line.backgrounds) append(background.annotation, line, background);
|
|
1573
|
+
}
|
|
1574
|
+
return ANNOTATION_KINDS.flatMap((kind) => {
|
|
1575
|
+
return renderAnnotationTracks(tracks[kind], kind, context);
|
|
1576
|
+
});
|
|
1577
|
+
};
|
|
1578
|
+
var renderMetadata = (lyric, lines, context, includeMeta) => {
|
|
1579
|
+
const metadata = renderAgents(lyric, context);
|
|
1580
|
+
const itunes = [];
|
|
1581
|
+
if (includeMeta && lyric.meta !== void 0) {
|
|
1582
|
+
if (context.profile === "amll") metadata.push(...renderAmllMeta(lyric.meta));
|
|
1583
|
+
itunes.push(...renderSongwriters(lyric.meta));
|
|
1584
|
+
}
|
|
1585
|
+
if (context.profile === "itunes") itunes.push(...renderHeadAnnotations(lines, context));
|
|
1586
|
+
if (itunes.length > 0) metadata.push(createXmlElement("iTunesMetadata", [["xmlns", NAMESPACE_ITUNES_METADATA]], itunes));
|
|
1587
|
+
return createXmlElement("metadata", renderLanguage(void 0, lyric.meta?.language), metadata);
|
|
1588
|
+
};
|
|
1589
|
+
var renderRootAttributes = (lyric, profile) => {
|
|
1590
|
+
const attributes = [
|
|
1591
|
+
["xmlns", NAMESPACE_TTML],
|
|
1592
|
+
["xmlns:ttm", NAMESPACE_TTML_METADATA],
|
|
1593
|
+
["xmlns:tts", NAMESPACE_TTML_STYLING],
|
|
1594
|
+
["xmlns:itunes", NAMESPACE_ITUNES]
|
|
1595
|
+
];
|
|
1596
|
+
if (profile === "amll") attributes.push(["xmlns:amll", NAMESPACE_AMLL]);
|
|
1597
|
+
attributes.push(...renderLanguage(lyric.meta?.language), ["itunes:timing", lyric.timing === Timing.WORD ? TIMING_WORD : TIMING_LINE]);
|
|
1598
|
+
return attributes;
|
|
1599
|
+
};
|
|
1600
|
+
/**
|
|
1601
|
+
* Generates one TTML document, retaining the source profile unless explicitly overridden.
|
|
1602
|
+
*
|
|
1603
|
+
* Offsets are baked into timestamps; a negative result or a missing required time throws.
|
|
1604
|
+
* Profile mappings retain the lead agent and first AMLL ruby, omitting unsupported fields and instrumental nodes.
|
|
1605
|
+
*/
|
|
1606
|
+
var generateTtml = (lyric, options = {}) => {
|
|
1607
|
+
if (lyric.timing !== Timing.LINE && lyric.timing !== Timing.WORD) throw new Error("TTML generation requires line or word timing");
|
|
1608
|
+
const resolved = resolveOptions(lyric, options);
|
|
1609
|
+
const lines = lyric.lines.filter((line) => {
|
|
1610
|
+
return line.type !== LineType.INSTRUMENTAL;
|
|
1611
|
+
});
|
|
1612
|
+
const context = makeContext(lyric, lines, resolved.profile);
|
|
1613
|
+
const metadata = renderMetadata(lyric, lines, context, resolved.includeMeta);
|
|
1614
|
+
const paragraphs = lines.map((line) => {
|
|
1615
|
+
return renderLine(line, context);
|
|
1616
|
+
});
|
|
1617
|
+
return serializeXml(createXmlElement("tt", renderRootAttributes(lyric, resolved.profile), [createXmlElement("head", [], [metadata]), createXmlElement("body", [], [createXmlElement("div", [], paragraphs)])]), { indent: resolved.indent });
|
|
1618
|
+
};
|
|
1619
|
+
//#endregion
|
|
1620
|
+
//#region src/formats/ttml/parser.ts
|
|
1621
|
+
var METADATA_NAMES = [
|
|
1622
|
+
"agent",
|
|
1623
|
+
"songwriter",
|
|
1624
|
+
"meta",
|
|
1625
|
+
"translation",
|
|
1626
|
+
"transliteration"
|
|
1627
|
+
];
|
|
1628
|
+
var WORD_SEPARATOR_PATTERN = /\p{White_Space}/u;
|
|
1629
|
+
var readTtmlProfile = (source) => {
|
|
1630
|
+
return AMLL_HINT_PATTERN.test(source) ? "amll" : "itunes";
|
|
1631
|
+
};
|
|
1632
|
+
var readSpanTime = (element) => {
|
|
1162
1633
|
const begin = getXmlAttribute(element, "begin");
|
|
1163
1634
|
if (!begin) return;
|
|
1164
1635
|
const start = parseTime(begin);
|
|
@@ -1169,120 +1640,345 @@ var readLineTime = (element) => {
|
|
|
1169
1640
|
end: end ? parseTime(end) : void 0
|
|
1170
1641
|
};
|
|
1171
1642
|
};
|
|
1643
|
+
var readLineTime = (element) => {
|
|
1644
|
+
return readSpanTime(element);
|
|
1645
|
+
};
|
|
1646
|
+
var readAgentType = (raw) => {
|
|
1647
|
+
switch (raw) {
|
|
1648
|
+
case "person": return {
|
|
1649
|
+
type: AgentType.PERSON,
|
|
1650
|
+
raw: void 0
|
|
1651
|
+
};
|
|
1652
|
+
case "group": return {
|
|
1653
|
+
type: AgentType.GROUP,
|
|
1654
|
+
raw: void 0
|
|
1655
|
+
};
|
|
1656
|
+
default: return {
|
|
1657
|
+
type: AgentType.OTHER,
|
|
1658
|
+
raw: raw ?? "other"
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
var readDeclaredTiming = (root) => {
|
|
1663
|
+
switch (getXmlQualifiedAttribute(root, "itunes:timing")) {
|
|
1664
|
+
case TIMING_WORD: return Timing.WORD;
|
|
1665
|
+
case TIMING_LINE: return Timing.LINE;
|
|
1666
|
+
default: return;
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1669
|
+
var readPreserveSpace = (element, inherited) => {
|
|
1670
|
+
switch (getXmlQualifiedAttribute(element, "xml:space")) {
|
|
1671
|
+
case "preserve": return true;
|
|
1672
|
+
case "default": return false;
|
|
1673
|
+
default: return inherited;
|
|
1674
|
+
}
|
|
1675
|
+
};
|
|
1676
|
+
var readLanguage = (element, inherited) => {
|
|
1677
|
+
const language = getXmlQualifiedAttribute(element, "xml:lang") ?? getXmlAttribute(element, "lang");
|
|
1678
|
+
return language === void 0 ? inherited : lowerTag(language);
|
|
1679
|
+
};
|
|
1680
|
+
var readText = (element, inherited) => {
|
|
1681
|
+
const text = getXmlTextContent(element);
|
|
1682
|
+
return readPreserveSpace(element, inherited) ? text : text.trim();
|
|
1683
|
+
};
|
|
1172
1684
|
/**
|
|
1173
|
-
*
|
|
1174
|
-
*
|
|
1175
|
-
* A span's own trailing space is part of the word it follows, so leading and trailing runs both survive.
|
|
1685
|
+
* Preserves the parser's whitespace segmentation when turning text into word drafts.
|
|
1176
1686
|
*/
|
|
1177
|
-
var splitWords = (text) => {
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
text
|
|
1181
|
-
space:
|
|
1687
|
+
var splitWords = (text, language) => {
|
|
1688
|
+
if (text === "") return [];
|
|
1689
|
+
if (!WORD_SEPARATOR_PATTERN.test(text)) return [{
|
|
1690
|
+
text,
|
|
1691
|
+
space: false,
|
|
1692
|
+
language,
|
|
1182
1693
|
rubies: []
|
|
1183
|
-
}
|
|
1694
|
+
}];
|
|
1695
|
+
const words = [];
|
|
1696
|
+
for (const run of text.split(/(\p{White_Space}+)/u)) if (run !== "") {
|
|
1697
|
+
const space = /^\p{White_Space}+$/u.test(run);
|
|
1698
|
+
words.push({
|
|
1699
|
+
text: run,
|
|
1700
|
+
space,
|
|
1701
|
+
language: space ? void 0 : language,
|
|
1702
|
+
rubies: []
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1184
1705
|
return words;
|
|
1185
1706
|
};
|
|
1186
1707
|
/**
|
|
1708
|
+
* Distinguishes indentation runs from separators using the parser's existing rule.
|
|
1709
|
+
*/
|
|
1710
|
+
var isFormattingWhitespace = (text) => {
|
|
1711
|
+
return text.trim() === "" && text.includes("\n");
|
|
1712
|
+
};
|
|
1713
|
+
/**
|
|
1714
|
+
* Builds words from source drafts without deriving missing timestamps.
|
|
1715
|
+
*/
|
|
1716
|
+
var buildWords = (draft) => {
|
|
1717
|
+
const words = [];
|
|
1718
|
+
for (const word of draft.words) {
|
|
1719
|
+
if (word.space) {
|
|
1720
|
+
words.push(makeWordSpace({ text: word.text }));
|
|
1721
|
+
continue;
|
|
1722
|
+
}
|
|
1723
|
+
const time = word.start === void 0 ? void 0 : makeTimeRange({
|
|
1724
|
+
start: word.start,
|
|
1725
|
+
end: word.end
|
|
1726
|
+
});
|
|
1727
|
+
if (word.rubies.length === 0) {
|
|
1728
|
+
words.push(makeWordNormal({
|
|
1729
|
+
text: word.text,
|
|
1730
|
+
time,
|
|
1731
|
+
language: word.language
|
|
1732
|
+
}));
|
|
1733
|
+
continue;
|
|
1734
|
+
}
|
|
1735
|
+
const rubies = word.rubies.map((ruby) => {
|
|
1736
|
+
return makeWordAnnotationRuby({
|
|
1737
|
+
time: ruby.start === void 0 ? void 0 : makeTimeRange({
|
|
1738
|
+
start: ruby.start,
|
|
1739
|
+
end: ruby.end
|
|
1740
|
+
}),
|
|
1741
|
+
language: ruby.language,
|
|
1742
|
+
tokens: ruby.tokens.map((token) => {
|
|
1743
|
+
return makeWordAnnotationToken({
|
|
1744
|
+
text: token.text,
|
|
1745
|
+
time: token.start === void 0 ? void 0 : makeTimeRange({
|
|
1746
|
+
start: token.start,
|
|
1747
|
+
end: token.end
|
|
1748
|
+
})
|
|
1749
|
+
});
|
|
1750
|
+
})
|
|
1751
|
+
});
|
|
1752
|
+
});
|
|
1753
|
+
words.push(makeWordNormal({
|
|
1754
|
+
text: word.text,
|
|
1755
|
+
time,
|
|
1756
|
+
language: word.language,
|
|
1757
|
+
annotation: makeWordAnnotation({ rubies })
|
|
1758
|
+
}));
|
|
1759
|
+
}
|
|
1760
|
+
return words;
|
|
1761
|
+
};
|
|
1762
|
+
/**
|
|
1763
|
+
* Builds only annotations explicitly present in the source drafts.
|
|
1764
|
+
*/
|
|
1765
|
+
var buildLineAnnotation = (draft) => {
|
|
1766
|
+
if (draft.translations.length === 0 && draft.romans.length === 0) return;
|
|
1767
|
+
return makeLineAnnotation({
|
|
1768
|
+
translations: draft.translations.map((item) => {
|
|
1769
|
+
return makeLineAnnotationTranslation({
|
|
1770
|
+
text: item.text,
|
|
1771
|
+
language: item.language
|
|
1772
|
+
});
|
|
1773
|
+
}),
|
|
1774
|
+
romans: draft.romans.map((item) => {
|
|
1775
|
+
return makeLineAnnotationRoman({
|
|
1776
|
+
text: item.text,
|
|
1777
|
+
language: item.language
|
|
1778
|
+
});
|
|
1779
|
+
})
|
|
1780
|
+
});
|
|
1781
|
+
};
|
|
1782
|
+
/**
|
|
1783
|
+
* Separates head annotation text from its background references.
|
|
1784
|
+
*/
|
|
1785
|
+
var splitBackgroundText = (element, preserveSpace, language) => {
|
|
1786
|
+
preserveSpace = readPreserveSpace(element, preserveSpace);
|
|
1787
|
+
language = readLanguage(element, language);
|
|
1788
|
+
let main = "";
|
|
1789
|
+
const backgrounds = [];
|
|
1790
|
+
for (const child of element.children) {
|
|
1791
|
+
if (child.nodeType === XmlNodeType.element && child.localName === "span" && getXmlAttribute(child, "role") === "x-bg") {
|
|
1792
|
+
backgrounds.push({
|
|
1793
|
+
text: readText(child, preserveSpace),
|
|
1794
|
+
key: getXmlAttribute(child, "for"),
|
|
1795
|
+
language: readLanguage(child, language)
|
|
1796
|
+
});
|
|
1797
|
+
continue;
|
|
1798
|
+
}
|
|
1799
|
+
main += getXmlTextContent(child);
|
|
1800
|
+
}
|
|
1801
|
+
return {
|
|
1802
|
+
main: preserveSpace ? main : main.trim(),
|
|
1803
|
+
backgrounds
|
|
1804
|
+
};
|
|
1805
|
+
};
|
|
1806
|
+
/**
|
|
1807
|
+
* Carries language and whitespace declarations through wrappers to the elements they govern.
|
|
1808
|
+
*/
|
|
1809
|
+
var groupElements = (root, names, preserveSpace, stopAtMatch = false, language) => {
|
|
1810
|
+
const wanted = new Set(names);
|
|
1811
|
+
const result = /* @__PURE__ */ new Map();
|
|
1812
|
+
const stack = [{
|
|
1813
|
+
element: root,
|
|
1814
|
+
preserveSpace,
|
|
1815
|
+
language
|
|
1816
|
+
}];
|
|
1817
|
+
while (stack.length > 0) {
|
|
1818
|
+
const entry = stack.pop();
|
|
1819
|
+
if (entry === void 0) continue;
|
|
1820
|
+
const element = entry.element;
|
|
1821
|
+
const currentSpace = readPreserveSpace(element, entry.preserveSpace);
|
|
1822
|
+
const currentLanguage = readLanguage(element, entry.language);
|
|
1823
|
+
if (wanted.has(element.localName)) {
|
|
1824
|
+
const group = result.get(element.localName);
|
|
1825
|
+
const context = {
|
|
1826
|
+
element,
|
|
1827
|
+
preserveSpace: currentSpace,
|
|
1828
|
+
language: currentLanguage
|
|
1829
|
+
};
|
|
1830
|
+
if (group === void 0) result.set(element.localName, [context]);
|
|
1831
|
+
else group.push(context);
|
|
1832
|
+
if (stopAtMatch) continue;
|
|
1833
|
+
}
|
|
1834
|
+
const children = element.children;
|
|
1835
|
+
for (let index = children.length - 1; index >= 0; index -= 1) {
|
|
1836
|
+
const child = children[index];
|
|
1837
|
+
if (child && child.nodeType === XmlNodeType.element) stack.push({
|
|
1838
|
+
element: child,
|
|
1839
|
+
preserveSpace: currentSpace,
|
|
1840
|
+
language: currentLanguage
|
|
1841
|
+
});
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
return result;
|
|
1845
|
+
};
|
|
1846
|
+
var findElements = (root, name, preserveSpace, stopAtMatch = false, language) => {
|
|
1847
|
+
return groupElements(root, [name], preserveSpace, stopAtMatch, language).get(name) ?? [];
|
|
1848
|
+
};
|
|
1849
|
+
/**
|
|
1187
1850
|
* Reads an AMLL ruby container into one word plus its per-syllable annotation.
|
|
1188
1851
|
*/
|
|
1189
|
-
var readRuby = (container) => {
|
|
1852
|
+
var readRuby = (container, preserveSpace, language) => {
|
|
1853
|
+
preserveSpace = readPreserveSpace(container, preserveSpace);
|
|
1854
|
+
language = readLanguage(container, language);
|
|
1190
1855
|
let base = "";
|
|
1856
|
+
let baseLanguage = language;
|
|
1857
|
+
let rubyTime;
|
|
1858
|
+
let rubyLanguage = language;
|
|
1191
1859
|
const tokens = [];
|
|
1192
1860
|
for (const child of getXmlChildElements(container, "span")) {
|
|
1193
1861
|
const kind = getXmlAttribute(child, "ruby");
|
|
1194
1862
|
if (kind === "base") {
|
|
1195
|
-
base =
|
|
1863
|
+
base = readText(child, preserveSpace);
|
|
1864
|
+
baseLanguage = readLanguage(child, language);
|
|
1196
1865
|
continue;
|
|
1197
1866
|
}
|
|
1198
1867
|
if (kind !== "textContainer") continue;
|
|
1868
|
+
rubyTime ??= readSpanTime(child);
|
|
1869
|
+
rubyLanguage = readLanguage(child, language);
|
|
1870
|
+
const rubySpace = readPreserveSpace(child, preserveSpace);
|
|
1199
1871
|
for (const text of getXmlChildElements(child, "span")) {
|
|
1200
1872
|
if (getXmlAttribute(text, "ruby") !== "text") continue;
|
|
1201
1873
|
const time = readSpanTime(text);
|
|
1202
|
-
const content =
|
|
1203
|
-
if (
|
|
1874
|
+
const content = readText(text, rubySpace);
|
|
1875
|
+
if (!content) continue;
|
|
1204
1876
|
tokens.push({
|
|
1205
|
-
start: time
|
|
1206
|
-
end: time
|
|
1877
|
+
start: time?.start,
|
|
1878
|
+
end: time?.end,
|
|
1207
1879
|
text: content
|
|
1208
1880
|
});
|
|
1209
1881
|
}
|
|
1210
1882
|
}
|
|
1211
|
-
|
|
1212
|
-
const
|
|
1213
|
-
|
|
1883
|
+
if (!base) return;
|
|
1884
|
+
const first = tokens.find((token) => {
|
|
1885
|
+
return token.start !== void 0;
|
|
1886
|
+
});
|
|
1887
|
+
const last = tokens.findLast((token) => {
|
|
1888
|
+
return token.start !== void 0;
|
|
1889
|
+
});
|
|
1890
|
+
const time = readSpanTime(container) ?? rubyTime ?? (first === void 0 ? void 0 : {
|
|
1891
|
+
start: first.start,
|
|
1892
|
+
end: last?.end
|
|
1893
|
+
});
|
|
1214
1894
|
return {
|
|
1215
1895
|
text: base,
|
|
1216
1896
|
space: false,
|
|
1217
|
-
start:
|
|
1218
|
-
end:
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1897
|
+
start: time?.start,
|
|
1898
|
+
end: time?.end,
|
|
1899
|
+
language: baseLanguage,
|
|
1900
|
+
rubies: tokens.length === 0 ? [] : [{
|
|
1901
|
+
start: rubyTime?.start,
|
|
1902
|
+
end: rubyTime?.end,
|
|
1903
|
+
language: rubyLanguage,
|
|
1222
1904
|
tokens
|
|
1223
1905
|
}]
|
|
1224
1906
|
};
|
|
1225
1907
|
};
|
|
1226
1908
|
/**
|
|
1227
|
-
* Reports whether a text run holds nothing but formatting whitespace, which only a pretty-printed source produces.
|
|
1228
|
-
*/
|
|
1229
|
-
var isFormattingWhitespace = (text) => {
|
|
1230
|
-
return text.trim() === "" && text.includes("\n");
|
|
1231
|
-
};
|
|
1232
|
-
/**
|
|
1233
1909
|
* Collects the words of one span-bearing element.
|
|
1234
1910
|
*
|
|
1235
|
-
* Text between spans carries the separators, and a span without
|
|
1911
|
+
* Text between spans carries the separators, and a span without a readable begin contributes its text untimed.
|
|
1236
1912
|
* A span carrying a role is annotation rather than a word, so it is skipped here.
|
|
1237
|
-
*
|
|
1913
|
+
* Whitespace inside a span remains a separator; indentation is dropped only without a preserve declaration.
|
|
1238
1914
|
*/
|
|
1239
|
-
var readWords = (element) => {
|
|
1915
|
+
var readWords = (element, preserveSpace, language) => {
|
|
1916
|
+
preserveSpace = readPreserveSpace(element, preserveSpace);
|
|
1917
|
+
language = readLanguage(element, language);
|
|
1240
1918
|
const words = [];
|
|
1241
1919
|
let wordLevel = false;
|
|
1242
1920
|
for (const child of element.children) {
|
|
1243
|
-
if (child.nodeType
|
|
1244
|
-
if (!isFormattingWhitespace(child.text)) words.push(...splitWords(child.text));
|
|
1921
|
+
if (child.nodeType !== XmlNodeType.element) {
|
|
1922
|
+
if (preserveSpace || !isFormattingWhitespace(child.text)) words.push(...splitWords(child.text, language));
|
|
1245
1923
|
continue;
|
|
1246
1924
|
}
|
|
1247
|
-
if (child.
|
|
1925
|
+
if (child.localName !== "span") continue;
|
|
1248
1926
|
if (getXmlAttribute(child, "role") !== void 0) continue;
|
|
1249
1927
|
if (getXmlAttribute(child, "ruby") === "container") {
|
|
1250
|
-
const ruby = readRuby(child);
|
|
1928
|
+
const ruby = readRuby(child, preserveSpace, language);
|
|
1251
1929
|
if (ruby !== void 0) {
|
|
1252
|
-
words.push(
|
|
1253
|
-
|
|
1930
|
+
for (const part of splitWords(ruby.text)) words.push(part.space ? part : {
|
|
1931
|
+
...ruby,
|
|
1932
|
+
text: part.text
|
|
1933
|
+
});
|
|
1934
|
+
wordLevel ||= ruby.start !== void 0;
|
|
1254
1935
|
}
|
|
1255
1936
|
continue;
|
|
1256
1937
|
}
|
|
1257
|
-
const text = getXmlTextContent(child)
|
|
1258
|
-
|
|
1938
|
+
const text = getXmlTextContent(child);
|
|
1939
|
+
const childSpace = readPreserveSpace(child, preserveSpace);
|
|
1940
|
+
if (!text || !childSpace && isFormattingWhitespace(text)) continue;
|
|
1259
1941
|
const time = readSpanTime(child);
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1942
|
+
for (const part of splitWords(text, readLanguage(child, language))) {
|
|
1943
|
+
if (!part.space && time !== void 0) {
|
|
1944
|
+
part.start = time.start;
|
|
1945
|
+
part.end = time.end;
|
|
1946
|
+
wordLevel = true;
|
|
1947
|
+
}
|
|
1948
|
+
words.push(part);
|
|
1267
1949
|
}
|
|
1268
|
-
wordLevel = true;
|
|
1269
|
-
words.push({
|
|
1270
|
-
text,
|
|
1271
|
-
space: false,
|
|
1272
|
-
start: time.start,
|
|
1273
|
-
end: time.end,
|
|
1274
|
-
rubies: []
|
|
1275
|
-
});
|
|
1276
1950
|
}
|
|
1277
1951
|
return {
|
|
1278
1952
|
words,
|
|
1279
1953
|
wordLevel
|
|
1280
1954
|
};
|
|
1281
1955
|
};
|
|
1956
|
+
var readInlineAnnotations = (element, preserveSpace, language) => {
|
|
1957
|
+
const translations = [];
|
|
1958
|
+
const romans = [];
|
|
1959
|
+
for (const span of getXmlChildElements(element, "span")) {
|
|
1960
|
+
const role = getXmlAttribute(span, "role");
|
|
1961
|
+
if (role !== "x-translation" && role !== "x-roman") continue;
|
|
1962
|
+
const text = readText(span, preserveSpace);
|
|
1963
|
+
if (!text) continue;
|
|
1964
|
+
const item = {
|
|
1965
|
+
language: readLanguage(span, language),
|
|
1966
|
+
text
|
|
1967
|
+
};
|
|
1968
|
+
if (role === "x-translation") translations.push(item);
|
|
1969
|
+
else romans.push(item);
|
|
1970
|
+
}
|
|
1971
|
+
return {
|
|
1972
|
+
translations,
|
|
1973
|
+
romans
|
|
1974
|
+
};
|
|
1975
|
+
};
|
|
1282
1976
|
/**
|
|
1283
1977
|
* Reads one paragraph, pulling its inline annotations and background lines out of the word stream.
|
|
1284
1978
|
*/
|
|
1285
|
-
var readLine = (element) => {
|
|
1979
|
+
var readLine = (element, preserveSpace, language) => {
|
|
1980
|
+
preserveSpace = readPreserveSpace(element, preserveSpace);
|
|
1981
|
+
language = readLanguage(element, language);
|
|
1286
1982
|
const time = readLineTime(element);
|
|
1287
1983
|
if (time === void 0) return;
|
|
1288
1984
|
const draft = {
|
|
@@ -1294,33 +1990,25 @@ var readLine = (element) => {
|
|
|
1294
1990
|
translations: [],
|
|
1295
1991
|
romans: [],
|
|
1296
1992
|
backgrounds: [],
|
|
1297
|
-
wordLevel: false
|
|
1993
|
+
wordLevel: false,
|
|
1994
|
+
preserveSpace,
|
|
1995
|
+
language
|
|
1298
1996
|
};
|
|
1299
1997
|
if (!hasXmlChildElement(element, "span")) {
|
|
1300
|
-
const plain =
|
|
1998
|
+
const plain = readText(element, preserveSpace);
|
|
1301
1999
|
if (!plain) return;
|
|
1302
|
-
draft.words = splitWords(plain);
|
|
2000
|
+
draft.words = splitWords(plain, language);
|
|
1303
2001
|
return draft;
|
|
1304
2002
|
}
|
|
1305
|
-
const read = readWords(element);
|
|
2003
|
+
const read = readWords(element, preserveSpace, language);
|
|
1306
2004
|
draft.words = read.words;
|
|
1307
2005
|
draft.wordLevel = read.wordLevel;
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
continue;
|
|
1315
|
-
}
|
|
1316
|
-
const text = getXmlTextContent(span).trim();
|
|
1317
|
-
if (!text) continue;
|
|
1318
|
-
const item = {
|
|
1319
|
-
language: lowerTag(getXmlAttribute(span, "lang")),
|
|
1320
|
-
text
|
|
1321
|
-
};
|
|
1322
|
-
if (role === "x-translation") draft.translations.push(item);
|
|
1323
|
-
else if (role === "x-roman") draft.romans.push(item);
|
|
2006
|
+
const annotations = readInlineAnnotations(element, preserveSpace, language);
|
|
2007
|
+
draft.translations = annotations.translations;
|
|
2008
|
+
draft.romans = annotations.romans;
|
|
2009
|
+
for (const span of getXmlChildElements(element, "span")) if (getXmlAttribute(span, "role") === "x-bg") {
|
|
2010
|
+
const background = readBackground(span, preserveSpace, language);
|
|
2011
|
+
if (background) draft.backgrounds.push(background);
|
|
1324
2012
|
}
|
|
1325
2013
|
return draft;
|
|
1326
2014
|
};
|
|
@@ -1329,8 +2017,10 @@ var readLine = (element) => {
|
|
|
1329
2017
|
*
|
|
1330
2018
|
* A background line states no time of its own in AMLL files, so its range comes from its words.
|
|
1331
2019
|
*/
|
|
1332
|
-
var readBackground = (element) => {
|
|
1333
|
-
|
|
2020
|
+
var readBackground = (element, preserveSpace, language) => {
|
|
2021
|
+
preserveSpace = readPreserveSpace(element, preserveSpace);
|
|
2022
|
+
language = readLanguage(element, language);
|
|
2023
|
+
const read = readWords(element, preserveSpace, language);
|
|
1334
2024
|
if (read.words.length === 0) return;
|
|
1335
2025
|
const begin = getXmlAttribute(element, "begin");
|
|
1336
2026
|
const end = getXmlAttribute(element, "end");
|
|
@@ -1348,77 +2038,19 @@ var readBackground = (element) => {
|
|
|
1348
2038
|
agentId: getXmlAttribute(element, "agent"),
|
|
1349
2039
|
key: getXmlAttribute(element, "key"),
|
|
1350
2040
|
words: read.words,
|
|
1351
|
-
|
|
1352
|
-
romans: [],
|
|
2041
|
+
...readInlineAnnotations(element, preserveSpace, language),
|
|
1353
2042
|
backgrounds: [],
|
|
1354
|
-
wordLevel: read.wordLevel
|
|
2043
|
+
wordLevel: read.wordLevel,
|
|
2044
|
+
preserveSpace,
|
|
2045
|
+
language
|
|
1355
2046
|
};
|
|
1356
2047
|
};
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
if (word.space) {
|
|
1364
|
-
words.push(makeWordSpace({ text: word.text }));
|
|
1365
|
-
continue;
|
|
1366
|
-
}
|
|
1367
|
-
const time = word.start === void 0 ? void 0 : makeTimeRange({
|
|
1368
|
-
start: word.start,
|
|
1369
|
-
end: word.end
|
|
1370
|
-
});
|
|
1371
|
-
if (word.rubies.length === 0) {
|
|
1372
|
-
words.push(makeWordNormal({
|
|
1373
|
-
text: word.text,
|
|
1374
|
-
time
|
|
1375
|
-
}));
|
|
1376
|
-
continue;
|
|
1377
|
-
}
|
|
1378
|
-
const rubies = word.rubies.map((ruby) => {
|
|
1379
|
-
return makeWordAnnotationRuby({
|
|
1380
|
-
time: makeTimeRange({
|
|
1381
|
-
start: ruby.start,
|
|
1382
|
-
end: ruby.end
|
|
1383
|
-
}),
|
|
1384
|
-
tokens: ruby.tokens.map((token) => {
|
|
1385
|
-
return makeWordAnnotationToken({
|
|
1386
|
-
text: token.text,
|
|
1387
|
-
time: makeTimeRange({
|
|
1388
|
-
start: token.start,
|
|
1389
|
-
end: token.end
|
|
1390
|
-
})
|
|
1391
|
-
});
|
|
1392
|
-
})
|
|
1393
|
-
});
|
|
1394
|
-
});
|
|
1395
|
-
words.push(makeWordNormal({
|
|
1396
|
-
text: word.text,
|
|
1397
|
-
time,
|
|
1398
|
-
annotation: makeWordAnnotation({ rubies })
|
|
1399
|
-
}));
|
|
1400
|
-
}
|
|
1401
|
-
return words;
|
|
1402
|
-
};
|
|
1403
|
-
/**
|
|
1404
|
-
* Builds a line's annotation container, which the model only keeps when it holds something.
|
|
1405
|
-
*/
|
|
1406
|
-
var buildLineAnnotation = (draft) => {
|
|
1407
|
-
if (draft.translations.length === 0 && draft.romans.length === 0) return;
|
|
1408
|
-
return makeLineAnnotation({
|
|
1409
|
-
translations: draft.translations.map((item) => {
|
|
1410
|
-
return makeLineAnnotationTranslation({
|
|
1411
|
-
text: item.text,
|
|
1412
|
-
language: item.language
|
|
1413
|
-
});
|
|
1414
|
-
}),
|
|
1415
|
-
romans: draft.romans.map((item) => {
|
|
1416
|
-
return makeLineAnnotationRoman({
|
|
1417
|
-
text: item.text,
|
|
1418
|
-
language: item.language
|
|
1419
|
-
});
|
|
1420
|
-
})
|
|
1421
|
-
});
|
|
2048
|
+
var buildContent = (draft) => {
|
|
2049
|
+
if (draft.wordLevel) return makeLineContentWords(buildWords(draft));
|
|
2050
|
+
const text = draft.words.map((word) => {
|
|
2051
|
+
return word.text;
|
|
2052
|
+
}).join("");
|
|
2053
|
+
return makeLineContentText(draft.preserveSpace ? text : text.trim());
|
|
1422
2054
|
};
|
|
1423
2055
|
/**
|
|
1424
2056
|
* Builds one background line.
|
|
@@ -1431,7 +2063,8 @@ var buildBackground = (draft) => {
|
|
|
1431
2063
|
end: draft.end
|
|
1432
2064
|
}),
|
|
1433
2065
|
agents: draft.agentId === void 0 ? [] : [draft.agentId],
|
|
1434
|
-
|
|
2066
|
+
languages: draft.language === void 0 ? [] : [draft.language],
|
|
2067
|
+
content: buildContent(draft),
|
|
1435
2068
|
annotation: buildLineAnnotation(draft)
|
|
1436
2069
|
});
|
|
1437
2070
|
};
|
|
@@ -1439,9 +2072,6 @@ var buildBackground = (draft) => {
|
|
|
1439
2072
|
* Builds one normal line, keeping plain text whole unless the source timed its words.
|
|
1440
2073
|
*/
|
|
1441
2074
|
var buildLine = (draft) => {
|
|
1442
|
-
const content = draft.wordLevel ? makeLineContentWords(buildWords(draft)) : makeLineContentText(draft.words.map((word) => {
|
|
1443
|
-
return word.text;
|
|
1444
|
-
}).join("").trim());
|
|
1445
2075
|
return makeLineNormal({
|
|
1446
2076
|
id: draft.key,
|
|
1447
2077
|
time: makeTimeRange({
|
|
@@ -1449,54 +2079,34 @@ var buildLine = (draft) => {
|
|
|
1449
2079
|
end: draft.end
|
|
1450
2080
|
}),
|
|
1451
2081
|
agents: draft.agentId === void 0 ? [] : [draft.agentId],
|
|
1452
|
-
|
|
2082
|
+
languages: draft.language === void 0 ? [] : [draft.language],
|
|
2083
|
+
content: buildContent(draft),
|
|
1453
2084
|
annotation: buildLineAnnotation(draft),
|
|
1454
2085
|
backgrounds: draft.backgrounds.map(buildBackground)
|
|
1455
2086
|
});
|
|
1456
2087
|
};
|
|
1457
2088
|
/**
|
|
1458
|
-
* Maps one iTunes agent type onto the model's.
|
|
1459
|
-
*
|
|
1460
|
-
* The model requires `raw` on `OTHER`, since it cannot tell whether the source stated that word or the reader gave up.
|
|
1461
|
-
*/
|
|
1462
|
-
var readAgentType = (raw) => {
|
|
1463
|
-
switch (raw) {
|
|
1464
|
-
case "person": return {
|
|
1465
|
-
type: AgentType.PERSON,
|
|
1466
|
-
raw: void 0
|
|
1467
|
-
};
|
|
1468
|
-
case "group": return {
|
|
1469
|
-
type: AgentType.GROUP,
|
|
1470
|
-
raw: void 0
|
|
1471
|
-
};
|
|
1472
|
-
default: return {
|
|
1473
|
-
type: AgentType.OTHER,
|
|
1474
|
-
raw: raw ?? "other"
|
|
1475
|
-
};
|
|
1476
|
-
}
|
|
1477
|
-
};
|
|
1478
|
-
/**
|
|
1479
2089
|
* Reads the head's agents, which lines reference by id.
|
|
1480
2090
|
*/
|
|
1481
|
-
var readAgents = (
|
|
1482
|
-
if (!metadata) return [];
|
|
2091
|
+
var readAgents = (elements) => {
|
|
1483
2092
|
const agents = [];
|
|
1484
|
-
for (const element of
|
|
2093
|
+
for (const { element, preserveSpace, language } of elements) {
|
|
1485
2094
|
const id = getXmlQualifiedAttribute(element, "xml:id") ?? getXmlAttribute(element, "id");
|
|
1486
2095
|
if (!id) continue;
|
|
1487
2096
|
const names = getXmlChildElements(element, "name").map((name) => {
|
|
1488
|
-
return
|
|
2097
|
+
return makeText({
|
|
2098
|
+
text: readText(name, preserveSpace),
|
|
2099
|
+
language: readLanguage(name, language)
|
|
2100
|
+
});
|
|
1489
2101
|
}).filter((name) => {
|
|
1490
|
-
return name !== "";
|
|
2102
|
+
return name.text !== "";
|
|
1491
2103
|
});
|
|
1492
2104
|
const kind = readAgentType(getXmlAttribute(element, "type"));
|
|
1493
2105
|
agents.push(makeAgent({
|
|
1494
2106
|
id,
|
|
1495
2107
|
type: kind.type,
|
|
1496
2108
|
raw: kind.raw,
|
|
1497
|
-
names
|
|
1498
|
-
return makeText({ text: name });
|
|
1499
|
-
})
|
|
2109
|
+
names
|
|
1500
2110
|
}));
|
|
1501
2111
|
}
|
|
1502
2112
|
return agents;
|
|
@@ -1506,43 +2116,56 @@ var readAgents = (metadata) => {
|
|
|
1506
2116
|
*
|
|
1507
2117
|
* AMLL wraps these in `iTunesMetadata`, so the whole head is searched rather than its direct children.
|
|
1508
2118
|
*/
|
|
1509
|
-
var readCredits = (
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
2119
|
+
var readCredits = (elements, meta) => {
|
|
2120
|
+
const names = elements.map(({ element, preserveSpace, language }) => {
|
|
2121
|
+
return makeText({
|
|
2122
|
+
text: readText(element, preserveSpace),
|
|
2123
|
+
language
|
|
2124
|
+
});
|
|
1513
2125
|
}).filter((name) => {
|
|
1514
|
-
return name !== "";
|
|
2126
|
+
return name.text !== "";
|
|
1515
2127
|
});
|
|
1516
2128
|
if (names.length === 0) return;
|
|
1517
2129
|
meta.credits.push(makeMetaCredit({
|
|
1518
2130
|
role: CreditRole.LYRICIST,
|
|
1519
|
-
names
|
|
1520
|
-
return makeText({ text: name });
|
|
1521
|
-
})
|
|
2131
|
+
names
|
|
1522
2132
|
}));
|
|
1523
2133
|
};
|
|
1524
2134
|
/**
|
|
1525
2135
|
* Reads one `amll:meta` entry, keeping an unknown key as written so a round trip loses nothing.
|
|
1526
2136
|
*/
|
|
1527
|
-
var applyAmllMeta = (meta, element) => {
|
|
2137
|
+
var applyAmllMeta = (meta, element, language) => {
|
|
1528
2138
|
const key = getXmlAttribute(element, "key");
|
|
1529
2139
|
const value = getXmlAttribute(element, "value")?.trim();
|
|
1530
2140
|
if (!key || !value) return;
|
|
2141
|
+
language = readLanguage(element, language);
|
|
1531
2142
|
switch (key) {
|
|
1532
2143
|
case "musicName":
|
|
1533
|
-
meta.titles.push(makeText({
|
|
2144
|
+
meta.titles.push(makeText({
|
|
2145
|
+
text: value,
|
|
2146
|
+
language
|
|
2147
|
+
}));
|
|
1534
2148
|
return;
|
|
1535
2149
|
case "artists":
|
|
1536
|
-
meta.artists.push(makeText({
|
|
2150
|
+
meta.artists.push(makeText({
|
|
2151
|
+
text: value,
|
|
2152
|
+
language
|
|
2153
|
+
}));
|
|
1537
2154
|
return;
|
|
1538
2155
|
case "album":
|
|
1539
|
-
meta.albums.push(makeText({
|
|
2156
|
+
meta.albums.push(makeText({
|
|
2157
|
+
text: value,
|
|
2158
|
+
language
|
|
2159
|
+
}));
|
|
1540
2160
|
return;
|
|
1541
2161
|
case "isrc":
|
|
1542
2162
|
meta.isrcs.push(value);
|
|
1543
2163
|
return;
|
|
1544
2164
|
case "ttmlAuthorGithubLogin":
|
|
1545
|
-
meta.authors.push(makeText({
|
|
2165
|
+
meta.authors.push(makeText({
|
|
2166
|
+
text: value,
|
|
2167
|
+
language
|
|
2168
|
+
}));
|
|
1546
2169
|
return;
|
|
1547
2170
|
default: meta.unknowns.push(makeUnknownEntry({
|
|
1548
2171
|
key,
|
|
@@ -1551,52 +2174,33 @@ var applyAmllMeta = (meta, element) => {
|
|
|
1551
2174
|
}
|
|
1552
2175
|
};
|
|
1553
2176
|
/**
|
|
1554
|
-
* Splits one annotation text into its main part and any background pieces it embeds.
|
|
1555
|
-
*/
|
|
1556
|
-
var splitBackgroundText = (element) => {
|
|
1557
|
-
let main = "";
|
|
1558
|
-
const backgrounds = [];
|
|
1559
|
-
for (const child of element.children) {
|
|
1560
|
-
if (child.nodeType === XmlNodeType.element && child.localName === "span" && getXmlAttribute(child, "role") === "x-bg") {
|
|
1561
|
-
backgrounds.push({
|
|
1562
|
-
text: getXmlTextContent(child).trim(),
|
|
1563
|
-
key: getXmlAttribute(child, "for")
|
|
1564
|
-
});
|
|
1565
|
-
continue;
|
|
1566
|
-
}
|
|
1567
|
-
main += getXmlTextContent(child);
|
|
1568
|
-
}
|
|
1569
|
-
return {
|
|
1570
|
-
main: main.trim(),
|
|
1571
|
-
backgrounds
|
|
1572
|
-
};
|
|
1573
|
-
};
|
|
1574
|
-
/**
|
|
1575
2177
|
* Attaches head translations and transliterations to the lines they name by `itunes:key`.
|
|
1576
2178
|
*
|
|
1577
2179
|
* A replacement translation rewrites the line's text, which is how a source swaps one script for another.
|
|
1578
2180
|
*/
|
|
1579
2181
|
var attachHeadAnnotations = (metadata, lineMap, backgroundMap) => {
|
|
1580
|
-
if (
|
|
2182
|
+
if (lineMap.size === 0) return;
|
|
1581
2183
|
const attach = (blocks, kind) => {
|
|
1582
|
-
for (const block of blocks) {
|
|
1583
|
-
const language = lowerTag(getXmlAttribute(block, "lang"));
|
|
2184
|
+
for (const { element: block, preserveSpace, language } of blocks) {
|
|
1584
2185
|
const type = getXmlQualifiedAttribute(block, "type") ?? getXmlAttribute(block, "type");
|
|
1585
2186
|
for (const text of getXmlChildElements(block, "text")) {
|
|
1586
2187
|
const key = getXmlAttribute(text, "for");
|
|
1587
2188
|
const line = key ? lineMap.get(key) : void 0;
|
|
1588
2189
|
if (!line) continue;
|
|
1589
|
-
const
|
|
2190
|
+
const textLanguage = readLanguage(text, language);
|
|
2191
|
+
const { main, backgrounds } = splitBackgroundText(text, preserveSpace, textLanguage);
|
|
1590
2192
|
if (kind === "translation" && type === "replacement") {
|
|
1591
2193
|
if (main) {
|
|
1592
|
-
line.words = splitWords(main);
|
|
2194
|
+
line.words = splitWords(main, textLanguage);
|
|
1593
2195
|
line.wordLevel = false;
|
|
2196
|
+
line.preserveSpace = readPreserveSpace(text, preserveSpace);
|
|
2197
|
+
line.language = textLanguage;
|
|
1594
2198
|
}
|
|
1595
2199
|
continue;
|
|
1596
2200
|
}
|
|
1597
2201
|
if (main) {
|
|
1598
2202
|
const item = {
|
|
1599
|
-
language,
|
|
2203
|
+
language: textLanguage,
|
|
1600
2204
|
text: main
|
|
1601
2205
|
};
|
|
1602
2206
|
if (kind === "translation") line.translations.push(item);
|
|
@@ -1607,7 +2211,7 @@ var attachHeadAnnotations = (metadata, lineMap, backgroundMap) => {
|
|
|
1607
2211
|
const target = background.key ? backgroundMap.get(background.key) : void 0;
|
|
1608
2212
|
if (!target) continue;
|
|
1609
2213
|
const item = {
|
|
1610
|
-
language,
|
|
2214
|
+
language: background.language,
|
|
1611
2215
|
text: background.text
|
|
1612
2216
|
};
|
|
1613
2217
|
if (kind === "translation") target.translations.push(item);
|
|
@@ -1616,36 +2220,8 @@ var attachHeadAnnotations = (metadata, lineMap, backgroundMap) => {
|
|
|
1616
2220
|
}
|
|
1617
2221
|
}
|
|
1618
2222
|
};
|
|
1619
|
-
attach(
|
|
1620
|
-
attach(
|
|
1621
|
-
};
|
|
1622
|
-
/**
|
|
1623
|
-
* Returns the paragraphs of a body, including any nested inside a div.
|
|
1624
|
-
*/
|
|
1625
|
-
var findParagraphs = (body) => {
|
|
1626
|
-
const result = [];
|
|
1627
|
-
const stack = [body];
|
|
1628
|
-
while (stack.length > 0) {
|
|
1629
|
-
const element = stack.pop();
|
|
1630
|
-
if (!element) continue;
|
|
1631
|
-
if (element.localName === "p") {
|
|
1632
|
-
result.push(element);
|
|
1633
|
-
continue;
|
|
1634
|
-
}
|
|
1635
|
-
const children = element.children;
|
|
1636
|
-
for (let index = children.length - 1; index >= 0; index -= 1) {
|
|
1637
|
-
const child = children[index];
|
|
1638
|
-
if (child && child.nodeType === XmlNodeType.element) stack.push(child);
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
return result;
|
|
1642
|
-
};
|
|
1643
|
-
var readDeclaredTiming = (root) => {
|
|
1644
|
-
switch (getXmlQualifiedAttribute(root, "itunes:timing")) {
|
|
1645
|
-
case TIMING_WORD: return Timing.WORD;
|
|
1646
|
-
case TIMING_LINE: return Timing.LINE;
|
|
1647
|
-
default: return;
|
|
1648
|
-
}
|
|
2223
|
+
attach(metadata.get("translation") ?? [], "translation");
|
|
2224
|
+
attach(metadata.get("transliteration") ?? [], "roman");
|
|
1649
2225
|
};
|
|
1650
2226
|
/**
|
|
1651
2227
|
* Reads a TTML document into the model.
|
|
@@ -1660,8 +2236,8 @@ var parseTtml = (input, options = {}) => {
|
|
|
1660
2236
|
status: LyricStatus.INVALID,
|
|
1661
2237
|
meta: makeMeta()
|
|
1662
2238
|
}));
|
|
1663
|
-
const extra = { profile: readTtmlProfile(content
|
|
1664
|
-
const document = parseXml(content
|
|
2239
|
+
const extra = { profile: readTtmlProfile(content) };
|
|
2240
|
+
const document = parseXml(content);
|
|
1665
2241
|
const root = document ? findXmlElement(document, "tt") : void 0;
|
|
1666
2242
|
if (!root) return finalizeLyric(makeLyric({
|
|
1667
2243
|
format: FORMAT,
|
|
@@ -1669,24 +2245,25 @@ var parseTtml = (input, options = {}) => {
|
|
|
1669
2245
|
meta: makeMeta(),
|
|
1670
2246
|
extra
|
|
1671
2247
|
}));
|
|
1672
|
-
const
|
|
1673
|
-
const
|
|
2248
|
+
const language = readLanguage(root, lowerTag(options.language));
|
|
2249
|
+
const body = findElements(root, "body", false, true, language)[0];
|
|
2250
|
+
const metadata = findElements(root, "metadata", false, true, language)[0];
|
|
2251
|
+
const metadataElements = metadata === void 0 ? /* @__PURE__ */ new Map() : groupElements(metadata.element, METADATA_NAMES, metadata.preserveSpace, false, metadata.language);
|
|
1674
2252
|
const meta = makeMeta();
|
|
1675
|
-
const language = lowerTag(getXmlQualifiedAttribute(root, "xml:lang")) ?? lowerTag(options.language);
|
|
1676
2253
|
if (language !== void 0) meta.language = language;
|
|
1677
|
-
readCredits(
|
|
1678
|
-
for (const element
|
|
2254
|
+
readCredits(metadataElements.get("songwriter") ?? [], meta);
|
|
2255
|
+
for (const { element, language: itemLanguage } of metadataElements.get("meta") ?? []) applyAmllMeta(meta, element, itemLanguage);
|
|
1679
2256
|
const drafts = [];
|
|
1680
2257
|
const lineMap = /* @__PURE__ */ new Map();
|
|
1681
2258
|
const backgroundMap = /* @__PURE__ */ new Map();
|
|
1682
|
-
for (const element of body ?
|
|
1683
|
-
const draft = readLine(element);
|
|
2259
|
+
for (const { element, preserveSpace, language: lineLanguage } of body ? findElements(body.element, "p", body.preserveSpace, true, body.language) : []) {
|
|
2260
|
+
const draft = readLine(element, preserveSpace, lineLanguage);
|
|
1684
2261
|
if (!draft) continue;
|
|
1685
2262
|
drafts.push(draft);
|
|
1686
2263
|
if (draft.key) lineMap.set(draft.key, draft);
|
|
1687
2264
|
for (const background of draft.backgrounds) if (background.key) backgroundMap.set(background.key, background);
|
|
1688
2265
|
}
|
|
1689
|
-
attachHeadAnnotations(
|
|
2266
|
+
attachHeadAnnotations(metadataElements, lineMap, backgroundMap);
|
|
1690
2267
|
if (drafts.length === 0) return finalizeLyric(makeLyric({
|
|
1691
2268
|
format: FORMAT,
|
|
1692
2269
|
status: LyricStatus.INVALID,
|
|
@@ -1695,7 +2272,9 @@ var parseTtml = (input, options = {}) => {
|
|
|
1695
2272
|
}));
|
|
1696
2273
|
const lines = drafts.map(buildLine);
|
|
1697
2274
|
const wordLevel = drafts.some((draft) => {
|
|
1698
|
-
return draft.wordLevel
|
|
2275
|
+
return draft.wordLevel || draft.backgrounds.some((background) => {
|
|
2276
|
+
return background.wordLevel;
|
|
2277
|
+
});
|
|
1699
2278
|
});
|
|
1700
2279
|
return finalizeLyric(makeLyric({
|
|
1701
2280
|
format: FORMAT,
|
|
@@ -1703,7 +2282,7 @@ var parseTtml = (input, options = {}) => {
|
|
|
1703
2282
|
timing: readDeclaredTiming(root) ?? (wordLevel ? Timing.WORD : Timing.LINE),
|
|
1704
2283
|
meta,
|
|
1705
2284
|
extra,
|
|
1706
|
-
agents: readAgents(
|
|
2285
|
+
agents: readAgents(metadataElements.get("agent") ?? []),
|
|
1707
2286
|
languages: deriveLanguageUsages(lines),
|
|
1708
2287
|
lines
|
|
1709
2288
|
}));
|
|
@@ -1731,18 +2310,21 @@ var parse = (input, options = {}) => {
|
|
|
1731
2310
|
case "ttml": return parseTtml(input, options);
|
|
1732
2311
|
}
|
|
1733
2312
|
};
|
|
2313
|
+
var GENERATORS = {
|
|
2314
|
+
lrc: generateLrc,
|
|
2315
|
+
ttml: generateTtml
|
|
2316
|
+
};
|
|
1734
2317
|
/**
|
|
1735
2318
|
* Generates text in the requested format, which need not match `lyric.format`.
|
|
1736
2319
|
*
|
|
1737
|
-
*
|
|
2320
|
+
* The writer map pairs each format's options with its result and requires a writer for every supported format.
|
|
2321
|
+
* An unknown target returns `null`.
|
|
1738
2322
|
*/
|
|
1739
2323
|
var generate = (lyric, target, options = {}) => {
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
case "ttml": throw new Error("ttml generation is not implemented yet");
|
|
1743
|
-
}
|
|
2324
|
+
if (!Object.hasOwn(GENERATORS, target)) return null;
|
|
2325
|
+
return GENERATORS[target](lyric, options);
|
|
1744
2326
|
};
|
|
1745
2327
|
//#endregion
|
|
1746
|
-
export {
|
|
2328
|
+
export { detectFormat, detectLrc, detectTtml, generate, generateLrc, generateTtml, parse, parseLrc, parseTtml };
|
|
1747
2329
|
|
|
1748
2330
|
//# sourceMappingURL=formats.js.map
|