diff --git a/CUERipper/Program.cs b/CUERipper/Program.cs index 9591669..8140a4b 100644 --- a/CUERipper/Program.cs +++ b/CUERipper/Program.cs @@ -1,11 +1,11 @@ using System; -using System.Collections.Generic; using System.Deployment.Application; using System.Globalization; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms; using CUETools.Processor; +using CUETools.Processor.Settings; namespace CUERipper { diff --git a/CUERipper/frmCUERipper.cs b/CUERipper/frmCUERipper.cs index e098e12..ccc5c2f 100644 --- a/CUERipper/frmCUERipper.cs +++ b/CUERipper/frmCUERipper.cs @@ -1,24 +1,18 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; -using System.Collections.ObjectModel; using System.ComponentModel; -using System.Data; using System.Drawing; using System.IO; using System.Net; -using System.Text; using System.Threading; using System.Windows.Forms; -using System.Configuration; -using System.Drawing.Drawing2D; +using CUEControls; using CUETools.AccurateRip; using CUETools.CTDB; -using CUETools.CDImage; -using CUETools.Codecs; using CUETools.Processor; +using CUETools.Processor.Settings; using CUETools.Ripper; -using CUEControls; using Freedb; namespace CUERipper diff --git a/CUETools.Converter/Program.cs b/CUETools.Converter/Program.cs index a5d9c95..e665efe 100644 --- a/CUETools.Converter/Program.cs +++ b/CUETools.Converter/Program.cs @@ -1,10 +1,8 @@ using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Text; using System.IO; using CUETools.Codecs; using CUETools.Processor; +using CUETools.Processor.Settings; namespace CUETools.Converter { diff --git a/CUETools.Processor/ArchiveFileAbstraction.cs b/CUETools.Processor/ArchiveFileAbstraction.cs new file mode 100644 index 0000000..9a6f1de --- /dev/null +++ b/CUETools.Processor/ArchiveFileAbstraction.cs @@ -0,0 +1,36 @@ +using System.IO; + +namespace CUETools.Processor +{ + public class ArchiveFileAbstraction : TagLib.File.IFileAbstraction + { + private string name; + private CUESheet _cueSheet; + + public string Name + { + get { return name; } + } + + public Stream ReadStream + { + get { return _cueSheet.OpenArchive(Name, true); } + } + + public Stream WriteStream + { + get { return null; } + } + + public ArchiveFileAbstraction(CUESheet cueSheet, string file) + { + name = file; + _cueSheet = cueSheet; + } + + public void CloseStream(Stream stream) + { + stream.Close(); + } + } +} diff --git a/CUETools.Processor/AudioEncoderType.cs b/CUETools.Processor/AudioEncoderType.cs new file mode 100644 index 0000000..08b4472 --- /dev/null +++ b/CUETools.Processor/AudioEncoderType.cs @@ -0,0 +1,13 @@ +namespace CUETools.Processor +{ + public enum AudioEncoderType + { + Lossless, + Hybrid, + Lossy, + /// + /// No Audio + /// + NoAudio, + } +} diff --git a/CUETools.Processor/AudioReadWrite.cs b/CUETools.Processor/AudioReadWrite.cs index ec64b16..584ed8b 100644 --- a/CUETools.Processor/AudioReadWrite.cs +++ b/CUETools.Processor/AudioReadWrite.cs @@ -3,7 +3,6 @@ using System.IO; using CUETools.CDImage; using CUETools.Codecs; using CUETools.Codecs.LossyWAV; -using System.Xml.Serialization; namespace CUETools.Processor { diff --git a/CUETools.Processor/CUEAction.cs b/CUETools.Processor/CUEAction.cs new file mode 100644 index 0000000..372350e --- /dev/null +++ b/CUETools.Processor/CUEAction.cs @@ -0,0 +1,10 @@ +namespace CUETools.Processor +{ + public enum CUEAction + { + Encode = 0, + Verify = 1, + CreateDummyCUE = 2, + CorrectFilenames = 3 + } +} diff --git a/CUETools.Processor/CUEConfig.cs b/CUETools.Processor/CUEConfig.cs new file mode 100644 index 0000000..dce7648 --- /dev/null +++ b/CUETools.Processor/CUEConfig.cs @@ -0,0 +1,651 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using System.Xml; +using System.Xml.Serialization; +using CUETools.Codecs; +using CUETools.Processor.Settings; + +namespace CUETools.Processor +{ + public class CUEConfig + { + public readonly static XmlSerializerNamespaces xmlEmptyNamespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] { XmlQualifiedName.Empty }); + public readonly static XmlWriterSettings xmlEmptySettings = new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }; + + public uint fixOffsetMinimumConfidence; + public uint fixOffsetMinimumTracksPercent; + public uint encodeWhenConfidence; + public uint encodeWhenPercent; + public bool encodeWhenZeroOffset; + public bool writeArTagsOnVerify; + public bool writeArLogOnVerify; + public bool writeArTagsOnEncode; + public bool writeArLogOnConvert; + public bool fixOffset; + public bool noUnverifiedOutput; + public bool autoCorrectFilenames; + public bool preserveHTOA; + public bool keepOriginalFilenames; + public string trackFilenameFormat; + public string singleFilenameFormat; + public bool removeSpecial; + public string specialExceptions; + public bool replaceSpaces; + public bool embedLog; + public bool extractLog; + public bool fillUpCUE; + public bool overwriteCUEData; + public bool filenamesANSISafe; + public bool bruteForceDTL; + public bool createEACLOG; + public bool detectHDCD; + public bool decodeHDCD; + public bool wait750FramesForHDCD; + public bool createM3U; + public bool createCUEFileWhenEmbedded; + public bool truncate4608ExtraSamples; + public int lossyWAVQuality; + public bool decodeHDCDtoLW16; + public bool decodeHDCDto24bit; + public bool oneInstance; + public bool checkForUpdates; + public string language; + public Dictionary formats; + public CUEToolsUDCList encoders; + public Dictionary decoders; + public Dictionary scripts; + public string defaultVerifyScript; + public string defaultEncodeScript; + public bool writeBasicTagsFromCUEData; + public bool copyBasicTags; + public bool copyUnknownTags; + public bool embedAlbumArt; + public bool extractAlbumArt; + public bool arLogToSourceFolder; + public bool arLogVerbose; + public bool fixOffsetToNearest; + public int maxAlbumArtSize; + public CUEStyle gapsHandling; + public bool separateDecodingThread; + + public CUEConfigAdvanced advanced { get; private set; } + public bool CopyAlbumArt { get; set; } + public string ArLogFilenameFormat { get; set; } + public string AlArtFilenameFormat { get; set; } + public CUEToolsUDCList Encoders + { + get { return encoders; } + } + + public CUEConfig() + { + fixOffsetMinimumConfidence = 2; + fixOffsetMinimumTracksPercent = 51; + encodeWhenConfidence = 2; + encodeWhenPercent = 100; + encodeWhenZeroOffset = false; + fixOffset = false; + noUnverifiedOutput = false; + writeArTagsOnEncode = true; + writeArLogOnConvert = true; + writeArTagsOnVerify = false; + writeArLogOnVerify = false; + + autoCorrectFilenames = true; + preserveHTOA = true; + keepOriginalFilenames = false; + trackFilenameFormat = "%tracknumber%. %title%"; + singleFilenameFormat = "%filename%"; + removeSpecial = false; + specialExceptions = "-()"; + replaceSpaces = false; + embedLog = true; + extractLog = true; + fillUpCUE = true; + overwriteCUEData = false; + filenamesANSISafe = true; + bruteForceDTL = false; + createEACLOG = true; + detectHDCD = true; + wait750FramesForHDCD = true; + decodeHDCD = false; + createM3U = false; + createCUEFileWhenEmbedded = true; + truncate4608ExtraSamples = true; + lossyWAVQuality = 5; + decodeHDCDtoLW16 = false; + decodeHDCDto24bit = true; + + oneInstance = true; + checkForUpdates = true; + + writeBasicTagsFromCUEData = true; + copyBasicTags = true; + copyUnknownTags = true; + CopyAlbumArt = true; + embedAlbumArt = true; + extractAlbumArt = true; + maxAlbumArtSize = 300; + + arLogToSourceFolder = false; + arLogVerbose = true; + fixOffsetToNearest = true; + ArLogFilenameFormat = "%filename%.accurip"; + AlArtFilenameFormat = "folder.jpg"; + + separateDecodingThread = true; + + gapsHandling = CUEStyle.GapsAppended; + + advanced = new CUEConfigAdvanced(); + + language = Thread.CurrentThread.CurrentUICulture.Name; + + encoders = new CUEToolsUDCList(); + foreach (Type type in CUEProcessorPlugins.encs) + foreach (AudioEncoderClass enc in Attribute.GetCustomAttributes(type, typeof(AudioEncoderClass))) + encoders.Add(new CUEToolsUDC(enc, type)); + decoders = new Dictionary(); + foreach (Type type in CUEProcessorPlugins.decs) + { + AudioDecoderClass dec = Attribute.GetCustomAttribute(type, typeof(AudioDecoderClass)) as AudioDecoderClass; + decoders.Add(dec.DecoderName, new CUEToolsUDC(dec, type)); + } + if (Type.GetType("Mono.Runtime", false) == null) + { + encoders.Add(new CUEToolsUDC("flake", "flac", true, "0 1 2 3 4 5 6 7 8 9 10 11 12", "8", "flake.exe", "-%M - -o %O -p %P")); + encoders.Add(new CUEToolsUDC("takc", "tak", true, "0 1 2 2e 2m 3 3e 3m 4 4e 4m", "2", "takc.exe", "-e -p%M -overwrite - %O")); + encoders.Add(new CUEToolsUDC("ffmpeg alac", "m4a", true, "", "", "ffmpeg.exe", "-i - -f ipod -acodec alac -y %O")); + encoders.Add(new CUEToolsUDC("lame vbr", "mp3", false, "V9 V8 V7 V6 V5 V4 V3 V2 V1 V0", "V2", "lame.exe", "--vbr-new -%M - %O")); + encoders.Add(new CUEToolsUDC("lame cbr", "mp3", false, "96 128 192 256 320", "256", "lame.exe", "-m s -q 0 -b %M --noreplaygain - %O")); + encoders.Add(new CUEToolsUDC("oggenc", "ogg", false, "-1 -0.5 0 0.5 1 1.5 2 2.5 3 3.5 4 4.5 5 5.5 6 6.5 7 7.5 8", "3", "oggenc.exe", "-q %M - -o %O")); + encoders.Add(new CUEToolsUDC("nero aac", "m4a", false, "0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9", "0.4", "neroAacEnc.exe", "-q %M -if - -of %O")); + encoders.Add(new CUEToolsUDC("qaac tvbr", "m4a", false, "10 20 30 40 50 60 70 80 90 100 110 127", "80", "qaac.exe", "-s -V %M -q 2 - -o %O")); + + decoders.Add("takc", new CUEToolsUDC("takc", "tak", true, "", "", "takc.exe", "-d %I -")); + decoders.Add("ffmpeg alac", new CUEToolsUDC("ffmpeg alac", "m4a", true, "", "", "ffmpeg.exe", "%I -f wav -")); + } + else + { + // !!! + } + + formats = new Dictionary(); + formats.Add("flac", new CUEToolsFormat("flac", CUEToolsTagger.TagLibSharp, true, false, true, true, true, encoders.GetDefault("flac", true), null, GetDefaultDecoder("flac"))); + formats.Add("wv", new CUEToolsFormat("wv", CUEToolsTagger.TagLibSharp, true, false, true, true, true, encoders.GetDefault("wv", true), null, GetDefaultDecoder("wv"))); + formats.Add("ape", new CUEToolsFormat("ape", CUEToolsTagger.TagLibSharp, true, false, false, true, true, encoders.GetDefault("ape", true), null, GetDefaultDecoder("ape"))); + formats.Add("tta", new CUEToolsFormat("tta", CUEToolsTagger.APEv2, true, false, false, false, true, encoders.GetDefault("tta", true), null, GetDefaultDecoder("tta"))); + formats.Add("wav", new CUEToolsFormat("wav", CUEToolsTagger.TagLibSharp, true, false, true, false, true, encoders.GetDefault("wav", true), null, GetDefaultDecoder("wav"))); + formats.Add("m4a", new CUEToolsFormat("m4a", CUEToolsTagger.TagLibSharp, true, true, false, false, true, encoders.GetDefault("m4a", true), encoders.GetDefault("m4a", false), GetDefaultDecoder("m4a"))); + formats.Add("tak", new CUEToolsFormat("tak", CUEToolsTagger.APEv2, true, false, true, true, true, encoders.GetDefault("tak", true), null, "takc")); + formats.Add("mp3", new CUEToolsFormat("mp3", CUEToolsTagger.TagLibSharp, false, true, false, false, true, null, encoders.GetDefault("mp3", false), null)); + formats.Add("ogg", new CUEToolsFormat("ogg", CUEToolsTagger.TagLibSharp, false, true, false, false, true, null, encoders.GetDefault("ogg", false), null)); + + scripts = new Dictionary(); + scripts.Add("default", new CUEToolsScript("default", true, + new CUEAction[] { CUEAction.Verify, CUEAction.Encode }, + "return processor.Go();")); + scripts.Add("only if found", new CUEToolsScript("only if found", true, + new CUEAction[] { CUEAction.Verify }, +@"if (processor.ArVerify.AccResult != HttpStatusCode.OK) + return processor.WriteReport(); +return processor.Go();")); + scripts.Add("fix offset", new CUEToolsScript("fix offset", true, + new CUEAction[] { CUEAction.Encode }, +@"if (processor.ArVerify.AccResult != HttpStatusCode.OK) + return processor.WriteReport(); +processor.WriteOffset = 0; +processor.Action = CUEAction.Verify; +string status = processor.Go(); +uint tracksMatch; +int bestOffset; +processor.FindBestOffset(processor.Config.fixOffsetMinimumConfidence, !processor.Config.fixOffsetToNearest, out tracksMatch, out bestOffset); +if (tracksMatch * 100 < processor.Config.fixOffsetMinimumTracksPercent * processor.TrackCount) + return status; +processor.WriteOffset = bestOffset; +processor.Action = CUEAction.Encode; +//MessageBox.Show(null, processor.AccurateRipLog, " + "\"Done\"" + @"MessageBoxButtons.OK, MessageBoxIcon.Information); +return processor.Go(); +")); + scripts.Add("encode if verified", new CUEToolsScript("encode if verified", true, + new CUEAction[] { CUEAction.Encode }, +@"if (processor.ArVerify.AccResult != HttpStatusCode.OK) + return processor.WriteReport(); +processor.Action = CUEAction.Verify; +string status = processor.Go(); +uint tracksMatch; +int bestOffset; +processor.FindBestOffset(processor.Config.encodeWhenConfidence, false, out tracksMatch, out bestOffset); +if (tracksMatch * 100 < processor.Config.encodeWhenPercent * processor.TrackCount || (processor.Config.encodeWhenZeroOffset && bestOffset != 0)) + return status; +processor.Action = CUEAction.Encode; +return processor.Go(); +")); + scripts.Add("repair", new CUEToolsScript("repair", true, + new CUEAction[] { CUEAction.Encode }, +@" +processor.UseCUEToolsDB(); +processor.Action = CUEAction.Verify; +if (processor.CTDB.DBStatus != null) + return CTDB.DBStatus; +processor.Go(); +processor.CTDB.DoVerify(); +if (!processor.CTDB.Verify.HasErrors) + return ""nothing to fix""; +if (!processor.CTDB.Verify.CanRecover) + return ""cannot fix""; +processor._useCUEToolsDBFix = true; +processor.Action = CUEAction.Encode; +return processor.Go(); +")); + defaultVerifyScript = "default"; + defaultEncodeScript = "default"; + } + + public void Save(SettingsWriter sw) + { + sw.Save("Version", 203); + sw.Save("ArFixWhenConfidence", fixOffsetMinimumConfidence); + sw.Save("ArFixWhenPercent", fixOffsetMinimumTracksPercent); + sw.Save("ArEncodeWhenConfidence", encodeWhenConfidence); + sw.Save("ArEncodeWhenPercent", encodeWhenPercent); + sw.Save("ArEncodeWhenZeroOffset", encodeWhenZeroOffset); + sw.Save("ArNoUnverifiedOutput", noUnverifiedOutput); + sw.Save("ArFixOffset", fixOffset); + sw.Save("ArWriteCRC", writeArTagsOnEncode); + sw.Save("ArWriteLog", writeArLogOnConvert); + sw.Save("ArWriteTagsOnVerify", writeArTagsOnVerify); + sw.Save("ArWriteLogOnVerify", writeArLogOnVerify); + + sw.Save("PreserveHTOA", preserveHTOA); + sw.Save("AutoCorrectFilenames", autoCorrectFilenames); + 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("ExtractLog", extractLog); + sw.Save("FillUpCUE", fillUpCUE); + sw.Save("OverwriteCUEData", overwriteCUEData); + sw.Save("FilenamesANSISafe", filenamesANSISafe); + if (bruteForceDTL) sw.Save("BruteForceDTL", bruteForceDTL); + sw.Save("CreateEACLOG", createEACLOG); + sw.Save("DetectHDCD", detectHDCD); + sw.Save("Wait750FramesForHDCD", wait750FramesForHDCD); + sw.Save("DecodeHDCD", decodeHDCD); + sw.Save("CreateM3U", createM3U); + sw.Save("CreateCUEFileWhenEmbedded", createCUEFileWhenEmbedded); + sw.Save("Truncate4608ExtraSamples", truncate4608ExtraSamples); + sw.Save("LossyWAVQuality", lossyWAVQuality); + sw.Save("DecodeHDCDToLossyWAV16", decodeHDCDtoLW16); + sw.Save("DecodeHDCDTo24bit", decodeHDCDto24bit); + sw.Save("OneInstance", oneInstance); + sw.Save("CheckForUpdates", checkForUpdates); + sw.Save("Language", language); + + sw.Save("SeparateDecodingThread", separateDecodingThread); + + sw.Save("WriteBasicTagsFromCUEData", writeBasicTagsFromCUEData); + sw.Save("CopyBasicTags", copyBasicTags); + sw.Save("CopyUnknownTags", copyUnknownTags); + sw.Save("CopyAlbumArt", CopyAlbumArt); + sw.Save("EmbedAlbumArt", embedAlbumArt); + sw.Save("ExtractAlbumArt", extractAlbumArt); + sw.Save("MaxAlbumArtSize", maxAlbumArtSize); + + sw.Save("ArLogToSourceFolder", arLogToSourceFolder); + sw.Save("ArLogVerbose", arLogVerbose); + sw.Save("FixOffsetToNearest", fixOffsetToNearest); + + sw.Save("ArLogFilenameFormat", ArLogFilenameFormat); + sw.Save("AlArtFilenameFormat", AlArtFilenameFormat); + + using (TextWriter tw = new StringWriter()) + using (XmlWriter xw = XmlTextWriter.Create(tw, xmlEmptySettings)) + { + CUEConfigAdvanced.serializer.Serialize(xw, advanced, xmlEmptyNamespaces); + sw.SaveText("Advanced", tw.ToString()); + } + + int nEncoders = 0; + foreach (CUEToolsUDC encoder in encoders) + { + sw.Save(string.Format("ExternalEncoder{0}Name", nEncoders), encoder.name); + sw.Save(string.Format("ExternalEncoder{0}Modes", nEncoders), encoder.supported_modes); + sw.Save(string.Format("ExternalEncoder{0}Mode", nEncoders), encoder.default_mode); + if (encoder.path != null) + { + sw.Save(string.Format("ExternalEncoder{0}Extension", nEncoders), encoder.extension); + sw.Save(string.Format("ExternalEncoder{0}Path", nEncoders), encoder.path); + sw.Save(string.Format("ExternalEncoder{0}Lossless", nEncoders), encoder.lossless); + sw.Save(string.Format("ExternalEncoder{0}Parameters", nEncoders), encoder.parameters); + } + else + { + if (encoder.settingsSerializer != null) + { + using (TextWriter tw = new StringWriter()) + using (XmlWriter xw = XmlTextWriter.Create(tw, xmlEmptySettings)) + { + encoder.settingsSerializer.Serialize(xw, encoder.settings, xmlEmptyNamespaces); + sw.SaveText(string.Format("ExternalEncoder{0}Parameters", nEncoders), tw.ToString()); + } + } + } + nEncoders++; + } + sw.Save("ExternalEncoders", nEncoders); + + int nDecoders = 0; + foreach (KeyValuePair decoder in decoders) + if (decoder.Value.path != null) + { + sw.Save(string.Format("ExternalDecoder{0}Name", nDecoders), decoder.Key); + sw.Save(string.Format("ExternalDecoder{0}Extension", nDecoders), decoder.Value.extension); + sw.Save(string.Format("ExternalDecoder{0}Path", nDecoders), decoder.Value.path); + sw.Save(string.Format("ExternalDecoder{0}Parameters", nDecoders), decoder.Value.parameters); + nDecoders++; + } + sw.Save("ExternalDecoders", nDecoders); + + int nFormats = 0; + foreach (KeyValuePair format in formats) + { + sw.Save(string.Format("CustomFormat{0}Name", nFormats), format.Key); + sw.Save(string.Format("CustomFormat{0}EncoderLossless", nFormats), format.Value.encoderLossless == null ? "" : format.Value.encoderLossless.Name); + sw.Save(string.Format("CustomFormat{0}EncoderLossy", nFormats), format.Value.encoderLossy == null ? "" : format.Value.encoderLossy.Name); + sw.Save(string.Format("CustomFormat{0}Decoder", nFormats), format.Value.decoder); + sw.Save(string.Format("CustomFormat{0}Tagger", nFormats), (int)format.Value.tagger); + sw.Save(string.Format("CustomFormat{0}AllowLossless", nFormats), format.Value.allowLossless); + sw.Save(string.Format("CustomFormat{0}AllowLossy", nFormats), format.Value.allowLossy); + sw.Save(string.Format("CustomFormat{0}AllowLossyWAV", nFormats), format.Value.allowLossyWAV); + sw.Save(string.Format("CustomFormat{0}AllowEmbed", nFormats), format.Value.allowEmbed); + nFormats++; + } + sw.Save("CustomFormats", nFormats); + + int nScripts = 0; + foreach (KeyValuePair script in scripts) + { + sw.Save(string.Format("CustomScript{0}Name", nScripts), script.Key); + sw.SaveText(string.Format("CustomScript{0}Code", nScripts), script.Value.code); + int nCondition = 0; + foreach (CUEAction action in script.Value.conditions) + sw.Save(string.Format("CustomScript{0}Condition{1}", nScripts, nCondition++), (int)action); + sw.Save(string.Format("CustomScript{0}Conditions", nScripts), nCondition); + nScripts++; + } + sw.Save("CustomScripts", nScripts); + sw.Save("DefaultVerifyScript", defaultVerifyScript); + sw.Save("DefaultVerifyAndConvertScript", defaultEncodeScript); + + sw.Save("GapsHandling", (int)gapsHandling); + } + + public void Load(SettingsReader sr) + { + int version = sr.LoadInt32("Version", null, null) ?? 202; + + fixOffsetMinimumConfidence = sr.LoadUInt32("ArFixWhenConfidence", 1, 1000) ?? 2; + fixOffsetMinimumTracksPercent = 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; + writeArTagsOnEncode = sr.LoadBoolean("ArWriteCRC") ?? true; + writeArLogOnConvert = sr.LoadBoolean("ArWriteLog") ?? true; + writeArTagsOnVerify = sr.LoadBoolean("ArWriteTagsOnVerify") ?? false; + writeArLogOnVerify = sr.LoadBoolean("ArWriteLogOnVerify") ?? false; + + preserveHTOA = sr.LoadBoolean("PreserveHTOA") ?? true; + autoCorrectFilenames = sr.LoadBoolean("AutoCorrectFilenames") ?? true; + keepOriginalFilenames = sr.LoadBoolean("KeepOriginalFilenames") ?? false; + singleFilenameFormat = sr.Load("SingleFilenameFormat") ?? singleFilenameFormat; + trackFilenameFormat = sr.Load("TrackFilenameFormat") ?? trackFilenameFormat; + removeSpecial = sr.LoadBoolean("RemoveSpecialCharacters") ?? false; + specialExceptions = sr.Load("SpecialCharactersExceptions") ?? "-()"; + replaceSpaces = sr.LoadBoolean("ReplaceSpaces") ?? false; + embedLog = sr.LoadBoolean("EmbedLog") ?? true; + extractLog = sr.LoadBoolean("ExtractLog") ?? true; + fillUpCUE = sr.LoadBoolean("FillUpCUE") ?? true; + overwriteCUEData = sr.LoadBoolean("OverwriteCUEData") ?? false; + filenamesANSISafe = sr.LoadBoolean("FilenamesANSISafe") ?? true; + bruteForceDTL = sr.LoadBoolean("BruteForceDTL") ?? false; + createEACLOG = sr.LoadBoolean("CreateEACLOG") ?? createEACLOG; + detectHDCD = sr.LoadBoolean("DetectHDCD") ?? true; + wait750FramesForHDCD = sr.LoadBoolean("Wait750FramesForHDCD") ?? true; + decodeHDCD = sr.LoadBoolean("DecodeHDCD") ?? false; + createM3U = sr.LoadBoolean("CreateM3U") ?? false; + createCUEFileWhenEmbedded = sr.LoadBoolean("CreateCUEFileWhenEmbedded") ?? true; + truncate4608ExtraSamples = sr.LoadBoolean("Truncate4608ExtraSamples") ?? true; + lossyWAVQuality = sr.LoadInt32("LossyWAVQuality", 0, 10) ?? 5; + decodeHDCDtoLW16 = sr.LoadBoolean("DecodeHDCDToLossyWAV16") ?? false; + decodeHDCDto24bit = sr.LoadBoolean("DecodeHDCDTo24bit") ?? true; + + oneInstance = sr.LoadBoolean("OneInstance") ?? true; + checkForUpdates = sr.LoadBoolean("CheckForUpdates") ?? true; + + writeBasicTagsFromCUEData = sr.LoadBoolean("WriteBasicTagsFromCUEData") ?? true; + copyBasicTags = sr.LoadBoolean("CopyBasicTags") ?? true; + copyUnknownTags = sr.LoadBoolean("CopyUnknownTags") ?? true; + CopyAlbumArt = sr.LoadBoolean("CopyAlbumArt") ?? true; + embedAlbumArt = sr.LoadBoolean("EmbedAlbumArt") ?? true; + extractAlbumArt = sr.LoadBoolean("ExtractAlbumArt") ?? true; + maxAlbumArtSize = sr.LoadInt32("MaxAlbumArtSize", 100, 10000) ?? maxAlbumArtSize; + + arLogToSourceFolder = sr.LoadBoolean("ArLogToSourceFolder") ?? arLogToSourceFolder; + arLogVerbose = sr.LoadBoolean("ArLogVerbose") ?? arLogVerbose; + fixOffsetToNearest = sr.LoadBoolean("FixOffsetToNearest") ?? fixOffsetToNearest; + ArLogFilenameFormat = sr.Load("ArLogFilenameFormat") ?? ArLogFilenameFormat; + AlArtFilenameFormat = sr.Load("AlArtFilenameFormat") ?? AlArtFilenameFormat; + + separateDecodingThread = sr.LoadBoolean("SeparateDecodingThread") ?? separateDecodingThread; + + try + { + using (TextReader reader = new StringReader(sr.Load("Advanced"))) + advanced = CUEConfigAdvanced.serializer.Deserialize(reader) as CUEConfigAdvanced; + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine(ex.Message); + } + + int totalEncoders = sr.LoadInt32("ExternalEncoders", 0, null) ?? 0; + for (int nEncoders = 0; nEncoders < totalEncoders; nEncoders++) + { + string name = sr.Load(string.Format("ExternalEncoder{0}Name", nEncoders)); + string extension = sr.Load(string.Format("ExternalEncoder{0}Extension", nEncoders)); + string path = sr.Load(string.Format("ExternalEncoder{0}Path", nEncoders)); + string parameters = sr.Load(string.Format("ExternalEncoder{0}Parameters", nEncoders)); + bool lossless = sr.LoadBoolean(string.Format("ExternalEncoder{0}Lossless", nEncoders)) ?? true; + string supported_modes = sr.Load(string.Format("ExternalEncoder{0}Modes", nEncoders)) ?? ""; + string default_mode = sr.Load(string.Format("ExternalEncoder{0}Mode", nEncoders)) ?? ""; + CUEToolsUDC encoder; + if (name == null) continue; + if (!encoders.TryGetValue(name, out encoder)) + { + if (path == null || parameters == null || extension == null) continue; + encoders.Add(new CUEToolsUDC(name, extension, lossless, supported_modes, default_mode, path, parameters)); + } + else if (version == 203) + { + if (encoder.path != null) + { + if (path == null || parameters == null || extension == null) continue; + encoder.extension = extension; + encoder.path = path; + encoder.lossless = lossless; + encoder.parameters = parameters; + } + else + { + if (encoder.settingsSerializer != null && parameters != "") + try + { + using (TextReader reader = new StringReader(parameters)) + encoder.settings = encoder.settingsSerializer.Deserialize(reader); + } + catch + { + } + } + encoder.supported_modes = supported_modes; + encoder.default_mode = default_mode; + } + } + + int totalDecoders = sr.LoadInt32("ExternalDecoders", 0, null) ?? 0; + for (int nDecoders = 0; nDecoders < totalDecoders; nDecoders++) + { + string name = sr.Load(string.Format("ExternalDecoder{0}Name", nDecoders)); + string extension = sr.Load(string.Format("ExternalDecoder{0}Extension", nDecoders)); + string path = sr.Load(string.Format("ExternalDecoder{0}Path", nDecoders)); + string parameters = sr.Load(string.Format("ExternalDecoder{0}Parameters", nDecoders)); + CUEToolsUDC decoder; + if (!decoders.TryGetValue(name, out decoder)) + decoders.Add(name, new CUEToolsUDC(name, extension, true, "", "", path, parameters)); + else + { + decoder.extension = extension; + decoder.path = path; + decoder.parameters = parameters; + } + } + + int totalFormats = sr.LoadInt32("CustomFormats", 0, null) ?? 0; + for (int nFormats = 0; nFormats < totalFormats; nFormats++) + { + string extension = sr.Load(string.Format("CustomFormat{0}Name", nFormats)); + string encoderLossless = sr.Load(string.Format("CustomFormat{0}EncoderLossless", nFormats)) ?? ""; + string encoderLossy = sr.Load(string.Format("CustomFormat{0}EncoderLossy", nFormats)) ?? ""; + string decoder = sr.Load(string.Format("CustomFormat{0}Decoder", nFormats)); + CUEToolsTagger tagger = (CUEToolsTagger)(sr.LoadInt32(string.Format("CustomFormat{0}Tagger", nFormats), 0, 2) ?? 0); + bool allowLossless = sr.LoadBoolean(string.Format("CustomFormat{0}AllowLossless", nFormats)) ?? false; + bool allowLossy = sr.LoadBoolean(string.Format("CustomFormat{0}AllowLossy", nFormats)) ?? false; + bool allowLossyWav = sr.LoadBoolean(string.Format("CustomFormat{0}AllowLossyWAV", nFormats)) ?? false; + bool allowEmbed = sr.LoadBoolean(string.Format("CustomFormat{0}AllowEmbed", nFormats)) ?? false; + CUEToolsFormat format; + CUEToolsUDC udcLossless, udcLossy; + if (encoderLossless == "" || !encoders.TryGetValue(encoderLossless, out udcLossless)) + udcLossless = null; + if (encoderLossy == "" || !encoders.TryGetValue(encoderLossy, out udcLossy)) + udcLossy = null; + if (!formats.TryGetValue(extension, out format)) + formats.Add(extension, new CUEToolsFormat(extension, tagger, allowLossless, allowLossy, allowLossyWav, allowEmbed, false, udcLossless, udcLossy, decoder)); + else + { + format.encoderLossless = udcLossless; + format.encoderLossy = udcLossy; + format.decoder = decoder; + if (!format.builtin) + { + format.tagger = tagger; + format.allowLossless = allowLossless; + format.allowLossy = allowLossy; + format.allowLossyWAV = allowLossyWav; + format.allowEmbed = allowEmbed; + } + } + } + + int totalScripts = sr.LoadInt32("CustomScripts", 0, null) ?? 0; + for (int nScripts = 0; nScripts < totalScripts; nScripts++) + { + string name = sr.Load(string.Format("CustomScript{0}Name", nScripts)); + string code = sr.Load(string.Format("CustomScript{0}Code", nScripts)); + List conditions = new List(); + int totalConditions = sr.LoadInt32(string.Format("CustomScript{0}Conditions", nScripts), 0, null) ?? 0; + for (int nCondition = 0; nCondition < totalConditions; nCondition++) + conditions.Add((CUEAction)sr.LoadInt32(string.Format("CustomScript{0}Condition{1}", nScripts, nCondition), 0, null)); + CUEToolsScript script; + if (!scripts.TryGetValue(name, out script)) + { + if (name != "submit") + scripts.Add(name, new CUEToolsScript(name, false, conditions, code)); + } + else + { + if (!script.builtin) + { + script.code = code; + script.conditions = conditions; + } + } + } + + defaultVerifyScript = sr.Load("DefaultVerifyScript") ?? "default"; + defaultEncodeScript = sr.Load("DefaultVerifyAndConvertScript") ?? "default"; + + gapsHandling = (CUEStyle?)sr.LoadInt32("GapsHandling", null, null) ?? gapsHandling; + + language = sr.Load("Language") ?? Thread.CurrentThread.CurrentUICulture.Name; + + if (ArLogFilenameFormat.Contains("%F")) + ArLogFilenameFormat = "%filename%.accurip"; + if (singleFilenameFormat.Contains("%F")) + singleFilenameFormat = "%filename%"; + if (trackFilenameFormat.Contains("%N")) + trackFilenameFormat = "%tracknumber%. %title%"; + } + + public string GetDefaultDecoder(string extension) + { + CUEToolsUDC result = null; + foreach (KeyValuePair decoder in decoders) + if (decoder.Value.Extension == extension && (result == null || result.priority < decoder.Value.priority)) + result = decoder.Value; + return result == null ? null : result.Name; + } + + public IWebProxy GetProxy() + { + IWebProxy proxy = null; + switch (advanced.UseProxyMode) + { + case CUEConfigAdvanced.ProxyMode.System: + proxy = WebRequest.GetSystemWebProxy(); + break; + case CUEConfigAdvanced.ProxyMode.Custom: + proxy = new WebProxy(advanced.ProxyServer, advanced.ProxyPort); + if (advanced.ProxyUser != "") + proxy.Credentials = new NetworkCredential(advanced.ProxyUser, advanced.ProxyPassword); + break; + } + return proxy; + } + + 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(); + } + } +} diff --git a/CUETools.Processor/CUEConfigAdvanced.cs b/CUETools.Processor/CUEConfigAdvanced.cs new file mode 100644 index 0000000..de99c76 --- /dev/null +++ b/CUETools.Processor/CUEConfigAdvanced.cs @@ -0,0 +1,72 @@ +using System; +using System.ComponentModel; +using System.Xml.Serialization; + +namespace CUETools.Processor +{ + [Serializable] + public class CUEConfigAdvanced + { + public enum ProxyMode + { + None, + System, + Custom + } + + public CUEConfigAdvanced() + { + // Iterate through each property and call ResetValue() + foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(this)) + { + property.ResetValue(this); + } + } + + internal static XmlSerializer serializer = new XmlSerializer(typeof(CUEConfigAdvanced)); + [DefaultValue("i"), Category("Freedb"), DisplayName("Email user")] + public string FreedbUser { get; set; } + + [DefaultValue("wont.tell"), Category("Freedb"), DisplayName("Email domain")] + public string FreedbDomain { get; set; } + + [DefaultValue(ProxyMode.System), Category("Proxy"), DisplayName("Proxy mode")] + public ProxyMode UseProxyMode { get; set; } + + [DefaultValue("127.0.0.1"), Category("Proxy"), DisplayName("Proxy server host")] + public string ProxyServer { get; set; } + + [DefaultValue(8080), Category("Proxy"), DisplayName("Proxy server port")] + public int ProxyPort { get; set; } + + [DefaultValue(""), Category("Proxy"), DisplayName("Proxy auth user")] + public string ProxyUser { get; set; } + + [DefaultValue(""), Category("Proxy"), DisplayName("Proxy auth password")] + public string ProxyPassword { get; set; } + + [DefaultValue(true), Category("Cache"), DisplayName("Cache metadata")] + public bool CacheMetadata { get; set; } + + [DefaultValue(new string[] { "folder.jpg", "cover.jpg", "albumart.jpg", "thumbnail.jpg", "albumartlarge.jpg", "front.jpg", "%album%.jpg" })] + [Category("Cover Art"), DisplayName("Cover Art Files")] + public string[] CoverArtFiles { get; set; } + + [DefaultValue(true)] + [Category("Cover Art"), DisplayName("Cover Art Extended Search")] + public bool CoverArtSearchSubdirs { get; set; } + + [DefaultValue(false)] + [DisplayName("Create TOC files")] + public bool CreateTOC { get; set; } + + [DefaultValue(true), Category("CTDB"), DisplayName("Submit to CTDB")] + public bool CTDBSubmit { get; set; } + + [DefaultValue(true), Category("CTDB"), DisplayName("Ask before submitting")] + public bool CTDBAsk { get; set; } + + [DefaultValue("db.cuetools.net"), Category("CTDB"), DisplayName("CTDB Server")] + public string CTDBServer { get; set; } + } +} diff --git a/CUETools.Processor/CUELine.cs b/CUETools.Processor/CUELine.cs new file mode 100644 index 0000000..de2801b --- /dev/null +++ b/CUETools.Processor/CUELine.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CUETools.Processor +{ + 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] || _params[i].Contains(" ")) sb.Append('"'); + sb.Append(_params[i].Replace('"', '\'')); + if (_quoted[i] || _params[i].Contains(" ")) sb.Append('"'); + if (i < last) sb.Append(' '); + } + + return sb.ToString(); + } + } +} diff --git a/CUETools.Processor/CUEMetadata.cs b/CUETools.Processor/CUEMetadata.cs index a1ec19f..82c138d 100644 --- a/CUETools.Processor/CUEMetadata.cs +++ b/CUETools.Processor/CUEMetadata.cs @@ -4,7 +4,7 @@ using System.ComponentModel; using System.IO; using System.Text; using System.Xml.Serialization; -using CUETools.CDImage; +using CUETools.Processor.Settings; namespace CUETools.Processor { @@ -330,48 +330,4 @@ namespace CUETools.Processor return isVarious; } } - - public class CUETrackMetadata - { - public CUETrackMetadata() - { - Artist = ""; - Title = ""; - ISRC = ""; - } - [DefaultValue("")] - public string Artist { get; set; } - [DefaultValue("")] - public string Title { get; set; } - [DefaultValue("")] - public string ISRC { get; set; } - } - - public class CUEMetadataEntry - { - public CUEMetadata metadata { get; set; } - public CDImageLayout TOC { get; set; } - public string ImageKey { get; set; } - - public CUEMetadataEntry(CUEMetadata metadata, CDImageLayout TOC, string key) - { - this.metadata = new CUEMetadata(metadata); - this.TOC = TOC; - this.ImageKey = key; - } - - public CUEMetadataEntry(CDImageLayout TOC, string key) - : this(new CUEMetadata(TOC.TOCID, (int)TOC.AudioTracks), TOC, key) - { - } - - public override string ToString() - { - return string.Format("{0}{1} - {2}{3}{4}", metadata.Year != "" ? metadata.Year + ": " : "", - metadata.Artist == "" ? "Unknown Artist" : metadata.Artist, - metadata.Title == "" ? "Unknown Title" : metadata.Title, - metadata.DiscNumberAndName == "" ? "" : " (disc " + metadata.DiscNumberAndName + ")", - metadata.ReleaseDateAndLabel == "" ? "" : " (" + metadata.ReleaseDateAndLabel + ")"); - } - } } diff --git a/CUETools.Processor/CUEMetadataEntry.cs b/CUETools.Processor/CUEMetadataEntry.cs new file mode 100644 index 0000000..668b2e6 --- /dev/null +++ b/CUETools.Processor/CUEMetadataEntry.cs @@ -0,0 +1,32 @@ +using CUETools.CDImage; + +namespace CUETools.Processor +{ + public class CUEMetadataEntry + { + public CUEMetadata metadata { get; set; } + public CDImageLayout TOC { get; set; } + public string ImageKey { get; set; } + + public CUEMetadataEntry(CUEMetadata metadata, CDImageLayout TOC, string key) + { + this.metadata = new CUEMetadata(metadata); + this.TOC = TOC; + this.ImageKey = key; + } + + public CUEMetadataEntry(CDImageLayout TOC, string key) + : this(new CUEMetadata(TOC.TOCID, (int)TOC.AudioTracks), TOC, key) + { + } + + public override string ToString() + { + return string.Format("{0}{1} - {2}{3}{4}", metadata.Year != "" ? metadata.Year + ": " : "", + metadata.Artist == "" ? "Unknown Artist" : metadata.Artist, + metadata.Title == "" ? "Unknown Title" : metadata.Title, + metadata.DiscNumberAndName == "" ? "" : " (disc " + metadata.DiscNumberAndName + ")", + metadata.ReleaseDateAndLabel == "" ? "" : " (" + metadata.ReleaseDateAndLabel + ")"); + } + } +} diff --git a/CUETools.Processor/CUEProcessorPlugins.cs b/CUETools.Processor/CUEProcessorPlugins.cs new file mode 100644 index 0000000..ef13cea --- /dev/null +++ b/CUETools.Processor/CUEProcessorPlugins.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using CUETools.Codecs; +using CUETools.Compression; +using CUETools.Ripper; + +namespace CUETools.Processor +{ + public static class CUEProcessorPlugins + { + public static List encs; + public static List decs; + public static List arcp; + public static List arcp_fmt; + public static Type hdcd; + public static Type ripper; + + static CUEProcessorPlugins() + { + encs = new List(); + decs = new List(); + arcp = new List(); + arcp_fmt = new List(); + + encs.Add(typeof(CUETools.Codecs.WAVWriter)); + decs.Add(typeof(CUETools.Codecs.WAVReader)); + + //ApplicationSecurityInfo asi = new ApplicationSecurityInfo(AppDomain.CurrentDomain.ActivationContext); + //string arch = asi.ApplicationId.ProcessorArchitecture; + //ActivationContext is null most of the time :( + + string arch = Type.GetType("Mono.Runtime", false) != null ? "mono" : Marshal.SizeOf(typeof(IntPtr)) == 8 ? "x64" : "Win32"; + string plugins_path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Plugins (" + arch + ")"); + if (Directory.Exists(plugins_path)) + AddPluginDirectory(plugins_path); + plugins_path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Plugins"); + if (Directory.Exists(plugins_path)) + AddPluginDirectory(plugins_path); + } + + private static void AddPluginDirectory(string plugins_path) + { + foreach (string plugin_path in Directory.GetFiles(plugins_path, "*.dll", SearchOption.TopDirectoryOnly)) + { + try + { + AddPlugin(plugin_path); + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine(ex.Message); + } + } + } + + private static void AddPlugin(string plugin_path) + { + AssemblyName name = AssemblyName.GetAssemblyName(plugin_path); + Assembly assembly = Assembly.Load(name, Assembly.GetEntryAssembly().Evidence); + System.Diagnostics.Trace.WriteLine("Loaded " + assembly.FullName); + foreach (Type type in assembly.GetExportedTypes()) + { + try + { + if (Attribute.GetCustomAttribute(type, typeof(AudioDecoderClass)) != null) + { + decs.Add(type); + } + //if (type.IsClass && !type.IsAbstract && typeof(IAudioDest).IsAssignableFrom(type)) + if (Attribute.GetCustomAttributes(type, typeof(AudioEncoderClass)).Length > 0) + { + encs.Add(type); + } + CompressionProviderClass archclass = Attribute.GetCustomAttribute(type, typeof(CompressionProviderClass)) as CompressionProviderClass; + if (archclass != null) + { + arcp.Add(type); + if (!arcp_fmt.Contains(archclass.Extension)) + arcp_fmt.Add(archclass.Extension); + } + if (type.Name == "HDCDDotNet") + { + hdcd = type; + } + if (type.IsClass && !type.IsAbstract && typeof(ICDRipper).IsAssignableFrom(type)) + { + ripper = type; + } + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine(ex.Message); + } + } + } + } +} diff --git a/CUETools.Processor/CUESheet.cs b/CUETools.Processor/CUESheet.cs new file mode 100644 index 0000000..be3b979 --- /dev/null +++ b/CUETools.Processor/CUESheet.cs @@ -0,0 +1,4496 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Globalization; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; +using CSScriptLibrary; +using CUETools.AccurateRip; +using CUETools.CDImage; +using CUETools.CTDB; +using CUETools.Codecs; +using CUETools.Compression; +using CUETools.Ripper; +using Freedb; + +namespace CUETools.Processor +{ + public class CUESheet + { + private bool _stop, _pause; + private List _attributes; + private List _tracks; + internal List _sources; + private List _sourcePaths, _trackFilenames; + private string _htoaFilename, _singleFilename; + private bool _hasHTOAFilename = false, _hasTrackFilenames = false, _hasSingleFilename = false, _appliedWriteOffset; + private bool _hasEmbeddedCUESheet; + private bool _paddedToFrame, _truncated4608, _usePregapForFirstTrackInSingleFile; + private int _writeOffset; + private CUEAction _action; + internal bool _useAccurateRip = false; + internal bool _useCUEToolsDB = false; + private bool _useCUEToolsDBFix = false; + private bool _processed = false; + private uint? _minDataTrackLength; + private string _accurateRipId; + private string _eacLog; + private string _defaultLog; + private List _logFiles; + private string _inputPath, _inputDir; + private string _outputPath; + private string[] _destPaths; + private TagLib.File _fileInfo; + private const int _arOffsetRange = 5 * 588 - 1; + private IAudioDest hdcdDecoder; + private AudioEncoderType _audioEncoderType = AudioEncoderType.Lossless; + private bool _outputLossyWAV = false; + private string _outputFormat = "wav"; + private CUEStyle _outputStyle = CUEStyle.SingleFile; + private CUEConfig _config; + private string _cddbDiscIdTag; + private bool _isCD; + private string _ripperLog; + private ICDRipper _ripper; + private bool _isArchive; + private List _archiveContents; + private string _archiveCUEpath; + private ICompressionProvider _archive; + private string _archivePassword; + private CUEToolsProgressEventArgs _progress; + private AccurateRipVerify _arVerify; + private CUEToolsDB _CUEToolsDB; + private CDImageLayout _toc; + private string _arLogFileName, _alArtFileName; + private List _albumArt = new List(); + private int _padding = 8192; + private IWebProxy proxy; + private CUEMetadata taglibMetadata; + private CUEMetadata cueMetadata; + private bool _useLocalDB; + private CUEToolsLocalDB _localDB; + + public event EventHandler PasswordRequired; + public event EventHandler CUEToolsProgress; + public event EventHandler CUEToolsSelection; + + public CUESheet(CUEConfig config) + { + _config = config; + _progress = new CUEToolsProgressEventArgs(); + _progress.cueSheet = this; + _attributes = new List(); + _tracks = new List(); + _trackFilenames = new List(); + _toc = new CDImageLayout(); + _sources = new List(); + _sourcePaths = new List(); + _stop = false; + _pause = false; + _outputPath = null; + _paddedToFrame = false; + _truncated4608 = false; + _usePregapForFirstTrackInSingleFile = false; + _action = CUEAction.Encode; + _appliedWriteOffset = false; + _minDataTrackLength = null; + hdcdDecoder = null; + _hasEmbeddedCUESheet = false; + _isArchive = false; + _isCD = false; + _useLocalDB = false; + proxy = _config.GetProxy(); + } + + public void OpenCD(ICDRipper ripper) + { + _ripper = ripper; + _toc = (CDImageLayout)_ripper.TOC.Clone(); + for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) + { + _trackFilenames.Add(string.Format("{0:00}.wav", iTrack + 1)); + _tracks.Add(new TrackInfo()); + } + cueMetadata = new CUEMetadata(TOC.TOCID, (int)TOC.AudioTracks); + _arVerify = new AccurateRipVerify(_toc, proxy); + _isCD = true; + SourceInfo cdInfo; + cdInfo.Path = _ripper.ARName; + cdInfo.Offset = 0; + cdInfo.Length = _toc.AudioLength * 588; + _sources.Add(cdInfo); + // Causes memory leak, so had to disable! + //_ripper.ReadProgress += new EventHandler(CDReadProgress); + _padding += TrackCount * 200; + _padding += _config.embedLog ? 500 + TrackCount * 200 : 0; + } + + public void Close() + { + if (_progress != null) + { + _progress.cueSheet = null; + _progress = null; + } + if (_archive != null) + _archive.Close(); + _archive = null; + if (_ripper != null) + { + //_ripper.ReadProgress -= new EventHandler(CDReadProgress); + _ripper.Close(); + } + _ripper = null; + } + + public string InputPath + { + get + { + return _inputPath; + } + } + + public AccurateRipVerify ArVerify + { + get + { + return _arVerify; + } + } + + public CUEToolsDB CTDB + { + get + { + return _CUEToolsDB; + } + } + + public ICDRipper CDRipper + { + get + { + return _ripper; + } + set + { + _ripper = value; + } + } + + public void CopyMetadata(CUEMetadata metadata) + { + if (this.cueMetadata == null) + this.cueMetadata = new CUEMetadata(TOC.TOCID, (int)TOC.AudioTracks); + this.cueMetadata.CopyMetadata(metadata); + } + + public CUEMetadata Metadata + { + get + { + return cueMetadata; + } + } + + protected void ReportProgress(string status, double percent) + { + ShowProgress(status, percent, null, null); + } + + public void ScanLocalDB(string folder) + { + var results = new List(); + + int n = 2, j = 0; + foreach (var fmt in _config.formats) + if (fmt.Value.allowLossless) + n++; + + CheckStop(); + ReportProgress("Scanning *.cue", (double)(j++) / n); + results.AddRange(Directory.GetFiles(folder, "*.cue", SearchOption.AllDirectories)); + + CheckStop(); + ReportProgress("Scanning *.m3u", (double)(j++) / n); + results.AddRange(Directory.GetFiles(folder, "*.m3u", SearchOption.AllDirectories)); + + foreach (var fmt in _config.formats) + if (fmt.Value.allowLossless) + { + CheckStop(); + ReportProgress("Scanning *." + fmt.Key, (double)(j++) / n); + results.AddRange(Directory.GetFiles(folder, "*." + fmt.Key, SearchOption.AllDirectories)); + } + + int i = 0; + foreach (var result in results) + { + CheckStop(); + + var path = CUEToolsLocalDBEntry.NormalizePath(result); + var pathextension = Path.GetExtension(path).ToLower(); + bool skip = false; + if (_localDB.Find( + item => item.HasPath(path) || + (item.AudioPaths != null && + item.AudioPaths.Count > 1 && + item.AudioPaths.Contains(path)) + ) != null) + skip = true; + if (!skip && pathextension == ".m3u") + { + var contents = new List(); + using (StreamReader m3u = new StreamReader(path)) + { + do + { + string line = m3u.ReadLine(); + if (line == null) break; + if (line == "" || line[0] == '#') continue; + //if (line.IndexOfAny(Path.GetInvalidPathChars()) >= 0) + // continue; + try + { + string extension = Path.GetExtension(line); + CUEToolsFormat fmt1; + if (!extension.StartsWith(".") || !_config.formats.TryGetValue(extension.ToLower().Substring(1), out fmt1) || !fmt1.allowLossless) + { + skip = true; + break; + } + string fullpath = CUEToolsLocalDBEntry.NormalizePath(Path.Combine(Path.GetDirectoryName(path), line)); + if (!File.Exists(fullpath)) + { + skip = true; + break; + } + contents.Add(fullpath); + } + catch + { + skip = true; + break; + } + } while (!skip); + } + if (!skip && _localDB.Find(item => item.EqualAudioPaths(contents)) != null) + skip = true; + } + if (!skip && pathextension != ".cue" && pathextension != ".m3u") + { + if (_localDB.Find(item => + item.AudioPaths != null && + item.AudioPaths.Count == 1 && + item.AudioPaths[0] == path + ) != null) + { + CUEToolsFormat fmt; + if (!pathextension.StartsWith(".") || !_config.formats.TryGetValue(pathextension.Substring(1), out fmt) || !fmt.allowLossless || !fmt.allowEmbed) + skip = true; + else + { + TagLib.File fileInfo; + TagLib.UserDefined.AdditionalFileTypes.Config = _config; + TagLib.File.IFileAbstraction file = (TagLib.File.IFileAbstraction)new TagLib.File.LocalFileAbstraction(path); + fileInfo = TagLib.File.Create(file); + NameValueCollection tags = Tagging.Analyze(fileInfo); + if (tags.Get("CUESHEET") == null) + skip = true; + } + } + } + if (skip) + { + ReportProgress("Skipping " + path, (double)(i++) / results.Count); + } + else + { + ReportProgress("Checking " + path, (double)(i++) / results.Count); + var cueSheet = new CUESheet(_config); + cueSheet.UseLocalDB(_localDB); + //cueSheet.PasswordRequired += new EventHandler(PasswordRequired); + //cueSheet.CUEToolsProgress += new EventHandler(SetStatus); + //cueSheet.CUEToolsSelection += new EventHandler(MakeSelection); + try + { + cueSheet.Open(path); + cueSheet.OpenLocalDBEntry(); + } + catch (Exception) + { + } + cueSheet.Close(); + } + } + _localDB.Save(); + } + + public List LookupAlbumInfo(bool useCache, bool useCUE, bool useCTDB, CTDBMetadataSearch metadataSearch) + { + List Releases = new List(); + + CUEMetadata dbmeta = null; + + if (useCache && _localDB != null) + { + List fullAudioPaths = this.SourcePaths.ConvertAll(p => CUEToolsLocalDBEntry.NormalizePath(p)); + var myEntry = _localDB.Find(e => e.Equals(this.TOC, fullAudioPaths)); + if (myEntry != null) + dbmeta = myEntry.Metadata; + } + + if (dbmeta != null) + Releases.Add(new CUEMetadataEntry(dbmeta, TOC, "local")); + + //if (useCache) + //{ + // try + // { + // CUEMetadata cache = CUEMetadata.Load(TOC.TOCID); + // if (cache != null) + // Releases.Add(new CUEMetadataEntry(cache, TOC, "local")); + // } + // catch (Exception ex) + // { + // System.Diagnostics.Trace.WriteLine(ex.Message); + // } + //} + + if (useCUE) + { + if (dbmeta == null || !dbmeta.Contains(cueMetadata)) + { + if (cueMetadata.Contains(taglibMetadata) || !taglibMetadata.Contains(cueMetadata)) + Releases.Add(new CUEMetadataEntry(new CUEMetadata(cueMetadata), TOC, "cue")); + } + if (dbmeta == null || !dbmeta.Contains(taglibMetadata)) + { + if (!cueMetadata.Contains(taglibMetadata)) + Releases.Add(new CUEMetadataEntry(new CUEMetadata(taglibMetadata), TOC, "tags")); + } + } + + if (useCache && _localDB != null) + { + foreach (var entry in _localDB) + if (entry.DiscID == TOC.TOCID && entry.Metadata != null && (dbmeta == null || !dbmeta.Contains(entry.Metadata))) + Releases.Add(new CUEMetadataEntry(entry.Metadata, TOC, "local")); + } + + bool ctdbFound = false; + if (useCTDB) + { + ShowProgress("Looking up album via CTDB...", 0.0, null, null); + var ctdb = new CUEToolsDB(TOC, proxy); + ctdb.ContactDB(_config.advanced.CTDBServer, "CUETools " + CUEToolsVersion, null, false, false, metadataSearch); + foreach (var meta in ctdb.Metadata) + { + CUEMetadata metadata = new CUEMetadata(TOC.TOCID, (int)TOC.AudioTracks); + metadata.FillFromCtdb(meta, TOC.FirstAudio - 1); + CDImageLayout toc = TOC; // TocFromCDEntry(meta); + Releases.Add(new CUEMetadataEntry(metadata, toc, meta.source)); + ctdbFound = true; + } + } + + if (!ctdbFound && metadataSearch == CTDBMetadataSearch.Extensive) + { + ShowProgress("Looking up album via Freedb...", 0.0, null, null); + + FreedbHelper m_freedb = new FreedbHelper(); + m_freedb.Proxy = proxy; + m_freedb.UserName = _config.advanced.FreedbUser; + m_freedb.Hostname = _config.advanced.FreedbDomain; + m_freedb.ClientName = "CUETools"; + m_freedb.Version = CUEToolsVersion; + m_freedb.SetDefaultSiteAddress("freedb.org"); + + QueryResult queryResult; + QueryResultCollection coll; + string code = string.Empty; + try + { + CDEntry cdEntry = null; + code = m_freedb.Query(AccurateRipVerify.CalculateCDDBQuery(_toc), out queryResult, out coll); + if (code == FreedbHelper.ResponseCodes.CODE_200) + { + ShowProgress("Looking up album via Freedb... " + queryResult.Discid, 0.5, null, null); + code = m_freedb.Read(queryResult, out cdEntry); + if (code == FreedbHelper.ResponseCodes.CODE_210) + { + CUEMetadata metadata = new CUEMetadata(TOC.TOCID, (int)TOC.AudioTracks); + metadata.FillFromFreedb(cdEntry, TOC.FirstAudio - 1); + CDImageLayout toc = TocFromCDEntry(cdEntry); + Releases.Add(new CUEMetadataEntry(metadata, toc, "freedb")); + } + } + else + if (code == FreedbHelper.ResponseCodes.CODE_210 || + code == FreedbHelper.ResponseCodes.CODE_211) + { + int i = 0; + foreach (QueryResult qr in coll) + { + ShowProgress("Looking up album via freedb... " + qr.Discid, (++i + 0.0) / coll.Count, null, null); + CheckStop(); + code = m_freedb.Read(qr, out cdEntry); + if (code == FreedbHelper.ResponseCodes.CODE_210) + { + CUEMetadata metadata = new CUEMetadata(TOC.TOCID, (int)TOC.AudioTracks); + metadata.FillFromFreedb(cdEntry, TOC.FirstAudio - 1); + CDImageLayout toc = TocFromCDEntry(cdEntry); + Releases.Add(new CUEMetadataEntry(metadata, toc, "freedb")); + } + } + } + } + catch (Exception ex) + { + if (ex is StopException) + throw ex; + } + } + + ShowProgress("", 0, null, null); + return Releases; + } + + public CDImageLayout TocFromCDEntry(CDEntry cdEntry) + { + CDImageLayout tocFromCDEntry = new CDImageLayout(); + for (int i = 0; i < cdEntry.Tracks.Count; i++) + { + if (i >= _toc.TrackCount) + break; + tocFromCDEntry.AddTrack(new CDTrack((uint)i + 1, + (uint)cdEntry.Tracks[i].FrameOffset - 150, + (i + 1 < cdEntry.Tracks.Count) ? (uint)(cdEntry.Tracks[i + 1].FrameOffset - cdEntry.Tracks[i].FrameOffset) : _toc[i + 1].Length, + _toc[i + 1].IsAudio, + false/*preEmphasis*/)); + } + if (tocFromCDEntry.TrackCount > 0 && tocFromCDEntry[1].IsAudio) + tocFromCDEntry[1][0].Start = 0; + return tocFromCDEntry; + } + + public CDImageLayout TocFromLog(string eacLog) + { + CDImageLayout tocFromLog = new CDImageLayout(); + using (StringReader sr = new StringReader(eacLog)) + { + bool isEACLog = false; + bool iscdda2wavlog = false; + string lineStr; + int prevTrNo = 1, prevTrStart = 0; + uint firstPreGap = 0; + while ((lineStr = sr.ReadLine()) != null) + { + if (isEACLog) + { + 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) && trNo == tocFromLog.TrackCount + 1) + { + bool isAudio = true; + if (tocFromLog.TrackCount >= _toc.TrackCount && + trStart == tocFromLog[tocFromLog.TrackCount].End + 1U + 152U * 75U + ) + isAudio = false; + if (tocFromLog.TrackCount < _toc.TrackCount && + !_toc[tocFromLog.TrackCount + 1].IsAudio + ) + isAudio = false; + tocFromLog.AddTrack(new CDTrack(trNo, trStart, trEnd + 1 - trStart, isAudio, false)); + } + else + { + string[] sepTrack = { "Track" }; + string[] sepGap = { "Pre-gap length" }; + + string[] partsTrack = lineStr.Split(sepTrack, StringSplitOptions.None); + if (partsTrack.Length == 2 && uint.TryParse(partsTrack[1], out trNo)) + { + prevTrNo = (int)trNo; + continue; + } + + string[] partsGap = lineStr.Split(sepGap, StringSplitOptions.None); + if (partsGap.Length == 2) + { + string[] n1 = partsGap[1].Split(':', '.'); + int h, m, s, f; + if (n1.Length == 4 && int.TryParse(n1[0], out h) && int.TryParse(n1[1], out m) && int.TryParse(n1[2], out s) && int.TryParse(n1[3], out f)) + { + uint gap = (uint)((f * 3 + 2) / 4 + 75 * (s + 60 * (m + 60 * h))); + if (prevTrNo == 1) + gap -= 150; + if (prevTrNo == 1) + firstPreGap = gap - _toc[1].Start; + //else + //firstPreGap += gap; + while (prevTrNo > tocFromLog.TrackCount && _toc.TrackCount > tocFromLog.TrackCount) + { + tocFromLog.AddTrack(new CDTrack((uint)tocFromLog.TrackCount + 1, + _toc[tocFromLog.TrackCount + 1].Start + firstPreGap, + _toc[tocFromLog.TrackCount + 1].Length, + _toc[tocFromLog.TrackCount + 1].IsAudio, false)); + } + if (prevTrNo <= tocFromLog.TrackCount) + tocFromLog[prevTrNo].Pregap = gap; + } + } + } + } + else if (iscdda2wavlog) + { + foreach (string entry in lineStr.Split(',')) + { + string[] n = entry.Split('('); + if (n.Length < 2) continue; + // assert n.Length == 2; + string key = n[0].Trim(' ', '.'); + int trStart = int.Parse(n[1].Trim(' ', ')')); + bool isAudio = true; // !!! + if (key != "1") + tocFromLog.AddTrack(new CDTrack((uint)prevTrNo, (uint)prevTrStart, (uint)(trStart - prevTrStart), isAudio, false)); + if (key == "lead-out") + { + iscdda2wavlog = false; + break; + } + prevTrNo = int.Parse(key); + prevTrStart = trStart; + } + } + else if (lineStr.StartsWith("TOC of the extracted CD") + || lineStr.StartsWith("Exact Audio Copy") + || lineStr.StartsWith("EAC extraction logfile") + || lineStr.StartsWith("CUERipper") + || lineStr.StartsWith(" Track | Start | Length | Start sector | End sector") + ) + isEACLog = true; + else if (lineStr.StartsWith("Table of Contents: starting sectors")) + iscdda2wavlog = true; + } + } + if (tocFromLog.TrackCount == 0) + return null; + tocFromLog[1][0].Start = 0; + return tocFromLog; + } + + public void Open(string pathIn) + { + _inputPath = pathIn; + _inputDir = Path.GetDirectoryName(_inputPath) ?? _inputPath; + if (_inputDir == "") _inputDir = "."; + if (_inputDir == pathIn && CUEProcessorPlugins.ripper != null) + { + ICDRipper ripper = Activator.CreateInstance(CUEProcessorPlugins.ripper) as ICDRipper; + try + { + ripper.Open(pathIn[0]); + if (ripper.TOC.AudioTracks > 0) + { + OpenCD(ripper); + int driveOffset; + if (!AccurateRipVerify.FindDriveReadOffset(_ripper.ARName, out driveOffset)) + throw new Exception("Failed to find drive read offset for drive" + _ripper.ARName); + _ripper.DriveOffset = driveOffset; + //LookupAlbumInfo(); + return; + } + } + catch + { + ripper.Dispose(); + _ripper = null; + throw; + } + } + + TextReader sr; + + if (Directory.Exists(pathIn)) + throw new Exception("is a directory"); + //{ + // 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", "*.tta" }; + // for (i = 0; i < audioExts.Length && cueSheet == null; i++) + // cueSheet = CUESheet.CreateDummyCUESheet(pathIn, audioExts[i]); + // if (_config.udc1Extension != null && cueSheet == null) + // cueSheet = CUESheet.CreateDummyCUESheet(pathIn, "*." + _config.udc1Extension); + // if (cueSheet == null) + // throw new Exception("Input directory doesn't contain supported audio files."); + // sr = new StringReader(cueSheet); + + // List logFiles = new List(); + // foreach (string logPath in Directory.GetFiles(pathIn, "*.log")) + // logFiles.Add(new CUEToolsSourceFile(logPath, new StreamReader(logPath, CUESheet.Encoding))); + // CUEToolsSourceFile selectedLogFile = ChooseFile(logFiles, null, false); + // _eacLog = selectedLogFile != null ? selectedLogFile.contents : null; + //} + else if (CUEProcessorPlugins.arcp_fmt.Contains(Path.GetExtension(pathIn).ToLower().Trim('.'))) + { + _archive = null; + foreach (Type type in CUEProcessorPlugins.arcp) + { + CompressionProviderClass archclass = Attribute.GetCustomAttribute(type, typeof(CompressionProviderClass)) as CompressionProviderClass; + if (archclass.Extension == Path.GetExtension(pathIn).ToLower().Trim('.')) + { + _archive = Activator.CreateInstance(type, pathIn) as ICompressionProvider; + break; + } + } + if (_archive == null) + throw new Exception("archive type not supported."); + _isArchive = true; + _archiveContents = new List(); + _archive.PasswordRequired += new EventHandler(unzip_PasswordRequired); + _archive.ExtractionProgress += new EventHandler(unzip_ExtractionProgress); + foreach (string f in _archive.Contents) + _archiveContents.Add(f); + + _logFiles = new List(); + List cueFiles = new List(); + foreach (string s in _archiveContents) + { + if (Path.GetExtension(s).ToLower() == ".cue" || Path.GetExtension(s).ToLower() == ".log") + { + Stream archiveStream = OpenArchive(s, false); + CUEToolsSourceFile sourceFile = new CUEToolsSourceFile(s, new StreamReader(archiveStream, CUESheet.Encoding)); + archiveStream.Close(); + if (Path.GetExtension(s).ToLower() == ".cue") + cueFiles.Add(sourceFile); + else + _logFiles.Add(sourceFile); + } + } + CUEToolsSourceFile selectedCUEFile = ChooseFile(cueFiles, null, true); + if (selectedCUEFile == null || selectedCUEFile.contents == "") + throw new Exception("Input archive doesn't contain a usable cue sheet."); + _defaultLog = Path.GetFileNameWithoutExtension(selectedCUEFile.path); + _archiveCUEpath = Path.GetDirectoryName(selectedCUEFile.path); + string cueText = selectedCUEFile.contents; + if (_config.autoCorrectFilenames) + { + string extension; + cueText = CorrectAudioFilenames(_config, _archiveCUEpath, cueText, false, _archiveContents, out extension); + } + sr = new StringReader(cueText); + if (_logFiles.Count == 1) + _eacLog = _logFiles[0].contents; + } + else if (Path.GetExtension(pathIn).ToLower() == ".cue") + { + if (_config.autoCorrectFilenames) + sr = new StringReader(CorrectAudioFilenames(_config, pathIn, false)); + else + sr = new StreamReader(pathIn, CUESheet.Encoding); + + _logFiles = new List(); + _defaultLog = Path.GetFileNameWithoutExtension(pathIn); + foreach (string logPath in Directory.GetFiles(_inputDir, "*.log")) + try { _logFiles.Add(new CUEToolsSourceFile(logPath, new StreamReader(logPath, CUESheet.Encoding))); } + catch { } + } + else if (Path.GetExtension(pathIn).ToLower() == ".m3u") + { + string cueSheet = CUESheet.CreateDummyCUESheet(_config, pathIn); + sr = new StringReader(cueSheet); + _logFiles = new List(); + _defaultLog = Path.GetFileNameWithoutExtension(pathIn); + foreach (string logPath in Directory.GetFiles(_inputDir, "*.log")) + try { _logFiles.Add(new CUEToolsSourceFile(logPath, new StreamReader(logPath, CUESheet.Encoding))); } + catch { } + } + else + { + string extension = Path.GetExtension(pathIn).ToLower(); + sr = null; + CUEToolsFormat fmt; + if (!extension.StartsWith(".") || !_config.formats.TryGetValue(extension.Substring(1), out fmt) || !fmt.allowLossless) + throw new Exception("Unknown input format."); + if (fmt.allowEmbed) + { + string cuesheetTag = null; + TagLib.File fileInfo; + GetSampleLength(pathIn, out fileInfo); + NameValueCollection tags = Tagging.Analyze(fileInfo); + 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"); + if (cuesheetTag != null) + { + sr = new StringReader(cuesheetTag); + _hasEmbeddedCUESheet = true; + } + } + if (!_hasEmbeddedCUESheet) + { + string cueSheet = CUESheet.CreateDummyCUESheet(_config, pathIn); + if (cueSheet == null) + throw new Exception("Input file doesn't seem to contain a cue sheet or be part of an album."); + sr = new StringReader(cueSheet); + _logFiles = new List(); + foreach (string logPath in Directory.GetFiles(_inputDir, "*.log")) + try { _logFiles.Add(new CUEToolsSourceFile(logPath, new StreamReader(logPath, CUESheet.Encoding))); } + catch { } + } + } + + OpenCUE(sr); + } + + public void OpenCUE(TextReader sr) + { + string pathAudio = null; + string lineStr, command, fileType; + bool fileIsBinary = false; + int timeRelativeToFileStart, absoluteFileStartTime = 0; + int fileTimeLengthSamples = 0, fileTimeLengthFrames = 0, i; + TagLib.File _trackFileInfo = null; + bool seenFirstFileIndex = false; + bool isAudioTrack = true; + List indexes = new List(); + IndexInfo indexInfo; + SourceInfo sourceInfo; + TrackInfo trackInfo = null; + int trackNumber = 0; + + using (sr) + { + while ((lineStr = sr.ReadLine()) != null) + { + CUELine line = new CUELine(lineStr); + if (line.Params.Count > 0) + { + command = line.Params[0].ToUpper(); + + if (command == "FILE") + { + fileType = line.Params[2].ToUpper(); + fileIsBinary = (fileType == "BINARY") || (fileType == "MOTOROLA"); + if (fileIsBinary) + { + if (!_hasEmbeddedCUESheet && _sourcePaths.Count == 0) + { + try + { + if (_isArchive) + pathAudio = LocateFile(_archiveCUEpath, line.Params[1], _archiveContents); + else + pathAudio = LocateFile(_inputDir, line.Params[1], null); + fileIsBinary = (pathAudio == null); + } + catch { } + } + } + if (!fileIsBinary) + { + if (_sourcePaths.Count != 0 && !seenFirstFileIndex) + throw new Exception("Double FILE in CUE sheet: \"" + line.Params[1] + "\"."); + if (!_hasEmbeddedCUESheet) + { + if (_isArchive) + pathAudio = LocateFile(_archiveCUEpath, line.Params[1], _archiveContents); + else + pathAudio = LocateFile(_inputDir, line.Params[1], null); + } + else + { + pathAudio = _inputPath; + if (_sourcePaths.Count > 0) + throw new Exception("Extra file in embedded CUE sheet: \"" + line.Params[1] + "\"."); + } + + if (pathAudio == null) + { + throw new Exception("Unable to locate file \"" + line.Params[1] + "\"."); + //fileTimeLengthFrames = 75 * 60 * 70;; + //fileTimeLengthSamples = fileTimeLengthFrames * 588; + //if (_hasEmbeddedCUESheet) + // _fileInfo = null; + //else + // _trackFileInfo = null; + } + else + { + // Wierd case: audio file after data track with only index 00 specified. + if (!isAudioTrack && _sourcePaths.Count == 0 && indexes.Count > 0 && indexes[indexes.Count - 1].Index == 0) + { + indexInfo.Track = indexes[indexes.Count - 1].Track; + indexInfo.Index = 1; + indexInfo.Time = indexes[indexes.Count - 1].Time + 150; + indexes.Add(indexInfo); + absoluteFileStartTime += 150; + } + + TagLib.File fileInfo; + _sourcePaths.Add(pathAudio); + absoluteFileStartTime += fileTimeLengthFrames; + fileTimeLengthSamples = GetSampleLength(pathAudio, out fileInfo); + if ((fileTimeLengthSamples % 588) == 492 && _config.truncate4608ExtraSamples) + { + _truncated4608 = true; + fileTimeLengthSamples -= 4608; + } + fileTimeLengthFrames = (int)((fileTimeLengthSamples + 587) / 588); + if (_hasEmbeddedCUESheet) + _fileInfo = fileInfo; + else + _trackFileInfo = fileInfo; + } + seenFirstFileIndex = false; + } + } + else if (command == "TRACK") + { + isAudioTrack = line.Params[2].ToUpper() == "AUDIO"; + trackNumber = int.Parse(line.Params[1]); + if (trackNumber != _toc.TrackCount + 1) + throw new Exception("Invalid track number"); + // Disabled this check: fails on Headcandy test image + //if (isAudioTrack && _sourcePaths.Count == 0) + // throw new Exception("No FILE seen before TRACK"); + _toc.AddTrack(new CDTrack((uint)trackNumber, 0, 0, isAudioTrack, false)); + if (isAudioTrack) + { + trackInfo = new TrackInfo(); + _tracks.Add(trackInfo); + } + } + 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."); + seenFirstFileIndex = true; + if (isAudioTrack) + { + if (_tracks.Count > 0 && _trackFileInfo != null) + _tracks[_tracks.Count - 1]._fileInfo = _trackFileInfo; + _trackFileInfo = null; + 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; + } + } + } + else + { + if (fileIsBinary) + { + fileTimeLengthFrames = timeRelativeToFileStart + 150; + sourceInfo.Path = null; + sourceInfo.Offset = 0; + sourceInfo.Length = 150 * 588; + _sources.Add(sourceInfo); + //throw new Exception("unexpected BINARY directive"); + } + else + { + if (timeRelativeToFileStart > fileTimeLengthFrames) + throw new Exception(string.Format("TRACK {0} INDEX {1} is at {2}, which is past {3} - the end of source file {4}", trackNumber, line.Params[1], CDImageLayout.TimeToString((uint)timeRelativeToFileStart), CDImageLayout.TimeToString((uint)fileTimeLengthFrames), pathAudio)); + } + } + indexInfo.Track = trackNumber; + indexInfo.Index = Int32.Parse(line.Params[1]); + indexInfo.Time = absoluteFileStartTime + timeRelativeToFileStart; + indexes.Add(indexInfo); + } + else if (!isAudioTrack) + { + // Ignore lines belonging to data tracks + } + 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() == "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 + { + if (line.Params.Count > 2 && !line.IsQuoted[1] && + (line.Params[0].ToUpper() == "TITLE" || line.Params[0].ToUpper() == "ARTIST" || + (line.Params[0].ToUpper() == "REM" && line.Params[1].ToUpper() == "GENRE" && line.Params.Count > 3 && !line.IsQuoted[2]))) + { + CUELine modline = new CUELine(); + int nParams = line.Params[0].ToUpper() == "REM" ? 2 : 1; + for (int iParam = 0; iParam < nParams; iParam++) + { + modline.Params.Add(line.Params[iParam]); + modline.IsQuoted.Add(false); + } + string s = line.Params[nParams]; + for (int iParam = nParams + 1; iParam < line.Params.Count; iParam++) + s += " " + line.Params[iParam]; + modline.Params.Add(s); + modline.IsQuoted.Add(true); + line = modline; + } + _attributes.Add(line); + } + } + } + } + sr.Close(); + } + + if (_tracks.Count == 0) + throw new Exception("File must contain at least one audio track."); + + // Add dummy index 01 for data track + if (!_toc[_toc.TrackCount].IsAudio && indexes[indexes.Count - 1].Index == 0) + { + fileTimeLengthFrames += 152 * 75; + indexInfo.Track = trackNumber; + indexInfo.Index = 1; + indexInfo.Time = absoluteFileStartTime + fileTimeLengthFrames; + indexes.Add(indexInfo); + } + + // 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++) + { + if (indexes[i + 1].Time - indexes[i].Time < 0) + throw new Exception("Indexes must be in chronological order."); + 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)); + } + + // Calculate the length of each track + for (int iTrack = 1; iTrack <= _toc.TrackCount; iTrack++) + { + _toc[iTrack].Start = _toc[iTrack][1].Start; + _toc[iTrack].Length = iTrack == _toc.TrackCount + ? (uint)indexes[indexes.Count - 1].Time - _toc[iTrack].Start + : _toc[iTrack + 1].IsAudio + ? _toc[iTrack + 1][1].Start - _toc[iTrack].Start + : _toc[iTrack + 1][0].Start - _toc[iTrack].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 = !_hasEmbeddedCUESheet && !_hasSingleFilename && (_sourcePaths.Count == TrackCount || _hasHTOAFilename); + for (i = 0; i < TrackCount; i++) + { + _trackFilenames.Add(_hasTrackFilenames ? Path.GetFileName( + _sourcePaths[i + (_hasHTOAFilename ? 1 : 0)]) : String.Format("{0:00}.wav", i + 1)); + } + if (!_hasEmbeddedCUESheet && _hasSingleFilename) + { + _fileInfo = _tracks[0]._fileInfo; + _tracks[0]._fileInfo = null; + } + taglibMetadata = new CUEMetadata(TOC.TOCID, (int)TOC.AudioTracks); + taglibMetadata.Artist = GetCommonTag(file => file.Tag.JoinedAlbumArtists) ?? GetCommonTag(file => file.Tag.JoinedPerformers) ?? ""; + taglibMetadata.Title = GetCommonTag(file => file.Tag.Album) ?? ""; + taglibMetadata.Year = GetCommonTag(file => file.Tag.Year != 0 ? file.Tag.Year.ToString() : null) ?? ""; + taglibMetadata.Genre = GetCommonTag(file => file.Tag.JoinedGenres) ?? ""; + taglibMetadata.TotalDiscs = GetCommonTag(file => file.Tag.DiscCount != 0 ? file.Tag.DiscCount.ToString() : null) ?? ""; + taglibMetadata.DiscNumber = GetCommonTag(file => file.Tag.Disc != 0 ? file.Tag.Disc.ToString() : null) ?? ""; + for (i = 0; i < TrackCount; i++) + { + TrackInfo track = _tracks[i]; + taglibMetadata.Tracks[i].Artist = (_hasTrackFilenames && track._fileInfo != null ? track._fileInfo.Tag.JoinedPerformers : + _hasEmbeddedCUESheet && _fileInfo != null ? Tagging.TagListToSingleValue(Tagging.GetMiscTag(_fileInfo, String.Format("cue_track{0:00}_ARTIST", i + 1))) : + null) ?? ""; + taglibMetadata.Tracks[i].Title = (_hasTrackFilenames && track._fileInfo != null ? track._fileInfo.Tag.Title : + _hasEmbeddedCUESheet && _fileInfo != null ? Tagging.TagListToSingleValue(Tagging.GetMiscTag(_fileInfo, String.Format("cue_track{0:00}_TITLE", i + 1))) : + null) ?? ""; + } + + cueMetadata = new CUEMetadata(TOC.TOCID, (int)TOC.AudioTracks); + cueMetadata.Artist = General.GetCUELine(_attributes, "PERFORMER"); + cueMetadata.Title = General.GetCUELine(_attributes, "TITLE"); + cueMetadata.Barcode = General.GetCUELine(_attributes, "CATALOG"); + cueMetadata.Year = General.GetCUELine(_attributes, "REM", "DATE"); + cueMetadata.DiscNumber = General.GetCUELine(_attributes, "REM", "DISCNUMBER"); + cueMetadata.TotalDiscs = General.GetCUELine(_attributes, "REM", "TOTALDISCS"); + cueMetadata.Genre = General.GetCUELine(_attributes, "REM", "GENRE"); + for (i = 0; i < Tracks.Count; i++) + { + cueMetadata.Tracks[i].Artist = General.GetCUELine(Tracks[i].Attributes, "PERFORMER"); + cueMetadata.Tracks[i].Title = General.GetCUELine(Tracks[i].Attributes, "TITLE"); + cueMetadata.Tracks[i].ISRC = General.GetCUELine(Tracks[i].Attributes, "ISRC"); + } + // Now, TOC.TOCID might change!!! + + if (_config.fillUpCUE) + { + cueMetadata.Merge(taglibMetadata, _config.overwriteCUEData); + for (i = 0; i < TrackCount; i++) + { + if (cueMetadata.Tracks[i].Title == "" && _hasTrackFilenames) + cueMetadata.Tracks[i].Title = Path.GetFileNameWithoutExtension(_trackFilenames[i]).TrimStart(" .-_0123456789".ToCharArray()); + } + } + + CUELine cddbDiscIdLine = General.FindCUELine(_attributes, "REM", "DISCID"); + _cddbDiscIdTag = cddbDiscIdLine != null && cddbDiscIdLine.Params.Count == 3 ? cddbDiscIdLine.Params[2] : null; + if (_cddbDiscIdTag == null) + _cddbDiscIdTag = GetCommonMiscTag("DISCID"); + + if (_accurateRipId == null) + _accurateRipId = GetCommonMiscTag("ACCURATERIPID"); + + if (_eacLog == null && _logFiles != null && _logFiles.Count > 0) + { + foreach (CUEToolsSourceFile sf in _logFiles) + { + CDImageLayout tocFromLog1 = TocFromLog(sf.contents); + if (tocFromLog1 != null && tocFromLog1.TOCID == _toc.TOCID) + { + if (_eacLog == null) + _eacLog = sf.contents; + else + { + _eacLog = null; + break; + } + } + } + } + + if (_eacLog == null && _logFiles != null && _logFiles.Count > 0) + { + CUEToolsSourceFile selectedLogFile = ChooseFile(_logFiles, _defaultLog, false); + _eacLog = selectedLogFile != null ? selectedLogFile.contents : null; + } + + CDImageLayout tocFromLog = _eacLog == null ? null : TocFromLog(_eacLog); + + if (tocFromLog == null) + { + string tocPath = Path.ChangeExtension(InputPath, ".toc"); + if (File.Exists(tocPath)) + tocFromLog = TocFromLog((new StreamReader(tocPath, CUESheet.Encoding)).ReadToEnd()); + } + + // use pregaps from log + if (tocFromLog != null) + { + //int srcNo = (int) _toc[_toc.FirstAudio].LastIndex - (PreGapLength == 0 ? 1 : 0); + if (PreGapLength < tocFromLog.Pregap) + { + PreGapLength = tocFromLog.Pregap; + //srcNo ++; + } + int trNo; + for (trNo = 1; trNo < tocFromLog.AudioTracks && trNo < _toc.AudioTracks; trNo++) + { + if (_toc[_toc.FirstAudio + trNo].Pregap < tocFromLog[tocFromLog.FirstAudio + trNo].Pregap) + _toc[_toc.FirstAudio + trNo].Pregap = tocFromLog[tocFromLog.FirstAudio + trNo].Pregap; + } + //if (_toc[_toc.FirstAudio].Length > tocFromLog[tocFromLog.FirstAudio].Length) + //{ + // uint offs = _toc[_toc.FirstAudio].Length - tocFromLog[tocFromLog.FirstAudio].Length; + // _toc[_toc.FirstAudio].Length -= offs; + + // sourceInfo = _sources[srcNo]; + // sourceInfo.Length -= offs * 588; + // _sources[srcNo] = sourceInfo; + // for (i = _toc.FirstAudio + 1; i <= _toc.TrackCount; i++) + // { + // _toc[i].Start -= offs; + // for (int j = 0; j <= _toc[i].LastIndex; j++) + // if (i != _toc.FirstAudio + 1 || j != 0 || _toc[i][0].Start == _toc[i][1].Start) + // _toc[i][j].Start -= offs; + // } + //} + //for (trNo = 1; trNo < tocFromLog.AudioTracks && trNo < _toc.AudioTracks; trNo++) + //{ + // srcNo ++; + // if (_toc[_toc.FirstAudio + trNo].Length > tocFromLog[tocFromLog.FirstAudio + trNo].Length) + // { + // uint offs = _toc[_toc.FirstAudio + trNo].Length - tocFromLog[tocFromLog.FirstAudio + trNo].Length; + // _toc[_toc.FirstAudio + trNo].Length -= offs; + // sourceInfo = _sources[srcNo]; + // sourceInfo.Length -= offs * 588; + // _sources[srcNo] = sourceInfo; + // for (i = _toc.FirstAudio + trNo + 1; i <= _toc.TrackCount; i++) + // { + // _toc[i].Start -= offs; + // for (int j = 0; j <= _toc[i].LastIndex; j++) + // if (i != _toc.FirstAudio + trNo + 1 || j != 0 || _toc[i][0].Start == _toc[i][1].Start) + // _toc[i][j].Start -= offs; + // } + // } + //} + } + + // use data track length from log + if (tocFromLog != null) + { + if (tocFromLog.AudioTracks == _toc.AudioTracks + && tocFromLog.TrackCount == tocFromLog.AudioTracks + 1 + && !tocFromLog[tocFromLog.TrackCount].IsAudio) + { + DataTrackLength = tocFromLog[tocFromLog.TrackCount].Length; + _toc[_toc.TrackCount].Start = tocFromLog[_toc.TrackCount].Start; + _toc[_toc.TrackCount][0].Start = tocFromLog[_toc.TrackCount].Start; + _toc[_toc.TrackCount][1].Start = tocFromLog[_toc.TrackCount].Start; + } + if (_toc.TrackCount == _toc.AudioTracks + && tocFromLog.TrackCount == tocFromLog.AudioTracks + && tocFromLog.TrackCount > _toc.TrackCount) + { + int dtracks = tocFromLog.TrackCount - _toc.TrackCount; + bool matches = true; + for (int iTrack = 1; iTrack <= _toc.TrackCount; iTrack++) + if (tocFromLog[iTrack + dtracks].Length != _toc[iTrack].Length) + matches = false; + if (matches) + { + for (int iTrack = 1; iTrack <= dtracks; iTrack++) + { + _toc.InsertTrack(new CDTrack((uint)iTrack, 0, 0, false, false)); + tocFromLog[iTrack].IsAudio = false; + } + tocFromLog.FirstAudio += dtracks; + tocFromLog.AudioTracks -= (uint)dtracks; + } + } + if (tocFromLog.AudioTracks == _toc.AudioTracks + && tocFromLog.TrackCount == _toc.TrackCount + && tocFromLog.FirstAudio == _toc.FirstAudio + && tocFromLog.TrackCount == tocFromLog.FirstAudio + tocFromLog.AudioTracks - 1) + { + //DataTrackLength = tocFromLog[1].Length; + uint delta = tocFromLog[_toc.FirstAudio].Start - _toc[_toc.FirstAudio].Start; + for (int itr = 1; itr < _toc.FirstAudio; itr++) + { + _toc[itr].Start = tocFromLog[itr].Start; + _toc[itr].Length = tocFromLog[itr].Length; + } + for (int itr = _toc.FirstAudio; itr <= _toc.TrackCount; itr++) + { + _toc[itr].Start += delta; + for (int j = 0; j <= _toc[itr].LastIndex; j++) + _toc[itr][j].Start += delta; + } + } + } + + // use data track length range from cddbId + if (DataTrackLength == 0 && _cddbDiscIdTag != null) + { + uint cddbDiscIdNum; + if (uint.TryParse(_cddbDiscIdTag, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out cddbDiscIdNum) && (cddbDiscIdNum & 0xff) == _toc.AudioTracks + 1) + { + if (_toc.TrackCount == _toc.AudioTracks) + _toc.AddTrack(new CDTrack((uint)_toc.TrackCount + 1, _toc.Length + 152 * 75, 0, false, false)); + uint lengthFromTag = ((cddbDiscIdNum >> 8) & 0xffff); + _minDataTrackLength = (lengthFromTag + _toc[1].Start / 75) * 75 - _toc.Length; + } + } + + _arVerify = new AccurateRipVerify(_toc, proxy); + + if (_eacLog != null) + { + sr = new StringReader(_eacLog); + bool isEACLog = false; + int trNo = 1; + while ((lineStr = sr.ReadLine()) != null) + { + if (isEACLog && trNo <= TrackCount) + { + string[] s = { "Copy CRC ", "CRC копии" }; + string[] s1 = { "CRC" }; + string[] n = lineStr.Split(s, StringSplitOptions.None); + uint crc; + if (n.Length == 2 && uint.TryParse(n[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out crc)) + _arVerify.CRCLOG(trNo++, crc); + else if (n.Length == 1) + { + n = lineStr.Split(s1, StringSplitOptions.None); + if (n.Length == 2 && n[0].Trim() == "" && uint.TryParse(n[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out crc)) + _arVerify.CRCLOG(trNo++, crc); + } + } + else + if (lineStr.StartsWith("Exact Audio Copy") + || lineStr.StartsWith("EAC extraction logfile")) + isEACLog = true; + } + if (trNo == 2) + { + _arVerify.CRCLOG(0, _arVerify.CRCLOG(1)); + if (TrackCount > 1) + _arVerify.CRCLOG(1, 0); + } + } + + LoadAlbumArt(_tracks[0]._fileInfo ?? _fileInfo); + ResizeAlbumArt(); + if (_config.embedAlbumArt || _config.CopyAlbumArt) + _albumArt.ForEach(t => _padding += _albumArt[0].Data.Count); + if (_config.embedLog && _eacLog != null) + _padding += _eacLog.Length; + + cueMetadata.Id = TOC.TOCID; + taglibMetadata.Id = TOC.TOCID; + // TODO: It should also be set when assigning a DataTrack!!! + } + + public void UseCUEToolsDB(string userAgent, string driveName, bool fuzzy, CTDBMetadataSearch metadataSearch) + { + ShowProgress((string)"Contacting CUETools database...", 0, null, null); + + _CUEToolsDB = new CUEToolsDB(_toc, proxy); + _CUEToolsDB.UploadHelper.onProgress += new EventHandler(UploadProgress); + _CUEToolsDB.ContactDB(_config.advanced.CTDBServer, userAgent, driveName, true, fuzzy, metadataSearch); + + if (!_toc[_toc.TrackCount].IsAudio && DataTrackLength == 0) + foreach (DBEntry e in _CUEToolsDB.Entries) + if (e.toc.TrackCount == _toc.TrackCount && e.toc.AudioLength == _toc.AudioLength && !e.toc[e.toc.TrackCount].IsAudio) + { + DataTrackLength = e.toc[e.toc.TrackCount].Length; + break; + } + + ShowProgress("", 0.0, null, null); + _useCUEToolsDB = true; + } + + public void UseAccurateRip() + { + ShowProgress((string)"Contacting AccurateRip database...", 0, null, null); + if (!_toc[_toc.TrackCount].IsAudio && DataTrackLength == 0 && _minDataTrackLength.HasValue && _accurateRipId == null && _config.bruteForceDTL) + { + uint minDTL = _minDataTrackLength.Value; + CDImageLayout toc2 = new CDImageLayout(_toc); + for (uint dtl = minDTL; dtl < minDTL + 75; dtl++) + { + toc2[toc2.TrackCount].Length = dtl; + _arVerify.ContactAccurateRip(AccurateRipVerify.CalculateAccurateRipId(toc2)); + if (_arVerify.ExceptionStatus == WebExceptionStatus.Success) + { + DataTrackLength = dtl; + break; + } + if (_arVerify.ExceptionStatus != WebExceptionStatus.ProtocolError || + _arVerify.ResponseStatus != HttpStatusCode.NotFound) + break; + ShowProgress((string)"Contacting AccurateRip database...", (dtl - minDTL) / 75.0, null, null); + CheckStop(); + } + } + else + { + _arVerify.ContactAccurateRip(_accurateRipId ?? AccurateRipVerify.CalculateAccurateRipId(_toc)); + } + _useAccurateRip = true; + } + + public static Encoding Encoding + { + get + { + return Encoding.Default; + } + } + + internal CUEToolsSourceFile ChooseFile(List sourceFiles, string defaultFileName, bool quietIfSingle) + { + if (sourceFiles.Count <= 0) + return null; + + if (defaultFileName != null) + { + CUEToolsSourceFile defaultFile = null; + foreach (CUEToolsSourceFile file in sourceFiles) + if (Path.GetFileNameWithoutExtension(file.path).ToLower() == defaultFileName.ToLower()) + { + if (defaultFile != null) + { + defaultFile = null; + break; + } + defaultFile = file; + } + if (defaultFile != null) + return defaultFile; + } + + if (quietIfSingle && sourceFiles.Count == 1) + return sourceFiles[0]; + + if (CUEToolsSelection == null) + return null; + + CUEToolsSelectionEventArgs e = new CUEToolsSelectionEventArgs(); + e.choices = sourceFiles.ToArray(); + CUEToolsSelection(this, e); + if (e.selection == -1) + return null; + + return sourceFiles[e.selection]; + } + + internal Stream OpenArchive(string fileName, bool showProgress) + { + if (_archive == null) + throw new Exception("Unknown archive type."); + return _archive.Decompress(fileName); + } + + private void ShowProgress(string status, double percent, string input, string output) + { + if (this.CUEToolsProgress == null) + return; + _progress.status = status; + _progress.percent = percent; + _progress.offset = 0; + _progress.input = input; + _progress.output = output; + this.CUEToolsProgress(this, _progress); + } + + private void ShowProgress(string status, int diskOffset, int diskLength, string input, string output) + { + if (this.CUEToolsProgress == null) + return; + _progress.status = status; + _progress.percent = (double)diskOffset / diskLength; + _progress.offset = diskOffset; + _progress.input = input; + _progress.output = output; + this.CUEToolsProgress(this, _progress); + } + + private void UploadProgress(object sender, Krystalware.UploadHelper.UploadProgressEventArgs e) + { + CheckStop(); + if (this.CUEToolsProgress == null) + return; + _progress.percent = e.percent; + _progress.offset = 0; + _progress.status = e.uri; + this.CUEToolsProgress(this, _progress); + } + + //private void CDReadProgress(object sender, ReadProgressArgs e) + //{ + // CheckStop(); + // if (this.CUEToolsProgress == null) + // return; + // ICDRipper audioSource = (ICDRipper)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.offset = 0; + // _progress.status = string.Format("Ripping @{0:00.00}x {1}", speed, e.Pass > 0 ? " (Retry " + e.Pass.ToString() + ")" : ""); + // this.CUEToolsProgress(this, _progress); + //} + + private void unzip_ExtractionProgress(object sender, CompressionExtractionProgressEventArgs e) + { + CheckStop(); + if (this.CUEToolsProgress == null) + return; + _progress.percent = e.PercentComplete / 100; + this.CUEToolsProgress(this, _progress); + } + + private void unzip_PasswordRequired(object sender, CompressionPasswordRequiredEventArgs e) + { + if (_archivePassword != null) + { + e.ContinueOperation = true; + e.Password = _archivePassword; + return; + } + if (this.PasswordRequired != null) + { + this.PasswordRequired(this, e); + if (e.ContinueOperation && e.Password != "") + { + _archivePassword = e.Password; + return; + } + } + throw new IOException("Password is required for extraction."); + } + + public delegate string GetStringTagProvider(TagLib.File file); + + public string GetCommonTag(GetStringTagProvider provider) + { + if (_hasEmbeddedCUESheet || _hasSingleFilename) + return _fileInfo == null ? null : General.EmptyStringToNull(provider(_fileInfo)); + if (_hasTrackFilenames) + { + string tagValue = null; + bool commonValue = true; + for (int i = 0; i < TrackCount; i++) + { + TrackInfo track = _tracks[i]; + string newValue = track._fileInfo == null ? null : + General.EmptyStringToNull(provider(track._fileInfo)); + if (tagValue == null) + tagValue = newValue; + else + commonValue = (newValue == null || tagValue == newValue); + } + return commonValue ? tagValue : null; + } + return null; + } + + public string GetCommonMiscTag(string tagName) + { + return GetCommonTag(delegate(TagLib.File file) { return Tagging.TagListToSingleValue(Tagging.GetMiscTag(file, tagName)); }); + } + + private static string LocateFile(string dir, string file, List contents) + { + List dirList, fileList; + string altDir; + + 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++) + { + string path = Path.Combine(dirList[iDir], fileList[iFile]); + if (contents == null && System.IO.File.Exists(path)) + return path; + if (contents != null) + { + List matching = contents.FindAll(s => s.ToLower().Replace('/', Path.DirectorySeparatorChar) == + path.ToLower().Replace('/', Path.DirectorySeparatorChar)); + if (matching.Count == 1) + return matching[0]; + } + } + } + + return null; + } + + private static bool IsCDROM(string pathIn) + { + return pathIn.Length == 3 && pathIn.Substring(1) == ":\\" && new DriveInfo(pathIn).DriveType == DriveType.CDRom; + } + + public string GenerateUniqueOutputPath(string format, string ext, CUEAction action, string pathIn) + { + return GenerateUniqueOutputPath(_config, format, ext, action, new NameValueCollection(), pathIn, this); + } + + public static string GenerateUniqueOutputPath(CUEConfig _config, string format, string ext, CUEAction action, NameValueCollection vars, string pathIn, CUESheet cueSheet) + { + if (pathIn == "" || (pathIn == null && action != CUEAction.Encode) || (pathIn != null && !IsCDROM(pathIn) && !File.Exists(pathIn) && !Directory.Exists(pathIn))) + return String.Empty; + if (action == CUEAction.Verify && _config.arLogToSourceFolder) + return Path.ChangeExtension(pathIn, ".cue"); + if (action == CUEAction.CreateDummyCUE) + return Path.ChangeExtension(pathIn, ".cue"); + if (action == CUEAction.CorrectFilenames) + return pathIn; + + if (_config.detectHDCD && _config.decodeHDCD && (!ext.StartsWith(".lossy.") || !_config.decodeHDCDtoLW16)) + { + if (_config.decodeHDCDto24bit) + ext = ".24bit" + ext; + else + ext = ".20bit" + ext; + } + + if (pathIn != null) + { + vars.Add("path", pathIn); + try + { + vars.Add("filename", Path.GetFileNameWithoutExtension(pathIn)); + vars.Add("filename_ext", Path.GetFileName(pathIn)); + vars.Add("directoryname", General.EmptyStringToNull(Path.GetDirectoryName(pathIn))); + } + catch { } + } + vars.Add("music", Environment.GetFolderPath(Environment.SpecialFolder.MyMusic)); + string artist = cueSheet == null ? "Artist" : cueSheet.Metadata.Artist == "" ? "Unknown Artist" : cueSheet.Metadata.Artist; + string album = cueSheet == null ? "Album" : cueSheet.Metadata.Title == "" ? "Unknown Title" : cueSheet.Metadata.Title; + vars.Add("artist", General.EmptyStringToNull(_config.CleanseString(artist))); + vars.Add("album", General.EmptyStringToNull(_config.CleanseString(album))); + + if (cueSheet != null) + { + vars.Add("year", General.EmptyStringToNull(_config.CleanseString(cueSheet.Metadata.Year))); + vars.Add("catalog", General.EmptyStringToNull(_config.CleanseString(cueSheet.Metadata.Barcode))); + vars.Add("label", General.EmptyStringToNull(_config.CleanseString(cueSheet.Metadata.Label))); + vars.Add("country", General.EmptyStringToNull(_config.CleanseString(cueSheet.Metadata.Country))); + vars.Add("releasedate", General.EmptyStringToNull(_config.CleanseString(cueSheet.Metadata.ReleaseDate))); + vars.Add("discname", General.EmptyStringToNull(_config.CleanseString(cueSheet.Metadata.DiscName))); + vars.Add("discnumber", General.EmptyStringToNull(_config.CleanseString(cueSheet.Metadata.DiscNumber01))); + vars.Add("totaldiscs", General.EmptyStringToNull(_config.CleanseString(cueSheet.Metadata.TotalDiscs))); + vars.Add("releasedateandlabel", General.EmptyStringToNull(_config.CleanseString(cueSheet.Metadata.ReleaseDateAndLabel))); + vars.Add("discnumberandname", General.EmptyStringToNull(_config.CleanseString(cueSheet.Metadata.DiscNumberAndName.Replace("/", " of ")))); + NameValueCollection tags = cueSheet.Tags; + if (tags != null) + foreach (string tag in tags.AllKeys) + { + string key = tag.ToLower(); + string val = tags[tag]; + if (vars.Get(key) == null && val != null && val != "") + vars.Add(key, _config.CleanseString(val)); + } + } + + vars.Add("unique", null); + + string outputPath = ""; + for (int maxLen = 260; maxLen >= 16; maxLen--) + { + outputPath = General.ReplaceMultiple(format, vars, "unique", pathOut => File.Exists(Path.ChangeExtension(pathOut, ext)), maxLen); + if (outputPath == "" || outputPath == null) + return ""; + try { outputPath = Path.ChangeExtension(outputPath, ext); } + catch { return ""; } + if (outputPath.Length < 260) + return outputPath; + } + return outputPath; + } + + private bool CheckIfFileExists(string output) + { + return File.Exists(Path.Combine(OutputDir, output)); + } + + public void GenerateFilenames(AudioEncoderType audioEncoderType, string format, string outputPath) + { + _audioEncoderType = audioEncoderType; + _outputLossyWAV = format.StartsWith("lossy."); + _outputFormat = format; + _outputPath = outputPath; + + string extension = "." + format; + string filename; + int iTrack; + + NameValueCollection vars = new NameValueCollection(); + vars.Add("unique", null); + vars.Add("album artist", General.EmptyStringToNull(_config.CleanseString(Metadata.Artist))); + vars.Add("artist", General.EmptyStringToNull(_config.CleanseString(Metadata.Artist))); + vars.Add("album", General.EmptyStringToNull(_config.CleanseString(Metadata.Title))); + vars.Add("year", General.EmptyStringToNull(_config.CleanseString(Metadata.Year))); + vars.Add("catalog", General.EmptyStringToNull(_config.CleanseString(Metadata.Barcode))); + vars.Add("discnumber", General.EmptyStringToNull(_config.CleanseString(Metadata.DiscNumber01))); + vars.Add("totaldiscs", General.EmptyStringToNull(_config.CleanseString(Metadata.TotalDiscs))); + vars.Add("filename", Path.GetFileNameWithoutExtension(outputPath)); + vars.Add("tracknumber", null); + vars.Add("title", null); + + if (_config.detectHDCD && _config.decodeHDCD && (!_outputLossyWAV || !_config.decodeHDCDtoLW16)) + { + if (_config.decodeHDCDto24bit) + extension = ".24bit" + extension; + else + extension = ".20bit" + extension; + } + + ArLogFileName = General.ReplaceMultiple(_config.ArLogFilenameFormat, vars, "unique", CheckIfFileExists, -1) + ?? vars["%filename%"] + ".accurip"; + AlArtFileName = General.ReplaceMultiple(_config.AlArtFilenameFormat, vars, "unique", CheckIfFileExists, -1) + ?? "folder.jpg"; + + if (OutputStyle == CUEStyle.SingleFileWithCUE) + SingleFilename = Path.ChangeExtension(Path.GetFileName(outputPath), extension); + else if (_config.keepOriginalFilenames && HasSingleFilename) + SingleFilename = Path.ChangeExtension(SingleFilename, extension); + else + SingleFilename = (General.ReplaceMultiple(_config.singleFilenameFormat, vars, -1) ?? "range") + extension; + + 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 = Metadata.Tracks[htoa ? 0 : iTrack].Artist; + string title = htoa ? "(HTOA)" : Metadata.Tracks[iTrack].Title; + + vars["tracknumber"] = trackStr; + vars["artist"] = General.EmptyStringToNull(_config.CleanseString(artist)) ?? vars["album artist"]; + vars["title"] = General.EmptyStringToNull(_config.CleanseString(title)); + + filename = ""; + for (int maxLen = 260; maxLen >= 16; maxLen--) + { + filename = General.ReplaceMultiple(_config.trackFilenameFormat, vars, maxLen); + if (filename == "" || filename == null) + { + filename = vars["tracknumber"]; + break; + } + if (OutputDir.Length + filename.Length < 255) + break; + } + + filename = filename + extension; + + if (htoa) + HTOAFilename = filename; + else + TrackFilenames[iTrack] = filename; + } + } + + if (OutputStyle == CUEStyle.SingleFile || OutputStyle == CUEStyle.SingleFileWithCUE) + { + _destPaths = new string[1]; + _destPaths[0] = Path.Combine(OutputDir, _singleFilename); + } + else + { + bool htoaToFile = ((OutputStyle == CUEStyle.GapsAppended) && _config.preserveHTOA && + (_toc.Pregap != 0)); + _destPaths = new string[TrackCount + (htoaToFile ? 1 : 0)]; + if (htoaToFile) + _destPaths[0] = Path.Combine(OutputDir, _htoaFilename); + for (int i = 0; i < TrackCount; i++) + _destPaths[i + (htoaToFile ? 1 : 0)] = Path.Combine(OutputDir, _trackFilenames[i]); + } + } + + public List OutputExists() + { + List outputExists = new List(); + bool outputCUE = Action == CUEAction.Encode && (OutputStyle != CUEStyle.SingleFileWithCUE || _config.createCUEFileWhenEmbedded); + bool outputAudio = Action == CUEAction.Encode && _audioEncoderType != AudioEncoderType.NoAudio; + if (outputCUE) + outputExists.Add(_outputPath); + if (_useAccurateRip && ( + (Action == CUEAction.Encode && _config.writeArLogOnConvert) || + (Action == CUEAction.Verify && _config.writeArLogOnVerify))) + outputExists.Add(Path.Combine(OutputDir, ArLogFileName)); + if (outputAudio) + { + if (_config.extractAlbumArt && AlbumArt != null && AlbumArt.Count != 0) + outputExists.Add(Path.Combine(OutputDir, AlArtFileName)); + if (OutputStyle == CUEStyle.SingleFile || OutputStyle == CUEStyle.SingleFileWithCUE) + outputExists.Add(Path.Combine(OutputDir, SingleFilename)); + else + { + if (OutputStyle == CUEStyle.GapsAppended && _config.preserveHTOA) + outputExists.Add(Path.Combine(OutputDir, HTOAFilename)); + for (int i = 0; i < TrackCount; i++) + outputExists.Add(Path.Combine(OutputDir, TrackFilenames[i])); + } + } + outputExists.RemoveAll(path => !File.Exists(path)); + return outputExists; + } + + private int GetSampleLength(string path, out TagLib.File fileInfo) + { + ShowProgress("Analyzing input file...", 0.0, path, null); + + if (Path.GetExtension(path).ToLower() == ".dummy" || Path.GetExtension(path).ToLower() == ".bin") + { + fileInfo = null; + } + else + { + TagLib.UserDefined.AdditionalFileTypes.Config = _config; + TagLib.File.IFileAbstraction file = _isArchive + ? (TagLib.File.IFileAbstraction)new ArchiveFileAbstraction(this, path) + : (TagLib.File.IFileAbstraction)new TagLib.File.LocalFileAbstraction(path); + fileInfo = TagLib.File.Create(file); + } + + IAudioSource audioSource = AudioReadWrite.GetAudioSource(path, _isArchive ? OpenArchive(path, true) : null, _config); + try + { + if (!audioSource.PCM.IsRedBook || + audioSource.Length <= 0 || + audioSource.Length >= Int32.MaxValue) + throw new Exception("Audio format is invalid."); + return (int)audioSource.Length; + } + finally + { + audioSource.Close(); + } + } + + public static void WriteText(string path, string text, Encoding encoding) + { + StreamWriter sw1 = new StreamWriter(path, false, encoding); + sw1.Write(text); + sw1.Close(); + } + + public static void WriteText(string path, string text) + { + bool utf8Required = CUESheet.Encoding.GetString(CUESheet.Encoding.GetBytes(text)) != text; + WriteText(path, text, utf8Required ? Encoding.UTF8 : CUESheet.Encoding); + } + + public string LOGContents + { + get + { + return _ripperLog; + } + } + + public bool PrintErrors(StringWriter logWriter, uint tr_start, uint len) + { + uint tr_end = (len + 74) / 75; + int errCount = 0; + for (uint iSecond = 0; iSecond < tr_end; iSecond++) + { + uint sec_start = tr_start + iSecond * 75; + uint sec_end = Math.Min(sec_start + 74, tr_start + len - 1); + bool fError = false; + for (uint iSector = sec_start; iSector <= sec_end; iSector++) + if (_ripper.Errors[(int)iSector - (int)_toc[_toc.FirstAudio][0].Start]) + fError = true; + if (fError) + { + uint end = tr_end - 1; + for (uint jSecond = iSecond + 1; jSecond < tr_end; jSecond++) + { + uint jsec_start = tr_start + jSecond * 75; + uint jsec_end = Math.Min(jsec_start + 74, tr_start + len - 1); + bool jfError = false; + for (uint jSector = jsec_start; jSector <= jsec_end; jSector++) + if (_ripper.Errors[(int)jSector - (int)_toc[_toc.FirstAudio][0].Start]) + jfError = true; + if (!jfError) + { + end = jSecond - 1; + break; + } + } + if (errCount == 0) + logWriter.WriteLine(); + if (errCount++ > 20) + break; + //"Suspicious position 0:02:20" + //" Suspicious position 0:02:23 - 0:02:24" + string s1 = CDImageLayout.TimeToString("0:{0:00}:{1:00}", iSecond * 75); + string s2 = CDImageLayout.TimeToString("0:{0:00}:{1:00}", end * 75); + if (iSecond == end) + logWriter.WriteLine(" Suspicious position {0}", s1); + else + logWriter.WriteLine(" Suspicious position {0} - {1}", s1, s2); + iSecond = end + 1; + } + } + return errCount > 0; + } + + public void CreateExactAudioCopyLOG() + { + StringWriter logWriter = new StringWriter(CultureInfo.InvariantCulture); + string eacHeader = "{7}\r\n" + + "\r\n" + + "EAC extraction logfile from {0:d'.' MMMM yyyy', 'H':'mm}\r\n" + + "\r\n" + + "{1} / {2}\r\n" + + "\r\n" + + "Used drive : {3} Adapter: 1 ID: 0\r\n" + + "\r\n" + + "Read mode : {4}\r\n" + + "Utilize accurate stream : Yes\r\n" + + "Defeat audio cache : Yes\r\n" + + "Make use of C2 pointers : No\r\n" + + "\r\n" + + "Read offset correction : {5}\r\n" + + "Overread into Lead-In and Lead-Out : No\r\n" + + "Fill up missing offset samples with silence : Yes\r\n" + + "Delete leading and trailing silent blocks : No\r\n" + + "Null samples used in CRC calculations : Yes\r\n" + + "Used interface : Native Win32 interface for Win NT & 2000\r\n" + + "{6}" + + "\r\n" + + "Used output format : Internal WAV Routines\r\n" + + "Sample format : 44.100 Hz; 16 Bit; Stereo\r\n"; + + logWriter.WriteLine(eacHeader, + DateTime.Now, + Metadata.Artist, Metadata.Title, + _ripper.EACName, + _ripper.CorrectionQuality > 0 ? "Secure" : "Burst", + _ripper.DriveOffset, + (OutputStyle == CUEStyle.SingleFile || OutputStyle == CUEStyle.SingleFileWithCUE) ? "" : + "Gap handling : " + + (_ripper.GapsDetected ? "Appended to previous track\r\n" : "Not detected, thus appended to previous track\r\n"), + _ripper.RipperVersion); // "Exact Audio Copy V0.99 prebeta 4 from 23. January 2008" + + logWriter.WriteLine(); + logWriter.WriteLine("TOC of the extracted CD"); + logWriter.WriteLine(); + logWriter.Write(TOCContents()); + logWriter.WriteLine(); + + bool htoaToFile = ((OutputStyle == CUEStyle.GapsAppended) && _config.preserveHTOA && + (_toc.Pregap != 0)); + int accurateTracks = 0, knownTracks = 0; + bool wereErrors = false; + if (OutputStyle != CUEStyle.SingleFile && OutputStyle != CUEStyle.SingleFileWithCUE) + { + logWriter.WriteLine(); + for (int track = 0; track < _toc.AudioTracks; track++) + { + logWriter.WriteLine("Track {0,2}", track + 1); + logWriter.WriteLine(); + logWriter.WriteLine(" Filename {0}", Path.ChangeExtension(Path.GetFullPath(_destPaths[track + (htoaToFile ? 1 : 0)]), ".wav")); + if (_toc[track + _toc.FirstAudio].Pregap > 0 || track + _toc.FirstAudio == 1) + { + logWriter.WriteLine(); + logWriter.WriteLine(" Pre-gap length 0:{0}.{1:00}", CDImageLayout.TimeToString("{0:00}:{1:00}", _toc[track + _toc.FirstAudio].Pregap + (track + _toc.FirstAudio == 1 ? 150U : 0U)), (_toc[track + _toc.FirstAudio].Pregap % 75) * 100 / 75); + } + + wereErrors |= PrintErrors(logWriter, _toc[track + _toc.FirstAudio].Start, _toc[track + _toc.FirstAudio].Length); + + logWriter.WriteLine(); + logWriter.WriteLine(" Peak level {0:F1} %", (_arVerify.PeakLevel(track + 1) * 1000 / 65534) * 0.1); + logWriter.WriteLine(" Track quality 100.0 %"); + logWriter.WriteLine(" Test CRC {0:X8}", _arVerify.CRC32(track + 1)); + logWriter.WriteLine(" Copy CRC {0:X8}", _arVerify.CRC32(track + 1)); + if (_arVerify.Total(track) == 0) + logWriter.WriteLine(" Track not present in AccurateRip database"); + else + { + knownTracks++; + if (_arVerify.Confidence(track) == 0) + logWriter.WriteLine(" Cannot be verified as accurate (confidence {0}) [{1:X8}], AccurateRip returned [{2:X8}]", _arVerify.Total(track), _arVerify.CRC(track), _arVerify.DBCRC(track)); + else + { + logWriter.WriteLine(" Accurately ripped (confidence {0}) [{1:X8}]", _arVerify.Confidence(track), _arVerify.CRC(track)); + accurateTracks++; + } + } + logWriter.WriteLine(" Copy OK"); + logWriter.WriteLine(); + } + } + else + { + logWriter.WriteLine(); + logWriter.WriteLine("Range status and errors"); + logWriter.WriteLine(); + logWriter.WriteLine("Selected range"); + logWriter.WriteLine(); + logWriter.WriteLine(" Filename {0}", Path.ChangeExtension(Path.GetFullPath(_destPaths[0]), ".wav")); + wereErrors = PrintErrors(logWriter, _toc[_toc.FirstAudio][0].Start, _toc.AudioLength); + logWriter.WriteLine(); + logWriter.WriteLine(" Peak level {0:F1} %", (_arVerify.PeakLevel() * 1000 / 65535) * 0.1); + logWriter.WriteLine(" Range quality 100.0 %"); + logWriter.WriteLine(" Test CRC {0:X8}", _arVerify.CRC32(0)); + logWriter.WriteLine(" Copy CRC {0:X8}", _arVerify.CRC32(0)); + logWriter.WriteLine(" Copy OK"); + logWriter.WriteLine(); + if (wereErrors) + logWriter.WriteLine("There were errors"); + else + logWriter.WriteLine("No errors occurred"); + logWriter.WriteLine(); + logWriter.WriteLine(); + logWriter.WriteLine("AccurateRip summary"); + logWriter.WriteLine(); + for (int track = 0; track < _toc.AudioTracks; track++) + { + if (_arVerify.Total(track) == 0) + logWriter.WriteLine("Track {0,2} not present in database", track + 1); + else + { + knownTracks++; + if (_arVerify.Confidence(track) == 0) + logWriter.WriteLine("Track {3,2} cannot be verified as accurate (confidence {0}) [{1:X8}], AccurateRip returned [{2:X8}]", _arVerify.Total(track), _arVerify.CRC(track), _arVerify.DBCRC(track), track + 1); + else + { + logWriter.WriteLine("Track {2,2} accurately ripped (confidence {0}) [{1:X8}]", _arVerify.Confidence(track), _arVerify.CRC(track), track + 1); + accurateTracks++; + } + } + } + } + logWriter.WriteLine(); + if (knownTracks == 0) + logWriter.WriteLine("None of the tracks are present in the AccurateRip database"); + else if (accurateTracks == 0) + { + logWriter.WriteLine("No tracks could be verified as accurate"); + logWriter.WriteLine("You may have a different pressing from the one(s) in the database"); + } + else if (accurateTracks == TrackCount) + logWriter.WriteLine("All tracks accurately ripped"); + else + { + logWriter.WriteLine("{0,2} track(s) accurately ripped", accurateTracks); + if (TrackCount - knownTracks > 0) + logWriter.WriteLine("{0,2} track(s) not present in the AccurateRip database", TrackCount - knownTracks); + logWriter.WriteLine(); + logWriter.WriteLine("Some tracks could not be verified as accurate"); + } + logWriter.WriteLine(); + if (OutputStyle != CUEStyle.SingleFile && OutputStyle != CUEStyle.SingleFileWithCUE) + { + if (wereErrors) + logWriter.WriteLine("There were errors"); + else + logWriter.WriteLine("No errors occurred"); + logWriter.WriteLine(); + } + logWriter.WriteLine("End of status report"); + logWriter.Close(); + _ripperLog = logWriter.ToString(); + } + + public void CreateRipperLOG() + { + if (!_isCD || _ripper == null || _ripperLog != null) + return; + if (_config.createEACLOG) + { + CreateExactAudioCopyLOG(); + return; + } + StringWriter logWriter = new StringWriter(CultureInfo.InvariantCulture); + logWriter.WriteLine("{0}", _ripper.RipperVersion); + logWriter.WriteLine("Extraction logfile from : {0}", DateTime.Now); + logWriter.WriteLine("Used drive : {0}", _ripper.ARName); + logWriter.WriteLine("Read offset correction : {0}", _ripper.DriveOffset); + logWriter.WriteLine("Read command : {0}", _ripper.CurrentReadCommand); + logWriter.WriteLine("Secure mode : {0}", _ripper.CorrectionQuality); + logWriter.WriteLine("Disk length : {0}", CDImageLayout.TimeToString(_toc.AudioLength)); + logWriter.WriteLine("AccurateRip : {0}", _arVerify.ARStatus == null ? "ok" : _arVerify.ARStatus); + if (hdcdDecoder != null && string.Format("{0:s}", hdcdDecoder) != "") + logWriter.WriteLine("HDCD : {0:f}", hdcdDecoder); + logWriter.WriteLine(); + logWriter.WriteLine("TOC of the extracted CD"); + logWriter.WriteLine(); + logWriter.Write(TOCContents()); + logWriter.WriteLine(); + logWriter.WriteLine(" Track | Pregap | Indexes"); + logWriter.WriteLine(" ---------------------------------------------------------"); + for (int track = 1; track <= _toc.TrackCount; track++) + logWriter.WriteLine("{0,9} | {1,8} | {2,2}", + _toc[track].Number, + CDImageLayout.TimeToString(_toc[track].Pregap + (track == 1 ? 150U : 0U)), + _toc[track].LastIndex); + logWriter.WriteLine(); + logWriter.WriteLine("Destination files"); + foreach (string path in _destPaths) + logWriter.WriteLine(" {0}", path); + bool wereErrors = PrintErrors(logWriter, _toc[_toc.FirstAudio][0].Start, _toc.AudioLength); + if (wereErrors) + { + logWriter.WriteLine(); + if (wereErrors) + logWriter.WriteLine("There were errors"); + else + logWriter.WriteLine("No errors occurred"); + } + if (_useCUEToolsDB) + { + logWriter.WriteLine(); + GenerateCTDBLog(logWriter); + } + if (_useAccurateRip) + { + logWriter.WriteLine(); + logWriter.WriteLine("AccurateRip summary"); + logWriter.WriteLine(); + _arVerify.GenerateFullLog(logWriter, true, AccurateRipVerify.CalculateAccurateRipId(_toc)); + } + logWriter.WriteLine(); + logWriter.WriteLine("End of status report"); + logWriter.Close(); + _ripperLog = logWriter.ToString(); + } + + public string M3UContents(CUEStyle style) + { + StringWriter sw = new StringWriter(); + if (style == CUEStyle.GapsAppended && _config.preserveHTOA && _toc.Pregap != 0) + WriteLine(sw, 0, _htoaFilename); + for (int iTrack = 0; iTrack < TrackCount; iTrack++) + WriteLine(sw, 0, _trackFilenames[iTrack]); + sw.Close(); + return sw.ToString(); + } + + public string TOCContents() + { + StringWriter sw = new StringWriter(); + sw.WriteLine(" Track | Start | Length | Start sector | End sector "); + sw.WriteLine(" ---------------------------------------------------------"); + for (int track = 1; track <= _toc.TrackCount; track++) + sw.WriteLine("{0,9} | {1,8} | {2,8} | {3,8} | {4,8} ", + track, // _toc[track].Number, + CDImageLayout.TimeToString("{0,2}:{1:00}.{2:00}", _toc[track].Start), + CDImageLayout.TimeToString("{0,2}:{1:00}.{2:00}", _toc[track].Length), + _toc[track].Start, + _toc[track].End); + return sw.ToString(); + } + + public string CUESheetContents() + { + CUEStyle style = _hasEmbeddedCUESheet ? CUEStyle.SingleFile + : _hasSingleFilename ? CUEStyle.SingleFileWithCUE + : CUEStyle.GapsAppended; + bool htoaToFile = _hasHTOAFilename; + return CUESheetContents(style, htoaToFile); + } + + public string CUESheetContents(CUEStyle style) + { + return CUESheetContents(style, (style == CUEStyle.GapsAppended && _config.preserveHTOA && _toc.Pregap != 0)); + } + + public string CUESheetContents(CUEStyle style, bool htoaToFile) + { + StringWriter sw = new StringWriter(); + int i, iTrack, iIndex; + + uint timeRelativeToFileStart = 0; + + General.SetCUELine(_attributes, "PERFORMER", Metadata.Artist, true); + General.SetCUELine(_attributes, "TITLE", Metadata.Title, true); + General.SetCUELine(_attributes, "CATALOG", Metadata.Barcode, false); + General.SetCUELine(_attributes, "REM", "DATE", Metadata.Year, false); + General.SetCUELine(_attributes, "REM", "DISCNUMBER", Metadata.DiscNumber, false); + General.SetCUELine(_attributes, "REM", "TOTALDISCS", Metadata.TotalDiscs, false); + General.SetCUELine(_attributes, "REM", "GENRE", Metadata.Genre, true); + for (i = 0; i < Tracks.Count; i++) + { + General.SetCUELine(Tracks[i].Attributes, "PERFORMER", Metadata.Tracks[i].Artist, true); + General.SetCUELine(Tracks[i].Attributes, "TITLE", Metadata.Tracks[i].Title, true); + General.SetCUELine(Tracks[i].Attributes, "ISRC", Metadata.Tracks[i].ISRC, false); + } + + using (sw) + { + if (_config.writeArTagsOnEncode) + WriteLine(sw, 0, "REM ACCURATERIPID " + (_accurateRipId ?? AccurateRipVerify.CalculateAccurateRipId(_toc))); + + 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++) + { + if ((style == CUEStyle.GapsPrepended) || + (style == CUEStyle.GapsLeftOut) || + ((style == CUEStyle.GapsAppended) && + ((_toc[_toc.FirstAudio + iTrack].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 < _tracks[iTrack].Attributes.Count; i++) + WriteLine(sw, 2, _tracks[iTrack].Attributes[i]); + + if (_toc[_toc.FirstAudio + iTrack].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[_toc.FirstAudio + iTrack].Pregap)); + else + { + WriteLine(sw, 2, String.Format("INDEX 00 {0}", CDImageLayout.TimeToString(timeRelativeToFileStart))); + timeRelativeToFileStart += _toc[_toc.FirstAudio + iTrack].Pregap; + if (style == CUEStyle.GapsAppended) + { + WriteLine(sw, 0, String.Format("FILE \"{0}\" WAVE", _trackFilenames[iTrack])); + timeRelativeToFileStart = 0; + } + } + } + for (iIndex = 1; iIndex <= _toc[_toc.FirstAudio + iTrack].LastIndex; iIndex++) + { + WriteLine(sw, 2, String.Format("INDEX {0:00} {1}", iIndex, CDImageLayout.TimeToString(timeRelativeToFileStart))); + timeRelativeToFileStart += _toc.IndexLength(_toc.FirstAudio + iTrack, iIndex); + } + } + } + sw.Close(); + return sw.ToString(); + } + + public void GenerateCTDBLog(TextWriter sw) + { + sw.WriteLine("[CTDB TOCID: {0}] {1}.", _toc.TOCID, _CUEToolsDB.DBStatus ?? "found"); + if (!_processed) + return; + if (_CUEToolsDB.SubStatus != null) + sw.WriteLine("CUETools DB: {0}.", _CUEToolsDB.SubStatus); + if (_CUEToolsDB.DBStatus == null) + sw.WriteLine(" [ CTDBID ] Status"); + foreach (DBEntry entry in _CUEToolsDB.Entries) + { + string confFormat = (_CUEToolsDB.Total < 10) ? "{0:0}/{1:0}" : + (_CUEToolsDB.Total < 100) ? "{0:00}/{1:00}" : "{0:000}/{1:000}"; + string conf = string.Format(confFormat, entry.conf, _CUEToolsDB.Total); + string dataTrackInfo = !entry.toc[entry.toc.TrackCount].IsAudio ? string.Format("CD-Extra data track length {0}", entry.toc[entry.toc.TrackCount].LengthMSF) : + !entry.toc[1].IsAudio ? string.Format("Playstation type data track length {0}", entry.toc[entry.toc.FirstAudio].StartMSF) : "Has no data track"; + string status = + entry.toc.Pregap != _toc.Pregap ? string.Format("Has pregap length {0}", CDImageLayout.TimeToString(entry.toc.Pregap)) : + entry.toc.AudioLength != _toc.AudioLength ? string.Format("Has audio length {0}", CDImageLayout.TimeToString(entry.toc.AudioLength)) : + ((entry.toc.TrackOffsets != _toc.TrackOffsets) ? dataTrackInfo + ", " : "") + + ((!entry.hasErrors) ? "Accurately ripped" : + //((!entry.hasErrors) ? string.Format("Accurately ripped, offset {0}", -entry.offset) : + entry.canRecover ? string.Format("Differs in {0} samples @{1}", entry.repair.CorrectableErrors, entry.repair.AffectedSectors) : + (entry.httpStatus == 0 || entry.httpStatus == HttpStatusCode.OK) ? "No match" : + entry.httpStatus.ToString()); + sw.WriteLine(" [{0:x8}] ({1}) {2}", entry.crc, conf, status); + } + } + + public readonly static string CUEToolsVersion = "2.1.2a"; + + public void GenerateAccurateRipLog(TextWriter sw) + { + sw.WriteLine("[CUETools log; Date: {0}; Version: {1}]", DateTime.Now, CUEToolsVersion); + if (PreGapLength != 0) + sw.WriteLine("Pregap length {0}.", PreGapLengthMSF); + if (!_toc[1].IsAudio) + sw.WriteLine("Playstation type data track length {0}.", _toc[_toc.FirstAudio].StartMSF); + if (!_toc[_toc.TrackCount].IsAudio) + sw.WriteLine("CD-Extra data track length {0}.", + _toc[_toc.TrackCount].Length == 0 && _minDataTrackLength.HasValue ? + CDImageLayout.TimeToString(_minDataTrackLength.Value) + " - " + CDImageLayout.TimeToString(_minDataTrackLength.Value + 74) : + _toc[_toc.TrackCount].LengthMSF); + if (_cddbDiscIdTag != null && AccurateRipVerify.CalculateCDDBId(_toc).ToUpper() != _cddbDiscIdTag.ToUpper() && !_minDataTrackLength.HasValue) + sw.WriteLine("CDDBId mismatch: {0} vs {1}", _cddbDiscIdTag.ToUpper(), AccurateRipVerify.CalculateCDDBId(_toc).ToUpper()); + if (_accurateRipId != null && AccurateRipVerify.CalculateAccurateRipId(_toc) != _accurateRipId) + sw.WriteLine("Using preserved id, actual id is {0}.", AccurateRipVerify.CalculateAccurateRipId(_toc)); + 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 (!_processed) + { + if (_useCUEToolsDB) GenerateCTDBLog(sw); + sw.WriteLine("[AccurateRip ID: {0}] {1}.", _accurateRipId ?? AccurateRipVerify.CalculateAccurateRipId(_toc), _arVerify.ARStatus ?? "found"); + return; + } + + if (hdcdDecoder != null && string.Format("{0:s}", hdcdDecoder) != "") + sw.WriteLine("HDCD: {0:f}", hdcdDecoder); + if (0 != _writeOffset) + sw.WriteLine("Offset applied: {0}", _writeOffset); + if (_useCUEToolsDBFix)// && _CUEToolsDB.SelectedEntry != null) + sw.WriteLine("CUETools DB: corrected {0} errors.", _CUEToolsDB.SelectedEntry.repair.CorrectableErrors); + else if (_useCUEToolsDB) + GenerateCTDBLog(sw); + _arVerify.GenerateFullLog(sw, _config.arLogVerbose, _accurateRipId ?? AccurateRipVerify.CalculateAccurateRipId(_toc)); + } + + public string GenerateAccurateRipStatus() + { + string prefix = ""; + if (hdcdDecoder != null && string.Format("{0:s}", hdcdDecoder) != "") + prefix += string.Format("{0:s}", hdcdDecoder); + if (_useAccurateRip) + { + if (prefix != "") prefix += ", "; + prefix += "AR: "; + if (_arVerify.ARStatus != null) + prefix += _arVerify.ARStatus; + else + { + uint tracksMatch = 0; + int bestOffset = 0; + FindBestOffset(1, false, out tracksMatch, out bestOffset); + if (bestOffset != 0) + prefix += string.Format("offset {0}, ", bestOffset); + if (_arVerify.WorstConfidence() > 0) + prefix += string.Format("rip accurate ({0}/{1})", _arVerify.WorstConfidence(), _arVerify.WorstTotal()); + else + prefix += string.Format("rip not accurate ({0}/{1})", 0, _arVerify.WorstTotal()); + } + } + if (!_useCUEToolsDBFix && _useCUEToolsDB) + { + if (prefix != "") prefix += ", "; + prefix += "CTDB: " + CTDB.Status; + } + if (_isCD && _ripper.ErrorsCount > 0) + { + if (prefix != "") prefix += ", "; + prefix += "ripper found " + _ripper.ErrorsCount + " suspicious sectors"; + } + if (prefix == "") + prefix += "done"; + return prefix; + } + + public void GenerateAccurateRipTagsForTrack(NameValueCollection tags, int bestOffset, int iTrack, string prefix) + { + tags.Add(String.Format("{0}ACCURATERIPCRC", prefix), String.Format("{0:x8}", _arVerify.CRC(iTrack, 0))); + tags.Add(String.Format("{0}AccurateRipDiscId", prefix), String.Format("{0:000}-{1}-{2:00}", TrackCount, _accurateRipId ?? AccurateRipVerify.CalculateAccurateRipId(_toc), iTrack + 1)); + tags.Add(String.Format("{0}ACCURATERIPCOUNT", prefix), String.Format("{0}", _arVerify.Confidence(iTrack, 0))); + tags.Add(String.Format("{0}ACCURATERIPCOUNTALLOFFSETS", prefix), String.Format("{0}", _arVerify.SumConfidence(iTrack))); + tags.Add(String.Format("{0}ACCURATERIPTOTAL", prefix), String.Format("{0}", _arVerify.Total(iTrack))); + if (bestOffset != 0) + tags.Add(String.Format("{0}ACCURATERIPCOUNTWITHOFFSET", prefix), String.Format("{0}", _arVerify.Confidence(iTrack, bestOffset))); + } + + public void GenerateAccurateRipTags(NameValueCollection tags, int bestOffset, int iTrack) + { + tags.Add("ACCURATERIPID", _accurateRipId ?? AccurateRipVerify.CalculateAccurateRipId(_toc)); + if (bestOffset != 0) + tags.Add("ACCURATERIPOFFSET", String.Format("{1}{0}", bestOffset, bestOffset > 0 ? "+" : "")); + if (iTrack != -1) + GenerateAccurateRipTagsForTrack(tags, bestOffset, iTrack, ""); + else + for (iTrack = 0; iTrack < TrackCount; iTrack++) + { + GenerateAccurateRipTagsForTrack(tags, 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]); + } + + public 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 + || offset == 0 && _arVerify.CRCV2(iTrack) == _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 string Go() + { + int[] destLengths; + bool htoaToFile = ((OutputStyle == CUEStyle.GapsAppended) && _config.preserveHTOA && + (_toc.Pregap != 0)); + + if (_isCD) + DetectGaps(); + + if (_usePregapForFirstTrackInSingleFile) + throw new Exception("UsePregapForFirstTrackInSingleFile is not supported for writing audio files."); + + if (_action != CUEAction.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(OutputStyle); + + // Lookup(); + + if (_action != CUEAction.Verify) + { + if (!Directory.Exists(OutputDir)) + Directory.CreateDirectory(OutputDir); + } + + if (_action == CUEAction.Encode) + { + string cueContents = CUESheetContents(OutputStyle); + if (_config.createEACLOG && _isCD) + cueContents = CUESheet.Encoding.GetString(CUESheet.Encoding.GetBytes(cueContents)); + if (OutputStyle == CUEStyle.SingleFileWithCUE && _config.createCUEFileWhenEmbedded) + WriteText(Path.ChangeExtension(_outputPath, ".cue"), cueContents); + else + WriteText(_outputPath, cueContents); + } + + if (_action == CUEAction.Verify) + VerifyAudio(); + // WriteAudioFilesPass(OutputDir, OutputStyle, destLengths, htoaToFile, _action == CUEAction.Verify); + else if (_audioEncoderType != AudioEncoderType.NoAudio) + WriteAudioFilesPass(OutputDir, OutputStyle, destLengths, htoaToFile, _action == CUEAction.Verify); + + if (_useCUEToolsDB && !_useCUEToolsDBFix) + _CUEToolsDB.DoVerify(); + + _processed = true; + + CreateRipperLOG(); + + if (_action == CUEAction.Verify && _useLocalDB) + { + var now = DateTime.Now; + var entry = OpenLocalDBEntry(); + entry.Status = this.GenerateAccurateRipStatus(); + entry.ARConfidence = _useAccurateRip ? _arVerify.WorstConfidence() : 0; + entry.CTDBConfidence = _useCUEToolsDB && !_useCUEToolsDBFix ? CTDB.Confidence : 0; + entry.Log = AccurateRipLog; + entry.VerificationDate = + _useAccurateRip && + (_arVerify.ExceptionStatus == WebExceptionStatus.Success || + (_arVerify.ExceptionStatus == WebExceptionStatus.ProtocolError && + _arVerify.ResponseStatus == HttpStatusCode.NotFound + ) + ) ? now : DateTime.MinValue; + entry.CTDBVerificationDate = + _useCUEToolsDB && + !_useCUEToolsDBFix && + (CTDB.QueryExceptionStatus == WebExceptionStatus.Success || + (CTDB.QueryExceptionStatus == WebExceptionStatus.ProtocolError && + CTDB.QueryResponseStatus == HttpStatusCode.NotFound + ) + ) ? now : DateTime.MinValue; + entry.OffsetSafeCRC = _arVerify.OffsetSafeCRC; + } + + if (_action == CUEAction.Encode) + { + uint tracksMatch = 0; + int bestOffset = 0; + + if (_useAccurateRip && + _config.writeArTagsOnEncode && + _arVerify.ExceptionStatus == WebExceptionStatus.Success) + FindBestOffset(1, true, out tracksMatch, out bestOffset); + + if (_config.createEACLOG && _ripperLog != null) + _ripperLog = CUESheet.Encoding.GetString(CUESheet.Encoding.GetBytes(_ripperLog)); + + if (_ripperLog != null) + WriteText(Path.ChangeExtension(_outputPath, ".log"), _ripperLog); + else + if (_eacLog != null && _config.extractLog) + WriteText(Path.ChangeExtension(_outputPath, ".log"), _eacLog); + + if (_audioEncoderType != AudioEncoderType.NoAudio && _config.extractAlbumArt) + ExtractAlbumArt(); + + bool fNeedAlbumArtist = false; + for (int iTrack = 1; iTrack < TrackCount; iTrack++) + if (Metadata.Tracks[iTrack].Artist != Metadata.Tracks[0].Artist) + fNeedAlbumArtist = true; + + if (OutputStyle == CUEStyle.SingleFileWithCUE || OutputStyle == CUEStyle.SingleFile) + { + if (_audioEncoderType != AudioEncoderType.NoAudio) + { + NameValueCollection tags = GenerateAlbumTags(bestOffset, OutputStyle == CUEStyle.SingleFileWithCUE, _ripperLog ?? _eacLog); + TagLib.UserDefined.AdditionalFileTypes.Config = _config; + TagLib.File fileInfo = TagLib.File.Create(new TagLib.File.LocalFileAbstraction(_destPaths[0])); + if (Tagging.UpdateTags(fileInfo, tags, _config)) + { + TagLib.File sourceFileInfo = _tracks[0]._fileInfo ?? _fileInfo; + + // first, use cue sheet information + if (_config.writeBasicTagsFromCUEData) + { + uint temp; + if (fileInfo.Tag.Album == null && Metadata.Title != "") + fileInfo.Tag.Album = Metadata.Title; + if (fNeedAlbumArtist && fileInfo.Tag.AlbumArtists.Length == 0 && Metadata.Artist != "") + fileInfo.Tag.AlbumArtists = new string[] { Metadata.Artist }; + if (!fNeedAlbumArtist && fileInfo.Tag.Performers.Length == 0 && Metadata.Artist != "") + fileInfo.Tag.Performers = new string[] { Metadata.Artist }; + if (fileInfo.Tag.Genres.Length == 0 && Metadata.Genre != "") + fileInfo.Tag.Genres = new string[] { Metadata.Genre }; + if (fileInfo.Tag.DiscCount == 0 && Metadata.TotalDiscs != "" && uint.TryParse(Metadata.TotalDiscs, out temp)) + fileInfo.Tag.DiscCount = temp; + if (fileInfo.Tag.Disc == 0 && Metadata.DiscNumber != "" && uint.TryParse(Metadata.DiscNumber, out temp)) + fileInfo.Tag.Disc = temp; + if (fileInfo.Tag.Year == 0 && Metadata.Year != "" && uint.TryParse(Metadata.Year, out temp)) + fileInfo.Tag.Year = temp; + } + + // fill up missing information from tags + if (_config.copyBasicTags && sourceFileInfo != null) + { + if (fileInfo.Tag.DiscCount == 0) + fileInfo.Tag.DiscCount = sourceFileInfo.Tag.DiscCount; // TODO: GetCommonTag? + if (fileInfo.Tag.Disc == 0) + fileInfo.Tag.Disc = sourceFileInfo.Tag.Disc; + //fileInfo.Tag.Performers = sourceFileInfo.Tag.Performers; + if (fileInfo.Tag.Album == null) + fileInfo.Tag.Album = sourceFileInfo.Tag.Album; + if (fileInfo.Tag.Performers.Length == 0) + fileInfo.Tag.Performers = sourceFileInfo.Tag.Performers; + if (fileInfo.Tag.AlbumArtists.Length == 0) + fileInfo.Tag.AlbumArtists = sourceFileInfo.Tag.AlbumArtists; + if (fileInfo.Tag.Genres.Length == 0) + fileInfo.Tag.Genres = sourceFileInfo.Tag.Genres; + if (fileInfo.Tag.Year == 0) + fileInfo.Tag.Year = sourceFileInfo.Tag.Year; + } + + if ((_config.embedAlbumArt || _config.CopyAlbumArt) && _albumArt.Count > 0) + fileInfo.Tag.Pictures = _albumArt.ToArray(); + + fileInfo.Save(); + } + } + } + else + { + if (_config.createM3U) + WriteText(Path.ChangeExtension(_outputPath, ".m3u"), M3UContents(OutputStyle)); + if (_audioEncoderType != AudioEncoderType.NoAudio) + for (int iTrack = 0; iTrack < TrackCount; iTrack++) + { + string path = _destPaths[iTrack + (htoaToFile ? 1 : 0)]; + NameValueCollection tags = GenerateTrackTags(iTrack, bestOffset); + TagLib.UserDefined.AdditionalFileTypes.Config = _config; + TagLib.File fileInfo = TagLib.File.Create(new TagLib.File.LocalFileAbstraction(path)); + if (Tagging.UpdateTags(fileInfo, tags, _config)) + { + TagLib.File sourceFileInfo = _tracks[iTrack]._fileInfo ?? _fileInfo; + + if (_config.writeBasicTagsFromCUEData) + { + uint temp; + fileInfo.Tag.TrackCount = (uint)TrackCount; + fileInfo.Tag.Track = (uint)iTrack + 1; + if (fileInfo.Tag.Title == null && Metadata.Tracks[iTrack].Title != "") + fileInfo.Tag.Title = Metadata.Tracks[iTrack].Title; + if (fileInfo.Tag.Album == null && Metadata.Title != "") + fileInfo.Tag.Album = Metadata.Title; + if (fileInfo.Tag.Performers.Length == 0 && Metadata.Tracks[iTrack].Artist != "") + fileInfo.Tag.Performers = new string[] { Metadata.Tracks[iTrack].Artist }; + if (fileInfo.Tag.Performers.Length == 0 && Metadata.Artist != "") + fileInfo.Tag.Performers = new string[] { Metadata.Artist }; + if (fNeedAlbumArtist && fileInfo.Tag.AlbumArtists.Length == 0 && Metadata.Artist != "") + fileInfo.Tag.AlbumArtists = new string[] { Metadata.Artist }; + if (fileInfo.Tag.Genres.Length == 0 && Metadata.Genre != "") + fileInfo.Tag.Genres = new string[] { Metadata.Genre }; + if (fileInfo.Tag.DiscCount == 0 && Metadata.TotalDiscs != "" && uint.TryParse(Metadata.TotalDiscs, out temp)) + fileInfo.Tag.DiscCount = temp; + if (fileInfo.Tag.Disc == 0 && Metadata.DiscNumber != "" && uint.TryParse(Metadata.DiscNumber, out temp)) + fileInfo.Tag.Disc = temp; + if (fileInfo.Tag.Year == 0 && Metadata.Year != "" && uint.TryParse(Metadata.Year, out temp)) + fileInfo.Tag.Year = temp; + } + + if (_config.copyBasicTags && sourceFileInfo != null) + { + if (fileInfo.Tag.Title == null && _tracks[iTrack]._fileInfo != null) + fileInfo.Tag.Title = _tracks[iTrack]._fileInfo.Tag.Title; + if (fileInfo.Tag.DiscCount == 0) + fileInfo.Tag.DiscCount = sourceFileInfo.Tag.DiscCount; + if (fileInfo.Tag.Disc == 0) + fileInfo.Tag.Disc = sourceFileInfo.Tag.Disc; + if (fileInfo.Tag.Performers.Length == 0) + fileInfo.Tag.Performers = sourceFileInfo.Tag.Performers; + if (fileInfo.Tag.AlbumArtists.Length == 0) + fileInfo.Tag.AlbumArtists = sourceFileInfo.Tag.AlbumArtists; + if (fileInfo.Tag.Album == null) + fileInfo.Tag.Album = sourceFileInfo.Tag.Album; + if (fileInfo.Tag.Year == 0) + fileInfo.Tag.Year = sourceFileInfo.Tag.Year; + if (fileInfo.Tag.Genres.Length == 0) + fileInfo.Tag.Genres = sourceFileInfo.Tag.Genres; + } + + if ((_config.embedAlbumArt || _config.CopyAlbumArt) && _albumArt.Count > 0) + fileInfo.Tag.Pictures = _albumArt.ToArray(); + + fileInfo.Save(); + } + } + } + } + + return WriteReport(); + } + + public CUEToolsLocalDBEntry OpenLocalDBEntry() + { + if (!_useLocalDB) + return null; + + string path = CUEToolsLocalDBEntry.NormalizePath(InputPath); + CUEToolsLocalDBEntry entry = _localDB.Lookup(_toc, _sourcePaths); + if (entry.InputPaths == null) + entry.InputPaths = new List(); + if (!entry.InputPaths.Contains(path)) + entry.InputPaths.Add(path); + if (entry.Metadata == null) + entry.Metadata = new CUEMetadata(cueMetadata); + _localDB.Dirty = true; + return entry; + } + + private static Bitmap resizeImage(Image imgToResize, Size size) + { + int sourceWidth = imgToResize.Width; + int sourceHeight = imgToResize.Height; + + float nPercent = 0; + float nPercentW = 0; + float nPercentH = 0; + + nPercentW = ((float)size.Width / (float)sourceWidth); + nPercentH = ((float)size.Height / (float)sourceHeight); + + if (nPercentH < nPercentW) + nPercent = nPercentH; + else + nPercent = nPercentW; + + int destWidth = (int)(sourceWidth * nPercent); + int destHeight = (int)(sourceHeight * nPercent); + + Bitmap b = new Bitmap(destWidth, destHeight); + Graphics g = Graphics.FromImage((Image)b); + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + + g.DrawImage(imgToResize, 0, 0, destWidth, destHeight); + g.Dispose(); + + return b; + } + + public void ExtractAlbumArt() + { + if (!_config.extractAlbumArt || _albumArt.Count == 0) + return; + + string imgPath = Path.Combine(OutputDir, AlArtFileName); + foreach (TagLib.IPicture picture in _albumArt) + using (FileStream file = new FileStream(imgPath, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + file.Write(picture.Data.Data, 0, picture.Data.Count); + return; + } + } + + public void LoadAlbumArt(TagLib.File fileInfo) + { + if ((_config.extractAlbumArt || _config.CopyAlbumArt) && fileInfo != null) + foreach (TagLib.IPicture picture in fileInfo.Tag.Pictures) + if (picture.Type == TagLib.PictureType.FrontCover) + if (picture.MimeType == "image/jpeg") + { + _albumArt.Add(picture); + return; + } + if ((_config.extractAlbumArt || _config.embedAlbumArt) && !_isCD) + { + foreach (string tpl in _config.advanced.CoverArtFiles) + { + string name = tpl.Replace("%album%", Metadata.Title).Replace("%artist%", Metadata.Artist); + string imgPath = Path.Combine(_isArchive ? _archiveCUEpath : _inputDir, name); + bool exists = _isArchive ? _archiveContents.Contains(imgPath) : File.Exists(imgPath); + if (exists) + { + TagLib.File.IFileAbstraction file = _isArchive + ? (TagLib.File.IFileAbstraction)new ArchiveFileAbstraction(this, imgPath) + : (TagLib.File.IFileAbstraction)new TagLib.File.LocalFileAbstraction(imgPath); + TagLib.Picture pic = new TagLib.Picture(file); + pic.Description = name; + _albumArt.Add(pic); + return; + } + } + + if (!_isArchive && _config.advanced.CoverArtSearchSubdirs) + { + List allfiles = new List(Directory.GetFiles(_inputDir, "*.jpg", SearchOption.AllDirectories)); + // TODO: archive case + foreach (string tpl in _config.advanced.CoverArtFiles) + { + string name = tpl.Replace("%album%", Metadata.Title).Replace("%artist%", Metadata.Artist); + List matching = allfiles.FindAll(s => Path.GetFileName(s) == name); + if (matching.Count == 1) + { + string imgPath = matching[0]; + TagLib.File.IFileAbstraction file = _isArchive + ? (TagLib.File.IFileAbstraction)new ArchiveFileAbstraction(this, imgPath) + : (TagLib.File.IFileAbstraction)new TagLib.File.LocalFileAbstraction(imgPath); + TagLib.Picture pic = new TagLib.Picture(file); + pic.Description = name; + _albumArt.Add(pic); + return; + } + } + + if (CUEToolsSelection != null + && ((Action == CUEAction.Encode && allfiles.Count < 32) + || (Action != CUEAction.Encode && allfiles.Count < 2) + ) + ) + { + foreach (string imgPath in allfiles) + { + TagLib.Picture pic = new TagLib.Picture(imgPath); + if (imgPath.StartsWith(_inputDir)) + pic.Description = imgPath.Substring(_inputDir.Length).Trim(Path.DirectorySeparatorChar); + else + pic.Description = Path.GetFileName(imgPath); + _albumArt.Add(pic); + } + if (_albumArt.Count > 0) + { + CUEToolsSelectionEventArgs e = new CUEToolsSelectionEventArgs(); + e.choices = _albumArt.ToArray(); + CUEToolsSelection(this, e); + TagLib.IPicture selected = e.selection == -1 ? null : _albumArt[e.selection]; + _albumArt.RemoveAll(t => t != selected); + } + } + } + } + } + + public void ResizeAlbumArt() + { + if (_albumArt == null) + return; + foreach (TagLib.IPicture picture in _albumArt) + using (MemoryStream imageStream = new MemoryStream(picture.Data.Data, 0, picture.Data.Count)) + try + { + using (Image img = Image.FromStream(imageStream)) + if (img.Width > _config.maxAlbumArtSize || img.Height > _config.maxAlbumArtSize) + { + using (Bitmap small = resizeImage(img, new Size(_config.maxAlbumArtSize, _config.maxAlbumArtSize))) + using (MemoryStream encoded = new MemoryStream()) + { + //System.Drawing.Imaging.EncoderParameters encoderParams = new EncoderParameters(1); + //encoderParams.Param[0] = new System.Drawing.Imaging.EncoderParameter(Encoder.Quality, quality); + small.Save(encoded, System.Drawing.Imaging.ImageFormat.Jpeg); + picture.Data = new TagLib.ByteVector(encoded.ToArray()); + picture.MimeType = "image/jpeg"; + } + } + } + catch + { + } + } + + public List AlbumArt + { + get + { + return _albumArt; + } + } + + public Image Cover + { + get + { + if (AlbumArt == null || AlbumArt.Count == 0) + return null; + TagLib.IPicture picture = AlbumArt[0]; + using (MemoryStream imageStream = new MemoryStream(picture.Data.Data, 0, picture.Data.Count)) + try { return Image.FromStream(imageStream); } + catch { } + return null; + } + } + + public List SourcePaths + { + get + { + return _sourcePaths; + } + } + + public string WriteReport() + { + if (_useAccurateRip) + { + ShowProgress((string)"Generating AccurateRip report...", 0, null, null); + if (_action == CUEAction.Verify && _config.writeArTagsOnVerify && _writeOffset == 0 && !_isArchive && !_isCD) + { + uint tracksMatch; + int bestOffset; + FindBestOffset(1, true, out tracksMatch, out bestOffset); + + if (_hasEmbeddedCUESheet) + { + if (_fileInfo is TagLib.Flac.File) + { + NameValueCollection tags = Tagging.Analyze(_fileInfo); + CleanupTags(tags, "ACCURATERIP"); + GenerateAccurateRipTags(tags, bestOffset, -1); + if (Tagging.UpdateTags(_fileInfo, tags, _config)) + _fileInfo.Save(); + } + } + else if (_hasTrackFilenames) + { + for (int iTrack = 0; iTrack < TrackCount; iTrack++) + if (_tracks[iTrack]._fileInfo is TagLib.Flac.File) + { + NameValueCollection tags = Tagging.Analyze(_tracks[iTrack]._fileInfo); + CleanupTags(tags, "ACCURATERIP"); + GenerateAccurateRipTags(tags, bestOffset, iTrack); + if (Tagging.UpdateTags(_tracks[iTrack]._fileInfo, tags, _config)) + _tracks[iTrack]._fileInfo.Save(); + } + } + } + + if ((_action != CUEAction.Verify && _config.writeArLogOnConvert) || + (_action == CUEAction.Verify && _config.writeArLogOnVerify)) + { + if (!Directory.Exists(OutputDir)) + Directory.CreateDirectory(OutputDir); + StreamWriter sw = new StreamWriter(Path.Combine(OutputDir, ArLogFileName), + false, CUESheet.Encoding); + GenerateAccurateRipLog(sw); + sw.Close(); + } + if (_config.advanced.CreateTOC) + { + if (!Directory.Exists(OutputDir)) + Directory.CreateDirectory(OutputDir); + WriteText(Path.ChangeExtension(_outputPath, ".toc"), TOCContents()); + } + } + return GenerateAccurateRipStatus(); + } + + private NameValueCollection GenerateTrackTags(int iTrack, int bestOffset) + { + NameValueCollection destTags = new NameValueCollection(); + + if (_config.copyUnknownTags) + { + if (_hasEmbeddedCUESheet) + { + if (_fileInfo != null) + { + string trackPrefix = String.Format("cue_track{0:00}_", iTrack + 1); + NameValueCollection albumTags = Tagging.Analyze(_fileInfo); + foreach (string key in albumTags.AllKeys) + { + if (key.ToLower().StartsWith(trackPrefix) + || !key.ToLower().StartsWith("cue_track")) + { + string name = key.ToLower().StartsWith(trackPrefix) ? + key.Substring(trackPrefix.Length) : key; + string[] values = albumTags.GetValues(key); + for (int j = 0; j < values.Length; j++) + destTags.Add(name, values[j]); + } + } + } + } + else if (_hasTrackFilenames) + { + if (_tracks[iTrack]._fileInfo != null) + destTags.Add(Tagging.Analyze(_tracks[iTrack]._fileInfo)); + } + else if (_hasSingleFilename) + { + // TODO? + } + + // these will be set explicitely + destTags.Remove("ARTIST"); + destTags.Remove("TITLE"); + destTags.Remove("ALBUM"); + destTags.Remove("ALBUMARTIST"); + destTags.Remove("ALBUM ARTIST"); + destTags.Remove("DATE"); + destTags.Remove("GENRE"); + destTags.Remove("TRACKNUMBER"); + destTags.Remove("TRACKTOTAL"); + destTags.Remove("TOTALTRACKS"); + destTags.Remove("DISCNUMBER"); + destTags.Remove("DISCTOTAL"); + destTags.Remove("TOTALDISCS"); + + destTags.Remove("LOG"); + destTags.Remove("LOGFILE"); + destTags.Remove("EACLOG"); + + // these are not valid + destTags.Remove("CUESHEET"); + CleanupTags(destTags, "ACCURATERIP"); + //CleanupTags(destTags, "REPLAYGAIN"); + } + + if (_config.writeArTagsOnEncode && + _action == CUEAction.Encode && + _useAccurateRip && + _arVerify.ExceptionStatus == WebExceptionStatus.Success) + GenerateAccurateRipTags(destTags, bestOffset, iTrack); + + return destTags; + } + + private NameValueCollection GenerateAlbumTags(int bestOffset, bool fWithCUE, string logContents) + { + NameValueCollection destTags = new NameValueCollection(); + + if (_config.copyUnknownTags) + { + if (_hasEmbeddedCUESheet || _hasSingleFilename) + { + if (_fileInfo != null) + destTags.Add(Tagging.Analyze(_fileInfo)); + if (!fWithCUE) + CleanupTags(destTags, "CUE_TRACK"); + } + else if (_hasTrackFilenames) + { + for (int iTrack = 0; iTrack < TrackCount; iTrack++) + { + if (_tracks[iTrack]._fileInfo == null) continue; + NameValueCollection trackTags = Tagging.Analyze(_tracks[iTrack]._fileInfo); + foreach (string key in trackTags.AllKeys) + { + string singleValue = GetCommonMiscTag(key); + if (singleValue != null) + { + if (destTags.Get(key) == null) + destTags.Add(key, singleValue); + } + else if (fWithCUE && key.ToUpper() != "TRACKNUMBER" && key.ToUpper() != "TITLE" && key.ToUpper() != "ARTIST") + { + string[] values = trackTags.GetValues(key); + for (int j = 0; j < values.Length; j++) + destTags.Add(String.Format("cue_track{0:00}_{1}", iTrack + 1, key), values[j]); + } + } + } + } + + // these will be set explicitely + destTags.Remove("ARTIST"); + destTags.Remove("TITLE"); + destTags.Remove("ALBUM"); + destTags.Remove("ALBUMARTIST"); + destTags.Remove("ALBUM ARTIST"); + destTags.Remove("DATE"); + destTags.Remove("GENRE"); + destTags.Remove("TRACKNUMBER"); + destTags.Remove("TRACKTOTAL"); + destTags.Remove("TOTALTRACKS"); + destTags.Remove("DISCNUMBER"); + destTags.Remove("DISCTOTAL"); + destTags.Remove("TOTALDISCS"); + destTags.Remove("LOG"); + destTags.Remove("LOGFILE"); + destTags.Remove("EACLOG"); + + // these are not valid + CleanupTags(destTags, "ACCURATERIP"); + //CleanupTags(destTags, "REPLAYGAIN"); + + destTags.Remove("CUESHEET"); + } + + if (fWithCUE) + destTags.Add("CUESHEET", CUESheetContents(CUEStyle.SingleFileWithCUE)); + + if (_config.embedLog && logContents != null) + destTags.Add("LOG", logContents); + + if (fWithCUE && + _config.writeArTagsOnEncode && + _action == CUEAction.Encode && + _useAccurateRip && + _arVerify.ExceptionStatus == WebExceptionStatus.Success) + GenerateAccurateRipTags(destTags, bestOffset, -1); + + return destTags; + } + + //public IAudioSource OpenSource(long position) + //{ + // int pos = 0; + // for (int iSource = 0; iSource < _sources.Count; iSource++) + // { + // if (position >= pos && position < pos + (int)_sources[iSource].Length) + // { + // IAudioSource audioSource = GetAudioSource(iSource); + // audioSource.Position = position - pos; + // return audioSource; + // } + // pos += (int)_sources[iSource].Length; + // } + // return null; + //} + + internal void ApplyWriteOffset() + { + if (_writeOffset == 0) + return; + + int absOffset = Math.Abs(_writeOffset); + SourceInfo sourceInfo; + + sourceInfo.Path = null; + sourceInfo.Offset = 0; + sourceInfo.Length = (uint)absOffset; + + if (_writeOffset < 0) + { + _sources.Insert(0, sourceInfo); + + int last = _sources.Count - 1; + while (absOffset >= _sources[last].Length) + { + absOffset -= (int)_sources[last].Length; + _sources.RemoveAt(last--); + } + sourceInfo = _sources[last]; + sourceInfo.Length -= (uint)absOffset; + _sources[last] = sourceInfo; + } + else + { + _sources.Add(sourceInfo); + + while (absOffset >= _sources[0].Length) + { + absOffset -= (int)_sources[0].Length; + _sources.RemoveAt(0); + } + sourceInfo = _sources[0]; + sourceInfo.Offset += (uint)absOffset; + sourceInfo.Length -= (uint)absOffset; + _sources[0] = sourceInfo; + } + + _appliedWriteOffset = true; + } + + public void WriteAudioFilesPass(string dir, CUEStyle style, int[] destLengths, bool htoaToFile, bool noOutput) + { + int iTrack, iIndex; + AudioBuffer sampleBuffer = new AudioBuffer(AudioPCMConfig.RedBook, 0x10000); + TrackInfo track; + IAudioSource audioSource = null; + IAudioDest audioDest = null; + bool discardOutput; + int iSource = -1; + int iDest = -1; + int samplesRemSource = 0; + + ApplyWriteOffset(); + + int destBPS = 16; + hdcdDecoder = null; + if (_config.detectHDCD && CUEProcessorPlugins.hdcd != null) + { + // currently broken verifyThenConvert on HDCD detection!!!! need to check for HDCD results higher + try + { + destBPS = ((_outputLossyWAV && _config.decodeHDCDtoLW16) || !_config.decodeHDCDto24bit) ? 20 : 24; + hdcdDecoder = Activator.CreateInstance(CUEProcessorPlugins.hdcd, 2, 44100, destBPS, _config.decodeHDCD) as IAudioDest; + } + catch { } + if (hdcdDecoder == null || !_config.decodeHDCD) + destBPS = 16; + } + + if (style == CUEStyle.SingleFile || style == CUEStyle.SingleFileWithCUE) + { + iDest++; + if (_isCD && style == CUEStyle.SingleFileWithCUE) + _padding += Encoding.UTF8.GetByteCount(CUESheetContents(style)); + audioDest = GetAudioDest(_destPaths[iDest], destLengths[iDest], destBPS, _padding, noOutput); + } + + int currentOffset = 0, previousOffset = 0; + int trackLength = (int)_toc.Pregap * 588; + int diskLength = 588 * (int)_toc.AudioLength; + int diskOffset = 0; + + // we init AR before CTDB so that CTDB gets inited with correct TOC + if (_useAccurateRip || _useCUEToolsDB) + _arVerify.Init(_toc); + if (_useCUEToolsDB && !_useCUEToolsDBFix) + { + _CUEToolsDB.TOC = _toc; // This might be unnecessary, because they point to the same structure - if we modify _toc, _CUEToolsDB.TOC gets updated. Unless we set cueSheet.TOC... + _CUEToolsDB.Init(_arVerify); + } + + ShowProgress(String.Format("{2} track {0:00} ({1:00}%)...", 0, 0, noOutput ? "Verifying" : "Writing"), 0.0, null, null); + +#if !DEBUG + try +#endif + { + for (iTrack = 0; iTrack < TrackCount; iTrack++) + { + track = _tracks[iTrack]; + + if ((style == CUEStyle.GapsPrepended) || (style == CUEStyle.GapsLeftOut)) + { + iDest++; + if (hdcdDecoder != null) + (hdcdDecoder as IAudioFilter).AudioDest = null; + if (audioDest != null) + audioDest.Close(); + audioDest = GetAudioDest(_destPaths[iDest], destLengths[iDest], destBPS, _padding, noOutput); + } + + for (iIndex = 0; iIndex <= _toc[_toc.FirstAudio + iTrack].LastIndex; iIndex++) + { + int samplesRemIndex = (int)_toc.IndexLength(_toc.FirstAudio + iTrack, iIndex) * 588; + + if (iIndex == 1) + { + previousOffset = currentOffset; + currentOffset = 0; + trackLength = (int)_toc[_toc.FirstAudio + iTrack].Length * 588; + } + + if ((style == CUEStyle.GapsAppended) && (iIndex == 1)) + { + if (hdcdDecoder != null) + (hdcdDecoder as IAudioFilter).AudioDest = null; + if (audioDest != null) + audioDest.Close(); + iDest++; + audioDest = GetAudioDest(_destPaths[iDest], destLengths[iDest], destBPS, _padding, noOutput); + } + + if ((style == CUEStyle.GapsAppended) && (iIndex == 0) && (iTrack == 0)) + { + discardOutput = !htoaToFile; + if (htoaToFile) + { + iDest++; + audioDest = GetAudioDest(_destPaths[iDest], destLengths[iDest], destBPS, _padding, noOutput); + } + } + else if ((style == CUEStyle.GapsLeftOut) && (iIndex == 0)) + { + discardOutput = true; + } + else + { + discardOutput = false; + } + + while (samplesRemIndex != 0) + { + if (samplesRemSource == 0) + { + // if (_isCD && audioSource != null && audioSource is CDDriveReader) + // updatedTOC = ((CDDriveReader)audioSource).TOC; + if (audioSource != null) audioSource.Close(); + audioSource = GetAudioSource(++iSource, _config.separateDecodingThread || _isCD); + samplesRemSource = (int)_sources[iSource].Length; + } + + int copyCount = Math.Min(samplesRemIndex, samplesRemSource); + + if (trackLength > 0 && !_isCD) + { + double trackPercent = (double)currentOffset / trackLength; + ShowProgress(String.Format("{2} track {0:00} ({1:00}%)...", iIndex > 0 ? iTrack + 1 : iTrack, (int)(100 * trackPercent), + noOutput ? "Verifying" : "Writing"), (int)diskOffset, (int)diskLength, + _isCD ? string.Format("{0}: {1:00} - {2}", audioSource.Path, iTrack + 1, Metadata.Tracks[iTrack].Title) : audioSource.Path, discardOutput ? null : audioDest.Path); + } + + copyCount = audioSource.Read(sampleBuffer, copyCount); + if (copyCount == 0) + throw new Exception("Unexpected end of file"); + if (_useCUEToolsDB && _useCUEToolsDBFix) + _CUEToolsDB.SelectedEntry.repair.Write(sampleBuffer); + // we use AR after CTDB fix, so that we can verify what we fixed + if (_useAccurateRip || _useCUEToolsDB) + _arVerify.Write(sampleBuffer); + if (!discardOutput) + { + if (!_config.detectHDCD || !_config.decodeHDCD) + audioDest.Write(sampleBuffer); + if (_config.detectHDCD && hdcdDecoder != null) + { + if (_config.wait750FramesForHDCD && diskOffset > 750 * 588 && string.Format("{0:s}", hdcdDecoder) == "") + { + (hdcdDecoder as IAudioFilter).AudioDest = null; + hdcdDecoder = null; + if (_config.decodeHDCD) + { + audioSource.Close(); + audioDest.Delete(); + throw new Exception("HDCD not detected."); + } + } + else + { + if (_config.decodeHDCD) + (hdcdDecoder as IAudioFilter).AudioDest = (discardOutput || noOutput) ? null : audioDest; + hdcdDecoder.Write(sampleBuffer); + } + } + } + + currentOffset += copyCount; + diskOffset += copyCount; + samplesRemIndex -= copyCount; + samplesRemSource -= copyCount; + + CheckStop(); + } + } + } + if (hdcdDecoder != null) + (hdcdDecoder as IAudioFilter).AudioDest = null; + hdcdDecoder = null; + if (audioSource != null) + audioSource.Close(); + audioSource = null; + if (audioDest != null) + audioDest.Close(); + audioDest = null; + } +#if !DEBUG + catch (Exception ex) + { + if (hdcdDecoder != null) + (hdcdDecoder as IAudioFilter).AudioDest = null; + hdcdDecoder = null; + if (audioSource != null) + try { audioSource.Close(); } catch { } + audioSource = null; + if (audioDest != null) + try { audioDest.Delete(); } catch { } + audioDest = null; + throw ex; + } +#endif + } + + public void VerifyAudio() + { + ApplyWriteOffset(); + + hdcdDecoder = null; + + // we init AR before CTDB so that CTDB gets inited with correct TOC + if (_useAccurateRip || _useCUEToolsDB) + _arVerify.Init(_toc); + if (_useCUEToolsDB && !_useCUEToolsDBFix) + { + _CUEToolsDB.TOC = _toc; + _CUEToolsDB.Init(_arVerify); + } + + ShowProgress(String.Format("Verifying ({0:00}%)...", 0), 0.0, null, null); + + AudioBuffer sampleBuffer = new AudioBuffer(AudioPCMConfig.RedBook, 0x10000); + + List tasks = new List(); + // also make sure all sources are seekable!!! + // use overlapped io with large buffers? + // ar.verify in each thread? + int nThreads = 1;// _isCD || !_config.separateDecodingThread || _useCUEToolsDB || _config.detectHDCD ? 1 : Environment.ProcessorCount; + + int diskLength = 588 * (int)_toc.AudioLength; + tasks.Add(new CUEToolsVerifyTask(this, 0, diskLength / nThreads, _arVerify)); + for (int iThread = 1; iThread < nThreads; iThread++) + tasks.Add(new CUEToolsVerifyTask(this, iThread * diskLength / nThreads, (iThread + 1) * diskLength / nThreads)); + +#if !DEBUG + try +#endif + { + int lastProgress = -588 * 75; + int diskOffset = 0; + int sourcesActive; + do + { + sourcesActive = 0; + for (int iSource = 0; iSource < tasks.Count; iSource++) + { + CUEToolsVerifyTask task = tasks[iSource]; + if (task.Remaining == 0) + continue; + sourcesActive++; + if (tasks.Count == 1 && task.source.Position - lastProgress >= 588 * 75) + { + lastProgress = (int)task.source.Position; + int pos = 0; + int trackStart = 0; + int trackLength = (int)_toc.Pregap * 588; + for (int iTrack = 0; iTrack < TrackCount; iTrack++) + for (int iIndex = 0; iIndex <= _toc[_toc.FirstAudio + iTrack].LastIndex; iIndex++) + { + int indexLen = (int)_toc.IndexLength(_toc.FirstAudio + iTrack, iIndex) * 588; + if (iIndex == 1) + { + trackStart = pos; + trackLength = (int)_toc[_toc.FirstAudio + iTrack].Length * 588; + } + if (task.source.Position < pos + indexLen) + { + if (trackLength > 0 && !_isCD) + { + double trackPercent = (double)(task.source.Position - trackStart) / trackLength; + ShowProgress(String.Format("{2} track {0:00} ({1:00}%)...", iIndex > 0 ? iTrack + 1 : iTrack, (int)(100 * trackPercent), + "Verifying"), diskOffset, diskLength, task.source.Path, null); + } + iTrack = TrackCount; + break; + } + pos += indexLen; + } + } + else if (tasks.Count > 1) + { + ShowProgress(String.Format("Verifying ({0:00}%)...", (uint)(100.0 * diskOffset / diskLength)), + diskOffset, diskLength, InputPath, null); + } + + int copyCount = task.Step(sampleBuffer); + if (copyCount == 0) + throw new Exception("Unexpected end of file"); + diskOffset += copyCount; + + CheckStop(); + } + } while (sourcesActive > 0); + } +#if !DEBUG + catch (Exception ex) + { + tasks.ForEach(t => t.TryClose()); + tasks.Clear(); + throw ex; + } +#endif + hdcdDecoder = tasks[0].hdcd; + for (int iThread = 1; iThread < nThreads; iThread++) + tasks[0].Combine(tasks[iThread]); + tasks.ForEach(t => t.Close()); + tasks.Clear(); + } + + private void DetectGaps() + { + if (!_isCD) + throw new Exception("not a CD"); + + try { _ripper.DetectGaps(); } + catch (Exception ex) + { + if (ex is StopException) + throw ex; + } + + if (!_ripper.GapsDetected) + return; + + _toc = (CDImageLayout)_ripper.TOC.Clone(); + if (_toc.Barcode != null) + Metadata.Barcode = _toc.Barcode; + for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) + { + if (_toc[_toc.FirstAudio + iTrack].ISRC != null) + Metadata.Tracks[iTrack].ISRC = _toc[_toc.FirstAudio + iTrack].ISRC; + //General.SetCUELine(_tracks[iTrack].Attributes, "ISRC", _toc[_toc.FirstAudio + iTrack].ISRC, false); + if (_toc[_toc.FirstAudio + iTrack].DCP || _toc[_toc.FirstAudio + iTrack].PreEmphasis) + _tracks[iTrack].Attributes.Add(new CUELine("FLAGS" + (_toc[_toc.FirstAudio + iTrack].PreEmphasis ? " PRE" : "") + (_toc[_toc.FirstAudio + iTrack].DCP ? " DCP" : ""))); + } + } + + public static string CreateDummyCUESheet(CUEConfig _config, string pathIn) + { + pathIn = Path.GetFullPath(pathIn); + List fileGroups = CUESheet.ScanFolder(_config, Path.GetDirectoryName(pathIn)); + FileGroupInfo fileGroup = fileGroups.Find(f => f.type == FileGroupInfoType.TrackFiles && f.Contains(pathIn)) ?? + fileGroups.Find(f => f.type == FileGroupInfoType.FileWithCUE && f.Contains(pathIn)) ?? + fileGroups.Find(f => f.type == FileGroupInfoType.M3UFile && f.Contains(pathIn)); + return fileGroup == null ? null : CreateDummyCUESheet(_config, fileGroup); + } + + public static string CreateDummyCUESheet(CUEConfig _config, FileGroupInfo fileGroup) + { + if (fileGroup.type == FileGroupInfoType.FileWithCUE) + { + TagLib.UserDefined.AdditionalFileTypes.Config = _config; + TagLib.File.IFileAbstraction fileAbsraction = new TagLib.File.LocalFileAbstraction(fileGroup.main.FullName); + TagLib.File fileInfo = TagLib.File.Create(fileAbsraction); + return Tagging.Analyze(fileInfo).Get("CUESHEET"); + } + + StringWriter sw = new StringWriter(); + sw.WriteLine(String.Format("REM COMMENT \"CUETools generated dummy CUE sheet\"")); + int trackNo = 0; + foreach (FileSystemInfo file in fileGroup.files) + { + string name = file.Name; + if (fileGroup.type == FileGroupInfoType.M3UFile + && Path.GetDirectoryName(file.FullName) != Path.GetDirectoryName(fileGroup.main.FullName) + && Path.GetDirectoryName(file.FullName).StartsWith(Path.GetDirectoryName(fileGroup.main.FullName))) + { + name = file.FullName.Substring(Path.GetDirectoryName(fileGroup.main.FullName).Length + 1); + } + sw.WriteLine(String.Format("FILE \"{0}\" WAVE", name)); + sw.WriteLine(String.Format(" TRACK {0:00} AUDIO", ++trackNo)); + sw.WriteLine(String.Format(" INDEX 01 00:00:00")); + } + sw.Close(); + return sw.ToString(); + } + + public static string CorrectAudioFilenames(CUEConfig _config, string path, bool always) + { + StreamReader sr = new StreamReader(path, CUESheet.Encoding); + string cue = sr.ReadToEnd(); + sr.Close(); + string extension; + return CorrectAudioFilenames(_config, Path.GetDirectoryName(path), cue, always, null, out extension); + } + + public static string CorrectAudioFilenames(CUEConfig _config, string dir, string cue, bool always, List files, out string extension) + { + List lines = new List(); + List filePos = new List(); + List origFiles = new List(); + bool foundAll = true; + string[] audioFiles = null; + string lineStr; + CUELine line; + int i; + string CDDBID = ""; + //bool isBinary = false; + + 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 == "MOTOROLA") + continue; + if (fileType == "BINARY") + continue; + //{ + // if (filePos.Count > 0) + // continue; + // isBinary = true; + //} + //else + //{ + // if (isBinary) + // { + // filePos.Clear(); + // origFiles.Clear(); + // foundAll = false; + // isBinary = false; + // } + //} + filePos.Add(lines.Count - 1); + origFiles.Add(line.Params[1]); + foundAll &= (LocateFile(dir, line.Params[1], files) != null); + } + if (line.Params.Count == 3 && line.Params[0].ToUpper() == "REM" && line.Params[1].ToUpper() == "DISCID") + CDDBID = line.Params[2].ToLower(); + } + sr.Close(); + } + + + extension = null; + if (foundAll && !always) + return cue; + + foundAll = false; + + foreach (KeyValuePair format in _config.formats) + { + List newFiles = new List(); + for (int j = 0; j < origFiles.Count; j++) + { + string newFilename = Path.ChangeExtension(Path.GetFileName(origFiles[j]), "." + format.Key); + string locatedFilename = LocateFile(dir, newFilename, files); + if (locatedFilename != null) + newFiles.Add(locatedFilename); + } + if (newFiles.Count == origFiles.Count) + { + audioFiles = newFiles.ToArray(); + extension = format.Key; + foundAll = true; + break; + } + } + + if (!foundAll && files == null) + { + List fileGroups = CUESheet.ScanFolder(_config, dir == "" ? "." : dir); + + // Choose filegroup by track count + List + matching = fileGroups.FindAll(f => f.type == FileGroupInfoType.TrackFiles && f.files.Count == filePos.Count); + // If ambiguous, use DISCID + if (matching.Count > 1) + matching = fileGroups.FindAll(f => f.type == FileGroupInfoType.TrackFiles && f.files.Count == filePos.Count && f.TOC != null && AccurateRipVerify.CalculateCDDBId(f.TOC).ToLower() == CDDBID); + if (matching.Count == 1) + { + audioFiles = matching[0].files.ConvertAll(info => info.FullName).ToArray(); + // No need to sort - hopefully already sorted by ScanFolder + extension = matching[0].main.Extension.ToLower().TrimStart('.'); + foundAll = true; + } + + if (!foundAll && filePos.Count == 1) + foreach (FileGroupInfo fileGroup in fileGroups) + { + if (fileGroup.type == FileGroupInfoType.FileWithCUE && fileGroup.TOC != null) + { + CDImageLayout toc = CUE2TOC(cue, (int)fileGroup.TOC.AudioLength); + if (toc == null || toc.TrackOffsets != fileGroup.TOC.TrackOffsets) + continue; + if (foundAll) + { + foundAll = false; + break; + } + audioFiles = new string[] { fileGroup.main.FullName }; + extension = fileGroup.main.Extension.ToLower().TrimStart('.'); + foundAll = true; + } + } + } + + // Use old-fashioned way if dealing with archive (files != null) + // or with single file (filePos.Count == 1). + // In other cases we use CUESheet.ScanFolder, which + // is better at sorting and separating albums, + // but doesn't support archives and single files yet. + if (!foundAll)// && (files != null || filePos.Count == 1)) + foreach (KeyValuePair format in _config.formats) + { + if (files == null) + audioFiles = Directory.GetFiles(dir == "" ? "." : dir, "*." + format.Key); + else + audioFiles = files.FindAll(s => Path.GetDirectoryName(s) == dir && Path.GetExtension(s).ToLower() == "." + format.Key).ToArray(); + if (audioFiles.Length == filePos.Count) + { + Array.Sort(audioFiles, FileGroupInfo.CompareTrackNames); + extension = format.Key; + 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[_toc.FirstAudio + iTrack].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.IndexLength(_toc.FirstAudio + iTrack, iIndex) * 588; + } + } + + return fileLengths; + } + + public void CheckStop() + { + lock (this) + { + if (_stop) + throw new StopException(); + if (_pause) + { + ShowProgress("Paused...", 0, null, null); + Monitor.Wait(this); + } + } + } + + 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; + } + } + + public string OutputPath + { + get + { + return _outputPath; + } + } + + public string OutputDir + { + get + { + string outDir = Path.GetDirectoryName(_outputPath); + return outDir == "" ? "." : outDir; + } + } + + public CDImageLayout TOC + { + get + { + return _toc; + } + set + { + _toc = new CDImageLayout(value); + if (Tracks.Count == 0) + { + for (int iTrack = 0; iTrack < _toc.AudioTracks; iTrack++) + { + //_trackFilenames.Add(string.Format("{0:00}.wav", iTrack + 1)); + _tracks.Add(new TrackInfo()); + } + } + } + } + + private IAudioDest GetAudioDest(string path, int finalSampleCount, int bps, int padding, bool noOutput) + { + if (noOutput) + return new DummyWriter(path, new AudioPCMConfig(bps, 2, 44100)); + return AudioReadWrite.GetAudioDest(_audioEncoderType, path, finalSampleCount, bps, 44100, padding, _config); + } + + internal IAudioSource GetAudioSource(int sourceIndex, bool pipe) + { + SourceInfo sourceInfo = _sources[sourceIndex]; + IAudioSource audioSource; + + if (sourceInfo.Path == null) + { + audioSource = new SilenceGenerator(sourceInfo.Offset + sourceInfo.Length); + } + else + { + if (_isCD) + { + _ripper.Position = 0; + //audioSource = _ripper; + if (pipe) + audioSource = new AudioPipe(_ripper, 0x100000, false, ThreadPriority.Highest); + else + audioSource = _ripper; + } + else + if (_isArchive) + audioSource = AudioReadWrite.GetAudioSource(sourceInfo.Path, OpenArchive(sourceInfo.Path, false), _config); + else + audioSource = AudioReadWrite.GetAudioSource(sourceInfo.Path, null, _config); + } + + if (sourceInfo.Offset != 0) + audioSource.Position = sourceInfo.Offset; + + //if (!(audioSource is AudioPipe) && !(audioSource is UserDefinedReader) && _config.separateDecodingThread) + if (!(audioSource is AudioPipe) && pipe) + audioSource = new AudioPipe(audioSource, 0x10000); + + 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 AccurateRipId + { + get + { + return _accurateRipId; + } + } + + public string ArLogFileName + { + get + { + return _arLogFileName; + } + set + { + _arLogFileName = value; + } + } + + public string AlArtFileName + { + get + { + return _alArtFileName; + } + set + { + _alArtFileName = value; + } + } + + public NameValueCollection Tags + { + get + { + TagLib.File fileInfo = _tracks[0]._fileInfo ?? _fileInfo; + return fileInfo != null ? Tagging.Analyze(fileInfo) : null; + } + } + + 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 uint DataTrackLength + { + get + { + if (!_toc[1].IsAudio) + return _toc[1].Length; + else if (!_toc[_toc.TrackCount].IsAudio) + return _toc[_toc.TrackCount].Length; + else + return 0U; + } + set + { + if (value == 0) + return; + if (!_toc[1].IsAudio) + { + // TODO: if track 2 has a pregap, we should adjust it!!! + for (int i = 2; i <= _toc.TrackCount; i++) + { + _toc[i].Start += value - _toc[1].Length; + for (int j = 0; j <= _toc[i].LastIndex; j++) + _toc[i][j].Start += value - _toc[1].Length; + } + _toc[1].Length = value; + } + else if (!_toc[_toc.TrackCount].IsAudio) + { + //_toc[_toc.TrackCount].Start = tocFromLog[_toc.TrackCount].Start; + _toc[_toc.TrackCount].Length = value; + //_toc[_toc.TrackCount][0].Start = tocFromLog[_toc.TrackCount].Start; + //_toc[_toc.TrackCount][1].Start = tocFromLog[_toc.TrackCount].Start; + } + else + _toc.AddTrack(new CDTrack((uint)_toc.TrackCount + 1, _toc.Length + 152U * 75U, value, false, false)); + } + } + + public string DataTrackLengthMSF + { + get + { + return CDImageLayout.TimeToString(DataTrackLength); + } + set + { + DataTrackLength = (uint)CDImageLayout.TimeFromString(value); + } + } + + public string PreGapLengthMSF + { + get + { + return CDImageLayout.TimeToString(_toc.Pregap); + } + set + { + PreGapLength = (uint)CDImageLayout.TimeFromString(value); + } + } + + public uint PreGapLength + { + get + { + return _toc.Pregap; + } + set + { + if (value == _toc.Pregap || value == 0) + return; + if (!_toc[1].IsAudio) + throw new Exception("can't set pregap to a data track"); + if (value < _toc.Pregap) + throw new Exception("can't set negative pregap"); + uint offs = value - _toc.Pregap; + for (int i = 1; i <= _toc.TrackCount; i++) + { + _toc[i].Start += offs; + for (int j = 0; j <= _toc[i].LastIndex; j++) + _toc[i][j].Start += offs; + } + _toc[1][0].Start = 0; + + SourceInfo sourceInfo; + sourceInfo.Path = null; + sourceInfo.Offset = 0; + sourceInfo.Length = offs * 588; + _sources.Insert(0, sourceInfo); + } + } + + public bool UsePregapForFirstTrackInSingleFile + { + get + { + return _usePregapForFirstTrackInSingleFile; + } + set + { + _usePregapForFirstTrackInSingleFile = value; + } + } + + public CUEConfig Config + { + get + { + return _config; + } + } + + public CUEAction Action + { + get + { + return _action; + } + set + { + _action = value; + } + } + + public CUEStyle OutputStyle + { + get + { + return _outputStyle; + } + set + { + _outputStyle = value; + } + } + + public bool Processed + { + get + { + return _processed; + } + } + + public bool IsCD + { + get + { + return _isCD; + } + } + + public static CDImageLayout CUE2TOC(string cue, int fileTimeLengthFrames) + { + CDImageLayout toc = new CDImageLayout(); + bool seenFirstFileIndex = false; + int absoluteFileStartTime = 0; + int trackStart = -1; + try + { + using (TextReader sr = new StringReader(cue)) + { + string lineStr; + while ((lineStr = sr.ReadLine()) != null) + { + CUELine line = new CUELine(lineStr); + if (line.Params.Count > 0) + { + string command = line.Params[0].ToUpper(); + + if (command == "TRACK") + { + if (line.Params[2].ToUpper() != "AUDIO") + return null; + } + else if (command == "INDEX") + { + int index = int.Parse(line.Params[1]); + int timeRelativeToFileStart = CDImageLayout.TimeFromString(line.Params[2]); + if (!seenFirstFileIndex) + { + if (timeRelativeToFileStart != 0) + return null; + seenFirstFileIndex = true; + } + else + { + if (timeRelativeToFileStart > fileTimeLengthFrames) + return null; + if (Int32.TryParse(line.Params[1], out index) && index == 1 && trackStart >= 0) + toc.AddTrack(new CDTrack((uint)toc.TrackCount + 1, (uint)trackStart, (uint)(absoluteFileStartTime + timeRelativeToFileStart - trackStart), true, false)); + } + if (index == 1) + trackStart = absoluteFileStartTime + timeRelativeToFileStart; + } + else if (command == "PREGAP") + { + if (seenFirstFileIndex) + return null; + int pregapLength = CDImageLayout.TimeFromString(line.Params[1]); + absoluteFileStartTime += pregapLength; + } + } + } + sr.Close(); + } + } + catch + { + return null; + } + toc.AddTrack(new CDTrack((uint)toc.TrackCount + 1, (uint)trackStart, (uint)(absoluteFileStartTime + fileTimeLengthFrames - trackStart), true, false)); + toc[1][0].Start = 0; + return toc; + } + + public static List ScanFolder(CUEConfig _config, string path) + { + DirectoryInfo dir = new DirectoryInfo(path); + return ScanFolder(_config, dir.GetFileSystemInfos()); + } + + public static List ScanFolder(CUEConfig _config, IEnumerable files) + { + List fileGroups = new List(); + foreach (FileSystemInfo file in files) + { + // file.Refresh(); + // file.Attributes returns -1 for long paths!!! + if ((file.Attributes & FileAttributes.Hidden) != 0) + continue; + if ((file.Attributes & FileAttributes.Directory) != 0) + { + // foreach (FileSystemInfo subfile in ((DirectoryInfo)e.file).GetFileSystemInfos()) + // if (IsVisible(subfile)) + // { + // e.isExpandable = true; + // break; + // } + fileGroups.Add(new FileGroupInfo(file, FileGroupInfoType.Folder)); + continue; + } + string ext = file.Extension.ToLower(); + if (ext == ".cue") + { + fileGroups.Add(new FileGroupInfo(file, FileGroupInfoType.CUESheetFile)); + continue; + } + if (ext == ".m3u") + { + FileGroupInfo m3uGroup = new FileGroupInfo(file, FileGroupInfoType.M3UFile); + using (StreamReader m3u = new StreamReader(file.FullName)) + { + do + { + string line = m3u.ReadLine(); + if (line == null) break; + if (line == "" || line[0] == '#') continue; + //if (line.IndexOfAny(Path.GetInvalidPathChars()) >= 0) + // continue; + try + { + line = Path.Combine(Path.GetDirectoryName(file.FullName), line); + if (File.Exists(line)) + { + FileInfo f = new FileInfo(line); + CUEToolsFormat fmt1; + if (!f.Extension.StartsWith(".") || !_config.formats.TryGetValue(f.Extension.ToLower().Substring(1), out fmt1) || !fmt1.allowLossless) + throw new Exception("not lossless"); + m3uGroup.files.Add(f); + continue; + } + } + catch { } + m3uGroup = null; + break; + } while (true); + }; + if (m3uGroup != null) + fileGroups.Add(m3uGroup); + continue; + } + if (ext == ".zip") + { + fileGroups.Add(new FileGroupInfo(file, FileGroupInfoType.Archive)); + //try + //{ + // using (ICSharpCode.SharpZipLib.Zip.ZipFile unzip = new ICSharpCode.SharpZipLib.Zip.ZipFile(file.FullName)) + // { + // foreach (ICSharpCode.SharpZipLib.Zip.ZipEntry entry in unzip) + // { + // if (entry.IsFile && Path.GetExtension(entry.Name).ToLower() == ".cue") + // { + // e.node.Nodes.Add(fileSystemTreeView1.NewNode(file, false)); + // break; + // } + + // } + // unzip.Close(); + // } + //} + //catch + //{ + //} + continue; + } + if (ext == ".rar") + { + fileGroups.Add(new FileGroupInfo(file, FileGroupInfoType.Archive)); + continue; + } + CUEToolsFormat fmt; + if (ext.StartsWith(".") && _config.formats.TryGetValue(ext.Substring(1), out fmt) && fmt.allowLossless) + { + uint disc = 0; + uint number = 0; + string album = null; + string cueFound = null; + TimeSpan dur = TimeSpan.Zero; + TagLib.UserDefined.AdditionalFileTypes.Config = _config; + TagLib.File.IFileAbstraction fileAbsraction = new TagLib.File.LocalFileAbstraction(file.FullName); + try + { + TagLib.File fileInfo = TagLib.File.Create(fileAbsraction); + disc = fileInfo.Tag.Disc; + album = fileInfo.Tag.Album; + number = fileInfo.Tag.Track; + dur = fileInfo.Properties.Duration; + cueFound = fmt.allowEmbed ? Tagging.Analyze(fileInfo).Get("CUESHEET") : null; + } + catch { } + if (cueFound != null) + { + FileGroupInfo group = new FileGroupInfo(file, FileGroupInfoType.FileWithCUE); + if (dur != TimeSpan.Zero) + group.TOC = CUE2TOC(cueFound, (int)((dur.TotalMilliseconds * 75 + 500) / 1000)); + fileGroups.Add(group); + continue; + } + disc = Math.Min(5, Math.Max(1, disc)); + FileGroupInfo groupFound = null; + foreach (FileGroupInfo fileGroup in fileGroups) + { + if (fileGroup.type == FileGroupInfoType.TrackFiles + && fileGroup.discNo == disc + && fileGroup.album == album + && fileGroup.main.Extension.ToLower() == ext) + { + groupFound = fileGroup; + break; + } + } + if (groupFound == null) + { + groupFound = new FileGroupInfo(file, FileGroupInfoType.TrackFiles); + groupFound.discNo = disc; + groupFound.album = album; + groupFound.durations = new Dictionary(); + fileGroups.Add(groupFound); + } + groupFound.files.Add(file); + if (number > 0) groupFound.numbers.Add(file, number); + if (dur != TimeSpan.Zero) groupFound.durations.Add(file, dur); + } + } + fileGroups.RemoveAll(group => group.type == FileGroupInfoType.TrackFiles && group.files.Count < 2); + // tracks must be sorted according to tracknumer (or filename if missing) + foreach (FileGroupInfo group in fileGroups) + if (group.type == FileGroupInfoType.TrackFiles) + { + group.files.Sort(group.Compare()); + group.numbers = null; + group.TOC = new CDImageLayout(); + foreach (FileSystemInfo f in group.files) + { + if (!group.durations.ContainsKey(f)) + { + group.TOC = null; + break; + } + uint len = (uint)((group.durations[f].TotalMilliseconds * 75 + 500) / 1000); + group.TOC.AddTrack(new CDTrack((uint)group.TOC.TrackCount + 1, group.TOC.Length, len, true, false)); + } + } + fileGroups.Sort(FileGroupInfo.Compare); + return fileGroups; + } + + public void UseLocalDB(CUEToolsLocalDB db) + { + _useLocalDB = true; + _localDB = db; + } + + public string AccurateRipLog + { + get + { + using (StringWriter logWriter = new StringWriter()) + { + GenerateAccurateRipLog(logWriter); + return logWriter.ToString(); + } + } + } + + public string ExecuteScript(CUEToolsScript script) + { + if (!script.builtin) + return ExecuteScript(script.code); + + switch (script.name) + { + case "default": + return Go(); + case "only if found": + return ArVerify.ExceptionStatus != WebExceptionStatus.Success ? WriteReport() : Go(); + case "repair": + { + UseCUEToolsDB("CUETools " + CUEToolsVersion, null, true, CTDBMetadataSearch.None); + Action = CUEAction.Verify; + if (CTDB.DBStatus != null) + return CTDB.DBStatus; + bool useAR = _useAccurateRip; + _useAccurateRip = true; + Go(); + _useAccurateRip = useAR; + List choices = new List(); + foreach (DBEntry entry in CTDB.Entries) + if (!entry.hasErrors || entry.canRecover) + { + StringBuilder sb = new StringBuilder(); + if (entry.hasErrors) + { + sb.AppendFormat("Affected positions:\n"); + for (int sec = 0; sec < entry.repair.AffectedSectorArray.Length; sec++) + if (entry.repair.AffectedSectorArray[sec]) + sb.AppendFormat("{0}\n", CDImageLayout.TimeToString((uint)sec)); + } + CUEToolsSourceFile choice = new CUEToolsSourceFile(entry.Status, new StringReader(sb.ToString())); + choice.data = entry; + choices.Add(choice); + } + CUEToolsSourceFile selectedEntry = ChooseFile(choices, null, true); + if (selectedEntry == null) + return CTDB.Status; + CTDB.SelectedEntry = (DBEntry)selectedEntry.data; + if (!CTDB.SelectedEntry.hasErrors) + return CTDB.Status; + _useCUEToolsDBFix = true; + Action = CUEAction.Encode; + return Go(); + } + case "fix offset": + { + if (ArVerify.ExceptionStatus != WebExceptionStatus.Success) + return WriteReport(); + + WriteOffset = 0; + Action = CUEAction.Verify; + string status = Go(); + + uint tracksMatch; + int bestOffset; + FindBestOffset(Config.fixOffsetMinimumConfidence, !Config.fixOffsetToNearest, out tracksMatch, out bestOffset); + if (tracksMatch * 100 >= Config.fixOffsetMinimumTracksPercent * TrackCount) + { + WriteOffset = bestOffset; + Action = CUEAction.Encode; + status = Go(); + } + return status; + } + + case "encode if verified": + { + if (ArVerify.ExceptionStatus != WebExceptionStatus.Success) + return WriteReport(); + + Action = CUEAction.Verify; + string status = Go(); + + uint tracksMatch; + int bestOffset; + FindBestOffset(Config.encodeWhenConfidence, false, out tracksMatch, out bestOffset); + if (tracksMatch * 100 >= Config.encodeWhenPercent * TrackCount && (!_config.encodeWhenZeroOffset || bestOffset == 0)) + { + Action = CUEAction.Encode; + status = Go(); + } + return status; + } + } + + return "internal error"; + } + + public string ExecuteScript(string script) + { + AsmHelper helper = CompileScript(script); + return (string)helper.Invoke("*.Execute", this); + } + + public static AsmHelper CompileScript(string script) + { + //CSScript.GlobalSettings.InMemoryAsssembly = true; + //CSScript.GlobalSettings.HideAutoGeneratedFiles = + //CSScript.CacheEnabled = false; + return new AsmHelper(CSScript.LoadCode("using System; using System.Windows.Forms; using System.Net; using CUETools.Processor; using CUETools.Codecs; using CUETools.AccurateRip; public class Script { " + + "public static string Execute(CUESheet processor) { \r\n" + + script + + "\r\n } " + + " }", null, true)); + } + + public static bool TryCompileScript(string script) + { + AsmHelper helper = CompileScript(script); + return helper != null; + } + } +} diff --git a/CUETools.Processor/CUESheetAudio.cs b/CUETools.Processor/CUESheetAudio.cs new file mode 100644 index 0000000..34590d1 --- /dev/null +++ b/CUETools.Processor/CUESheetAudio.cs @@ -0,0 +1,119 @@ +using System; +using CUETools.Codecs; + +namespace CUETools.Processor +{ + public class CUESheetAudio : IAudioSource + { + private CUESheet cueSheet; + private IAudioSource currentAudio; + private int currentSource; + private long nextPos; + private long _samplePos, _sampleLen; + + public long Length + { + get { return _sampleLen; } + } + + public long Remaining + { + get { return _sampleLen - _samplePos; } + } + + public AudioPCMConfig PCM + { + get { return AudioPCMConfig.RedBook; } + } + + public string Path + { + get { return cueSheet.InputPath; } + } + + public long Position + { + get + { + return _samplePos; + } + set + { + SetPosition(value); + } + } + + public CUESheetAudio(CUESheet cueSheet) + { + this.cueSheet = cueSheet; + this.currentAudio = null; + this._samplePos = 0; + this._sampleLen = 0; + this.currentSource = -1; + this.nextPos = 0; + cueSheet._sources.ForEach(s => this._sampleLen += s.Length); + } + + private void SetPosition(long value) + { + if (value == _samplePos) + return; + long sourceStart = 0; + for (int iSource = 0; iSource < cueSheet._sources.Count; iSource++) + { + if (value >= sourceStart && value < sourceStart + cueSheet._sources[iSource].Length) + { + if (iSource != currentSource) + { + if (currentAudio != null) + currentAudio.Close(); + currentSource = iSource; + currentAudio = cueSheet.GetAudioSource(currentSource, false); + nextPos = sourceStart + cueSheet._sources[currentSource].Length; + } + currentAudio.Position = value - sourceStart + cueSheet._sources[currentSource].Offset; + _samplePos = value; + return; + } + sourceStart += cueSheet._sources[iSource].Length; + } + throw new Exception("Invalid position"); + } + + public void Close() + { + if (currentAudio != null) + { + currentAudio.Close(); + currentAudio = null; + } + } + + public int Read(AudioBuffer buff, int maxLength) + { + buff.Prepare(this, maxLength); + while (_samplePos >= nextPos) + { + currentSource++; + if (currentSource >= cueSheet._sources.Count) + { + buff.Length = 0; + return 0; + } + if (currentAudio != null) + currentAudio.Close(); + currentAudio = cueSheet.GetAudioSource(currentSource, false); + int offset = (int)(_samplePos - nextPos); + if (offset != 0) + currentAudio.Position += offset; + nextPos += cueSheet._sources[currentSource].Length; + } + int count = (int)(nextPos - _samplePos); + if (maxLength >= 0) + count = Math.Min(count, maxLength); + count = currentAudio.Read(buff, count); + _samplePos += count; + return count; + } + } +} diff --git a/CUETools.Processor/CUEStyle.cs b/CUETools.Processor/CUEStyle.cs new file mode 100644 index 0000000..d611af7 --- /dev/null +++ b/CUETools.Processor/CUEStyle.cs @@ -0,0 +1,26 @@ +namespace CUETools.Processor +{ + public enum CUEStyle + { + /// + /// Single file with embedded CUE + /// + SingleFileWithCUE, + /// + /// Single file with external CUE + /// + SingleFile, + /// + /// Gaps prepended file-per-track + /// + GapsPrepended, + /// + /// Gaps appended (noncompliant) file-per-track + /// + GapsAppended, + /// + /// Gaps left out file-per-track + /// + GapsLeftOut + } +} diff --git a/CUETools.Processor/CUETools.Processor.csproj b/CUETools.Processor/CUETools.Processor.csproj index 9cdba61..1d82f60 100644 --- a/CUETools.Processor/CUETools.Processor.csproj +++ b/CUETools.Processor/CUETools.Processor.csproj @@ -72,12 +72,44 @@ + + + + + + - + + + + + + + + + + + + + + + + + + + + + + - + + + + + + @@ -141,6 +173,9 @@ false + + +