Refactor BaseItemAnalyzerTask and add Edl support for credits

This commit is contained in:
rlauu 2024-04-18 18:08:35 +02:00 committed by TwistedUmbrellaX
parent 46d446718f
commit 4556524f7e
8 changed files with 104 additions and 96 deletions

View File

@ -34,4 +34,9 @@ public enum EdlAction
/// Show a skip button.
/// </summary>
Intro,
/// <summary>
/// Show a skip button.
/// </summary>
Credit,
}

View File

@ -63,14 +63,12 @@ public static class EdlManager
{
var id = episode.EpisodeId;
if (!Plugin.Instance!.Intros.TryGetValue(id, out var intro))
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 (!hasIntro && !hasCredit)
{
_logger?.LogDebug("Episode {Id} did not have an introduction, skipping", id);
continue;
}
else if (!intro.Valid)
{
_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;
}
@ -84,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);
}
}

View File

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@ -259,46 +261,34 @@ public class Entrypoint : IServerEntryPoint
var progress = new Progress<double>();
var cancellationToken = _cancellationTokenSource.Token;
var modes = new List<AnalysisMode>();
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<DetectIntrosCreditsTask>(),
_loggerFactory,
_libraryManager);
baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken);
var baseCreditAnalyzer = new BaseItemAnalyzerTask(
AnalysisMode.Credits,
_loggerFactory.CreateLogger<DetectIntrosCreditsTask>(),
_loggerFactory,
_libraryManager);
baseCreditAnalyzer.AnalyzeItems(progress, cancellationToken);
modes.Add(AnalysisMode.Introduction);
modes.Add(AnalysisMode.Credits);
tasklogger = _loggerFactory.CreateLogger<DetectIntrosCreditsTask>();
}
else if (Plugin.Instance!.Configuration.AutoDetectIntros)
{
var baseIntroAnalyzer = new BaseItemAnalyzerTask(
AnalysisMode.Introduction,
_loggerFactory.CreateLogger<DetectIntrosTask>(),
_loggerFactory,
_libraryManager);
baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken);
modes.Add(AnalysisMode.Introduction);
tasklogger = _loggerFactory.CreateLogger<DetectIntrosTask>();
}
else if (Plugin.Instance!.Configuration.AutoDetectCredits)
{
modes.Add(AnalysisMode.Credits);
tasklogger = _loggerFactory.CreateLogger<DetectCreditsTask>();
}
var baseCreditAnalyzer = new BaseItemAnalyzerTask(
AnalysisMode.Credits,
_loggerFactory.CreateLogger<DetectCreditsTask>(),
modes.AsReadOnly(),
tasklogger,
_loggerFactory,
_libraryManager);
baseCreditAnalyzer.AnalyzeItems(progress, cancellationToken);
}
}
Plugin.Instance!.Configuration.PathRestrictions.Clear();
_autoTaskCompletEvent.Set();

View File

@ -231,17 +231,16 @@ 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.
/// </summary>
/// <param name="candidates">Queued media items.</param>
/// <param name="mode">Analysis mode.</param>
/// <param name="modes">Analysis mode.</param>
/// <returns>Media items that have been verified to exist in Jellyfin and in storage.</returns>
public (ReadOnlyCollection<QueuedEpisode> VerifiedItems, bool AnyUnanalyzed)
VerifyQueue(ReadOnlyCollection<QueuedEpisode> candidates, AnalysisMode mode)
public (ReadOnlyCollection<QueuedEpisode> VerifiedItems, ReadOnlyCollection<AnalysisMode> RequiredModes)
VerifyQueue(ReadOnlyCollection<QueuedEpisode> candidates, ReadOnlyCollection<AnalysisMode> modes)
{
var unanalyzed = false;
var verified = new List<QueuedEpisode>();
var reqModes = new List<AnalysisMode>();
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)
{
@ -252,24 +251,31 @@ public class QueueManager
if (File.Exists(path))
{
verified.Add(candidate);
if (requiresIntroAnalysis && !Plugin.Instance!.Intros.ContainsKey(candidate.EpisodeId))
{
reqModes.Add(AnalysisMode.Introduction);
requiresIntroAnalysis = false; // No need to check again
}
if (!timestamps.ContainsKey(candidate.EpisodeId))
if (requiresCreditsAnalysis && !Plugin.Instance!.Credits.ContainsKey(candidate.EpisodeId))
{
unanalyzed = true;
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());
}
}

View File

