Change fingerprint cache policy
This commit is contained in:
parent
5867bb378f
commit
e4da9342c8
@ -89,8 +89,14 @@ public class TestAudioFingerprinting
|
|||||||
|
|
||||||
var lhsEpisode = queueEpisode("audio/big_buck_bunny_intro.mp3");
|
var lhsEpisode = queueEpisode("audio/big_buck_bunny_intro.mp3");
|
||||||
var rhsEpisode = queueEpisode("audio/big_buck_bunny_clip.mp3");
|
var rhsEpisode = queueEpisode("audio/big_buck_bunny_clip.mp3");
|
||||||
|
var lhsFingerprint = Chromaprint.Fingerprint(lhsEpisode);
|
||||||
|
var rhsFingerprint = Chromaprint.Fingerprint(rhsEpisode);
|
||||||
|
|
||||||
var (lhs, rhs) = task.CompareEpisodes(lhsEpisode, rhsEpisode);
|
var (lhs, rhs) = task.CompareEpisodes(
|
||||||
|
lhsEpisode.EpisodeId,
|
||||||
|
lhsFingerprint,
|
||||||
|
rhsEpisode.EpisodeId,
|
||||||
|
rhsFingerprint);
|
||||||
|
|
||||||
Assert.True(lhs.Valid);
|
Assert.True(lhs.Valid);
|
||||||
Assert.Equal(0, lhs.IntroStart);
|
Assert.Equal(0, lhs.IntroStart);
|
||||||
@ -111,10 +117,11 @@ public class TestAudioFingerprinting
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FactSkipFFmpegTests : FactAttribute {
|
public class FactSkipFFmpegTests : FactAttribute
|
||||||
#if SKIP_FFMPEG_TESTS
|
{
|
||||||
|
#if SKIP_FFMPEG_TESTS
|
||||||
public FactSkipFFmpegTests() {
|
public FactSkipFFmpegTests() {
|
||||||
Skip = "SKIP_FFMPEG_TESTS defined, skipping unit tests that require FFmpeg to be installed";
|
Skip = "SKIP_FFMPEG_TESTS defined, skipping unit tests that require FFmpeg to be installed";
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -36,22 +36,11 @@ public class AnalyzeEpisodesTask : IScheduledTask
|
|||||||
|
|
||||||
private readonly ILibraryManager? _libraryManager;
|
private readonly ILibraryManager? _libraryManager;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Lock which guards the fingerprint cache dictionary.
|
|
||||||
/// </summary>
|
|
||||||
private readonly object _fingerprintCacheLock = new object();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lock which guards the shared dictionary of intros.
|
/// Lock which guards the shared dictionary of intros.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly object _introsLock = new object();
|
private readonly object _introsLock = new object();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Temporary fingerprint cache to speed up reanalysis.
|
|
||||||
/// Fingerprints are removed from this after a season is analyzed.
|
|
||||||
/// </summary>
|
|
||||||
private Dictionary<Guid, uint[]> _fingerprintCache;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Statistics for the currently running analysis task.
|
/// Statistics for the currently running analysis task.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -81,8 +70,6 @@ public class AnalyzeEpisodesTask : IScheduledTask
|
|||||||
_logger = loggerFactory.CreateLogger<AnalyzeEpisodesTask>();
|
_logger = loggerFactory.CreateLogger<AnalyzeEpisodesTask>();
|
||||||
_queueLogger = loggerFactory.CreateLogger<QueueManager>();
|
_queueLogger = loggerFactory.CreateLogger<QueueManager>();
|
||||||
|
|
||||||
_fingerprintCache = new Dictionary<Guid, uint[]>();
|
|
||||||
|
|
||||||
EdlManager.Initialize(_logger);
|
EdlManager.Initialize(_logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,15 +169,6 @@ public class AnalyzeEpisodesTask : IScheduledTask
|
|||||||
ex);
|
ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear this season's episodes from the temporary fingerprint cache.
|
|
||||||
lock (_fingerprintCacheLock)
|
|
||||||
{
|
|
||||||
foreach (var ep in season.Value)
|
|
||||||
{
|
|
||||||
_fingerprintCache.Remove(ep.EpisodeId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (writeEdl && Plugin.Instance!.Configuration.EdlAction != EdlAction.None)
|
if (writeEdl && Plugin.Instance!.Configuration.EdlAction != EdlAction.None)
|
||||||
{
|
{
|
||||||
EdlManager.UpdateEDLFiles(season.Value.AsReadOnly());
|
EdlManager.UpdateEDLFiles(season.Value.AsReadOnly());
|
||||||
@ -255,6 +233,7 @@ public class AnalyzeEpisodesTask : IScheduledTask
|
|||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var seasonIntros = new Dictionary<Guid, Intro>();
|
var seasonIntros = new Dictionary<Guid, Intro>();
|
||||||
|
var fingerprintCache = new Dictionary<Guid, uint[]>();
|
||||||
|
|
||||||
/* Don't analyze specials or seasons with an insufficient number of episodes.
|
/* 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,
|
* A season with only 1 episode can't be analyzed as it would compare the episode to itself,
|
||||||
@ -273,7 +252,23 @@ public class AnalyzeEpisodesTask : IScheduledTask
|
|||||||
first.SeriesName,
|
first.SeriesName,
|
||||||
first.SeasonNumber);
|
first.SeasonNumber);
|
||||||
|
|
||||||
// TODO: cache fingerprints and inverted indexes
|
// Cache all fingerprints
|
||||||
|
foreach (var episode in episodes)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fingerprintCache[episode.EpisodeId] = Chromaprint.Fingerprint(episode);
|
||||||
|
}
|
||||||
|
catch (FingerprintException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Caught fingerprint error: {Ex}", ex);
|
||||||
|
|
||||||
|
// fallback to an empty fingerprint
|
||||||
|
fingerprintCache[episode.EpisodeId] = Array.Empty<uint>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: cache inverted indexes
|
||||||
|
|
||||||
// TODO: implementing bucketing
|
// TODO: implementing bucketing
|
||||||
|
|
||||||
@ -293,16 +288,11 @@ public class AnalyzeEpisodesTask : IScheduledTask
|
|||||||
Intro outerIntro;
|
Intro outerIntro;
|
||||||
Intro innerIntro;
|
Intro innerIntro;
|
||||||
|
|
||||||
try
|
(outerIntro, innerIntro) = CompareEpisodes(
|
||||||
{
|
outer.EpisodeId,
|
||||||
(outerIntro, innerIntro) = CompareEpisodes(outer, inner);
|
fingerprintCache[outer.EpisodeId],
|
||||||
}
|
inner.EpisodeId,
|
||||||
catch (FingerprintException ex)
|
fingerprintCache[inner.EpisodeId]);
|
||||||
{
|
|
||||||
// TODO: remove the episode that threw the error from additional processing
|
|
||||||
_logger.LogWarning("Caught fingerprint error: {Ex}", ex);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!outerIntro.Valid)
|
if (!outerIntro.Valid)
|
||||||
{
|
{
|
||||||
@ -349,34 +339,6 @@ public class AnalyzeEpisodesTask : IScheduledTask
|
|||||||
|
|
||||||
#pragma warning restore CA1002
|
#pragma warning restore CA1002
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Analyze two episodes to find an introduction sequence shared between them.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="lhsEpisode">First episode to analyze.</param>
|
|
||||||
/// <param name="rhsEpisode">Second episode to analyze.</param>
|
|
||||||
/// <returns>Intros for the first and second episodes.</returns>
|
|
||||||
public (Intro Lhs, Intro Rhs) CompareEpisodes(QueuedEpisode lhsEpisode, QueuedEpisode rhsEpisode)
|
|
||||||
{
|
|
||||||
var start = DateTime.Now;
|
|
||||||
var lhsFingerprint = Chromaprint.Fingerprint(lhsEpisode);
|
|
||||||
var rhsFingerprint = Chromaprint.Fingerprint(rhsEpisode);
|
|
||||||
analysisStatistics.FingerprintCPUTime.AddDuration(start);
|
|
||||||
|
|
||||||
// Cache the fingerprints for quicker recall in the second pass (if one is needed).
|
|
||||||
lock (_fingerprintCacheLock)
|
|
||||||
{
|
|
||||||
_fingerprintCache[lhsEpisode.EpisodeId] = lhsFingerprint;
|
|
||||||
_fingerprintCache[rhsEpisode.EpisodeId] = rhsFingerprint;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CompareEpisodes(
|
|
||||||
lhsEpisode.EpisodeId,
|
|
||||||
lhsFingerprint,
|
|
||||||
rhsEpisode.EpisodeId,
|
|
||||||
rhsFingerprint,
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Analyze two episodes to find an introduction sequence shared between them.
|
/// Analyze two episodes to find an introduction sequence shared between them.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -384,17 +346,14 @@ public class AnalyzeEpisodesTask : IScheduledTask
|
|||||||
/// <param name="lhsPoints">First episode fingerprint points.</param>
|
/// <param name="lhsPoints">First episode fingerprint points.</param>
|
||||||
/// <param name="rhsId">Second episode id.</param>
|
/// <param name="rhsId">Second episode id.</param>
|
||||||
/// <param name="rhsPoints">Second episode fingerprint points.</param>
|
/// <param name="rhsPoints">Second episode fingerprint points.</param>
|
||||||
/// <param name="isFirstPass">If this was called as part of the first analysis pass, add the elapsed time to the statistics.</param>
|
|
||||||
/// <returns>Intros for the first and second episodes.</returns>
|
/// <returns>Intros for the first and second episodes.</returns>
|
||||||
public (Intro Lhs, Intro Rhs) CompareEpisodes(
|
public (Intro Lhs, Intro Rhs) CompareEpisodes(
|
||||||
Guid lhsId,
|
Guid lhsId,
|
||||||
uint[] lhsPoints,
|
uint[] lhsPoints,
|
||||||
Guid rhsId,
|
Guid rhsId,
|
||||||
uint[] rhsPoints,
|
uint[] rhsPoints)
|
||||||
bool isFirstPass)
|
|
||||||
{
|
{
|
||||||
// If this isn't running as part of the first analysis pass, don't count this CPU time as first pass time.
|
var start = DateTime.Now;
|
||||||
var start = isFirstPass ? DateTime.Now : DateTime.MinValue;
|
|
||||||
|
|
||||||
// Creates an inverted fingerprint point index for both episodes.
|
// Creates an inverted fingerprint point index for both episodes.
|
||||||
// For every point which is a 100% match, search for an introduction at that point.
|
// For every point which is a 100% match, search for an introduction at that point.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user