diff --git a/DICUI.Library/CueSheets/CueFile.cs b/DICUI.Library/CueSheets/CueFile.cs index be543476..bd0bfda3 100644 --- a/DICUI.Library/CueSheets/CueFile.cs +++ b/DICUI.Library/CueSheets/CueFile.cs @@ -1,10 +1,10 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.IO; /// /// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf /// -namespace DICUI.Library.CueSheets +namespace DICUI.CueSheets { /// /// The audio or data file’s filetype @@ -45,7 +45,7 @@ namespace DICUI.Library.CueSheets /// /// filename /// - public string Name { get; set; } + public string FileName { get; set; } /// /// filetype @@ -56,5 +56,94 @@ namespace DICUI.Library.CueSheets /// List of TRACK in FILE /// public List Tracks { get; set; } + + /// + /// Create an empty FILE + /// + public CueFile() + { + } + + /// + /// Fill a FILE from a stream reader + /// + /// File name to set + /// File type to set + /// StreamReader to pull from + public CueFile(string fileName, string fileType, StreamReader sr) + { + if (sr == null) + return; + + // Set the current fields + this.FileName = fileName; + this.FileType = GetFileType(fileType); + + while (!sr.EndOfStream) + { + string line = sr.ReadLine().Trim(); + string[] splitLine = line.Split(' '); + + // If we have an empty line, we skip + if (string.IsNullOrWhiteSpace(line)) + continue; + + switch (splitLine[0]) + { + // Read comments + case "REM": + // We ignore all comments for now + break; + + // Read track information + case "TRACK": + if (splitLine.Length < 3) + continue; // TODO: Make this throw an exception + + if (this.Tracks == null) + this.Tracks = new List(); + + var track = new CueTrack(splitLine[1], splitLine[2], sr); + if (track == default) + continue; // TODO: Make this throw an exception + + this.Tracks.Add(track); + break; + + // Default means return + default: + return; + } + } + } + + /// + /// Get the file type from a given string + /// + /// String to get value from + /// CueFileType, if possible (default BINARY) + private CueFileType GetFileType(string fileType) + { + switch (fileType.ToLowerInvariant()) + { + case "binary": + return CueFileType.BINARY; + + case "motorola": + return CueFileType.MOTOROLA; + + case "aiff": + return CueFileType.AIFF; + + case "wave": + return CueFileType.WAVE; + + case "mp3": + return CueFileType.MP3; + + default: + return CueFileType.BINARY; + } + } } } diff --git a/DICUI.Library/CueSheets/CueIndex.cs b/DICUI.Library/CueSheets/CueIndex.cs index 3a418da6..9fcbecb6 100644 --- a/DICUI.Library/CueSheets/CueIndex.cs +++ b/DICUI.Library/CueSheets/CueIndex.cs @@ -1,7 +1,9 @@ -/// +using System.Linq; + +/// /// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf /// -namespace DICUI.Library.CueSheets +namespace DICUI.CueSheets { /// /// Represents a single INDEX in a TRACK @@ -9,25 +11,86 @@ namespace DICUI.Library.CueSheets public class CueIndex { /// - /// number, between 0 and 99 + /// INDEX number, between 0 and 99 /// public int Index { get; set; } /// - /// Starting time of index in minutes + /// Starting time of INDEX in minutes /// public int Minutes { get; set; } /// - /// Starting time of index in seconds + /// Starting time of INDEX in seconds /// /// There are 60 seconds in a minute public int Seconds { get; set; } /// - /// Starting time of index in frames. + /// Starting time of INDEX in frames. /// /// There are 75 frames per second public int Frames { get; set; } + + /// + /// Create an empty INDEX + /// + public CueIndex() + { + } + + /// + /// Fill a INDEX from a stream reader + /// + /// Index to set + /// Start time to set + public CueIndex(string index, string startTime) + { + // Set the current fields + if (!int.TryParse(index, out int parsedIndex)) + return; // TODO: Make this throw an exception + else if (parsedIndex < 1 || parsedIndex > 99) + return; // TODO: Make this throw an exception + + // Ignore empty lines + if (string.IsNullOrWhiteSpace(startTime)) + return; // TODO: Make this throw an exception + + // Ignore lines that don't contain the correct information + if (startTime.Length != 8 || startTime.Count(c => c == ':') != 2) + return; // TODO: Make this throw an exception + + // Split the line + string[] splitTime = startTime.Split(':'); + if (splitTime.Length != 3) + return; // TODO: Make this throw an exception + + // Parse the lengths + int[] lengthSegments = new int[3]; + + // Minutes + if (!int.TryParse(splitTime[0], out lengthSegments[0])) + return; // TODO: Make this throw an exception + else if (lengthSegments[0] < 0) + return; // TODO: Make this throw an exception + + // Seconds + if (!int.TryParse(splitTime[1], out lengthSegments[1])) + return; // TODO: Make this throw an exception + else if (lengthSegments[1] < 0 || lengthSegments[1] > 60) + return; // TODO: Make this throw an exception + + // Frames + if (!int.TryParse(splitTime[2], out lengthSegments[2])) + return; // TODO: Make this throw an exception + else if (lengthSegments[2] < 0 || lengthSegments[2] > 75) + return; // TODO: Make this throw an exception + + // Set the values + this.Index = parsedIndex; + this.Minutes = lengthSegments[0]; + this.Seconds = lengthSegments[1]; + this.Frames = lengthSegments[2]; + } } } diff --git a/DICUI.Library/CueSheets/CueSheet.cs b/DICUI.Library/CueSheets/CueSheet.cs index 09ba08fe..952d1ad7 100644 --- a/DICUI.Library/CueSheets/CueSheet.cs +++ b/DICUI.Library/CueSheets/CueSheet.cs @@ -1,13 +1,11 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.IO; /// /// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf /// -namespace DICUI.Library.CueSheets +namespace DICUI.CueSheets { /// /// Represents a single cuesheet @@ -43,5 +41,108 @@ namespace DICUI.Library.CueSheets /// List of FILE in cuesheet /// public List Files { get; set; } + + /// + /// Create an empty cuesheet + /// + public CueSheet() + { + } + + /// + /// Create a cuesheet from a file, if possible + /// + /// + public CueSheet(string filename) + { + // Check that the file exists + if (!File.Exists(filename)) + return; + + // Check the extension + string ext = Path.GetExtension(filename).TrimStart('.'); + if (!string.Equals(ext, "cue", StringComparison.OrdinalIgnoreCase) + && !string.Equals(ext, "txt", StringComparison.OrdinalIgnoreCase)) + { + return; + } + + // Open the file and begin reading + using (var sr = new StreamReader(filename)) + { + while (!sr.EndOfStream) + { + string line = sr.ReadLine().Trim(); + string[] splitLine = line.Split(' '); + + // If we have an empty line, we skip + if (string.IsNullOrWhiteSpace(line)) + continue; + + switch (splitLine[0]) + { + // Read comments + case "REM": + // We ignore all comments for now + break; + + // Read MCN + case "CATALOG": + if (splitLine.Length < 2) + continue; // TODO: Make this throw an exception + + this.Catalog = splitLine[1]; + break; + + // Read external CD-Text file path + case "CDTEXTFILE": + if (splitLine.Length < 2) + continue; // TODO: Make this throw an exception + + this.CdTextFile = splitLine[1]; + break; + + // Read CD-Text enhanced performer + case "PERFORMER": + if (splitLine.Length < 2) + continue; // TODO: Make this throw an exception + + this.Performer = splitLine[1]; + break; + + // Read CD-Text enhanced songwriter + case "SONGWRITER": + if (splitLine.Length < 2) + continue; // TODO: Make this throw an exception + + this.Songwriter = splitLine[1]; + break; + + // Read CD-Text enhanced title + case "TITLE": + if (splitLine.Length < 2) + continue; // TODO: Make this throw an exception + + this.Title = splitLine[1]; + break; + + // Read file information + case "FILE": + if (splitLine.Length < 3) + continue; // TODO: Make this throw an exception + + if (this.Files == null) + this.Files = new List(); + + var file = new CueFile(splitLine[1], splitLine[2], sr); + if (file == default) + continue; // TODO: Make this throw an exception + + this.Files.Add(file); + break; + } + } + } + } } } diff --git a/DICUI.Library/CueSheets/CueTrack.cs b/DICUI.Library/CueSheets/CueTrack.cs index 2cc8d949..72b46cb3 100644 --- a/DICUI.Library/CueSheets/CueTrack.cs +++ b/DICUI.Library/CueSheets/CueTrack.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; +using System.IO; /// /// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf /// -namespace DICUI.Library.CueSheets +namespace DICUI.CueSheets { /// /// Track datatype @@ -140,5 +141,215 @@ namespace DICUI.Library.CueSheets /// POSTGAP /// public PostGap PostGap { get; set; } + + /// + /// Create an empty TRACK + /// + public CueTrack() + { + } + + /// + /// Fill a TRACK from a stream reader + /// + /// Number to set + /// Data type to set + /// StreamReader to pull from + public CueTrack(string number, string dataType, StreamReader sr) + { + if (sr == null) + return; + + // Set the current fields + if (!int.TryParse(number, out int parsedNumber)) + return; // TODO: Make this throw an exception + else if (parsedNumber < 1 || parsedNumber > 99) + return; // TODO: Make this throw an exception + + this.Number = parsedNumber; + this.DataType = GetDataType(dataType); + + while (!sr.EndOfStream) + { + string line = sr.ReadLine().Trim(); + string[] splitLine = line.Split(' '); + + // If we have an empty line, we skip + if (string.IsNullOrWhiteSpace(line)) + continue; + + switch (splitLine[0]) + { + // Read comments + case "REM": + // We ignore all comments for now + break; + + // Read flag information + case "FLAGS": + if (splitLine.Length < 2) + continue; // TODO: Make this throw an exception + + this.Flags = GetFlags(splitLine); + break; + + // Read International Standard Recording Code + case "ISRC": + if (splitLine.Length < 2) + continue; // TODO: Make this throw an exception + + this.ISRC = splitLine[1]; + break; + + // Read CD-Text enhanced performer + case "PERFORMER": + if (splitLine.Length < 2) + continue; // TODO: Make this throw an exception + + this.Performer = splitLine[1]; + break; + + // Read CD-Text enhanced songwriter + case "SONGWRITER": + if (splitLine.Length < 2) + continue; // TODO: Make this throw an exception + + this.Songwriter = splitLine[1]; + break; + + // Read CD-Text enhanced title + case "TITLE": + if (splitLine.Length < 2) + continue; // TODO: Make this throw an exception + + this.Title = splitLine[1]; + break; + + // Read pregap information + case "PREGAP": + if (splitLine.Length < 2) + continue; // TODO: Make this throw an exception + + var pregap = new PreGap(splitLine[1]); + if (pregap == default) + continue; // TODO: Make this throw an exception + + this.PreGap = pregap; + break; + + // Read index information + case "INDEX": + if (splitLine.Length < 3) + continue; // TODO: Make this throw an exception + + if (this.Indices == null) + this.Indices = new List(); + + var index = new CueIndex(splitLine[1], splitLine[2]); + if (index == default) + continue; // TODO: Make this throw an exception + + this.Indices.Add(index); + break; + + // Read postgap information + case "POSTGAP": + if (splitLine.Length < 2) + continue; // TODO: Make this throw an exception + + var postgap = new PostGap(splitLine[1]); + if (postgap == default) + continue; // TODO: Make this throw an exception + + this.PostGap = postgap; + break; + + // Default means return + default: + return; + } + } + } + + /// + /// Get the data type from a given string + /// + /// String to get value from + /// CueTrackDataType, if possible (default AUDIO) + private CueTrackDataType GetDataType(string dataType) + { + switch (dataType.ToLowerInvariant()) + { + case "audio": + return CueTrackDataType.AUDIO; + + case "cdg": + return CueTrackDataType.CDG; + + case "mode1/2048": + return CueTrackDataType.MODE1_2048; + + case "mode1/2352": + return CueTrackDataType.MODE1_2352; + + case "mode2/2336": + return CueTrackDataType.MODE2_2336; + + case "mode2/2352": + return CueTrackDataType.MODE2_2352; + + case "cdi/2336": + return CueTrackDataType.CDI_2336; + + case "cdi/2352": + return CueTrackDataType.CDI_2352; + + default: + return CueTrackDataType.AUDIO; + } + } + + /// + /// Get the flag value for an array of strings + /// + /// Possible flags as strings + /// CueTrackFlag value representing the strings, if possible + private CueTrackFlag GetFlags(string[] flagStrings) + { + CueTrackFlag flag = 0; + + foreach (string flagString in flagStrings) + { + // TODO: Make default throw an exception + switch (flagString.ToLowerInvariant()) + { + case "flags": + // No-op since this is the start of the line + break; + + case "dcp": + flag |= CueTrackFlag.DCP; + break; + + case "4ch": + flag |= CueTrackFlag.FourCH; + break; + + case "pre": + flag |= CueTrackFlag.PRE; + break; + + case "scms": + flag |= CueTrackFlag.SCMS; + break; + + case "data": + flag |= CueTrackFlag.DATA; + break; + } + } + + return flag; + } } } diff --git a/DICUI.Library/CueSheets/PostGap.cs b/DICUI.Library/CueSheets/PostGap.cs index 07663cfa..e5c572b6 100644 --- a/DICUI.Library/CueSheets/PostGap.cs +++ b/DICUI.Library/CueSheets/PostGap.cs @@ -1,28 +1,82 @@ -/// +using System.Linq; + +/// /// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf /// -namespace DICUI.Library.CueSheets +namespace DICUI.CueSheets { /// - /// Represents postgap information of a track + /// Represents POSTGAP information of a track /// public class PostGap { /// - /// Length of postgap in minutes + /// Length of POSTGAP in minutes /// public int Minutes { get; set; } /// - /// Length of postgap in seconds + /// Length of POSTGAP in seconds /// /// There are 60 seconds in a minute public int Seconds { get; set; } /// - /// Length of postgap in frames. + /// Length of POSTGAP in frames. /// /// There are 75 frames per second public int Frames { get; set; } + + /// Create an empty POSTGAP + /// + public PostGap() + { + } + + /// + /// Create a POSTGAP from a mm:ss:ff length + /// + /// String to get length information from + public PostGap(string length) + { + // Ignore empty lines + if (string.IsNullOrWhiteSpace(length)) + return; // TODO: Make this throw an exception + + // Ignore lines that don't contain the correct information + if (length.Length != 8 || length.Count(c => c == ':') != 2) + return; // TODO: Make this throw an exception + + // Split the line + string[] splitLength = length.Split(':'); + if (splitLength.Length != 3) + return; // TODO: Make this throw an exception + + // Parse the lengths + int[] lengthSegments = new int[3]; + + // Minutes + if (!int.TryParse(splitLength[0], out lengthSegments[0])) + return; // TODO: Make this throw an exception + else if (lengthSegments[0] < 0) + return; // TODO: Make this throw an exception + + // Seconds + if (!int.TryParse(splitLength[1], out lengthSegments[1])) + return; // TODO: Make this throw an exception + else if (lengthSegments[1] < 0 || lengthSegments[1] > 60) + return; // TODO: Make this throw an exception + + // Frames + if (!int.TryParse(splitLength[2], out lengthSegments[2])) + return; // TODO: Make this throw an exception + else if (lengthSegments[2] < 0 || lengthSegments[2] > 75) + return; // TODO: Make this throw an exception + + // Set the values + this.Minutes = lengthSegments[0]; + this.Seconds = lengthSegments[1]; + this.Frames = lengthSegments[2]; + } } } diff --git a/DICUI.Library/CueSheets/PreGap.cs b/DICUI.Library/CueSheets/PreGap.cs index 219fee53..0cd65e42 100644 --- a/DICUI.Library/CueSheets/PreGap.cs +++ b/DICUI.Library/CueSheets/PreGap.cs @@ -1,28 +1,83 @@ -/// +using System.Linq; + +/// /// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf /// -namespace DICUI.Library.CueSheets +namespace DICUI.CueSheets { /// - /// Represents pregap information of a track + /// Represents PREGAP information of a track /// public class PreGap { /// - /// Length of postgap in minutes + /// Length of PREGAP in minutes /// public int Minutes { get; set; } /// - /// Length of postgap in seconds + /// Length of PREGAP in seconds /// /// There are 60 seconds in a minute public int Seconds { get; set; } /// - /// Length of postgap in frames. + /// Length of PREGAP in frames. /// /// There are 75 frames per second public int Frames { get; set; } + + /// + /// Create an empty PREGAP + /// + public PreGap() + { + } + + /// + /// Create a PREGAP from a mm:ss:ff length + /// + /// String to get length information from + public PreGap(string length) + { + // Ignore empty lines + if (string.IsNullOrWhiteSpace(length)) + return; // TODO: Make this throw an exception + + // Ignore lines that don't contain the correct information + if (length.Length != 8 || length.Count(c => c == ':') != 2) + return; // TODO: Make this throw an exception + + // Split the line + string[] splitLength = length.Split(':'); + if (splitLength.Length != 3) + return; // TODO: Make this throw an exception + + // Parse the lengths + int[] lengthSegments = new int[3]; + + // Minutes + if (!int.TryParse(splitLength[0], out lengthSegments[0])) + return; // TODO: Make this throw an exception + else if (lengthSegments[0] < 0) + return; // TODO: Make this throw an exception + + // Seconds + if (!int.TryParse(splitLength[1], out lengthSegments[1])) + return; // TODO: Make this throw an exception + else if (lengthSegments[1] < 0 || lengthSegments[1] > 60) + return; // TODO: Make this throw an exception + + // Frames + if (!int.TryParse(splitLength[2], out lengthSegments[2])) + return; // TODO: Make this throw an exception + else if (lengthSegments[2] < 0 || lengthSegments[2] > 75) + return; // TODO: Make this throw an exception + + // Set the values + this.Minutes = lengthSegments[0]; + this.Seconds = lengthSegments[1]; + this.Frames = lengthSegments[2]; + } } }