diff --git a/CUETools.Codecs.ALAC/ALACDotNet.cs b/CUETools.Codecs.ALAC/ALACDotNet.cs index 00e6186..608edd3 100644 --- a/CUETools.Codecs.ALAC/ALACDotNet.cs +++ b/CUETools.Codecs.ALAC/ALACDotNet.cs @@ -165,7 +165,9 @@ namespace CUETools.Codecs.ALAC _IO.Close(); } - public long Length + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + + public long Length { get { diff --git a/CUETools.Codecs.Flake/AudioDecoder.cs b/CUETools.Codecs.Flake/AudioDecoder.cs index bc456fc..16428d6 100644 --- a/CUETools.Codecs.Flake/AudioDecoder.cs +++ b/CUETools.Codecs.Flake/AudioDecoder.cs @@ -127,7 +127,9 @@ namespace CUETools.Codecs.Flake _IO.Close(); } - public long Length + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + + public long Length { get { diff --git a/CUETools.Codecs.LossyWAV/LossyWAVReader.cs b/CUETools.Codecs.LossyWAV/LossyWAVReader.cs index 9929e1b..9eca674 100644 --- a/CUETools.Codecs.LossyWAV/LossyWAVReader.cs +++ b/CUETools.Codecs.LossyWAV/LossyWAVReader.cs @@ -12,13 +12,9 @@ namespace CUETools.Codecs.LossyWAV public IAudioDecoderSettings Settings => null; - public long Length - { - get - { - return _audioSource.Length; - } - } + public TimeSpan Duration => _audioSource.Duration; + + public long Length => _audioSource.Length; public long Position { @@ -33,29 +29,11 @@ namespace CUETools.Codecs.LossyWAV } } - public long Remaining - { - get - { - return _audioSource.Remaining; - } - } + public long Remaining => _audioSource.Remaining; - public AudioPCMConfig PCM - { - get - { - return _audioSource.PCM; - } - } + public AudioPCMConfig PCM => _audioSource.PCM; - public string Path - { - get - { - return _audioSource.Path; - } - } + public string Path => _audioSource.Path; public LossyWAVReader(IAudioSource audioSource, IAudioSource lwcdfSource) { diff --git a/CUETools.Codecs.MACLib/AudioDecoder.cs b/CUETools.Codecs.MACLib/AudioDecoder.cs index 6ca6f51..8f69879 100644 --- a/CUETools.Codecs.MACLib/AudioDecoder.cs +++ b/CUETools.Codecs.MACLib/AudioDecoder.cs @@ -74,6 +74,8 @@ namespace CUETools.Codecs.MACLib public string Path => _path; + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length => _sampleCount; internal long SamplesInBuffer => _bufferLength - _bufferOffset; diff --git a/CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs b/CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs new file mode 100644 index 0000000..f559aa3 --- /dev/null +++ b/CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs @@ -0,0 +1,1043 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using static CUETools.Codecs.AudioPCMConfig; + +namespace CUETools.Codecs.MPEG.ATSI +{ + public class AudioDecoder : IAudioSource, IAudioTitleSet + { + public unsafe AudioDecoder(DecoderSettings settings, string path, Stream IO) + { + m_settings = settings; + _path = path; + _IO = IO != null ? IO : new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000); + int length = (int)_IO.Length; + if (length > 0x100000) throw new Exception("File too big"); + contents = new byte[length]; + if (_IO.Read(contents, 0, length) != length) throw new Exception(""); + fixed (byte* ptr = &contents[0]) + { + var fr = new FrameReader(ptr, length); + hdr_m = parseHeader(fr); + fr = new FrameReader(ptr, length); + parseTitles(fr); + } + } + + void openEntries() + { + readers = new List(); + var pids = new List(); + //foreach (var item in hdr_m.play_item) + // foreach (var audio in item.audio) + // { + // if (audio.coding_type != 0x80 /* LPCM */) continue; + // pids.Add(audio.pid); + // } + //int chosenPid; + //if (m_settings.StreamId.HasValue) + //{ + // if (!pids.Contains(m_settings.StreamId.Value)) + // throw new Exception("StreamId can be " + + // string.Join(", ", pids.ConvertAll(pid => pid.ToString()).ToArray())); + // chosenPid = m_settings.StreamId.Value; + //} + //else if (m_settings.Stream.HasValue) + //{ + // if (m_settings.Stream.Value < 0 || m_settings.Stream.Value >= pids.Count) + // throw new Exception("Stream can be 0.." + (pids.Count - 1).ToString()); + // chosenPid = pids[m_settings.Stream.Value]; + //} + //else throw new Exception("multiple streams present, please specify StreamId or Stream"); + //foreach (var item in hdr_m.play_item) + // foreach (var audio in item.audio) + // { + // if (audio.coding_type != 0x80 /* LPCM */) continue; + // if (m_settings.IgnoreShortItems && item.out_time - item.in_time < shortItemDuration) continue; + // if (audio.pid == chosenPid) + // { + // var parent = Directory.GetParent(System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(_path))); + // var m2ts = System.IO.Path.Combine( + // System.IO.Path.Combine(parent.FullName, "STREAM"), + // item.clip_id + ".m2ts"); + // var settings = new BDLPCM.DecoderSettings() { StreamId = chosenPid }; + // var entry = settings.Open(m2ts); + // readers.Add(entry); + // break; + // } + // } + currentReader = readers[0]; + pcm = currentReader.PCM; + } + + ATSIHeader parseHeader(FrameReader fr) + { + var hdr = new ATSIHeader(fr); + + uint aob_offset = 0; + for (int i = 0; i < 9; i++) + { + var aob = new DVDAAOBFile(); + aob.fileName = System.IO.Path.Combine( + System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(_path)), + $"ATS_{System.IO.Path.GetFileNameWithoutExtension(_path).Substring(4, 2)}_{i + 1}.AOB"); + aob.first = aob_offset; + try + { + aob.atsFile = new FileStream(aob.fileName, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000); + aob.last = (uint)(aob.first + aob.atsFile.Length / DVDA.BLOCK_SIZE) - 1; + aob.isExist = true; + } + catch (FileNotFoundException) + { + aob.last = aob.first + (uint)((1024 * 1024 - 32) * 1024 / DVDA.BLOCK_SIZE - 1); + aob.isExist = false; + } + aob_offset = aob.last + 1; + hdr.aobs.Add(aob); + } + + return hdr; + } + + unsafe void parseTitles(FrameReader parentFr) + { + for (int i = 0; i < hdr_m.nr_of_titles; i++) + { + var fr = new FrameReader(parentFr.Ptr + 0x800 + hdr_m.ats_title_idx[i], parentFr.Length - 0x800 - hdr_m.ats_title_idx[i]); + hdr_m.titles.Add(parseTitle(fr)); + } + for (int i = 0; i < hdr_m.nr_of_titles; i++) + { + var fr = new FrameReader(parentFr.Ptr + 0x100 + i * 16, 16); + hdr_m.titles[i].codec = fr.read_ushort(); + hdr_m.titles[i].format = fr.read_uint(); + } + } + + ATSITitle parseTitle(FrameReader fr) + { + var titleFr = new FrameReader(fr, fr.Length); + var title = new ATSITitle(this, titleFr); + for (int i = 0; i < title.ntracks; i++) + title.track_timestamp.Add(new ATSITrackTimestamp(titleFr)); + fr.skip(title.track_sector_table_offset); + for (int i = 0; i < title.nindexes; i++) + { + var dvdaSectorPointer = new ATSITrackSector(fr); + title.track_sector.Add(dvdaSectorPointer); + for (int k = 0; k < title.ntracks; k++) + { + var track_curr_idx = title.track_timestamp[k].pg_id; + var track_next_idx = (k < title.ntracks - 1) ? title.track_timestamp[k + 1].pg_id : 0; + if (i + 1 >= track_curr_idx && (i + 1 < track_next_idx || track_next_idx == 0)) + { + title.track_timestamp[k].sector_pointers.Add(i); + } + } + + var nblocks = Math.Min(SEGMENT_HEADER_BLOCKS, dvdaSectorPointer.last_sector - dvdaSectorPointer.first_sector + 1); + var head_buf = new byte[nblocks * DVDA.BLOCK_SIZE]; + for (int b = 0; b < nblocks; b++) + { + getBlock(dvdaSectorPointer.first_sector + b, head_buf, b * DVDA.BLOCK_SIZE); + } + dvdaSectorPointer.dvdaBlock = new DVDABlock(head_buf); + } + return title; + } + + void getBlock(long block_no, byte[] buf, int offset) + { + var aob = hdr_m.aobs.Find(a => a.isExist && block_no >= a.first && block_no <= a.last); + if (aob == null) return; + aob.atsFile.Seek((block_no - aob.first) * DVDA.BLOCK_SIZE, SeekOrigin.Begin); + if (aob.atsFile.Read(buf, offset, DVDA.BLOCK_SIZE) != DVDA.BLOCK_SIZE) + throw new Exception(); + // theZone->decryptBlock(buf_ptr); + } + + public IAudioDecoderSettings Settings => m_settings; + + public void Close() + { + if (readers != null) + foreach (var rdr in readers) + { + rdr.Close(); + } + readers = null; + currentReader = null; + _IO = null; + } + + public long Length + { + get + { + return -1; + } + } + + public TimeSpan Duration + { + get + { + int title = 0; + if (!m_settings.Title.HasValue) + { + if (hdr_m.titles.Count > 1) throw new Exception("multiple titles present, please specify Title"); + } + else + { + if (m_settings.Title.Value < 0 || m_settings.Title >= hdr_m.titles.Count) + throw new Exception($"Title can be 0..{hdr_m.titles.Count - 1}"); + title = m_settings.Title.Value; + } + var chapters = hdr_m.titles[title].Chapters; + return chapters[chapters.Count - 1]; + } + } + + public long Remaining + { + get + { + return -1; + } + } + + public long Position + { + get + { + long res = 0; + foreach (var rdr in readers) + { + res += rdr.Position; + if (rdr == currentReader) break; + } + return res; + } + set + { + throw new NotSupportedException(); + } + } + + public unsafe AudioPCMConfig PCM + { + get { + if (readers == null) openEntries(); + return pcm; + } + } + + public string Path { get { return _path; } } + + public unsafe int Read(AudioBuffer buff, int maxLength) + { + if (readers == null) openEntries(); + int res = currentReader.Read(buff, maxLength); + if (res == 0) + { + bool nextOne = false; + foreach (var rdr in readers) + { + if (nextOne) + { + currentReader = rdr; + return currentReader.Read(buff, maxLength); + } + nextOne = (rdr == currentReader); + } + currentReader = null; + } + return res; + } + + public string FileName + { + get + { + return System.IO.Path.GetFileName(_path); + } + } + + public List AudioTitles => hdr_m.titles.ConvertAll(x => x as IAudioTitle); + + public ATSIHeader ATSIHeader + { + get + { + return hdr_m; + } + } + + readonly static int SEGMENT_HEADER_BLOCKS = 16; + + string _path; + Stream _IO; + byte[] contents; + + AudioPCMConfig pcm; + List readers; + IAudioSource currentReader; + ATSIHeader hdr_m; + DecoderSettings m_settings; + } + + public class ATSITitle : IAudioTitle + { + internal ATSITitle(AudioDecoder atsi, FrameReader fr) + { + this.atsi = atsi; + + track_timestamp = new List(); + track_sector = new List(); + + unknown1 = fr.read_ushort(); + ntracks = fr.read_byte(); + nindexes = fr.read_byte(); + length_pts = fr.read_uint(); + unknown2 = fr.read_ushort(); + unknown3 = fr.read_ushort(); + track_sector_table_offset = fr.read_ushort(); + unknown4 = fr.read_ushort(); + } + + // ?Unknown (e.g. 0x0000) + public ushort unknown1; + // ???Number of tracks in title (repeated - e.g. 0x0303 for 3 tracks, 0x0b0b for 12 tracks) + public byte ntracks; + public byte nindexes; + // Length of track in PTS ticks + public uint length_pts; + // ?Unknown (e.g. 0x0000) + public ushort unknown2; + // ?Unknown (e.g. 0x0010) + public ushort unknown3; + // Byte pointer to start of sector pointers table (relative to the start of this title record) + public ushort track_sector_table_offset; + // ?Unknown (e.g. 0x0000) + public ushort unknown4; + + public const int SIZE = 16; + + AudioDecoder atsi; + + public ushort codec; + public uint format; + + public int StreamId + { + get + { + if (track_sector.Count < 1) return 0; + if (track_sector[0].dvdaBlock == null) return 0; + if (track_sector[0].dvdaBlock.sh == null) return 0; + return track_sector[0].dvdaBlock.sh.stream_id; + } + } + + public List Chapters + { + get + { + //settings.IgnoreShortItems + var res = new List(); + double time_base = 90000.0; + var durations = track_timestamp.ConvertAll(track => ((long)track.len_in_pts)); + bool ignoreShortItems = true; // m_settings.IgnoreShortItems + const int shortItemDuration = 90000 * 10; + for (int i = 0; i < track_timestamp.Count; i++) + { + if (ignoreShortItems && durations[i] < shortItemDuration) continue; + long item_offset = 0; + for (int j = 0; j < i; j++) + { + var item_duration = durations[j]; + if (ignoreShortItems && item_duration < shortItemDuration) continue; + item_offset += item_duration; + } + res.Add(TimeSpan.FromSeconds((uint)item_offset / time_base)); + } + long end_offset = 0; + for (int j = 0; j < track_timestamp.Count; j++) + { + var item_duration = durations[j]; + if (ignoreShortItems && item_duration < shortItemDuration) continue; + end_offset += item_duration; + } + res.Add(TimeSpan.FromSeconds((uint)end_offset / time_base)); + //while (res.Count > 1 && res[1] - res[0] < 45000) res.RemoveAt(1); + //while (res.Count > 1 && res[res.Count - 1] - res[res.Count - 2] < 45000) res.RemoveAt(res.Count - 2); + return res; + } + } + + public AudioPCMConfig PCM + { + get + { + if (track_sector.Count < 1) return null; + if (track_sector[0].dvdaBlock == null) return null; + return new AudioPCMConfig( + track_sector[0].dvdaBlock.gr1_bits, + track_sector[0].dvdaBlock.channels, + track_sector[0].dvdaBlock.gr1_frequency); + } + } + + public string Codec + { + get + { + switch (StreamId) + { + case DVDA.PCM_STREAM_ID: return "RAW/PCM"; + case DVDA.MLP_STREAM_ID: return "MLP"; + default: return StreamId.ToString(); + } + //switch (codec) + //{ + // case 0x000: return "RAW/PCM"; + // case 0x100: return "MLP"; + // default: return codec.ToString(); + //} + } + } + + public string Language => ""; + + public List track_timestamp; + public List track_sector; + } + + public class ATSITrackTimestamp + { + internal ATSITrackTimestamp(FrameReader fr) + { + sector_pointers = new List(); + track_nr = fr.read_ushort(); + unknown2 = fr.read_ushort(); + pg_id = fr.read_byte(); + unknown3 = fr.read_byte(); + first_pts = fr.read_uint(); + len_in_pts = fr.read_uint(); + padding1 = fr.read_ushort(); + padding2 = fr.read_uint(); + } + + // ?Unknown(e.g. 0xc010 for first track, and 0x0010 for subsequent) + public ushort track_nr; + // ?Unknown(e.g. 0x0000) + public ushort unknown2; + // Track number in title + public byte pg_id; + // ?Unknown(e.g. 0x00) + public byte unknown3; + // First PTS of track + public uint first_pts; + //Length of track in PTS ticks + public uint len_in_pts; + //Padding(zero) + public ushort padding1; + //Padding(zero) + public uint padding2; + + public const int SIZE = 20; + + public List sector_pointers; + } + + public class DVDABlock + { + public DVDABlock(byte[] p_blocks) + { + head_buf = new byte[p_blocks.Length]; + head_len = 0; + scrambled = false; + int n_blocks = p_blocks.Length / 2048; + for (int i = 0; i < n_blocks; i++) + getPS1Payload(p_blocks, i * DVDA.BLOCK_SIZE); + if (head_len > 0) + getPS1Params(head_buf, head_len); + } + + void getPS1Payload(byte[] p_block, int offset, bool ignore_scrambling = true) + { + int i_ps1_body; + int i_curr = offset; + bool scrambling_checked = false; + if (p_block[i_curr] != 0x00 || p_block[i_curr + 1] != 0x00 || p_block[i_curr + 2] != 0x01 || p_block[i_curr + 3] != 0xBA) + return; + // scrambled = false; + i_curr += 14 + (p_block[i_curr + 13] & 0x07); + while (i_curr < offset + DVDA.BLOCK_SIZE) + { + int pes_len = (p_block[i_curr + 4] << 8) + p_block[i_curr + 5]; + if (p_block[i_curr + 0] != 0x00 || p_block[i_curr + 1] != 0x00 || p_block[i_curr + 2] != 0x01) + break; + uint pes_sid = p_block[i_curr + 3]; + if (pes_sid == 0xbd) + { // check for private stream 1 + if (!scrambling_checked && (p_block[i_curr + 6] & 0x30) != 0) + scrambled = true; // check for pes_scrambling_control + scrambling_checked = true; + int i_ps1_header = i_curr + 9 + p_block[i_curr + 8]; + int i_ps1_end = i_curr + 6 + pes_len; + if (scrambled && !ignore_scrambling && (i_ps1_end - offset > 127)) + throw new Exception("Block scrambled"); + i_ps1_body = i_ps1_header + ((head_len > 0) ? getSubstreamHeaderLen(p_block, i_ps1_header, i_ps1_end - i_ps1_header) : 0); + int ps1_body_len = i_ps1_end - i_ps1_body; + if (ps1_body_len > 0) + { + Array.Copy(p_block, i_ps1_body, head_buf, head_len, ps1_body_len); + head_len += ps1_body_len; + } + } + i_curr += 6 + pes_len; + } + } + + unsafe void getPS1Params(byte[] ps1_header, int ps1_len) + { + ch_assignment = -1; + gr1_frequency = 0; + gr1_bits = 0; + gr2_frequency = 0; + gr2_bits = 0; + vbr = false; + peak_bitrate = 0; + substreams = 0; + cci = 0; + if (ps1_len == 0) + return; + fixed (byte* p_ps1_header = &ps1_header[0]) + { + var fr = new FrameReader(p_ps1_header, ps1_len); + sh = new SUB_HEADER(fr); + switch (sh.stream_id) + { + case DVDA.PCM_STREAM_ID: + { + if (sh.extra_len < PCM_EXTRAHEADER.SIZE) break; + PCM_EXTRAHEADER pcm_ehdr = new PCM_EXTRAHEADER(fr); + cci = pcm_ehdr.cci; + ch_assignment = pcm_ehdr.channel_assignment; + decode_grp1_bits(pcm_ehdr.group1_bits); + decode_grp2_bits(pcm_ehdr.group2_bits); + decode_grp1_freq(pcm_ehdr.group1_freq); + decode_grp2_freq(pcm_ehdr.group2_freq); + vbr = false; + peak_bitrate = gr1_channels * gr1_frequency * gr1_bits + gr2_channels * gr2_frequency * gr2_bits; + substreams = 1; + break; + } + + case DVDA.MLP_STREAM_ID: + { + if (sh.extra_len < MLP_EXTRAHEADER.SIZE) break; + MLP_EXTRAHEADER mlp_ehdr = new MLP_EXTRAHEADER(fr); + fr.skip(sh.extra_len - MLP_EXTRAHEADER.SIZE); + while (fr.Length > MLP_LINK.SIZE + MLP_SIGNATURE.SIZE) + { + FrameReader fr1 = new FrameReader(fr, MLP_LINK.SIZE + MLP_SIGNATURE.SIZE); + fr1.skip(MLP_LINK.SIZE); + MLP_SIGNATURE mlp_sign = new MLP_SIGNATURE(fr1); + if (mlp_sign.signature1 != 0xF8726FBB /*|| p_mlp_sign->signature2 != 0xB752*/) + { + fr.skip(1); + continue; + } + cci = mlp_ehdr.cci; + fr.skip(MLP_LINK.SIZE); + ch_assignment = mlp_sign.channel_assignment; + decode_grp1_bits(mlp_sign.group1_bits); + decode_grp2_bits(mlp_sign.group2_bits); + decode_grp1_freq(mlp_sign.group1_freq); + decode_grp2_freq(mlp_sign.group2_freq); + vbr = (mlp_sign.bitrate & 0x8000) != 0; + peak_bitrate = ((mlp_sign.bitrate & ~0x8000) * gr1_frequency + 8) >> 4; + substreams = mlp_sign.substreams; + break; + } + break; + } + } + } + } + + unsafe int getSubstreamHeaderLen(byte[] substream_buf, int substream_off, int substream_len) + { + if (substream_len <= 4) return 0; + fixed (byte* p = &substream_buf[substream_off]) + { + var fr = new FrameReader(p, substream_len); + var hdr = new SUB_HEADER(fr); + switch (hdr.stream_id) + { + case DVDA.PCM_STREAM_ID: + case DVDA.MLP_STREAM_ID: + return SUB_HEADER.SIZE + hdr.extra_len; + default: + return 0; + } + } + } + + public int gr1_channels => ChannelsInMask(DVDA.grp1_ch_table[ch_assignment]); + + public int gr2_channels => ChannelsInMask(DVDA.grp2_ch_table[ch_assignment]); + + public int channels => gr1_channels + gr2_channels; + + public void decode_grp1_bits(byte b) + { + switch (b) + { + case 0: + gr1_bits = 16; + break; + case 1: + gr1_bits = 20; + break; + case 2: + gr1_bits = 24; + break; + default: + gr1_bits = 0; + break; + } + } + + public void decode_grp2_bits(byte b) + { + switch (b) + { + case 0: + gr2_bits = 16; + break; + case 1: + gr2_bits = 20; + break; + case 2: + gr2_bits = 24; + break; + case 0x0f: + default: + gr2_bits = 0; + break; + } + } + + public void decode_grp1_freq(byte b) + { + switch (b) + { + case 0: + gr1_frequency = 48000; + break; + case 1: + gr1_frequency = 96000; + break; + case 2: + gr1_frequency = 192000; + break; + case 8: + gr1_frequency = 44100; + break; + case 9: + gr1_frequency = 88200; + break; + case 0x0A: + gr1_frequency = 176400; + break; + default: + gr1_frequency = 0; + break; + } + } + + public void decode_grp2_freq(byte b) + { + switch (b) + { + case 0: + gr2_frequency = 48000; + break; + case 1: + gr2_frequency = 96000; + break; + case 8: + gr2_frequency = 44100; + break; + case 9: + gr2_frequency = 88200; + break; + case 0x0F: + default: + gr2_frequency = 0; + break; + } + } + + //uint getChannelId(int channel) + //{ + // if (channel < gr1_channels + gr2_channels) + // { + // if (channel < gr1_channels) + // return DVDA.grp1_ch_table[ch_assignment].[channel]; + // else + // return DVDA.grp2_ch_table[ch_assignment].[channel - gr1_channels]; + // } + // return 0; + //} + + //int remapChannel(int channel) + //{ + // return ch_remap[ch_assignment][channel]; + //} + + public SUB_HEADER sh; + public int head_check_ofs; + public int tail_check_ofs; + public byte[] head_buf; + public int head_len; + public int ch_assignment; + public int gr1_frequency; + public int gr1_bits; + public int gr2_frequency; + public int gr2_bits; + public bool vbr; + public int peak_bitrate; + public int substreams; + public byte cci; + + + private bool scrambled; + } + + public class ATSITrackSector + { + internal ATSITrackSector(FrameReader fr) + { + unknown4 = fr.read_uint(); + first_sector = fr.read_uint(); + last_sector = fr.read_uint(); + } + + // ?? Unknown (e.g. 0x01000000) + public uint unknown4; + // Relative sector pointer to first sector of track (relative to the start of the first .AOB file) + public uint first_sector; + // Relative sector pointer to last sector of track(relative to the start of the first.AOB file) + public uint last_sector; + + public DVDABlock dvdaBlock; + } + + public class SUB_HEADER + { + internal SUB_HEADER(FrameReader fr) + { + stream_id = fr.read_byte(); + cyclic = fr.read_byte(); + padding1 = fr.read_byte(); + extra_len = fr.read_byte(); + } + public byte stream_id; + public byte cyclic; + public byte padding1; + public byte extra_len; + + public const int SIZE = 4; + }; + + public struct PCM_EXTRAHEADER + { + internal PCM_EXTRAHEADER(FrameReader fr) + { + byte tmp; + first_audio_frame = fr.read_ushort(); + padding1 = fr.read_byte(); + group1_bits = (byte)(((tmp = fr.read_byte()) >> 4) & 0xf); + group2_bits = (byte)(tmp & 0xf); + group1_freq = (byte)(((tmp = fr.read_byte()) >> 4) & 0xf); + group2_freq = (byte)(tmp & 0xf); + padding2 = fr.read_byte(); + channel_assignment = fr.read_byte(); + padding3 = fr.read_byte(); + cci = fr.read_byte(); + } + + public ushort first_audio_frame; + public byte padding1; + public byte group2_bits;// : 4; + public byte group1_bits;// : 4; + public byte group2_freq;// : 4; + public byte group1_freq;// : 4; + public byte padding2; + public byte channel_assignment; + public byte padding3; + public byte cci; + + public const int SIZE = 9; + }; + + public class MLP_EXTRAHEADER + { + internal MLP_EXTRAHEADER(FrameReader fr) + { + fr.read_uint(); + cci = fr.read_byte(); + } + + public byte padding1; + public byte padding2; + public byte padding3; + public byte padding4; + public byte cci; + + public const int SIZE = 5; + }; + + public struct MLP_LINK + { + public ushort block_length;// : 12; + public ushort padding; + + public const int SIZE = 4; + }; + + public struct MLP_SIGNATURE + { + internal MLP_SIGNATURE(FrameReader fr) + { + byte tmp; + signature1 = fr.read_uint(); + group1_bits = (byte)(((tmp = fr.read_byte()) >> 4) & 0xf); + group2_bits = (byte)(tmp & 0xf); + group1_freq = (byte)(((tmp = fr.read_byte()) >> 4) & 0xf); + group2_freq = (byte)(tmp & 0xf); + padding1 = fr.read_byte(); + channel_assignment = fr.read_byte(); + signature2 = fr.read_ushort(); + padding2 = fr.read_uint(); + bitrate = fr.read_ushort(); + substreams = (byte)(fr.read_byte() & 0xf); + } + + public uint signature1; + public byte group2_bits;// : 4; + public byte group1_bits;// : 4; + public byte group2_freq;// : 4; + public byte group1_freq;// : 4; + public byte padding1; + public byte channel_assignment; + public ushort signature2; + public uint padding2; + public ushort bitrate; + public byte substreams;// : 4; + + public const int SIZE = 17; + }; + + public class ATSAudioFormat + { + public ushort audio_type; + } + + public class DVDAAOBFile + { + public string fileName; + public Stream atsFile; + public uint first; + public uint last; + public bool isExist; + } + + // Audio Title Set Information Management Table. + public class ATSIMAT + { + internal ATSIMAT(FrameReader fr) + { + ats_audio_format = new ATSAudioFormat[8]; + ats_downmix_matrix = new ushort[16, 8]; + ats_identifier = fr.read_string(12); + if (ats_identifier != "DVDAUDIO-ATS") throw new NotSupportedException(); + ats_last_sector = fr.read_uint(); + atsi_last_sector = fr.read_uint(); + ats_category = fr.read_uint(); + atsi_last_byte = fr.read_uint(); + atsm_vobs = fr.read_uint(); + atstt_vobs = fr.read_uint(); + ats_ptt_srpt = fr.read_uint(); + ats_pgcit = fr.read_uint(); + atsm_pgci_ut = fr.read_uint(); + ats_tmapt = fr.read_uint(); + atsm_c_adt = fr.read_uint(); + atsm_vobu_admap = fr.read_uint(); + ats_c_adt = fr.read_uint(); + ats_vobu_admap = fr.read_uint(); + for (int i = 0; i < 8; i++) + { + ats_audio_format[i] = new ATSAudioFormat(); + ats_audio_format[i].audio_type = fr.read_ushort(); + } + for (int i = 0; i < 16; i++) + for (int j = 0; j < 8; j++) + ats_downmix_matrix[i, j] = fr.read_ushort(); + } + + public string ats_identifier; // [12]; + public uint ats_last_sector; + public uint atsi_last_sector; + public uint ats_category; + public uint atsi_last_byte; + public uint atsm_vobs; + public uint atstt_vobs; + public uint ats_ptt_srpt; + public uint ats_pgcit; + public uint atsm_pgci_ut; + public uint ats_tmapt; + public uint atsm_c_adt; + public uint atsm_vobu_admap; + public uint ats_c_adt; + public uint ats_vobu_admap; + public ATSAudioFormat[] ats_audio_format; + public ushort[,] ats_downmix_matrix; + } + + public class ATSIHeader + { + internal ATSIHeader(FrameReader fr) + { + titles = new List(); + aobs = new List(); + ats_title_idx = new List(); + + var frMat = new FrameReader(fr, fr.Length); + mat = new ATSIMAT(frMat); + + //if (mat.atsm_vobs == 0) + // dvdaTitlesetType = DVDTitlesetAudio; + //else + // dvdaTitlesetType = DVDTitlesetVideo; + // aobs_last_sector = mat.ats_last_sector - 2 * (mat.atsi_last_sector + 1); + + fr.skip(2048); + nr_of_titles = fr.read_ushort(); + padding = fr.read_ushort(); + last_byte = fr.read_uint(); + for (int i = 0; i < nr_of_titles; i++) + { + // ?? Unknown - e.g. 0x8100 for first title, 0x8200 for second etc etc + fr.skip(2); + // ??unknown (e.g. 0x0000 or 0x0100) + fr.skip(2); + // Byte offset to record in following table (relative to the start of this sector) + ats_title_idx.Add(fr.read_uint()); + } + } + + ATSIMAT mat; + + // audio_pgcit_t + // Number of titles in the ATS + public ushort nr_of_titles; + // Padding (zero) + public ushort padding; + // Address of last byte in this table + public uint last_byte; + + public List ats_title_idx; + public List titles; + public List aobs; + } + + public static class DVDA + { + public static SpeakerConfig[] grp1_ch_table = + { + SpeakerConfig.DVDAUDIO_GR1_0, + SpeakerConfig.DVDAUDIO_GR1_1, + SpeakerConfig.DVDAUDIO_GR1_2, + SpeakerConfig.DVDAUDIO_GR1_3, + SpeakerConfig.DVDAUDIO_GR1_4, + SpeakerConfig.DVDAUDIO_GR1_5, + SpeakerConfig.DVDAUDIO_GR1_6, + SpeakerConfig.DVDAUDIO_GR1_7, + SpeakerConfig.DVDAUDIO_GR1_8, + SpeakerConfig.DVDAUDIO_GR1_9, + SpeakerConfig.DVDAUDIO_GR1_10, + SpeakerConfig.DVDAUDIO_GR1_11, + SpeakerConfig.DVDAUDIO_GR1_12, + SpeakerConfig.DVDAUDIO_GR1_13, + SpeakerConfig.DVDAUDIO_GR1_14, + SpeakerConfig.DVDAUDIO_GR1_15, + SpeakerConfig.DVDAUDIO_GR1_16, + SpeakerConfig.DVDAUDIO_GR1_17, + SpeakerConfig.DVDAUDIO_GR1_18, + SpeakerConfig.DVDAUDIO_GR1_19, + SpeakerConfig.DVDAUDIO_GR1_20, + }; + + public static SpeakerConfig[] grp2_ch_table = + { + SpeakerConfig.DVDAUDIO_GR2_0, + SpeakerConfig.DVDAUDIO_GR2_1, + SpeakerConfig.DVDAUDIO_GR2_2, + SpeakerConfig.DVDAUDIO_GR2_3, + SpeakerConfig.DVDAUDIO_GR2_4, + SpeakerConfig.DVDAUDIO_GR2_5, + SpeakerConfig.DVDAUDIO_GR2_6, + SpeakerConfig.DVDAUDIO_GR2_7, + SpeakerConfig.DVDAUDIO_GR2_8, + SpeakerConfig.DVDAUDIO_GR2_9, + SpeakerConfig.DVDAUDIO_GR2_10, + SpeakerConfig.DVDAUDIO_GR2_11, + SpeakerConfig.DVDAUDIO_GR2_12, + SpeakerConfig.DVDAUDIO_GR2_13, + SpeakerConfig.DVDAUDIO_GR2_14, + SpeakerConfig.DVDAUDIO_GR2_15, + SpeakerConfig.DVDAUDIO_GR2_16, + SpeakerConfig.DVDAUDIO_GR2_17, + SpeakerConfig.DVDAUDIO_GR2_18, + SpeakerConfig.DVDAUDIO_GR2_19, + SpeakerConfig.DVDAUDIO_GR2_20, + }; + + //int ch_remap[][] = { + // // Canonical order: Lf Rf C LFE Ls Rs S + // /* 0 */ { 0, -1, -1, -1, -1, -1, -1}, + // /* 1 */ { 0, 1, -1, -1, -1, -1, -1}, + // /* 2 */ { 0, 1, 2, -1, -1, -1, -1}, + // /* 3 */ { 0, 1, 2, 3, -1, -1, -1}, + // /* 4 */ { 0, 1, 2, -1, -1, -1, -1}, + // /* 5 */ { 0, 1, 2, 3, -1, -1, -1}, + // /* 6 */ { 0, 1, 2, 3, 4, -1, -1}, + // /* 7 */ { 0, 1, 2, -1, -1, -1, -1}, + // /* 8 */ { 0, 1, 2, 3, -1, -1, -1}, + // /* 9 */ { 0, 1, 2, 3, 4, -1, -1}, + // /* 10 */ { 0, 1, 2, 3, -1, -1, -1}, + // /* 11 */ { 0, 1, 2, 3, 4, -1, -1}, + // /* 12 */ { 0, 1, 2, 3, 4, 5, -1}, + // /* 13 */ { 0, 1, 2, 3, -1, -1, -1}, + // /* 14 */ { 0, 1, 2, 3, 4, -1, -1}, + // /* 15 */ { 0, 1, 2, 3, -1, -1, -1}, + // /* 16 */ { 0, 1, 2, 3, 4, -1, -1}, + // /* 17 */ { 0, 1, 2, 3, 4, 5, -1}, + // /* 18 */ { 0, 1, 3, 4, 2, -1, -1}, + // /* 19 */ { 0, 1, 3, 4, 2, -1, -1}, + // /* 20 */ { 0, 1, 4, 5, 2, 3, -1}, + //}; + + public const int BLOCK_SIZE = 2048; + public const byte PCM_STREAM_ID = 0xa0; + public const byte MLP_STREAM_ID = 0xa1; + } +} diff --git a/CUETools.Codecs.MPEG/ATSI/DecoderSettings.cs b/CUETools.Codecs.MPEG/ATSI/DecoderSettings.cs new file mode 100644 index 0000000..445fe22 --- /dev/null +++ b/CUETools.Codecs.MPEG/ATSI/DecoderSettings.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using Newtonsoft.Json; + +namespace CUETools.Codecs.MPEG.ATSI +{ + [JsonObject(MemberSerialization.OptIn)] + public class DecoderSettings : IAudioDecoderSettings + { + #region IAudioDecoderSettings implementation + [Browsable(false)] + public string Extension => "ifo"; + + [Browsable(false)] + public string Name => "cuetools"; + + [Browsable(false)] + public Type DecoderType => typeof(AudioDecoder); + + [Browsable(false)] + public int Priority => 2; + + public IAudioDecoderSettings Clone() + { + return MemberwiseClone() as IAudioDecoderSettings; + } + #endregion + + public DecoderSettings() + { + this.Init(); + } + + [DefaultValue(true)] + public bool IgnoreShortItems { get; set; } + + [Browsable(false)] + public int? Stream { get; set; } + + [Browsable(false)] + public int? StreamId { get; set; } + + [Browsable(false)] + public int? Title { get; set; } + } +} diff --git a/CUETools.Codecs.MPEG/BDLPCM/AudioDecoder.cs b/CUETools.Codecs.MPEG/BDLPCM/AudioDecoder.cs index 09d1a03..720c095 100644 --- a/CUETools.Codecs.MPEG/BDLPCM/AudioDecoder.cs +++ b/CUETools.Codecs.MPEG/BDLPCM/AudioDecoder.cs @@ -8,11 +8,6 @@ namespace CUETools.Codecs.MPEG.BDLPCM { public class AudioDecoder : IAudioSource { - public unsafe AudioDecoder(string path, Stream IO, int pid) - : this(new DecoderSettings() { StreamId = pid }, path, IO) - { - } - public unsafe AudioDecoder(DecoderSettings settings, string path, Stream IO) { _path = path; @@ -38,6 +33,8 @@ namespace CUETools.Codecs.MPEG.BDLPCM _IO = null; } + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length { get diff --git a/CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs b/CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs index 6b8301c..61798ab 100644 --- a/CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs +++ b/CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs @@ -5,19 +5,15 @@ using System.IO; namespace CUETools.Codecs.MPEG.MPLS { - public class AudioDecoder : IAudioSource + public class AudioDecoder : IAudioSource, IAudioTitleSet { - public unsafe AudioDecoder(string path, Stream IO, ushort pid) - : this(new DecoderSettings() { StreamId = pid }, path, IO) - { - } - public unsafe AudioDecoder(DecoderSettings settings, string path, Stream IO) { m_settings = settings; _path = path; _IO = IO != null ? IO : new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000); int length = (int)_IO.Length; + if (length > 0x100000) throw new Exception("File too big"); contents = new byte[length]; if (_IO.Read(contents, 0, length) != length) throw new Exception(""); fixed (byte* ptr = &contents[0]) @@ -33,7 +29,7 @@ namespace CUETools.Codecs.MPEG.MPLS void openEntries() { - readers = new List(); + readers = new List(); var pids = new List(); foreach (var item in hdr_m.play_item) foreach (var audio in item.audio) @@ -67,7 +63,8 @@ namespace CUETools.Codecs.MPEG.MPLS var m2ts = System.IO.Path.Combine( System.IO.Path.Combine(parent.FullName, "STREAM"), item.clip_id + ".m2ts"); - var entry = new BDLPCM.AudioDecoder(m2ts, null, chosenPid); + var settings = new BDLPCM.DecoderSettings() { StreamId = chosenPid }; + var entry = settings.Open(m2ts); readers.Add(entry); break; } @@ -387,15 +384,30 @@ namespace CUETools.Codecs.MPEG.MPLS } } - public List Chapters + public List AudioTitles + { + get + { + var titles = new List(); + foreach (var item in hdr_m.play_item) + foreach (var audio in item.audio) + { + //if (audio.coding_type != 0x80 /* LPCM */) continue; + titles.Add(new AudioTitle(this, audio.pid)); + } + return titles; + } + } + + public List Chapters { get { //settings.IgnoreShortItems - var res = new List(); + var res = new List(); if (hdr_m.play_mark.Count < 1) return res; if (hdr_m.play_item.Count < 1) return res; - res.Add(0); + res.Add(TimeSpan.Zero); for (int i = 0; i < hdr_m.mark_count; i++) { ushort mark_item = hdr_m.play_mark[i].play_item_ref; @@ -410,7 +422,7 @@ namespace CUETools.Codecs.MPEG.MPLS if (m_settings.IgnoreShortItems && item_duration < shortItemDuration) continue; item_offset += item_duration; } - res.Add(hdr_m.play_mark[i].time - item_in_time + item_offset); + res.Add(TimeSpan.FromSeconds((hdr_m.play_mark[i].time - item_in_time + item_offset) / 45000.0)); } uint end_offset = 0; for (int j = 0; j < hdr_m.play_item.Count; j++) @@ -420,9 +432,9 @@ namespace CUETools.Codecs.MPEG.MPLS if (m_settings.IgnoreShortItems && item_duration < shortItemDuration) continue; end_offset += hdr_m.play_item[j].out_time - hdr_m.play_item[j].in_time; } - res.Add(end_offset); - while (res.Count > 1 && res[1] - res[0] < 45000) res.RemoveAt(1); - while (res.Count > 1 && res[res.Count - 1] - res[res.Count - 2] < 45000) res.RemoveAt(res.Count - 2); + res.Add(TimeSpan.FromSeconds(end_offset / 45000.0)); + while (res.Count > 1 && res[1] - res[0] < TimeSpan.FromSeconds(1.0)) res.RemoveAt(1); + while (res.Count > 1 && res[res.Count - 1] - res[res.Count - 2] < TimeSpan.FromSeconds(1.0)) res.RemoveAt(res.Count - 2); return res; } } @@ -434,12 +446,64 @@ namespace CUETools.Codecs.MPEG.MPLS byte[] contents; AudioPCMConfig pcm; - List readers; - BDLPCM.AudioDecoder currentReader; + List readers; + IAudioSource currentReader; MPLSHeader hdr_m; DecoderSettings m_settings; } + public class AudioTitle : IAudioTitle + { + public AudioTitle(AudioDecoder source, int pid) + { + this.source = source; + this.pid = pid; + } + + public List Chapters => source.Chapters; + public AudioPCMConfig PCM + { + get + { + var s = FirstStream; + int channelCount = s.format == 1 ? 1 : s.format == 3 ? 2 : s.format == 6 ? 5 : 0; + int sampleRate = s.rate == 1 ? 48000 : s.rate == 4 ? 96000 : s.rate == 5 ? 192000 : s.rate == 12 ? 192000 : s.rate == 14 ? 96000 : 0; + int bitsPerSample = 0; + return new AudioPCMConfig(bitsPerSample, channelCount, sampleRate); + } + } + public string Codec + { + get + { + var s = FirstStream; + return s != null ? s.CodecString : "?"; + } + } + public string Language + { + get + { + var s = FirstStream; + return s != null ? s.LanguageString : "?"; + } + } + public int StreamId => pid; + + MPLSStream FirstStream + { + get + { + MPLSStream result = null; + source.MPLSHeader.play_item.ForEach(i => i.audio.FindAll(a => a.pid == pid).ForEach(x => result = x)); + return result; + } + } + + AudioDecoder source; + int pid; + } + public struct MPLSPlaylistMark { public byte mark_id; @@ -450,7 +514,7 @@ namespace CUETools.Codecs.MPEG.MPLS public uint duration; } - public struct MPLSStream + public class MPLSStream { public byte stream_type; public byte coding_type; diff --git a/CUETools.Codecs.TTA/CUETools.Codecs.TTA.cpp b/CUETools.Codecs.TTA/CUETools.Codecs.TTA.cpp index 3314117..25e2f62 100644 --- a/CUETools.Codecs.TTA/CUETools.Codecs.TTA.cpp +++ b/CUETools.Codecs.TTA/CUETools.Codecs.TTA.cpp @@ -127,6 +127,11 @@ namespace TTA { } } + virtual property TimeSpan Duration { + TimeSpan get() { + return Length < 0 ? TimeSpan::Zero : TimeSpan::FromSeconds((double)Length / PCM->SampleRate); + } + } virtual property Int64 Length { Int64 get() { return _sampleCount; diff --git a/CUETools.Codecs.WMA/AudioDecoder.cs b/CUETools.Codecs.WMA/AudioDecoder.cs index 7ce86e6..6482de1 100644 --- a/CUETools.Codecs.WMA/AudioDecoder.cs +++ b/CUETools.Codecs.WMA/AudioDecoder.cs @@ -252,6 +252,8 @@ namespace CUETools.Codecs.WMA m_syncReader = null; } + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length { get diff --git a/CUETools.Codecs.ffmpeg/AudioDecoder.cs b/CUETools.Codecs.ffmpeg/AudioDecoder.cs index 49d7055..0b6c868 100644 --- a/CUETools.Codecs.ffmpeg/AudioDecoder.cs +++ b/CUETools.Codecs.ffmpeg/AudioDecoder.cs @@ -191,7 +191,7 @@ namespace CUETools.Codecs.ffmpegdll //if (stream->duration > 0) // _sampleCount = stream->duration; //else - _sampleCount = -1; + _sampleCount = -1; int bps = stream->codecpar->bits_per_raw_sample != 0 ? stream->codecpar->bits_per_raw_sample : @@ -279,6 +279,21 @@ namespace CUETools.Codecs.ffmpegdll public string Path => _path; + public TimeSpan Duration + { + get + { + // Sadly, duration is unreliable for most codecs. + if (stream->codecpar->codec_id == AVCodecID.AV_CODEC_ID_MLP) + return TimeSpan.Zero; + if (stream->duration > 0) + return TimeSpan.FromSeconds((double)stream->duration / stream->codecpar->sample_rate); + if (fmt_ctx->duration > 0) + return TimeSpan.FromSeconds((double)fmt_ctx->duration / ffmpeg.AV_TIME_BASE); + return TimeSpan.Zero; + } + } + public long Length => _sampleCount; public long Position diff --git a/CUETools.Codecs.ffmpeg/DecoderSettings.cs b/CUETools.Codecs.ffmpeg/DecoderSettings.cs index 23b0b6b..0bb43ad 100644 --- a/CUETools.Codecs.ffmpeg/DecoderSettings.cs +++ b/CUETools.Codecs.ffmpeg/DecoderSettings.cs @@ -13,7 +13,7 @@ namespace CUETools.Codecs.ffmpegdll #region IAudioDecoderSettings implementation [Browsable(false)] - public string Name => "ffmpeg"; + public string Name => "ffmpeg.dll"; [Browsable(false)] public Type DecoderType => typeof(AudioDecoder); diff --git a/CUETools.Codecs.libFLAC/Reader.cs b/CUETools.Codecs.libFLAC/Reader.cs index e566eda..02596d1 100644 --- a/CUETools.Codecs.libFLAC/Reader.cs +++ b/CUETools.Codecs.libFLAC/Reader.cs @@ -20,7 +20,7 @@ namespace CUETools.Codecs.libFLAC public string Name => "libFLAC"; [Browsable(false)] - public Type DecoderType => typeof(Reader); + public Type DecoderType => typeof(AudioDecoder); [Browsable(false)] public int Priority => 1; @@ -37,9 +37,9 @@ namespace CUETools.Codecs.libFLAC } } - public unsafe class Reader : IAudioSource + public unsafe class AudioDecoder : IAudioSource { - public Reader(DecoderSettings settings, string path, Stream IO) + public AudioDecoder(DecoderSettings settings, string path, Stream IO) { m_settings = settings; @@ -306,6 +306,8 @@ namespace CUETools.Codecs.libFLAC public string Path => m_path; + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length => m_sampleCount; private int SamplesInBuffer => m_bufferLength - m_bufferOffset; diff --git a/CUETools.Codecs.libwavpack/Reader.cs b/CUETools.Codecs.libwavpack/Reader.cs index 3347f39..b29342f 100644 --- a/CUETools.Codecs.libwavpack/Reader.cs +++ b/CUETools.Codecs.libwavpack/Reader.cs @@ -99,6 +99,8 @@ namespace CUETools.Codecs.libwavpack public string Path => _path; + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length => _sampleCount; public long Position diff --git a/CUETools.Codecs/AudioDecoderSettings.cs b/CUETools.Codecs/AudioDecoderSettings.cs index 8b9189b..1f34ebb 100644 --- a/CUETools.Codecs/AudioDecoderSettings.cs +++ b/CUETools.Codecs/AudioDecoderSettings.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Xml.Serialization; using System.Text; using Newtonsoft.Json; +using System.IO; namespace CUETools.Codecs { @@ -44,5 +45,10 @@ namespace CUETools.Codecs foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(settings)) property.ResetValue(settings); } + + public static IAudioSource Open(this IAudioDecoderSettings settings, string path, Stream IO = null) + { + return Activator.CreateInstance(settings.DecoderType, settings, path, IO) as IAudioSource; + } } } diff --git a/CUETools.Codecs/AudioPCMConfig.cs b/CUETools.Codecs/AudioPCMConfig.cs index 0b736f1..b574670 100644 --- a/CUETools.Codecs/AudioPCMConfig.cs +++ b/CUETools.Codecs/AudioPCMConfig.cs @@ -32,7 +32,51 @@ KSAUDIO_SPEAKER_5POINT1 = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT), KSAUDIO_SPEAKER_5POINT1_SURROUND = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT), KSAUDIO_SPEAKER_7POINT1 = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER), - KSAUDIO_SPEAKER_7POINT1_SURROUND = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) + KSAUDIO_SPEAKER_7POINT1_SURROUND = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT), + + DVDAUDIO_GR1_0 = SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_1 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_2 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_3 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_4 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_5 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_6 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_7 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_8 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_9 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_10 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_11 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_12 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_13 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_14 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_15 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_16 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_17 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_18 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR1_19 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR1_20 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + + DVDAUDIO_GR2_0 = 0, + DVDAUDIO_GR2_1 = 0, + DVDAUDIO_GR2_2 = SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_3 = SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_4 = SPEAKER_LOW_FREQUENCY, + DVDAUDIO_GR2_5 = SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_6 = SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_7 = SPEAKER_FRONT_CENTER, + DVDAUDIO_GR2_8 = SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_9 = SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_10 = SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY, + DVDAUDIO_GR2_11 = SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_12 = SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_13 = SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_14 = SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_15 = SPEAKER_LOW_FREQUENCY, + DVDAUDIO_GR2_16 = SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_17 = SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_18 = SPEAKER_LOW_FREQUENCY, + DVDAUDIO_GR2_19 = SPEAKER_FRONT_CENTER, + DVDAUDIO_GR2_20 = SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY, } private int _bitsPerSample; @@ -46,6 +90,17 @@ public int BlockAlign { get { return _channelCount * ((_bitsPerSample + 7) / 8); } } public SpeakerConfig ChannelMask { get { return _channelMask; } } public bool IsRedBook { get { return _bitsPerSample == 16 && _channelCount == 2 && _sampleRate == 44100; } } + public static int ChannelsInMask(SpeakerConfig mask) + { + int count = 0; + while (mask != 0) + { + count++; + mask &= (mask - 1); + } + return count; + } + public static SpeakerConfig GetDefaultChannelMask(int channelCount) { switch (channelCount) diff --git a/CUETools.Codecs/AudioPipe.cs b/CUETools.Codecs/AudioPipe.cs index 1545b9d..46fa580 100644 --- a/CUETools.Codecs/AudioPipe.cs +++ b/CUETools.Codecs/AudioPipe.cs @@ -54,6 +54,8 @@ namespace CUETools.Codecs } } + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length { get diff --git a/CUETools.Codecs/CUEToolsCodecsConfig.cs b/CUETools.Codecs/CUEToolsCodecsConfig.cs index e2e883d..c237c15 100644 --- a/CUETools.Codecs/CUEToolsCodecsConfig.cs +++ b/CUETools.Codecs/CUEToolsCodecsConfig.cs @@ -52,18 +52,18 @@ namespace CUETools.Codecs if (Type.GetType("Mono.Runtime", false) == null) { - encoders.Add(new CommandLine.EncoderSettings("flake", "flac", true, "0 1 2 3 4 5 6 7 8 9 10 11 12", "8", "flake.exe", "-%M - -o %O -p %P")); - encoders.Add(new CommandLine.EncoderSettings("takc", "tak", true, "0 1 2 2e 2m 3 3e 3m 4 4e 4m", "2", "takc.exe", "-e -p%M -overwrite - %O")); - encoders.Add(new CommandLine.EncoderSettings("ffmpeg alac", "m4a", true, "", "", "ffmpeg.exe", "-i - -f ipod -acodec alac -y %O")); - encoders.Add(new CommandLine.EncoderSettings("VBR (lame.exe)", "mp3", false, "V9 V8 V7 V6 V5 V4 V3 V2 V1 V0", "V2", "lame.exe", "--vbr-new -%M - %O")); - encoders.Add(new CommandLine.EncoderSettings("CBR (lame.exe)", "mp3", false, "96 128 192 256 320", "256", "lame.exe", "-m s -q 0 -b %M --noreplaygain - %O")); - encoders.Add(new CommandLine.EncoderSettings("oggenc", "ogg", false, "-1 -0.5 0 0.5 1 1.5 2 2.5 3 3.5 4 4.5 5 5.5 6 6.5 7 7.5 8", "3", "oggenc.exe", "-q %M - -o %O")); - encoders.Add(new CommandLine.EncoderSettings("opusenc", "opus", false, "6 16 32 48 64 96 128 192 256", "128", "opusenc.exe", "--bitrate %M - %O")); - encoders.Add(new CommandLine.EncoderSettings("nero aac", "m4a", false, "0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9", "0.4", "neroAacEnc.exe", "-q %M -if - -of %O")); - encoders.Add(new CommandLine.EncoderSettings("qaac tvbr", "m4a", false, "10 20 30 40 50 60 70 80 90 100 110 127", "80", "qaac.exe", "-s -V %M -q 2 - -o %O")); + encoders.Add(new CommandLine.EncoderSettings("flake.exe", "flac", true, "0 1 2 3 4 5 6 7 8 9 10 11 12", "8", "flake.exe", "-%M - -o %O -p %P")); + encoders.Add(new CommandLine.EncoderSettings("takc.exe", "tak", true, "0 1 2 2e 2m 3 3e 3m 4 4e 4m", "2", "takc.exe", "-e -p%M -overwrite - %O")); + encoders.Add(new CommandLine.EncoderSettings("ffmpeg.exe", "m4a", true, "", "", "ffmpeg.exe", "-i - -f ipod -acodec alac -y %O")); + encoders.Add(new CommandLine.EncoderSettings("lame.exe (VBR)", "mp3", false, "V9 V8 V7 V6 V5 V4 V3 V2 V1 V0", "V2", "lame.exe", "--vbr-new -%M - %O")); + encoders.Add(new CommandLine.EncoderSettings("lame.exe (CBR)", "mp3", false, "96 128 192 256 320", "256", "lame.exe", "-m s -q 0 -b %M --noreplaygain - %O")); + encoders.Add(new CommandLine.EncoderSettings("oggenc.exe", "ogg", false, "-1 -0.5 0 0.5 1 1.5 2 2.5 3 3.5 4 4.5 5 5.5 6 6.5 7 7.5 8", "3", "oggenc.exe", "-q %M - -o %O")); + encoders.Add(new CommandLine.EncoderSettings("opusenc.exe", "opus", false, "6 16 32 48 64 96 128 192 256", "128", "opusenc.exe", "--bitrate %M - %O")); + encoders.Add(new CommandLine.EncoderSettings("neroAacEnc.exe", "m4a", false, "0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9", "0.4", "neroAacEnc.exe", "-q %M -if - -of %O")); + encoders.Add(new CommandLine.EncoderSettings("qaac.exe (tvbr)", "m4a", false, "10 20 30 40 50 60 70 80 90 100 110 127", "80", "qaac.exe", "-s -V %M -q 2 - -o %O")); - decoders.Add(new CommandLine.DecoderSettings("takc", "tak", "takc.exe", "-d %I -")); - decoders.Add(new CommandLine.DecoderSettings("ffmpeg alac", "m4a", "ffmpeg.exe", "-v 0 -i %I -f wav -")); + decoders.Add(new CommandLine.DecoderSettings("takc.exe", "tak", "takc.exe", "-d %I -")); + decoders.Add(new CommandLine.DecoderSettings("ffmpeg.exe", "m4a", "ffmpeg.exe", "-v 0 -i %I -f wav -")); } else { diff --git a/CUETools.Codecs/CommandLine/AudioDecoder.cs b/CUETools.Codecs/CommandLine/AudioDecoder.cs index 40e63ca..91ea5c1 100644 --- a/CUETools.Codecs/CommandLine/AudioDecoder.cs +++ b/CUETools.Codecs/CommandLine/AudioDecoder.cs @@ -27,6 +27,15 @@ namespace CUETools.Codecs.CommandLine } } + public TimeSpan Duration + { + get + { + Initialize(); + return rdr.Duration; + } + } + public long Length { get diff --git a/CUETools.Codecs/IAudioSource.cs b/CUETools.Codecs/IAudioSource.cs index 3dbb2f0..50ecd40 100644 --- a/CUETools.Codecs/IAudioSource.cs +++ b/CUETools.Codecs/IAudioSource.cs @@ -1,4 +1,7 @@ -namespace CUETools.Codecs +using System; +using System.Collections.Generic; + +namespace CUETools.Codecs { public interface IAudioSource { @@ -7,11 +10,73 @@ AudioPCMConfig PCM { get; } string Path { get; } - long Length { get; } + TimeSpan Duration { get; } + long Length { get; } long Position { get; set; } long Remaining { get; } int Read(AudioBuffer buffer, int maxLength); void Close(); } + + public interface IAudioTitle + { + List Chapters { get; } + AudioPCMConfig PCM { get; } + string Codec { get; } + string Language { get; } + int StreamId { get; } + //IAudioSource Open { get; } + } + + public interface IAudioTitleSet + { + List AudioTitles { get; } + } + + public static class IAudioTitleExtensions + { + public static TimeSpan GetDuration(this IAudioTitle title) + { + var chapters = title.Chapters; + return chapters[chapters.Count - 1]; + } + + + public static string GetRateString(this IAudioTitle title) + { + var sr = title.PCM.SampleRate; + if (sr % 1000 == 0) return $"{sr / 1000}KHz"; + if (sr % 100 == 0) return $"{sr / 100}.{(sr / 100) % 10}KHz"; + return $"{sr}Hz"; + } + + public static string GetFormatString(this IAudioTitle title) + { + switch (title.PCM.ChannelCount) + { + case 1: return "mono"; + case 2: return "stereo"; + default: return "multi-channel"; + } + } + } + + public class SingleAudioTitle : IAudioTitle + { + public SingleAudioTitle(IAudioSource source) { this.source = source; } + public List Chapters => new List { TimeSpan.Zero, source.Duration }; + public AudioPCMConfig PCM => source.PCM; + public string Codec => source.Settings.Extension; + public string Language => ""; + public int StreamId => 0; + IAudioSource source; + } + + public class SingleAudioTitleSet : IAudioTitleSet + { + public SingleAudioTitleSet(IAudioSource source) { this.source = source; } + public List AudioTitles => new List { new SingleAudioTitle(source) }; + IAudioSource source; + } } diff --git a/CUETools.Codecs/NULL/AudioDecoder.cs b/CUETools.Codecs/NULL/AudioDecoder.cs index b4b78ff..651db6a 100644 --- a/CUETools.Codecs/NULL/AudioDecoder.cs +++ b/CUETools.Codecs/NULL/AudioDecoder.cs @@ -1,4 +1,6 @@ -namespace CUETools.Codecs.NULL +using System; + +namespace CUETools.Codecs.NULL { public class AudioDecoder : IAudioSource { @@ -8,6 +10,8 @@ public IAudioDecoderSettings Settings => null; + public TimeSpan Duration => TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length { get { return _sampleCount; } diff --git a/CUETools.Codecs/WAV/AudioDecoder.cs b/CUETools.Codecs/WAV/AudioDecoder.cs index d3df5e7..3cd32d0 100644 --- a/CUETools.Codecs/WAV/AudioDecoder.cs +++ b/CUETools.Codecs/WAV/AudioDecoder.cs @@ -55,6 +55,8 @@ namespace CUETools.Codecs.WAV } } + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length { get diff --git a/CUETools.Converter/Program.cs b/CUETools.Converter/Program.cs index e46f914..ef0dc0a 100644 --- a/CUETools.Converter/Program.cs +++ b/CUETools.Converter/Program.cs @@ -162,7 +162,7 @@ namespace CUETools.Converter AudioBuffer buff = new AudioBuffer(audioSource, 0x10000); Console.Error.WriteLine("Filename : {0}", sourceFile); - Console.Error.WriteLine("File Info : {0}kHz; {1} channel; {2} bit; {3}", audioSource.PCM.SampleRate, audioSource.PCM.ChannelCount, audioSource.PCM.BitsPerSample, TimeSpan.FromSeconds(audioSource.Length * 1.0 / audioSource.PCM.SampleRate)); + Console.Error.WriteLine("File Info : {0}kHz; {1} channel; {2} bit; {3}", audioSource.PCM.SampleRate, audioSource.PCM.ChannelCount, audioSource.PCM.BitsPerSample, audioSource.Duration); CUEToolsFormat fmt; if (encoderFormat == null) @@ -226,15 +226,16 @@ namespace CUETools.Converter TimeSpan elapsed = DateTime.Now - start; if ((elapsed - lastPrint).TotalMilliseconds > 60) { - long length = audioSource.Length; - if (length < 0 && sourceInfo != null) length = (long)(sourceInfo.Properties.Duration.TotalMilliseconds * audioSource.PCM.SampleRate / 1000); - if (length < audioSource.Position) length = audioSource.Position; - if (length < 1) length = 1; + var duration = audioSource.Duration; + var position = TimeSpan.FromSeconds((double)audioSource.Position / audioSource.PCM.SampleRate); + if (duration == TimeSpan.Zero && sourceInfo != null) duration = sourceInfo.Properties.Duration; + if (duration < position) duration = position; + if (duration < TimeSpan.FromSeconds(1)) duration = TimeSpan.FromSeconds(1); Console.Error.Write("\rProgress : {0:00}%; {1:0.00}x; {2}/{3}", - 100.0 * audioSource.Position / length, - audioSource.Position / elapsed.TotalSeconds / audioSource.PCM.SampleRate, + 100.0 * position.TotalSeconds / duration.TotalSeconds, + position.TotalSeconds / elapsed.TotalSeconds, elapsed, - TimeSpan.FromMilliseconds(elapsed.TotalMilliseconds / audioSource.Position * length) + TimeSpan.FromSeconds(elapsed.TotalSeconds / position.TotalSeconds * duration.TotalSeconds) ); lastPrint = elapsed; } diff --git a/CUETools.DSP.Mixer/MixingSource.cs b/CUETools.DSP.Mixer/MixingSource.cs index 4ed6f7d..4ed63fc 100644 --- a/CUETools.DSP.Mixer/MixingSource.cs +++ b/CUETools.DSP.Mixer/MixingSource.cs @@ -29,6 +29,11 @@ namespace CUETools.DSP.Mixer set { throw new NotSupportedException(); } } + public TimeSpan Duration + { + get { throw new NotSupportedException(); } + } + public long Length { get { throw new NotSupportedException(); } diff --git a/CUETools.Processor/CUESheetAudio.cs b/CUETools.Processor/CUESheetAudio.cs index 2f9af32..f68ea23 100644 --- a/CUETools.Processor/CUESheetAudio.cs +++ b/CUETools.Processor/CUESheetAudio.cs @@ -13,6 +13,8 @@ namespace CUETools.Processor public IAudioDecoderSettings Settings => null; + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length { get { return _sampleLen; } diff --git a/CUETools.Ripper.SCSI/SCSIDrive.cs b/CUETools.Ripper.SCSI/SCSIDrive.cs index 3554829..671cc4b 100644 --- a/CUETools.Ripper.SCSI/SCSIDrive.cs +++ b/CUETools.Ripper.SCSI/SCSIDrive.cs @@ -1149,7 +1149,9 @@ namespace CUETools.Ripper.SCSI return buff.Length; } - public long Length + public TimeSpan Duration => TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + + public long Length { get { diff --git a/CUETools.TestHelpers/NoiseAndErrorsGenerator.cs b/CUETools.TestHelpers/NoiseAndErrorsGenerator.cs index da89c79..a4cab6e 100644 --- a/CUETools.TestHelpers/NoiseAndErrorsGenerator.cs +++ b/CUETools.TestHelpers/NoiseAndErrorsGenerator.cs @@ -56,7 +56,9 @@ namespace CUETools.TestHelpers { } - public long Length + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + + public long Length { get { diff --git a/CUETools.eac3to/Program.cs b/CUETools.eac3to/Program.cs index 9f34b5d..7d7f76b 100644 --- a/CUETools.eac3to/Program.cs +++ b/CUETools.eac3to/Program.cs @@ -4,6 +4,7 @@ using CUETools.CTDB; using CUETools.Processor; using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Text; @@ -49,6 +50,7 @@ namespace CUETools.eac3to string encoderName = null; string encoderFormat = null; AudioEncoderType audioEncoderType = AudioEncoderType.NoAudio; + var decoderOptions = new Dictionary(); bool queryMeta = false; for (int arg = 0; arg < args.Length; arg++) @@ -61,14 +63,20 @@ namespace CUETools.eac3to encoderFormat = args[arg]; else if ((args[arg] == "-p" || args[arg] == "--padding") && ++arg < args.Length) ok = int.TryParse(args[arg], out padding); - else if ((args[arg] == "-m" || args[arg] == "--mode") && ++arg < args.Length) - encoderMode = args[arg]; + else if ((args[arg] == "-m" || args[arg] == "--mode") && arg + 1 < args.Length) + encoderMode = args[++arg]; else if (args[arg] == "--lossy") audioEncoderType = AudioEncoderType.Lossy; else if (args[arg] == "--lossless") audioEncoderType = AudioEncoderType.Lossless; else if (args[arg] == "--ctdb") queryMeta = true; + else if (args[arg] == "--decoder-option" && arg + 2 < args.Length) + { + var optionName = args[++arg]; + var optionValue = args[++arg]; + decoderOptions.Add(optionName, optionValue); + } else if ((args[arg][0] != '-' || args[arg] == "-") && sourceFile == null) sourceFile = args[arg]; else if ((args[arg][0] != '-' || args[arg] == "-") && sourceFile != null && destFile == null) @@ -122,11 +130,11 @@ namespace CUETools.eac3to #endif { IAudioSource audioSource = null; + IAudioTitleSet audioContainer = null; IAudioDest audioDest = null; var videos = new List(); - var audios = new List(); - List chapters; - TimeSpan duration; + List audios = null; + List chapters = null; TagLib.UserDefined.AdditionalFileTypes.Config = config; #if !DEBUG @@ -135,19 +143,43 @@ namespace CUETools.eac3to { if (true) { - var mpls = new Codecs.MPEG.MPLS.AudioDecoder(new Codecs.MPEG.MPLS.DecoderSettings(), sourceFile, null); - audioSource = mpls; + IAudioDecoderSettings decoderSettings = null; + if (Path.GetExtension(sourceFile) == ".mpls") + { + decoderSettings = new Codecs.MPEG.MPLS.DecoderSettings(); + } else + { + decoderSettings = new Codecs.MPEG.ATSI.DecoderSettings(); + } + foreach (var decOpt in decoderOptions) + { + var property = TypeDescriptor.GetProperties(decoderSettings).Find(decOpt.Key, true); + if (property == null) + throw new Exception($"{decoderSettings.Name} {decoderSettings.Extension} decoder settings object (of type {decoderSettings.GetType().FullName}) doesn't have a property named {decOpt.Key}."); + property.SetValue(decoderSettings, + TypeDescriptor.GetConverter(property.PropertyType).ConvertFromString(decOpt.Value)); + } + audioSource = decoderSettings.Open(sourceFile); + audioContainer = audioSource as IAudioTitleSet; + if (audioContainer == null) audioContainer = new SingleAudioTitleSet(audioSource); Console.ForegroundColor = ConsoleColor.White; int frameRate = 0; bool interlaced = false; - chapters = mpls.Chapters; - mpls.MPLSHeader.play_item.ForEach(i => i.video.ForEach(v => { if (!videos.Exists(v1 => v1.pid == v.pid)) videos.Add(v); })); - mpls.MPLSHeader.play_item.ForEach(i => i.audio.ForEach(v => { if (!audios.Exists(v1 => v1.pid == v.pid)) audios.Add(v); })); + audios = audioContainer.AudioTitles; + audios.ForEach(t => chapters = t.Chapters); + if (audioSource is Codecs.MPEG.MPLS.AudioDecoder) + { + var mpls = audioSource as Codecs.MPEG.MPLS.AudioDecoder; + mpls.MPLSHeader.play_item.ForEach(i => i.video.ForEach(v => { if (!videos.Exists(v1 => v1.pid == v.pid)) videos.Add(v); })); + } videos.ForEach(v => { frameRate = v.FrameRate; interlaced = v.Interlaced; }); - Console.Error.WriteLine("M2TS, {0} video track{1}, {2} audio track{3}, {4}, {5}{6}", - videos.Count, videos.Count > 1 ? "s" : "", - audios.Count, audios.Count > 1 ? "s" : "", - CDImageLayout.TimeToString(mpls.Duration, "{0:0}:{1:00}:{2:00}"), frameRate * (interlaced ? 2 : 1), interlaced ? "i" : "p"); + Console.Error.Write($@"M2TS, { + videos.Count} video track{(videos.Count != 1 ? "s" : "")}, { + audios.Count} audio track{(audios.Count != 1 ? "s" : "")}, { + CDImageLayout.TimeToString(audios[0].GetDuration(), "{0:0}:{1:00}:{2:00}")}, { + (frameRate * (interlaced ? 2 : 1))}{ + (interlaced ? "i" : "p")}"); + Console.Error.WriteLine(); //foreach (var item in mpls.MPLSHeader.play_item) //Console.Error.WriteLine("{0}.m2ts", item.clip_id); { @@ -175,11 +207,9 @@ namespace CUETools.eac3to Console.Error.Write(id++); Console.Error.Write(": "); Console.ForegroundColor = ConsoleColor.Gray; - Console.Error.WriteLine("{0}, {1}, {2}, {3}", audio.CodecString, audio.LanguageString, audio.FormatString, audio.RateString); + Console.Error.WriteLine("{0}, {1}, {2}, {3}", audio.Codec, audio.Language, audio.GetFormatString(), audio.GetRateString()); } } - - duration = mpls.Duration; } if (destFile == null) @@ -187,7 +217,7 @@ namespace CUETools.eac3to string strtoc = ""; for (int i = 0; i < chapters.Count; i++) - strtoc += string.Format(" {0}", chapters[i] / 600); + strtoc += string.Format(" {0}", (int)Math.Round((chapters[i].TotalSeconds * 75))); strtoc = strtoc.Substring(1); CDImageLayout toc = new CDImageLayout(strtoc); CTDBResponseMeta meta = null; @@ -238,9 +268,9 @@ namespace CUETools.eac3to for (int i = 0; i < chapters.Count - 1; i++) { sw.WriteLine("CHAPTER{0:00}={1}", i + 1, - CDImageLayout.TimeToString(TimeSpan.FromSeconds(chapters[i] / 45000.0))); - if (meta != null && meta.track.Length >= toc[i+1].Number) - sw.WriteLine("CHAPTER{0:00}NAME={1}", i + 1, meta.track[(int)toc[i+1].Number - 1].name); + CDImageLayout.TimeToString(chapters[i])); + if (meta != null && meta.track.Length >= toc[i + 1].Number) + sw.WriteLine("CHAPTER{0:00}NAME={1}", i + 1, meta.track[(int)toc[i + 1].Number - 1].name); else sw.WriteLine("CHAPTER{0:00}NAME=", i + 1); } @@ -309,21 +339,21 @@ namespace CUETools.eac3to throw new Exception("Unknown encoder format: " + destFile); } + if (stream - chapterStreams <= videos.Count) + throw new Exception("Video extraction not supported."); + if (stream - chapterStreams - videos.Count > audios.Count) + throw new Exception(string.Format("The source file doesn't contain a track with the number {0}.", stream)); + int streamId = audios[stream - chapterStreams - videos.Count - 1].StreamId; if (audioSource is Codecs.MPEG.MPLS.AudioDecoder) { - if (stream - chapterStreams <= videos.Count) - throw new Exception("Video extraction not supported."); - if (stream - chapterStreams - videos.Count > audios.Count) - throw new Exception(string.Format("The source file doesn't contain a track with the number {0}.", stream)); - int pid = audios[stream - chapterStreams - videos.Count - 1].pid; - (audioSource.Settings as Codecs.MPEG.MPLS.DecoderSettings).StreamId = pid; + (audioSource.Settings as Codecs.MPEG.MPLS.DecoderSettings).StreamId = streamId; } } AudioBuffer buff = new AudioBuffer(audioSource, 0x10000); Console.Error.WriteLine("Filename : {0}", sourceFile); Console.Error.WriteLine("File Info : {0}kHz; {1} channel; {2} bit; {3}", audioSource.PCM.SampleRate, audioSource.PCM.ChannelCount, audioSource.PCM.BitsPerSample, - duration); + audioSource.Duration); CUEToolsFormat fmt; if (encoderFormat == null) @@ -397,14 +427,15 @@ namespace CUETools.eac3to TimeSpan elapsed = DateTime.Now - start; if ((elapsed - lastPrint).TotalMilliseconds > 60) { - long length = (long)(duration.TotalSeconds * audioSource.PCM.SampleRate); - if (length < audioSource.Position) length = audioSource.Position; - if (length < 1) length = 1; + var duration = audioSource.Duration; + var position = TimeSpan.FromSeconds((double)audioSource.Position / audioSource.PCM.SampleRate); + if (duration < position) duration = position; + if (duration < TimeSpan.FromSeconds(1)) duration = TimeSpan.FromSeconds(1); Console.Error.Write("\rProgress : {0:00}%; {1:0.00}x; {2}/{3}", - 100.0 * audioSource.Position / length, - audioSource.Position / elapsed.TotalSeconds / audioSource.PCM.SampleRate, + 100.0 * position.TotalSeconds / duration.TotalSeconds, + position.TotalSeconds / elapsed.TotalSeconds, elapsed, - TimeSpan.FromMilliseconds(elapsed.TotalMilliseconds / audioSource.Position * length) + TimeSpan.FromSeconds(elapsed.TotalSeconds / position.TotalSeconds * duration.TotalSeconds) ); lastPrint = elapsed; } diff --git a/CUETools.eac3ui/MainWindow.xaml.cs b/CUETools.eac3ui/MainWindow.xaml.cs index 26e1c38..d093bc2 100644 --- a/CUETools.eac3ui/MainWindow.xaml.cs +++ b/CUETools.eac3ui/MainWindow.xaml.cs @@ -125,7 +125,7 @@ namespace BluTools { string strtoc = ""; for (int i = 0; i < chapters.Count; i++) - strtoc += string.Format(" {0}", chapters[i] / 600); + strtoc += string.Format(" {0}", (int) Math.Round((chapters[i].TotalSeconds * 75))); strtoc = strtoc.Substring(1); CDImageLayout toc = new CDImageLayout(strtoc); ctdb = new CUEToolsDB(toc, null); @@ -228,10 +228,11 @@ namespace BluTools void workerExtract_DoWork(object sender, DoWorkEventArgs e) { - CUETools.Codecs.MPEG.MPLS.AudioDecoder reader = null; + IAudioSource reader = null; try { - reader = new CUETools.Codecs.MPEG.MPLS.AudioDecoder(chosenReader.Path, null, pid); + var decoderSettings = new CUETools.Codecs.MPEG.MPLS.DecoderSettings() { StreamId = pid }; + reader = decoderSettings.Open(chosenReader.Path); Directory.CreateDirectory(outputFolderPath); if (File.Exists(outputCuePath)) throw new Exception(string.Format("File \"{0}\" already exists", outputCuePath)); if (File.Exists(outputAudioPath)) throw new Exception(string.Format("File \"{0}\" already exists", outputAudioPath)); diff --git a/CUETools/CUETools.TestCodecs/FlacWriterTest.cs b/CUETools/CUETools.TestCodecs/FlacWriterTest.cs index 192a2df..de1899e 100644 --- a/CUETools/CUETools.TestCodecs/FlacWriterTest.cs +++ b/CUETools/CUETools.TestCodecs/FlacWriterTest.cs @@ -49,7 +49,7 @@ namespace CUETools.TestCodecs [TestMethod()] public void SeekTest() { - var r = new CUETools.Codecs.libFLAC.Reader(new Codecs.libFLAC.DecoderSettings(), "test.flac", null); + var r = new Codecs.libFLAC.DecoderSettings().Open("test.flac", null); var buff1 = new AudioBuffer(r, 16536); var buff2 = new AudioBuffer(r, 16536); Assert.AreEqual(0, r.Position);