apply auto-fixes from VS Code (#283)

Co-authored-by: rlauu <46294892+rlauu@users.noreply.github.com>
This commit is contained in:
rlauuzo 2024-09-10 18:08:42 +02:00 committed by GitHub
parent d428efb1f2
commit 60c735282e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 181 additions and 217 deletions

View File

@ -14,7 +14,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
public class AnalyzerHelper
{
private readonly ILogger _logger;
private readonly double silenceDetectionMinimumDuration;
private readonly double _silenceDetectionMinimumDuration;
/// <summary>
/// Initializes a new instance of the <see cref="AnalyzerHelper"/> class.
@ -23,7 +23,7 @@ public class AnalyzerHelper
public AnalyzerHelper(ILogger logger)
{
var config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
silenceDetectionMinimumDuration = config.SilenceDetectionMinimumDuration;
_silenceDetectionMinimumDuration = config.SilenceDetectionMinimumDuration;
_logger = logger;
}
@ -126,7 +126,7 @@ public class AnalyzerHelper
private bool IsValidSilenceForIntroAdjustment(TimeRange silenceRange, TimeRange originalIntroEnd, Intro adjustedIntro)
{
return originalIntroEnd.Intersects(silenceRange) &&
silenceRange.Duration >= silenceDetectionMinimumDuration &&
silenceRange.Duration >= _silenceDetectionMinimumDuration &&
silenceRange.Start >= adjustedIntro.IntroStart;
}
}

View File

@ -19,11 +19,11 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
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>
/// Initializes a new instance of the <see cref="BlackFrameAnalyzer"/> class.
@ -32,9 +32,9 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
public BlackFrameAnalyzer(ILogger<BlackFrameAnalyzer> logger)
{
var config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
minimumCreditsDuration = config.MinimumCreditsDuration;
maximumCreditsDuration = 2 * config.MaximumCreditsDuration;
blackFrameMinimumPercentage = config.BlackFrameMinimumPercentage;
_minimumCreditsDuration = config.MinimumCreditsDuration;
_maximumCreditsDuration = 2 * config.MaximumCreditsDuration;
_blackFrameMinimumPercentage = config.BlackFrameMinimumPercentage;
_logger = logger;
}
@ -56,9 +56,9 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
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)))
{
@ -73,7 +73,7 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
var scanTime = episode.Duration - searchStart;
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
{
@ -82,16 +82,16 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
scanTime = episode.Duration - searchStart;
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;
}
}
if (searchStart == minimumCreditsDuration) // Skip if no black frames were found
if (searchStart == _minimumCreditsDuration) // Skip if no black frames were found
{
continue;
}
@ -103,12 +103,12 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
episode,
searchStart,
searchDistance,
blackFrameMinimumPercentage);
_blackFrameMinimumPercentage);
if (credit is null)
{
// If no credits were found, reset the first-episode search logic for the next episode in the sequence.
searchStart = minimumCreditsDuration;
searchStart = _minimumCreditsDuration;
isFirstEpisode = true;
continue;
}
@ -139,7 +139,7 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
{
// Start by analyzing the last N minutes of the file.
var upperLimit = searchStart;
var lowerLimit = Math.Max(searchStart - searchDistance, minimumCreditsDuration);
var lowerLimit = Math.Max(searchStart - searchDistance, _minimumCreditsDuration);
var start = TimeSpan.FromSeconds(upperLimit);
var end = TimeSpan.FromSeconds(lowerLimit);
var firstFrameTime = 0.0;
@ -176,7 +176,7 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
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
end = TimeSpan.FromSeconds(lowerLimit);
@ -190,7 +190,7 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
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
start = TimeSpan.FromSeconds(upperLimit);

View File

@ -22,15 +22,15 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
/// </summary>
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;
@ -41,10 +41,10 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
public ChromaprintAnalyzer(ILogger<ChromaprintAnalyzer> logger)
{
var config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
maximumDifferences = config.MaximumFingerprintPointDifferences;
invertedIndexShift = config.InvertedIndexShift;
maximumTimeSkip = config.MaximumTimeSkip;
minimumIntroDuration = config.MinimumIntroDuration;
_maximumDifferences = config.MaximumFingerprintPointDifferences;
_invertedIndexShift = config.InvertedIndexShift;
_maximumTimeSkip = config.MaximumTimeSkip;
_minimumIntroDuration = config.MinimumIntroDuration;
_logger = logger;
}
@ -87,7 +87,7 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
.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
foreach (var episode in episodesWithFingerprint)
@ -113,7 +113,7 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
WarningManager.SetFlag(PluginWarning.InvalidChromaprintFingerprint);
// 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.
var analyzerHelper = new AnalyzerHelper(_logger);
seasonIntros = analyzerHelper.AdjustIntroTimes(analysisQueue, seasonIntros, this._analysisMode);
seasonIntros = analyzerHelper.AdjustIntroTimes(analysisQueue, seasonIntros, _analysisMode);
Plugin.Instance!.UpdateTimestamps(seasonIntros, _analysisMode);
@ -307,7 +307,7 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
{
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);
@ -372,7 +372,7 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
var diff = lhs[lhsPosition] ^ rhs[rhsPosition];
// If the difference between the samples is small, flag both times as similar.
if (CountBits(diff) > maximumDifferences)
if (CountBits(diff) > _maximumDifferences)
{
continue;
}
@ -389,14 +389,14 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
rhsTimes.Add(double.MaxValue);
// 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);
if (lContiguous is null || lContiguous.Duration < minimumIntroDuration)
var lContiguous = TimeRangeHelpers.FindContiguous(lhsTimes.ToArray(), _maximumTimeSkip);
if (lContiguous is null || lContiguous.Duration < _minimumIntroDuration)
{
return (new TimeRange(), new TimeRange());
}
// 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);
}

