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;
///
/// Server entrypoint.
///
public class Entrypoint : IServerEntryPoint
{
private readonly IUserManager _userManager;
private readonly IUserViewManager _userViewManager;
private readonly ITaskManager _taskManager;
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly ILoggerFactory _loggerFactory;
private Timer _queueTimer;
private bool _analyzeAgain;
///
/// Initializes a new instance of the class.
///
/// User manager.
/// User view manager.
/// Library manager.
/// Task manager.
/// Logger.
/// Logger factory.
public Entrypoint(
IUserManager userManager,
IUserViewManager userViewManager,
ILibraryManager libraryManager,
ITaskManager taskManager,
ILogger logger,
ILoggerFactory loggerFactory)
{
_userManager = userManager;
_userViewManager = userViewManager;
_libraryManager = libraryManager;
_taskManager = taskManager;
_logger = logger;
_loggerFactory = loggerFactory;
_queueTimer = new Timer(
OnTimerCallback,
null,
Timeout.InfiniteTimeSpan,
Timeout.InfiniteTimeSpan);
}
///
/// Registers event handler.
///
/// Task.
public Task RunAsync()
{
if (Plugin.Instance!.Configuration.AutoDetectIntros || Plugin.Instance!.Configuration.AutoDetectCredits)
{
_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(), _libraryManager);
queueManager.GetMediaItems();
}
catch (Exception ex)
{
_logger.LogError("Unable to run startup enqueue: {Exception}", ex);
}
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
///
/// Library item was added.
///
/// The sending entity.
/// The .
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();
}
///
/// Library item was modified.
///
/// The sending entity.
/// The .
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();
}
///
/// TaskManager task ended.
///
/// The sending entity.
/// The .
private void OnLibraryRefresh(object? sender, TaskCompletionEventArgs eventArgs)
{
var result = eventArgs.Result;
if (result.Key != "RefreshLibrary")
{
return;
}
if (result.Status != TaskCompletionStatus.Completed)
{
return;
}
StartTimer();
}
///
/// Start timer to debounce analyzing.
///
private void StartTimer()
{
if (Plugin.Instance!.AnalyzerTaskIsRunning)
{
_analyzeAgain = true; // Items added during a scan will be included later.
}
else
{
_logger.LogInformation("Media Library changed, analyzis will start soon!");
_queueTimer.Change(TimeSpan.FromMilliseconds(20000), Timeout.InfiniteTimeSpan);
}
}
///
/// Wait for timer callback to be completed.
///
private void OnTimerCallback(object? state)
{
try
{
PerformAnalysis();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in PerformAnalysis");
}
}
///
/// Wait for timer to be completed.
///
private void PerformAnalysis()
{
_logger.LogInformation("Timer elapsed - start analyzing");
Plugin.Instance!.AnalyzerTaskIsRunning = true;
var progress = new Progress();
var cancellationToken = new CancellationToken(false);
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(),
_loggerFactory,
_libraryManager);
baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken);
var baseCreditAnalyzer = new BaseItemAnalyzerTask(
AnalysisMode.Credits,
_loggerFactory.CreateLogger(),
_loggerFactory,
_libraryManager);
baseCreditAnalyzer.AnalyzeItems(progress, cancellationToken);
}
else if (Plugin.Instance!.Configuration.AutoDetectIntros)
{
var baseIntroAnalyzer = new BaseItemAnalyzerTask(
AnalysisMode.Introduction,
_loggerFactory.CreateLogger(),
_loggerFactory,
_libraryManager);
baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken);
}
else if (Plugin.Instance!.Configuration.AutoDetectCredits)
{
var baseCreditAnalyzer = new BaseItemAnalyzerTask(
AnalysisMode.Credits,
_loggerFactory.CreateLogger(),
_loggerFactory,
_libraryManager);
baseCreditAnalyzer.AnalyzeItems(progress, cancellationToken);
}
Plugin.Instance!.AnalyzerTaskIsRunning = false;
// New item detected, start timer again
if (_analyzeAgain)
{
_logger.LogInformation("Analyzing ended, but we need to analyze again!");
_analyzeAgain = false;
StartTimer();
}
}
///
/// Dispose.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Protected dispose.
///
/// Dispose.
protected virtual void Dispose(bool dispose)
{
if (!dispose)
{
_libraryManager.ItemAdded -= OnItemAdded;
_libraryManager.ItemUpdated -= OnItemModified;
_taskManager.TaskCompleted -= OnLibraryRefresh;
_queueTimer.Dispose();
return;
}
}
}