New mp3 options and improved encoding performance

This commit is contained in:
Michael Bucari-Tovo 2023-02-24 12:12:41 -07:00
parent 49982043e0
commit 1b0a7f5062
25 changed files with 299 additions and 63 deletions

View File

@ -13,7 +13,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AAXClean.Codecs" Version="0.5.16" /> <PackageReference Include="AAXClean.Codecs" Version="1.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -40,8 +40,14 @@ namespace AaxDecrypter
AaxFile.AppleTags.Album = AaxFile.AppleTags.Album?.Replace(" (Unabridged)", ""); AaxFile.AppleTags.Album = AaxFile.AppleTags.Album?.Replace(" (Unabridged)", "");
} }
if (DownloadOptions.FixupFile && !string.IsNullOrWhiteSpace(AaxFile.AppleTags.Narrator)) if (DownloadOptions.FixupFile)
AaxFile.AppleTags.AppleListBox.EditOrAddTag("TCOM", AaxFile.AppleTags.Narrator); {
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("&#169;", "©");
}
//Finishing configuring lame encoder. //Finishing configuring lame encoder.
if (DownloadOptions.OutputFormat == OutputFormat.Mp3) if (DownloadOptions.OutputFormat == OutputFormat.Mp3)

View File

@ -93,15 +93,13 @@ That naming may not be desirable for everyone, but it's an easy change to instea
? AaxFile.ConvertToMultiMp4aAsync ? AaxFile.ConvertToMultiMp4aAsync
( (
splitChapters, splitChapters,
newSplitCallback => newSplit(++chapterCount, splitChapters, newSplitCallback), newSplitCallback => newSplit(++chapterCount, splitChapters, newSplitCallback)
DownloadOptions.TrimOutputToChapterLength
) )
: AaxFile.ConvertToMultiMp3Async : AaxFile.ConvertToMultiMp3Async
( (
splitChapters, splitChapters,
newSplitCallback => newSplit(++chapterCount, splitChapters, newSplitCallback), newSplitCallback => newSplit(++chapterCount, splitChapters, newSplitCallback),
DownloadOptions.LameConfig, DownloadOptions.LameConfig
DownloadOptions.TrimOutputToChapterLength
); );
void newSplit(int currentChapter, ChapterInfo splitChapters, NewSplitCallback newSplitCallback) void newSplit(int currentChapter, ChapterInfo splitChapters, NewSplitCallback newSplitCallback)

View File

