Fix occasional error of audio downloads hanging.

This commit is contained in:
Michael Bucari-Tovo 2025-05-09 16:22:48 -06:00
parent 9366b3baca
commit 10c01f4147
7 changed files with 140 additions and 75 deletions

View File

@ -1,5 +1,6 @@
using AAXClean; using AAXClean;
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace AaxDecrypter namespace AaxDecrypter
@ -33,20 +34,33 @@ namespace AaxDecrypter
{ {
if (DownloadOptions.InputType is FileType.Dash) if (DownloadOptions.InputType is FileType.Dash)
{ {
//We may have multiple keys , so use the key whose key ID matches
//the dash files default Key ID.
var keyIds = DownloadOptions.DecryptionKeys.Select(k => new Guid(k.KeyPart1, bigEndian: true)).ToArray();
var dash = new DashFile(InputFileStream); var dash = new DashFile(InputFileStream);
dash.SetDecryptionKey(DownloadOptions.AudibleKey, DownloadOptions.AudibleIV); var kidIndex = Array.IndexOf(keyIds, dash.Tenc.DefaultKID);
if (kidIndex == -1)
throw new InvalidOperationException($"None of the {keyIds.Length} key IDs match the dash file's default KeyID of {dash.Tenc.DefaultKID}");
DownloadOptions.DecryptionKeys[0] = DownloadOptions.DecryptionKeys[kidIndex];
var keyId = DownloadOptions.DecryptionKeys[kidIndex].KeyPart1;
var key = DownloadOptions.DecryptionKeys[kidIndex].KeyPart2;
dash.SetDecryptionKey(keyId, key);
return dash; return dash;
} }
else if (DownloadOptions.InputType is FileType.Aax) else if (DownloadOptions.InputType is FileType.Aax)
{ {
var aax = new AaxFile(InputFileStream); var aax = new AaxFile(InputFileStream);
aax.SetDecryptionKey(DownloadOptions.AudibleKey); aax.SetDecryptionKey(DownloadOptions.DecryptionKeys[0].KeyPart1);
return aax; return aax;
} }
else if (DownloadOptions.InputType is FileType.Aaxc) else if (DownloadOptions.InputType is FileType.Aaxc)
{ {
var aax = new AaxFile(InputFileStream); var aax = new AaxFile(InputFileStream);
aax.SetDecryptionKey(DownloadOptions.AudibleKey, DownloadOptions.AudibleIV); aax.SetDecryptionKey(DownloadOptions.DecryptionKeys[0].KeyPart1, DownloadOptions.DecryptionKeys[0].KeyPart2);
return aax; return aax;
} }
else throw new InvalidOperationException($"{nameof(DownloadOptions.InputType)} of '{DownloadOptions.InputType}' is unknown."); else throw new InvalidOperationException($"{nameof(DownloadOptions.InputType)} of '{DownloadOptions.InputType}' is unknown.");

View File

@ -73,11 +73,16 @@ namespace AaxDecrypter
AsyncSteps[$"Cleanup"] = CleanupAsync; AsyncSteps[$"Cleanup"] = CleanupAsync;
(bool success, var elapsed) = await AsyncSteps.RunAsync(); (bool success, var elapsed) = await AsyncSteps.RunAsync();
//Stop the downloader so it doesn't keep running in the background.
if (!success)
nfsPersister.Dispose();
await progressTask; await progressTask;
var speedup = DownloadOptions.RuntimeLength / elapsed; var speedup = DownloadOptions.RuntimeLength / elapsed;
Serilog.Log.Information($"Speedup is {speedup:F0}x realtime."); Serilog.Log.Information($"Speedup is {speedup:F0}x realtime.");
nfsPersister.Dispose();
return success; return success;
async Task reportProgress() async Task reportProgress()
@ -177,7 +182,7 @@ namespace AaxDecrypter
FileUtility.SaferDelete(jsonDownloadState); FileUtility.SaferDelete(jsonDownloadState);
if (!string.IsNullOrEmpty(DownloadOptions.AudibleKey) && if (DownloadOptions.DecryptionKeys != null &&
DownloadOptions.RetainEncryptedFile && DownloadOptions.RetainEncryptedFile &&
DownloadOptions.InputType is AAXClean.FileType fileType) DownloadOptions.InputType is AAXClean.FileType fileType)
{ {
@ -188,17 +193,21 @@ namespace AaxDecrypter
if (fileType is AAXClean.FileType.Aax) if (fileType is AAXClean.FileType.Aax)
{ {
await File.WriteAllTextAsync(keyPath, $"ActivationBytes={DownloadOptions.AudibleKey}"); await File.WriteAllTextAsync(keyPath, $"ActivationBytes={Convert.ToHexString(DownloadOptions.DecryptionKeys[0].KeyPart1)}");
aaxPath = Path.ChangeExtension(tempFilePath, ".aax"); aaxPath = Path.ChangeExtension(tempFilePath, ".aax");
} }
else if (fileType is AAXClean.FileType.Aaxc) else if (fileType is AAXClean.FileType.Aaxc)
{ {
await File.WriteAllTextAsync(keyPath, $"Key={DownloadOptions.AudibleKey}{Environment.NewLine}IV={DownloadOptions.AudibleIV}"); await File.WriteAllTextAsync(keyPath,
$"Key={Convert.ToHexString(DownloadOptions.DecryptionKeys[0].KeyPart1)}{Environment.NewLine}" +
$"IV={Convert.ToHexString(DownloadOptions.DecryptionKeys[0].KeyPart2)}");
aaxPath = Path.ChangeExtension(tempFilePath, ".aaxc"); aaxPath = Path.ChangeExtension(tempFilePath, ".aaxc");
} }
else if (fileType is AAXClean.FileType.Dash) else if (fileType is AAXClean.FileType.Dash)
{ {
await File.WriteAllTextAsync(keyPath, $"KeyId={DownloadOptions.AudibleKey}{Environment.NewLine}Key={DownloadOptions.AudibleIV}"); await File.WriteAllTextAsync(keyPath,
$"KeyId={Convert.ToHexString(DownloadOptions.DecryptionKeys[0].KeyPart1)}{Environment.NewLine}" +
$"Key={Convert.ToHexString(DownloadOptions.DecryptionKeys[0].KeyPart2)}");
aaxPath = Path.ChangeExtension(tempFilePath, ".dash"); aaxPath = Path.ChangeExtension(tempFilePath, ".dash");
} }
else else

View File

@ -2,15 +2,35 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
#nullable enable
namespace AaxDecrypter namespace AaxDecrypter
{ {
public interface IDownloadOptions public class KeyData
{
public byte[] KeyPart1 { get; }
public byte[]? KeyPart2 { get; }
public KeyData(byte[] keyPart1, byte[]? keyPart2 = null)
{
KeyPart1 = keyPart1;
KeyPart2 = keyPart2;
}
public KeyData(string keyPart1, string? keyPart2 = null)
{
ArgumentNullException.ThrowIfNull(keyPart1, nameof(keyPart1));
KeyPart1 = Convert.FromBase64String(keyPart1);
if (keyPart2 != null)
KeyPart2 = Convert.FromBase64String(keyPart2);
}
}
public interface IDownloadOptions
{ {
event EventHandler<long> DownloadSpeedChanged; event EventHandler<long> DownloadSpeedChanged;
string DownloadUrl { get; } string DownloadUrl { get; }
string UserAgent { get; } string UserAgent { get; }
string AudibleKey { get; } KeyData[]? DecryptionKeys { get; }
string AudibleIV { get; }
TimeSpan RuntimeLength { get; } TimeSpan RuntimeLength { get; }
OutputFormat OutputFormat { get; } OutputFormat OutputFormat { get; }
bool TrimOutputToChapterLength { get; } bool TrimOutputToChapterLength { get; }
@ -21,14 +41,14 @@ namespace AaxDecrypter
long DownloadSpeedBps { get; } long DownloadSpeedBps { get; }
ChapterInfo ChapterInfo { get; } ChapterInfo ChapterInfo { get; }
bool FixupFile { get; } bool FixupFile { get; }
string AudibleProductId { get; } string? AudibleProductId { get; }
string Title { get; } string? Title { get; }
string Subtitle { get; } string? Subtitle { get; }
string Publisher { get; } string? Publisher { get; }
string Language { get; } string? Language { get; }
string SeriesName { get; } string? SeriesName { get; }
float? SeriesNumber { get; } float? SeriesNumber { get; }
NAudio.Lame.LameConfig LameConfig { get; } NAudio.Lame.LameConfig? LameConfig { get; }
bool Downsample { get; } bool Downsample { get; }
bool MatchSourceBitrate { get; } bool MatchSourceBitrate { get; }
bool MoveMoovToBeginning { get; } bool MoveMoovToBeginning { get; }

View File

@ -110,14 +110,16 @@ namespace AaxDecrypter
#region Downloader #region Downloader
/// <summary> Update the <see cref="Dinah.Core.IO.JsonFilePersister{T}"/>. </summary> /// <summary> Update the <see cref="Dinah.Core.IO.JsonFilePersister{T}"/>. </summary>
private void OnUpdate() private void OnUpdate(bool waitForWrite = false)
{ {
try try
{ {
if (DateTime.UtcNow > NextUpdateTime) if (waitForWrite || DateTime.UtcNow > NextUpdateTime)
{ {
Updated?.Invoke(this, EventArgs.Empty); Updated?.Invoke(this, EventArgs.Empty);
//JsonFilePersister Will not allow update intervals shorter than 100 milliseconds //JsonFilePersister Will not allow update intervals shorter than 100 milliseconds
//If an update is called less than 100 ms since the last update, persister will
//sleep the thread until 100 ms has elapsed.
NextUpdateTime = DateTime.UtcNow.AddMilliseconds(110); NextUpdateTime = DateTime.UtcNow.AddMilliseconds(110);
} }
} }
@ -305,7 +307,7 @@ namespace AaxDecrypter
finally finally
{ {
_downloadedPiece.Set(); _downloadedPiece.Set();
OnUpdate(); OnUpdate(waitForWrite: true);
} }
} }
@ -402,7 +404,7 @@ namespace AaxDecrypter
_cancellationSource?.Dispose(); _cancellationSource?.Dispose();
_readFile.Dispose(); _readFile.Dispose();
_writeFile.Dispose(); _writeFile.Dispose();
OnUpdate(); OnUpdate(waitForWrite: true);
} }
disposed = true; disposed = true;

View File

@ -55,7 +55,7 @@ public class WidevineKey
Type = (KeyType)type; Type = (KeyType)type;
Key = key; Key = key;
} }
public override string ToString() => $"{Convert.ToHexString(Kid.ToByteArray()).ToLower()}:{Convert.ToHexString(Key).ToLower()}"; public override string ToString() => $"{Convert.ToHexString(Kid.ToByteArray(bigEndian: true)).ToLower()}:{Convert.ToHexString(Key).ToLower()}";
} }
public partial class Cdm public partial class Cdm
@ -192,7 +192,7 @@ public partial class Cdm
id = id.Append(new byte[16 - id.Length]); id = id.Append(new byte[16 - id.Length]);
} }
keys[i] = new WidevineKey(new Guid(id), keyContainer.Type, keyBytes); keys[i] = new WidevineKey(new Guid(id,bigEndian: true), keyContainer.Type, keyBytes);
} }
return keys; return keys;
} }

