Unit test time ranges and audio fingerprinting

This commit is contained in:
ConfusedPolarBear 2022-05-09 03:31:10 -05:00
parent 61932fcf89
commit 928f467871
9 changed files with 169 additions and 5 deletions

View File

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ConfusedPolarBear.Plugin.IntroSkipper\ConfusedPolarBear.Plugin.IntroSkipper.csproj" />
</ItemGroup>
</Project>

View File

@ -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<FingerprinterTask>(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
};
}
}

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfusedPolarBear.Plugin.IntroSkipper", "ConfusedPolarBear.Plugin.IntroSkipper\ConfusedPolarBear.Plugin.IntroSkipper.csproj", "{D921B930-CF91-406F-ACBC-08914DCD0D34}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{D921B930-CF91-406F-ACBC-08914DCD0D34}.Release|Any CPU.Build.0 = 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 EndGlobalSection
EndGlobal EndGlobal

View File

@ -115,7 +115,7 @@ public static class FPCalc {
fingerprint = new List<uint>().AsReadOnly(); fingerprint = new List<uint>().AsReadOnly();
// If fingerprint caching isn't enabled, don't try to load anything. // 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; return false;
} }
@ -151,7 +151,7 @@ public static class FPCalc {
private static void cacheFingerprint(QueuedEpisode episode, List<uint> fingerprint) private static void cacheFingerprint(QueuedEpisode episode, List<uint> fingerprint)
{ {
// Bail out if caching isn't enabled. // Bail out if caching isn't enabled.
if (!Plugin.Instance!.Configuration.CacheFingerprints) if (!(Plugin.Instance?.Configuration.CacheFingerprints ?? false))
{ {
return; return;
} }

View File

@ -34,6 +34,11 @@ public class FingerprinterTask : IScheduledTask {
/// </summary> /// </summary>
private const double SAMPLES_TO_SECONDS = 0.128; private const double SAMPLES_TO_SECONDS = 0.128;
/// <summary>
/// Gets or sets the last detected intro sequence. Only populated when a unit test is running.
/// </summary>
public static Intro LastIntro { get; private set; } = new Intro();
/// <summary> /// <summary>
/// Constructor. /// Constructor.
/// </summary> /// </summary>
@ -173,7 +178,7 @@ public class FingerprinterTask : IScheduledTask {
/// <param name="lhsEpisode">First episode to analyze.</param> /// <param name="lhsEpisode">First episode to analyze.</param>
/// <param name="rhsEpisode">Second episode to analyze.</param> /// <param name="rhsEpisode">Second episode to analyze.</param>
/// <returns>true if an intro was found in both episodes, otherwise false.</returns> /// <returns>true if an intro was found in both episodes, otherwise false.</returns>
private bool FingerprintEpisodes(QueuedEpisode lhsEpisode, QueuedEpisode rhsEpisode) public bool FingerprintEpisodes(QueuedEpisode lhsEpisode, QueuedEpisode rhsEpisode)
{ {
var lhs = FPCalc.Fingerprint(lhsEpisode); var lhs = FPCalc.Fingerprint(lhsEpisode);
var rhs = FPCalc.Fingerprint(rhsEpisode); var rhs = FPCalc.Fingerprint(rhsEpisode);
@ -353,13 +358,21 @@ public class FingerprinterTask : IScheduledTask {
private static void storeIntro(Guid episode, double introStart, double introEnd) private static void storeIntro(Guid episode, double introStart, double introEnd)
{ {
Plugin.Instance!.Intros[episode] = new Intro() var intro = new Intro()
{ {
EpisodeId = episode, EpisodeId = episode,
Valid = introEnd > 0, Valid = introEnd > 0,
IntroStart = introStart, IntroStart = introStart,
IntroEnd = introEnd IntroEnd = introEnd
}; };
if (Plugin.Instance is null)
{
LastIntro = intro;
return;
}
Plugin.Instance.Intros[episode] = intro;
} }
private static int countBits(uint number) { private static int countBits(uint number) {