@ -3,6 +3,7 @@ using AAXClean.Codecs;
using Dinah.Core.Net.Http; using Dinah.Core.Net.Http;
using FileManager; using FileManager;
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -14,12 +15,15 @@ namespace AaxDecrypter
public AaxcDownloadSingleConverter(string outFileName, string cacheDirectory, IDownloadOptions dlOptions) public AaxcDownloadSingleConverter(string outFileName, string cacheDirectory, IDownloadOptions dlOptions)
: base(outFileName, cacheDirectory, dlOptions) : base(outFileName, cacheDirectory, dlOptions)
{ {
var step = 1;
AsyncSteps.Name = $"Download and Convert Aaxc To {DownloadOptions.OutputFormat}"; AsyncSteps.Name = $"Download and Convert Aaxc To {DownloadOptions.OutputFormat}";
AsyncSteps["Step 1: Get Aaxc Metadata"] = () => Task.Run(Step_GetMetadata); AsyncSteps[$"Step {step++}: Get Aaxc Metadata"] = () => Task.Run(Step_GetMetadata);
AsyncSteps["Step 2: Download Decrypted Audiobook"] = Step_DownloadAndDecryptAudiobookAsync; AsyncSteps[$"Step {step++}: Download Decrypted Audiobook"] = Step_DownloadAndDecryptAudiobookAsync;
AsyncSteps["Step 3: Download Clips and Bookmarks"] = Step_DownloadClipsBookmarksAsync; if (DownloadOptions.MoveMoovToBeginning && DownloadOptions.OutputFormat is OutputFormat.M4b)
AsyncSteps["Step 4: Create Cue"] = Step_CreateCueAsync; 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<bool> Step_DownloadAndDecryptAudiobookAsync() protected async override Task<bool> Step_DownloadAndDecryptAudiobookAsync()
@ -33,17 +37,6 @@ namespace AaxDecrypter
{ {
await (AaxConversion = decryptAsync(outputFile)); 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;
}
return AaxConversion.IsCompletedSuccessfully; return AaxConversion.IsCompletedSuccessfully;
} }
finally finally
@ -52,23 +45,30 @@ namespace AaxDecrypter
} }
} }
private async Task<bool> 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) private void AaxConversion_MoovProgressUpdate(object sender, ConversionProgressEventArgs e)
{ {
averageSpeed.AddPosition(e.ProcessPosition.TotalSeconds); averageSpeed.AddPosition(e.ProcessPosition.TotalSeconds);
var remainingTimeToProcess = (e.TotalDuration - e.ProcessPosition).TotalSeconds; var remainingTimeToProcess = (e.EndTime - e.ProcessPosition).TotalSeconds;
var estTimeRemaining = remainingTimeToProcess / averageSpeed.Average; var estTimeRemaining = remainingTimeToProcess / averageSpeed.Average;
if (double.IsNormal(estTimeRemaining)) if (double.IsNormal(estTimeRemaining))
OnDecryptTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining)); OnDecryptTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining));
var progressPercent = 100d * (1 - remainingTimeToProcess / e.TotalDuration.TotalSeconds);
OnDecryptProgressUpdate( OnDecryptProgressUpdate(
new DownloadProgress new DownloadProgress
{ {
ProgressPercentage = progressPercent, ProgressPercentage = 100 * e.FractionCompleted,
BytesReceived = (long)(InputFileStream.Length * progressPercent), BytesReceived = (long)(InputFileStream.Length * e.FractionCompleted),
TotalBytesToReceive = InputFileStream.Length TotalBytesToReceive = InputFileStream.Length
}); });
} }
@ -79,15 +79,13 @@ namespace AaxDecrypter
( (
outputFile, outputFile,
DownloadOptions.LameConfig, DownloadOptions.LameConfig,
DownloadOptions.ChapterInfo, DownloadOptions.ChapterInfo
DownloadOptions.TrimOutputToChapterLength
) )
: DownloadOptions.FixupFile : DownloadOptions.FixupFile
? AaxFile.ConvertToMp4aAsync ? AaxFile.ConvertToMp4aAsync
( (
outputFile, outputFile,
DownloadOptions.ChapterInfo, DownloadOptions.ChapterInfo
DownloadOptions.TrimOutputToChapterLength
) )
: AaxFile.ConvertToMp4aAsync(outputFile); : AaxFile.ConvertToMp4aAsync(outputFile);
} }

View File

