analyze when item is added to the server (#96)
This commit is contained in:
parent
78c05a7e29
commit
ded3c3d43a
@ -27,6 +27,11 @@ public class PluginConfiguration : BasePluginConfiguration
|
||||
/// </summary>
|
||||
public string SelectedLibraries { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to analyze automatically, when new Items are added.
|
||||
/// </summary>
|
||||
public bool AutomaticAnalysis { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to analyze season 0.
|
||||
/// </summary>
|
||||
|
@ -27,6 +27,17 @@
|
||||
<fieldset class="verticalSection-extrabottompadding">
|
||||
<legend>Analysis</legend>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="AutomaticAnalysis" type="checkbox" is="emby-checkbox" />
|
||||
<span>Automatic analysis</span>
|
||||
</label>
|
||||
|
||||
<div class="fieldDescription">
|
||||
If checked, newly added items will be automatically analyzed.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label class="emby-checkbox-label">
|
||||
<input id="AnalyzeSeasonZero" type="checkbox" is="emby-checkbox" />
|
||||
@ -586,6 +597,7 @@
|
||||
]
|
||||
|
||||
var booleanConfigurationFields = [
|
||||
"AutomaticAnalysis",
|
||||
"AnalyzeSeasonZero",
|
||||
"RegenerateEdlFiles",
|
||||
"UseChromaprint",
|
||||
|
@ -1,8 +1,12 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
||||
@ -14,9 +18,11 @@ public class Entrypoint : IServerEntryPoint
|
||||
{
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Entrypoint"/> class.
|
||||
@ -24,20 +30,29 @@ public class Entrypoint : IServerEntryPoint
|
||||
/// <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;
|
||||
_loggerFactory = loggerFactory;
|
||||
|
||||
_queueTimer = new Timer(
|
||||
OnTimerCallback,
|
||||
null,
|
||||
Timeout.InfiniteTimeSpan,
|
||||
Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -46,10 +61,14 @@ public class Entrypoint : IServerEntryPoint
|
||||
/// <returns>Task.</returns>
|
||||
public Task RunAsync()
|
||||
{
|
||||
FFmpegWrapper.Logger = _logger;
|
||||
if (Plugin.Instance!.Configuration.AutomaticAnalysis)
|
||||
{
|
||||
_libraryManager.ItemAdded += OnItemAdded;
|
||||
_libraryManager.ItemUpdated += OnItemModified;
|
||||
_taskManager.TaskCompleted += OnLibraryRefresh;
|
||||
}
|
||||
|
||||
// TODO: when a new item is added to the server, immediately analyze the season it belongs to
|
||||
// instead of waiting for the next task interval. The task start should be debounced by a few seconds.
|
||||
FFmpegWrapper.Logger = _logger;
|
||||
|
||||
try
|
||||
{
|
||||
@ -66,6 +85,137 @@ public class Entrypoint : IServerEntryPoint
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// Disclose source for inspiration
|
||||
// Implementation based on the principles of jellyfin-plugin-media-analyzer:
|
||||
// https://github.com/endrl/jellyfin-plugin-media-analyzer
|
||||
|
||||
/// <summary>
|
||||
/// Library item was added.
|
||||
/// </summary>
|
||||
/// <param name="sender">The sending entity.</param>
|
||||
/// <param name="itemChangeEventArgs">The <see cref="ItemChangeEventArgs"/>.</param>
|
||||
private void OnItemAdded(object? sender, ItemChangeEventArgs itemChangeEventArgs)
|
||||
{
|
||||
// Don't do anything if it's not a supported media type
|
||||
if (itemChangeEventArgs.Item is not Episode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemChangeEventArgs.Item.LocationType == LocationType.Virtual)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StartTimer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Library item was modified.
|
||||
/// </summary>
|
||||
/// <param name="sender">The sending entity.</param>
|
||||
/// <param name="itemChangeEventArgs">The <see cref="ItemChangeEventArgs"/>.</param>
|
||||
private void OnItemModified(object? sender, ItemChangeEventArgs itemChangeEventArgs)
|
||||
{
|
||||
// Don't do anything if it's not a supported media type
|
||||
if (itemChangeEventArgs.Item is not Episode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemChangeEventArgs.Item.LocationType == LocationType.Virtual)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StartTimer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TaskManager task ended.
|
||||
/// </summary>
|
||||
/// <param name="sender">The sending entity.</param>
|
||||
/// <param name="eventArgs">The <see cref="TaskCompletionEventArgs"/>.</param>
|
||||
private void OnLibraryRefresh(object? sender, TaskCompletionEventArgs eventArgs)
|
||||
{
|
||||
var result = eventArgs.Result;
|
||||
|
||||
if (result.Key != "RefreshLibrary")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.Status != TaskCompletionStatus.Completed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StartTimer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start timer to debounce analyzing.
|
||||
/// </summary>
|
||||
private void StartTimer()
|
||||
{
|
||||
if (Plugin.Instance!.AnalyzerTaskIsRunning)
|
||||
{
|
||||
return; // Don't do anything if a Analyzer is running
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Media Library changed, analyzis will start soon!");
|
||||
_queueTimer.Change(TimeSpan.FromMilliseconds(20000), Timeout.InfiniteTimeSpan);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for timer callback to be completed.
|
||||
/// </summary>
|
||||
private void OnTimerCallback(object? state)
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformAnalysis();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in PerformAnalysis");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wait for timer to be completed.
|
||||
/// </summary>
|
||||
private void PerformAnalysis()
|
||||
{
|
||||
_logger.LogInformation("Timer elapsed - start analyzing");
|
||||
Plugin.Instance!.AnalyzerTaskIsRunning = true;
|
||||
|
||||
var progress = new Progress<double>();
|
||||
var cancellationToken = new CancellationToken(false);
|
||||
|
||||
// intro
|
||||
var introductionAnalyzer = new BaseItemAnalyzerTask(
|
||||
AnalysisMode.Introduction,
|
||||
_loggerFactory.CreateLogger<Entrypoint>(),
|
||||
_loggerFactory,
|
||||
_libraryManager);
|
||||
|
||||
introductionAnalyzer.AnalyzeItems(progress, cancellationToken);
|
||||
|
||||
// outro
|
||||
var creditsAnalyzer = new BaseItemAnalyzerTask(
|
||||
AnalysisMode.Credits,
|
||||
_loggerFactory.CreateLogger<Entrypoint>(),
|
||||
_loggerFactory,
|
||||
_libraryManager);
|
||||
|
||||
creditsAnalyzer.AnalyzeItems(progress, cancellationToken);
|
||||
|
||||
Plugin.Instance!.AnalyzerTaskIsRunning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose.
|
||||
/// </summary>
|
||||
@ -83,6 +233,12 @@ public class Entrypoint : IServerEntryPoint
|
||||
{
|
||||
if (!dispose)
|
||||
{
|
||||
_libraryManager.ItemAdded -= OnItemAdded;
|
||||
_libraryManager.ItemUpdated -= OnItemModified;
|
||||
_taskManager.TaskCompleted -= OnLibraryRefresh;
|
||||
|
||||
_queueTimer.Dispose();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -144,6 +144,11 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
||||
/// </summary>
|
||||
public event EventHandler? AutoSkipChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether analysis is running.
|
||||
/// </summary>
|
||||
public bool AnalyzerTaskIsRunning { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the results of fingerprinting all episodes.
|
||||
/// </summary>
|
||||
|
@ -14,6 +14,8 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
||||
/// </summary>
|
||||
public class DetectCreditsTask : IScheduledTask
|
||||
{
|
||||
private readonly ILogger<DetectCreditsTask> _logger;
|
||||
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
@ -23,10 +25,13 @@ public class DetectCreditsTask : IScheduledTask
|
||||
/// </summary>
|
||||
/// <param name="loggerFactory">Logger factory.</param>
|
||||
/// <param name="libraryManager">Library manager.</param>
|
||||
/// <param name="logger">Logger.</param>
|
||||
public DetectCreditsTask(
|
||||
ILogger<DetectCreditsTask> logger,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_loggerFactory = loggerFactory;
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
@ -64,6 +69,40 @@ public class DetectCreditsTask : IScheduledTask
|
||||
throw new InvalidOperationException("Library manager was null");
|
||||
}
|
||||
|
||||
// Wait for running analyzer
|
||||
if (Plugin.Instance!.AnalyzerTaskIsRunning)
|
||||
{
|
||||
_logger.LogInformation("Other running Analyzer Task detected. Wait...");
|
||||
using (var timer = new Timer(_ => { }, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan))
|
||||
{
|
||||
try
|
||||
{
|
||||
void DisposeTimerOnCancellation() => timer.Dispose();
|
||||
cancellationToken.Register(DisposeTimerOnCancellation);
|
||||
while (Plugin.Instance!.AnalyzerTaskIsRunning && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
timer.Change(TimeSpan.FromMilliseconds(20000), Timeout.InfiniteTimeSpan); // Adjust delay
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cancellationToken.IsCancellationRequested) // Check cancellation again before logging
|
||||
{
|
||||
_logger.LogInformation("No other Task active. Run Analyzer Task");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Task was canceled");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
Plugin.Instance!.AnalyzerTaskIsRunning = true;
|
||||
|
||||
var baseAnalyzer = new BaseItemAnalyzerTask(
|
||||
AnalysisMode.Credits,
|
||||
_loggerFactory.CreateLogger<DetectCreditsTask>(),
|
||||
@ -72,6 +111,8 @@ public class DetectCreditsTask : IScheduledTask
|
||||
|
||||
baseAnalyzer.AnalyzeItems(progress, cancellationToken);
|
||||
|
||||
Plugin.Instance!.AnalyzerTaskIsRunning = false;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,8 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
||||
/// </summary>
|
||||
public class DetectIntroductionsTask : IScheduledTask
|
||||
{
|
||||
private readonly ILogger<DetectIntroductionsTask> _logger;
|
||||
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
@ -22,10 +24,13 @@ public class DetectIntroductionsTask : IScheduledTask
|
||||
/// </summary>
|
||||
/// <param name="loggerFactory">Logger factory.</param>
|
||||
/// <param name="libraryManager">Library manager.</param>
|
||||
/// <param name="logger">Logger.</param>
|
||||
public DetectIntroductionsTask(
|
||||
ILogger<DetectIntroductionsTask> logger,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_loggerFactory = loggerFactory;
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
@ -63,6 +68,40 @@ public class DetectIntroductionsTask : IScheduledTask
|
||||
throw new InvalidOperationException("Library manager was null");
|
||||
}
|
||||
|
||||
// Wait for running analyzer
|
||||
if (Plugin.Instance!.AnalyzerTaskIsRunning)
|
||||
{
|
||||
_logger.LogInformation("Other running Analyzer Task detected. Wait...");
|
||||
using (var timer = new Timer(_ => { }, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan))
|
||||
{
|
||||
try
|
||||
{
|
||||
void DisposeTimerOnCancellation() => timer.Dispose();
|
||||
cancellationToken.Register(DisposeTimerOnCancellation);
|
||||
while (Plugin.Instance!.AnalyzerTaskIsRunning && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
timer.Change(TimeSpan.FromMilliseconds(20000), Timeout.InfiniteTimeSpan); // Adjust delay
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cancellationToken.IsCancellationRequested) // Check cancellation again before logging
|
||||
{
|
||||
_logger.LogInformation("No other Task active. Run Analyzer Task");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Task was canceled");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
Plugin.Instance!.AnalyzerTaskIsRunning = true;
|
||||
|
||||
var baseAnalyzer = new BaseItemAnalyzerTask(
|
||||
AnalysisMode.Introduction,
|
||||
_loggerFactory.CreateLogger<DetectIntroductionsTask>(),
|
||||
@ -71,6 +110,8 @@ public class DetectIntroductionsTask : IScheduledTask
|
||||
|
||||
baseAnalyzer.AnalyzeItems(progress, cancellationToken);
|
||||
|
||||
Plugin.Instance!.AnalyzerTaskIsRunning = false;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user