From 1b0a7f5062947915e8258d4d4547800282f5b2af Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Fri, 24 Feb 2023 12:12:41 -0700 Subject: [PATCH] New mp3 options and improved encoding performance --- Source/AaxDecrypter/AaxDecrypter.csproj | 2 +- .../AaxDecrypter/AaxcDownloadConvertBase.cs | 10 ++- .../AaxcDownloadMultiConverter.cs | 6 +- .../AaxcDownloadSingleConverter.cs | 48 ++++++------ Source/AaxDecrypter/AudiobookDownloadBase.cs | 10 ++- Source/AaxDecrypter/MpegUtil.cs | 14 +++- Source/AaxDecrypter/NetworkFileStream.cs | 5 ++ .../UnencryptedAudiobookDownloader.cs | 7 +- Source/FileLiberator/AudioDecodable.cs | 10 ++- Source/FileLiberator/ConvertToMp3.cs | 8 +- Source/FileLiberator/DownloadOptions.cs | 2 +- .../Dialogs/SettingsDialog.axaml | 23 +++++- .../Dialogs/SettingsDialog.axaml.cs | 32 ++++++++ .../Views/MainWindow.ProcessQueue.cs | 17 +++++ .../LibationAvalonia/Views/MainWindow.axaml | 3 +- .../Views/ProductsDisplay.axaml.cs | 8 ++ .../Configuration.PersistentSettings.cs | 6 ++ Source/LibationUiBase/SampleRateSelection.cs | 18 +++++ .../Dialogs/SettingsDialog.AudioSettings.cs | 25 +++++++ .../Dialogs/SettingsDialog.Designer.cs | 73 +++++++++++++++---- Source/LibationWinForms/Form1.Designer.cs | 1 + Source/LibationWinForms/Form1.ProcessQueue.cs | 17 +++++ .../GridView/ProductsDisplay.Designer.cs | 1 + .../GridView/ProductsDisplay.cs | 7 ++ .../LibationWinForms/GridView/ProductsGrid.cs | 9 +++ 25 files changed, 299 insertions(+), 63 deletions(-) create mode 100644 Source/LibationUiBase/SampleRateSelection.cs diff --git a/Source/AaxDecrypter/AaxDecrypter.csproj b/Source/AaxDecrypter/AaxDecrypter.csproj index ea3d58f3..91ce5ac5 100644 --- a/Source/AaxDecrypter/AaxDecrypter.csproj +++ b/Source/AaxDecrypter/AaxDecrypter.csproj @@ -13,7 +13,7 @@ - + diff --git a/Source/AaxDecrypter/AaxcDownloadConvertBase.cs b/Source/AaxDecrypter/AaxcDownloadConvertBase.cs index 9476e4b7..1b309e59 100644 --- a/Source/AaxDecrypter/AaxcDownloadConvertBase.cs +++ b/Source/AaxDecrypter/AaxcDownloadConvertBase.cs @@ -40,8 +40,14 @@ namespace AaxDecrypter AaxFile.AppleTags.Album = AaxFile.AppleTags.Album?.Replace(" (Unabridged)", ""); } - if (DownloadOptions.FixupFile && !string.IsNullOrWhiteSpace(AaxFile.AppleTags.Narrator)) - AaxFile.AppleTags.AppleListBox.EditOrAddTag("TCOM", AaxFile.AppleTags.Narrator); + if (DownloadOptions.FixupFile) + { + if (!string.IsNullOrWhiteSpace(AaxFile.AppleTags.Narrator)) + AaxFile.AppleTags.AppleListBox.EditOrAddTag("TCOM", AaxFile.AppleTags.Narrator); + + if (!string.IsNullOrWhiteSpace(AaxFile.AppleTags.Copyright)) + AaxFile.AppleTags.Copyright = AaxFile.AppleTags.Copyright.Replace("(P)", "℗").Replace("©", "©"); + } //Finishing configuring lame encoder. if (DownloadOptions.OutputFormat == OutputFormat.Mp3) diff --git a/Source/AaxDecrypter/AaxcDownloadMultiConverter.cs b/Source/AaxDecrypter/AaxcDownloadMultiConverter.cs index 0a8fb4e9..917e42c8 100644 --- a/Source/AaxDecrypter/AaxcDownloadMultiConverter.cs +++ b/Source/AaxDecrypter/AaxcDownloadMultiConverter.cs @@ -93,15 +93,13 @@ That naming may not be desirable for everyone, but it's an easy change to instea ? AaxFile.ConvertToMultiMp4aAsync ( splitChapters, - newSplitCallback => newSplit(++chapterCount, splitChapters, newSplitCallback), - DownloadOptions.TrimOutputToChapterLength + newSplitCallback => newSplit(++chapterCount, splitChapters, newSplitCallback) ) : AaxFile.ConvertToMultiMp3Async ( splitChapters, newSplitCallback => newSplit(++chapterCount, splitChapters, newSplitCallback), - DownloadOptions.LameConfig, - DownloadOptions.TrimOutputToChapterLength + DownloadOptions.LameConfig ); void newSplit(int currentChapter, ChapterInfo splitChapters, NewSplitCallback newSplitCallback) diff --git a/Source/AaxDecrypter/AaxcDownloadSingleConverter.cs b/Source/AaxDecrypter/AaxcDownloadSingleConverter.cs index 77674e74..4a2b60f3 100644 --- a/Source/AaxDecrypter/AaxcDownloadSingleConverter.cs +++ b/Source/AaxDecrypter/AaxcDownloadSingleConverter.cs @@ -3,6 +3,7 @@ using AAXClean.Codecs; using Dinah.Core.Net.Http; using FileManager; using System; +using System.Diagnostics; using System.IO; using System.Threading.Tasks; @@ -14,12 +15,15 @@ namespace AaxDecrypter public AaxcDownloadSingleConverter(string outFileName, string cacheDirectory, IDownloadOptions dlOptions) : base(outFileName, cacheDirectory, dlOptions) { + var step = 1; AsyncSteps.Name = $"Download and Convert Aaxc To {DownloadOptions.OutputFormat}"; - AsyncSteps["Step 1: Get Aaxc Metadata"] = () => Task.Run(Step_GetMetadata); - AsyncSteps["Step 2: Download Decrypted Audiobook"] = Step_DownloadAndDecryptAudiobookAsync; - AsyncSteps["Step 3: Download Clips and Bookmarks"] = Step_DownloadClipsBookmarksAsync; - AsyncSteps["Step 4: Create Cue"] = Step_CreateCueAsync; + AsyncSteps[$"Step {step++}: Get Aaxc Metadata"] = () => Task.Run(Step_GetMetadata); + AsyncSteps[$"Step {step++}: Download Decrypted Audiobook"] = Step_DownloadAndDecryptAudiobookAsync; + if (DownloadOptions.MoveMoovToBeginning && DownloadOptions.OutputFormat is OutputFormat.M4b) + AsyncSteps[$"Step {step++}: Move moov atom to beginning"] = Step_MoveMoov; + AsyncSteps[$"Step {step++}: Download Clips and Bookmarks"] = Step_DownloadClipsBookmarksAsync; + AsyncSteps[$"Step {step++}: Create Cue"] = Step_CreateCueAsync; } protected async override Task Step_DownloadAndDecryptAudiobookAsync() @@ -31,18 +35,7 @@ namespace AaxDecrypter try { - await (AaxConversion = decryptAsync(outputFile)); - - if (AaxConversion.IsCompletedSuccessfully - && DownloadOptions.MoveMoovToBeginning - && DownloadOptions.OutputFormat is OutputFormat.M4b) - { - outputFile.Close(); - AaxConversion = Mp4File.RelocateMoovAsync(OutputFileName); - AaxConversion.ConversionProgressUpdate += AaxConversion_MoovProgressUpdate; - await AaxConversion; - AaxConversion.ConversionProgressUpdate -= AaxConversion_MoovProgressUpdate; - } + await (AaxConversion = decryptAsync(outputFile)); return AaxConversion.IsCompletedSuccessfully; } @@ -52,23 +45,30 @@ namespace AaxDecrypter } } + private async Task Step_MoveMoov() + { + AaxConversion = Mp4File.RelocateMoovAsync(OutputFileName); + AaxConversion.ConversionProgressUpdate += AaxConversion_MoovProgressUpdate; + await AaxConversion; + AaxConversion.ConversionProgressUpdate -= AaxConversion_MoovProgressUpdate; + return AaxConversion.IsCompletedSuccessfully; + } + private void AaxConversion_MoovProgressUpdate(object sender, ConversionProgressEventArgs e) { averageSpeed.AddPosition(e.ProcessPosition.TotalSeconds); - var remainingTimeToProcess = (e.TotalDuration - e.ProcessPosition).TotalSeconds; + var remainingTimeToProcess = (e.EndTime - e.ProcessPosition).TotalSeconds; var estTimeRemaining = remainingTimeToProcess / averageSpeed.Average; if (double.IsNormal(estTimeRemaining)) OnDecryptTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining)); - var progressPercent = 100d * (1 - remainingTimeToProcess / e.TotalDuration.TotalSeconds); - OnDecryptProgressUpdate( new DownloadProgress { - ProgressPercentage = progressPercent, - BytesReceived = (long)(InputFileStream.Length * progressPercent), + ProgressPercentage = 100 * e.FractionCompleted, + BytesReceived = (long)(InputFileStream.Length * e.FractionCompleted), TotalBytesToReceive = InputFileStream.Length }); } @@ -79,15 +79,13 @@ namespace AaxDecrypter ( outputFile, DownloadOptions.LameConfig, - DownloadOptions.ChapterInfo, - DownloadOptions.TrimOutputToChapterLength + DownloadOptions.ChapterInfo ) : DownloadOptions.FixupFile ? AaxFile.ConvertToMp4aAsync ( outputFile, - DownloadOptions.ChapterInfo, - DownloadOptions.TrimOutputToChapterLength + DownloadOptions.ChapterInfo ) : AaxFile.ConvertToMp4aAsync(outputFile); } diff --git a/Source/AaxDecrypter/AudiobookDownloadBase.cs b/Source/AaxDecrypter/AudiobookDownloadBase.cs index fbbb3716..fff17d58 100644 --- a/Source/AaxDecrypter/AudiobookDownloadBase.cs +++ b/Source/AaxDecrypter/AudiobookDownloadBase.cs @@ -27,6 +27,7 @@ namespace AaxDecrypter protected IDownloadOptions DownloadOptions { get; } protected NetworkFileStream InputFileStream => nfsPersister.NetworkFileStream; protected virtual long InputFilePosition => InputFileStream.Position; + private bool downloadFinished; private readonly NetworkFileStreamPersister nfsPersister; private readonly DownloadProgress zeroProgress; @@ -84,7 +85,11 @@ namespace AaxDecrypter { AverageSpeed averageSpeed = new(); - while (InputFileStream.CanRead && InputFileStream.Length > InputFilePosition && !InputFileStream.IsCancelled) + while ( + InputFileStream.CanRead + && InputFileStream.Length > InputFilePosition + && !InputFileStream.IsCancelled + && !downloadFinished) { averageSpeed.AddPosition(InputFilePosition); @@ -138,8 +143,7 @@ namespace AaxDecrypter protected virtual void FinalizeDownload() { nfsPersister?.Dispose(); - OnDecryptTimeRemaining(TimeSpan.Zero); - OnDecryptProgressUpdate(zeroProgress); + downloadFinished = true; } protected async Task Step_DownloadClipsBookmarksAsync() diff --git a/Source/AaxDecrypter/MpegUtil.cs b/Source/AaxDecrypter/MpegUtil.cs index 4d35fd2d..d7ab9c37 100644 --- a/Source/AaxDecrypter/MpegUtil.cs +++ b/Source/AaxDecrypter/MpegUtil.cs @@ -1,5 +1,6 @@ using AAXClean; using NAudio.Lame; +using System; namespace AaxDecrypter { @@ -9,17 +10,26 @@ namespace AaxDecrypter { double bitrateMultiple = 1; + if (mp4File.TimeScale < lameConfig.OutputSampleRate) + { + lameConfig.OutputSampleRate = mp4File.TimeScale; + } + else if (mp4File.TimeScale > lameConfig.OutputSampleRate) + { + bitrateMultiple *= (double)lameConfig.OutputSampleRate / mp4File.TimeScale; + } + if (mp4File.AudioChannels == 2) { if (downsample) - bitrateMultiple = 0.5; + bitrateMultiple /= 2; else lameConfig.Mode = MPEGMode.Stereo; } if (matchSourceBitrate) { - int kbps = (int)(mp4File.AverageBitrate * bitrateMultiple / 1024); + int kbps = (int)Math.Round(mp4File.AverageBitrate * bitrateMultiple / 1024); if (lameConfig.VBR is null) lameConfig.BitRate = kbps; diff --git a/Source/AaxDecrypter/NetworkFileStream.cs b/Source/AaxDecrypter/NetworkFileStream.cs index 53f1753e..bd8ce19c 100644 --- a/Source/AaxDecrypter/NetworkFileStream.cs +++ b/Source/AaxDecrypter/NetworkFileStream.cs @@ -14,6 +14,7 @@ namespace AaxDecrypter public class NetworkFileStream : Stream, IUpdatable { public event EventHandler Updated; + public event EventHandler DownloadCompleted; #region Public Properties @@ -139,7 +140,10 @@ namespace AaxDecrypter public async Task BeginDownloadingAsync() { if (ContentLength != 0 && WritePosition == ContentLength) + { + _backgroundDownloadTask = Task.CompletedTask; return; + } if (ContentLength != 0 && WritePosition > ContentLength) throw new WebException($"Specified write position (0x{WritePosition:X10}) is larger than {nameof(ContentLength)} (0x{ContentLength:X10})."); @@ -230,6 +234,7 @@ namespace AaxDecrypter _writeFile.Close(); _downloadedPiece.Set(); OnUpdate(); + DownloadCompleted?.Invoke(this, null); } } diff --git a/Source/AaxDecrypter/UnencryptedAudiobookDownloader.cs b/Source/AaxDecrypter/UnencryptedAudiobookDownloader.cs index 138c8ea0..193cd9b6 100644 --- a/Source/AaxDecrypter/UnencryptedAudiobookDownloader.cs +++ b/Source/AaxDecrypter/UnencryptedAudiobookDownloader.cs @@ -26,8 +26,11 @@ namespace AaxDecrypter protected override async Task Step_DownloadAndDecryptAudiobookAsync() { - while (InputFilePosition < InputFileStream.Length && !InputFileStream.IsCancelled) - await Task.Delay(200); + TaskCompletionSource completionSource = new(); + + InputFileStream.DownloadCompleted += (_, _) => completionSource.SetResult(); + + await completionSource.Task; if (IsCanceled) return false; diff --git a/Source/FileLiberator/AudioDecodable.cs b/Source/FileLiberator/AudioDecodable.cs index 800b711b..9f6a803d 100644 --- a/Source/FileLiberator/AudioDecodable.cs +++ b/Source/FileLiberator/AudioDecodable.cs @@ -17,10 +17,14 @@ namespace FileLiberator protected LameConfig GetLameOptions(Configuration config) { - LameConfig lameConfig = new(); - lameConfig.Mode = MPEGMode.Mono; + LameConfig lameConfig = new() + { + Mode = MPEGMode.Mono, + Quality = config.LameEncoderQuality, + OutputSampleRate = (int)config.MaxSampleRate + }; - if (config.LameTargetBitrate) + if (config.LameTargetBitrate) { if (config.LameConstantBitrate) lameConfig.BitRate = config.LameBitrate; diff --git a/Source/FileLiberator/ConvertToMp3.cs b/Source/FileLiberator/ConvertToMp3.cs index 5b487d4f..4f70b1ef 100644 --- a/Source/FileLiberator/ConvertToMp3.cs +++ b/Source/FileLiberator/ConvertToMp3.cs @@ -103,20 +103,20 @@ namespace FileLiberator { averageSpeed.AddPosition(e.ProcessPosition.TotalSeconds); - var remainingTimeToProcess = (e.TotalDuration - e.ProcessPosition).TotalSeconds; + var remainingTimeToProcess = (e.EndTime - e.ProcessPosition).TotalSeconds; var estTimeRemaining = remainingTimeToProcess / averageSpeed.Average; if (double.IsNormal(estTimeRemaining)) OnStreamingTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining)); - double progressPercent = 100 * e.ProcessPosition.TotalSeconds / e.TotalDuration.TotalSeconds; + double progressPercent = 100 * e.FractionCompleted; OnStreamingProgressChanged( new DownloadProgress { ProgressPercentage = progressPercent, - BytesReceived = (long)e.ProcessPosition.TotalSeconds, - TotalBytesToReceive = (long)e.TotalDuration.TotalSeconds + BytesReceived = (long)(e.ProcessPosition - e.StartTime).TotalSeconds, + TotalBytesToReceive = (long)(e.EndTime - e.StartTime).TotalSeconds }); } } diff --git a/Source/FileLiberator/DownloadOptions.cs b/Source/FileLiberator/DownloadOptions.cs index 94315542..7b46170d 100644 --- a/Source/FileLiberator/DownloadOptions.cs +++ b/Source/FileLiberator/DownloadOptions.cs @@ -22,7 +22,7 @@ namespace FileLiberator public OutputFormat OutputFormat { get; init; } public ChapterInfo ChapterInfo { get; init; } public NAudio.Lame.LameConfig LameConfig { get; init; } - public string UserAgent => AudibleApi.Resources.User_Agent; + public string UserAgent => AudibleApi.Resources.Download_User_Agent; public bool TrimOutputToChapterLength => config.AllowLibationFixup && config.StripAudibleBrandAudio; public bool StripUnabridged => config.AllowLibationFixup && config.StripUnabridged; public bool CreateCueSheet => config.CreateCueSheet; diff --git a/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml b/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml index c2b17889..ffaa7fc9 100644 --- a/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml +++ b/Source/LibationAvalonia/Dialogs/SettingsDialog.axaml @@ -2,8 +2,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" d:DesignWidth="850" d:DesignHeight="620" - MinWidth="800" MinHeight="620" + mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="700" + MinWidth="900" MinHeight="700" x:Class="LibationAvalonia.Dialogs.SettingsDialog" xmlns:controls="clr-namespace:LibationAvalonia.Controls" Title="Edit Settings" @@ -603,6 +603,25 @@ + + + + + + + + + + SampleRates { get; } + = new( + new [] + { + AAXClean.SampleRate.Hz_44100, + AAXClean.SampleRate.Hz_32000, + AAXClean.SampleRate.Hz_24000, + AAXClean.SampleRate.Hz_22050, + AAXClean.SampleRate.Hz_16000, + AAXClean.SampleRate.Hz_12000, + } + .Select(s => new SampleRateSelection(s))); + + public AvaloniaList EncoderQualities { get; } + = new( + new[] + { + NAudio.Lame.EncoderQuality.High, + NAudio.Lame.EncoderQuality.Standard, + NAudio.Lame.EncoderQuality.Fast, + }); + public AudioSettings(Configuration config) { @@ -398,6 +424,9 @@ namespace LibationAvalonia.Dialogs LameMatchSource = config.LameMatchSourceBR; LameBitrate = config.LameBitrate; LameVBRQuality = config.LameVBRQuality; + + SelectedSampleRate = SampleRates.FirstOrDefault(s => s.SampleRate == config.MaxSampleRate); + SelectedEncoderQuality = config.LameEncoderQuality; } public Task SaveSettingsAsync(Configuration config) @@ -422,6 +451,9 @@ namespace LibationAvalonia.Dialogs config.LameBitrate = LameBitrate; config.LameVBRQuality = LameVBRQuality; + config.LameEncoderQuality = SelectedEncoderQuality; + config.MaxSampleRate = SelectedSampleRate?.SampleRate ?? config.MaxSampleRate; + return Task.FromResult(true); } diff --git a/Source/LibationAvalonia/Views/MainWindow.ProcessQueue.cs b/Source/LibationAvalonia/Views/MainWindow.ProcessQueue.cs index d6bdad54..77e8b626 100644 --- a/Source/LibationAvalonia/Views/MainWindow.ProcessQueue.cs +++ b/Source/LibationAvalonia/Views/MainWindow.ProcessQueue.cs @@ -47,6 +47,23 @@ namespace LibationAvalonia.Views Serilog.Log.Logger.Error(ex, "An error occurred while handling the stop light button click for {libraryBook}", libraryBook); } } + + public void ProductsDisplay_ConvertToMp3Clicked(object sender, LibraryBook libraryBook) + { + try + { + if (libraryBook.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated) + { + Serilog.Log.Logger.Information("Begin single pdf backup of {libraryBook}", libraryBook); + SetQueueCollapseState(false); + _viewModel.ProcessQueue.AddConvertMp3(libraryBook); + } + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, "An error occurred while handling the stop light button click for {libraryBook}", libraryBook); + } + } private void SetQueueCollapseState(bool collapsed) { _viewModel.QueueOpen = !collapsed; diff --git a/Source/LibationAvalonia/Views/MainWindow.axaml b/Source/LibationAvalonia/Views/MainWindow.axaml index 6241135c..c1a44e47 100644 --- a/Source/LibationAvalonia/Views/MainWindow.axaml +++ b/Source/LibationAvalonia/Views/MainWindow.axaml @@ -188,7 +188,8 @@ + LiberateClicked="ProductsDisplay_LiberateClicked" + ConvertToMp3Clicked="ProductsDisplay_ConvertToMp3Clicked" /> diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs index 3bdd2d6d..c7f2a29a 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs @@ -19,6 +19,7 @@ namespace LibationAvalonia.Views public partial class ProductsDisplay : UserControl { public event EventHandler LiberateClicked; + public event EventHandler ConvertToMp3Clicked; private ProductsDisplayViewModel _viewModel => DataContext as ProductsDisplayViewModel; ImageDisplayDialog imageDisplayDialog; @@ -131,6 +132,12 @@ namespace LibationAvalonia.Views await MessageBox.ShowAdminAlert(null, msg, msg, ex); } }; + var convertToMp3MenuItem = new MenuItem + { + Header = "_Convert to Mp3", + IsEnabled = entry.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated + }; + convertToMp3MenuItem.Click += (_, _) => ConvertToMp3Clicked?.Invoke(this, entry.LibraryBook); var bookRecordMenuItem = new MenuItem { Header = "View _Bookmarks/Clips" }; bookRecordMenuItem.Click += async (_, _) => await new BookRecordsDialog(entry.LibraryBook).ShowDialog(VisualRoot as Window); @@ -141,6 +148,7 @@ namespace LibationAvalonia.Views setNotDownloadMenuItem, removeMenuItem, locateFileMenuItem, + convertToMp3MenuItem, new Separator(), bookRecordMenuItem }); diff --git a/Source/LibationFileManager/Configuration.PersistentSettings.cs b/Source/LibationFileManager/Configuration.PersistentSettings.cs index 152ebe5f..4902d289 100644 --- a/Source/LibationFileManager/Configuration.PersistentSettings.cs +++ b/Source/LibationFileManager/Configuration.PersistentSettings.cs @@ -121,6 +121,12 @@ namespace LibationFileManager [Description("Lame encoder target. true = Bitrate, false = Quality")] public bool LameTargetBitrate { get => GetNonString(defaultValue: false); set => SetNonString(value); } + [Description("Maximum audio sample rate")] + public AAXClean.SampleRate MaxSampleRate { get => GetNonString(defaultValue: AAXClean.SampleRate.Hz_44100); set => SetNonString(value); } + + [Description("Lame encoder quality")] + public NAudio.Lame.EncoderQuality LameEncoderQuality { get => GetNonString(defaultValue: NAudio.Lame.EncoderQuality.High); set => SetNonString(value); } + [Description("Lame encoder downsamples to mono")] public bool LameDownsampleMono { get => GetNonString(defaultValue: true); set => SetNonString(value); } diff --git a/Source/LibationUiBase/SampleRateSelection.cs b/Source/LibationUiBase/SampleRateSelection.cs new file mode 100644 index 00000000..bc7c8ee0 --- /dev/null +++ b/Source/LibationUiBase/SampleRateSelection.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibationUiBase +{ + public class SampleRateSelection + { + public AAXClean.SampleRate SampleRate { get; } + public SampleRateSelection(AAXClean.SampleRate sampleRate) + { + SampleRate = sampleRate; + } + public override string ToString() => $"{(int)SampleRate} Hz"; + } +} diff --git a/Source/LibationWinForms/Dialogs/SettingsDialog.AudioSettings.cs b/Source/LibationWinForms/Dialogs/SettingsDialog.AudioSettings.cs index 0647cc2b..c36e9652 100644 --- a/Source/LibationWinForms/Dialogs/SettingsDialog.AudioSettings.cs +++ b/Source/LibationWinForms/Dialogs/SettingsDialog.AudioSettings.cs @@ -1,6 +1,7 @@ using System; using LibationFileManager; using System.Linq; +using LibationUiBase; namespace LibationWinForms.Dialogs { @@ -26,6 +27,25 @@ namespace LibationWinForms.Dialogs Configuration.ClipBookmarkFormat.Json }); + maxSampleRateCb.Items.AddRange( + new object[] + { + new SampleRateSelection(AAXClean.SampleRate.Hz_44100), + new SampleRateSelection(AAXClean.SampleRate.Hz_32000), + new SampleRateSelection(AAXClean.SampleRate.Hz_24000), + new SampleRateSelection(AAXClean.SampleRate.Hz_22050), + new SampleRateSelection(AAXClean.SampleRate.Hz_16000), + new SampleRateSelection(AAXClean.SampleRate.Hz_12000) + }); + + encoderQualityCb.Items.AddRange( + new object[] + { + NAudio.Lame.EncoderQuality.High, + NAudio.Lame.EncoderQuality.Standard, + NAudio.Lame.EncoderQuality.Fast, + }); + allowLibationFixupCbox.Checked = config.AllowLibationFixup; createCueSheetCbox.Checked = config.CreateCueSheet; downloadCoverArtCbox.Checked = config.DownloadCoverArt; @@ -42,6 +62,8 @@ namespace LibationWinForms.Dialogs lameTargetBitrateRb.Checked = config.LameTargetBitrate; lameTargetQualityRb.Checked = !config.LameTargetBitrate; + maxSampleRateCb.SelectedItem = maxSampleRateCb.Items.Cast().Single(s => s.SampleRate == config.MaxSampleRate); + encoderQualityCb.SelectedItem = config.LameEncoderQuality; lameDownsampleMonoCbox.Checked = config.LameDownsampleMono; lameBitrateTb.Value = config.LameBitrate; lameConstantBitrateCbox.Checked = config.LameConstantBitrate; @@ -75,6 +97,9 @@ namespace LibationWinForms.Dialogs config.MoveMoovToBeginning = moveMoovAtomCbox.Checked; config.LameTargetBitrate = lameTargetBitrateRb.Checked; + config.MaxSampleRate = ((SampleRateSelection)maxSampleRateCb.SelectedItem).SampleRate; + config.LameEncoderQuality = (NAudio.Lame.EncoderQuality)encoderQualityCb.SelectedItem; + encoderQualityCb.SelectedItem = config.LameEncoderQuality; config.LameDownsampleMono = lameDownsampleMonoCbox.Checked; config.LameBitrate = lameBitrateTb.Value; config.LameConstantBitrate = lameConstantBitrateCbox.Checked; diff --git a/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs b/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs index 719a6e2c..dfea7df1 100644 --- a/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs +++ b/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs @@ -115,6 +115,10 @@ this.retainAaxFileCbox = new System.Windows.Forms.CheckBox(); this.downloadCoverArtCbox = new System.Windows.Forms.CheckBox(); this.createCueSheetCbox = new System.Windows.Forms.CheckBox(); + this.maxSampleRateCb = new System.Windows.Forms.ComboBox(); + this.encoderQualityCb = new System.Windows.Forms.ComboBox(); + this.label20 = new System.Windows.Forms.Label(); + this.label21 = new System.Windows.Forms.Label(); this.badBookGb.SuspendLayout(); this.tabControl.SuspendLayout(); this.tab1ImportantSettings.SuspendLayout(); @@ -743,6 +747,10 @@ // // lameOptionsGb // + this.lameOptionsGb.Controls.Add(this.label20); + this.lameOptionsGb.Controls.Add(this.label21); + this.lameOptionsGb.Controls.Add(this.encoderQualityCb); + this.lameOptionsGb.Controls.Add(this.maxSampleRateCb); this.lameOptionsGb.Controls.Add(this.lameDownsampleMonoCbox); this.lameOptionsGb.Controls.Add(this.lameBitrateGb); this.lameOptionsGb.Controls.Add(this.label1); @@ -757,8 +765,7 @@ // // lameDownsampleMonoCbox // - this.lameDownsampleMonoCbox.AutoSize = true; - this.lameDownsampleMonoCbox.Location = new System.Drawing.Point(234, 35); + this.lameDownsampleMonoCbox.Location = new System.Drawing.Point(237, 30); this.lameDownsampleMonoCbox.Name = "lameDownsampleMonoCbox"; this.lameDownsampleMonoCbox.Size = new System.Drawing.Size(184, 34); this.lameDownsampleMonoCbox.TabIndex = 1; @@ -776,9 +783,9 @@ this.lameBitrateGb.Controls.Add(this.label11); this.lameBitrateGb.Controls.Add(this.label3); this.lameBitrateGb.Controls.Add(this.lameBitrateTb); - this.lameBitrateGb.Location = new System.Drawing.Point(6, 84); + this.lameBitrateGb.Location = new System.Drawing.Point(6, 104); this.lameBitrateGb.Name = "lameBitrateGb"; - this.lameBitrateGb.Size = new System.Drawing.Size(421, 101); + this.lameBitrateGb.Size = new System.Drawing.Size(421, 102); this.lameBitrateGb.TabIndex = 0; this.lameBitrateGb.TabStop = false; this.lameBitrateGb.Text = "Bitrate"; @@ -786,7 +793,7 @@ // LameMatchSourceBRCbox // this.LameMatchSourceBRCbox.AutoSize = true; - this.LameMatchSourceBRCbox.Location = new System.Drawing.Point(260, 77); + this.LameMatchSourceBRCbox.Location = new System.Drawing.Point(275, 76); this.LameMatchSourceBRCbox.Name = "LameMatchSourceBRCbox"; this.LameMatchSourceBRCbox.Size = new System.Drawing.Size(140, 19); this.LameMatchSourceBRCbox.TabIndex = 3; @@ -883,7 +890,7 @@ this.label1.AutoSize = true; this.label1.Enabled = false; this.label1.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Italic, System.Drawing.GraphicsUnit.Point); - this.label1.Location = new System.Drawing.Point(6, 298); + this.label1.Location = new System.Drawing.Point(6, 325); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(172, 15); this.label1.TabIndex = 1; @@ -904,9 +911,9 @@ this.lameQualityGb.Controls.Add(this.label14); this.lameQualityGb.Controls.Add(this.label2); this.lameQualityGb.Controls.Add(this.lameVBRQualityTb); - this.lameQualityGb.Location = new System.Drawing.Point(6, 186); + this.lameQualityGb.Location = new System.Drawing.Point(6, 212); this.lameQualityGb.Name = "lameQualityGb"; - this.lameQualityGb.Size = new System.Drawing.Size(421, 109); + this.lameQualityGb.Size = new System.Drawing.Size(421, 103); this.lameQualityGb.TabIndex = 0; this.lameQualityGb.TabStop = false; this.lameQualityGb.Text = "Quality"; @@ -986,7 +993,7 @@ // label13 // this.label13.AutoSize = true; - this.label13.Location = new System.Drawing.Point(376, 81); + this.label13.Location = new System.Drawing.Point(376, 80); this.label13.Name = "label13"; this.label13.Size = new System.Drawing.Size(39, 15); this.label13.TabIndex = 1; @@ -995,7 +1002,7 @@ // label10 // this.label10.AutoSize = true; - this.label10.Location = new System.Drawing.Point(6, 81); + this.label10.Location = new System.Drawing.Point(6, 80); this.label10.Name = "label10"; this.label10.Size = new System.Drawing.Size(43, 15); this.label10.TabIndex = 1; @@ -1036,7 +1043,7 @@ this.groupBox2.Controls.Add(this.lameTargetBitrateRb); this.groupBox2.Location = new System.Drawing.Point(6, 22); this.groupBox2.Name = "groupBox2"; - this.groupBox2.Size = new System.Drawing.Size(222, 56); + this.groupBox2.Size = new System.Drawing.Size(214, 47); this.groupBox2.TabIndex = 0; this.groupBox2.TabStop = false; this.groupBox2.Text = "Target"; @@ -1044,7 +1051,7 @@ // lameTargetQualityRb // this.lameTargetQualityRb.AutoSize = true; - this.lameTargetQualityRb.Location = new System.Drawing.Point(138, 23); + this.lameTargetQualityRb.Location = new System.Drawing.Point(139, 22); this.lameTargetQualityRb.Name = "lameTargetQualityRb"; this.lameTargetQualityRb.Size = new System.Drawing.Size(63, 19); this.lameTargetQualityRb.TabIndex = 0; @@ -1056,7 +1063,7 @@ // lameTargetBitrateRb // this.lameTargetBitrateRb.AutoSize = true; - this.lameTargetBitrateRb.Location = new System.Drawing.Point(6, 23); + this.lameTargetBitrateRb.Location = new System.Drawing.Point(6, 22); this.lameTargetBitrateRb.Name = "lameTargetBitrateRb"; this.lameTargetBitrateRb.Size = new System.Drawing.Size(59, 19); this.lameTargetBitrateRb.TabIndex = 0; @@ -1112,6 +1119,42 @@ this.createCueSheetCbox.UseVisualStyleBackColor = true; this.createCueSheetCbox.CheckedChanged += new System.EventHandler(this.allowLibationFixupCbox_CheckedChanged); // + // maxSampleRateCb + // + this.maxSampleRateCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.maxSampleRateCb.FormattingEnabled = true; + this.maxSampleRateCb.Location = new System.Drawing.Point(119, 75); + this.maxSampleRateCb.Name = "maxSampleRateCb"; + this.maxSampleRateCb.Size = new System.Drawing.Size(101, 23); + this.maxSampleRateCb.TabIndex = 2; + // + // encoderQualityCb + // + this.encoderQualityCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.encoderQualityCb.FormattingEnabled = true; + this.encoderQualityCb.Location = new System.Drawing.Point(337, 75); + this.encoderQualityCb.Name = "encoderQualityCb"; + this.encoderQualityCb.Size = new System.Drawing.Size(90, 23); + this.encoderQualityCb.TabIndex = 2; + // + // label20 + // + this.label20.AutoSize = true; + this.label20.Location = new System.Drawing.Point(12, 78); + this.label20.Name = "label20"; + this.label20.Size = new System.Drawing.Size(101, 15); + this.label20.TabIndex = 3; + this.label20.Text = "Max Sample Rate:"; + // + // label21 + // + this.label21.AutoSize = true; + this.label21.Location = new System.Drawing.Point(239, 78); + this.label21.Name = "label21"; + this.label21.Size = new System.Drawing.Size(94, 15); + this.label21.TabIndex = 3; + this.label21.Text = "Encoder Quality:"; + // // SettingsDialog // this.AcceptButton = this.saveBtn; @@ -1253,5 +1296,9 @@ private System.Windows.Forms.ComboBox clipsBookmarksFormatCb; private System.Windows.Forms.CheckBox downloadClipsBookmarksCbox; private System.Windows.Forms.CheckBox moveMoovAtomCbox; + private System.Windows.Forms.ComboBox encoderQualityCb; + private System.Windows.Forms.ComboBox maxSampleRateCb; + private System.Windows.Forms.Label label21; + private System.Windows.Forms.Label label20; } } \ No newline at end of file diff --git a/Source/LibationWinForms/Form1.Designer.cs b/Source/LibationWinForms/Form1.Designer.cs index fc51a930..4a525c52 100644 --- a/Source/LibationWinForms/Form1.Designer.cs +++ b/Source/LibationWinForms/Form1.Designer.cs @@ -520,6 +520,7 @@ this.productsDisplay.VisibleCountChanged += new System.EventHandler(this.productsDisplay_VisibleCountChanged); this.productsDisplay.RemovableCountChanged += new System.EventHandler(this.productsDisplay_RemovableCountChanged); this.productsDisplay.LiberateClicked += new System.EventHandler(this.ProductsDisplay_LiberateClicked); + this.productsDisplay.ConvertToMp3Clicked += new System.EventHandler(this.ProductsDisplay_ConvertToMp3Clicked); this.productsDisplay.InitialLoaded += new System.EventHandler(this.productsDisplay_InitialLoaded); // // toggleQueueHideBtn diff --git a/Source/LibationWinForms/Form1.ProcessQueue.cs b/Source/LibationWinForms/Form1.ProcessQueue.cs index 398e0802..dfd7ac90 100644 --- a/Source/LibationWinForms/Form1.ProcessQueue.cs +++ b/Source/LibationWinForms/Form1.ProcessQueue.cs @@ -56,6 +56,23 @@ namespace LibationWinForms } } + private void ProductsDisplay_ConvertToMp3Clicked(object sender, LibraryBook libraryBook) + { + try + { + if (libraryBook.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated) + { + Serilog.Log.Logger.Information("Begin single pdf backup of {libraryBook}", libraryBook); + SetQueueCollapseState(false); + processBookQueue1.AddConvertMp3(libraryBook); + } + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, "An error occurred while handling the stop light button click for {libraryBook}", libraryBook); + } + } + private void SetQueueCollapseState(bool collapsed) { if (collapsed && !splitContainer1.Panel2Collapsed) diff --git a/Source/LibationWinForms/GridView/ProductsDisplay.Designer.cs b/Source/LibationWinForms/GridView/ProductsDisplay.Designer.cs index b84d670c..1e32af52 100644 --- a/Source/LibationWinForms/GridView/ProductsDisplay.Designer.cs +++ b/Source/LibationWinForms/GridView/ProductsDisplay.Designer.cs @@ -41,6 +41,7 @@ this.productsGrid.TabIndex = 0; this.productsGrid.VisibleCountChanged += new System.EventHandler(this.productsGrid_VisibleCountChanged); this.productsGrid.LiberateClicked += new LibationWinForms.GridView.LibraryBookEntryClickedEventHandler(this.productsGrid_LiberateClicked); + this.productsGrid.ConvertToMp3Clicked += new LibationWinForms.GridView.LibraryBookEntryClickedEventHandler(this.productsGrid_ConvertToMp3Clicked); this.productsGrid.CoverClicked += new LibationWinForms.GridView.GridEntryClickedEventHandler(this.productsGrid_CoverClicked); this.productsGrid.DetailsClicked += new LibationWinForms.GridView.LibraryBookEntryClickedEventHandler(this.productsGrid_DetailsClicked); this.productsGrid.DescriptionClicked += new LibationWinForms.GridView.GridEntryRectangleClickedEventHandler(this.productsGrid_DescriptionClicked); diff --git a/Source/LibationWinForms/GridView/ProductsDisplay.cs b/Source/LibationWinForms/GridView/ProductsDisplay.cs index 56a710af..75027385 100644 --- a/Source/LibationWinForms/GridView/ProductsDisplay.cs +++ b/Source/LibationWinForms/GridView/ProductsDisplay.cs @@ -19,6 +19,7 @@ namespace LibationWinForms.GridView public event EventHandler VisibleCountChanged; public event EventHandler RemovableCountChanged; public event EventHandler LiberateClicked; + public event EventHandler ConvertToMp3Clicked; public event EventHandler InitialLoaded; private bool hasBeenDisplayed; @@ -204,6 +205,12 @@ namespace LibationWinForms.GridView LiberateClicked?.Invoke(this, liveGridEntry.LibraryBook); } + private void productsGrid_ConvertToMp3Clicked(LibraryBookEntry liveGridEntry) + { + if (liveGridEntry.LibraryBook.Book.UserDefinedItem.BookStatus is not LiberatedStatus.Error) + ConvertToMp3Clicked?.Invoke(this, liveGridEntry.LibraryBook); + } + private void productsGrid_RemovableCountChanged(object sender, EventArgs e) { RemovableCountChanged?.Invoke(sender, productsGrid.GetAllBookEntries().Count(lbe => lbe.Remove is RemoveStatus.Removed)); diff --git a/Source/LibationWinForms/GridView/ProductsGrid.cs b/Source/LibationWinForms/GridView/ProductsGrid.cs index 7766a73c..75355bf9 100644 --- a/Source/LibationWinForms/GridView/ProductsGrid.cs +++ b/Source/LibationWinForms/GridView/ProductsGrid.cs @@ -22,6 +22,7 @@ namespace LibationWinForms.GridView /// Number of visible rows has changed public event EventHandler VisibleCountChanged; public event LibraryBookEntryClickedEventHandler LiberateClicked; + public event LibraryBookEntryClickedEventHandler ConvertToMp3Clicked; public event GridEntryClickedEventHandler CoverClicked; public event LibraryBookEntryClickedEventHandler DetailsClicked; public event GridEntryRectangleClickedEventHandler DescriptionClicked; @@ -176,6 +177,13 @@ namespace LibationWinForms.GridView } }; + var convertToMp3MenuItem = new ToolStripMenuItem + { + Text = "&Convert to Mp3", + Enabled = entry.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated + }; + convertToMp3MenuItem.Click += (_, e) => ConvertToMp3Clicked?.Invoke(entry as LibraryBookEntry); + var bookRecordMenuItem = new ToolStripMenuItem { Text = "View &Bookmarks/Clips" }; bookRecordMenuItem.Click += (_, _) => new BookRecordsDialog(entry.LibraryBook).ShowDialog(this); @@ -184,6 +192,7 @@ namespace LibationWinForms.GridView stopLightContextMenu.Items.Add(setNotDownloadMenuItem); stopLightContextMenu.Items.Add(removeMenuItem); stopLightContextMenu.Items.Add(locateFileMenuItem); + stopLightContextMenu.Items.Add(convertToMp3MenuItem); stopLightContextMenu.Items.Add(new ToolStripSeparator()); stopLightContextMenu.Items.Add(bookRecordMenuItem);