This commit is contained in:
rlauu 2024-09-25 17:23:25 +02:00
parent 5e08381ed5
commit 5beaf35198
8 changed files with 49 additions and 74 deletions

View File

@ -124,12 +124,12 @@ public class TestAudioFingerprinting
var expected = new TimeRange[] var expected = new TimeRange[]
{ {
new TimeRange(44.6310, 44.8072), new(44.6310, 44.8072),
new TimeRange(53.5905, 53.8070), new(53.5905, 53.8070),
new TimeRange(53.8458, 54.2024), new(53.8458, 54.2024),
new TimeRange(54.2611, 54.5935), new(54.2611, 54.5935),
new TimeRange(54.7098, 54.9293), new(54.7098, 54.9293),
new TimeRange(54.9294, 55.2590), new(54.9294, 55.2590),
}; };
var range = new TimeRange(0, 60); var range = new TimeRange(0, 60);

View File

@ -19,7 +19,7 @@ public class TestBlackFrames
expected.AddRange(CreateFrameSequence(5, 6)); expected.AddRange(CreateFrameSequence(5, 6));
expected.AddRange(CreateFrameSequence(8, 9.96)); expected.AddRange(CreateFrameSequence(8, 9.96));
var actual = FFmpegWrapper.DetectBlackFrames(queueFile("rainbow.mp4"), new(0, 10), 85); var actual = FFmpegWrapper.DetectBlackFrames(QueueFile("rainbow.mp4"), new(0, 10), 85);
for (var i = 0; i < expected.Count; i++) for (var i = 0; i < expected.Count; i++)
{ {
@ -37,7 +37,7 @@ public class TestBlackFrames
var analyzer = CreateBlackFrameAnalyzer(); var analyzer = CreateBlackFrameAnalyzer();
var episode = queueFile("credits.mp4"); var episode = QueueFile("credits.mp4");
episode.Duration = (int)new TimeSpan(0, 5, 30).TotalSeconds; episode.Duration = (int)new TimeSpan(0, 5, 30).TotalSeconds;
var result = analyzer.AnalyzeMediaFile(episode, 240, 30, 85); var result = analyzer.AnalyzeMediaFile(episode, 240, 30, 85);
@ -45,7 +45,7 @@ public class TestBlackFrames
Assert.InRange(result.Start, 300 - range, 300 + range); Assert.InRange(result.Start, 300 - range, 300 + range);
} }
private QueuedEpisode queueFile(string path) private static QueuedEpisode QueueFile(string path)
{ {
return new() return new()
{ {
@ -55,7 +55,7 @@ public class TestBlackFrames
}; };
} }
private BlackFrame[] CreateFrameSequence(double start, double end) private static BlackFrame[] CreateFrameSequence(double start, double end)
{ {
var frames = new List<BlackFrame>(); var frames = new List<BlackFrame>();
@ -64,10 +64,10 @@ public class TestBlackFrames
frames.Add(new(100, i)); frames.Add(new(100, i));
} }
return frames.ToArray(); return [.. frames];
} }
private BlackFrameAnalyzer CreateBlackFrameAnalyzer() private static BlackFrameAnalyzer CreateBlackFrameAnalyzer()
{ {
var logger = new LoggerFactory().CreateLogger<BlackFrameAnalyzer>(); var logger = new LoggerFactory().CreateLogger<BlackFrameAnalyzer>();
return new(logger); return new(logger);

View File

@ -74,7 +74,7 @@ public class TestChapterAnalyzer
/// <param name="name">Chapter name.</param> /// <param name="name">Chapter name.</param>
/// <param name="position">Chapter position (in seconds).</param> /// <param name="position">Chapter position (in seconds).</param>
/// <returns>ChapterInfo.</returns> /// <returns>ChapterInfo.</returns>
private ChapterInfo CreateChapter(string name, int position) private static ChapterInfo CreateChapter(string name, int position)
{ {
return new() return new()
{ {

View File

@ -38,7 +38,7 @@ public class TestEdl
Assert.Equal(edlPath, EdlManager.GetEdlPath(mediaPath)); Assert.Equal(edlPath, EdlManager.GetEdlPath(mediaPath));
} }
private Segment MakeIntro(double start, double end) private static Segment MakeIntro(double start, double end)
{ {
return new Segment(Guid.Empty, new TimeRange(start, end)); return new Segment(Guid.Empty, new TimeRange(start, end));
} }

View File

@ -250,7 +250,7 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
/// <param name="rhsId">Second episode id.</param> /// <param name="rhsId">Second episode id.</param>
/// <param name="rhsRanges">Second episode shared timecodes.</param> /// <param name="rhsRanges">Second episode shared timecodes.</param>
/// <returns>Intros for the first and second episodes.</returns> /// <returns>Intros for the first and second episodes.</returns>
private (Segment Lhs, Segment Rhs) GetLongestTimeRange( private static (Segment Lhs, Segment Rhs) GetLongestTimeRange(
Guid lhsId, Guid lhsId,
List<TimeRange> lhsRanges, List<TimeRange> lhsRanges,
Guid rhsId, Guid rhsId,

View File

@ -22,7 +22,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
/// <summary> /// <summary>
/// Intro skipper plugin. Uses audio analysis to find common sequences of audio shared between episodes. /// Intro skipper plugin. Uses audio analysis to find common sequences of audio shared between episodes.
/// </summary> /// </summary>
public partial class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
{ {
private readonly object _serializationLock = new(); private readonly object _serializationLock = new();
private readonly object _introsLock = new(); private readonly object _introsLock = new();
@ -488,7 +488,7 @@ public partial class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
// Inject a link to the script at the end of the <head> section. // Inject a link to the script at the end of the <head> section.
// A regex is used here to ensure the replacement is only done once. // A regex is used here to ensure the replacement is only done once.
Regex headEnd = HeadRegex(); Regex headEnd = new Regex(@"</head>", RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));
contents = headEnd.Replace(contents, scriptTag + "</head>", 1); contents = headEnd.Replace(contents, scriptTag + "</head>", 1);
// Write the modified file contents // Write the modified file contents
@ -496,7 +496,4 @@ public partial class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
_logger.LogInformation("Skip intro button successfully added"); _logger.LogInformation("Skip intro button successfully added");
} }
[GeneratedRegex("</head>", RegexOptions.IgnoreCase)]
private static partial Regex HeadRegex();
} }

View File

@ -57,7 +57,7 @@ public class BaseItemAnalyzerTask
public void AnalyzeItems( public void AnalyzeItems(
IProgress<double> progress, IProgress<double> progress,
CancellationToken cancellationToken, CancellationToken cancellationToken,
HashSet<Guid>? seasonsToAnalyze = null) IReadOnlyCollection<Guid>? seasonsToAnalyze = null)
{ {
var ffmpegValid = FFmpegWrapper.CheckFFmpegVersion(); var ffmpegValid = FFmpegWrapper.CheckFFmpegVersion();
// Assert that ffmpeg with chromaprint is installed // Assert that ffmpeg with chromaprint is installed
@ -74,20 +74,12 @@ public class BaseItemAnalyzerTask
var queue = queueManager.GetMediaItems(); var queue = queueManager.GetMediaItems();
// Filter the queue based on seasonsToAnalyze // Filter the queue based on seasonsToAnalyze
if (seasonsToAnalyze != null && seasonsToAnalyze.Count > 0) if (seasonsToAnalyze is { Count: > 0 })
{ {
queue = queue.Where(kvp => seasonsToAnalyze.Contains(kvp.Key)) queue = queue.Where(kvp => seasonsToAnalyze.Contains(kvp.Key)).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value).AsReadOnly();
} }
var totalQueued = 0; int totalQueued = queue.Sum(kvp => kvp.Value.Count) * _analysisModes.Count;
foreach (var kvp in queue)
{
totalQueued += kvp.Value.Count;
}
totalQueued *= _analysisModes.Count;
if (totalQueued == 0) if (totalQueued == 0)
{ {
throw new FingerprintException( throw new FingerprintException(
@ -100,7 +92,6 @@ public class BaseItemAnalyzerTask
} }
var totalProcessed = 0; var totalProcessed = 0;
var modeCount = _analysisModes.Count;
var options = new ParallelOptions var options = new ParallelOptions
{ {
MaxDegreeOfParallelism = Plugin.Instance.Configuration.MaxParallelism MaxDegreeOfParallelism = Plugin.Instance.Configuration.MaxParallelism
@ -116,32 +107,28 @@ public class BaseItemAnalyzerTask
season.Value, season.Value,
_analysisModes.Where(m => !Plugin.Instance!.IsIgnored(season.Key, m)).ToList()); _analysisModes.Where(m => !Plugin.Instance!.IsIgnored(season.Key, m)).ToList());
var episodeCount = episodes.Count; if (episodes.Count == 0)
if (episodeCount == 0)
{ {
return; return;
} }
var first = episodes.First(); var first = episodes.First();
var requiredModeCount = requiredModes.Count; if (requiredModes.Count == 0)
if (requiredModeCount == 0)
{ {
_logger.LogDebug( _logger.LogDebug(
"All episodes in {Name} season {Season} have already been analyzed", "All episodes in {Name} season {Season} have already been analyzed",
first.SeriesName, first.SeriesName,
first.SeasonNumber); first.SeasonNumber);
Interlocked.Add(ref totalProcessed, episodeCount * modeCount); // Update total Processed directly Interlocked.Add(ref totalProcessed, episodes.Count * _analysisModes.Count); // Update total Processed directly
progress.Report(totalProcessed * 100 / totalQueued); progress.Report(totalProcessed * 100 / totalQueued);
return; return;
} }
if (modeCount != requiredModeCount) if (_analysisModes.Count != requiredModes.Count)
{ {
Interlocked.Add(ref totalProcessed, episodeCount); Interlocked.Add(ref totalProcessed, episodes.Count);
progress.Report(totalProcessed * 100 / totalQueued); // Partial analysis some modes have already been analyzed progress.Report(totalProcessed * 100 / totalQueued); // Partial analysis some modes have already been analyzed
} }
@ -223,9 +210,8 @@ public class BaseItemAnalyzerTask
{ {
new ChapterAnalyzer(_loggerFactory.CreateLogger<ChapterAnalyzer>()) new ChapterAnalyzer(_loggerFactory.CreateLogger<ChapterAnalyzer>())
}; };
if (first.IsAnime)
{ if (first.IsAnime && Plugin.Instance!.Configuration.UseChromaprint)
if (Plugin.Instance!.Configuration.UseChromaprint)
{ {
analyzers.Add(new ChromaprintAnalyzer(_loggerFactory.CreateLogger<ChromaprintAnalyzer>())); analyzers.Add(new ChromaprintAnalyzer(_loggerFactory.CreateLogger<ChromaprintAnalyzer>()));
} }
@ -234,19 +220,11 @@ public class BaseItemAnalyzerTask
{ {
analyzers.Add(new BlackFrameAnalyzer(_loggerFactory.CreateLogger<BlackFrameAnalyzer>())); analyzers.Add(new BlackFrameAnalyzer(_loggerFactory.CreateLogger<BlackFrameAnalyzer>()));
} }
}
else
{
if (mode == AnalysisMode.Credits)
{
analyzers.Add(new BlackFrameAnalyzer(_loggerFactory.CreateLogger<BlackFrameAnalyzer>()));
}
if (Plugin.Instance!.Configuration.UseChromaprint) if (!first.IsAnime && Plugin.Instance!.Configuration.UseChromaprint)
{ {
analyzers.Add(new ChromaprintAnalyzer(_loggerFactory.CreateLogger<ChromaprintAnalyzer>())); analyzers.Add(new ChromaprintAnalyzer(_loggerFactory.CreateLogger<ChromaprintAnalyzer>()));
} }
}
// Use each analyzer to find skippable ranges in all media files, removing successfully // Use each analyzer to find skippable ranges in all media files, removing successfully
// analyzed items from the queue. // analyzed items from the queue.

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
using ConfusedPolarBear.Plugin.IntroSkipper.Data; using ConfusedPolarBear.Plugin.IntroSkipper.Data;
using ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks; using ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
@ -24,6 +25,7 @@ public sealed class Entrypoint : IHostedService, IDisposable
private readonly ILoggerFactory _loggerFactory; private readonly ILoggerFactory _loggerFactory;
private readonly HashSet<Guid> _seasonsToAnalyze = []; private readonly HashSet<Guid> _seasonsToAnalyze = [];
private readonly Timer _queueTimer; private readonly Timer _queueTimer;
private readonly PluginConfiguration _config;
private static readonly ManualResetEventSlim _autoTaskCompletEvent = new(false); private static readonly ManualResetEventSlim _autoTaskCompletEvent = new(false);
private bool _analyzeAgain; private bool _analyzeAgain;
private static CancellationTokenSource? _cancellationTokenSource; private static CancellationTokenSource? _cancellationTokenSource;
@ -46,6 +48,7 @@ public sealed class Entrypoint : IHostedService, IDisposable
_logger = logger; _logger = logger;
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
_config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
_queueTimer = new Timer( _queueTimer = new Timer(
OnTimerCallback, OnTimerCallback,
null, null,
@ -119,7 +122,7 @@ public sealed class Entrypoint : IHostedService, IDisposable
private void OnItemAdded(object? sender, ItemChangeEventArgs itemChangeEventArgs) private void OnItemAdded(object? sender, ItemChangeEventArgs itemChangeEventArgs)
{ {
// Don't do anything if auto detection is disabled // Don't do anything if auto detection is disabled
if (!Plugin.Instance!.Configuration.AutoDetectIntros && !Plugin.Instance.Configuration.AutoDetectCredits) if (!_config.AutoDetectIntros && !_config.AutoDetectCredits)
{ {
return; return;
} }
@ -148,7 +151,7 @@ public sealed class Entrypoint : IHostedService, IDisposable
private void OnItemModified(object? sender, ItemChangeEventArgs itemChangeEventArgs) private void OnItemModified(object? sender, ItemChangeEventArgs itemChangeEventArgs)
{ {
// Don't do anything if auto detection is disabled // Don't do anything if auto detection is disabled
if (!Plugin.Instance!.Configuration.AutoDetectIntros && !Plugin.Instance.Configuration.AutoDetectCredits) if (!_config.AutoDetectIntros && !_config.AutoDetectCredits)
{ {
return; return;
} }
@ -177,7 +180,7 @@ public sealed class Entrypoint : IHostedService, IDisposable
private void OnLibraryRefresh(object? sender, TaskCompletionEventArgs eventArgs) private void OnLibraryRefresh(object? sender, TaskCompletionEventArgs eventArgs)
{ {
// Don't do anything if auto detection is disabled // Don't do anything if auto detection is disabled
if (!Plugin.Instance!.Configuration.AutoDetectIntros && !Plugin.Instance.Configuration.AutoDetectCredits) if (!_config.AutoDetectIntros && !_config.AutoDetectCredits)
{ {
return; return;
} }
@ -257,21 +260,18 @@ public sealed class Entrypoint : IHostedService, IDisposable
var modes = new List<AnalysisMode>(); var modes = new List<AnalysisMode>();
var tasklogger = _loggerFactory.CreateLogger("DefaultLogger"); var tasklogger = _loggerFactory.CreateLogger("DefaultLogger");
if (Plugin.Instance!.Configuration.AutoDetectIntros && Plugin.Instance.Configuration.AutoDetectCredits) if (_config.AutoDetectIntros)
{
modes.Add(AnalysisMode.Introduction);
modes.Add(AnalysisMode.Credits);
tasklogger = _loggerFactory.CreateLogger<DetectIntrosCreditsTask>();
}
else if (Plugin.Instance.Configuration.AutoDetectIntros)
{ {
modes.Add(AnalysisMode.Introduction); modes.Add(AnalysisMode.Introduction);
tasklogger = _loggerFactory.CreateLogger<DetectIntrosTask>(); tasklogger = _loggerFactory.CreateLogger<DetectIntrosTask>();
} }
else if (Plugin.Instance.Configuration.AutoDetectCredits)
if (_config.AutoDetectCredits)
{ {
modes.Add(AnalysisMode.Credits); modes.Add(AnalysisMode.Credits);
tasklogger = _loggerFactory.CreateLogger<DetectCreditsTask>(); tasklogger = modes.Count == 2
? _loggerFactory.CreateLogger<DetectIntrosCreditsTask>()
: _loggerFactory.CreateLogger<DetectCreditsTask>();
} }
var baseCreditAnalyzer = new BaseItemAnalyzerTask( var baseCreditAnalyzer = new BaseItemAnalyzerTask(