diff --git a/ConfusedPolarBear.Plugin.IntroSkipper.Tests/ConfusedPolarBear.Plugin.IntroSkipper.Tests.csproj b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/ConfusedPolarBear.Plugin.IntroSkipper.Tests.csproj new file mode 100644 index 0000000..59d33f8 --- /dev/null +++ b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/ConfusedPolarBear.Plugin.IntroSkipper.Tests.csproj @@ -0,0 +1,27 @@ + + + + net6.0 + enable + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestAudioFingerprinting.cs b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestAudioFingerprinting.cs new file mode 100644 index 0000000..a51542a --- /dev/null +++ b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestAudioFingerprinting.cs @@ -0,0 +1,74 @@ +using Xunit; +using Microsoft.Extensions.Logging; + +namespace ConfusedPolarBear.Plugin.IntroSkipper.Tests; + +public class TestFPCalc +{ + [Fact] + public void TestInstallationCheck() + { + Assert.True(FPCalc.CheckFPCalcInstalled()); + } + + [Fact] + public void TestFingerprinting() + { + // Generated with `fpcalc -raw audio/big_buck_bunny_intro.mp3` + var expected = new uint[]{ + 3269995649, 3261610160, 3257403872, 1109989680, 1109993760, 1110010656, 1110142768, 1110175504, + 1110109952, 1126874880, 2788611, 2787586, 6981634, 15304754, 28891170, 43579426, 43542561, + 47737888, 41608640, 40559296, 36352644, 53117572, 2851460, 1076465548, 1080662428, 1080662492, + 1089182044, 1148041501, 1148037422, 3291343918, 3290980398, 3429367854, 3437756714, 3433698090, + 3433706282, 3366600490, 3366464314, 2296916250, 3362269210, 3362265115, 3362266441, 3370784472, + 3366605480, 1218990776, 1223217816, 1231602328, 1260950200, 1245491640, 169845176, 1510908120, + 1510911000, 2114365528, 2114370008, 1996929688, 1996921480, 1897171592, 1884588680, 1347470984, + 1343427226, 1345467054, 1349657318, 1348673570, 1356869666, 1356865570, 295837698, 60957698, + 44194818, 48416770, 40011778, 36944210, 303147954, 369146786, 1463847842, 1434488738, 1417709474, + 1417713570, 3699441634, 3712167202, 3741460534, 2585144342, 2597725238, 2596200487, 2595926077, + 2595984141, 2594734600, 2594736648, 2598931176, 2586348264, 2586348264, 2586561257, 2586451659, + 2603225802, 2603225930, 2573860970, 2561151018, 3634901034, 3634896954, 3651674122, 3416793162, + 3416816715, 3404331257, 3395844345, 3395836155, 3408464089, 3374975369, 1282036360, 1290457736, + 1290400440, 1290314408, 1281925800, 1277727404, 1277792932, 1278785460, 1561962388, 1426698196, + 3607924711, 4131892839, 4140215815, 4292259591, 3218515717, 3209938229, 3171964197, 3171956013, + 4229117295, 4229312879, 4242407935, 4238016959, 4239987133, 4239990013, 3703060732, 1547188252, + 1278748677, 1278748935, 1144662786, 1148854786, 1090388802, 1090388962, 1086260130, 1085940098, + 1102709122, 45811586, 44634002, 44596656, 44592544, 1122527648, 1109944736, 1109977504, 1111030243, + 1111017762, 1109969186, 1126721826, 1101556002, 1084844322, 1084979506, 1084914450, 1084914449, + 1084873520, 3228093296, 3224996817, 3225062275, 3241840002, 3346701698, 3349843394, 3349782306, + 3349719842, 3353914146, 3328748322, 3328747810, 3328809266, 3471476754, 3472530451, 3472473123, + 3472417825, 3395841056, 3458735136, 3341420624, 1076496560, 1076501168, 1076501136, 1076497024 + }; + + var actual = FPCalc.Fingerprint(queueEpisode("audio/big_buck_bunny_intro.mp3")); + + Assert.Equal(expected, actual); + } + + [Fact] + public void TestIntroDetection() + { + var logger = new Logger(new LoggerFactory()); + var task = new FingerprinterTask(logger); + + var lhs = queueEpisode("audio/big_buck_bunny_intro.mp3"); + var rhs = queueEpisode("audio/big_buck_bunny_clip.mp3"); + + var result = task.FingerprintEpisodes(lhs, rhs); + var actual = FingerprinterTask.LastIntro; + + Assert.True(result); + Assert.True(actual.Valid); + Assert.Equal(5.12, actual.IntroStart); + Assert.Equal(22.912, actual.IntroEnd); + } + + private QueuedEpisode queueEpisode(string path) + { + return new QueuedEpisode() + { + Path = "../../../" + path, + FingerprintDuration = 60 + }; + } +} diff --git a/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestContiguous.cs b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestContiguous.cs new file mode 100644 index 0000000..c27076a --- /dev/null +++ b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestContiguous.cs @@ -0,0 +1,36 @@ +using Xunit; + +namespace ConfusedPolarBear.Plugin.IntroSkipper.Tests; + +public class TestTimeRanges +{ + [Fact] + public void TestSmallRange() + { + var times = new double[]{ + 1, 1.5, 2, 2.5, 3, 3.5, 4, + 100, 100.5, 101, 101.5 + }; + + var expected = new TimeRange(1, 4); + var actual = TimeRangeHelpers.FindContiguous(times, 3.25); + + Assert.Equal(expected, actual); + } + + [Fact] + public void TestLargeRange() + { + var times = new double[]{ + 1, 1.5, 2, + 2.8, 2.9, 2.995, 3.0, 3.01, 3.02, 3.4, 3.45, 3.48, 3.7, 3.77, 3.78, 3.781, 3.782, 3.789, 3.85, + 4.5, 5.3122, 5.3123, 5.3124, 5.3125, 5.3126, 5.3127, 5.3128, + 55, 55.5, 55.6, 55.7 + }; + + var expected = new TimeRange(1, 5.3128); + var actual = TimeRangeHelpers.FindContiguous(times, 3.25); + + Assert.Equal(expected, actual); + } +} diff --git a/ConfusedPolarBear.Plugin.IntroSkipper.Tests/audio/README.txt b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/audio/README.txt new file mode 100644 index 0000000..095d013 --- /dev/null +++ b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/audio/README.txt @@ -0,0 +1,7 @@ +The audio used in the fingerprinting unit tests is from Big Buck Bunny, attributed below. + +Both big_buck_bunny_intro.mp3 and big_buck_bunny_clip.mp3 are derived from Big Buck Bunny, (c) copyright 2008, Blender Foundation / www.bigbuckbunny.org. They are used under the Creative Commons Attribution 3.0 and the original source can be found at https://www.youtube.com/watch?v=YE7VzlLtp-4. + +Both files have been downmixed to two audio channels. +big_buck_bunny_intro.mp3 is from 5 to 30 seconds. +big_buck_bunny_clip.mp3 is from 0 to 60 seconds. diff --git a/ConfusedPolarBear.Plugin.IntroSkipper.Tests/audio/big_buck_bunny_clip.mp3 b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/audio/big_buck_bunny_clip.mp3 new file mode 100644 index 0000000..d7132d0 Binary files /dev/null and b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/audio/big_buck_bunny_clip.mp3 differ diff --git a/ConfusedPolarBear.Plugin.IntroSkipper.Tests/audio/big_buck_bunny_intro.mp3 b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/audio/big_buck_bunny_intro.mp3 new file mode 100644 index 0000000..95ecf7b Binary files /dev/null and b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/audio/big_buck_bunny_intro.mp3 differ diff --git a/ConfusedPolarBear.Plugin.IntroSkipper.sln b/ConfusedPolarBear.Plugin.IntroSkipper.sln index d0b6462..394a21f 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper.sln +++ b/ConfusedPolarBear.Plugin.IntroSkipper.sln @@ -1,6 +1,9 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfusedPolarBear.Plugin.IntroSkipper", "ConfusedPolarBear.Plugin.IntroSkipper\ConfusedPolarBear.Plugin.IntroSkipper.csproj", "{D921B930-CF91-406F-ACBC-08914DCD0D34}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfusedPolarBear.Plugin.IntroSkipper.Tests", "ConfusedPolarBear.Plugin.IntroSkipper.Tests\ConfusedPolarBear.Plugin.IntroSkipper.Tests.csproj", "{9E30DA42-983E-46E0-A3BF-A2BA56FE9718}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -11,5 +14,9 @@ Global {D921B930-CF91-406F-ACBC-08914DCD0D34}.Debug|Any CPU.Build.0 = Debug|Any CPU {D921B930-CF91-406F-ACBC-08914DCD0D34}.Release|Any CPU.ActiveCfg = Release|Any CPU {D921B930-CF91-406F-ACBC-08914DCD0D34}.Release|Any CPU.Build.0 = Release|Any CPU + {9E30DA42-983E-46E0-A3BF-A2BA56FE9718}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E30DA42-983E-46E0-A3BF-A2BA56FE9718}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E30DA42-983E-46E0-A3BF-A2BA56FE9718}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E30DA42-983E-46E0-A3BF-A2BA56FE9718}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/FPCalc.cs b/ConfusedPolarBear.Plugin.IntroSkipper/FPCalc.cs index 34fc4f1..b69034e 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/FPCalc.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/FPCalc.cs @@ -115,7 +115,7 @@ public static class FPCalc { fingerprint = new List().AsReadOnly(); // If fingerprint caching isn't enabled, don't try to load anything. - if (!Plugin.Instance!.Configuration.CacheFingerprints) + if (!(Plugin.Instance?.Configuration.CacheFingerprints ?? false)) { return false; } @@ -151,7 +151,7 @@ public static class FPCalc { private static void cacheFingerprint(QueuedEpisode episode, List fingerprint) { // Bail out if caching isn't enabled. - if (!Plugin.Instance!.Configuration.CacheFingerprints) + if (!(Plugin.Instance?.Configuration.CacheFingerprints ?? false)) { return; } diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/FingerprinterTask.cs b/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/FingerprinterTask.cs index 6b5804f..80cdea3 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/FingerprinterTask.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/FingerprinterTask.cs @@ -34,6 +34,11 @@ public class FingerprinterTask : IScheduledTask { /// private const double SAMPLES_TO_SECONDS = 0.128; + /// + /// Gets or sets the last detected intro sequence. Only populated when a unit test is running. + /// + public static Intro LastIntro { get; private set; } = new Intro(); + /// /// Constructor. /// @@ -173,7 +178,7 @@ public class FingerprinterTask : IScheduledTask { /// First episode to analyze. /// Second episode to analyze. /// true if an intro was found in both episodes, otherwise false. - private bool FingerprintEpisodes(QueuedEpisode lhsEpisode, QueuedEpisode rhsEpisode) + public bool FingerprintEpisodes(QueuedEpisode lhsEpisode, QueuedEpisode rhsEpisode) { var lhs = FPCalc.Fingerprint(lhsEpisode); var rhs = FPCalc.Fingerprint(rhsEpisode); @@ -353,13 +358,21 @@ public class FingerprinterTask : IScheduledTask { private static void storeIntro(Guid episode, double introStart, double introEnd) { - Plugin.Instance!.Intros[episode] = new Intro() + var intro = new Intro() { EpisodeId = episode, Valid = introEnd > 0, IntroStart = introStart, IntroEnd = introEnd }; + + if (Plugin.Instance is null) + { + LastIntro = intro; + return; + } + + Plugin.Instance.Intros[episode] = intro; } private static int countBits(uint number) {