diff --git a/AaxDecrypter/AaxcDownloadConverter.cs b/AaxDecrypter/AaxcDownloadConverter.cs index c91429ba..0fa7606b 100644 --- a/AaxDecrypter/AaxcDownloadConverter.cs +++ b/AaxDecrypter/AaxcDownloadConverter.cs @@ -1,5 +1,4 @@ using AAXClean; -using Dinah.Core; using Dinah.Core.IO; using Dinah.Core.Net.Http; using Dinah.Core.StepRunner; @@ -8,52 +7,25 @@ using System.IO; namespace AaxDecrypter { - public enum OutputFormat { Mp4a, Mp3 } - public class AaxcDownloadConverter + public class AaxcDownloadConverter : AudiobookDownloadBase { - public event EventHandler RetrievedTags; - public event EventHandler RetrievedCoverArt; - public event EventHandler DecryptProgressUpdate; - public event EventHandler DecryptTimeRemaining; + protected override StepSequence steps { get; } - public string AppName { get; set; } = nameof(AaxcDownloadConverter); - - private string outputFileName { get; } - private string cacheDir { get; } - private DownloadLicense downloadLicense { get; } private AaxFile aaxFile; - private OutputFormat OutputFormat; - private StepSequence steps { get; } - 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"); + private OutputFormat OutputFormat { get; } public AaxcDownloadConverter(string outFileName, string cacheDirectory, DownloadLicense dlLic, OutputFormat outputFormat) + :base(outFileName, cacheDirectory, dlLic) { - ArgumentValidator.EnsureNotNullOrWhiteSpace(outFileName, nameof(outFileName)); - outputFileName = outFileName; - - var outDir = Path.GetDirectoryName(outputFileName); - if (!Directory.Exists(outDir)) - throw new ArgumentNullException(nameof(outDir), "Directory does not exist"); - if (File.Exists(outputFileName)) - File.Delete(outputFileName); - - if (!Directory.Exists(cacheDirectory)) - throw new ArgumentNullException(nameof(cacheDirectory), "Directory does not exist"); - cacheDir = cacheDirectory; - - downloadLicense = ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic)); OutputFormat = outputFormat; steps = new StepSequence { - Name = "Download and Convert Aaxc To " + (outputFormat == OutputFormat.Mp4a ? "M4b" : "Mp3"), + Name = "Download and Convert Aaxc To " + OutputFormat, ["Step 1: Get Aaxc Metadata"] = Step1_GetMetadata, - ["Step 2: Download Decrypted Audiobook"] = Step2_DownloadAndCombine, + ["Step 2: Download Decrypted Audiobook"] = Step2_DownloadAudiobook, ["Step 3: Create Cue"] = Step3_CreateCue, ["Step 4: Cleanup"] = Step4_Cleanup, }; @@ -62,102 +34,56 @@ namespace AaxDecrypter /// /// Setting cover art by this method will insert the art into the audiobook metadata /// - public void SetCoverArt(byte[] coverArt) + public override void SetCoverArt(byte[] coverArt) { - if (coverArt is null) return; + base.SetCoverArt(coverArt); aaxFile?.AppleTags.SetCoverArt(coverArt); - - RetrievedCoverArt?.Invoke(this, coverArt); } - public bool Run() - { - var (IsSuccess, Elapsed) = steps.Run(); + protected override bool Step1_GetMetadata() + { + aaxFile = new AaxFile(InputFileStream); - if (!IsSuccess) - { - Console.WriteLine("WARNING-Conversion failed"); - return false; - } - - var speedup = (int)(aaxFile.Duration.TotalSeconds / (long)Elapsed.TotalSeconds); - Serilog.Log.Logger.Information($"Speedup is {speedup}x realtime."); - return true; - } - - public bool Step1_GetMetadata() - { - //Get metadata from the file over http - - if (File.Exists(jsonDownloadState)) - { - try - { - nfsPersister = new NetworkFileStreamPersister(jsonDownloadState); - //If More than ~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)); - } - catch - { - FileExt.SafeDelete(jsonDownloadState); - FileExt.SafeDelete(tempFile); - nfsPersister = NewNetworkFilePersister(); - } - } - else - { - nfsPersister = NewNetworkFilePersister(); - } - - aaxFile = new AaxFile(nfsPersister.NetworkFileStream); - - RetrievedTags?.Invoke(this, aaxFile.AppleTags); - RetrievedCoverArt?.Invoke(this, aaxFile.AppleTags.Cover); + OnRetrievedTitle(aaxFile.AppleTags.TitleSansUnabridged); + OnRetrievedAuthors(aaxFile.AppleTags.FirstAuthor ?? "[unknown]"); + OnRetrievedNarrators(aaxFile.AppleTags.Narrator ?? "[unknown]"); + OnRetrievedCoverArt(aaxFile.AppleTags.Cover); return !isCanceled; } - private NetworkFileStreamPersister NewNetworkFilePersister() - { - var headers = new System.Net.WebHeaderCollection - { - { "User-Agent", downloadLicense.UserAgent } - }; - var networkFileStream = new NetworkFileStream(tempFile, new Uri(downloadLicense.DownloadUrl), 0, headers); - return new NetworkFileStreamPersister(networkFileStream, jsonDownloadState); - } - - public bool Step2_DownloadAndCombine() + protected override bool Step2_DownloadAudiobook() { var zeroProgress = new DownloadProgress { BytesReceived = 0, ProgressPercentage = 0, - TotalBytesToReceive = nfsPersister.NetworkFileStream.Length + TotalBytesToReceive = InputFileStream.Length }; - DecryptProgressUpdate?.Invoke(this, zeroProgress); + OnDecryptProgressUpdate(zeroProgress); + + + aaxFile.SetDecryptionKey(downloadLicense.AudibleKey, downloadLicense.AudibleIV); + if (File.Exists(outputFileName)) FileExt.SafeDelete(outputFileName); - FileStream outFile = File.Open(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite); - - aaxFile.SetDecryptionKey(downloadLicense.AudibleKey, downloadLicense.AudibleIV); + var outputFile = File.Open(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite); aaxFile.ConversionProgressUpdate += AaxFile_ConversionProgressUpdate; - var decryptionResult = OutputFormat == OutputFormat.Mp4a ? aaxFile.ConvertToMp4a(outFile, downloadLicense.ChapterInfo) : aaxFile.ConvertToMp3(outFile); + var decryptionResult = OutputFormat == OutputFormat.M4b ? aaxFile.ConvertToMp4a(outputFile, downloadLicense.ChapterInfo) : aaxFile.ConvertToMp3(outputFile); aaxFile.ConversionProgressUpdate -= AaxFile_ConversionProgressUpdate; aaxFile.Close(); downloadLicense.ChapterInfo = aaxFile.Chapters; - nfsPersister.Dispose(); + CloseInputFileStream(); - DecryptProgressUpdate?.Invoke(this, zeroProgress); + OnDecryptProgressUpdate(zeroProgress); return decryptionResult == ConversionResult.NoErrorsDetected && !isCanceled; } @@ -169,47 +95,28 @@ namespace AaxDecrypter double estTimeRemaining = remainingSecsToProcess / e.ProcessSpeed; if (double.IsNormal(estTimeRemaining)) - DecryptTimeRemaining?.Invoke(this, TimeSpan.FromSeconds(estTimeRemaining)); + OnDecryptTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining)); - double progressPercent = 100 * e.ProcessPosition.TotalSeconds / duration.TotalSeconds; + double progressPercent = e.ProcessPosition.TotalSeconds / duration.TotalSeconds; - DecryptProgressUpdate?.Invoke(this, + OnDecryptProgressUpdate( new DownloadProgress { - ProgressPercentage = progressPercent, - BytesReceived = (long)(nfsPersister.NetworkFileStream.Length * progressPercent), - TotalBytesToReceive = nfsPersister.NetworkFileStream.Length + ProgressPercentage = 100 * progressPercent, + BytesReceived = (long)(InputFileStream.Length * progressPercent), + TotalBytesToReceive = InputFileStream.Length }); } - public bool Step3_CreateCue() - { - // not a critical step. its failure should not prevent future steps from running - try - { - File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".cue"), Cue.CreateContents(Path.GetFileName(outputFileName), downloadLicense.ChapterInfo)); - } - catch (Exception ex) - { - Serilog.Log.Logger.Error(ex, $"{nameof(Step3_CreateCue)}. FAILED"); - } - return !isCanceled; - } - - public bool Step4_Cleanup() - { - FileExt.SafeDelete(jsonDownloadState); - FileExt.SafeDelete(tempFile); - return !isCanceled; - } - - public void Cancel() + public override void Cancel() { isCanceled = true; aaxFile?.Cancel(); aaxFile?.Dispose(); - nfsPersister?.NetworkFileStream?.Close(); - nfsPersister?.Dispose(); + CloseInputFileStream(); } - } + + protected override int GetSpeedup(TimeSpan elapsed) + => (int)(aaxFile.Duration.TotalSeconds / (long)elapsed.TotalSeconds); + } } diff --git a/AaxDecrypter/AudiobookDownloadBase.cs b/AaxDecrypter/AudiobookDownloadBase.cs new file mode 100644 index 00000000..7a2425d4 --- /dev/null +++ b/AaxDecrypter/AudiobookDownloadBase.cs @@ -0,0 +1,165 @@ +using Dinah.Core; +using Dinah.Core.IO; +using Dinah.Core.Net.Http; +using Dinah.Core.StepRunner; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AaxDecrypter +{ + public enum OutputFormat { M4b, Mp3 } + + public abstract class AudiobookDownloadBase + { + public event EventHandler RetrievedTitle; + public event EventHandler RetrievedAuthors; + public event EventHandler RetrievedNarrators; + public event EventHandler RetrievedCoverArt; + public event EventHandler DecryptProgressUpdate; + public event EventHandler DecryptTimeRemaining; + + public string AppName { get; set; } + + protected bool isCanceled { get; set; } + protected string outputFileName { get; } + protected string cacheDir { get; } + protected DownloadLicense downloadLicense { get; } + protected NetworkFileStream InputFileStream => (nfsPersister ??= OpenNetworkFileStream()).NetworkFileStream; + + + protected abstract StepSequence steps { get; } + private NetworkFileStreamPersister nfsPersister; + + private string jsonDownloadState => Path.Combine(cacheDir, Path.GetFileNameWithoutExtension(outputFileName) + ".json"); + private string tempFile => PathLib.ReplaceExtension(jsonDownloadState, ".tmp"); + + public AudiobookDownloadBase(string outFileName, string cacheDirectory, DownloadLicense dlLic) + { + AppName = GetType().Name; + + ArgumentValidator.EnsureNotNullOrWhiteSpace(outFileName, nameof(outFileName)); + outputFileName = outFileName; + + var outDir = Path.GetDirectoryName(outputFileName); + if (!Directory.Exists(outDir)) + throw new ArgumentNullException(nameof(outDir), "Directory does not exist"); + if (File.Exists(outputFileName)) + File.Delete(outputFileName); + + if (!Directory.Exists(cacheDirectory)) + throw new ArgumentNullException(nameof(cacheDirectory), "Directory does not exist"); + cacheDir = cacheDirectory; + + downloadLicense = ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic)); + } + + public abstract void Cancel(); + protected abstract int GetSpeedup(TimeSpan elapsed); + protected abstract bool Step2_DownloadAudiobook(); + protected abstract bool Step1_GetMetadata(); + + public virtual void SetCoverArt(byte[] coverArt) + { + if (coverArt is null) return; + + OnRetrievedCoverArt(coverArt); + } + + + public bool Run() + { + var (IsSuccess, Elapsed) = steps.Run(); + + if (!IsSuccess) + { + Console.WriteLine("WARNING-Conversion failed"); + return false; + } + + Serilog.Log.Logger.Information($"Speedup is {GetSpeedup(Elapsed)}x realtime."); + return true; + } + + protected void OnRetrievedTitle(string title) + => RetrievedTitle?.Invoke(this, title); + protected void OnRetrievedAuthors(string authors) + => RetrievedAuthors?.Invoke(this, authors); + protected void OnRetrievedNarrators(string narrators) + => RetrievedNarrators?.Invoke(this, narrators); + protected void OnRetrievedCoverArt(byte[] coverArt) + => RetrievedCoverArt?.Invoke(this, coverArt); + protected void OnDecryptProgressUpdate(DownloadProgress downloadProgress) + => DecryptProgressUpdate?.Invoke(this, downloadProgress); + protected void OnDecryptTimeRemaining(TimeSpan timeRemaining) + => DecryptTimeRemaining?.Invoke(this, timeRemaining); + + protected void CloseInputFileStream() + { + nfsPersister?.NetworkFileStream?.Close(); + nfsPersister?.Dispose(); + } + + protected bool Step3_CreateCue() + { + // not a critical step. its failure should not prevent future steps from running + try + { + File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".cue"), Cue.CreateContents(Path.GetFileName(outputFileName), downloadLicense.ChapterInfo)); + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, $"{nameof(Step3_CreateCue)}. FAILED"); + } + return !isCanceled; + } + + protected bool Step4_Cleanup() + { + FileExt.SafeDelete(jsonDownloadState); + FileExt.SafeDelete(tempFile); + return !isCanceled; + } + + private NetworkFileStreamPersister OpenNetworkFileStream() + { + NetworkFileStreamPersister nfsp; + + if (File.Exists(jsonDownloadState)) + { + try + { + nfsp = new NetworkFileStreamPersister(jsonDownloadState); + //If More than ~1 hour has elapsed since getting the download url, it will expire. + //The new url will be to the same file. + nfsp.NetworkFileStream.SetUriForSameFile(new Uri(downloadLicense.DownloadUrl)); + } + catch + { + FileExt.SafeDelete(jsonDownloadState); + FileExt.SafeDelete(tempFile); + nfsp = NewNetworkFilePersister(); + } + } + else + { + nfsp = NewNetworkFilePersister(); + } + return nfsp; + } + + private NetworkFileStreamPersister NewNetworkFilePersister() + { + var headers = new System.Net.WebHeaderCollection + { + { "User-Agent", downloadLicense.UserAgent } + }; + + var networkFileStream = new NetworkFileStream(tempFile, new Uri(downloadLicense.DownloadUrl), 0, headers); + return new NetworkFileStreamPersister(networkFileStream, jsonDownloadState); + } + } +} diff --git a/AaxDecrypter/NetworkFileStream.cs b/AaxDecrypter/NetworkFileStream.cs index 01b63781..d2314b11 100644 --- a/AaxDecrypter/NetworkFileStream.cs +++ b/AaxDecrypter/NetworkFileStream.cs @@ -83,7 +83,7 @@ namespace AaxDecrypter private FileStream _readFile { get; } private Stream _networkStream { get; set; } private bool hasBegunDownloading { get; set; } - private bool isCancelled { get; set; } + public bool IsCancelled { get; private set; } private EventWaitHandle downloadEnded { get; set; } private EventWaitHandle downloadedPiece { get; set; } @@ -238,7 +238,7 @@ namespace AaxDecrypter downloadedPiece.Set(); } - } while (downloadPosition < ContentLength && !isCancelled); + } while (downloadPosition < ContentLength && !IsCancelled); _writeFile.Close(); _networkStream.Close(); @@ -248,7 +248,7 @@ namespace AaxDecrypter downloadedPiece.Set(); downloadEnded.Set(); - if (!isCancelled && WritePosition < ContentLength) + if (!IsCancelled && WritePosition < ContentLength) throw new WebException($"Downloaded size (0x{WritePosition:X10}) is less than {nameof(ContentLength)} (0x{ContentLength:X10})."); if (WritePosition > ContentLength) @@ -421,12 +421,12 @@ namespace AaxDecrypter /// The minimum required flished data length in . private void WaitToPosition(long requiredPosition) { - while (requiredPosition > WritePosition && !isCancelled && hasBegunDownloading && !downloadedPiece.WaitOne(1000)) ; + while (requiredPosition > WritePosition && !IsCancelled && hasBegunDownloading && !downloadedPiece.WaitOne(1000)) ; } public override void Close() { - isCancelled = true; + IsCancelled = true; while (downloadEnded is not null && !downloadEnded.WaitOne(1000)) ; diff --git a/AaxDecrypter/UnencryptedAudiobookDownloader.cs b/AaxDecrypter/UnencryptedAudiobookDownloader.cs new file mode 100644 index 00000000..b12ade35 --- /dev/null +++ b/AaxDecrypter/UnencryptedAudiobookDownloader.cs @@ -0,0 +1,86 @@ +using Dinah.Core.IO; +using Dinah.Core.Net.Http; +using Dinah.Core.StepRunner; +using System; +using System.IO; +using System.Linq; +using System.Threading; + +namespace AaxDecrypter +{ + public class UnencryptedAudiobookDownloader : AudiobookDownloadBase + { + protected override StepSequence steps { get; } + + public UnencryptedAudiobookDownloader(string outFileName, string cacheDirectory, DownloadLicense dlLic) + : base(outFileName, cacheDirectory, dlLic) + { + + steps = new StepSequence + { + Name = "Download Mp3 Audiobook", + + ["Step 1: Get Mp3 Metadata"] = Step1_GetMetadata, + ["Step 2: Download Audiobook"] = Step2_DownloadAudiobook, + ["Step 3: Create Cue"] = Step3_CreateCue, + ["Step 4: Cleanup"] = Step4_Cleanup, + }; + } + + public override void Cancel() + { + isCanceled = true; + CloseInputFileStream(); + } + + protected override int GetSpeedup(TimeSpan elapsed) + { + //Not implemented + return 0; + } + + protected override bool Step1_GetMetadata() + { + OnRetrievedCoverArt(null); + + return !isCanceled; + } + + protected override bool Step2_DownloadAudiobook() + { + DateTime startTime = DateTime.Now; + + //MUST put InputFileStream.Length first, because it starts background downloader. + + while (InputFileStream.Length > InputFileStream.WritePosition && !InputFileStream.IsCancelled) + { + var rate = InputFileStream.WritePosition / (DateTime.Now - startTime).TotalSeconds; + + var estTimeRemaining = (InputFileStream.Length - InputFileStream.WritePosition) / rate; + + if (double.IsNormal(estTimeRemaining)) + OnDecryptTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining)); + + var progressPercent = (double)InputFileStream.WritePosition / InputFileStream.Length; + + OnDecryptProgressUpdate( + new DownloadProgress + { + ProgressPercentage = 100 * progressPercent, + BytesReceived = (long)(InputFileStream.Length * progressPercent), + TotalBytesToReceive = InputFileStream.Length + }); + Thread.Sleep(200); + } + + CloseInputFileStream(); + + if (File.Exists(outputFileName)) + FileExt.SafeDelete(outputFileName); + + FileExt.SafeMove(InputFileStream.SaveFilePath, outputFileName); + + return !isCanceled; + } + } +} diff --git a/FileLiberator/ConvertToMp3.cs b/FileLiberator/ConvertToMp3.cs index 07da90bf..ab99927a 100644 --- a/FileLiberator/ConvertToMp3.cs +++ b/FileLiberator/ConvertToMp3.cs @@ -29,21 +29,6 @@ namespace FileLiberator public event EventHandler StatusUpdate; public event EventHandler Completed; - public ConvertToMp3() - { - RequestCoverArt += (o, e) => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(RequestCoverArt) }); - TitleDiscovered += (o, e) => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(TitleDiscovered), Title = e }); - AuthorsDiscovered += (o, e) => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(AuthorsDiscovered), Authors = e }); - NarratorsDiscovered += (o, e) => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(NarratorsDiscovered), Narrators = e }); - CoverImageDiscovered += (o, e) => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(CoverImageDiscovered), CoverImageBytes = e?.Length }); - - StreamingBegin += (o, e) => Serilog.Log.Logger.Information("Event fired {@DebugInfo}", new { Name = nameof(StreamingBegin), Message = e }); - StreamingCompleted += (o, e) => Serilog.Log.Logger.Information("Event fired {@DebugInfo}", new { Name = nameof(StreamingCompleted), Message = e }); - - Begin += (o, e) => Serilog.Log.Logger.Information("Event fired {@DebugInfo}", new { Name = nameof(Begin), Book = e.LogFriendly() }); - Completed += (o, e) => Serilog.Log.Logger.Information("Event fired {@DebugInfo}", new { Name = nameof(Completed), Book = e.LogFriendly() }); - } - private long fileSize; private string Mp3FileName(string m4bPath) => m4bPath is null ? string.Empty : PathLib.ReplaceExtension(m4bPath, ".mp3"); diff --git a/FileLiberator/DownloadDecryptBook.cs b/FileLiberator/DownloadDecryptBook.cs index 98d87b2a..4843ecd1 100644 --- a/FileLiberator/DownloadDecryptBook.cs +++ b/FileLiberator/DownloadDecryptBook.cs @@ -15,7 +15,7 @@ namespace FileLiberator { public class DownloadDecryptBook : IAudioDecodable { - private AaxcDownloadConverter aaxcDownloader; + private AudiobookDownloadBase aaxcDownloader; public event EventHandler StreamingTimeRemaining; public event EventHandler> RequestCoverArt; @@ -30,21 +30,6 @@ namespace FileLiberator public event EventHandler StatusUpdate; public event EventHandler Completed; - public DownloadDecryptBook() - { - RequestCoverArt += (o, e) => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(RequestCoverArt) }); - TitleDiscovered += (o, e) => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(TitleDiscovered), Title = e }); - AuthorsDiscovered += (o, e) => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(AuthorsDiscovered), Authors = e }); - NarratorsDiscovered += (o, e) => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(NarratorsDiscovered), Narrators = e }); - CoverImageDiscovered += (o, e) => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(CoverImageDiscovered), CoverImageBytes = e?.Length }); - - StreamingBegin += (o, e) => Serilog.Log.Logger.Information("Event fired {@DebugInfo}", new { Name = nameof(StreamingBegin), Message = e }); - StreamingCompleted += (o, e) => Serilog.Log.Logger.Information("Event fired {@DebugInfo}", new { Name = nameof(StreamingCompleted), Message = e }); - - Begin += (o, e) => Serilog.Log.Logger.Information("Event fired {@DebugInfo}", new { Name = nameof(Begin), Book = e.LogFriendly() }); - Completed += (o, e) => Serilog.Log.Logger.Information("Event fired {@DebugInfo}", new { Name = nameof(Completed), Book = e.LogFriendly() }); - } - public async Task ProcessAsync(LibraryBook libraryBook) { Begin?.Invoke(this, libraryBook); @@ -54,7 +39,7 @@ namespace FileLiberator if (libraryBook.Book.Audio_Exists) return new StatusHandler { "Cannot find decrypt. Final audio file already exists" }; - var outputAudioFilename = await aaxToM4bConverterDecryptAsync(AudibleFileStorage.DownloadsInProgress, AudibleFileStorage.DecryptInProgress, libraryBook); + var outputAudioFilename = await downloadAudiobookAsync(AudibleFileStorage.DownloadsInProgress, AudibleFileStorage.DecryptInProgress, libraryBook); // decrypt failed if (outputAudioFilename is null) @@ -76,7 +61,7 @@ namespace FileLiberator } } - private async Task aaxToM4bConverterDecryptAsync(string cacheDir, string destinationDir, LibraryBook libraryBook) + private async Task downloadAudiobookAsync(string cacheDir, string destinationDir, LibraryBook libraryBook) { StreamingBegin?.Invoke(this, $"Begin decrypting {libraryBook}"); @@ -87,7 +72,7 @@ namespace FileLiberator var api = await libraryBook.GetApiAsync(); var contentLic = await api.GetDownloadLicenseAsync(libraryBook.Book.AudibleProductId); - var aaxcDecryptDlLic = new DownloadLicense + var audiobookDlLic = new DownloadLicense ( contentLic?.ContentMetadata?.ContentUrl?.OfflineUrl, contentLic?.Voucher?.Key, @@ -95,32 +80,31 @@ namespace FileLiberator Resources.USER_AGENT ); - if (Configuration.Instance.AllowLibationFixup) + //I assume if ContentFormat == "MPEG" that the delivered file is an unencrypted mp3. + //I also assume that if DrmType != Adrm, the file will be an mp3. + //These assumptions may be wrong, and only time and bug reports will tell. + var outputFormat = + contentLic.ContentMetadata.ContentReference.ContentFormat == "MPEG" || + (Configuration.Instance.AllowLibationFixup && Configuration.Instance.DecryptToLossy) ? + OutputFormat.Mp3 : OutputFormat.M4b; + + if (Configuration.Instance.AllowLibationFixup || outputFormat == OutputFormat.Mp3) { - aaxcDecryptDlLic.ChapterInfo = new AAXClean.ChapterInfo(); + audiobookDlLic.ChapterInfo = new AAXClean.ChapterInfo(); foreach (var chap in contentLic.ContentMetadata?.ChapterInfo?.Chapters) - aaxcDecryptDlLic.ChapterInfo.AddChapter(chap.Title, TimeSpan.FromMilliseconds(chap.LengthMs)); + audiobookDlLic.ChapterInfo.AddChapter(chap.Title, TimeSpan.FromMilliseconds(chap.LengthMs)); } + var outFileName = Path.Combine(destinationDir, $"{PathLib.ToPathSafeString(libraryBook.Book.Title)} [{libraryBook.Book.AudibleProductId}].{outputFormat.ToString().ToLower()}"); - var format = Configuration.Instance.DecryptToLossy ? OutputFormat.Mp3 : OutputFormat.Mp4a; - - var extension = format switch - { - OutputFormat.Mp4a => "m4b", - OutputFormat.Mp3 => "mp3", - _ => throw new NotImplementedException(), - }; - - var outFileName = Path.Combine(destinationDir, $"{PathLib.ToPathSafeString(libraryBook.Book.Title)} [{libraryBook.Book.AudibleProductId}].{extension}"); - - - aaxcDownloader = new AaxcDownloadConverter(outFileName, cacheDir, aaxcDecryptDlLic, format) { AppName = "Libation" }; + aaxcDownloader = contentLic.DrmType == AudibleApi.Common.DrmType.Adrm ? new AaxcDownloadConverter(outFileName, cacheDir, audiobookDlLic, outputFormat) { AppName = "Libation" } : new UnencryptedAudiobookDownloader(outFileName, cacheDir, audiobookDlLic); aaxcDownloader.DecryptProgressUpdate += (s, progress) => StreamingProgressChanged?.Invoke(this, progress); aaxcDownloader.DecryptTimeRemaining += (s, remaining) => StreamingTimeRemaining?.Invoke(this, remaining); + aaxcDownloader.RetrievedTitle += (s, title) => TitleDiscovered?.Invoke(this, title); + aaxcDownloader.RetrievedAuthors += (s, authors) => AuthorsDiscovered?.Invoke(this, authors); + aaxcDownloader.RetrievedNarrators += (s, narrators) => NarratorsDiscovered?.Invoke(this, narrators); aaxcDownloader.RetrievedCoverArt += AaxcDownloader_RetrievedCoverArt; - aaxcDownloader.RetrievedTags += aaxcDownloader_RetrievedTags; // REAL WORK DONE HERE var success = await Task.Run(() => aaxcDownloader.Run()); @@ -137,7 +121,6 @@ namespace FileLiberator } } - private void AaxcDownloader_RetrievedCoverArt(object sender, byte[] e) { if (e is null && Configuration.Instance.AllowLibationFixup) @@ -151,13 +134,6 @@ namespace FileLiberator } } - private void aaxcDownloader_RetrievedTags(object sender, AAXClean.AppleTags e) - { - TitleDiscovered?.Invoke(this, e.TitleSansUnabridged); - AuthorsDiscovered?.Invoke(this, e.FirstAuthor ?? "[unknown]"); - NarratorsDiscovered?.Invoke(this, e.Narrator ?? "[unknown]"); - } - private static (string destinationDir, bool movedAudioFile) MoveFilesToBooksDir(Book product, string outputAudioFilename) { // create final directory. move each file into it. MOVE AUDIO FILE LAST diff --git a/FileLiberator/DownloadFile.cs b/FileLiberator/DownloadFile.cs index d0a816b8..9f23b1e0 100644 --- a/FileLiberator/DownloadFile.cs +++ b/FileLiberator/DownloadFile.cs @@ -13,12 +13,6 @@ namespace FileLiberator public event EventHandler StreamingCompleted; public event EventHandler StreamingTimeRemaining; - public DownloadFile() - { - StreamingBegin += (o, e) => Serilog.Log.Logger.Information("Event fired {@DebugInfo}", new { Name = nameof(StreamingBegin), Message = e }); - StreamingCompleted += (o, e) => Serilog.Log.Logger.Information("Event fired {@DebugInfo}", new { Name = nameof(StreamingCompleted), Message = e }); - } - public async Task PerformDownloadFileAsync(string downloadUrl, string proposedDownloadFilePath) { var client = new HttpClient(); diff --git a/FileLiberator/DownloadPdf.cs b/FileLiberator/DownloadPdf.cs index 74e4a66d..0ed4cd6b 100644 --- a/FileLiberator/DownloadPdf.cs +++ b/FileLiberator/DownloadPdf.cs @@ -17,15 +17,6 @@ namespace FileLiberator => !string.IsNullOrWhiteSpace(getdownloadUrl(libraryBook)) && !libraryBook.Book.PDF_Exists; - public DownloadPdf() - { - StreamingBegin += (o, e) => Serilog.Log.Logger.Information("Event fired {@DebugInfo}", new { Name = nameof(StreamingBegin), Message = e }); - StreamingCompleted += (o, e) => Serilog.Log.Logger.Information("Event fired {@DebugInfo}", new { Name = nameof(StreamingCompleted), Message = e }); - - Begin += (o, e) => Serilog.Log.Logger.Information("Event fired {@DebugInfo}", new { Name = nameof(Begin), Book = e.LogFriendly() }); - Completed += (o, e) => Serilog.Log.Logger.Information("Event fired {@DebugInfo}", new { Name = nameof(Completed), Book = e.LogFriendly() }); - } - public override async Task ProcessItemAsync(LibraryBook libraryBook) { var proposedDownloadFilePath = getProposedDownloadFilePath(libraryBook); diff --git a/LibationWinForms/BookLiberation/AudioConvertForm.cs b/LibationWinForms/BookLiberation/AudioConvertForm.cs index 1bedf9bf..4960cfa3 100644 --- a/LibationWinForms/BookLiberation/AudioConvertForm.cs +++ b/LibationWinForms/BookLiberation/AudioConvertForm.cs @@ -24,7 +24,10 @@ namespace LibationWinForms.BookLiberation base.OnBegin(sender, libraryBook); } public override void OnCompleted(object sender, LibraryBook libraryBook) - => LogMe.Info($"Convert Step, Completed: {libraryBook.Book}{Environment.NewLine}"); + { + base.OnCompleted(sender, libraryBook); + LogMe.Info($"Convert Step, Completed: {libraryBook.Book}{Environment.NewLine}"); + } #endregion } diff --git a/LibationWinForms/BookLiberation/AudioDecodeForm.cs b/LibationWinForms/BookLiberation/AudioDecodeForm.cs index 255268c4..3a54f5aa 100644 --- a/LibationWinForms/BookLiberation/AudioDecodeForm.cs +++ b/LibationWinForms/BookLiberation/AudioDecodeForm.cs @@ -21,6 +21,8 @@ namespace LibationWinForms.BookLiberation #region IProcessable event handler overrides public override void OnBegin(object sender, LibraryBook libraryBook) { + base.OnBegin(sender, libraryBook); + GetCoverArtDelegate = () => FileManager.PictureStorage.GetPictureSynchronously( new FileManager.PictureDefinition( libraryBook.Book.PictureId, @@ -41,6 +43,7 @@ namespace LibationWinForms.BookLiberation #region IStreamable event handler overrides public override void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress) { + base.OnStreamingProgressChanged(sender, downloadProgress); if (!downloadProgress.ProgressPercentage.HasValue) return; @@ -51,16 +54,23 @@ namespace LibationWinForms.BookLiberation } public override void OnStreamingTimeRemaining(object sender, TimeSpan timeRemaining) - => updateRemainingTime((int)timeRemaining.TotalSeconds); + { + base.OnStreamingTimeRemaining(sender, timeRemaining); + updateRemainingTime((int)timeRemaining.TotalSeconds); + } #endregion #region IAudioDecodable event handlers public override void OnRequestCoverArt(object sender, Action setCoverArtDelegate) - => setCoverArtDelegate(GetCoverArtDelegate?.Invoke()); + { + base.OnRequestCoverArt(sender, setCoverArtDelegate); + setCoverArtDelegate(GetCoverArtDelegate?.Invoke()); + } public override void OnTitleDiscovered(object sender, string title) { + base.OnTitleDiscovered(sender, title); this.UIThreadAsync(() => this.Text = DecodeActionName + " " + title); this.title = title; updateBookInfo(); @@ -68,18 +78,23 @@ namespace LibationWinForms.BookLiberation public override void OnAuthorsDiscovered(object sender, string authors) { + base.OnAuthorsDiscovered(sender, authors); authorNames = authors; updateBookInfo(); } public override void OnNarratorsDiscovered(object sender, string narrators) { + base.OnNarratorsDiscovered(sender, narrators); narratorNames = narrators; updateBookInfo(); } - public override void OnCoverImageDiscovered(object sender, byte[] coverArt) - => pictureBox1.UIThreadAsync(() => pictureBox1.Image = Dinah.Core.Drawing.ImageReader.ToImage(coverArt)); + public override void OnCoverImageDiscovered(object sender, byte[] coverArt) + { + base.OnCoverImageDiscovered(sender, coverArt); + pictureBox1.UIThreadAsync(() => pictureBox1.Image = Dinah.Core.Drawing.ImageReader.ToImage(coverArt)); + } #endregion // thread-safe UI updates diff --git a/LibationWinForms/BookLiberation/AudioDecryptForm.cs b/LibationWinForms/BookLiberation/AudioDecryptForm.cs index 477c3bb7..58d8d3ac 100644 --- a/LibationWinForms/BookLiberation/AudioDecryptForm.cs +++ b/LibationWinForms/BookLiberation/AudioDecryptForm.cs @@ -24,7 +24,10 @@ namespace LibationWinForms.BookLiberation base.OnBegin(sender, libraryBook); } public override void OnCompleted(object sender, LibraryBook libraryBook) - => LogMe.Info($"Download & Decrypt Step, Completed: {libraryBook.Book}{Environment.NewLine}"); + { + base.OnCompleted(sender, libraryBook); + LogMe.Info($"Download & Decrypt Step, Completed: {libraryBook.Book}{Environment.NewLine}"); + } #endregion } diff --git a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs index 5cd32e01..0b8eeef7 100644 --- a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs @@ -137,24 +137,36 @@ namespace LibationWinForms.BookLiberation.BaseForms #endregion #region IStreamable event handlers - public virtual void OnStreamingBegin(object sender, string beginString) { } + public virtual void OnStreamingBegin(object sender, string beginString) + => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(IStreamable.StreamingBegin), Message = beginString }); public virtual void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress) { } public virtual void OnStreamingTimeRemaining(object sender, TimeSpan timeRemaining) { } - public virtual void OnStreamingCompleted(object sender, string completedString) { } + public virtual void OnStreamingCompleted(object sender, string completedString) + => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(IStreamable.StreamingCompleted), Message = completedString }); + #endregion #region IProcessable event handlers - public virtual void OnBegin(object sender, LibraryBook libraryBook) { } - public virtual void OnStatusUpdate(object sender, string statusUpdate) { } - public virtual void OnCompleted(object sender, LibraryBook libraryBook) { } + public virtual void OnBegin(object sender, LibraryBook libraryBook) + => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(IProcessable.Begin), Book = libraryBook.LogFriendly() }); + public virtual void OnStatusUpdate(object sender, string statusUpdate) + => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(IProcessable.StatusUpdate), Status = statusUpdate }); + public virtual void OnCompleted(object sender, LibraryBook libraryBook) + => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(IProcessable.Completed), Book = libraryBook.LogFriendly() }); + #endregion #region IAudioDecodable event handlers - public virtual void OnRequestCoverArt(object sender, Action setCoverArtDelegate) { } - public virtual void OnTitleDiscovered(object sender, string title) { } - public virtual void OnAuthorsDiscovered(object sender, string authors) { } - public virtual void OnNarratorsDiscovered(object sender, string narrators) { } - public virtual void OnCoverImageDiscovered(object sender, byte[] coverArt) { } + public virtual void OnRequestCoverArt(object sender, Action setCoverArtDelegate) + => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(IAudioDecodable.RequestCoverArt) }); + public virtual void OnTitleDiscovered(object sender, string title) + => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(IAudioDecodable.TitleDiscovered), Title = title }); + public virtual void OnAuthorsDiscovered(object sender, string authors) + => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(IAudioDecodable.AuthorsDiscovered), Authors = authors }); + public virtual void OnNarratorsDiscovered(object sender, string narrators) + => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(IAudioDecodable.NarratorsDiscovered), Narrators = narrators }); + public virtual void OnCoverImageDiscovered(object sender, byte[] coverArt) + => Serilog.Log.Logger.Debug("Event fired {@DebugInfo}", new { Name = nameof(IAudioDecodable.CoverImageDiscovered), CoverImageBytes = coverArt?.Length }); #endregion } } diff --git a/LibationWinForms/BookLiberation/DownloadForm.cs b/LibationWinForms/BookLiberation/DownloadForm.cs index 1843aaa3..42127070 100644 --- a/LibationWinForms/BookLiberation/DownloadForm.cs +++ b/LibationWinForms/BookLiberation/DownloadForm.cs @@ -20,10 +20,12 @@ namespace LibationWinForms.BookLiberation #region IStreamable event handler overrides public override void OnStreamingBegin(object sender, string beginString) { + base.OnStreamingBegin(sender, beginString); filenameLbl.UIThreadAsync(() => filenameLbl.Text = beginString); } public override void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress) { + base.OnStreamingProgressChanged(sender, downloadProgress); // this won't happen with download file. it will happen with download string if (!downloadProgress.TotalBytesToReceive.HasValue || downloadProgress.TotalBytesToReceive.Value <= 0) return; diff --git a/LibationWinForms/BookLiberation/PdfDownloadForm.cs b/LibationWinForms/BookLiberation/PdfDownloadForm.cs index 3def88c2..85d85eef 100644 --- a/LibationWinForms/BookLiberation/PdfDownloadForm.cs +++ b/LibationWinForms/BookLiberation/PdfDownloadForm.cs @@ -4,7 +4,15 @@ namespace LibationWinForms.BookLiberation { internal class PdfDownloadForm : DownloadForm { - public override void OnBegin(object sender, LibraryBook libraryBook) => LogMe.Info($"PDF Step, Begin: {libraryBook.Book}"); - public override void OnCompleted(object sender, LibraryBook libraryBook) => LogMe.Info($"PDF Step, Completed: {libraryBook.Book}"); + public override void OnBegin(object sender, LibraryBook libraryBook) + { + base.OnBegin(sender, libraryBook); + LogMe.Info($"PDF Step, Begin: {libraryBook.Book}"); + } + public override void OnCompleted(object sender, LibraryBook libraryBook) + { + base.OnCompleted(sender, libraryBook); + LogMe.Info($"PDF Step, Completed: {libraryBook.Book}"); + } } } diff --git a/LibationWinForms/Dialogs/SettingsDialog.Designer.cs b/LibationWinForms/Dialogs/SettingsDialog.Designer.cs index 1764204c..bb9694f3 100644 --- a/LibationWinForms/Dialogs/SettingsDialog.Designer.cs +++ b/LibationWinForms/Dialogs/SettingsDialog.Designer.cs @@ -204,9 +204,9 @@ this.convertLossyRb.AutoSize = true; this.convertLossyRb.Location = new System.Drawing.Point(6, 81); this.convertLossyRb.Name = "convertLossyRb"; - this.convertLossyRb.Size = new System.Drawing.Size(242, 19); + this.convertLossyRb.Size = new System.Drawing.Size(329, 19); this.convertLossyRb.TabIndex = 10; - this.convertLossyRb.Text = "Download my books as .MP3 files (Lossy)"; + this.convertLossyRb.Text = "Download my books as .MP3 files (transcode if necessary)"; this.convertLossyRb.UseVisualStyleBackColor = true; // // convertLosslessRb @@ -215,10 +215,10 @@ this.convertLosslessRb.Checked = true; this.convertLosslessRb.Location = new System.Drawing.Point(6, 56); this.convertLosslessRb.Name = "convertLosslessRb"; - this.convertLosslessRb.Size = new System.Drawing.Size(327, 19); + this.convertLosslessRb.Size = new System.Drawing.Size(335, 19); this.convertLosslessRb.TabIndex = 9; this.convertLosslessRb.TabStop = true; - this.convertLosslessRb.Text = "Download my books as .M4B files (Lossless Mp4a format)"; + this.convertLosslessRb.Text = "Download my books in the original audio format (Lossless)"; this.convertLosslessRb.UseVisualStyleBackColor = true; // // inProgressSelectControl