< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Plugins.Tmdb.Movies.TmdbMovieProvider
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
Line coverage
2%
Covered lines: 4
Uncovered lines: 192
Coverable lines: 196
Total lines: 401
Line coverage: 2%
Branch coverage
0%
Covered branches: 0
Total branches: 136
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/16/2026 - 12:15:55 AM Line coverage: 2% (4/193) Branch coverage: 0% (0/138) Total lines: 39610/29/2026 - 12:17:11 AM Line coverage: 2% (4/196) Branch coverage: 0% (0/136) Total lines: 401 5/16/2026 - 12:15:55 AM Line coverage: 2% (4/193) Branch coverage: 0% (0/138) Total lines: 39610/29/2026 - 12:17:11 AM Line coverage: 2% (4/196) Branch coverage: 0% (0/136) Total lines: 401

Coverage delta

Coverage delta 1 -1

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Order()100%210%
get_Name()100%210%
GetSearchResults()0%930300%
GetMetadata()0%113421060%
GetImageResponse(...)100%210%

File(s)

/srv/git/jellyfin/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.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.Movies;
 13using MediaBrowser.Controller.Library;
 14using MediaBrowser.Controller.Providers;
 15using MediaBrowser.Model.Entities;
 16using MediaBrowser.Model.Providers;
 17using TMDbLib.Objects.Find;
 18using TMDbLib.Objects.General;
 19using TMDbLib.Objects.Search;
 20
 21namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
 22{
 23    /// <summary>
 24    /// Movie provider powered by TMDb.
 25    /// </summary>
 26    public class TmdbMovieProvider : IRemoteMetadataProvider<Movie, MovieInfo>, 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="TmdbMovieProvider"/> 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 TmdbMovieProvider(
 39            ILibraryManager libraryManager,
 40            TmdbClientManager tmdbClientManager,
 41            IHttpClientFactory httpClientFactory)
 42        {
 2743            _libraryManager = libraryManager;
 2744            _tmdbClientManager = tmdbClientManager;
 2745            _httpClientFactory = httpClientFactory;
 2746        }
 47
 48        /// <inheritdoc />
 049        public int Order => 1;
 50
 51        /// <inheritdoc />
 052        public string Name => TmdbUtils.ProviderName;
 53
 54        /// <inheritdoc />
 55        public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MovieInfo searchInfo, CancellationToken canc
 56        {
 057            if (searchInfo.TryGetTmdbId(out var tmdbId))
 58            {
 059                var movie = await _tmdbClientManager
 060                    .GetMovieAsync(
 061                        tmdbId,
 062                        searchInfo.MetadataLanguage,
 063                        TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode),
 064                        searchInfo.MetadataCountryCode,
 065                        cancellationToken)
 066                    .ConfigureAwait(false);
 67
 068                if (movie is not null)
 69                {
 070                    var remoteResult = new RemoteSearchResult
 071                    {
 072                        Name = movie.Title ?? movie.OriginalTitle,
 073                        SearchProviderName = Name,
 074                        ImageUrl = _tmdbClientManager.GetPosterUrl(movie.PosterPath),
 075                        Overview = movie.Overview
 076                    };
 77
 078                    if (movie.ReleaseDate is not null)
 79                    {
 080                        var releaseDate = movie.ReleaseDate.Value.ToUniversalTime();
 081                        remoteResult.PremiereDate = releaseDate;
 082                        remoteResult.ProductionYear = releaseDate.Year;
 83                    }
 84
 085                    remoteResult.SetProviderId(MetadataProvider.Tmdb, movie.Id.ToString(CultureInfo.InvariantCulture));
 086                    remoteResult.TrySetProviderId(MetadataProvider.Imdb, movie.ImdbId);
 87
 088                    return [remoteResult];
 89                }
 90            }
 91
 092            IReadOnlyList<SearchMovie>? movieResults = null;
 093            if (searchInfo.TryGetProviderId(MetadataProvider.Imdb, out var id))
 94            {
 095                var result = await _tmdbClientManager.FindByExternalIdAsync(
 096                    id,
 097                    FindExternalSource.Imdb,
 098                    TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode),
 099                    searchInfo.MetadataCountryCode,
 0100                    cancellationToken).ConfigureAwait(false);
 0101                movieResults = result?.MovieResults;
 102            }
 103
 0104            if (movieResults is null && searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out id))
 105            {
 0106                var result = await _tmdbClientManager.FindByExternalIdAsync(
 0107                    id,
 0108                    FindExternalSource.TvDb,
 0109                    TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode),
 0110                    searchInfo.MetadataCountryCode,
 0111                    cancellationToken).ConfigureAwait(false);
 0112                movieResults = result?.MovieResults;
 113            }
 114
 0115            if (movieResults is null)
 116            {
 0117                movieResults = await _tmdbClientManager
 0118                    .SearchMovieAsync(searchInfo.Name, searchInfo.Year ?? 0, searchInfo.MetadataLanguage, searchInfo.Met
 0119                    .ConfigureAwait(false);
 120            }
 121
 0122            if (movieResults is null)
 123            {
 0124                return [];
 125            }
 126
 0127            var len = movieResults.Count;
 0128            var remoteSearchResults = new RemoteSearchResult[len];
 0129            for (var i = 0; i < len; i++)
 130            {
 0131                var movieResult = movieResults[i];
 0132                var remoteSearchResult = new RemoteSearchResult
 0133                {
 0134                    Name = movieResult.Title ?? movieResult.OriginalTitle,
 0135                    ImageUrl = _tmdbClientManager.GetPosterUrl(movieResult.PosterPath),
 0136                    Overview = movieResult.Overview,
 0137                    SearchProviderName = Name
 0138                };
 139
 0140                var releaseDate = movieResult.ReleaseDate?.ToUniversalTime();
 0141                remoteSearchResult.PremiereDate = releaseDate;
 0142                remoteSearchResult.ProductionYear = releaseDate?.Year;
 143
 0144                remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, movieResult.Id.ToString(CultureInfo.InvariantCul
 0145                remoteSearchResults[i] = remoteSearchResult;
 146            }
 147
 0148            return remoteSearchResults;
 0149        }
 150
 151        /// <inheritdoc />
 152        public async Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken)
 153        {
 154            // A stored id that is not a TMDb id is treated as no id, so the search below can repair it
 155            // rather than the lookup failing for as long as the bad id stays on the item.
 0156            info.TryGetTmdbId(out var tmdbId);
 0157            var imdbId = info.GetProviderId(MetadataProvider.Imdb);
 0158            var config = Plugin.Instance.Configuration;
 159
 0160            if (tmdbId <= 0 && string.IsNullOrEmpty(imdbId))
 161            {
 162                // ParseName is required here.
 163                // Caller provides the filename with extension stripped and NOT the parsed filename
 0164                var parsedName = _libraryManager.ParseName(info.Name);
 0165                var cleanedName = TmdbUtils.CleanName(parsedName.Name);
 0166                var searchYear = info.Year ?? parsedName.Year ?? 0;
 167
 0168                var searchResults = await _tmdbClientManager.SearchMovieAsync(cleanedName, searchYear, info.MetadataLang
 169
 0170                var match = TmdbUtils.FindBestMatch(searchResults, parsedName.Name, searchYear);
 171
 0172                if (match is not null)
 173                {
 0174                    tmdbId = match.Id;
 175                }
 0176            }
 177
 0178            if (tmdbId <= 0 && !string.IsNullOrEmpty(imdbId))
 179            {
 0180                var movieResultFromImdbId = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Im
 0181                if (movieResultFromImdbId?.MovieResults?.Count > 0)
 182                {
 0183                    tmdbId = movieResultFromImdbId.MovieResults[0].Id;
 184                }
 185            }
 186
 0187            if (tmdbId <= 0)
 188            {
 0189                return new MetadataResult<Movie>();
 190            }
 191
 0192            var movieResult = await _tmdbClientManager
 0193                .GetMovieAsync(tmdbId, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage, in
 0194                .ConfigureAwait(false);
 195
 0196            if (movieResult is null)
 197            {
 0198                return new MetadataResult<Movie>();
 199            }
 200
 0201            var movie = new Movie
 0202            {
 0203                Name = movieResult.Title ?? movieResult.OriginalTitle,
 0204                OriginalTitle = movieResult.OriginalTitle,
 0205                Overview = movieResult.Overview?.Replace("\n\n", "\n", StringComparison.InvariantCulture),
 0206                Tagline = movieResult.Tagline,
 0207                ProductionLocations = movieResult.ProductionCountries?.Select(pc => pc.Name).ToArray() ?? Array.Empty<st
 0208            };
 0209            var metadataResult = new MetadataResult<Movie>
 0210            {
 0211                HasMetadata = true,
 0212                ResultLanguage = info.MetadataLanguage,
 0213                Item = movie
 0214            };
 215
 0216            movie.SetProviderId(MetadataProvider.Tmdb, tmdbId.ToString(CultureInfo.InvariantCulture));
 0217            movie.TrySetProviderId(MetadataProvider.Imdb, movieResult.ImdbId);
 0218            if (movieResult.BelongsToCollection is not null)
 219            {
 0220                movie.SetProviderId(MetadataProvider.TmdbCollection, movieResult.BelongsToCollection.Id.ToString(Culture
 0221                movie.CollectionName = movieResult.BelongsToCollection.Name;
 222            }
 223
 0224            movie.CommunityRating = Convert.ToSingle(movieResult.VoteAverage);
 225
 0226            if (movieResult.Releases?.Countries is not null)
 227            {
 0228                var releases = movieResult.Releases.Countries.Where(i => !string.IsNullOrWhiteSpace(i.Certification)).To
 229
 0230                var ourRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, info.MetadataCountryCode, Stri
 231
 0232                if (ourRelease?.Certification is not null)
 233                {
 0234                    movie.OfficialRating = TmdbUtils.BuildParentalRating(info.MetadataCountryCode, ourRelease.Certificat
 235                }
 236                else
 237                {
 0238                    var usRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, "US", StringComparison.Ordi
 0239                    if (usRelease?.Certification is not null)
 240                    {
 0241                        movie.OfficialRating = usRelease.Certification;
 242                    }
 243                }
 244            }
 245
 0246            movie.PremiereDate = movieResult.ReleaseDate;
 0247            movie.ProductionYear = movieResult.ReleaseDate?.Year;
 248
 0249            if (movieResult.ProductionCompanies is not null)
 250            {
 0251                movie.SetStudios(movieResult.ProductionCompanies.Select(c => c.Name));
 252            }
 253
 0254            var genres = movieResult.Genres;
 255
 0256            if (genres is not null)
 257            {
 0258                foreach (var genre in genres.Select(g => g.Name).Trimmed())
 259                {
 0260                    movie.AddGenre(genre);
 261                }
 262            }
 263
 0264            if (movieResult.Keywords?.Keywords is not null)
 265            {
 0266                foreach (var keyword in movieResult.Keywords.Keywords)
 267                {
 0268                    var name = keyword.Name;
 0269                    if (!string.IsNullOrWhiteSpace(name))
 270                    {
 0271                        movie.AddTag(name);
 272                    }
 273                }
 274            }
 275
 0276            if (movieResult.Credits?.Cast is not null)
 277            {
 0278                var castQuery = movieResult.Credits.Cast.AsEnumerable();
 279
 0280                if (config.HideMissingCastMembers)
 281                {
 0282                    castQuery = castQuery.Where(a => !string.IsNullOrEmpty(a.ProfilePath));
 283                }
 284
 0285                castQuery = castQuery.OrderBy(a => a.Order).Take(config.MaxCastMembers);
 286
 0287                foreach (var actor in castQuery)
 288                {
 0289                    if (string.IsNullOrWhiteSpace(actor.Name))
 290                    {
 291                        continue;
 292                    }
 293
 0294                    var personInfo = new PersonInfo
 0295                    {
 0296                        Name = actor.Name.Trim(),
 0297                        Role = actor.Character?.Trim() ?? string.Empty,
 0298                        Type = PersonKind.Actor,
 0299                        SortOrder = actor.Order
 0300                    };
 301
 0302                    if (!string.IsNullOrWhiteSpace(actor.ProfilePath))
 303                    {
 0304                        personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath);
 305                    }
 306
 0307                    if (actor.Id > 0)
 308                    {
 0309                        personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture))
 310                    }
 311
 0312                    metadataResult.AddPerson(personInfo);
 313                }
 314            }
 315
 0316            if (movieResult.Credits?.Crew is not null)
 317            {
 0318                var crewQuery = movieResult.Credits.Crew
 0319                    .Select(crewMember => new
 0320                    {
 0321                        CrewMember = crewMember,
 0322                        PersonType = TmdbUtils.MapCrewToPersonType(crewMember)
 0323                    })
 0324                    .Where(entry => TmdbUtils.WantedCrewKinds.Contains(entry.PersonType));
 325
 0326                if (config.HideMissingCrewMembers)
 327                {
 0328                    crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath));
 329                }
 330
 0331                crewQuery = crewQuery.Take(config.MaxCrewMembers);
 332
 0333                foreach (var entry in crewQuery)
 334                {
 0335                    var crewMember = entry.CrewMember;
 336
 0337                    if (string.IsNullOrWhiteSpace(crewMember.Name))
 338                    {
 339                        continue;
 340                    }
 341
 0342                    var personInfo = new PersonInfo
 0343                    {
 0344                        Name = crewMember.Name.Trim(),
 0345                        Role = crewMember.Job?.Trim() ?? string.Empty,
 0346                        Type = entry.PersonType
 0347                    };
 348
 0349                    if (!string.IsNullOrWhiteSpace(crewMember.ProfilePath))
 350                    {
 0351                        personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath);
 352                    }
 353
 0354                    if (crewMember.Id > 0)
 355                    {
 0356                        personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCult
 357                    }
 358
 0359                    metadataResult.AddPerson(personInfo);
 360                }
 361            }
 362
 0363            if (movieResult.Videos?.Results is not null)
 364            {
 0365                var trailers = new List<MediaUrl>();
 366
 0367                var sortedVideos = movieResult.Videos.Results
 0368                    .OrderByDescending(video => string.Equals(video.Type, "trailer", StringComparison.OrdinalIgnoreCase)
 369
 0370                foreach (var video in sortedVideos)
 371                {
 0372                    if (!TmdbUtils.IsTrailerType(video))
 373                    {
 374                        continue;
 375                    }
 376
 0377                    trailers.Add(new MediaUrl
 0378                    {
 0379                        Url = string.Format(CultureInfo.InvariantCulture, "https://www.youtube.com/watch?v={0}", video.K
 0380                        Name = video.Name
 0381                    });
 382                }
 383
 0384                movie.RemoteTrailers = trailers;
 385            }
 386
 0387            if (!string.IsNullOrEmpty(movieResult.OriginalLanguage))
 388            {
 0389                movie.OriginalLanguage = movieResult.OriginalLanguage;
 390            }
 391
 0392            return metadataResult;
 0393        }
 394
 395        /// <inheritdoc />
 396        public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
 397        {
 0398            return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
 399        }
 400    }
 401}