From ebfdd44142ed61add1aa1c4e460647d610f34d15 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Wed, 16 Jun 2021 16:03:41 -0600 Subject: [PATCH 1/4] Abstracted Chapters class, adding chapter titles and end times. Updated references. --- AaxDecrypter/UNTESTED/AAXChapters.cs | 40 +++++++++++++ AaxDecrypter/UNTESTED/AaxToM4bConverter.cs | 6 +- AaxDecrypter/UNTESTED/Chapter.cs | 27 +++++++++ AaxDecrypter/UNTESTED/Chapters.cs | 65 +++++----------------- 4 files changed, 83 insertions(+), 55 deletions(-) create mode 100644 AaxDecrypter/UNTESTED/AAXChapters.cs create mode 100644 AaxDecrypter/UNTESTED/Chapter.cs diff --git a/AaxDecrypter/UNTESTED/AAXChapters.cs b/AaxDecrypter/UNTESTED/AAXChapters.cs new file mode 100644 index 00000000..f9770e87 --- /dev/null +++ b/AaxDecrypter/UNTESTED/AAXChapters.cs @@ -0,0 +1,40 @@ +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using Dinah.Core.Diagnostics; + +namespace AaxDecrypter +{ + public class AAXChapters : Chapters + { + public AAXChapters(string file) + { + var info = new ProcessStartInfo + { + FileName = DecryptSupportLibraries.ffprobePath, + Arguments = "-loglevel panic -show_chapters -print_format xml \"" + file + "\"" + }; + var xml = info.RunHidden().Output; + + var xmlDocument = new System.Xml.XmlDocument(); + xmlDocument.LoadXml(xml); + var chaptersXml = xmlDocument.SelectNodes("/ffprobe/chapters/chapter") + .Cast() + .Where(n => n.Name == "chapter"); + + foreach (var cnode in chaptersXml) + { + double startTime = double.Parse(cnode.Attributes["start_time"].Value.Replace(",", "."), CultureInfo.InvariantCulture); + double endTime = double.Parse(cnode.Attributes["end_time"].Value.Replace(",", "."), CultureInfo.InvariantCulture); + + string chapterTitle = cnode.ChildNodes + .Cast() + .Where(childnode => childnode.Attributes["key"].Value == "title") + .Select(childnode => childnode.Attributes["value"].Value) + .FirstOrDefault(); + + AddChapter(new Chapter(startTime, endTime, chapterTitle)); + } + } + } +} diff --git a/AaxDecrypter/UNTESTED/AaxToM4bConverter.cs b/AaxDecrypter/UNTESTED/AaxToM4bConverter.cs index c1b00d11..cbce511f 100644 --- a/AaxDecrypter/UNTESTED/AaxToM4bConverter.cs +++ b/AaxDecrypter/UNTESTED/AaxToM4bConverter.cs @@ -98,7 +98,7 @@ namespace AaxDecrypter { tags = new Tags(inputFileName); encodingInfo = new EncodingInfo(inputFileName); - chapters = new Chapters(inputFileName, tags.duration.TotalSeconds); + chapters = new AAXChapters(inputFileName); var defaultFilename = Path.Combine( Path.GetDirectoryName(inputFileName), @@ -278,9 +278,9 @@ namespace AaxDecrypter public bool Step3_Chapterize() { var str1 = ""; - if (chapters.FirstChapterStart != 0.0) + if (chapters.FirstChapter.StartTime != 0.0) { - str1 = " -ss " + chapters.FirstChapterStart.ToString("0.000", CultureInfo.InvariantCulture) + " -t " + (chapters.LastChapterStart - 1.0).ToString("0.000", CultureInfo.InvariantCulture) + " "; + str1 = " -ss " + chapters.FirstChapter.StartTime.ToString("0.000", CultureInfo.InvariantCulture) + " -t " + chapters.LastChapter.EndTime.ToString("0.000", CultureInfo.InvariantCulture) + " "; } var ffmpegTags = tags.GenerateFfmpegTags(); diff --git a/AaxDecrypter/UNTESTED/Chapter.cs b/AaxDecrypter/UNTESTED/Chapter.cs new file mode 100644 index 00000000..b4507d43 --- /dev/null +++ b/AaxDecrypter/UNTESTED/Chapter.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AaxDecrypter +{ + public class Chapter + { + public Chapter(double startTime, double endTime, string title) + { + StartTime = startTime; + EndTime = endTime; + Title = title; + } + /// + /// Chapter start time, in seconds. + /// + public double StartTime { get; private set; } + /// + /// Chapter end time, in seconds. + /// + public double EndTime { get; private set; } + public string Title { get; private set; } + } +} diff --git a/AaxDecrypter/UNTESTED/Chapters.cs b/AaxDecrypter/UNTESTED/Chapters.cs index d83ef3f2..10487915 100644 --- a/AaxDecrypter/UNTESTED/Chapters.cs +++ b/AaxDecrypter/UNTESTED/Chapters.cs @@ -1,72 +1,33 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; using System.Linq; using System.Text; -using Dinah.Core.Diagnostics; namespace AaxDecrypter { - public class Chapters + public abstract class Chapters { - private List markers { get; } - - public double FirstChapterStart => markers[0]; - public double LastChapterStart => markers[markers.Count - 1]; - - public Chapters(string file, double totalTime) + private List _chapterList = new(); + public int Count => _chapterList.Count; + public Chapter FirstChapter => _chapterList[0]; + public Chapter LastChapter => _chapterList[Count - 1]; + public IEnumerable ChapterList => _chapterList.AsEnumerable(); + public IEnumerable GetBeginningTimes() => ChapterList.Select(c => TimeSpan.FromSeconds(c.StartTime)); + protected void AddChapter(Chapter chapter) { - markers = getAAXChapters(file); - - // add end time - markers.Add(totalTime); + _chapterList.Add(chapter); } - - private static List getAAXChapters(string file) - { - var info = new ProcessStartInfo - { - FileName = DecryptSupportLibraries.ffprobePath, - Arguments = "-loglevel panic -show_chapters -print_format xml \"" + file + "\"" - }; - var xml = info.RunHidden().Output; - - var xmlDocument = new System.Xml.XmlDocument(); - xmlDocument.LoadXml(xml); - var chapters = xmlDocument.SelectNodes("/ffprobe/chapters/chapter") - .Cast() - .Select(xmlNode => double.Parse(xmlNode.Attributes["start_time"].Value.Replace(",", "."), CultureInfo.InvariantCulture)) - .ToList(); - return chapters; - } - - // subtract 1 b/c end time marker is a real entry but isn't a real chapter. ie: fencepost - public int Count => markers.Count - 1; - - public IEnumerable GetBeginningTimes() - { - for (var i = 0; i < Count; i++) - yield return TimeSpan.FromSeconds(markers[i]); - } - public string GenerateFfmpegChapters() { var stringBuilder = new StringBuilder(); - for (var i = 0; i < Count; i++) + foreach (Chapter c in ChapterList) { - var chapter = i + 1; - - var start = markers[i] * 1000.0; - var end = markers[i + 1] * 1000.0; - var chapterName = chapter.ToString("D3"); - stringBuilder.Append("[CHAPTER]\n"); stringBuilder.Append("TIMEBASE=1/1000\n"); - stringBuilder.Append("START=" + start + "\n"); - stringBuilder.Append("END=" + end + "\n"); - stringBuilder.Append("title=" + chapterName + "\n"); + stringBuilder.Append("START=" + c.StartTime * 1000 + "\n"); + stringBuilder.Append("END=" + c.EndTime * 1000 + "\n"); + stringBuilder.Append("title=" + c.Title + "\n"); } return stringBuilder.ToString(); From 9271114408474d56f3d50a7710f12a113e6659d4 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Wed, 16 Jun 2021 16:27:23 -0600 Subject: [PATCH 2/4] Allow caller to specify alternate chapters source. --- AaxDecrypter/UNTESTED/AaxToM4bConverter.cs | 4 ++-- AaxDecrypter/UNTESTED/Chapters.cs | 4 ++++ LibationLauncher/LibationLauncher.csproj | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/AaxDecrypter/UNTESTED/AaxToM4bConverter.cs b/AaxDecrypter/UNTESTED/AaxToM4bConverter.cs index cbce511f..6baf6637 100644 --- a/AaxDecrypter/UNTESTED/AaxToM4bConverter.cs +++ b/AaxDecrypter/UNTESTED/AaxToM4bConverter.cs @@ -62,9 +62,10 @@ namespace AaxDecrypter public Tags tags { get; private set; } public EncodingInfo encodingInfo { get; private set; } - public static async Task CreateAsync(string inputFile, string decryptKey) + public static async Task CreateAsync(string inputFile, string decryptKey, Chapters chapters = null) { var converter = new AaxToM4bConverter(inputFile, decryptKey); + converter.chapters = chapters ?? new AAXChapters(inputFile); await converter.prelimProcessing(); converter.printPrelim(); @@ -98,7 +99,6 @@ namespace AaxDecrypter { tags = new Tags(inputFileName); encodingInfo = new EncodingInfo(inputFileName); - chapters = new AAXChapters(inputFileName); var defaultFilename = Path.Combine( Path.GetDirectoryName(inputFileName), diff --git a/AaxDecrypter/UNTESTED/Chapters.cs b/AaxDecrypter/UNTESTED/Chapters.cs index 10487915..29ec9102 100644 --- a/AaxDecrypter/UNTESTED/Chapters.cs +++ b/AaxDecrypter/UNTESTED/Chapters.cs @@ -17,6 +17,10 @@ namespace AaxDecrypter { _chapterList.Add(chapter); } + protected void AddChapters(IEnumerable chapters) + { + _chapterList.AddRange(chapters); + } public string GenerateFfmpegChapters() { var stringBuilder = new StringBuilder(); diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index e97f27d2..5f3caf92 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 4.2.4.1 + 4.2.4.3 From ff722b6a5287499f49553f68f4b576d2c00d2e39 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Wed, 16 Jun 2021 16:58:01 -0600 Subject: [PATCH 3/4] Added support for chapter titles and refactored. --- AaxDecrypter/UNTESTED/Cue.cs | 19 +++++++------------ LibationLauncher/LibationLauncher.csproj | 2 +- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/AaxDecrypter/UNTESTED/Cue.cs b/AaxDecrypter/UNTESTED/Cue.cs index 5401dc25..86ec88c3 100644 --- a/AaxDecrypter/UNTESTED/Cue.cs +++ b/AaxDecrypter/UNTESTED/Cue.cs @@ -14,20 +14,15 @@ namespace AaxDecrypter stringBuilder.AppendLine(GetFileLine(filePath, "MP3")); - var beginningTimes = chapters.GetBeginningTimes().ToList(); - for (var i = 0; i < beginningTimes.Count; i++) + var trackCount = 0; + foreach (Chapter c in chapters.ChapterList) { - var chapter = i + 1; + trackCount++; + var startTime = TimeSpan.FromSeconds(c.StartTime); - var timeSpan = beginningTimes[i]; - var minutes = Math.Floor(timeSpan.TotalMinutes).ToString(); - var seconds = timeSpan.Seconds.ToString("D2"); - var milliseconds = (timeSpan.Milliseconds / 10).ToString("D2"); - var time = minutes + ":" + seconds + ":" + milliseconds; - - stringBuilder.AppendLine($"TRACK {chapter} AUDIO"); - stringBuilder.AppendLine($" TITLE \"Chapter {chapter:D2}\""); - stringBuilder.AppendLine($" INDEX 01 {time}"); + stringBuilder.AppendLine($"TRACK {trackCount} AUDIO"); + stringBuilder.AppendLine($" TITLE \"{c.Title}\""); + stringBuilder.AppendLine($" INDEX 01 {(int)startTime.TotalMinutes}:{startTime:ss\\:ff}"); } return stringBuilder.ToString(); diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index 5f3caf92..c5b085d2 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 4.2.4.3 + 4.2.4.8 From 5731a8f693550b26231d12be7d0c111f9f112155 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Wed, 16 Jun 2021 17:04:42 -0600 Subject: [PATCH 4/4] Added support for downloaded chapters. --- FileLiberator/UNTESTED/DecryptBook.cs | 25 +++++++++++++++++--- FileLiberator/UNTESTED/DownloadedChapters.cs | 22 +++++++++++++++++ LibationLauncher/LibationLauncher.csproj | 2 +- 3 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 FileLiberator/UNTESTED/DownloadedChapters.cs diff --git a/FileLiberator/UNTESTED/DecryptBook.cs b/FileLiberator/UNTESTED/DecryptBook.cs index 685d9c41..b331d137 100644 --- a/FileLiberator/UNTESTED/DecryptBook.cs +++ b/FileLiberator/UNTESTED/DecryptBook.cs @@ -56,7 +56,9 @@ namespace FileLiberator if (AudibleFileStorage.Audio.Exists(libraryBook.Book.AudibleProductId)) return new StatusHandler { "Cannot find decrypt. Final audio file already exists" }; - var outputAudioFilename = await aaxToM4bConverterDecrypt(aaxFilename, libraryBook); + var chapters = await downloadChapterNames(libraryBook); + + var outputAudioFilename = await aaxToM4bConverterDecrypt(aaxFilename, libraryBook, chapters); // decrypt failed if (outputAudioFilename == null) @@ -90,7 +92,7 @@ namespace FileLiberator } } - private async Task aaxToM4bConverterDecrypt(string aaxFilename, LibraryBook libraryBook) + private async Task aaxToM4bConverterDecrypt(string aaxFilename, LibraryBook libraryBook, Chapters chapters = null) { DecryptBegin?.Invoke(this, $"Begin decrypting {aaxFilename}"); @@ -102,7 +104,7 @@ namespace FileLiberator .AccountsSettings .GetAccount(libraryBook.Account, libraryBook.Book.Locale); - var converter = await AaxToM4bConverter.CreateAsync(aaxFilename, account.DecryptKey); + var converter = await AaxToM4bConverter.CreateAsync(aaxFilename, account.DecryptKey, chapters); converter.AppName = "Libation"; TitleDiscovered?.Invoke(this, converter.tags.title); @@ -132,6 +134,23 @@ namespace FileLiberator } } + private async Task downloadChapterNames(LibraryBook libraryBook) + { + try + { + var api = await AudibleApiActions.GetApiAsync(libraryBook.Account, libraryBook.Book.Locale); + var contentMetadata = await api.GetLibraryBookMetadataAsync(libraryBook.Book.AudibleProductId); + + if (contentMetadata?.ChapterInfo != null) + return new DownloadedChapters(contentMetadata.ChapterInfo); + return null; + } + catch + { + return null; + } + } + private static string moveFilesToBooksDir(Book product, string outputAudioFilename) { // create final directory. move each file into it. MOVE AUDIO FILE LAST diff --git a/FileLiberator/UNTESTED/DownloadedChapters.cs b/FileLiberator/UNTESTED/DownloadedChapters.cs new file mode 100644 index 00000000..7a95f83c --- /dev/null +++ b/FileLiberator/UNTESTED/DownloadedChapters.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Text; +using AaxDecrypter; +using AudibleApiDTOs; +using Dinah.Core.Diagnostics; + + +namespace FileLiberator +{ + public class DownloadedChapters : Chapters + { + public DownloadedChapters(ChapterInfo chapterInfo) + { + AddChapters(chapterInfo.Chapters + .Select(c => new AaxDecrypter.Chapter(c.StartOffsetMs / 1000d, (c.StartOffsetMs + c.LengthMs) / 1000d, c.Title))); + } + } +} diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index c5b085d2..2bf9d8e8 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 4.2.4.8 + 4.2.4.9