From f68fd9d06af04919c3dd28c93510d5f76853eb0a Mon Sep 17 00:00:00 2001 From: rlauu <46294892+rlauu@users.noreply.github.com> Date: Wed, 6 Nov 2024 10:30:00 +0100 Subject: [PATCH] Fix bug where timestamps were reset to zero. --- IntroSkipper/Analyzers/AnalyzerHelper.cs | 17 ++++----- IntroSkipper/Analyzers/BlackFrameAnalyzer.cs | 14 ++++---- IntroSkipper/Analyzers/ChapterAnalyzer.cs | 13 +++---- IntroSkipper/Analyzers/ChromaprintAnalyzer.cs | 4 +-- .../Controllers/SkipIntroController.cs | 8 ++--- IntroSkipper/Data/QueuedEpisode.cs | 8 ++--- IntroSkipper/Data/Segment.cs | 6 ++-- IntroSkipper/Manager/QueueManager.cs | 8 +++-- IntroSkipper/Plugin.cs | 35 ++++++++++--------- .../ScheduledTasks/BaseItemAnalyzerTask.cs | 13 +++---- 10 files changed, 63 insertions(+), 63 deletions(-) diff --git a/IntroSkipper/Analyzers/AnalyzerHelper.cs b/IntroSkipper/Analyzers/AnalyzerHelper.cs index b572676..70133a9 100644 --- a/IntroSkipper/Analyzers/AnalyzerHelper.cs +++ b/IntroSkipper/Analyzers/AnalyzerHelper.cs @@ -36,20 +36,21 @@ namespace IntroSkipper.Analyzers /// Original introductions. /// Analysis mode. /// Modified Intro Timestamps. - public Dictionary AdjustIntroTimes( + public IReadOnlyList AdjustIntroTimes( IReadOnlyList episodes, - IReadOnlyDictionary originalIntros, + IReadOnlyList originalIntros, AnalysisMode mode) { - return episodes - .Where(episode => originalIntros.TryGetValue(episode.EpisodeId, out var _)) - .ToDictionary( - episode => episode.EpisodeId, - episode => AdjustIntroForEpisode(episode, originalIntros[episode.EpisodeId], mode)); + return originalIntros.Select(i => AdjustIntroForEpisode(episodes.FirstOrDefault(e => originalIntros.Any(i => i.EpisodeId == e.EpisodeId)), i, mode)).ToList(); } - private Segment AdjustIntroForEpisode(QueuedEpisode episode, Segment originalIntro, AnalysisMode mode) + private Segment AdjustIntroForEpisode(QueuedEpisode? episode, Segment originalIntro, AnalysisMode mode) { + if (episode is null) + { + return new Segment(originalIntro.EpisodeId); + } + _logger.LogTrace("{Name} original intro: {Start} - {End}", episode.Name, originalIntro.Start, originalIntro.End); var adjustedIntro = new Segment(originalIntro); diff --git a/IntroSkipper/Analyzers/BlackFrameAnalyzer.cs b/IntroSkipper/Analyzers/BlackFrameAnalyzer.cs index f5ea273..ae11304 100644 --- a/IntroSkipper/Analyzers/BlackFrameAnalyzer.cs +++ b/IntroSkipper/Analyzers/BlackFrameAnalyzer.cs @@ -37,9 +37,7 @@ public class BlackFrameAnalyzer(ILogger logger) : IMediaFile throw new NotImplementedException("mode must equal Credits"); } - var creditTimes = new Dictionary(); - - var episodeAnalysisQueue = new List(analysisQueue); + var creditTimes = new List(); bool isFirstEpisode = true; @@ -47,7 +45,7 @@ public class BlackFrameAnalyzer(ILogger logger) : IMediaFile var searchDistance = 2 * _minimumCreditsDuration; - foreach (var episode in episodeAnalysisQueue.Where(e => !e.GetAnalyzed(mode))) + foreach (var episode in analysisQueue.Where(e => !e.GetAnalyzed(mode))) { if (cancellationToken.IsCancellationRequested) { @@ -116,16 +114,16 @@ public class BlackFrameAnalyzer(ILogger logger) : IMediaFile searchStart = episode.Duration - credit.Start + (0.5 * searchDistance); - creditTimes.Add(episode.EpisodeId, credit); + creditTimes.Add(credit); episode.SetAnalyzed(mode, true); } var analyzerHelper = new AnalyzerHelper(_logger); - creditTimes = analyzerHelper.AdjustIntroTimes(analysisQueue, creditTimes, mode); + var adjustedCreditTimes = analyzerHelper.AdjustIntroTimes(analysisQueue, creditTimes, mode); - await Plugin.Instance!.UpdateTimestamps(creditTimes, mode).ConfigureAwait(false); + await Plugin.Instance!.UpdateTimestamps(adjustedCreditTimes, mode).ConfigureAwait(false); - return episodeAnalysisQueue; + return analysisQueue; } /// diff --git a/IntroSkipper/Analyzers/ChapterAnalyzer.cs b/IntroSkipper/Analyzers/ChapterAnalyzer.cs index efb272e..653c42c 100644 --- a/IntroSkipper/Analyzers/ChapterAnalyzer.cs +++ b/IntroSkipper/Analyzers/ChapterAnalyzer.cs @@ -24,7 +24,7 @@ namespace IntroSkipper.Analyzers; /// Logger. public class ChapterAnalyzer(ILogger logger) : IMediaFileAnalyzer { - private ILogger _logger = logger; + private readonly ILogger _logger = logger; /// public async Task> AnalyzeMediaFiles( @@ -32,10 +32,7 @@ public class ChapterAnalyzer(ILogger logger) : IMediaFileAnalyz AnalysisMode mode, CancellationToken cancellationToken) { - var skippableRanges = new Dictionary(); - - // Episode analysis queue. - var episodeAnalysisQueue = new List(analysisQueue); + var skippableRanges = new List(); var expression = mode == AnalysisMode.Introduction ? Plugin.Instance!.Configuration.ChapterAnalyzerIntroductionPattern : @@ -46,7 +43,7 @@ public class ChapterAnalyzer(ILogger logger) : IMediaFileAnalyz return analysisQueue; } - foreach (var episode in episodeAnalysisQueue.Where(e => !e.GetAnalyzed(mode))) + foreach (var episode in analysisQueue.Where(e => !e.GetAnalyzed(mode))) { if (cancellationToken.IsCancellationRequested) { @@ -64,13 +61,13 @@ public class ChapterAnalyzer(ILogger logger) : IMediaFileAnalyz continue; } - skippableRanges.Add(episode.EpisodeId, skipRange); + skippableRanges.Add(skipRange); episode.SetAnalyzed(mode, true); } await Plugin.Instance.UpdateTimestamps(skippableRanges, mode).ConfigureAwait(false); - return episodeAnalysisQueue; + return analysisQueue; } /// diff --git a/IntroSkipper/Analyzers/ChromaprintAnalyzer.cs b/IntroSkipper/Analyzers/ChromaprintAnalyzer.cs index e6159a6..95a75d2 100644 --- a/IntroSkipper/Analyzers/ChromaprintAnalyzer.cs +++ b/IntroSkipper/Analyzers/ChromaprintAnalyzer.cs @@ -208,9 +208,9 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer // Adjust all introduction times. var analyzerHelper = new AnalyzerHelper(_logger); - seasonIntros = analyzerHelper.AdjustIntroTimes(analysisQueue, seasonIntros, _analysisMode); + var adjustedSeasonIntros = analyzerHelper.AdjustIntroTimes(analysisQueue, [.. seasonIntros.Values], _analysisMode); - await Plugin.Instance!.UpdateTimestamps(seasonIntros, _analysisMode).ConfigureAwait(false); + await Plugin.Instance!.UpdateTimestamps(adjustedSeasonIntros, _analysisMode).ConfigureAwait(false); return episodeAnalysisQueue; } diff --git a/IntroSkipper/Controllers/SkipIntroController.cs b/IntroSkipper/Controllers/SkipIntroController.cs index 5911fe6..fdc807a 100644 --- a/IntroSkipper/Controllers/SkipIntroController.cs +++ b/IntroSkipper/Controllers/SkipIntroController.cs @@ -75,14 +75,14 @@ public class SkipIntroController(MediaSegmentUpdateManager mediaSegmentUpdateMan if (timestamps?.Introduction.End > 0.0) { - var tr = new TimeRange(timestamps.Introduction.Start, timestamps.Introduction.End); - await Plugin.Instance!.UpdateTimestamps(new Dictionary { [id] = new Segment(id, tr) }, AnalysisMode.Introduction).ConfigureAwait(false); + var seg = new Segment(id, new TimeRange(timestamps.Introduction.Start, timestamps.Introduction.End)); + await Plugin.Instance!.UpdateTimestamps([seg], AnalysisMode.Introduction).ConfigureAwait(false); } if (timestamps?.Credits.End > 0.0) { - var tr = new TimeRange(timestamps.Credits.Start, timestamps.Credits.End); - await Plugin.Instance!.UpdateTimestamps(new Dictionary { [id] = new Segment(id, tr) }, AnalysisMode.Credits).ConfigureAwait(false); + var seg = new Segment(id, new TimeRange(timestamps.Credits.Start, timestamps.Credits.End)); + await Plugin.Instance!.UpdateTimestamps([seg], AnalysisMode.Credits).ConfigureAwait(false); } if (Plugin.Instance.Configuration.UpdateMediaSegments) diff --git a/IntroSkipper/Data/QueuedEpisode.cs b/IntroSkipper/Data/QueuedEpisode.cs index 85a3ce1..2f684ea 100644 --- a/IntroSkipper/Data/QueuedEpisode.cs +++ b/IntroSkipper/Data/QueuedEpisode.cs @@ -11,7 +11,7 @@ namespace IntroSkipper.Data; /// public class QueuedEpisode { - private readonly Dictionary _isAnalyzed = []; + private readonly bool[] _isAnalyzed = new bool[2]; /// /// Gets or sets the series name. @@ -36,7 +36,7 @@ public class QueuedEpisode /// /// Gets a value indicating whether this media has been already analyzed. /// - public IReadOnlyDictionary IsAnalyzed => _isAnalyzed; + public IReadOnlyList IsAnalyzed => _isAnalyzed; /// /// Gets or sets the full path to episode. @@ -80,7 +80,7 @@ public class QueuedEpisode /// Value to set. public void SetAnalyzed(AnalysisMode mode, bool value) { - _isAnalyzed[mode] = value; + _isAnalyzed[(int)mode] = value; } /// @@ -90,6 +90,6 @@ public class QueuedEpisode /// Value of the analyzed mode. public bool GetAnalyzed(AnalysisMode mode) { - return _isAnalyzed.TryGetValue(mode, out var value) && value; + return _isAnalyzed[(int)mode]; } } diff --git a/IntroSkipper/Data/Segment.cs b/IntroSkipper/Data/Segment.cs index c170045..6710357 100644 --- a/IntroSkipper/Data/Segment.cs +++ b/IntroSkipper/Data/Segment.cs @@ -33,8 +33,8 @@ public class Segment public Segment(Guid episode) { EpisodeId = episode; - Start = 0; - End = 0; + Start = 0.0; + End = 0.0; } /// @@ -88,7 +88,7 @@ public class Segment /// Gets a value indicating whether this introduction is valid or not. /// Invalid results must not be returned through the API. /// - public bool Valid => End > 0; + public bool Valid => End > 0.0; /// /// Gets the duration of this intro. diff --git a/IntroSkipper/Manager/QueueManager.cs b/IntroSkipper/Manager/QueueManager.cs index a4c6e9b..066512c 100644 --- a/IntroSkipper/Manager/QueueManager.cs +++ b/IntroSkipper/Manager/QueueManager.cs @@ -292,7 +292,7 @@ namespace IntroSkipper.Manager VerifyQueue(IReadOnlyList candidates, IReadOnlyCollection modes) { var verified = new List(); - var reqModes = new HashSet(modes); // Start with all modes and remove completed ones + var reqModes = new HashSet(); foreach (var candidate in candidates) { @@ -315,8 +315,10 @@ namespace IntroSkipper.Manager { candidate.SetAnalyzed(mode, true); } - - reqModes.Remove(mode); + } + else + { + reqModes.Add(mode); } } } diff --git a/IntroSkipper/Plugin.cs b/IntroSkipper/Plugin.cs index 17b030c..335ce23 100644 --- a/IntroSkipper/Plugin.cs +++ b/IntroSkipper/Plugin.cs @@ -301,36 +301,37 @@ public class Plugin : BasePlugin, IHasWebPages return _itemRepository.GetChapters(item); } - internal async Task UpdateTimestamps(IReadOnlyDictionary newTimestamps, AnalysisMode mode) + internal async Task UpdateTimestamps(IReadOnlyList newTimestamps, AnalysisMode mode) { + if (newTimestamps.Count == 0) + { + return; + } + + _logger.LogDebug("Starting UpdateTimestamps with {Count} segments for mode {Mode}", newTimestamps.Count, mode); + using var db = new IntroSkipperDbContext(_dbPath); - // Get all existing segments in a single query - var existingSegments = db.DbSegment - .Where(s => newTimestamps.Keys.Contains(s.ItemId) && s.Type == mode) - .ToDictionary(s => s.ItemId); + var segments = newTimestamps.Select(s => new DbSegment(s, mode)).ToList(); - // Batch updates and inserts - var segmentsToAdd = new List(); + var newItemIds = segments.Select(s => s.ItemId).ToHashSet(); + var existingIds = db.DbSegment + .Where(s => s.Type == mode && newItemIds.Contains(s.ItemId)) + .Select(s => s.ItemId) + .ToHashSet(); - foreach (var (itemId, segment) in newTimestamps) + foreach (var segment in segments) { - var dbSegment = new DbSegment(segment, mode); - if (existingSegments.TryGetValue(itemId, out var existing)) + if (existingIds.Contains(segment.ItemId)) { - db.Entry(existing).CurrentValues.SetValues(dbSegment); + db.DbSegment.Update(segment); } else { - segmentsToAdd.Add(dbSegment); + db.DbSegment.Add(segment); } } - if (segmentsToAdd.Count > 0) - { - await db.DbSegment.AddRangeAsync(segmentsToAdd).ConfigureAwait(false); - } - await db.SaveChangesAsync().ConfigureAwait(false); } diff --git a/IntroSkipper/ScheduledTasks/BaseItemAnalyzerTask.cs b/IntroSkipper/ScheduledTasks/BaseItemAnalyzerTask.cs index 02bf299..1998a15 100644 --- a/IntroSkipper/ScheduledTasks/BaseItemAnalyzerTask.cs +++ b/IntroSkipper/ScheduledTasks/BaseItemAnalyzerTask.cs @@ -205,22 +205,22 @@ public class BaseItemAnalyzerTask var analyzers = new Collection(); - if (action == AnalyzerAction.Chapter || action == AnalyzerAction.Default) + if (action is AnalyzerAction.Chapter or AnalyzerAction.Default) { analyzers.Add(new ChapterAnalyzer(_loggerFactory.CreateLogger())); } - if (first.IsAnime && !first.IsMovie && (action == AnalyzerAction.Chromaprint || action == AnalyzerAction.Default)) + if (first.IsAnime && !first.IsMovie && action is AnalyzerAction.Chromaprint or AnalyzerAction.Default) { analyzers.Add(new ChromaprintAnalyzer(_loggerFactory.CreateLogger())); } - if (mode == AnalysisMode.Credits && (action == AnalyzerAction.BlackFrame || action == AnalyzerAction.Default)) + if (mode is AnalysisMode.Credits && action is AnalyzerAction.BlackFrame or AnalyzerAction.Default) { analyzers.Add(new BlackFrameAnalyzer(_loggerFactory.CreateLogger())); } - if (!first.IsAnime && !first.IsMovie && (action == AnalyzerAction.Chromaprint || action == AnalyzerAction.Default)) + if (!first.IsAnime && !first.IsMovie && action is AnalyzerAction.Chromaprint or AnalyzerAction.Default) { analyzers.Add(new ChromaprintAnalyzer(_loggerFactory.CreateLogger())); } @@ -234,8 +234,9 @@ public class BaseItemAnalyzerTask } // Add items without intros/credits to blacklist. - var blacklisted = items.Where(e => !e.GetAnalyzed(mode)).ToList(); - await Plugin.Instance!.UpdateTimestamps(blacklisted.ToDictionary(e => e.EpisodeId, e => new Segment(e.EpisodeId)), mode).ConfigureAwait(false); + var blacklisted = new List(items.Where(e => !e.GetAnalyzed(mode)).Select(e => new Segment(e.EpisodeId))); + _logger.LogDebug("Blacklisting {Count} items for mode {Mode}", blacklisted.Count, mode); + await Plugin.Instance!.UpdateTimestamps(blacklisted, mode).ConfigureAwait(false); totalItems -= blacklisted.Count; return totalItems;