Add lame options to ConvertToMp3

This commit is contained in:
Michael Bucari-Tovo 2022-06-13 21:55:06 -06:00
parent 2bc74d5378
commit 159f5cbd00
4 changed files with 320 additions and 316 deletions

View File

@ -21,6 +21,7 @@ namespace AaxDecrypter
public event EventHandler<string> FileCreated; public event EventHandler<string> FileCreated;
public bool IsCanceled { get; set; } public bool IsCanceled { get; set; }
public string TempFilePath { get; }
protected string OutputFileName { get; private set; } protected string OutputFileName { get; private set; }
protected DownloadOptions DownloadOptions { get; } protected DownloadOptions DownloadOptions { get; }
@ -33,7 +34,6 @@ namespace AaxDecrypter
private NetworkFileStreamPersister nfsPersister; private NetworkFileStreamPersister nfsPersister;
private string jsonDownloadState { get; } private string jsonDownloadState { get; }
public string TempFilePath { get; }
protected AudiobookDownloadBase(string outFileName, string cacheDirectory, DownloadOptions dlLic) protected AudiobookDownloadBase(string outFileName, string cacheDirectory, DownloadOptions dlLic)
{ {
@ -65,7 +65,7 @@ namespace AaxDecrypter
public bool Run() public bool Run()
{ {
var (IsSuccess, Elapsed) = Steps.Run(); var (IsSuccess, _) = Steps.Run();
if (!IsSuccess) if (!IsSuccess)
Serilog.Log.Logger.Error("Conversion failed"); Serilog.Log.Logger.Error("Conversion failed");
@ -79,10 +79,8 @@ namespace AaxDecrypter
=> RetrievedAuthors?.Invoke(this, authors); => RetrievedAuthors?.Invoke(this, authors);
protected void OnRetrievedNarrators(string narrators) protected void OnRetrievedNarrators(string narrators)
=> RetrievedNarrators?.Invoke(this, narrators); => RetrievedNarrators?.Invoke(this, narrators);
protected void OnRetrievedCoverArt(byte[] coverArt) protected void OnRetrievedCoverArt(byte[] coverArt)
=> RetrievedCoverArt?.Invoke(this, coverArt); => RetrievedCoverArt?.Invoke(this, coverArt);
protected void OnDecryptProgressUpdate(DownloadProgress downloadProgress) protected void OnDecryptProgressUpdate(DownloadProgress downloadProgress)
=> DecryptProgressUpdate?.Invoke(this, downloadProgress); => DecryptProgressUpdate?.Invoke(this, downloadProgress);
protected void OnDecryptTimeRemaining(TimeSpan timeRemaining) protected void OnDecryptTimeRemaining(TimeSpan timeRemaining)

View File

@ -1,4 +1,6 @@
using System; using LibationFileManager;
using NAudio.Lame;
using System;
namespace FileLiberator namespace FileLiberator
{ {
@ -12,6 +14,30 @@ namespace FileLiberator
public event EventHandler<byte[]> CoverImageDiscovered; public event EventHandler<byte[]> CoverImageDiscovered;
public abstract void Cancel(); public abstract void Cancel();
protected LameConfig GetLameOptions(Configuration config)
{
LameConfig lameConfig = new();
lameConfig.Mode = MPEGMode.Mono;
if (config.LameTargetBitrate)
{
if (config.LameConstantBitrate)
lameConfig.BitRate = config.LameBitrate;
else
{
lameConfig.ABRRateKbps = config.LameBitrate;
lameConfig.VBR = VBRMode.ABR;
lameConfig.WriteVBRTag = true;
}
}
else
{
lameConfig.VBR = VBRMode.Default;
lameConfig.VBRQuality = config.LameVBRQuality;
lameConfig.WriteVBRTag = true;
}
return lameConfig;
}
protected void OnTitleDiscovered(string title) => OnTitleDiscovered(null, title); protected void OnTitleDiscovered(string title) => OnTitleDiscovered(null, title);
protected void OnTitleDiscovered(object _, string title) protected void OnTitleDiscovered(object _, string title)
{ {

View File

@ -4,7 +4,6 @@ using System.Threading.Tasks;
using AAXClean; using AAXClean;
using AAXClean.Codecs; using AAXClean.Codecs;
using DataLayer; using DataLayer;
using Dinah.Core;
using Dinah.Core.ErrorHandling; using Dinah.Core.ErrorHandling;
using Dinah.Core.Net.Http; using Dinah.Core.Net.Http;
using FileManager; using FileManager;
@ -12,85 +11,85 @@ using LibationFileManager;
namespace FileLiberator namespace FileLiberator
{ {
public class ConvertToMp3 : AudioDecodable public class ConvertToMp3 : AudioDecodable
{ {
public override string Name => "Convert to Mp3"; public override string Name => "Convert to Mp3";
private Mp4File m4bBook; private Mp4File m4bBook;
private long fileSize; private long fileSize;
private static string Mp3FileName(string m4bPath) => Path.ChangeExtension(m4bPath ?? "", ".mp3"); private static string Mp3FileName(string m4bPath) => Path.ChangeExtension(m4bPath ?? "", ".mp3");
public override void Cancel() public override void Cancel()
{
m4bBook?.Cancel();
}
public static bool ValidateMp3(LibraryBook libraryBook)
{ {
var path = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId); m4bBook?.Cancel();
return path?.ToLower()?.EndsWith(".m4b") == true && !File.Exists(Mp3FileName(path)); }
}
public override bool Validate(LibraryBook libraryBook) => ValidateMp3(libraryBook); public static bool ValidateMp3(LibraryBook libraryBook)
{
var path = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId);
return path?.ToLower()?.EndsWith(".m4b") == true && !File.Exists(Mp3FileName(path));
}
public override async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook) public override bool Validate(LibraryBook libraryBook) => ValidateMp3(libraryBook);
{
OnBegin(libraryBook);
try public override async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
{ {
var m4bPath = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId); OnBegin(libraryBook);
m4bBook = new Mp4File(m4bPath, FileAccess.Read);
m4bBook.ConversionProgressUpdate += M4bBook_ConversionProgressUpdate;
fileSize = m4bBook.InputStream.Length; try
{
var m4bPath = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId);
m4bBook = new Mp4File(m4bPath, FileAccess.Read);
m4bBook.ConversionProgressUpdate += M4bBook_ConversionProgressUpdate;
OnTitleDiscovered(m4bBook.AppleTags.Title); fileSize = m4bBook.InputStream.Length;
OnAuthorsDiscovered(m4bBook.AppleTags.FirstAuthor);
OnNarratorsDiscovered(m4bBook.AppleTags.Narrator);
OnCoverImageDiscovered(m4bBook.AppleTags.Cover);
using var mp3File = File.OpenWrite(Path.GetTempFileName()); OnTitleDiscovered(m4bBook.AppleTags.Title);
OnAuthorsDiscovered(m4bBook.AppleTags.FirstAuthor);
OnNarratorsDiscovered(m4bBook.AppleTags.Narrator);
OnCoverImageDiscovered(m4bBook.AppleTags.Cover);
var result = await Task.Run(() => m4bBook.ConvertToMp3(mp3File)); using var mp3File = File.OpenWrite(Path.GetTempFileName());
m4bBook.InputStream.Close(); var lameConfig = GetLameOptions(Configuration.Instance);
mp3File.Close(); var result = await Task.Run(() => m4bBook.ConvertToMp3(mp3File, lameConfig));
m4bBook.InputStream.Close();
mp3File.Close();
var proposedMp3Path = Mp3FileName(m4bPath); var proposedMp3Path = Mp3FileName(m4bPath);
var realMp3Path = FileUtility.SaferMoveToValidPath(mp3File.Name, proposedMp3Path); var realMp3Path = FileUtility.SaferMoveToValidPath(mp3File.Name, proposedMp3Path);
OnFileCreated(libraryBook, realMp3Path); OnFileCreated(libraryBook, realMp3Path);
if (result == ConversionResult.Failed) if (result == ConversionResult.Failed)
return new StatusHandler { "Conversion failed" }; return new StatusHandler { "Conversion failed" };
else if (result == ConversionResult.Cancelled) else if (result == ConversionResult.Cancelled)
return new StatusHandler { "Cancelled" }; return new StatusHandler { "Cancelled" };
else else
return new StatusHandler(); return new StatusHandler();
} }
finally finally
{ {
OnCompleted(libraryBook); OnCompleted(libraryBook);
} }
} }
private void M4bBook_ConversionProgressUpdate(object sender, ConversionProgressEventArgs e) private void M4bBook_ConversionProgressUpdate(object sender, ConversionProgressEventArgs e)
{ {
var duration = m4bBook.Duration; var duration = m4bBook.Duration;
var remainingSecsToProcess = (duration - e.ProcessPosition).TotalSeconds; var remainingSecsToProcess = (duration - e.ProcessPosition).TotalSeconds;
var estTimeRemaining = remainingSecsToProcess / e.ProcessSpeed; var estTimeRemaining = remainingSecsToProcess / e.ProcessSpeed;
if (double.IsNormal(estTimeRemaining)) if (double.IsNormal(estTimeRemaining))
OnStreamingTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining)); OnStreamingTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining));
double progressPercent = 100 * e.ProcessPosition.TotalSeconds / duration.TotalSeconds; double progressPercent = 100 * e.ProcessPosition.TotalSeconds / duration.TotalSeconds;
OnStreamingProgressChanged( OnStreamingProgressChanged(
new DownloadProgress new DownloadProgress
{ {
ProgressPercentage = progressPercent, ProgressPercentage = progressPercent,
BytesReceived = (long)(fileSize * progressPercent), BytesReceived = (long)(fileSize * progressPercent),
TotalBytesToReceive = fileSize TotalBytesToReceive = fileSize
}); });
} }
} }
} }

