< 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: 77
Coverable lines: 92
Total lines: 219
Line coverage: 16.3%
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 1/23/2026 - 12:11:06 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: 219 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: 219

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                    };
 107
 0108                    if (skipIfAudioTrackMatches)
 109                    {
 0110                        query.HasNoAudioTrackWithLanguage = lang;
 111                    }
 112
 0113                    if (skipIfEmbeddedSubtitlesPresent)
 114                    {
 115                        // Exclude if it already has any subtitles of the same language
 0116                        query.HasNoSubtitleTrackWithLanguage = lang;
 117                    }
 118                    else
 119                    {
 120                        // Exclude if it already has external subtitles of the same language
 0121                        query.HasNoExternalSubtitleTrackWithLanguage = lang;
 122                    }
 123
 0124                    var videosByLanguage = _libraryManager.GetItemList(query);
 125
 0126                    foreach (var video in videosByLanguage)
 127                    {
 0128                        dict[video.Id] = video;
 129                    }
 130                }
 131            }
 132
 0133            var videos = dict.Values.ToList();
 0134            if (videos.Count == 0)
 135            {
 0136                return;
 137            }
 138
 0139            var numComplete = 0;
 140
 0141            foreach (var video in videos)
 142            {
 0143                cancellationToken.ThrowIfCancellationRequested();
 144
 145                try
 146                {
 0147                    await DownloadSubtitles(video as Video, cancellationToken).ConfigureAwait(false);
 0148                }
 0149                catch (Exception ex)
 150                {
 0151                    _logger.LogError(ex, "Error downloading subtitles for {Path}", video.Path);
 0152                }
 153
 154                // Update progress
 0155                numComplete++;
 0156                double percent = numComplete;
 0157                percent /= videos.Count;
 158
 0159                progress.Report(100 * percent);
 0160            }
 0161        }
 162
 163        private async Task<bool> DownloadSubtitles(Video video, CancellationToken cancellationToken)
 164        {
 0165            var mediaStreams = video.GetMediaStreams();
 166
 0167            var libraryOptions = _libraryManager.GetLibraryOptions(video);
 168
 169            string[] subtitleDownloadLanguages;
 170            bool skipIfEmbeddedSubtitlesPresent;
 171            bool skipIfAudioTrackMatches;
 172            bool requirePerfectMatch;
 173
 0174            if (libraryOptions.SubtitleDownloadLanguages is null)
 175            {
 176                // Subtitle downloading is not configured for this library
 0177                return true;
 178            }
 179
 0180            subtitleDownloadLanguages = libraryOptions.SubtitleDownloadLanguages;
 0181            skipIfEmbeddedSubtitlesPresent = libraryOptions.SkipSubtitlesIfEmbeddedSubtitlesPresent;
 0182            skipIfAudioTrackMatches = libraryOptions.SkipSubtitlesIfAudioTrackMatches;
 0183            requirePerfectMatch = libraryOptions.RequirePerfectSubtitleMatch;
 184
 0185            var downloadedLanguages = await new SubtitleDownloader(
 0186                _logger,
 0187                _subtitleManager).DownloadSubtitles(
 0188                    video,
 0189                    mediaStreams,
 0190                    skipIfEmbeddedSubtitlesPresent,
 0191                    skipIfAudioTrackMatches,
 0192                    requirePerfectMatch,
 0193                    subtitleDownloadLanguages,
 0194                    libraryOptions.DisabledSubtitleFetchers,
 0195                    libraryOptions.SubtitleFetcherOrder,
 0196                    true,
 0197                    cancellationToken).ConfigureAwait(false);
 198
 199            // Rescan
 0200            if (downloadedLanguages.Count > 0)
 201            {
 0202                await video.RefreshMetadata(cancellationToken).ConfigureAwait(false);
 0203                return false;
 204            }
 205
 0206            return true;
 0207        }
 208
 209        /// <inheritdoc />
 210        public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
 211        {
 21212            return new[]
 21213            {
 21214                // Every so often
 21215                new TaskTriggerInfo { Type = TaskTriggerInfoType.IntervalTrigger, IntervalTicks = TimeSpan.FromHours(24)
 21216            };
 217        }
 218    }
 219}