dart_rip_log 0.2.0
dart_rip_log: ^0.2.0 copied to clipboard
Parses CD rip log files from EAC, XLD, and other rippers into structured, JSON-serialisable quality data (AccurateRip, CRCs, peaks, errors).
dart_rip_log #
A pure Dart library that parses CD rip log files from the major CD-ripping tools (EAC, XLD) into structured, JSON-serialisable quality data. Extracts AccurateRip verification status, CRC-32 checksums, peak levels, track quality, and per-track error statistics.
Zero runtime dependencies beyond the Dart SDK.
Features #
- Auto-detects the log format from the log content.
- EAC (Exact Audio Copy) — full parser, including:
- Header: tool version, extraction date, drive, read mode, read offset, overread, gap handling, media type.
- Per track: filename, peak level, track quality, test/copy CRCs, Copy OK.
- AccurateRip:
verified(with confidence, v1 CRC and optional v2 signature),mismatch,notInDatabase. - Full error-statistics block (read / skip / edge jitter / atom jitter / drift / dropped bytes / duplicated bytes / inconsistency).
- Footer: summary line and log checksum.
- Range-rip logs (single-track whole-disc extractions).
- XLD (X Lossless Decoder) — full parser, including per-track AR v1/v2
signatures and the
Statisticsblock. - CUERipper, whipper, dBpoweramp — format detection and scaffolded parsers (tool version captured; full per-track parsing pending real-world sample logs).
- Tolerant of malformed input: truncated files, garbled lines, CRLF/LF/mixed
line endings, and missing fields return a best-effort
RipLograther than throwing. toJson()on every model for easy serialisation.- Command-line tool (
bin/riplog.dart) for quick inspection.
Installing #
Add to your pubspec.yaml:
dependencies:
dart_rip_log: ^0.2.0
Then:
dart pub get
Quick start #
import 'dart:io';
import 'package:dart_rip_log/dart_rip_log.dart';
Future<void> main(List<String> args) async {
final log = await parseRipLogFile(args.first);
print('Format: ${log.logFormat.name}');
print('Tool: ${log.toolVersion}');
print('Tracks: ${log.tracks.length}');
print('Verified: ${isFullyVerified(log)}');
for (final track in tracksWithArMismatch(log)) {
print(' ! Track ${track.trackNumber} did not verify against AccurateRip');
}
}
Parsing from a string #
final log = parseRipLog(logContent);
if (log.logFormat == RipLogFormat.unknown) {
// Format could not be identified — handle gracefully.
}
Convenience helpers #
-
isFullyVerified(log)—truewhen every track has AccurateRip statusverified. -
tracksWithErrors(log)— tracks whoseTrackErrors.hasErrorsis true. -
tracksWithArMismatch(log)— tracks whose AccurateRip check failed. -
toJson(log)— JSON-compatibleMap<String, dynamic>. -
RipLog.fromJson(map)— inverse oftoJson. All models support round-trip serialisation. -
compareRipLogs(a, b)— structured per-field diff between two logs, useful for verifying re-rips:final diff = compareRipLogs(oldLog, newLog); for (final e in diff.trackDifferences) { print('${e.path}: ${e.left} → ${e.right}'); }
Command-line tool #
Install globally:
dart pub global activate dart_rip_log
Then:
riplog --format json my_rip.log # pretty-printed JSON (default)
riplog --format text my_rip.log # human-readable
riplog --format ndjson *.log # one JSON per line (jq-friendly)
riplog --summary my_rip.log # one line per track
riplog -q *.log # tab-separated one line per file
riplog -r ~/music/rips/ # walk directory for *.log
riplog --filter problems log.log # only mismatch / errored tracks
riplog --fail-on mismatch -q *.log # exit 1 only on AR mismatch
riplog --color always log.log | less -R # keep colour through pagers
cat my_rip.log | riplog -q - # read from stdin
riplog --help # full usage
Exit-code policy is controlled by --fail-on:
| Policy | Exits 1 when… |
|---|---|
any |
default — any AR mismatch or track error counts |
mismatch |
any AR mismatch |
errors |
any track error counts |
never |
never (always exits 0 on successful parse) |
2 is always returned for bad arguments or I/O errors. With multiple files
the JSON output is an array (one entry per file); ndjson emits one object
per line; text / --summary each file is prefixed with # <path>.
Colour output respects NO_COLOR and stdout.hasTerminal by default.
Data model #
| Class | Purpose |
|---|---|
RipLog |
Top-level result — header fields + tracks. |
RipLogTrack |
Per-track quality data (unified across formats). |
TrackErrors |
Per-track error counters. |
DriveInfo |
Optical drive name, read offset, adapter. |
LogSource |
Lineage metadata: byte size, line count, parser, timestamp. |
RipLogFormat (enum) |
eac, xld, cueRipper, whipper, dbPoweramp, unknown. |
AccurateRipStatus (enum) |
verified, mismatch, notInDatabase, notChecked. |
RipLogQuality (enum) |
allVerified, partiallyVerified, mismatches, errors, unknown. |
RipLog.quality returns a single-value summary of the whole log. Precedence
(most severe first): errors > mismatches > partiallyVerified >
allVerified > unknown.
Error tolerance #
The library never throws on malformed content. It may still throw at the
file-I/O layer — for example, parseRipLogFile will throw a
FileSystemException when the file does not exist or cannot be read. File
input is decoded with BOM detection: UTF-16LE (the default encoding of real
EAC logs), UTF-16BE, and UTF-8 with BOM are handled transparently, and
BOM-less input falls back from UTF-8 to Latin-1 rather than throwing. A
deliberate consequence of the Latin-1 fallback is that every byte sequence
decodes: a binary or otherwise non-log file no longer throws (as it did
before 0.2.0) — it parses to an unknown-format RipLog with zero tracks,
which the riplog CLI treats as a failure under the default
--fail-on any. On truncated, garbled, or partially-unknown input the
parser returns a RipLog with whatever it could extract; any parsing
warnings are collected in RipLog.errors.
JSON shape #
Calling toJson(log) (or log.toJson()) returns a stable, JSON-compatible
Map<String, dynamic>. The shape is pinned by golden tests in
test/fixtures/*.expected.json — any intentional change requires regenerating
the goldens. Top-level keys:
logFormat, toolVersion, extractionDate, drive{}, readMode, readOffset,
overread, gapHandling, mediaType, tracks[], accurateRipSummary,
integrityHash, testAndCopy, accurateRipDiscId, accurateRipTotalSubmissions,
quality, source{}, errors[]
Each track includes: trackNumber, filename, peakLevel, trackQuality, copyCrc, testCrc, accurateRipStatus, accurateRipCrcV1, accurateRipCrcV2, accurateRipConfidence, copyOk, errors{}, logFormat, startSector, lengthSectors, durationSeconds. Numeric peak/quality values are fractions in
[0.0, 1.0]. Dates are ISO-8601 strings. Optional fields are omitted when
null — so quality, logFormat, tracks, and errors are always present,
while the rest appear only when the log supplied them.
copyOk is format-specific: for EAC it reflects the per-track Copy OK
line; XLD has no equivalent concept, so for XLD tracks it is derived —
true when the track's Statistics block reports zero errors,
independent of AccurateRip status (a clean rip of a disc that is not in
the AccurateRip database is still a good copy).
source (byte size, line count, parser name, parse timestamp) is populated
only by parseRipLogFile, not by parseRipLog.
Running the tests #
dart test # full suite (unit + CLI integration)
dart test --exclude-tags cli # fast: skip shell-out tests
The test suite covers header/track/footer parsing, AccurateRip variants, per-track error statistics, line-ending tolerance, Unicode filenames (Latin accents, CJK, emoji), a 500-track performance smoke, CLI behaviour, and JSON-shape golden tests.
Benchmarking #
dart run benchmark/main.dart # full measurement (~20 s)
dart run benchmark/main.dart --smoke # 1 iter per case (~1 s; `dart test` exercises this path via test/benchmark_smoke_test.dart)
Measures detectLogFormat, parseRipLog, and toJson against EAC small,
XLD small, and a checked-in 500-track EAC fixture. Prints a grouped table
with per-track timings for the 500-track case. Results are print-only — no
baseline file, no CI threshold — so compare runs manually when evaluating
a parser change.
Contributing #
Issues and pull requests welcome on GitHub. Additional real-world log samples from CUERipper, whipper, and dBpoweramp would be especially valuable for finishing those parsers.
Licence #
Apache-2.0. See LICENSE.