220 lines
7.7 KiB
C#
Raw Normal View History

2022-06-22 22:03:34 -05:00
namespace ConfusedPolarBear.Plugin.IntroSkipper;
using System;
using System.Collections.Generic;
using System.Linq;
2022-06-22 22:03:34 -05:00
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging;
/// <summary>
/// Manages enqueuing library items for analysis.
/// </summary>
public class QueueManager
{
private ILibraryManager _libraryManager;
private ILogger<QueueManager> _logger;
2022-06-26 22:54:47 -05:00
private double analysisPercent;
2022-07-08 00:57:12 -05:00
private IList<string> selectedLibraries;
2022-06-26 22:54:47 -05:00
2022-06-22 22:03:34 -05:00
/// <summary>
/// Initializes a new instance of the <see cref="QueueManager"/> class.
/// </summary>
/// <param name="logger">Logger.</param>
/// <param name="libraryManager">Library manager.</param>
public QueueManager(ILogger<QueueManager> logger, ILibraryManager libraryManager)
{
_logger = logger;
_libraryManager = libraryManager;
2022-07-08 00:57:12 -05:00
selectedLibraries = new List<string>();
2022-06-22 22:03:34 -05:00
}
/// <summary>
/// Iterates through all libraries on the server and queues all episodes for analysis.
/// </summary>
public void EnqueueAllEpisodes()
{
// Assert that ffmpeg with chromaprint is installed
2022-08-28 22:35:43 -05:00
if (!FFmpegWrapper.CheckFFmpegVersion())
2022-06-22 22:03:34 -05:00
{
throw new FingerprintException(
"ffmpeg with chromaprint is not installed on this system - episodes will not be analyzed. If Jellyfin is running natively, install jellyfin-ffmpeg5. If Jellyfin is running in a container, upgrade it to the latest version of 10.8.0.");
2022-06-22 22:03:34 -05:00
}
Plugin.Instance!.AnalysisQueue.Clear();
2022-06-27 00:34:57 -05:00
Plugin.Instance!.TotalQueued = 0;
2022-06-22 22:03:34 -05:00
2022-07-08 00:57:12 -05:00
LoadAnalysisSettings();
// For all selected TV show libraries, enqueue all contained items.
2022-06-22 22:03:34 -05:00
foreach (var folder in _libraryManager.GetVirtualFolders())
{
if (folder.CollectionType != CollectionTypeOptions.TvShows)
{
2022-07-25 00:27:24 -05:00
_logger.LogDebug("Not analyzing library \"{Name}\": not a TV show library", folder.Name);
2022-06-22 22:03:34 -05:00
continue;
}
// If libraries have been selected for analysis, ensure this library was selected.
2022-07-08 00:57:12 -05:00
if (selectedLibraries.Count > 0 && !selectedLibraries.Contains(folder.Name))
{
2022-07-25 00:27:24 -05:00
_logger.LogDebug("Not analyzing library \"{Name}\": not selected by user", folder.Name);
continue;
}
2022-06-22 22:03:34 -05:00
_logger.LogInformation(
"Running enqueue of items in library {Name} ({ItemId})",
folder.Name,
folder.ItemId);
try
{
QueueLibraryContents(folder.ItemId);
}
catch (Exception ex)
{
_logger.LogError("Failed to enqueue items from library {Name}: {Exception}", folder.Name, ex);
}
}
}
2022-07-08 00:57:12 -05:00
/// <summary>
/// Loads the list of libraries which have been selected for analysis and the minimum intro duration.
/// Settings which have been modified from the defaults are logged.
/// </summary>
private void LoadAnalysisSettings()
{
var config = Plugin.Instance!.Configuration;
// Store the analysis percent
analysisPercent = Convert.ToDouble(config.AnalysisPercent) / 100;
// Get the list of library names which have been selected for analysis, ignoring whitespace and empty entries.
selectedLibraries = config.SelectedLibraries
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.ToList();
// If any libraries have been selected for analysis, log their names.
if (selectedLibraries.Count > 0)
{
_logger.LogInformation("Limiting analysis to the following libraries: {Selected}", selectedLibraries);
}
else
{
_logger.LogDebug("Not limiting analysis by library name");
}
// If analysis settings have been changed from the default, log the modified settings.
if (config.AnalysisLengthLimit != 10 || config.AnalysisPercent != 25 || config.MinimumIntroDuration != 15)
{
_logger.LogInformation(
"Analysis settings have been changed to: {Percent}%/{Minutes}m and a minimum of {Minimum}s",
config.AnalysisPercent,
config.AnalysisLengthLimit,
config.MinimumIntroDuration);
}
}
2022-06-22 22:03:34 -05:00
private void QueueLibraryContents(string rawId)
{
_logger.LogDebug("Constructing anonymous internal query");
var query = new InternalItemsQuery()
{
// Order by series name, season, and then episode number so that status updates are logged in order
ParentId = Guid.Parse(rawId),
OrderBy = new[]
{
("SeriesSortName", SortOrder.Ascending),
("ParentIndexNumber", SortOrder.Ascending),
("IndexNumber", SortOrder.Ascending),
},
IncludeItemTypes = new BaseItemKind[] { BaseItemKind.Episode },
Recursive = true,
2022-09-10 02:24:44 -05:00
IsVirtualItem = false
2022-06-22 22:03:34 -05:00
};
_logger.LogDebug("Getting items");
var items = _libraryManager.GetItemList(query, false);
if (items is null)
{
_logger.LogError("Library query result is null");
return;
}
// Queue all episodes on the server for fingerprinting.
_logger.LogDebug("Iterating through library items");
foreach (var item in items)
{
if (item is not Episode episode)
{
_logger.LogError("Item {Name} is not an episode", item.Name);
continue;
}
QueueEpisode(episode);
}
_logger.LogDebug("Queued {Count} episodes", items.Count);
}
private void QueueEpisode(Episode episode)
{
if (Plugin.Instance is null)
{
throw new InvalidOperationException("plugin instance was null");
}
if (string.IsNullOrEmpty(episode.Path))
{
2022-09-10 02:24:44 -05:00
_logger.LogWarning(
"Not queuing episode \"{Name}\" from series \"{Series}\" ({Id}) as no path was provided by Jellyfin",
episode.Name,
episode.SeriesName,
episode.Id);
2022-06-22 22:03:34 -05:00
return;
}
2022-06-26 22:54:47 -05:00
// Limit analysis to the first X% of the episode and at most Y minutes.
// X and Y default to 25% and 10 minutes.
2022-06-22 22:03:34 -05:00
var duration = TimeSpan.FromTicks(episode.RunTimeTicks ?? 0).TotalSeconds;
2022-10-31 01:00:39 -05:00
var fingerprintDuration = duration;
if (fingerprintDuration >= 5 * 60)
2022-06-22 22:03:34 -05:00
{
2022-10-31 01:00:39 -05:00
fingerprintDuration *= analysisPercent;
2022-06-22 22:03:34 -05:00
}
2022-10-31 01:00:39 -05:00
fingerprintDuration = Math.Min(
fingerprintDuration,
60 * Plugin.Instance!.Configuration.AnalysisLengthLimit);
2022-06-22 22:03:34 -05:00
2022-07-03 02:47:48 -05:00
// Allocate a new list for each new season
Plugin.Instance!.AnalysisQueue.TryAdd(episode.SeasonId, new List<QueuedEpisode>());
// Queue the episode for analysis
2022-10-31 01:00:39 -05:00
var maxCreditsDuration = Plugin.Instance!.Configuration.MaximumEpisodeCreditsDuration * 60;
2022-06-22 22:03:34 -05:00
Plugin.Instance.AnalysisQueue[episode.SeasonId].Add(new QueuedEpisode()
{
SeriesName = episode.SeriesName,
SeasonNumber = episode.AiredSeasonNumber ?? 0,
EpisodeId = episode.Id,
Name = episode.Name,
Path = episode.Path,
2022-10-31 01:00:39 -05:00
Duration = Convert.ToInt32(duration),
IntroFingerprintEnd = Convert.ToInt32(fingerprintDuration),
CreditsFingerprintStart = Convert.ToInt32(duration - maxCreditsDuration),
2022-06-22 22:03:34 -05:00
});
Plugin.Instance!.TotalQueued++;
}
}