diff --git a/SabreTools.Serialization/Files/CueSheet.Deserializer.cs b/SabreTools.Serialization/Files/CueSheet.Deserializer.cs
index 51146c3b..4223650b 100644
--- a/SabreTools.Serialization/Files/CueSheet.Deserializer.cs
+++ b/SabreTools.Serialization/Files/CueSheet.Deserializer.cs
@@ -1,9 +1,3 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text.RegularExpressions;
-using SabreTools.Models.CueSheets;
using SabreTools.Serialization.Interfaces;
namespace SabreTools.Serialization.Files
@@ -13,587 +7,8 @@ namespace SabreTools.Serialization.Files
///
public Models.CueSheets.CueSheet? Deserialize(string? path)
{
- // Check that the file exists
- if (string.IsNullOrEmpty(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.IsNullOrEmpty(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;
+ using var stream = PathProcessor.OpenStream(path);
+ return new Streams.CueSheet().Deserialize(stream);
}
-
- ///
- /// 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
- private static CueFile? CreateCueFile(string fileName, string fileType, string[]? cueLines, ref int i)
- {
- // 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.IsNullOrEmpty(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;
-
- // Next file found, return
- case "FILE":
- i--;
- cueFile.Tracks = cueTracks.ToArray();
- return cueFile;
-
- // 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
- private static CueTrack? CreateCueTrack(string number, string dataType, string[]? cueLines, ref int i)
- {
- // 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.IsNullOrEmpty(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;
-
- // Next track or file found, return
- case "TRACK":
- case "FILE":
- i--;
- cueTrack.Indices = cueIndices.ToArray();
- return cueTrack;
-
- // 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
- private static PreGap CreatePreGap(string? length)
- {
- // Ignore empty lines
- if (string.IsNullOrEmpty(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
- private static CueIndex CreateCueIndex(string? index, string? startTime)
- {
- // 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.IsNullOrEmpty(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
- private static PostGap CreatePostGap(string? length)
- {
- // Ignore empty lines
- if (string.IsNullOrEmpty(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
- private static 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;
- }
- }
-
- ///
- /// Get the data type from a given string
- ///
- /// String to get value from
- /// CueTrackDataType, if possible (default AUDIO)
- private static 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 static CueTrackFlag GetFlags(string?[]? flagStrings)
- {
- CueTrackFlag flag = 0;
- if (flagStrings == null)
- return flag;
-
- foreach (string? flagString in flagStrings)
- {
- 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
}
}
diff --git a/SabreTools.Serialization/Streams/CueSheet.Deserializer.cs b/SabreTools.Serialization/Streams/CueSheet.Deserializer.cs
index 8b3384b5..fb258be4 100644
--- a/SabreTools.Serialization/Streams/CueSheet.Deserializer.cs
+++ b/SabreTools.Serialization/Streams/CueSheet.Deserializer.cs
@@ -1,5 +1,10 @@
using System;
+using System.Collections.Generic;
using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using SabreTools.IO;
+using SabreTools.Models.CueSheets;
using SabreTools.Serialization.Interfaces;
namespace SabreTools.Serialization.Streams
@@ -7,6 +12,644 @@ namespace SabreTools.Serialization.Streams
public partial class CueSheet : IStreamSerializer
{
///
- public Models.CueSheets.CueSheet? Deserialize(Stream? data) => throw new NotImplementedException();
+ public Models.CueSheets.CueSheet? Deserialize(Stream? data)
+ {
+ // If the data is invalid
+ if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
+ return null;
+
+ // If the offset is out of bounds
+ if (data.Position < 0 || data.Position >= data.Length)
+ return null;
+
+ // Cache the current offset
+ int initialOffset = (int)data.Position;
+
+ // Create a new cuesheet to fill
+ var cueSheet = new Models.CueSheets.CueSheet();
+ var cueFiles = new List();
+
+ // Read the next line from the input
+ string? lastLine = null;
+ while (true)
+ {
+ string? line = lastLine ?? ReadLine(data);
+ lastLine = null;
+
+ // If we have a null line, break from the loop
+ if (line == null)
+ break;
+
+ // If we have an empty line, we skip
+ if (string.IsNullOrEmpty(line))
+ continue;
+
+ // 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();
+
+ 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].Trim('"');
+ break;
+
+ // Read external CD-Text file path
+ case "CDTEXTFILE":
+ if (splitLine.Length < 2)
+ throw new FormatException($"CDTEXTFILE line malformed: {line}");
+
+ cueSheet.CdTextFile = splitLine[1].Trim('"');
+ break;
+
+ // Read CD-Text enhanced performer
+ case "PERFORMER":
+ if (splitLine.Length < 2)
+ throw new FormatException($"PERFORMER line malformed: {line}");
+
+ cueSheet.Performer = splitLine[1].Trim('"');
+ break;
+
+ // Read CD-Text enhanced songwriter
+ case "SONGWRITER":
+ if (splitLine.Length < 2)
+ throw new FormatException($"SONGWRITER line malformed: {line}");
+
+ cueSheet.Songwriter = splitLine[1].Trim('"');
+ break;
+
+ // Read CD-Text enhanced title
+ case "TITLE":
+ if (splitLine.Length < 2)
+ throw new FormatException($"TITLE line malformed: {line}");
+
+ cueSheet.Title = splitLine[1].Trim('"');
+ break;
+
+ // Read file information
+ case "FILE":
+ if (splitLine.Length < 3)
+ throw new FormatException($"FILE line malformed: {line}");
+
+ var file = CreateCueFile(splitLine[1], splitLine[2], data, out lastLine);
+ if (file == default)
+ throw new FormatException($"FILE line malformed: {line}");
+
+ cueFiles.Add(file);
+ break;
+ }
+ }
+
+ cueSheet.Files = [.. cueFiles];
+ return cueSheet;
+ }
+
+ ///
+ /// Fill a FILE from an array of lines
+ ///
+ /// File name to set
+ /// File type to set
+ /// Stream to pull from
+ private static CueFile? CreateCueFile(string fileName, string fileType, Stream data, out string? lastLine)
+ {
+ // Check the required parameters
+ if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
+ throw new ArgumentNullException(nameof(data));
+ if (data.Position < 0 || data.Position >= data.Length)
+ throw new IndexOutOfRangeException();
+
+ // Create the holding objects
+ lastLine = null;
+ var cueFile = new CueFile();
+ var cueTracks = new List();
+
+ // Set the current fields
+ cueFile.FileName = fileName.Trim('"');
+ cueFile.FileType = GetFileType(fileType);
+
+ while (true)
+ {
+ string? line = lastLine ?? ReadLine(data);
+ lastLine = null;
+
+ // If we have a null line, break from the loop
+ if (line == null)
+ break;
+
+ // If we have an empty line, we skip
+ if (string.IsNullOrEmpty(line))
+ continue;
+
+ // 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();
+
+ 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], data, out lastLine);
+ if (track == default)
+ throw new FormatException($"TRACK line malformed: {line}");
+
+ cueTracks.Add(track);
+ break;
+
+ // Next file found, return
+ case "FILE":
+ lastLine = line;
+ cueFile.Tracks = [.. cueTracks];
+ return cueFile;
+
+ // Default means return
+ default:
+ lastLine = line;
+ cueFile.Tracks = [.. cueTracks];
+ return cueFile;
+ }
+ }
+
+ cueFile.Tracks = [.. cueTracks];
+ return cueFile;
+ }
+
+ ///
+ /// Fill a TRACK from an array of lines
+ ///
+ /// Number to set
+ /// Data type to set
+ /// Stream to pull from
+ private static CueTrack? CreateCueTrack(string number, string dataType, Stream data, out string? lastLine)
+ {
+ // Check the required parameters
+ if (data == null || data.Length == 0 || !data.CanSeek || !data.CanRead)
+ throw new ArgumentNullException(nameof(data));
+ if (data.Position < 0 || data.Position >= data.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
+ lastLine = null;
+ var cueTrack = new CueTrack();
+ var cueIndices = new List();
+
+ cueTrack.Number = parsedNumber;
+ cueTrack.DataType = GetDataType(dataType);
+
+ while (true)
+ {
+ string? line = lastLine ?? ReadLine(data);
+ lastLine = null;
+
+ // If we have a null line, break from the loop
+ if (line == null)
+ break;
+
+ // If we have an empty line, we skip
+ if (string.IsNullOrEmpty(line))
+ continue;
+
+ // 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();
+
+ 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].Trim('"');
+ break;
+
+ // Read CD-Text enhanced performer
+ case "PERFORMER":
+ if (splitLine.Length < 2)
+ throw new FormatException($"PERFORMER line malformed: {line}");
+
+ cueTrack.Performer = splitLine[1].Trim('"');
+ break;
+
+ // Read CD-Text enhanced songwriter
+ case "SONGWRITER":
+ if (splitLine.Length < 2)
+ throw new FormatException($"SONGWRITER line malformed: {line}");
+
+ cueTrack.Songwriter = splitLine[1].Trim('"');
+ break;
+
+ // Read CD-Text enhanced title
+ case "TITLE":
+ if (splitLine.Length < 2)
+ throw new FormatException($"TITLE line malformed: {line}");
+
+ cueTrack.Title = splitLine[1].Trim('"');
+ 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;
+
+ // Next track or file found, return
+ case "TRACK":
+ case "FILE":
+ lastLine = line;
+ cueTrack.Indices = [.. cueIndices];
+ return cueTrack;
+
+ // Default means return
+ default:
+ lastLine = line;
+ cueTrack.Indices = [.. cueIndices];
+ return cueTrack;
+ }
+ }
+
+ cueTrack.Indices = [.. cueIndices];
+ return cueTrack;
+ }
+
+ ///
+ /// Create a PREGAP from a mm:ss:ff length
+ ///
+ /// String to get length information from
+ private static PreGap CreatePreGap(string length)
+ {
+ // Ignore empty lines
+ if (string.IsNullOrEmpty(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
+ private static CueIndex CreateCueIndex(string index, string startTime)
+ {
+ // 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.IsNullOrEmpty(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
+ private static PostGap CreatePostGap(string length)
+ {
+ // Ignore empty lines
+ if (string.IsNullOrEmpty(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
+
+ ///
+ /// Read a line from the input file, where a line is either terminated by a newline character
+ /// or by an end quote
+ ///
+ private static string? ReadLine(Stream data)
+ {
+ // If we are at the end of data
+ if (data.Position >= data.Length)
+ return null;
+
+ var bytes = new List();
+ bool openQuote = false;
+ while (data.Position < data.Length)
+ {
+ // Read the byte value
+ byte b = data.ReadByteValue();
+
+ // If we have a quote, flip the flag
+ if (b == (byte)'"')
+ openQuote = !openQuote;
+
+ // If we have a newline not in a quoted string, exit the loop
+ else if (b == (byte)'\n' && !openQuote)
+ break;
+
+ // Add the byte to the set
+ bytes.Add(b);
+ }
+
+ var line = new string(bytes.Select(b => (char)b).ToArray());
+ return line.TrimEnd();
+ }
+
+ ///
+ /// Get the file type from a given string
+ ///
+ /// String to get value from
+ /// CueFileType, if possible
+ private static 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;
+ }
+ }
+
+ ///
+ /// Get the data type from a given string
+ ///
+ /// String to get value from
+ /// CueTrackDataType, if possible (default AUDIO)
+ private static 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 static CueTrackFlag GetFlags(string?[]? flagStrings)
+ {
+ CueTrackFlag flag = 0;
+ if (flagStrings == null)
+ return flag;
+
+ foreach (string? flagString in flagStrings)
+ {
+ 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/SabreTools.Serialization/Streams/CueSheet.Serializer.cs b/SabreTools.Serialization/Streams/CueSheet.Serializer.cs
index c377be3c..06bda4d4 100644
--- a/SabreTools.Serialization/Streams/CueSheet.Serializer.cs
+++ b/SabreTools.Serialization/Streams/CueSheet.Serializer.cs
@@ -15,8 +15,6 @@ namespace SabreTools.Serialization.Streams
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));