apply auto-fixes from VS Code (#283)
Co-authored-by: rlauu <46294892+rlauu@users.noreply.github.com>
This commit is contained in:
parent
d428efb1f2
commit
60c735282e
@ -14,7 +14,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
|||||||
public class AnalyzerHelper
|
public class AnalyzerHelper
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly double silenceDetectionMinimumDuration;
|
private readonly double _silenceDetectionMinimumDuration;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AnalyzerHelper"/> class.
|
/// Initializes a new instance of the <see cref="AnalyzerHelper"/> class.
|
||||||
@ -23,7 +23,7 @@ public class AnalyzerHelper
|
|||||||
public AnalyzerHelper(ILogger logger)
|
public AnalyzerHelper(ILogger logger)
|
||||||
{
|
{
|
||||||
var config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
|
var config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
|
||||||
silenceDetectionMinimumDuration = config.SilenceDetectionMinimumDuration;
|
_silenceDetectionMinimumDuration = config.SilenceDetectionMinimumDuration;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ public class AnalyzerHelper
|
|||||||
private bool IsValidSilenceForIntroAdjustment(TimeRange silenceRange, TimeRange originalIntroEnd, Intro adjustedIntro)
|
private bool IsValidSilenceForIntroAdjustment(TimeRange silenceRange, TimeRange originalIntroEnd, Intro adjustedIntro)
|
||||||
{
|
{
|
||||||
return originalIntroEnd.Intersects(silenceRange) &&
|
return originalIntroEnd.Intersects(silenceRange) &&
|
||||||
silenceRange.Duration >= silenceDetectionMinimumDuration &&
|
silenceRange.Duration >= _silenceDetectionMinimumDuration &&
|
||||||
silenceRange.Start >= adjustedIntro.IntroStart;
|
silenceRange.Start >= adjustedIntro.IntroStart;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,11 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
|
|||||||
|
|
||||||
private readonly ILogger<BlackFrameAnalyzer> _logger;
|
private readonly ILogger<BlackFrameAnalyzer> _logger;
|
||||||
|
|
||||||
private int minimumCreditsDuration;
|
private readonly int _minimumCreditsDuration;
|
||||||
|
|
||||||
private int maximumCreditsDuration;
|
private readonly int _maximumCreditsDuration;
|
||||||
|
|
||||||
private int blackFrameMinimumPercentage;
|
private readonly int _blackFrameMinimumPercentage;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BlackFrameAnalyzer"/> class.
|
/// Initializes a new instance of the <see cref="BlackFrameAnalyzer"/> class.
|
||||||
@ -32,9 +32,9 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
|
|||||||
public BlackFrameAnalyzer(ILogger<BlackFrameAnalyzer> logger)
|
public BlackFrameAnalyzer(ILogger<BlackFrameAnalyzer> logger)
|
||||||
{
|
{
|
||||||
var config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
|
var config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
|
||||||
minimumCreditsDuration = config.MinimumCreditsDuration;
|
_minimumCreditsDuration = config.MinimumCreditsDuration;
|
||||||
maximumCreditsDuration = 2 * config.MaximumCreditsDuration;
|
_maximumCreditsDuration = 2 * config.MaximumCreditsDuration;
|
||||||
blackFrameMinimumPercentage = config.BlackFrameMinimumPercentage;
|
_blackFrameMinimumPercentage = config.BlackFrameMinimumPercentage;
|
||||||
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
@ -56,9 +56,9 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
|
|||||||
|
|
||||||
bool isFirstEpisode = true;
|
bool isFirstEpisode = true;
|
||||||
|
|
||||||
double searchStart = minimumCreditsDuration;
|
double searchStart = _minimumCreditsDuration;
|
||||||
|
|
||||||
var searchDistance = 2 * minimumCreditsDuration;
|
var searchDistance = 2 * _minimumCreditsDuration;
|
||||||
|
|
||||||
foreach (var episode in episodeAnalysisQueue.Where(e => !e.State.IsAnalyzed(mode)))
|
foreach (var episode in episodeAnalysisQueue.Where(e => !e.State.IsAnalyzed(mode)))
|
||||||
{
|
{
|
||||||
@ -73,7 +73,7 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
|
|||||||
var scanTime = episode.Duration - searchStart;
|
var scanTime = episode.Duration - searchStart;
|
||||||
var tr = new TimeRange(scanTime - 0.5, scanTime); // Short search range since accuracy isn't important here.
|
var tr = new TimeRange(scanTime - 0.5, scanTime); // Short search range since accuracy isn't important here.
|
||||||
|
|
||||||
var frames = FFmpegWrapper.DetectBlackFrames(episode, tr, blackFrameMinimumPercentage);
|
var frames = FFmpegWrapper.DetectBlackFrames(episode, tr, _blackFrameMinimumPercentage);
|
||||||
|
|
||||||
while (frames.Length > 0) // While black frames are found increase searchStart
|
while (frames.Length > 0) // While black frames are found increase searchStart
|
||||||
{
|
{
|
||||||
@ -82,16 +82,16 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
|
|||||||
scanTime = episode.Duration - searchStart;
|
scanTime = episode.Duration - searchStart;
|
||||||
tr = new TimeRange(scanTime - 0.5, scanTime);
|
tr = new TimeRange(scanTime - 0.5, scanTime);
|
||||||
|
|
||||||
frames = FFmpegWrapper.DetectBlackFrames(episode, tr, blackFrameMinimumPercentage);
|
frames = FFmpegWrapper.DetectBlackFrames(episode, tr, _blackFrameMinimumPercentage);
|
||||||
|
|
||||||
if (searchStart > maximumCreditsDuration)
|
if (searchStart > _maximumCreditsDuration)
|
||||||
{
|
{
|
||||||
searchStart = maximumCreditsDuration;
|
searchStart = _maximumCreditsDuration;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchStart == minimumCreditsDuration) // Skip if no black frames were found
|
if (searchStart == _minimumCreditsDuration) // Skip if no black frames were found
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -103,12 +103,12 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
|
|||||||
episode,
|
episode,
|
||||||
searchStart,
|
searchStart,
|
||||||
searchDistance,
|
searchDistance,
|
||||||
blackFrameMinimumPercentage);
|
_blackFrameMinimumPercentage);
|
||||||
|
|
||||||
if (credit is null)
|
if (credit is null)
|
||||||
{
|
{
|
||||||
// If no credits were found, reset the first-episode search logic for the next episode in the sequence.
|
// If no credits were found, reset the first-episode search logic for the next episode in the sequence.
|
||||||
searchStart = minimumCreditsDuration;
|
searchStart = _minimumCreditsDuration;
|
||||||
isFirstEpisode = true;
|
isFirstEpisode = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -139,7 +139,7 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
|
|||||||
{
|
{
|
||||||
// Start by analyzing the last N minutes of the file.
|
// Start by analyzing the last N minutes of the file.
|
||||||
var upperLimit = searchStart;
|
var upperLimit = searchStart;
|
||||||
var lowerLimit = Math.Max(searchStart - searchDistance, minimumCreditsDuration);
|
var lowerLimit = Math.Max(searchStart - searchDistance, _minimumCreditsDuration);
|
||||||
var start = TimeSpan.FromSeconds(upperLimit);
|
var start = TimeSpan.FromSeconds(upperLimit);
|
||||||
var end = TimeSpan.FromSeconds(lowerLimit);
|
var end = TimeSpan.FromSeconds(lowerLimit);
|
||||||
var firstFrameTime = 0.0;
|
var firstFrameTime = 0.0;
|
||||||
@ -176,7 +176,7 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
|
|||||||
|
|
||||||
if (midpoint - TimeSpan.FromSeconds(lowerLimit) < _maximumError)
|
if (midpoint - TimeSpan.FromSeconds(lowerLimit) < _maximumError)
|
||||||
{
|
{
|
||||||
lowerLimit = Math.Max(lowerLimit - (0.5 * searchDistance), minimumCreditsDuration);
|
lowerLimit = Math.Max(lowerLimit - (0.5 * searchDistance), _minimumCreditsDuration);
|
||||||
|
|
||||||
// Reset end for a new search with the increased duration
|
// Reset end for a new search with the increased duration
|
||||||
end = TimeSpan.FromSeconds(lowerLimit);
|
end = TimeSpan.FromSeconds(lowerLimit);
|
||||||
@ -190,7 +190,7 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
|
|||||||
|
|
||||||
if (TimeSpan.FromSeconds(upperLimit) - midpoint < _maximumError)
|
if (TimeSpan.FromSeconds(upperLimit) - midpoint < _maximumError)
|
||||||
{
|
{
|
||||||
upperLimit = Math.Min(upperLimit + (0.5 * searchDistance), maximumCreditsDuration);
|
upperLimit = Math.Min(upperLimit + (0.5 * searchDistance), _maximumCreditsDuration);
|
||||||
|
|
||||||
// Reset start for a new search with the increased duration
|
// Reset start for a new search with the increased duration
|
||||||
start = TimeSpan.FromSeconds(upperLimit);
|
start = TimeSpan.FromSeconds(upperLimit);
|
||||||
|
@ -22,15 +22,15 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const double SamplesToSeconds = 0.1238;
|
private const double SamplesToSeconds = 0.1238;
|
||||||
|
|
||||||
private int minimumIntroDuration;
|
private readonly int _minimumIntroDuration;
|
||||||
|
|
||||||
private int maximumDifferences;
|
private readonly int _maximumDifferences;
|
||||||
|
|
||||||
private int invertedIndexShift;
|
private readonly int _invertedIndexShift;
|
||||||
|
|
||||||
private double maximumTimeSkip;
|
private readonly double _maximumTimeSkip;
|
||||||
|
|
||||||
private ILogger<ChromaprintAnalyzer> _logger;
|
private readonly ILogger<ChromaprintAnalyzer> _logger;
|
||||||
|
|
||||||
private AnalysisMode _analysisMode;
|
private AnalysisMode _analysisMode;
|
||||||
|
|
||||||
@ -41,10 +41,10 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
|
|||||||
public ChromaprintAnalyzer(ILogger<ChromaprintAnalyzer> logger)
|
public ChromaprintAnalyzer(ILogger<ChromaprintAnalyzer> logger)
|
||||||
{
|
{
|
||||||
var config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
|
var config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
|
||||||
maximumDifferences = config.MaximumFingerprintPointDifferences;
|
_maximumDifferences = config.MaximumFingerprintPointDifferences;
|
||||||
invertedIndexShift = config.InvertedIndexShift;
|
_invertedIndexShift = config.InvertedIndexShift;
|
||||||
maximumTimeSkip = config.MaximumTimeSkip;
|
_maximumTimeSkip = config.MaximumTimeSkip;
|
||||||
minimumIntroDuration = config.MinimumIntroDuration;
|
_minimumIntroDuration = config.MinimumIntroDuration;
|
||||||
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
@ -87,7 +87,7 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
|
|||||||
.Where((episode, index) => Math.Abs(index - indexInAnalysisQueue) <= 1 && index != indexInAnalysisQueue));
|
.Where((episode, index) => Math.Abs(index - indexInAnalysisQueue) <= 1 && index != indexInAnalysisQueue));
|
||||||
}
|
}
|
||||||
|
|
||||||
seasonIntros = episodesWithFingerprint.Where(e => e.State.IsAnalyzed(mode)).ToDictionary(e => e.EpisodeId, e => Plugin.Instance!.GetIntroByMode(e.EpisodeId, mode));
|
seasonIntros = episodesWithFingerprint.Where(e => e.State.IsAnalyzed(mode)).ToDictionary(e => e.EpisodeId, e => Plugin.GetIntroByMode(e.EpisodeId, mode));
|
||||||
|
|
||||||
// Compute fingerprints for all episodes in the season
|
// Compute fingerprints for all episodes in the season
|
||||||
foreach (var episode in episodesWithFingerprint)
|
foreach (var episode in episodesWithFingerprint)
|
||||||
@ -113,7 +113,7 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
|
|||||||
WarningManager.SetFlag(PluginWarning.InvalidChromaprintFingerprint);
|
WarningManager.SetFlag(PluginWarning.InvalidChromaprintFingerprint);
|
||||||
|
|
||||||
// Fallback to an empty fingerprint on any error
|
// Fallback to an empty fingerprint on any error
|
||||||
fingerprintCache[episode.EpisodeId] = Array.Empty<uint>();
|
fingerprintCache[episode.EpisodeId] = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +203,7 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
|
|||||||
|
|
||||||
// Adjust all introduction times.
|
// Adjust all introduction times.
|
||||||
var analyzerHelper = new AnalyzerHelper(_logger);
|
var analyzerHelper = new AnalyzerHelper(_logger);
|
||||||
seasonIntros = analyzerHelper.AdjustIntroTimes(analysisQueue, seasonIntros, this._analysisMode);
|
seasonIntros = analyzerHelper.AdjustIntroTimes(analysisQueue, seasonIntros, _analysisMode);
|
||||||
|
|
||||||
Plugin.Instance!.UpdateTimestamps(seasonIntros, _analysisMode);
|
Plugin.Instance!.UpdateTimestamps(seasonIntros, _analysisMode);
|
||||||
|
|
||||||
@ -307,7 +307,7 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
|
|||||||
{
|
{
|
||||||
var originalPoint = kvp.Key;
|
var originalPoint = kvp.Key;
|
||||||
|
|
||||||
for (var i = -1 * invertedIndexShift; i <= invertedIndexShift; i++)
|
for (var i = -1 * _invertedIndexShift; i <= _invertedIndexShift; i++)
|
||||||
{
|
{
|
||||||
var modifiedPoint = (uint)(originalPoint + i);
|
var modifiedPoint = (uint)(originalPoint + i);
|
||||||
|
|
||||||
@ -372,7 +372,7 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
|
|||||||
var diff = lhs[lhsPosition] ^ rhs[rhsPosition];
|
var diff = lhs[lhsPosition] ^ rhs[rhsPosition];
|
||||||
|
|
||||||
// If the difference between the samples is small, flag both times as similar.
|
// If the difference between the samples is small, flag both times as similar.
|
||||||
if (CountBits(diff) > maximumDifferences)
|
if (CountBits(diff) > _maximumDifferences)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -389,14 +389,14 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
|
|||||||
rhsTimes.Add(double.MaxValue);
|
rhsTimes.Add(double.MaxValue);
|
||||||
|
|
||||||
// Now that both fingerprints have been compared at this shift, see if there's a contiguous time range.
|
// Now that both fingerprints have been compared at this shift, see if there's a contiguous time range.
|
||||||
var lContiguous = TimeRangeHelpers.FindContiguous(lhsTimes.ToArray(), maximumTimeSkip);
|
var lContiguous = TimeRangeHelpers.FindContiguous(lhsTimes.ToArray(), _maximumTimeSkip);
|
||||||
if (lContiguous is null || lContiguous.Duration < minimumIntroDuration)
|
if (lContiguous is null || lContiguous.Duration < _minimumIntroDuration)
|
||||||
{
|
{
|
||||||
return (new TimeRange(), new TimeRange());
|
return (new TimeRange(), new TimeRange());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since LHS had a contiguous time range, RHS must have one also.
|
// Since LHS had a contiguous time range, RHS must have one also.
|
||||||
var rContiguous = TimeRangeHelpers.FindContiguous(rhsTimes.ToArray(), maximumTimeSkip)!;
|
var rContiguous = TimeRangeHelpers.FindContiguous(rhsTimes.ToArray(), _maximumTimeSkip)!;
|
||||||
return (lContiguous, rContiguous);
|
return (lContiguous, rContiguous);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Analyzers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SegmentAnalyzer : IMediaFileAnalyzer
|
public class SegmentAnalyzer : IMediaFileAnalyzer
|
||||||
{
|
{
|
||||||
private ILogger<SegmentAnalyzer> _logger;
|
private readonly ILogger<SegmentAnalyzer> _logger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="SegmentAnalyzer"/> class.
|
/// Initializes a new instance of the <see cref="SegmentAnalyzer"/> class.
|
||||||
|
@ -71,7 +71,7 @@ public class PluginConfiguration : BasePluginConfiguration
|
|||||||
/// By default, EDL files are only written for a season if the season had at least one newly analyzed episode.
|
/// By default, EDL files are only written for a season if the season had at least one newly analyzed episode.
|
||||||
/// If this is set, all EDL files will be regenerated and overwrite any existing EDL file.
|
/// If this is set, all EDL files will be regenerated and overwrite any existing EDL file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool RegenerateEdlFiles { get; set; } = false;
|
public bool RegenerateEdlFiles { get; set; }
|
||||||
|
|
||||||
// ===== Custom analysis settings =====
|
// ===== Custom analysis settings =====
|
||||||
|
|
||||||
@ -172,7 +172,7 @@ public class PluginConfiguration : BasePluginConfiguration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the amount of credit at start to play (in seconds).
|
/// Gets or sets the amount of credit at start to play (in seconds).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int SecondsOfCreditsStartToPlay { get; set; } = 0;
|
public int SecondsOfCreditsStartToPlay { get; set; }
|
||||||
|
|
||||||
// ===== Internal algorithm settings =====
|
// ===== Internal algorithm settings =====
|
||||||
|
|
||||||
@ -228,7 +228,7 @@ public class PluginConfiguration : BasePluginConfiguration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the number of threads for an ffmpeg process.
|
/// Gets or sets the number of threads for an ffmpeg process.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int ProcessThreads { get; set; } = 0;
|
public int ProcessThreads { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the relative priority for an ffmpeg process.
|
/// Gets or sets the relative priority for an ffmpeg process.
|
||||||
|
@ -3,33 +3,26 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// User interface configuration.
|
/// User interface configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UserInterfaceConfiguration
|
/// <remarks>
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="UserInterfaceConfiguration"/> class.
|
/// Initializes a new instance of the <see cref="UserInterfaceConfiguration"/> class.
|
||||||
/// </summary>
|
/// </remarks>
|
||||||
/// <param name="visible">Skip button visibility.</param>
|
/// <param name="visible">Skip button visibility.</param>
|
||||||
/// <param name="introText">Skip button intro text.</param>
|
/// <param name="introText">Skip button intro text.</param>
|
||||||
/// <param name="creditsText">Skip button end credits text.</param>
|
/// <param name="creditsText">Skip button end credits text.</param>
|
||||||
public UserInterfaceConfiguration(bool visible, string introText, string creditsText)
|
public class UserInterfaceConfiguration(bool visible, string introText, string creditsText)
|
||||||
{
|
{
|
||||||
SkipButtonVisible = visible;
|
|
||||||
SkipButtonIntroText = introText;
|
|
||||||
SkipButtonEndCreditsText = creditsText;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether to show the skip intro button.
|
/// Gets or sets a value indicating whether to show the skip intro button.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool SkipButtonVisible { get; set; }
|
public bool SkipButtonVisible { get; set; } = visible;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the text to display in the skip intro button in introduction mode.
|
/// Gets or sets the text to display in the skip intro button in introduction mode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SkipButtonIntroText { get; set; }
|
public string SkipButtonIntroText { get; set; } = introText;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the text to display in the skip intro button in end credits mode.
|
/// Gets or sets the text to display in the skip intro button in end credits mode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SkipButtonEndCreditsText { get; set; }
|
public string SkipButtonEndCreditsText { get; set; } = creditsText;
|
||||||
}
|
}
|
||||||
|
@ -146,16 +146,16 @@ public class SkipIntroController : ControllerBase
|
|||||||
/// <param name="id">Unique identifier of this episode.</param>
|
/// <param name="id">Unique identifier of this episode.</param>
|
||||||
/// <param name="mode">Mode.</param>
|
/// <param name="mode">Mode.</param>
|
||||||
/// <returns>Intro object if the provided item has an intro, null otherwise.</returns>
|
/// <returns>Intro object if the provided item has an intro, null otherwise.</returns>
|
||||||
private Intro? GetIntro(Guid id, AnalysisMode mode)
|
private static Intro? GetIntro(Guid id, AnalysisMode mode)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var timestamp = Plugin.Instance!.GetIntroByMode(id, mode);
|
var timestamp = Plugin.GetIntroByMode(id, mode);
|
||||||
|
|
||||||
// Operate on a copy to avoid mutating the original Intro object stored in the dictionary.
|
// Operate on a copy to avoid mutating the original Intro object stored in the dictionary.
|
||||||
var segment = new Intro(timestamp);
|
var segment = new Intro(timestamp);
|
||||||
|
|
||||||
var config = Plugin.Instance.Configuration;
|
var config = Plugin.Instance!.Configuration;
|
||||||
segment.IntroEnd -= config.RemainingSecondsOfIntro;
|
segment.IntroEnd -= config.RemainingSecondsOfIntro;
|
||||||
if (config.PersistSkipButton)
|
if (config.PersistSkipButton)
|
||||||
{
|
{
|
||||||
@ -219,7 +219,7 @@ public class SkipIntroController : ControllerBase
|
|||||||
public ActionResult<List<IntroWithMetadata>> GetAllTimestamps(
|
public ActionResult<List<IntroWithMetadata>> GetAllTimestamps(
|
||||||
[FromQuery] AnalysisMode mode = AnalysisMode.Introduction)
|
[FromQuery] AnalysisMode mode = AnalysisMode.Introduction)
|
||||||
{
|
{
|
||||||
List<IntroWithMetadata> intros = new();
|
List<IntroWithMetadata> intros = [];
|
||||||
|
|
||||||
var timestamps = mode == AnalysisMode.Introduction ?
|
var timestamps = mode == AnalysisMode.Introduction ?
|
||||||
Plugin.Instance!.Intros :
|
Plugin.Instance!.Intros :
|
||||||
|
@ -135,7 +135,7 @@ public class TroubleshootingController : ControllerBase
|
|||||||
return bundle.ToString().TrimEnd('\n');
|
return bundle.ToString().TrimEnd('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetHumanReadableSize(long bytes)
|
private static string GetHumanReadableSize(long bytes)
|
||||||
{
|
{
|
||||||
string[] sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
string[] sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||||
double len = bytes;
|
double len = bytes;
|
||||||
@ -144,7 +144,7 @@ public class TroubleshootingController : ControllerBase
|
|||||||
while (len >= 1024 && order < sizes.Length - 1)
|
while (len >= 1024 && order < sizes.Length - 1)
|
||||||
{
|
{
|
||||||
order++;
|
order++;
|
||||||
len = len / 1024;
|
len /= 1024;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"{len:0.##} {sizes[order]}";
|
return $"{len:0.##} {sizes[order]}";
|
||||||
|
@ -178,7 +178,7 @@ public class VisualizationController : ControllerBase
|
|||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetSeasonName(QueuedEpisode episode)
|
private static string GetSeasonName(QueuedEpisode episode)
|
||||||
{
|
{
|
||||||
return "Season " + episode.SeasonNumber.ToString(CultureInfo.InvariantCulture);
|
return "Season " + episode.SeasonNumber.ToString(CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
@ -190,7 +190,7 @@ public class VisualizationController : ControllerBase
|
|||||||
/// <param name="season">Season name.</param>
|
/// <param name="season">Season name.</param>
|
||||||
/// <param name="episodes">Episodes.</param>
|
/// <param name="episodes">Episodes.</param>
|
||||||
/// <returns>Boolean indicating if the requested season was found.</returns>
|
/// <returns>Boolean indicating if the requested season was found.</returns>
|
||||||
private bool LookupSeasonByName(string series, string season, out List<QueuedEpisode> episodes)
|
private static bool LookupSeasonByName(string series, string season, out List<QueuedEpisode> episodes)
|
||||||
{
|
{
|
||||||
foreach (var queuedEpisodes in Plugin.Instance!.QueuedMediaItems)
|
foreach (var queuedEpisodes in Plugin.Instance!.QueuedMediaItems)
|
||||||
{
|
{
|
||||||
@ -209,7 +209,7 @@ public class VisualizationController : ControllerBase
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
episodes = new List<QueuedEpisode>();
|
episodes = [];
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,26 +3,20 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A frame of video that partially (or entirely) consists of black pixels.
|
/// A frame of video that partially (or entirely) consists of black pixels.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BlackFrame
|
/// <remarks>
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="BlackFrame"/> class.
|
/// Initializes a new instance of the <see cref="BlackFrame"/> class.
|
||||||
/// </summary>
|
/// </remarks>
|
||||||
/// <param name="percent">Percentage of the frame that is black.</param>
|
/// <param name="percent">Percentage of the frame that is black.</param>
|
||||||
/// <param name="time">Time this frame appears at.</param>
|
/// <param name="time">Time this frame appears at.</param>
|
||||||
public BlackFrame(int percent, double time)
|
public class BlackFrame(int percent, double time)
|
||||||
{
|
{
|
||||||
Percentage = percent;
|
|
||||||
Time = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the percentage of the frame that is black.
|
/// Gets or sets the percentage of the frame that is black.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Percentage { get; set; }
|
public int Percentage { get; set; } = percent;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the time (in seconds) this frame appeared at.
|
/// Gets or sets the time (in seconds) this frame appeared at.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double Time { get; set; }
|
public double Time { get; set; } = time;
|
||||||
}
|
}
|
||||||
|
@ -5,26 +5,20 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Episode name and internal ID as returned by the visualization controller.
|
/// Episode name and internal ID as returned by the visualization controller.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EpisodeVisualization
|
/// <remarks>
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="EpisodeVisualization"/> class.
|
/// Initializes a new instance of the <see cref="EpisodeVisualization"/> class.
|
||||||
/// </summary>
|
/// </remarks>
|
||||||
/// <param name="id">Episode id.</param>
|
/// <param name="id">Episode id.</param>
|
||||||
/// <param name="name">Episode name.</param>
|
/// <param name="name">Episode name.</param>
|
||||||
public EpisodeVisualization(Guid id, string name)
|
public class EpisodeVisualization(Guid id, string name)
|
||||||
{
|
{
|
||||||
Id = id;
|
|
||||||
Name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the id.
|
/// Gets the id.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Guid Id { get; private set; }
|
public Guid Id { get; private set; } = id;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name.
|
/// Gets the name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; private set; } = string.Empty;
|
public string Name { get; private set; } = name;
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ public class QueuedEpisode
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether an episode is Anime.
|
/// Gets or sets a value indicating whether an episode is Anime.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsAnime { get; set; } = false;
|
public bool IsAnime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the timestamp (in seconds) to stop searching for an introduction at.
|
/// Gets or sets the timestamp (in seconds) to stop searching for an introduction at.
|
||||||
|
@ -61,7 +61,7 @@ public class TimeRange : IComparable
|
|||||||
/// <returns>int.</returns>
|
/// <returns>int.</returns>
|
||||||
public int CompareTo(object? obj)
|
public int CompareTo(object? obj)
|
||||||
{
|
{
|
||||||
if (!(obj is TimeRange tr))
|
if (obj is not TimeRange tr)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("obj must be a TimeRange");
|
throw new ArgumentException("obj must be a TimeRange");
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
namespace ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||||
#pragma warning restore CA1036
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time range helpers.
|
/// Time range helpers.
|
||||||
|
@ -5,7 +5,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class WarningManager
|
public static class WarningManager
|
||||||
{
|
{
|
||||||
private static PluginWarning warnings;
|
private static PluginWarning _warnings;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set warning.
|
/// Set warning.
|
||||||
@ -13,7 +13,7 @@ public static class WarningManager
|
|||||||
/// <param name="warning">Warning.</param>
|
/// <param name="warning">Warning.</param>
|
||||||
public static void SetFlag(PluginWarning warning)
|
public static void SetFlag(PluginWarning warning)
|
||||||
{
|
{
|
||||||
warnings |= warning;
|
_warnings |= warning;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -21,7 +21,7 @@ public static class WarningManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static void Clear()
|
public static void Clear()
|
||||||
{
|
{
|
||||||
warnings = PluginWarning.None;
|
_warnings = PluginWarning.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -30,7 +30,7 @@ public static class WarningManager
|
|||||||
/// <returns>Warnings.</returns>
|
/// <returns>Warnings.</returns>
|
||||||
public static string GetWarnings()
|
public static string GetWarnings()
|
||||||
{
|
{
|
||||||
return warnings.ToString();
|
return _warnings.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -40,6 +40,6 @@ public static class WarningManager
|
|||||||
/// <returns>True if the flag is set, otherwise false.</returns>
|
/// <returns>True if the flag is set, otherwise false.</returns>
|
||||||
public static bool HasFlag(PluginWarning warning)
|
public static bool HasFlag(PluginWarning warning)
|
||||||
{
|
{
|
||||||
return (warnings & warning) == warning;
|
return (_warnings & warning) == warning;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||||
@ -19,38 +18,30 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class Entrypoint : IHostedService, IDisposable
|
public class Entrypoint : IHostedService, IDisposable
|
||||||
{
|
{
|
||||||
private readonly IUserManager _userManager;
|
|
||||||
private readonly IUserViewManager _userViewManager;
|
|
||||||
private readonly ITaskManager _taskManager;
|
private readonly ITaskManager _taskManager;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly ILogger<Entrypoint> _logger;
|
private readonly ILogger<Entrypoint> _logger;
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
private Timer _queueTimer;
|
private readonly HashSet<Guid> _seasonsToAnalyze = [];
|
||||||
|
private readonly Timer _queueTimer;
|
||||||
|
private static readonly ManualResetEventSlim _autoTaskCompletEvent = new(false);
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private bool _analyzeAgain;
|
private bool _analyzeAgain;
|
||||||
private HashSet<Guid> _seasonsToAnalyze = new HashSet<Guid>();
|
|
||||||
private static CancellationTokenSource? _cancellationTokenSource;
|
private static CancellationTokenSource? _cancellationTokenSource;
|
||||||
private static ManualResetEventSlim _autoTaskCompletEvent = new ManualResetEventSlim(false);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Entrypoint"/> class.
|
/// Initializes a new instance of the <see cref="Entrypoint"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userManager">User manager.</param>
|
|
||||||
/// <param name="userViewManager">User view manager.</param>
|
|
||||||
/// <param name="libraryManager">Library manager.</param>
|
/// <param name="libraryManager">Library manager.</param>
|
||||||
/// <param name="taskManager">Task manager.</param>
|
/// <param name="taskManager">Task manager.</param>
|
||||||
/// <param name="logger">Logger.</param>
|
/// <param name="logger">Logger.</param>
|
||||||
/// <param name="loggerFactory">Logger factory.</param>
|
/// <param name="loggerFactory">Logger factory.</param>
|
||||||
public Entrypoint(
|
public Entrypoint(
|
||||||
IUserManager userManager,
|
|
||||||
IUserViewManager userViewManager,
|
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
ITaskManager taskManager,
|
ITaskManager taskManager,
|
||||||
ILogger<Entrypoint> logger,
|
ILogger<Entrypoint> logger,
|
||||||
ILoggerFactory loggerFactory)
|
ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
|
||||||
_userViewManager = userViewManager;
|
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_taskManager = taskManager;
|
_taskManager = taskManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
@ -14,25 +14,24 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wrapper for libchromaprint and the silencedetect filter.
|
/// Wrapper for libchromaprint and the silencedetect filter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class FFmpegWrapper
|
public static partial class FFmpegWrapper
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used with FFmpeg's silencedetect filter to extract the start and end times of silence.
|
/// Used with FFmpeg's silencedetect filter to extract the start and end times of silence.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Regex SilenceDetectionExpression = new(
|
private static readonly Regex _silenceDetectionExpression = SilenceRegex();
|
||||||
"silence_(?<type>start|end): (?<time>[0-9\\.]+)");
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used with FFmpeg's blackframe filter to extract the time and percentage of black pixels.
|
/// Used with FFmpeg's blackframe filter to extract the time and percentage of black pixels.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Regex BlackFrameRegex = new("(pblack|t):[0-9.]+");
|
private static readonly Regex _blackFrameRegex = BlackFrameRegex();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the logger.
|
/// Gets or sets the logger.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static ILogger? Logger { get; set; }
|
public static ILogger? Logger { get; set; }
|
||||||
|
|
||||||
private static Dictionary<string, string> ChromaprintLogs { get; set; } = new();
|
private static Dictionary<string, string> ChromaprintLogs { get; set; } = [];
|
||||||
|
|
||||||
private static ConcurrentDictionary<(Guid Id, AnalysisMode Mode), Dictionary<uint, int>> InvertedIndexCache { get; set; } = new();
|
private static ConcurrentDictionary<(Guid Id, AnalysisMode Mode), Dictionary<uint, int>> InvertedIndexCache { get; set; } = new();
|
||||||
|
|
||||||
@ -205,7 +204,7 @@ public static class FFmpegWrapper
|
|||||||
* [silencedetect @ 0x000000000000] silence_end: 56.123 | silence_duration: 43.783
|
* [silencedetect @ 0x000000000000] silence_end: 56.123 | silence_duration: 43.783
|
||||||
*/
|
*/
|
||||||
var raw = Encoding.UTF8.GetString(GetOutput(args, cacheKey, true));
|
var raw = Encoding.UTF8.GetString(GetOutput(args, cacheKey, true));
|
||||||
foreach (Match match in SilenceDetectionExpression.Matches(raw))
|
foreach (Match match in _silenceDetectionExpression.Matches(raw))
|
||||||
{
|
{
|
||||||
var isStart = match.Groups["type"].Value == "start";
|
var isStart = match.Groups["type"].Value == "start";
|
||||||
var time = Convert.ToDouble(match.Groups["time"].Value, CultureInfo.InvariantCulture);
|
var time = Convert.ToDouble(match.Groups["time"].Value, CultureInfo.InvariantCulture);
|
||||||
@ -267,7 +266,7 @@ public static class FFmpegWrapper
|
|||||||
// In our case, the metadata contained something that matched the regex.
|
// In our case, the metadata contained something that matched the regex.
|
||||||
if (line.StartsWith("[Parsed_blackframe_", StringComparison.OrdinalIgnoreCase))
|
if (line.StartsWith("[Parsed_blackframe_", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var matches = BlackFrameRegex.Matches(line);
|
var matches = _blackFrameRegex.Matches(line);
|
||||||
if (matches.Count != 2)
|
if (matches.Count != 2)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@ -422,8 +421,7 @@ public static class FFmpegWrapper
|
|||||||
RedirectStandardError = stderr
|
RedirectStandardError = stderr
|
||||||
};
|
};
|
||||||
|
|
||||||
using (var ffmpeg = new Process { StartInfo = info })
|
using var ffmpeg = new Process { StartInfo = info };
|
||||||
{
|
|
||||||
Logger?.LogDebug("Starting ffmpeg with the following arguments: {Arguments}", ffmpeg.StartInfo.Arguments);
|
Logger?.LogDebug("Starting ffmpeg with the following arguments: {Arguments}", ffmpeg.StartInfo.Arguments);
|
||||||
|
|
||||||
ffmpeg.Start();
|
ffmpeg.Start();
|
||||||
@ -437,8 +435,7 @@ public static class FFmpegWrapper
|
|||||||
Logger?.LogDebug("ffmpeg priority could not be modified. {Message}", e.Message);
|
Logger?.LogDebug("ffmpeg priority could not be modified. {Message}", e.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var ms = new MemoryStream())
|
using var ms = new MemoryStream();
|
||||||
{
|
|
||||||
var buf = new byte[4096];
|
var buf = new byte[4096];
|
||||||
int bytesRead;
|
int bytesRead;
|
||||||
|
|
||||||
@ -462,8 +459,6 @@ public static class FFmpegWrapper
|
|||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fingerprint a queued episode.
|
/// Fingerprint a queued episode.
|
||||||
@ -696,4 +691,10 @@ public static class FFmpegWrapper
|
|||||||
|
|
||||||
return formatted;
|
return formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex("silence_(?<type>start|end): (?<time>[0-9\\.]+)")]
|
||||||
|
private static partial Regex SilenceRegex();
|
||||||
|
|
||||||
|
[GeneratedRegex("(pblack|t):[0-9.]+")]
|
||||||
|
private static partial Regex BlackFrameRegex();
|
||||||
}
|
}
|
||||||
|
@ -21,15 +21,15 @@ 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 class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
public partial 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();
|
||||||
private ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private IItemRepository _itemRepository;
|
private readonly IItemRepository _itemRepository;
|
||||||
private ILogger<Plugin> _logger;
|
private readonly ILogger<Plugin> _logger;
|
||||||
private string _introPath;
|
private readonly string _introPath;
|
||||||
private string _creditsPath;
|
private readonly string _creditsPath;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Plugin"/> class.
|
/// Initializes a new instance of the <see cref="Plugin"/> class.
|
||||||
@ -201,7 +201,7 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
/// <param name="mode">Mode.</param>
|
/// <param name="mode">Mode.</param>
|
||||||
public void SaveTimestamps(AnalysisMode mode)
|
public void SaveTimestamps(AnalysisMode mode)
|
||||||
{
|
{
|
||||||
List<Intro> introList = new List<Intro>();
|
List<Intro> introList = [];
|
||||||
var filePath = mode == AnalysisMode.Introduction
|
var filePath = mode == AnalysisMode.Introduction
|
||||||
? _introPath
|
? _introPath
|
||||||
: _creditsPath;
|
: _creditsPath;
|
||||||
@ -256,11 +256,11 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<PluginPageInfo> GetPages()
|
public IEnumerable<PluginPageInfo> GetPages()
|
||||||
{
|
{
|
||||||
return new[]
|
return
|
||||||
{
|
[
|
||||||
new PluginPageInfo
|
new PluginPageInfo
|
||||||
{
|
{
|
||||||
Name = this.Name,
|
Name = Name,
|
||||||
EmbeddedResourcePath = GetType().Namespace + ".Configuration.configPage.html"
|
EmbeddedResourcePath = GetType().Namespace + ".Configuration.configPage.html"
|
||||||
},
|
},
|
||||||
new PluginPageInfo
|
new PluginPageInfo
|
||||||
@ -273,7 +273,7 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
Name = "skip-intro-button.js",
|
Name = "skip-intro-button.js",
|
||||||
EmbeddedResourcePath = GetType().Namespace + ".Configuration.inject.js"
|
EmbeddedResourcePath = GetType().Namespace + ".Configuration.inject.js"
|
||||||
}
|
}
|
||||||
};
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -311,7 +311,7 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
/// <param name="id">Item id.</param>
|
/// <param name="id">Item id.</param>
|
||||||
/// <param name="mode">Mode.</param>
|
/// <param name="mode">Mode.</param>
|
||||||
/// <returns>Intro.</returns>
|
/// <returns>Intro.</returns>
|
||||||
internal Intro GetIntroByMode(Guid id, AnalysisMode mode)
|
internal static Intro GetIntroByMode(Guid id, AnalysisMode mode)
|
||||||
{
|
{
|
||||||
return mode == AnalysisMode.Introduction
|
return mode == AnalysisMode.Introduction
|
||||||
? Instance!.Intros[id]
|
? Instance!.Intros[id]
|
||||||
@ -353,7 +353,7 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
{
|
{
|
||||||
// Handle the case where the item is not found
|
// Handle the case where the item is not found
|
||||||
_logger.LogWarning("Item with ID {Id} not found.", id);
|
_logger.LogWarning("Item with ID {Id} not found.", id);
|
||||||
return new List<ChapterInfo>();
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return _itemRepository.GetChapters(item);
|
return _itemRepository.GetChapters(item);
|
||||||
@ -447,7 +447,7 @@ public 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 = new Regex("</head>", RegexOptions.IgnoreCase);
|
Regex headEnd = HeadRegex();
|
||||||
contents = headEnd.Replace(contents, scriptTag + "</head>", 1);
|
contents = headEnd.Replace(contents, scriptTag + "</head>", 1);
|
||||||
|
|
||||||
// Write the modified file contents
|
// Write the modified file contents
|
||||||
@ -455,4 +455,7 @@ public 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();
|
||||||
}
|
}
|
||||||
|
@ -15,28 +15,18 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manages enqueuing library items for analysis.
|
/// Manages enqueuing library items for analysis.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class QueueManager
|
/// <remarks>
|
||||||
{
|
|
||||||
private ILibraryManager _libraryManager;
|
|
||||||
private ILogger<QueueManager> _logger;
|
|
||||||
|
|
||||||
private double analysisPercent;
|
|
||||||
private List<string> selectedLibraries;
|
|
||||||
private Dictionary<Guid, List<QueuedEpisode>> _queuedEpisodes;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="QueueManager"/> class.
|
/// Initializes a new instance of the <see cref="QueueManager"/> class.
|
||||||
/// </summary>
|
/// </remarks>
|
||||||
/// <param name="logger">Logger.</param>
|
/// <param name="logger">Logger.</param>
|
||||||
/// <param name="libraryManager">Library manager.</param>
|
/// <param name="libraryManager">Library manager.</param>
|
||||||
public QueueManager(ILogger<QueueManager> logger, ILibraryManager libraryManager)
|
public class QueueManager(ILogger<QueueManager> logger, ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
private readonly ILibraryManager _libraryManager = libraryManager;
|
||||||
_libraryManager = libraryManager;
|
private readonly ILogger<QueueManager> _logger = logger;
|
||||||
|
private readonly Dictionary<Guid, List<QueuedEpisode>> _queuedEpisodes = [];
|
||||||
selectedLibraries = new();
|
private double _analysisPercent;
|
||||||
_queuedEpisodes = new();
|
private List<string> _selectedLibraries = [];
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all media items on the server.
|
/// Gets all media items on the server.
|
||||||
@ -52,7 +42,7 @@ public class QueueManager
|
|||||||
foreach (var folder in _libraryManager.GetVirtualFolders())
|
foreach (var folder in _libraryManager.GetVirtualFolders())
|
||||||
{
|
{
|
||||||
// If libraries have been selected for analysis, ensure this library was selected.
|
// If libraries have been selected for analysis, ensure this library was selected.
|
||||||
if (selectedLibraries.Count > 0 && !selectedLibraries.Contains(folder.Name))
|
if (_selectedLibraries.Count > 0 && !_selectedLibraries.Contains(folder.Name))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Not analyzing library \"{Name}\": not selected by user", folder.Name);
|
_logger.LogDebug("Not analyzing library \"{Name}\": not selected by user", folder.Name);
|
||||||
continue;
|
continue;
|
||||||
@ -100,17 +90,15 @@ public class QueueManager
|
|||||||
var config = Plugin.Instance!.Configuration;
|
var config = Plugin.Instance!.Configuration;
|
||||||
|
|
||||||
// Store the analysis percent
|
// Store the analysis percent
|
||||||
analysisPercent = Convert.ToDouble(config.AnalysisPercent) / 100;
|
_analysisPercent = Convert.ToDouble(config.AnalysisPercent) / 100;
|
||||||
|
|
||||||
// Get the list of library names which have been selected for analysis, ignoring whitespace and empty entries.
|
// Get the list of library names which have been selected for analysis, ignoring whitespace and empty entries.
|
||||||
selectedLibraries = config.SelectedLibraries
|
_selectedLibraries = [.. config.SelectedLibraries.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)];
|
||||||
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// If any libraries have been selected for analysis, log their names.
|
// If any libraries have been selected for analysis, log their names.
|
||||||
if (selectedLibraries.Count > 0)
|
if (_selectedLibraries.Count > 0)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Limiting analysis to the following libraries: {Selected}", selectedLibraries);
|
_logger.LogInformation("Limiting analysis to the following libraries: {Selected}", _selectedLibraries);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -136,7 +124,7 @@ public class QueueManager
|
|||||||
{
|
{
|
||||||
// Order by series name, season, and then episode number so that status updates are logged in order
|
// Order by series name, season, and then episode number so that status updates are logged in order
|
||||||
ParentId = id,
|
ParentId = id,
|
||||||
OrderBy = new[] { (ItemSortBy.SeriesSortName, SortOrder.Ascending), (ItemSortBy.ParentIndexNumber, SortOrder.Ascending), (ItemSortBy.IndexNumber, SortOrder.Ascending), },
|
OrderBy = [(ItemSortBy.SeriesSortName, SortOrder.Ascending), (ItemSortBy.ParentIndexNumber, SortOrder.Ascending), (ItemSortBy.IndexNumber, SortOrder.Ascending),],
|
||||||
IncludeItemTypes = [BaseItemKind.Episode],
|
IncludeItemTypes = [BaseItemKind.Episode],
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
IsVirtualItem = false
|
IsVirtualItem = false
|
||||||
@ -184,7 +172,7 @@ public class QueueManager
|
|||||||
// Allocate a new list for each new season
|
// Allocate a new list for each new season
|
||||||
if (!_queuedEpisodes.TryGetValue(episode.SeasonId, out var seasonEpisodes))
|
if (!_queuedEpisodes.TryGetValue(episode.SeasonId, out var seasonEpisodes))
|
||||||
{
|
{
|
||||||
seasonEpisodes = new List<QueuedEpisode>();
|
seasonEpisodes = [];
|
||||||
_queuedEpisodes[episode.SeasonId] = seasonEpisodes;
|
_queuedEpisodes[episode.SeasonId] = seasonEpisodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,7 +190,7 @@ public class QueueManager
|
|||||||
// X and Y default to 25% and 10 minutes.
|
// X and Y default to 25% and 10 minutes.
|
||||||
var duration = TimeSpan.FromTicks(episode.RunTimeTicks ?? 0).TotalSeconds;
|
var duration = TimeSpan.FromTicks(episode.RunTimeTicks ?? 0).TotalSeconds;
|
||||||
var fingerprintDuration = Math.Min(
|
var fingerprintDuration = Math.Min(
|
||||||
duration >= 5 * 60 ? duration * analysisPercent : duration,
|
duration >= 5 * 60 ? duration * _analysisPercent : duration,
|
||||||
60 * pluginInstance.Configuration.AnalysisLengthLimit);
|
60 * pluginInstance.Configuration.AnalysisLengthLimit);
|
||||||
|
|
||||||
// Queue the episode for analysis
|
// Queue the episode for analysis
|
||||||
|
@ -134,7 +134,7 @@ public class BaseItemAnalyzerTask
|
|||||||
first.SeasonNumber);
|
first.SeasonNumber);
|
||||||
|
|
||||||
Interlocked.Add(ref totalProcessed, episodeCount * modeCount); // Update total Processed directly
|
Interlocked.Add(ref totalProcessed, episodeCount * modeCount); // Update total Processed directly
|
||||||
progress.Report((totalProcessed * 100) / totalQueued);
|
progress.Report(totalProcessed * 100 / totalQueued);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -142,7 +142,7 @@ public class BaseItemAnalyzerTask
|
|||||||
if (modeCount != requiredModeCount)
|
if (modeCount != requiredModeCount)
|
||||||
{
|
{
|
||||||
Interlocked.Add(ref totalProcessed, episodeCount);
|
Interlocked.Add(ref totalProcessed, episodeCount);
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -159,7 +159,7 @@ public class BaseItemAnalyzerTask
|
|||||||
|
|
||||||
writeEdl = analyzed > 0 || Plugin.Instance.Configuration.RegenerateEdlFiles;
|
writeEdl = analyzed > 0 || Plugin.Instance.Configuration.RegenerateEdlFiles;
|
||||||
|
|
||||||
progress.Report((totalProcessed * 100) / totalQueued);
|
progress.Report(totalProcessed * 100 / totalQueued);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (FingerprintException ex)
|
catch (FingerprintException ex)
|
||||||
@ -219,9 +219,10 @@ public class BaseItemAnalyzerTask
|
|||||||
first.SeriesName,
|
first.SeriesName,
|
||||||
first.SeasonNumber);
|
first.SeasonNumber);
|
||||||
|
|
||||||
var analyzers = new Collection<IMediaFileAnalyzer>();
|
var analyzers = new Collection<IMediaFileAnalyzer>
|
||||||
|
{
|
||||||
analyzers.Add(new ChapterAnalyzer(_loggerFactory.CreateLogger<ChapterAnalyzer>()));
|
new ChapterAnalyzer(_loggerFactory.CreateLogger<ChapterAnalyzer>())
|
||||||
|
};
|
||||||
if (first.IsAnime)
|
if (first.IsAnime)
|
||||||
{
|
{
|
||||||
if (Plugin.Instance!.Configuration.UseChromaprint)
|
if (Plugin.Instance!.Configuration.UseChromaprint)
|
||||||
|
@ -112,6 +112,6 @@ public class CleanCacheTask : IScheduledTask
|
|||||||
/// <returns>Task triggers.</returns>
|
/// <returns>Task triggers.</returns>
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||||
{
|
{
|
||||||
return Array.Empty<TaskTriggerInfo>();
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,6 @@ public class DetectCreditsTask : IScheduledTask
|
|||||||
/// <returns>Task triggers.</returns>
|
/// <returns>Task triggers.</returns>
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||||
{
|
{
|
||||||
return Array.Empty<TaskTriggerInfo>();
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,13 +101,13 @@ public class DetectIntrosCreditsTask : IScheduledTask
|
|||||||
/// <returns>Task triggers.</returns>
|
/// <returns>Task triggers.</returns>
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||||
{
|
{
|
||||||
return new[]
|
return
|
||||||
{
|
[
|
||||||
new TaskTriggerInfo
|
new TaskTriggerInfo
|
||||||
{
|
{
|
||||||
Type = TaskTriggerInfo.TriggerDaily,
|
Type = TaskTriggerInfo.TriggerDaily,
|
||||||
TimeOfDayTicks = TimeSpan.FromHours(0).Ticks
|
TimeOfDayTicks = TimeSpan.FromHours(0).Ticks
|
||||||
}
|
}
|
||||||
};
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,6 +101,6 @@ public class DetectIntrosTask : IScheduledTask
|
|||||||
/// <returns>Task triggers.</returns>
|
/// <returns>Task triggers.</returns>
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||||
{
|
{
|
||||||
return Array.Empty<TaskTriggerInfo>();
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,9 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
|
|||||||
|
|
||||||
internal sealed class ScheduledTaskSemaphore : IDisposable
|
internal sealed class ScheduledTaskSemaphore : IDisposable
|
||||||
{
|
{
|
||||||
private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
|
private static readonly SemaphoreSlim _semaphore = new(1, 1);
|
||||||
|
|
||||||
private static bool _isHeld = false;
|
private static bool _isHeld;
|
||||||
|
|
||||||
private ScheduledTaskSemaphore()
|
private ScheduledTaskSemaphore()
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user