2022-06-22 22:03:34 -05:00
namespace ConfusedPolarBear.Plugin.IntroSkipper ;
using System ;
using System.Collections.Generic ;
2022-06-27 00:21:30 -05:00
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 (
2022-06-26 19:02:50 -05:00
"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 ( ) ;
2022-06-27 00:21:30 -05:00
// 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 ;
}
2022-06-27 00:21:30 -05:00
// 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-06-27 00:21:30 -05:00
{
2022-07-25 00:27:24 -05:00
_logger . LogDebug ( "Not analyzing library \"{Name}\": not selected by user" , folder . Name ) ;
2022-06-27 00:21:30 -05:00
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 + + ;
}
}