View File

@ -14,241 +14,222 @@ using LibationFileManager;
namespace FileLiberator namespace FileLiberator
{ {
public class DownloadDecryptBook : AudioDecodable public class DownloadDecryptBook : AudioDecodable
{ {
public override string Name => "Download & Decrypt"; public override string Name => "Download & Decrypt";
private AudiobookDownloadBase abDownloader; private AudiobookDownloadBase abDownloader;
public override bool Validate(LibraryBook libraryBook) => !libraryBook.Book.Audio_Exists(); public override bool Validate(LibraryBook libraryBook) => !libraryBook.Book.Audio_Exists();
public override void Cancel() => abDownloader?.Cancel(); public override void Cancel() => abDownloader?.Cancel();
public override async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook) public override async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
{ {
var entries = new List<FilePathCache.CacheEntry>(); var entries = new List<FilePathCache.CacheEntry>();
// these only work so minimally b/c CacheEntry is a record. // these only work so minimally b/c CacheEntry is a record.
// in case of parallel decrypts, only capture the ones for this book id. // in case of parallel decrypts, only capture the ones for this book id.
// if user somehow starts multiple decrypts of the same book in parallel: on their own head be it // if user somehow starts multiple decrypts of the same book in parallel: on their own head be it
void FilePathCache_Inserted(object sender, FilePathCache.CacheEntry e) void FilePathCache_Inserted(object sender, FilePathCache.CacheEntry e)
{
if (e.Id.EqualsInsensitive(libraryBook.Book.AudibleProductId))
entries.Add(e);
}
void FilePathCache_Removed(object sender, FilePathCache.CacheEntry e)
{
if (e.Id.EqualsInsensitive(libraryBook.Book.AudibleProductId))
entries.Remove(e);
}
OnBegin(libraryBook);
try
{
if (libraryBook.Book.Audio_Exists())
return new StatusHandler { "Cannot find decrypt. Final audio file already exists" };
bool success = false;
try
{
FilePathCache.Inserted += FilePathCache_Inserted;
FilePathCache.Removed += FilePathCache_Removed;
success = await downloadAudiobookAsync(libraryBook);
}
finally
{
FilePathCache.Inserted -= FilePathCache_Inserted;
FilePathCache.Removed -= FilePathCache_Removed;
}
// decrypt failed
if (!success)
{
foreach (var tmpFile in entries.Where(f => f.FileType != FileType.AAXC))
FileUtility.SaferDelete(tmpFile.Path);
return abDownloader?.IsCanceled == true ?
new StatusHandler { "Cancelled" } :
new StatusHandler { "Decrypt failed" };
}
// moves new files from temp dir to final dest.
// This could take a few seconds if moving hundreds of files.
var movedAudioFile = await Task.Run(() => moveFilesToBooksDir(libraryBook, entries));
// decrypt failed
if (!movedAudioFile)
return new StatusHandler { "Cannot find final audio file after decryption" };
if (Configuration.Instance.DownloadCoverArt)
DownloadCoverArt(libraryBook);
libraryBook.Book.UpdateBookStatus(LiberatedStatus.Liberated);
return new StatusHandler();
}
finally
{
OnCompleted(libraryBook);
}
}
private async Task<bool> downloadAudiobookAsync(LibraryBook libraryBook)
{
var config = Configuration.Instance;
downloadValidation(libraryBook);
var api = await libraryBook.GetApiAsync();
var contentLic = await api.GetDownloadLicenseAsync(libraryBook.Book.AudibleProductId);
var audiobookDlLic = BuildDownloadOptions(config, contentLic);
var outFileName = AudibleFileStorage.Audio.GetInProgressFilename(libraryBook, audiobookDlLic.OutputFormat.ToString().ToLower());
var cacheDir = AudibleFileStorage.DownloadsInProgressDirectory;
if (contentLic.DrmType != AudibleApi.Common.DrmType.Adrm)
abDownloader = new UnencryptedAudiobookDownloader(outFileName, cacheDir, audiobookDlLic);
else
{
AaxcDownloadConvertBase converter
= config.SplitFilesByChapter ? new AaxcDownloadMultiConverter(
outFileName, cacheDir, audiobookDlLic,
AudibleFileStorage.Audio.CreateMultipartRenamerFunc(libraryBook))
: new AaxcDownloadSingleConverter(outFileName, cacheDir, audiobookDlLic);
if (config.AllowLibationFixup)
converter.RetrievedMetadata += (_, tags) => tags.Generes = string.Join(", ", libraryBook.Book.CategoriesNames());
abDownloader = converter;
}
abDownloader.DecryptProgressUpdate += OnStreamingProgressChanged;
abDownloader.DecryptTimeRemaining += OnStreamingTimeRemaining;
abDownloader.RetrievedTitle += OnTitleDiscovered;
abDownloader.RetrievedAuthors += OnAuthorsDiscovered;
abDownloader.RetrievedNarrators += OnNarratorsDiscovered;
abDownloader.RetrievedCoverArt += AaxcDownloader_RetrievedCoverArt;
abDownloader.FileCreated += (_, path) => OnFileCreated(libraryBook, path);
// REAL WORK DONE HERE
var success = await Task.Run(abDownloader.Run);
return success;
}
private static DownloadOptions BuildDownloadOptions(Configuration config, AudibleApi.Common.ContentLicense contentLic)
{
//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.
bool encrypted = contentLic.DrmType == AudibleApi.Common.DrmType.Adrm;
var outputFormat = !encrypted || (config.AllowLibationFixup && config.DecryptToLossy) ?
OutputFormat.Mp3 : OutputFormat.M4b;
var audiobookDlLic = new DownloadOptions
(
contentLic?.ContentMetadata?.ContentUrl?.OfflineUrl,
Resources.USER_AGENT
)
{
AudibleKey = contentLic?.Voucher?.Key,
AudibleIV = contentLic?.Voucher?.Iv,
OutputFormat = outputFormat,
TrimOutputToChapterLength = config.AllowLibationFixup && config.StripAudibleBrandAudio,
RetainEncryptedFile = config.RetainAaxFile && encrypted,
StripUnabridged = config.AllowLibationFixup && config.StripUnabridged,
Downsample = config.AllowLibationFixup && config.LameDownsampleMono,
MatchSourceBitrate = config.AllowLibationFixup && config.LameMatchSourceBR && config.LameTargetBitrate,
CreateCueSheet = config.CreateCueSheet
};
if (config.AllowLibationFixup || outputFormat == OutputFormat.Mp3)
{
long startMs = audiobookDlLic.TrimOutputToChapterLength ?
contentLic.ContentMetadata.ChapterInfo.BrandIntroDurationMs : 0;
audiobookDlLic.ChapterInfo = new AAXClean.ChapterInfo(TimeSpan.FromMilliseconds(startMs));
for (int i = 0; i < contentLic.ContentMetadata.ChapterInfo.Chapters.Length; i++)
{
var chapter = contentLic.ContentMetadata.ChapterInfo.Chapters[i];
long chapLenMs = chapter.LengthMs;
if (i == 0)
chapLenMs -= startMs;
if (config.StripAudibleBrandAudio && i == contentLic.ContentMetadata.ChapterInfo.Chapters.Length - 1)
chapLenMs -= contentLic.ContentMetadata.ChapterInfo.BrandOutroDurationMs;
audiobookDlLic.ChapterInfo.AddChapter(chapter.Title, TimeSpan.FromMilliseconds(chapLenMs));
}
}
audiobookDlLic.LameConfig = new();
audiobookDlLic.LameConfig.Mode = NAudio.Lame.MPEGMode.Mono;
if (config.LameTargetBitrate)
{ {
if (config.LameConstantBitrate) if (e.Id.EqualsInsensitive(libraryBook.Book.AudibleProductId))
audiobookDlLic.LameConfig.BitRate = config.LameBitrate; entries.Add(e);
else
{
audiobookDlLic.LameConfig.ABRRateKbps = config.LameBitrate;
audiobookDlLic.LameConfig.VBR = NAudio.Lame.VBRMode.ABR;
audiobookDlLic.LameConfig.WriteVBRTag = true;
}
} }
void FilePathCache_Removed(object sender, FilePathCache.CacheEntry e)
{
if (e.Id.EqualsInsensitive(libraryBook.Book.AudibleProductId))
entries.Remove(e);
}
OnBegin(libraryBook);
try
{
if (libraryBook.Book.Audio_Exists())
return new StatusHandler { "Cannot find decrypt. Final audio file already exists" };
bool success = false;
try
{
FilePathCache.Inserted += FilePathCache_Inserted;
FilePathCache.Removed += FilePathCache_Removed;
success = await downloadAudiobookAsync(libraryBook);
}
finally
{
FilePathCache.Inserted -= FilePathCache_Inserted;
FilePathCache.Removed -= FilePathCache_Removed;
}
// decrypt failed
if (!success)
{
foreach (var tmpFile in entries.Where(f => f.FileType != FileType.AAXC))
FileUtility.SaferDelete(tmpFile.Path);
return abDownloader?.IsCanceled == true ?
new StatusHandler { "Cancelled" } :
new StatusHandler { "Decrypt failed" };
}
// moves new files from temp dir to final dest.
// This could take a few seconds if moving hundreds of files.
var movedAudioFile = await Task.Run(() => moveFilesToBooksDir(libraryBook, entries));
// decrypt failed
if (!movedAudioFile)
return new StatusHandler { "Cannot find final audio file after decryption" };
if (Configuration.Instance.DownloadCoverArt)
DownloadCoverArt(libraryBook);
libraryBook.Book.UpdateBookStatus(LiberatedStatus.Liberated);
return new StatusHandler();
}
finally
{
OnCompleted(libraryBook);
}
}
private async Task<bool> downloadAudiobookAsync(LibraryBook libraryBook)
{
var config = Configuration.Instance;
downloadValidation(libraryBook);
var api = await libraryBook.GetApiAsync();
var contentLic = await api.GetDownloadLicenseAsync(libraryBook.Book.AudibleProductId);
var audiobookDlLic = BuildDownloadOptions(config, contentLic);
var outFileName = AudibleFileStorage.Audio.GetInProgressFilename(libraryBook, audiobookDlLic.OutputFormat.ToString().ToLower());
var cacheDir = AudibleFileStorage.DownloadsInProgressDirectory;
if (contentLic.DrmType != AudibleApi.Common.DrmType.Adrm)
abDownloader = new UnencryptedAudiobookDownloader(outFileName, cacheDir, audiobookDlLic);
else else
{ {
audiobookDlLic.LameConfig.VBR = NAudio.Lame.VBRMode.Default; AaxcDownloadConvertBase converter
audiobookDlLic.LameConfig.VBRQuality = config.LameVBRQuality; = config.SplitFilesByChapter ?
audiobookDlLic.LameConfig.WriteVBRTag = true; new AaxcDownloadMultiConverter(
} outFileName, cacheDir, audiobookDlLic,
AudibleFileStorage.Audio.CreateMultipartRenamerFunc(libraryBook)) :
new AaxcDownloadSingleConverter(outFileName, cacheDir, audiobookDlLic);
return audiobookDlLic; if (config.AllowLibationFixup)
} converter.RetrievedMetadata += (_, tags) => tags.Generes = string.Join(", ", libraryBook.Book.CategoriesNames());
private static void downloadValidation(LibraryBook libraryBook) abDownloader = converter;
{ }
string errorString(string field)
=> $"{errorTitle()}\r\nCannot download book. {field} is not known. Try re-importing the account which owns this book.";
string errorTitle() abDownloader.DecryptProgressUpdate += OnStreamingProgressChanged;
{ abDownloader.DecryptTimeRemaining += OnStreamingTimeRemaining;
var title abDownloader.RetrievedTitle += OnTitleDiscovered;
= (libraryBook.Book.Title.Length > 53) abDownloader.RetrievedAuthors += OnAuthorsDiscovered;
? $"{libraryBook.Book.Title.Truncate(50)}..." abDownloader.RetrievedNarrators += OnNarratorsDiscovered;
: libraryBook.Book.Title; abDownloader.RetrievedCoverArt += AaxcDownloader_RetrievedCoverArt;
var errorBookTitle = $"{title} [{libraryBook.Book.AudibleProductId}]"; abDownloader.FileCreated += (_, path) => OnFileCreated(libraryBook, path);
return errorBookTitle;
};
if (string.IsNullOrWhiteSpace(libraryBook.Account)) // REAL WORK DONE HERE
throw new Exception(errorString("Account")); var success = await Task.Run(abDownloader.Run);
if (string.IsNullOrWhiteSpace(libraryBook.Book.Locale)) return success;
throw new Exception(errorString("Locale")); }
}
private void AaxcDownloader_RetrievedCoverArt(object _, byte[] e) private DownloadOptions BuildDownloadOptions(Configuration config, AudibleApi.Common.ContentLicense contentLic)
{ {
if (e is not null) //I assume if ContentFormat == "MPEG" that the delivered file is an unencrypted mp3.
OnCoverImageDiscovered(e); //I also assume that if DrmType != Adrm, the file will be an mp3.
else if (Configuration.Instance.AllowLibationFixup) //These assumptions may be wrong, and only time and bug reports will tell.
abDownloader.SetCoverArt(OnRequestCoverArt());
}
/// <summary>Move new files to 'Books' directory</summary> bool encrypted = contentLic.DrmType == AudibleApi.Common.DrmType.Adrm;
/// <returns>True if audiobook file(s) were successfully created and can be located on disk. Else false.</returns>
private static bool moveFilesToBooksDir(LibraryBook libraryBook, List<FilePathCache.CacheEntry> entries)
{
// create final directory. move each file into it
var destinationDir = AudibleFileStorage.Audio.GetDestinationDirectory(libraryBook);
Directory.CreateDirectory(destinationDir);
FilePathCache.CacheEntry getFirstAudio() => entries.FirstOrDefault(f => f.FileType == FileType.Audio); var outputFormat = !encrypted || (config.AllowLibationFixup && config.DecryptToLossy) ?
OutputFormat.Mp3 : OutputFormat.M4b;
var dlOptions = new DownloadOptions
(
contentLic?.ContentMetadata?.ContentUrl?.OfflineUrl,
Resources.USER_AGENT
)
{
AudibleKey = contentLic?.Voucher?.Key,
AudibleIV = contentLic?.Voucher?.Iv,
OutputFormat = outputFormat,
TrimOutputToChapterLength = config.AllowLibationFixup && config.StripAudibleBrandAudio,
RetainEncryptedFile = config.RetainAaxFile && encrypted,
StripUnabridged = config.AllowLibationFixup && config.StripUnabridged,
Downsample = config.AllowLibationFixup && config.LameDownsampleMono,
MatchSourceBitrate = config.AllowLibationFixup && config.LameMatchSourceBR && config.LameTargetBitrate,
CreateCueSheet = config.CreateCueSheet,
LameConfig = GetLameOptions(config)
};
if (config.AllowLibationFixup || outputFormat == OutputFormat.Mp3)
{
long startMs = dlOptions.TrimOutputToChapterLength ?
contentLic.ContentMetadata.ChapterInfo.BrandIntroDurationMs : 0;
dlOptions.ChapterInfo = new AAXClean.ChapterInfo(TimeSpan.FromMilliseconds(startMs));
for (int i = 0; i < contentLic.ContentMetadata.ChapterInfo.Chapters.Length; i++)
{
var chapter = contentLic.ContentMetadata.ChapterInfo.Chapters[i];
long chapLenMs = chapter.LengthMs;
if (i == 0)
chapLenMs -= startMs;
if (config.StripAudibleBrandAudio && i == contentLic.ContentMetadata.ChapterInfo.Chapters.Length - 1)
chapLenMs -= contentLic.ContentMetadata.ChapterInfo.BrandOutroDurationMs;
dlOptions.ChapterInfo.AddChapter(chapter.Title, TimeSpan.FromMilliseconds(chapLenMs));
}
}
return dlOptions;
}
private static void downloadValidation(LibraryBook libraryBook)
{
string errorString(string field)
=> $"{errorTitle()}\r\nCannot download book. {field} is not known. Try re-importing the account which owns this book.";
string errorTitle()
{
var title
= (libraryBook.Book.Title.Length > 53)
? $"{libraryBook.Book.Title.Truncate(50)}..."
: libraryBook.Book.Title;
var errorBookTitle = $"{title} [{libraryBook.Book.AudibleProductId}]";
return errorBookTitle;
};
if (string.IsNullOrWhiteSpace(libraryBook.Account))
throw new Exception(errorString("Account"));
if (string.IsNullOrWhiteSpace(libraryBook.Book.Locale))
throw new Exception(errorString("Locale"));
}
private void AaxcDownloader_RetrievedCoverArt(object _, byte[] e)
{
if (e is not null)
OnCoverImageDiscovered(e);
else if (Configuration.Instance.AllowLibationFixup)
abDownloader.SetCoverArt(OnRequestCoverArt());
}
/// <summary>Move new files to 'Books' directory</summary>
/// <returns>True if audiobook file(s) were successfully created and can be located on disk. Else false.</returns>
private static bool moveFilesToBooksDir(LibraryBook libraryBook, List<FilePathCache.CacheEntry> entries)
{
// create final directory. move each file into it
var destinationDir = AudibleFileStorage.Audio.GetDestinationDirectory(libraryBook);
Directory.CreateDirectory(destinationDir);
FilePathCache.CacheEntry getFirstAudio() => entries.FirstOrDefault(f => f.FileType == FileType.Audio);
if (getFirstAudio() == default) if (getFirstAudio() == default)
return false; return false;
@ -273,33 +254,33 @@ namespace FileLiberator
return true; return true;
} }
private void DownloadCoverArt(LibraryBook libraryBook) private void DownloadCoverArt(LibraryBook libraryBook)
{ {
var destinationDir = AudibleFileStorage.Audio.GetDestinationDirectory(libraryBook); var destinationDir = AudibleFileStorage.Audio.GetDestinationDirectory(libraryBook);
var coverPath = AudibleFileStorage.Audio.GetBooksDirectoryFilename(libraryBook, ".jpg"); var coverPath = AudibleFileStorage.Audio.GetBooksDirectoryFilename(libraryBook, ".jpg");
coverPath = Path.Combine(destinationDir, Path.GetFileName(coverPath)); coverPath = Path.Combine(destinationDir, Path.GetFileName(coverPath));
try try
{ {
if (File.Exists(coverPath)) if (File.Exists(coverPath))
FileUtility.SaferDelete(coverPath); FileUtility.SaferDelete(coverPath);
(string picId, PictureSize size) = libraryBook.Book.PictureLarge is null ? (string picId, PictureSize size) = libraryBook.Book.PictureLarge is null ?
(libraryBook.Book.PictureId, PictureSize.Native) : (libraryBook.Book.PictureId, PictureSize.Native) :
(libraryBook.Book.PictureLarge, PictureSize.Native); (libraryBook.Book.PictureLarge, PictureSize.Native);
var picBytes = PictureStorage.GetPictureSynchronously(new PictureDefinition(picId, size)); var picBytes = PictureStorage.GetPictureSynchronously(new PictureDefinition(picId, size));
if (picBytes.Length > 0) if (picBytes.Length > 0)
File.WriteAllBytes(coverPath, picBytes); File.WriteAllBytes(coverPath, picBytes);
} }
catch (Exception ex) catch (Exception ex)
{ {
//Failure to download cover art should not be //Failure to download cover art should not be
//considered a failure to download the book //considered a failure to download the book
Serilog.Log.Logger.Error(ex, $"Error downloading cover art of {libraryBook.Book.AudibleProductId} to {coverPath} catalog product."); Serilog.Log.Logger.Error(ex, $"Error downloading cover art of {libraryBook.Book.AudibleProductId} to {coverPath} catalog product.");
} }
} }
} }
} }