2022-05-01 00:33:22 -05:00
|
|
|
using System;
|
2024-03-29 16:58:16 +01:00
|
|
|
using System.IO;
|
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.Controller.Plugins;
|
|
|
|
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-03-22 23:41:58 +01:00
|
|
|
public class Entrypoint : IHostedService
|
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;
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
{
|
2024-03-30 19:02:18 +01:00
|
|
|
_analyzeAgain = true; // Items added during a scan will be included later.
|
2024-03-29 16:58:16 +01:00
|
|
|
}
|
|
|
|
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);
|
|
|
|
|
2024-03-30 18:22:54 -04:00
|
|
|
if (Plugin.Instance!.Configuration.AutoDetectIntros && Plugin.Instance!.Configuration.AutoDetectCredits)
|
2024-03-30 19:02:18 +01:00
|
|
|
{
|
2024-03-30 18:22:54 -04:00
|
|
|
// This is where we can optimize a single scan
|
2024-03-30 19:02:18 +01:00
|
|
|
var baseIntroAnalyzer = new BaseItemAnalyzerTask(
|
|
|
|
AnalysisMode.Introduction,
|
2024-03-30 18:22:54 -04:00
|
|
|
_loggerFactory.CreateLogger<DetectIntrosCreditsTask>(),
|
2024-03-30 19:02:18 +01:00
|
|
|
_loggerFactory,
|
|
|
|
_libraryManager);
|
2024-03-29 16:58:16 +01:00
|
|
|
|
2024-03-30 19:02:18 +01:00
|
|
|
baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken);
|
2024-03-30 18:22:54 -04:00
|
|
|
|
|
|
|
var baseCreditAnalyzer = new BaseItemAnalyzerTask(
|
|
|
|
AnalysisMode.Credits,
|
|
|
|
_loggerFactory.CreateLogger<DetectIntrosCreditsTask>(),
|
|
|
|
_loggerFactory,
|
|
|
|
_libraryManager);
|
|
|
|
|
|
|
|
baseCreditAnalyzer.AnalyzeItems(progress, cancellationToken);
|
2024-03-30 19:02:18 +01:00
|
|
|
}
|
2024-03-30 18:22:54 -04:00
|
|
|
else if (Plugin.Instance!.Configuration.AutoDetectIntros)
|
|
|
|
{
|
|
|
|
var baseIntroAnalyzer = new BaseItemAnalyzerTask(
|
|
|
|
AnalysisMode.Introduction,
|
|
|
|
_loggerFactory.CreateLogger<DetectIntrosTask>(),
|
|
|
|
_loggerFactory,
|
|
|
|
_libraryManager);
|
2024-03-29 16:58:16 +01:00
|
|
|
|
2024-03-30 18:22:54 -04:00
|
|
|
baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken);
|
|
|
|
}
|
|
|
|
else if (Plugin.Instance!.Configuration.AutoDetectCredits)
|
2024-03-30 19:02:18 +01:00
|
|
|
{
|
|
|
|
var baseCreditAnalyzer = new BaseItemAnalyzerTask(
|
|
|
|
AnalysisMode.Credits,
|
2024-03-30 18:22:54 -04:00
|
|
|
_loggerFactory.CreateLogger<DetectCreditsTask>(),
|
2024-03-30 19:02:18 +01:00
|
|
|
_loggerFactory,
|
|
|
|
_libraryManager);
|
2024-03-29 16:58:16 +01:00
|
|
|
|
2024-03-30 19:02:18 +01:00
|
|
|
baseCreditAnalyzer.AnalyzeItems(progress, cancellationToken);
|
|
|
|
}
|
2024-03-29 16:58:16 +01:00
|
|
|
|
|
|
|
Plugin.Instance!.AnalyzerTaskIsRunning = false;
|
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
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
2024-03-22 23:41:58 +01:00
|
|
|
/// Dispose.
|
|
|
|
/// </summary>
|
|
|
|
public void Dispose()
|
|
|
|
{
|
|
|
|
Dispose(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <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
|
|
|
_libraryManager.ItemAdded -= OnItemAdded;
|
|
|
|
_libraryManager.ItemUpdated -= OnItemModified;
|
|
|
|
_taskManager.TaskCompleted -= OnLibraryRefresh;
|
|
|
|
|
|
|
|
_queueTimer.Dispose();
|
|
|
|
|
2024-03-22 23:41:58 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
public Task StartAsync(CancellationToken cancellationToken)
|
2022-05-01 00:33:22 -05:00
|
|
|
{
|
2024-03-30 18:22:54 -04:00
|
|
|
if (Plugin.Instance!.Configuration.AutoDetectIntros || Plugin.Instance!.Configuration.AutoDetectCredits)
|
2024-03-29 16:58:16 +01:00
|
|
|
{
|
|
|
|
_libraryManager.ItemAdded += OnItemAdded;
|
|
|
|
_libraryManager.ItemUpdated += OnItemModified;
|
|
|
|
_taskManager.TaskCompleted += OnLibraryRefresh;
|
|
|
|
}
|
2022-05-01 00:33:22 -05:00
|
|
|
|
2024-03-29 16:58:16 +01:00
|
|
|
FFmpegWrapper.Logger = _logger;
|
2022-05-01 00:33:22 -05:00
|
|
|
|
2022-06-12 16:08:31 -05:00
|
|
|
try
|
2022-05-09 22:50:41 -05:00
|
|
|
{
|
2022-11-23 02:34:28 -06:00
|
|
|
// Enqueue all episodes at startup to ensure any FFmpeg errors appear as early as possible
|
2022-06-22 22:03:34 -05:00
|
|
|
_logger.LogInformation("Running startup enqueue");
|
|
|
|
var queueManager = new QueueManager(_loggerFactory.CreateLogger<QueueManager>(), _libraryManager);
|
2024-03-22 23:41:58 +01:00
|
|
|
queueManager?.GetMediaItems();
|
2022-06-12 16:08:31 -05:00
|
|
|
}
|
|
|
|
catch (Exception ex)
|
|
|
|
{
|
|
|
|
_logger.LogError("Unable to run startup enqueue: {Exception}", ex);
|
2022-05-01 00:33:22 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
}
|
|
|
|
|
2024-03-22 23:41:58 +01:00
|
|
|
/// <inheritdoc />
|
|
|
|
public Task StopAsync(CancellationToken cancellationToken)
|
2022-05-01 00:33:22 -05:00
|
|
|
{
|
2024-03-22 23:41:58 +01:00
|
|
|
return Task.CompletedTask;
|
2022-05-01 00:33:22 -05:00
|
|
|
}
|
|
|
|
}
|