@maxmellon/cue-parser 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +374 -0
- package/bin/index.js +283 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +34 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/parser.d.ts +49 -0
- package/dist/src/parser.d.ts.map +1 -0
- package/dist/src/parser.js +395 -0
- package/dist/src/parser.js.map +1 -0
- package/dist/src/serializer.d.ts +31 -0
- package/dist/src/serializer.d.ts.map +1 -0
- package/dist/src/serializer.js +240 -0
- package/dist/src/serializer.js.map +1 -0
- package/dist/src/types.d.ts +95 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +5 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils.d.ts +72 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/utils.js +142 -0
- package/dist/src/utils.js.map +1 -0
- package/dist/src/youtube-serializer.d.ts +11 -0
- package/dist/src/youtube-serializer.d.ts.map +1 -0
- package/dist/src/youtube-serializer.js +40 -0
- package/dist/src/youtube-serializer.js.map +1 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 MaxMEllon
|
|
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
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# CUE Parser
|
|
2
|
+
|
|
3
|
+
A TypeScript library for parsing CUE sheet files according to the [CUE Sheet specification](https://wyday.com/cuesharp/specification.php).
|
|
4
|
+
|
|
5
|
+
š **[Live Demo](https://melocil.de/cue-parser/)** - Try the CUE parser online!
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- šµ Complete CUE sheet parsing support
|
|
10
|
+
- š TypeScript type definitions
|
|
11
|
+
- ā±ļø MSF (Minutes:Seconds:Frames) time format utilities
|
|
12
|
+
- šØ Comprehensive error handling and validation
|
|
13
|
+
- š Detailed parse results with errors and warnings
|
|
14
|
+
- šÆ Support for all standard CUE sheet commands
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install cue-parser
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## CLI Usage
|
|
23
|
+
|
|
24
|
+
After installation, you can use the `cue-parser` command directly:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Parse and display a CUE file
|
|
28
|
+
cue-parser album.cue
|
|
29
|
+
|
|
30
|
+
# Output as JSON
|
|
31
|
+
cue-parser album.cue --json
|
|
32
|
+
|
|
33
|
+
# Output as CUE sheet format
|
|
34
|
+
cue-parser album.cue --cue
|
|
35
|
+
|
|
36
|
+
# Output minimal CUE sheet
|
|
37
|
+
cue-parser album.cue --cue --minimal
|
|
38
|
+
|
|
39
|
+
# Validate only (no output)
|
|
40
|
+
cue-parser album.cue --validate
|
|
41
|
+
|
|
42
|
+
# Show parsing statistics
|
|
43
|
+
cue-parser album.cue --stats
|
|
44
|
+
|
|
45
|
+
# Quiet mode (errors only)
|
|
46
|
+
cue-parser album.cue --quiet
|
|
47
|
+
|
|
48
|
+
# Show help
|
|
49
|
+
cue-parser --help
|
|
50
|
+
|
|
51
|
+
# Show version
|
|
52
|
+
cue-parser --version
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### CLI Options
|
|
56
|
+
|
|
57
|
+
- `-h, --help` - Show help message
|
|
58
|
+
- `-v, --version` - Show version
|
|
59
|
+
- `-j, --json` - Output as JSON
|
|
60
|
+
- `-c, --cue` - Output as CUE sheet format
|
|
61
|
+
- `--minimal` - Output minimal CUE sheet (use with --cue)
|
|
62
|
+
- `-q, --quiet` - Only show errors
|
|
63
|
+
- `--validate` - Only validate, don't output parsed content
|
|
64
|
+
- `--stats` - Show parsing statistics
|
|
65
|
+
|
|
66
|
+
### CLI Examples
|
|
67
|
+
|
|
68
|
+
**Basic parsing:**
|
|
69
|
+
```bash
|
|
70
|
+
$ cue-parser album.cue
|
|
71
|
+
|
|
72
|
+
š CUE Sheet Information
|
|
73
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
74
|
+
Title: My Album
|
|
75
|
+
Artist: My Artist
|
|
76
|
+
Catalog: 1234567890123
|
|
77
|
+
|
|
78
|
+
šµ Tracks (3)
|
|
79
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
80
|
+
|
|
81
|
+
[01] Track One
|
|
82
|
+
Mode: AUDIO
|
|
83
|
+
File: audio.wav (WAVE)
|
|
84
|
+
Indexes:
|
|
85
|
+
01: 00:00:00 (0.00s)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**JSON output:**
|
|
89
|
+
```bash
|
|
90
|
+
$ cue-parser album.cue --json
|
|
91
|
+
{
|
|
92
|
+
"global": {
|
|
93
|
+
"title": "My Album",
|
|
94
|
+
"performer": "My Artist"
|
|
95
|
+
},
|
|
96
|
+
"tracks": [...]
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**CUE format output:**
|
|
101
|
+
```bash
|
|
102
|
+
$ cue-parser album.cue --cue
|
|
103
|
+
TITLE "My Album"
|
|
104
|
+
PERFORMER "My Artist"
|
|
105
|
+
|
|
106
|
+
FILE "audio.wav" WAVE
|
|
107
|
+
TRACK 01 AUDIO
|
|
108
|
+
TITLE "Track One"
|
|
109
|
+
INDEX 01 00:00:00
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Minimal CUE format:**
|
|
113
|
+
```bash
|
|
114
|
+
$ cue-parser album.cue --cue --minimal
|
|
115
|
+
TITLE "My Album"
|
|
116
|
+
PERFORMER "My Artist"
|
|
117
|
+
FILE "audio.wav" WAVE
|
|
118
|
+
TRACK 01 AUDIO
|
|
119
|
+
TITLE "Track One"
|
|
120
|
+
INDEX 01 00:00:00
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Validation with statistics:**
|
|
124
|
+
```bash
|
|
125
|
+
$ cue-parser album.cue --validate --stats
|
|
126
|
+
|
|
127
|
+
ā
Validation successful
|
|
128
|
+
|
|
129
|
+
š Parsing Statistics
|
|
130
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
131
|
+
ā
Successfully parsed
|
|
132
|
+
Tracks: 3
|
|
133
|
+
Indexes: 5
|
|
134
|
+
Files referenced: 2
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Library Usage
|
|
138
|
+
|
|
139
|
+
### Basic Parsing
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { parseCueSheet } from 'cue-parser';
|
|
143
|
+
|
|
144
|
+
const cueContent = `
|
|
145
|
+
TITLE "Example Album"
|
|
146
|
+
PERFORMER "Example Artist"
|
|
147
|
+
FILE "audio.wav" WAVE
|
|
148
|
+
TRACK 01 AUDIO
|
|
149
|
+
TITLE "Track 1"
|
|
150
|
+
INDEX 01 00:00:00
|
|
151
|
+
`;
|
|
152
|
+
|
|
153
|
+
const result = parseCueSheet(cueContent);
|
|
154
|
+
|
|
155
|
+
if (result.cueSheet) {
|
|
156
|
+
console.log(result.cueSheet.global.title); // "Example Album"
|
|
157
|
+
console.log(result.cueSheet.tracks[0].title); // "Track 1"
|
|
158
|
+
} else {
|
|
159
|
+
console.error('Parse errors:', result.errors);
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Using the Parser Class
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { CueParser } from 'cue-parser';
|
|
167
|
+
|
|
168
|
+
const parser = new CueParser();
|
|
169
|
+
const result = parser.parse(cueContent);
|
|
170
|
+
|
|
171
|
+
// Check for errors and warnings
|
|
172
|
+
if (result.errors.length > 0) {
|
|
173
|
+
result.errors.forEach(error => {
|
|
174
|
+
console.error(`Line ${error.line}: ${error.message}`);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (result.warnings.length > 0) {
|
|
179
|
+
result.warnings.forEach(warning => {
|
|
180
|
+
console.warn(`Line ${warning.line}: ${warning.message}`);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Working with MSF Time Format
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { parseHMSTime, formatHMSTime, hmsToSeconds } from 'cue-parser';
|
|
189
|
+
|
|
190
|
+
// Parse MSF time string
|
|
191
|
+
const time = parseHMSTime('1:30:45'); // { hour: 1, minute: 30, second: 45 }
|
|
192
|
+
|
|
193
|
+
// Format MSF time back to string
|
|
194
|
+
const timeStr = formatHMSTime(time); // "01:30:45"
|
|
195
|
+
|
|
196
|
+
// Convert to seconds
|
|
197
|
+
const totalSeconds = hmsToSeconds(time); // 5445 seconds
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Serializing CUE Sheets
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
import { parseCueSheet, serializeCueSheet, formatCueSheet, createMinimalCueSheet } from 'cue-parser';
|
|
204
|
+
|
|
205
|
+
const result = parseCueSheet(cueContent);
|
|
206
|
+
|
|
207
|
+
if (result.cueSheet) {
|
|
208
|
+
// Basic serialization
|
|
209
|
+
const cueString = serializeCueSheet(result.cueSheet);
|
|
210
|
+
|
|
211
|
+
// Formatted with spacing
|
|
212
|
+
const formatted = formatCueSheet(result.cueSheet, { trackSpacing: true });
|
|
213
|
+
|
|
214
|
+
// Minimal version (essential fields only)
|
|
215
|
+
const minimal = createMinimalCueSheet(result.cueSheet);
|
|
216
|
+
|
|
217
|
+
console.log(cueString);
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Supported CUE Sheet Commands
|
|
222
|
+
|
|
223
|
+
### Global Commands
|
|
224
|
+
- `CATALOG` - Sets the catalog number of the CD
|
|
225
|
+
- `CDTEXTFILE` - Sets an external file for CD-TEXT data
|
|
226
|
+
- `TITLE` - Title of the album
|
|
227
|
+
- `PERFORMER` - Name(s) of the performer(s)
|
|
228
|
+
- `SONGWRITER` - Name(s) of the songwriter(s)
|
|
229
|
+
- `COMPOSER` - Name(s) of the composer(s)
|
|
230
|
+
- `ARRANGER` - Name(s) of the arranger(s)
|
|
231
|
+
- `MESSAGE` - Message from the content provider and/or artist
|
|
232
|
+
- `DISC_ID` - Disc identification information
|
|
233
|
+
- `GENRE` - Genre identification
|
|
234
|
+
- `UPC_EAN` - UPC/EAN code of the album
|
|
235
|
+
- `REM` - Comment lines
|
|
236
|
+
|
|
237
|
+
### Track Commands
|
|
238
|
+
- `FILE` - Sets a new input file
|
|
239
|
+
- `TRACK` - Starts a new track
|
|
240
|
+
- `INDEX` - Sets a track index
|
|
241
|
+
- `PREGAP` - Sets track pregap
|
|
242
|
+
- `POSTGAP` - Sets track postgap
|
|
243
|
+
- `FLAGS` - Sets track flags (PRE, DCP, 4CH, SCMS)
|
|
244
|
+
- `ISRC` - Sets track ISRC number
|
|
245
|
+
- `TITLE` - Track title
|
|
246
|
+
- `PERFORMER` - Track performer
|
|
247
|
+
- `SONGWRITER` - Track songwriter
|
|
248
|
+
- `COMPOSER` - Track composer
|
|
249
|
+
- `ARRANGER` - Track arranger
|
|
250
|
+
- `MESSAGE` - Track message
|
|
251
|
+
|
|
252
|
+
## Supported File Formats
|
|
253
|
+
|
|
254
|
+
- `BINARY`
|
|
255
|
+
- `MOTOROLA`
|
|
256
|
+
- `AIFF`
|
|
257
|
+
- `WAVE`
|
|
258
|
+
- `MP3`
|
|
259
|
+
|
|
260
|
+
## Supported Track Modes
|
|
261
|
+
|
|
262
|
+
- `AUDIO`
|
|
263
|
+
- `CDG`
|
|
264
|
+
- `MODE1/2048`
|
|
265
|
+
- `MODE1/2352`
|
|
266
|
+
- `MODE2/2336`
|
|
267
|
+
- `MODE2/2352`
|
|
268
|
+
- `CDI/2336`
|
|
269
|
+
- `CDI/2352`
|
|
270
|
+
|
|
271
|
+
## API Reference
|
|
272
|
+
|
|
273
|
+
### Types
|
|
274
|
+
|
|
275
|
+
#### `CueSheet`
|
|
276
|
+
The main interface representing a parsed CUE sheet.
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
interface CueSheet {
|
|
280
|
+
global: CueGlobal;
|
|
281
|
+
tracks: Track[];
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### `ParseResult`
|
|
286
|
+
The result of parsing a CUE sheet.
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
interface ParseResult {
|
|
290
|
+
cueSheet?: CueSheet;
|
|
291
|
+
errors: ParseError[];
|
|
292
|
+
warnings: ParseError[];
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
#### `MSFTime`
|
|
297
|
+
Represents time in Minutes:Seconds:Frames format.
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
interface MSFTime {
|
|
301
|
+
minutes: number;
|
|
302
|
+
seconds: number;
|
|
303
|
+
frames: number; // 0-74 (1/75 of a second)
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Functions
|
|
308
|
+
|
|
309
|
+
#### `parseCueSheet(content: string): ParseResult`
|
|
310
|
+
Parses a CUE sheet from string content.
|
|
311
|
+
|
|
312
|
+
#### `parseHMSTime(timeString: string): HMSTime`
|
|
313
|
+
Parses an HMS time string (e.g., "1:30:45") into an HMSTime object.
|
|
314
|
+
|
|
315
|
+
#### `formatHMSTime(time: HMSTime, zeroPad?: boolean): string`
|
|
316
|
+
Formats an HMSTime object back to string format.
|
|
317
|
+
|
|
318
|
+
#### `hmsToSeconds(time: HMSTime): number`
|
|
319
|
+
Converts HMS time to total seconds.
|
|
320
|
+
|
|
321
|
+
#### `secondsToHMS(seconds: number): HMSTime`
|
|
322
|
+
Converts seconds to HMS time format.
|
|
323
|
+
|
|
324
|
+
#### `serializeCueSheet(cueSheet: CueSheet): string`
|
|
325
|
+
Serializes a CueSheet object back to CUE sheet format string.
|
|
326
|
+
|
|
327
|
+
#### `formatCueSheet(cueSheet: CueSheet, options?: object): string`
|
|
328
|
+
Formats a CueSheet with proper indentation and spacing options.
|
|
329
|
+
|
|
330
|
+
#### `createMinimalCueSheet(cueSheet: CueSheet): string`
|
|
331
|
+
Creates a minimal CUE sheet with only essential information.
|
|
332
|
+
|
|
333
|
+
## Error Handling
|
|
334
|
+
|
|
335
|
+
The parser provides detailed error information including line numbers and descriptions:
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
const result = parseCueSheet(invalidContent);
|
|
339
|
+
|
|
340
|
+
if (result.errors.length > 0) {
|
|
341
|
+
result.errors.forEach(error => {
|
|
342
|
+
console.error(`Parse error on line ${error.line}: ${error.message}`);
|
|
343
|
+
console.error(`Raw line: ${error.rawLine}`);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Examples
|
|
349
|
+
|
|
350
|
+
See the `examples/` directory for sample CUE files and usage examples.
|
|
351
|
+
|
|
352
|
+
## Development
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
# Install dependencies
|
|
356
|
+
npm install
|
|
357
|
+
|
|
358
|
+
# Build the project
|
|
359
|
+
npm run build
|
|
360
|
+
|
|
361
|
+
# Run tests
|
|
362
|
+
npm test
|
|
363
|
+
|
|
364
|
+
# Watch mode for development
|
|
365
|
+
npm run dev
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## License
|
|
369
|
+
|
|
370
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
371
|
+
|
|
372
|
+
## Contributing
|
|
373
|
+
|
|
374
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync, existsSync } from 'fs';
|
|
4
|
+
import { resolve } from 'path';
|
|
5
|
+
import { parseCueSheet, formatHMSTime, hmsToSeconds, serializeCueSheet, formatCueSheet, createMinimalCueSheet, serializeYouTubeTimeline } from '../dist/src/index.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* CUE Parser CLI
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
function showHelp() {
|
|
12
|
+
console.log(`
|
|
13
|
+
CUE Parser CLI
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
cue-parser <file.cue> [options]
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
-h, --help Show this help message
|
|
20
|
+
-v, --version Show version
|
|
21
|
+
-j, --json Output as JSON
|
|
22
|
+
-c, --cue Output as CUE sheet format
|
|
23
|
+
--minimal Output minimal CUE sheet (with --cue)
|
|
24
|
+
-q, --quiet Only show errors
|
|
25
|
+
--validate Only validate, don't output parsed content
|
|
26
|
+
--stats Show parsing statistics
|
|
27
|
+
--youtube Output YouTube timeline format
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
cue-parser album.cue
|
|
31
|
+
cue-parser album.cue --json
|
|
32
|
+
cue-parser album.cue --cue
|
|
33
|
+
cue-parser album.cue --cue --minimal
|
|
34
|
+
cue-parser album.cue --validate
|
|
35
|
+
cue-parser album.cue --stats
|
|
36
|
+
cue-parser album.cue --youtube
|
|
37
|
+
`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function showVersion() {
|
|
41
|
+
try {
|
|
42
|
+
const packageJson = JSON.parse(readFileSync(resolve(new URL(import.meta.url).pathname, '../../package.json'), 'utf-8'));
|
|
43
|
+
console.log(`cue-parser v${packageJson.version}`);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.log('cue-parser (version unknown)');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function formatTime(time) {
|
|
50
|
+
if (!time) return 'N/A';
|
|
51
|
+
const timeStr = formatHMSTime(time);
|
|
52
|
+
const seconds = msfToSeconds(time);
|
|
53
|
+
return `${timeStr} (${seconds.toFixed(2)}s)`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function displayYouTubeTimeline(cueSheet) {
|
|
57
|
+
console.log(serializeYouTubeTimeline(cueSheet));
|
|
58
|
+
}function displayCueSheet(cueSheet, options = {}) {
|
|
59
|
+
if (options.json) {
|
|
60
|
+
console.log(JSON.stringify(cueSheet, null, 2));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (options.youtube) {
|
|
65
|
+
displayYouTubeTimeline(cueSheet);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (options.cue) {
|
|
70
|
+
if (options.minimal) {
|
|
71
|
+
console.log(createMinimalCueSheet(cueSheet));
|
|
72
|
+
} else {
|
|
73
|
+
console.log(formatCueSheet(cueSheet, { trackSpacing: true }));
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Display global information
|
|
79
|
+
console.log('š CUE Sheet Information');
|
|
80
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
81
|
+
|
|
82
|
+
const global = cueSheet.global;
|
|
83
|
+
if (global.title) console.log(`Title: ${global.title}`);
|
|
84
|
+
if (global.performer) console.log(`Artist: ${global.performer}`);
|
|
85
|
+
if (global.songwriter) console.log(`Songwriter: ${global.songwriter}`);
|
|
86
|
+
if (global.composer) console.log(`Composer: ${global.composer}`);
|
|
87
|
+
if (global.arranger) console.log(`Arranger: ${global.arranger}`);
|
|
88
|
+
if (global.catalog) console.log(`Catalog: ${global.catalog}`);
|
|
89
|
+
if (global.cdTextFile) console.log(`CD-TEXT: ${global.cdTextFile}`);
|
|
90
|
+
if (global.genre) console.log(`Genre: ${global.genre}`);
|
|
91
|
+
if (global.upcEan) console.log(`UPC/EAN: ${global.upcEan}`);
|
|
92
|
+
if (global.message) console.log(`Message: ${global.message}`);
|
|
93
|
+
|
|
94
|
+
console.log(`\nšµ Tracks (${cueSheet.tracks.length})`);
|
|
95
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
96
|
+
|
|
97
|
+
cueSheet.tracks.forEach((track, index) => {
|
|
98
|
+
console.log(`\n[${track.number.toString().padStart(2, '0')}] ${track.title || 'Untitled'}`);
|
|
99
|
+
console.log(` Mode: ${track.mode}`);
|
|
100
|
+
|
|
101
|
+
if (track.performer && track.performer !== global.performer) {
|
|
102
|
+
console.log(` Artist: ${track.performer}`);
|
|
103
|
+
}
|
|
104
|
+
if (track.songwriter) console.log(` Songwriter: ${track.songwriter}`);
|
|
105
|
+
if (track.composer) console.log(` Composer: ${track.composer}`);
|
|
106
|
+
if (track.arranger) console.log(` Arranger: ${track.arranger}`);
|
|
107
|
+
if (track.isrc) console.log(` ISRC: ${track.isrc}`);
|
|
108
|
+
if (track.message) console.log(` Message: ${track.message}`);
|
|
109
|
+
|
|
110
|
+
if (track.file) {
|
|
111
|
+
console.log(` File: ${track.file.filename}${track.file.format ? ` (${track.file.format})` : ''}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (track.flags && track.flags.length > 0) {
|
|
115
|
+
console.log(` Flags: ${track.flags.join(', ')}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (track.pregap) {
|
|
119
|
+
console.log(` Pregap: ${formatTime(track.pregap)}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (track.indexes && track.indexes.length > 0) {
|
|
123
|
+
console.log(' Indexes:');
|
|
124
|
+
track.indexes.forEach(idx => {
|
|
125
|
+
console.log(` ${idx.number.toString().padStart(2, '0')}: ${formatTime(idx.time)}`);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (track.postgap) {
|
|
130
|
+
console.log(` Postgap: ${formatTime(track.postgap)}`);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function displayStats(result, options = {}) {
|
|
136
|
+
const { cueSheet, errors, warnings } = result;
|
|
137
|
+
|
|
138
|
+
console.log('\nš Parsing Statistics');
|
|
139
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
140
|
+
|
|
141
|
+
if (cueSheet) {
|
|
142
|
+
console.log(`ā
Successfully parsed`);
|
|
143
|
+
console.log(` Tracks: ${cueSheet.tracks.length}`);
|
|
144
|
+
|
|
145
|
+
const totalIndexes = cueSheet.tracks.reduce((sum, track) =>
|
|
146
|
+
sum + (track.indexes ? track.indexes.length : 0), 0);
|
|
147
|
+
console.log(` Indexes: ${totalIndexes}`);
|
|
148
|
+
|
|
149
|
+
const filesUsed = new Set(cueSheet.tracks
|
|
150
|
+
.map(track => track.file?.filename)
|
|
151
|
+
.filter(Boolean));
|
|
152
|
+
console.log(` Files referenced: ${filesUsed.size}`);
|
|
153
|
+
|
|
154
|
+
// Calculate total duration if possible
|
|
155
|
+
const lastTrack = cueSheet.tracks[cueSheet.tracks.length - 1];
|
|
156
|
+
if (lastTrack?.indexes && lastTrack.indexes.length > 0) {
|
|
157
|
+
const lastIndex = lastTrack.indexes[lastTrack.indexes.length - 1];
|
|
158
|
+
if (lastIndex.time) {
|
|
159
|
+
console.log(` Approximate duration: ${formatTime(lastIndex.time)}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
console.log(`ā Parsing failed`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (warnings.length > 0) {
|
|
167
|
+
console.log(`ā ļø Warnings: ${warnings.length}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (errors.length > 0) {
|
|
171
|
+
console.log(`šØ Errors: ${errors.length}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function displayErrors(errors, warnings, options = {}) {
|
|
176
|
+
if (warnings.length > 0 && !options.quiet) {
|
|
177
|
+
console.log('\nā ļø Warnings:');
|
|
178
|
+
warnings.forEach(warning => {
|
|
179
|
+
console.log(` Line ${warning.line}: ${warning.message}`);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (errors.length > 0) {
|
|
184
|
+
console.log('\nšØ Errors:');
|
|
185
|
+
errors.forEach(error => {
|
|
186
|
+
console.log(` Line ${error.line}: ${error.message}`);
|
|
187
|
+
if (!options.quiet && error.rawLine.trim()) {
|
|
188
|
+
console.log(` ā ${error.rawLine.trim()}`);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function main() {
|
|
195
|
+
const args = process.argv.slice(2);
|
|
196
|
+
|
|
197
|
+
if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
|
|
198
|
+
showHelp();
|
|
199
|
+
process.exit(0);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (args.includes('-v') || args.includes('--version')) {
|
|
203
|
+
showVersion();
|
|
204
|
+
process.exit(0);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const options = {
|
|
208
|
+
json: args.includes('-j') || args.includes('--json'),
|
|
209
|
+
cue: args.includes('-c') || args.includes('--cue'),
|
|
210
|
+
minimal: args.includes('--minimal'),
|
|
211
|
+
quiet: args.includes('-q') || args.includes('--quiet'),
|
|
212
|
+
validate: args.includes('--validate'),
|
|
213
|
+
stats: args.includes('--stats'),
|
|
214
|
+
youtube: args.includes('--youtube')
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// Find the file path (first non-option argument)
|
|
218
|
+
const filePath = args.find(arg => !arg.startsWith('-'));
|
|
219
|
+
|
|
220
|
+
if (!filePath) {
|
|
221
|
+
console.error('ā Error: No input file specified');
|
|
222
|
+
console.error('Use --help for usage information');
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const resolvedPath = resolve(filePath);
|
|
227
|
+
|
|
228
|
+
if (!existsSync(resolvedPath)) {
|
|
229
|
+
console.error(`ā Error: File not found: ${resolvedPath}`);
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
const content = readFileSync(resolvedPath, 'utf-8');
|
|
235
|
+
const result = parseCueSheet(content);
|
|
236
|
+
|
|
237
|
+
// Always show errors
|
|
238
|
+
if (result.errors.length > 0 || result.warnings.length > 0) {
|
|
239
|
+
displayErrors(result.errors, result.warnings, options);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Exit with error code if parsing failed
|
|
243
|
+
if (result.errors.length > 0) {
|
|
244
|
+
if (options.validate) {
|
|
245
|
+
console.log('\nā Validation failed');
|
|
246
|
+
}
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (options.validate) {
|
|
251
|
+
console.log('\nā
Validation successful');
|
|
252
|
+
if (options.stats) {
|
|
253
|
+
displayStats(result, options);
|
|
254
|
+
}
|
|
255
|
+
process.exit(0);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (result.cueSheet) {
|
|
259
|
+
if (!options.quiet) {
|
|
260
|
+
displayCueSheet(result.cueSheet, options);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (options.stats) {
|
|
264
|
+
displayStats(result, options);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
process.exit(0);
|
|
269
|
+
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error(`ā Error reading file: ${error.message}`);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Handle unhandled promise rejections
|
|
277
|
+
process.on('unhandledRejection', (error) => {
|
|
278
|
+
console.error('ā Unhandled error:', error);
|
|
279
|
+
process.exit(1);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Run the CLI
|
|
283
|
+
main();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CUE Sheet Parser
|
|
3
|
+
*
|
|
4
|
+
* A TypeScript library for parsing CUE sheet files according to the specification.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { parseCueSheet, CueParser } from 'cue-parser';
|
|
9
|
+
*
|
|
10
|
+
* const cueContent = `
|
|
11
|
+
* TITLE "Example Album"
|
|
12
|
+
* PERFORMER "Example Artist"
|
|
13
|
+
* FILE "audio.wav" WAVE
|
|
14
|
+
* TRACK 01 AUDIO
|
|
15
|
+
* TITLE "Track 1"
|
|
16
|
+
* INDEX 01 00:00:00
|
|
17
|
+
* `;
|
|
18
|
+
*
|
|
19
|
+
* const result = parseCueSheet(cueContent);
|
|
20
|
+
* if (result.cueSheet) {
|
|
21
|
+
* console.log(result.cueSheet.global.title); // "Example Album"
|
|
22
|
+
* console.log(result.cueSheet.tracks[0].title); // "Track 1"
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export type { HMSTime, TrackFlag, TrackMode, FileFormat, CDText, TrackIndex, FileInfo, Track, CueGlobal, CueSheet, ParseError, ParseResult } from './types.js';
|
|
27
|
+
export { parseHMSTime, formatHMSTime, formatCueTime, hmsToSeconds, secondsToHMS, msfToFrames, framesToMSF, addHMSTime, subtractHMSTime, compareHMSTime } from './utils.js';
|
|
28
|
+
export { CueParser, parseCueSheet } from './parser.js';
|
|
29
|
+
export { serializeCueSheet, formatCueSheet, createMinimalCueSheet } from './serializer.js';
|
|
30
|
+
export { serializeYouTubeTimeline } from './youtube-serializer.js';
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,YAAY,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,UAAU,EACV,MAAM,EACN,UAAU,EACV,QAAQ,EACR,KAAK,EACL,SAAS,EACT,QAAQ,EACR,UAAU,EACV,WAAW,EACZ,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,EACX,UAAU,EACV,eAAe,EACf,cAAc,EACf,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGvD,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACtB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC"}
|