diff --git a/Source/AaxDecrypter/AaxcDownloadConvertBase.cs b/Source/AaxDecrypter/AaxcDownloadConvertBase.cs index 773e11de..10f2cc37 100644 --- a/Source/AaxDecrypter/AaxcDownloadConvertBase.cs +++ b/Source/AaxDecrypter/AaxcDownloadConvertBase.cs @@ -34,27 +34,12 @@ namespace AaxDecrypter //Finishing configuring lame encoder. if (DownloadOptions.OutputFormat == OutputFormat.Mp3) - { - double bitrateMultiple = 1; + MpegUtil.ConfigureLameOptions( + AaxFile, + DownloadOptions.LameConfig, + DownloadOptions.Downsample, + DownloadOptions.MatchSourceBitrate); - if (AaxFile.AudioChannels == 2) - { - if (DownloadOptions.Downsample) - bitrateMultiple = 0.5; - else - DownloadOptions.LameConfig.Mode = NAudio.Lame.MPEGMode.Stereo; - } - - if (DownloadOptions.MatchSourceBitrate) - { - int kbps = (int)(AaxFile.AverageBitrate * bitrateMultiple / 1024); - - if (DownloadOptions.LameConfig.VBR is null) - DownloadOptions.LameConfig.BitRate = kbps; - else if (DownloadOptions.LameConfig.VBR == NAudio.Lame.VBRMode.ABR) - DownloadOptions.LameConfig.ABRRateKbps = kbps; - } - } OnRetrievedTitle(AaxFile.AppleTags.TitleSansUnabridged); OnRetrievedAuthors(AaxFile.AppleTags.FirstAuthor ?? "[unknown]"); diff --git a/Source/AaxDecrypter/AaxcDownloadSingleConverter.cs b/Source/AaxDecrypter/AaxcDownloadSingleConverter.cs index fcb73ecc..85dc62a1 100644 --- a/Source/AaxDecrypter/AaxcDownloadSingleConverter.cs +++ b/Source/AaxDecrypter/AaxcDownloadSingleConverter.cs @@ -78,13 +78,10 @@ namespace AaxDecrypter OnFileCreated(OutputFileName); AaxFile.ConversionProgressUpdate += AaxFile_ConversionProgressUpdate; - var decryptionResult - = DownloadOptions.OutputFormat == OutputFormat.M4b - ? await AaxFile.ConvertToMp4aAsync(outputFile, DownloadOptions.ChapterInfo, DownloadOptions.TrimOutputToChapterLength) - : await AaxFile.ConvertToMp3Async(outputFile, DownloadOptions.LameConfig, DownloadOptions.ChapterInfo, DownloadOptions.TrimOutputToChapterLength); - AaxFile.ConversionProgressUpdate -= AaxFile_ConversionProgressUpdate; - DownloadOptions.ChapterInfo = AaxFile.Chapters; + ConversionResult decryptionResult = await decryptAsync(outputFile); + + AaxFile.ConversionProgressUpdate -= AaxFile_ConversionProgressUpdate; Step_DownloadAudiobook_End(zeroProgress); @@ -94,5 +91,23 @@ namespace AaxDecrypter return success; } + + private Task decryptAsync(Stream outputFile) + => DownloadOptions.OutputFormat == OutputFormat.Mp3 ? + AaxFile.ConvertToMp3Async + ( + outputFile, + DownloadOptions.LameConfig, + DownloadOptions.ChapterInfo, + DownloadOptions.TrimOutputToChapterLength + ) + : DownloadOptions.FixupFile ? + AaxFile.ConvertToMp4aAsync + ( + outputFile, + DownloadOptions.ChapterInfo, + DownloadOptions.TrimOutputToChapterLength + ) + : AaxFile.ConvertToMp4aAsync(outputFile); } } diff --git a/Source/AaxDecrypter/IDownloadOptions.cs b/Source/AaxDecrypter/IDownloadOptions.cs index 58d6e90f..2e098af6 100644 --- a/Source/AaxDecrypter/IDownloadOptions.cs +++ b/Source/AaxDecrypter/IDownloadOptions.cs @@ -14,10 +14,11 @@ namespace AaxDecrypter bool RetainEncryptedFile { get; } bool StripUnabridged { get; } bool CreateCueSheet { get; } - ChapterInfo ChapterInfo { get; set; } - NAudio.Lame.LameConfig LameConfig { get; set; } - bool Downsample { get; set; } - bool MatchSourceBitrate { get; set; } + ChapterInfo ChapterInfo { get; } + bool FixupFile { get; } + NAudio.Lame.LameConfig LameConfig { get; } + bool Downsample { get; } + bool MatchSourceBitrate { get; } string GetMultipartFileName(MultiConvertFileProperties props); string GetMultipartTitleName(MultiConvertFileProperties props); } diff --git a/Source/AaxDecrypter/MpegUtil.cs b/Source/AaxDecrypter/MpegUtil.cs new file mode 100644 index 00000000..116725b4 --- /dev/null +++ b/Source/AaxDecrypter/MpegUtil.cs @@ -0,0 +1,33 @@ +using AAXClean; +using NAudio.Lame; +using System; +using System.Linq; + +namespace AaxDecrypter +{ + public static class MpegUtil + { + public static void ConfigureLameOptions(Mp4File mp4File, LameConfig lameConfig, bool downsample, bool matchSourceBitrate) + { + double bitrateMultiple = 1; + + if (mp4File.AudioChannels == 2) + { + if (downsample) + bitrateMultiple = 0.5; + else + lameConfig.Mode = MPEGMode.Stereo; + } + + if (matchSourceBitrate) + { + int kbps = (int)(mp4File.AverageBitrate * bitrateMultiple / 1024); + + if (lameConfig.VBR is null) + lameConfig.BitRate = kbps; + else if (lameConfig.VBR == VBRMode.ABR) + lameConfig.ABRRateKbps = kbps; + } + } + } +} diff --git a/Source/AppScaffolding/AppScaffolding.csproj b/Source/AppScaffolding/AppScaffolding.csproj index 53eec16a..69858b98 100644 --- a/Source/AppScaffolding/AppScaffolding.csproj +++ b/Source/AppScaffolding/AppScaffolding.csproj @@ -2,7 +2,7 @@ net6.0-windows - 8.1.5.1 + 8.1.5.23 @@ -18,6 +18,6 @@ embedded - + \ No newline at end of file diff --git a/Source/FileLiberator/ConvertToMp3.cs b/Source/FileLiberator/ConvertToMp3.cs index 70940d05..94744596 100644 --- a/Source/FileLiberator/ConvertToMp3.cs +++ b/Source/FileLiberator/ConvertToMp3.cs @@ -53,8 +53,17 @@ namespace FileLiberator OnNarratorsDiscovered(m4bBook.AppleTags.Narrator); OnCoverImageDiscovered(m4bBook.AppleTags.Cover); + var config = Configuration.Instance; + var lameConfig = GetLameOptions(config); + + //Finishing configuring lame encoder. + AaxDecrypter.MpegUtil.ConfigureLameOptions( + m4bBook, + lameConfig, + config.LameDownsampleMono, + config.LameMatchSourceBR); + using var mp3File = File.OpenWrite(Path.GetTempFileName()); - var lameConfig = GetLameOptions(Configuration.Instance); var result = await m4bBook.ConvertToMp3Async(mp3File, lameConfig); m4bBook.InputStream.Close(); mp3File.Close(); diff --git a/Source/FileLiberator/DownloadDecryptBook.cs b/Source/FileLiberator/DownloadDecryptBook.cs index 855a0322..7399b48f 100644 --- a/Source/FileLiberator/DownloadDecryptBook.cs +++ b/Source/FileLiberator/DownloadDecryptBook.cs @@ -146,55 +146,127 @@ namespace FileLiberator var outputFormat = !encrypted || (config.AllowLibationFixup && config.DecryptToLossy) ? OutputFormat.Mp3 : OutputFormat.M4b; + long chapterStartMs = config.StripAudibleBrandAudio ? + contentLic.ContentMetadata.ChapterInfo.BrandIntroDurationMs : 0; + var dlOptions = new DownloadOptions ( libraryBook, 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) - }; + { + 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), + ChapterInfo = new AAXClean.ChapterInfo(TimeSpan.FromMilliseconds(chapterStartMs)), + FixupFile = config.AllowLibationFixup + }; var chapters = flattenChapters(contentLic.ContentMetadata.ChapterInfo.Chapters).OrderBy(c => c.StartOffsetMs).ToList(); - + if (config.MergeOpeningAndEndCredits) combineCredits(chapters); - if (config.AllowLibationFixup || outputFormat == OutputFormat.Mp3) + for (int i = 0; i < chapters.Count; i++) { - long startMs = dlOptions.TrimOutputToChapterLength ? - contentLic.ContentMetadata.ChapterInfo.BrandIntroDurationMs : 0; + var chapter = chapters[i]; + long chapLenMs = chapter.LengthMs; - dlOptions.ChapterInfo = new AAXClean.ChapterInfo(TimeSpan.FromMilliseconds(startMs)); + if (i == 0) + chapLenMs -= chapterStartMs; - for (int i = 0; i < chapters.Count; i++) - { - var chapter = chapters[i]; - long chapLenMs = chapter.LengthMs; + if (config.StripAudibleBrandAudio && i == chapters.Count - 1) + chapLenMs -= contentLic.ContentMetadata.ChapterInfo.BrandOutroDurationMs; - if (i == 0) - chapLenMs -= startMs; - - if (config.StripAudibleBrandAudio && i == chapters.Count - 1) - chapLenMs -= contentLic.ContentMetadata.ChapterInfo.BrandOutroDurationMs; - - dlOptions.ChapterInfo.AddChapter(chapter.Title, TimeSpan.FromMilliseconds(chapLenMs)); - } + dlOptions.ChapterInfo.AddChapter(chapter.Title, TimeSpan.FromMilliseconds(chapLenMs)); } return dlOptions; } + /* + + Flatten Audible's new hierarchical chapters, combining children into parents. + + Audible may deliver chapters like this: + + 00:00 - 00:10 Opening Credits + 00:10 - 00:12 Book 1 + 00:12 - 00:14 | Part 1 + 00:14 - 01:40 | | Chapter 1 + 01:40 - 03:20 | | Chapter 2 + 03:20 - 03:22 | Part 2 + 03:22 - 05:00 | | Chapter 3 + 05:00 - 06:40 | | Chapter 4 + 06:40 - 06:42 Book 2 + 06:42 - 06:44 | Part 3 + 06:44 - 08:20 | | Chapter 5 + 08:20 - 10:00 | | Chapter 6 + 10:00 - 10:02 | Part 4 + 10:02 - 11:40 | | Chapter 7 + 11:40 - 13:20 | | Chapter 8 + 13:20 - 13:30 End Credits + + And flattenChapters will combine them into this: + + 00:00 - 00:10 Opening Credits + 00:10 - 01:40 Book 1: Part 1: Chapter 1 + 01:40 - 03:20 Book 1: Part 1: Chapter 2 + 03:20 - 05:00 Book 1: Part 2: Chapter 3 + 05:00 - 06:40 Book 1: Part 2: Chapter 4 + 06:40 - 08:20 Book 2: Part 3: Chapter 5 + 08:20 - 10:00 Book 2: Part 3: Chapter 6 + 10:00 - 11:40 Book 2: Part 4: Chapter 7 + 11:40 - 13:20 Book 2: Part 4: Chapter 8 + 13:20 - 13:40 End Credits + + However, if one of the parent chapters is longer than 10000 milliseconds, it's kept as its own + chapter. A duration longer than a few seconds implies that the chapter contains more than just + the narrator saying the chapter title, so it should probably be preserved as a separate chapter. + Using the example above, if "Book 1" was 15 seconds long and "Part 3" was 20 seconds long: + + 00:00 - 00:10 Opening Credits + 00:10 - 00:25 Book 1 + 00:25 - 00:27 | Part 1 + 00:27 - 01:40 | | Chapter 1 + 01:40 - 03:20 | | Chapter 2 + 03:20 - 03:22 | Part 2 + 03:22 - 05:00 | | Chapter 3 + 05:00 - 06:40 | | Chapter 4 + 06:40 - 06:42 Book 2 + 06:42 - 07:02 | Part 3 + 07:02 - 08:20 | | Chapter 5 + 08:20 - 10:00 | | Chapter 6 + 10:00 - 10:02 | Part 4 + 10:02 - 11:40 | | Chapter 7 + 11:40 - 13:20 | | Chapter 8 + 13:20 - 13:30 End Credits + + then flattenChapters will combine them into this: + + 00:00 - 00:10 Opening Credits + 00:10 - 00:25 Book 1 + 00:25 - 01:40 Book 1: Part 1: Chapter 1 + 01:40 - 03:20 Book 1: Part 1: Chapter 2 + 03:20 - 05:00 Book 1: Part 2: Chapter 3 + 05:00 - 06:40 Book 1: Part 2: Chapter 4 + 06:40 - 07:02 Book 2: Part 3 + 07:02 - 08:20 Book 2: Part 3: Chapter 5 + 08:20 - 10:00 Book 2: Part 3: Chapter 6 + 10:00 - 11:40 Book 2: Part 4: Chapter 7 + 11:40 - 13:20 Book 2: Part 4: Chapter 8 + 13:20 - 13:40 End Credits + + */ + public static List flattenChapters(IList chapters, string titleConcat = ": ") { List chaps = new(); @@ -203,9 +275,14 @@ namespace FileLiberator { if (c.Chapters is not null) { - c.Chapters[0].StartOffsetMs = c.StartOffsetMs; - c.Chapters[0].StartOffsetSec = c.StartOffsetSec; - c.Chapters[0].LengthMs += c.LengthMs; + if (c.LengthMs < 10000) + { + c.Chapters[0].StartOffsetMs = c.StartOffsetMs; + c.Chapters[0].StartOffsetSec = c.StartOffsetSec; + c.Chapters[0].LengthMs += c.LengthMs; + } + else + chaps.Add(c); var children = flattenChapters(c.Chapters); @@ -213,6 +290,7 @@ namespace FileLiberator child.Title = $"{c.Title}{titleConcat}{child.Title}"; chaps.AddRange(children); + c.Chapters = null; } else chaps.Add(c); diff --git a/Source/FileLiberator/DownloadOptions.cs b/Source/FileLiberator/DownloadOptions.cs index dc2fd940..17ac6651 100644 --- a/Source/FileLiberator/DownloadOptions.cs +++ b/Source/FileLiberator/DownloadOptions.cs @@ -20,10 +20,11 @@ namespace FileLiberator public bool RetainEncryptedFile { get; init; } public bool StripUnabridged { get; init; } public bool CreateCueSheet { get; init; } - public ChapterInfo ChapterInfo { get; set; } - public NAudio.Lame.LameConfig LameConfig { get; set; } - public bool Downsample { get; set; } - public bool MatchSourceBitrate { get; set; } + public ChapterInfo ChapterInfo { get; init; } + public bool FixupFile { get; init; } + public NAudio.Lame.LameConfig LameConfig { get; init; } + public bool Downsample { get; init; } + public bool MatchSourceBitrate { get; init; } public ReplacementCharacters ReplacementCharacters => Configuration.Instance.ReplacementCharacters; public string GetMultipartFileName(MultiConvertFileProperties props) diff --git a/Source/LibationWinForms/Dialogs/SettingsDialog.AudioSettings.cs b/Source/LibationWinForms/Dialogs/SettingsDialog.AudioSettings.cs index d7b53a2e..a9e3c3a7 100644 --- a/Source/LibationWinForms/Dialogs/SettingsDialog.AudioSettings.cs +++ b/Source/LibationWinForms/Dialogs/SettingsDialog.AudioSettings.cs @@ -43,6 +43,7 @@ namespace LibationWinForms.Dialogs LameMatchSourceBRCbox_CheckedChanged(this, EventArgs.Empty); convertFormatRb_CheckedChanged(this, EventArgs.Empty); allowLibationFixupCbox_CheckedChanged(this, EventArgs.Empty); + splitFilesByChapterCbox_CheckedChanged(this, EventArgs.Empty); } private void Save_AudioSettings(Configuration config) @@ -92,6 +93,7 @@ namespace LibationWinForms.Dialogs } private void allowLibationFixupCbox_CheckedChanged(object sender, EventArgs e) { + audiobookFixupsGb.Enabled = allowLibationFixupCbox.Checked; convertLosslessRb.Enabled = allowLibationFixupCbox.Checked; convertLossyRb.Enabled = allowLibationFixupCbox.Checked; splitFilesByChapterCbox.Enabled = allowLibationFixupCbox.Checked; diff --git a/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs b/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs index 19166a4f..5932dcd7 100644 --- a/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs +++ b/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs @@ -105,10 +105,11 @@ this.lameTargetQualityRb = new System.Windows.Forms.RadioButton(); this.lameTargetBitrateRb = new System.Windows.Forms.RadioButton(); this.stripUnabridgedCbox = new System.Windows.Forms.CheckBox(); + this.mergeOpeningEndCreditsCbox = new System.Windows.Forms.CheckBox(); this.retainAaxFileCbox = new System.Windows.Forms.CheckBox(); this.downloadCoverArtCbox = new System.Windows.Forms.CheckBox(); this.createCueSheetCbox = new System.Windows.Forms.CheckBox(); - this.mergeOpeningEndCreditsCbox = new System.Windows.Forms.CheckBox(); + this.audiobookFixupsGb = new System.Windows.Forms.GroupBox(); this.badBookGb.SuspendLayout(); this.tabControl.SuspendLayout(); this.tab1ImportantSettings.SuspendLayout(); @@ -125,6 +126,7 @@ this.lameQualityGb.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.lameVBRQualityTb)).BeginInit(); this.groupBox2.SuspendLayout(); + this.audiobookFixupsGb.SuspendLayout(); this.SuspendLayout(); // // booksLocationDescLbl @@ -252,7 +254,7 @@ // stripAudibleBrandingCbox // this.stripAudibleBrandingCbox.AutoSize = true; - this.stripAudibleBrandingCbox.Location = new System.Drawing.Point(19, 193); + this.stripAudibleBrandingCbox.Location = new System.Drawing.Point(13, 72); this.stripAudibleBrandingCbox.Name = "stripAudibleBrandingCbox"; this.stripAudibleBrandingCbox.Size = new System.Drawing.Size(143, 34); this.stripAudibleBrandingCbox.TabIndex = 13; @@ -262,7 +264,7 @@ // splitFilesByChapterCbox // this.splitFilesByChapterCbox.AutoSize = true; - this.splitFilesByChapterCbox.Location = new System.Drawing.Point(19, 118); + this.splitFilesByChapterCbox.Location = new System.Drawing.Point(13, 22); this.splitFilesByChapterCbox.Name = "splitFilesByChapterCbox"; this.splitFilesByChapterCbox.Size = new System.Drawing.Size(162, 19); this.splitFilesByChapterCbox.TabIndex = 13; @@ -275,7 +277,7 @@ this.allowLibationFixupCbox.AutoSize = true; this.allowLibationFixupCbox.Checked = true; this.allowLibationFixupCbox.CheckState = System.Windows.Forms.CheckState.Checked; - this.allowLibationFixupCbox.Location = new System.Drawing.Point(19, 18); + this.allowLibationFixupCbox.Location = new System.Drawing.Point(19, 118); this.allowLibationFixupCbox.Name = "allowLibationFixupCbox"; this.allowLibationFixupCbox.Size = new System.Drawing.Size(163, 19); this.allowLibationFixupCbox.TabIndex = 10; @@ -286,7 +288,7 @@ // convertLossyRb // this.convertLossyRb.AutoSize = true; - this.convertLossyRb.Location = new System.Drawing.Point(19, 257); + this.convertLossyRb.Location = new System.Drawing.Point(13, 136); this.convertLossyRb.Name = "convertLossyRb"; this.convertLossyRb.Size = new System.Drawing.Size(329, 19); this.convertLossyRb.TabIndex = 12; @@ -298,7 +300,7 @@ // this.convertLosslessRb.AutoSize = true; this.convertLosslessRb.Checked = true; - this.convertLosslessRb.Location = new System.Drawing.Point(19, 232); + this.convertLosslessRb.Location = new System.Drawing.Point(13, 111); this.convertLosslessRb.Name = "convertLosslessRb"; this.convertLosslessRb.Size = new System.Drawing.Size(335, 19); this.convertLosslessRb.TabIndex = 11; @@ -603,14 +605,10 @@ // // tab4AudioFileOptions // + this.tab4AudioFileOptions.Controls.Add(this.audiobookFixupsGb); this.tab4AudioFileOptions.Controls.Add(this.chapterTitleTemplateGb); this.tab4AudioFileOptions.Controls.Add(this.lameOptionsGb); - this.tab4AudioFileOptions.Controls.Add(this.convertLossyRb); - this.tab4AudioFileOptions.Controls.Add(this.stripAudibleBrandingCbox); - this.tab4AudioFileOptions.Controls.Add(this.convertLosslessRb); - this.tab4AudioFileOptions.Controls.Add(this.stripUnabridgedCbox); this.tab4AudioFileOptions.Controls.Add(this.mergeOpeningEndCreditsCbox); - this.tab4AudioFileOptions.Controls.Add(this.splitFilesByChapterCbox); this.tab4AudioFileOptions.Controls.Add(this.retainAaxFileCbox); this.tab4AudioFileOptions.Controls.Add(this.downloadCoverArtCbox); this.tab4AudioFileOptions.Controls.Add(this.createCueSheetCbox); @@ -982,17 +980,27 @@ // stripUnabridgedCbox // this.stripUnabridgedCbox.AutoSize = true; - this.stripUnabridgedCbox.Location = new System.Drawing.Point(19, 168); + this.stripUnabridgedCbox.Location = new System.Drawing.Point(13, 47); this.stripUnabridgedCbox.Name = "stripUnabridgedCbox"; this.stripUnabridgedCbox.Size = new System.Drawing.Size(147, 19); this.stripUnabridgedCbox.TabIndex = 13; this.stripUnabridgedCbox.Text = "[StripUnabridged desc]"; this.stripUnabridgedCbox.UseVisualStyleBackColor = true; // + // mergeOpeningEndCreditsCbox + // + this.mergeOpeningEndCreditsCbox.AutoSize = true; + this.mergeOpeningEndCreditsCbox.Location = new System.Drawing.Point(19, 93); + this.mergeOpeningEndCreditsCbox.Name = "mergeOpeningEndCreditsCbox"; + this.mergeOpeningEndCreditsCbox.Size = new System.Drawing.Size(198, 19); + this.mergeOpeningEndCreditsCbox.TabIndex = 13; + this.mergeOpeningEndCreditsCbox.Text = "[MergeOpeningEndCredits desc]"; + this.mergeOpeningEndCreditsCbox.UseVisualStyleBackColor = true; + // // retainAaxFileCbox // this.retainAaxFileCbox.AutoSize = true; - this.retainAaxFileCbox.Location = new System.Drawing.Point(19, 93); + this.retainAaxFileCbox.Location = new System.Drawing.Point(19, 68); this.retainAaxFileCbox.Name = "retainAaxFileCbox"; this.retainAaxFileCbox.Size = new System.Drawing.Size(132, 19); this.retainAaxFileCbox.TabIndex = 10; @@ -1005,7 +1013,7 @@ this.downloadCoverArtCbox.AutoSize = true; this.downloadCoverArtCbox.Checked = true; this.downloadCoverArtCbox.CheckState = System.Windows.Forms.CheckState.Checked; - this.downloadCoverArtCbox.Location = new System.Drawing.Point(19, 68); + this.downloadCoverArtCbox.Location = new System.Drawing.Point(19, 43); this.downloadCoverArtCbox.Name = "downloadCoverArtCbox"; this.downloadCoverArtCbox.Size = new System.Drawing.Size(162, 19); this.downloadCoverArtCbox.TabIndex = 10; @@ -1018,7 +1026,7 @@ this.createCueSheetCbox.AutoSize = true; this.createCueSheetCbox.Checked = true; this.createCueSheetCbox.CheckState = System.Windows.Forms.CheckState.Checked; - this.createCueSheetCbox.Location = new System.Drawing.Point(19, 43); + this.createCueSheetCbox.Location = new System.Drawing.Point(19, 18); this.createCueSheetCbox.Name = "createCueSheetCbox"; this.createCueSheetCbox.Size = new System.Drawing.Size(145, 19); this.createCueSheetCbox.TabIndex = 10; @@ -1026,16 +1034,19 @@ this.createCueSheetCbox.UseVisualStyleBackColor = true; this.createCueSheetCbox.CheckedChanged += new System.EventHandler(this.allowLibationFixupCbox_CheckedChanged); // - // mergeBeginningEndCreditsCbox + // audiobookFixupsGb // - this.mergeOpeningEndCreditsCbox.AutoSize = true; - this.mergeOpeningEndCreditsCbox.Location = new System.Drawing.Point(19, 143); - this.mergeOpeningEndCreditsCbox.Name = "mergeOpeningEndCreditsCbox"; - this.mergeOpeningEndCreditsCbox.Size = new System.Drawing.Size(206, 19); - this.mergeOpeningEndCreditsCbox.TabIndex = 13; - this.mergeOpeningEndCreditsCbox.Text = "[MergeOpeningEndCredits desc]"; - this.mergeOpeningEndCreditsCbox.UseVisualStyleBackColor = true; - this.mergeOpeningEndCreditsCbox.CheckedChanged += new System.EventHandler(this.splitFilesByChapterCbox_CheckedChanged); + this.audiobookFixupsGb.Controls.Add(this.splitFilesByChapterCbox); + this.audiobookFixupsGb.Controls.Add(this.stripUnabridgedCbox); + this.audiobookFixupsGb.Controls.Add(this.convertLosslessRb); + this.audiobookFixupsGb.Controls.Add(this.convertLossyRb); + this.audiobookFixupsGb.Controls.Add(this.stripAudibleBrandingCbox); + this.audiobookFixupsGb.Location = new System.Drawing.Point(6, 143); + this.audiobookFixupsGb.Name = "audiobookFixupsGb"; + this.audiobookFixupsGb.Size = new System.Drawing.Size(403, 160); + this.audiobookFixupsGb.TabIndex = 19; + this.audiobookFixupsGb.TabStop = false; + this.audiobookFixupsGb.Text = "Audiobook Fix-ups"; // // SettingsDialog // @@ -1083,6 +1094,8 @@ ((System.ComponentModel.ISupportInitialize)(this.lameVBRQualityTb)).EndInit(); this.groupBox2.ResumeLayout(false); this.groupBox2.PerformLayout(); + this.audiobookFixupsGb.ResumeLayout(false); + this.audiobookFixupsGb.PerformLayout(); this.ResumeLayout(false); } @@ -1169,5 +1182,6 @@ private System.Windows.Forms.TextBox chapterTitleTemplateTb; private System.Windows.Forms.Button editCharreplacementBtn; private System.Windows.Forms.CheckBox mergeOpeningEndCreditsCbox; + private System.Windows.Forms.GroupBox audiobookFixupsGb; } } \ No newline at end of file diff --git a/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs b/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs index c2df3666..1e6c02a7 100644 --- a/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs +++ b/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs @@ -148,6 +148,141 @@ namespace FileLiberator.Tests }, }; + + private static Chapter[] HierarchicalChapters_LongerParents => new Chapter[] + { + new () + { + Title = "Opening Credits", + StartOffsetMs = 0, + StartOffsetSec = 0, + LengthMs = 10000, + }, + new () + { + Title = "Book 1", + StartOffsetMs = 10000, + StartOffsetSec = 10, + LengthMs = 15000, + Chapters = new Chapter[] + { + new () + { + Title = "Part 1", + StartOffsetMs = 25000, + StartOffsetSec = 25, + LengthMs = 2000, + Chapters = new Chapter[] + { + new () + { + Title = "Chapter 1", + StartOffsetMs = 27000, + StartOffsetSec = 27, + LengthMs = 73000, + }, + new() + { + Title = "Chapter 2", + StartOffsetMs = 100000, + StartOffsetSec = 100, + LengthMs = 100000, + }, + } + }, + new() + { + Title = "Part 2", + StartOffsetMs = 200000, + StartOffsetSec = 200, + LengthMs = 2000, + Chapters = new Chapter[] + { + new() + { + Title = "Chapter 3", + StartOffsetMs = 202000, + StartOffsetSec = 202, + LengthMs = 98000, + }, + new() + { + Title = "Chapter 4", + StartOffsetMs = 300000, + StartOffsetSec = 300, + LengthMs = 100000, + }, + } + } + } + }, + new() + { + Title = "Book 2", + StartOffsetMs = 400000, + StartOffsetSec = 400, + LengthMs = 2000, + Chapters = new Chapter[] + { + new() + { + Title = "Part 3", + StartOffsetMs = 402000, + StartOffsetSec = 402, + LengthMs = 20000, + Chapters = new Chapter[] + { + new() + { + Title = "Chapter 5", + StartOffsetMs = 422000, + StartOffsetSec = 422, + LengthMs = 78000, + }, + new() + { + Title = "Chapter 6", + StartOffsetMs = 500000, + StartOffsetSec = 500, + LengthMs = 100000, + }, + } + }, + new() + { + Title = "Part 4", + StartOffsetMs = 600000, + StartOffsetSec = 600, + LengthMs = 2000, + Chapters = new Chapter[] + { + new() + { + Title = "Chapter 7", + StartOffsetMs = 602000, + StartOffsetSec = 602, + LengthMs = 98000, + }, + new() + { + Title = "Chapter 8", + StartOffsetMs = 700000, + StartOffsetSec = 700, + LengthMs = 100000, + }, + } + } + } + }, + new() + { + Title = "End Credits", + StartOffsetMs = 800000, + StartOffsetSec = 800, + LengthMs = 10000, + }, + }; + [TestMethod] public void Chapters_CombineCredits() { @@ -299,6 +434,102 @@ namespace FileLiberator.Tests checkChapters(flatChapters, expected); } + [TestMethod] + public void HierarchicalChapters_LongerParents_Flatten() + { + var expected = new Chapter[] + { + new() + { + Title = "Opening Credits", + StartOffsetMs = 0, + StartOffsetSec = 0, + LengthMs = 10000, + }, + new() + { + Title = "Book 1", + StartOffsetMs = 10000, + StartOffsetSec = 10, + LengthMs = 15000, + }, + new() + { + Title = "Book 1: Part 1: Chapter 1", + StartOffsetMs = 25000, + StartOffsetSec = 25, + LengthMs = 75000, + }, + new() + { + Title = "Book 1: Part 1: Chapter 2", + StartOffsetMs = 100000, + StartOffsetSec = 100, + LengthMs = 100000, + }, + new() + { + Title = "Book 1: Part 2: Chapter 3", + StartOffsetMs = 200000, + StartOffsetSec = 200, + LengthMs = 100000, + }, + new() + { + Title = "Book 1: Part 2: Chapter 4", + StartOffsetMs = 300000, + StartOffsetSec = 300, + LengthMs = 100000, + }, + new() + { + Title = "Book 2: Part 3", + StartOffsetMs = 400000, + StartOffsetSec = 400, + LengthMs = 22000, + }, + new() + { + Title = "Book 2: Part 3: Chapter 5", + StartOffsetMs = 422000, + StartOffsetSec = 422, + LengthMs = 78000, + }, + new() + { + Title = "Book 2: Part 3: Chapter 6", + StartOffsetMs = 500000, + StartOffsetSec = 500, + LengthMs = 100000, + }, + new() + { + Title = "Book 2: Part 4: Chapter 7", + StartOffsetMs = 600000, + StartOffsetSec = 600, + LengthMs = 100000, + }, + new() + { + Title = "Book 2: Part 4: Chapter 8", + StartOffsetMs = 700000, + StartOffsetSec = 700, + LengthMs = 100000, + }, + new() + { + Title = "End Credits", + StartOffsetMs = 800000, + StartOffsetSec = 800, + LengthMs = 10000, + } + }; + + var flatChapters = DownloadDecryptBook.flattenChapters(HierarchicalChapters_LongerParents); + + checkChapters(flatChapters, expected); + } + private static void checkChapters(IList value, IList expected) { value.Count.Should().Be(expected.Count);