From c3c8a6fa6bbceb67729268850e1d1f97dc119967 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 10 Jul 2021 20:21:01 -0600 Subject: [PATCH] Replaced FFMpeg decryptor and taglib with AAXClean --- AaxDecrypter/AaxDecrypter.csproj | 1 + AaxDecrypter/AaxcDownloadConverter.cs | 116 +++-------- AaxDecrypter/AaxcTagLibFile.cs | 75 ------- AaxDecrypter/Chapters.cs | 86 -------- AaxDecrypter/Cue.cs | 1 + AaxDecrypter/DownloadLicense.cs | 3 +- AaxDecrypter/FFMpegAaxcProcesser.cs | 254 ----------------------- AaxDecrypter/NFO.cs | 42 ++-- AaxDecrypter/NetworkFileStream.cs | 4 +- FileLiberator/DownloadDecryptBook.cs | 11 +- Libation.sln | 7 + LibationLauncher/LibationLauncher.csproj | 2 +- 12 files changed, 68 insertions(+), 534 deletions(-) delete mode 100644 AaxDecrypter/AaxcTagLibFile.cs delete mode 100644 AaxDecrypter/Chapters.cs delete mode 100644 AaxDecrypter/FFMpegAaxcProcesser.cs diff --git a/AaxDecrypter/AaxDecrypter.csproj b/AaxDecrypter/AaxDecrypter.csproj index 04be65c8..9262a8fc 100644 --- a/AaxDecrypter/AaxDecrypter.csproj +++ b/AaxDecrypter/AaxDecrypter.csproj @@ -9,6 +9,7 @@ + diff --git a/AaxDecrypter/AaxcDownloadConverter.cs b/AaxDecrypter/AaxcDownloadConverter.cs index c24a8c14..ec1f89ef 100644 --- a/AaxDecrypter/AaxcDownloadConverter.cs +++ b/AaxDecrypter/AaxcDownloadConverter.cs @@ -1,4 +1,5 @@ -using Dinah.Core; +using AAXClean; +using Dinah.Core; using Dinah.Core.Diagnostics; using Dinah.Core.IO; using Dinah.Core.StepRunner; @@ -9,7 +10,7 @@ namespace AaxDecrypter { public interface ISimpleAaxcToM4bConverter { - event EventHandler RetrievedTags; + event EventHandler RetrievedTags; event EventHandler RetrievedCoverArt; event EventHandler DecryptTimeRemaining; event EventHandler DecryptProgressUpdate; @@ -18,7 +19,7 @@ namespace AaxDecrypter string outDir { get; } string outputFileName { get; } DownloadLicense downloadLicense { get; } - AaxcTagLibFile aaxcTagLib { get; } + Mp4File aaxFile { get; } byte[] coverArt { get; } void SetCoverArt(byte[] coverArt); void SetOutputFilename(string outFileName); @@ -29,14 +30,13 @@ namespace AaxDecrypter bool Step1_CreateDir(); bool Step2_GetMetadata(); bool Step3_DownloadAndCombine(); - bool Step4_RestoreMetadata(); bool Step5_CreateCue(); bool Step6_CreateNfo(); bool Step7_Cleanup(); } public class AaxcDownloadConverter : IAdvancedAaxcToM4bConverter { - public event EventHandler RetrievedTags; + public event EventHandler RetrievedTags; public event EventHandler RetrievedCoverArt; public event EventHandler DecryptProgressUpdate; public event EventHandler DecryptTimeRemaining; @@ -45,11 +45,11 @@ namespace AaxDecrypter public string cacheDir { get; private set; } public string outputFileName { get; private set; } public DownloadLicense downloadLicense { get; private set; } - public AaxcTagLibFile aaxcTagLib { get; private set; } + public Mp4File aaxFile { get; private set; } public byte[] coverArt { get; private set; } private StepSequence steps { get; } - private FFMpegAaxcProcesser aaxcProcesser; + private NetworkFileStreamPersister nfsPersister; private bool isCanceled { get; set; } private string jsonDownloadState => Path.Combine(cacheDir, Path.GetFileNameWithoutExtension(outputFileName) + ".json"); private string tempFile => PathLib.ReplaceExtension(jsonDownloadState, ".aaxc"); @@ -81,15 +81,11 @@ namespace AaxDecrypter ["Step 1: Create Dir"] = Step1_CreateDir, ["Step 2: Get Aaxc Metadata"] = Step2_GetMetadata, ["Step 3: Download Decrypted Audiobook"] = Step3_DownloadAndCombine, - ["Step 4: Restore Aaxc Metadata"] = Step4_RestoreMetadata, ["Step 5: Create Cue"] = Step5_CreateCue, ["Step 6: Create Nfo"] = Step6_CreateNfo, ["Step 7: Cleanup"] = Step7_Cleanup, }; - aaxcProcesser = new FFMpegAaxcProcesser(dlLic); - aaxcProcesser.ProgressUpdate += AaxcProcesser_ProgressUpdate; - downloadLicense = dlLic; } @@ -120,7 +116,7 @@ namespace AaxDecrypter return false; } - var speedup = (int)(aaxcTagLib.Properties.Duration.TotalSeconds / (long)Elapsed.TotalSeconds); + var speedup = (int)(aaxFile.Duration.TotalSeconds / (long)Elapsed.TotalSeconds); Console.WriteLine("Speedup is " + speedup + "x realtime."); Console.WriteLine("Done"); return true; @@ -137,8 +133,7 @@ namespace AaxDecrypter public bool Step2_GetMetadata() { //Get metadata from the file over http - - NetworkFileStreamPersister nfsPersister; + if (File.Exists(jsonDownloadState)) { nfsPersister = new NetworkFileStreamPersister(jsonDownloadState); @@ -154,18 +149,12 @@ namespace AaxDecrypter NetworkFileStream networkFileStream = new NetworkFileStream(tempFile, new Uri(downloadLicense.DownloadUrl), 0, headers); nfsPersister = new NetworkFileStreamPersister(networkFileStream, jsonDownloadState); } + nfsPersister.NetworkFileStream.BeginDownloading(); - var networkFile = new NetworkFileAbstraction(nfsPersister.NetworkFileStream); - aaxcTagLib = new AaxcTagLibFile(networkFile); - nfsPersister.Dispose(); + aaxFile = new Mp4File(nfsPersister.NetworkFileStream); + coverArt = aaxFile.AppleTags.Cover; - - if (coverArt is null && aaxcTagLib.AppleTags.Pictures.Length > 0) - { - coverArt = aaxcTagLib.AppleTags.Pictures[0].Data.Data; - } - - RetrievedTags?.Invoke(this, aaxcTagLib); + RetrievedTags?.Invoke(this, aaxFile.AppleTags); RetrievedCoverArt?.Invoke(this, coverArt); return !isCanceled; @@ -175,87 +164,40 @@ namespace AaxDecrypter { DecryptProgressUpdate?.Invoke(this, int.MaxValue); - NetworkFileStreamPersister nfsPersister; - if (File.Exists(jsonDownloadState)) - { - nfsPersister = new NetworkFileStreamPersister(jsonDownloadState); - //If More thaan ~1 hour has elapsed since getting the download url, it will expire. - //The new url will be to the same file. - nfsPersister.NetworkFileStream.SetUriForSameFile(new Uri(downloadLicense.DownloadUrl)); - } - else - { - var headers = new System.Net.WebHeaderCollection(); - headers.Add("User-Agent", downloadLicense.UserAgent); + if (File.Exists(outputFileName)) + FileExt.SafeDelete(outputFileName); - NetworkFileStream networkFileStream = new NetworkFileStream(tempFile, new Uri(downloadLicense.DownloadUrl), 0, headers); - nfsPersister = new NetworkFileStreamPersister(networkFileStream, jsonDownloadState); - } + FileStream outFile = File.OpenWrite(outputFileName); - string metadataPath = Path.Combine(outDir, Path.GetFileName(outputFileName) + ".ffmeta"); + aaxFile.DecryptionProgressUpdate += AaxFile_DecryptionProgressUpdate; + var decryptedBook = aaxFile.DecryptAaxc(outFile, downloadLicense.AudibleKey, downloadLicense.AudibleIV, downloadLicense.ChapterInfo); + aaxFile.DecryptionProgressUpdate -= AaxFile_DecryptionProgressUpdate; - if (downloadLicense.ChapterInfo is null) - { - //If we want to keep the original chapters, we need to get them from the url. - //Ffprobe needs to seek to find metadata and it can't seek a pipe. Also, there's - //no guarantee that enough of the file will have been downloaded at this point - //to be able to use the cache file. - downloadLicense.ChapterInfo = new ChapterInfo(downloadLicense.DownloadUrl); - } + decryptedBook?.AppleTags?.SetCoverArt(coverArt); + decryptedBook?.Save(); + decryptedBook?.Close(); - //Only write chapters to the metadata file. All other aaxc metadata will be - //wiped out but is restored in Step 3. - File.WriteAllText(metadataPath, downloadLicense.ChapterInfo.ToFFMeta(true)); - - - aaxcProcesser.ProcessBook( - nfsPersister.NetworkFileStream, - outputFileName, - metadataPath) - .GetAwaiter() - .GetResult(); - - nfsPersister.NetworkFileStream.Close(); nfsPersister.Dispose(); - FileExt.SafeDelete(metadataPath); - DecryptProgressUpdate?.Invoke(this, 0); - return aaxcProcesser.Succeeded && !isCanceled; + return aaxFile is not null && !isCanceled; } - private void AaxcProcesser_ProgressUpdate(object sender, AaxcProcessUpdate e) + private void AaxFile_DecryptionProgressUpdate(object sender, DecryptionProgressEventArgs e) { - double remainingSecsToProcess = (aaxcTagLib.Properties.Duration - e.ProcessPosition).TotalSeconds; + var duration = aaxFile.Duration; + double remainingSecsToProcess = (duration - e.ProcessPosition).TotalSeconds; double estTimeRemaining = remainingSecsToProcess / e.ProcessSpeed; if (double.IsNormal(estTimeRemaining)) DecryptTimeRemaining?.Invoke(this, TimeSpan.FromSeconds(estTimeRemaining)); - double progressPercent = 100 * e.ProcessPosition.TotalSeconds / aaxcTagLib.Properties.Duration.TotalSeconds; + double progressPercent = 100 * e.ProcessPosition.TotalSeconds / duration.TotalSeconds; DecryptProgressUpdate?.Invoke(this, (int)progressPercent); } - /// - /// Copy all aacx metadata to m4b file, including cover art. - /// - public bool Step4_RestoreMetadata() - { - var outFile = new AaxcTagLibFile(outputFileName); - outFile.CopyTagsFrom(aaxcTagLib); - - if (outFile.AppleTags.Pictures.Length == 0 && coverArt is not null) - { - outFile.AddPicture(coverArt); - } - - outFile.Save(); - - return !isCanceled; - } - public bool Step5_CreateCue() { try @@ -273,7 +215,7 @@ namespace AaxDecrypter { try { - File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".nfo"), NFO.CreateContents(AppName, aaxcTagLib, downloadLicense.ChapterInfo)); + File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".nfo"), NFO.CreateContents(AppName, aaxFile, downloadLicense.ChapterInfo)); } catch (Exception ex) { @@ -292,7 +234,7 @@ namespace AaxDecrypter public void Cancel() { isCanceled = true; - aaxcProcesser.Cancel(); + aaxFile?.Cancel(); } } } diff --git a/AaxDecrypter/AaxcTagLibFile.cs b/AaxDecrypter/AaxcTagLibFile.cs deleted file mode 100644 index 44461dfd..00000000 --- a/AaxDecrypter/AaxcTagLibFile.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Linq; -using Dinah.Core; -using TagLib; -using TagLib.Mpeg4; - -namespace AaxDecrypter -{ - public class AaxcTagLibFile : TagLib.Mpeg4.File - { - // © - private const byte COPYRIGHT = 0xa9; - - private static ReadOnlyByteVector narratorType { get; } = new ReadOnlyByteVector(COPYRIGHT, (byte)'n', (byte)'r', (byte)'t'); - private static ReadOnlyByteVector descriptionType { get; } = new ReadOnlyByteVector(COPYRIGHT, (byte)'d', (byte)'e', (byte)'s'); - private static ReadOnlyByteVector publisherType { get; } = new ReadOnlyByteVector(COPYRIGHT, (byte)'p', (byte)'u', (byte)'b'); - - public string AsciiTitleSansUnabridged => TitleSansUnabridged?.UnicodeToAscii(); - public string AsciiFirstAuthor => FirstAuthor?.UnicodeToAscii(); - public string AsciiNarrator => Narrator?.UnicodeToAscii(); - public string AsciiComment => Comment?.UnicodeToAscii(); - public string AsciiLongDescription => LongDescription?.UnicodeToAscii(); - - public AppleTag AppleTags => GetTag(TagTypes.Apple) as AppleTag; - - public string Comment => AppleTags.Comment; - public string[] Authors => AppleTags.Performers; - public string FirstAuthor => Authors?.Length > 0 ? Authors[0] : default; - public string TitleSansUnabridged => AppleTags.Title?.Replace(" (Unabridged)", ""); - - public string BookCopyright => _copyright is not null && _copyright.Length > 0 ? _copyright[0] : default; - public string RecordingCopyright => _copyright is not null && _copyright.Length > 1 ? _copyright[1] : default; - private string[] _copyright => AppleTags.Copyright?.Replace("©", string.Empty)?.Replace("(P)", string.Empty)?.Split(';'); - - public string Narrator => getAppleTagsText(narratorType); - public string LongDescription => getAppleTagsText(descriptionType); - public string ReleaseDate => getAppleTagsText("rldt"); - public string Publisher => getAppleTagsText(publisherType); - private string getAppleTagsText(ByteVector byteVector) - { - string[] text = AppleTags.GetText(byteVector); - return text.Length == 0 ? default : text[0]; - } - - public AaxcTagLibFile(IFileAbstraction abstraction) - : base(abstraction, ReadStyle.Average) - { - } - - public AaxcTagLibFile(string path) - : this(new LocalFileAbstraction(path)) - { - } - /// - /// Copy all metadata fields in the source file, even those that TagLib doesn't - /// recognize, to the output file. - /// NOTE: Chapters aren't stored in MPEG-4 metadata. They are encoded as a Timed - /// Text Stream (MPEG-4 Part 17), so taglib doesn't read or write them. - /// - /// File from which tags will be coppied. - public void CopyTagsFrom(AaxcTagLibFile sourceFile) - { - AppleTags.Clear(); - - foreach (var stag in sourceFile.AppleTags) - { - AppleTags.SetData(stag.BoxType, stag.Children.Cast().ToArray()); - } - } - public void AddPicture(byte[] coverArt) - { - AppleTags.SetData("covr", coverArt, 0); - } - } -} diff --git a/AaxDecrypter/Chapters.cs b/AaxDecrypter/Chapters.cs deleted file mode 100644 index 490de153..00000000 --- a/AaxDecrypter/Chapters.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Dinah.Core; -using Dinah.Core.Diagnostics; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; - -namespace AaxDecrypter -{ - public class ChapterInfo - { - private List _chapterList = new List(); - public IEnumerable Chapters => _chapterList.AsEnumerable(); - public int Count => _chapterList.Count; - public ChapterInfo() { } - public ChapterInfo(string audiobookFile) - { - var info = new ProcessStartInfo - { - FileName = DecryptSupportLibraries.ffprobePath, - Arguments = "-loglevel panic -show_chapters -print_format json \"" + audiobookFile + "\"" - }; - - var jString = info.RunHidden().Output; - var chapterJObject = JObject.Parse(jString); - var chapters = chapterJObject["chapters"] - .Select(c => new Chapter( - c["tags"]?["title"]?.Value(), - c["start_time"].Value(), - c["end_time"].Value() - )); - - _chapterList.AddRange(chapters); - } - public void AddChapter(Chapter chapter) - { - ArgumentValidator.EnsureNotNull(chapter, nameof(chapter)); - _chapterList.Add(chapter); - } - public string ToFFMeta(bool includeFFMetaHeader) - { - var ffmetaChapters = new StringBuilder(); - - if (includeFFMetaHeader) - ffmetaChapters.AppendLine(";FFMETADATA1"); - - foreach (var c in Chapters) - { - ffmetaChapters.AppendLine(c.ToFFMeta()); - } - return ffmetaChapters.ToString(); - } - } - public class Chapter - { - public string Title { get; } - public TimeSpan StartOffset { get; } - public TimeSpan EndOffset { get; } - public Chapter(string title, long startOffsetMs, long lengthMs) - { - ArgumentValidator.EnsureNotNullOrEmpty(title, nameof(title)); - ArgumentValidator.EnsureGreaterThan(startOffsetMs, nameof(startOffsetMs), -1); - - // do not validate lengthMs for '> 0'. It is valid to set sections this way. eg: 11-22-63 [B005UR3VFO] by Stephen King - - Title = title; - StartOffset = TimeSpan.FromMilliseconds(startOffsetMs); - EndOffset = StartOffset + TimeSpan.FromMilliseconds(lengthMs); - } - public Chapter(string title, double startTimeSec, double endTimeSec) - : this(title, (long)(startTimeSec * 1000), (long)((endTimeSec - startTimeSec) * 1000)) - { - } - - public string ToFFMeta() - { - return "[CHAPTER]\n" + - "TIMEBASE=1/1000\n" + - "START=" + StartOffset.TotalMilliseconds + "\n" + - "END=" + EndOffset.TotalMilliseconds + "\n" + - "title=" + Title; - } - } -} diff --git a/AaxDecrypter/Cue.cs b/AaxDecrypter/Cue.cs index 8f492f66..44c6fc7c 100644 --- a/AaxDecrypter/Cue.cs +++ b/AaxDecrypter/Cue.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Text; +using AAXClean; using Dinah.Core; namespace AaxDecrypter diff --git a/AaxDecrypter/DownloadLicense.cs b/AaxDecrypter/DownloadLicense.cs index 633e8bf2..30e6cb3d 100644 --- a/AaxDecrypter/DownloadLicense.cs +++ b/AaxDecrypter/DownloadLicense.cs @@ -1,4 +1,5 @@ -using Dinah.Core; +using AAXClean; +using Dinah.Core; namespace AaxDecrypter { diff --git a/AaxDecrypter/FFMpegAaxcProcesser.cs b/AaxDecrypter/FFMpegAaxcProcesser.cs deleted file mode 100644 index 403eb87f..00000000 --- a/AaxDecrypter/FFMpegAaxcProcesser.cs +++ /dev/null @@ -1,254 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; - -namespace AaxDecrypter -{ - class AaxcProcessUpdate - { - public AaxcProcessUpdate(TimeSpan position, double speed) - { - ProcessPosition = position; - ProcessSpeed = speed; - EventTime = DateTime.Now; - } - public TimeSpan ProcessPosition { get; } - public double ProcessSpeed { get; } - public DateTime EventTime { get; } - } - - /// - /// Download audible aaxc, decrypt, and remux with chapters. - /// - class FFMpegAaxcProcesser - { - public event EventHandler ProgressUpdate; - public string FFMpegPath { get; } - public DownloadLicense DownloadLicense { get; } - public bool IsRunning { get; private set; } - public bool Succeeded { get; private set; } - public string FFMpegRemuxerStandardError => remuxerError.ToString(); - public string FFMpegDecrypterStandardError => decrypterError.ToString(); - - - private StringBuilder remuxerError { get; } = new StringBuilder(); - private StringBuilder decrypterError { get; } = new StringBuilder(); - private static Regex processedTimeRegex { get; } = new Regex("time=(\\d{2}):(\\d{2}):(\\d{2}).\\d{2}.*speed=\\s{0,1}([0-9]*[.]?[0-9]+)(?:e\\+([0-9]+)){0,1}", RegexOptions.IgnoreCase | RegexOptions.Compiled); - private Process decrypter; - private Process remuxer; - private Stream inputFile; - private bool isCanceled = false; - - public FFMpegAaxcProcesser(DownloadLicense downloadLicense) - { - FFMpegPath = DecryptSupportLibraries.ffmpegPath; - DownloadLicense = downloadLicense; - } - - public async Task ProcessBook(Stream inputFile, string outputFile, string ffmetaChaptersPath) - { - this.inputFile = inputFile; - - //This process gets the aaxc from the url and streams the decrypted - //aac stream to standard output - decrypter = new Process - { - StartInfo = getDownloaderStartInfo() - }; - - //This process retreves an aac stream from standard input and muxes - // it into an m4b along with the cover art and metadata. - remuxer = new Process - { - StartInfo = getRemuxerStartInfo(outputFile, ffmetaChaptersPath) - }; - - IsRunning = true; - - decrypter.ErrorDataReceived += Downloader_ErrorDataReceived; - decrypter.Start(); - decrypter.BeginErrorReadLine(); - - remuxer.ErrorDataReceived += Remuxer_ErrorDataReceived; - remuxer.Start(); - remuxer.BeginErrorReadLine(); - - //Thic check needs to be placed after remuxer has started. - if (isCanceled) return; - - var decrypterInput = decrypter.StandardInput.BaseStream; - var decrypterOutput = decrypter.StandardOutput.BaseStream; - var remuxerInput = remuxer.StandardInput.BaseStream; - - //Read inputFile into decrypter stdin in the background - var t = new Thread(() => CopyStream(inputFile, decrypterInput, decrypter)); - t.Start(); - - //All the work done here. Copy download standard output into - //remuxer standard input - await Task.Run(() => CopyStream(decrypterOutput, remuxerInput, remuxer)); - - //If the remuxer exited due to failure, downloader will still have - //data in the pipe. Force kill downloader to continue. - if (remuxer.HasExited && !decrypter.HasExited) - decrypter.Kill(); - - remuxer.WaitForExit(); - decrypter.WaitForExit(); - - IsRunning = false; - Succeeded = decrypter.ExitCode == 0 && remuxer.ExitCode == 0; - } - - private void CopyStream(Stream inputStream, Stream outputStream, Process returnOnProcExit) - { - try - { - byte[] buffer = new byte[32 * 1024]; - int lastRead; - do - { - lastRead = inputStream.Read(buffer, 0, buffer.Length); - outputStream.Write(buffer, 0, lastRead); - } while (lastRead > 0 && !returnOnProcExit.HasExited); - } - catch (IOException ex) - { - //There is no way to tell if the process closed the input stream - //before trying to write to it. If it did close, throws IOException. - isCanceled = true; - } - finally - { - outputStream.Close(); - } - } - - public void Cancel() - { - isCanceled = true; - - if (IsRunning && !remuxer.HasExited) - remuxer.Kill(); - if (IsRunning && !decrypter.HasExited) - decrypter.Kill(); - inputFile?.Close(); - } - private void Downloader_ErrorDataReceived(object sender, DataReceivedEventArgs e) - { - if (string.IsNullOrEmpty(e.Data)) - return; - - decrypterError.AppendLine(e.Data); - } - - private void Remuxer_ErrorDataReceived(object sender, DataReceivedEventArgs e) - { - if (string.IsNullOrEmpty(e.Data)) - return; - - remuxerError.AppendLine(e.Data); - - if (processedTimeRegex.IsMatch(e.Data)) - { - //get timestamp of of last processed audio stream position - //and processing speed - var match = processedTimeRegex.Match(e.Data); - - int hours = int.Parse(match.Groups[1].Value); - int minutes = int.Parse(match.Groups[2].Value); - int seconds = int.Parse(match.Groups[3].Value); - - var position = new TimeSpan(hours, minutes, seconds); - - double speed = double.Parse(match.Groups[4].Value); - int exp = match.Groups[5].Success ? int.Parse(match.Groups[5].Value) : 0; - speed *= Math.Pow(10, exp); - - ProgressUpdate?.Invoke(this, new AaxcProcessUpdate(position, speed)); - } - - if (e.Data.Contains("aac bitstream error")) - { - //This happens if input is corrupt (should never happen) or if caller - //supplied wrong key/iv - var process = sender as Process; - process.Kill(); - } - } - - private ProcessStartInfo getDownloaderStartInfo() => - new ProcessStartInfo - { - FileName = FFMpegPath, - RedirectStandardError = true, - RedirectStandardInput = true, - RedirectStandardOutput = true, - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden, - UseShellExecute = false, - WorkingDirectory = Path.GetDirectoryName(FFMpegPath), - ArgumentList ={ - "-audible_key", - DownloadLicense.AudibleKey, - "-audible_iv", - DownloadLicense.AudibleIV, - "-f", - "mp4", - "-i", - "pipe:", - "-c:a", //audio codec - "copy", //copy stream - "-f", //force output format: adts - "adts", - "pipe:" //pipe output to stdout - } - }; - - private ProcessStartInfo getRemuxerStartInfo(string outputFile, string ffmetaChaptersPath = null) - { - var startInfo = new ProcessStartInfo - { - FileName = FFMpegPath, - RedirectStandardError = true, - RedirectStandardInput = true, - CreateNoWindow = true, - WindowStyle = ProcessWindowStyle.Hidden, - UseShellExecute = false, - WorkingDirectory = Path.GetDirectoryName(FFMpegPath), - }; - - startInfo.ArgumentList.Add("-thread_queue_size"); - startInfo.ArgumentList.Add("1024"); - startInfo.ArgumentList.Add("-f"); //force input format: aac - startInfo.ArgumentList.Add("aac"); - startInfo.ArgumentList.Add("-i"); //read input from stdin - startInfo.ArgumentList.Add("pipe:"); - - //copy metadata from supplied metadata file - startInfo.ArgumentList.Add("-f"); - startInfo.ArgumentList.Add("ffmetadata"); - startInfo.ArgumentList.Add("-i"); - startInfo.ArgumentList.Add(ffmetaChaptersPath); - - startInfo.ArgumentList.Add("-map"); //map file 0 (aac audio stream) - startInfo.ArgumentList.Add("0"); - startInfo.ArgumentList.Add("-map_chapters"); //copy chapter data from file metadata file - startInfo.ArgumentList.Add("1"); - startInfo.ArgumentList.Add("-c"); //copy all mapped streams - startInfo.ArgumentList.Add("copy"); - startInfo.ArgumentList.Add("-f"); //force output format: mp4 - startInfo.ArgumentList.Add("mp4"); - startInfo.ArgumentList.Add("-movflags"); - startInfo.ArgumentList.Add("disable_chpl"); //Disable Nero chapters format - startInfo.ArgumentList.Add(outputFile); - startInfo.ArgumentList.Add("-y"); //overwrite existing - - return startInfo; - } - } -} \ No newline at end of file diff --git a/AaxDecrypter/NFO.cs b/AaxDecrypter/NFO.cs index f2bcd8c2..64f601fd 100644 --- a/AaxDecrypter/NFO.cs +++ b/AaxDecrypter/NFO.cs @@ -1,27 +1,29 @@ - +using AAXClean; +using Dinah.Core; + namespace AaxDecrypter { public static class NFO { - public static string CreateContents(string ripper, AaxcTagLibFile aaxcTagLib, ChapterInfo chapters) + public static string CreateContents(string ripper, AAXClean.Mp4File aaxcTagLib, ChapterInfo chapters) { - var _hours = (int)aaxcTagLib.Properties.Duration.TotalHours; + var _hours = (int)aaxcTagLib.Duration.TotalHours; var myDuration = (_hours > 0 ? _hours + " hours, " : string.Empty) - + aaxcTagLib.Properties.Duration.Minutes + " minutes, " - + aaxcTagLib.Properties.Duration.Seconds + " seconds"; + + aaxcTagLib.Duration.Minutes + " minutes, " + + aaxcTagLib.Duration.Seconds + " seconds"; var nfoString = "General Information\r\n" + "======================\r\n" - + $" Title: {aaxcTagLib.AsciiTitleSansUnabridged ?? "[unknown]"}\r\n" - + $" Author: {aaxcTagLib.AsciiFirstAuthor ?? "[unknown]"}\r\n" - + $" Read By: {aaxcTagLib.AsciiNarrator ?? "[unknown]"}\r\n" - + $" Release Date: {aaxcTagLib.ReleaseDate ?? "[unknown]"}\r\n" - + $" Book Copyright: {aaxcTagLib.BookCopyright ?? "[unknown]"}\r\n" - + $" Recording Copyright: {aaxcTagLib.RecordingCopyright ?? "[unknown]"}\r\n" - + $" Genre: {aaxcTagLib.AppleTags.FirstGenre ?? "[unknown]"}\r\n" - + $" Publisher: {aaxcTagLib.Publisher ?? "[unknown]"}\r\n" + + $" Title: {aaxcTagLib.AppleTags.TitleSansUnabridged?.UnicodeToAscii() ?? "[unknown]"}\r\n" + + $" Author: {aaxcTagLib.AppleTags.FirstAuthor?.UnicodeToAscii() ?? "[unknown]"}\r\n" + + $" Read By: {aaxcTagLib.AppleTags.Narrator?.UnicodeToAscii() ?? "[unknown]"}\r\n" + + $" Release Date: {aaxcTagLib.AppleTags.ReleaseDate ?? "[unknown]"}\r\n" + + $" Book Copyright: {aaxcTagLib.AppleTags.BookCopyright ?? "[unknown]"}\r\n" + + $" Recording Copyright: {aaxcTagLib.AppleTags.RecordingCopyright ?? "[unknown]"}\r\n" + + $" Genre: {aaxcTagLib.AppleTags.Generes ?? "[unknown]"}\r\n" + + $" Publisher: {aaxcTagLib.AppleTags.Publisher ?? "[unknown]"}\r\n" + $" Duration: {myDuration}\r\n" + $" Chapters: {chapters.Count}\r\n" + "\r\n" @@ -29,22 +31,22 @@ namespace AaxDecrypter + "Media Information\r\n" + "======================\r\n" + " Source Format: Audible AAX\r\n" - + $" Source Sample Rate: {aaxcTagLib.Properties.AudioSampleRate} Hz\r\n" - + $" Source Channels: {aaxcTagLib.Properties.AudioChannels}\r\n" - + $" Source Bitrate: {aaxcTagLib.Properties.AudioBitrate} Kbps\r\n" + + $" Source Sample Rate: {aaxcTagLib.TimeScale} Hz\r\n" + + $" Source Channels: {aaxcTagLib.AudioChannels}\r\n" + + $" Source Bitrate: {aaxcTagLib.AverageBitrate} Kbps\r\n" + "\r\n" + " Lossless Encode: Yes\r\n" + " Encoded Codec: AAC / M4B\r\n" - + $" Encoded Sample Rate: {aaxcTagLib.Properties.AudioSampleRate} Hz\r\n" - + $" Encoded Channels: {aaxcTagLib.Properties.AudioChannels}\r\n" - + $" Encoded Bitrate: {aaxcTagLib.Properties.AudioBitrate} Kbps\r\n" + + $" Encoded Sample Rate: {aaxcTagLib.TimeScale} Hz\r\n" + + $" Encoded Channels: {aaxcTagLib.AudioChannels}\r\n" + + $" Encoded Bitrate: {aaxcTagLib.AverageBitrate} Kbps\r\n" + "\r\n" + $" Ripper: {ripper}\r\n" + "\r\n" + "\r\n" + "Book Description\r\n" + "================\r\n" - + (!string.IsNullOrWhiteSpace(aaxcTagLib.LongDescription) ? aaxcTagLib.AsciiLongDescription : aaxcTagLib.AsciiComment); + + (!string.IsNullOrWhiteSpace(aaxcTagLib.AppleTags.LongDescription) ? aaxcTagLib.AppleTags.LongDescription.UnicodeToAscii() : aaxcTagLib.AppleTags.Comment?.UnicodeToAscii()); return nfoString; } diff --git a/AaxDecrypter/NetworkFileStream.cs b/AaxDecrypter/NetworkFileStream.cs index 362d5805..f3d6091b 100644 --- a/AaxDecrypter/NetworkFileStream.cs +++ b/AaxDecrypter/NetworkFileStream.cs @@ -176,7 +176,7 @@ namespace AaxDecrypter /// /// Begins downloading to in a background thread. /// - private void BeginDownloading() + public void BeginDownloading() { if (ContentLength != 0 && WritePosition == ContentLength) { @@ -393,7 +393,7 @@ namespace AaxDecrypter //read operation will block until file contains enough data //to fulfil the request, or until cancelled. while (requiredPosition > WritePosition && !isCancelled) - Thread.Sleep(0); + Thread.Sleep(2); return _readFile.Read(buffer, offset, count); } diff --git a/FileLiberator/DownloadDecryptBook.cs b/FileLiberator/DownloadDecryptBook.cs index 9a9270d7..fe88d1a2 100644 --- a/FileLiberator/DownloadDecryptBook.cs +++ b/FileLiberator/DownloadDecryptBook.cs @@ -81,15 +81,10 @@ namespace FileLiberator if (Configuration.Instance.AllowLibationFixup) { - aaxcDecryptDlLic.ChapterInfo = new ChapterInfo(); + aaxcDecryptDlLic.ChapterInfo = new AAXClean.ChapterInfo(); foreach (var chap in contentLic.ContentMetadata?.ChapterInfo?.Chapters) - aaxcDecryptDlLic.ChapterInfo.AddChapter( - new Chapter( - chap.Title, - chap.StartOffsetMs, - chap.LengthMs - )); + aaxcDecryptDlLic.ChapterInfo.AddChapter(chap.Title, TimeSpan.FromMilliseconds(chap.LengthMs)); } aaxcDownloader = AaxcDownloadConverter.Create(cacheDir, destinationDir, aaxcDecryptDlLic); @@ -132,7 +127,7 @@ namespace FileLiberator } } - private void aaxcDownloader_RetrievedTags(object sender, AaxcTagLibFile e) + private void aaxcDownloader_RetrievedTags(object sender, AAXClean.AppleTags e) { TitleDiscovered?.Invoke(this, e.TitleSansUnabridged); AuthorsDiscovered?.Invoke(this, e.FirstAuthor ?? "[unknown]"); diff --git a/Libation.sln b/Libation.sln index 180dc899..9de9110b 100644 --- a/Libation.sln +++ b/Libation.sln @@ -86,6 +86,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationSearchEngine.Tests" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dinah.EntityFrameworkCore.Tests", "..\Dinah.Core\_Tests\Dinah.EntityFrameworkCore.Tests\Dinah.EntityFrameworkCore.Tests.csproj", "{6F5131A0-09AE-4707-B82B-5E53CB74688E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAXClean", "..\AAXClean\AAXClean.csproj", "{94BEB7CC-511D-45AB-9F09-09BE858EE486}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -208,6 +210,10 @@ Global {6F5131A0-09AE-4707-B82B-5E53CB74688E}.Debug|Any CPU.Build.0 = Debug|Any CPU {6F5131A0-09AE-4707-B82B-5E53CB74688E}.Release|Any CPU.ActiveCfg = Release|Any CPU {6F5131A0-09AE-4707-B82B-5E53CB74688E}.Release|Any CPU.Build.0 = Release|Any CPU + {94BEB7CC-511D-45AB-9F09-09BE858EE486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94BEB7CC-511D-45AB-9F09-09BE858EE486}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94BEB7CC-511D-45AB-9F09-09BE858EE486}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94BEB7CC-511D-45AB-9F09-09BE858EE486}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -242,6 +248,7 @@ Global {8447C956-B03E-4F59-9DD4-877793B849D9} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53} {C5B21768-C7C9-4FCB-AC1E-187B223D5A98} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53} {6F5131A0-09AE-4707-B82B-5E53CB74688E} = {38E6C6D9-963A-4C5B-89F4-F2F14885ADFD} + {94BEB7CC-511D-45AB-9F09-09BE858EE486} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {615E00ED-BAEF-4E8E-A92A-9B82D87942A9} diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index de6ea0c6..a7f2f9f0 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.1.6.6 + 5.1.6.74