diff --git a/CUETools.Processor/AudioReadWrite.cs b/CUETools.Processor/AudioReadWrite.cs
new file mode 100644
index 0000000..a363fab
--- /dev/null
+++ b/CUETools.Processor/AudioReadWrite.cs
@@ -0,0 +1,102 @@
+using System;
+using System.IO;
+using FLACDotNet;
+using WavPackDotNet;
+using APEDotNet;
+using CUETools.Codecs;
+using CUETools.Codecs.ALAC;
+using CUETools.Codecs.LossyWAV;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+
+namespace CUETools.Processor
+{
+ public static class AudioReadWrite {
+ public static IAudioSource GetAudioSource(string path, Stream IO, string extension)
+ {
+ switch (extension)
+ {
+ case ".wav":
+ return new WAVReader(path, IO);
+ case ".m4a":
+ return new ALACReader(path, IO);
+#if !MONO
+ case ".flac":
+ return new FLACReader(path, IO);
+ case ".wv":
+ return new WavPackReader(path, IO, null);
+ case ".ape":
+ return new APEReader(path, IO);
+#endif
+ default:
+ throw new Exception("Unsupported audio type.");
+ }
+ }
+
+ public static IAudioSource GetAudioSource(string path, Stream IO)
+ {
+ string extension = Path.GetExtension(path).ToLower();
+ string filename = Path.GetFileNameWithoutExtension(path);
+ string secondExtension = Path.GetExtension(filename).ToLower();
+ if (secondExtension != ".lossy" && secondExtension != ".lwcdf")
+ return GetAudioSource(path, IO, extension);
+
+ string lossyPath = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(filename) + ".lossy" + extension);
+ string lwcdfPath = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(filename) + ".lwcdf" + extension);
+ IAudioSource lossySource = GetAudioSource(lossyPath, null, extension);
+ IAudioSource lwcdfSource = GetAudioSource(lwcdfPath, null, extension);
+ return new LossyWAVReader(lossySource, lwcdfSource);
+ }
+
+ public static IAudioDest GetAudioDest(string path, int bitsPerSample, int channelCount, int sampleRate, long finalSampleCount, string extension, CUEConfig config) {
+ IAudioDest dest;
+ switch (extension) {
+ case ".wav":
+ dest = new WAVWriter(path, bitsPerSample, channelCount, sampleRate, null);
+ break;
+#if !MONO
+ case ".flac":
+ dest = new FLACWriter(path, bitsPerSample, channelCount, sampleRate);
+ ((FLACWriter)dest).CompressionLevel = (int)config.flacCompressionLevel;
+ ((FLACWriter)dest).Verify = config.flacVerify;
+ break;
+ case ".wv":
+ dest = new WavPackWriter(path, bitsPerSample, channelCount, sampleRate);
+ ((WavPackWriter)dest).CompressionMode = config.wvCompressionMode;
+ ((WavPackWriter)dest).ExtraMode = config.wvExtraMode;
+ ((WavPackWriter)dest).MD5Sum = config.wvStoreMD5;
+ break;
+ case ".ape":
+ dest = new APEWriter(path, bitsPerSample, channelCount, sampleRate);
+ ((APEWriter)dest).CompressionLevel = (int)config.apeCompressionLevel;
+ break;
+ case ".dummy":
+ dest = new DummyWriter(path, bitsPerSample, channelCount, sampleRate);
+ break;
+#endif
+ default:
+ throw new Exception("Unsupported audio type.");
+ }
+ dest.FinalSampleCount = finalSampleCount;
+ return dest;
+ }
+
+ public static IAudioDest GetAudioDest(string path, long finalSampleCount, CUEConfig config)
+ {
+ string extension = Path.GetExtension(path).ToLower();
+ string filename = Path.GetFileNameWithoutExtension(path);
+ if (Path.GetExtension(filename).ToLower() != ".lossy")
+ {
+ int bitsPerSample = (config.detectHDCD && config.decodeHDCD) ? (config.decodeHDCDto24bit ? 24 : 20) : 16;
+ return GetAudioDest(path, bitsPerSample, 2, 44100, finalSampleCount, extension, config);
+ }
+
+ string lwcdfPath = Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(filename) + ".lwcdf" + extension);
+ 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);
+ return new LossyWAVWriter(lossyDest, lwcdfDest, destBitsPerSample, 2, 44100, config.lossyWAVQuality);
+ }
+ }
+}
\ No newline at end of file
diff --git a/CUETools.Processor/CUETools.Processor.csproj b/CUETools.Processor/CUETools.Processor.csproj
new file mode 100644
index 0000000..6ad0da6
--- /dev/null
+++ b/CUETools.Processor/CUETools.Processor.csproj
@@ -0,0 +1,141 @@
+
+
+ Debug
+ AnyCPU
+ 8.0.50727
+ 2.0
+ {4911BD82-49EF-4858-8B51-5394F86739A4}
+ Library
+ Properties
+ CUETools.Processor
+ CUETools.Processor
+
+
+ true
+ full
+ false
+ ..\bin\win32\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ true
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ true
+
+
+ true
+ ..\bin\win32\Debug\
+ DEBUG;TRACE
+ full
+ x86
+ C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules
+ true
+ GlobalSuppressions.cs
+ prompt
+ true
+
+
+ ..\bin\win32\Release\
+ TRACE
+ true
+ true
+ pdbonly
+ x86
+ C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules
+ true
+ GlobalSuppressions.cs
+ prompt
+
+
+ true
+ ..\bin\x64\Debug\
+ DEBUG;TRACE
+ full
+ x64
+ C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules
+ true
+ GlobalSuppressions.cs
+ prompt
+ true
+
+
+ ..\bin\x64\Release\
+ TRACE
+ true
+ true
+ pdbonly
+ x64
+ C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules
+ true
+ GlobalSuppressions.cs
+ prompt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {F2EC7193-D5E5-4252-9803-5CEB407E910F}
+ CUETools.Codecs.ALAC
+
+
+ {9AE965C4-301E-4C01-B90F-297AF341ACC6}
+ APEDotNet
+
+
+ {6458A13A-30EF-45A9-9D58-E5031B17BEE2}
+ CUETools.Codecs
+
+
+ {5802C7E9-157E-4124-946D-70B5AE48A5A1}
+ CUETools.AccurateRip
+
+
+ {1DD41038-D885-46C5-8DDE-E0B82F066584}
+ CUETools.CDImage
+
+
+ {E70FA90A-7012-4A52-86B5-362B699D1540}
+ FLACDotNet
+
+
+ {32338A04-5B6B-4C63-8EE7-C6400F73B5D7}
+ HDCDDotNet
+
+
+ {8A0426FA-0BC2-4C49-A6E5-1F9A68156F19}
+ CUETools.Codecs.LossyWAV
+
+
+ {8427CAA5-80B8-4952-9A68-5F3DFCFBDF40}
+ UnRarDotNet
+
+
+ {CC2E74B6-534A-43D8-9F16-AC03FE955000}
+ WavPackDotNet
+
+
+
+
+
\ No newline at end of file
diff --git a/CUETools.Processor/Main.cs b/CUETools.Processor/Main.cs
new file mode 100644
index 0000000..176118d
--- /dev/null
+++ b/CUETools.Processor/Main.cs
@@ -0,0 +1,2548 @@
+// ****************************************************************************
+//
+// CUE Tools
+// Copyright (C) 2006-2007 Moitah (moitah@yahoo.com)
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// ****************************************************************************
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Text;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Security.Cryptography;
+using System.Threading;
+using System.Xml;
+using HDCDDotNet;
+using CUETools.Codecs;
+using CUETools.Codecs.LossyWAV;
+using CUETools.CDImage;
+using CUETools.AccurateRip;
+#if !MONO
+using UnRarDotNet;
+using FLACDotNet;
+#endif
+
+namespace CUETools.Processor
+{
+
+ public enum OutputAudioFormat
+ {
+ WAV,
+ FLAC,
+ WavPack,
+ APE,
+ NoAudio
+ }
+
+ public static class General {
+ public static string FormatExtension(OutputAudioFormat value)
+ {
+ switch (value)
+ {
+ case OutputAudioFormat.FLAC: return ".flac";
+ case OutputAudioFormat.WavPack: return ".wv";
+ case OutputAudioFormat.APE: return ".ape";
+ case OutputAudioFormat.WAV: return ".wav";
+ case OutputAudioFormat.NoAudio: return ".dummy";
+ }
+ return ".wav";
+ }
+
+ public static CUELine FindCUELine(List list, string command) {
+ command = command.ToUpper();
+ foreach (CUELine line in list) {
+ if (line.Params[0].ToUpper() == command) {
+ return line;
+ }
+ }
+ return null;
+ }
+
+ public static CUELine FindCUELine(List list, string command, string command2)
+ {
+ command = command.ToUpper();
+ command2 = command2.ToUpper();
+ foreach (CUELine line in list)
+ {
+ if (line.Params.Count > 1 && line.Params[0].ToUpper() == command && line.Params[1].ToUpper() == command2)
+ {
+ return line;
+ }
+ }
+ return null;
+ }
+
+ public static void SetCUELine(List list, string command, string value, bool quoted)
+ {
+ CUELine line = General.FindCUELine(list, command);
+ if (line == null)
+ {
+ line = new CUELine();
+ line.Params.Add(command); line.IsQuoted.Add(false);
+ line.Params.Add(value); line.IsQuoted.Add(true);
+ list.Add(line);
+ }
+ else
+ {
+ line.Params[1] = value;
+ line.IsQuoted[1] = quoted;
+ }
+ }
+
+ public static void SetCUELine(List list, string command, string command2, string value, bool quoted)
+ {
+ CUELine line = General.FindCUELine(list, command, command2);
+ if (line == null)
+ {
+ 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);
+ list.Add(line);
+ }
+ else
+ {
+ line.Params[2] = value;
+ line.IsQuoted[2] = quoted;
+ }
+ }
+
+ public static string ReplaceMultiple(string s, List find, List replace)
+ {
+ if (find.Count != replace.Count)
+ {
+ throw new ArgumentException();
+ }
+ StringBuilder sb;
+ int iChar, iFind;
+ string f;
+ bool found;
+
+ sb = new StringBuilder();
+
+ for (iChar = 0; iChar < s.Length; iChar++)
+ {
+ found = false;
+ for (iFind = 0; iFind < find.Count; iFind++)
+ {
+ f = find[iFind];
+ if ((f.Length <= (s.Length - iChar)) && (s.Substring(iChar, f.Length) == f))
+ {
+ if (replace[iFind] == null)
+ {
+ return null;
+ }
+ sb.Append(replace[iFind]);
+ iChar += f.Length - 1;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ sb.Append(s[iChar]);
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ public static string EmptyStringToNull(string s)
+ {
+ return ((s != null) && (s.Length == 0)) ? null : s;
+ }
+ }
+
+ public enum CUEStyle {
+ SingleFileWithCUE,
+ SingleFile,
+ GapsPrepended,
+ GapsAppended,
+ GapsLeftOut
+ }
+
+ public class CUEConfig {
+ public uint fixWhenConfidence;
+ public uint fixWhenPercent;
+ public uint encodeWhenConfidence;
+ public uint encodeWhenPercent;
+ public bool encodeWhenZeroOffset;
+ public bool writeArTagsOnVerify;
+ public bool writeArLogOnVerify;
+ public bool writeArTagsOnConvert;
+ public bool writeArLogOnConvert;
+ public bool fixOffset;
+ public bool noUnverifiedOutput;
+ public bool autoCorrectFilenames;
+ public bool flacVerify;
+ public uint flacCompressionLevel;
+ public uint apeCompressionLevel;
+ public bool preserveHTOA;
+ public int wvCompressionMode;
+ public int wvExtraMode;
+ public bool wvStoreMD5;
+ public bool keepOriginalFilenames;
+ public string trackFilenameFormat;
+ public string singleFilenameFormat;
+ public bool removeSpecial;
+ public string specialExceptions;
+ public bool replaceSpaces;
+ public bool embedLog;
+ public bool fillUpCUE;
+ public bool filenamesANSISafe;
+ public bool bruteForceDTL;
+ public bool detectHDCD;
+ public bool decodeHDCD;
+ public bool wait750FramesForHDCD;
+ public bool createM3U;
+ public bool createTOC;
+ public bool createCUEFileWhenEmbedded;
+ public bool truncate4608ExtraSamples;
+ public int lossyWAVQuality;
+ public bool decodeHDCDtoLW16;
+ public bool decodeHDCDto24bit;
+
+ public CUEConfig()
+ {
+ fixWhenConfidence = 2;
+ fixWhenPercent = 51;
+ encodeWhenConfidence = 2;
+ encodeWhenPercent = 100;
+ encodeWhenZeroOffset = false;
+ fixOffset = false;
+ noUnverifiedOutput = false;
+ writeArTagsOnConvert = true;
+ writeArLogOnConvert = true;
+ writeArTagsOnVerify = false;
+ writeArLogOnVerify = true;
+
+ autoCorrectFilenames = true;
+ flacVerify = false;
+ flacCompressionLevel = 8;
+ apeCompressionLevel = 2;
+ preserveHTOA = true;
+ wvCompressionMode = 1;
+ wvExtraMode = 0;
+ wvStoreMD5 = false;
+ keepOriginalFilenames = true;
+ trackFilenameFormat = "%N-%A-%T";
+ singleFilenameFormat = "%F";
+ removeSpecial = false;
+ specialExceptions = "-()";
+ replaceSpaces = true;
+ embedLog = true;
+ fillUpCUE = true;
+ filenamesANSISafe = true;
+ bruteForceDTL = false;
+ detectHDCD = true;
+ wait750FramesForHDCD = true;
+ decodeHDCD = false;
+ createM3U = false;
+ createTOC = false;
+ createCUEFileWhenEmbedded = false;
+ truncate4608ExtraSamples = true;
+ lossyWAVQuality = 5;
+ decodeHDCDtoLW16 = false;
+ decodeHDCDto24bit = true;
+ }
+
+ public void Save (SettingsWriter sw)
+ {
+ sw.Save("ArFixWhenConfidence", fixWhenConfidence);
+ sw.Save("ArFixWhenPercent", fixWhenPercent);
+ sw.Save("ArEncodeWhenConfidence", encodeWhenConfidence);
+ sw.Save("ArEncodeWhenPercent", encodeWhenPercent);
+ sw.Save("ArEncodeWhenZeroOffset", encodeWhenZeroOffset);
+ sw.Save("ArNoUnverifiedOutput", noUnverifiedOutput);
+ sw.Save("ArFixOffset", fixOffset);
+ sw.Save("ArWriteCRC", writeArTagsOnConvert);
+ sw.Save("ArWriteLog", writeArLogOnConvert);
+ sw.Save("ArWriteTagsOnVerify", writeArTagsOnVerify);
+ sw.Save("ArWriteLogOnVerify", writeArLogOnVerify);
+
+ sw.Save("PreserveHTOA", preserveHTOA);
+ sw.Save("AutoCorrectFilenames", autoCorrectFilenames);
+ sw.Save("FLACCompressionLevel", flacCompressionLevel);
+ sw.Save("APECompressionLevel", apeCompressionLevel);
+ sw.Save("FLACVerify", flacVerify);
+ sw.Save("WVCompressionMode", wvCompressionMode);
+ sw.Save("WVExtraMode", wvExtraMode);
+ sw.Save("WVStoreMD5", wvStoreMD5);
+ sw.Save("KeepOriginalFilenames", keepOriginalFilenames);
+ sw.Save("SingleFilenameFormat", singleFilenameFormat);
+ sw.Save("TrackFilenameFormat", trackFilenameFormat);
+ sw.Save("RemoveSpecialCharacters", removeSpecial);
+ sw.Save("SpecialCharactersExceptions", specialExceptions);
+ sw.Save("ReplaceSpaces", replaceSpaces);
+ sw.Save("EmbedLog", embedLog);
+ sw.Save("FillUpCUE", fillUpCUE);
+ sw.Save("FilenamesANSISafe", filenamesANSISafe);
+ sw.Save("BruteForceDTL", bruteForceDTL);
+ sw.Save("DetectHDCD", detectHDCD);
+ sw.Save("Wait750FramesForHDCD", wait750FramesForHDCD);
+ sw.Save("DecodeHDCD", decodeHDCD);
+ sw.Save("CreateM3U", createM3U);
+ sw.Save("CreateTOC", createTOC);
+ sw.Save("CreateCUEFileWhenEmbedded", createCUEFileWhenEmbedded);
+ sw.Save("Truncate4608ExtraSamples", truncate4608ExtraSamples);
+ sw.Save("LossyWAVQuality", lossyWAVQuality);
+ sw.Save("DecodeHDCDToLossyWAV16", decodeHDCDtoLW16);
+ sw.Save("DecodeHDCDTo24bit", decodeHDCDto24bit);
+ }
+
+ public void Load(SettingsReader sr)
+ {
+ fixWhenConfidence = sr.LoadUInt32("ArFixWhenConfidence", 1, 1000) ?? 2;
+ fixWhenPercent = sr.LoadUInt32("ArFixWhenPercent", 1, 100) ?? 51;
+ encodeWhenConfidence = sr.LoadUInt32("ArEncodeWhenConfidence", 1, 1000) ?? 2;
+ encodeWhenPercent = sr.LoadUInt32("ArEncodeWhenPercent", 1, 100) ?? 100;
+ encodeWhenZeroOffset = sr.LoadBoolean("ArEncodeWhenZeroOffset") ?? false;
+ noUnverifiedOutput = sr.LoadBoolean("ArNoUnverifiedOutput") ?? false;
+ fixOffset = sr.LoadBoolean("ArFixOffset") ?? false;
+ writeArTagsOnConvert = sr.LoadBoolean("ArWriteCRC") ?? true;
+ writeArLogOnConvert = sr.LoadBoolean("ArWriteLog") ?? true;
+ writeArTagsOnVerify = sr.LoadBoolean("ArWriteTagsOnVerify") ?? false;
+ writeArLogOnVerify = sr.LoadBoolean("ArWriteLogOnVerify") ?? true;
+
+ preserveHTOA = sr.LoadBoolean("PreserveHTOA") ?? true;
+ autoCorrectFilenames = sr.LoadBoolean("AutoCorrectFilenames") ?? true;
+ flacCompressionLevel = sr.LoadUInt32("FLACCompressionLevel", 0, 8) ?? 8;
+ flacVerify = sr.LoadBoolean("FLACVerify") ?? false;
+ apeCompressionLevel = sr.LoadUInt32("APECompressionLevel", 1, 5) ?? 2;
+ wvCompressionMode = sr.LoadInt32("WVCompressionMode", 0, 3) ?? 1;
+ wvExtraMode = sr.LoadInt32("WVExtraMode", 0, 6) ?? 0;
+ wvStoreMD5 = sr.LoadBoolean("WVStoreMD5") ?? false;
+ keepOriginalFilenames = sr.LoadBoolean("KeepOriginalFilenames") ?? true;
+ singleFilenameFormat = sr.Load("SingleFilenameFormat") ?? "%F";
+ trackFilenameFormat = sr.Load("TrackFilenameFormat") ?? "%N-%A-%T";
+ removeSpecial = sr.LoadBoolean("RemoveSpecialCharacters") ?? false;
+ specialExceptions = sr.Load("SpecialCharactersExceptions") ?? "-()";
+ replaceSpaces = sr.LoadBoolean("ReplaceSpaces") ?? true;
+ embedLog = sr.LoadBoolean("EmbedLog") ?? true;
+ fillUpCUE = sr.LoadBoolean("FillUpCUE") ?? true;
+ filenamesANSISafe = sr.LoadBoolean("FilenamesANSISafe") ?? true;
+ bruteForceDTL = sr.LoadBoolean("BruteForceDTL") ?? false;
+ detectHDCD = sr.LoadBoolean("DetectHDCD") ?? true;
+ wait750FramesForHDCD = sr.LoadBoolean("Wait750FramesForHDCD") ?? true;
+ decodeHDCD = sr.LoadBoolean("DecodeHDCD") ?? false;
+ createM3U = sr.LoadBoolean("CreateM3U") ?? false;
+ createTOC = sr.LoadBoolean("CreateTOC") ?? false;
+ createCUEFileWhenEmbedded = sr.LoadBoolean("CreateCUEFileWhenEmbedded") ?? false;
+ truncate4608ExtraSamples = sr.LoadBoolean("Truncate4608ExtraSamples") ?? true;
+ lossyWAVQuality = sr.LoadInt32("LossyWAVQuality", 0, 10) ?? 5;
+ decodeHDCDtoLW16 = sr.LoadBoolean("DecodeHDCDToLossyWAV16") ?? false;
+ decodeHDCDto24bit = sr.LoadBoolean("DecodeHDCDTo24bit") ?? true;
+ }
+
+ public string CleanseString (string s)
+ {
+ StringBuilder sb = new StringBuilder();
+ char[] invalid = Path.GetInvalidFileNameChars();
+
+ if (filenamesANSISafe)
+ s = Encoding.Default.GetString(Encoding.Default.GetBytes(s));
+
+ for (int i = 0; i < s.Length; i++)
+ {
+ char ch = s[i];
+ if (filenamesANSISafe && removeSpecial && specialExceptions.IndexOf(ch) < 0 && !(
+ ((ch >= 'a') && (ch <= 'z')) ||
+ ((ch >= 'A') && (ch <= 'Z')) ||
+ ((ch >= '0') && (ch <= '9')) ||
+ (ch == ' ') || (ch == '_')))
+ ch = '_';
+ if ((Array.IndexOf(invalid, ch) >= 0) || (replaceSpaces && ch == ' '))
+ sb.Append("_");
+ else
+ sb.Append(ch);
+ }
+
+ return sb.ToString();
+ }
+ }
+
+ public class CUEToolsProgressEventArgs
+ {
+ public string status = string.Empty;
+ public uint percentTrack = 0;
+ public double percentDisk = 0.0;
+ public string input = string.Empty;
+ public string output = string.Empty;
+ }
+
+ public class ArchivePasswordRequiredEventArgs
+ {
+ public string Password = string.Empty;
+ public bool ContinueOperation = true;
+ }
+
+ public delegate void CUEToolsProgressHandler(object sender, CUEToolsProgressEventArgs e);
+ public delegate void ArchivePasswordRequiredHandler(object sender, ArchivePasswordRequiredEventArgs e);
+
+ public class CUESheet {
+ private bool _stop, _pause;
+ private List _attributes;
+ private List _tracks;
+ private List _sources;
+ private List _sourcePaths, _trackFilenames;
+ private string _htoaFilename, _singleFilename;
+ private bool _hasHTOAFilename, _hasTrackFilenames, _hasSingleFilename, _appliedWriteOffset;
+ private bool _hasEmbeddedCUESheet;
+ private bool _paddedToFrame, _truncated4608, _usePregapForFirstTrackInSingleFile;
+ private int _writeOffset;
+ private bool _accurateRip, _accurateOffset;
+ private uint? _dataTrackLength;
+ private uint? _minDataTrackLength;
+ private string _accurateRipId;
+ private string _accurateRipIdActual;
+ private string _mbDiscId;
+ private string _mbReleaseId;
+ private string _eacLog;
+ private string _cuePath;
+ private NameValueCollection _albumTags;
+ private const int _arOffsetRange = 5 * 588 - 1;
+ private HDCDDotNet.HDCDDotNet hdcdDecoder;
+ private bool _outputLossyWAV = false;
+ CUEConfig _config;
+ string _cddbDiscIdTag;
+ private bool _isArchive;
+ private List _archiveContents;
+ private string _archiveCUEpath;
+ private string _archivePath;
+ private string _archivePassword;
+ private CUEToolsProgressEventArgs _progress;
+ private AccurateRipVerify _arVerify;
+
+ public event ArchivePasswordRequiredHandler PasswordRequired;
+ public event CUEToolsProgressHandler CUEToolsProgress;
+
+ public CUESheet(CUEConfig config)
+ {
+ _config = config;
+ _progress = new CUEToolsProgressEventArgs();
+ _attributes = new List();
+ _tracks = new List();
+ _toc = new CDImageLayout(0);
+ _sources = new List();
+ _sourcePaths = new List();
+ _albumTags = new NameValueCollection();
+ _stop = false;
+ _pause = false;
+ _cuePath = null;
+ _paddedToFrame = false;
+ _truncated4608 = false;
+ _usePregapForFirstTrackInSingleFile = false;
+ _accurateRip = false;
+ _accurateOffset = false;
+ _appliedWriteOffset = false;
+ _dataTrackLength = null;
+ _minDataTrackLength = null;
+ hdcdDecoder = null;
+ _hasEmbeddedCUESheet = false;
+ _isArchive = false;
+ }
+
+ public void Open(string pathIn, bool outputLossyWAV)
+ {
+ _outputLossyWAV = outputLossyWAV;
+ if (_config.detectHDCD)
+ {
+ try { hdcdDecoder = new HDCDDotNet.HDCDDotNet(2, 44100, ((_outputLossyWAV && _config.decodeHDCDtoLW16) || !_config.decodeHDCDto24bit) ? 20 : 24, _config.decodeHDCD); }
+ catch { }
+ }
+
+ string cueDir, lineStr, command, pathAudio = null, fileType;
+ CUELine line;
+ TrackInfo trackInfo;
+ int timeRelativeToFileStart, absoluteFileStartTime;
+ int fileTimeLengthSamples, fileTimeLengthFrames, 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)
+ throw new Exception("Input directory must end on path separator character.");
+ string cueSheet = null;
+ string[] audioExts = new string[] { "*.wav", "*.flac", "*.wv", "*.ape", "*.m4a" };
+ for (i = 0; i < audioExts.Length && cueSheet == null; i++)
+ cueSheet = CUESheet.CreateDummyCUESheet(pathIn, audioExts[i]);
+ if (cueSheet == null)
+ throw new Exception("Input directory doesn't contain supported audio files.");
+ sr = new StringReader(cueSheet);
+ }
+#if !MONO
+ else if (Path.GetExtension(pathIn).ToLower() == ".rar")
+ {
+ Unrar _unrar = new Unrar();
+ _unrar.PasswordRequired += new PasswordRequiredHandler(unrar_PasswordRequired);
+ string cueName = null, cueText = null;
+ _unrar.Open(pathIn, Unrar.OpenMode.List);
+ _archiveContents = new List();
+ while (_unrar.ReadHeader())
+ {
+ if (!_unrar.CurrentFile.IsDirectory)
+ {
+ _archiveContents.Add(_unrar.CurrentFile.FileName);
+ if (Path.GetExtension(_unrar.CurrentFile.FileName).ToLower() == ".cue")
+ cueName = _unrar.CurrentFile.FileName;
+ }
+ _unrar.Skip();
+ }
+ _unrar.Close();
+ if (cueName != null)
+ {
+ RarStream rarStream = new RarStream(pathIn, cueName);
+ rarStream.PasswordRequired += new PasswordRequiredHandler(unrar_PasswordRequired);
+ StreamReader cueReader = new StreamReader(rarStream, CUESheet.Encoding);
+ cueText = cueReader.ReadToEnd();
+ cueReader.Close();
+ rarStream.Close();
+ if (cueText == "")
+ throw new Exception("Empty cue sheet.");
+ }
+ if (cueText == null)
+ throw new Exception("Input archive doesn't contain a cue sheet.");
+ _archiveCUEpath = Path.GetDirectoryName(cueName);
+ sr = new StringReader(cueText);
+ _isArchive = true;
+ _archivePath = pathIn;
+ }
+#endif
+ else if (Path.GetExtension(pathIn).ToLower() == ".cue")
+ {
+ if (_config.autoCorrectFilenames)
+ sr = new StringReader (CorrectAudioFilenames(pathIn, false));
+ else
+ sr = new StreamReader (pathIn, CUESheet.Encoding);
+
+ try
+ {
+ StreamReader logReader = new StreamReader(Path.ChangeExtension(pathIn, ".log"), CUESheet.Encoding);
+ _eacLog = logReader.ReadToEnd();
+ logReader.Close();
+ }
+ catch { }
+ } else
+ {
+ IAudioSource audioSource;
+ NameValueCollection tags;
+ string cuesheetTag = null;
+
+ audioSource = AudioReadWrite.GetAudioSource(pathIn,null);
+ tags = audioSource.Tags;
+ cuesheetTag = tags.Get("CUESHEET");
+ _accurateRipId = tags.Get("ACCURATERIPID");
+ _eacLog = tags.Get("LOG");
+ if (_eacLog == null) _eacLog = tags.Get("LOGFILE");
+ if (_eacLog == null) _eacLog = tags.Get("EACLOG");
+ audioSource.Close();
+ if (cuesheetTag == null)
+ throw new Exception("Input file does not contain a .cue sheet.");
+ sr = new StringReader (cuesheetTag);
+ pathAudio = pathIn;
+ _hasEmbeddedCUESheet = true;
+ }
+
+ using (sr) {
+ while ((lineStr = sr.ReadLine()) != null) {
+ line = new CUELine(lineStr);
+ if (line.Params.Count > 0) {
+ command = line.Params[0].ToUpper();
+
+ if (command == "FILE") {
+ fileType = line.Params[2].ToUpper();
+ if ((fileType == "BINARY") || (fileType == "MOTOROLA")) {
+ seenDataTrack = true;
+ }
+ else if (seenDataTrack) {
+ throw new Exception("Audio tracks cannot appear after data tracks.");
+ }
+ else {
+ if (!_hasEmbeddedCUESheet)
+ {
+ if (_isArchive)
+ pathAudio = LocateFile(_archiveCUEpath, line.Params[1], _archiveContents);
+ else
+ pathAudio = LocateFile(cueDir, line.Params[1], null);
+ if (pathAudio == null)
+ throw new Exception("Unable to locate file \"" + line.Params[1] + "\".");
+ } else
+ {
+ if (_sourcePaths.Count > 0 )
+ throw new Exception("Extra file in embedded CUE sheet: \"" + line.Params[1] + "\".");
+ }
+ _sourcePaths.Add(pathAudio);
+ absoluteFileStartTime += fileTimeLengthFrames;
+ NameValueCollection tags;
+ fileTimeLengthSamples = GetSampleLength(pathAudio, out tags);
+ if ((fileTimeLengthSamples % 588) == 492 && _config.truncate4608ExtraSamples)
+ {
+ _truncated4608 = true;
+ fileTimeLengthSamples -= 4608;
+ }
+ fileTimeLengthFrames = (int)((fileTimeLengthSamples + 587) / 588);
+ if (_hasEmbeddedCUESheet)
+ _albumTags = tags;
+ else
+ _trackTags = tags;
+ seenFirstFileIndex = false;
+ }
+ }
+ else if (command == "TRACK") {
+ if (line.Params[2].ToUpper() != "AUDIO") {
+ seenDataTrack = true;
+ }
+ else if (seenDataTrack) {
+ throw new Exception("Audio tracks cannot appear after data tracks.");
+ }
+ else {
+ trackNumber = int.Parse(line.Params[1]);
+ if (trackNumber != _tracks.Count + 1) {
+ throw new Exception("Invalid track number.");
+ }
+ trackInfo = new TrackInfo();
+ _tracks.Add(trackInfo);
+ _toc.AddTrack(new CDTrack((uint)trackNumber, 0, 0, true));
+ }
+ }
+ else if (seenDataTrack) {
+ // Ignore lines belonging to data tracks
+ }
+ else if (command == "INDEX") {
+ timeRelativeToFileStart = CDImageLayout.TimeFromString(line.Params[2]);
+ if (!seenFirstFileIndex)
+ {
+ if (timeRelativeToFileStart != 0)
+ {
+ throw new Exception("First index must start at file beginning.");
+ }
+ if (trackNumber > 0 && _trackTags != null && _trackTags.Count != 0)
+ _tracks[trackNumber-1]._trackTags = _trackTags;
+ seenFirstFileIndex = true;
+ sourceInfo.Path = pathAudio;
+ sourceInfo.Offset = 0;
+ sourceInfo.Length = (uint)fileTimeLengthSamples;
+ _sources.Add(sourceInfo);
+ if ((fileTimeLengthSamples % 588) != 0)
+ {
+ sourceInfo.Path = null;
+ sourceInfo.Offset = 0;
+ sourceInfo.Length = (uint)((fileTimeLengthFrames * 588) - fileTimeLengthSamples);
+ _sources.Add(sourceInfo);
+ _paddedToFrame = true;
+ }
+ }
+ indexInfo.Track = trackNumber;
+ indexInfo.Index = Int32.Parse(line.Params[1]);
+ indexInfo.Time = absoluteFileStartTime + timeRelativeToFileStart;
+ indexes.Add(indexInfo);
+ }
+ else if (command == "PREGAP") {
+ if (seenFirstFileIndex) {
+ throw new Exception("Pregap must occur at the beginning of a file.");
+ }
+ int pregapLength = CDImageLayout.TimeFromString(line.Params[1]);
+ indexInfo.Track = trackNumber;
+ indexInfo.Index = 0;
+ indexInfo.Time = absoluteFileStartTime;
+ indexes.Add(indexInfo);
+ sourceInfo.Path = null;
+ sourceInfo.Offset = 0;
+ sourceInfo.Length = (uint)pregapLength * 588;
+ _sources.Add(sourceInfo);
+ absoluteFileStartTime += pregapLength;
+ }
+ else if (command == "POSTGAP") {
+ throw new Exception("POSTGAP command isn't supported.");
+ }
+ else if ((command == "REM") &&
+ (line.Params.Count >= 3) &&
+ (line.Params[1].Length >= 10) &&
+ (line.Params[1].Substring(0, 10).ToUpper() == "REPLAYGAIN"))
+ {
+ // Remove ReplayGain lines
+ }
+ else if ((command == "REM") &&
+ (line.Params.Count == 3) &&
+ (line.Params[1].ToUpper() == "DATATRACKLENGTH"))
+ {
+ _dataTrackLength = (uint)CDImageLayout.TimeFromString(line.Params[2]);
+ }
+ else if ((command == "REM") &&
+ (line.Params.Count == 3) &&
+ (line.Params[1].ToUpper() == "ACCURATERIPID"))
+ {
+ _accurateRipId = line.Params[2];
+ }
+ //else if ((command == "REM") &&
+ // (line.Params.Count == 3) &&
+ // (line.Params[1].ToUpper() == "SHORTEN"))
+ //{
+ // fileTimeLengthFrames -= General.TimeFromString(line.Params[2]);
+ //}
+ //else if ((command == "REM") &&
+ // (line.Params.Count == 3) &&
+ // (line.Params[1].ToUpper() == "LENGTHEN"))
+ //{
+ // fileTimeLengthFrames += General.TimeFromString(line.Params[2]);
+ //}
+ else
+ {
+ if (trackInfo != null)
+ {
+ trackInfo.Attributes.Add(line);
+ }
+ else
+ {
+ _attributes.Add(line);
+ }
+ }
+ }
+ }
+ sr.Close();
+ }
+
+ if (trackNumber == 0) {
+ throw new Exception("File must contain at least one audio track.");
+ }
+
+ // Add dummy track for calculation purposes
+ indexInfo.Track = trackNumber + 1;
+ indexInfo.Index = 1;
+ indexInfo.Time = absoluteFileStartTime + fileTimeLengthFrames;
+ indexes.Add(indexInfo);
+
+ // 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)
+ 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));
+ }
+ _toc.Length = (uint) indexes[indexes.Count - 1].Time;
+ for (i = 1; i <= TrackCount; i++)
+ {
+ 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);
+ }
+
+ // Store the audio filenames, generating generic names if necessary
+ _hasSingleFilename = (_sourcePaths.Count == 1);
+ _singleFilename = _hasSingleFilename ? Path.GetFileName(_sourcePaths[0]) :
+ "Range.wav";
+
+ _hasHTOAFilename = (_sourcePaths.Count == (TrackCount + 1));
+ _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) );
+ }
+
+ if (_hasTrackFilenames)
+ for (i = 0; i < TrackCount; i++)
+ {
+ TrackInfo track = _tracks[i];
+ string artist = track._trackTags.Get("ARTIST");
+ string title = track._trackTags.Get("TITLE");
+ if (track.Artist == "" && artist != null)
+ track.Artist = artist;
+ if (track.Title == "" && title != null)
+ track.Title = title;
+ }
+ if (!_hasEmbeddedCUESheet && _hasSingleFilename)
+ {
+ _albumTags = _tracks[0]._trackTags;
+ _tracks[0]._trackTags = new NameValueCollection();
+ }
+ if (_config.fillUpCUE)
+ {
+ if (General.FindCUELine(_attributes, "PERFORMER") == null && GetCommonTag("ALBUM ARTIST") != null)
+ General.SetCUELine(_attributes, "PERFORMER", GetCommonTag("ALBUM ARTIST"), true);
+ if (General.FindCUELine(_attributes, "PERFORMER") == null && GetCommonTag("ARTIST") != null)
+ General.SetCUELine(_attributes, "PERFORMER", GetCommonTag("ARTIST"), true);
+ if (General.FindCUELine(_attributes, "TITLE") == null && GetCommonTag("ALBUM") != null)
+ General.SetCUELine(_attributes, "TITLE", GetCommonTag("ALBUM"), true);
+ if (General.FindCUELine(_attributes, "REM", "DATE") == null && GetCommonTag("DATE") != null)
+ General.SetCUELine(_attributes, "REM", "DATE", GetCommonTag("DATE"), false);
+ if (General.FindCUELine(_attributes, "REM", "DATE") == null && GetCommonTag("YEAR") != null)
+ General.SetCUELine(_attributes, "REM", "DATE", GetCommonTag("YEAR"), false);
+ if (General.FindCUELine(_attributes, "REM", "GENRE") == null && GetCommonTag("GENRE") != null)
+ General.SetCUELine(_attributes, "REM", "GENRE", GetCommonTag("GENRE"), true);
+ }
+ if (_accurateRipId == null)
+ _accurateRipId = GetCommonTag("ACCURATERIPID");
+
+ if (_accurateRipId == null && _dataTrackLength == null && _eacLog != null)
+ {
+ sr = new StringReader(_eacLog);
+ int lastAudioSector = -1;
+ bool isEACLog = false;
+ while ((lineStr = sr.ReadLine()) != null)
+ {
+ 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 { }
+ }
+ }
+
+ 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;
+ }
+
+ _arVerify = new AccurateRipVerify(_toc);
+
+ //if (!_dataTrackLength.HasValue && _cddbDiscIdTag != null)
+ //{
+ // uint cddbDiscIdNum = UInt32.Parse(_cddbDiscIdTag, NumberStyles.HexNumber);
+ // if ((cddbDiscIdNum & 0xff) == TrackCount)
+ // {
+ // _cutOneFrame = true;
+ // string cddbDiscIdTagCut = CalculateAccurateRipId().Split('-')[2];
+ // if (cddbDiscIdTagCut.ToUpper() != _cddbDiscIdTag.ToUpper())
+ // _cutOneFrame = false;
+ // }
+ //}
+ }
+
+ public static Encoding Encoding {
+ get {
+ return Encoding.Default;
+ }
+ }
+
+ private void ShowProgress(string status, uint percentTrack, double percentDisk, string input, string output)
+ {
+ if (this.CUEToolsProgress == null)
+ return;
+ _progress.status = status;
+ _progress.percentTrack = percentTrack;
+ _progress.percentDisk = percentDisk;
+ _progress.input = input;
+ _progress.output = output;
+ this.CUEToolsProgress(this, _progress);
+ }
+
+#if !MONO
+ private void unrar_ExtractionProgress(object sender, ExtractionProgressEventArgs e)
+ {
+ if (this.CUEToolsProgress == null)
+ return;
+ _progress.percentTrack = (uint)Math.Round(e.PercentComplete);
+ this.CUEToolsProgress(this, _progress);
+ }
+
+ private void unrar_PasswordRequired(object sender, PasswordRequiredEventArgs e)
+ {
+ if (_archivePassword != null)
+ {
+ e.ContinueOperation = true;
+ e.Password = _archivePassword;
+ return;
+ }
+ if (this.PasswordRequired != null)
+ {
+ ArchivePasswordRequiredEventArgs e1 = new ArchivePasswordRequiredEventArgs();
+ this.PasswordRequired(this, e1);
+ if (e1.ContinueOperation && e1.Password != "")
+ {
+ _archivePassword = e1.Password;
+ e.ContinueOperation = true;
+ e.Password = e1.Password;
+ return;
+ }
+ }
+ throw new IOException("Password is required for extraction.");
+ }
+#endif
+
+ public string GetCommonTag(string tagName)
+ {
+ if (_hasEmbeddedCUESheet || _hasSingleFilename)
+ return _albumTags.Get(tagName);
+ if (_hasTrackFilenames)
+ {
+ string tagValue = null;
+ bool commonValue = true;
+ for (int i = 0; i < TrackCount; i++)
+ {
+ TrackInfo track = _tracks[i];
+ string newValue = track._trackTags.Get (tagName);
+ if (tagValue == null)
+ tagValue = newValue;
+ else
+ commonValue = (newValue == null || tagValue == newValue);
+ }
+ return commonValue ? tagValue : null;
+ }
+ return null;
+ }
+
+ private static string LocateFile(string dir, string file, List contents) {
+ List dirList, fileList;
+ string altDir, path;
+
+ dirList = new List();
+ fileList = new List();
+ altDir = Path.GetDirectoryName(file);
+ file = Path.GetFileName(file);
+
+ dirList.Add(dir);
+ if (altDir.Length != 0) {
+ dirList.Add(Path.IsPathRooted(altDir) ? altDir : Path.Combine(dir, altDir));
+ }
+
+ fileList.Add(file);
+ fileList.Add(file.Replace(' ', '_'));
+ fileList.Add(file.Replace('_', ' '));
+
+ for (int iDir = 0; iDir < dirList.Count; iDir++) {
+ for (int iFile = 0; iFile < fileList.Count; iFile++) {
+ path = Path.Combine(dirList[iDir], fileList[iFile]);
+ if ( (contents == null && File.Exists(path))
+ || (contents != null && contents.Contains (path)))
+ return path;
+ }
+ }
+
+ return null;
+ }
+
+ public void GenerateFilenames (OutputAudioFormat format, string outputPath)
+ {
+ _cuePath = outputPath;
+
+ string extension = General.FormatExtension(format);
+ List find, replace;
+ string filename;
+ int iTrack;
+
+ find = new List();
+ replace = new List();
+
+ find.Add("%D"); // 0: Album artist
+ find.Add("%C"); // 1: Album title
+ find.Add("%N"); // 2: Track number
+ find.Add("%A"); // 3: Track artist
+ find.Add("%T"); // 4: Track title
+ find.Add("%F"); // 5: Input filename
+
+ replace.Add(General.EmptyStringToNull(_config.CleanseString(Artist)));
+ replace.Add(General.EmptyStringToNull(_config.CleanseString(Title)));
+ replace.Add(null);
+ replace.Add(null);
+ replace.Add(null);
+ replace.Add(Path.GetFileNameWithoutExtension(outputPath));
+
+ if (_outputLossyWAV)
+ extension = ".lossy" + extension;
+ if (_config.detectHDCD && _config.decodeHDCD && (!_outputLossyWAV || !_config.decodeHDCDtoLW16))
+ {
+ if (_config.decodeHDCDto24bit )
+ extension = ".24bit" + extension;
+ else
+ extension = ".20bit" + extension;
+ }
+
+ if (_config.keepOriginalFilenames && HasSingleFilename)
+ {
+ SingleFilename = Path.ChangeExtension(SingleFilename, extension);
+ }
+ else
+ {
+ filename = General.ReplaceMultiple(_config.singleFilenameFormat, find, replace);
+ if (filename == null)
+ filename = "Range";
+ filename += extension;
+ SingleFilename = filename;
+ }
+
+ for (iTrack = -1; iTrack < TrackCount; iTrack++)
+ {
+ bool htoa = (iTrack == -1);
+
+ if (_config.keepOriginalFilenames && htoa && HasHTOAFilename)
+ {
+ HTOAFilename = Path.ChangeExtension(HTOAFilename, extension);
+ }
+ else if (_config.keepOriginalFilenames && !htoa && HasTrackFilenames)
+ {
+ TrackFilenames[iTrack] = Path.ChangeExtension(
+ TrackFilenames[iTrack], extension);
+ }
+ else
+ {
+ string trackStr = htoa ? "01.00" : String.Format("{0:00}", iTrack + 1);
+ string artist = Tracks[htoa ? 0 : iTrack].Artist;
+ string title = htoa ? "(HTOA)" : Tracks[iTrack].Title;
+
+ replace[2] = trackStr;
+ replace[3] = General.EmptyStringToNull(_config.CleanseString(artist==""?Artist:artist));
+ replace[4] = General.EmptyStringToNull(_config.CleanseString(title));
+
+ filename = General.ReplaceMultiple(_config.trackFilenameFormat, find, replace);
+ if (filename == null)
+ filename = replace[2];
+ filename += extension;
+
+ if (htoa)
+ {
+ HTOAFilename = filename;
+ }
+ else
+ {
+ TrackFilenames[iTrack] = filename;
+ }
+ }
+ }
+ }
+
+ private int GetSampleLength(string path, out NameValueCollection tags)
+ {
+ IAudioSource audioSource;
+
+ ShowProgress("Analyzing input file...", 0, 0.0, path, null);
+#if !MONO
+ if (_isArchive)
+ {
+ RarStream IO = new RarStream(_archivePath, path);
+ IO.PasswordRequired += new PasswordRequiredHandler(unrar_PasswordRequired);
+ IO.ExtractionProgress += new ExtractionProgressHandler(unrar_ExtractionProgress);
+ audioSource = AudioReadWrite.GetAudioSource(path, IO);
+ } else
+#endif
+ audioSource = AudioReadWrite.GetAudioSource(path, null);
+
+ if ((audioSource.BitsPerSample != 16) ||
+ (audioSource.ChannelCount != 2) ||
+ (audioSource.SampleRate != 44100) ||
+ (audioSource.Length > Int32.MaxValue))
+ {
+ audioSource.Close();
+ throw new Exception("Audio format is invalid.");
+ }
+
+ tags = audioSource.Tags;
+ audioSource.Close();
+ return (int)audioSource.Length;
+ }
+
+ public void WriteM3U(string path, CUEStyle style)
+ {
+ StringWriter sw = new StringWriter();
+ WriteM3U(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();
+ }
+
+ public void WriteM3U(TextWriter sw, CUEStyle style)
+ {
+ int iTrack;
+ bool htoaToFile = ((style == CUEStyle.GapsAppended) && _config.preserveHTOA &&
+ (_toc.Pregap != 0));
+
+ if (htoaToFile) {
+ WriteLine(sw, 0, _htoaFilename);
+ }
+ for (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();
+ }
+
+ public void WriteTOC(TextWriter sw)
+ {
+ 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();
+ }
+
+ public void Write(TextWriter sw, CUEStyle style) {
+ int i, iTrack, iIndex;
+ TrackInfo track;
+ 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);
+
+ for (i = 0; i < _attributes.Count; i++) {
+ WriteLine(sw, 0, _attributes[i]);
+ }
+
+ if (style == CUEStyle.SingleFile || style == CUEStyle.SingleFileWithCUE) {
+ WriteLine(sw, 0, String.Format("FILE \"{0}\" WAVE", _singleFilename));
+ }
+ if (htoaToFile) {
+ WriteLine(sw, 0, String.Format("FILE \"{0}\" WAVE", _htoaFilename));
+ }
+
+ for (iTrack = 0; iTrack < TrackCount; iTrack++) {
+ track = _tracks[iTrack];
+
+ if ((style == CUEStyle.GapsPrepended) ||
+ (style == CUEStyle.GapsLeftOut) ||
+ ((style == CUEStyle.GapsAppended) &&
+ ((_toc[iTrack+1].Pregap == 0) || ((iTrack == 0) && !htoaToFile))))
+ {
+ WriteLine(sw, 0, String.Format("FILE \"{0}\" WAVE", _trackFilenames[iTrack]));
+ timeRelativeToFileStart = 0;
+ }
+
+ 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 (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)))
+ {
+ 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;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ 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();
+ }
+ }
+ throw;
+ }
+ }
+
+ public void GenerateAccurateRipLog(TextWriter sw)
+ {
+ int iTrack;
+ sw.WriteLine (String.Format("[Disc ID: {0}]", _accurateRipId));
+ if (_dataTrackLength.HasValue)
+ sw.WriteLine("Assuming a data track was present, length {0}.", CDImageLayout.TimeToString(_dataTrackLength.Value));
+ else
+ {
+ if (_cddbDiscIdTag != null && _accurateRipId.Split('-')[2].ToUpper() != _cddbDiscIdTag.ToUpper())
+ sw.WriteLine("CDDBId mismatch: {0} vs {1}", _cddbDiscIdTag.ToUpper(), _accurateRipId.Split('-')[2].ToUpper());
+ if (_minDataTrackLength.HasValue)
+ sw.WriteLine("Data track was probably present, length {0}-{1}.", CDImageLayout.TimeToString(_minDataTrackLength.Value), CDImageLayout.TimeToString(_minDataTrackLength.Value + 74));
+ if (_accurateRipIdActual != _accurateRipId)
+ sw.WriteLine("Using preserved id, actual id is {0}.", _accurateRipIdActual);
+ if (_truncated4608)
+ sw.WriteLine("Truncated 4608 extra samples in some input files.");
+ if (_paddedToFrame)
+ sw.WriteLine("Padded some input files to a frame boundary.");
+ }
+
+ if (hdcdDecoder != null && hdcdDecoder.Detected)
+ {
+ hdcd_decoder_statistics stats;
+ hdcdDecoder.GetStatistics(out stats);
+ sw.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))
+ );
+ }
+
+ 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);
+ }
+ }
+ }
+ }
+
+ public void GenerateAccurateRipTagsForTrack(NameValueCollection tags, int offset, int bestOffset, int iTrack, string prefix)
+ {
+ uint total = 0;
+ uint matching = 0;
+ uint matching2 = 0;
+ uint matching3 = 0;
+ for (int iDisk = 0; iDisk < _arVerify.AccDisks.Count; iDisk++)
+ {
+ total += _arVerify.AccDisks[iDisk].tracks[iTrack].count;
+ if (_arVerify.CRC(iTrack, offset) ==
+ _arVerify.AccDisks[iDisk].tracks[iTrack].CRC)
+ matching += _arVerify.AccDisks[iDisk].tracks[iTrack].count;
+ if (_arVerify.CRC(iTrack, bestOffset) ==
+ _arVerify.AccDisks[iDisk].tracks[iTrack].CRC)
+ matching2 += _arVerify.AccDisks[iDisk].tracks[iTrack].count;
+ for (int oi = -_arOffsetRange; oi <= _arOffsetRange; oi++)
+ if (_arVerify.CRC(iTrack, oi) ==
+ _arVerify.AccDisks[iDisk].tracks[iTrack].CRC)
+ matching3 += _arVerify.AccDisks[iDisk].tracks[iTrack].count;
+ }
+ tags.Add(String.Format("{0}ACCURATERIPCRC", prefix), String.Format("{0:x8}", _arVerify.CRC(iTrack, offset)));
+ tags.Add(String.Format("{0}AccurateRipDiscId", prefix), String.Format("{0:000}-{1}-{2:00}", TrackCount, _accurateRipId, iTrack+1));
+ tags.Add(String.Format("{0}ACCURATERIPCOUNT", prefix), String.Format("{0}", matching));
+ tags.Add(String.Format("{0}ACCURATERIPCOUNTALLOFFSETS", prefix), String.Format("{0}", matching3));
+ tags.Add(String.Format("{0}ACCURATERIPTOTAL", prefix), String.Format("{0}", total));
+ if (bestOffset != offset)
+ tags.Add(String.Format("{0}ACCURATERIPCOUNTWITHOFFSET", prefix), String.Format("{0}", matching2));
+ }
+
+ public void GenerateAccurateRipTags(NameValueCollection tags, int offset, int bestOffset, int iTrack)
+ {
+ tags.Add("ACCURATERIPID", _accurateRipId);
+ if (bestOffset != offset)
+ tags.Add("ACCURATERIPOFFSET", String.Format("{1}{0}", bestOffset - offset, bestOffset > offset ? "+" : ""));
+ if (iTrack != -1)
+ GenerateAccurateRipTagsForTrack(tags, offset, bestOffset, iTrack, "");
+ else
+ for (iTrack = 0; iTrack < TrackCount; iTrack++)
+ {
+ GenerateAccurateRipTagsForTrack(tags, offset, bestOffset, iTrack,
+ String.Format("cue_track{0:00}_", iTrack + 1));
+ }
+ }
+
+ public void CleanupTags (NameValueCollection tags, string substring)
+ {
+ string [] keys = tags.AllKeys;
+ for (int i = 0; i < keys.Length; i++)
+ if (keys[i].ToUpper().Contains(substring))
+ tags.Remove (keys[i]);
+ }
+
+ private void FindBestOffset(uint minConfidence, bool optimizeConfidence, out uint outTracksMatch, out int outBestOffset)
+ {
+ uint bestTracksMatch = 0;
+ uint bestConfidence = 0;
+ int bestOffset = 0;
+
+ for (int offset = -_arOffsetRange; offset <= _arOffsetRange; offset++)
+ {
+ uint tracksMatch = 0;
+ uint sumConfidence = 0;
+
+ for (int iTrack = 0; iTrack < TrackCount; iTrack++)
+ {
+ uint confidence = 0;
+
+ for (int di = 0; di < (int)_arVerify.AccDisks.Count; di++)
+ if (_arVerify.CRC(iTrack, offset) == _arVerify.AccDisks[di].tracks[iTrack].CRC)
+ confidence += _arVerify.AccDisks[di].tracks[iTrack].count;
+
+ if (confidence >= minConfidence)
+ tracksMatch++;
+
+ sumConfidence += confidence;
+ }
+
+ if (tracksMatch > bestTracksMatch
+ || (tracksMatch == bestTracksMatch && optimizeConfidence && sumConfidence > bestConfidence)
+ || (tracksMatch == bestTracksMatch && optimizeConfidence && sumConfidence == bestConfidence && Math.Abs(offset) < Math.Abs(bestOffset))
+ || (tracksMatch == bestTracksMatch && !optimizeConfidence && Math.Abs(offset) < Math.Abs(bestOffset))
+ )
+ {
+ bestTracksMatch = tracksMatch;
+ bestConfidence = sumConfidence;
+ bestOffset = offset;
+ }
+ }
+ outBestOffset = bestOffset;
+ outTracksMatch = bestTracksMatch;
+ }
+
+ public void WriteAudioFiles(string dir, CUEStyle style) {
+ string[] destPaths;
+ int[] destLengths;
+ bool htoaToFile = ((style == CUEStyle.GapsAppended) && _config.preserveHTOA &&
+ (_toc.Pregap != 0));
+
+ if (_usePregapForFirstTrackInSingleFile) {
+ throw new Exception("UsePregapForFirstTrackInSingleFile is not supported for writing audio files.");
+ }
+
+ if (style == CUEStyle.SingleFile || style == CUEStyle.SingleFileWithCUE) {
+ destPaths = new string[1];
+ destPaths[0] = Path.Combine(dir, _singleFilename);
+ }
+ else {
+ destPaths = new string[TrackCount + (htoaToFile ? 1 : 0)];
+ if (htoaToFile) {
+ destPaths[0] = Path.Combine(dir, _htoaFilename);
+ }
+ for (int i = 0; i < TrackCount; i++) {
+ destPaths[i + (htoaToFile ? 1 : 0)] = Path.Combine(dir, _trackFilenames[i]);
+ }
+ }
+
+ 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.");
+ }
+ }
+ }
+
+ destLengths = CalculateAudioFileLengths(style);
+
+ bool SkipOutput = false;
+
+ if (_accurateRip) {
+ ShowProgress((string)"Contacting AccurateRip database...", 0, 0, null, null);
+ if (!_dataTrackLength.HasValue && _minDataTrackLength.HasValue && _accurateRipId == _accurateRipIdActual && _config.bruteForceDTL)
+ {
+ uint minDTL = _minDataTrackLength.Value;
+ for (uint dtl = minDTL; dtl < minDTL + 75; dtl++)
+ {
+ _dataTrackLength = dtl;
+ _accurateRipId = CalculateAccurateRipId();
+ _arVerify.ContactAccurateRip(_accurateRipId);
+ if (_arVerify.AccResult != HttpStatusCode.NotFound)
+ break;
+ ShowProgress((string)"Contacting AccurateRip database...", 0, (dtl - minDTL) / 75.0, null, null);
+ lock (this) {
+ if (_stop)
+ throw new StopException();
+ if (_pause)
+ {
+ ShowProgress("Paused...", 0, 0, null, null);
+ Monitor.Wait(this);
+ }
+ else
+ Monitor.Wait(this, 1000);
+ }
+ }
+ if (_arVerify.AccResult != HttpStatusCode.OK)
+ {
+ _dataTrackLength = null;
+ _accurateRipId = _accurateRipIdActual;
+ }
+ } else
+ _arVerify.ContactAccurateRip(_accurateRipId);
+
+ if (_arVerify.AccResult != HttpStatusCode.OK)
+ {
+ if (!_accurateOffset || _config.noUnverifiedOutput)
+ {
+ if ((_accurateOffset && _config.writeArLogOnConvert) ||
+ (!_accurateOffset && _config.writeArLogOnVerify))
+ {
+ if (!Directory.Exists(dir))
+ Directory.CreateDirectory(dir);
+ StreamWriter sw = new StreamWriter(Path.ChangeExtension(_cuePath, ".accurip"),
+ false, CUESheet.Encoding);
+ GenerateAccurateRipLog(sw);
+ sw.Close();
+ }
+ if (_config.createTOC)
+ {
+ if (!Directory.Exists(dir))
+ Directory.CreateDirectory(dir);
+ WriteTOC(Path.ChangeExtension(_cuePath, ".toc"));
+ }
+ return;
+ }
+ }
+ else if (_accurateOffset)
+ {
+ _writeOffset = 0;
+ WriteAudioFilesPass(dir, style, destPaths, destLengths, htoaToFile, true);
+
+ uint tracksMatch;
+ int bestOffset;
+
+ if (_config.noUnverifiedOutput)
+ {
+ FindBestOffset(_config.encodeWhenConfidence, false, out tracksMatch, out bestOffset);
+ if (tracksMatch * 100 < _config.encodeWhenPercent * TrackCount || (_config.encodeWhenZeroOffset && bestOffset != 0))
+ SkipOutput = true;
+ }
+
+ if (!SkipOutput && _config.fixOffset)
+ {
+ FindBestOffset(_config.fixWhenConfidence, false, out tracksMatch, out bestOffset);
+ if (tracksMatch * 100 >= _config.fixWhenPercent * TrackCount)
+ _writeOffset = bestOffset;
+ }
+ }
+ }
+
+ if (!SkipOutput)
+ {
+ bool verifyOnly = _accurateRip && !_accurateOffset;
+ if (!verifyOnly)
+ {
+ 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 (_accurateRip)
+ {
+ ShowProgress((string)"Generating AccurateRip report...", 0, 0, null, null);
+ if (!_accurateOffset && _config.writeArTagsOnVerify && _writeOffset == 0 && !_isArchive)
+ {
+ uint tracksMatch;
+ int bestOffset;
+ FindBestOffset(1, true, out tracksMatch, out bestOffset);
+
+ if (_hasEmbeddedCUESheet)
+ {
+ IAudioSource audioSource = AudioReadWrite.GetAudioSource(_sourcePaths[0], null);
+ NameValueCollection tags = audioSource.Tags;
+ CleanupTags(tags, "ACCURATERIP");
+ GenerateAccurateRipTags (tags, 0, bestOffset, -1);
+#if !MONO
+ if (audioSource is FLACReader)
+ ((FLACReader)audioSource).UpdateTags (true);
+#endif
+ audioSource.Close();
+ audioSource = null;
+ } else if (_hasTrackFilenames)
+ {
+ for (int iTrack = 0; iTrack < TrackCount; iTrack++)
+ {
+ string src = _sourcePaths[iTrack + (_hasHTOAFilename ? 1 : 0)];
+ IAudioSource audioSource = AudioReadWrite.GetAudioSource(src, null);
+#if !MONO
+ if (audioSource is FLACReader)
+ {
+ NameValueCollection tags = audioSource.Tags;
+ CleanupTags(tags, "ACCURATERIP");
+ GenerateAccurateRipTags (tags, 0, bestOffset, iTrack);
+ ((FLACReader)audioSource).UpdateTags(true);
+ }
+#endif
+ audioSource.Close();
+ audioSource = null;
+ }
+ }
+ }
+
+ if ((_accurateOffset && _config.writeArLogOnConvert) ||
+ (!_accurateOffset && _config.writeArLogOnVerify))
+ {
+ if (!Directory.Exists(dir))
+ Directory.CreateDirectory(dir);
+ StreamWriter sw = new StreamWriter(Path.ChangeExtension(_cuePath, ".accurip"),
+ false, CUESheet.Encoding);
+ GenerateAccurateRipLog(sw);
+ sw.Close();
+ }
+ if (_config.createTOC)
+ {
+ if (!Directory.Exists(dir))
+ Directory.CreateDirectory(dir);
+ WriteTOC(Path.ChangeExtension(_cuePath, ".toc"));
+ }
+ }
+ }
+
+ private void SetTrackTags(IAudioDest audioDest, int iTrack, int bestOffset)
+ {
+ NameValueCollection destTags = new NameValueCollection();
+
+ if (_hasEmbeddedCUESheet)
+ {
+ string trackPrefix = String.Format ("cue_track{0:00}_", iTrack + 1);
+ string[] keys = _albumTags.AllKeys;
+ for (int i = 0; i < keys.Length; i++)
+ {
+ if (keys[i].ToLower().StartsWith(trackPrefix)
+ || !keys[i].ToLower().StartsWith("cue_track"))
+ {
+ string name = keys[i].ToLower().StartsWith(trackPrefix) ?
+ keys[i].Substring(trackPrefix.Length) : keys[i];
+ string[] values = _albumTags.GetValues(keys[i]);
+ for (int j = 0; j < values.Length; j++)
+ destTags.Add(name, values[j]);
+ }
+ }
+ }
+ else if (_hasTrackFilenames)
+ destTags.Add(_tracks[iTrack]._trackTags);
+ else if (_hasSingleFilename)
+ {
+ // TODO?
+ }
+
+ destTags.Remove("CUESHEET");
+ destTags.Remove("TRACKNUMBER");
+ destTags.Remove("LOG");
+ destTags.Remove("LOGFILE");
+ destTags.Remove("EACLOG");
+ CleanupTags(destTags, "ACCURATERIP");
+ CleanupTags(destTags, "REPLAYGAIN");
+
+ if (destTags.Get("TITLE") == null && "" != _tracks[iTrack].Title)
+ destTags.Add("TITLE", _tracks[iTrack].Title);
+ 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 (_accurateOffset && _arVerify.AccResult == HttpStatusCode.OK)
+ GenerateAccurateRipTags(destTags, _writeOffset, bestOffset, iTrack);
+ else
+ destTags.Add("ACCURATERIPID", _accurateRipId);
+ }
+ audioDest.SetTags(destTags);
+ }
+
+ private void SetAlbumTags(IAudioDest audioDest, int bestOffset, bool fWithCUE)
+ {
+ NameValueCollection destTags = new NameValueCollection();
+
+ if (_hasEmbeddedCUESheet || _hasSingleFilename)
+ {
+ destTags.Add(_albumTags);
+ if (!fWithCUE)
+ CleanupTags(destTags, "CUE_TRACK");
+ }
+ else if (_hasTrackFilenames)
+ {
+ for (int iTrack = 0; iTrack < TrackCount; iTrack++)
+ {
+ string[] keys = _tracks[iTrack]._trackTags.AllKeys;
+ for (int i = 0; i < keys.Length; i++)
+ {
+ string singleValue = GetCommonTag (keys[i]);
+ if (singleValue != null)
+ {
+ if (destTags.Get(keys[i]) == null)
+ destTags.Add(keys[i], singleValue);
+ }
+ else if (fWithCUE && keys[i].ToUpper() != "TRACKNUMBER")
+ {
+ string[] values = _tracks[iTrack]._trackTags.GetValues(keys[i]);
+ for (int j = 0; j < values.Length; j++)
+ destTags.Add(String.Format("cue_track{0:00}_{1}", iTrack + 1, keys[i]), values[j]);
+ }
+ }
+ }
+ }
+
+ destTags.Remove("CUESHEET");
+ destTags.Remove("TITLE");
+ destTags.Remove("TRACKNUMBER");
+ CleanupTags(destTags, "ACCURATERIP");
+ CleanupTags(destTags, "REPLAYGAIN");
+
+ if (fWithCUE)
+ {
+ StringWriter sw = new StringWriter();
+ Write(sw, CUEStyle.SingleFileWithCUE);
+ destTags.Add("CUESHEET", sw.ToString());
+ sw.Close();
+ }
+
+ if (_config.embedLog)
+ {
+ destTags.Remove("LOG");
+ destTags.Remove("LOGFILE");
+ destTags.Remove("EACLOG");
+ if (_eacLog != null)
+ destTags.Add("LOG", _eacLog);
+ }
+
+ if (_accurateRipId != null && _config.writeArTagsOnConvert)
+ {
+ if (fWithCUE && _accurateOffset && _arVerify.AccResult == HttpStatusCode.OK)
+ GenerateAccurateRipTags(destTags, _writeOffset, bestOffset, -1);
+ else
+ destTags.Add("ACCURATERIPID", _accurateRipId);
+ }
+ audioDest.SetTags(destTags);
+ }
+
+ public void WriteAudioFilesPass(string dir, CUEStyle style, string[] destPaths, int[] destLengths, bool htoaToFile, bool noOutput)
+ {
+ const int buffLen = 16384;
+ int iTrack, iIndex;
+ int[,] sampleBuffer = new int[buffLen, 2];
+ TrackInfo track;
+ IAudioSource audioSource = null;
+ IAudioDest audioDest = null;
+ bool discardOutput;
+ int iSource = -1;
+ int iDest = -1;
+ uint samplesRemSource = 0;
+
+ if (_writeOffset != 0)
+ {
+ uint absOffset = (uint)Math.Abs(_writeOffset);
+ SourceInfo sourceInfo;
+
+ sourceInfo.Path = null;
+ sourceInfo.Offset = 0;
+ sourceInfo.Length = absOffset;
+
+ if (_writeOffset < 0)
+ {
+ _sources.Insert(0, sourceInfo);
+
+ int last = _sources.Count - 1;
+ while (absOffset >= _sources[last].Length)
+ {
+ absOffset -= _sources[last].Length;
+ _sources.RemoveAt(last--);
+ }
+ sourceInfo = _sources[last];
+ sourceInfo.Length -= absOffset;
+ _sources[last] = sourceInfo;
+ }
+ else
+ {
+ _sources.Add(sourceInfo);
+
+ while (absOffset >= _sources[0].Length)
+ {
+ absOffset -= _sources[0].Length;
+ _sources.RemoveAt(0);
+ }
+ sourceInfo = _sources[0];
+ sourceInfo.Offset += absOffset;
+ sourceInfo.Length -= absOffset;
+ _sources[0] = sourceInfo;
+ }
+
+ _appliedWriteOffset = true;
+ }
+
+ uint tracksMatch;
+ int bestOffset = _writeOffset;
+ if (!noOutput && _accurateRipId != null && _config.writeArTagsOnConvert && _accurateOffset && _arVerify.AccResult == HttpStatusCode.OK)
+ FindBestOffset(1, true, out tracksMatch, out bestOffset);
+
+ if (hdcdDecoder != null)
+ hdcdDecoder.Reset();
+
+ if (style == CUEStyle.SingleFile || style == CUEStyle.SingleFileWithCUE)
+ {
+ iDest++;
+ audioDest = GetAudioDest(destPaths[iDest], destLengths[iDest], noOutput);
+ if (!noOutput)
+ SetAlbumTags(audioDest, bestOffset, style == CUEStyle.SingleFileWithCUE);
+ }
+
+ uint currentOffset = 0, previousOffset = 0;
+ uint trackLength = _toc.Pregap * 588;
+ uint diskLength = _toc.Length * 588, diskOffset = 0;
+
+ if (_accurateRip && noOutput)
+ _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];
+
+ 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 (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) {
+ 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;
+ }
+
+ uint copyCount = (uint) Math.Min(Math.Min(samplesRemIndex, samplesRemSource), buffLen);
+
+ if ( trackLength > 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)
+ {
+ if (_config.wait750FramesForHDCD && diskOffset > 750 * 588 && !hdcdDecoder.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);
+ }
+ }
+ }
+ if (_accurateRip && noOutput)
+ _arVerify.Write(sampleBuffer, 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)
+ {
+ ShowProgress("Paused...", 0, 0, null, null);
+ Monitor.Wait(this);
+ }
+ }
+ }
+ }
+ }
+
+ if (hdcdDecoder != null)
+ hdcdDecoder.AudioDest = null;
+ if (audioSource != null)
+ audioSource.Close();
+ if (audioDest != null)
+ audioDest.Close();
+ }
+
+ public static string CreateDummyCUESheet(string path, string extension)
+ {
+ string[] audioFiles = Directory.GetFiles(path, extension);
+ if (audioFiles.Length < 2)
+ return null;
+ Array.Sort(audioFiles);
+ StringWriter sw = new StringWriter();
+ sw.WriteLine(String.Format("REM COMMENT \"CUETools generated dummy CUE sheet\""));
+ for (int iFile = 0; iFile < audioFiles.Length; iFile++)
+ {
+ sw.WriteLine(String.Format("FILE \"{0}\" WAVE", Path.GetFileName(audioFiles[iFile])));
+ sw.WriteLine(String.Format(" TRACK {0:00} AUDIO", iFile + 1));
+ sw.WriteLine(String.Format(" INDEX 01 00:00:00"));
+ }
+ sw.Close();
+ return sw.ToString();
+ }
+
+ public static string CorrectAudioFilenames(string path, bool always)
+ {
+ StreamReader sr = new StreamReader(path, CUESheet.Encoding);
+ string cue = sr.ReadToEnd();
+ sr.Close();
+ return CorrectAudioFilenames(Path.GetDirectoryName(path), cue, always);
+ }
+
+ public static string CorrectAudioFilenames(string dir, string cue, bool always) {
+ string[] audioExts = new string[] { "*.wav", "*.flac", "*.wv", "*.ape", "*.m4a" };
+ List lines = new List();
+ List filePos = new List();
+ List origFiles = new List();
+ bool foundAll = true;
+ string[] audioFiles = null;
+ string lineStr;
+ CUELine line;
+ int i;
+
+ using (StringReader sr = new StringReader(cue)) {
+ while ((lineStr = sr.ReadLine()) != null) {
+ lines.Add(lineStr);
+ line = new CUELine(lineStr);
+ if ((line.Params.Count == 3) && (line.Params[0].ToUpper() == "FILE")) {
+ string fileType = line.Params[2].ToUpper();
+ if ((fileType != "BINARY") && (fileType != "MOTOROLA")) {
+ filePos.Add(lines.Count - 1);
+ origFiles.Add(line.Params[1]);
+ foundAll &= (LocateFile(dir, line.Params[1], null) != null);
+ }
+ }
+ }
+ sr.Close();
+ }
+
+ if (!foundAll || always)
+ {
+ foundAll = false;
+ for (i = 0; i < audioExts.Length; i++)
+ {
+ foundAll = true;
+ List newFiles = new List();
+ for (int j = 0; j < origFiles.Count; j++)
+ {
+ string newFilename = Path.ChangeExtension(Path.GetFileName(origFiles[j]), audioExts[i].Substring(1));
+ foundAll &= LocateFile(dir, newFilename, null) != null;
+ newFiles.Add (newFilename);
+ }
+ if (foundAll)
+ {
+ audioFiles = newFiles.ToArray();
+ break;
+ }
+ }
+ if (!foundAll)
+ for (i = 0; i < audioExts.Length; i++)
+ {
+ audioFiles = Directory.GetFiles(dir == "" ? "." : dir, audioExts[i]);
+ if (audioFiles.Length == filePos.Count)
+ {
+ Array.Sort(audioFiles);
+ foundAll = true;
+ break;
+ }
+ }
+ if (!foundAll)
+ throw new Exception("Unable to locate the audio files.");
+
+ for (i = 0; i < filePos.Count; i++)
+ lines[filePos[i]] = "FILE \"" + Path.GetFileName(audioFiles[i]) + "\" WAVE";
+ }
+
+ using (StringWriter sw = new StringWriter()) {
+ for (i = 0; i < lines.Count; i++) {
+ sw.WriteLine(lines[i]);
+ }
+ return sw.ToString ();
+ }
+ }
+
+ private int[] CalculateAudioFileLengths(CUEStyle style) {
+ int iTrack, iIndex, iFile;
+ TrackInfo track;
+ int[] fileLengths;
+ bool htoaToFile = (style == CUEStyle.GapsAppended && _config.preserveHTOA && _toc.Pregap != 0);
+ bool discardOutput;
+
+ if (style == CUEStyle.SingleFile || style == CUEStyle.SingleFileWithCUE) {
+ fileLengths = new int[1];
+ iFile = 0;
+ }
+ else {
+ fileLengths = new int[TrackCount + (htoaToFile ? 1 : 0)];
+ iFile = -1;
+ }
+
+ for (iTrack = 0; iTrack < TrackCount; iTrack++) {
+ track = _tracks[iTrack];
+
+ if (style == CUEStyle.GapsPrepended || style == CUEStyle.GapsLeftOut)
+ iFile++;
+
+ for (iIndex = 0; iIndex <= _toc[iTrack+1].LastIndex; iIndex++) {
+ if (style == CUEStyle.GapsAppended && (iIndex == 1 || (iIndex == 0 && iTrack == 0 && htoaToFile)))
+ iFile++;
+
+ if (style == CUEStyle.GapsAppended && iIndex == 0 && iTrack == 0)
+ discardOutput = !htoaToFile;
+ else
+ discardOutput = (style == CUEStyle.GapsLeftOut && iIndex == 0);
+
+ if (!discardOutput)
+ fileLengths[iFile] += (int) _toc[iTrack+1][iIndex].Length * 588;
+ }
+ }
+
+ return fileLengths;
+ }
+
+ public void Stop() {
+ lock (this) {
+ if (_pause)
+ {
+ _pause = false;
+ Monitor.Pulse(this);
+ }
+ _stop = true;
+ }
+ }
+
+ public void Pause()
+ {
+ lock (this)
+ {
+ if (_pause)
+ {
+ _pause = false;
+ Monitor.Pulse(this);
+ } else
+ {
+ _pause = true;
+ }
+ }
+ }
+
+ public int TrackCount {
+ get {
+ return _tracks.Count;
+ }
+ }
+
+ private IAudioDest GetAudioDest(string path, int finalSampleCount, bool noOutput)
+ {
+ if (noOutput)
+ return new DummyWriter(path, (_config.detectHDCD && _config.decodeHDCD) ? 24 : 16, 2, 44100);
+ return AudioReadWrite.GetAudioDest(path, finalSampleCount, _config);
+ }
+
+ private IAudioSource GetAudioSource(int sourceIndex) {
+ SourceInfo sourceInfo = _sources[sourceIndex];
+ IAudioSource audioSource;
+
+ if (sourceInfo.Path == null) {
+ audioSource = new SilenceGenerator(sourceInfo.Offset + sourceInfo.Length);
+ }
+ else {
+#if !MONO
+ if (_isArchive)
+ {
+ RarStream IO = new RarStream(_archivePath, sourceInfo.Path);
+ IO.PasswordRequired += new PasswordRequiredHandler(unrar_PasswordRequired);
+ audioSource = AudioReadWrite.GetAudioSource(sourceInfo.Path, IO);
+ }
+ else
+#endif
+ audioSource = AudioReadWrite.GetAudioSource(sourceInfo.Path, null);
+ }
+
+ if (sourceInfo.Offset != 0)
+ audioSource.Position = sourceInfo.Offset;
+
+ return audioSource;
+ }
+
+ private void WriteLine(TextWriter sw, int level, CUELine line) {
+ WriteLine(sw, level, line.ToString());
+ }
+
+ private void WriteLine(TextWriter sw, int level, string line) {
+ sw.Write(new string(' ', level * 2));
+ sw.WriteLine(line);
+ }
+
+ public List Attributes {
+ get {
+ return _attributes;
+ }
+ }
+
+ public List Tracks {
+ get {
+ return _tracks;
+ }
+ }
+
+ public bool HasHTOAFilename {
+ get {
+ return _hasHTOAFilename;
+ }
+ }
+
+ public string HTOAFilename {
+ get {
+ return _htoaFilename;
+ }
+ set {
+ _htoaFilename = value;
+ }
+ }
+
+ public bool HasTrackFilenames {
+ get {
+ return _hasTrackFilenames;
+ }
+ }
+
+ public List TrackFilenames {
+ get {
+ return _trackFilenames;
+ }
+ }
+
+ public bool HasSingleFilename {
+ get {
+ return _hasSingleFilename;
+ }
+ }
+
+ public string SingleFilename {
+ get {
+ return _singleFilename;
+ }
+ set {
+ _singleFilename = value;
+ }
+ }
+
+ public string Artist {
+ get {
+ CUELine line = General.FindCUELine(_attributes, "PERFORMER");
+ return (line == null) ? String.Empty : line.Params[1];
+ }
+ set {
+ General.SetCUELine(_attributes, "PERFORMER", value, true);
+ }
+ }
+
+ public string Title {
+ get {
+ CUELine line = General.FindCUELine(_attributes, "TITLE");
+ return (line == null) ? String.Empty : line.Params[1];
+ }
+ set {
+ General.SetCUELine(_attributes, "TITLE", value, true);
+ }
+ }
+
+ public int WriteOffset {
+ get {
+ return _writeOffset;
+ }
+ set {
+ if (_appliedWriteOffset) {
+ throw new Exception("Cannot change write offset after audio files have been written.");
+ }
+ _writeOffset = value;
+ }
+ }
+
+ public bool PaddedToFrame {
+ get {
+ return _paddedToFrame;
+ }
+ }
+
+ public string DataTrackLength
+ {
+ get
+ {
+ return CDImageLayout.TimeToString(_dataTrackLength.HasValue ? _dataTrackLength.Value : 0);
+ }
+ set
+ {
+ uint dtl = (uint)CDImageLayout.TimeFromString(value);
+ if (dtl != 0)
+ {
+ _dataTrackLength = dtl;
+ _accurateRipId = _accurateRipIdActual = CalculateAccurateRipId();
+ }
+ }
+ }
+
+ public bool UsePregapForFirstTrackInSingleFile {
+ get {
+ return _usePregapForFirstTrackInSingleFile;
+ }
+ set{
+ _usePregapForFirstTrackInSingleFile = value;
+ }
+ }
+
+ public CUEConfig Config {
+ get {
+ return _config;
+ }
+ }
+
+ public bool AccurateRip {
+ get {
+ return _accurateRip;
+ }
+ set {
+ _accurateRip = value;
+ }
+ }
+
+ public bool AccurateOffset {
+ get {
+ return _accurateOffset;
+ }
+ set {
+ _accurateOffset = value;
+ }
+ }
+
+ CDImageLayout _toc;
+ }
+
+ public class CUELine {
+ private List _params;
+ private List _quoted;
+
+ public CUELine() {
+ _params = new List();
+ _quoted = new List();
+ }
+
+ public CUELine(string line) {
+ int start, end, lineLen;
+ bool isQuoted;
+
+ _params = new List();
+ _quoted = new List();
+
+ start = 0;
+ lineLen = line.Length;
+
+ while (true) {
+ while ((start < lineLen) && (line[start] == ' ')) {
+ start++;
+ }
+ if (start >= lineLen) {
+ break;
+ }
+
+ isQuoted = (line[start] == '"');
+ if (isQuoted) {
+ start++;
+ }
+
+ end = line.IndexOf(isQuoted ? '"' : ' ', start);
+ if (end == -1) {
+ end = lineLen;
+ }
+
+ _params.Add(line.Substring(start, end - start));
+ _quoted.Add(isQuoted);
+
+ start = isQuoted ? end + 1 : end;
+ }
+ }
+
+ public List Params {
+ get {
+ return _params;
+ }
+ }
+
+ public List IsQuoted {
+ get {
+ return _quoted;
+ }
+ }
+
+ public override string ToString() {
+ if (_params.Count != _quoted.Count) {
+ throw new Exception("Parameter and IsQuoted lists must match.");
+ }
+
+ StringBuilder sb = new StringBuilder();
+ int last = _params.Count - 1;
+
+ for (int i = 0; i <= last; i++) {
+ if (_quoted[i]) sb.Append('"');
+ sb.Append(_params[i]);
+ if (_quoted[i]) sb.Append('"');
+ if (i < last) sb.Append(' ');
+ }
+
+ return sb.ToString();
+ }
+ }
+
+ public class TrackInfo {
+ private List _attributes;
+ public NameValueCollection _trackTags;
+
+ public TrackInfo() {
+ _attributes = new List();
+ _trackTags = new NameValueCollection();
+ }
+
+ public List Attributes {
+ get {
+ return _attributes;
+ }
+ }
+
+ public string Artist {
+ get {
+ CUELine line = General.FindCUELine(_attributes, "PERFORMER");
+ return (line == null) ? String.Empty : line.Params[1];
+ }
+ set
+ {
+ General.SetCUELine(_attributes, "PERFORMER", value, true);
+ }
+ }
+
+ public string Title {
+ get {
+ CUELine line = General.FindCUELine(_attributes, "TITLE");
+ return (line == null) ? String.Empty : line.Params[1];
+ }
+ set
+ {
+ General.SetCUELine(_attributes, "TITLE", value, true);
+ }
+ }
+ }
+
+ struct IndexInfo {
+ public int Track;
+ public int Index;
+ public int Time;
+ }
+
+ struct SourceInfo {
+ public string Path;
+ public uint Offset;
+ public uint Length;
+ }
+
+ public class StopException : Exception {
+ public StopException() : base() {
+ }
+ }
+
+ class HttpNotFoundException : Exception {
+ }
+}
\ No newline at end of file
diff --git a/CUETools.Processor/Properties/AssemblyInfo.cs b/CUETools.Processor/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..0bfe4fa
--- /dev/null
+++ b/CUETools.Processor/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("CUEToolsLib")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("CUEToolsLib")]
+[assembly: AssemblyCopyright("Copyright © 2006-2008 Moitah, Gregory S. Chudov")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("d3afb938-f35e-4e5a-b650-7d7a4e885aff")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Revision and Build Numbers
+// by using the '*' as shown below:
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/CUETools.Processor/Settings.cs b/CUETools.Processor/Settings.cs
new file mode 100644
index 0000000..1959fb1
--- /dev/null
+++ b/CUETools.Processor/Settings.cs
@@ -0,0 +1,129 @@
+// ****************************************************************************
+//
+// CUE Tools
+// Copyright (C) 2006-2007 Moitah (moitah@yahoo.com)
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// ****************************************************************************
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace CUETools.Processor
+{
+ static class SettingsShared
+ {
+ public static string GetMyAppDataDir(string appName) {
+ string appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
+ string myAppDataDir = Path.Combine(appDataDir, appName);
+
+ if (Directory.Exists(myAppDataDir) == false) {
+ Directory.CreateDirectory(myAppDataDir);
+ }
+
+ return myAppDataDir;
+ }
+ }
+
+ public class SettingsReader {
+ Dictionary _settings;
+
+ public SettingsReader(string appName, string fileName) {
+ _settings = new Dictionary();
+
+ string path = Path.Combine(SettingsShared.GetMyAppDataDir(appName), fileName);
+ if (!File.Exists(path)) {
+ return;
+ }
+
+ using (StreamReader sr = new StreamReader(path, Encoding.UTF8)) {
+ string line, name, val;
+ int pos;
+
+ while ((line = sr.ReadLine()) != null) {
+ pos = line.IndexOf('=');
+ if (pos != -1) {
+ name = line.Substring(0, pos);
+ val = line.Substring(pos + 1);
+
+ if (!_settings.ContainsKey(name)) {
+ _settings.Add(name, val);
+ }
+ }
+ }
+ }
+ }
+
+ public string Load(string name) {
+ return _settings.ContainsKey(name) ? _settings[name] : null;
+ }
+
+ public bool? LoadBoolean(string name) {
+ string val = Load(name);
+ if (val == "0") return false;
+ if (val == "1") return true;
+ return null;
+ }
+
+ public int? LoadInt32(string name, int? min, int? max) {
+ int val;
+ if (!Int32.TryParse(Load(name), out val)) return null;
+ if (min.HasValue && (val < min.Value)) return null;
+ if (max.HasValue && (val > max.Value)) return null;
+ return val;
+ }
+
+ public uint? LoadUInt32(string name, uint? min, uint? max) {
+ uint val;
+ if (!UInt32.TryParse(Load(name), out val)) return null;
+ if (min.HasValue && (val < min.Value)) return null;
+ if (max.HasValue && (val > max.Value)) return null;
+ return val;
+ }
+ }
+
+ public class SettingsWriter {
+ StreamWriter _sw;
+
+ public SettingsWriter(string appName, string fileName) {
+ string path = Path.Combine(SettingsShared.GetMyAppDataDir(appName), fileName);
+
+ _sw = new StreamWriter(path, false, Encoding.UTF8);
+ }
+
+ public void Save(string name, string value) {
+ _sw.WriteLine(name + "=" + value);
+ }
+
+ public void Save(string name, bool value) {
+ Save(name, value ? "1" : "0");
+ }
+
+ public void Save(string name, int value) {
+ Save(name, value.ToString());
+ }
+
+ public void Save(string name, uint value) {
+ Save(name, value.ToString());
+ }
+
+ public void Close() {
+ _sw.Close();
+ }
+ }
+}
\ No newline at end of file