2022-05-01 00:33:22 -05:00
|
|
|
using System;
|
2024-03-22 23:41:58 +01:00
|
|
|
using System.Threading;
|
2022-05-01 00:33:22 -05:00
|
|
|
using System.Threading.Tasks;
|
2024-03-29 16:58:16 +01:00
|
|
|
using MediaBrowser.Controller.Entities.TV;
|
2022-05-01 00:33:22 -05:00
|
|
|
using MediaBrowser.Controller.Library;
|
2024-03-29 16:58:16 +01:00
|
|
|
using MediaBrowser.Model.Entities;
|
|
|
|
using MediaBrowser.Model.Tasks;
|
2024-03-22 23:41:58 +01:00
|
|
|
using Microsoft.Extensions.Hosting;
|
2022-05-01 00:33:22 -05:00
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
|
|
namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Server entrypoint.
|
|
|
|
/// </summary>
|
2024-04-10 15:13:37 +02:00
|
|
|
public class Entrypoint : IHostedService, IDisposable
|
2022-05-01 00:33:22 -05:00
|
|
|
{
|
|
|
|
private readonly IUserManager _userManager;
|
|
|
|
private readonly IUserViewManager _userViewManager;
|
2024-03-29 16:58:16 +01:00
|
|
|
private readonly ITaskManager _taskManager;
|
2022-05-01 00:33:22 -05:00
|
|
|
private readonly ILibraryManager _libraryManager;
|
|
|
|
private readonly ILogger<Entrypoint> _logger;
|
2022-06-22 22:03:34 -05:00
|
|
|
private readonly ILoggerFactory _loggerFactory;
|
2024-03-29 16:58:16 +01:00
|
|
|
private Timer _queueTimer;
|
2024-03-30 19:02:18 +01:00
|
|
|
private bool _analyzeAgain;
|
2024-04-13 16:34:58 +02:00
|
|
|
private static CancellationTokenSource? _cancellationTokenSource;
|
|
|
|
private static ManualResetEventSlim _autoTaskCompletEvent = new ManualResetEventSlim(false);
|
2022-05-01 00:33:22 -05:00
|
|
|
|
|
|
|
/// <summary>
|
2022-05-09 22:50:41 -05:00
|
|
|
/// Initializes a new instance of the <see cref="Entrypoint"/> class.
|
2022-05-01 00:33:22 -05:00
|
|
|
/// </summary>
|
|
|
|
/// <param name="userManager">User manager.</param>
|
|
|
|
/// <param name="userViewManager">User view manager.</param>
|
|
|
|
/// <param name="libraryManager">Library manager.</param>
|
2024-03-29 16:58:16 +01:00
|
|
|
/// <param name="taskManager">Task manager.</param>
|
2022-05-01 00:33:22 -05:00
|
|
|
/// <param name="logger">Logger.</param>
|
2022-06-22 22:03:34 -05:00
|
|
|
/// <param name="loggerFactory">Logger factory.</param>
|
2022-05-01 00:33:22 -05:00
|
|
|
public Entrypoint(
|
|
|
|
IUserManager userManager,
|
|
|
|
IUserViewManager userViewManager,
|
|
|
|
ILibraryManager libraryManager,
|
2024-03-29 16:58:16 +01:00
|
|
|
ITaskManager taskManager,
|
2022-06-22 22:03:34 -05:00
|
|
|
ILogger<Entrypoint> logger,
|
|
|
|
ILoggerFactory loggerFactory)
|
2022-05-01 00:33:22 -05:00
|
|
|
{
|
|
|
|
_userManager = userManager;
|
|
|
|
_userViewManager = userViewManager;
|
|
|
|
_libraryManager = libraryManager;
|
2024-03-29 16:58:16 +01:00
|
|
|
_taskManager = taskManager;
|
2022-05-01 00:33:22 -05:00
|
|
|
_logger = logger;
|
2022-06-22 22:03:34 -05:00
|
|
|
_loggerFactory = loggerFactory;
|
2024-03-29 16:58:16 +01:00
|
|
|
|
|
|
|
_queueTimer = new Timer(
|
|
|
|
OnTimerCallback,
|
|
|
|
null,
|
|
|
|
Timeout.InfiniteTimeSpan,
|
|
|
|
Timeout.InfiniteTimeSpan);
|
|
|
|
}
|
|
|
|
|
2024-04-13 16:34:58 +02:00
|
|
|
/// <summary>
|
|
|
|
/// Gets State of the automatic task.
|
|
|
|
/// </summary>
|
|
|
|
public static TaskState AutomaticTaskState
|
|
|
|
{
|
|
|
|
get
|
|
|
|
{
|
|
|
|
if (_cancellationTokenSource is not null)
|
|
|
|
{
|
|
|
|
return _cancellationTokenSource.IsCancellationRequested
|
|
|
|
? TaskState.Cancelling
|
|
|
|
: TaskState.Running;
|
|
|
|
}
|
|
|
|
|
|
|
|
return TaskState.Idle;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-10 15:13:37 +02:00
|
|
|
/// <inheritdoc />
|
|
|
|
public Task StartAsync(CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
_libraryManager.ItemAdded += OnItemAdded;
|
|
|
|
_libraryManager.ItemUpdated += OnItemModified;
|
|
|
|
_taskManager.TaskCompleted += OnLibraryRefresh;
|
|
|
|
|
|
|
|
FFmpegWrapper.Logger = _logger;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
// Enqueue all episodes at startup to ensure any FFmpeg errors appear as early as possible
|
|
|
|
_logger.LogInformation("Running startup enqueue");
|
|
|
|
var queueManager = new QueueManager(_loggerFactory.CreateLogger<QueueManager>(), _libraryManager);
|
|
|
|
queueManager?.GetMediaItems();
|
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
_logger.LogError("Unable to run startup enqueue: {Exception}", ex);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public Task StopAsync(CancellationToken cancellationToken)
|
|
|
|
{
|
2024-04-11 16:58:37 +02:00
|
|
|
_libraryManager.ItemAdded -= OnItemAdded;
|
|
|
|
_libraryManager.ItemUpdated -= OnItemModified;
|
|
|
|
_taskManager.TaskCompleted -= OnLibraryRefresh;
|
|
|
|
|
|
|
|
// Stop the timer
|
|
|
|
_queueTimer.Change(Timeout.Infinite, 0);
|
|
|
|
|
2024-04-13 16:34:58 +02:00
|
|
|
if (_cancellationTokenSource != null) // Null Check
|
2024-04-20 12:29:40 +02:00
|
|
|
{
|
|
|
|
_cancellationTokenSource.Dispose();
|
|
|
|
_cancellationTokenSource = null;
|
|
|
|
}
|
2024-04-13 16:34:58 +02:00
|
|
|
|
2024-04-10 15:13:37 +02:00
|
|
|
return Task.CompletedTask;
|
|
|
|
}
|
|
|
|
|
2024-03-29 16:58:16 +01:00
|
|
|
// 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)
|
|
|
|
{
|
2024-04-03 17:09:42 +02:00
|
|
|
// Don't do anything if auto detection is disabled
|
2024-04-20 12:21:07 +02:00
|
|
|
if (!Plugin.Instance!.Configuration.AutoDetectIntros && !Plugin.Instance.Configuration.AutoDetectCredits)
|
2024-04-03 17:09:42 +02:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-03-29 16:58:16 +01:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2024-04-20 12:21:07 +02:00
|
|
|
Plugin.Instance.Configuration.PathRestrictions.Add(itemChangeEventArgs.Item.ContainingFolderPath);
|
2024-04-14 01:13:43 -04:00
|
|
|
|
2024-03-29 16:58:16 +01:00
|
|
|
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)
|
|
|
|
{
|
2024-04-03 17:09:42 +02:00
|
|
|
// Don't do anything if auto detection is disabled
|
2024-04-20 12:21:07 +02:00
|
|
|
if (!Plugin.Instance!.Configuration.AutoDetectIntros && !Plugin.Instance.Configuration.AutoDetectCredits)
|
2024-04-03 17:09:42 +02:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-03-29 16:58:16 +01:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2024-04-20 12:21:07 +02:00
|
|
|
Plugin.Instance.Configuration.PathRestrictions.Add(itemChangeEventArgs.Item.ContainingFolderPath);
|
2024-04-14 01:13:43 -04:00
|
|
|
|
2024-03-29 16:58:16 +01:00
|
|
|
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)
|
|
|
|
{
|
2024-04-03 17:09:42 +02:00
|
|
|
// Don't do anything if auto detection is disabled
|
2024-04-20 12:21:07 +02:00
|
|
|
if (!Plugin.Instance!.Configuration.AutoDetectIntros && !Plugin.Instance.Configuration.AutoDetectCredits)
|
2024-04-03 17:09:42 +02:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-03-29 16:58:16 +01:00
|
|
|
var result = eventArgs.Result;
|
|
|
|
|
|
|
|
if (result.Key != "RefreshLibrary")
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result.Status != TaskCompletionStatus.Completed)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-04-16 14:01:48 -04:00
|
|
|
// Unless user initiated, this is likely an overlap
|
2024-04-20 12:58:29 +02:00
|
|
|
if (AutomaticTaskState == TaskState.Running)
|
2024-04-14 09:36:27 -04:00
|
|
|
{
|
2024-04-16 14:01:48 -04:00
|
|
|
return;
|
2024-04-14 09:36:27 -04:00
|
|
|
}
|
|
|
|
|
2024-03-29 16:58:16 +01:00
|
|
|
StartTimer();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Start timer to debounce analyzing.
|
|
|
|
/// </summary>
|
|
|
|
private void StartTimer()
|
|
|
|
{
|
2024-04-20 12:58:29 +02:00
|
|
|
if (AutomaticTaskState == TaskState.Running)
|
2024-03-29 16:58:16 +01:00
|
|
|
{
|
2024-04-20 12:29:40 +02:00
|
|
|
_analyzeAgain = true; // Items added during a scan will be included later.
|
2024-03-29 16:58:16 +01:00
|
|
|
}
|
2024-04-16 18:19:05 +02:00
|
|
|
else if (ScheduledTaskSemaphore.CurrentCount > 0)
|
2024-03-29 16:58:16 +01:00
|
|
|
{
|
|
|
|
_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");
|
2024-04-16 18:19:05 +02:00
|
|
|
_autoTaskCompletEvent.Reset();
|
2024-03-29 16:58:16 +01:00
|
|
|
|
2024-04-13 16:34:58 +02:00
|
|
|
using (_cancellationTokenSource = new CancellationTokenSource())
|
2024-03-30 19:02:18 +01:00
|
|
|
{
|
2024-04-13 16:34:58 +02:00
|
|
|
var progress = new Progress<double>();
|
|
|
|
var cancellationToken = _cancellationTokenSource.Token;
|
|
|
|
|
2024-04-20 12:21:07 +02:00
|
|
|
if (Plugin.Instance!.Configuration.AutoDetectIntros && Plugin.Instance.Configuration.AutoDetectCredits)
|
2024-04-13 16:34:58 +02:00
|
|
|
{
|
|
|
|
// 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);
|
|
|
|
}
|
2024-04-20 12:21:07 +02:00
|
|
|
else if (Plugin.Instance.Configuration.AutoDetectIntros)
|
2024-04-13 16:34:58 +02:00
|
|
|
{
|
|
|
|
var baseIntroAnalyzer = new BaseItemAnalyzerTask(
|
|
|
|
AnalysisMode.Introduction,
|
|
|
|
_loggerFactory.CreateLogger<DetectIntrosTask>(),
|
|
|
|
_loggerFactory,
|
|
|
|
_libraryManager);
|
|
|
|
|
|
|
|
baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken);
|
|
|
|
}
|
2024-04-20 12:21:07 +02:00
|
|
|
else if (Plugin.Instance.Configuration.AutoDetectCredits)
|
2024-04-13 16:34:58 +02:00
|
|
|
{
|
|
|
|
var baseCreditAnalyzer = new BaseItemAnalyzerTask(
|
|
|
|
AnalysisMode.Credits,
|
|
|
|
_loggerFactory.CreateLogger<DetectCreditsTask>(),
|
|
|
|
_loggerFactory,
|
|
|
|
_libraryManager);
|
|
|
|
|
|
|
|
baseCreditAnalyzer.AnalyzeItems(progress, cancellationToken);
|
|
|
|
}
|
2024-03-30 19:02:18 +01:00
|
|
|
}
|
2024-03-29 16:58:16 +01:00
|
|
|
|
2024-04-20 12:21:07 +02:00
|
|
|
Plugin.Instance.Configuration.PathRestrictions.Clear();
|
2024-04-13 16:34:58 +02:00
|
|
|
_autoTaskCompletEvent.Set();
|
2024-04-16 18:19:05 +02:00
|
|
|
_cancellationTokenSource = null;
|
2024-03-30 19:02:18 +01:00
|
|
|
|
|
|
|
// New item detected, start timer again
|
|
|
|
if (_analyzeAgain)
|
|
|
|
{
|
|
|
|
_logger.LogInformation("Analyzing ended, but we need to analyze again!");
|
|
|
|
_analyzeAgain = false;
|
|
|
|
StartTimer();
|
|
|
|
}
|
2022-05-01 00:33:22 -05:00
|
|
|
}
|
|
|
|
|
2024-04-13 16:34:58 +02:00
|
|
|
/// <summary>
|
|
|
|
/// Method to cancel the automatic task.
|
|
|
|
/// </summary>
|
2024-04-16 18:19:05 +02:00
|
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
|
|
public static void CancelAutomaticTask(CancellationToken cancellationToken)
|
2024-04-13 16:34:58 +02:00
|
|
|
{
|
|
|
|
if (_cancellationTokenSource != null)
|
|
|
|
{
|
2024-04-16 18:19:05 +02:00
|
|
|
if (!_cancellationTokenSource.IsCancellationRequested)
|
|
|
|
{
|
|
|
|
_cancellationTokenSource.Cancel();
|
|
|
|
}
|
2024-04-13 16:34:58 +02:00
|
|
|
|
2024-04-16 18:19:05 +02:00
|
|
|
_autoTaskCompletEvent.Wait(TimeSpan.FromSeconds(60), cancellationToken); // Wait for the signal
|
2024-04-13 16:34:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-01 00:33:22 -05:00
|
|
|
/// <summary>
|
2024-03-22 23:41:58 +01:00
|
|
|
/// Dispose.
|
|
|
|
/// </summary>
|
|
|
|
public void Dispose()
|
|
|
|
{
|
|
|
|
Dispose(true);
|
2024-04-10 15:13:37 +02:00
|
|
|
GC.SuppressFinalize(this);
|
2024-03-22 23:41:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Protected dispose.
|
2022-05-01 00:33:22 -05:00
|
|
|
/// </summary>
|
2024-03-22 23:41:58 +01:00
|
|
|
/// <param name="dispose">Dispose.</param>
|
|
|
|
protected virtual void Dispose(bool dispose)
|
|
|
|
{
|
|
|
|
if (!dispose)
|
|
|
|
{
|
2024-03-29 16:58:16 +01:00
|
|
|
_queueTimer.Dispose();
|
2024-03-22 23:41:58 +01:00
|
|
|
}
|
|
|
|
}
|
2022-05-01 00:33:22 -05:00
|
|
|
}
|