analyze when item is added to the server (#96)
This commit is contained in:
parent
5d31b02de5
commit
057352b212
@ -27,6 +27,11 @@ public class PluginConfiguration : BasePluginConfiguration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string SelectedLibraries { get; set; } = string.Empty;
|
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>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether to analyze season 0.
|
/// Gets or sets a value indicating whether to analyze season 0.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -27,6 +27,17 @@
|
|||||||
<fieldset class="verticalSection-extrabottompadding">
|
<fieldset class="verticalSection-extrabottompadding">
|
||||||
<legend>Analysis</legend>
|
<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">
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||||
<label class="emby-checkbox-label">
|
<label class="emby-checkbox-label">
|
||||||
<input id="AnalyzeSeasonZero" type="checkbox" is="emby-checkbox" />
|
<input id="AnalyzeSeasonZero" type="checkbox" is="emby-checkbox" />
|
||||||
@ -587,6 +598,7 @@
|
|||||||
]
|
]
|
||||||
|
|
||||||
var booleanConfigurationFields = [
|
var booleanConfigurationFields = [
|
||||||
|
"AutomaticAnalysis",
|
||||||
"AnalyzeSeasonZero",
|
"AnalyzeSeasonZero",
|
||||||
"RegenerateEdlFiles",
|
"RegenerateEdlFiles",
|
||||||
"UseChromaprint",
|
"UseChromaprint",
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Plugins;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@ -14,9 +19,11 @@ public class Entrypoint : IHostedService
|
|||||||
{
|
{
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IUserViewManager _userViewManager;
|
private readonly IUserViewManager _userViewManager;
|
||||||
|
private readonly ITaskManager _taskManager;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly ILogger<Entrypoint> _logger;
|
private readonly ILogger<Entrypoint> _logger;
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
|
private Timer _queueTimer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Entrypoint"/> class.
|
/// Initializes a new instance of the <see cref="Entrypoint"/> class.
|
||||||
@ -24,20 +31,160 @@ public class Entrypoint : IHostedService
|
|||||||
/// <param name="userManager">User manager.</param>
|
/// <param name="userManager">User manager.</param>
|
||||||
/// <param name="userViewManager">User view manager.</param>
|
/// <param name="userViewManager">User view manager.</param>
|
||||||
/// <param name="libraryManager">Library manager.</param>
|
/// <param name="libraryManager">Library manager.</param>
|
||||||
|
/// <param name="taskManager">Task manager.</param>
|
||||||
/// <param name="logger">Logger.</param>
|
/// <param name="logger">Logger.</param>
|
||||||
/// <param name="loggerFactory">Logger factory.</param>
|
/// <param name="loggerFactory">Logger factory.</param>
|
||||||
public Entrypoint(
|
public Entrypoint(
|
||||||
IUserManager userManager,
|
IUserManager userManager,
|
||||||
IUserViewManager userViewManager,
|
IUserViewManager userViewManager,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
|
ITaskManager taskManager,
|
||||||
ILogger<Entrypoint> logger,
|
ILogger<Entrypoint> logger,
|
||||||
ILoggerFactory loggerFactory)
|
ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_userViewManager = userViewManager;
|
_userViewManager = userViewManager;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
_taskManager = taskManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
|
|
||||||
|
_queueTimer = new Timer(
|
||||||
|
OnTimerCallback,
|
||||||
|
null,
|
||||||
|
Timeout.InfiniteTimeSpan,
|
||||||
|
Timeout.InfiniteTimeSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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>
|
/// <summary>
|
||||||
@ -56,6 +203,12 @@ public class Entrypoint : IHostedService
|
|||||||
{
|
{
|
||||||
if (!dispose)
|
if (!dispose)
|
||||||
{
|
{
|
||||||
|
_libraryManager.ItemAdded -= OnItemAdded;
|
||||||
|
_libraryManager.ItemUpdated -= OnItemModified;
|
||||||
|
_taskManager.TaskCompleted -= OnLibraryRefresh;
|
||||||
|
|
||||||
|
_queueTimer.Dispose();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,10 +216,14 @@ public class Entrypoint : IHostedService
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
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
|
FFmpegWrapper.Logger = _logger;
|
||||||
// instead of waiting for the next task interval. The task start should be debounced by a few seconds.
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -147,6 +147,11 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler? AutoSkipChanged;
|
public event EventHandler? AutoSkipChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether analysis is running.
|
||||||
|
/// </summary>
|
||||||
|
public bool AnalyzerTaskIsRunning { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the results of fingerprinting all episodes.
|
/// Gets the results of fingerprinting all episodes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -14,6 +14,8 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DetectCreditsTask : IScheduledTask
|
public class DetectCreditsTask : IScheduledTask
|
||||||
{
|
{
|
||||||
|
private readonly ILogger<DetectCreditsTask> _logger;
|
||||||
|
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
@ -23,10 +25,13 @@ public class DetectCreditsTask : IScheduledTask
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="loggerFactory">Logger factory.</param>
|
/// <param name="loggerFactory">Logger factory.</param>
|
||||||
/// <param name="libraryManager">Library manager.</param>
|
/// <param name="libraryManager">Library manager.</param>
|
||||||
|
/// <param name="logger">Logger.</param>
|
||||||
public DetectCreditsTask(
|
public DetectCreditsTask(
|
||||||
|
ILogger<DetectCreditsTask> logger,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
ILibraryManager libraryManager)
|
ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
}
|
}
|
||||||
@ -64,6 +69,40 @@ public class DetectCreditsTask : IScheduledTask
|
|||||||
throw new InvalidOperationException("Library manager was null");
|
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(
|
var baseAnalyzer = new BaseItemAnalyzerTask(
|
||||||
AnalysisMode.Credits,
|
AnalysisMode.Credits,
|
||||||
_loggerFactory.CreateLogger<DetectCreditsTask>(),
|
_loggerFactory.CreateLogger<DetectCreditsTask>(),
|
||||||
@ -72,6 +111,8 @@ public class DetectCreditsTask : IScheduledTask
|
|||||||
|
|
||||||
baseAnalyzer.AnalyzeItems(progress, cancellationToken);
|
baseAnalyzer.AnalyzeItems(progress, cancellationToken);
|
||||||
|
|
||||||
|
Plugin.Instance!.AnalyzerTaskIsRunning = false;
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DetectIntroductionsTask : IScheduledTask
|
public class DetectIntroductionsTask : IScheduledTask
|
||||||
{
|
{
|
||||||
|
private readonly ILogger<DetectIntroductionsTask> _logger;
|
||||||
|
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
@ -22,10 +24,13 @@ public class DetectIntroductionsTask : IScheduledTask
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="loggerFactory">Logger factory.</param>
|
/// <param name="loggerFactory">Logger factory.</param>
|
||||||
/// <param name="libraryManager">Library manager.</param>
|
/// <param name="libraryManager">Library manager.</param>
|
||||||
|
/// <param name="logger">Logger.</param>
|
||||||
public DetectIntroductionsTask(
|
public DetectIntroductionsTask(
|
||||||
|
ILogger<DetectIntroductionsTask> logger,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
ILibraryManager libraryManager)
|
ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
}
|
}
|
||||||
@ -63,6 +68,40 @@ public class DetectIntroductionsTask : IScheduledTask
|
|||||||
throw new InvalidOperationException("Library manager was null");
|
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(
|
var baseAnalyzer = new BaseItemAnalyzerTask(
|
||||||
AnalysisMode.Introduction,
|
AnalysisMode.Introduction,
|
||||||
_loggerFactory.CreateLogger<DetectIntroductionsTask>(),
|
_loggerFactory.CreateLogger<DetectIntroductionsTask>(),
|
||||||
@ -71,6 +110,8 @@ public class DetectIntroductionsTask : IScheduledTask
|
|||||||
|
|
||||||
baseAnalyzer.AnalyzeItems(progress, cancellationToken);
|
baseAnalyzer.AnalyzeItems(progress, cancellationToken);
|
||||||
|
|
||||||
|
Plugin.Instance!.AnalyzerTaskIsRunning = false;
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user