< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Plugins.Tmdb.TV.TmdbSeriesProvider
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
Line coverage
1%
Covered lines: 4
Uncovered lines: 212
Coverable lines: 216
Total lines: 462
Line coverage: 1.8%
Branch coverage
0%
Covered branches: 0
Total branches: 162
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 4/8/2026 - 12:11:47 AM Line coverage: 5.2% (4/76) Branch coverage: 0% (0/58) Total lines: 4234/19/2026 - 12:14:27 AM Line coverage: 2% (4/198) Branch coverage: 0% (0/150) Total lines: 4235/8/2026 - 12:15:13 AM Line coverage: 2% (4/200) Branch coverage: 0% (0/152) Total lines: 4286/18/2026 - 12:16:24 AM Line coverage: 1.8% (4/212) Branch coverage: 0% (0/160) Total lines: 4537/18/2026 - 12:15:19 AM Line coverage: 1.8% (4/216) Branch coverage: 0% (0/162) Total lines: 462 4/8/2026 - 12:11:47 AM Line coverage: 5.2% (4/76) Branch coverage: 0% (0/58) Total lines: 4234/19/2026 - 12:14:27 AM Line coverage: 2% (4/198) Branch coverage: 0% (0/150) Total lines: 4235/8/2026 - 12:15:13 AM Line coverage: 2% (4/200) Branch coverage: 0% (0/152) Total lines: 4286/18/2026 - 12:16:24 AM Line coverage: 1.8% (4/212) Branch coverage: 0% (0/160) Total lines: 4537/18/2026 - 12:15:19 AM Line coverage: 1.8% (4/216) Branch coverage: 0% (0/162) Total lines: 462

Coverage delta

Coverage delta 4 -4

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Name()100%210%
get_Order()100%210%
GetSearchResults()0%600240%
MapTvShowToRemoteSearchResult(...)0%7280%
MapSearchTvToRemoteSearchResult(...)0%4260%
GetMetadata()0%1332360%
MapTvShowToSeries(...)0%2352480%
GetPersons()0%1640400%
GetImageResponse(...)100%210%

File(s)

