@gondola-lyrics/kit 0.1.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/README.md CHANGED
@@ -20,7 +20,7 @@ npm install @gondola-lyrics/kit
20
20
  > This package is ESM-only.
21
21
  > In your TS config, use `module: node20` / `module: nodenext` / `moduleResolution: bundler`.
22
22
 
23
- Parsing returns a `runtime.Lyric`, so the result is ready to validate and hand to a player without a second conversion step.
23
+ Parsing returns a canonical `runtime.Lyric` together with the diagnostics it produced, so the result is ready to hand to a player without a second conversion step.
24
24
 
25
25
  ### Parse
26
26
 
@@ -28,14 +28,15 @@ Parsing returns a `runtime.Lyric`, so the result is ready to validate and hand t
28
28
  import { parse, parseLrc } from '@gondola-lyrics/kit'
29
29
 
30
30
  const content = `[00:01.00]Like the first time tangled by the fishing line`
31
- const lyric = parse(content)
31
+ const parsed = parse(content)
32
32
 
33
- lyric?.format // => 'lrc'
34
- lyric?.lines[0]?.time?.start // => 1000
33
+ parsed?.lyric.format // => 'lrc'
34
+ parsed?.lyric.lines[0]?.time?.start // => 1000
35
+ parsed?.diagnostics // => []
35
36
 
36
37
  // `parse` recognizes the format and returns `null` when it cannot.
37
38
  // `parseLrc` takes a known format and never returns `null`, so an unreadable source only carries `status: INVALID`.
38
- parseLrc('no timestamps here').status // => LyricStatus.INVALID
39
+ parseLrc('no timestamps here').lyric.status // => LyricStatus.INVALID
39
40
 
40
41
  // LRC keeps translations and romanizations in separate files, attached here by timestamp.
