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..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 Chapters(inputFileName, tags.duration.TotalSeconds); 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..29ec9102 100644 --- a/AaxDecrypter/UNTESTED/Chapters.cs +++ b/AaxDecrypter/UNTESTED/Chapters.cs @@ -1,72 +1,37 @@ 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) + protected void AddChapters(IEnumerable chapters) { - 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; + _chapterList.AddRange(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(); 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/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 e97f27d2..2bf9d8e8 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 4.2.4.1 + 4.2.4.9