/srv/git/jellyfin/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Globalization;
 4using System.Linq;
 5using System.Net.Http;
 6using System.Threading;
 7using System.Threading.Tasks;
 8using Jellyfin.Data.Enums;
 9using Jellyfin.Extensions;
 10using MediaBrowser.Common.Net;
 11using MediaBrowser.Controller.Entities;
 12using MediaBrowser.Controller.Entities.TV;
 13using MediaBrowser.Controller.Library;
 14using MediaBrowser.Controller.Providers;
 15using MediaBrowser.Model.Entities;
 16using MediaBrowser.Model.Providers;
 17using TMDbLib.Objects.Find;
 18using TMDbLib.Objects.Search;
 19using TMDbLib.Objects.TvShows;
 20
 21namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 22{
 23    /// <summary>
 24    /// TV series provider powered by TheMovieDb.
 25    /// </summary>
 26    public class TmdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder
 27    {
 28        private readonly IHttpClientFactory _httpClientFactory;
 29        private readonly ILibraryManager _libraryManager;
 30        private readonly TmdbClientManager _tmdbClientManager;
 31
 32        /// <summary>
 33        /// Initializes a new instance of the <see cref="TmdbSeriesProvider"/> class.
 34        /// </summary>
 35        /// <param name="libraryManager">The <see cref="ILibraryManager"/>.</param>
 36        /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
 37        /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param>
 38        public TmdbSeriesProvider(
 39            ILibraryManager libraryManager,
 40            IHttpClientFactory httpClientFactory,
 41            TmdbClientManager tmdbClientManager)
 42        {
 2243            _libraryManager = libraryManager;
 2244            _httpClientFactory = httpClientFactory;
 2245            _tmdbClientManager = tmdbClientManager;
 2246        }
 47
 48        /// <inheritdoc />
 049        public string Name => TmdbUtils.ProviderName;
 50
 51        /// <inheritdoc />
 052        public int Order => 1;
 53
 54        /// <inheritdoc />
 55        public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken can
 56        {
 057            if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var tmdbId))
 58            {
 059                var series = await _tmdbClientManager
 060                    .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, 
 061                    .ConfigureAwait(false);
 62
 063                if (series is not null)
 64                {
 065                    var remoteResult = MapTvShowToRemoteSearchResult(series);
 66
 067                    return new[] { remoteResult };
 68                }
 69            }
 70
 071            if (searchInfo.TryGetProviderId(MetadataProvider.Imdb, out var imdbId))
 72            {
 073                var findResult = await _tmdbClientManager
 074                    .FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, searchInfo.MetadataLanguage, searchInfo.Meta
 075                    .ConfigureAwait(false);
 76
 077                var tvResults = findResult?.TvResults;
 078                if (tvResults is not null)
 79                {
 080                    var imdbIdResults = new RemoteSearchResult[tvResults.Count];
 081                    for (var i = 0; i < tvResults.Count; i++)
 82                    {
 083                        var remoteResult = MapSearchTvToRemoteSearchResult(tvResults[i]);
 084                        remoteResult.SetProviderId(MetadataProvider.Imdb, imdbId);
 085                        imdbIdResults[i] = remoteResult;
 86                    }
 87
 088                    return imdbIdResults;
 89                }
 90            }
 91
 092            if (searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out var tvdbId))
 93            {
 094                var findResult = await _tmdbClientManager
 095                    .FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, searchInfo.MetadataLanguage, searchInfo.Meta
 096                    .ConfigureAwait(false);
 97
 098                var tvResults = findResult?.TvResults;
 099                if (tvResults is not null)
 100                {
 0101                    var tvIdResults = new RemoteSearchResult[tvResults.Count];
 0102                    for (var i = 0; i < tvResults.Count; i++)
 103                    {
 0104                        var remoteResult = MapSearchTvToRemoteSearchResult(tvResults[i]);
 0105                        remoteResult.SetProviderId(MetadataProvider.Tvdb, tvdbId);
 0106                        tvIdResults[i] = remoteResult;
 107                    }
 108
 0109                    return tvIdResults;
 110                }
 111            }
 112
 0113            var tvSearchResults = await _tmdbClientManager.SearchSeriesAsync(searchInfo.Name, searchInfo.MetadataLanguag
 0114                .ConfigureAwait(false);
 0115            if (tvSearchResults is null)
 116            {
 0117                return [];
 118            }
 119
 0120            var remoteResults = new RemoteSearchResult[tvSearchResults.Count];
 0121            for (var i = 0; i < tvSearchResults.Count; i++)
 122            {
 0123                remoteResults[i] = MapSearchTvToRemoteSearchResult(tvSearchResults[i]);
 124            }
 125
 0126            return remoteResults;
 0127        }
 128
 129        private RemoteSearchResult MapTvShowToRemoteSearchResult(TvShow series)
 130        {
 0131            var remoteResult = new RemoteSearchResult
 0132            {
 0133                Name = series.Name ?? series.OriginalName,
 0134                SearchProviderName = Name,
 0135                ImageUrl = _tmdbClientManager.GetPosterUrl(series.PosterPath),
 0136                Overview = series.Overview
 0137            };
 138
 0139            remoteResult.SetProviderId(MetadataProvider.Tmdb, series.Id.ToString(CultureInfo.InvariantCulture));
 0140            if (series.ExternalIds is not null)
 141            {
 0142                remoteResult.TrySetProviderId(MetadataProvider.Imdb, series.ExternalIds.ImdbId);
 143
 0144                remoteResult.TrySetProviderId(MetadataProvider.Tvdb, series.ExternalIds.TvdbId);
 145            }
 146
 0147            remoteResult.PremiereDate = series.FirstAirDate?.ToUniversalTime();
 0148            remoteResult.ProductionYear = series.FirstAirDate?.Year;
 149
 0150            return remoteResult;
 151        }
 152
 153        private RemoteSearchResult MapSearchTvToRemoteSearchResult(SearchTv series)
 154        {
 0155            var remoteResult = new RemoteSearchResult
 0156            {
 0157                Name = series.Name ?? series.OriginalName,
 0158                SearchProviderName = Name,
 0159                ImageUrl = _tmdbClientManager.GetPosterUrl(series.PosterPath),
 0160                Overview = series.Overview
 0161            };
 162
 0163            remoteResult.SetProviderId(MetadataProvider.Tmdb, series.Id.ToString(CultureInfo.InvariantCulture));
 0164            remoteResult.PremiereDate = series.FirstAirDate?.ToUniversalTime();
 0165            remoteResult.ProductionYear = series.FirstAirDate?.Year;
 166
 0167            return remoteResult;
 168        }
 169
 170        /// <inheritdoc />
 171        public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken)
 172        {
 0173            var result = new MetadataResult<Series>
 0174            {
 0175                QueriedById = true
 0176            };
 177
 0178            var tmdbId = info.GetProviderId(MetadataProvider.Tmdb);
 179
 0180            if (string.IsNullOrEmpty(tmdbId) && info.TryGetProviderId(MetadataProvider.Imdb, out var imdbId))
 181            {
 0182                var searchResult = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.
 0183                if (searchResult?.TvResults?.Count > 0)
 184                {
 0185                    tmdbId = searchResult.TvResults[0].Id.ToString(CultureInfo.InvariantCulture);
 186                }
 187            }
 188
 0189            if (string.IsNullOrEmpty(tmdbId) && info.TryGetProviderId(MetadataProvider.Tvdb, out var tvdbId))
 190            {
 0191                var searchResult = await _tmdbClientManager.FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, info.
 0192                if (searchResult?.TvResults?.Count > 0)
 193                {
 0194                    tmdbId = searchResult.TvResults[0].Id.ToString(CultureInfo.InvariantCulture);
 195                }
 196            }
 197
 0198            if (string.IsNullOrEmpty(tmdbId))
 199            {
 0200                result.QueriedById = false;
 201                // ParseName is required here.
 202                // Caller provides the filename with extension stripped and NOT the parsed filename
 0203                var parsedName = _libraryManager.ParseName(info.Name);
 0204                var cleanedName = TmdbUtils.CleanName(parsedName.Name);
 0205                var searchResults = await _tmdbClientManager.SearchSeriesAsync(cleanedName, info.MetadataLanguage, info.
 206
 0207                if (searchResults?.Count > 0)
 208                {
 0209                    tmdbId = searchResults[0].Id.ToString(CultureInfo.InvariantCulture);
 210                }
 211            }
 212
 0213            if (!int.TryParse(tmdbId, CultureInfo.InvariantCulture, out int tmdbIdInt))
 214            {
 0215                return result;
 216            }
 217
 0218            cancellationToken.ThrowIfCancellationRequested();
 219
 0220            var tvShow = await _tmdbClientManager
 0221                .GetSeriesAsync(tmdbIdInt, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage
 0222                .ConfigureAwait(false);
 223
 0224            if (tvShow is null)
 225            {
 0226                return result;
 227            }
 228
 0229            result = new MetadataResult<Series>
 0230            {
 0231                Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode),
 0232                ResultLanguage = info.MetadataLanguage ?? tvShow.OriginalLanguage
 0233            };
 234
 0235            foreach (var person in GetPersons(tvShow))
 236            {
 0237                result.AddPerson(person);
 238            }
 239
 0240            result.HasMetadata = result.Item is not null;
 241
 0242            return result;
 0243        }
 244
 245        private static Series MapTvShowToSeries(TvShow seriesResult, string preferredCountryCode)
 246        {
 0247            var series = new Series
 0248            {
 0249                Name = seriesResult.Name,
 0250                OriginalTitle = seriesResult.OriginalName
 0251            };
 252
 0253            series.SetProviderId(MetadataProvider.Tmdb, seriesResult.Id.ToString(CultureInfo.InvariantCulture));
 254
 0255            series.CommunityRating = Convert.ToSingle(seriesResult.VoteAverage);
 256
 0257            series.Overview = seriesResult.Overview;
 258
 0259            var studios = Enumerable.Empty<string>();
 260
 0261            if (seriesResult.Networks is not null)
 262            {
 0263                studios = studios.Concat(seriesResult.Networks.Select(i => i.Name).OfType<string>());
 264            }
 265
 0266            if (seriesResult.ProductionCompanies is not null)
 267            {
 0268                studios = studios.Concat(seriesResult.ProductionCompanies.Select(i => i.Name).OfType<string>());
 269            }
 270
 0271            series.SetStudios(studios);
 272
 0273            if (seriesResult.Genres is not null)
 274            {
 0275                series.Genres = seriesResult.Genres.Select(i => i.Name).ToArray();
 276            }
 277
 0278            if (seriesResult.Keywords?.Results is not null)
 279            {
 0280                foreach (var result in seriesResult.Keywords.Results)
 281                {
 0282                    var name = result.Name;
 0283                    if (!string.IsNullOrWhiteSpace(name))
 284                    {
 0285                        series.AddTag(name);
 286                    }
 287                }
 288            }
 289
 0290            series.HomePageUrl = seriesResult.Homepage;
 291
 0292            series.RunTimeTicks = seriesResult.EpisodeRunTime?.Select(i => TimeSpan.FromMinutes(i).Ticks).FirstOrDefault
 293
 0294            if (Emby.Naming.TV.TvParserHelpers.TryParseSeriesStatus(seriesResult.Status, out var seriesStatus))
 295            {
 0296                series.Status = seriesStatus;
 297            }
 298
 0299            series.EndDate = seriesResult.LastAirDate;
 0300            series.PremiereDate = seriesResult.FirstAirDate;
 0301            series.ProductionYear = seriesResult.FirstAirDate?.Year;
 302
 0303            var ids = seriesResult.ExternalIds;
 0304            if (ids is not null)
 305            {
 0306                series.TrySetProviderId(MetadataProvider.Imdb, ids.ImdbId);
 0307                series.TrySetProviderId(MetadataProvider.TvRage, ids.TvrageId);
 0308                series.TrySetProviderId(MetadataProvider.Tvdb, ids.TvdbId);
 309            }
 310
 0311            var contentRatings = seriesResult.ContentRatings?.Results ?? new List<ContentRating>();
 312
 0313            var ourRelease = contentRatings.FirstOrDefault(c => string.Equals(c.Iso_3166_1, preferredCountryCode, String
 0314            var usRelease = contentRatings.FirstOrDefault(c => string.Equals(c.Iso_3166_1, "US", StringComparison.Ordina
 0315            var minimumRelease = contentRatings.FirstOrDefault();
 316
 0317            if (ourRelease?.Rating is not null)
 318            {
 0319                series.OfficialRating = TmdbUtils.BuildParentalRating(preferredCountryCode, ourRelease.Rating);
 320            }
 0321            else if (usRelease?.Rating is not null)
 322            {
 0323                series.OfficialRating = usRelease.Rating;
 324            }
 0325            else if (minimumRelease?.Rating is not null)
 326            {
 0327                series.OfficialRating = minimumRelease.Rating;
 328            }
 329
 0330            if (seriesResult.Videos?.Results is not null)
 331            {
 0332                foreach (var video in seriesResult.Videos.Results)
 333                {
 0334                    if (TmdbUtils.IsTrailerType(video))
 335                    {
 0336                        series.AddTrailerUrl("https://www.youtube.com/watch?v=" + video.Key);
 337                    }
 338                }
 339            }
 340
 0341            if (!string.IsNullOrEmpty(seriesResult.OriginalLanguage))
 342            {
 0343                series.OriginalLanguage = seriesResult.OriginalLanguage;
 344            }
 345
 0346            return series;
 347        }
 348
 349        private IEnumerable<PersonInfo> GetPersons(TvShow seriesResult)
 350        {
 0351            var config = Plugin.Instance.Configuration;
 352
 0353            if (seriesResult.Credits?.Cast is not null)
 354            {
 0355                IEnumerable<Cast> castQuery = seriesResult.Credits.Cast.OrderBy(a => a.Order);
 356
 0357                if (config.HideMissingCastMembers)
 358                {
 0359                    castQuery = castQuery.Where(a => !string.IsNullOrEmpty(a.ProfilePath));
 360                }
 361
 0362                foreach (var actor in castQuery.Take(config.MaxCastMembers))
 363                {
 0364                    if (string.IsNullOrWhiteSpace(actor.Name))
 365                    {
 366                        continue;
 367                    }
 368
 0369                    var personInfo = new PersonInfo
 0370                    {
 0371                        Name = actor.Name.Trim(),
 0372                        Role = actor.Character?.Trim() ?? string.Empty,
 0373                        Type = PersonKind.Actor,
 0374                        SortOrder = actor.Order,
 0375                        // NOTE: Null values are filtered out above
 0376                        ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath!)
 0377                    };
 378
 0379                    if (actor.Id > 0)
 380                    {
 0381                        personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture))
 382                    }
 383
 0384                    yield return personInfo;
 385                }
 386            }
 387
 0388            if (seriesResult.Credits?.Crew is not null)
 389            {
 0390                var crewQuery = seriesResult.Credits.Crew
 0391                    .Select(crewMember => new
 0392                    {
 0393                        CrewMember = crewMember,
 0394                        PersonType = TmdbUtils.MapCrewToPersonType(crewMember)
 0395                    })
 0396                    .Where(entry => TmdbUtils.WantedCrewKinds.Contains(entry.PersonType));
 397
 0398                if (config.HideMissingCrewMembers)
 399                {
 0400                    crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath));
 401                }
 402
 0403                foreach (var entry in crewQuery.Take(config.MaxCrewMembers))
 404                {
 0405                    var crewMember = entry.CrewMember;
 406
 0407                    if (string.IsNullOrWhiteSpace(crewMember.Name))
 408                    {
 409                        continue;
 410                    }
 411
 0412                    var personInfo = new PersonInfo
 0413                    {
 0414                        Name = crewMember.Name.Trim(),
 0415                        Role = crewMember.Job?.Trim() ?? string.Empty,
 0416                        Type = entry.PersonType,
 0417                        // NOTE: Null values are filtered out above
 0418                        ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath!)
 0419                    };
 420
 0421                    if (crewMember.Id > 0)
 422                    {
 0423                        personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCult
 424                    }
 425
 0426                    yield return personInfo;
 427                }
 428            }
 429
 0430            if (seriesResult.CreatedBy is not null)
 431            {
 0432                foreach (var person in seriesResult.CreatedBy)
 433                {
 0434                    if (string.IsNullOrWhiteSpace(person.Name))
 435                    {
 436                        continue;
 437                    }
 438
 0439                    var personInfo = new PersonInfo
 0440                    {
 0441                        Name = person.Name.Trim(),
 0442                        Type = PersonKind.Creator,
 0443                        ImageUrl = _tmdbClientManager.GetProfileUrl(person.ProfilePath)
 0444                    };
 445
 0446                    if (person.Id > 0)
 447                    {
 0448                        personInfo.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture)
 449                    }
 450
 0451                    yield return personInfo;
 452                }
 453            }
 0454        }
 455
 456        /// <inheritdoc />
 457        public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
 458        {
 0459            return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
 460        }
 461    }
 462}