< Summary - Jellyfin

Information
Class: Emby.Server.Implementations.ScheduledTasks.Tasks.ChapterImagesTask
Assembly: Emby.Server.Implementations
File(s): /srv/git/jellyfin/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
Line coverage
22%
Covered lines: 15
Uncovered lines: 52
Coverable lines: 67
Total lines: 168
Line coverage: 22.3%
Branch coverage
0%
Covered branches: 0
Total branches: 12
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 1/23/2026 - 12:11:06 AM Line coverage: 72.7% (8/11) Total lines: 1684/19/2026 - 12:14:27 AM Line coverage: 22.3% (15/67) Branch coverage: 0% (0/12) Total lines: 168 4/19/2026 - 12:14:27 AM Line coverage: 22.3% (15/67) Branch coverage: 0% (0/12) Total lines: 168

Coverage delta

Coverage delta 51 -51

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Name()100%11100%
get_Description()100%210%
get_Category()100%210%
get_Key()100%210%
GetDefaultTriggers()100%11100%
ExecuteAsync()0%156120%

File(s)

/srv/git/jellyfin/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.IO;
 4using System.Linq;
 5using System.Threading;
 6using System.Threading.Tasks;
 7using Jellyfin.Data.Enums;
 8using Jellyfin.Extensions;
 9using MediaBrowser.Common.Configuration;
 10using MediaBrowser.Controller.Chapters;
 11using MediaBrowser.Controller.Dto;
 12using MediaBrowser.Controller.Entities;
 13using MediaBrowser.Controller.Library;
 14using MediaBrowser.Controller.Providers;
 15using MediaBrowser.Model.Globalization;
 16using MediaBrowser.Model.IO;
 17using MediaBrowser.Model.Tasks;
 18using Microsoft.Extensions.Logging;
 19
 20namespace Emby.Server.Implementations.ScheduledTasks.Tasks;
 21
 22/// <summary>
 23/// Class ChapterImagesTask.
 24/// </summary>
 25public class ChapterImagesTask : IScheduledTask
 26{
 27    private readonly ILogger<ChapterImagesTask> _logger;
 28    private readonly ILibraryManager _libraryManager;
 29    private readonly IApplicationPaths _appPaths;
 30    private readonly IChapterManager _chapterManager;
 31    private readonly IFileSystem _fileSystem;
 32    private readonly ILocalizationManager _localization;
 33
 34    /// <summary>
 35    /// Initializes a new instance of the <see cref="ChapterImagesTask" /> class.
 36    /// </summary>
 37    /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
 38    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 39    /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
 40    /// <param name="chapterManager">Instance of the <see cref="IChapterManager"/> interface.</param>
 41    /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
 42    /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
 43    public ChapterImagesTask(
 44        ILogger<ChapterImagesTask> logger,
 45        ILibraryManager libraryManager,
 46        IApplicationPaths appPaths,
 47        IChapterManager chapterManager,
 48        IFileSystem fileSystem,
 49        ILocalizationManager localization)
 50    {
 2151        _logger = logger;
 2152        _libraryManager = libraryManager;
 2153        _appPaths = appPaths;
 2154        _chapterManager = chapterManager;
 2155        _fileSystem = fileSystem;
 2156        _localization = localization;
 2157    }
 58
 59    /// <inheritdoc />
 2160    public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages");
 61
 62    /// <inheritdoc />
 063    public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription");
 64
 65    /// <inheritdoc />
 066    public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
 67
 68    /// <inheritdoc />
 069    public string Key => "RefreshChapterImages";
 70
 71    /// <inheritdoc />
 72    public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
 73    {
 2174        yield return new TaskTriggerInfo
 2175        {
 2176            Type = TaskTriggerInfoType.DailyTrigger,
 2177            TimeOfDayTicks = TimeSpan.FromHours(2).Ticks,
 2178            MaxRuntimeTicks = TimeSpan.FromHours(4).Ticks
 2179        };
 2180    }
 81
 82    /// <inheritdoc />
 83    public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
 84    {
 085        var videos = _libraryManager.GetItemList(new InternalItemsQuery
 086        {
 087            MediaTypes = [MediaType.Video],
 088            IsFolder = false,
 089            Recursive = true,
 090            DtoOptions = new DtoOptions(false)
 091            {
 092                EnableImages = false
 093            },
 094            SourceTypes = [SourceType.Library],
 095            IsVirtualItem = false
 096        })
 097        .OfType<Video>()
 098        .ToList();
 99
 0100        var numComplete = 0;
 101
 0102        var failHistoryPath = Path.Combine(_appPaths.CachePath, "chapter-failures.txt");
 103
 104        List<string> previouslyFailedImages;
 105
 0106        if (File.Exists(failHistoryPath))
 107        {
 108            try
 109            {
 0110                previouslyFailedImages = (await File.ReadAllTextAsync(failHistoryPath, cancellationToken).ConfigureAwait
 0111                    .Split('|', StringSplitOptions.RemoveEmptyEntries)
 0112                    .ToList();
 0113            }
 0114            catch (IOException)
 115            {
 0116                previouslyFailedImages = [];
 0117            }
 118        }
 119        else
 120        {
 0121            previouslyFailedImages = [];
 122        }
 123
 0124        var directoryService = new DirectoryService(_fileSystem);
 125
 0126        foreach (var video in videos)
 127        {
 0128            cancellationToken.ThrowIfCancellationRequested();
 129
 0130            var key = video.Path + video.DateModified.Ticks;
 131
 0132            var extract = !previouslyFailedImages.Contains(key, StringComparison.OrdinalIgnoreCase);
 133
 134            try
 135            {
 0136                var chapters = _chapterManager.GetChapters(video.Id);
 137
 0138                var success = await _chapterManager.RefreshChapterImages(video, directoryService, chapters, extract, tru
 139
 0140                if (!success)
 141                {
 0142                    previouslyFailedImages.Add(key);
 143
 0144                    var parentPath = Path.GetDirectoryName(failHistoryPath);
 0145                    if (parentPath is not null)
 146                    {
 0147                        Directory.CreateDirectory(parentPath);
 148                    }
 149
 0150                    string text = string.Join('|', previouslyFailedImages);
 0151                    await File.WriteAllTextAsync(failHistoryPath, text, cancellationToken).ConfigureAwait(false);
 152                }
 153
 0154                numComplete++;
 0155                double percent = numComplete;
 0156                percent /= videos.Count;
 157
 0158                progress.Report(100 * percent);
 0159            }
 0160            catch (ObjectDisposedException ex)
 161            {
 162                // TODO Investigate and properly fix.
 0163                _logger.LogError(ex, "Object Disposed");
 0164                break;
 165            }
 0166        }
 0167    }
 168}