mirror of
https://github.com/claunia/cuetools.net.git
synced 2025-12-16 10:04:24 +00:00
* Prevent exception on non-standard CUE sheet
In case of a CUE sheet with more than about 99 tracks, this `string`
constructor [1] threw an exception, because its repetition parameter
was negative.
Fixes Exception: 'count' must be non-negative.
* Update CDImage.cs
Shortened code by use of Math.Max()
[1] 980e63d956/CUETools.CDImage/CDImage.cs (L435)
599 lines
13 KiB
C#
599 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Globalization;
|
|
|
|
namespace CUETools.CDImage
|
|
{
|
|
public class CDTrackIndex
|
|
{
|
|
public CDTrackIndex(uint index, uint start)
|
|
{
|
|
_start = start;
|
|
_index = index;
|
|
}
|
|
|
|
public CDTrackIndex(CDTrackIndex src)
|
|
{
|
|
_start = src._start;
|
|
_index = src._index;
|
|
}
|
|
|
|
public uint Start
|
|
{
|
|
get
|
|
{
|
|
return _start;
|
|
}
|
|
set
|
|
{
|
|
_start = value;
|
|
}
|
|
}
|
|
|
|
public uint Index
|
|
{
|
|
get
|
|
{
|
|
return _index;
|
|
}
|
|
}
|
|
|
|
public string MSF
|
|
{
|
|
get
|
|
{
|
|
return CDImageLayout.TimeToString(_start);
|
|
}
|
|
}
|
|
|
|
uint _start, _index;
|
|
}
|
|
|
|
public class CDTrack : ICloneable
|
|
{
|
|
public CDTrack(uint number, uint start, uint length, bool isAudio, bool preEmpasis)
|
|
{
|
|
_number = number;
|
|
_start = start;
|
|
_length = length;
|
|
_isAudio = isAudio;
|
|
_preEmphasis = preEmpasis;
|
|
_indexes = new List<CDTrackIndex>();
|
|
_indexes.Add(new CDTrackIndex(0, start));
|
|
_indexes.Add(new CDTrackIndex(1, start));
|
|
}
|
|
|
|
public CDTrack(CDTrack src)
|
|
{
|
|
_number = src._number;
|
|
_start = src._start;
|
|
_length = src._length;
|
|
_isAudio = src._isAudio;
|
|
_preEmphasis = src._preEmphasis;
|
|
_dcp = src._dcp;
|
|
_isrc = src._isrc;
|
|
_indexes = new List<CDTrackIndex>();
|
|
for (int i = 0; i < src._indexes.Count; i++)
|
|
_indexes.Add(new CDTrackIndex(src._indexes[i]));
|
|
}
|
|
|
|
public object Clone()
|
|
{
|
|
return new CDTrack(this);
|
|
}
|
|
|
|
public uint Start
|
|
{
|
|
get
|
|
{
|
|
return _start;
|
|
}
|
|
set
|
|
{
|
|
_start = value;
|
|
}
|
|
}
|
|
|
|
public string StartMSF
|
|
{
|
|
get
|
|
{
|
|
return CDImageLayout.TimeToString(_start);
|
|
}
|
|
}
|
|
|
|
public uint Length
|
|
{
|
|
get
|
|
{
|
|
return _length;
|
|
}
|
|
set
|
|
{
|
|
_length = value;
|
|
}
|
|
}
|
|
|
|
public string LengthMSF
|
|
{
|
|
get
|
|
{
|
|
return CDImageLayout.TimeToString(_length);
|
|
}
|
|
}
|
|
|
|
public string ISRC
|
|
{
|
|
get
|
|
{
|
|
return _isrc;
|
|
}
|
|
set
|
|
{
|
|
_isrc = value;
|
|
}
|
|
}
|
|
|
|
public uint End
|
|
{
|
|
get
|
|
{
|
|
return _start + _length - 1;
|
|
}
|
|
}
|
|
|
|
public string EndMSF
|
|
{
|
|
get
|
|
{
|
|
return CDImageLayout.TimeToString(End);
|
|
}
|
|
}
|
|
|
|
public uint Number
|
|
{
|
|
get
|
|
{
|
|
return _number;
|
|
}
|
|
internal set
|
|
{
|
|
_number = value;
|
|
}
|
|
}
|
|
|
|
public uint Pregap
|
|
{
|
|
get
|
|
{
|
|
return _start - _indexes[0].Start;
|
|
}
|
|
set
|
|
{
|
|
_indexes[0].Start = _start - value;
|
|
}
|
|
}
|
|
|
|
public CDTrackIndex this[int key]
|
|
{
|
|
get
|
|
{
|
|
return _indexes[key];
|
|
}
|
|
}
|
|
|
|
public uint LastIndex
|
|
{
|
|
get
|
|
{
|
|
return (uint) _indexes.Count - 1;
|
|
}
|
|
}
|
|
|
|
public bool IsAudio
|
|
{
|
|
get
|
|
{
|
|
return _isAudio;
|
|
}
|
|
set
|
|
{
|
|
_isAudio = value;
|
|
}
|
|
}
|
|
|
|
public bool PreEmphasis
|
|
{
|
|
get
|
|
{
|
|
return _preEmphasis;
|
|
}
|
|
set
|
|
{
|
|
_preEmphasis = value;
|
|
}
|
|
}
|
|
|
|
public bool DCP
|
|
{
|
|
get
|
|
{
|
|
return _dcp;
|
|
}
|
|
set
|
|
{
|
|
_dcp = value;
|
|
}
|
|
}
|
|
|
|
public void AddIndex(CDTrackIndex index)
|
|
{
|
|
if (index.Index < 2)
|
|
_indexes[(int)index.Index] = index;
|
|
else
|
|
_indexes.Add(index);
|
|
}
|
|
|
|
IList<CDTrackIndex> _indexes;
|
|
string _isrc;
|
|
bool _isAudio;
|
|
bool _preEmphasis, _dcp;
|
|
uint _start;
|
|
uint _length;
|
|
uint _number;
|
|
}
|
|
|
|
public class CDImageLayout : ICloneable
|
|
{
|
|
public CDImageLayout()
|
|
{
|
|
_tracks = new List<CDTrack>();
|
|
}
|
|
|
|
public CDImageLayout(CDImageLayout src)
|
|
{
|
|
_barcode = src._barcode;
|
|
_audioTracks = src._audioTracks;
|
|
_firstAudio = src._firstAudio;
|
|
_tracks = new List<CDTrack>();
|
|
for (int i = 0; i < src.TrackCount; i++)
|
|
_tracks.Add(new CDTrack(src._tracks[i]));
|
|
}
|
|
|
|
public CDImageLayout(string trackoffsets)
|
|
: this(trackoffsets.Split(' ').Length - 1, trackoffsets.Split(' ').Length - 1, 1, trackoffsets)
|
|
{
|
|
}
|
|
|
|
public CDImageLayout(int trackcount, int audiotracks, int firstaudio, string trackoffsets)
|
|
{
|
|
_audioTracks = audiotracks;
|
|
_firstAudio = firstaudio - 1;
|
|
_tracks = new List<CDTrack>();
|
|
string[] n = trackoffsets.Split(' ');
|
|
if (n.Length != trackcount + 1)
|
|
throw new Exception("Invalid trackoffsets.");
|
|
for (int i = 0; i < trackcount; i++)
|
|
{
|
|
uint len = uint.Parse(n[i + 1]) - uint.Parse(n[i]) -
|
|
((i + 1 < _firstAudio + _audioTracks || i + 1 == trackcount) ? 0U : 152U * 75U);
|
|
bool isaudio = i >= _firstAudio && i < _firstAudio + _audioTracks;
|
|
_tracks.Add(new CDTrack((uint)i + 1, uint.Parse(n[i]), len, isaudio, false));
|
|
}
|
|
_tracks[0][0].Start = 0;
|
|
if (TrackOffsets != trackoffsets)
|
|
throw new Exception("TrackOffsets != trackoffsets");
|
|
}
|
|
|
|
public object Clone()
|
|
{
|
|
return new CDImageLayout(this);
|
|
}
|
|
|
|
public uint Length
|
|
{
|
|
get
|
|
{
|
|
return TrackCount > 0 ? _tracks[TrackCount - 1].End + 1U : 0U;
|
|
}
|
|
}
|
|
|
|
public CDTrack this[int key]
|
|
{
|
|
get
|
|
{
|
|
return _tracks[key - 1];
|
|
}
|
|
}
|
|
|
|
public int TrackCount
|
|
{
|
|
get
|
|
{
|
|
return _tracks.Count;
|
|
}
|
|
}
|
|
|
|
public uint Pregap
|
|
{
|
|
get
|
|
{
|
|
return _tracks[_firstAudio].Pregap;
|
|
}
|
|
}
|
|
|
|
public uint AudioTracks
|
|
{
|
|
get
|
|
{
|
|
return (uint) _audioTracks;
|
|
}
|
|
|
|
set
|
|
{
|
|
_audioTracks = (int) value;
|
|
}
|
|
}
|
|
|
|
public int FirstAudio
|
|
{
|
|
get
|
|
{
|
|
return _firstAudio + 1;
|
|
}
|
|
set
|
|
{
|
|
_firstAudio = value - 1;
|
|
}
|
|
}
|
|
|
|
public int LastAudio
|
|
{
|
|
get
|
|
{
|
|
return _audioTracks + _firstAudio;
|
|
}
|
|
}
|
|
|
|
public uint Leadout
|
|
{
|
|
get
|
|
{
|
|
return _tracks[_firstAudio][0].Start + AudioLength;
|
|
}
|
|
}
|
|
|
|
public uint AudioLength
|
|
{
|
|
get
|
|
{
|
|
return AudioTracks > 0 ? _tracks[_firstAudio + _audioTracks - 1].End + 1U - _tracks[_firstAudio][0].Start : 0U;
|
|
}
|
|
}
|
|
|
|
public string Barcode
|
|
{
|
|
get
|
|
{
|
|
return _barcode;
|
|
}
|
|
set
|
|
{
|
|
_barcode = value;
|
|
}
|
|
}
|
|
|
|
public string MusicBrainzTOC
|
|
{
|
|
get
|
|
{
|
|
StringBuilder mbSB = new StringBuilder();
|
|
mbSB.AppendFormat("{0} {1} {2}", 1, LastAudio, _tracks[LastAudio - 1].End + 1 + 150);
|
|
for (int iTrack = 0; iTrack < LastAudio; iTrack++)
|
|
mbSB.AppendFormat(" {0}", _tracks[iTrack].Start + 150);
|
|
return mbSB.ToString();
|
|
}
|
|
}
|
|
|
|
public string MusicBrainzId
|
|
{
|
|
get
|
|
{
|
|
StringBuilder mbSB = new StringBuilder();
|
|
mbSB.AppendFormat("{0:X2}{1:X2}", 1, LastAudio);
|
|
mbSB.AppendFormat("{0:X8}", _tracks[LastAudio - 1].End + 1 + 150);
|
|
for (int iTrack = 0; iTrack < LastAudio ; iTrack++)
|
|
mbSB.AppendFormat("{0:X8}", _tracks[iTrack].Start + 150);
|
|
mbSB.Append(new string('0', (99 - LastAudio) * 8));
|
|
byte[] hashBytes = (new SHA1CryptoServiceProvider()).ComputeHash(Encoding.ASCII.GetBytes(mbSB.ToString()));
|
|
return Convert.ToBase64String(hashBytes).Replace('+', '.').Replace('/', '_').Replace('=', '-');
|
|
}
|
|
}
|
|
|
|
public string TrackOffsets
|
|
{
|
|
get
|
|
{
|
|
StringBuilder mbSB = new StringBuilder();
|
|
for (int iTrack = 0; iTrack < TrackCount; iTrack++)
|
|
mbSB.AppendFormat("{0} ", _tracks[iTrack].Start);
|
|
mbSB.AppendFormat("{0}", Length);
|
|
return mbSB.ToString();
|
|
}
|
|
}
|
|
|
|
public string TOCID
|
|
{
|
|
get
|
|
{
|
|
StringBuilder mbSB = new StringBuilder();
|
|
for (int iTrack = 1; iTrack < AudioTracks; iTrack++)
|
|
mbSB.AppendFormat("{0:X8}", _tracks[_firstAudio + iTrack].Start - _tracks[_firstAudio].Start);
|
|
mbSB.AppendFormat("{0:X8}", _tracks[_firstAudio + (int)AudioTracks - 1].End + 1 - _tracks[_firstAudio].Start);
|
|
// Use Math.Max() to avoid negative count number in case of non-standard CUE sheet with more than 99 tracks.
|
|
mbSB.Append(new string('0', Math.Max(0, (100 - (int)AudioTracks) * 8)));
|
|
byte[] hashBytes = (new SHA1CryptoServiceProvider()).ComputeHash(Encoding.ASCII.GetBytes(mbSB.ToString()));
|
|
return Convert.ToBase64String(hashBytes).Replace('+', '.').Replace('/', '_').Replace('=', '-');
|
|
}
|
|
}
|
|
|
|
public string TAG
|
|
{
|
|
get
|
|
{
|
|
StringBuilder mbSB = new StringBuilder();
|
|
mbSB.AppendFormat(CultureInfo.InvariantCulture, "{0:X}", AudioTracks);
|
|
for (int iTrack = _firstAudio; iTrack < TrackCount; iTrack++)
|
|
mbSB.AppendFormat("+{0:X}", _tracks[iTrack].Start + 150);
|
|
mbSB.AppendFormat("+{0:X}", Length + 150);
|
|
for (int iTrack = 0; iTrack < _firstAudio; iTrack++)
|
|
mbSB.AppendFormat("+X{0:X}", _tracks[iTrack].Start + 150);
|
|
return mbSB.ToString();
|
|
}
|
|
}
|
|
|
|
public static CDImageLayout FromString(string str)
|
|
{
|
|
var ids = str.Split(':');
|
|
int firstaudio = 1;
|
|
int audiotracks = 0;
|
|
int trackcount = ids.Length - 1;
|
|
while (firstaudio < ids.Length && ids[firstaudio - 1][0] == '-')
|
|
firstaudio ++;
|
|
while (firstaudio + audiotracks < ids.Length && ids[firstaudio + audiotracks - 1][0] != '-')
|
|
audiotracks ++;
|
|
for (var i = 0; i < ids.Length; i++)
|
|
if (ids[i][0] == '-')
|
|
ids[i] = ids[i].Substring(1);
|
|
return new CDImageLayout(trackcount, audiotracks, firstaudio, string.Join(" ", ids));
|
|
}
|
|
|
|
public static CDImageLayout FromTag(string str)
|
|
{
|
|
var ids = str.Split('+');
|
|
int audiotracks;
|
|
int firstaudio = 1;
|
|
int trackcount = ids.Length - 2;
|
|
int offs;
|
|
var ids2 = new List<string>();
|
|
if (!int.TryParse(ids[0], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out audiotracks))
|
|
return null;
|
|
if (trackcount > audiotracks && ids[ids.Length - 1][0] == 'X')
|
|
{
|
|
firstaudio = 1 + trackcount - audiotracks;
|
|
for (int i = 2 + audiotracks; i < 2 + trackcount; i++)
|
|
{
|
|
if (!int.TryParse(ids[i].Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out offs))
|
|
return null;
|
|
ids2.Add((offs - 150).ToString());
|
|
}
|
|
for (int i = 1; i < 2 + audiotracks; i++)
|
|
{
|
|
if (!int.TryParse(ids[i], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out offs))
|
|
return null;
|
|
ids2.Add((offs - 150).ToString());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int i = 1; i < 2 + trackcount; i++)
|
|
{
|
|
if (!int.TryParse(ids[i], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out offs))
|
|
return null;
|
|
ids2.Add((offs - 150).ToString());
|
|
}
|
|
}
|
|
return new CDImageLayout(trackcount, audiotracks, firstaudio, string.Join(" ", ids2.ToArray()));
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
StringBuilder mbSB = new StringBuilder();
|
|
for (int iTrack = 0; iTrack < TrackCount; iTrack++)
|
|
mbSB.AppendFormat("{0}{1}:", _tracks[iTrack].IsAudio ? "" : "-", _tracks[iTrack].Start);
|
|
mbSB.AppendFormat("{0}", Length);
|
|
return mbSB.ToString();
|
|
}
|
|
|
|
public void InsertTrack(CDTrack track)
|
|
{
|
|
_tracks.Insert((int)track.Number - 1, track);
|
|
for (int i = (int)track.Number; i < _tracks.Count; i++)
|
|
_tracks[i].Number++;
|
|
if (track.IsAudio)
|
|
_audioTracks++;
|
|
if (!track.IsAudio && track.Number <= FirstAudio)
|
|
_firstAudio++;
|
|
}
|
|
|
|
public void AddTrack(CDTrack track)
|
|
{
|
|
_tracks.Add(track);
|
|
if (track.IsAudio)
|
|
{
|
|
_audioTracks++;
|
|
if (!_tracks[_firstAudio].IsAudio)
|
|
_firstAudio = _tracks.Count - 1;
|
|
}
|
|
}
|
|
|
|
public uint IndexLength(int iTrack, int iIndex)
|
|
{
|
|
if (iIndex < _tracks[iTrack - 1].LastIndex)
|
|
return _tracks[iTrack - 1][iIndex + 1].Start - _tracks[iTrack - 1][iIndex].Start;
|
|
if (iTrack < TrackCount && _tracks[iTrack].IsAudio)
|
|
return _tracks[iTrack][0].Start - _tracks[iTrack - 1][iIndex].Start;
|
|
return _tracks[iTrack - 1].End + 1 - _tracks[iTrack - 1][iIndex].Start;
|
|
}
|
|
|
|
public static int TimeFromString(string s)
|
|
{
|
|
string[] n = s.Split(':');
|
|
if (n.Length != 3)
|
|
{
|
|
throw new Exception("Invalid timestamp.");
|
|
}
|
|
int min, sec, frame;
|
|
|
|
min = Int32.Parse(n[0]);
|
|
sec = Int32.Parse(n[1]);
|
|
frame = Int32.Parse(n[2]);
|
|
|
|
return frame + (sec * 75) + (min * 60 * 75);
|
|
}
|
|
|
|
public static string TimeToString(uint t, string format = "{0:00}:{1:00}:{2:00}")
|
|
{
|
|
uint min, sec, frame;
|
|
|
|
frame = t % 75;
|
|
t /= 75;
|
|
sec = t % 60;
|
|
t /= 60;
|
|
min = t;
|
|
|
|
return String.Format(format, min, sec, frame);
|
|
}
|
|
|
|
public static string TimeToString(TimeSpan ts, string format = "{0:00}:{1:00}:{2:00}.{3:000}")
|
|
{
|
|
ulong t = (ulong) ts.TotalMilliseconds;
|
|
ulong ms = t % 1000;
|
|
t /= 1000;
|
|
ulong s = t % 60;
|
|
t /= 60;
|
|
ulong m = t % 60;
|
|
t /= 60;
|
|
ulong hr = t;
|
|
return String.Format(format, hr, m, s, ms);
|
|
}
|
|
|
|
string _barcode;
|
|
IList<CDTrack> _tracks;
|
|
int _audioTracks;
|
|
int _firstAudio;
|
|
}
|
|
}
|