private void Parse(LogBuffer parser) { List <int> arItems = null; int arV2 = 0; string lx = parser.ReadLineLTrim(); if (lx.StartsWith("X Lossless Decoder")) { Data.Application = lx; } lx = parser.ReadLineLTrim(); if (lx.StartsWith("XLD extraction logfile from ")) { Data.RipDate = lx.Substring(28); } lx = parser.ReadLineNonempty(); int slashPos = lx.IndexOf(" / "); if (slashPos < 0) { IssueModel.Add("Missing '<artist> / <album>', " + parser.GetPlace() + "."); return; } Data.RipArtist = lx.Substring(0, slashPos); Data.RipAlbum = lx.Substring(slashPos + 3); for (;;) { lx = parser.ReadLineNonempty(); if (parser.EOF) { return; } string px = ParseArg(lx, out string arg); if (arg == null) { break; } if (px == "Used drive") { Data.Drive = arg; } else if (px == "Media type") { Data.Media = arg; } else if (px == "Ripper mode") { Data.ReadMode = arg; } else if (px == "Disable audio cache") { Data.DefeatCache = arg; } else if (px == "Make use of C2 pointers") { Data.UseC2 = arg; } else if (px == "Read offset correction") { Data.ReadOffset = arg; } else if (px == "Gap status") { Data.GapHandling = arg; } } for (;;) { if (parser.EOF || lx == null) { return; } if (lx.StartsWith("TOC")) { ParseToC(); } if (lx.StartsWith("AccurateRip Summary")) { ParseAccurateRip(); } else if (lx.StartsWith("Track ")) { ParseTracks(); } else if (lx == "No errors occurred") { Data.HasNoErrors = true; lx = parser.ReadLine(); break; } else { lx = parser.ReadLine(); } } for (;;) { if (parser.EOF) { break; } if (lx == "-----BEGIN XLD SIGNATURE-----") { lx = parser.ReadLineLTrim(); if (!parser.EOF) { Data.storedHash = lx; lx = parser.ReadLineLTrim(); } } lx = parser.ReadLineLTrim(); } if (arItems != null) { if (arItems.Count != TracksModel.Data.Items.Count || arItems.Count == 0) { Data.ArIssue = IssueModel.Add("AccurateRip table mismatch.", Severity.Error, IssueTags.Failure); } else { Data.AccurateRipConfidence = arItems.Min(); Data.AccurateRip = arV2 == arItems.Count ? 2 : 1; } } return; void ParseToC() { // Q: does the ToC include data tracks? lx = parser.ReadLine(); } void ParseAccurateRip() { lx = parser.ReadLine(); if (lx != null && lx.Contains("Disc not found")) { Data.AccurateRipConfidence = 0; return; } arItems = new List <int>(); for (;;) { if (String.IsNullOrWhiteSpace(lx) || lx.StartsWith("->")) { return; } string px = ParseArg(lx, out string arg); if (px.Length != 8 || !Char.IsDigit(px[6]) || !Char.IsDigit(px[7])) { return; } int.TryParse(px.Substring(6), out int tn); if (tn != arItems.Count + 1) { return; } if (arg == "Not Found") { arItems.Add(0); } else if (arg.Contains("OK ")) { int p1 = arg.IndexOf("confidence "); if (p1 < 0) { return; // malformed AR } int p2 = p1 = p1 + 11; for (; p2 < arg.Length; ++p2) { if (!Char.IsDigit(arg[p2])) { break; } } if (p2 == p1) { return; // malformed AR } int.TryParse(arg.Substring(p1, p2 - p1), out int val); arItems.Add(val); if (arg.Contains("v2")) { ++arV2; } } else { arItems.Add(-1); } lx = parser.ReadLine(); } } void ParseTracks() { for (;;) { int number = 0; string fileName = null; uint? testCRC = null, copyCRC = null; string arg = lx.Substring(6); bool isOk = arg.Length >= 2 && Char.IsDigit(arg[0]); if (isOk) { isOk = int.TryParse(arg, out number); } if (!isOk) { IssueModel.Add($"Malformed track number '{arg}'."); return; } for (;;) { lx = parser.ReadLine(); if (parser.EOF || (lx.Length > 0 && lx[0] != ' ')) { TracksModel.Add(number, fileName, testCRC, copyCRC); if (lx.StartsWith("Track ")) { break; } else { return; } } string px = ParseArg(lx, out string ax); if (px == "Filename") { fileName = px; } else if (px == "CRC32 hash (test run)") { if (uint.TryParse(ax, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint word)) { testCRC = word; } else { IssueModel.Add("Malformed test CRC"); return; } } else if (px == "CRC32 hash") { if (uint.TryParse(ax, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint word)) { copyCRC = word; } else { IssueModel.Add("Malformed copy CRC"); return; } } } } } }
string Parse(LogBuffer parser) { string lx = null; ParseHeader(); if (!Data.IsRangeRip) { lx = ParseTracks(); if (Data.Issues.HasFatal) { return(null); } } while (!parser.EOF && !lx.Contains("errors") && !lx.StartsWith("==== ")) { lx = parser.ReadLineLTrim(); } if (lx == "No errors occured" || lx == "No errors occurred") { lx = parser.ReadLineLTrim(); } else if (lx == "There were errors") { if (Data.Issues.MaxSeverity < Severity.Error) { IssueModel.Add("There were errors."); } lx = parser.ReadLineLTrim(); } else { IssueModel.Add("Missing 'errors' line."); } if (Data.IsRangeRip) { if (lx == "AccurateRip summary") { for (;;) { lx = parser.ReadLineLTrim(); if (parser.EOF || !lx.StartsWith("Track ")) { break; } if (lx.Contains("ccurately ripped (confidence ")) { int arVersion = lx.Contains("AR v2") ? 2 : 1; int advanced = ConvertTo.FromStringToInt32(lx, 40, out int val); int arConfidence = advanced > 0 && val > 0 ? val : -1; lx = parser.ReadLineLTrim(); if (Data.AccurateRipConfidence == null || Data.AccurateRipConfidence.Value > arConfidence) { Data.AccurateRipConfidence = arConfidence; } if (Data.AccurateRip == null || Data.AccurateRip.Value > arVersion) { Data.AccurateRip = arVersion; } } } } } if (lx == "All tracks accurately ripped") { lx = parser.ReadLineLTrim(); } if (lx.StartsWith("End of status report")) { lx = parser.ReadLineLTrim(); } if (lx.StartsWith("---- CUETools")) { lx = ParseCueTools(); } while (!parser.EOF && !lx.StartsWith("==== ")) { lx = parser.ReadLine(); } if (lx.StartsWith("==== Log checksum ") && lx.Length >= 82) { Data.storedHash = ConvertTo.FromHexStringToBytes(lx, 18, 32); lx = parser.ReadLine(); if (!parser.EOF || !String.IsNullOrEmpty(lx)) { IssueModel.Add("Unexpected content at end of file.", Severity.Warning, IssueTags.StrictErr); } } return(lx); string ParseHeader() { lx = parser.ReadLine(); if (lx.StartsWith("Exact Audio Copy V")) { var spacePos = lx.IndexOf(' ', 18); if (spacePos > 0) { Data.EacVersionString = lx.Substring(18, spacePos - 18); } lx = parser.ReadLine(); lx = parser.ReadLine(); } if (!lx.StartsWith("EAC extraction logfile from ")) { IssueModel.Add("Unexpected contents, " + parser.GetPlace() + ": Expecting 'EAC extraction logfile'."); return(lx); } lx = lx.Substring(28); if (lx.Length < 12) { IssueModel.Add("Missing rip date, " + parser.GetPlace() + "."); return(lx); } Data.RipDate = lx.EndsWith(" for CD") ? lx.Substring(0, lx.Length - 7) : lx; lx = parser.ReadLine(); if (String.IsNullOrWhiteSpace(lx)) { lx = parser.ReadLine(); } int slashPos = lx.IndexOf('/'); if (slashPos < 0) { IssueModel.Add("Missing '<artist> / <album>', " + parser.GetPlace() + "."); return(lx); } Data.RipArtist = lx.Substring(0, slashPos).Trim(); Data.RipAlbum = lx.Substring(slashPos + 1).Trim(); for (;;) { TOP: if (parser.EOF) { lx = String.Empty; return(lx); } lx = parser.ReadLine(); if (lx.StartsWith("Track ") || lx.StartsWith("====")) { return(lx); } if (lx == "Range status and errors") { Data.IsRangeRip = true; lx = parser.ReadLine(); return(lx); } if (lx == "TOC of the extracted CD") { for (Data.TocTrackCount = 0;;) { lx = parser.ReadLine(); if (parser.EOF || lx.StartsWith("==== ") || (lx.StartsWith("Track") && !lx.Contains("|"))) { return(lx); } if (lx == "Range status and errors") { Data.IsRangeRip = true; lx = parser.ReadLine(); return(lx); } if (lx.Length >= 60) { if (int.TryParse(lx.Substring(0, 9), out int tn)) { ++Data.TocTrackCount; } } } } int ik0 = 0, ik1 = 0, ii = 0, iv0 = 0, iv1; for (;; ++ik0) { if (ik0 >= lx.Length) { goto TOP; } else if (lx[ik0] != ' ') { for (ii = ik0, ik1 = ii;;) { ++ii; if (ii >= lx.Length) { string kk = lx.Substring(ik0, ik1 - ik0 + 1); if (kk == "Installed external ASPI interface" || kk == "Native Win32 interface for Win NT & 2000") { Data.Interface = kk; } goto TOP; } if (lx[ii] == ':') { break; } if (lx[ii] != ' ') { ik1 = ii; } } for (iv0 = ii + 1;; ++iv0) { if (iv0 >= lx.Length) { goto TOP; } else if (lx[iv0] != ' ') { for (ii = iv0 + 1, iv1 = iv0;; ++ii) { if (ii == lx.Length) { break; } else if (lx[ii] != ' ') { iv1 = ii; } } string optKey = lx.Substring(ik0, ik1 - ik0 + 1), optVal = lx.Substring(iv0, iv1 - iv0 + 1); if (optKey == "Used drive") { Data.Drive = optVal; } else if (optKey == "Utilize accurate stream") { Data.AccurateStream = optVal; } else if (optKey == "Defeat audio cache") { Data.DefeatCache = optVal; } else if (optKey == "Make use of C2 pointers") { Data.UseC2 = optVal; } else if (optKey == "Read mode") { Data.ReadMode = optVal; } else if (optKey == "Read offset correction" || optKey == "Combined read/write offset correction") { Data.ReadOffset = optVal; } else if (optKey == "Overread into Lead-In and Lead-Out") { Data.Overread = optVal; } else if (optKey == "Fill up missing offset samples with silence") { Data.FillWithSilence = optVal; } else if (optKey == "Delete leading and trailing silent blocks") { Data.TrimSilence = optVal; } else if (optKey == "Null samples used in CRC calculations") { Data.CalcWithNulls = optVal; } else if (optKey == "Normalize to") { Data.NormalizeTo = optVal; } else if (optKey == "Used interface") { Data.Interface = optVal; } else if (optKey == "Gap handling") { Data.GapHandling = optVal; } else if (optKey == "Sample format") { Data.SampleFormat = optVal; } else if (optKey == "Quality") { Data.Quality = optVal; } else if (optKey == "Add ID3 tag") { Data.Id3Tag = optVal; } break; } } } } } } string ParseTracks() { for (;;) { if (parser.EOF || lx == "No errors occured" || lx == "No errors occurred" || lx == "There were errors" || lx.Contains("accurate")) { break; } if (!lx.StartsWith("Track") || lx.StartsWith("Track quality")) { lx = parser.ReadLineLTrim(); continue; } bool success = Int32.TryParse(lx.Substring(6), out int num); if (!success) { Data.TkIssue = IssueModel.Add("Invalid track " + parser.GetPlace(), Severity.Fatal, IssueTags.Failure); break; } lx = parser.ReadLineLTrim(); if (!lx.StartsWith("Filename ")) { Data.TkIssue = IssueModel.Add($"Track {num}: Missing 'Filename'.", Severity.Error, IssueTags.Failure); } else { lx = ParseTrack(num); } } return(lx); } string ParseTrack(int num) { string name = "", peak = "", speed = "", pregap = "", qual = ""; int? arVersion = null; int? arConfidence = null; uint? testCrc = null, copyCrc = null; bool trackErr = false; uint word; name = lx.Substring(9); if (name.Length < 10 || (lx.Length < 252 && !lx.EndsWith(".wav"))) { IssueModel.Add("Unexpected extension " + parser.GetPlace()); } lx = parser.ReadLineLTrim(); if (lx.StartsWith("Pre-gap length ")) { pregap = lx.Substring(16); lx = parser.ReadLineLTrim(); } for (;;) { if (parser.EOF) { return(lx); } if (lx.StartsWith("Track quality") || lx.StartsWith("Peak level ")) { break; } if (lx.StartsWith("Track ")) { return(lx); // Unexpected start of next track. } if (!trackErr) { trackErr = true; IssueModel.Add($"Track {num}: '{lx}'."); } lx = parser.ReadLineLTrim(); } if (lx.StartsWith("Peak level ")) { peak = lx.Substring(11); lx = parser.ReadLineLTrim(); } if (lx.StartsWith("Extraction speed ")) { speed = lx.Substring(17); lx = parser.ReadLineLTrim(); } if (lx.StartsWith("Track quality ")) { qual = lx.Substring(14); lx = parser.ReadLineLTrim(); } if (lx.StartsWith("Test CRC ")) { if (uint.TryParse(lx.Substring(9), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out word)) { testCrc = word; } else { Data.TpIssue = IssueModel.Add($"Track {num}: Invalid test CRC-32.", Severity.Error, IssueTags.Failure); } lx = parser.ReadLineLTrim(); } if (!lx.StartsWith("Copy CRC ")) { IssueModel.Add($"Track {num}: Missing copy CRC-32.", Severity.Warning); } else { if (uint.TryParse(lx.Substring(9), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out word)) { copyCrc = word; } else { Data.TkIssue = IssueModel.Add($"Track {num}: Invalid copy CRC-32.", Severity.Error, IssueTags.Failure); } lx = parser.ReadLineLTrim(); } if (lx.StartsWith("Accurately ripped (confidence ")) { arVersion = lx.Contains("AR v2") ? 2 : 1; int advanced = ConvertTo.FromStringToInt32(lx, 30, out int val); arConfidence = advanced > 0 && val > 0 ? val : -1; lx = parser.ReadLineLTrim(); } else if (lx.StartsWith("Cannot be verified")) { arVersion = lx.Contains("AR v2") ? 2 : 1; arConfidence = -1; lx = parser.ReadLineLTrim(); } else if (lx.StartsWith("Track not present")) { if (peak != "0.0 %") { arConfidence = 0; } lx = parser.ReadLineLTrim(); } if (arConfidence != null) { if (Data.AccurateRipConfidence == null || Data.AccurateRipConfidence > arConfidence) { Data.AccurateRipConfidence = arConfidence; } if (arVersion != null) { if (Data.AccurateRip == null || Data.AccurateRip.Value > arVersion.Value) { Data.AccurateRip = arVersion; } } } bool hasOK = false; if (lx == "Copy OK") { hasOK = true; lx = parser.ReadLineLTrim(); } TracksModel.Add(num, name, pregap, peak, speed, qual, testCrc, copyCrc, hasOK, arVersion, arConfidence); return(lx); } string ParseCueTools() { do { lx = parser.ReadLineLTrim(); if (parser.EOF || lx.StartsWith("==== ")) { return(lx); } } while (!lx.StartsWith("[CTDB")); if (lx.Contains("not present")) { Data.CueToolsConfidence = 0; lx = parser.ReadLineLTrim(); if (!parser.EOF && lx.StartsWith("Submit")) { lx = parser.ReadLineLTrim(); } return(lx); } if (!lx.Contains(" found")) { return(lx); } do { lx = parser.ReadLineLTrim(); if (parser.EOF || lx.StartsWith("==== ")) { return(lx); } if (lx.StartsWith("[")) { int ctConfidence = -1; if (lx.Contains("Accurately ripped")) { int advanced = ConvertTo.FromStringToInt32(lx, 12, out ctConfidence); } Data.CueToolsConfidence = ctConfidence; lx = parser.ReadLineLTrim(); return(lx); } } while (!lx.StartsWith("Track")); for (int tx = 0; ; ++tx) { lx = parser.ReadLine(); if (parser.EOF || lx.StartsWith("==== ") || lx.StartsWith("All tracks")) { return(lx); } int ctConfidence; if (lx.Contains("Accurately ripped")) { int advanced = ConvertTo.FromStringToInt32(lx, 9, out ctConfidence); if (advanced == 0) { Data.CueToolsConfidence = -1; return(lx); } } else if (lx.Contains("Differs")) { ctConfidence = -1; } else { break; } if (tx < TracksModel.Data.Items.Count) { TracksModel.SetCtConfidence(tx, ctConfidence); } if (Data.CueToolsConfidence == null || Data.CueToolsConfidence > ctConfidence) { Data.CueToolsConfidence = ctConfidence; } } return(parser.ReadLineLTrim()); } }