diff --git a/Source/FileLiberator/DownloadDecryptBook.cs b/Source/FileLiberator/DownloadDecryptBook.cs index a426cff2..855a0322 100644 --- a/Source/FileLiberator/DownloadDecryptBook.cs +++ b/Source/FileLiberator/DownloadDecryptBook.cs @@ -166,6 +166,9 @@ namespace FileLiberator }; var chapters = flattenChapters(contentLic.ContentMetadata.ChapterInfo.Chapters).OrderBy(c => c.StartOffsetMs).ToList(); + + if (config.MergeOpeningAndEndCredits) + combineCredits(chapters); if (config.AllowLibationFixup || outputFormat == OutputFormat.Mp3) { @@ -192,7 +195,7 @@ namespace FileLiberator return dlOptions; } - public static List flattenChapters(IEnumerable chapters, string titleConcat = ": ") + public static List flattenChapters(IList chapters, string titleConcat = ": ") { List chaps = new(); @@ -217,6 +220,22 @@ namespace FileLiberator return chaps; } + public static void combineCredits(IList chapters) + { + if (chapters.Count > 1 && chapters[0].Title == "Opening Credits") + { + chapters[1].StartOffsetMs = chapters[0].StartOffsetMs; + chapters[1].StartOffsetSec = chapters[0].StartOffsetSec; + chapters[1].LengthMs += chapters[0].LengthMs; + chapters.RemoveAt(0); + } + if (chapters.Count > 1 && chapters[^1].Title == "End Credits") + { + chapters[^2].LengthMs += chapters[^1].LengthMs; + chapters.Remove(chapters[^1]); + } + } + private static void downloadValidation(LibraryBook libraryBook) { string errorString(string field) diff --git a/Source/LibationFileManager/Configuration.cs b/Source/LibationFileManager/Configuration.cs index d545df22..23403699 100644 --- a/Source/LibationFileManager/Configuration.cs +++ b/Source/LibationFileManager/Configuration.cs @@ -117,6 +117,13 @@ namespace LibationFileManager set => persistentDictionary.SetNonString(nameof(SplitFilesByChapter), value); } + [Description("Merge Opening/End Credits into the following/preceding chapters")] + public bool MergeOpeningAndEndCredits + { + get => persistentDictionary.GetNonString(nameof(MergeOpeningAndEndCredits)); + set => persistentDictionary.SetNonString(nameof(MergeOpeningAndEndCredits), value); + } + [Description("Strip \"(Unabridged)\" from audiobook metadata tags")] public bool StripUnabridged { diff --git a/Source/LibationWinForms/Dialogs/SettingsDialog.AudioSettings.cs b/Source/LibationWinForms/Dialogs/SettingsDialog.AudioSettings.cs index 72f9a151..d7b53a2e 100644 --- a/Source/LibationWinForms/Dialogs/SettingsDialog.AudioSettings.cs +++ b/Source/LibationWinForms/Dialogs/SettingsDialog.AudioSettings.cs @@ -13,6 +13,7 @@ namespace LibationWinForms.Dialogs this.downloadCoverArtCbox.Text = desc(nameof(config.DownloadCoverArt)); this.retainAaxFileCbox.Text = desc(nameof(config.RetainAaxFile)); this.splitFilesByChapterCbox.Text = desc(nameof(config.SplitFilesByChapter)); + this.mergeOpeningEndCreditsCbox.Text = desc(nameof(config.MergeOpeningAndEndCredits)); this.stripAudibleBrandingCbox.Text = desc(nameof(config.StripAudibleBrandAudio)); this.stripUnabridgedCbox.Text = desc(nameof(config.StripUnabridged)); @@ -21,6 +22,7 @@ namespace LibationWinForms.Dialogs downloadCoverArtCbox.Checked = config.DownloadCoverArt; retainAaxFileCbox.Checked = config.RetainAaxFile; splitFilesByChapterCbox.Checked = config.SplitFilesByChapter; + mergeOpeningEndCreditsCbox.Checked = config.MergeOpeningAndEndCredits; stripUnabridgedCbox.Checked = config.StripUnabridged; stripAudibleBrandingCbox.Checked = config.StripAudibleBrandAudio; convertLosslessRb.Checked = !config.DecryptToLossy; @@ -50,6 +52,7 @@ namespace LibationWinForms.Dialogs config.DownloadCoverArt = downloadCoverArtCbox.Checked; config.RetainAaxFile = retainAaxFileCbox.Checked; config.SplitFilesByChapter = splitFilesByChapterCbox.Checked; + config.MergeOpeningAndEndCredits = mergeOpeningEndCreditsCbox.Checked; config.StripUnabridged = stripUnabridgedCbox.Checked; config.StripAudibleBrandAudio = stripAudibleBrandingCbox.Checked; config.DecryptToLossy = convertLossyRb.Checked; diff --git a/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs b/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs index d117e4fe..19166a4f 100644 --- a/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs +++ b/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs @@ -108,6 +108,7 @@ 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.badBookGb.SuspendLayout(); this.tabControl.SuspendLayout(); this.tab1ImportantSettings.SuspendLayout(); @@ -251,7 +252,7 @@ // stripAudibleBrandingCbox // this.stripAudibleBrandingCbox.AutoSize = true; - this.stripAudibleBrandingCbox.Location = new System.Drawing.Point(19, 168); + this.stripAudibleBrandingCbox.Location = new System.Drawing.Point(19, 193); this.stripAudibleBrandingCbox.Name = "stripAudibleBrandingCbox"; this.stripAudibleBrandingCbox.Size = new System.Drawing.Size(143, 34); this.stripAudibleBrandingCbox.TabIndex = 13; @@ -285,7 +286,7 @@ // convertLossyRb // this.convertLossyRb.AutoSize = true; - this.convertLossyRb.Location = new System.Drawing.Point(19, 232); + this.convertLossyRb.Location = new System.Drawing.Point(19, 257); this.convertLossyRb.Name = "convertLossyRb"; this.convertLossyRb.Size = new System.Drawing.Size(329, 19); this.convertLossyRb.TabIndex = 12; @@ -297,7 +298,7 @@ // this.convertLosslessRb.AutoSize = true; this.convertLosslessRb.Checked = true; - this.convertLosslessRb.Location = new System.Drawing.Point(19, 207); + this.convertLosslessRb.Location = new System.Drawing.Point(19, 232); this.convertLosslessRb.Name = "convertLosslessRb"; this.convertLosslessRb.Size = new System.Drawing.Size(335, 19); this.convertLosslessRb.TabIndex = 11; @@ -608,6 +609,7 @@ 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); @@ -980,7 +982,7 @@ // stripUnabridgedCbox // this.stripUnabridgedCbox.AutoSize = true; - this.stripUnabridgedCbox.Location = new System.Drawing.Point(19, 143); + this.stripUnabridgedCbox.Location = new System.Drawing.Point(19, 168); this.stripUnabridgedCbox.Name = "stripUnabridgedCbox"; this.stripUnabridgedCbox.Size = new System.Drawing.Size(147, 19); this.stripUnabridgedCbox.TabIndex = 13; @@ -1024,6 +1026,17 @@ this.createCueSheetCbox.UseVisualStyleBackColor = true; this.createCueSheetCbox.CheckedChanged += new System.EventHandler(this.allowLibationFixupCbox_CheckedChanged); // + // mergeBeginningEndCreditsCbox + // + 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); + // // SettingsDialog // this.AcceptButton = this.saveBtn; @@ -1155,5 +1168,6 @@ private System.Windows.Forms.Button chapterTitleTemplateBtn; private System.Windows.Forms.TextBox chapterTitleTemplateTb; private System.Windows.Forms.Button editCharreplacementBtn; + private System.Windows.Forms.CheckBox mergeOpeningEndCreditsCbox; } } \ No newline at end of file diff --git a/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs b/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs index a30e585f..c2df3666 100644 --- a/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs +++ b/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs @@ -14,8 +14,142 @@ namespace FileLiberator.Tests [TestClass] public class DownloadDecryptBookTests { + private static Chapter[] HierarchicalChapters => new Chapter[] + { + new () + { + Title = "Opening Credits", + StartOffsetMs = 0, + StartOffsetSec = 0, + LengthMs = 10000, + }, + new () + { + Title = "Book 1", + StartOffsetMs = 10000, + StartOffsetSec = 10, + LengthMs = 2000, + Chapters = new Chapter[] + { + new () + { + Title = "Part 1", + StartOffsetMs = 12000, + StartOffsetSec = 12, + LengthMs = 2000, + Chapters = new Chapter[] + { + new () + { + Title = "Chapter 1", + StartOffsetMs = 14000, + StartOffsetSec = 14, + LengthMs = 86000, + }, + 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 = 2000, + Chapters = new Chapter[] + { + new() + { + Title = "Chapter 5", + StartOffsetMs = 404000, + StartOffsetSec = 404, + LengthMs = 96000, + }, + 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 HierarchicalChapters_Flatten() + public void Chapters_CombineCredits() { var expected = new Chapter[] { @@ -73,129 +207,109 @@ namespace FileLiberator.Tests Title = "Book 2: Part 4: Chapter 8", StartOffsetMs = 700000, StartOffsetSec = 700, - LengthMs = 100000, + LengthMs = 110000, } }; - var hierarchicalChapters = new Chapter[] - { - new() - { - Title = "Book 1", - StartOffsetMs = 0, - StartOffsetSec = 0, - LengthMs = 2000, - Chapters = new Chapter[] - { - new() - { Title = "Part 1", - StartOffsetMs = 2000, - StartOffsetSec = 2, - LengthMs = 2000, - Chapters = new Chapter[] - { - new() - { Title = "Chapter 1", - StartOffsetMs = 4000, - StartOffsetSec = 4, - LengthMs = 96000, - }, - 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 = 2000, - Chapters = new Chapter[] - { - new() - { Title = "Chapter 5", - StartOffsetMs = 404000, - StartOffsetSec = 404, - LengthMs = 96000, - }, - 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, - }, - } - } - } - } - }; + var flatChapters = DownloadDecryptBook.flattenChapters(HierarchicalChapters); + DownloadDecryptBook.combineCredits(flatChapters); + checkChapters(flatChapters, expected); + } - var flatChapters = DownloadDecryptBook.flattenChapters(hierarchicalChapters); - flatChapters.Count.Should().Be(expected.Length); - - for (int i = 0; i < flatChapters.Count; i++) + [TestMethod] + public void HierarchicalChapters_Flatten() + { + var expected = new Chapter[] { - flatChapters[i].Title.Should().Be(expected[i].Title); - flatChapters[i].StartOffsetMs.Should().Be(expected[i].StartOffsetMs); - flatChapters[i].StartOffsetSec.Should().Be(expected[i].StartOffsetSec); - flatChapters[i].LengthMs.Should().Be(expected[i].LengthMs); - flatChapters[i].Chapters.Should().BeNull(); + new() + { + Title = "Opening Credits", + StartOffsetMs = 0, + StartOffsetSec = 0, + LengthMs = 10000, + }, + new() + { + Title = "Book 1: Part 1: Chapter 1", + StartOffsetMs = 10000, + StartOffsetSec = 10, + LengthMs = 90000, + }, + 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: Chapter 5", + StartOffsetMs = 400000, + StartOffsetSec = 400, + LengthMs = 100000, + }, + 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); + + checkChapters(flatChapters, expected); + } + + private static void checkChapters(IList value, IList expected) + { + value.Count.Should().Be(expected.Count); + + for (int i = 0; i < value.Count; i++) + { + value[i].Title.Should().Be(expected[i].Title); + value[i].StartOffsetMs.Should().Be(expected[i].StartOffsetMs); + value[i].StartOffsetSec.Should().Be(expected[i].StartOffsetSec); + value[i].LengthMs.Should().Be(expected[i].LengthMs); + value[i].Chapters.Should().BeNull(); } } }