@gondola-lyrics/kit 0.0.0-stage → 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 folltoshe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,135 @@
1
- # Temporary Holding Version
1
+ # Gondola Lyric Kit For Node
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Gondola lyrics toolkit: parses and generates lyric.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @gondola-lyrics/kit
9
+ ```
10
+
11
+ ## Supports
12
+
13
+ | Format | Parse | Generate | Annotation tracks | Notes |
14
+ | ------ | ----- | -------- | ----------------- | -------------------------------------------------------------------- |
15
+ | LRC | ✅ | ✅ | ✅ | Line and enhanced word timing, metadata, translations, romanizations |
16
+
17
+ ## Usage
18
+
19
+ > [!IMPORTANT]
20
+ > This package is ESM-only.
21
+ > In your TS config, use `module: node20` / `module: nodenext` / `moduleResolution: bundler`.
22
+
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
+
25
+ ### Parse
26
+
27
+ ```ts
28
+ import { parse, parseLrc } from '@gondola-lyrics/kit'
29
+
30
+ const content = `[00:01.00]Like the first time tangled by the fishing line`
31
+ const parsed = parse(content)
32
+
33
+ parsed?.lyric.format // => 'lrc'
34
+ parsed?.lyric.lines[0]?.time?.start // => 1000
35
+ parsed?.diagnostics // => []
36
+
37
+ // `parse` recognizes the format and returns `null` when it cannot.
38
+ // `parseLrc` takes a known format and never returns `null`, so an unreadable source only carries `status: INVALID`.
39
+ parseLrc('no timestamps here').lyric.status // => LyricStatus.INVALID
40
+
41
+ // LRC keeps translations and romanizations in separate files, attached here by timestamp.
42
+ const annotated = parseLrc({
43
+ original: `[00:01.00]Like the first time
44
+ [00:03.00]tangled by the line`,
45
+ translations: `[00:01.00]就像第一次
46
+ [00:03.00]被渔线缠住那样`,
47
+ romans: `[00:01.00]raiku za fasuto taimu
48
+ [00:03.00]tanguru do bai za rain`,
49
+ })
50
+
51
+ annotated.lyric.lines[0]?.annotation?.translations[0].text // => '就像第一次'
52
+ annotated.lyric.lines[0]?.annotation?.romans[0].text // => 'raiku za fasuto taimu'
53
+
54
+ // An exact timestamp match is assigned before any fuzzy one, and rows matching no line are dropped.
55
+ // A drifting track attaches within a tolerance, where the nearest unclaimed line wins.
56
+ const drifted = {
57
+ original: `[00:01.00]Like the first time
58
+ [00:03.00]tangled by the line`,
59
+ translations: `[00:01.30]就像第一次
60
+ [00:03.20]被渔线缠住那样`,
61
+ }
62
+
63
+ parseLrc(drifted).lyric.lines[0]?.annotation // => undefined
64
+ parseLrc(drifted, { annotationTolerance: 500 }).lyric.lines[0]?.annotation?.translations[0].text // => '就像第一次'
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
+
69
+ ### Generate
70
+
71
+ `generate` takes the target format explicitly, so a lyric parsed from one format can be written out as another.
72
+
73
+ ```ts
74
+ import { generate, parseLrc } from '@gondola-lyrics/kit'
75
+
76
+ const { lyric } = parseLrc('[00:01.00]Like the first time')
77
+
78
+ // Separate tracks come back as separate fields, so the result parses again unchanged.
79
+ const { original, translations, romans } = generate(lyric, 'lrc')
80
+
81
+ parseLrc(generate(lyric, 'lrc')) // round-trips
82
+ ```
83
+
84
+ `generateLrc` is the same thing without the dispatch step.
85
+
86
+ ### Detect
87
+
88
+ ```ts
89
+ import { detectFormat } from '@gondola-lyrics/kit'
90
+
91
+ detectFormat('[00:01.00]Like the first time') // => 'lrc'
92
+ detectFormat('plain text without timestamps') // => undefined
93
+ ```
94
+
95
+ Detection reads only the original text. It answers whether the source is recognizable, not whether it is valid: a recognized source can still parse into an `INVALID` lyric.
96
+
97
+ ### Validation
98
+
99
+ The kit does not invent diagnostics. It canonicalizes the lyric and hands back exactly what `@gondola-lyrics/model` reports for it.
100
+
101
+ ```ts
102
+ const { lyric, diagnostics } = parseLrc(source)
103
+
104
+ // The same list, computed by the kit on the way out.
105
+ import { validateLyric } from '@gondola-lyrics/model/runtime'
106
+
107
+ validateLyric(lyric) // deep-equals `diagnostics`
108
+ ```
109
+
110
+ ### Entries
111
+
112
+ | Import | Contains |
113
+ | ------------------------------- | ---------- |
114
+ | `@gondola-lyrics/kit` | Everything |
115
+ | `@gondola-lyrics/kit/parser` | Parsing |
116
+ | `@gondola-lyrics/kit/generator` | Generation |
117
+
118
+ ## Commands
119
+
120
+ **Build**
121
+
122
+ ```bash
123
+ npm run build
124
+ ```
125
+
126
+ **Check**
127
+
128
+ ```bash
129
+ npm run check
130
+ npm run format
131
+ ```
132
+
133
+ ## License
134
+
135
+ [MIT](./LICENSE)
@@ -0,0 +1,133 @@
1
+ import { Diagnostic } from '@gondola-lyrics/model/runtime';
2
+ import { runtime } from '@gondola-lyrics/model';
3
+
4
+ export declare const AMLL_HINT_PATTERN: RegExp;
5
+
6
+ /**
7
+ * Recognition reads only the original text; the annotation tracks never decide the format.
8
+ *
9
+ * Unrecognized text returns `undefined`.
10
+ */
11
+ export declare const detectFormat: (input: unknown) => "lrc" | "ttml" | undefined;
12
+
13
+ /**
14
+ * A single readable time tag is enough for recognition; metadata alone is not.
15
+ * This is a format probe, not a validity check, so an input of another shape is simply not LRC.
16
+ */
17
+ export declare const detectLrc: (input: unknown) => boolean;
18
+
19
+ /**
20
+ * Recognition needs a profile hint, since plain TTML says nothing about which profile wrote it.
21
+ *
22
+ * A bare `tt` root also counts, which is what a hand-written document has.
23
+ * This is a format probe, not a validity check, so an input of another shape is simply not TTML.
24
+ */
25
+ export declare const detectTtml: (input: unknown) => boolean;
26
+
27
+ export declare const FORMAT = "ttml";
28
+
29
+ /**
30
+ * Generates text in the requested format, which need not match `lyric.format`.
31
+ *
32
+ * A format without a writer has no branch here, so adding one to `LyricFormat` makes this fail to compile.
33
+ */
34
+ export declare const generate: (lyric: runtime.Lyric, target: LyricFormat, options?: LrcGenerateOptions) => LrcGenerateResult;
35
+
36
+ /**
37
+ * Generates the LRC text plus the annotations a single file cannot hold.
38
+ *
39
+ * Word tags appear only for a word-timed lyric when `wordTiming` is not `none`; otherwise every line stays whole.
40
+ */
41
+ export declare const generateLrc: (lyric: runtime.Lyric, options?: LrcGenerateOptions) => LrcGenerateResult;
42
+
43
+ export declare const ITUNES_HINT_PATTERN: RegExp;
44
+
45
+ declare interface LrcContent {
46
+ original: string;
47
+ translations?: string;
48
+ romans?: string;
49
+ }
50
+
51
+ export declare interface LrcGenerateOptions {
52
+ /**
53
+ * Word-level timestamp style; defaults to `angle`.
54
+ *
55
+ * `angle` is the enhanced LRC form and round-trips through this parser.
56
+ *
57
+ * `bracket` matches line timestamps, so this parser reads it as new lines rather than words.
58
+ */
59
+ wordTiming?: 'angle' | 'bracket' | 'none';
60
+ /** Clock format for every timestamp; defaults to `mm:ss.SSS`. */
61
+ timeFormat?: string;
62
+ /** Emit the metadata header; defaults to `true`. */
63
+ includeMeta?: boolean;
64
+ /** Append the word duration to its tag, as in `<00:01.00,500>`. */
65
+ includeWordEnd?: boolean;
66
+ /** Emit instrumental lines as a timestamp with no text; defaults to `true`. */
67
+ includeInstrumental?: boolean;
68
+ }
69
+
70
+ export declare type LrcGenerateResult = LrcContent;
71
+
72
+ export declare type LrcParseInput = LrcContent;
73
+
74
+ export declare interface LrcParseOptions {
75
+ /**
76
+ * How far an annotation's timestamp may sit from a line's start, in milliseconds.
77
+ * Defaults to `0`, which requires an exact match.
78
+ */
79
+ annotationTolerance?: number;
80
+ }
81
+
82
+ /**
83
+ * Values match the lowercase format names stored in `Lyric.format`.
84
+ */
85
+ export declare type LyricFormat = 'lrc' | 'ttml';
86
+
87
+ /**
88
+ * Unrecognized text returns `null`; a recognized format returns its canonical lyric and diagnostics.
89
+ * Annotation tracks travel with the source and are attached by the format's parser.
90
+ */
91
+ export declare const parse: (input: unknown, options?: LrcParseOptions & TtmlParseOptions) => ParseResult | null;
92
+
93
+ /**
94
+ * Source order, empty timed lines, and missing word times are preserved for model validation.
95
+ *
96
+ * Annotation tracks are read as lines too and attached by timestamp, and rows that match no line are dropped.
97
+ *
98
+ * `INVALID` means no readable timed lines; the returned diagnostics carry whatever the canonical tree breaks.
99
+ */
100
+ export declare const parseLrc: (input: string | LrcParseInput, options?: LrcParseOptions) => ParseResult;
101
+
102
+ /**
103
+ * A parsed lyric in canonical form, with every rule it breaks.
104
+ */
105
+ export declare interface ParseResult {
106
+ lyric: runtime.Lyric;
107
+ diagnostics: Diagnostic[];
108
+ }
109
+
110
+ /**
111
+ * Reads a TTML document into the model.
112
+ *
113
+ * The profile only changes where metadata comes from, so one reader serves both.
114
+ * A document that parses but holds no timed paragraph is `INVALID` rather than an error.
115
+ */
116
+ export declare const parseTtml: (input: string | TtmlContent, options?: TtmlParseOptions) => ParseResult;
117
+
118
+ export declare const readTtmlContent: (input: unknown) => TtmlContent | null;
119
+
120
+ export declare const readTtmlProfile: (source: string) => TtmlProfile;
121
+
122
+ export declare interface TtmlContent {
123
+ original: string;
124
+ }
125
+
126
+ export declare interface TtmlParseOptions {
127
+ /** Language tag the lyric declares; the document's own `xml:lang` wins when it states one. */
128
+ language?: string;
129
+ }
130
+
131
+ export declare type TtmlProfile = 'amll' | 'itunes';
132
+
133
+ export { }