mirror of
https://github.com/SabreTools/MPF.git
synced 2026-07-02 17:24:48 +00:00
Migrate to Nuget package for cuesheets
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
- Migrate to Nuget package for Redump
|
||||
- Remove dd for Windows
|
||||
- Migrate to Nuget package for XMID
|
||||
- Migrate to Nuget package for cuesheets
|
||||
|
||||
### 2.6.3 (2023-08-15)
|
||||
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
/// <remarks>
|
||||
/// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf
|
||||
/// </remarks>
|
||||
namespace MPF.CueSheets
|
||||
{
|
||||
/// <summary>
|
||||
/// The audio or data file’s filetype
|
||||
/// </summary>
|
||||
public enum CueFileType
|
||||
{
|
||||
/// <summary>
|
||||
/// Intel binary file (least significant byte first). Use for data files.
|
||||
/// </summary>
|
||||
BINARY,
|
||||
|
||||
/// <summary>
|
||||
/// Motorola binary file (most significant byte first). Use for data files.
|
||||
/// </summary>
|
||||
MOTOROLA,
|
||||
|
||||
/// <summary>
|
||||
/// Audio AIFF file (44.1KHz 16-bit stereo)
|
||||
/// </summary>
|
||||
AIFF,
|
||||
|
||||
/// <summary>
|
||||
/// Audio WAVE file (44.1KHz 16-bit stereo)
|
||||
/// </summary>
|
||||
WAVE,
|
||||
|
||||
/// <summary>
|
||||
/// Audio MP3 file (44.1KHz 16-bit stereo)
|
||||
/// </summary>
|
||||
MP3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single FILE in a cuesheet
|
||||
/// </summary>
|
||||
public class CueFile
|
||||
{
|
||||
/// <summary>
|
||||
/// filename
|
||||
/// </summary>
|
||||
public string FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// filetype
|
||||
/// </summary>
|
||||
public CueFileType FileType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of TRACK in FILE
|
||||
/// </summary>
|
||||
public List<CueTrack> Tracks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create an empty FILE
|
||||
/// </summary>
|
||||
public CueFile()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill a FILE from an array of lines
|
||||
/// </summary>
|
||||
/// <param name="fileName">File name to set</param>
|
||||
/// <param name="fileType">File type to set</param>
|
||||
/// <param name="cueLines">Lines array to pull from</param>
|
||||
/// <param name="i">Reference to index in array</param>
|
||||
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
|
||||
public CueFile(string fileName, string fileType, string[] cueLines, ref int i, bool throwOnError = false)
|
||||
{
|
||||
if (cueLines == null)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new ArgumentNullException(nameof(cueLines));
|
||||
|
||||
return;
|
||||
}
|
||||
else if (i < 0 || i > cueLines.Length)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the current fields
|
||||
this.FileName = fileName.Trim('"');
|
||||
this.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)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"TRACK line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.Tracks == null)
|
||||
this.Tracks = new List<CueTrack>();
|
||||
|
||||
var track = new CueTrack(splitLine[1], splitLine[2], cueLines, ref i);
|
||||
if (track == default)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"TRACK line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.Tracks.Add(track);
|
||||
break;
|
||||
|
||||
// Default means return
|
||||
default:
|
||||
i--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the FILE out to a stream
|
||||
/// </summary>
|
||||
/// <param name="sw">StreamWriter to write to</param>
|
||||
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
|
||||
public void Write(StreamWriter sw, bool throwOnError = false)
|
||||
{
|
||||
// If we don't have any tracks, it's invalid
|
||||
if (this.Tracks == null)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new ArgumentNullException(nameof(this.Tracks));
|
||||
|
||||
return;
|
||||
}
|
||||
else if (this.Tracks.Count == 0)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new ArgumentException("No tracks provided to write");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sw.WriteLine($"FILE \"{this.FileName}\" {FromFileType(this.FileType)}");
|
||||
|
||||
foreach (var track in Tracks)
|
||||
{
|
||||
track.Write(sw);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the file type from a given string
|
||||
/// </summary>
|
||||
/// <param name="fileType">String to get value from</param>
|
||||
/// <returns>CueFileType, if possible</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the string from a given file type
|
||||
/// </summary>
|
||||
/// <param name="fileType">CueFileType to get value from</param>
|
||||
/// <returns>String, if possible (default BINARY)</returns>
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
/// <remarks>
|
||||
/// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf
|
||||
/// </remarks>
|
||||
namespace MPF.CueSheets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single INDEX in a TRACK
|
||||
/// </summary>
|
||||
public class CueIndex
|
||||
{
|
||||
/// <summary>
|
||||
/// INDEX number, between 0 and 99
|
||||
/// </summary>
|
||||
public int Index { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Starting time of INDEX in minutes
|
||||
/// </summary>
|
||||
public int Minutes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Starting time of INDEX in seconds
|
||||
/// </summary>
|
||||
/// <remarks>There are 60 seconds in a minute</remarks>
|
||||
public int Seconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Starting time of INDEX in frames.
|
||||
/// </summary>
|
||||
/// <remarks>There are 75 frames per second</remarks>
|
||||
public int Frames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create an empty INDEX
|
||||
/// </summary>
|
||||
public CueIndex()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill a INDEX from an array of lines
|
||||
/// </summary>
|
||||
/// <param name="index">Index to set</param>
|
||||
/// <param name="startTime">Start time to set</param>
|
||||
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
|
||||
public CueIndex(string index, string startTime, bool throwOnError = false)
|
||||
{
|
||||
// Set the current fields
|
||||
if (!int.TryParse(index, out int parsedIndex))
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new ArgumentException($"Index was not a number: {index}");
|
||||
|
||||
return;
|
||||
}
|
||||
else if (parsedIndex < 0 || parsedIndex > 99)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new IndexOutOfRangeException($"Index must be between 0 and 99: {parsedIndex}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore empty lines
|
||||
if (string.IsNullOrWhiteSpace(startTime))
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new ArgumentException("Start time was null or whitespace");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore lines that don't contain the correct information
|
||||
if (startTime.Length != 8 || startTime.Count(c => c == ':') != 2)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"Start time was not in a recognized format: {startTime}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Split the line
|
||||
string[] splitTime = startTime.Split(':');
|
||||
if (splitTime.Length != 3)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"Start time was not in a recognized format: {startTime}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the lengths
|
||||
int[] lengthSegments = new int[3];
|
||||
|
||||
// Minutes
|
||||
if (!int.TryParse(splitTime[0], out lengthSegments[0]))
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"Minutes segment was not a number: {splitTime[0]}");
|
||||
|
||||
return;
|
||||
}
|
||||
else if (lengthSegments[0] < 0)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new IndexOutOfRangeException($"Minutes segment must be 0 or greater: {lengthSegments[0]}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Seconds
|
||||
if (!int.TryParse(splitTime[1], out lengthSegments[1]))
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"Seconds segment was not a number: {splitTime[1]}");
|
||||
|
||||
return;
|
||||
}
|
||||
else if (lengthSegments[1] < 0 || lengthSegments[1] > 60)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new IndexOutOfRangeException($"Seconds segment must be between 0 and 60: {lengthSegments[1]}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Frames
|
||||
if (!int.TryParse(splitTime[2], out lengthSegments[2]))
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"Frames segment was not a number: {splitTime[2]}");
|
||||
|
||||
return;
|
||||
}
|
||||
else if (lengthSegments[2] < 0 || lengthSegments[2] > 75)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new IndexOutOfRangeException($"Frames segment must be between 0 and 75: {lengthSegments[2]}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the values
|
||||
this.Index = parsedIndex;
|
||||
this.Minutes = lengthSegments[0];
|
||||
this.Seconds = lengthSegments[1];
|
||||
this.Frames = lengthSegments[2];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the INDEX out to a stream
|
||||
/// </summary>
|
||||
/// <param name="sw">StreamWriter to write to</param>
|
||||
public void Write(StreamWriter sw)
|
||||
{
|
||||
sw.WriteLine($" INDEX {this.Index:D2} {this.Minutes:D2}:{this.Seconds:D2}:{this.Frames:D2}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
/// <remarks>
|
||||
/// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf
|
||||
/// </remarks>
|
||||
namespace MPF.CueSheets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single cuesheet
|
||||
/// </summary>
|
||||
public class CueSheet
|
||||
{
|
||||
/// <summary>
|
||||
/// CATALOG
|
||||
/// </summary>
|
||||
public string Catalog { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// CDTEXTFILE
|
||||
/// </summary>
|
||||
public string CdTextFile { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// PERFORMER
|
||||
/// </summary>
|
||||
public string Performer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SONGWRITER
|
||||
/// </summary>
|
||||
public string Songwriter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// TITLE
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of FILE in cuesheet
|
||||
/// </summary>
|
||||
public List<CueFile> Files { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create an empty cuesheet
|
||||
/// </summary>
|
||||
public CueSheet()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a cuesheet from a file, if possible
|
||||
/// </summary>
|
||||
/// <param name="filename"></param>
|
||||
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
|
||||
public CueSheet(string filename, bool throwOnError = false)
|
||||
{
|
||||
// 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
|
||||
string[] cueLines = File.ReadAllLines(filename);
|
||||
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<Match>()
|
||||
.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)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"CATALOG line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.Catalog = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read external CD-Text file path
|
||||
case "CDTEXTFILE":
|
||||
if (splitLine.Length < 2)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"CDTEXTFILE line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.CdTextFile = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced performer
|
||||
case "PERFORMER":
|
||||
if (splitLine.Length < 2)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"PERFORMER line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.Performer = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced songwriter
|
||||
case "SONGWRITER":
|
||||
if (splitLine.Length < 2)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"SONGWRITER line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.Songwriter = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced title
|
||||
case "TITLE":
|
||||
if (splitLine.Length < 2)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"TITLE line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.Title = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read file information
|
||||
case "FILE":
|
||||
if (splitLine.Length < 3)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"FILE line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.Files == null)
|
||||
this.Files = new List<CueFile>();
|
||||
|
||||
var file = new CueFile(splitLine[1], splitLine[2], cueLines, ref i);
|
||||
if (file == default)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"FILE line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.Files.Add(file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the cuesheet out to a file
|
||||
/// </summary>
|
||||
/// <param name="filename">File path to write to</param>
|
||||
public void Write(string filename)
|
||||
{
|
||||
using (var fs = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite))
|
||||
{
|
||||
Write(fs);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the cuesheet out to a stream
|
||||
/// </summary>
|
||||
/// <param name="stream">Stream to write to</param>
|
||||
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
|
||||
public void Write(Stream stream, bool throwOnError = false)
|
||||
{
|
||||
// If we don't have any files, it's invalid
|
||||
if (this.Files == null)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new ArgumentNullException(nameof(this.Files));
|
||||
|
||||
return;
|
||||
}
|
||||
else if (this.Files.Count == 0)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new ArgumentException("No files provided to write");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
using (var sw = new StreamWriter(stream, Encoding.ASCII, 1024, true))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(this.Catalog))
|
||||
sw.WriteLine($"CATALOG {this.Catalog}");
|
||||
|
||||
if (!string.IsNullOrEmpty(this.CdTextFile))
|
||||
sw.WriteLine($"CDTEXTFILE {this.CdTextFile}");
|
||||
|
||||
if (!string.IsNullOrEmpty(this.Performer))
|
||||
sw.WriteLine($"PERFORMER {this.Performer}");
|
||||
|
||||
if (!string.IsNullOrEmpty(this.Songwriter))
|
||||
sw.WriteLine($"SONGWRITER {this.Songwriter}");
|
||||
|
||||
if (!string.IsNullOrEmpty(this.Title))
|
||||
sw.WriteLine($"TITLE {this.Title}");
|
||||
|
||||
foreach (var file in Files)
|
||||
{
|
||||
file.Write(sw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,554 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
/// <remarks>
|
||||
/// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf
|
||||
/// </remarks>
|
||||
namespace MPF.CueSheets
|
||||
{
|
||||
/// <summary>
|
||||
/// Track datatype
|
||||
/// </summary>
|
||||
public enum CueTrackDataType
|
||||
{
|
||||
/// <summary>
|
||||
/// AUDIO, Audio/Music (2352)
|
||||
/// </summary>
|
||||
AUDIO,
|
||||
|
||||
/// <summary>
|
||||
/// CDG, Karaoke CD+G (2448)
|
||||
/// </summary>
|
||||
CDG,
|
||||
|
||||
/// <summary>
|
||||
/// MODE1/2048, CD-ROM Mode1 Data (cooked)
|
||||
/// </summary>
|
||||
MODE1_2048,
|
||||
|
||||
/// <summary>
|
||||
/// MODE1/2352 CD-ROM Mode1 Data (raw)
|
||||
/// </summary>
|
||||
MODE1_2352,
|
||||
|
||||
/// <summary>
|
||||
/// MODE2/2336, CD-ROM XA Mode2 Data
|
||||
/// </summary>
|
||||
MODE2_2336,
|
||||
|
||||
/// <summary>
|
||||
/// MODE2/2352, CD-ROM XA Mode2 Data
|
||||
/// </summary>
|
||||
MODE2_2352,
|
||||
|
||||
/// <summary>
|
||||
/// CDI/2336, CD-I Mode2 Data
|
||||
/// </summary>
|
||||
CDI_2336,
|
||||
|
||||
/// <summary>
|
||||
/// CDI/2352, CD-I Mode2 Data
|
||||
/// </summary>
|
||||
CDI_2352,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Special subcode flags within a track
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum CueTrackFlag
|
||||
{
|
||||
/// <summary>
|
||||
/// DCP, Digital copy permitted
|
||||
/// </summary>
|
||||
DCP = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// 4CH, Four channel audio
|
||||
/// </summary>
|
||||
FourCH = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// PRE, Pre-emphasis enabled (audio tracks only)
|
||||
/// </summary>
|
||||
PRE = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// SCMS, Serial Copy Management System (not supported by all recorders)
|
||||
/// </summary>
|
||||
SCMS = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// DATA, set for data files. This flag is set automatically based on the track’s filetype
|
||||
/// </summary>
|
||||
DATA = 1 << 4,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single TRACK in a FILE
|
||||
/// </summary>
|
||||
public class CueTrack
|
||||
{
|
||||
/// <summary>
|
||||
/// Track number. The range is 1 to 99.
|
||||
/// </summary>
|
||||
public int Number { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Track datatype
|
||||
/// </summary>
|
||||
public CueTrackDataType DataType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// FLAGS
|
||||
/// </summary>
|
||||
public CueTrackFlag Flags { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ISRC
|
||||
/// </summary>
|
||||
/// <remarks>12 characters in length</remarks>
|
||||
public string ISRC { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// PERFORMER
|
||||
/// </summary>
|
||||
public string Performer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SONGWRITER
|
||||
/// </summary>
|
||||
public string Songwriter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// TITLE
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// PREGAP
|
||||
/// </summary>
|
||||
public PreGap PreGap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of INDEX in TRACK
|
||||
/// </summary>
|
||||
/// <remarks>Must start with 0 or 1 and then sequential</remarks>
|
||||
public List<CueIndex> Indices { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// POSTGAP
|
||||
/// </summary>
|
||||
public PostGap PostGap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create an empty TRACK
|
||||
/// </summary>
|
||||
public CueTrack()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill a TRACK from an array of lines
|
||||
/// </summary>
|
||||
/// <param name="number">Number to set</param>
|
||||
/// <param name="dataType">Data type to set</param>
|
||||
/// <param name="cueLines">Lines array to pull from</param>
|
||||
/// <param name="i">Reference to index in array</param>
|
||||
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
|
||||
public CueTrack(string number, string dataType, string[] cueLines, ref int i, bool throwOnError = false)
|
||||
{
|
||||
if (cueLines == null)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new ArgumentNullException(nameof(cueLines));
|
||||
|
||||
return;
|
||||
}
|
||||
else if (i < 0 || i > cueLines.Length)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the current fields
|
||||
if (!int.TryParse(number, out int parsedNumber))
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new ArgumentException($"Number was not a number: {number}");
|
||||
|
||||
return;
|
||||
}
|
||||
else if (parsedNumber < 1 || parsedNumber > 99)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new IndexOutOfRangeException($"Index must be between 1 and 99: {parsedNumber}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.Number = parsedNumber;
|
||||
this.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)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"FLAGS line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.Flags = GetFlags(splitLine);
|
||||
break;
|
||||
|
||||
// Read International Standard Recording Code
|
||||
case "ISRC":
|
||||
if (splitLine.Length < 2)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"ISRC line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.ISRC = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced performer
|
||||
case "PERFORMER":
|
||||
if (splitLine.Length < 2)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"PERFORMER line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.Performer = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced songwriter
|
||||
case "SONGWRITER":
|
||||
if (splitLine.Length < 2)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"SONGWRITER line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.Songwriter = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read CD-Text enhanced title
|
||||
case "TITLE":
|
||||
if (splitLine.Length < 2)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"TITLE line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.Title = splitLine[1];
|
||||
break;
|
||||
|
||||
// Read pregap information
|
||||
case "PREGAP":
|
||||
if (splitLine.Length < 2)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"PREGAP line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var pregap = new PreGap(splitLine[1]);
|
||||
if (pregap == default)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"PREGAP line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.PreGap = pregap;
|
||||
break;
|
||||
|
||||
// Read index information
|
||||
case "INDEX":
|
||||
if (splitLine.Length < 3)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"INDEX line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.Indices == null)
|
||||
this.Indices = new List<CueIndex>();
|
||||
|
||||
var index = new CueIndex(splitLine[1], splitLine[2]);
|
||||
if (index == default)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"INDEX line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.Indices.Add(index);
|
||||
break;
|
||||
|
||||
// Read postgap information
|
||||
case "POSTGAP":
|
||||
if (splitLine.Length < 2)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"POSTGAP line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var postgap = new PostGap(splitLine[1]);
|
||||
if (postgap == default)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"POSTGAP line malformed: {line}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
this.PostGap = postgap;
|
||||
break;
|
||||
|
||||
// Default means return
|
||||
default:
|
||||
i--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the TRACK out to a stream
|
||||
/// </summary>
|
||||
/// <param name="sw">StreamWriter to write to</param
|
||||
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
|
||||
public void Write(StreamWriter sw, bool throwOnError = false)
|
||||
{
|
||||
// If we don't have any indices, it's invalid
|
||||
if (this.Indices == null)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new ArgumentNullException(nameof(this.Indices));
|
||||
|
||||
return;
|
||||
}
|
||||
else if (this.Indices.Count == 0)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new ArgumentException("No indices provided to write");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sw.WriteLine($" TRACK {this.Number:D2} {FromDataType(this.DataType)}");
|
||||
|
||||
if (this.Flags != 0)
|
||||
sw.WriteLine($" FLAGS {FromFlags(this.Flags)}");
|
||||
|
||||
if (!string.IsNullOrEmpty(this.ISRC))
|
||||
sw.WriteLine($"ISRC {this.ISRC}");
|
||||
|
||||
if (!string.IsNullOrEmpty(this.Performer))
|
||||
sw.WriteLine($"PERFORMER {this.Performer}");
|
||||
|
||||
if (!string.IsNullOrEmpty(this.Songwriter))
|
||||
sw.WriteLine($"SONGWRITER {this.Songwriter}");
|
||||
|
||||
if (!string.IsNullOrEmpty(this.Title))
|
||||
sw.WriteLine($"TITLE {this.Title}");
|
||||
|
||||
if (this.PreGap != null)
|
||||
this.PreGap.Write(sw);
|
||||
|
||||
foreach (var index in Indices)
|
||||
{
|
||||
index.Write(sw);
|
||||
}
|
||||
|
||||
if (this.PostGap != null)
|
||||
this.PostGap.Write(sw);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the data type from a given string
|
||||
/// </summary>
|
||||
/// <param name="dataType">String to get value from</param>
|
||||
/// <returns>CueTrackDataType, if possible (default AUDIO)</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the string from a given data type
|
||||
/// </summary>
|
||||
/// <param name="dataType">CueTrackDataType to get value from</param>
|
||||
/// <returns>string, if possible</returns>
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the flag value for an array of strings
|
||||
/// </summary>
|
||||
/// <param name="flagStrings">Possible flags as strings</param>
|
||||
/// <returns>CueTrackFlag value representing the strings, if possible</returns>
|
||||
private CueTrackFlag GetFlags(string[] flagStrings)
|
||||
{
|
||||
CueTrackFlag flag = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the string value for a set of track flags
|
||||
/// </summary>
|
||||
/// <param name="flags">CueTrackFlag to get value from</param>
|
||||
/// <returns>String value representing the CueTrackFlag, if possible</returns>
|
||||
private 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net48;net6.0</TargetFrameworks>
|
||||
<RuntimeIdentifiers>win7-x64;win8-x64;win81-x64;win10-x64;linux-x64;osx-x64</RuntimeIdentifiers>
|
||||
<Authors>Matt Nadareski;ReignStumble;Jakz</Authors>
|
||||
<Copyright>Copyright (c)2019-2023</Copyright>
|
||||
<RepositoryUrl>https://github.com/SabreTools/MPF</RepositoryUrl>
|
||||
<Version>2.6.3</Version>
|
||||
<AssemblyVersion>$(Version)</AssemblyVersion>
|
||||
<FileVersion>$(Version)</FileVersion>
|
||||
<IncludeSource>true</IncludeSource>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<NrtRevisionFormat>$(Version)-{chash:8}</NrtRevisionFormat>
|
||||
<NrtResolveSimpleAttributes>true</NrtResolveSimpleAttributes>
|
||||
<NrtShowRevision>false</NrtShowRevision>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
|
||||
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,139 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
/// <remarks>
|
||||
/// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf
|
||||
/// </remarks>
|
||||
namespace MPF.CueSheets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents POSTGAP information of a track
|
||||
/// </summary>
|
||||
public class PostGap
|
||||
{
|
||||
/// <summary>
|
||||
/// Length of POSTGAP in minutes
|
||||
/// </summary>
|
||||
public int Minutes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Length of POSTGAP in seconds
|
||||
/// </summary>
|
||||
/// <remarks>There are 60 seconds in a minute</remarks>
|
||||
public int Seconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Length of POSTGAP in frames.
|
||||
/// </summary>
|
||||
/// <remarks>There are 75 frames per second</remarks>
|
||||
public int Frames { get; set; }
|
||||
|
||||
/// Create an empty POSTGAP
|
||||
/// </summary>
|
||||
public PostGap()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a POSTGAP from a mm:ss:ff length
|
||||
/// </summary>
|
||||
/// <param name="length">String to get length information from</param>
|
||||
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
|
||||
public PostGap(string length, bool throwOnError = false)
|
||||
{
|
||||
// Ignore empty lines
|
||||
if (string.IsNullOrWhiteSpace(length))
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new ArgumentException("Length was null or whitespace");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore lines that don't contain the correct information
|
||||
if (length.Length != 8 || length.Count(c => c == ':') != 2)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"Length was not in a recognized format: {length}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Split the line
|
||||
string[] splitLength = length.Split(':');
|
||||
if (splitLength.Length != 3)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"Length was not in a recognized format: {length}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the lengths
|
||||
int[] lengthSegments = new int[3];
|
||||
|
||||
// Minutes
|
||||
if (!int.TryParse(splitLength[0], out lengthSegments[0]))
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"Minutes segment was not a number: {splitLength[0]}");
|
||||
|
||||
return;
|
||||
}
|
||||
else if (lengthSegments[0] < 0)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new IndexOutOfRangeException($"Minutes segment must be 0 or greater: {lengthSegments[0]}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Seconds
|
||||
if (!int.TryParse(splitLength[1], out lengthSegments[1]))
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"Seconds segment was not a number: {splitLength[1]}");
|
||||
|
||||
return;
|
||||
}
|
||||
else if (lengthSegments[1] < 0 || lengthSegments[1] > 60)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new IndexOutOfRangeException($"Seconds segment must be between 0 and 60: {lengthSegments[1]}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Frames
|
||||
if (!int.TryParse(splitLength[2], out lengthSegments[2]))
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"Frames segment was not a number: {splitLength[2]}");
|
||||
|
||||
return;
|
||||
}
|
||||
else if (lengthSegments[2] < 0 || lengthSegments[2] > 75)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new IndexOutOfRangeException($"Frames segment must be between 0 and 75: {lengthSegments[2]}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the values
|
||||
this.Minutes = lengthSegments[0];
|
||||
this.Seconds = lengthSegments[1];
|
||||
this.Frames = lengthSegments[2];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the POSTGAP out to a stream
|
||||
/// </summary>
|
||||
/// <param name="sw">StreamWriter to write to</param>
|
||||
public void Write(StreamWriter sw)
|
||||
{
|
||||
sw.WriteLine($" POSTGAP {this.Minutes:D2}:{this.Seconds:D2}:{this.Frames:D2}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
/// <remarks>
|
||||
/// Information sourced from http://web.archive.org/web/20070221154246/http://www.goldenhawk.com/download/cdrwin.pdf
|
||||
/// </remarks>
|
||||
namespace MPF.CueSheets
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents PREGAP information of a track
|
||||
/// </summary>
|
||||
public class PreGap
|
||||
{
|
||||
/// <summary>
|
||||
/// Length of PREGAP in minutes
|
||||
/// </summary>
|
||||
public int Minutes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Length of PREGAP in seconds
|
||||
/// </summary>
|
||||
/// <remarks>There are 60 seconds in a minute</remarks>
|
||||
public int Seconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Length of PREGAP in frames.
|
||||
/// </summary>
|
||||
/// <remarks>There are 75 frames per second</remarks>
|
||||
public int Frames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create an empty PREGAP
|
||||
/// </summary>
|
||||
public PreGap()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a PREGAP from a mm:ss:ff length
|
||||
/// </summary>
|
||||
/// <param name="length">String to get length information from</param>
|
||||
/// <param name="throwOnError">True if errors throw an exception, false otherwise</param>
|
||||
public PreGap(string length, bool throwOnError = false)
|
||||
{
|
||||
// Ignore empty lines
|
||||
if (string.IsNullOrWhiteSpace(length))
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new ArgumentException("Length was null or whitespace");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore lines that don't contain the correct information
|
||||
if (length.Length != 8 || length.Count(c => c == ':') != 2)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"Length was not in a recognized format: {length}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Split the line
|
||||
string[] splitLength = length.Split(':');
|
||||
if (splitLength.Length != 3)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"Length was not in a recognized format: {length}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the lengths
|
||||
int[] lengthSegments = new int[3];
|
||||
|
||||
// Minutes
|
||||
if (!int.TryParse(splitLength[0], out lengthSegments[0]))
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"Minutes segment was not a number: {splitLength[0]}");
|
||||
|
||||
return;
|
||||
}
|
||||
else if (lengthSegments[0] < 0)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new IndexOutOfRangeException($"Minutes segment must be 0 or greater: {lengthSegments[0]}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Seconds
|
||||
if (!int.TryParse(splitLength[1], out lengthSegments[1]))
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"Seconds segment was not a number: {splitLength[1]}");
|
||||
|
||||
return;
|
||||
}
|
||||
else if (lengthSegments[1] < 0 || lengthSegments[1] > 60)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new IndexOutOfRangeException($"Seconds segment must be between 0 and 60: {lengthSegments[1]}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Frames
|
||||
if (!int.TryParse(splitLength[2], out lengthSegments[2]))
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new FormatException($"Frames segment was not a number: {splitLength[2]}");
|
||||
|
||||
return;
|
||||
}
|
||||
else if (lengthSegments[2] < 0 || lengthSegments[2] > 75)
|
||||
{
|
||||
if (throwOnError)
|
||||
throw new IndexOutOfRangeException($"Frames segment must be between 0 and 75: {lengthSegments[2]}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the values
|
||||
this.Minutes = lengthSegments[0];
|
||||
this.Seconds = lengthSegments[1];
|
||||
this.Frames = lengthSegments[2];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the PREGAP out to a stream
|
||||
/// </summary>
|
||||
/// <param name="sw">StreamWriter to write to</param>
|
||||
public void Write(StreamWriter sw)
|
||||
{
|
||||
sw.WriteLine($" PREGAP {this.Minutes:D2}:{this.Seconds:D2}:{this.Frames:D2}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ using System.Xml.Schema;
|
||||
using System.Xml.Serialization;
|
||||
using MPF.Core.Converters;
|
||||
using MPF.Core.Data;
|
||||
using MPF.CueSheets;
|
||||
using SabreTools.Models.CueSheets;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
using Schemas;
|
||||
|
||||
@@ -2441,10 +2441,10 @@ namespace MPF.Modules.Aaru
|
||||
|
||||
// Required variables
|
||||
uint totalTracks = 0;
|
||||
CueSheet cueSheet = new CueSheet
|
||||
var cueFiles = new List<CueFile>();
|
||||
var cueSheet = new CueSheet
|
||||
{
|
||||
Performer = string.Join(", ", cicmSidecar.Performer ?? new string[0]),
|
||||
Files = new List<CueFile>(),
|
||||
};
|
||||
|
||||
// Only care about OpticalDisc types
|
||||
@@ -2482,13 +2482,13 @@ namespace MPF.Modules.Aaru
|
||||
{
|
||||
FileName = GenerateTrackName(basePath, (int)totalTracks, cueTrack.Number, opticalDisc.DiscType),
|
||||
FileType = CueFileType.BINARY,
|
||||
Tracks = new List<CueTrack>(),
|
||||
};
|
||||
|
||||
// Add index data
|
||||
var cueTracks = new List<CueTrack>();
|
||||
if (track.Indexes != null && track.Indexes.Length > 0)
|
||||
{
|
||||
cueTrack.Indices = new List<CueIndex>();
|
||||
var cueIndicies = new List<CueIndex>();
|
||||
|
||||
// Loop through each index
|
||||
foreach (TrackIndexType trackIndex in track.Indexes)
|
||||
@@ -2502,36 +2502,60 @@ namespace MPF.Modules.Aaru
|
||||
|
||||
// Pregap information
|
||||
if (trackIndex.Value < 0)
|
||||
cueTrack.PreGap = new PreGap(timeString);
|
||||
{
|
||||
string[] timeStringSplit = timeString.Split(':');
|
||||
cueTrack.PreGap = new PreGap
|
||||
{
|
||||
Minutes = int.Parse(timeStringSplit[0]),
|
||||
Seconds = int.Parse(timeStringSplit[1]),
|
||||
Frames = int.Parse(timeStringSplit[2]),
|
||||
};
|
||||
}
|
||||
|
||||
// Individual indexes
|
||||
else
|
||||
cueTrack.Indices.Add(new CueIndex(trackIndex.index.ToString(), timeString));
|
||||
{
|
||||
string[] timeStringSplit = timeString.Split(':');
|
||||
cueIndicies.Add(new CueIndex
|
||||
{
|
||||
Index = trackIndex.index,
|
||||
Minutes = int.Parse(timeStringSplit[0]),
|
||||
Seconds = int.Parse(timeStringSplit[1]),
|
||||
Frames = int.Parse(timeStringSplit[2]),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
cueTrack.Indices = cueIndicies.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default if index data missing from sidecar
|
||||
cueTrack.Indices = new List<CueIndex>()
|
||||
cueTrack.Indices = new CueIndex[]
|
||||
{
|
||||
new CueIndex("01", "00:00:00"),
|
||||
new CueIndex
|
||||
{
|
||||
Index = 1,
|
||||
Minutes = 0,
|
||||
Seconds = 0,
|
||||
Frames = 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Add the track to the file
|
||||
cueFile.Tracks.Add(cueTrack);
|
||||
cueTracks.Add(cueTrack);
|
||||
|
||||
// Add the file to the cuesheet
|
||||
cueSheet.Files.Add(cueFile);
|
||||
cueFiles.Add(cueFile);
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a cuesheet to write out, do so
|
||||
cueSheet.Files = cueFiles.ToArray();
|
||||
if (cueSheet != null && cueSheet != default)
|
||||
{
|
||||
MemoryStream ms = new MemoryStream();
|
||||
cueSheet.Write(ms);
|
||||
ms.Position = 0;
|
||||
var ms = new SabreTools.Serialization.Streams.CueSheet().Serialize(cueSheet);
|
||||
using (var sr = new StreamReader(ms))
|
||||
{
|
||||
return sr.ReadToEnd();
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using MPF.Core.Converters;
|
||||
using MPF.Core.Data;
|
||||
using MPF.Core.Utilities;
|
||||
using MPF.CueSheets;
|
||||
using SabreTools.RedumpLib.Data;
|
||||
|
||||
namespace MPF.Modules.DiscImageCreator
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MPF.Core\MPF.Core.csproj" />
|
||||
<ProjectReference Include="..\MPF.CueSheets\MPF.CueSheets.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.Models" Version="1.1.2" />
|
||||
<PackageReference Include="SabreTools.RedumpLib" Version="1.1.1" />
|
||||
<PackageReference Include="SabreTools.Serialization" Version="1.1.2" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
|
||||
<PackageReference Include="Unclassified.NetRevisionTask" Version="0.4.3">
|
||||
|
||||
7
MPF.sln
7
MPF.sln
@@ -23,8 +23,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
publish-win.bat = publish-win.bat
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MPF.CueSheets", "MPF.CueSheets\MPF.CueSheets.csproj", "{F2C12798-DF53-4D4C-A55B-F5A77F29D6B1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MPF.Core", "MPF.Core\MPF.Core.csproj", "{70B1265D-FE49-472A-A83D-0B462152D37A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MPF.Modules", "MPF.Modules\MPF.Modules.csproj", "{8A4254BD-552F-4238-B8EB-D59AACD768B9}"
|
||||
@@ -55,10 +53,6 @@ Global
|
||||
{8CFDE289-E171-4D49-A40D-5293265C1253}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8CFDE289-E171-4D49-A40D-5293265C1253}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8CFDE289-E171-4D49-A40D-5293265C1253}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{F2C12798-DF53-4D4C-A55B-F5A77F29D6B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F2C12798-DF53-4D4C-A55B-F5A77F29D6B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F2C12798-DF53-4D4C-A55B-F5A77F29D6B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F2C12798-DF53-4D4C-A55B-F5A77F29D6B1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{70B1265D-FE49-472A-A83D-0B462152D37A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{70B1265D-FE49-472A-A83D-0B462152D37A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{70B1265D-FE49-472A-A83D-0B462152D37A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -80,7 +74,6 @@ Global
|
||||
{7CC064D2-38AB-4A05-8519-28660DE4562A} = {4160167D-681D-480B-ABC6-06AC869E5769}
|
||||
{51AB0928-13F9-44BF-A407-B6957A43A056} = {4160167D-681D-480B-ABC6-06AC869E5769}
|
||||
{8CFDE289-E171-4D49-A40D-5293265C1253} = {4160167D-681D-480B-ABC6-06AC869E5769}
|
||||
{F2C12798-DF53-4D4C-A55B-F5A77F29D6B1} = {4160167D-681D-480B-ABC6-06AC869E5769}
|
||||
{70B1265D-FE49-472A-A83D-0B462152D37A} = {4160167D-681D-480B-ABC6-06AC869E5769}
|
||||
{8A4254BD-552F-4238-B8EB-D59AACD768B9} = {4160167D-681D-480B-ABC6-06AC869E5769}
|
||||
{EA3768DB-694A-4653-82E4-9FF71B8963F3} = {4160167D-681D-480B-ABC6-06AC869E5769}
|
||||
|
||||
Reference in New Issue
Block a user