From 5867bb378f5f4078c7f86f71897d1fb193146e51 Mon Sep 17 00:00:00 2001
From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com>
Date: Thu, 25 Aug 2022 22:11:39 -0500
Subject: [PATCH] Implement new search algorithm
---
.../TestAudioFingerprinting.cs | 2 +-
.../Data/Intro.cs | 7 ++
.../ScheduledTasks/AnalyzeEpisodesTask.cs | 106 +++++++++++-------
3 files changed, 76 insertions(+), 39 deletions(-)
diff --git a/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestAudioFingerprinting.cs b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestAudioFingerprinting.cs
index c6e2570..dbd4fab 100644
--- a/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestAudioFingerprinting.cs
+++ b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestAudioFingerprinting.cs
@@ -90,7 +90,7 @@ public class TestAudioFingerprinting
var lhsEpisode = queueEpisode("audio/big_buck_bunny_intro.mp3");
var rhsEpisode = queueEpisode("audio/big_buck_bunny_clip.mp3");
- var (lhs, rhs) = task.FingerprintEpisodes(lhsEpisode, rhsEpisode);
+ var (lhs, rhs) = task.CompareEpisodes(lhsEpisode, rhsEpisode);
Assert.True(lhs.Valid);
Assert.Equal(0, lhs.IntroStart);
diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Data/Intro.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Data/Intro.cs
index 0875b80..4cd88e5 100644
--- a/ConfusedPolarBear.Plugin.IntroSkipper/Data/Intro.cs
+++ b/ConfusedPolarBear.Plugin.IntroSkipper/Data/Intro.cs
@@ -1,4 +1,5 @@
using System;
+using System.Text.Json.Serialization;
namespace ConfusedPolarBear.Plugin.IntroSkipper;
@@ -49,6 +50,12 @@ public class Intro
///
public bool Valid => IntroEnd > 0;
+ ///
+ /// Gets the duration of this intro.
+ ///
+ [JsonIgnore]
+ public double Duration => IntroEnd - IntroStart;
+
///
/// Gets or sets the introduction sequence start time.
///
diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/AnalyzeEpisodesTask.cs b/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/AnalyzeEpisodesTask.cs
index c66b5e9..d66cae7 100644
--- a/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/AnalyzeEpisodesTask.cs
+++ b/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/AnalyzeEpisodesTask.cs
@@ -161,7 +161,7 @@ public class AnalyzeEpisodesTask : IScheduledTask
{
// Increment totalProcessed by the number of episodes in this season that were actually analyzed
// (instead of just using the number of episodes in the current season).
- var analyzed = AnalyzeSeason(season, cancellationToken);
+ var analyzed = AnalyzeSeason(season.Value, cancellationToken);
Interlocked.Add(ref totalProcessed, analyzed);
writeEdl = analyzed > 0 || Plugin.Instance!.Configuration.RegenerateEdlFiles;
}
@@ -241,66 +241,94 @@ public class AnalyzeEpisodesTask : IScheduledTask
return previous;
}
+ // TODO: restore warning
+#pragma warning disable CA1002
+
///
/// Fingerprints all episodes in the provided season and stores the timestamps of all introductions.
///
- /// Pairing of season GUID to a list of QueuedEpisode objects.
+ /// Episodes in this season.
/// Cancellation token provided by the scheduled task.
/// Number of episodes from the provided season that were analyzed.
private int AnalyzeSeason(
- KeyValuePair> season,
+ List episodes,
CancellationToken cancellationToken)
{
var seasonIntros = new Dictionary();
- var episodes = season.Value;
- var first = episodes[0];
/* Don't analyze specials or seasons with an insufficient number of episodes.
* A season with only 1 episode can't be analyzed as it would compare the episode to itself,
* which would result in the entire episode being marked as an introduction, as the audio is identical.
*/
- if (season.Value.Count < 2 || first.SeasonNumber == 0)
+ if (episodes.Count < 2 || episodes[0].SeasonNumber == 0)
{
return episodes.Count;
}
+ var first = episodes[0];
+
_logger.LogInformation(
"Analyzing {Count} episodes from {Name} season {Season}",
- season.Value.Count,
+ episodes.Count,
first.SeriesName,
first.SeasonNumber);
- // Ensure there are an even number of episodes
- if (episodes.Count % 2 != 0)
+ // TODO: cache fingerprints and inverted indexes
+
+ // TODO: implementing bucketing
+
+ // For all episodes
+ foreach (var outer in episodes)
{
- episodes.Add(episodes[episodes.Count - 2]);
+ // Compare the outer episode to all other episodes
+ foreach (var inner in episodes)
+ {
+ // Don't compare the episode to itself
+ if (outer.EpisodeId == inner.EpisodeId)
+ {
+ continue;
+ }
+
+ // Fingerprint both episodes
+ Intro outerIntro;
+ Intro innerIntro;
+
+ try
+ {
+ (outerIntro, innerIntro) = CompareEpisodes(outer, inner);
+ }
+ catch (FingerprintException ex)
+ {
+ // TODO: remove the episode that threw the error from additional processing
+ _logger.LogWarning("Caught fingerprint error: {Ex}", ex);
+ continue;
+ }
+
+ if (!outerIntro.Valid)
+ {
+ continue;
+ }
+
+ // Save this intro if:
+ // - it is the first one we've seen for this episode
+ // - OR it is longer than the previous one
+ if (
+ !seasonIntros.TryGetValue(outer.EpisodeId, out var currentOuterIntro) ||
+ outerIntro.Duration > currentOuterIntro.Duration)
+ {
+ seasonIntros[outer.EpisodeId] = outerIntro;
+ }
+
+ if (
+ !seasonIntros.TryGetValue(inner.EpisodeId, out var currentInnerIntro) ||
+ innerIntro.Duration > currentInnerIntro.Duration)
+ {
+ seasonIntros[inner.EpisodeId] = innerIntro;
+ }
+ }
}
- // Analyze each pair of episodes in the current season
- for (var i = 0; i < episodes.Count; i += 2)
- {
- if (cancellationToken.IsCancellationRequested)
- {
- break;
- }
-
- var lhs = episodes[i];
- var rhs = episodes[i + 1];
-
- try
- {
- _logger.LogTrace("Analyzing {LHS} and {RHS}", lhs.Path, rhs.Path);
-
- var (lhsIntro, rhsIntro) = FingerprintEpisodes(lhs, rhs);
- seasonIntros[lhsIntro.EpisodeId] = lhsIntro;
- seasonIntros[rhsIntro.EpisodeId] = rhsIntro;
- analysisStatistics.TotalAnalyzedEpisodes.Add(2);
- }
- catch (FingerprintException ex)
- {
- _logger.LogError("Caught fingerprint error: {Ex}", ex);
- }
- }
+ // TODO: analysisStatistics.TotalAnalyzedEpisodes.Add(2);
// Ensure only one thread at a time can update the shared intro dictionary.
lock (_introsLock)
@@ -319,13 +347,15 @@ public class AnalyzeEpisodesTask : IScheduledTask
return episodes.Count;
}
+#pragma warning restore CA1002
+
///
/// Analyze two episodes to find an introduction sequence shared between them.
///
/// First episode to analyze.
/// Second episode to analyze.
/// Intros for the first and second episodes.
- public (Intro Lhs, Intro Rhs) FingerprintEpisodes(QueuedEpisode lhsEpisode, QueuedEpisode rhsEpisode)
+ public (Intro Lhs, Intro Rhs) CompareEpisodes(QueuedEpisode lhsEpisode, QueuedEpisode rhsEpisode)
{
var start = DateTime.Now;
var lhsFingerprint = Chromaprint.Fingerprint(lhsEpisode);
@@ -339,7 +369,7 @@ public class AnalyzeEpisodesTask : IScheduledTask
_fingerprintCache[rhsEpisode.EpisodeId] = rhsFingerprint;
}
- return FingerprintEpisodes(
+ return CompareEpisodes(
lhsEpisode.EpisodeId,
lhsFingerprint,
rhsEpisode.EpisodeId,
@@ -356,7 +386,7 @@ public class AnalyzeEpisodesTask : IScheduledTask
/// Second episode fingerprint points.
/// If this was called as part of the first analysis pass, add the elapsed time to the statistics.
/// Intros for the first and second episodes.
- public (Intro Lhs, Intro Rhs) FingerprintEpisodes(
+ public (Intro Lhs, Intro Rhs) CompareEpisodes(
Guid lhsId,
uint[] lhsPoints,
Guid rhsId,