From e35aa22a6536f226561f2c4c672a01484f32c160 Mon Sep 17 00:00:00 2001 From: Grigory Chudov Date: Sat, 17 Feb 2018 20:35:34 -0500 Subject: [PATCH] CUETools.eac3to initial version --- CUETools.CDImage/CDImage.cs | 20 +- CUETools.Codecs.BDLPCM/FrameReader.cs | 8 + CUETools.Codecs.BDLPCM/MPLSReader.cs | 246 +++++++++--- CUETools.Processor/CUESheet.cs | 4 +- CUETools.Processor/CUESheetLogWriter.cs | 6 +- CUETools.eac3to/CUETools.eac3to.csproj | 114 ++++++ CUETools.eac3to/Program.cs | 443 +++++++++++++++++++++ CUETools.eac3to/Properties/AssemblyInfo.cs | 33 ++ CUETools/CUETools.sln | 17 + 9 files changed, 829 insertions(+), 62 deletions(-) create mode 100644 CUETools.eac3to/CUETools.eac3to.csproj create mode 100644 CUETools.eac3to/Program.cs create mode 100644 CUETools.eac3to/Properties/AssemblyInfo.cs diff --git a/CUETools.CDImage/CDImage.cs b/CUETools.CDImage/CDImage.cs index cf7467d..c909b73 100644 --- a/CUETools.CDImage/CDImage.cs +++ b/CUETools.CDImage/CDImage.cs @@ -563,7 +563,7 @@ namespace CUETools.CDImage return frame + (sec * 75) + (min * 60 * 75); } - public static string TimeToString(string format, uint t) + public static string TimeToString(uint t, string format = "{0:00}:{1:00}:{2:00}") { uint min, sec, frame; @@ -576,12 +576,20 @@ namespace CUETools.CDImage return String.Format(format, min, sec, frame); } - public static string TimeToString(uint t) - { - return TimeToString("{0:00}:{1:00}:{2:00}", t); - } + 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; + string _barcode; IList _tracks; int _audioTracks; int _firstAudio; diff --git a/CUETools.Codecs.BDLPCM/FrameReader.cs b/CUETools.Codecs.BDLPCM/FrameReader.cs index 695f287..935cbce 100644 --- a/CUETools.Codecs.BDLPCM/FrameReader.cs +++ b/CUETools.Codecs.BDLPCM/FrameReader.cs @@ -40,6 +40,14 @@ namespace CUETools.Codecs.BDLPCM return res; } + internal string read_string(int len) + { + var res = new byte[len]; + fixed (byte* ptr = &res[0]) + read_bytes(ptr, len); + return Encoding.UTF8.GetString(res, 0, res.Length);; + } + internal byte read_byte() { if (ptr_m + 1 > end_m) throw new IndexOutOfRangeException(); diff --git a/CUETools.Codecs.BDLPCM/MPLSReader.cs b/CUETools.Codecs.BDLPCM/MPLSReader.cs index de98662..efa5d5c 100644 --- a/CUETools.Codecs.BDLPCM/MPLSReader.cs +++ b/CUETools.Codecs.BDLPCM/MPLSReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Text; using System.IO; +using System.Globalization; namespace CUETools.Codecs.BDLPCM { @@ -123,8 +124,7 @@ namespace CUETools.Codecs.BDLPCM parentFr.skip(len); // Primary Clip identifer - var clip_id = fr.read_bytes(5); - item.clip_id = Encoding.UTF8.GetString(clip_id, 0, clip_id.Length); + item.clip_id = fr.read_string(5); // skip the redundant "M2TS" CodecIdentifier uint codecId = fr.read_uint(); @@ -218,30 +218,12 @@ namespace CUETools.Codecs.BDLPCM parentFr.skip(len); s.coding_type = fr.read_byte(); - //value_map_t codec_map[] = { - // {0x01, "MPEG-1 Video"}, - // {0x02, "MPEG-2 Video"}, - // {0x03, "MPEG-1 Audio"}, - // {0x04, "MPEG-2 Audio"}, - // {0x80, "LPCM"}, - // {0x81, "AC-3"}, - // {0x82, "DTS"}, - // {0x83, "TrueHD"}, - // {0x84, "AC-3 Plus"}, - // {0x85, "DTS-HD"}, - // {0x86, "DTS-HD Master"}, - // {0xea, "VC-1"}, - // {0x1b, "H.264"}, - // {0x90, "Presentation Graphics"}, - // {0x91, "Interactive Graphics"}, - // {0x92, "Text Subtitle"}, - // {0, NULL} - //}; if (s.coding_type == 0x01 || s.coding_type == 0x02 || s.coding_type == 0xea || s.coding_type == 0x1b) { + // Video byte fmt = fr.read_byte(); s.format = (byte)(fmt >> 4); s.rate = (byte)(fmt & 15); @@ -257,42 +239,20 @@ namespace CUETools.Codecs.BDLPCM || s.coding_type == 0x86) { // Audio - //value_map_t audio_format_map[] = { - // {0, "Reserved1"}, - // {1, "Mono"}, - // {2, "Reserved2"}, - // {3, "Stereo"}, - // {4, "Reserved3"}, - // {5, "Reserved4"}, - // {6, "Multi Channel"}, - // {12, "Combo"}, - // {0, NULL} - //}; - //value_map_t audio_rate_map[] = { - // {0, "Reserved1"}, - // {1, "48 Khz"}, - // {2, "Reserved2"}, - // {3, "Reserved3"}, - // {4, "96 Khz"}, - // {5, "192 Khz"}, - // {12, "48/192 Khz"}, - // {14, "48/96 Khz"}, - // {0, NULL} - //}; byte fmt = fr.read_byte(); s.format = (byte)(fmt >> 4); s.rate = (byte)(fmt & 15); - s.lang = fr.read_bytes(3); + s.lang = fr.read_string(3); } else if (s.coding_type == 0x90 || s.coding_type == 0x91) { - s.lang = fr.read_bytes(3); + s.lang = fr.read_string(3); } else if (s.coding_type == 0x92) { s.char_code = fr.read_byte(); - s.lang = fr.read_bytes(3); + s.lang = fr.read_string(3); } else throw new Exception("unrecognized stream type"); @@ -332,6 +292,20 @@ namespace CUETools.Codecs.BDLPCM } } + public TimeSpan Duration + { + get + { + uint totalLength = 0; + foreach (var item in hdr_m.play_item) + { + totalLength += item.out_time - item.in_time; + } + + return TimeSpan.FromSeconds(totalLength / 45000.0); + } + } + public long Remaining { get @@ -389,6 +363,14 @@ namespace CUETools.Codecs.BDLPCM return res; } + public MPLSHeader MPLSHeader + { + get + { + return hdr_m; + } + } + string _path; Stream _IO; byte[] contents; @@ -400,7 +382,7 @@ namespace CUETools.Codecs.BDLPCM BDLPCMReaderSettings settings; } - internal struct MPLSPlaylistMark + public struct MPLSPlaylistMark { public byte mark_id; public byte mark_type; @@ -410,7 +392,7 @@ namespace CUETools.Codecs.BDLPCM public uint duration; } - internal struct MPLSStream + public struct MPLSStream { public byte stream_type; public byte coding_type; @@ -420,10 +402,151 @@ namespace CUETools.Codecs.BDLPCM public byte format; public byte rate; public byte char_code; - public byte[] lang; + public string lang; + + public string FormatString + { + get + { + if (coding_type == 0x01 + || coding_type == 0x02 + || coding_type == 0xea + || coding_type == 0x1b) + switch (format) + { + case 0: return "reserved0"; + case 1: return "480i"; + case 2: return "576i"; + case 3: return "480p"; + case 4: return "1080i"; + case 5: return "720p"; + case 6: return "1080p"; + case 7: return "576p"; + default: return format.ToString(); + } + switch (format) + { + case 0: return "reserved0"; + case 1: return "mono"; + case 2: return "reserved2"; + case 3: return "stereo"; + case 4: return "reserved4"; + case 5: return "reserved5"; + case 6: return "multi-channel"; + case 12: return "combo"; + default: return format.ToString(); + } + } + } + + public int FrameRate + { + get + { + switch (rate) + { + case 1: return 23; + case 2: return 24; + case 3: return 25; + case 4: return 30; + case 6: return 50; + case 7: return 60; + default: throw new NotSupportedException(); + } + } + } + + public bool Interlaced + { + get + { + return format == 1 || format == 2 || format == 4; + } + } + + public string RateString + { + get + { + if (coding_type == 0x01 + || coding_type == 0x02 + || coding_type == 0xea + || coding_type == 0x1b) + switch (rate) + { + case 0: return "reserved0"; + case 1: return "23.976"; + case 2: return "24"; + case 3: return "25"; + case 4: return "29.97"; + case 5: return "reserved5"; + case 6: return "50"; + case 7: return "59.94"; + default: return rate.ToString(); + } + switch (rate) + { + case 0: return "reserved0"; + case 1: return "48KHz"; + case 2: return "reserved2"; + case 3: return "reserved3"; + case 4: return "96KHz"; + case 5: return "192KHz"; + case 12: return "48/192KHz"; + case 14: return "48/96KHz"; + default: return rate.ToString(); + } + } + } + + public string CodecString + { + get + { + switch (coding_type) + { + case 0x01: return "MPEG-1 Video"; + case 0x02: return "MPEG-2 Video"; + case 0x03: return "MPEG-1 Audio"; + case 0x04: return "MPEG-2 Audio"; + //case 0x80: return "LPCM"; + case 0x80: return "RAW/PCM"; + case 0x81: return "AC-3"; + case 0x82: return "DTS"; + case 0x83: return "TrueHD"; + case 0x84: return "AC-3 Plus"; + case 0x85: return "DTS-HD"; + case 0x86: return "DTS-HD Master"; + case 0xea: return "VC-1"; + case 0x1b: return "h264/AVC"; + case 0x90: return "Presentation Graphics"; + case 0x91: return "Interactive Graphics"; + case 0x92: return "Text Subtitle"; + default: return coding_type.ToString(); + } + } + } + + public string LanguageString + { + get + { + CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.AllCultures); + foreach (var culture in cultures) + { + // Exclude custom cultures. + if ((culture.CultureTypes & CultureTypes.UserCustomCulture) == CultureTypes.UserCustomCulture) + continue; + + if (culture.ThreeLetterISOLanguageName == lang) + return culture.EnglishName; + } + return lang; + } + } } - internal struct MPLSPlaylistItem + public struct MPLSPlaylistItem { public string clip_id; public byte connection_condition; @@ -443,7 +566,7 @@ namespace CUETools.Codecs.BDLPCM public List pg; } - internal struct MPLSHeader + public struct MPLSHeader { public uint type_indicator; public uint type_indicator2; @@ -457,5 +580,26 @@ namespace CUETools.Codecs.BDLPCM public List play_item; public List play_mark; + + public List Chapters + { + get + { + var res = new List(); + if (play_mark.Count < 1) return res; + if (play_item.Count < 1) return res; + uint start = play_item[0].in_time; + uint end = play_item[play_item.Count - 1].out_time; + if (play_mark[0].time - start > 45000) + res.Add(0); + for (int i = 0; i < mark_count; i++) + { + if (end - play_mark[i].time > 45000) + res.Add(play_mark[i].time - start); + } + res.Add(end - start); + return res; + } + } } } diff --git a/CUETools.Processor/CUESheet.cs b/CUETools.Processor/CUESheet.cs index ea2909e..db575a4 100644 --- a/CUETools.Processor/CUESheet.cs +++ b/CUETools.Processor/CUESheet.cs @@ -2263,8 +2263,8 @@ namespace CUETools.Processor break; //"Suspicious position 0:02:20" //" Suspicious position 0:02:23 - 0:02:24" - string s1 = CDImageLayout.TimeToString("0:{0:00}:{1:00}", iSecond * 75); - string s2 = CDImageLayout.TimeToString("0:{0:00}:{1:00}", end * 75); + string s1 = CDImageLayout.TimeToString(iSecond * 75, "0:{0:00}:{1:00}"); + string s2 = CDImageLayout.TimeToString(end * 75, "0:{0:00}:{1:00}"); if (iSecond == end) logWriter.WriteLine(" Suspicious position {0}", s1); else diff --git a/CUETools.Processor/CUESheetLogWriter.cs b/CUETools.Processor/CUESheetLogWriter.cs index 80652d0..567e64d 100644 --- a/CUETools.Processor/CUESheetLogWriter.cs +++ b/CUETools.Processor/CUESheetLogWriter.cs @@ -73,8 +73,8 @@ namespace CUETools.Processor { sw.WriteLine("{0,9} | {1,8} | {2,8} | {3,8} | {4,8} ", track, // sheet.TOC[track].Number, - CDImageLayout.TimeToString("{0,2}:{1:00}.{2:00}", sheet.TOC[track].Start), - CDImageLayout.TimeToString("{0,2}:{1:00}.{2:00}", sheet.TOC[track].Length), + CDImageLayout.TimeToString(sheet.TOC[track].Start, "{0,2}:{1:00}.{2:00}"), + CDImageLayout.TimeToString(sheet.TOC[track].Length, "{0,2}:{1:00}.{2:00}"), sheet.TOC[track].Start, sheet.TOC[track].End); } @@ -157,7 +157,7 @@ namespace CUETools.Processor if (sheet.TOC[track + sheet.TOC.FirstAudio].Pregap > 0 || track + sheet.TOC.FirstAudio == 1) { logWriter.WriteLine(); - logWriter.WriteLine(" Pre-gap length 0:{0}.{1:00}", CDImageLayout.TimeToString("{0:00}:{1:00}", sheet.TOC[track + sheet.TOC.FirstAudio].Pregap + (track + sheet.TOC.FirstAudio == 1 ? 150U : 0U)), (sheet.TOC[track + sheet.TOC.FirstAudio].Pregap % 75) * 100 / 75); + logWriter.WriteLine(" Pre-gap length 0:{0}.{1:00}", CDImageLayout.TimeToString(sheet.TOC[track + sheet.TOC.FirstAudio].Pregap + (track + sheet.TOC.FirstAudio == 1 ? 150U : 0U), "{0:00}:{1:00}"), (sheet.TOC[track + sheet.TOC.FirstAudio].Pregap % 75) * 100 / 75); } wereErrors |= sheet.PrintErrors(logWriter, sheet.TOC[track + sheet.TOC.FirstAudio].Start, sheet.TOC[track + sheet.TOC.FirstAudio].Length); diff --git a/CUETools.eac3to/CUETools.eac3to.csproj b/CUETools.eac3to/CUETools.eac3to.csproj new file mode 100644 index 0000000..9f259c6 --- /dev/null +++ b/CUETools.eac3to/CUETools.eac3to.csproj @@ -0,0 +1,114 @@ + + + + Debug + AnyCPU + 8.0.50727 + 2.0 + {E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE} + Exe + Properties + CUETools.eac3to + CUETools.eac3to + + + 3.5 + + + v2.0 + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + + + true + full + false + ..\bin\Debug\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + + + pdbonly + true + ..\bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + + + + + + + + + + + + + {1DD41038-D885-46C5-8DDE-E0B82F066584} + CUETools.CDImage + + + {E75F7CCD-4266-42E1-A039-DC7EB5EDD8F6} + CUETools.Codecs.BDLPCM + + + {6458A13A-30EF-45A9-9D58-E5031B17BEE2} + CUETools.Codecs + + + {AA2A9A7E-45FB-4632-AD85-85B0E556F818} + CUETools.CTDB + + + {4911BD82-49EF-4858-8B51-5394F86739A4} + CUETools.Processor + + + {6B143A39-C7B2-4743-9917-92262C60E9A6} + taglib-sharp + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + \ No newline at end of file diff --git a/CUETools.eac3to/Program.cs b/CUETools.eac3to/Program.cs new file mode 100644 index 0000000..3c3c99c --- /dev/null +++ b/CUETools.eac3to/Program.cs @@ -0,0 +1,443 @@ +using System; +using System.ComponentModel; +using System.Collections.Generic; +using System.IO; +using CUETools.Codecs; +using CUETools.Processor; +using System.Collections.Specialized; +using CUETools.Codecs.BDLPCM; +using CUETools.CDImage; +using CUETools.CTDB; + +namespace CUETools.eac3to +{ + class Program + { + static void Usage() + { + Console.Error.WriteLine("CUETools.eac3to, Copyright (C) 2018 Grigory Chudov."); + Console.Error.WriteLine("This is free software under the GNU GPLv3+ license; There is NO WARRANTY, to"); + Console.Error.WriteLine("the extent permitted by law. for details."); + Console.Error.WriteLine(); + Console.Error.WriteLine("Usage : CUETools.eac3to.exe [options] [trackno:] [destfile]"); + Console.Error.WriteLine(); + Console.Error.WriteLine("Options:"); + Console.Error.WriteLine(); + Console.Error.WriteLine(" --encoder Use non-default encoder."); + Console.Error.WriteLine(" --encoder-format Use encoder format different from file extension."); + Console.Error.WriteLine(" --lossy Use lossy encoder/mode."); + Console.Error.WriteLine(" --lossless Use lossless encoder/mode (default)."); + Console.Error.WriteLine(" -p # Padding bytes."); + Console.Error.WriteLine(" -m Encoder mode (0..8 for flac, V0..V9 for mp3, etc)"); + Console.Error.WriteLine(); + } + + public static CUEToolsUDC GetEncoder(CUEToolsCodecsConfig config, CUEToolsFormat fmt, bool lossless, string chosenEncoder) + { + CUEToolsUDC tmpEncoder; + return chosenEncoder != null ? + (config.encoders.TryGetValue(fmt.extension, lossless, chosenEncoder, out tmpEncoder) ? tmpEncoder : null) : + (lossless ? fmt.encoderLossless : fmt.encoderLossy); + } + + static int Main(string[] args) + { + bool ok = true; + string sourceFile = null, destFile = null; + int padding = 8192; + int stream = 0; + string encoderMode = null; + string encoderName = null; + string encoderFormat = null; + AudioEncoderType audioEncoderType = AudioEncoderType.NoAudio; + + for (int arg = 0; arg < args.Length; arg++) + { + if (args[arg].Length == 0) + ok = false; + else if (args[arg] == "--encoder" && ++arg < args.Length) + encoderName = args[arg]; + else if (args[arg] == "--encoder-format" && ++arg < args.Length) + 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] == "--lossy") + audioEncoderType = AudioEncoderType.Lossy; + else if (args[arg] == "--lossless") + audioEncoderType = AudioEncoderType.Lossless; + else if ((args[arg][0] != '-' || args[arg] == "-") && sourceFile == null) + sourceFile = args[arg]; + else if ((args[arg][0] != '-' || args[arg] == "-") && sourceFile != null && destFile == null) + { + destFile = args[arg]; + var x = destFile.Split(':'); + if (x.Length > 1) + { + stream = int.Parse(x[0]); + if (x[1] != "") + { + destFile = x[1]; + } + else + { + arg++; + if (arg >= args.Length) + { + ok = false; + break; + } + destFile = args[arg]; + } + } + } + else + ok = false; + if (!ok) + break; + } + + if (!ok || sourceFile == null) + { + Usage(); + return 22; + } + + if (destFile != null && destFile != "-" && destFile != "nul" && File.Exists(destFile)) + { + Console.Error.WriteLine("Error: file {0} already exists.", destFile); + return 17; + } + + DateTime start = DateTime.Now; + TimeSpan lastPrint = TimeSpan.FromMilliseconds(0); + CUEToolsCodecsConfig config = new CUEConfig(); + +#if !DEBUG + try +#endif + { + MPLSReader audioSource = null; + IAudioDest audioDest = null; + TagLib.UserDefined.AdditionalFileTypes.Config = config; + + try + { + audioSource = new MPLSReader(sourceFile, null); + Console.ForegroundColor = ConsoleColor.White; + int maxVideo = 0, maxAudio = 0, frameRate = 0; + bool interlaced = false; + audioSource.MPLSHeader.play_item.ForEach(i => maxVideo = Math.Max(maxVideo, i.video.Count)); + audioSource.MPLSHeader.play_item.ForEach(i => maxAudio = Math.Max(maxAudio, i.audio.Count)); + audioSource.MPLSHeader.play_item.ForEach(i => i.video.ForEach(v => frameRate = v.FrameRate)); + audioSource.MPLSHeader.play_item.ForEach(i => i.video.ForEach(v => interlaced = v.Interlaced)); + Console.Error.WriteLine("M2TS, {0} video track{1}, {2} audio track{3}, {4}, {5}{6}", maxVideo, maxVideo > 1 ? "s" : "", maxAudio, maxAudio > 1 ? "s" : "", + CDImageLayout.TimeToString(audioSource.Duration, "{0:0}:{1:00}:{2:00}"), frameRate * (interlaced ? 2 : 1), interlaced ? "i" : "p"); + //foreach (var item in audioSource.MPLSHeader.play_item) + //Console.Error.WriteLine("{0}.m2ts", item.clip_id); + { + Console.ForegroundColor = ConsoleColor.Gray; + int id = 1; + if (audioSource.MPLSHeader.mark_count > 2) + { + Console.ForegroundColor = ConsoleColor.White; + Console.Error.Write(id++); + Console.Error.Write(": "); + Console.ForegroundColor = ConsoleColor.Gray; + Console.Error.WriteLine("Chapters, {0} chapters", audioSource.MPLSHeader.mark_count - 1); + } + if (audioSource.MPLSHeader.play_item.Count > 0) + { + foreach (var video in audioSource.MPLSHeader.play_item[0].video) + { + Console.ForegroundColor = ConsoleColor.White; + Console.Error.Write(id++); + Console.Error.Write(": "); + Console.ForegroundColor = ConsoleColor.Gray; + Console.Error.WriteLine("{0}, {1}{2}", video.CodecString, video.FormatString, video.FrameRate * (video.Interlaced ? 2 : 1)); + } + foreach (var audio in audioSource.MPLSHeader.play_item[0].audio) + { + Console.ForegroundColor = ConsoleColor.White; + 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); + } + } + } + + if (destFile == null) + return 0; + + if (stream > 0) + { + int id = 1; + ushort pid = 0; + var chapters = audioSource.MPLSHeader.Chapters; + if (chapters.Count > 1) + { + if (stream == id) + { + string extension = Path.GetExtension(destFile).ToLower(); + if (!extension.StartsWith(".")) + throw new Exception("Unknown encoder format: " + destFile); + encoderFormat = extension.Substring(1); + + if (encoderFormat == "txt") + { + Console.Error.WriteLine("Creating file \"{0}\"...", destFile); + using (StreamWriter sw = new StreamWriter(destFile)) + { + 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))); + sw.WriteLine("CHAPTER{0:00}NAME=", i + 1); + } + } + Console.BackgroundColor = ConsoleColor.DarkGreen; + Console.Error.Write("Done."); + Console.BackgroundColor = ConsoleColor.Black; + Console.Error.WriteLine(); + return 0; + } + + if (encoderFormat == "cue") + { + Console.Error.WriteLine("Creating file \"{0}\"...", destFile); + string strtoc = ""; + for (int i = 0; i < chapters.Count; i++) + strtoc += string.Format(" {0}", chapters[i] / 600); + strtoc = strtoc.Substring(1); + CDImageLayout toc = new CDImageLayout(strtoc); + CTDBResponseMeta meta = null; + bool queryMeta = true; + if (queryMeta) + { + var ctdb = new CUEToolsDB(toc, null); + Console.Error.WriteLine("Contacting CTDB..."); + ctdb.ContactDB(null, "CUETools.eac3to 2.1.7", "", false, true, CTDBMetadataSearch.Extensive); + foreach (var imeta in ctdb.Metadata) + { + meta = imeta; + break; + } + } + //if (outputPath == null) + //{ + // if (meta != null) + // outputPath = string.Format("{0} - {1} - {2}.cue", meta.artist ?? "Unknown Artist", meta.year ?? "XXXX", meta.album ?? "Unknown Album"); + // else + // outputPath = "unknown.cue"; + //} + using (StreamWriter cueWriter = new StreamWriter(destFile)) + { + cueWriter.WriteLine("REM COMMENT \"{0}\"", "Created by CUETools.eac3to"); + if (meta != null && meta.year != null) + cueWriter.WriteLine("REM DATE {0}", meta.year); + else + cueWriter.WriteLine("REM DATE XXXX"); + if (meta != null) + { + cueWriter.WriteLine("PERFORMER \"{0}\"", meta.artist); + cueWriter.WriteLine("TITLE \"{0}\"", meta.album); + } + else + { + cueWriter.WriteLine("PERFORMER \"\""); + cueWriter.WriteLine("TITLE \"\""); + } + if (meta != null) + { + //cueWriter.WriteLine("FILE \"{0}\" WAVE", Path.GetFileNameWithoutExtension(destFile) + (extension ?? ".wav")); + cueWriter.WriteLine("FILE \"{0}\" WAVE", Path.GetFileNameWithoutExtension(destFile) + (".wav")); + } + else + { + cueWriter.WriteLine("FILE \"{0}\" WAVE", ""); + } + for (int track = 1; track <= toc.TrackCount; track++) + if (toc[track].IsAudio) + { + cueWriter.WriteLine(" TRACK {0:00} AUDIO", toc[track].Number); + if (meta != null && meta.track.Length >= toc[track].Number) + { + cueWriter.WriteLine(" TITLE \"{0}\"", meta.track[(int)toc[track].Number - 1].name); + if (meta.track[(int)toc[track].Number - 1].artist != null) + cueWriter.WriteLine(" PERFORMER \"{0}\"", meta.track[(int)toc[track].Number - 1].artist); + } + else + { + cueWriter.WriteLine(" TITLE \"\""); + } + if (toc[track].ISRC != null) + cueWriter.WriteLine(" ISRC {0}", toc[track].ISRC); + for (int index = toc[track].Pregap > 0 ? 0 : 1; index <= toc[track].LastIndex; index++) + cueWriter.WriteLine(" INDEX {0:00} {1}", index, toc[track][index].MSF); + } + } + Console.BackgroundColor = ConsoleColor.DarkGreen; + Console.Error.Write("Done."); + Console.BackgroundColor = ConsoleColor.Black; + Console.Error.WriteLine(); + return 0; + } + + Console.Error.WriteLine("Unsupported chapters file format \"{0}\"", encoderFormat); + return 0; + } + else + { + id++; + } + } + if (audioSource.MPLSHeader.play_item.Count > 0) + { + foreach (var video in audioSource.MPLSHeader.play_item[0].video) + { + if (stream == id) + { + pid = video.pid; + } + id++; + } + foreach (var audio in audioSource.MPLSHeader.play_item[0].audio) + { + if (stream == id) + { + pid = audio.pid; + } + id++; + } + } + if (pid == 0) + { + Console.Error.WriteLine("Stream {0} not found.", stream); + return 0; + } + (audioSource.Settings as BDLPCMReaderSettings).Pid = pid; + } + + 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, + audioSource.Duration); + + CUEToolsFormat fmt; + if (encoderFormat == null) + { + if (destFile == "-" || destFile == "nul") + encoderFormat = "wav"; + else + { + string extension = Path.GetExtension(destFile).ToLower(); + if (!extension.StartsWith(".")) + throw new Exception("Unknown encoder format: " + destFile); + encoderFormat = extension.Substring(1); + } + } + if (!config.formats.TryGetValue(encoderFormat, out fmt)) + throw new Exception("Unsupported encoder format: " + encoderFormat); + CUEToolsUDC encoder = + audioEncoderType == AudioEncoderType.Lossless ? Program.GetEncoder(config, fmt, true, encoderName) : + audioEncoderType == AudioEncoderType.Lossy ? Program.GetEncoder(config, fmt, false, encoderName) : + Program.GetEncoder(config, fmt, true, encoderName) ?? Program.GetEncoder(config, fmt, false, encoderName); + if (encoder == null) + { + var lst = new List(config.encoders).FindAll( + e => e.extension == fmt.extension && (audioEncoderType == AudioEncoderType.NoAudio || audioEncoderType == (e.Lossless ? AudioEncoderType.Lossless : AudioEncoderType.Lossy))). + ConvertAll(e => e.Name + (e.Lossless ? " (lossless)" : " (lossy)")); + throw new Exception("Encoders available for format " + fmt.extension + ": " + (lst.Count == 0 ? "none" : string.Join(", ", lst.ToArray()))); + } + var settings = encoder.settings.Clone(); + settings.PCM = audioSource.PCM; + settings.Padding = padding; + settings.EncoderMode = encoderMode ?? settings.EncoderMode; + object o = null; + try + { + o = destFile == "-" ? Activator.CreateInstance(encoder.type, "", Console.OpenStandardOutput(), settings) : + destFile == "nul" ? Activator.CreateInstance(encoder.type, "", new NullStream(), settings) : + Activator.CreateInstance(encoder.type, destFile, settings); + } + catch (System.Reflection.TargetInvocationException ex) + { + throw ex.InnerException; + } + if (o == null || !(o is IAudioDest)) + throw new Exception("Unsupported audio type: " + destFile + ": " + encoder.type.FullName); + audioDest = o as IAudioDest; + audioDest.FinalSampleCount = audioSource.Length; + + bool keepRunning = true; + Console.CancelKeyPress += delegate(object sender, ConsoleCancelEventArgs e) + { + keepRunning = false; + if (e.SpecialKey == ConsoleSpecialKey.ControlC) + e.Cancel = true; + else + audioDest.Delete(); + }; + + while (audioSource.Read(buff, -1) != 0) + { + audioDest.Write(buff); + TimeSpan elapsed = DateTime.Now - start; + if ((elapsed - lastPrint).TotalMilliseconds > 60) + { + long length = (long)(audioSource.Duration.TotalSeconds * audioSource.PCM.SampleRate); + if (length < audioSource.Position) length = audioSource.Position; + if (length < 1) length = 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, + elapsed, + TimeSpan.FromMilliseconds(elapsed.TotalMilliseconds / audioSource.Position * length) + ); + lastPrint = elapsed; + } + if (!keepRunning) + throw new Exception("Aborted"); + } + + TimeSpan totalElapsed = DateTime.Now - start; + Console.Error.Write("\r \r"); + Console.Error.WriteLine("Results : {0:0.00}x; {1}", + audioSource.Position / totalElapsed.TotalSeconds / audioSource.PCM.SampleRate, + totalElapsed + ); + } + catch (Exception ex) + { + if (audioSource != null) audioSource.Close(); + if (audioDest != null) audioDest.Delete(); + throw ex; + } + audioSource.Close(); + audioDest.Close(); + + if (sourceFile != "-" && destFile != "-" && destFile != "nul") + { + //TagLib.File destInfo = TagLib.File.Create(new TagLib.File.LocalFileAbstraction(destFile)); + //NameValueCollection tags; + //if (Tagging.UpdateTags(destInfo, tags, config, false)) + //{ + // destInfo.Save(); + //} + } + } +#if !DEBUG + catch (Exception ex) + { + Console.Error.Write("\r \r"); + Console.Error.WriteLine("Error : {0}", ex.Message); + return 1; + //Console.WriteLine("{0}", ex.StackTrace); + } +#endif + return 0; + } + } +} diff --git a/CUETools.eac3to/Properties/AssemblyInfo.cs b/CUETools.eac3to/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..66158aa --- /dev/null +++ b/CUETools.eac3to/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CUETools.eac3to")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("CUETools.eac3to")] +[assembly: AssemblyCopyright("Copyright © Grigory Chudov 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("401fabd2-a202-4648-bd39-4db7398eff3a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/CUETools/CUETools.sln b/CUETools/CUETools.sln index aef8a66..5eec664 100644 --- a/CUETools/CUETools.sln +++ b/CUETools/CUETools.sln @@ -182,6 +182,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CUETools.Codecs.WMA", "..\C EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CUETools.Codecs.BDLPCM", "..\CUETools.Codecs.BDLPCM\CUETools.Codecs.BDLPCM.csproj", "{E75F7CCD-4266-42E1-A039-DC7EB5EDD8F6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CUETools.eac3to", "..\CUETools.eac3to\CUETools.eac3to.csproj", "{E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE}" +EndProject Global GlobalSection(TestCaseManagementSettings) = postSolution CategoryFile = CUETools1.vsmdi @@ -1091,6 +1093,20 @@ Global {E75F7CCD-4266-42E1-A039-DC7EB5EDD8F6}.Release|Win32.ActiveCfg = Release|Any CPU {E75F7CCD-4266-42E1-A039-DC7EB5EDD8F6}.Release|x64.ActiveCfg = Release|Any CPU {E75F7CCD-4266-42E1-A039-DC7EB5EDD8F6}.Release|x86.ActiveCfg = Release|Any CPU + {E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE}.Debug|Win32.ActiveCfg = Debug|Any CPU + {E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE}.Debug|x64.ActiveCfg = Debug|Any CPU + {E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE}.Debug|x86.ActiveCfg = Debug|Any CPU + {E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE}.Release|Any CPU.Build.0 = Release|Any CPU + {E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE}.Release|Win32.ActiveCfg = Release|Any CPU + {E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE}.Release|x64.ActiveCfg = Release|Any CPU + {E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE}.Release|x86.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1113,6 +1129,7 @@ Global {8E6E1763-39AE-491D-A10F-44C8844ABA5B} = {4B59E09C-A51F-4B80-91BE-987904DCEF7D} {1FCA8834-34E6-47CF-B53F-D8DF35345363} = {4B59E09C-A51F-4B80-91BE-987904DCEF7D} {F8C29953-A697-4462-82DC-DA7146654A64} = {4B59E09C-A51F-4B80-91BE-987904DCEF7D} + {E3FF7539-6B22-4922-8FEF-6D26F2C2E3CE} = {4B59E09C-A51F-4B80-91BE-987904DCEF7D} {8B179853-B7D6-479C-B8B2-6CBCE835D040} = {39A17A65-E893-44B8-A312-DDCDD990D9D1} {E70FA90A-7012-4A52-86B5-362B699D1540} = {39A17A65-E893-44B8-A312-DDCDD990D9D1} {9AE965C4-301E-4C01-B90F-297AF341ACC6} = {39A17A65-E893-44B8-A312-DDCDD990D9D1}