dart_cue
A pure Dart CUE sheet parser and serialiser. Reads album, track and index metadata into a well-typed model, writes it back losslessly, and tolerates the quirks of real-world CUE files produced by EAC, cdrdao, XLD and friends.
No runtime dependencies. Works on the Dart VM, AOT binaries, Flutter (mobile, desktop, web) and plain Dart web.
On the web,
parseCueFileis not available (there is no filesystem) — fetch the file bytes yourself and callparseCueBytes.
Features
- Parse from a
String, raw bytes, or a file path. - Serialise a
CueSheetback to CUE text (toCueString) with frame-accurate round-trip. - Full coverage of standard commands:
CATALOG,CDTEXTFILE,PERFORMER,SONGWRITER,TITLE,FILE,TRACK,INDEX,PREGAP,POSTGAP,ISRC,FLAGS,REM(album- and track-scoped). - File types:
WAVE,MP3,AIFF,AIFC,BINARY,MOTOROLA. - Track types:
AUDIO,CDG,MODE1/2048,MODE1/2352,MODE2/2336,MODE2/2352,CDI/2336,CDI/2352,DATA. - Flags:
DCP,4CH,PRE,SCMS,DATA. - MSF (
mm:ss:ff, 75 fps) timestamps with rounding-safeparseMsf/formatMsf. - Automatic
endTimeanddurationderivation per track. - Encoding detection: UTF-8, UTF-16 LE and UTF-16 BE byte-order marks are handled; falls back to Latin-1 for legacy Windows rippers.
- Permissive parsing — malformed timestamps, unknown tokens and misplaced
commands never throw; valid data is preserved. Opt in to
parseCueSheetWithDiagnosticsfor a line-numbered warning list. - Unmodifiable collections on parsed sheets — safe to share across isolates and cache layers without defensive copies.
- Structural
==/hashCode/toStringonCueSheet,CueFileandCueTrack— parsed sheets can be compared, put inSets, or keyed inMaps by their content. - ReplayGain helpers:
replayGainAlbumGain/replayGainAlbumPeakonCueSheetandreplayGainTrackGain/replayGainTrackPeakonCueTrackparse the standardREM REPLAYGAIN_*fields todouble.
Install
dependencies:
dart_cue: ^0.0.6
Usage
Parse a file
import 'package:dart_cue/dart_cue.dart';
Future<void> main() async {
final sheet = await parseCueFile('album.cue');
if (sheet == null) return;
print('${sheet.performer} — ${sheet.title}');
for (final file in sheet.files) {
for (final track in file.tracks) {
print(' ${track.trackNumber}. ${track.title} '
'(${track.duration})');
}
}
}
Parse a string or bytes
final sheet = parseCueSheet(cueText);
final sheet2 = parseCueBytes(Uint8List.fromList(utf8.encode(cueText)));
Surface diagnostics
final result = parseCueSheetWithDiagnostics(cueText);
for (final issue in result.issues) {
print(issue); // WARNING: line 3: unknown TRACK type "WEIRD" (defaulting to AUDIO)
}
// result.sheet is still populated — the parser stays permissive.
Round-trip
final sheet = parseCueSheet(input)!;
final output = toCueString(sheet); // re-parseable, lossless
Edit with copyWith
final louder = track.copyWith(
remComments: {...track.remComments, 'REPLAYGAIN_TRACK_GAIN': '0 dB'},
);
final renamed = sheet.copyWith(title: 'Director\'s Cut');
JSON persistence
import 'dart:convert';
final text = jsonEncode(sheet.toJson());
final restored = CueSheet.fromJson(jsonDecode(text) as Map<String, Object?>);
assert(restored == sheet); // lossless round-trip
MSF timestamps
final d = parseMsf('04:12:37'); // Duration
final s = formatMsf(Duration(minutes: 1, seconds: 5)); // '01:05:00'
Data model at a glance
CueSheet
├── performer, title, songwriter, catalog, cdTextFile
├── remComments: Map<String, String> // album-level REM
└── files: List<CueFile>
├── filename, fileType
└── tracks: List<CueTrack>
├── trackNumber, trackType
├── title, performer, songwriter, isrc
├── pregap, postgap, flags
├── indices: Map<int, Duration>
├── remComments: Map<String, String> // track-level REM
└── startTime, endTime, duration // derived
CLI
The package ships a cueinfo executable. Install it globally:
$ dart pub global activate dart_cue
$ cueinfo --help
Or run it from a cloned repo with dart run bin/cueinfo.dart ….
Subcommands
cueinfo info <file.cue> [--format text|json] # default
cueinfo validate <file.cue> # exit 0 = clean, 1 = issues
cueinfo reformat <file.cue> # canonical CUE to stdout
cueinfo tracks <file.cue> # one line per track
Examples:
$ cueinfo info album.cue --format text
Title : Great Album
Performer : The Artist
...
$ cueinfo validate album.cue
album.cue: OK
$ cueinfo validate broken.cue
broken.cue: TRACK 01: missing INDEX 01
broken.cue: CATALOG "123" is not 13 digits
$ cueinfo reformat messy.cue > clean.cue # normalise formatting
$ cueinfo tracks album.cue
01 03:42:15 The Artist — First Song
02 04:03:30 The Artist — Second Song
validate checks: missing INDEX 01, non-monotonic track numbers,
out-of-range track numbers, empty filenames, missing tracks, and malformed
CATALOG / ISRC values. Exits non-zero on any issue, so it's suitable for
CI pipelines.
Testing
$ dart test
The suite includes malformed-input fuzzing, immutability contracts, a public
API surface check, and end-to-end fixtures under test/fixtures/ modelled on
EAC, cdrdao, per-track WAV and hidden-pregap layouts.
Author
Paul Snow
Licence
Apache License 2.0. See LICENSE.
Libraries
- dart_cue
- dart_cue — Pure Dart CUE sheet parser.