41
42
  const annotated = parseLrc({
@@ -47,8 +48,8 @@ const annotated = parseLrc({
47
48
  [00:03.00]tanguru do bai za rain`,
48
49
  })
49
50
 
50
- annotated.lines[0]?.annotation?.translations[0].text // => '就像第一次'
51
- annotated.lines[0]?.annotation?.romans[0].text // => 'raiku za fasuto taimu'
51
+ annotated.lyric.lines[0]?.annotation?.translations[0].text // => '就像第一次'
52
+ annotated.lyric.lines[0]?.annotation?.romans[0].text // => 'raiku za fasuto taimu'
52
53
 
53
54
  // An exact timestamp match is assigned before any fuzzy one, and rows matching no line are dropped.
54
55
  // A drifting track attaches within a tolerance, where the nearest unclaimed line wins.
@@ -59,10 +60,12 @@ const drifted = {
59
60
  [00:03.20]被渔线缠住那样`,
60
61
  }
61
62
 
62
- parseLrc(drifted).lines[0]?.annotation // => undefined
63
- parseLrc(drifted, { annotationTolerance: 500 }).lines[0]?.annotation?.translations[0].text // => '就像第一次'
63
+ parseLrc(drifted).lyric.lines[0]?.annotation // => undefined
64
+ parseLrc(drifted, { annotationTolerance: 500 }).lyric.lines[0]?.annotation?.translations[0].text // => '就像第一次'
64
65
  ```
65
66
 
67
+ Every parser canonicalizes its lyric and validates the result before returning, so `lyric` is already in canonical order and `diagnostics` is exactly what `@gondola-lyrics/model` reports for it.
68
+
66
69
  ### Generate
67
70
 
68
71
  `generate` takes the target format explicitly, so a lyric parsed from one format can be written out as another.
@@ -70,7 +73,7 @@ parseLrc(drifted, { annotationTolerance: 500 }).lines[0]?.annotation?.translatio
70
73
  ```ts
71
74
  import { generate, parseLrc } from '@gondola-lyrics/kit'
72
75
 
73
- const lyric = parseLrc('[00:01.00]Like the first time')
76
+ const { lyric } = parseLrc('[00:01.00]Like the first time')
74
77
 
75
78
  // Separate tracks come back as separate fields, so the result parses again unchanged.
76
79
  const { original, translations, romans } = generate(lyric, 'lrc')
@@ -93,14 +96,15 @@ Detection reads only the original text. It answers whether the source is recogni
93
96
 
94
97
  ### Validation
95
98
 
96
- The kit does not report diagnostics of its own. Parsing keeps the source as written, and `@gondola-lyrics/model` decides what is valid.
99
+ The kit does not invent diagnostics. It canonicalizes the lyric and hands back exactly what `@gondola-lyrics/model` reports for it.
97
100
 
98
101
  ```ts
99
- import { canonicalizeLyric, validateLyric } from '@gondola-lyrics/model/runtime'
102
+ const { lyric, diagnostics } = parseLrc(source)
100
103
 
101
- const lyric = parseLrc(source)
104
+ // The same list, computed by the kit on the way out.
105
+ import { validateLyric } from '@gondola-lyrics/model/runtime'
102
106
 
103
- validateLyric(canonicalizeLyric(lyric)) // => []
107
+ validateLyric(lyric) // deep-equals `diagnostics`
104
108
  ```
105
109
 
106
110
  ### Entries
package/dist/formats.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Diagnostic } from '@gondola-lyrics/model/runtime';
1
2
  import { runtime } from '@gondola-lyrics/model';
2
3
 
3
4
  /**
@@ -5,7 +6,7 @@ import { runtime } from '@gondola-lyrics/model';
5
6
  *
6
7
  * Unrecognized text returns `undefined`.
7
8
  */
8
- export declare const detectFormat: (input: unknown) => "lrc" | undefined;
9
+ export declare const detectFormat: (input: unknown) => LyricFormat | undefined;
9
10
 
10
11
  /**
11
12
  * A single readable time tag is enough for recognition; metadata alone is not.
@@ -13,10 +14,21 @@ export declare const detectFormat: (input: unknown) => "lrc" | undefined;
13
14
  */
14
15
  export declare const detectLrc: (input: unknown) => boolean;
15
16
 
17
+ /**
18
+ * Recognition needs a profile hint, since plain TTML says nothing about which profile wrote it.
19
+ *
20
+ * A bare `tt` root also counts, which is what a hand-written document has.
21
+ * This is a format probe, not a validity check, so an input of another shape is simply not TTML.
22
+ */
23
+ export declare const detectTtml: (input: unknown) => boolean;
24
+
16
25
  /**
17
26
  * Generates text in the requested format, which need not match `lyric.format`.
27
+ *
28
+ * The writer map pairs each format's options with its result and requires a writer for every supported format.
29
+ * An unknown target returns `null`.
18
30
  */
19
- export declare const generate: (lyric: runtime.Lyric, target: LyricFormat, options?: LrcGenerateOptions) => LrcGenerateResult;
31
+ export declare const generate: <Target extends LyricFormat>(lyric: runtime.Lyric, target: Target, options?: GenerateOptions[Target]) => GenerateResults[Target] | null;
20
32
 
21
33
  /**
22
34
  * Generates the LRC text plus the annotations a single file cannot hold.
@@ -25,6 +37,24 @@ export declare const generate: (lyric: runtime.Lyric, target: LyricFormat, optio
25
37
  */
26
38
  export declare const generateLrc: (lyric: runtime.Lyric, options?: LrcGenerateOptions) => LrcGenerateResult;
27
39
 
40
+ declare interface GenerateOptions {
41
+ lrc: LrcGenerateOptions;
42
+ ttml: TtmlGenerateOptions;
43
+ }
44
+
45
+ declare interface GenerateResults {
46
+ lrc: LrcGenerateResult;
47
+ ttml: TtmlGenerateResult;
48
+ }
49
+
50
+ /**
51
+ * Generates one TTML document, retaining the source profile unless explicitly overridden.
52
+ *
53
+ * Offsets are baked into timestamps; a negative result or a missing required time throws.
54
+ * Profile mappings retain the lead agent and first AMLL ruby, omitting unsupported fields and instrumental nodes.
55
+ */
56
+ export declare const generateTtml: (lyric: runtime.Lyric, options?: TtmlGenerateOptions) => TtmlGenerateResult;
57
+
28
58
  declare interface LrcContent {
29
59
  original: string;
30
60
  translations?: string;
@@ -65,21 +95,65 @@ export declare interface LrcParseOptions {
65
95
  /**
66
96
  * Values match the lowercase format names stored in `Lyric.format`.
67
97
  */
68
- export declare type LyricFormat = 'lrc';
98
+ export declare type LyricFormat = 'lrc' | 'ttml';
69
99
 
70
100
  /**
71
- * Unrecognized text returns `null`; a recognized format returns a lyric with its parser-assigned status.
101
+ * Unrecognized text returns `null`; a recognized format returns its canonical lyric and diagnostics.
72
102
  * Annotation tracks travel with the source and are attached by the format's parser.
73
103
  */
74
- export declare const parse: (input: unknown, options?: LrcParseOptions) => runtime.Lyric | null;
104
+ export declare const parse: (input: unknown, options?: LrcParseOptions & TtmlParseOptions) => ParseResult | null;
75
105
 
76
106
  /**
77
107
  * Source order, empty timed lines, and missing word times are preserved for model validation.
78
108
  *
79
109
  * Annotation tracks are read as lines too and attached by timestamp, and rows that match no line are dropped.
80
110
  *
81
- * `INVALID` means no readable timed lines; `VALID` does not imply model validation has passed.
111
+ * `INVALID` means no readable timed lines; the returned diagnostics carry whatever the canonical tree breaks.
82
112
  */
83
- export declare const parseLrc: (input: string | LrcParseInput, options?: LrcParseOptions) => runtime.Lyric;
113
+ export declare const parseLrc: (input: string | LrcParseInput, options?: LrcParseOptions) => ParseResult;
114
+
115
+ /**
116
+ * A parsed lyric in canonical form, with every rule it breaks.
117
+ */
118
+ export declare interface ParseResult {
119
+ lyric: runtime.Lyric;
120
+ diagnostics: Diagnostic[];
121
+ }
122
+
123
+ /**
124
+ * Reads a TTML document into the model.
125
+ *
126
+ * The profile only changes where metadata comes from, so one reader serves both.
127
+ * A document that parses but holds no timed paragraph is `INVALID` rather than an error.
128
+ */
129
+ export declare const parseTtml: (input: TtmlParseInput, options?: TtmlParseOptions) => ParseResult;
130
+
131
+ export declare interface TtmlGenerateOptions {
132
+ /**
133
+ * Defaults to the source TTML profile, or `amll` when the lyric came from another format.
134
+ */
135
+ profile?: TtmlProfile;
136
+ /**
137
+ * Indent document structure; defaults to `false`.
138
+ * Lyric content stays flush to keep formatting whitespace out of the words.
139
+ */
140
+ indent?: boolean;
141
+ /**
142
+ * Emit song metadata; defaults to `true`.
143
+ * Agents and annotation tracks are retained because the body references them.
144
+ */
145
+ includeMeta?: boolean;
146
+ }
147
+
148
+ export declare type TtmlGenerateResult = string;
149
+
150
+ export declare type TtmlParseInput = string;
151
+
152
+ export declare interface TtmlParseOptions {
153
+ /** Language tag the lyric declares; the document's own `xml:lang` wins when it states one. */
154
+ language?: string;
155
+ }
156
+
157
+ export declare type TtmlProfile = 'amll' | 'itunes';
84
158
 
85
159
  export { }