using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; namespace JellyfinPlugin.Tasks { public class LibraryRetroactiveScanTask : IScheduledTask { private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; public LibraryRetroactiveScanTask(ILogger logger, ILibraryManager libraryManager) { _logger = logger; _libraryManager = libraryManager; } public string Name => "Scan Missing Black Bars (Black Bar Remover)"; public string Key => "BlackBarRetroactiveScanTask"; public string Description => "Scans the existing library, skipping already analyzed files. Run this after installing the plugin."; public string Category => "Library"; public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) { var config = Plugin.Instance!.Configuration; if (!config.IsEnabled) { _logger.LogInformation("Plugin is disabled. Skipping."); return; } var items = _libraryManager.RootFolder.GetRecursiveChildren() .Where(x => x is Movie || x is Episode) .ToList(); int total = items.Count; if (total == 0) return; int processed = 0; int maxParallel = Math.Clamp(config.MaxParallelScans, 1, 16); var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = maxParallel, CancellationToken = cancellationToken }; _logger.LogInformation("Starting Retroactive Scan of {Total} items with {MaxParallel} parallel workers.", total, maxParallel); await Parallel.ForEachAsync(items, parallelOptions, async (item, ct) => { if (item.ProviderIds != null && item.ProviderIds.TryGetValue("BlackBarCrop", out string? existingCrop) && !string.IsNullOrEmpty(existingCrop)) { int currentSkipped = Interlocked.Increment(ref processed); progress.Report((currentSkipped / (double)total) * 100); return; } var path = item.Path; if (!string.IsNullOrEmpty(path) && File.Exists(path)) { try { string cropData = await CropHelper.DetectCropAsync(path, config.CropDetectLimit); if (!string.IsNullOrEmpty(cropData)) { if (item.ProviderIds == null) { item.ProviderIds = new Dictionary(); } item.ProviderIds["BlackBarCrop"] = cropData; await _libraryManager.UpdateItemAsync(item, item, ItemUpdateType.MetadataEdit, ct); _logger.LogInformation("Found black bars {Crop} in {Path}", cropData, path); } } catch (Exception ex) { _logger.LogError(ex, "Error scanning file {Path}", path); } } int currentDone = Interlocked.Increment(ref processed); progress.Report((currentDone / (double)total) * 100); }); } public IEnumerable GetDefaultTriggers() { return Array.Empty(); } } }