using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Enums; using Jellyfin.MediaEncoding.Hls.Extractors; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Tasks; namespace Jellyfin.MediaEncoding.Hls.ScheduledTasks; /// public class KeyframeExtractionScheduledTask : IScheduledTask { private const int Pagesize = 1000; private readonly ILocalizationManager _localizationManager; private readonly ILibraryManager _libraryManager; private readonly IKeyframeExtractor[] _keyframeExtractors; private static readonly BaseItemKind[] _itemTypes = { BaseItemKind.Episode, BaseItemKind.Movie }; /// /// Initializes a new instance of the class. /// /// An instance of the interface. /// An instance of the interface. /// The keyframe extractors. public KeyframeExtractionScheduledTask(ILocalizationManager localizationManager, ILibraryManager libraryManager, IEnumerable keyframeExtractors) { _localizationManager = localizationManager; _libraryManager = libraryManager; _keyframeExtractors = keyframeExtractors.OrderByDescending(e => e.IsMetadataBased).ToArray(); } /// public string Name => "Keyframe Extractor"; /// public string Key => "KeyframeExtraction"; /// public string Description => "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time."; /// public string Category => _localizationManager.GetLocalizedString("TasksLibraryCategory"); /// public Task Execute(CancellationToken cancellationToken, IProgress progress) { var query = new InternalItemsQuery { MediaTypes = new[] { MediaType.Video }, IsVirtualItem = false, IncludeItemTypes = _itemTypes, DtoOptions = new DtoOptions(true), SourceTypes = new[] { SourceType.Library }, Recursive = true, Limit = Pagesize }; var numberOfVideos = _libraryManager.GetCount(query); var startIndex = 0; var numComplete = 0; while (startIndex < numberOfVideos) { query.StartIndex = startIndex; var videos = _libraryManager.GetItemList(query); var currentPageCount = videos.Count; // TODO parallelize with Parallel.ForEach? for (var i = 0; i < currentPageCount; i++) { var video = videos[i]; // Only local files supported if (video.IsFileProtocol && File.Exists(video.Path)) { for (var j = 0; j < _keyframeExtractors.Length; j++) { var extractor = _keyframeExtractors[j]; // The cache decorator will make sure to save them in the data dir if (extractor.TryExtractKeyframes(video.Path, out _)) { break; } } } // Update progress numComplete++; double percent = (double)numComplete / numberOfVideos; progress.Report(100 * percent); } startIndex += Pagesize; } progress.Report(100); return Task.CompletedTask; } /// public IEnumerable GetDefaultTriggers() => Enumerable.Empty(); }