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, parseCueFile is not available (there is no filesystem) — fetch the file bytes yourself and call parseCueBytes.

Features

  • Parse from a String, raw bytes, or a file path.
  • Serialise a CueSheet back 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-safe parseMsf / formatMsf.
  • Automatic endTime and duration derivation 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 parseCueSheetWithDiagnostics for a line-numbered warning list.
  • Unmodifiable collections on parsed sheets — safe to share across isolates and cache layers without defensive copies.
  • Structural == / hashCode / toString on CueSheet, CueFile and CueTrack — parsed sheets can be compared, put in Sets, or keyed in Maps by their content.
  • ReplayGain helpers: replayGainAlbumGain / replayGainAlbumPeak on CueSheet and replayGainTrackGain / replayGainTrackPeak on CueTrack parse the standard REM REPLAYGAIN_* fields to double.

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.