diff --git a/AaxDecrypter/AaxcDownloadConverter.cs b/AaxDecrypter/AaxcDownloadConverter.cs index c91429ba..28192d07 100644 --- a/AaxDecrypter/AaxcDownloadConverter.cs +++ b/AaxDecrypter/AaxcDownloadConverter.cs @@ -1,5 +1,4 @@ using AAXClean; -using Dinah.Core; using Dinah.Core.IO; using Dinah.Core.Net.Http; using Dinah.Core.StepRunner; @@ -8,52 +7,25 @@ using System.IO; namespace AaxDecrypter { - public enum OutputFormat { Mp4a, Mp3 } - public class AaxcDownloadConverter + public class AaxcDownloadConverter : AudioDownloadBase { - public event EventHandler RetrievedTags; - public event EventHandler RetrievedCoverArt; - public event EventHandler DecryptProgressUpdate; - public event EventHandler DecryptTimeRemaining; + protected override StepSequence steps { get; } - public string AppName { get; set; } = nameof(AaxcDownloadConverter); - - private string outputFileName { get; } - private string cacheDir { get; } - private DownloadLicense downloadLicense { get; } private AaxFile aaxFile; - private OutputFormat OutputFormat; - private StepSequence steps { get; } - private NetworkFileStreamPersister nfsPersister; - private bool isCanceled { get; set; } - private string jsonDownloadState => Path.Combine(cacheDir, Path.GetFileNameWithoutExtension(outputFileName) + ".json"); - private string tempFile => PathLib.ReplaceExtension(jsonDownloadState, ".aaxc"); + private OutputFormat OutputFormat { get; } public AaxcDownloadConverter(string outFileName, string cacheDirectory, DownloadLicense dlLic, OutputFormat outputFormat) + :base(outFileName, cacheDirectory, dlLic) { - ArgumentValidator.EnsureNotNullOrWhiteSpace(outFileName, nameof(outFileName)); - outputFileName = outFileName; - - var outDir = Path.GetDirectoryName(outputFileName); - if (!Directory.Exists(outDir)) - throw new ArgumentNullException(nameof(outDir), "Directory does not exist"); - if (File.Exists(outputFileName)) - File.Delete(outputFileName); - - if (!Directory.Exists(cacheDirectory)) - throw new ArgumentNullException(nameof(cacheDirectory), "Directory does not exist"); - cacheDir = cacheDirectory; - - downloadLicense = ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic)); OutputFormat = outputFormat; steps = new StepSequence { - Name = "Download and Convert Aaxc To " + (outputFormat == OutputFormat.Mp4a ? "M4b" : "Mp3"), + Name = "Download and Convert Aaxc To " + OutputFormat, ["Step 1: Get Aaxc Metadata"] = Step1_GetMetadata, - ["Step 2: Download Decrypted Audiobook"] = Step2_DownloadAndCombine, + ["Step 2: Download Decrypted Audiobook"] = Step2_DownloadAudiobook, ["Step 3: Create Cue"] = Step3_CreateCue, ["Step 4: Cleanup"] = Step4_Cleanup, }; @@ -62,102 +34,56 @@ namespace AaxDecrypter /// /// Setting cover art by this method will insert the art into the audiobook metadata /// - public void SetCoverArt(byte[] coverArt) + public override void SetCoverArt(byte[] coverArt) { - if (coverArt is null) return; + base.SetCoverArt(coverArt); aaxFile?.AppleTags.SetCoverArt(coverArt); - - RetrievedCoverArt?.Invoke(this, coverArt); } - public bool Run() - { - var (IsSuccess, Elapsed) = steps.Run(); + protected override bool Step1_GetMetadata() + { + aaxFile = new AaxFile(InputFileStream); - if (!IsSuccess) - { - Console.WriteLine("WARNING-Conversion failed"); - return false; - } - - var speedup = (int)(aaxFile.Duration.TotalSeconds / (long)Elapsed.TotalSeconds); - Serilog.Log.Logger.Information($"Speedup is {speedup}x realtime."); - return true; - } - - public bool Step1_GetMetadata() - { - //Get metadata from the file over http - - if (File.Exists(jsonDownloadState)) - { - try - { - nfsPersister = new NetworkFileStreamPersister(jsonDownloadState); - //If More than ~1 hour has elapsed since getting the download url, it will expire. - //The new url will be to the same file. - nfsPersister.NetworkFileStream.SetUriForSameFile(new Uri(downloadLicense.DownloadUrl)); - } - catch - { - FileExt.SafeDelete(jsonDownloadState); - FileExt.SafeDelete(tempFile); - nfsPersister = NewNetworkFilePersister(); - } - } - else - { - nfsPersister = NewNetworkFilePersister(); - } - - aaxFile = new AaxFile(nfsPersister.NetworkFileStream); - - RetrievedTags?.Invoke(this, aaxFile.AppleTags); - RetrievedCoverArt?.Invoke(this, aaxFile.AppleTags.Cover); + OnRetrievedTitle(aaxFile.AppleTags.TitleSansUnabridged); + OnRetrievedAuthors(aaxFile.AppleTags.FirstAuthor ?? "[unknown]"); + OnRetrievedNarrators(aaxFile.AppleTags.Narrator ?? "[unknown]"); + OnRetrievedCoverArt(aaxFile.AppleTags.Cover); return !isCanceled; } - private NetworkFileStreamPersister NewNetworkFilePersister() - { - var headers = new System.Net.WebHeaderCollection - { - { "User-Agent", downloadLicense.UserAgent } - }; - var networkFileStream = new NetworkFileStream(tempFile, new Uri(downloadLicense.DownloadUrl), 0, headers); - return new NetworkFileStreamPersister(networkFileStream, jsonDownloadState); - } - - public bool Step2_DownloadAndCombine() + protected override bool Step2_DownloadAudiobook() { var zeroProgress = new DownloadProgress { BytesReceived = 0, ProgressPercentage = 0, - TotalBytesToReceive = nfsPersister.NetworkFileStream.Length + TotalBytesToReceive = InputFileStream.Length }; - DecryptProgressUpdate?.Invoke(this, zeroProgress); + OnDecryptProgressUpdate(zeroProgress); + + + aaxFile.SetDecryptionKey(downloadLicense.AudibleKey, downloadLicense.AudibleIV); + if (File.Exists(outputFileName)) FileExt.SafeDelete(outputFileName); - FileStream outFile = File.Open(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite); - - aaxFile.SetDecryptionKey(downloadLicense.AudibleKey, downloadLicense.AudibleIV); + var outputFile = File.Open(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite); aaxFile.ConversionProgressUpdate += AaxFile_ConversionProgressUpdate; - var decryptionResult = OutputFormat == OutputFormat.Mp4a ? aaxFile.ConvertToMp4a(outFile, downloadLicense.ChapterInfo) : aaxFile.ConvertToMp3(outFile); + var decryptionResult = OutputFormat == OutputFormat.M4b ? aaxFile.ConvertToMp4a(outputFile, downloadLicense.ChapterInfo) : aaxFile.ConvertToMp3(outputFile); aaxFile.ConversionProgressUpdate -= AaxFile_ConversionProgressUpdate; aaxFile.Close(); downloadLicense.ChapterInfo = aaxFile.Chapters; - nfsPersister.Dispose(); + CloseInputFileStream(); - DecryptProgressUpdate?.Invoke(this, zeroProgress); + OnDecryptProgressUpdate(zeroProgress); return decryptionResult == ConversionResult.NoErrorsDetected && !isCanceled; } @@ -169,47 +95,28 @@ namespace AaxDecrypter double estTimeRemaining = remainingSecsToProcess / e.ProcessSpeed; if (double.IsNormal(estTimeRemaining)) - DecryptTimeRemaining?.Invoke(this, TimeSpan.FromSeconds(estTimeRemaining)); + OnDecryptTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining)); - double progressPercent = 100 * e.ProcessPosition.TotalSeconds / duration.TotalSeconds; + double progressPercent = e.ProcessPosition.TotalSeconds / duration.TotalSeconds; - DecryptProgressUpdate?.Invoke(this, + OnDecryptProgressUpdate( new DownloadProgress { - ProgressPercentage = progressPercent, - BytesReceived = (long)(nfsPersister.NetworkFileStream.Length * progressPercent), - TotalBytesToReceive = nfsPersister.NetworkFileStream.Length + ProgressPercentage = 100 * progressPercent, + BytesReceived = (long)(InputFileStream.Length * progressPercent), + TotalBytesToReceive = InputFileStream.Length }); } - public bool Step3_CreateCue() - { - // not a critical step. its failure should not prevent future steps from running - try - { - File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".cue"), Cue.CreateContents(Path.GetFileName(outputFileName), downloadLicense.ChapterInfo)); - } - catch (Exception ex) - { - Serilog.Log.Logger.Error(ex, $"{nameof(Step3_CreateCue)}. FAILED"); - } - return !isCanceled; - } - - public bool Step4_Cleanup() - { - FileExt.SafeDelete(jsonDownloadState); - FileExt.SafeDelete(tempFile); - return !isCanceled; - } - - public void Cancel() + public override void Cancel() { isCanceled = true; aaxFile?.Cancel(); aaxFile?.Dispose(); - nfsPersister?.NetworkFileStream?.Close(); - nfsPersister?.Dispose(); + CloseInputFileStream(); } - } + + protected override int GetSpeedup(TimeSpan elapsed) + => (int)(aaxFile.Duration.TotalSeconds / (long)elapsed.TotalSeconds); + } } diff --git a/AaxDecrypter/AudioDownloadBase.cs b/AaxDecrypter/AudioDownloadBase.cs new file mode 100644 index 00000000..2792265d --- /dev/null +++ b/AaxDecrypter/AudioDownloadBase.cs @@ -0,0 +1,165 @@ +using Dinah.Core; +using Dinah.Core.IO; +using Dinah.Core.Net.Http; +using Dinah.Core.StepRunner; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AaxDecrypter +{ + public enum OutputFormat { M4b, Mp3 } + + public abstract class AudioDownloadBase + { + public event EventHandler RetrievedTitle; + public event EventHandler RetrievedAuthors; + public event EventHandler RetrievedNarrators; + public event EventHandler RetrievedCoverArt; + public event EventHandler DecryptProgressUpdate; + public event EventHandler DecryptTimeRemaining; + + public string AppName { get; set; } + + protected bool isCanceled { get; set; } + protected string outputFileName { get; } + protected string cacheDir { get; } + protected DownloadLicense downloadLicense { get; } + protected NetworkFileStream InputFileStream => (nfsPersister ??= OpenNetworkFileStream()).NetworkFileStream; + + + protected abstract StepSequence steps { get; } + private NetworkFileStreamPersister nfsPersister; + + private string jsonDownloadState => Path.Combine(cacheDir, Path.GetFileNameWithoutExtension(outputFileName) + ".json"); + private string tempFile => PathLib.ReplaceExtension(jsonDownloadState, ".tmp"); + + public AudioDownloadBase(string outFileName, string cacheDirectory, DownloadLicense dlLic) + { + AppName = GetType().Name; + + ArgumentValidator.EnsureNotNullOrWhiteSpace(outFileName, nameof(outFileName)); + outputFileName = outFileName; + + var outDir = Path.GetDirectoryName(outputFileName); + if (!Directory.Exists(outDir)) + throw new ArgumentNullException(nameof(outDir), "Directory does not exist"); + if (File.Exists(outputFileName)) + File.Delete(outputFileName); + + if (!Directory.Exists(cacheDirectory)) + throw new ArgumentNullException(nameof(cacheDirectory), "Directory does not exist"); + cacheDir = cacheDirectory; + + downloadLicense = ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic)); + } + + public abstract void Cancel(); + protected abstract int GetSpeedup(TimeSpan elapsed); + protected abstract bool Step2_DownloadAudiobook(); + protected abstract bool Step1_GetMetadata(); + + public virtual void SetCoverArt(byte[] coverArt) + { + if (coverArt is null) return; + + OnRetrievedCoverArt(coverArt); + } + + + public bool Run() + { + var (IsSuccess, Elapsed) = steps.Run(); + + if (!IsSuccess) + { + Console.WriteLine("WARNING-Conversion failed"); + return false; + } + + Serilog.Log.Logger.Information($"Speedup is {GetSpeedup(Elapsed)}x realtime."); + return true; + } + + protected void OnRetrievedTitle(string title) + => RetrievedTitle?.Invoke(this, title); + protected void OnRetrievedAuthors(string authors) + => RetrievedAuthors?.Invoke(this, authors); + protected void OnRetrievedNarrators(string narrators) + => RetrievedNarrators?.Invoke(this, narrators); + protected void OnRetrievedCoverArt(byte[] coverArt) + => RetrievedCoverArt?.Invoke(this, coverArt); + protected void OnDecryptProgressUpdate(DownloadProgress downloadProgress) + => DecryptProgressUpdate?.Invoke(this, downloadProgress); + protected void OnDecryptTimeRemaining(TimeSpan timeRemaining) + => DecryptTimeRemaining?.Invoke(this, timeRemaining); + + protected void CloseInputFileStream() + { + nfsPersister?.NetworkFileStream?.Close(); + nfsPersister?.Dispose(); + } + + protected bool Step3_CreateCue() + { + // not a critical step. its failure should not prevent future steps from running + try + { + File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".cue"), Cue.CreateContents(Path.GetFileName(outputFileName), downloadLicense.ChapterInfo)); + } + catch (Exception ex) + { + Serilog.Log.Logger.Error(ex, $"{nameof(Step3_CreateCue)}. FAILED"); + } + return !isCanceled; + } + + protected bool Step4_Cleanup() + { + FileExt.SafeDelete(jsonDownloadState); + FileExt.SafeDelete(tempFile); + return !isCanceled; + } + + private NetworkFileStreamPersister OpenNetworkFileStream() + { + NetworkFileStreamPersister nfsp; + + if (File.Exists(jsonDownloadState)) + { + try + { + nfsp = new NetworkFileStreamPersister(jsonDownloadState); + //If More than ~1 hour has elapsed since getting the download url, it will expire. + //The new url will be to the same file. + nfsp.NetworkFileStream.SetUriForSameFile(new Uri(downloadLicense.DownloadUrl)); + } + catch + { + FileExt.SafeDelete(jsonDownloadState); + FileExt.SafeDelete(tempFile); + nfsp = NewNetworkFilePersister(); + } + } + else + { + nfsp = NewNetworkFilePersister(); + } + return nfsp; + } + + private NetworkFileStreamPersister NewNetworkFilePersister() + { + var headers = new System.Net.WebHeaderCollection + { + { "User-Agent", downloadLicense.UserAgent } + }; + + var networkFileStream = new NetworkFileStream(tempFile, new Uri(downloadLicense.DownloadUrl), 0, headers); + return new NetworkFileStreamPersister(networkFileStream, jsonDownloadState); + } + } +} diff --git a/AaxDecrypter/Mp3Downloader.cs b/AaxDecrypter/Mp3Downloader.cs new file mode 100644 index 00000000..72265d3c --- /dev/null +++ b/AaxDecrypter/Mp3Downloader.cs @@ -0,0 +1,86 @@ +using Dinah.Core.IO; +using Dinah.Core.Net.Http; +using Dinah.Core.StepRunner; +using System; +using System.IO; +using System.Linq; +using System.Threading; + +namespace AaxDecrypter +{ + public class Mp3Downloader : AudioDownloadBase + { + protected override StepSequence steps { get; } + + public Mp3Downloader(string outFileName, string cacheDirectory, DownloadLicense dlLic) + : base(outFileName, cacheDirectory, dlLic) + { + + steps = new StepSequence + { + Name = "Download Mp3 Audiobook", + + ["Step 1: Get Mp3 Metadata"] = Step1_GetMetadata, + ["Step 2: Download Audiobook"] = Step2_DownloadAudiobook, + ["Step 3: Create Cue"] = Step3_CreateCue, + ["Step 4: Cleanup"] = Step4_Cleanup, + }; + } + + public override void Cancel() + { + isCanceled = true; + CloseInputFileStream(); + } + + protected override int GetSpeedup(TimeSpan elapsed) + { + //Not implemented + return 0; + } + + protected override bool Step1_GetMetadata() + { + OnRetrievedCoverArt(null); + + return !isCanceled; + } + + protected override bool Step2_DownloadAudiobook() + { + DateTime startTime = DateTime.Now; + + //MUST put InputFileStream.Length first, because it starts background downloader. + + while (InputFileStream.Length > InputFileStream.WritePosition && !InputFileStream.IsCancelled) + { + var rate = InputFileStream.WritePosition / (DateTime.Now - startTime).TotalSeconds; + + var estTimeRemaining = (InputFileStream.Length - InputFileStream.WritePosition) / rate; + + if (double.IsNormal(estTimeRemaining)) + OnDecryptTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining)); + + var progressPercent = (double)InputFileStream.WritePosition / InputFileStream.Length; + + OnDecryptProgressUpdate( + new DownloadProgress + { + ProgressPercentage = 100 * progressPercent, + BytesReceived = (long)(InputFileStream.Length * progressPercent), + TotalBytesToReceive = InputFileStream.Length + }); + Thread.Sleep(200); + } + + CloseInputFileStream(); + + if (File.Exists(outputFileName)) + FileExt.SafeDelete(outputFileName); + + FileExt.SafeMove(InputFileStream.SaveFilePath, outputFileName); + + return !isCanceled; + } + } +} diff --git a/AaxDecrypter/NetworkFileStream.cs b/AaxDecrypter/NetworkFileStream.cs index 01b63781..d2314b11 100644 --- a/AaxDecrypter/NetworkFileStream.cs +++ b/AaxDecrypter/NetworkFileStream.cs @@ -83,7 +83,7 @@ namespace AaxDecrypter private FileStream _readFile { get; } private Stream _networkStream { get; set; } private bool hasBegunDownloading { get; set; } - private bool isCancelled { get; set; } + public bool IsCancelled { get; private set; } private EventWaitHandle downloadEnded { get; set; } private EventWaitHandle downloadedPiece { get; set; } @@ -238,7 +238,7 @@ namespace AaxDecrypter downloadedPiece.Set(); } - } while (downloadPosition < ContentLength && !isCancelled); + } while (downloadPosition < ContentLength && !IsCancelled); _writeFile.Close(); _networkStream.Close(); @@ -248,7 +248,7 @@ namespace AaxDecrypter downloadedPiece.Set(); downloadEnded.Set(); - if (!isCancelled && WritePosition < ContentLength) + if (!IsCancelled && WritePosition < ContentLength) throw new WebException($"Downloaded size (0x{WritePosition:X10}) is less than {nameof(ContentLength)} (0x{ContentLength:X10})."); if (WritePosition > ContentLength) @@ -421,12 +421,12 @@ namespace AaxDecrypter /// The minimum required flished data length in . private void WaitToPosition(long requiredPosition) { - while (requiredPosition > WritePosition && !isCancelled && hasBegunDownloading && !downloadedPiece.WaitOne(1000)) ; + while (requiredPosition > WritePosition && !IsCancelled && hasBegunDownloading && !downloadedPiece.WaitOne(1000)) ; } public override void Close() { - isCancelled = true; + IsCancelled = true; while (downloadEnded is not null && !downloadEnded.WaitOne(1000)) ; diff --git a/FileLiberator/DownloadDecryptBook.cs b/FileLiberator/DownloadDecryptBook.cs index 98d87b2a..b08b0d18 100644 --- a/FileLiberator/DownloadDecryptBook.cs +++ b/FileLiberator/DownloadDecryptBook.cs @@ -15,7 +15,7 @@ namespace FileLiberator { public class DownloadDecryptBook : IAudioDecodable { - private AaxcDownloadConverter aaxcDownloader; + private AudioDownloadBase aaxcDownloader; public event EventHandler StreamingTimeRemaining; public event EventHandler> RequestCoverArt; @@ -103,24 +103,22 @@ namespace FileLiberator aaxcDecryptDlLic.ChapterInfo.AddChapter(chap.Title, TimeSpan.FromMilliseconds(chap.LengthMs)); } + //I assume if ContentFormat == "MPEG" that the delivered file is an unencrypted mp3. + //This may be wrong, and only time and bug reports will tell. + var outputFormat = + contentLic.ContentMetadata.ContentReference.ContentFormat == "MPEG" || + Configuration.Instance.DecryptToLossy ? + OutputFormat.Mp3 : OutputFormat.M4b; - var format = Configuration.Instance.DecryptToLossy ? OutputFormat.Mp3 : OutputFormat.Mp4a; + var outFileName = Path.Combine(destinationDir, $"{PathLib.ToPathSafeString(libraryBook.Book.Title)} [{libraryBook.Book.AudibleProductId}].{outputFormat.ToString().ToLower()}"); - var extension = format switch - { - OutputFormat.Mp4a => "m4b", - OutputFormat.Mp3 => "mp3", - _ => throw new NotImplementedException(), - }; - - var outFileName = Path.Combine(destinationDir, $"{PathLib.ToPathSafeString(libraryBook.Book.Title)} [{libraryBook.Book.AudibleProductId}].{extension}"); - - - aaxcDownloader = new AaxcDownloadConverter(outFileName, cacheDir, aaxcDecryptDlLic, format) { AppName = "Libation" }; + aaxcDownloader = contentLic.DrmType == AudibleApi.Common.DrmType.Adrm ? new AaxcDownloadConverter(outFileName, cacheDir, aaxcDecryptDlLic, outputFormat) { AppName = "Libation" } : new Mp3Downloader(outFileName, cacheDir, aaxcDecryptDlLic); aaxcDownloader.DecryptProgressUpdate += (s, progress) => StreamingProgressChanged?.Invoke(this, progress); aaxcDownloader.DecryptTimeRemaining += (s, remaining) => StreamingTimeRemaining?.Invoke(this, remaining); + aaxcDownloader.RetrievedTitle += (s, title) => TitleDiscovered?.Invoke(this, title); + aaxcDownloader.RetrievedAuthors += (s, authors) => AuthorsDiscovered?.Invoke(this, authors); + aaxcDownloader.RetrievedNarrators += (s, narrators) => NarratorsDiscovered?.Invoke(this, narrators); aaxcDownloader.RetrievedCoverArt += AaxcDownloader_RetrievedCoverArt; - aaxcDownloader.RetrievedTags += aaxcDownloader_RetrievedTags; // REAL WORK DONE HERE var success = await Task.Run(() => aaxcDownloader.Run()); @@ -137,7 +135,6 @@ namespace FileLiberator } } - private void AaxcDownloader_RetrievedCoverArt(object sender, byte[] e) { if (e is null && Configuration.Instance.AllowLibationFixup) @@ -151,13 +148,6 @@ namespace FileLiberator } } - private void aaxcDownloader_RetrievedTags(object sender, AAXClean.AppleTags e) - { - TitleDiscovered?.Invoke(this, e.TitleSansUnabridged); - AuthorsDiscovered?.Invoke(this, e.FirstAuthor ?? "[unknown]"); - NarratorsDiscovered?.Invoke(this, e.Narrator ?? "[unknown]"); - } - private static (string destinationDir, bool movedAudioFile) MoveFilesToBooksDir(Book product, string outputAudioFilename) { // create final directory. move each file into it. MOVE AUDIO FILE LAST diff --git a/LibationWinForms/Dialogs/SettingsDialog.Designer.cs b/LibationWinForms/Dialogs/SettingsDialog.Designer.cs index 1764204c..bb9694f3 100644 --- a/LibationWinForms/Dialogs/SettingsDialog.Designer.cs +++ b/LibationWinForms/Dialogs/SettingsDialog.Designer.cs @@ -204,9 +204,9 @@ this.convertLossyRb.AutoSize = true; this.convertLossyRb.Location = new System.Drawing.Point(6, 81); this.convertLossyRb.Name = "convertLossyRb"; - this.convertLossyRb.Size = new System.Drawing.Size(242, 19); + this.convertLossyRb.Size = new System.Drawing.Size(329, 19); this.convertLossyRb.TabIndex = 10; - this.convertLossyRb.Text = "Download my books as .MP3 files (Lossy)"; + this.convertLossyRb.Text = "Download my books as .MP3 files (transcode if necessary)"; this.convertLossyRb.UseVisualStyleBackColor = true; // // convertLosslessRb @@ -215,10 +215,10 @@ this.convertLosslessRb.Checked = true; this.convertLosslessRb.Location = new System.Drawing.Point(6, 56); this.convertLosslessRb.Name = "convertLosslessRb"; - this.convertLosslessRb.Size = new System.Drawing.Size(327, 19); + this.convertLosslessRb.Size = new System.Drawing.Size(335, 19); this.convertLosslessRb.TabIndex = 9; this.convertLosslessRb.TabStop = true; - this.convertLosslessRb.Text = "Download my books as .M4B files (Lossless Mp4a format)"; + this.convertLosslessRb.Text = "Download my books in the original audio format (Lossless)"; this.convertLosslessRb.UseVisualStyleBackColor = true; // // inProgressSelectControl