Add option to merge Opening/End Credits with following/preceding chapters

This commit is contained in:
Michael Bucari-Tovo 2022-06-24 23:26:52 -06:00
parent 1b20bb06ad
commit d58092968a
5 changed files with 280 additions and 123 deletions

View File

@ -166,6 +166,9 @@ namespace FileLiberator
}; };
var chapters = flattenChapters(contentLic.ContentMetadata.ChapterInfo.Chapters).OrderBy(c => c.StartOffsetMs).ToList(); var chapters = flattenChapters(contentLic.ContentMetadata.ChapterInfo.Chapters).OrderBy(c => c.StartOffsetMs).ToList();
if (config.MergeOpeningAndEndCredits)
combineCredits(chapters);
if (config.AllowLibationFixup || outputFormat == OutputFormat.Mp3) if (config.AllowLibationFixup || outputFormat == OutputFormat.Mp3)
{ {
@ -192,7 +195,7 @@ namespace FileLiberator
return dlOptions; return dlOptions;
} }
public static List<AudibleApi.Common.Chapter> flattenChapters(IEnumerable<AudibleApi.Common.Chapter> chapters, string titleConcat = ": ") public static List<AudibleApi.Common.Chapter> flattenChapters(IList<AudibleApi.Common.Chapter> chapters, string titleConcat = ": ")
{ {
List<AudibleApi.Common.Chapter> chaps = new(); List<AudibleApi.Common.Chapter> chaps = new();
@ -217,6 +220,22 @@ namespace FileLiberator
return chaps; return chaps;
} }
public static void combineCredits(IList<AudibleApi.Common.Chapter> 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) private static void downloadValidation(LibraryBook libraryBook)
{ {
string errorString(string field) string errorString(string field)

View File

@ -117,6 +117,13 @@ namespace LibationFileManager
set => persistentDictionary.SetNonString(nameof(SplitFilesByChapter), value); set => persistentDictionary.SetNonString(nameof(SplitFilesByChapter), value);
} }
[Description("Merge Opening/End Credits into the following/preceding chapters")]
public bool MergeOpeningAndEndCredits
{
get => persistentDictionary.GetNonString<bool>(nameof(MergeOpeningAndEndCredits));
set => persistentDictionary.SetNonString(nameof(MergeOpeningAndEndCredits), value);
}
[Description("Strip \"(Unabridged)\" from audiobook metadata tags")] [Description("Strip \"(Unabridged)\" from audiobook metadata tags")]
public bool StripUnabridged public bool StripUnabridged
{ {

View File

@ -13,6 +13,7 @@ namespace LibationWinForms.Dialogs
this.downloadCoverArtCbox.Text = desc(nameof(config.DownloadCoverArt)); this.downloadCoverArtCbox.Text = desc(nameof(config.DownloadCoverArt));
this.retainAaxFileCbox.Text = desc(nameof(config.RetainAaxFile)); this.retainAaxFileCbox.Text = desc(nameof(config.RetainAaxFile));
this.splitFilesByChapterCbox.Text = desc(nameof(config.SplitFilesByChapter)); this.splitFilesByChapterCbox.Text = desc(nameof(config.SplitFilesByChapter));
this.mergeOpeningEndCreditsCbox.Text = desc(nameof(config.MergeOpeningAndEndCredits));
this.stripAudibleBrandingCbox.Text = desc(nameof(config.StripAudibleBrandAudio)); this.stripAudibleBrandingCbox.Text = desc(nameof(config.StripAudibleBrandAudio));
this.stripUnabridgedCbox.Text = desc(nameof(config.StripUnabridged)); this.stripUnabridgedCbox.Text = desc(nameof(config.StripUnabridged));
@ -21,6 +22,7 @@ namespace LibationWinForms.Dialogs
downloadCoverArtCbox.Checked = config.DownloadCoverArt; downloadCoverArtCbox.Checked = config.DownloadCoverArt;
retainAaxFileCbox.Checked = config.RetainAaxFile; retainAaxFileCbox.Checked = config.RetainAaxFile;
splitFilesByChapterCbox.Checked = config.SplitFilesByChapter; splitFilesByChapterCbox.Checked = config.SplitFilesByChapter;
mergeOpeningEndCreditsCbox.Checked = config.MergeOpeningAndEndCredits;
stripUnabridgedCbox.Checked = config.StripUnabridged; stripUnabridgedCbox.Checked = config.StripUnabridged;
stripAudibleBrandingCbox.Checked = config.StripAudibleBrandAudio; stripAudibleBrandingCbox.Checked = config.StripAudibleBrandAudio;
convertLosslessRb.Checked = !config.DecryptToLossy; convertLosslessRb.Checked = !config.DecryptToLossy;
@ -50,6 +52,7 @@ namespace LibationWinForms.Dialogs
config.DownloadCoverArt = downloadCoverArtCbox.Checked; config.DownloadCoverArt = downloadCoverArtCbox.Checked;
config.RetainAaxFile = retainAaxFileCbox.Checked; config.RetainAaxFile = retainAaxFileCbox.Checked;
config.SplitFilesByChapter = splitFilesByChapterCbox.Checked; config.SplitFilesByChapter = splitFilesByChapterCbox.Checked;
config.MergeOpeningAndEndCredits = mergeOpeningEndCreditsCbox.Checked;
config.StripUnabridged = stripUnabridgedCbox.Checked; config.StripUnabridged = stripUnabridgedCbox.Checked;
config.StripAudibleBrandAudio = stripAudibleBrandingCbox.Checked; config.StripAudibleBrandAudio = stripAudibleBrandingCbox.Checked;
config.DecryptToLossy = convertLossyRb.Checked; config.DecryptToLossy = convertLossyRb.Checked;

View File

@ -108,6 +108,7 @@
this.retainAaxFileCbox = new System.Windows.Forms.CheckBox(); this.retainAaxFileCbox = new System.Windows.Forms.CheckBox();
this.downloadCoverArtCbox = new System.Windows.Forms.CheckBox(); this.downloadCoverArtCbox = new System.Windows.Forms.CheckBox();
this.createCueSheetCbox = new System.Windows.Forms.CheckBox(); this.createCueSheetCbox = new System.Windows.Forms.CheckBox();
this.mergeOpeningEndCreditsCbox = new System.Windows.Forms.CheckBox();
this.badBookGb.SuspendLayout(); this.badBookGb.SuspendLayout();
this.tabControl.SuspendLayout(); this.tabControl.SuspendLayout();
this.tab1ImportantSettings.SuspendLayout(); this.tab1ImportantSettings.SuspendLayout();
@ -251,7 +252,7 @@
// stripAudibleBrandingCbox // stripAudibleBrandingCbox
// //
this.stripAudibleBrandingCbox.AutoSize = true; 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.Name = "stripAudibleBrandingCbox";
this.stripAudibleBrandingCbox.Size = new System.Drawing.Size(143, 34); this.stripAudibleBrandingCbox.Size = new System.Drawing.Size(143, 34);
this.stripAudibleBrandingCbox.TabIndex = 13; this.stripAudibleBrandingCbox.TabIndex = 13;
@ -285,7 +286,7 @@
// convertLossyRb // convertLossyRb
// //
this.convertLossyRb.AutoSize = true; 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.Name = "convertLossyRb";
this.convertLossyRb.Size = new System.Drawing.Size(329, 19); this.convertLossyRb.Size = new System.Drawing.Size(329, 19);
this.convertLossyRb.TabIndex = 12; this.convertLossyRb.TabIndex = 12;
@ -297,7 +298,7 @@
// //
this.convertLosslessRb.AutoSize = true; this.convertLosslessRb.AutoSize = true;
this.convertLosslessRb.Checked = 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.Name = "convertLosslessRb";
this.convertLosslessRb.Size = new System.Drawing.Size(335, 19); this.convertLosslessRb.Size = new System.Drawing.Size(335, 19);
this.convertLosslessRb.TabIndex = 11; this.convertLosslessRb.TabIndex = 11;
@ -608,6 +609,7 @@
this.tab4AudioFileOptions.Controls.Add(this.stripAudibleBrandingCbox); this.tab4AudioFileOptions.Controls.Add(this.stripAudibleBrandingCbox);
this.tab4AudioFileOptions.Controls.Add(this.convertLosslessRb); this.tab4AudioFileOptions.Controls.Add(this.convertLosslessRb);
this.tab4AudioFileOptions.Controls.Add(this.stripUnabridgedCbox); this.tab4AudioFileOptions.Controls.Add(this.stripUnabridgedCbox);
this.tab4AudioFileOptions.Controls.Add(this.mergeOpeningEndCreditsCbox);
this.tab4AudioFileOptions.Controls.Add(this.splitFilesByChapterCbox); this.tab4AudioFileOptions.Controls.Add(this.splitFilesByChapterCbox);
this.tab4AudioFileOptions.Controls.Add(this.retainAaxFileCbox); this.tab4AudioFileOptions.Controls.Add(this.retainAaxFileCbox);
this.tab4AudioFileOptions.Controls.Add(this.downloadCoverArtCbox); this.tab4AudioFileOptions.Controls.Add(this.downloadCoverArtCbox);
@ -980,7 +982,7 @@
// stripUnabridgedCbox // stripUnabridgedCbox
// //
this.stripUnabridgedCbox.AutoSize = true; 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.Name = "stripUnabridgedCbox";
this.stripUnabridgedCbox.Size = new System.Drawing.Size(147, 19); this.stripUnabridgedCbox.Size = new System.Drawing.Size(147, 19);
this.stripUnabridgedCbox.TabIndex = 13; this.stripUnabridgedCbox.TabIndex = 13;
@ -1024,6 +1026,17 @@
this.createCueSheetCbox.UseVisualStyleBackColor = true; this.createCueSheetCbox.UseVisualStyleBackColor = true;
this.createCueSheetCbox.CheckedChanged += new System.EventHandler(this.allowLibationFixupCbox_CheckedChanged); 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 // SettingsDialog
// //
this.AcceptButton = this.saveBtn; this.AcceptButton = this.saveBtn;
@ -1155,5 +1168,6 @@
private System.Windows.Forms.Button chapterTitleTemplateBtn; private System.Windows.Forms.Button chapterTitleTemplateBtn;
private System.Windows.Forms.TextBox chapterTitleTemplateTb; private System.Windows.Forms.TextBox chapterTitleTemplateTb;
private System.Windows.Forms.Button editCharreplacementBtn; private System.Windows.Forms.Button editCharreplacementBtn;
private System.Windows.Forms.CheckBox mergeOpeningEndCreditsCbox;
} }
} }

