From 226031f3bdf27e125cd0f485d5ad5bce577d802c Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Wed, 13 Sep 2023 14:55:52 -0400 Subject: [PATCH] Port cuesheet serialization from MPF --- Files/CueSheet.Deserializer.cs | 625 +++++++++++++++++++++++++++++++ Files/CueSheet.Serializer.cs | 30 ++ Streams/CueSheet.Deserializer.cs | 15 + Streams/CueSheet.Serializer.cs | 281 ++++++++++++++ 4 files changed, 951 insertions(+) create mode 100644 Files/CueSheet.Deserializer.cs create mode 100644 Files/CueSheet.Serializer.cs create mode 100644 Streams/CueSheet.Deserializer.cs create mode 100644 Streams/CueSheet.Serializer.cs diff --git a/Files/CueSheet.Deserializer.cs b/Files/CueSheet.Deserializer.cs new file mode 100644 index 00000000..90839777 --- /dev/null +++ b/Files/CueSheet.Deserializer.cs @@ -0,0 +1,625 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using SabreTools.Models.CueSheets; + +namespace SabreTools.Serialization.Files +{ + public partial class CueSheet : IFileSerializer + { + /// +#if NET48 + public Models.CueSheets.CueSheet Deserialize(string path) +#else + public Models.CueSheets.CueSheet? Deserialize(string? path) +#endif + { + // Check that the file exists + if (string.IsNullOrWhiteSpace(path) || !File.Exists(path)) + return null; + + // Check the extension + string ext = Path.GetExtension(path).TrimStart('.'); + if (!string.Equals(ext, "cue", StringComparison.OrdinalIgnoreCase) + && !string.Equals(ext, "txt", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + // Create the holding objects + var cueSheet = new Models.CueSheets.CueSheet(); + var cueFiles = new List(); + + // Open the file and begin reading + string[] cueLines = File.ReadAllLines(path); + for (int i = 0; i < cueLines.Length; i++) + { + string line = cueLines[i].Trim(); + + // http://stackoverflow.com/questions/554013/regular-expression-to-split-on-spaces-unless-in-quotes + string[] splitLine = Regex + .Matches(line, @"[^\s""]+|""[^""]*""") + .Cast() + .Select(m => m.Groups[0].Value) + .ToArray(); + + // 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) + throw new FormatException($"CATALOG line malformed: {line}"); + + cueSheet.Catalog = splitLine[1]; + break; + + // Read external CD-Text file path + case "CDTEXTFILE": + if (splitLine.Length < 2) + throw new FormatException($"CDTEXTFILE line malformed: {line}"); + + cueSheet.CdTextFile = splitLine[1]; + break; + + // Read CD-Text enhanced performer + case "PERFORMER": + if (splitLine.Length < 2) + throw new FormatException($"PERFORMER line malformed: {line}"); + + cueSheet.Performer = splitLine[1]; + break; + + // Read CD-Text enhanced songwriter + case "SONGWRITER": + if (splitLine.Length < 2) + throw new FormatException($"SONGWRITER line malformed: {line}"); + + cueSheet.Songwriter = splitLine[1]; + break; + + // Read CD-Text enhanced title + case "TITLE": + if (splitLine.Length < 2) + throw new FormatException($"TITLE line malformed: {line}"); + + cueSheet.Title = splitLine[1]; + break; + + // Read file information + case "FILE": + if (splitLine.Length < 3) + throw new FormatException($"FILE line malformed: {line}"); + + var file = CreateCueFile(splitLine[1], splitLine[2], cueLines, ref i); + if (file == default) + throw new FormatException($"FILE line malformed: {line}"); + + cueFiles.Add(file); + break; + } + } + + cueSheet.Files = cueFiles.ToArray(); + return cueSheet; + } + + /// + /// Fill a FILE from an array of lines + /// + /// File name to set + /// File type to set + /// Lines array to pull from + /// Reference to index in array +#if NET48 + private static CueFile CreateCueFile(string fileName, string fileType, string[] cueLines, ref int i) +#else + private static CueFile? CreateCueFile(string fileName, string fileType, string[]? cueLines, ref int i) +#endif + { + // Check the required parameters + if (cueLines == null) + throw new ArgumentNullException(nameof(cueLines)); + else if (i < 0 || i > cueLines.Length) + throw new IndexOutOfRangeException(); + + // Create the holding objects + var cueFile = new CueFile(); + var cueTracks = new List(); + + // Set the current fields + cueFile.FileName = fileName.Trim('"'); + cueFile.FileType = GetFileType(fileType); + + // Increment to start + i++; + + for (; i < cueLines.Length; i++) + { + string line = cueLines[i].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) + throw new FormatException($"TRACK line malformed: {line}"); + + var track = CreateCueTrack(splitLine[1], splitLine[2], cueLines, ref i); + if (track == default) + throw new FormatException($"TRACK line malformed: {line}"); + + cueTracks.Add(track); + break; + + // Default means return + default: + i--; + return null; + } + } + + cueFile.Tracks = cueTracks.ToArray(); + return cueFile; + } + + /// + /// Fill a TRACK from an array of lines + /// + /// Number to set + /// Data type to set + /// Lines array to pull from + /// Reference to index in array +#if NET48 + private static CueTrack CreateCueTrack(string number, string dataType, string[] cueLines, ref int i) +#else + private static CueTrack? CreateCueTrack(string number, string dataType, string[]? cueLines, ref int i) +#endif + { + // Check the required parameters + if (cueLines == null) + throw new ArgumentNullException(nameof(cueLines)); + else if (i < 0 || i > cueLines.Length) + throw new IndexOutOfRangeException(); + + // Set the current fields + if (!int.TryParse(number, out int parsedNumber)) + throw new ArgumentException($"Number was not a number: {number}"); + else if (parsedNumber < 1 || parsedNumber > 99) + throw new IndexOutOfRangeException($"Index must be between 1 and 99: {parsedNumber}"); + + // Create the holding objects + var cueTrack = new CueTrack(); + var cueIndices = new List(); + + cueTrack.Number = parsedNumber; + cueTrack.DataType = GetDataType(dataType); + + // Increment to start + i++; + + for (; i < cueLines.Length; i++) + { + string line = cueLines[i].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) + throw new FormatException($"FLAGS line malformed: {line}"); + + cueTrack.Flags = GetFlags(splitLine); + break; + + // Read International Standard Recording Code + case "ISRC": + if (splitLine.Length < 2) + throw new FormatException($"ISRC line malformed: {line}"); + + cueTrack.ISRC = splitLine[1]; + break; + + // Read CD-Text enhanced performer + case "PERFORMER": + if (splitLine.Length < 2) + throw new FormatException($"PERFORMER line malformed: {line}"); + + cueTrack.Performer = splitLine[1]; + break; + + // Read CD-Text enhanced songwriter + case "SONGWRITER": + if (splitLine.Length < 2) + throw new FormatException($"SONGWRITER line malformed: {line}"); + + cueTrack.Songwriter = splitLine[1]; + break; + + // Read CD-Text enhanced title + case "TITLE": + if (splitLine.Length < 2) + throw new FormatException($"TITLE line malformed: {line}"); + + cueTrack.Title = splitLine[1]; + break; + + // Read pregap information + case "PREGAP": + if (splitLine.Length < 2) + throw new FormatException($"PREGAP line malformed: {line}"); + + var pregap = CreatePreGap(splitLine[1]); + if (pregap == default) + throw new FormatException($"PREGAP line malformed: {line}"); + + cueTrack.PreGap = pregap; + break; + + // Read index information + case "INDEX": + if (splitLine.Length < 3) + throw new FormatException($"INDEX line malformed: {line}"); + + var index = CreateCueIndex(splitLine[1], splitLine[2]); + if (index == default) + throw new FormatException($"INDEX line malformed: {line}"); + + cueIndices.Add(index); + break; + + // Read postgap information + case "POSTGAP": + if (splitLine.Length < 2) + throw new FormatException($"POSTGAP line malformed: {line}"); + + var postgap = CreatePostGap(splitLine[1]); + if (postgap == default) + throw new FormatException($"POSTGAP line malformed: {line}"); + + cueTrack.PostGap = postgap; + break; + + // Default means return + default: + i--; + return null; + } + } + + cueTrack.Indices = cueIndices.ToArray(); + return cueTrack; + } + + /// + /// Create a PREGAP from a mm:ss:ff length + /// + /// String to get length information from +#if NET48 + private static PreGap CreatePreGap(string length) +#else + private static PreGap CreatePreGap(string? length) +#endif + { + // Ignore empty lines + if (string.IsNullOrWhiteSpace(length)) + throw new ArgumentException("Length was null or whitespace"); + + // Ignore lines that don't contain the correct information + if (length.Length != 8 || length.Count(c => c == ':') != 2) + throw new FormatException($"Length was not in a recognized format: {length}"); + + // Split the line + string[] splitLength = length.Split(':'); + if (splitLength.Length != 3) + throw new FormatException($"Length was not in a recognized format: {length}"); + + // Parse the lengths + int[] lengthSegments = new int[3]; + + // Minutes + if (!int.TryParse(splitLength[0], out lengthSegments[0])) + throw new FormatException($"Minutes segment was not a number: {splitLength[0]}"); + else if (lengthSegments[0] < 0) + throw new IndexOutOfRangeException($"Minutes segment must be 0 or greater: {lengthSegments[0]}"); + + // Seconds + if (!int.TryParse(splitLength[1], out lengthSegments[1])) + throw new FormatException($"Seconds segment was not a number: {splitLength[1]}"); + else if (lengthSegments[1] < 0 || lengthSegments[1] > 60) + throw new IndexOutOfRangeException($"Seconds segment must be between 0 and 60: {lengthSegments[1]}"); + + // Frames + if (!int.TryParse(splitLength[2], out lengthSegments[2])) + throw new FormatException($"Frames segment was not a number: {splitLength[2]}"); + else if (lengthSegments[2] < 0 || lengthSegments[2] > 75) + throw new IndexOutOfRangeException($"Frames segment must be between 0 and 75: {lengthSegments[2]}"); + + // Set the values + var preGap = new PreGap + { + Minutes = lengthSegments[0], + Seconds = lengthSegments[1], + Frames = lengthSegments[2], + }; + return preGap; + } + + /// + /// Fill a INDEX from an array of lines + /// + /// Index to set + /// Start time to set +#if NET48 + private static CueIndex CreateCueIndex(string index, string startTime) +#else + private static CueIndex CreateCueIndex(string? index, string? startTime) +#endif + { + // Set the current fields + if (!int.TryParse(index, out int parsedIndex)) + throw new ArgumentException($"Index was not a number: {index}"); + else if (parsedIndex < 0 || parsedIndex > 99) + throw new IndexOutOfRangeException($"Index must be between 0 and 99: {parsedIndex}"); + + // Ignore empty lines + if (string.IsNullOrWhiteSpace(startTime)) + throw new ArgumentException("Start time was null or whitespace"); + + // Ignore lines that don't contain the correct information + if (startTime.Length != 8 || startTime.Count(c => c == ':') != 2) + throw new FormatException($"Start time was not in a recognized format: {startTime}"); + + // Split the line + string[] splitTime = startTime.Split(':'); + if (splitTime.Length != 3) + throw new FormatException($"Start time was not in a recognized format: {startTime}"); + + // Parse the lengths + int[] lengthSegments = new int[3]; + + // Minutes + if (!int.TryParse(splitTime[0], out lengthSegments[0])) + throw new FormatException($"Minutes segment was not a number: {splitTime[0]}"); + else if (lengthSegments[0] < 0) + throw new IndexOutOfRangeException($"Minutes segment must be 0 or greater: {lengthSegments[0]}"); + + // Seconds + if (!int.TryParse(splitTime[1], out lengthSegments[1])) + throw new FormatException($"Seconds segment was not a number: {splitTime[1]}"); + else if (lengthSegments[1] < 0 || lengthSegments[1] > 60) + throw new IndexOutOfRangeException($"Seconds segment must be between 0 and 60: {lengthSegments[1]}"); + + // Frames + if (!int.TryParse(splitTime[2], out lengthSegments[2])) + throw new FormatException($"Frames segment was not a number: {splitTime[2]}"); + else if (lengthSegments[2] < 0 || lengthSegments[2] > 75) + throw new IndexOutOfRangeException($"Frames segment must be between 0 and 75: {lengthSegments[2]}"); + + // Set the values + var cueIndex = new CueIndex + { + Index = parsedIndex, + Minutes = lengthSegments[0], + Seconds = lengthSegments[1], + Frames = lengthSegments[2], + }; + return cueIndex; + } + + /// + /// Create a POSTGAP from a mm:ss:ff length + /// + /// String to get length information from +#if NET48 + private static PostGap CreatePostGap(string length) +#else + private static PostGap CreatePostGap(string? length) +#endif + { + // Ignore empty lines + if (string.IsNullOrWhiteSpace(length)) + throw new ArgumentException("Length was null or whitespace"); + + // Ignore lines that don't contain the correct information + if (length.Length != 8 || length.Count(c => c == ':') != 2) + throw new FormatException($"Length was not in a recognized format: {length}"); + + // Split the line + string[] splitLength = length.Split(':'); + if (splitLength.Length != 3) + throw new FormatException($"Length was not in a recognized format: {length}"); + + // Parse the lengths + int[] lengthSegments = new int[3]; + + // Minutes + if (!int.TryParse(splitLength[0], out lengthSegments[0])) + throw new FormatException($"Minutes segment was not a number: {splitLength[0]}"); + else if (lengthSegments[0] < 0) + throw new IndexOutOfRangeException($"Minutes segment must be 0 or greater: {lengthSegments[0]}"); + + // Seconds + if (!int.TryParse(splitLength[1], out lengthSegments[1])) + throw new FormatException($"Seconds segment was not a number: {splitLength[1]}"); + else if (lengthSegments[1] < 0 || lengthSegments[1] > 60) + throw new IndexOutOfRangeException($"Seconds segment must be between 0 and 60: {lengthSegments[1]}"); + + // Frames + if (!int.TryParse(splitLength[2], out lengthSegments[2])) + throw new FormatException($"Frames segment was not a number: {splitLength[2]}"); + else if (lengthSegments[2] < 0 || lengthSegments[2] > 75) + throw new IndexOutOfRangeException($"Frames segment must be between 0 and 75: {lengthSegments[2]}"); + + // Set the values + var postGap = new PostGap + { + Minutes = lengthSegments[0], + Seconds = lengthSegments[1], + Frames = lengthSegments[2], + }; + return postGap; + } + + #region Helpers + + /// + /// Get the file type from a given string + /// + /// String to get value from + /// CueFileType, if possible +#if NET48 + private static CueFileType GetFileType(string fileType) +#else + private static CueFileType GetFileType(string? fileType) +#endif + { + 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; + } + } + + /// + /// Get the data type from a given string + /// + /// String to get value from + /// CueTrackDataType, if possible (default AUDIO) +#if NET48 + private static CueTrackDataType GetDataType(string dataType) +#else + private static CueTrackDataType GetDataType(string? dataType) +#endif + { + 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 +#if NET48 + private static CueTrackFlag GetFlags(string[] flagStrings) +#else + private static CueTrackFlag GetFlags(string?[]? flagStrings) +#endif + { + CueTrackFlag flag = 0; + if (flagStrings == null) + return flag; + +#if NET48 + foreach (string flagString in flagStrings) +#else + foreach (string? flagString in flagStrings) +#endif + { + 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; + } + + #endregion + } +} \ No newline at end of file diff --git a/Files/CueSheet.Serializer.cs b/Files/CueSheet.Serializer.cs new file mode 100644 index 00000000..b30cfca8 --- /dev/null +++ b/Files/CueSheet.Serializer.cs @@ -0,0 +1,30 @@ +using System.IO; + +namespace SabreTools.Serialization.Files +{ + public partial class CueSheet : IFileSerializer + { + /// +#if NET48 + public bool Serialize(Models.CueSheets.CueSheet obj, string path) +#else + public bool Serialize(Models.CueSheets.CueSheet? obj, string? path) +#endif + { + if (string.IsNullOrWhiteSpace(path)) + return false; + + using (var stream = new Streams.CueSheet().Serialize(obj)) + { + if (stream == null) + return false; + + using (var fs = File.OpenWrite(path)) + { + stream.CopyTo(fs); + return true; + } + } + } + } +} \ No newline at end of file diff --git a/Streams/CueSheet.Deserializer.cs b/Streams/CueSheet.Deserializer.cs new file mode 100644 index 00000000..97fa4092 --- /dev/null +++ b/Streams/CueSheet.Deserializer.cs @@ -0,0 +1,15 @@ +using System; +using System.IO; + +namespace SabreTools.Serialization.Streams +{ + public partial class CueSheet : IStreamSerializer + { + /// +#if NET48 + public Models.CueSheets.CueSheet Deserialize(Stream data) => throw new NotImplementedException(); +#else + public Models.CueSheets.CueSheet? Deserialize(Stream? data) => throw new NotImplementedException(); +#endif + } +} \ No newline at end of file diff --git a/Streams/CueSheet.Serializer.cs b/Streams/CueSheet.Serializer.cs new file mode 100644 index 00000000..e6d5b13e --- /dev/null +++ b/Streams/CueSheet.Serializer.cs @@ -0,0 +1,281 @@ +using System; +using System.IO; +using System.Text; +using SabreTools.Models.CueSheets; + +namespace SabreTools.Serialization.Streams +{ + public partial class CueSheet : IStreamSerializer + { + /// +#if NET48 + public Stream Serialize(Models.CueSheets.CueSheet obj) +#else + public Stream? Serialize(Models.CueSheets.CueSheet? obj) +#endif + { + // If the cuesheet is null + if (obj == null) + return null; + + + + // If we don't have any files, it's invalid + if (obj?.Files == null) + throw new ArgumentNullException(nameof(obj.Files)); + else if (obj.Files.Length == 0) + throw new ArgumentException("No files provided to write"); + + // Setup the writer and output + var stream = new MemoryStream(); + var writer = new StreamWriter(stream, Encoding.ASCII, 1024, true); + + // Write the file + WriteCueSheet(obj, writer); + + // Return the stream + stream.Seek(0, SeekOrigin.Begin); + return stream; + } + + /// + /// Write the cuesheet out to a stream + /// + /// CueSheet to write + /// StreamWriter to write to + private static void WriteCueSheet(Models.CueSheets.CueSheet cueSheet, StreamWriter sw) + { + // If we don't have any files, it's invalid + if (cueSheet.Files == null) + throw new ArgumentNullException(nameof(cueSheet.Files)); + else if (cueSheet.Files.Length == 0) + throw new ArgumentException("No files provided to write"); + + if (!string.IsNullOrEmpty(cueSheet.Catalog)) + sw.WriteLine($"CATALOG {cueSheet.Catalog}"); + + if (!string.IsNullOrEmpty(cueSheet.CdTextFile)) + sw.WriteLine($"CDTEXTFILE {cueSheet.CdTextFile}"); + + if (!string.IsNullOrEmpty(cueSheet.Performer)) + sw.WriteLine($"PERFORMER {cueSheet.Performer}"); + + if (!string.IsNullOrEmpty(cueSheet.Songwriter)) + sw.WriteLine($"SONGWRITER {cueSheet.Songwriter}"); + + if (!string.IsNullOrEmpty(cueSheet.Title)) + sw.WriteLine($"TITLE {cueSheet.Title}"); + + foreach (var cueFile in cueSheet.Files) + { + WriteCueFile(cueFile, sw); + } + } + + /// + /// Write the FILE out to a stream + /// + /// CueFile to write + /// StreamWriter to write to +#if NET48 + private static void WriteCueFile(CueFile cueFile, StreamWriter sw) +#else + private static void WriteCueFile(CueFile? cueFile, StreamWriter sw) +#endif + { + // If we don't have any tracks, it's invalid + if (cueFile?.Tracks == null) + throw new ArgumentNullException(nameof(cueFile.Tracks)); + else if (cueFile.Tracks.Length == 0) + throw new ArgumentException("No tracks provided to write"); + + sw.WriteLine($"FILE \"{cueFile.FileName}\" {FromFileType(cueFile.FileType)}"); + + foreach (var track in cueFile.Tracks) + { + WriteCueTrack(track, sw); + } + } + + /// + /// Write the TRACK out to a stream + /// + /// CueFile to write + /// StreamWriter to write to +#if NET48 + private static void WriteCueTrack(CueTrack cueTrack, StreamWriter sw) +#else + private static void WriteCueTrack(CueTrack? cueTrack, StreamWriter sw) +#endif + { + // If we don't have any indices, it's invalid + if (cueTrack?.Indices == null) + throw new ArgumentNullException(nameof(cueTrack.Indices)); + else if (cueTrack.Indices.Length == 0) + throw new ArgumentException("No indices provided to write"); + + sw.WriteLine($" TRACK {cueTrack.Number:D2} {FromDataType(cueTrack.DataType)}"); + + if (cueTrack.Flags != 0) + sw.WriteLine($" FLAGS {FromFlags(cueTrack.Flags)}"); + + if (!string.IsNullOrEmpty(cueTrack.ISRC)) + sw.WriteLine($" ISRC {cueTrack.ISRC}"); + + if (!string.IsNullOrEmpty(cueTrack.Performer)) + sw.WriteLine($" PERFORMER {cueTrack.Performer}"); + + if (!string.IsNullOrEmpty(cueTrack.Songwriter)) + sw.WriteLine($" SONGWRITER {cueTrack.Songwriter}"); + + if (!string.IsNullOrEmpty(cueTrack.Title)) + sw.WriteLine($" TITLE {cueTrack.Title}"); + + if (cueTrack.PreGap != null) + WritePreGap(cueTrack.PreGap, sw); + + foreach (var index in cueTrack.Indices) + { + WriteCueIndex(index, sw); + } + + if (cueTrack.PostGap != null) + WritePostGap(cueTrack.PostGap, sw); + } + + /// + /// Write the PREGAP out to a stream + /// + /// PreGap to write + /// StreamWriter to write to + private static void WritePreGap(PreGap preGap, StreamWriter sw) + { + sw.WriteLine($" PREGAP {preGap.Minutes:D2}:{preGap.Seconds:D2}:{preGap.Frames:D2}"); + } + + /// + /// Write the INDEX out to a stream + /// + /// CueIndex to write + /// StreamWriter to write to +#if NET48 + private static void WriteCueIndex(CueIndex cueIndex, StreamWriter sw) +#else + private static void WriteCueIndex(CueIndex? cueIndex, StreamWriter sw) +#endif + { + if (cueIndex == null) + throw new ArgumentNullException(nameof(cueIndex)); + + sw.WriteLine($" INDEX {cueIndex.Index:D2} {cueIndex.Minutes:D2}:{cueIndex.Seconds:D2}:{cueIndex.Frames:D2}"); + } + + /// + /// Write the POSTGAP out to a stream + /// + /// PostGap to write + /// StreamWriter to write to + private static void WritePostGap(PostGap postGap, StreamWriter sw) + { + sw.WriteLine($" POSTGAP {postGap.Minutes:D2}:{postGap.Seconds:D2}:{postGap.Frames:D2}"); + } + + #region Helpers + + /// + /// Get the string from a given file type + /// + /// CueFileType to get value from + /// String, if possible (default BINARY) + private static string FromFileType(CueFileType fileType) + { + switch (fileType) + { + case CueFileType.BINARY: + return "BINARY"; + + case CueFileType.MOTOROLA: + return "MOTOROLA"; + + case CueFileType.AIFF: + return "AIFF"; + + case CueFileType.WAVE: + return "WAVE"; + + case CueFileType.MP3: + return "MP3"; + + default: + return string.Empty; + } + } + + /// + /// Get the string from a given data type + /// + /// CueTrackDataType to get value from + /// string, if possible + private static string FromDataType(CueTrackDataType dataType) + { + switch (dataType) + { + case CueTrackDataType.AUDIO: + return "AUDIO"; + + case CueTrackDataType.CDG: + return "CDG"; + + case CueTrackDataType.MODE1_2048: + return "MODE1/2048"; + + case CueTrackDataType.MODE1_2352: + return "MODE1/2352"; + + case CueTrackDataType.MODE2_2336: + return "MODE2/2336"; + + case CueTrackDataType.MODE2_2352: + return "MODE2/2352"; + + case CueTrackDataType.CDI_2336: + return "CDI/2336"; + + case CueTrackDataType.CDI_2352: + return "CDI/2352"; + + default: + return string.Empty; + } + } + + /// + /// Get the string value for a set of track flags + /// + /// CueTrackFlag to get value from + /// String value representing the CueTrackFlag, if possible + private static string FromFlags(CueTrackFlag flags) + { + string outputFlagString = string.Empty; + + if (flags.HasFlag(CueTrackFlag.DCP)) + outputFlagString += "DCP "; + + if (flags.HasFlag(CueTrackFlag.FourCH)) + outputFlagString += "4CH "; + + if (flags.HasFlag(CueTrackFlag.PRE)) + outputFlagString += "PRE "; + + if (flags.HasFlag(CueTrackFlag.SCMS)) + outputFlagString += "SCMS "; + + if (flags.HasFlag(CueTrackFlag.DATA)) + outputFlagString += "DATA "; + + return outputFlagString.Trim(); + } + + #endregion + } +} \ No newline at end of file