Reformat code to comply with StyleCop analyzers

This commit is contained in:
ConfusedPolarBear 2022-05-09 22:50:41 -05:00
parent 928f467871
commit 547a2c705b
10 changed files with 149 additions and 106 deletions

View File

@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MediaBrowser.Model.Plugins;
namespace ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
@ -19,17 +15,17 @@ public class PluginConfiguration : BasePluginConfiguration
}
/// <summary>
/// If the output of fpcalc should be cached to the filesystem.
/// Gets or sets a value indicating whether the output of fpcalc should be cached to the filesystem.
/// </summary>
public bool CacheFingerprints { get; set; }
/// <summary>
/// Seconds before the intro starts to show the skip prompt at.
/// Gets or sets the seconds before the intro starts to show the skip prompt at.
/// </summary>
public int ShowPromptAdjustment { get; set; } = 5;
/// <summary>
/// Seconds after the intro starts to hide the skip prompt at.
/// Gets or sets the seconds after the intro starts to hide the skip prompt at.
/// </summary>
public int HidePromptAdjustment { get; set; } = 10;
}

View File

@ -14,7 +14,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Controllers;
public class SkipIntroController : ControllerBase
{
/// <summary>
/// Constructor.
/// Initializes a new instance of the <see cref="SkipIntroController"/> class.
/// </summary>
public SkipIntroController()
{
@ -26,6 +26,7 @@ public class SkipIntroController : ControllerBase
/// <param name="id">ID of the episode. Required.</param>
/// <response code="200">Episode contains an intro.</response>
/// <response code="404">Failed to find an intro in the provided episode.</response>
/// <returns>Detected intro.</returns>
[HttpGet("Episode/{id}/IntroTimestamps")]
public ActionResult<Intro> GetIntroTimestamps([FromRoute] Guid id)
{

View File

@ -5,25 +5,29 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
/// <summary>
/// Exception raised when an error is encountered analyzing audio.
/// </summary>
public class FingerprintException: Exception {
public class FingerprintException : Exception
{
/// <summary>
/// Constructor.
/// Initializes a new instance of the <see cref="FingerprintException"/> class.
/// </summary>
public FingerprintException()
{
}
/// <summary>
/// Constructor.
/// Initializes a new instance of the <see cref="FingerprintException"/> class.
/// </summary>
public FingerprintException(string message): base(message)
/// <param name="message">Exception message.</param>
public FingerprintException(string message) : base(message)
{
}
/// <summary>
/// Constructor.
/// Initializes a new instance of the <see cref="FingerprintException"/> class.
/// </summary>
public FingerprintException(string message, Exception inner): base(message, inner)
/// <param name="message">Exception message.</param>
/// <param name="inner">Inner exception.</param>
public FingerprintException(string message, Exception inner) : base(message, inner)
{
}
}

View File

@ -6,34 +6,36 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
/// Result of fingerprinting and analyzing two episodes in a season.
/// All times are measured in seconds relative to the beginning of the media file.
/// </summary>
public class Intro {
public class Intro
{
/// <summary>
/// Episode ID.
/// Gets or sets the Episode ID.
/// </summary>
public Guid EpisodeId { get; set; }
/// <summary>
/// If this introduction is valid or not. Invalid results should not be returned through the API.
/// Gets or sets a value indicating whether this introduction is valid or not.
/// Invalid results must not be returned through the API.
/// </summary>
public bool Valid { get; set; }
/// <summary>
/// Introduction sequence start time.
/// Gets or sets the introduction sequence start time.
/// </summary>
public double IntroStart { get; set; }
/// <summary>
/// Introduction sequence end time.
/// Gets or sets the introduction sequence end time.
/// </summary>
public double IntroEnd { get; set; }
/// <summary>
/// Recommended time to display the skip intro prompt.
/// Gets or sets the recommended time to display the skip intro prompt.
/// </summary>
public double ShowSkipPromptAt { get; set; }
/// <summary>
/// Recommended time to hide the skip intro prompt.
/// Gets or sets the recommended time to hide the skip intro prompt.
/// </summary>
public double HideSkipPromptAt { get; set; }
}

View File

@ -5,29 +5,30 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
/// <summary>
/// Episode queued for analysis.
/// </summary>
public class QueuedEpisode {
public class QueuedEpisode
{
/// <summary>
/// Series name.
/// Gets or sets the series name.
/// </summary>
public string SeriesName { get; set; } = "";
public string SeriesName { get; set; } = string.Empty;
/// <summary>
/// Season number.
/// Gets or sets the season number.
/// </summary>
public int SeasonNumber { get; set; }
/// <summary>
/// Episode id.
/// Gets or sets the episode id.
/// </summary>
public Guid EpisodeId { get; set; }
/// <summary>
/// Full path to episode.
/// Gets or sets the full path to episode.
/// </summary>
public string Path { get; set; } = "";
public string Path { get; set; } = string.Empty;
/// <summary>
/// Seconds of media file to fingerprint.
/// Gets or sets the seconds of media file to fingerprint.
/// </summary>
public int FingerprintDuration { get; set; }
}

View File

@ -9,22 +9,22 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
public class TimeRange : IComparable
{
/// <summary>
/// Time range start (in seconds).
/// Gets or sets the time range start (in seconds).
/// </summary>
public double Start { get; set; }
/// <summary>
/// Time range end (in seconds).
/// Gets or sets the time range end (in seconds).
/// </summary>
public double End { get; set; }
/// <summary>
/// Duration of this time range (in seconds).
/// Gets the duration of this time range (in seconds).
/// </summary>
public double Duration => End - Start;
/// <summary>
/// Default constructor.
/// Initializes a new instance of the <see cref="TimeRange"/> class.
/// </summary>
public TimeRange()
{
@ -33,8 +33,10 @@ public class TimeRange : IComparable
}
/// <summary>
/// Constructor.
/// Initializes a new instance of the <see cref="TimeRange"/> class.
/// </summary>
/// <param name="start">Time range start.</param>
/// <param name="end">Time range end.</param>
public TimeRange(double start, double end)
{
Start = start;
@ -42,8 +44,9 @@ public class TimeRange : IComparable
}
/// <summary>
/// Copy constructor.
/// Initializes a new instance of the <see cref="TimeRange"/> class.
/// </summary>
/// <param name="original">Original TimeRange.</param>
public TimeRange(TimeRange original)
{
Start = original.Start;
@ -54,6 +57,7 @@ public class TimeRange : IComparable
/// Compares this TimeRange to another TimeRange.
/// </summary>
/// <param name="obj">Other object to compare against.</param>
/// <returns>A signed integer that indicates whether this instance precedes, follows, or appears in the same position in the sort order as the obj parameter.</returns>
public int CompareTo(object? obj)
{
if (obj is not TimeRange tr)
@ -128,6 +132,7 @@ public static class TimeRangeHelpers
/// </summary>
/// <param name="times">Sorted timestamps to search.</param>
/// <param name="maximumDistance">Maximum distance permitted between contiguous timestamps.</param>
/// <returns>The longest contiguous time range (if one was found), or null (if none was found).</returns>
public static TimeRange? FindContiguous(double[] times, double maximumDistance)
{
if (times.Length == 0)

View File

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
@ -9,7 +9,6 @@ using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Library;
using Microsoft.Extensions.Logging;
using Jellyfin.Data.Enums;
namespace ConfusedPolarBear.Plugin.IntroSkipper;
@ -26,7 +25,7 @@ public class Entrypoint : IServerEntryPoint
private readonly object _queueLock = new object();
/// <summary>
/// Constructor.
/// Initializes a new instance of the <see cref="Entrypoint"/> class.
/// </summary>
/// <param name="userManager">User manager.</param>
/// <param name="userViewManager">User view manager.</param>
@ -47,12 +46,14 @@ public class Entrypoint : IServerEntryPoint
/// <summary>
/// Registers event handler.
/// </summary>
/// <returns>Task.</returns>
public Task RunAsync()
{
FPCalc.Logger = _logger;
// Assert that fpcalc is installed
if (!FPCalc.CheckFPCalcInstalled()) {
if (!FPCalc.CheckFPCalcInstalled())
{
_logger.LogError("fpcalc is not installed on this system - episodes will not be analyzed");
return Task.CompletedTask;
}
@ -61,8 +62,10 @@ public class Entrypoint : IServerEntryPoint
_libraryManager.ItemAdded += ItemAdded;
// For all TV show libraries, enqueue all contained items.
foreach (var folder in _libraryManager.GetVirtualFolders()) {
if (folder.CollectionType != CollectionTypeOptions.TvShows) {
foreach (var folder in _libraryManager.GetVirtualFolders())
{
if (folder.CollectionType != CollectionTypeOptions.TvShows)
{
continue;
}
@ -77,25 +80,30 @@ public class Entrypoint : IServerEntryPoint
return Task.CompletedTask;
}
private void QueueLibraryContents(string rawId) {
private void QueueLibraryContents(string rawId)
{
// FIXME: do smarterer
var query = new UserViewQuery() {
var query = new UserViewQuery()
{
UserId = GetAdministrator(),
};
// Get all items from this library. Since intros may change within a season, sort the items before adding them.
var folder = _userViewManager.GetUserViews(query)[0];
var items = folder.GetItems(new InternalItemsQuery() {
var items = folder.GetItems(new InternalItemsQuery()
{
ParentId = Guid.Parse(rawId),
OrderBy = new [] { ("SortName", SortOrder.Ascending) },
OrderBy = new[] { ("SortName", SortOrder.Ascending) },
IncludeItemTypes = new BaseItemKind[] { BaseItemKind.Episode },
Recursive = true,
});
// Queue all episodes on the server for fingerprinting.
foreach (var item in items.Items) {
if (item is not Episode episode) {
foreach (var item in items.Items)
{
if (item is not Episode episode)
{
_logger.LogError("Item {Name} is not an episode", item.Name);
continue;
}
@ -111,7 +119,8 @@ public class Entrypoint : IServerEntryPoint
/// <param name="e">ItemChangeEventArgs.</param>
private void ItemAdded(object? sender, ItemChangeEventArgs e)
{
if (e.Item is not Episode episode) {
if (e.Item is not Episode episode)
{
return;
}
@ -120,28 +129,34 @@ public class Entrypoint : IServerEntryPoint
QueueEpisode(episode);
}
private void QueueEpisode(Episode episode) {
if (Plugin.Instance is null) {
private void QueueEpisode(Episode episode)
{
if (Plugin.Instance is null)
{
throw new InvalidOperationException("plugin instance was null");
}
lock (_queueLock) {
lock (_queueLock)
{
var queue = Plugin.Instance.AnalysisQueue;
// Allocate a new list for each new season
if (!queue.ContainsKey(episode.SeasonId)) {
if (!queue.ContainsKey(episode.SeasonId))
{
Plugin.Instance.AnalysisQueue[episode.SeasonId] = new List<QueuedEpisode>();
}
// Only fingerprint up to 25% of the episode and at most 10 minutes.
var duration = TimeSpan.FromTicks(episode.RunTimeTicks ?? 0).TotalSeconds;
if (duration >= 5*60) {
if (duration >= 5 * 60)
{
duration /= 4;
}
duration = Math.Min(duration, 10 * 60);
Plugin.Instance.AnalysisQueue[episode.SeasonId].Add(new QueuedEpisode() {
Plugin.Instance.AnalysisQueue[episode.SeasonId].Add(new QueuedEpisode()
{
SeriesName = episode.SeriesName,
SeasonNumber = episode.AiredSeasonNumber ?? 0,
EpisodeId = episode.Id,
@ -156,9 +171,12 @@ public class Entrypoint : IServerEntryPoint
/// <summary>
/// FIXME: don't do this.
/// </summary>
private Guid GetAdministrator() {
foreach (var user in _userManager.Users) {
if (!user.HasPermission(Jellyfin.Data.Enums.PermissionKind.IsAdministrator)) {
private Guid GetAdministrator()
{
foreach (var user in _userManager.Users)
{
if (!user.HasPermission(Jellyfin.Data.Enums.PermissionKind.IsAdministrator))
{
continue;
}

View File

@ -12,20 +12,22 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
/// <summary>
/// Wrapper for the fpcalc utility.
/// </summary>
public static class FPCalc {
public static class FPCalc
{
/// <summary>
/// Logger.
/// Gets or sets the logger.
/// </summary>
public static ILogger? Logger { get; set; }
/// <summary>
/// Check that the fpcalc utility is installed.
/// </summary>
/// <returns>true if fpcalc is installed, false on any error.</returns>
public static bool CheckFPCalcInstalled()
{
try
{
var version = getOutput("-version", 2000);
var version = GetOutput("-version", 2000);
Logger?.LogDebug("fpcalc version: {Version}", version);
return version.StartsWith("fpcalc version", StringComparison.OrdinalIgnoreCase);
}
@ -39,10 +41,11 @@ public static class FPCalc {
/// Fingerprint a queued episode.
/// </summary>
/// <param name="episode">Queued episode to fingerprint.</param>
/// <returns>Numerical fingerprint points.</returns>
public static ReadOnlyCollection<uint> Fingerprint(QueuedEpisode episode)
{
// Try to load this episode from cache before running fpcalc.
if (loadCachedFingerprint(episode, out ReadOnlyCollection<uint> cachedFingerprint))
if (LoadCachedFingerprint(episode, out ReadOnlyCollection<uint> cachedFingerprint))
{
Logger?.LogDebug("Fingerprint cache hit on {File}", episode.Path);
return cachedFingerprint;
@ -60,7 +63,7 @@ public static class FPCalc {
* FINGERPRINT=123456789,987654321,123456789,987654321,123456789,987654321
*/
var raw = getOutput(args);
var raw = GetOutput(args);
var lines = raw.Split("\n");
if (lines.Length < 2)
@ -79,7 +82,7 @@ public static class FPCalc {
}
// Try to cache this fingerprint.
cacheFingerprint(episode, results);
CacheFingerprint(episode, results);
return results.AsReadOnly();
}
@ -89,7 +92,7 @@ public static class FPCalc {
/// </summary>
/// <param name="args">Arguments to pass to fpcalc.</param>
/// <param name="timeout">Timeout (in seconds) to wait for fpcalc to exit.</param>
private static string getOutput(string args, int timeout = 60 * 1000)
private static string GetOutput(string args, int timeout = 60 * 1000)
{
var info = new ProcessStartInfo("fpcalc", args);
info.CreateNoWindow = true;
@ -110,7 +113,7 @@ public static class FPCalc {
/// <param name="episode">Episode to try to load from cache.</param>
/// <param name="fingerprint">ReadOnlyCollection to store the fingerprint in.</param>
/// <returns>true if the episode was successfully loaded from cache, false on any other error.</returns>
private static bool loadCachedFingerprint(QueuedEpisode episode, out ReadOnlyCollection<uint> fingerprint)
private static bool LoadCachedFingerprint(QueuedEpisode episode, out ReadOnlyCollection<uint> fingerprint)
{
fingerprint = new List<uint>().AsReadOnly();
@ -120,7 +123,7 @@ public static class FPCalc {
return false;
}
var path = getFingerprintCachePath(episode);
var path = GetFingerprintCachePath(episode);
// If this episode isn't cached, bail out.
if (!File.Exists(path))
@ -148,7 +151,7 @@ public static class FPCalc {
/// </summary>
/// <param name="episode">Episode to store in cache.</param>
/// <param name="fingerprint">Fingerprint of the episode to store.</param>
private static void cacheFingerprint(QueuedEpisode episode, List<uint> fingerprint)
private static void CacheFingerprint(QueuedEpisode episode, List<uint> fingerprint)
{
// Bail out if caching isn't enabled.
if (!(Plugin.Instance?.Configuration.CacheFingerprints ?? false))
@ -164,14 +167,14 @@ public static class FPCalc {
}
// Cache the episode.
File.WriteAllLinesAsync(getFingerprintCachePath(episode), lines, Encoding.UTF8).ConfigureAwait(false);
File.WriteAllLinesAsync(GetFingerprintCachePath(episode), lines, Encoding.UTF8).ConfigureAwait(false);
}
/// <summary>
/// Determines the path an episode should be cached at.
/// </summary>
/// <param name="episode">Episode.</param>
private static string getFingerprintCachePath(QueuedEpisode episode)
private static string GetFingerprintCachePath(QueuedEpisode episode)
{
return Path.Join(Plugin.Instance!.FingerprintCachePath, episode.EpisodeId.ToString("N"));
}

View File

@ -92,22 +92,22 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
}
/// <summary>
/// Results of fingerprinting all episodes.
/// Gets the results of fingerprinting all episodes.
/// </summary>
public Dictionary<Guid, Intro> Intros { get; }
/// <summary>
/// Map of season ids to episodes that have been queued for fingerprinting.
/// Gets the mapping of season ids to episodes that have been queued for fingerprinting.
/// </summary>
public Dictionary<Guid, List<QueuedEpisode>> AnalysisQueue { get; }
/// <summary>
/// Total number of episodes in the queue.
/// Gets or sets the total number of episodes in the queue.
/// </summary>
public int TotalQueued { get; set; }
/// <summary>
/// Directory to cache fingerprints in.
/// Gets the directory to cache fingerprints in.
/// </summary>
public string FingerprintCachePath { get; private set; }

View File

@ -11,7 +11,8 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
/// <summary>
/// Fingerprint all queued episodes at the set time.
/// </summary>
public class FingerprinterTask : IScheduledTask {
public class FingerprinterTask : IScheduledTask
{
private readonly ILogger<FingerprinterTask> _logger;
/// <summary>
@ -35,35 +36,36 @@ public class FingerprinterTask : IScheduledTask {
private const double SAMPLES_TO_SECONDS = 0.128;
/// <summary>
/// Gets or sets the last detected intro sequence. Only populated when a unit test is running.
/// Gets the last detected intro sequence. Only populated when a unit test is running.
/// </summary>
public static Intro LastIntro { get; private set; } = new Intro();
/// <summary>
/// Constructor.
/// Initializes a new instance of the <see cref="FingerprinterTask"/> class.
/// </summary>
/// <param name="logger">Logger.</param>
public FingerprinterTask(ILogger<FingerprinterTask> logger)
{
_logger = logger;
}
/// <summary>
/// Task name.
/// Gets the task name.
/// </summary>
public string Name => "Analyze episodes";
/// <summary>
/// Task category.
/// Gets the task category.
/// </summary>
public string Category => "Intro Skipper";
/// <summary>
/// Task description.
/// Gets the task description.
/// </summary>
public string Description => "Analyzes the audio of all television episodes to find introduction sequences.";
/// <summary>
/// Key.
/// Gets the task key.
/// </summary>
public string Key => "CPBIntroSkipperRunFingerprinter";
@ -72,12 +74,14 @@ public class FingerprinterTask : IScheduledTask {
/// </summary>
/// <param name="progress">Progress.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Task.</returns>
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
var queue = Plugin.Instance!.AnalysisQueue;
var totalProcessed = 0;
foreach (var season in queue) {
foreach (var season in queue)
{
var first = season.Value[0];
// Don't analyze seasons with <= 1 episode or specials
@ -94,7 +98,8 @@ public class FingerprinterTask : IScheduledTask {
// Ensure there are an even number of episodes
var episodes = season.Value;
if (episodes.Count % 2 != 0) {
if (episodes.Count % 2 != 0)
{
episodes.Add(episodes[episodes.Count - 2]);
}
@ -109,7 +114,7 @@ public class FingerprinterTask : IScheduledTask {
}
var lhs = episodes[i];
var rhs = episodes[i+1];
var rhs = episodes[i + 1];
// TODO: make configurable
if (!everFoundIntro && failures >= 6)
@ -190,7 +195,7 @@ public class FingerprinterTask : IScheduledTask {
var limit = Math.Min(lhs.Count, rhs.Count);
// First, test if an intro can be found within the first 5 seconds of the episodes (±5/0.128 = ±40 samples).
var (lhsContiguous, rhsContiguous) = shiftEpisodes(lhs, rhs, -40, 40);
var (lhsContiguous, rhsContiguous) = ShiftEpisodes(lhs, rhs, -40, 40);
lhsRanges.AddRange(lhsContiguous);
rhsRanges.AddRange(rhsContiguous);
@ -199,7 +204,7 @@ public class FingerprinterTask : IScheduledTask {
{
_logger.LogDebug("using full scan");
(lhsContiguous, rhsContiguous) = shiftEpisodes(lhs, rhs, -1 * limit, limit);
(lhsContiguous, rhsContiguous) = ShiftEpisodes(lhs, rhs, -1 * limit, limit);
lhsRanges.AddRange(lhsContiguous);
rhsRanges.AddRange(rhsContiguous);
}
@ -219,8 +224,8 @@ public class FingerprinterTask : IScheduledTask {
// TODO: is this the optimal way to indicate that an intro couldn't be found?
// the goal here is to not waste time every task run reprocessing episodes that we know will fail.
storeIntro(lhsEpisode.EpisodeId, 0, 0);
storeIntro(rhsEpisode.EpisodeId, 0, 0);
StoreIntro(lhsEpisode.EpisodeId, 0, 0);
StoreIntro(rhsEpisode.EpisodeId, 0, 0);
return false;
}
@ -243,8 +248,8 @@ public class FingerprinterTask : IScheduledTask {
rhsIntro.Start = 0;
}
storeIntro(lhsEpisode.EpisodeId, lhsIntro.Start, lhsIntro.End);
storeIntro(rhsEpisode.EpisodeId, rhsIntro.Start, rhsIntro.End);
StoreIntro(lhsEpisode.EpisodeId, lhsIntro.Start, lhsIntro.End);
StoreIntro(rhsEpisode.EpisodeId, rhsIntro.Start, rhsIntro.End);
return true;
}
@ -256,18 +261,18 @@ public class FingerprinterTask : IScheduledTask {
/// <param name="rhs">Second episode fingerprint.</param>
/// <param name="lower">Lower end of the shift range.</param>
/// <param name="upper">Upper end of the shift range.</param>
private static (List<TimeRange>, List<TimeRange>) shiftEpisodes(
private static (List<TimeRange> Lhs, List<TimeRange> Rhs) ShiftEpisodes(
ReadOnlyCollection<uint> lhs,
ReadOnlyCollection<uint> rhs,
int lower,
int upper
) {
int upper)
{
var lhsRanges = new List<TimeRange>();
var rhsRanges = new List<TimeRange>();
for (int amount = lower; amount <= upper; amount++)
{
var (lRange, rRange) = findContiguous(lhs, rhs, amount);
var (lRange, rRange) = FindContiguous(lhs, rhs, amount);
if (lRange.End == 0 && rRange.End == 0)
{
@ -287,18 +292,21 @@ public class FingerprinterTask : IScheduledTask {
/// <param name="lhs">First fingerprint to compare.</param>
/// <param name="rhs">Second fingerprint to compare.</param>
/// <param name="shiftAmount">Amount to shift one fingerprint by.</param>
private static (TimeRange, TimeRange) findContiguous(
private static (TimeRange Lhs, TimeRange Rhs) FindContiguous(
ReadOnlyCollection<uint> lhs,
ReadOnlyCollection<uint> rhs,
int shiftAmount
) {
int shiftAmount)
{
var leftOffset = 0;
var rightOffset = 0;
// Calculate the offsets for the left and right hand sides.
if (shiftAmount < 0) {
if (shiftAmount < 0)
{
leftOffset -= shiftAmount;
} else {
}
else
{
rightOffset += shiftAmount;
}
@ -308,14 +316,15 @@ public class FingerprinterTask : IScheduledTask {
var upperLimit = Math.Min(lhs.Count, rhs.Count) - Math.Abs(shiftAmount);
// XOR all elements in LHS and RHS, using the shift amount from above.
for (var i = 0; i < upperLimit; i++) {
for (var i = 0; i < upperLimit; i++)
{
// XOR both samples at the current position.
var lhsPosition = i + leftOffset;
var rhsPosition = i + rightOffset;
var diff = lhs[lhsPosition] ^ rhs[rhsPosition];
// If the difference between the samples is small, flag both times as similar.
if (countBits(diff) > MAXIMUM_DIFFERENCES)
if (CountBits(diff) > MAXIMUM_DIFFERENCES)
{
continue;
}
@ -328,8 +337,8 @@ public class FingerprinterTask : IScheduledTask {
}
// Ensure the last timestamp is checked
lhsTimes.Add(Double.MaxValue);
rhsTimes.Add(Double.MaxValue);
lhsTimes.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.
var lContiguous = TimeRangeHelpers.FindContiguous(lhsTimes.ToArray(), MAXIMUM_DISTANCE);
@ -356,7 +365,7 @@ public class FingerprinterTask : IScheduledTask {
return (lContiguous, rContiguous);
}
private static void storeIntro(Guid episode, double introStart, double introEnd)
private static void StoreIntro(Guid episode, double introStart, double introEnd)
{
var intro = new Intro()
{
@ -375,12 +384,15 @@ public class FingerprinterTask : IScheduledTask {
Plugin.Instance.Intros[episode] = intro;
}
private static int countBits(uint number) {
private static int CountBits(uint number)
{
var count = 0;
for (var i = 0; i < 32; i++) {
for (var i = 0; i < 32; i++)
{
var low = (number >> i) & 1;
if (low == 1) {
if (low == 1)
{
count++;
}
}
@ -391,6 +403,7 @@ public class FingerprinterTask : IScheduledTask {
/// <summary>
/// Get task triggers.
/// </summary>
/// <returns>Task triggers.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[]