View File

@ -14,8 +14,142 @@ namespace FileLiberator.Tests
[TestClass] [TestClass]
public class DownloadDecryptBookTests 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] [TestMethod]
public void HierarchicalChapters_Flatten() public void Chapters_CombineCredits()
{ {
var expected = new Chapter[] var expected = new Chapter[]
{ {
@ -73,129 +207,109 @@ namespace FileLiberator.Tests
Title = "Book 2: Part 4: Chapter 8", Title = "Book 2: Part 4: Chapter 8",
StartOffsetMs = 700000, StartOffsetMs = 700000,
StartOffsetSec = 700, StartOffsetSec = 700,
LengthMs = 100000, LengthMs = 110000,
} }
}; };
var hierarchicalChapters = new Chapter[] var flatChapters = DownloadDecryptBook.flattenChapters(HierarchicalChapters);
{ DownloadDecryptBook.combineCredits(flatChapters);
new() checkChapters(flatChapters, expected);
{ }
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);
flatChapters.Count.Should().Be(expected.Length); [TestMethod]
public void HierarchicalChapters_Flatten()
for (int i = 0; i < flatChapters.Count; i++) {
var expected = new Chapter[]
{ {
flatChapters[i].Title.Should().Be(expected[i].Title); new()
flatChapters[i].StartOffsetMs.Should().Be(expected[i].StartOffsetMs); {
flatChapters[i].StartOffsetSec.Should().Be(expected[i].StartOffsetSec); Title = "Opening Credits",
flatChapters[i].LengthMs.Should().Be(expected[i].LengthMs); StartOffsetMs = 0,
flatChapters[i].Chapters.Should().BeNull(); 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<Chapter> value, IList<Chapter> 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();
} }
} }
} }