< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.MediaInfo.SubtitleScheduledTask
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
Line coverage
16%
Covered lines: 15
Uncovered lines: 78
Coverable lines: 93
Total lines: 220
Line coverage: 16.1%
Branch coverage
0%
Covered branches: 0
Total branches: 22
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 3/16/2026 - 12:14:00 AM Line coverage: 63.1% (12/19) Total lines: 2234/19/2026 - 12:14:27 AM Line coverage: 13.6% (12/88) Branch coverage: 0% (0/20) Total lines: 2074/24/2026 - 12:14:24 AM Line coverage: 16.3% (15/92) Branch coverage: 0% (0/22) Total lines: 2196/9/2026 - 12:16:23 AM Line coverage: 16.1% (15/93) Branch coverage: 0% (0/22) Total lines: 220 4/19/2026 - 12:14:27 AM Line coverage: 13.6% (12/88) Branch coverage: 0% (0/20) Total lines: 2074/24/2026 - 12:14:24 AM Line coverage: 16.3% (15/92) Branch coverage: 0% (0/22) Total lines: 2196/9/2026 - 12:16:23 AM Line coverage: 16.1% (15/93) Branch coverage: 0% (0/22) Total lines: 220

Coverage delta

Coverage delta 50 -50

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%
get_IsHidden()100%210%
get_IsEnabled()100%210%
get_IsLogged()100%210%
ExecuteAsync()0%342180%
DownloadSubtitles()0%2040%
GetDefaultTriggers()100%11100%

File(s)