View File

@ -41,12 +41,31 @@ public partial class DownloadOptions
return options; return options;
} }
private static async Task<ContentLicense> ChooseContent(Api api, LibraryBook libraryBook, Configuration config) private class LicenseInfo
{
public DrmType DrmType { get; }
public ContentMetadata ContentMetadata { get; set; }
public KeyData[]? DecryptionKeys { get; }
public LicenseInfo(ContentLicense license, IEnumerable<KeyData>? keys = null)
{
DrmType = license.DrmType;
ContentMetadata = license.ContentMetadata;
DecryptionKeys = keys?.ToArray() ?? ToKeys(license.Voucher);
}
private static KeyData[]? ToKeys(VoucherDtoV10? voucher)
=> voucher is null ? null : [new KeyData(voucher.Key, voucher.Iv)];
}
private static async Task<LicenseInfo> ChooseContent(Api api, LibraryBook libraryBook, Configuration config)
{ {
var dlQuality = config.FileDownloadQuality == Configuration.DownloadQuality.Normal ? DownloadQuality.Normal : DownloadQuality.High; var dlQuality = config.FileDownloadQuality == Configuration.DownloadQuality.Normal ? DownloadQuality.Normal : DownloadQuality.High;
if (!config.UseWidevine || await Cdm.GetCdmAsync() is not Cdm cdm) if (!config.UseWidevine || await Cdm.GetCdmAsync() is not Cdm cdm)
return await api.GetDownloadLicenseAsync(libraryBook.Book.AudibleProductId, dlQuality); {
var license = await api.GetDownloadLicenseAsync(libraryBook.Book.AudibleProductId, dlQuality);
return new LicenseInfo(license);
}
try try
{ {
@ -62,6 +81,9 @@ public partial class DownloadOptions
config.RequestSpatial, config.RequestSpatial,
codecChoice); codecChoice);
if (contentLic.DrmType is not DrmType.Widevine)
return new LicenseInfo(contentLic);
using var client = new HttpClient(); using var client = new HttpClient();
using var mpdResponse = await client.GetAsync(contentLic.LicenseResponse); using var mpdResponse = await client.GetAsync(contentLic.LicenseResponse);
var dash = new MpegDash(mpdResponse.Content.ReadAsStream()); var dash = new MpegDash(mpdResponse.Content.ReadAsStream());
@ -75,12 +97,7 @@ public partial class DownloadOptions
var challenge = session.GetLicenseChallenge(dash); var challenge = session.GetLicenseChallenge(dash);
var licenseMessage = await api.WidevineDrmLicense(libraryBook.Book.AudibleProductId, challenge); var licenseMessage = await api.WidevineDrmLicense(libraryBook.Book.AudibleProductId, challenge);
var keys = session.ParseLicense(licenseMessage); var keys = session.ParseLicense(licenseMessage);
contentLic.Voucher = new VoucherDtoV10() return new LicenseInfo(contentLic, keys.Select(k => new KeyData(k.Kid.ToByteArray(bigEndian: true), k.Key)));
{
Key = Convert.ToHexStringLower(keys[0].Kid.ToByteArray()),
Iv = Convert.ToHexStringLower(keys[0].Key)
};
return contentLic;
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -93,41 +110,20 @@ public partial class DownloadOptions
} }
private static DownloadOptions BuildDownloadOptions(LibraryBook libraryBook, Configuration config, ContentLicense contentLic) private static DownloadOptions BuildDownloadOptions(LibraryBook libraryBook, Configuration config, LicenseInfo licInfo)
{ {
//If DrmType is not Adrm or Widevine, the delivered file is an unencrypted mp3.
var outputFormat
= contentLic.DrmType is not DrmType.Adrm and not DrmType.Widevine ||
(config.AllowLibationFixup && config.DecryptToLossy && contentLic.ContentMetadata.ContentReference.Codec != "ac-4")
? OutputFormat.Mp3
: OutputFormat.M4b;
long chapterStartMs long chapterStartMs
= config.StripAudibleBrandAudio = config.StripAudibleBrandAudio
? contentLic.ContentMetadata.ChapterInfo.BrandIntroDurationMs ? licInfo.ContentMetadata.ChapterInfo.BrandIntroDurationMs
: 0; : 0;
AAXClean.FileType? inputType var dlOptions = new DownloadOptions(config, libraryBook, licInfo)
= contentLic.DrmType is DrmType.Widevine ? AAXClean.FileType.Dash
: contentLic.DrmType is DrmType.Adrm && contentLic.Voucher?.Key.Length == 8 && contentLic.Voucher?.Iv == null ? AAXClean.FileType.Aax
: contentLic.DrmType is DrmType.Adrm && contentLic.Voucher?.Key.Length == 32 && contentLic.Voucher?.Iv.Length == 32 ? AAXClean.FileType.Aaxc
: null;
var dlOptions = new DownloadOptions(config, libraryBook, contentLic.ContentMetadata.ContentUrl?.OfflineUrl)
{ {
AudibleKey = contentLic.Voucher?.Key,
AudibleIV = contentLic.Voucher?.Iv,
InputType = inputType,
OutputFormat = outputFormat,
DrmType = contentLic.DrmType,
ContentMetadata = contentLic.ContentMetadata,
LameConfig = outputFormat == OutputFormat.Mp3 ? GetLameOptions(config) : null,
ChapterInfo = new AAXClean.ChapterInfo(TimeSpan.FromMilliseconds(chapterStartMs)), ChapterInfo = new AAXClean.ChapterInfo(TimeSpan.FromMilliseconds(chapterStartMs)),
RuntimeLength = TimeSpan.FromMilliseconds(contentLic.ContentMetadata.ChapterInfo.RuntimeLengthMs), RuntimeLength = TimeSpan.FromMilliseconds(licInfo.ContentMetadata.ChapterInfo.RuntimeLengthMs),
}; };
dlOptions.LibraryBookDto.Codec = contentLic.ContentMetadata.ContentReference.Codec; if (TryGetAudioInfo(licInfo.ContentMetadata.ContentUrl, out int? bitrate, out int? sampleRate, out int? channels))
if (TryGetAudioInfo(contentLic.ContentMetadata.ContentUrl, out int? bitrate, out int? sampleRate, out int? channels))
{ {
dlOptions.LibraryBookDto.BitRate = bitrate; dlOptions.LibraryBookDto.BitRate = bitrate;
dlOptions.LibraryBookDto.SampleRate = sampleRate; dlOptions.LibraryBookDto.SampleRate = sampleRate;
@ -136,7 +132,7 @@ public partial class DownloadOptions
var titleConcat = config.CombineNestedChapterTitles ? ": " : null; var titleConcat = config.CombineNestedChapterTitles ? ": " : null;
var chapters var chapters
= flattenChapters(contentLic.ContentMetadata.ChapterInfo.Chapters, titleConcat) = flattenChapters(licInfo.ContentMetadata.ChapterInfo.Chapters, titleConcat)
.OrderBy(c => c.StartOffsetMs) .OrderBy(c => c.StartOffsetMs)
.ToList(); .ToList();
@ -152,7 +148,7 @@ public partial class DownloadOptions
chapLenMs -= chapterStartMs; chapLenMs -= chapterStartMs;
if (config.StripAudibleBrandAudio && i == chapters.Count - 1) if (config.StripAudibleBrandAudio && i == chapters.Count - 1)
chapLenMs -= contentLic.ContentMetadata.ChapterInfo.BrandOutroDurationMs; chapLenMs -= licInfo.ContentMetadata.ChapterInfo.BrandOutroDurationMs;
dlOptions.ChapterInfo.AddChapter(chapter.Title, TimeSpan.FromMilliseconds(chapLenMs)); dlOptions.ChapterInfo.AddChapter(chapter.Title, TimeSpan.FromMilliseconds(chapLenMs));
} }

View File

@ -9,27 +9,27 @@ using System.IO;
using ApplicationServices; using ApplicationServices;
using LibationFileManager.Templates; using LibationFileManager.Templates;
#nullable enable
namespace FileLiberator namespace FileLiberator
{ {
public partial class DownloadOptions : IDownloadOptions, IDisposable public partial class DownloadOptions : IDownloadOptions, IDisposable
{ {
public event EventHandler<long> DownloadSpeedChanged; public event EventHandler<long>? DownloadSpeedChanged;
public LibraryBook LibraryBook { get; } public LibraryBook LibraryBook { get; }
public LibraryBookDto LibraryBookDto { get; } public LibraryBookDto LibraryBookDto { get; }
public string DownloadUrl { get; } public string DownloadUrl { get; }
public string AudibleKey { get; init; } public KeyData[]? DecryptionKeys { get; }
public string AudibleIV { get; init; } public required TimeSpan RuntimeLength { get; init; }
public TimeSpan RuntimeLength { get; init; } public OutputFormat OutputFormat { get; }
public OutputFormat OutputFormat { get; init; } public required ChapterInfo ChapterInfo { get; init; }
public ChapterInfo ChapterInfo { get; init; }
public string Title => LibraryBook.Book.Title; public string Title => LibraryBook.Book.Title;
public string Subtitle => LibraryBook.Book.Subtitle; public string Subtitle => LibraryBook.Book.Subtitle;
public string Publisher => LibraryBook.Book.Publisher; public string Publisher => LibraryBook.Book.Publisher;
public string Language => LibraryBook.Book.Language; public string Language => LibraryBook.Book.Language;
public string AudibleProductId => LibraryBookDto.AudibleProductId; public string? AudibleProductId => LibraryBookDto.AudibleProductId;
public string SeriesName => LibraryBookDto.FirstSeries?.Name; public string? SeriesName => LibraryBookDto.FirstSeries?.Name;
public float? SeriesNumber => LibraryBookDto.FirstSeries?.Number; public float? SeriesNumber => LibraryBookDto.FirstSeries?.Number;
public NAudio.Lame.LameConfig LameConfig { get; init; } public NAudio.Lame.LameConfig? LameConfig { get; }
public string UserAgent => AudibleApi.Resources.Download_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;
@ -41,15 +41,15 @@ namespace FileLiberator
public bool Downsample => Config.AllowLibationFixup && Config.LameDownsampleMono; public bool Downsample => Config.AllowLibationFixup && Config.LameDownsampleMono;
public bool MatchSourceBitrate => Config.AllowLibationFixup && Config.LameMatchSourceBR && Config.LameTargetBitrate; public bool MatchSourceBitrate => Config.AllowLibationFixup && Config.LameMatchSourceBR && Config.LameTargetBitrate;
public bool MoveMoovToBeginning => Config.MoveMoovToBeginning; public bool MoveMoovToBeginning => Config.MoveMoovToBeginning;
public AAXClean.FileType? InputType { get; init; } public AAXClean.FileType? InputType { get; }
public AudibleApi.Common.DrmType DrmType { get; init; } public AudibleApi.Common.DrmType DrmType { get; }
public AudibleApi.Common.ContentMetadata ContentMetadata { get; init; } public AudibleApi.Common.ContentMetadata ContentMetadata { get; }
public string GetMultipartFileName(MultiConvertFileProperties props) public string GetMultipartFileName(MultiConvertFileProperties props)
{ {
var baseDir = Path.GetDirectoryName(props.OutputFileName); var baseDir = Path.GetDirectoryName(props.OutputFileName);
var extension = Path.GetExtension(props.OutputFileName); var extension = Path.GetExtension(props.OutputFileName);
return Templates.ChapterFile.GetFilename(LibraryBookDto, props, baseDir, extension); return Templates.ChapterFile.GetFilename(LibraryBookDto, props, baseDir!, extension);
} }
public string GetMultipartTitle(MultiConvertFileProperties props) public string GetMultipartTitle(MultiConvertFileProperties props)
@ -92,14 +92,38 @@ namespace FileLiberator
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
private DownloadOptions(Configuration config, LibraryBook libraryBook, [System.Diagnostics.CodeAnalysis.NotNull] string downloadUrl) private DownloadOptions(Configuration config, LibraryBook libraryBook, LicenseInfo licInfo)
{ {
Config = ArgumentValidator.EnsureNotNull(config, nameof(config)); Config = ArgumentValidator.EnsureNotNull(config, nameof(config));
LibraryBook = ArgumentValidator.EnsureNotNull(libraryBook, nameof(libraryBook)); LibraryBook = ArgumentValidator.EnsureNotNull(libraryBook, nameof(libraryBook));
DownloadUrl = ArgumentValidator.EnsureNotNullOrEmpty(downloadUrl, nameof(downloadUrl));
// no null/empty check for key/iv. unencrypted files do not have them
ArgumentValidator.EnsureNotNull(licInfo, nameof(licInfo));
if (licInfo.ContentMetadata.ContentUrl.OfflineUrl is not string licUrl)
throw new InvalidDataException("Content license doesn't contain an offline Url");
DownloadUrl = licUrl;
DecryptionKeys = licInfo.DecryptionKeys;
DrmType = licInfo.DrmType;
ContentMetadata = licInfo.ContentMetadata;
InputType
= licInfo.DrmType is AudibleApi.Common.DrmType.Widevine ? AAXClean.FileType.Dash
: licInfo.DrmType is AudibleApi.Common.DrmType.Adrm && licInfo.DecryptionKeys?.Length == 1 && licInfo.DecryptionKeys[0].KeyPart1.Length == 8 && licInfo.DecryptionKeys[0].KeyPart2 is null ? AAXClean.FileType.Aax
: licInfo.DrmType is AudibleApi.Common.DrmType.Adrm && licInfo.DecryptionKeys?.Length == 1 && licInfo.DecryptionKeys[0].KeyPart1.Length == 32 && licInfo.DecryptionKeys[0].KeyPart2?.Length == 32 ? AAXClean.FileType.Aaxc
: null;
//If DrmType is not Adrm or Widevine, the delivered file is an unencrypted mp3.
OutputFormat
= licInfo.DrmType is not AudibleApi.Common.DrmType.Adrm and not AudibleApi.Common.DrmType.Widevine ||
(config.AllowLibationFixup && config.DecryptToLossy && licInfo.ContentMetadata.ContentReference.Codec != Ac4Codec)
? OutputFormat.Mp3
: OutputFormat.M4b;
LameConfig = OutputFormat == OutputFormat.Mp3 ? GetLameOptions(config) : null;
// no null/empty check for key/iv. unencrypted files do not have them
LibraryBookDto = LibraryBook.ToDto(); LibraryBookDto = LibraryBook.ToDto();
LibraryBookDto.Codec = licInfo.ContentMetadata.ContentReference.Codec;
cancellation = cancellation =
config config