diff --git a/ArCueDotNet/Program.cs b/ArCueDotNet/Program.cs index e2e7cf7..0e7d282 100644 --- a/ArCueDotNet/Program.cs +++ b/ArCueDotNet/Program.cs @@ -29,8 +29,8 @@ namespace ArCueDotNet try { CUESheet cueSheet = new CUESheet(config); - cueSheet.Open(pathIn, false); - cueSheet.GenerateFilenames(OutputAudioFormat.NoAudio, pathIn); + cueSheet.Open(pathIn); + cueSheet.GenerateFilenames(OutputAudioFormat.NoAudio, false, pathIn); cueSheet.AccurateRip = AccurateRipMode.Verify; cueSheet.WriteAudioFiles(Path.GetDirectoryName(pathIn), CUEStyle.SingleFile); cueSheet.GenerateAccurateRipLog(sw); diff --git a/CUERipper/CUERipper.csproj b/CUERipper/CUERipper.csproj index a35354e..e1b2462 100644 --- a/CUERipper/CUERipper.csproj +++ b/CUERipper/CUERipper.csproj @@ -125,6 +125,10 @@ {6458A13A-30EF-45A9-9D58-E5031B17BEE2} CUETools.Codecs + + {4911BD82-49EF-4858-8B51-5394F86739A4} + CUETools.Processor + {8CF07381-BEA2-4AFC-B3DD-9B2F21C65A3A} CUETools.Ripper.SCSI diff --git a/CUERipper/frmCUERipper.Designer.cs b/CUERipper/frmCUERipper.Designer.cs index f1bc0cf..475c78a 100644 --- a/CUERipper/frmCUERipper.Designer.cs +++ b/CUERipper/frmCUERipper.Designer.cs @@ -136,34 +136,34 @@ namespace CUERipper // comboLossless // this.comboLossless.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - resources.ApplyResources(this.comboLossless, "comboLossless"); this.comboLossless.FormattingEnabled = true; this.comboLossless.Items.AddRange(new object[] { resources.GetString("comboLossless.Items"), resources.GetString("comboLossless.Items1"), resources.GetString("comboLossless.Items2")}); + resources.ApplyResources(this.comboLossless, "comboLossless"); this.comboLossless.Name = "comboLossless"; // // comboCodec // this.comboCodec.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - resources.ApplyResources(this.comboCodec, "comboCodec"); this.comboCodec.FormattingEnabled = true; this.comboCodec.Items.AddRange(new object[] { resources.GetString("comboCodec.Items"), resources.GetString("comboCodec.Items1"), resources.GetString("comboCodec.Items2"), resources.GetString("comboCodec.Items3")}); + resources.ApplyResources(this.comboCodec, "comboCodec"); this.comboCodec.Name = "comboCodec"; // // comboImage // this.comboImage.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - resources.ApplyResources(this.comboImage, "comboImage"); this.comboImage.FormattingEnabled = true; this.comboImage.Items.AddRange(new object[] { resources.GetString("comboImage.Items"), resources.GetString("comboImage.Items1")}); + resources.ApplyResources(this.comboImage, "comboImage"); this.comboImage.Name = "comboImage"; // // buttonAbort diff --git a/CUERipper/frmCUERipper.cs b/CUERipper/frmCUERipper.cs index 1e57be6..2571ef3 100644 --- a/CUERipper/frmCUERipper.cs +++ b/CUERipper/frmCUERipper.cs @@ -7,21 +7,29 @@ using System.Drawing; using System.Text; using System.Threading; using System.Windows.Forms; -using CUETools.Ripper.SCSI; +using CUETools.AccurateRip; using CUETools.CDImage; +using CUETools.Codecs; +using CUETools.Processor; +using CUETools.Ripper.SCSI; using MusicBrainz; namespace CUERipper { public partial class frmCUERipper : Form { - private CDDriveReader _reader = null; private Thread _workThread = null; + private CDDriveReader _reader = null; private StartStop _startStop; + private CUEConfig _config; + private OutputAudioFormat _format; + private CUEStyle _style; + private CUESheet _cueSheet; public frmCUERipper() { InitializeComponent(); + _config = new CUEConfig(); _startStop = new StartStop(); } @@ -44,9 +52,12 @@ namespace CUERipper private void SetupControls () { bool running = _workThread != null; - listTracks.Enabled = !running; - comboDrives.Enabled = !running; - comboRelease.Enabled = !running; + listTracks.Enabled = + comboDrives.Enabled = + comboRelease.Enabled = + comboCodec.Enabled = + comboImage.Enabled = + comboLossless.Enabled = !running; buttonPause.Visible = buttonPause.Enabled = buttonAbort.Visible = buttonAbort.Enabled = running; buttonGo.Visible = buttonGo.Enabled = !running; toolStripStatusLabel1.Text = String.Empty; @@ -95,26 +106,57 @@ namespace CUERipper CDDriveReader audioSource = (CDDriveReader)o; audioSource.ReadProgress += new EventHandler(CDReadProgress); int[,] buff = new int[audioSource.BestBlockSize, audioSource.ChannelCount]; + AccurateRipVerify arVerify = new AccurateRipVerify(audioSource.TOC); + string ArId = AccurateRipVerify.CalculateAccurateRipId(audioSource.TOC); + + arVerify.ContactAccurateRip(ArId); + + IAudioDest audioDest = null; try { audioSource.Position = 0; - do + if (_style == CUEStyle.SingleFile || _style == CUEStyle.SingleFileWithCUE) + audioDest = AudioReadWrite.GetAudioDest(_cueSheet.SingleFilename, (long)audioSource.Length, _config); + + for (int iTrack = 0; iTrack <= audioSource.TOC.AudioTracks; iTrack++) { - uint toRead = Math.Min((uint)buff.GetLength(0), (uint)audioSource.Remaining); - uint samplesRead = audioSource.Read(buff, toRead); - if (samplesRead == 0) break; - if (samplesRead != toRead) - throw new Exception("samples read != samples requested"); - //arVerify.Write(buff, samplesRead); - //audioDest.Write(buff, samplesRead); - } while (true); + uint samplesRemTrack = (iTrack > 0 ? audioSource.TOC[iTrack].Length : audioSource.TOC.Pregap) * 588; + if (_style == CUEStyle.GapsAppended) + { + if (audioDest != null) + audioDest.Close(); + audioDest = AudioReadWrite.GetAudioDest(iTrack > 0 ? _cueSheet.TrackFilenames[iTrack - 1] : _cueSheet.HTOAFilename, (long)samplesRemTrack, _config); + } + while (samplesRemTrack > 0) + { + uint toRead = Math.Min((uint)buff.GetLength(0), (uint)samplesRemTrack); + uint samplesRead = audioSource.Read(buff, toRead); + if (samplesRead != toRead) + throw new Exception("samples read != samples requested"); + arVerify.Write(buff, samplesRead); + audioDest.Write(buff, samplesRead); + samplesRemTrack -= samplesRead; + } + } + + if (audioDest != null) + audioDest.Close(); + audioDest = null; } catch (StopException) { + if (audioDest != null) + try { audioDest.Close(); } + catch { }; + audioDest = null; } catch (Exception ex) { + if (audioDest != null) + try { audioDest.Close(); } + catch { }; + audioDest = null; this.Invoke((MethodInvoker)delegate() { string message = "Exception"; @@ -134,6 +176,18 @@ namespace CUERipper { if (_reader == null) return; + + _config.lossyWAVHybrid = comboLossless.SelectedIndex == 1; // _cueSheet.Config? + _config.singleFilenameFormat = "%D - %C.cue"; + _format = (string)comboCodec.SelectedItem == "wav" ? OutputAudioFormat.WAV : + (string)comboCodec.SelectedItem == "flac" ? OutputAudioFormat.FLAC : + (string)comboCodec.SelectedItem == "wv" ? OutputAudioFormat.WavPack : + (string)comboCodec.SelectedItem == "ape" ? OutputAudioFormat.APE : + OutputAudioFormat.NoAudio; + _style = comboImage.SelectedIndex == 0 ? CUEStyle.SingleFileWithCUE : + CUEStyle.GapsAppended; + _cueSheet.GenerateFilenames(_format, comboLossless.SelectedIndex != 0, "."); + _workThread = new Thread(Rip); _workThread.Priority = ThreadPriority.BelowNormal; _workThread.IsBackground = true; @@ -174,26 +228,22 @@ namespace CUERipper { if (e.ListItem is string) return; - ReadOnlyCollection events = ((Release)e.ListItem).GetEvents(); - string year = events.Count > 0 ? events[0].Date.Substring(0, 4) + ": " : ""; - e.Value = string.Format("{0}{1} - {2}", year, ((Release)e.ListItem).GetArtist(), ((Release)e.ListItem).GetTitle()); + CUELine date = General.FindCUELine(((CUESheet)e.ListItem).Attributes, "REM", "DATE"); + e.Value = string.Format("{0}{1} - {2}", date != null ? date.Params[2] + ": " : "", ((CUESheet)e.ListItem).Artist, ((CUESheet)e.ListItem).Title); } private void comboRelease_SelectedIndexChanged(object sender, EventArgs e) { listTracks.Items.Clear(); if (comboRelease.SelectedItem == null || comboRelease.SelectedItem is string) - { - for (int i = 1; i <= _reader.TOC.AudioTracks; i++) - listTracks.Items.Add(new ListViewItem(new string[] { "Track " + _reader.TOC[i].Number.ToString(), _reader.TOC[i].Number.ToString(), _reader.TOC[i].StartMSF, _reader.TOC[i].LengthMSF })); return; - } - Release release = (Release) comboRelease.SelectedItem; + _cueSheet = (CUESheet)comboRelease.SelectedItem; for (int i = 1; i <= _reader.TOC.AudioTracks; i++) - { - Track track = release.GetTracks()[(int)_reader.TOC[i].Number - 1]; - listTracks.Items.Add(new ListViewItem(new string[] { track.GetTitle(), _reader.TOC[i].Number.ToString(), _reader.TOC[i].StartMSF, _reader.TOC[i].LengthMSF })); - } + listTracks.Items.Add(new ListViewItem(new string[] { + _cueSheet.Tracks[i-1].Title, + _reader.TOC[i].Number.ToString(), + _reader.TOC[i].StartMSF, + _reader.TOC[i].LengthMSF })); } private void MusicBrainz_LookupProgress(object sender, XmlRequestEventArgs e) @@ -230,23 +280,41 @@ namespace CUERipper CDDriveReader audioSource = (CDDriveReader)o; ReleaseQueryParameters p = new ReleaseQueryParameters(); - p.DiscId = _reader.TOC.MusicBrainzId; + p.DiscId = audioSource.TOC.MusicBrainzId; Query results = Release.Query(p); MusicBrainzService.XmlRequest += new EventHandler(MusicBrainz_LookupProgress); foreach (Release release in results) { release.GetEvents(); release.GetTracks(); + this.BeginInvoke((MethodInvoker)delegate() { - comboRelease.Items.Add(release); + CUESheet cueSheet = new CUESheet(_config); + cueSheet.OpenTOC(audioSource.TOC); + cueSheet.Artist = release.GetArtist(); + cueSheet.Title = release.GetTitle(); + if (release.GetEvents().Count > 0) + General.SetCUELine(cueSheet.Attributes, "REM", "DATE", release.GetEvents()[0].Date.Substring(0, 4), false); + for (int i = 1; i <= audioSource.TOC.AudioTracks; i++) + { + Track track = release.GetTracks()[(int)audioSource.TOC[i].Number - 1]; + cueSheet.Tracks[i - 1].Title = track.GetTitle(); + cueSheet.Tracks[i - 1].Artist = track.GetArtist(); + } + comboRelease.Items.Add(cueSheet); }); } MusicBrainzService.XmlRequest -= new EventHandler(MusicBrainz_LookupProgress); this.BeginInvoke((MethodInvoker)delegate() { if (comboRelease.Items.Count == 0) - comboRelease.Items.Add("MusicBrainz: not found"); + { + CUESheet cueSheet = new CUESheet(_config); + cueSheet.OpenTOC(audioSource.TOC); + comboRelease.Items.Add(cueSheet); + // cueSheet.Tracks[i - 1].Title = "Track " + _reader.TOC[i].Number.ToString(); + } }); _workThread = null; this.BeginInvoke((MethodInvoker)delegate() diff --git a/CUERipper/frmCUERipper.resx b/CUERipper/frmCUERipper.resx index c675e9c..f7d7834 100644 --- a/CUERipper/frmCUERipper.resx +++ b/CUERipper/frmCUERipper.resx @@ -270,9 +270,6 @@ 6 - - False - lossless @@ -303,14 +300,11 @@ 5 - - False - flac - WavPack + wv wav @@ -339,9 +333,6 @@ 4 - - False - image diff --git a/CUETools.Codecs.APE/CUETools.Codecs.APE.cpp b/CUETools.Codecs.APE/CUETools.Codecs.APE.cpp new file mode 100644 index 0000000..6542b91 --- /dev/null +++ b/CUETools.Codecs.APE/CUETools.Codecs.APE.cpp @@ -0,0 +1,558 @@ +// This is the main DLL file. + +using namespace System; +using namespace System::Text; +using namespace System::Collections::Generic; +using namespace System::Collections::Specialized; +using namespace System::Runtime::InteropServices; +using namespace System::IO; +using namespace APETagsDotNet; +using namespace CUETools::Codecs; + +#ifndef _WAVEFORMATEX_ +#define _WAVEFORMATEX_ + +#define BOOL int +#define TRUE 1 +#define FALSE 0 +#define HWND long + +/* + * extended waveform format structure used for all non-PCM formats. this + * structure is common to all non-PCM formats. + */ +typedef struct tWAVEFORMATEX +{ + Int16 wFormatTag; /* format type */ + Int16 nChannels; /* number of channels (i.e. mono, stereo...) */ + Int32 nSamplesPerSec; /* sample rate */ + Int32 nAvgBytesPerSec; /* for buffer estimation */ + Int16 nBlockAlign; /* block size of data */ + Int16 wBitsPerSample; /* number of bits per sample of mono data */ + Int16 cbSize; /* the count in bytes of the size of */ + /* extra information (after cbSize) */ +} WAVEFORMATEX, *PWAVEFORMATEX, *NPWAVEFORMATEX, *LPWAVEFORMATEX; + +#endif /* _WAVEFORMATEX_ */ + +#include "All.h" +#include "MACLib.h" +#include "IO.h" + +namespace CUETools { namespace Codecs { namespace APE { + + class CWinFileIO : public CIO + { + public: + + // construction / destruction + CWinFileIO(GCHandle gchIO, GCHandle gchBuffer) + { + _gchIO = gchIO; + _gchBuffer = gchBuffer; + } + ~CWinFileIO() + { + } + + // open / close + int Open(const wchar_t * pName) + { + throw gcnew Exception("CIO::Open Unsupported."); + } + int Close() + { + throw gcnew Exception("CIO::Close Unsupported."); + } + + // read / write + int Read(void * pBuffer, unsigned int nBytesToRead, unsigned int * pBytesRead); + int Write(const void * pBuffer, unsigned int nBytesToWrite, unsigned int * pBytesWritten); + + // seek + int Seek(int nDistance, unsigned int nMoveMode); + + // other functions + int SetEOF() + { + throw gcnew Exception("CIO::SetEOF unsupported."); + } + + // creation / destruction + int Create(const wchar_t * pName) + { + throw gcnew Exception("CIO::Create unsupported."); + } + + int Delete() + { + throw gcnew Exception("CIO::Delete unsupported."); + } + + // attributes + int GetPosition(); + int GetSize(); + + int GetName(wchar_t * pBuffer) + { + throw gcnew Exception("CIO::GetName unsupported."); + } + + private: + GCHandle _gchIO; + GCHandle _gchBuffer; + }; + + public ref class APEReader : public IAudioSource + { + public: + APEReader(String^ path, Stream^ IO) + { + pAPEDecompress = NULL; + _sampleOffset = 0; + _bufferOffset = 0; + _bufferLength = 0; + _path = path; + + int nRetVal = 0; + + _IO = (IO != nullptr) ? IO : gcnew FileStream (path, FileMode::Open, FileAccess::Read, FileShare::Read); + _readBuffer = gcnew array(0x4000); + _gchIO = GCHandle::Alloc(_IO); + _gchReadBuffer = GCHandle::Alloc(_readBuffer); + _winFileIO = new CWinFileIO(_gchIO, _gchReadBuffer); + pAPEDecompress = CreateIAPEDecompressEx (_winFileIO, &nRetVal); + if (!pAPEDecompress) { + throw gcnew Exception("Unable to open file."); + } + + _sampleRate = pAPEDecompress->GetInfo (APE_INFO_SAMPLE_RATE, 0, 0); + _bitsPerSample = pAPEDecompress->GetInfo (APE_INFO_BITS_PER_SAMPLE, 0, 0); + _channelCount = pAPEDecompress->GetInfo (APE_INFO_CHANNELS, 0, 0); + + // make a buffer to hold 16384 blocks of audio data + nBlockAlign = pAPEDecompress->GetInfo (APE_INFO_BLOCK_ALIGN, 0, 0); + _samplesBuffer = gcnew array (16384 * nBlockAlign); + + // loop through the whole file + _sampleCount = pAPEDecompress->GetInfo (APE_DECOMPRESS_TOTAL_BLOCKS, 0, 0); // * ? + } + + ~APEReader () + { + if (_winFileIO) + delete _winFileIO; + if (_gchIO.IsAllocated) + _gchIO.Free(); + if (_gchReadBuffer.IsAllocated) + _gchReadBuffer.Free(); + } + + virtual property Int32 BitsPerSample { + Int32 get() { + return _bitsPerSample; + } + } + + virtual property Int32 ChannelCount { + Int32 get() { + return _channelCount; + } + } + + virtual property Int32 SampleRate { + Int32 get() { + return _sampleRate; + } + } + + virtual property UInt64 Length { + UInt64 get() { + return _sampleCount; + } + } + + virtual property UInt64 Position + { + UInt64 get() { + return _sampleOffset - SamplesInBuffer; + } + void set(UInt64 offset) { + _sampleOffset = offset; + _bufferOffset = 0; + _bufferLength = 0; + if (pAPEDecompress->Seek ((int) offset /*? */)) + throw gcnew Exception("Unable to seek."); + } + } + + virtual property UInt64 Remaining { + UInt64 get() { + return _sampleCount - _sampleOffset + SamplesInBuffer; + } + } + + virtual void Close() + { + if (pAPEDecompress) + { + delete pAPEDecompress; + pAPEDecompress = NULL; + } + if (_IO != nullptr) + { + _IO->Close (); + _IO = nullptr; + } + } + + virtual property String^ Path { + String^ get() { + return _path; + } + } + + virtual property NameValueCollection^ Tags { + NameValueCollection^ get () + { + if (!_tags) + { + APETagDotNet^ apeTag = gcnew APETagDotNet (_IO, true); + _tags = apeTag->GetStringTags (true); + apeTag->Close (); + } + return _tags; + } + void set (NameValueCollection ^tags) + { + _tags = tags; + } + } + + virtual bool UpdateTags(bool preserveTime) + { + Close (); + APETagDotNet^ apeTag = gcnew APETagDotNet (_path, true, false); + apeTag->SetStringTags (_tags, true); + apeTag->Save(); + apeTag->Close(); + return true; + } + + virtual UInt32 Read([Out] array^ buff, UInt32 sampleCount) + { + UInt32 buffOffset = 0; + UInt32 samplesNeeded = sampleCount; + + while (samplesNeeded != 0) + { + if (SamplesInBuffer == 0) + { + int nBlocksRetrieved; + pin_ptr pSampleBuffer = &_samplesBuffer[0]; + if (pAPEDecompress->GetData ((char *) pSampleBuffer, 16384, &nBlocksRetrieved)) + throw gcnew Exception("An error occurred while decoding."); + _bufferOffset = 0; + _bufferLength = nBlocksRetrieved; + _sampleOffset += nBlocksRetrieved; + } + UInt32 copyCount = Math::Min(samplesNeeded, SamplesInBuffer); + AudioSamples::BytesToFLACSamples_16(_samplesBuffer, _bufferOffset*nBlockAlign, buff, buffOffset, copyCount, _channelCount); + samplesNeeded -= copyCount; + buffOffset += copyCount; + _bufferOffset += copyCount; + } + return sampleCount; + } + + private: + IAPEDecompress * pAPEDecompress; + + NameValueCollection^ _tags; + Int64 _sampleCount, _sampleOffset; + Int32 _bitsPerSample, _channelCount, _sampleRate; + UInt32 _bufferOffset, _bufferLength; + int nBlockAlign; + array^ _samplesBuffer; + String^ _path; + Stream^ _IO; + array^ _readBuffer; + CWinFileIO* _winFileIO; + GCHandle _gchIO, _gchReadBuffer; + + property UInt32 SamplesInBuffer + { + UInt32 get () + { + return (UInt32) (_bufferLength - _bufferOffset); + } + } + }; + + public ref class APEWriter : IAudioDest + { + public: + APEWriter(String^ path, Int32 bitsPerSample, Int32 channelCount, Int32 sampleRate) + { + if (channelCount != 1 && channelCount != 2) + throw gcnew Exception("Only stereo and mono audio formats are allowed."); + if (bitsPerSample != 16 && bitsPerSample != 24) + throw gcnew Exception("Monkey's Audio doesn't support selected bits per sample value."); + + _path = path; + _tags = gcnew NameValueCollection(); + _winFileIO = NULL; + + _compressionLevel = COMPRESSION_LEVEL_NORMAL; + + _bitsPerSample = bitsPerSample; + _channelCount = channelCount; + _sampleRate = sampleRate; + _blockAlign = _channelCount * ((_bitsPerSample + 7) / 8); + + int nRetVal; + pAPECompress = CreateIAPECompress (&nRetVal); + if (!pAPECompress) + throw gcnew Exception("Unable to open APE compressor."); + } + + ~APEWriter() + { + if (_winFileIO) + delete _winFileIO; + if (_gchIO.IsAllocated) + _gchIO.Free(); + if (_gchBuffer.IsAllocated) + _gchBuffer.Free(); + } + + virtual void Close() + { + if (pAPECompress) + { + pAPECompress->Finish (NULL, 0, 0); + delete pAPECompress; + pAPECompress = NULL; + } + + if ((_finalSampleCount != 0) && (_samplesWritten != _finalSampleCount)) { + throw gcnew Exception("Samples written differs from the expected sample count."); + } + + if (_tags->Count > 0) + { + APETagDotNet^ apeTag = gcnew APETagDotNet (_IO, true); + apeTag->SetStringTags (_tags, true); + apeTag->Save(); + apeTag->Close(); + _tags->Clear (); + } + + if (_IO != nullptr) + { + _IO->Close (); + _IO = nullptr; + } + } + + virtual void Delete() + { + try { Close (); } catch (Exception^) {} + File::Delete(_path); + } + + virtual property Int64 FinalSampleCount + { + Int64 get() + { + return _finalSampleCount; + } + void set(Int64 value) + { + if (value < 0) + throw gcnew Exception("Invalid final sample count."); + if (_initialized) + throw gcnew Exception("Final sample count cannot be changed after encoding begins."); + _finalSampleCount = value; + } + } + + virtual property Int64 BlockSize + { + void set(Int64 value) + { + } + } + + virtual property int BitsPerSample + { + int get() { return _bitsPerSample; } + } + + virtual void Write(array^ buff, UInt32 sampleCount) + { + if (_sampleBuffer == nullptr || _sampleBuffer.Length < sampleCount * _blockAlign) + _sampleBuffer = gcnew array(sampleCount * _blockAlign); + AudioSamples::FLACSamplesToBytes(buff, 0, _sampleBuffer, 0, sampleCount, _channelCount, _bitsPerSample); + if (!_initialized) Initialize(); + pin_ptr pSampleBuffer = &_sampleBuffer[0]; + if (pAPECompress->AddData (pSampleBuffer, sampleCount * _blockAlign)) + throw gcnew Exception("An error occurred while encoding."); + _samplesWritten += sampleCount; + } + + virtual property String^ Path + { + String^ get() { + return _path; + } + } + + virtual bool SetTags (NameValueCollection^ tags) + { + _tags = tags; + return true; + } + + property Int32 CompressionLevel { + Int32 get() { + return _compressionLevel; + } + void set(Int32 value) { + if ((value < 1) || (value > 5)) { + throw gcnew Exception("Invalid compression mode."); + } + _compressionLevel = value * 1000; + } + } + + private: + IAPECompress * pAPECompress; + bool _initialized; + Int32 _finalSampleCount, _samplesWritten; + Int32 _bitsPerSample, _channelCount, _sampleRate, _blockAlign; + Int32 _compressionLevel; + NameValueCollection^ _tags; + String^ _path; + Stream^ _IO; + GCHandle _gchIO, _gchBuffer; + CWinFileIO* _winFileIO; + array^ _writeBuffer; + array^ _sampleBuffer; + + void Initialize() { + _IO = gcnew FileStream (_path, FileMode::Create, FileAccess::ReadWrite, FileShare::Read); + _writeBuffer = gcnew array(0x4000); + + _gchIO = GCHandle::Alloc(_IO); + _gchBuffer = GCHandle::Alloc(_writeBuffer); + _winFileIO = new CWinFileIO(_gchIO, _gchBuffer); + + WAVEFORMATEX waveFormat; + FillWaveFormatEx (&waveFormat, _sampleRate, _bitsPerSample, _channelCount); + + int res = pAPECompress->StartEx (_winFileIO, + &waveFormat, + (_finalSampleCount == 0) ? MAX_AUDIO_BYTES_UNKNOWN : _finalSampleCount * _blockAlign, + _compressionLevel, + NULL, + CREATE_WAV_HEADER_ON_DECOMPRESSION); + if (res) + throw gcnew Exception("Unable to create the encoder."); + + _initialized = true; + } + }; + + int CWinFileIO::Read(void * pBuffer, unsigned int nBytesToRead, unsigned int * pBytesRead) + { + array^ buff = (array^) _gchBuffer.Target; + if (buff->Length < nBytesToRead) + { + Array::Resize (buff, nBytesToRead); + _gchBuffer.Target = buff; + } + int len = ((Stream^)_gchIO.Target)->Read (buff, 0, nBytesToRead); + if (len) Marshal::Copy (buff, 0, (IntPtr)pBuffer, len); + *pBytesRead = len; + return 0; + } + + int CWinFileIO::Write(const void * pBuffer, unsigned int nBytesToWrite, unsigned int * pBytesWritten) + { + array^ buff = (array^) _gchBuffer.Target; + if (buff->Length < nBytesToWrite) + { + Array::Resize (buff, nBytesToWrite); + _gchBuffer.Target = buff; + } + Marshal::Copy ((IntPtr)(void*)pBuffer, buff, 0, nBytesToWrite); + ((Stream^)_gchIO.Target)->Write (buff, 0, nBytesToWrite); + *pBytesWritten = nBytesToWrite; + return 0; + } + + int CWinFileIO::GetPosition() + { + return ((Stream^)_gchIO.Target)->Position; + } + + int CWinFileIO::GetSize() + { + return ((Stream^)_gchIO.Target)->Length; + } + + int CWinFileIO::Seek(int delta, unsigned int mode) + { + switch (mode) + { + case FILE_BEGIN: + ((Stream^)_gchIO.Target)->Seek (delta, System::IO::SeekOrigin::Begin); + break; + case FILE_END: + ((Stream^)_gchIO.Target)->Seek (delta, System::IO::SeekOrigin::End); + break; + case FILE_CURRENT: + ((Stream^)_gchIO.Target)->Seek (delta, System::IO::SeekOrigin::Current); + break; + default: + return -1; + } + return 0; + } + +#if 0 +extern "C" +{ + BOOL GetMMXAvailable(); + int CalculateDotProduct8(const short* pA, const short* pB, int nOrder); +}; + + public ref class SSE2Functions + { + public: + SSE2Functions () + { + _haveSSE2 = GetMMXAvailable(); + } + int SumInts (short*a, short*b, int count) + { + if (_haveSSE2 && count == 8) + return CalculateDotProduct8(a, b, count); + int sum = 0; + for (int j = 0; j < count; j++) + sum += a[j] * b[j]; + return sum; + } + int SumInts (int*a, short*b, int count) + { + int sum = 0; + for (int j = 0; j < count; j++) + sum += a[j] * b[j]; + return sum; + } + private: + bool _haveSSE2; + }; +#endif +}}} diff --git a/CUETools.Codecs.FLAC/AssemblyInfo.cpp b/CUETools.Codecs.FLAC/AssemblyInfo.cpp index ff2a0c9..b4a2e61 100644 --- a/CUETools.Codecs.FLAC/AssemblyInfo.cpp +++ b/CUETools.Codecs.FLAC/AssemblyInfo.cpp @@ -10,7 +10,7 @@ using namespace System::Security::Permissions; // set of attributes. Change these attribute values to modify the information // associated with an assembly. // -[assembly:AssemblyTitleAttribute("FLACDotNet")]; +[assembly:AssemblyTitleAttribute("CUETools.Codecs.FLAC")]; [assembly:AssemblyDescriptionAttribute("")]; [assembly:AssemblyConfigurationAttribute("")]; [assembly:AssemblyCompanyAttribute("")]; diff --git a/CUETools.Codecs.LossyWAV/LossyWAV.cs b/CUETools.Codecs.LossyWAV/LossyWAV.cs index a075711..0784e74 100644 --- a/CUETools.Codecs.LossyWAV/LossyWAV.cs +++ b/CUETools.Codecs.LossyWAV/LossyWAV.cs @@ -85,8 +85,10 @@ namespace CUETools.Codecs.LossyWAV process_this_codec_block(); } } + if (_lwcdfDest != null) + try { _lwcdfDest.Close(); } + catch { } if (_audioDest != null) _audioDest.Close(); - if (_lwcdfDest != null) _lwcdfDest.Close(); } public void Write(int[,] buff, uint sampleCount) diff --git a/CUETools.Codecs.WavPack/CUETools.Codecs.WavPack.cpp b/CUETools.Codecs.WavPack/CUETools.Codecs.WavPack.cpp new file mode 100644 index 0000000..5d6434c --- /dev/null +++ b/CUETools.Codecs.WavPack/CUETools.Codecs.WavPack.cpp @@ -0,0 +1,561 @@ +// **************************************************************************** +// +// Copyright (c) 2006-2007 Moitah (moitah@yahoo.com) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the author nor the names of its contributors may be +// used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// **************************************************************************** + +using namespace System; +using namespace System::Runtime::InteropServices; +using namespace System::Collections::Specialized; +using namespace System::Security::Cryptography; +using namespace System::IO; +using namespace APETagsDotNet; +using namespace CUETools::Codecs; + +#include +#include +#include "wavpack.h" +#include + +namespace CUETools { namespace Codecs { namespace WavPack { + int write_block(void *id, void *data, int32_t length); + + [UnmanagedFunctionPointer(CallingConvention::Cdecl)] + public delegate int32_t DecoderReadDelegate(void *id, void *data, int32_t bcount); + [UnmanagedFunctionPointer(CallingConvention::Cdecl)] + public delegate uint32_t DecoderTellDelegate(void *id); + [UnmanagedFunctionPointer(CallingConvention::Cdecl)] + public delegate int DecoderSeekDelegate(void *id, uint32_t pos); + [UnmanagedFunctionPointer(CallingConvention::Cdecl)] + public delegate int DecoderSeekRelativeDelegate(void *id, int32_t delta, int mode); + [UnmanagedFunctionPointer(CallingConvention::Cdecl)] + public delegate int DecoderPushBackDelegate(void *id, int c); + [UnmanagedFunctionPointer(CallingConvention::Cdecl)] + public delegate uint32_t DecoderLengthDelegate(void *id); + [UnmanagedFunctionPointer(CallingConvention::Cdecl)] + public delegate int DecoderCanSeekDelegate(void *id); + + public ref class WavPackReader : public IAudioSource + { + public: + WavPackReader(String^ path, Stream^ IO, Stream^ IO_WVC) + { + char errorMessage[256]; + + _readDel = gcnew DecoderReadDelegate (this, &WavPackReader::ReadCallback); + _tellDel = gcnew DecoderTellDelegate (this, &WavPackReader::TellCallback); + _seekDel = gcnew DecoderSeekDelegate (this, &WavPackReader::SeekCallback); + _seekRelDel = gcnew DecoderSeekRelativeDelegate (this, &WavPackReader::SeekRelCallback); + _pushBackDel = gcnew DecoderPushBackDelegate (this, &WavPackReader::PushBackCallback); + _lengthDel = gcnew DecoderLengthDelegate (this, &WavPackReader::LengthCallback); + _canSeekDel = gcnew DecoderCanSeekDelegate (this, &WavPackReader::CanSeekCallback); + + ioReader = new WavpackStreamReader; + ioReader->read_bytes = (int32_t (*)(void *, void *, int32_t)) Marshal::GetFunctionPointerForDelegate(_readDel).ToPointer(); + ioReader->get_pos = (uint32_t (*)(void *)) Marshal::GetFunctionPointerForDelegate(_tellDel).ToPointer(); + ioReader->set_pos_abs = (int (*)(void *, uint32_t)) Marshal::GetFunctionPointerForDelegate(_seekDel).ToPointer(); + ioReader->set_pos_rel = (int (*)(void *, int32_t, int)) Marshal::GetFunctionPointerForDelegate(_seekRelDel).ToPointer(); + ioReader->push_back_byte = (int (*)(void *, int)) Marshal::GetFunctionPointerForDelegate(_pushBackDel).ToPointer(); + ioReader->get_length = (uint32_t (*)(void *)) Marshal::GetFunctionPointerForDelegate(_lengthDel).ToPointer(); + ioReader->can_seek = (int (*)(void *)) Marshal::GetFunctionPointerForDelegate(_canSeekDel).ToPointer(); + ioReader->write_bytes = NULL; + + _IO_ungetc = _IO_WVC_ungetc = -1; + + _path = path; + + _IO = (IO != nullptr) ? IO : gcnew FileStream (path, FileMode::Open, FileAccess::Read, FileShare::Read); + _IO_WVC = (IO != nullptr) ? IO_WVC : System::IO::File::Exists (path+"c") ? gcnew FileStream (path+"c", FileMode::Open, FileAccess::Read, FileShare::Read) : nullptr; + + _wpc = WavpackOpenFileInputEx (ioReader, "v", _IO_WVC != nullptr ? "c" : NULL, errorMessage, OPEN_WVC, 0); + if (_wpc == NULL) { + throw gcnew Exception("Unable to initialize the decoder."); + } + + _bitsPerSample = WavpackGetBitsPerSample(_wpc); + _channelCount = WavpackGetNumChannels(_wpc); + _sampleRate = WavpackGetSampleRate(_wpc); + _sampleCount = WavpackGetNumSamples(_wpc); + _sampleOffset = 0; + } + + ~WavPackReader() + { + delete ioReader; + } + + virtual property Int32 BitsPerSample { + Int32 get() { + return _bitsPerSample; + } + } + + virtual property Int32 ChannelCount { + Int32 get() { + return _channelCount; + } + } + + virtual property Int32 SampleRate { + Int32 get() { + return _sampleRate; + } + } + + virtual property UInt64 Length { + UInt64 get() { + return _sampleCount; + } + } + + virtual property UInt64 Position { + UInt64 get() { + return _sampleOffset; + } + void set(UInt64 offset) { + _sampleOffset = offset; + if (!WavpackSeekSample(_wpc, offset)) { + throw gcnew Exception("Unable to seek."); + } + } + } + + virtual property UInt64 Remaining { + UInt64 get() { + return _sampleCount - _sampleOffset; + } + } + + virtual property String^ Path { + String^ get() { + return _path; + } + } + + virtual property NameValueCollection^ Tags { + NameValueCollection^ get () { + if (!_tags) + { + APETagDotNet^ apeTag = gcnew APETagDotNet (_IO, true); + _tags = apeTag->GetStringTags (true); + apeTag->Close (); + } + return _tags; + } + void set (NameValueCollection ^tags) { + _tags = tags; + } + } + + virtual bool UpdateTags(bool preserveTime) + { + Close (); + APETagDotNet^ apeTag = gcnew APETagDotNet (_path, true, false); + apeTag->SetStringTags (_tags, true); + apeTag->Save(); + apeTag->Close(); + return true; + } + + virtual void Close() + { + if (_wpc != NULL) + _wpc = WavpackCloseFile(_wpc); + if (_IO != nullptr) + { + _IO->Close (); + _IO = nullptr; + } + if (_IO_WVC != nullptr) + { + _IO_WVC->Close (); + _IO_WVC = nullptr; + } + } + + virtual UInt32 Read(array^ sampleBuffer, UInt32 sampleCount) + { + pin_ptr pSampleBuffer = &sampleBuffer[0, 0]; + int samplesRead = WavpackUnpackSamples(_wpc, pSampleBuffer, sampleCount); + _sampleOffset += samplesRead; + if (samplesRead != sampleCount) + throw gcnew Exception("Decoder returned a different number of samples than requested."); + return sampleCount; + } + + private: + WavpackContext *_wpc; + NameValueCollection^ _tags; + Int32 _sampleCount, _sampleOffset; + Int32 _bitsPerSample, _channelCount, _sampleRate; + String^ _path; + Stream^ _IO; + Stream^ _IO_WVC; + DecoderReadDelegate^ _readDel; + DecoderTellDelegate^ _tellDel; + DecoderSeekDelegate^ _seekDel; + DecoderSeekRelativeDelegate^ _seekRelDel; + DecoderPushBackDelegate^ _pushBackDel; + DecoderLengthDelegate^ _lengthDel; + DecoderCanSeekDelegate^ _canSeekDel; + array^ _readBuffer; + int _IO_ungetc, _IO_WVC_ungetc; + WavpackStreamReader* ioReader; + + int32_t ReadCallback (void *id, void *data, int32_t bcount) + { + Stream^ IO = (*(char*)id=='c') ? _IO_WVC : _IO; + int IO_ungetc = (*(char*)id=='c') ? _IO_WVC_ungetc : _IO_ungetc; + int unget_len = 0; + + if (IO_ungetc != -1) + { + *(unsigned char*)data = (unsigned char) IO_ungetc; + if (IO == _IO) + _IO_ungetc = -1; + else + _IO_WVC_ungetc = -1; + bcount --; + if (!bcount) + return 1; + data = 1 + (unsigned char*)data; + unget_len = 1; + } + + if (_readBuffer == nullptr || _readBuffer->Length < bcount) + _readBuffer = gcnew array(bcount < 0x4000 ? 0x4000 : bcount); + int len = IO->Read (_readBuffer, 0, bcount); + if (len) Marshal::Copy (_readBuffer, 0, (IntPtr)data, len); + return len + unget_len; + } + + uint32_t TellCallback(void *id) + { + Stream^ IO = (*(char*)id=='c') ? _IO_WVC : _IO; + return IO->Position; + } + + int SeekCallback (void *id, uint32_t pos) + { + Stream^ IO = (*(char*)id=='c') ? _IO_WVC : _IO; + IO->Position = pos; + return 0; + } + + int SeekRelCallback (void *id, int32_t delta, int mode) + { + Stream^ IO = (*(char*)id=='c') ? _IO_WVC : _IO; + switch (mode) + { + case SEEK_SET: + IO->Seek (delta, System::IO::SeekOrigin::Begin); + break; + case SEEK_END: + IO->Seek (delta, System::IO::SeekOrigin::End); + break; + case SEEK_CUR: + IO->Seek (delta, System::IO::SeekOrigin::Current); + break; + default: + return -1; + } + return 0; + } + + int PushBackCallback (void *id, int c) + { + Stream^ IO = (*(char*)id=='c') ? _IO_WVC : _IO; + if (IO == _IO) + { + if (_IO_ungetc != -1) + throw gcnew Exception("Double PushBackCallback unsupported."); + _IO_ungetc = c; + } else + { + if (_IO_WVC_ungetc != -1) + throw gcnew Exception("Double PushBackCallback unsupported."); + _IO_WVC_ungetc = c; + } + } + + uint32_t LengthCallback (void *id) + { + Stream^ IO = (*(char*)id=='c') ? _IO_WVC : _IO; + return IO->Length; + } + + int CanSeekCallback(void *id) + { + Stream^ IO = (*(char*)id=='c') ? _IO_WVC : _IO; + return IO->CanSeek; + } + }; + + public ref class WavPackWriter : IAudioDest + { + public: + WavPackWriter(String^ path, Int32 bitsPerSample, Int32 channelCount, Int32 sampleRate) + { + IntPtr pathChars; + + if (channelCount != 1 && channelCount != 2) + throw gcnew Exception("Only stereo and mono audio formats are allowed."); + if (bitsPerSample < 16 || bitsPerSample > 24) + throw gcnew Exception("Bits per sample must be 16..24."); + + _path = path; + _tags = gcnew NameValueCollection(); + + _compressionMode = 1; + _extraMode = 0; + _blockSize = 0; + + _bitsPerSample = bitsPerSample; + _channelCount = channelCount; + _sampleRate = sampleRate; + _blockAlign = _channelCount * ((_bitsPerSample + 7) / 8); + + pathChars = Marshal::StringToHGlobalUni(path); + _hFile = _wfopen((const wchar_t*)pathChars.ToPointer(), L"w+b"); + Marshal::FreeHGlobal(pathChars); + if (!_hFile) { + throw gcnew Exception("Unable to open file."); + } + } + + virtual void Close() + { + if (_md5Sum) + { + _md5hasher->TransformFinalBlock (gcnew array(1), 0, 0); + pin_ptr md5_digest = &_md5hasher->Hash[0]; + WavpackStoreMD5Sum (_wpc, md5_digest); + } + + WavpackFlushSamples(_wpc); + _wpc = WavpackCloseFile(_wpc); + fclose(_hFile); + + if ((_finalSampleCount != 0) && (_samplesWritten != _finalSampleCount)) { + throw gcnew Exception("Samples written differs from the expected sample count."); + } + + if (_tags->Count > 0) + { + APETagDotNet^ apeTag = gcnew APETagDotNet (_path, true, false); + apeTag->SetStringTags (_tags, true); + apeTag->Save(); + apeTag->Close(); + _tags->Clear (); + } + } + + virtual void Delete() + { + try { Close (); } catch (Exception^) {} + File::Delete(_path); + } + + virtual property Int64 FinalSampleCount + { + Int64 get() + { + return _finalSampleCount; + } + void set(Int64 value) + { + if (value < 0) + throw gcnew Exception("Invalid final sample count."); + if (_initialized) + throw gcnew Exception("Final sample count cannot be changed after encoding begins."); + _finalSampleCount = value; + } + } + + virtual property Int64 BlockSize + { + void set(Int64 value) + { + _blockSize = value; + } + } + + virtual property int BitsPerSample + { + int get() { return _bitsPerSample; } + } + + virtual void Write(array^ sampleBuffer, UInt32 sampleCount) + { + if (!_initialized) + Initialize(); + + if (MD5Sum) + { + if (_sampleBuffer == nullptr || _sampleBuffer.Length < sampleCount * _blockAlign) + _sampleBuffer = gcnew array(sampleCount * _blockAlign); + AudioSamples::FLACSamplesToBytes(sampleBuffer, 0, _sampleBuffer, 0, sampleCount, _channelCount, _bitsPerSample); + UpdateHash(_sampleBuffer, (int) sampleCount * _blockAlign); + } + + if ((_bitsPerSample & 7) != 0) + { + if (_shiftedSampleBuffer == nullptr || _shiftedSampleBuffer.GetLength(0) < sampleCount) + _shiftedSampleBuffer = gcnew array(sampleCount, _channelCount); + for (int i = 0; i < sampleCount; i++) + for (int c = 0; c < _channelCount; c++) + _shiftedSampleBuffer[i,c] = sampleBuffer[i,c] << 8 - (_bitsPerSample & 7); + pin_ptr pSampleBuffer = &_shiftedSampleBuffer[0, 0]; + if (!WavpackPackSamples(_wpc, (int32_t*)pSampleBuffer, sampleCount)) + throw gcnew Exception("An error occurred while encoding."); + } else + { + pin_ptr pSampleBuffer = &sampleBuffer[0, 0]; + if (!WavpackPackSamples(_wpc, (int32_t*)pSampleBuffer, sampleCount)) + throw gcnew Exception("An error occurred while encoding."); + } + + _samplesWritten += sampleCount; + } + + virtual property String^ Path + { + String^ get() { + return _path; + } + } + + virtual bool SetTags (NameValueCollection^ tags) + { + _tags = tags; + return true; + } + + property Int32 CompressionMode { + Int32 get() { + return _compressionMode; + } + void set(Int32 value) { + if ((value < 0) || (value > 3)) { + throw gcnew Exception("Invalid compression mode."); + } + _compressionMode = value; + } + } + + property Int32 ExtraMode { + Int32 get() { + return _extraMode; + } + void set(Int32 value) { + if ((value < 0) || (value > 6)) { + throw gcnew Exception("Invalid extra mode."); + } + _extraMode = value; + } + } + + property bool MD5Sum { + bool get() { + return _md5Sum; + } + void set(bool value) { + _md5Sum = value; + } + } + + void UpdateHash(array^ buff, Int32 len) + { + if (!_initialized) Initialize(); + + if (!_md5Sum || !_md5hasher) + throw gcnew Exception("MD5 not enabled."); + _md5hasher->TransformBlock (buff, 0, len, buff, 0); + } + + private: + FILE *_hFile; + bool _initialized; + WavpackContext *_wpc; + Int32 _finalSampleCount, _samplesWritten; + Int32 _bitsPerSample, _channelCount, _sampleRate, _blockAlign; + Int32 _compressionMode, _extraMode, _blockSize; + NameValueCollection^ _tags; + String^ _path; + bool _md5Sum; + MD5^ _md5hasher; + array^ _sampleBuffer; + array^ _shiftedSampleBuffer; + + void Initialize() { + WavpackConfig config; + + _wpc = WavpackOpenFileOutput(write_block, _hFile, NULL); + if (!_wpc) { + throw gcnew Exception("Unable to create the encoder."); + } + + memset(&config, 0, sizeof(WavpackConfig)); + config.bits_per_sample = _bitsPerSample; + config.bytes_per_sample = (_bitsPerSample + 7) / 8; + config.num_channels = _channelCount; + config.channel_mask = 5 - _channelCount; + config.sample_rate = _sampleRate; + if (_compressionMode == 0) config.flags |= CONFIG_FAST_FLAG; + if (_compressionMode == 2) config.flags |= CONFIG_HIGH_FLAG; + if (_compressionMode == 3) config.flags |= CONFIG_HIGH_FLAG | CONFIG_VERY_HIGH_FLAG; + if (_extraMode != 0) { + config.flags |= CONFIG_EXTRA_MODE; + config.xmode = _extraMode; + } + if (_md5Sum) + { + _md5hasher = gcnew MD5CryptoServiceProvider (); + config.flags |= CONFIG_MD5_CHECKSUM; + } + config.block_samples = (int)_blockSize; + if (_blockSize > 0 && _blockSize < 2048) + config.flags |= CONFIG_MERGE_BLOCKS; + + if (!WavpackSetConfiguration(_wpc, &config, (_finalSampleCount == 0) ? -1 : _finalSampleCount)) { + throw gcnew Exception("Invalid configuration setting."); + } + + if (!WavpackPackInit(_wpc)) { + throw gcnew Exception("Unable to initialize the encoder."); + } + + _initialized = true; + } + }; + +#pragma unmanaged + int write_block(void *id, void *data, int32_t length) { + return (fwrite(data, 1, length, (FILE*)id) == length); + } +}}} diff --git a/CUETools.Processor/AudioReadWrite.cs b/CUETools.Processor/AudioReadWrite.cs index a363fab..45e6a7d 100644 --- a/CUETools.Processor/AudioReadWrite.cs +++ b/CUETools.Processor/AudioReadWrite.cs @@ -1,10 +1,10 @@ using System; using System.IO; -using FLACDotNet; -using WavPackDotNet; -using APEDotNet; using CUETools.Codecs; using CUETools.Codecs.ALAC; +using CUETools.Codecs.FLAC; +using CUETools.Codecs.WavPack; +using CUETools.Codecs.APE; using CUETools.Codecs.LossyWAV; using System.Collections.Generic; using System.Collections.Specialized; @@ -95,7 +95,7 @@ namespace CUETools.Processor int destBitsPerSample = (config.detectHDCD && config.decodeHDCD) ? ((!config.decodeHDCDtoLW16 && config.decodeHDCDto24bit) ? 24 : 20) : 16; int lossyBitsPerSample = (config.detectHDCD && config.decodeHDCD && !config.decodeHDCDtoLW16) ? 24 : 16; IAudioDest lossyDest = GetAudioDest(path, lossyBitsPerSample, 2, 44100, finalSampleCount, extension, config); - IAudioDest lwcdfDest = GetAudioDest(lwcdfPath, destBitsPerSample, 2, 44100, finalSampleCount, extension, config); + IAudioDest lwcdfDest = config.lossyWAVHybrid ? GetAudioDest(lwcdfPath, destBitsPerSample, 2, 44100, finalSampleCount, extension, config) : null; return new LossyWAVWriter(lossyDest, lwcdfDest, destBitsPerSample, 2, 44100, config.lossyWAVQuality); } } diff --git a/CUETools.Processor/CUETools.Processor.csproj b/CUETools.Processor/CUETools.Processor.csproj index bf5a9c0..58586db 100644 --- a/CUETools.Processor/CUETools.Processor.csproj +++ b/CUETools.Processor/CUETools.Processor.csproj @@ -84,7 +84,7 @@ - + diff --git a/CUETools.Processor/Main.cs b/CUETools.Processor/Processor.cs similarity index 69% rename from CUETools.Processor/Main.cs rename to CUETools.Processor/Processor.cs index 176118d..853f4b9 100644 --- a/CUETools.Processor/Main.cs +++ b/CUETools.Processor/Processor.cs @@ -20,6 +20,7 @@ // **************************************************************************** using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Text; @@ -34,14 +35,15 @@ using CUETools.Codecs; using CUETools.Codecs.LossyWAV; using CUETools.CDImage; using CUETools.AccurateRip; +using CUETools.Ripper.SCSI; +using MusicBrainz; #if !MONO using UnRarDotNet; -using FLACDotNet; +using CUETools.Codecs.FLAC; #endif namespace CUETools.Processor { - public enum OutputAudioFormat { WAV, @@ -51,6 +53,23 @@ namespace CUETools.Processor NoAudio } + public enum AccurateRipMode + { + None, + Verify, + VerifyThenConvert, + VerifyAndConvert + } + + public enum CUEStyle + { + SingleFileWithCUE, + SingleFile, + GapsPrepended, + GapsAppended, + GapsLeftOut + } + public static class General { public static string FormatExtension(OutputAudioFormat value) { @@ -96,7 +115,7 @@ namespace CUETools.Processor { line = new CUELine(); line.Params.Add(command); line.IsQuoted.Add(false); - line.Params.Add(value); line.IsQuoted.Add(true); + line.Params.Add(value); line.IsQuoted.Add(quoted); list.Add(line); } else @@ -114,7 +133,7 @@ namespace CUETools.Processor line = new CUELine(); line.Params.Add(command); line.IsQuoted.Add(false); line.Params.Add(command2); line.IsQuoted.Add(false); - line.Params.Add(value); line.IsQuoted.Add(true); + line.Params.Add(value); line.IsQuoted.Add(quoted); list.Add(line); } else @@ -171,14 +190,6 @@ namespace CUETools.Processor } } - public enum CUEStyle { - SingleFileWithCUE, - SingleFile, - GapsPrepended, - GapsAppended, - GapsLeftOut - } - public class CUEConfig { public uint fixWhenConfidence; public uint fixWhenPercent; @@ -217,6 +228,7 @@ namespace CUETools.Processor public bool createCUEFileWhenEmbedded; public bool truncate4608ExtraSamples; public int lossyWAVQuality; + public bool lossyWAVHybrid; public bool decodeHDCDtoLW16; public bool decodeHDCDto24bit; @@ -247,7 +259,7 @@ namespace CUETools.Processor singleFilenameFormat = "%F"; removeSpecial = false; specialExceptions = "-()"; - replaceSpaces = true; + replaceSpaces = false; embedLog = true; fillUpCUE = true; filenamesANSISafe = true; @@ -260,6 +272,7 @@ namespace CUETools.Processor createCUEFileWhenEmbedded = false; truncate4608ExtraSamples = true; lossyWAVQuality = 5; + lossyWAVHybrid = true; decodeHDCDtoLW16 = false; decodeHDCDto24bit = true; } @@ -304,6 +317,7 @@ namespace CUETools.Processor sw.Save("CreateCUEFileWhenEmbedded", createCUEFileWhenEmbedded); sw.Save("Truncate4608ExtraSamples", truncate4608ExtraSamples); sw.Save("LossyWAVQuality", lossyWAVQuality); + sw.Save("LossyWAVHybrid", lossyWAVHybrid); sw.Save("DecodeHDCDToLossyWAV16", decodeHDCDtoLW16); sw.Save("DecodeHDCDTo24bit", decodeHDCDto24bit); } @@ -335,7 +349,7 @@ namespace CUETools.Processor trackFilenameFormat = sr.Load("TrackFilenameFormat") ?? "%N-%A-%T"; removeSpecial = sr.LoadBoolean("RemoveSpecialCharacters") ?? false; specialExceptions = sr.Load("SpecialCharactersExceptions") ?? "-()"; - replaceSpaces = sr.LoadBoolean("ReplaceSpaces") ?? true; + replaceSpaces = sr.LoadBoolean("ReplaceSpaces") ?? false; embedLog = sr.LoadBoolean("EmbedLog") ?? true; fillUpCUE = sr.LoadBoolean("FillUpCUE") ?? true; filenamesANSISafe = sr.LoadBoolean("FilenamesANSISafe") ?? true; @@ -348,6 +362,7 @@ namespace CUETools.Processor createCUEFileWhenEmbedded = sr.LoadBoolean("CreateCUEFileWhenEmbedded") ?? false; truncate4608ExtraSamples = sr.LoadBoolean("Truncate4608ExtraSamples") ?? true; lossyWAVQuality = sr.LoadInt32("LossyWAVQuality", 0, 10) ?? 5; + lossyWAVHybrid = sr.LoadBoolean("LossyWAVHybrid") ?? true; decodeHDCDtoLW16 = sr.LoadBoolean("DecodeHDCDToLossyWAV16") ?? false; decodeHDCDto24bit = sr.LoadBoolean("DecodeHDCDTo24bit") ?? true; } @@ -382,7 +397,7 @@ namespace CUETools.Processor public class CUEToolsProgressEventArgs { public string status = string.Empty; - public uint percentTrack = 0; + public double percentTrck = 0; public double percentDisk = 0.0; public string input = string.Empty; public string output = string.Empty; @@ -404,11 +419,11 @@ namespace CUETools.Processor private List _sources; private List _sourcePaths, _trackFilenames; private string _htoaFilename, _singleFilename; - private bool _hasHTOAFilename, _hasTrackFilenames, _hasSingleFilename, _appliedWriteOffset; + private bool _hasHTOAFilename = false, _hasTrackFilenames = false, _hasSingleFilename = false, _appliedWriteOffset; private bool _hasEmbeddedCUESheet; private bool _paddedToFrame, _truncated4608, _usePregapForFirstTrackInSingleFile; private int _writeOffset; - private bool _accurateRip, _accurateOffset; + private AccurateRipMode _accurateRipMode; private uint? _dataTrackLength; private uint? _minDataTrackLength; private string _accurateRipId; @@ -421,8 +436,12 @@ namespace CUETools.Processor private const int _arOffsetRange = 5 * 588 - 1; private HDCDDotNet.HDCDDotNet hdcdDecoder; private bool _outputLossyWAV = false; - CUEConfig _config; - string _cddbDiscIdTag; + private CUEConfig _config; + private string _cddbDiscIdTag; + private bool _isCD; + private string _driveName; + private int _driveOffset; + private BitArray _cdErrors; private bool _isArchive; private List _archiveContents; private string _archiveCUEpath; @@ -430,6 +449,7 @@ namespace CUETools.Processor private string _archivePassword; private CUEToolsProgressEventArgs _progress; private AccurateRipVerify _arVerify; + private CDImageLayout _toc; public event ArchivePasswordRequiredHandler PasswordRequired; public event CUEToolsProgressHandler CUEToolsProgress; @@ -440,7 +460,8 @@ namespace CUETools.Processor _progress = new CUEToolsProgressEventArgs(); _attributes = new List(); _tracks = new List(); - _toc = new CDImageLayout(0); + _trackFilenames = new List(); + _toc = new CDImageLayout(); _sources = new List(); _sourcePaths = new List(); _albumTags = new NameValueCollection(); @@ -450,47 +471,104 @@ namespace CUETools.Processor _paddedToFrame = false; _truncated4608 = false; _usePregapForFirstTrackInSingleFile = false; - _accurateRip = false; - _accurateOffset = false; + _accurateRipMode = AccurateRipMode.None; _appliedWriteOffset = false; _dataTrackLength = null; _minDataTrackLength = null; hdcdDecoder = null; _hasEmbeddedCUESheet = false; _isArchive = false; + _isCD = false; } - public void Open(string pathIn, bool outputLossyWAV) + public void OpenTOC(CDImageLayout toc) { - _outputLossyWAV = outputLossyWAV; - if (_config.detectHDCD) + _toc = toc; + for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) { - try { hdcdDecoder = new HDCDDotNet.HDCDDotNet(2, 44100, ((_outputLossyWAV && _config.decodeHDCDtoLW16) || !_config.decodeHDCDto24bit) ? 20 : 24, _config.decodeHDCD); } - catch { } + _trackFilenames.Add(string.Format("{0:00}.wav", iTrack + 1)); + _tracks.Add(new TrackInfo()); } + _hasTrackFilenames = false; + _accurateRipId = _accurateRipIdActual = AccurateRipVerify.CalculateAccurateRipId(_toc); + _arVerify = new AccurateRipVerify(_toc); + } - string cueDir, lineStr, command, pathAudio = null, fileType; + public void Open(string pathIn) + { + string cueDir = Path.GetDirectoryName(pathIn) ?? pathIn; +#if !MONO + if (cueDir == pathIn) + { + CDDriveReader ripper = new CDDriveReader(); + ripper.Open(pathIn[0]); + _toc = ripper.TOC; + _driveName = ripper.ARName; + ripper.Close(); + if (_toc.AudioTracks > 0) + { + if (!AccurateRipVerify.FindDriveReadOffset(_driveName, out _driveOffset)) + throw new Exception("Failed to find drive read offset for drive" + _driveName); + _isCD = true; + SourceInfo cdInfo; + cdInfo.Path = pathIn; + cdInfo.Offset = 0; + cdInfo.Length = _toc.AudioLength * 588; + _sources.Add(cdInfo); + for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) + { + _trackFilenames.Add(string.Format("{0:00}.wav", iTrack + 1)); + _tracks.Add(new TrackInfo()); + } + _hasTrackFilenames = false; + _accurateRipId = _accurateRipIdActual = AccurateRipVerify.CalculateAccurateRipId(_toc); + _arVerify = new AccurateRipVerify(_toc); + + Release release; + ReleaseQueryParameters p = new ReleaseQueryParameters(); + p.DiscId = _toc.MusicBrainzId; + Query results = Release.Query(p); + MusicBrainzService.XmlRequest += new EventHandler(MusicBrainz_LookupProgress); + _progress.percentDisk = 0; + try + { + release = results.First(); + General.SetCUELine(_attributes, "REM", "DISCID", AccurateRipVerify.CalculateCDDBId(_toc), false); + General.SetCUELine(_attributes, "REM", "COMMENT", CDDriveReader.RipperVersion(), true); + General.SetCUELine(_attributes, "REM", "DATE", release.GetEvents()[0].Date.Substring(0, 4), false); + General.SetCUELine(_attributes, "PERFORMER", release.GetArtist(), true); + General.SetCUELine(_attributes, "TITLE", release.GetTitle(), true); + for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) + { + General.SetCUELine(_tracks[iTrack].Attributes, "TITLE", release.GetTracks()[iTrack].GetTitle(), true); + General.SetCUELine(_tracks[iTrack].Attributes, "PERFORMER ", release.GetTracks()[iTrack].GetArtist(), true); + } + } + catch + { + release = null; + } + return; + } + } +#endif + + SourceInfo sourceInfo; + string lineStr, command, pathAudio = null, fileType; CUELine line; - TrackInfo trackInfo; - int timeRelativeToFileStart, absoluteFileStartTime; - int fileTimeLengthSamples, fileTimeLengthFrames, i; + TrackInfo trackInfo = null; + int timeRelativeToFileStart, absoluteFileStartTime = 0; + int fileTimeLengthSamples = 0, fileTimeLengthFrames = 0, i; int trackNumber = 0; bool seenFirstFileIndex = false, seenDataTrack = false; List indexes = new List(); IndexInfo indexInfo; - SourceInfo sourceInfo; NameValueCollection _trackTags = null; - - cueDir = Path.GetDirectoryName(pathIn); - trackInfo = null; - absoluteFileStartTime = 0; - fileTimeLengthSamples = 0; - fileTimeLengthFrames = 0; TextReader sr; if (Directory.Exists(pathIn)) { - if (cueDir + Path.DirectorySeparatorChar != pathIn) + if (cueDir + Path.DirectorySeparatorChar != pathIn && cueDir != pathIn) throw new Exception("Input directory must end on path separator character."); string cueSheet = null; string[] audioExts = new string[] { "*.wav", "*.flac", "*.wv", "*.ape", "*.m4a" }; @@ -632,7 +710,7 @@ namespace CUETools.Processor } trackInfo = new TrackInfo(); _tracks.Add(trackInfo); - _toc.AddTrack(new CDTrack((uint)trackNumber, 0, 0, true)); + _toc.AddTrack(new CDTrack((uint)trackNumber, 0, 0, true, false)); } } else if (seenDataTrack) { @@ -745,18 +823,21 @@ namespace CUETools.Processor // Calculate the length of each index for (i = 0; i < indexes.Count - 1; i++) { - int length = indexes[i + 1].Time - indexes[i].Time; - if (length < 0) + if (indexes[i + 1].Time - indexes[i].Time < 0) throw new Exception("Indexes must be in chronological order."); - _toc[indexes[i].Track].AddIndex(new CDTrackIndex((uint)indexes[i].Index, (uint)indexes[i].Time, (uint)length)); + if ((indexes[i+1].Track != indexes[i].Track || indexes[i+1].Index != indexes[i].Index + 1) && + (indexes[i + 1].Track != indexes[i].Track + 1 || indexes[i].Index < 1 || indexes[i + 1].Index > 1)) + throw new Exception("Indexes must be in chronological order."); + if (indexes[i].Index == 1 && (i == 0 || indexes[i - 1].Index != 0)) + _toc[indexes[i].Track].AddIndex(new CDTrackIndex(0U, (uint)indexes[i].Time)); + _toc[indexes[i].Track].AddIndex(new CDTrackIndex((uint)indexes[i].Index, (uint)indexes[i].Time)); } - _toc.Length = (uint) indexes[indexes.Count - 1].Time; - for (i = 1; i <= TrackCount; i++) + + // Calculate the length of each track + for (int iTrack = 1; iTrack <= TrackCount; iTrack++) { - if (_toc[i].LastIndex < 1) - throw new Exception("Track must have an INDEX 01."); - _toc[i].Start = _toc[i][1].Start; - _toc[i].Length = (i == TrackCount ? _toc.Length - _toc[i].Start : _toc[i+1][1].Start - _toc[i].Start); + _toc[iTrack].Start = _toc[iTrack][1].Start; + _toc[iTrack].Length = (iTrack == TrackCount ? (uint)indexes[indexes.Count - 1].Time - _toc[iTrack].Start : _toc[iTrack + 1][1].Start - _toc[iTrack].Start); } // Store the audio filenames, generating generic names if necessary @@ -768,7 +849,6 @@ namespace CUETools.Processor _htoaFilename = _hasHTOAFilename ? Path.GetFileName(_sourcePaths[0]) : "01.00.wav"; _hasTrackFilenames = (_sourcePaths.Count == TrackCount) || _hasHTOAFilename; - _trackFilenames = new List(); for (i = 0; i < TrackCount; i++) { _trackFilenames.Add( _hasTrackFilenames ? Path.GetFileName( _sourcePaths[i + (_hasHTOAFilename ? 1 : 0)]) : String.Format("{0:00}.wav", i + 1) ); @@ -805,53 +885,58 @@ namespace CUETools.Processor if (General.FindCUELine(_attributes, "REM", "GENRE") == null && GetCommonTag("GENRE") != null) General.SetCUELine(_attributes, "REM", "GENRE", GetCommonTag("GENRE"), true); } + + CUELine cddbDiscIdLine = General.FindCUELine(_attributes, "REM", "DISCID"); + _cddbDiscIdTag = cddbDiscIdLine != null && cddbDiscIdLine.Params.Count == 3 ? cddbDiscIdLine.Params[2] : null; + if (_cddbDiscIdTag == null) _cddbDiscIdTag = GetCommonTag("DISCID"); + if (_accurateRipId == null) _accurateRipId = GetCommonTag("ACCURATERIPID"); if (_accurateRipId == null && _dataTrackLength == null && _eacLog != null) { sr = new StringReader(_eacLog); - int lastAudioSector = -1; bool isEACLog = false; + CDImageLayout tocFromLog = new CDImageLayout(); while ((lineStr = sr.ReadLine()) != null) { - if (!isEACLog) + if (isEACLog) { - if (!lineStr.StartsWith("Exact Audio Copy")) - break; - isEACLog = true; - } - string[] n = lineStr.Split('|'); - if (n.Length == 5) - try - { - int trNo = Int32.Parse(n[0]); - int trStart = Int32.Parse(n[3]); - int trEnd = Int32.Parse(n[4]); - if (trNo == TrackCount && trEnd > 0) - lastAudioSector = trEnd; - if (trNo == TrackCount + 1 && lastAudioSector != -1 && trEnd > lastAudioSector + (90 + 60) * 75 + 150) - { - _dataTrackLength = (uint)(trEnd - lastAudioSector - (90 + 60) * 75 - 150); - break; - } - } - catch { } + string[] n = lineStr.Split('|'); + uint trNo, trStart, trEnd; + if (n.Length == 5 && uint.TryParse(n[0], out trNo) && uint.TryParse(n[3], out trStart) && uint.TryParse(n[4], out trEnd)) + tocFromLog.AddTrack(new CDTrack(trNo, trStart, trEnd + 1 - trStart, + tocFromLog.TrackCount < _toc.TrackCount || trStart != tocFromLog[tocFromLog.TrackCount].End + 1U + 152U * 75U, false)); + } else + if (lineStr.StartsWith("TOC of the extracted CD") + || lineStr.StartsWith("Exact Audio Copy") + || lineStr.StartsWith("CUERipper")) + isEACLog = true; + } + if (tocFromLog.TrackCount == _toc.TrackCount + 1 && !tocFromLog[tocFromLog.TrackCount].IsAudio) + _accurateRipId = AccurateRipVerify.CalculateAccurateRipId(tocFromLog); + } + + if (_accurateRipId == null && _dataTrackLength != null) + { + CDImageLayout toc2 = new CDImageLayout(_toc); + toc2.AddTrack(new CDTrack((uint)_toc.TrackCount, _toc.Length + 152U * 75U, _dataTrackLength.Value, false, false)); + _accurateRipId = AccurateRipVerify.CalculateAccurateRipId(toc2); + } + + if (_dataTrackLength == null && _cddbDiscIdTag != null) + { + uint cddbDiscIdNum; + if (uint.TryParse(_cddbDiscIdTag, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out cddbDiscIdNum) && (cddbDiscIdNum & 0xff) == TrackCount + 1) + { + uint lengthFromTag = ((cddbDiscIdNum >> 8) & 0xffff); + _minDataTrackLength = ((lengthFromTag + _toc[1].Start / 75) - 152) * 75 - _toc.Length; } } - CUELine cddbDiscIdLine = General.FindCUELine(_attributes, "REM", "DISCID"); - _cddbDiscIdTag = cddbDiscIdLine != null && cddbDiscIdLine.Params.Count == 3 ? cddbDiscIdLine.Params[2] : null; - if (_cddbDiscIdTag == null) _cddbDiscIdTag = GetCommonTag("DISCID"); - - if (_dataTrackLength != null) - _accurateRipIdActual = _accurateRipId = CalculateAccurateRipId(); - else - { - _accurateRipIdActual = CalculateAccurateRipId(); - if (_accurateRipId == null) - _accurateRipId = _accurateRipIdActual; - } + _accurateRipIdActual = AccurateRipVerify.CalculateAccurateRipId(_toc); + if (_accurateRipId == null) + _accurateRipId = _accurateRipIdActual; _arVerify = new AccurateRipVerify(_toc); @@ -874,12 +959,12 @@ namespace CUETools.Processor } } - private void ShowProgress(string status, uint percentTrack, double percentDisk, string input, string output) + private void ShowProgress(string status, double percentTrack, double percentDisk, string input, string output) { if (this.CUEToolsProgress == null) return; _progress.status = status; - _progress.percentTrack = percentTrack; + _progress.percentTrck = percentTrack; _progress.percentDisk = percentDisk; _progress.input = input; _progress.output = output; @@ -887,11 +972,47 @@ namespace CUETools.Processor } #if !MONO + private void CDReadProgress(object sender, ReadProgressArgs e) + { + lock (this) + { + if (_stop) + throw new StopException(); + if (_pause) + { + ShowProgress("Paused...", 0, 0, null, null); + Monitor.Wait(this); + } + } + if (this.CUEToolsProgress == null) + return; + CDDriveReader audioSource = (CDDriveReader)sender; + int processed = e.Position - e.PassStart; + TimeSpan elapsed = DateTime.Now - e.PassTime; + double speed = elapsed.TotalSeconds > 0 ? processed / elapsed.TotalSeconds / 75 : 1.0; + _progress.percentDisk = (double)(e.PassStart + (processed + e.Pass * (e.PassEnd - e.PassStart)) / (audioSource.CorrectionQuality + 1)) / audioSource.TOC.AudioLength; + _progress.percentTrck = (double) (e.Position - e.PassStart) / (e.PassEnd - e.PassStart); + _progress.status = string.Format("Ripping @{0:00.00}x {1}", speed, e.Pass > 0 ? " (Retry " + e.Pass.ToString() + ")" : ""); + this.CUEToolsProgress(this, _progress); + } + + private void MusicBrainz_LookupProgress(object sender, XmlRequestEventArgs e) + { + if (this.CUEToolsProgress == null) + return; + _progress.percentDisk = (1.0 + _progress.percentDisk) / 2; + _progress.percentTrck = 0; + _progress.input = e.Uri.ToString(); + _progress.output = null; + _progress.status = "Looking up album via MusicBrainz"; + this.CUEToolsProgress(this, _progress); + } + private void unrar_ExtractionProgress(object sender, ExtractionProgressEventArgs e) { if (this.CUEToolsProgress == null) return; - _progress.percentTrack = (uint)Math.Round(e.PercentComplete); + _progress.percentTrck = e.PercentComplete/100; this.CUEToolsProgress(this, _progress); } @@ -971,8 +1092,9 @@ namespace CUETools.Processor return null; } - public void GenerateFilenames (OutputAudioFormat format, string outputPath) + public void GenerateFilenames(OutputAudioFormat format, bool outputLossyWAV, string outputPath) { + _outputLossyWAV = outputLossyWAV; _cuePath = outputPath; string extension = General.FormatExtension(format); @@ -1064,7 +1186,7 @@ namespace CUETools.Processor { IAudioSource audioSource; - ShowProgress("Analyzing input file...", 0, 0.0, path, null); + ShowProgress("Analyzing input file...", 0.0, 0.0, path, null); #if !MONO if (_isArchive) { @@ -1090,81 +1212,136 @@ namespace CUETools.Processor return (int)audioSource.Length; } - public void WriteM3U(string path, CUEStyle style) + public void WriteText(string path, string text) { - StringWriter sw = new StringWriter(); - WriteM3U(sw, style); - sw.Close(); - bool utf8Required = CUESheet.Encoding.GetString(CUESheet.Encoding.GetBytes(sw.ToString())) != sw.ToString(); + bool utf8Required = CUESheet.Encoding.GetString(CUESheet.Encoding.GetBytes(text)) != text; StreamWriter sw1 = new StreamWriter(path, false, utf8Required ? Encoding.UTF8 : CUESheet.Encoding); - sw1.Write(sw.ToString()); + sw1.Write(text); sw1.Close(); } - public void WriteM3U(TextWriter sw, CUEStyle style) + public string LOGContents() { - int iTrack; - bool htoaToFile = ((style == CUEStyle.GapsAppended) && _config.preserveHTOA && - (_toc.Pregap != 0)); + if (!_isCD) + return null; +#if !MONO + StringWriter logWriter = new StringWriter(); + logWriter.WriteLine("{0}", CDDriveReader.RipperVersion()); + logWriter.WriteLine("Extraction logfile from : {0}", DateTime.Now); + logWriter.WriteLine("Used drive : {0}", _driveName); + logWriter.WriteLine("Read offset correction : {0}", _driveOffset); + //logWriter.WriteLine("Read command : {0}", ); + //logWriter.WriteLine("Secure mode : {0}", ); + if (hdcdDecoder != null && hdcdDecoder.Detected) + { + hdcd_decoder_statistics stats; + hdcdDecoder.GetStatistics(out stats); + logWriter.WriteLine("HDCD : peak extend: {0}, transient filter: {1}, gain: {2}", + (stats.enabled_peak_extend ? (stats.disabled_peak_extend ? "some" : "yes") : "none"), + (stats.enabled_transient_filter ? (stats.disabled_transient_filter ? "some" : "yes") : "none"), + stats.min_gain_adjustment == stats.max_gain_adjustment ? + (stats.min_gain_adjustment == 1.0 ? "none" : String.Format("{0:0.0}dB", (Math.Log10(stats.min_gain_adjustment) * 20))) : + String.Format("{0:0.0}dB..{1:0.0}dB", (Math.Log10(stats.min_gain_adjustment) * 20), (Math.Log10(stats.max_gain_adjustment) * 20)) + ); + logWriter.WriteLine(); + } + logWriter.WriteLine(); + logWriter.WriteLine("TOC of the extracted CD"); + logWriter.WriteLine(); + logWriter.WriteLine(" Track | Start | Length | Start sector | End sector"); + logWriter.WriteLine(" ---------------------------------------------------------"); + for (int track = 1; track <= _toc.TrackCount; track++) + logWriter.WriteLine("{0,9} | {1,8} | {2,8} | {3,9} | {4,9}", + _toc[track].Number, + _toc[track].StartMSF, + _toc[track].LengthMSF, + _toc[track].Start, + _toc[track].End); + bool wereErrors = false; + for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) + { + int cdErrors = 0; + bool crcMismatch = _accurateRipMode == AccurateRipMode.VerifyThenConvert && + _arVerify.BackupCRC(iTrack) != _arVerify.CRC(iTrack); + for (uint iSector = _toc[iTrack + 1].Start; iSector <= _toc[iTrack + 1].End; iSector++) + if (_cdErrors[(int)iSector]) + cdErrors++; + if (crcMismatch || cdErrors != 0) + { + if (!wereErrors) + { + logWriter.WriteLine(); + logWriter.WriteLine("Errors detected"); + logWriter.WriteLine(); + } + wereErrors = true; + if (crcMismatch) + logWriter.WriteLine("Track {0} contains {1} errors, CRC mismatch: test {2:X8} vs copy {3:X8}", iTrack + 1, cdErrors, _arVerify.BackupCRC(iTrack), _arVerify.CRC(iTrack)); + else + logWriter.WriteLine("Track {0} contains {1} errors", iTrack + 1, cdErrors); + } + } + if (_accurateRipMode != AccurateRipMode.None) + { + logWriter.WriteLine(); + logWriter.WriteLine("AccurateRip summary"); + logWriter.WriteLine(); + _arVerify.GenerateFullLog(logWriter, 0); + logWriter.WriteLine(); + } + logWriter.WriteLine(); + logWriter.WriteLine("End of status report"); + logWriter.Close(); + return logWriter.ToString(); +#else + return null; +#endif + } - if (htoaToFile) { + public string M3UContents(CUEStyle style) + { + StringWriter sw = new StringWriter(); + if (style == CUEStyle.GapsAppended && _config.preserveHTOA && _toc.Pregap != 0) WriteLine(sw, 0, _htoaFilename); - } - for (iTrack = 0; iTrack < TrackCount; iTrack++) { + for (int iTrack = 0; iTrack < TrackCount; iTrack++) WriteLine(sw, 0, _trackFilenames[iTrack]); - } - } - - public void WriteTOC(string path) - { - StreamWriter sw = new StreamWriter(path, false, CUESheet.Encoding); - WriteTOC(sw); sw.Close(); + return sw.ToString(); } - public void WriteTOC(TextWriter sw) + public string TOCContents() { + StringWriter sw = new StringWriter(); for (int iTrack = 0; iTrack < TrackCount; iTrack++) WriteLine(sw, 0, "\t" + _toc[iTrack+1].Start + 150); - } - - public void Write(string path, CUEStyle style) { - StringWriter sw = new StringWriter(); - Write(sw, style); sw.Close(); - bool utf8Required = CUESheet.Encoding.GetString(CUESheet.Encoding.GetBytes(sw.ToString())) != sw.ToString(); - StreamWriter sw1 = new StreamWriter(path, false, utf8Required?Encoding.UTF8:CUESheet.Encoding); - sw1.Write(sw.ToString()); - sw1.Close(); + return sw.ToString(); } - public void Write(TextWriter sw, CUEStyle style) { + public string CUESheetContents(CUEStyle style) + { + StringWriter sw = new StringWriter(); int i, iTrack, iIndex; - TrackInfo track; - bool htoaToFile = ((style == CUEStyle.GapsAppended) && _config.preserveHTOA && - (_toc.Pregap != 0)); + bool htoaToFile = (style == CUEStyle.GapsAppended && _config.preserveHTOA && _toc.Pregap != 0); uint timeRelativeToFileStart = 0; - using (sw) { - if (_accurateRipId != null && _config.writeArTagsOnConvert) - WriteLine(sw, 0, "REM ACCURATERIPID " + - _accurateRipId); + using (sw) + { + if (_config.writeArTagsOnConvert) + WriteLine(sw, 0, "REM ACCURATERIPID " + _accurateRipId); - for (i = 0; i < _attributes.Count; i++) { + for (i = 0; i < _attributes.Count; i++) WriteLine(sw, 0, _attributes[i]); - } - if (style == CUEStyle.SingleFile || style == CUEStyle.SingleFileWithCUE) { + if (style == CUEStyle.SingleFile || style == CUEStyle.SingleFileWithCUE) WriteLine(sw, 0, String.Format("FILE \"{0}\" WAVE", _singleFilename)); - } - if (htoaToFile) { + + if (htoaToFile) WriteLine(sw, 0, String.Format("FILE \"{0}\" WAVE", _htoaFilename)); - } - - for (iTrack = 0; iTrack < TrackCount; iTrack++) { - track = _tracks[iTrack]; + for (iTrack = 0; iTrack < TrackCount; iTrack++) + { if ((style == CUEStyle.GapsPrepended) || (style == CUEStyle.GapsLeftOut) || ((style == CUEStyle.GapsAppended) && @@ -1175,201 +1352,41 @@ namespace CUETools.Processor } WriteLine(sw, 1, String.Format("TRACK {0:00} AUDIO", iTrack + 1)); - for (i = 0; i < track.Attributes.Count; i++) { - WriteLine(sw, 2, track.Attributes[i]); - } + for (i = 0; i < _tracks[iTrack].Attributes.Count; i++) + WriteLine(sw, 2, _tracks[iTrack].Attributes[i]); - for (iIndex = 0; iIndex <= _toc[iTrack+1].LastIndex; iIndex++) { - if (_toc[iTrack+1][iIndex].Length != 0) { - if ((iIndex == 0) && - ((style == CUEStyle.GapsLeftOut) || - ((style == CUEStyle.GapsAppended) && (iTrack == 0) && !htoaToFile) || - ((style == CUEStyle.SingleFile || style == CUEStyle.SingleFileWithCUE) && (iTrack == 0) && _usePregapForFirstTrackInSingleFile))) + if (_toc[iTrack + 1].Pregap != 0) + { + if (((style == CUEStyle.GapsLeftOut) || + ((style == CUEStyle.GapsAppended) && (iTrack == 0) && !htoaToFile) || + ((style == CUEStyle.SingleFile || style == CUEStyle.SingleFileWithCUE) && (iTrack == 0) && _usePregapForFirstTrackInSingleFile))) + WriteLine(sw, 2, "PREGAP " + CDImageLayout.TimeToString(_toc[iTrack + 1].Pregap)); + else + { + WriteLine(sw, 2, String.Format("INDEX 00 {0}", CDImageLayout.TimeToString(timeRelativeToFileStart))); + timeRelativeToFileStart += _toc[iTrack + 1].Pregap; + if (style == CUEStyle.GapsAppended) { - WriteLine(sw, 2, "PREGAP " + CDImageLayout.TimeToString(_toc[iTrack + 1][iIndex].Length)); - } - else { - WriteLine(sw, 2, String.Format( "INDEX {0:00} {1}", iIndex, - CDImageLayout.TimeToString(timeRelativeToFileStart))); - timeRelativeToFileStart += _toc[iTrack + 1][iIndex].Length; - - if ((style == CUEStyle.GapsAppended) && (iIndex == 0)) { - WriteLine(sw, 0, String.Format("FILE \"{0}\" WAVE", _trackFilenames[iTrack])); - timeRelativeToFileStart = 0; - } + WriteLine(sw, 0, String.Format("FILE \"{0}\" WAVE", _trackFilenames[iTrack])); + timeRelativeToFileStart = 0; } } } - } - } - } - - private uint sumDigits(uint n) - { - uint r = 0; - while (n > 0) - { - r = r + (n % 10); - n = n / 10; - } - return r; - } - - private string CalculateAccurateRipId () - { - // Calculate the three disc ids used by AR - uint discId1 = 0; - uint discId2 = 0; - uint cddbDiscId = 0; - - for (int iTrack = 1; iTrack <= _toc.TrackCount; iTrack++) - { - discId1 += _toc[iTrack].Start; - discId2 += (_toc[iTrack].Start == 0 ? 1 : _toc[iTrack].Start) * ((uint)iTrack); - cddbDiscId += sumDigits(_toc[iTrack].Start / 75 + 2); - } - uint trackOffset = _toc.Length; - if (_dataTrackLength.HasValue) - { - trackOffset += ((90 + 60) * 75) + 150; // 90 second lead-out, 60 second lead-in, 150 sector gap - cddbDiscId += sumDigits((uint)(trackOffset / 75) + 2); - trackOffset += _dataTrackLength.Value; - } - discId1 += trackOffset; - discId2 += (trackOffset == 0 ? 1 : trackOffset) * ((uint)TrackCount + 1); - - if (!_dataTrackLength.HasValue && _cddbDiscIdTag != null) - { - uint cddbDiscIdNum = UInt32.Parse(_cddbDiscIdTag, NumberStyles.HexNumber); - if ((cddbDiscIdNum & 0xff) == TrackCount + 1) - { - uint lengthFromTag = ((cddbDiscIdNum >> 8) & 0xffff); - _minDataTrackLength = ((lengthFromTag + _toc[1].Start / 75) - 152) * 75 - trackOffset; - } - } - - cddbDiscId = ((cddbDiscId % 255) << 24) + - ((trackOffset / 75 - _toc[1].Start / 75) << 8) + - (uint)(TrackCount + (_dataTrackLength.HasValue ? 1 : 0)); - - discId1 &= 0xFFFFFFFF; - discId2 &= 0xFFFFFFFF; - cddbDiscId &= 0xFFFFFFFF; - - return String.Format("{0:x8}-{1:x8}-{2:x8}", discId1, discId2, cddbDiscId); - } - - private void CalculateMusicBrainzDiscID() { - StringBuilder mbSB = new StringBuilder(); - mbSB.AppendFormat("{0:X2}{1:X2}{2:X8}", 1, TrackCount, _toc.Length + 150); - for (int iTrack = 1; iTrack <= _toc.TrackCount; iTrack++) - mbSB.AppendFormat("{0:X8}", _toc[iTrack].Start + 150); - mbSB.Append(new string('0', (99 - TrackCount) * 8)); - - byte[] hashBytes = (new SHA1CryptoServiceProvider()).ComputeHash(Encoding.ASCII.GetBytes(mbSB.ToString())); - _mbDiscId = Convert.ToBase64String(hashBytes).Replace('+', '.').Replace('/', '_').Replace('=', '-'); - System.Diagnostics.Debug.WriteLine(_mbDiscId); - } - - private void GetMetadataFromMusicBrainz() { - if (_mbDiscId == null) return; - - using (Stream respStream = HttpGetToStream( - "http://musicbrainz.org/ws/1/release/?type=xml&limit=1&discid=" + _mbDiscId)) - { - XmlDocument xd = GetXmlDocument(respStream); - XmlNode xn; - - xn = xd.SelectSingleNode("/metadata/release-list/release"); - if (xn != null) - _mbReleaseId = xn.Attributes["id"].InnerText; - } - - if (_mbReleaseId == null) return; - - using (Stream respStream = HttpGetToStream(String.Format( - "http://musicbrainz.org/ws/1/release/{0}?type=xml&inc=artist+tracks", _mbReleaseId))) - { - string discArtist = null; - string discTitle = null; - XmlDocument xd = GetXmlDocument(respStream); - XmlNode xn; - - XmlNode xnRelease = xd.DocumentElement.SelectSingleNode("/metadata/release"); - if (xnRelease == null) return; - - XmlNodeList xnlTracks = xnRelease.SelectNodes("track-list/track"); - if (xnlTracks.Count != TrackCount) return; - - xn = xnRelease.SelectSingleNode("title"); - if (xn != null) - discTitle = xn.InnerText; - - xn = xnRelease.SelectSingleNode("artist/name"); - if (xn != null) - discArtist = xn.InnerText; - - Artist = discArtist; - Title = discTitle; - - for (int iTrack = 0; iTrack < TrackCount; iTrack++) { - string trackArtist = null; - string trackTitle = null; - XmlNode xnTrack = xnlTracks[iTrack]; - TrackInfo trackInfo = Tracks[iTrack]; - - xn = xnTrack.SelectSingleNode("title"); - if (xn != null) - trackTitle = xn.InnerText; - - xn = xnTrack.SelectSingleNode("artist/name"); - if (xn != null) - trackArtist = xn.InnerText; - - trackInfo.Artist = trackArtist ?? discArtist; - trackInfo.Title = trackTitle; - } - } - } - - private XmlDocument GetXmlDocument(Stream stream) { - XmlDocument xd = new XmlDocument(); - - xd.Load(stream); - - if (xd.DocumentElement.NamespaceURI.Length > 0) { - // Strip namespace to simplify xpath expressions - XmlDocument xdNew = new XmlDocument(); - xd.DocumentElement.SetAttribute("xmlns", String.Empty); - xdNew.LoadXml(xd.OuterXml); - xd = xdNew; - } - - return xd; - } - - private Stream HttpGetToStream(string url) { - HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url); - req.UserAgent = "CUE Tools"; - try { - HttpWebResponse resp = (HttpWebResponse)req.GetResponse(); - return resp.GetResponseStream(); - } - catch (WebException ex) { - if (ex.Status == WebExceptionStatus.ProtocolError) { - HttpStatusCode code = ((HttpWebResponse)ex.Response).StatusCode; - if (code == HttpStatusCode.NotFound) { - throw new HttpNotFoundException(); + for (iIndex = 1; iIndex <= _toc[iTrack+1].LastIndex; iIndex++) + { + WriteLine(sw, 2, String.Format( "INDEX {0:00} {1}", iIndex, CDImageLayout.TimeToString(timeRelativeToFileStart))); + timeRelativeToFileStart += _toc.IndexLength(iTrack + 1, iIndex); } } - throw; } + sw.Close(); + return sw.ToString(); } public void GenerateAccurateRipLog(TextWriter sw) { - int iTrack; - sw.WriteLine (String.Format("[Disc ID: {0}]", _accurateRipId)); + sw.WriteLine("[Verification date: {0}]", DateTime.Now); + sw.WriteLine("[Disc ID: {0}]", _accurateRipId); if (_dataTrackLength.HasValue) sw.WriteLine("Assuming a data track was present, length {0}.", CDImageLayout.TimeToString(_dataTrackLength.Value)); else @@ -1399,46 +1416,9 @@ namespace CUETools.Processor ); } - if (_arVerify.AccResult == HttpStatusCode.NotFound) - { - sw.WriteLine("Disk not present in database."); - //for (iTrack = 0; iTrack < TrackCount; iTrack++) - // sw.WriteLine(String.Format(" {0:00}\t[{1:x8}] Disk not present in database", iTrack + 1, _tracks[iTrack].CRC)); - } - else if (_arVerify.AccResult != HttpStatusCode.OK) - { - sw.WriteLine("Database access error: " + _arVerify.AccResult.ToString()); - //for (iTrack = 0; iTrack < TrackCount; iTrack++) - // sw.WriteLine(String.Format(" {0:00}\t[{1:x8}] Database access error {2}", iTrack + 1, _tracks[iTrack].CRC, accResult.ToString())); - } - else - { - if (0 != _writeOffset) - sw.WriteLine(String.Format("Offset applied: {0}", _writeOffset)); - int offsetApplied = _accurateOffset ? _writeOffset : 0; - sw.WriteLine(String.Format("Track\t[ CRC ] Status")); - _arVerify.GenerateAccurateRipLog(sw, offsetApplied); - uint offsets_match = 0; - for (int oi = -_arOffsetRange; oi <= _arOffsetRange; oi++) - { - uint matches = 0; - for (iTrack = 0; iTrack < TrackCount; iTrack++) - for (int di = 0; di < (int)_arVerify.AccDisks.Count; di++) - if ((_arVerify.CRC(iTrack, oi) == _arVerify.AccDisks[di].tracks[iTrack].CRC && _arVerify.AccDisks[di].tracks[iTrack].CRC != 0) || - (_arVerify.CRC450(iTrack, oi) == _arVerify.AccDisks[di].tracks[iTrack].Frame450CRC && _arVerify.AccDisks[di].tracks[iTrack].Frame450CRC != 0)) - matches++; - if (matches != 0 && oi != offsetApplied) - { - if (offsets_match++ > 10) - { - sw.WriteLine("More than 10 offsets match!"); - break; - } - sw.WriteLine(String.Format("Offsetted by {0}:", oi)); - _arVerify.GenerateAccurateRipLog(sw, oi); - } - } - } + if (0 != _writeOffset) + sw.WriteLine("Offset applied: {0}", _writeOffset); + _arVerify.GenerateFullLog(sw, 0); } public void GenerateAccurateRipTagsForTrack(NameValueCollection tags, int offset, int bestOffset, int iTrack, string prefix) @@ -1539,9 +1519,11 @@ namespace CUETools.Processor bool htoaToFile = ((style == CUEStyle.GapsAppended) && _config.preserveHTOA && (_toc.Pregap != 0)); - if (_usePregapForFirstTrackInSingleFile) { + if (_isCD && (style == CUEStyle.GapsLeftOut || style == CUEStyle.GapsPrepended) && (_accurateRipMode == AccurateRipMode.None || _accurateRipMode == AccurateRipMode.VerifyAndConvert)) + throw new Exception("When ripping a CD, gaps Left Out/Gaps prepended modes can only be used in verify-then-convert mode"); + + if (_usePregapForFirstTrackInSingleFile) throw new Exception("UsePregapForFirstTrackInSingleFile is not supported for writing audio files."); - } if (style == CUEStyle.SingleFile || style == CUEStyle.SingleFileWithCUE) { destPaths = new string[1]; @@ -1557,31 +1539,34 @@ namespace CUETools.Processor } } - if ( !_accurateRip || _accurateOffset ) - for (int i = 0; i < destPaths.Length; i++) { - for (int j = 0; j < _sourcePaths.Count; j++) { - if (destPaths[i].ToLower() == _sourcePaths[j].ToLower()) { - throw new Exception("Source and destination audio file paths cannot be the same."); - } - } - } + if (_accurateRipMode != AccurateRipMode.Verify) + for (int i = 0; i < destPaths.Length; i++) + for (int j = 0; j < _sourcePaths.Count; j++) + if (destPaths[i].ToLower() == _sourcePaths[j].ToLower()) + throw new Exception("Source and destination audio file paths cannot be the same."); destLengths = CalculateAudioFileLengths(style); bool SkipOutput = false; - if (_accurateRip) { + if (_accurateRipMode != AccurateRipMode.None) + { ShowProgress((string)"Contacting AccurateRip database...", 0, 0, null, null); if (!_dataTrackLength.HasValue && _minDataTrackLength.HasValue && _accurateRipId == _accurateRipIdActual && _config.bruteForceDTL) { uint minDTL = _minDataTrackLength.Value; + CDImageLayout toc2 = new CDImageLayout(_toc); + toc2.AddTrack(new CDTrack((uint)_toc.TrackCount, _toc.Length + 152 * 75, minDTL, false, false)); for (uint dtl = minDTL; dtl < minDTL + 75; dtl++) { - _dataTrackLength = dtl; - _accurateRipId = CalculateAccurateRipId(); + toc2[toc2.TrackCount].Length = dtl; + _accurateRipId = AccurateRipVerify.CalculateAccurateRipId(toc2); _arVerify.ContactAccurateRip(_accurateRipId); if (_arVerify.AccResult != HttpStatusCode.NotFound) + { + _dataTrackLength = dtl; break; + } ShowProgress((string)"Contacting AccurateRip database...", 0, (dtl - minDTL) / 75.0, null, null); lock (this) { if (_stop) @@ -1597,7 +1582,6 @@ namespace CUETools.Processor } if (_arVerify.AccResult != HttpStatusCode.OK) { - _dataTrackLength = null; _accurateRipId = _accurateRipIdActual; } } else @@ -1605,10 +1589,10 @@ namespace CUETools.Processor if (_arVerify.AccResult != HttpStatusCode.OK) { - if (!_accurateOffset || _config.noUnverifiedOutput) + if (_accurateRipMode == AccurateRipMode.Verify || _config.noUnverifiedOutput) { - if ((_accurateOffset && _config.writeArLogOnConvert) || - (!_accurateOffset && _config.writeArLogOnVerify)) + if ((_accurateRipMode != AccurateRipMode.Verify && _config.writeArLogOnConvert) || + (_accurateRipMode == AccurateRipMode.Verify && _config.writeArLogOnVerify)) { if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); @@ -1621,12 +1605,18 @@ namespace CUETools.Processor { if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); - WriteTOC(Path.ChangeExtension(_cuePath, ".toc")); + WriteText(Path.ChangeExtension(_cuePath, ".toc"), TOCContents()); } return; } + if (_accurateRipMode == AccurateRipMode.VerifyThenConvert && _isCD) + { + _writeOffset = 0; + WriteAudioFilesPass(dir, style, destPaths, destLengths, htoaToFile, true); + _arVerify.CreateBackup(_writeOffset); + } } - else if (_accurateOffset) + else if (_accurateRipMode == AccurateRipMode.VerifyThenConvert) { _writeOffset = 0; WriteAudioFilesPass(dir, style, destPaths, destLengths, htoaToFile, true); @@ -1647,30 +1637,87 @@ namespace CUETools.Processor if (tracksMatch * 100 >= _config.fixWhenPercent * TrackCount) _writeOffset = bestOffset; } + _arVerify.CreateBackup(_writeOffset); } } if (!SkipOutput) { - bool verifyOnly = _accurateRip && !_accurateOffset; - if (!verifyOnly) + if (_accurateRipMode != AccurateRipMode.Verify) { if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); - if (style != CUEStyle.SingleFileWithCUE) - Write(_cuePath, style); - else if (_config.createCUEFileWhenEmbedded) - Write(Path.ChangeExtension(_cuePath, ".cue"), style); - if (style != CUEStyle.SingleFileWithCUE && style != CUEStyle.SingleFile && _config.createM3U) - WriteM3U(Path.ChangeExtension(_cuePath, ".m3u"), style); } - WriteAudioFilesPass(dir, style, destPaths, destLengths, htoaToFile, verifyOnly); + if (_isCD) + destLengths = CalculateAudioFileLengths(style); // need to recalc, might have changed after scanning the CD + WriteAudioFilesPass(dir, style, destPaths, destLengths, htoaToFile, _accurateRipMode == AccurateRipMode.Verify); + if (_accurateRipMode != AccurateRipMode.Verify) + { + string logContents = LOGContents(); + string cueContents = CUESheetContents(style); + bool needNewCRCs = _accurateRipMode != AccurateRipMode.None && + (_accurateRipMode == AccurateRipMode.VerifyAndConvert || _isCD) && + _config.writeArTagsOnConvert && + _arVerify.AccResult == HttpStatusCode.OK; + uint tracksMatch = 0; + int bestOffset = 0; + if (needNewCRCs) + FindBestOffset(1, true, out tracksMatch, out bestOffset); + + if (logContents != null) + WriteText(Path.ChangeExtension(_cuePath, ".log"), logContents); + if (style != CUEStyle.SingleFileWithCUE) + { + WriteText(_cuePath, cueContents); +#if !MONO + if (needNewCRCs && style != CUEStyle.SingleFile) + { + for (int iTrack = 0; iTrack < TrackCount; iTrack++) + { + IAudioSource audioSource = AudioReadWrite.GetAudioSource(destPaths[iTrack + (htoaToFile ? 1 : 0)], null); + CleanupTags(audioSource.Tags, "ACCURATERIP"); + GenerateAccurateRipTags(audioSource.Tags, 0, bestOffset, iTrack); + audioSource.UpdateTags(false); + audioSource.Close(); + audioSource = null; + } + } +#endif + } + else + { + if (_config.createCUEFileWhenEmbedded) + WriteText(Path.ChangeExtension(_cuePath, ".cue"), cueContents); +#if !MONO + if (needNewCRCs || _isCD) + { + IAudioSource audioSource = AudioReadWrite.GetAudioSource(destPaths[0], null); + if (_isCD) + { + if (_accurateRipMode != AccurateRipMode.VerifyThenConvert) + audioSource.Tags.Add("CUESHEET", cueContents); + audioSource.Tags.Add("LOG", logContents); + } + if (needNewCRCs) + { + CleanupTags(audioSource.Tags, "ACCURATERIP"); + GenerateAccurateRipTags(audioSource.Tags, 0, bestOffset, -1); + } + audioSource.UpdateTags(false); + audioSource.Close(); + audioSource = null; + } +#endif + } + if (style != CUEStyle.SingleFileWithCUE && style != CUEStyle.SingleFile && _config.createM3U) + WriteText(Path.ChangeExtension(_cuePath, ".m3u"), M3UContents(style)); + } } - if (_accurateRip) + if (_accurateRipMode != AccurateRipMode.None) { ShowProgress((string)"Generating AccurateRip report...", 0, 0, null, null); - if (!_accurateOffset && _config.writeArTagsOnVerify && _writeOffset == 0 && !_isArchive) + if (_accurateRipMode == AccurateRipMode.Verify && _config.writeArTagsOnVerify && _writeOffset == 0 && !_isArchive && !_isCD) { uint tracksMatch; int bestOffset; @@ -1684,7 +1731,7 @@ namespace CUETools.Processor GenerateAccurateRipTags (tags, 0, bestOffset, -1); #if !MONO if (audioSource is FLACReader) - ((FLACReader)audioSource).UpdateTags (true); + audioSource.UpdateTags (true); #endif audioSource.Close(); audioSource = null; @@ -1700,7 +1747,7 @@ namespace CUETools.Processor NameValueCollection tags = audioSource.Tags; CleanupTags(tags, "ACCURATERIP"); GenerateAccurateRipTags (tags, 0, bestOffset, iTrack); - ((FLACReader)audioSource).UpdateTags(true); + audioSource.UpdateTags(true); } #endif audioSource.Close(); @@ -1709,8 +1756,8 @@ namespace CUETools.Processor } } - if ((_accurateOffset && _config.writeArLogOnConvert) || - (!_accurateOffset && _config.writeArLogOnVerify)) + if ((_accurateRipMode != AccurateRipMode.Verify && _config.writeArLogOnConvert) || + (_accurateRipMode == AccurateRipMode.Verify && _config.writeArLogOnVerify)) { if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); @@ -1723,7 +1770,7 @@ namespace CUETools.Processor { if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); - WriteTOC(Path.ChangeExtension(_cuePath, ".toc")); + WriteText(Path.ChangeExtension(_cuePath, ".toc"), TOCContents()); } } } @@ -1769,9 +1816,9 @@ namespace CUETools.Processor if (destTags.Get("ARTIST") == null && "" != _tracks[iTrack].Artist) destTags.Add("ARTIST", _tracks[iTrack].Artist); destTags.Add("TRACKNUMBER", (iTrack + 1).ToString()); - if (_accurateRipId != null && _config.writeArTagsOnConvert) + if (_config.writeArTagsOnConvert) { - if (_accurateOffset && _arVerify.AccResult == HttpStatusCode.OK) + if (!_isCD && _accurateRipMode == AccurateRipMode.VerifyThenConvert && _arVerify.AccResult == HttpStatusCode.OK) GenerateAccurateRipTags(destTags, _writeOffset, bestOffset, iTrack); else destTags.Add("ACCURATERIPID", _accurateRipId); @@ -1818,13 +1865,8 @@ namespace CUETools.Processor CleanupTags(destTags, "ACCURATERIP"); CleanupTags(destTags, "REPLAYGAIN"); - if (fWithCUE) - { - StringWriter sw = new StringWriter(); - Write(sw, CUEStyle.SingleFileWithCUE); - destTags.Add("CUESHEET", sw.ToString()); - sw.Close(); - } + if (fWithCUE && (!_isCD || _accurateRipMode == AccurateRipMode.VerifyThenConvert)) + destTags.Add("CUESHEET", CUESheetContents(CUEStyle.SingleFileWithCUE)); if (_config.embedLog) { @@ -1835,9 +1877,9 @@ namespace CUETools.Processor destTags.Add("LOG", _eacLog); } - if (_accurateRipId != null && _config.writeArTagsOnConvert) + if (_config.writeArTagsOnConvert) { - if (fWithCUE && _accurateOffset && _arVerify.AccResult == HttpStatusCode.OK) + if (fWithCUE && !_isCD && _accurateRipMode == AccurateRipMode.VerifyThenConvert && _arVerify.AccResult == HttpStatusCode.OK) GenerateAccurateRipTags(destTags, _writeOffset, bestOffset, -1); else destTags.Add("ACCURATERIPID", _accurateRipId); @@ -1857,6 +1899,7 @@ namespace CUETools.Processor int iSource = -1; int iDest = -1; uint samplesRemSource = 0; + CDImageLayout updatedTOC = null; if (_writeOffset != 0) { @@ -1901,11 +1944,16 @@ namespace CUETools.Processor uint tracksMatch; int bestOffset = _writeOffset; - if (!noOutput && _accurateRipId != null && _config.writeArTagsOnConvert && _accurateOffset && _arVerify.AccResult == HttpStatusCode.OK) + if (!noOutput && _accurateRipMode == AccurateRipMode.VerifyThenConvert && _config.writeArTagsOnConvert && _arVerify.AccResult == HttpStatusCode.OK) FindBestOffset(1, true, out tracksMatch, out bestOffset); - if (hdcdDecoder != null) - hdcdDecoder.Reset(); + if (_config.detectHDCD) + { + // currently broken verifyThenConvert on HDCD detection!!!! need to check for HDCD results higher + try { hdcdDecoder = new HDCDDotNet.HDCDDotNet(2, 44100, ((_outputLossyWAV && _config.decodeHDCDtoLW16) || !_config.decodeHDCDto24bit) ? 20 : 24, _config.decodeHDCD); } + catch { } + } + if (style == CUEStyle.SingleFile || style == CUEStyle.SingleFileWithCUE) { @@ -1917,137 +1965,183 @@ namespace CUETools.Processor uint currentOffset = 0, previousOffset = 0; uint trackLength = _toc.Pregap * 588; - uint diskLength = _toc.Length * 588, diskOffset = 0; + uint diskLength = 588 * (_toc[_toc.TrackCount].IsAudio ? _toc[_toc.TrackCount].End + 1 : _toc[_toc.TrackCount - 1].End + 1); + uint diskOffset = 0; - if (_accurateRip && noOutput) + if (_accurateRipMode != AccurateRipMode.None) _arVerify.Init(); ShowProgress(String.Format("{2} track {0:00} ({1:00}%)...", 0, 0, noOutput ? "Verifying" : "Writing"), 0, 0.0, null, null); - for (iTrack = 0; iTrack < TrackCount; iTrack++) { - track = _tracks[iTrack]; + try + { + for (iTrack = 0; iTrack < TrackCount; iTrack++) + { + track = _tracks[iTrack]; - if ((style == CUEStyle.GapsPrepended) || (style == CUEStyle.GapsLeftOut)) { - iDest++; - if (hdcdDecoder != null) - hdcdDecoder.AudioDest = null; - if (audioDest != null) - audioDest.Close(); - audioDest = GetAudioDest(destPaths[iDest], destLengths[iDest], noOutput); - if (!noOutput) - SetTrackTags(audioDest, iTrack, bestOffset); - } - - for (iIndex = 0; iIndex <= _toc[iTrack+1].LastIndex; iIndex++) { - uint trackPercent= 0, lastTrackPercent= 101; - uint samplesRemIndex = _toc[iTrack + 1][iIndex].Length * 588; - - if (iIndex == 1) - { - previousOffset = currentOffset; - currentOffset = 0; - trackLength = _toc[iTrack + 1].Length * 588; - } - - if ((style == CUEStyle.GapsAppended) && (iIndex == 1)) + if ((style == CUEStyle.GapsPrepended) || (style == CUEStyle.GapsLeftOut)) { + iDest++; if (hdcdDecoder != null) hdcdDecoder.AudioDest = null; if (audioDest != null) audioDest.Close(); - iDest++; audioDest = GetAudioDest(destPaths[iDest], destLengths[iDest], noOutput); if (!noOutput) SetTrackTags(audioDest, iTrack, bestOffset); } - if ((style == CUEStyle.GapsAppended) && (iIndex == 0) && (iTrack == 0)) { - discardOutput = !htoaToFile; - if (htoaToFile) { + for (iIndex = 0; iIndex <= _toc[iTrack + 1].LastIndex; iIndex++) + { + uint samplesRemIndex = _toc.IndexLength(iTrack + 1, iIndex) * 588; + + if (iIndex == 1) + { + previousOffset = currentOffset; + currentOffset = 0; + trackLength = _toc[iTrack + 1].Length * 588; + } + + if ((style == CUEStyle.GapsAppended) && (iIndex == 1)) + { + if (hdcdDecoder != null) + hdcdDecoder.AudioDest = null; + if (audioDest != null) + audioDest.Close(); iDest++; audioDest = GetAudioDest(destPaths[iDest], destLengths[iDest], noOutput); - } - } - else if ((style == CUEStyle.GapsLeftOut) && (iIndex == 0)) { - discardOutput = true; - } - else { - discardOutput = false; - } - - while (samplesRemIndex != 0) { - if (samplesRemSource == 0) { - if (audioSource != null) audioSource.Close(); - audioSource = GetAudioSource(++iSource); - samplesRemSource = (uint) _sources[iSource].Length; + if (!noOutput) + SetTrackTags(audioDest, iTrack, bestOffset); } - uint copyCount = (uint) Math.Min(Math.Min(samplesRemIndex, samplesRemSource), buffLen); - - if ( trackLength > 0 ) + if ((style == CUEStyle.GapsAppended) && (iIndex == 0) && (iTrack == 0)) { - trackPercent = (uint)(currentOffset / 0.01 / trackLength); - double diskPercent = ((float)diskOffset) / diskLength; - if (trackPercent != lastTrackPercent) - ShowProgress(String.Format("{2} track {0:00} ({1:00}%)...", iIndex > 0 ? iTrack + 1 : iTrack, trackPercent, - noOutput ? "Verifying" : "Writing"), trackPercent, diskPercent, - audioSource.Path, discardOutput ? null : audioDest.Path); - lastTrackPercent = trackPercent; - } - - audioSource.Read(sampleBuffer, copyCount); - if (!discardOutput) - { - if (!_config.detectHDCD || !_config.decodeHDCD) - audioDest.Write(sampleBuffer, copyCount); - if (_config.detectHDCD && hdcdDecoder != null) + discardOutput = !htoaToFile; + if (htoaToFile) { - if (_config.wait750FramesForHDCD && diskOffset > 750 * 588 && !hdcdDecoder.Detected) + iDest++; + audioDest = GetAudioDest(destPaths[iDest], destLengths[iDest], noOutput); + } + } + else if ((style == CUEStyle.GapsLeftOut) && (iIndex == 0)) + { + discardOutput = true; + } + else + { + discardOutput = false; + } + + while (samplesRemIndex != 0) + { + if (samplesRemSource == 0) + { +#if !MONO + if (_isCD && audioSource != null && audioSource is CDDriveReader) { - hdcdDecoder.AudioDest = null; - hdcdDecoder = null; - if (_config.decodeHDCD) + updatedTOC = ((CDDriveReader)audioSource).TOC; + _cdErrors = ((CDDriveReader)audioSource).Errors; + } +#endif + if (audioSource != null) audioSource.Close(); + audioSource = GetAudioSource(++iSource); + samplesRemSource = (uint)_sources[iSource].Length; + } + + uint copyCount = (uint)Math.Min(Math.Min(samplesRemIndex, samplesRemSource), buffLen); + + if (trackLength > 0 && !_isCD) + { + double trackPercent = (double)currentOffset / trackLength; + double diskPercent = (double)diskOffset / diskLength; + ShowProgress(String.Format("{2} track {0:00} ({1:00}%)...", iIndex > 0 ? iTrack + 1 : iTrack, (uint)(100*trackPercent), + noOutput ? "Verifying" : "Writing"), trackPercent, diskPercent, + _isCD ? string.Format("{0}: {1:00} - {2}", audioSource.Path, iTrack + 1, _tracks[iTrack].Title) : audioSource.Path, discardOutput ? null : audioDest.Path); + } + + if (audioSource.Read(sampleBuffer, copyCount) != copyCount) + throw new Exception("samples read != samples expected"); + if (!discardOutput) + { + if (!_config.detectHDCD || !_config.decodeHDCD) + audioDest.Write(sampleBuffer, copyCount); + if (_config.detectHDCD && hdcdDecoder != null) + { + if (_config.wait750FramesForHDCD && diskOffset > 750 * 588 && !hdcdDecoder.Detected) { - audioSource.Close(); - audioDest.Delete(); - throw new Exception("HDCD not detected."); + hdcdDecoder.AudioDest = null; + hdcdDecoder = null; + if (_config.decodeHDCD) + { + audioSource.Close(); + audioDest.Delete(); + throw new Exception("HDCD not detected."); + } + } + else + { + if (_config.decodeHDCD) + hdcdDecoder.AudioDest = (discardOutput || noOutput) ? null : audioDest; + hdcdDecoder.Process(sampleBuffer, copyCount); } } - else - { - if (_config.decodeHDCD) - hdcdDecoder.AudioDest = (discardOutput || noOutput) ? null : audioDest; - hdcdDecoder.Process(sampleBuffer, copyCount); - } } - } - if (_accurateRip && noOutput) - _arVerify.Write(sampleBuffer, copyCount); + if (_accurateRipMode != AccurateRipMode.None) + _arVerify.Write(sampleBuffer, copyCount); - currentOffset += copyCount; - diskOffset += copyCount; - samplesRemIndex -= copyCount; - samplesRemSource -= copyCount; + currentOffset += copyCount; + diskOffset += copyCount; + samplesRemIndex -= copyCount; + samplesRemSource -= copyCount; - lock (this) { - if (_stop) { - if (hdcdDecoder != null) - hdcdDecoder.AudioDest = null; - audioSource.Close(); - try { - if (audioDest != null) audioDest.Close(); - } catch { } - throw new StopException(); - } - if (_pause) + lock (this) { - ShowProgress("Paused...", 0, 0, null, null); - Monitor.Wait(this); + if (_stop) + throw new StopException(); + if (_pause) + { + ShowProgress("Paused...", 0, 0, null, null); + Monitor.Wait(this); + } } } } } } + catch (Exception ex) + { + if (hdcdDecoder != null) + hdcdDecoder.AudioDest = null; + hdcdDecoder = null; + try { if (audioSource != null) audioSource.Close(); } + catch { } + audioSource = null; + try { if (audioDest != null) audioDest.Close(); } + catch { } + audioDest = null; + throw ex; + } + +#if !MONO + if (_isCD && audioSource != null && audioSource is CDDriveReader) + { + updatedTOC = ((CDDriveReader)audioSource).TOC; + _cdErrors = ((CDDriveReader)audioSource).Errors; + } + if (updatedTOC != null) + { + _toc = updatedTOC; + if (_toc.Catalog != null) + General.SetCUELine(_attributes, "CATALOG", _toc.Catalog, false); + for (iTrack = 1; iTrack <= _toc.TrackCount; iTrack++) + { + if (_toc[iTrack].IsAudio && _toc[iTrack].ISRC != null) + General.SetCUELine(_tracks[iTrack - 1].Attributes, "ISRC", _toc[iTrack].ISRC, false); + if (_toc[iTrack].IsAudio && _toc[iTrack].PreEmphasis) + General.SetCUELine(_tracks[iTrack - 1].Attributes, "FLAGS", "PRE", false); + } + } +#endif if (hdcdDecoder != null) hdcdDecoder.AudioDest = null; @@ -2155,7 +2249,8 @@ namespace CUETools.Processor } } - private int[] CalculateAudioFileLengths(CUEStyle style) { + private int[] CalculateAudioFileLengths(CUEStyle style) + { int iTrack, iIndex, iFile; TrackInfo track; int[] fileLengths; @@ -2187,7 +2282,7 @@ namespace CUETools.Processor discardOutput = (style == CUEStyle.GapsLeftOut && iIndex == 0); if (!discardOutput) - fileLengths[iFile] += (int) _toc[iTrack+1][iIndex].Length * 588; + fileLengths[iFile] += (int)_toc.IndexLength(iTrack + 1, iIndex) * 588; } } @@ -2242,6 +2337,14 @@ namespace CUETools.Processor } else { #if !MONO + if (_isCD) + { + CDDriveReader ripper = new CDDriveReader(); + ripper.Open(sourceInfo.Path[0]); + ripper.DriveOffset = _driveOffset; + ripper.ReadProgress += new EventHandler(CDReadProgress); + audioSource = ripper; + } else if (_isArchive) { RarStream IO = new RarStream(_archivePath, sourceInfo.Path); @@ -2372,7 +2475,9 @@ namespace CUETools.Processor if (dtl != 0) { _dataTrackLength = dtl; - _accurateRipId = _accurateRipIdActual = CalculateAccurateRipId(); + CDImageLayout toc2 = new CDImageLayout(_toc); + toc2.AddTrack(new CDTrack((uint)_toc.TrackCount, _toc.Length + 152 * 75, dtl, false, false)); + _accurateRipIdActual = _accurateRipId = AccurateRipVerify.CalculateAccurateRipId(toc2); } } } @@ -2386,31 +2491,33 @@ namespace CUETools.Processor } } - public CUEConfig Config { - get { + public CUEConfig Config + { + get + { return _config; } } - public bool AccurateRip { - get { - return _accurateRip; + public AccurateRipMode AccurateRip + { + get + { + return _accurateRipMode; } - set { - _accurateRip = value; + set + { + _accurateRipMode = value; } } - public bool AccurateOffset { - get { - return _accurateOffset; - } - set { - _accurateOffset = value; + public bool IsCD + { + get + { + return _isCD; } } - - CDImageLayout _toc; } public class CUELine { @@ -2542,7 +2649,4 @@ namespace CUETools.Processor public StopException() : base() { } } - - class HttpNotFoundException : Exception { - } } \ No newline at end of file diff --git a/CUETools.Ripper.SCSI/SCSIDrive.cs b/CUETools.Ripper.SCSI/SCSIDrive.cs index 204f6f0..5f5932e 100644 --- a/CUETools.Ripper.SCSI/SCSIDrive.cs +++ b/CUETools.Ripper.SCSI/SCSIDrive.cs @@ -806,6 +806,8 @@ namespace CUETools.Ripper.SCSI } set { + _currentTrack = -1; + _currentIndex = -1; _sampleOffset = (int) value + _driveOffset; } } diff --git a/CUETools/frmBatch.cs b/CUETools/frmBatch.cs index 6847b95..aac94f0 100644 --- a/CUETools/frmBatch.cs +++ b/CUETools/frmBatch.cs @@ -133,7 +133,7 @@ namespace JDP cueName = Path.GetFileNameWithoutExtension(pathIn) + ".cue"; bool outputAudio = _accurateRip != AccurateRipMode.Verify; - cueSheet.Open(pathIn, _lossyWAV); + cueSheet.Open(pathIn); if (outputAudio) { bool pathFound = false; @@ -153,7 +153,7 @@ namespace JDP } else pathOut = Path.Combine(Path.GetDirectoryName(pathIn) ?? pathIn, cueName); - cueSheet.GenerateFilenames(_audioFormat, pathOut); + cueSheet.GenerateFilenames(_audioFormat, _lossyWAV, pathOut); if (outputAudio) { if (_cueStyle == CUEStyle.SingleFileWithCUE) diff --git a/CUETools/frmCUETools.cs b/CUETools/frmCUETools.cs index 47adc3b..ec0be01 100644 --- a/CUETools/frmCUETools.cs +++ b/CUETools/frmCUETools.cs @@ -304,7 +304,7 @@ namespace JDP { bool outputCUE = cueStyle != CUEStyle.SingleFileWithCUE && accurateRip != AccurateRipMode.Verify; string pathOut = null; - cueSheet.Open(pathIn, lossyWAV); + cueSheet.Open(pathIn); this.Invoke((MethodInvoker)delegate() { @@ -312,7 +312,7 @@ namespace JDP { pathOut = txtOutputPath.Text; }); - cueSheet.GenerateFilenames(outputFormat, pathOut); + cueSheet.GenerateFilenames(outputFormat, lossyWAV, pathOut); string outDir = Path.GetDirectoryName(pathOut); if (cueStyle == CUEStyle.SingleFileWithCUE) cueSheet.SingleFilename = Path.GetFileName(pathOut); diff --git a/MAC_SDK/Source/MACLib/Assembly/Assembly64.obj b/MAC_SDK/Source/MACLib/Assembly/Assembly64.obj index 0391c8e..130fc61 100644 Binary files a/MAC_SDK/Source/MACLib/Assembly/Assembly64.obj and b/MAC_SDK/Source/MACLib/Assembly/Assembly64.obj differ