/srv/git/jellyfin/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CS1591
 4
 5using System;
 6using System.Collections.Generic;
 7using System.Linq;
 8using System.Threading;
 9using System.Threading.Tasks;
 10using Jellyfin.Data.Enums;
 11using MediaBrowser.Controller.Configuration;
 12using MediaBrowser.Controller.Dto;
 13using MediaBrowser.Controller.Entities;
 14using MediaBrowser.Controller.Library;
 15using MediaBrowser.Controller.Providers;
 16using MediaBrowser.Controller.Subtitles;
 17using MediaBrowser.Model.Globalization;
 18using MediaBrowser.Model.Tasks;
 19using Microsoft.Extensions.Logging;
 20
 21namespace MediaBrowser.Providers.MediaInfo
 22{
 23    public class SubtitleScheduledTask : IScheduledTask
 24    {
 25        private readonly ILibraryManager _libraryManager;
 26        private readonly IServerConfigurationManager _config;
 27        private readonly ISubtitleManager _subtitleManager;
 28        private readonly ILogger<SubtitleScheduledTask> _logger;
 29        private readonly ILocalizationManager _localization;
 30        private readonly ISubtitleProvider[] _subtitleProviders;
 31
 32        public SubtitleScheduledTask(
 33            ILibraryManager libraryManager,
 34            IServerConfigurationManager config,
 35            ISubtitleManager subtitleManager,
 36            ILogger<SubtitleScheduledTask> logger,
 37            ILocalizationManager localization,
 38            IEnumerable<ISubtitleProvider> subtitleProviders)
 39        {
 2140            _libraryManager = libraryManager;
 2141            _config = config;
 2142            _subtitleManager = subtitleManager;
 2143            _logger = logger;
 2144            _localization = localization;
 2145            _subtitleProviders = subtitleProviders
 2146                .OrderBy(i => i is IHasOrder hasOrder ? hasOrder.Order : 0)
 2147                .ToArray();
 2148        }
 49
 2150        public string Name => _localization.GetLocalizedString("TaskDownloadMissingSubtitles");
 51
 052        public string Description => _localization.GetLocalizedString("TaskDownloadMissingSubtitlesDescription");
 53
 054        public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
 55
 056        public string Key => "DownloadSubtitles";
 57
 058        public bool IsHidden => false;
 59
 060        public bool IsEnabled => true;
 61
 062        public bool IsLogged => true;
 63
 64        /// <inheritdoc />
 65        public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
 66        {
 067            var types = new[] { BaseItemKind.Episode, BaseItemKind.Movie };
 68
 069            var dict = new Dictionary<Guid, BaseItem>();
 70
 071            foreach (var library in _libraryManager.RootFolder.Children.ToList())
 72            {
 073                var libraryOptions = _libraryManager.GetLibraryOptions(library);
 74
 75                string[] subtitleDownloadLanguages;
 76                bool skipIfEmbeddedSubtitlesPresent;
 77                bool skipIfAudioTrackMatches;
 78
 079                if (libraryOptions.SubtitleDownloadLanguages is null)
 80                {
 81                    // Skip this library if subtitle download languages are not configured
 82                    continue;
 83                }
 84
 085                if (_subtitleProviders.All(provider => libraryOptions.DisabledSubtitleFetchers.Contains(provider.Name, S
 86                {
 87                    // Skip this library if all subtitle providers are disabled
 88                    continue;
 89                }
 90
 091                subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
 092                skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
 093                skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
 94
 095                foreach (var lang in subtitleDownloadLanguages)
 96                {
 097                    var query = new InternalItemsQuery
 098                    {
 099                        MediaTypes = new[] { MediaType.Video },
 0100                        IsVirtualItem = false,
 0101                        IncludeItemTypes = types,
 0102                        DtoOptions = new DtoOptions(true),
 0103                        SourceTypes = new[] { SourceType.Library },
 0104                        Parent = library,
 0105                        Recursive = true,
 0106                        IncludeOwnedItems = true
 0107                    };
 108
 0109                    if (skipIfAudioTrackMatches)
 110                    {
 0111                        query.HasNoAudioTrackWithLanguage = lang;
 112                    }
 113
 0114                    if (skipIfEmbeddedSubtitlesPresent)
 115                    {
 116                        // Exclude if it already has any subtitles of the same language
 0117                        query.HasNoSubtitleTrackWithLanguage = lang;
 118                    }
 119                    else
 120                    {
 121                        // Exclude if it already has external subtitles of the same language
 0122                        query.HasNoExternalSubtitleTrackWithLanguage = lang;
 123                    }
 124
 0125                    var videosByLanguage = _libraryManager.GetItemList(query);
 126
 0127                    foreach (var video in videosByLanguage)
 128                    {
 0129                        dict[video.Id] = video;
 130                    }
 131                }
 132            }
 133
 0134            var videos = dict.Values.ToList();
 0135            if (videos.Count == 0)
 136            {
 0137                return;
 138            }
 139
 0140            var numComplete = 0;
 141
 0142            foreach (var video in videos)
 143            {
 0144                cancellationToken.ThrowIfCancellationRequested();
 145
 146                try
 147                {
 0148                    await DownloadSubtitles(video as Video, cancellationToken).ConfigureAwait(false);
 0149                }
 0150                catch (Exception ex)
 151                {
 0152                    _logger.LogError(ex, "Error downloading subtitles for {Path}", video.Path);
 0153                }
 154
 155                // Update progress
 0156                numComplete++;
 0157                double percent = numComplete;
 0158                percent /= videos.Count;
 159
 0160                progress.Report(100 * percent);
 0161            }
 0162        }
 163
 164        private async Task<bool> DownloadSubtitles(Video video, CancellationToken cancellationToken)
 165        {
 0166            var mediaStreams = video.GetMediaStreams();
 167
 0168            var libraryOptions = _libraryManager.GetLibraryOptions(video);
 169
 170            string[] subtitleDownloadLanguages;
 171            bool skipIfEmbeddedSubtitlesPresent;
 172            bool skipIfAudioTrackMatches;
 173            bool requirePerfectMatch;
 174
 0175            if (libraryOptions.SubtitleDownloadLanguages is null)
 176            {
 177                // Subtitle downloading is not configured for this library
 0178                return true;
 179            }
 180
 0181            subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
 0182            skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
 0183            skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
 0184            requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
 185
 0186            var downloadedLanguages = await new SubtitleDownloader(
 0187                _logger,
 0188                _subtitleManager).DownloadSubtitles(
 0189                    video,
 0190                    mediaStreams,
 0191                    skipIfEmbeddedSubtitlesPresent,
 0192                    skipIfAudioTrackMatches,
 0193                    requirePerfectMatch,
 0194                    subtitleDownloadLanguages,
 0195                    libraryOptions.DisabledSubtitleFetchers,
 0196                    libraryOptions.SubtitleFetcherOrder,
 0197                    true,
 0198                    cancellationToken).ConfigureAwait(false);
 199
 200            // Rescan
 0201            if (downloadedLanguages.Count > 0)
 202            {
 0203                await video.RefreshMetadata(cancellationToken).ConfigureAwait(false);
 0204                return false;
 205            }
 206
 0207            return true;
 0208        }
 209
 210        /// <inheritdoc />
 211        public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
 212        {
 21213            return new[]
 21214            {
 21215                // Every so often
 21216                new TaskTriggerInfo { Type = TaskTriggerInfoType.IntervalTrigger, IntervalTicks = TimeSpan.FromHours(24)
 21217            };
 218        }
 219    }
 220}