@ -27,6 +27,7 @@ namespace AaxDecrypter
protected IDownloadOptions DownloadOptions { get; } protected IDownloadOptions DownloadOptions { get; }
protected NetworkFileStream InputFileStream => nfsPersister.NetworkFileStream; protected NetworkFileStream InputFileStream => nfsPersister.NetworkFileStream;
protected virtual long InputFilePosition => InputFileStream.Position; protected virtual long InputFilePosition => InputFileStream.Position;
private bool downloadFinished;
private readonly NetworkFileStreamPersister nfsPersister; private readonly NetworkFileStreamPersister nfsPersister;
private readonly DownloadProgress zeroProgress; private readonly DownloadProgress zeroProgress;
@ -84,7 +85,11 @@ namespace AaxDecrypter
{ {
AverageSpeed averageSpeed = new(); AverageSpeed averageSpeed = new();
while (InputFileStream.CanRead && InputFileStream.Length > InputFilePosition && !InputFileStream.IsCancelled) while (
InputFileStream.CanRead
&& InputFileStream.Length > InputFilePosition
&& !InputFileStream.IsCancelled
&& !downloadFinished)
{ {
averageSpeed.AddPosition(InputFilePosition); averageSpeed.AddPosition(InputFilePosition);
@ -138,8 +143,7 @@ namespace AaxDecrypter
protected virtual void FinalizeDownload() protected virtual void FinalizeDownload()
{ {
nfsPersister?.Dispose(); nfsPersister?.Dispose();
OnDecryptTimeRemaining(TimeSpan.Zero); downloadFinished = true;
OnDecryptProgressUpdate(zeroProgress);
} }
protected async Task<bool> Step_DownloadClipsBookmarksAsync() protected async Task<bool> Step_DownloadClipsBookmarksAsync()

View File

@ -1,5 +1,6 @@
using AAXClean; using AAXClean;
using NAudio.Lame; using NAudio.Lame;
using System;
namespace AaxDecrypter namespace AaxDecrypter
{ {
@ -9,17 +10,26 @@ namespace AaxDecrypter
{ {
double bitrateMultiple = 1; 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 (mp4File.AudioChannels == 2)
{ {
if (downsample) if (downsample)
bitrateMultiple = 0.5; bitrateMultiple /= 2;
else else
lameConfig.Mode = MPEGMode.Stereo; lameConfig.Mode = MPEGMode.Stereo;
} }
if (matchSourceBitrate) if (matchSourceBitrate)
{ {
int kbps = (int)(mp4File.AverageBitrate * bitrateMultiple / 1024); int kbps = (int)Math.Round(mp4File.AverageBitrate * bitrateMultiple / 1024);
if (lameConfig.VBR is null) if (lameConfig.VBR is null)
lameConfig.BitRate = kbps; lameConfig.BitRate = kbps;

View File

@ -14,6 +14,7 @@ namespace AaxDecrypter
public class NetworkFileStream : Stream, IUpdatable public class NetworkFileStream : Stream, IUpdatable
{ {
public event EventHandler Updated; public event EventHandler Updated;
public event EventHandler DownloadCompleted;
#region Public Properties #region Public Properties
@ -139,7 +140,10 @@ namespace AaxDecrypter
public async Task BeginDownloadingAsync() public async Task BeginDownloadingAsync()
{ {
if (ContentLength != 0 && WritePosition == ContentLength) if (ContentLength != 0 && WritePosition == ContentLength)
{
_backgroundDownloadTask = Task.CompletedTask;
return; return;
}
if (ContentLength != 0 && WritePosition > ContentLength) if (ContentLength != 0 && WritePosition > ContentLength)
throw new WebException($"Specified write position (0x{WritePosition:X10}) is larger than {nameof(ContentLength)} (0x{ContentLength:X10})."); 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(); _writeFile.Close();
_downloadedPiece.Set(); _downloadedPiece.Set();
OnUpdate(); OnUpdate();
DownloadCompleted?.Invoke(this, null);
} }
} }

View File

@ -26,8 +26,11 @@ namespace AaxDecrypter
protected override async Task<bool> Step_DownloadAndDecryptAudiobookAsync() protected override async Task<bool> Step_DownloadAndDecryptAudiobookAsync()
{ {
while (InputFilePosition < InputFileStream.Length && !InputFileStream.IsCancelled) TaskCompletionSource completionSource = new();
await Task.Delay(200);
InputFileStream.DownloadCompleted += (_, _) => completionSource.SetResult();
await completionSource.Task;
if (IsCanceled) if (IsCanceled)
return false; return false;

View File

@ -17,10 +17,14 @@ namespace FileLiberator
protected LameConfig GetLameOptions(Configuration config) protected LameConfig GetLameOptions(Configuration config)
{ {
LameConfig lameConfig = new(); LameConfig lameConfig = new()
lameConfig.Mode = MPEGMode.Mono; {
Mode = MPEGMode.Mono,
Quality = config.LameEncoderQuality,
OutputSampleRate = (int)config.MaxSampleRate
};
if (config.LameTargetBitrate) if (config.LameTargetBitrate)
{ {
if (config.LameConstantBitrate) if (config.LameConstantBitrate)
lameConfig.BitRate = config.LameBitrate; lameConfig.BitRate = config.LameBitrate;

View File

@ -103,20 +103,20 @@ namespace FileLiberator
{ {
averageSpeed.AddPosition(e.ProcessPosition.TotalSeconds); averageSpeed.AddPosition(e.ProcessPosition.TotalSeconds);
var remainingTimeToProcess = (e.TotalDuration - e.ProcessPosition).TotalSeconds; var remainingTimeToProcess = (e.EndTime - e.ProcessPosition).TotalSeconds;
var estTimeRemaining = remainingTimeToProcess / averageSpeed.Average; var estTimeRemaining = remainingTimeToProcess / averageSpeed.Average;
if (double.IsNormal(estTimeRemaining)) if (double.IsNormal(estTimeRemaining))
OnStreamingTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining)); OnStreamingTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining));
double progressPercent = 100 * e.ProcessPosition.TotalSeconds / e.TotalDuration.TotalSeconds; double progressPercent = 100 * e.FractionCompleted;
OnStreamingProgressChanged( OnStreamingProgressChanged(
new DownloadProgress new DownloadProgress
{ {
ProgressPercentage = progressPercent, ProgressPercentage = progressPercent,
BytesReceived = (long)e.ProcessPosition.TotalSeconds, BytesReceived = (long)(e.ProcessPosition - e.StartTime).TotalSeconds,
TotalBytesToReceive = (long)e.TotalDuration.TotalSeconds TotalBytesToReceive = (long)(e.EndTime - e.StartTime).TotalSeconds
}); });
} }
} }

View File

@ -22,7 +22,7 @@ namespace FileLiberator
public OutputFormat OutputFormat { get; init; } public OutputFormat OutputFormat { get; init; }
public ChapterInfo ChapterInfo { get; init; } public ChapterInfo ChapterInfo { get; init; }
public NAudio.Lame.LameConfig LameConfig { 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 TrimOutputToChapterLength => config.AllowLibationFixup && config.StripAudibleBrandAudio;
public bool StripUnabridged => config.AllowLibationFixup && config.StripUnabridged; public bool StripUnabridged => config.AllowLibationFixup && config.StripUnabridged;
public bool CreateCueSheet => config.CreateCueSheet; public bool CreateCueSheet => config.CreateCueSheet;

View File

@ -2,8 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="850" d:DesignHeight="620" mc:Ignorable="d" d:DesignWidth="900" d:DesignHeight="700"
MinWidth="800" MinHeight="620" MinWidth="900" MinHeight="700"
x:Class="LibationAvalonia.Dialogs.SettingsDialog" x:Class="LibationAvalonia.Dialogs.SettingsDialog"
xmlns:controls="clr-namespace:LibationAvalonia.Controls" xmlns:controls="clr-namespace:LibationAvalonia.Controls"
Title="Edit Settings" Title="Edit Settings"
@ -603,6 +603,25 @@
</CheckBox> </CheckBox>
</Grid> </Grid>
<Grid Margin="5,5,5,0" RowDefinitions="Auto,Auto" ColumnDefinitions="Auto,*,Auto">
<TextBlock Margin="0,0,0,5" Text="Max audio sample rate:" />
<controls:WheelComboBox
Grid.Row="1"
HorizontalAlignment="Stretch"
Items="{Binding AudioSettings.SampleRates}"
SelectedItem="{Binding AudioSettings.SelectedSampleRate, Mode=TwoWay}"/>
<TextBlock Margin="0,0,0,5" Grid.Column="2" Text="Encoder Quality:" />
<controls:WheelComboBox
Grid.Column="2"
Grid.Row="1"
HorizontalAlignment="Stretch"
Items="{Binding AudioSettings.EncoderQualities}"
SelectedItem="{Binding AudioSettings.SelectedEncoderQuality, Mode=TwoWay}"/>
</Grid>
<controls:GroupBox <controls:GroupBox
Margin="5,5,5,0" Margin="5,5,5,0"
BorderWidth="1" BorderWidth="1"

View File

@ -11,6 +11,7 @@ using System.Linq;
using FileManager; using FileManager;
using System.IO; using System.IO;
using Avalonia.Collections; using Avalonia.Collections;
using LibationUiBase;
namespace LibationAvalonia.Dialogs namespace LibationAvalonia.Dialogs
{ {
@ -372,6 +373,31 @@ namespace LibationAvalonia.Dialogs
private int _lameBitrate; private int _lameBitrate;
private int _lameVBRQuality; private int _lameVBRQuality;
private string _chapterTitleTemplate; private string _chapterTitleTemplate;
public SampleRateSelection SelectedSampleRate { get; set; }
public NAudio.Lame.EncoderQuality SelectedEncoderQuality { get; set; }
public AvaloniaList<SampleRateSelection> 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<NAudio.Lame.EncoderQuality> EncoderQualities { get; }
= new(
new[]
{
NAudio.Lame.EncoderQuality.High,
NAudio.Lame.EncoderQuality.Standard,
NAudio.Lame.EncoderQuality.Fast,
});
public AudioSettings(Configuration config) public AudioSettings(Configuration config)
{ {
@ -398,6 +424,9 @@ namespace LibationAvalonia.Dialogs
LameMatchSource = config.LameMatchSourceBR; LameMatchSource = config.LameMatchSourceBR;
LameBitrate = config.LameBitrate; LameBitrate = config.LameBitrate;
LameVBRQuality = config.LameVBRQuality; LameVBRQuality = config.LameVBRQuality;
SelectedSampleRate = SampleRates.FirstOrDefault(s => s.SampleRate == config.MaxSampleRate);
SelectedEncoderQuality = config.LameEncoderQuality;
} }
public Task<bool> SaveSettingsAsync(Configuration config) public Task<bool> SaveSettingsAsync(Configuration config)
@ -422,6 +451,9 @@ namespace LibationAvalonia.Dialogs
config.LameBitrate = LameBitrate; config.LameBitrate = LameBitrate;
config.LameVBRQuality = LameVBRQuality; config.LameVBRQuality = LameVBRQuality;
config.LameEncoderQuality = SelectedEncoderQuality;
config.MaxSampleRate = SelectedSampleRate?.SampleRate ?? config.MaxSampleRate;
return Task.FromResult(true); return Task.FromResult(true);
} }

View File

@ -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); 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) private void SetQueueCollapseState(bool collapsed)
{ {
_viewModel.QueueOpen = !collapsed; _viewModel.QueueOpen = !collapsed;

View File

@ -188,7 +188,8 @@
<views:ProductsDisplay <views:ProductsDisplay
Name="productsDisplay" Name="productsDisplay"
DataContext="{Binding ProductsDisplay}" DataContext="{Binding ProductsDisplay}"
LiberateClicked="ProductsDisplay_LiberateClicked"/> LiberateClicked="ProductsDisplay_LiberateClicked"
ConvertToMp3Clicked="ProductsDisplay_ConvertToMp3Clicked" />
</SplitView> </SplitView>
</Border> </Border>

View File

@ -19,6 +19,7 @@ namespace LibationAvalonia.Views
public partial class ProductsDisplay : UserControl public partial class ProductsDisplay : UserControl
{ {
public event EventHandler<LibraryBook> LiberateClicked; public event EventHandler<LibraryBook> LiberateClicked;
public event EventHandler<LibraryBook> ConvertToMp3Clicked;
private ProductsDisplayViewModel _viewModel => DataContext as ProductsDisplayViewModel; private ProductsDisplayViewModel _viewModel => DataContext as ProductsDisplayViewModel;
ImageDisplayDialog imageDisplayDialog; ImageDisplayDialog imageDisplayDialog;
@ -131,6 +132,12 @@ namespace LibationAvalonia.Views
await MessageBox.ShowAdminAlert(null, msg, msg, ex); 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" }; var bookRecordMenuItem = new MenuItem { Header = "View _Bookmarks/Clips" };
bookRecordMenuItem.Click += async (_, _) => await new BookRecordsDialog(entry.LibraryBook).ShowDialog(VisualRoot as Window); bookRecordMenuItem.Click += async (_, _) => await new BookRecordsDialog(entry.LibraryBook).ShowDialog(VisualRoot as Window);
@ -141,6 +148,7 @@ namespace LibationAvalonia.Views
setNotDownloadMenuItem, setNotDownloadMenuItem,
removeMenuItem, removeMenuItem,
locateFileMenuItem, locateFileMenuItem,
convertToMp3MenuItem,
new Separator(), new Separator(),
bookRecordMenuItem bookRecordMenuItem
}); });

View File

@ -121,6 +121,12 @@ namespace LibationFileManager
[Description("Lame encoder target. true = Bitrate, false = Quality")] [Description("Lame encoder target. true = Bitrate, false = Quality")]
public bool LameTargetBitrate { get => GetNonString(defaultValue: false); set => SetNonString(value); } 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")] [Description("Lame encoder downsamples to mono")]
public bool LameDownsampleMono { get => GetNonString(defaultValue: true); set => SetNonString(value); } public bool LameDownsampleMono { get => GetNonString(defaultValue: true); set => SetNonString(value); }

View File

@ -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";
}
}

View File

@ -1,6 +1,7 @@
using System; using System;
using LibationFileManager; using LibationFileManager;
using System.Linq; using System.Linq;
using LibationUiBase;
namespace LibationWinForms.Dialogs namespace LibationWinForms.Dialogs
{ {
@ -26,6 +27,25 @@ namespace LibationWinForms.Dialogs
Configuration.ClipBookmarkFormat.Json 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; allowLibationFixupCbox.Checked = config.AllowLibationFixup;
createCueSheetCbox.Checked = config.CreateCueSheet; createCueSheetCbox.Checked = config.CreateCueSheet;
downloadCoverArtCbox.Checked = config.DownloadCoverArt; downloadCoverArtCbox.Checked = config.DownloadCoverArt;
@ -42,6 +62,8 @@ namespace LibationWinForms.Dialogs
lameTargetBitrateRb.Checked = config.LameTargetBitrate; lameTargetBitrateRb.Checked = config.LameTargetBitrate;
lameTargetQualityRb.Checked = !config.LameTargetBitrate; lameTargetQualityRb.Checked = !config.LameTargetBitrate;
maxSampleRateCb.SelectedItem = maxSampleRateCb.Items.Cast<SampleRateSelection>().Single(s => s.SampleRate == config.MaxSampleRate);
encoderQualityCb.SelectedItem = config.LameEncoderQuality;
lameDownsampleMonoCbox.Checked = config.LameDownsampleMono; lameDownsampleMonoCbox.Checked = config.LameDownsampleMono;
lameBitrateTb.Value = config.LameBitrate; lameBitrateTb.Value = config.LameBitrate;
lameConstantBitrateCbox.Checked = config.LameConstantBitrate; lameConstantBitrateCbox.Checked = config.LameConstantBitrate;
@ -75,6 +97,9 @@ namespace LibationWinForms.Dialogs
config.MoveMoovToBeginning = moveMoovAtomCbox.Checked; config.MoveMoovToBeginning = moveMoovAtomCbox.Checked;
config.LameTargetBitrate = lameTargetBitrateRb.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.LameDownsampleMono = lameDownsampleMonoCbox.Checked;
config.LameBitrate = lameBitrateTb.Value; config.LameBitrate = lameBitrateTb.Value;
config.LameConstantBitrate = lameConstantBitrateCbox.Checked; config.LameConstantBitrate = lameConstantBitrateCbox.Checked;

View File

@ -115,6 +115,10 @@
this.retainAaxFileCbox = new System.Windows.Forms.CheckBox(); this.retainAaxFileCbox = new System.Windows.Forms.CheckBox();
this.downloadCoverArtCbox = new System.Windows.Forms.CheckBox(); this.downloadCoverArtCbox = new System.Windows.Forms.CheckBox();
this.createCueSheetCbox = 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.badBookGb.SuspendLayout();
this.tabControl.SuspendLayout(); this.tabControl.SuspendLayout();
this.tab1ImportantSettings.SuspendLayout(); this.tab1ImportantSettings.SuspendLayout();
@ -743,6 +747,10 @@
// //
// lameOptionsGb // 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.lameDownsampleMonoCbox);
this.lameOptionsGb.Controls.Add(this.lameBitrateGb); this.lameOptionsGb.Controls.Add(this.lameBitrateGb);
this.lameOptionsGb.Controls.Add(this.label1); this.lameOptionsGb.Controls.Add(this.label1);
@ -757,8 +765,7 @@
// //
// lameDownsampleMonoCbox // lameDownsampleMonoCbox
// //
this.lameDownsampleMonoCbox.AutoSize = true; this.lameDownsampleMonoCbox.Location = new System.Drawing.Point(237, 30);
this.lameDownsampleMonoCbox.Location = new System.Drawing.Point(234, 35);
this.lameDownsampleMonoCbox.Name = "lameDownsampleMonoCbox"; this.lameDownsampleMonoCbox.Name = "lameDownsampleMonoCbox";
this.lameDownsampleMonoCbox.Size = new System.Drawing.Size(184, 34); this.lameDownsampleMonoCbox.Size = new System.Drawing.Size(184, 34);
this.lameDownsampleMonoCbox.TabIndex = 1; this.lameDownsampleMonoCbox.TabIndex = 1;
@ -776,9 +783,9 @@
this.lameBitrateGb.Controls.Add(this.label11); this.lameBitrateGb.Controls.Add(this.label11);
this.lameBitrateGb.Controls.Add(this.label3); this.lameBitrateGb.Controls.Add(this.label3);
this.lameBitrateGb.Controls.Add(this.lameBitrateTb); 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.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.TabIndex = 0;
this.lameBitrateGb.TabStop = false; this.lameBitrateGb.TabStop = false;
this.lameBitrateGb.Text = "Bitrate"; this.lameBitrateGb.Text = "Bitrate";
@ -786,7 +793,7 @@
// LameMatchSourceBRCbox // LameMatchSourceBRCbox
// //
this.LameMatchSourceBRCbox.AutoSize = true; 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.Name = "LameMatchSourceBRCbox";
this.LameMatchSourceBRCbox.Size = new System.Drawing.Size(140, 19); this.LameMatchSourceBRCbox.Size = new System.Drawing.Size(140, 19);
this.LameMatchSourceBRCbox.TabIndex = 3; this.LameMatchSourceBRCbox.TabIndex = 3;
@ -883,7 +890,7 @@
this.label1.AutoSize = true; this.label1.AutoSize = true;
this.label1.Enabled = false; this.label1.Enabled = false;
this.label1.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Italic, System.Drawing.GraphicsUnit.Point); 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.Name = "label1";
this.label1.Size = new System.Drawing.Size(172, 15); this.label1.Size = new System.Drawing.Size(172, 15);
this.label1.TabIndex = 1; this.label1.TabIndex = 1;
@ -904,9 +911,9 @@
this.lameQualityGb.Controls.Add(this.label14); this.lameQualityGb.Controls.Add(this.label14);
this.lameQualityGb.Controls.Add(this.label2); this.lameQualityGb.Controls.Add(this.label2);
this.lameQualityGb.Controls.Add(this.lameVBRQualityTb); 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.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.TabIndex = 0;
this.lameQualityGb.TabStop = false; this.lameQualityGb.TabStop = false;
this.lameQualityGb.Text = "Quality"; this.lameQualityGb.Text = "Quality";
@ -986,7 +993,7 @@
// label13 // label13
// //
this.label13.AutoSize = true; 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.Name = "label13";
this.label13.Size = new System.Drawing.Size(39, 15); this.label13.Size = new System.Drawing.Size(39, 15);
this.label13.TabIndex = 1; this.label13.TabIndex = 1;
@ -995,7 +1002,7 @@
// label10 // label10
// //
this.label10.AutoSize = true; 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.Name = "label10";
this.label10.Size = new System.Drawing.Size(43, 15); this.label10.Size = new System.Drawing.Size(43, 15);
this.label10.TabIndex = 1; this.label10.TabIndex = 1;
@ -1036,7 +1043,7 @@
this.groupBox2.Controls.Add(this.lameTargetBitrateRb); this.groupBox2.Controls.Add(this.lameTargetBitrateRb);
this.groupBox2.Location = new System.Drawing.Point(6, 22); this.groupBox2.Location = new System.Drawing.Point(6, 22);
this.groupBox2.Name = "groupBox2"; 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.TabIndex = 0;
this.groupBox2.TabStop = false; this.groupBox2.TabStop = false;
this.groupBox2.Text = "Target"; this.groupBox2.Text = "Target";
@ -1044,7 +1051,7 @@
// lameTargetQualityRb // lameTargetQualityRb
// //
this.lameTargetQualityRb.AutoSize = true; 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.Name = "lameTargetQualityRb";
this.lameTargetQualityRb.Size = new System.Drawing.Size(63, 19); this.lameTargetQualityRb.Size = new System.Drawing.Size(63, 19);
this.lameTargetQualityRb.TabIndex = 0; this.lameTargetQualityRb.TabIndex = 0;
@ -1056,7 +1063,7 @@
// lameTargetBitrateRb // lameTargetBitrateRb
// //
this.lameTargetBitrateRb.AutoSize = true; 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.Name = "lameTargetBitrateRb";
this.lameTargetBitrateRb.Size = new System.Drawing.Size(59, 19); this.lameTargetBitrateRb.Size = new System.Drawing.Size(59, 19);
this.lameTargetBitrateRb.TabIndex = 0; this.lameTargetBitrateRb.TabIndex = 0;
@ -1112,6 +1119,42 @@
this.createCueSheetCbox.UseVisualStyleBackColor = true; this.createCueSheetCbox.UseVisualStyleBackColor = true;
this.createCueSheetCbox.CheckedChanged += new System.EventHandler(this.allowLibationFixupCbox_CheckedChanged); 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 // SettingsDialog
// //
this.AcceptButton = this.saveBtn; this.AcceptButton = this.saveBtn;
@ -1253,5 +1296,9 @@
private System.Windows.Forms.ComboBox clipsBookmarksFormatCb; private System.Windows.Forms.ComboBox clipsBookmarksFormatCb;
private System.Windows.Forms.CheckBox downloadClipsBookmarksCbox; private System.Windows.Forms.CheckBox downloadClipsBookmarksCbox;
private System.Windows.Forms.CheckBox moveMoovAtomCbox; 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;
} }
} }

View File

@ -520,6 +520,7 @@
this.productsDisplay.VisibleCountChanged += new System.EventHandler<int>(this.productsDisplay_VisibleCountChanged); this.productsDisplay.VisibleCountChanged += new System.EventHandler<int>(this.productsDisplay_VisibleCountChanged);
this.productsDisplay.RemovableCountChanged += new System.EventHandler<int>(this.productsDisplay_RemovableCountChanged); this.productsDisplay.RemovableCountChanged += new System.EventHandler<int>(this.productsDisplay_RemovableCountChanged);
this.productsDisplay.LiberateClicked += new System.EventHandler<DataLayer.LibraryBook>(this.ProductsDisplay_LiberateClicked); this.productsDisplay.LiberateClicked += new System.EventHandler<DataLayer.LibraryBook>(this.ProductsDisplay_LiberateClicked);
this.productsDisplay.ConvertToMp3Clicked += new System.EventHandler<DataLayer.LibraryBook>(this.ProductsDisplay_ConvertToMp3Clicked);
this.productsDisplay.InitialLoaded += new System.EventHandler(this.productsDisplay_InitialLoaded); this.productsDisplay.InitialLoaded += new System.EventHandler(this.productsDisplay_InitialLoaded);
// //
// toggleQueueHideBtn // toggleQueueHideBtn

View File

@ -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) private void SetQueueCollapseState(bool collapsed)
{ {
if (collapsed && !splitContainer1.Panel2Collapsed) if (collapsed && !splitContainer1.Panel2Collapsed)

View File

@ -41,6 +41,7 @@
this.productsGrid.TabIndex = 0; this.productsGrid.TabIndex = 0;
this.productsGrid.VisibleCountChanged += new System.EventHandler<int>(this.productsGrid_VisibleCountChanged); this.productsGrid.VisibleCountChanged += new System.EventHandler<int>(this.productsGrid_VisibleCountChanged);
this.productsGrid.LiberateClicked += new LibationWinForms.GridView.LibraryBookEntryClickedEventHandler(this.productsGrid_LiberateClicked); 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.CoverClicked += new LibationWinForms.GridView.GridEntryClickedEventHandler(this.productsGrid_CoverClicked);
this.productsGrid.DetailsClicked += new LibationWinForms.GridView.LibraryBookEntryClickedEventHandler(this.productsGrid_DetailsClicked); this.productsGrid.DetailsClicked += new LibationWinForms.GridView.LibraryBookEntryClickedEventHandler(this.productsGrid_DetailsClicked);
this.productsGrid.DescriptionClicked += new LibationWinForms.GridView.GridEntryRectangleClickedEventHandler(this.productsGrid_DescriptionClicked); this.productsGrid.DescriptionClicked += new LibationWinForms.GridView.GridEntryRectangleClickedEventHandler(this.productsGrid_DescriptionClicked);

View File

@ -19,6 +19,7 @@ namespace LibationWinForms.GridView
public event EventHandler<int> VisibleCountChanged; public event EventHandler<int> VisibleCountChanged;
public event EventHandler<int> RemovableCountChanged; public event EventHandler<int> RemovableCountChanged;
public event EventHandler<LibraryBook> LiberateClicked; public event EventHandler<LibraryBook> LiberateClicked;
public event EventHandler<LibraryBook> ConvertToMp3Clicked;
public event EventHandler InitialLoaded; public event EventHandler InitialLoaded;
private bool hasBeenDisplayed; private bool hasBeenDisplayed;
@ -204,6 +205,12 @@ namespace LibationWinForms.GridView
LiberateClicked?.Invoke(this, liveGridEntry.LibraryBook); 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) private void productsGrid_RemovableCountChanged(object sender, EventArgs e)
{ {
RemovableCountChanged?.Invoke(sender, productsGrid.GetAllBookEntries().Count(lbe => lbe.Remove is RemoveStatus.Removed)); RemovableCountChanged?.Invoke(sender, productsGrid.GetAllBookEntries().Count(lbe => lbe.Remove is RemoveStatus.Removed));

View File

@ -22,6 +22,7 @@ namespace LibationWinForms.GridView
/// <summary>Number of visible rows has changed</summary> /// <summary>Number of visible rows has changed</summary>
public event EventHandler<int> VisibleCountChanged; public event EventHandler<int> VisibleCountChanged;
public event LibraryBookEntryClickedEventHandler LiberateClicked; public event LibraryBookEntryClickedEventHandler LiberateClicked;
public event LibraryBookEntryClickedEventHandler ConvertToMp3Clicked;
public event GridEntryClickedEventHandler CoverClicked; public event GridEntryClickedEventHandler CoverClicked;
public event LibraryBookEntryClickedEventHandler DetailsClicked; public event LibraryBookEntryClickedEventHandler DetailsClicked;
public event GridEntryRectangleClickedEventHandler DescriptionClicked; 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" }; var bookRecordMenuItem = new ToolStripMenuItem { Text = "View &Bookmarks/Clips" };
bookRecordMenuItem.Click += (_, _) => new BookRecordsDialog(entry.LibraryBook).ShowDialog(this); bookRecordMenuItem.Click += (_, _) => new BookRecordsDialog(entry.LibraryBook).ShowDialog(this);
@ -184,6 +192,7 @@ namespace LibationWinForms.GridView
stopLightContextMenu.Items.Add(setNotDownloadMenuItem); stopLightContextMenu.Items.Add(setNotDownloadMenuItem);
stopLightContextMenu.Items.Add(removeMenuItem); stopLightContextMenu.Items.Add(removeMenuItem);
stopLightContextMenu.Items.Add(locateFileMenuItem); stopLightContextMenu.Items.Add(locateFileMenuItem);
stopLightContextMenu.Items.Add(convertToMp3MenuItem);
stopLightContextMenu.Items.Add(new ToolStripSeparator()); stopLightContextMenu.Items.Add(new ToolStripSeparator());
stopLightContextMenu.Items.Add(bookRecordMenuItem); stopLightContextMenu.Items.Add(bookRecordMenuItem);