View File

@ -10,7 +10,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Analyzers;
/// </summary>
public class SegmentAnalyzer : IMediaFileAnalyzer
{
private ILogger<SegmentAnalyzer> _logger;
private readonly ILogger<SegmentAnalyzer> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="SegmentAnalyzer"/> class.

View File

@ -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.
/// If this is set, all EDL files will be regenerated and overwrite any existing EDL file.
/// </summary>
public bool RegenerateEdlFiles { get; set; } = false;
public bool RegenerateEdlFiles { get; set; }
// ===== Custom analysis settings =====
@ -172,7 +172,7 @@ public class PluginConfiguration : BasePluginConfiguration
/// <summary>
/// Gets or sets the amount of credit at start to play (in seconds).
/// </summary>
public int SecondsOfCreditsStartToPlay { get; set; } = 0;
public int SecondsOfCreditsStartToPlay { get; set; }
// ===== Internal algorithm settings =====
@ -228,7 +228,7 @@ public class PluginConfiguration : BasePluginConfiguration
/// <summary>
/// Gets or sets the number of threads for an ffmpeg process.
/// </summary>
public int ProcessThreads { get; set; } = 0;
public int ProcessThreads { get; set; }
/// <summary>
/// Gets or sets the relative priority for an ffmpeg process.

View File

@ -3,33 +3,26 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
/// <summary>
/// User interface configuration.
/// </summary>
public class UserInterfaceConfiguration
/// <remarks>
/// Initializes a new instance of the <see cref="UserInterfaceConfiguration"/> class.
/// </remarks>
/// <param name="visible">Skip button visibility.</param>
/// <param name="introText">Skip button intro text.</param>
/// <param name="creditsText">Skip button end credits text.</param>
public class UserInterfaceConfiguration(bool visible, string introText, string creditsText)
{
/// <summary>
/// Initializes a new instance of the <see cref="UserInterfaceConfiguration"/> class.
/// </summary>
/// <param name="visible">Skip button visibility.</param>
/// <param name="introText">Skip button intro text.</param>
/// <param name="creditsText">Skip button end credits text.</param>
public UserInterfaceConfiguration(bool visible, string introText, string creditsText)
{
SkipButtonVisible = visible;
SkipButtonIntroText = introText;
SkipButtonEndCreditsText = creditsText;
}
/// <summary>
/// Gets or sets a value indicating whether to show the skip intro button.
/// </summary>
public bool SkipButtonVisible { get; set; }
public bool SkipButtonVisible { get; set; } = visible;
/// <summary>
/// Gets or sets the text to display in the skip intro button in introduction mode.
/// </summary>
public string SkipButtonIntroText { get; set; }
public string SkipButtonIntroText { get; set; } = introText;
/// <summary>
/// Gets or sets the text to display in the skip intro button in end credits mode.
/// </summary>
public string SkipButtonEndCreditsText { get; set; }
public string SkipButtonEndCreditsText { get; set; } = creditsText;
}

View File

@ -146,16 +146,16 @@ public class SkipIntroController : ControllerBase
/// <param name="id">Unique identifier of this episode.</param>
/// <param name="mode">Mode.</param>
/// <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
{
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.
var segment = new Intro(timestamp);
var config = Plugin.Instance.Configuration;
var config = Plugin.Instance!.Configuration;
segment.IntroEnd -= config.RemainingSecondsOfIntro;
if (config.PersistSkipButton)
{
@ -219,7 +219,7 @@ public class SkipIntroController : ControllerBase
public ActionResult<List<IntroWithMetadata>> GetAllTimestamps(
[FromQuery] AnalysisMode mode = AnalysisMode.Introduction)
{
List<IntroWithMetadata> intros = new();
List<IntroWithMetadata> intros = [];
var timestamps = mode == AnalysisMode.Introduction ?
Plugin.Instance!.Intros :

View File

@ -135,7 +135,7 @@ public class TroubleshootingController : ControllerBase
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"];
double len = bytes;
@ -144,7 +144,7 @@ public class TroubleshootingController : ControllerBase
while (len >= 1024 && order < sizes.Length - 1)
{
order++;
len = len / 1024;
len /= 1024;
}
return $"{len:0.##} {sizes[order]}";

View File

@ -178,7 +178,7 @@ public class VisualizationController : ControllerBase
return NoContent();
}
private string GetSeasonName(QueuedEpisode episode)
private static string GetSeasonName(QueuedEpisode episode)
{
return "Season " + episode.SeasonNumber.ToString(CultureInfo.InvariantCulture);
}
@ -190,7 +190,7 @@ public class VisualizationController : ControllerBase
/// <param name="season">Season name.</param>
/// <param name="episodes">Episodes.</param>
/// <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)
{
@ -209,7 +209,7 @@ public class VisualizationController : ControllerBase
return true;
}
episodes = new List<QueuedEpisode>();
episodes = [];
return false;
}
}

View File

@ -3,26 +3,20 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Data;
/// <summary>
/// A frame of video that partially (or entirely) consists of black pixels.
/// </summary>
public class BlackFrame
/// <remarks>
/// Initializes a new instance of the <see cref="BlackFrame"/> class.
/// </remarks>
/// <param name="percent">Percentage of the frame that is black.</param>
/// <param name="time">Time this frame appears at.</param>
public class BlackFrame(int percent, double time)
{
/// <summary>
/// Initializes a new instance of the <see cref="BlackFrame"/> class.
/// </summary>
/// <param name="percent">Percentage of the frame that is black.</param>
/// <param name="time">Time this frame appears at.</param>
public BlackFrame(int percent, double time)
{
Percentage = percent;
Time = time;
}
/// <summary>
/// Gets or sets the percentage of the frame that is black.
/// </summary>
public int Percentage { get; set; }
public int Percentage { get; set; } = percent;
/// <summary>
/// Gets or sets the time (in seconds) this frame appeared at.
/// </summary>
public double Time { get; set; }
public double Time { get; set; } = time;
}

View File

@ -5,26 +5,20 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Data;
/// <summary>
/// Episode name and internal ID as returned by the visualization controller.
/// </summary>
public class EpisodeVisualization
/// <remarks>
/// Initializes a new instance of the <see cref="EpisodeVisualization"/> class.
/// </remarks>
/// <param name="id">Episode id.</param>
/// <param name="name">Episode name.</param>
public class EpisodeVisualization(Guid id, string name)
{
/// <summary>
/// Initializes a new instance of the <see cref="EpisodeVisualization"/> class.
/// </summary>
/// <param name="id">Episode id.</param>
/// <param name="name">Episode name.</param>
public EpisodeVisualization(Guid id, string name)
{
Id = id;
Name = name;
}
/// <summary>
/// Gets the id.
/// </summary>
public Guid Id { get; private set; }
public Guid Id { get; private set; } = id;
/// <summary>
/// Gets the name.
/// </summary>
public string Name { get; private set; } = string.Empty;
public string Name { get; private set; } = name;
}

View File

@ -40,7 +40,7 @@ public class QueuedEpisode
/// <summary>
/// Gets or sets a value indicating whether an episode is Anime.
/// </summary>
public bool IsAnime { get; set; } = false;
public bool IsAnime { get; set; }
/// <summary>
/// Gets or sets the timestamp (in seconds) to stop searching for an introduction at.

View File

@ -61,7 +61,7 @@ public class TimeRange : IComparable
/// <returns>int.</returns>
public int CompareTo(object? obj)
{
if (!(obj is TimeRange tr))
if (obj is not TimeRange tr)
{
throw new ArgumentException("obj must be a TimeRange");
}

View File

@ -2,7 +2,6 @@
using System.Collections.Generic;
namespace ConfusedPolarBear.Plugin.IntroSkipper.Data;
#pragma warning restore CA1036
/// <summary>
/// Time range helpers.

View File

@ -5,7 +5,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Data;
/// </summary>
public static class WarningManager
{
private static PluginWarning warnings;
private static PluginWarning _warnings;
/// <summary>
/// Set warning.
@ -13,7 +13,7 @@ public static class WarningManager
/// <param name="warning">Warning.</param>
public static void SetFlag(PluginWarning warning)
{
warnings |= warning;
_warnings |= warning;
}
/// <summary>
@ -21,7 +21,7 @@ public static class WarningManager
/// </summary>
public static void Clear()
{
warnings = PluginWarning.None;
_warnings = PluginWarning.None;
}
/// <summary>
@ -30,7 +30,7 @@ public static class WarningManager
/// <returns>Warnings.</returns>
public static string GetWarnings()
{
return warnings.ToString();
return _warnings.ToString();
}
/// <summary>
@ -40,6 +40,6 @@ public static class WarningManager
/// <returns>True if the flag is set, otherwise false.</returns>
public static bool HasFlag(PluginWarning warning)
{
return (warnings & warning) == warning;
return (_warnings & warning) == warning;
}
}

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
@ -19,38 +18,30 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
/// </summary>
public class Entrypoint : IHostedService, IDisposable
{
private readonly IUserManager _userManager;
private readonly IUserViewManager _userViewManager;
private readonly ITaskManager _taskManager;
private readonly ILibraryManager _libraryManager;
private readonly ILogger<Entrypoint> _logger;
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 _analyzeAgain;
private HashSet<Guid> _seasonsToAnalyze = new HashSet<Guid>();
private static CancellationTokenSource? _cancellationTokenSource;
private static ManualResetEventSlim _autoTaskCompletEvent = new ManualResetEventSlim(false);
/// <summary>
/// Initializes a new instance of the <see cref="Entrypoint"/> class.
/// </summary>
/// <param name="userManager">User manager.</param>
/// <param name="userViewManager">User view manager.</param>
/// <param name="libraryManager">Library manager.</param>
/// <param name="taskManager">Task manager.</param>
/// <param name="logger">Logger.</param>
/// <param name="loggerFactory">Logger factory.</param>
public Entrypoint(
IUserManager userManager,
IUserViewManager userViewManager,
ILibraryManager libraryManager,
ITaskManager taskManager,
ILogger<Entrypoint> logger,
ILoggerFactory loggerFactory)
{
_userManager = userManager;
_userViewManager = userViewManager;
_libraryManager = libraryManager;
_taskManager = taskManager;
_logger = logger;

View File

@ -14,25 +14,24 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
/// <summary>
/// Wrapper for libchromaprint and the silencedetect filter.
/// </summary>
public static class FFmpegWrapper
public static partial class FFmpegWrapper
{
/// <summary>
/// Used with FFmpeg's silencedetect filter to extract the start and end times of silence.
/// </summary>
private static readonly Regex SilenceDetectionExpression = new(
"silence_(?<type>start|end): (?<time>[0-9\\.]+)");
private static readonly Regex _silenceDetectionExpression = SilenceRegex();
/// <summary>
/// Used with FFmpeg's blackframe filter to extract the time and percentage of black pixels.
/// </summary>
private static readonly Regex BlackFrameRegex = new("(pblack|t):[0-9.]+");
private static readonly Regex _blackFrameRegex = BlackFrameRegex();
/// <summary>
/// Gets or sets the logger.
/// </summary>
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();
@ -205,7 +204,7 @@ public static class FFmpegWrapper
* [silencedetect @ 0x000000000000] silence_end: 56.123 | silence_duration: 43.783
*/
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 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.
if (line.StartsWith("[Parsed_blackframe_", StringComparison.OrdinalIgnoreCase))
{
var matches = BlackFrameRegex.Matches(line);
var matches = _blackFrameRegex.Matches(line);
if (matches.Count != 2)
{
continue;
@ -422,47 +421,43 @@ public static class FFmpegWrapper
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);
ffmpeg.Start();
try
{
Logger?.LogDebug("Starting ffmpeg with the following arguments: {Arguments}", ffmpeg.StartInfo.Arguments);
ffmpeg.PriorityClass = Plugin.Instance?.Configuration.ProcessPriority ?? ProcessPriorityClass.BelowNormal;
}
catch (Exception e)
{
Logger?.LogDebug("ffmpeg priority could not be modified. {Message}", e.Message);
}
ffmpeg.Start();
using var ms = new MemoryStream();
var buf = new byte[4096];
int bytesRead;
try
using (var streamReader = stderr ? ffmpeg.StandardError : ffmpeg.StandardOutput)
{
while ((bytesRead = streamReader.BaseStream.Read(buf, 0, buf.Length)) > 0)
{
ffmpeg.PriorityClass = Plugin.Instance?.Configuration.ProcessPriority ?? ProcessPriorityClass.BelowNormal;
}
catch (Exception e)
{
Logger?.LogDebug("ffmpeg priority could not be modified. {Message}", e.Message);
}
using (var ms = new MemoryStream())
{
var buf = new byte[4096];
int bytesRead;
using (var streamReader = stderr ? ffmpeg.StandardError : ffmpeg.StandardOutput)
{
while ((bytesRead = streamReader.BaseStream.Read(buf, 0, buf.Length)) > 0)
{
ms.Write(buf, 0, bytesRead);
}
}
ffmpeg.WaitForExit(timeout);
var output = ms.ToArray();
// If caching is enabled, cache the output of this command.
if (cacheOutput)
{
File.WriteAllBytes(cacheFilename, output);
}
return output;
ms.Write(buf, 0, bytesRead);
}
}
ffmpeg.WaitForExit(timeout);
var output = ms.ToArray();
// If caching is enabled, cache the output of this command.
if (cacheOutput)
{
File.WriteAllBytes(cacheFilename, output);
}
return output;
}
/// <summary>
@ -696,4 +691,10 @@ public static class FFmpegWrapper
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();
}

View File

@ -21,15 +21,15 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
/// <summary>
/// Intro skipper plugin. Uses audio analysis to find common sequences of audio shared between episodes.
/// </summary>
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
public partial class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
{
private readonly object _serializationLock = new();
private readonly object _introsLock = new();
private ILibraryManager _libraryManager;
private IItemRepository _itemRepository;
private ILogger<Plugin> _logger;
private string _introPath;
private string _creditsPath;
private readonly ILibraryManager _libraryManager;
private readonly IItemRepository _itemRepository;
private readonly ILogger<Plugin> _logger;
private readonly string _introPath;
private readonly string _creditsPath;
/// <summary>
/// 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>
public void SaveTimestamps(AnalysisMode mode)
{
List<Intro> introList = new List<Intro>();
List<Intro> introList = [];
var filePath = mode == AnalysisMode.Introduction
? _introPath
: _creditsPath;
@ -256,11 +256,11 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
/// <inheritdoc />
public IEnumerable<PluginPageInfo> GetPages()
{
return new[]
{
return
[
new PluginPageInfo
{
Name = this.Name,
Name = Name,
EmbeddedResourcePath = GetType().Namespace + ".Configuration.configPage.html"
},
new PluginPageInfo
@ -273,7 +273,7 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
Name = "skip-intro-button.js",
EmbeddedResourcePath = GetType().Namespace + ".Configuration.inject.js"
}
};
];
}
/// <summary>
@ -311,7 +311,7 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
/// <param name="id">Item id.</param>
/// <param name="mode">Mode.</param>
/// <returns>Intro.</returns>
internal Intro GetIntroByMode(Guid id, AnalysisMode mode)
internal static Intro GetIntroByMode(Guid id, AnalysisMode mode)
{
return mode == AnalysisMode.Introduction
? Instance!.Intros[id]
@ -353,7 +353,7 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
{
// Handle the case where the item is not found
_logger.LogWarning("Item with ID {Id} not found.", id);
return new List<ChapterInfo>();
return [];
}
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.
// 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);
// Write the modified file contents
@ -455,4 +455,7 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
_logger.LogInformation("Skip intro button successfully added");
}
[GeneratedRegex("</head>", RegexOptions.IgnoreCase)]
private static partial Regex HeadRegex();
}

View File

@ -15,28 +15,18 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
/// <summary>
/// Manages enqueuing library items for analysis.
/// </summary>
public class QueueManager
/// <remarks>
/// Initializes a new instance of the <see cref="QueueManager"/> class.
/// </remarks>
/// <param name="logger">Logger.</param>
/// <param name="libraryManager">Library manager.</param>
public class QueueManager(ILogger<QueueManager> logger, ILibraryManager libraryManager)
{
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.
/// </summary>
/// <param name="logger">Logger.</param>
/// <param name="libraryManager">Library manager.</param>
public QueueManager(ILogger<QueueManager> logger, ILibraryManager libraryManager)
{
_logger = logger;
_libraryManager = libraryManager;
selectedLibraries = new();
_queuedEpisodes = new();
}
private readonly ILibraryManager _libraryManager = libraryManager;
private readonly ILogger<QueueManager> _logger = logger;
private readonly Dictionary<Guid, List<QueuedEpisode>> _queuedEpisodes = [];
private double _analysisPercent;
private List<string> _selectedLibraries = [];
/// <summary>
/// Gets all media items on the server.
@ -52,7 +42,7 @@ public class QueueManager
foreach (var folder in _libraryManager.GetVirtualFolders())
{
// 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);
continue;
@ -100,17 +90,15 @@ public class QueueManager
var config = Plugin.Instance!.Configuration;
// 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.
selectedLibraries = config.SelectedLibraries
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.ToList();
_selectedLibraries = [.. config.SelectedLibraries.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)];
// 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
{
@ -136,7 +124,7 @@ public class QueueManager
{
// Order by series name, season, and then episode number so that status updates are logged in order
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],
Recursive = true,
IsVirtualItem = false
@ -184,7 +172,7 @@ public class QueueManager
// Allocate a new list for each new season
if (!_queuedEpisodes.TryGetValue(episode.SeasonId, out var seasonEpisodes))
{
seasonEpisodes = new List<QueuedEpisode>();
seasonEpisodes = [];
_queuedEpisodes[episode.SeasonId] = seasonEpisodes;
}
@ -202,7 +190,7 @@ public class QueueManager
// X and Y default to 25% and 10 minutes.
var duration = TimeSpan.FromTicks(episode.RunTimeTicks ?? 0).TotalSeconds;
var fingerprintDuration = Math.Min(
duration >= 5 * 60 ? duration * analysisPercent : duration,
duration >= 5 * 60 ? duration * _analysisPercent : duration,
60 * pluginInstance.Configuration.AnalysisLengthLimit);
// Queue the episode for analysis

View File

@ -134,7 +134,7 @@ public class BaseItemAnalyzerTask
first.SeasonNumber);
Interlocked.Add(ref totalProcessed, episodeCount * modeCount); // Update total Processed directly
progress.Report((totalProcessed * 100) / totalQueued);
progress.Report(totalProcessed * 100 / totalQueued);
return;
}
@ -142,7 +142,7 @@ public class BaseItemAnalyzerTask
if (modeCount != requiredModeCount)
{
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
@ -159,7 +159,7 @@ public class BaseItemAnalyzerTask
writeEdl = analyzed > 0 || Plugin.Instance.Configuration.RegenerateEdlFiles;
progress.Report((totalProcessed * 100) / totalQueued);
progress.Report(totalProcessed * 100 / totalQueued);
}
}
catch (FingerprintException ex)
@ -219,9 +219,10 @@ public class BaseItemAnalyzerTask
first.SeriesName,
first.SeasonNumber);
var analyzers = new Collection<IMediaFileAnalyzer>();
analyzers.Add(new ChapterAnalyzer(_loggerFactory.CreateLogger<ChapterAnalyzer>()));
var analyzers = new Collection<IMediaFileAnalyzer>
{
new ChapterAnalyzer(_loggerFactory.CreateLogger<ChapterAnalyzer>())
};
if (first.IsAnime)
{
if (Plugin.Instance!.Configuration.UseChromaprint)

View File

@ -112,6 +112,6 @@ public class CleanCacheTask : IScheduledTask
/// <returns>Task triggers.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return Array.Empty<TaskTriggerInfo>();
return [];
}
}

View File

@ -102,6 +102,6 @@ public class DetectCreditsTask : IScheduledTask
/// <returns>Task triggers.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return Array.Empty<TaskTriggerInfo>();
return [];
}
}

View File

@ -101,13 +101,13 @@ public class DetectIntrosCreditsTask : IScheduledTask
/// <returns>Task triggers.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[]
{
return
[
new TaskTriggerInfo
{
Type = TaskTriggerInfo.TriggerDaily,
TimeOfDayTicks = TimeSpan.FromHours(0).Ticks
}
};
];
}
}

View File

@ -101,6 +101,6 @@ public class DetectIntrosTask : IScheduledTask
/// <returns>Task triggers.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return Array.Empty<TaskTriggerInfo>();
return [];
}
}

View File

@ -5,9 +5,9 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
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()
{