@ -1,6 +1,7 @@
namespace ConfusedPolarBear.Plugin.IntroSkipper;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
@ -12,7 +13,7 @@ using Microsoft.Extensions.Logging;
/// </summary>
public class BaseItemAnalyzerTask
{
private readonly AnalysisMode _analysisMode;
private readonly ReadOnlyCollection<AnalysisMode> _analysisModes;
private readonly ILogger _logger;
@ -23,22 +24,22 @@ public class BaseItemAnalyzerTask
/// <summary>
/// Initializes a new instance of the <see cref="BaseItemAnalyzerTask"/> class.
/// </summary>
/// <param name="mode">Analysis mode.</param>
/// <param name="modes">Analysis mode.</param>
/// <param name="logger">Task logger.</param>
/// <param name="loggerFactory">Logger factory.</param>
/// <param name="libraryManager">Library manager.</param>
public BaseItemAnalyzerTask(
AnalysisMode mode,
ReadOnlyCollection<AnalysisMode> 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);
}
@ -79,7 +80,7 @@ public class BaseItemAnalyzerTask
"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();
}
@ -96,9 +97,9 @@ 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)
{
@ -107,7 +108,7 @@ public class BaseItemAnalyzerTask
var first = episodes[0];
if (!unanalyzed)
if (requiredModes.Count == 0)
{
_logger.LogDebug(
"All episodes in {Name} season {Season} have already been analyzed",
@ -124,11 +125,14 @@ public class BaseItemAnalyzerTask
return;
}
var analyzed = AnalyzeItems(episodes, cancellationToken);
foreach (AnalysisMode mode in requiredModes)
{
var analyzed = AnalyzeItems(episodes, mode, cancellationToken);
Interlocked.Add(ref totalProcessed, analyzed);
writeEdl = analyzed > 0 || Plugin.Instance!.Configuration.RegenerateEdlFiles;
}
}
catch (FingerprintException ex)
{
_logger.LogWarning(
@ -138,34 +142,15 @@ 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 (_logger is ILogger<DetectIntrosCreditsTask>)
{
if (_analysisMode == AnalysisMode.Introduction)
{
progress.Report(((totalProcessed * 100) / totalQueued) / 2);
}
else
{
progress.Report((((totalProcessed * 100) / totalQueued) / 2) + 50);
}
}
else
{
progress.Report((totalProcessed * 100) / totalQueued);
}
});
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;
@ -177,10 +162,12 @@ public class BaseItemAnalyzerTask
/// Analyze a group of media items for skippable segments.
/// </summary>
/// <param name="items">Media items to analyze.</param>
/// <param name="mode">Analysis mode.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Number of items that were successfully analyzed.</returns>
private int AnalyzeItems(
ReadOnlyCollection<QueuedEpisode> items,
AnalysisMode mode,
CancellationToken cancellationToken)
{
var totalItems = items.Count;
@ -206,7 +193,7 @@ public class BaseItemAnalyzerTask
analyzers.Add(new ChromaprintAnalyzer(_loggerFactory.CreateLogger<ChromaprintAnalyzer>()));
}
if (this._analysisMode == AnalysisMode.Credits)
if (mode == AnalysisMode.Credits)
{
analyzers.Add(new BlackFrameAnalyzer(_loggerFactory.CreateLogger<BlackFrameAnalyzer>()));
}
@ -215,7 +202,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;

View File

@ -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> { AnalysisMode.Credits };
var baseCreditAnalyzer = new BaseItemAnalyzerTask(
AnalysisMode.Credits,
modes.AsReadOnly(),
_loggerFactory.CreateLogger<DetectCreditsTask>(),
_loggerFactory,
_libraryManager);

View File

@ -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> { AnalysisMode.Introduction, AnalysisMode.Credits };
var baseIntroAnalyzer = new BaseItemAnalyzerTask(
AnalysisMode.Introduction,
modes.AsReadOnly(),
_loggerFactory.CreateLogger<DetectIntrosCreditsTask>(),
_loggerFactory,
_libraryManager);
baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken);
var baseCreditAnalyzer = new BaseItemAnalyzerTask(
AnalysisMode.Credits,
_loggerFactory.CreateLogger<DetectIntrosCreditsTask>(),
_loggerFactory,
_libraryManager);
baseCreditAnalyzer.AnalyzeItems(progress, cancellationToken);
ScheduledTaskSemaphore.Release();
return Task.CompletedTask;
}

View File

@ -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> { AnalysisMode.Introduction };
var baseIntroAnalyzer = new BaseItemAnalyzerTask(
AnalysisMode.Introduction,
modes.AsReadOnly(),
_loggerFactory.CreateLogger<DetectIntrosTask>(),
_loggerFactory,
_libraryManager);