diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html index 22cbe8d..92aa13e 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html @@ -193,7 +193,7 @@ - Intro (show a skip button, *experimental*) + Intro/Credit (show a skip button, *experimental*) @@ -223,7 +223,7 @@ If checked, the plugin will overwrite all EDL files associated with - your episodes with the currently discovered introduction timestamps and EDL action. + your episodes with the currently discovered introduction/credit timestamps and EDL action. diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Data/EdlAction.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Data/EdlAction.cs index 5ae2aa9..159e950 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Data/EdlAction.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Data/EdlAction.cs @@ -34,4 +34,9 @@ public enum EdlAction /// Show a skip button. /// Intro, + + /// + /// Show a skip button. + /// + Credit, } diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/EdlManager.cs b/ConfusedPolarBear.Plugin.IntroSkipper/EdlManager.cs index 075fa65..9b5b296 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/EdlManager.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/EdlManager.cs @@ -63,15 +63,12 @@ public static class EdlManager { var id = episode.EpisodeId; - if (!Plugin.Instance.Intros.TryGetValue(id, out var intro)) - { - _logger?.LogDebug("Episode {Id} did not have an introduction, skipping", id); - continue; - } + bool hasIntro = Plugin.Instance!.Intros.TryGetValue(id, out var intro) && intro.Valid; + bool hasCredit = Plugin.Instance!.Credits.TryGetValue(id, out var credit) && credit.Valid; - if (!intro.Valid) + if (!hasIntro && !hasCredit) { - _logger?.LogDebug("Episode {Id} did not have a valid introduction, skipping", id); + _logger?.LogDebug("Episode {Id} has neither a valid intro nor credit, skipping", id); continue; } @@ -85,7 +82,31 @@ public static class EdlManager continue; } - File.WriteAllText(edlPath, intro.ToEdl(action)); + var edlContent = string.Empty; + + if (hasIntro) + { + edlContent += intro?.ToEdl(action); + } + + if (hasCredit) + { + if (edlContent.Length > 0) + { + edlContent += Environment.NewLine; + } + + if (action == EdlAction.Intro) + { + edlContent += credit?.ToEdl(EdlAction.Credit); + } + else + { + edlContent += credit?.ToEdl(action); + } + } + + File.WriteAllText(edlPath, edlContent); } } diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Entrypoint.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Entrypoint.cs index db82ffa..89dd5d3 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Entrypoint.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Entrypoint.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities.TV; @@ -258,45 +260,33 @@ public class Entrypoint : IHostedService, IDisposable var progress = new Progress(); var cancellationToken = _cancellationTokenSource.Token; + var modes = new List(); + var tasklogger = _loggerFactory.CreateLogger("DefaultLogger"); + if (Plugin.Instance!.Configuration.AutoDetectIntros && Plugin.Instance.Configuration.AutoDetectCredits) { - // This is where we can optimize a single scan - var baseIntroAnalyzer = new BaseItemAnalyzerTask( - AnalysisMode.Introduction, - _loggerFactory.CreateLogger(), - _loggerFactory, - _libraryManager); - - baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken); - - var baseCreditAnalyzer = new BaseItemAnalyzerTask( - AnalysisMode.Credits, - _loggerFactory.CreateLogger(), - _loggerFactory, - _libraryManager); - - baseCreditAnalyzer.AnalyzeItems(progress, cancellationToken); + modes.Add(AnalysisMode.Introduction); + modes.Add(AnalysisMode.Credits); + tasklogger = _loggerFactory.CreateLogger(); } else if (Plugin.Instance.Configuration.AutoDetectIntros) { - var baseIntroAnalyzer = new BaseItemAnalyzerTask( - AnalysisMode.Introduction, - _loggerFactory.CreateLogger(), - _loggerFactory, - _libraryManager); - - baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken); + modes.Add(AnalysisMode.Introduction); + tasklogger = _loggerFactory.CreateLogger(); } else if (Plugin.Instance.Configuration.AutoDetectCredits) { - var baseCreditAnalyzer = new BaseItemAnalyzerTask( - AnalysisMode.Credits, - _loggerFactory.CreateLogger(), + modes.Add(AnalysisMode.Credits); + tasklogger = _loggerFactory.CreateLogger(); + } + + var baseCreditAnalyzer = new BaseItemAnalyzerTask( + modes.AsReadOnly(), + tasklogger, _loggerFactory, _libraryManager); - baseCreditAnalyzer.AnalyzeItems(progress, cancellationToken); - } + baseCreditAnalyzer.AnalyzeItems(progress, cancellationToken); } Plugin.Instance.Configuration.PathRestrictions.Clear(); diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/QueueManager.cs b/ConfusedPolarBear.Plugin.IntroSkipper/QueueManager.cs index aed7535..05b8427 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/QueueManager.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/QueueManager.cs @@ -222,45 +222,51 @@ public class QueueManager /// This is done to ensure that we don't analyze items that were deleted between the call to GetMediaItems() and popping them from the queue. /// /// Queued media items. - /// Analysis mode. + /// Analysis mode. /// Media items that have been verified to exist in Jellyfin and in storage. - public (ReadOnlyCollection VerifiedItems, bool AnyUnanalyzed) - VerifyQueue(ReadOnlyCollection candidates, AnalysisMode mode) + public (ReadOnlyCollection VerifiedItems, ReadOnlyCollection RequiredModes) + VerifyQueue(ReadOnlyCollection candidates, ReadOnlyCollection modes) { - var unanalyzed = false; var verified = new List(); + var reqModes = new List(); - var timestamps = mode == AnalysisMode.Introduction ? - Plugin.Instance!.Intros : - Plugin.Instance!.Credits; + var requiresIntroAnalysis = modes.Contains(AnalysisMode.Introduction); + var requiresCreditsAnalysis = modes.Contains(AnalysisMode.Credits); foreach (var candidate in candidates) { try { - var path = Plugin.Instance.GetItemPath(candidate.EpisodeId); + var path = Plugin.Instance!.GetItemPath(candidate.EpisodeId); if (File.Exists(path)) { verified.Add(candidate); - } - if (!timestamps.ContainsKey(candidate.EpisodeId)) - { - unanalyzed = true; + if (requiresIntroAnalysis && !Plugin.Instance!.Intros.ContainsKey(candidate.EpisodeId)) + { + reqModes.Add(AnalysisMode.Introduction); + requiresIntroAnalysis = false; // No need to check again + } + + if (requiresCreditsAnalysis && !Plugin.Instance!.Credits.ContainsKey(candidate.EpisodeId)) + { + reqModes.Add(AnalysisMode.Credits); + requiresCreditsAnalysis = false; // No need to check again + } } } catch (Exception ex) { _logger.LogDebug( "Skipping {Mode} analysis of {Name} ({Id}): {Exception}", - mode, + modes, candidate.Name, candidate.EpisodeId, ex); } } - return (verified.AsReadOnly(), unanalyzed); + return (verified.AsReadOnly(), reqModes.AsReadOnly()); } } diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/BaseItemAnalyzerTask.cs b/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/BaseItemAnalyzerTask.cs index 6101309..fa4650d 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/BaseItemAnalyzerTask.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/BaseItemAnalyzerTask.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Threading; using System.Threading.Tasks; @@ -12,7 +13,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper; /// public class BaseItemAnalyzerTask { - private readonly AnalysisMode _analysisMode; + private readonly ReadOnlyCollection _analysisModes; private readonly ILogger _logger; @@ -23,22 +24,22 @@ public class BaseItemAnalyzerTask /// /// Initializes a new instance of the class. /// - /// Analysis mode. + /// Analysis mode. /// Task logger. /// Logger factory. /// Library manager. public BaseItemAnalyzerTask( - AnalysisMode mode, + ReadOnlyCollection modes, ILogger logger, ILoggerFactory loggerFactory, ILibraryManager libraryManager) { - _analysisMode = mode; + _analysisModes = modes; _logger = logger; _loggerFactory = loggerFactory; _libraryManager = libraryManager; - if (mode == AnalysisMode.Introduction) + if (Plugin.Instance!.Configuration.EdlAction != EdlAction.None) { EdlManager.Initialize(_logger); } @@ -73,18 +74,21 @@ public class BaseItemAnalyzerTask totalQueued += kvp.Value.Count; } + totalQueued *= _analysisModes.Count; + if (totalQueued == 0) { throw new FingerprintException( "No episodes to analyze. If you are limiting the list of libraries to analyze, check that all library names have been spelled correctly."); } - if (this._analysisMode == AnalysisMode.Introduction) + if (Plugin.Instance!.Configuration.EdlAction != EdlAction.None) { EdlManager.LogConfiguration(); } var totalProcessed = 0; + var modeCount = _analysisModes.Count; var options = new ParallelOptions { MaxDegreeOfParallelism = Plugin.Instance.Configuration.MaxParallelism @@ -96,27 +100,39 @@ public class BaseItemAnalyzerTask // Since the first run of the task can run for multiple hours, ensure that none // of the current media items were deleted from Jellyfin since the task was started. - var (episodes, unanalyzed) = queueManager.VerifyQueue( + var (episodes, requiredModes) = queueManager.VerifyQueue( season.Value.AsReadOnly(), - this._analysisMode); + _analysisModes); - if (episodes.Count == 0) + var episodeCount = episodes.Count; + + if (episodeCount == 0) { return; } var first = episodes[0]; + var requiredModeCount = requiredModes.Count; - if (!unanalyzed) + if (requiredModeCount == 0) { _logger.LogDebug( "All episodes in {Name} season {Season} have already been analyzed", first.SeriesName, first.SeasonNumber); + Interlocked.Add(ref totalProcessed, episodeCount * modeCount); // Update total Processed directly + progress.Report((totalProcessed * 100) / totalQueued); + return; } + if (modeCount != requiredModeCount) + { + Interlocked.Add(ref totalProcessed, episodeCount); + progress.Report((totalProcessed * 100) / totalQueued); // Partial analysis some modes have already been analyzed + } + try { if (cancellationToken.IsCancellationRequested) @@ -124,10 +140,15 @@ public class BaseItemAnalyzerTask return; } - var analyzed = AnalyzeItems(episodes, cancellationToken); - Interlocked.Add(ref totalProcessed, analyzed); + foreach (AnalysisMode mode in requiredModes) + { + var analyzed = AnalyzeItems(episodes, mode, cancellationToken); + Interlocked.Add(ref totalProcessed, analyzed); - writeEdl = analyzed > 0 || Plugin.Instance.Configuration.RegenerateEdlFiles; + writeEdl = analyzed > 0 || Plugin.Instance.Configuration.RegenerateEdlFiles; + + progress.Report((totalProcessed * 100) / totalQueued); + } } catch (FingerprintException ex) { @@ -138,27 +159,13 @@ public class BaseItemAnalyzerTask ex); } - if ( - writeEdl && - Plugin.Instance.Configuration.EdlAction != EdlAction.None && - _analysisMode == AnalysisMode.Introduction) + if (writeEdl && Plugin.Instance.Configuration.EdlAction != EdlAction.None) { EdlManager.UpdateEDLFiles(episodes); } - - if (_analysisMode == AnalysisMode.Introduction) - { - progress.Report(((totalProcessed * 100) / totalQueued) / 2); - } - else - { - progress.Report((((totalProcessed * 100) / totalQueued) / 2) + 50); - } }); - if ( - _analysisMode == AnalysisMode.Introduction && - Plugin.Instance.Configuration.RegenerateEdlFiles) + if (Plugin.Instance.Configuration.RegenerateEdlFiles) { _logger.LogInformation("Turning EDL file regeneration flag off"); Plugin.Instance.Configuration.RegenerateEdlFiles = false; @@ -170,10 +177,12 @@ public class BaseItemAnalyzerTask /// Analyze a group of media items for skippable segments. /// /// Media items to analyze. + /// Analysis mode. /// Cancellation token. /// Number of items that were successfully analyzed. private int AnalyzeItems( ReadOnlyCollection items, + AnalysisMode mode, CancellationToken cancellationToken) { var totalItems = items.Count; @@ -186,7 +195,8 @@ public class BaseItemAnalyzerTask } _logger.LogInformation( - "Analyzing {Count} files from {Name} season {Season}", + "[Mode: {Mode}] Analyzing {Count} files from {Name} season {Season}", + mode, items.Count, first.SeriesName, first.SeasonNumber); @@ -199,7 +209,7 @@ public class BaseItemAnalyzerTask analyzers.Add(new ChromaprintAnalyzer(_loggerFactory.CreateLogger())); } - if (this._analysisMode == AnalysisMode.Credits) + if (mode == AnalysisMode.Credits) { analyzers.Add(new BlackFrameAnalyzer(_loggerFactory.CreateLogger())); } @@ -208,7 +218,7 @@ public class BaseItemAnalyzerTask // analyzed items from the queue. foreach (var analyzer in analyzers) { - items = analyzer.AnalyzeMediaFiles(items, this._analysisMode, cancellationToken); + items = analyzer.AnalyzeMediaFiles(items, mode, cancellationToken); } return totalItems; diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/DetectCreditsTask.cs b/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/DetectCreditsTask.cs index c2d71c7..b7b17e4 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/DetectCreditsTask.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/DetectCreditsTask.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Library; @@ -87,9 +88,10 @@ public class DetectCreditsTask : IScheduledTask _logger.LogInformation("Scheduled Task is starting"); Plugin.Instance!.Configuration.PathRestrictions.Clear(); + var modes = new List { AnalysisMode.Credits }; var baseCreditAnalyzer = new BaseItemAnalyzerTask( - AnalysisMode.Credits, + modes.AsReadOnly(), _loggerFactory.CreateLogger(), _loggerFactory, _libraryManager); diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/DetectIntrosCreditsTask.cs b/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/DetectIntrosCreditsTask.cs index 925d5c0..aea91ab 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/DetectIntrosCreditsTask.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/DetectIntrosCreditsTask.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Library; @@ -86,23 +87,16 @@ public class DetectIntrosCreditsTask : IScheduledTask _logger.LogInformation("Scheduled Task is starting"); Plugin.Instance!.Configuration.PathRestrictions.Clear(); + var modes = new List { AnalysisMode.Introduction, AnalysisMode.Credits }; var baseIntroAnalyzer = new BaseItemAnalyzerTask( - AnalysisMode.Introduction, + modes.AsReadOnly(), _loggerFactory.CreateLogger(), _loggerFactory, _libraryManager); baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken); - var baseCreditAnalyzer = new BaseItemAnalyzerTask( - AnalysisMode.Credits, - _loggerFactory.CreateLogger(), - _loggerFactory, - _libraryManager); - - baseCreditAnalyzer.AnalyzeItems(progress, cancellationToken); - ScheduledTaskSemaphore.Release(); return Task.CompletedTask; } diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/DetectIntrosTask.cs b/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/DetectIntrosTask.cs index d6afc35..1ba8900 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/DetectIntrosTask.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/ScheduledTasks/DetectIntrosTask.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Library; @@ -86,9 +87,10 @@ public class DetectIntrosTask : IScheduledTask _logger.LogInformation("Scheduled Task is starting"); Plugin.Instance!.Configuration.PathRestrictions.Clear(); + var modes = new List { AnalysisMode.Introduction }; var baseIntroAnalyzer = new BaseItemAnalyzerTask( - AnalysisMode.Introduction, + modes.AsReadOnly(), _loggerFactory.CreateLogger(), _loggerFactory, _libraryManager);