using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Text; using System.Globalization; using System.Net; using System.IO; using CUETools.Codecs; using CUETools.CDImage; namespace CUETools.AccurateRip { public class AccurateRipVerify : IAudioDest { public AccurateRipVerify(CDImageLayout toc) { _toc = toc; _accDisks = new List(); Init(); } unsafe private void CalculateAccurateRipCRCsSemifast(int* samples, uint count, int iTrack, uint currentOffset, uint previousOffset, uint trackLength) { fixed (uint* CRCsA = &_offsetedCRC[Math.Max(0, iTrack - 1), 0], CRCsB = &_offsetedCRC[iTrack, 0], CRCsC = &_offsetedCRC[Math.Min(_toc.AudioTracks - 1, iTrack + 1), 0]) { for (uint si = 0; si < count; si++) { uint sampleValue = (uint)((samples[2 * si] & 0xffff) + (samples[2 * si + 1] << 16)); int i; int iB = Math.Max(0, _arOffsetRange - (int)(currentOffset + si)); int iC = Math.Min(2 * _arOffsetRange + 1, _arOffsetRange + (int)trackLength - (int)(currentOffset + si)); uint baseSumA = sampleValue * (uint)(previousOffset + 1 - iB); for (i = 0; i < iB; i++) { CRCsA[i] += baseSumA; baseSumA += sampleValue; } uint baseSumB = sampleValue * (uint)Math.Max(1, (int)(currentOffset + si) - _arOffsetRange + 1); for (i = iB; i < iC; i++) { CRCsB[i] += baseSumB; baseSumB += sampleValue; } uint baseSumC = sampleValue; for (i = iC; i <= 2 * _arOffsetRange; i++) { CRCsC[i] += baseSumC; baseSumC += sampleValue; } } return; } } unsafe private void CalculateAccurateRipCRCs(int* samples, uint count, int iTrack, uint currentOffset, uint previousOffset, uint trackLength) { for (int si = 0; si < count; si++) { uint sampleValue = (uint)((samples[2 * si] & 0xffff) + (samples[2 * si + 1] << 16)); for (int oi = -_arOffsetRange; oi <= _arOffsetRange; oi++) { int iTrack2 = iTrack; int currentOffset2 = (int)currentOffset + si - oi; if (currentOffset2 < 5 * 588 - 1 && iTrack == 0) // we are in the skipped area at the start of the disk { continue; } else if (currentOffset2 < 0) // offset takes us to previous track { iTrack2--; currentOffset2 += (int)previousOffset; } else if (currentOffset2 >= trackLength - 5 * 588 && iTrack == _toc.AudioTracks - 1) // we are in the skipped area at the end of the disc { continue; } else if (currentOffset2 >= trackLength) // offset takes us to the next track { iTrack2++; currentOffset2 -= (int)trackLength; } _offsetedCRC[iTrack2, _arOffsetRange - oi] += sampleValue * (uint)(currentOffset2 + 1); } } } unsafe private void CalculateAccurateRipCRCsFast(int* samples, uint count, int iTrack, uint currentOffset) { int s1 = (int)Math.Min(count, Math.Max(0, 450 * 588 - _arOffsetRange - (int)currentOffset)); int s2 = (int)Math.Min(count, Math.Max(0, 451 * 588 + _arOffsetRange - (int)currentOffset)); if (s1 < s2) fixed (uint* FrameCRCs = &_offsetedFrame450CRC[iTrack, 0]) for (int sj = s1; sj < s2; sj++) { int magicFrameOffset = (int)currentOffset + sj - 450 * 588 + 1; int firstOffset = Math.Max(-_arOffsetRange, magicFrameOffset - 588); int lastOffset = Math.Min(magicFrameOffset - 1, _arOffsetRange); uint sampleValue = (uint)((samples[2 * sj] & 0xffff) + (samples[2 * sj + 1] << 16)); for (int oi = firstOffset; oi <= lastOffset; oi++) FrameCRCs[_arOffsetRange - oi] += sampleValue * (uint)(magicFrameOffset - oi); } fixed (uint* CRCs = &_offsetedCRC[iTrack, 0]) { uint baseSum = 0, stepSum = 0; currentOffset += (uint)_arOffsetRange + 1; for (uint si = 0; si < count; si++) { uint sampleValue = (uint)((samples[2 * si] & 0xffff) + (samples[2 * si + 1] << 16)); stepSum += sampleValue; baseSum += sampleValue * (uint)(currentOffset + si); } for (int i = 2 * _arOffsetRange; i >= 0; i--) { CRCs[i] += baseSum; baseSum -= stepSum; } } } public uint CRC(int iTrack) { return CRC(iTrack); } public uint CRC(int iTrack, int oi) { return _offsetedCRC[iTrack, _arOffsetRange - oi]; } public uint CRC450(int iTrack, int oi) { return _offsetedFrame450CRC[iTrack, _arOffsetRange - oi]; } public void Write(int[,] sampleBuffer, uint sampleCount) { for (uint pos = 0; pos < sampleCount; ) { uint copyCount = Math.Min(sampleCount - pos, (uint)_samplesRemTrack); if (_currentTrack != 0) { unsafe { fixed (int* pSampleBuff = &sampleBuffer[pos, 0]) { uint trackLength = _toc[_currentTrack].Length * 588; uint currentOffset = (uint)_sampleCount - _toc[_currentTrack].Start * 588; uint previousOffset = _currentTrack > 1 ? _toc[_currentTrack - 1].Length * 588 : _toc.Pregap * 588; uint si1 = (uint)Math.Min(copyCount, Math.Max(0, 588 * (_currentTrack == 1 ? 10 : 5) - (int)currentOffset)); uint si2 = (uint)Math.Min(copyCount, Math.Max(si1, trackLength - (int)currentOffset - 588 * (_currentTrack == _toc.AudioTracks ? 10 : 5))); if (_currentTrack == 1) CalculateAccurateRipCRCs(pSampleBuff, si1, _currentTrack - 1, currentOffset, previousOffset, trackLength); else CalculateAccurateRipCRCsSemifast(pSampleBuff, si1, _currentTrack - 1, currentOffset, previousOffset, trackLength); if (si2 > si1) CalculateAccurateRipCRCsFast(pSampleBuff + si1 * 2, (uint)(si2 - si1), _currentTrack - 1, currentOffset + si1); if (_currentTrack == _toc.AudioTracks) CalculateAccurateRipCRCs(pSampleBuff + si2 * 2, copyCount - si2, _currentTrack - 1, currentOffset + si2, previousOffset, trackLength); else CalculateAccurateRipCRCsSemifast(pSampleBuff + si2 * 2, copyCount - si2, _currentTrack - 1, currentOffset + si2, previousOffset, trackLength); } } } pos += copyCount; _samplesRemTrack -= copyCount; _sampleCount += copyCount; CheckPosition(); } } public void Init() { _offsetedCRC = new uint[_toc.AudioTracks, 10 * 588]; _offsetedFrame450CRC = new uint[_toc.AudioTracks, 10 * 588]; _currentTrack = 0; _sampleCount = 0; _samplesRemTrack = _toc.Pregap * 588; CheckPosition(); } private void CheckPosition() { while (_samplesRemTrack <= 0) { if (++_currentTrack > _toc.AudioTracks) return; _samplesRemTrack = _toc[_currentTrack].Length * 588; } } private uint readIntLE(byte[] data, int pos) { return (uint)(data[pos] + (data[pos + 1] << 8) + (data[pos + 2] << 16) + (data[pos + 3] << 24)); } public void ContactAccurateRip(string accurateRipId) { // Calculate the three disc ids used by AR uint discId1 = 0; uint discId2 = 0; uint cddbDiscId = 0; string[] n = accurateRipId.Split('-'); if (n.Length != 3) { throw new Exception("Invalid accurateRipId."); } discId1 = UInt32.Parse(n[0], NumberStyles.HexNumber); discId2 = UInt32.Parse(n[1], NumberStyles.HexNumber); cddbDiscId = UInt32.Parse(n[2], NumberStyles.HexNumber); string url = String.Format("http://www.accuraterip.com/accuraterip/{0:x}/{1:x}/{2:x}/dBAR-{3:d3}-{4:x8}-{5:x8}-{6:x8}.bin", discId1 & 0xF, discId1 >> 4 & 0xF, discId1 >> 8 & 0xF, _toc.AudioTracks, discId1, discId2, cddbDiscId); HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url); req.Method = "GET"; try { HttpWebResponse resp = (HttpWebResponse)req.GetResponse(); _accResult = resp.StatusCode; if (_accResult == HttpStatusCode.OK) { // Retrieve response stream and wrap in StreamReader Stream respStream = resp.GetResponseStream(); // Allocate byte buffer to hold stream contents byte[] urlData = new byte[13]; int urlDataLen, bytesRead; _accDisks.Clear(); while (true) { for (urlDataLen = 0; urlDataLen < 13; urlDataLen += bytesRead) { bytesRead = respStream.Read(urlData, urlDataLen, 13 - urlDataLen); if (0 == bytesRead) break; } if (urlDataLen == 0) break; if (urlDataLen < 13) { _accResult = HttpStatusCode.PartialContent; return; } AccDisk dsk = new AccDisk(); dsk.count = urlData[0]; dsk.discId1 = readIntLE(urlData, 1); dsk.discId2 = readIntLE(urlData, 5); dsk.cddbDiscId = readIntLE(urlData, 9); for (int i = 0; i < dsk.count; i++) { for (urlDataLen = 0; urlDataLen < 9; urlDataLen += bytesRead) { bytesRead = respStream.Read(urlData, urlDataLen, 9 - urlDataLen); if (0 == bytesRead) { _accResult = HttpStatusCode.PartialContent; return; } } AccTrack trk = new AccTrack(); trk.count = urlData[0]; trk.CRC = readIntLE(urlData, 1); trk.Frame450CRC = readIntLE(urlData, 5); dsk.tracks.Add(trk); } _accDisks.Add(dsk); } respStream.Close(); } } catch (WebException ex) { if (ex.Status == WebExceptionStatus.ProtocolError) _accResult = ((HttpWebResponse)ex.Response).StatusCode; else _accResult = HttpStatusCode.BadRequest; } } public bool SetTags(NameValueCollection tags) { throw new Exception("unsupported"); } public void Close() { if (_sampleCount != _finalSampleCount) throw new Exception("_sampleCount != _finalSampleCount"); } public void Delete() { throw new Exception("unsupported"); } public int BitsPerSample { get { return 16; } } public long FinalSampleCount { set { _finalSampleCount = value; } } public long BlockSize { set { throw new Exception("unsupported"); } } public string Path { get { throw new Exception("unsupported"); } } public void GenerateAccurateRipLog(TextWriter sw, int oi) { for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) { uint count = 0; uint partials = 0; uint conf = 0; string pressings = ""; string partpressings = ""; for (int di = 0; di < (int)AccDisks.Count; di++) { count += AccDisks[di].tracks[iTrack].count; if (CRC(iTrack, oi) == AccDisks[di].tracks[iTrack].CRC) { conf += AccDisks[di].tracks[iTrack].count; if (pressings != "") pressings = pressings + ","; pressings = pressings + (di + 1).ToString(); } if (CRC450(iTrack, oi) == AccDisks[di].tracks[iTrack].Frame450CRC) { partials += AccDisks[di].tracks[iTrack].count; if (partpressings != "") partpressings = partpressings + ","; partpressings = partpressings + (di + 1).ToString(); } } if (conf > 0) sw.WriteLine(String.Format(" {0:00}\t[{1:x8}] ({3:00}/{2:00}) Accurately ripped as in pressing(s) #{4}", iTrack + 1, CRC(iTrack, oi), count, conf, pressings)); else if (partials > 0) sw.WriteLine(String.Format(" {0:00}\t[{1:x8}] ({3:00}/{2:00}) Partial match to pressing(s) #{4} ", iTrack + 1, CRC(iTrack, oi), count, partials, partpressings)); else sw.WriteLine(String.Format(" {0:00}\t[{1:x8}] (00/{2:00}) No matches", iTrack + 1, CRC(iTrack, oi), count)); } } private static uint sumDigits(uint n) { uint r = 0; while (n > 0) { r = r + (n % 10); n = n / 10; } return r; } public static string CalculateCDDBId(CDImageLayout toc) { uint cddbDiscId = 0; for (int iTrack = 1; iTrack <= toc.TrackCount; iTrack++) cddbDiscId += sumDigits(toc[iTrack].Start / 75 + 2); return string.Format("{0:X8}", ((cddbDiscId << 24) + ((toc.Length / 75 - toc[1].Start / 75) << 8) + (uint)toc.TrackCount) & 0xFFFFFFFF); } public static string CalculateAccurateRipId(CDImageLayout toc) { // Calculate the three disc ids used by AR uint discId1 = 0; uint discId2 = 0; for (int iTrack = 1; iTrack <= toc.TrackCount; iTrack++) if (toc[iTrack].IsAudio) { discId1 += toc[iTrack].Start; discId2 += Math.Max(toc[iTrack].Start, 1) * toc[iTrack].Number; } discId1 += toc.Length; discId2 += Math.Max(toc.Length, 1) * ((uint)toc.AudioTracks + 1); discId1 &= 0xFFFFFFFF; discId2 &= 0xFFFFFFFF; return string.Format("{0:x8}-{1:x8}-{2}", discId1, discId2, CalculateCDDBId(toc).ToLower()); } public List AccDisks { get { return _accDisks; } } public HttpStatusCode AccResult { get { return _accResult; } } public string ARStatus { get { return _accResult == HttpStatusCode.NotFound ? "disk not present in database" : _accResult == HttpStatusCode.OK ? null : _accResult.ToString(); } } CDImageLayout _toc; long _sampleCount, _finalSampleCount, _samplesRemTrack; int _currentTrack; private List _accDisks; private HttpStatusCode _accResult; private uint[,] _offsetedCRC; private uint[,] _offsetedFrame450CRC; private const int _arOffsetRange = 5 * 588 - 1; } public struct AccTrack { public uint count; public uint CRC; public uint Frame450CRC; } public class AccDisk { public uint count; public uint discId1; public uint discId2; public uint cddbDiscId; public List tracks; public AccDisk() { tracks = new List(); } } }