DVDAudio titleset (ATSI) parser

This commit is contained in:
Grigory Chudov
2018-04-29 16:53:55 -04:00
parent deb3448a55
commit b6c3256d32
8 changed files with 1068 additions and 496 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -41,5 +41,8 @@ namespace CUETools.Codecs.MPEG.ATSI
[Browsable(false)]
public int? StreamId { get; set; }
[Browsable(false)]
public int? Title { get; set; }
}
}

View File

@@ -5,7 +5,7 @@ using System.IO;
namespace CUETools.Codecs.MPEG.MPLS
{
public class AudioDecoder : IAudioSource
public class AudioDecoder : IAudioSource, IAudioContainer
{
public unsafe AudioDecoder(DecoderSettings settings, string path, Stream IO)
{
@@ -13,6 +13,7 @@ namespace CUETools.Codecs.MPEG.MPLS
_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])
@@ -383,15 +384,30 @@ namespace CUETools.Codecs.MPEG.MPLS
}
}
public List<uint> Chapters
public List<IAudioTitle> AudioTitles
{
get
{
var titles = new List<IAudioTitle>();
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<TimeSpan> Chapters
{
get
{
//settings.IgnoreShortItems
var res = new List<uint>();
var res = new List<TimeSpan>();
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;
@@ -406,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++)
@@ -416,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;
}
}
@@ -436,6 +452,20 @@ namespace CUETools.Codecs.MPEG.MPLS
DecoderSettings m_settings;
}
public class AudioTitle : IAudioTitle
{
public AudioTitle(AudioDecoder source, int pid)
{
this.source = source;
this.pid = pid;
}
public List<TimeSpan> Chapters => source.Chapters;
AudioDecoder source;
int pid;
}
public struct MPLSPlaylistMark
{
public byte mark_id;

View File

@@ -56,4 +56,13 @@ namespace CUETools.Codecs.MPEG
savedBufferSize = 0;
}
};
public class AudioDescription
{
public int StreamId;
public string CodecString;
public string LanguageString;
public string FormatString;
public string RateString;
};
}

View File

@@ -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)

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
namespace CUETools.Codecs
{
@@ -17,4 +18,29 @@ namespace CUETools.Codecs
int Read(AudioBuffer buffer, int maxLength);
void Close();
}
public interface IAudioTitle
{
List<TimeSpan> Chapters { get; }
//IAudioSource Open { get; }
}
public interface IAudioContainer
{
List<IAudioTitle> AudioTitles { get; }
}
public class NoContainerAudioTitle : IAudioTitle
{
public NoContainerAudioTitle(IAudioSource source) { this.source = source; }
public List<TimeSpan> Chapters => new List<TimeSpan> { TimeSpan.Zero, source.Duration };
IAudioSource source;
}
public class NoContainer : IAudioContainer
{
public NoContainer(IAudioSource source) { this.source = source; }
public List<IAudioTitle> AudioTitles => new List<IAudioTitle> { new NoContainerAudioTitle(source) };
IAudioSource source;
}
}

View File

@@ -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<string, string>();
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,10 +130,11 @@ namespace CUETools.eac3to
#endif
{
IAudioSource audioSource = null;
IAudioContainer audioContainer = null;
IAudioDest audioDest = null;
var videos = new List<Codecs.MPEG.MPLS.MPLSStream>();
var audios = new List<Codecs.MPEG.MPLS.MPLSStream>();
List<uint> chapters;
var audios = new List<Codecs.MPEG.AudioDescription>();
List<TimeSpan> chapters = null;
TagLib.UserDefined.AdditionalFileTypes.Config = config;
#if !DEBUG
@@ -134,19 +143,49 @@ 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 IAudioContainer;
if (audioContainer == null) audioContainer = new NoContainer(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); }));
audioContainer.AudioTitles.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); }));
mpls.MPLSHeader.play_item.ForEach(i => i.audio.ForEach(v => { if (!audios.Exists(v1 => v1.StreamId == v.pid)) audios.Add(new Codecs.MPEG.AudioDescription() { StreamId = v.pid, CodecString = v.CodecString, LanguageString = v.LanguageString, FormatString = v.FormatString, RateString = v.RateString }); }));
}
else
if (audioSource is Codecs.MPEG.ATSI.AudioDecoder)
{
var atsi = audioSource as Codecs.MPEG.ATSI.AudioDecoder;
atsi.ATSIHeader.titles.ForEach(t => audios.Add(new Codecs.MPEG.AudioDescription() { StreamId = 0/*!*/, CodecString = t.CodecString, FormatString = t.FormatString, RateString = t.RateString }));
}
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(audioSource.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(audioSource.Duration, "{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);
{
@@ -184,7 +223,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;
@@ -235,9 +274,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);
}
@@ -306,14 +345,14 @@ 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;
}
}

View File

@@ -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);