< 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
57%
Covered lines: 4
Uncovered lines: 3
Coverable lines: 7
Total lines: 391
Line coverage: 57.1%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 11/28/2025 - 12:11:11 AM Line coverage: 57.1% (4/7) Total lines: 3801/19/2026 - 12:13:54 AM Line coverage: 57.1% (4/7) Total lines: 3782/19/2026 - 12:13:41 AM Line coverage: 57.1% (4/7) Total lines: 391

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_Order()100%210%
get_Name()100%210%
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        {
 2143            _libraryManager = libraryManager;
 2144            _tmdbClientManager = tmdbClientManager;
 2145            _httpClientFactory = httpClientFactory;
 2146        }
 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        {
 57            if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var id))
 58            {
 59                var movie = await _tmdbClientManager
 60                    .GetMovieAsync(
 61                        int.Parse(id, CultureInfo.InvariantCulture),
 62                        searchInfo.MetadataLanguage,
 63                        TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode),
 64                        searchInfo.MetadataCountryCode,
 65                        cancellationToken)
 66                    .ConfigureAwait(false);
 67
 68                if (movie is not null)
 69                {
 70                    var remoteResult = new RemoteSearchResult
 71                    {
 72                        Name = movie.Title ?? movie.OriginalTitle,
 73                        SearchProviderName = Name,
 74                        ImageUrl = _tmdbClientManager.GetPosterUrl(movie.PosterPath),
 75                        Overview = movie.Overview
 76                    };
 77
 78                    if (movie.ReleaseDate is not null)
 79                    {
 80                        var releaseDate = movie.ReleaseDate.Value.ToUniversalTime();
 81                        remoteResult.PremiereDate = releaseDate;
 82                        remoteResult.ProductionYear = releaseDate.Year;
 83                    }
 84
 85                    remoteResult.SetProviderId(MetadataProvider.Tmdb, movie.Id.ToString(CultureInfo.InvariantCulture));
 86                    remoteResult.TrySetProviderId(MetadataProvider.Imdb, movie.ImdbId);
 87
 88                    return [remoteResult];
 89                }
 90            }
 91
 92            IReadOnlyList<SearchMovie>? movieResults = null;
 93            if (searchInfo.TryGetProviderId(MetadataProvider.Imdb, out id))
 94            {
 95                var result = await _tmdbClientManager.FindByExternalIdAsync(
 96                    id,
 97                    FindExternalSource.Imdb,
 98                    TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode),
 99                    searchInfo.MetadataCountryCode,
 100                    cancellationToken).ConfigureAwait(false);
 101                movieResults = result?.MovieResults;
 102            }
 103
 104            if (movieResults is null && searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out id))
 105            {
 106                var result = await _tmdbClientManager.FindByExternalIdAsync(
 107                    id,
 108                    FindExternalSource.TvDb,
 109                    TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage, searchInfo.MetadataCountryCode),
 110                    searchInfo.MetadataCountryCode,
 111                    cancellationToken).ConfigureAwait(false);
 112                movieResults = result?.MovieResults;
 113            }
 114
 115            if (movieResults is null)
 116            {
 117                movieResults = await _tmdbClientManager
 118                    .SearchMovieAsync(searchInfo.Name, searchInfo.Year ?? 0, searchInfo.MetadataLanguage, searchInfo.Met
 119                    .ConfigureAwait(false);
 120            }
 121
 122            if (movieResults is null)
 123            {
 124                return [];
 125            }
 126
 127            var len = movieResults.Count;
 128            var remoteSearchResults = new RemoteSearchResult[len];
 129            for (var i = 0; i < len; i++)
 130            {
 131                var movieResult = movieResults[i];
 132                var remoteSearchResult = new RemoteSearchResult
 133                {
 134                    Name = movieResult.Title ?? movieResult.OriginalTitle,
 135                    ImageUrl = _tmdbClientManager.GetPosterUrl(movieResult.PosterPath),
 136                    Overview = movieResult.Overview,
 137                    SearchProviderName = Name
 138                };
 139
 140                var releaseDate = movieResult.ReleaseDate?.ToUniversalTime();
 141                remoteSearchResult.PremiereDate = releaseDate;
 142                remoteSearchResult.ProductionYear = releaseDate?.Year;
 143
 144                remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, movieResult.Id.ToString(CultureInfo.InvariantCul
 145                remoteSearchResults[i] = remoteSearchResult;
 146            }
 147
 148            return remoteSearchResults;
 149        }
 150
 151        /// <inheritdoc />
 152        public async Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken)
 153        {
 154            var tmdbId = info.GetProviderId(MetadataProvider.Tmdb);
 155            var imdbId = info.GetProviderId(MetadataProvider.Imdb);
 156            var config = Plugin.Instance.Configuration;
 157
 158            if (string.IsNullOrEmpty(tmdbId) && string.IsNullOrEmpty(imdbId))
 159            {
 160                // ParseName is required here.
 161                // Caller provides the filename with extension stripped and NOT the parsed filename
 162                var parsedName = _libraryManager.ParseName(info.Name);
 163                var cleanedName = TmdbUtils.CleanName(parsedName.Name);
 164
 165                var searchResults = await _tmdbClientManager.SearchMovieAsync(cleanedName, info.Year ?? parsedName.Year 
 166
 167                if (searchResults?.Count > 0)
 168                {
 169                    tmdbId = searchResults[0].Id.ToString(CultureInfo.InvariantCulture);
 170                }
 171            }
 172
 173            if (string.IsNullOrEmpty(tmdbId) && !string.IsNullOrEmpty(imdbId))
 174            {
 175                var movieResultFromImdbId = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Im
 176                if (movieResultFromImdbId?.MovieResults?.Count > 0)
 177                {
 178                    tmdbId = movieResultFromImdbId.MovieResults[0].Id.ToString(CultureInfo.InvariantCulture);
 179                }
 180            }
 181
 182            if (string.IsNullOrEmpty(tmdbId))
 183            {
 184                return new MetadataResult<Movie>();
 185            }
 186
 187            var movieResult = await _tmdbClientManager
 188                .GetMovieAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.G
 189                .ConfigureAwait(false);
 190
 191            if (movieResult is null)
 192            {
 193                return new MetadataResult<Movie>();
 194            }
 195
 196            var movie = new Movie
 197            {
 198                Name = movieResult.Title ?? movieResult.OriginalTitle,
 199                OriginalTitle = movieResult.OriginalTitle,
 200                Overview = movieResult.Overview?.Replace("\n\n", "\n", StringComparison.InvariantCulture),
 201                Tagline = movieResult.Tagline,
 202                ProductionLocations = movieResult.ProductionCountries?.Select(pc => pc.Name).ToArray() ?? Array.Empty<st
 203            };
 204            var metadataResult = new MetadataResult<Movie>
 205            {
 206                HasMetadata = true,
 207                ResultLanguage = info.MetadataLanguage,
 208                Item = movie
 209            };
 210
 211            movie.SetProviderId(MetadataProvider.Tmdb, tmdbId);
 212            movie.TrySetProviderId(MetadataProvider.Imdb, movieResult.ImdbId);
 213            if (movieResult.BelongsToCollection is not null)
 214            {
 215                movie.SetProviderId(MetadataProvider.TmdbCollection, movieResult.BelongsToCollection.Id.ToString(Culture
 216                movie.CollectionName = movieResult.BelongsToCollection.Name;
 217            }
 218
 219            movie.CommunityRating = Convert.ToSingle(movieResult.VoteAverage);
 220
 221            if (movieResult.Releases?.Countries is not null)
 222            {
 223                var releases = movieResult.Releases.Countries.Where(i => !string.IsNullOrWhiteSpace(i.Certification)).To
 224
 225                var ourRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, info.MetadataCountryCode, Stri
 226
 227                if (ourRelease?.Certification is not null)
 228                {
 229                    movie.OfficialRating = TmdbUtils.BuildParentalRating(info.MetadataCountryCode, ourRelease.Certificat
 230                }
 231                else
 232                {
 233                    var usRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, "US", StringComparison.Ordi
 234                    if (usRelease?.Certification is not null)
 235                    {
 236                        movie.OfficialRating = usRelease.Certification;
 237                    }
 238                }
 239            }
 240
 241            movie.PremiereDate = movieResult.ReleaseDate;
 242            movie.ProductionYear = movieResult.ReleaseDate?.Year;
 243
 244            if (movieResult.ProductionCompanies is not null)
 245            {
 246                movie.SetStudios(movieResult.ProductionCompanies.Select(c => c.Name));
 247            }
 248
 249            var genres = movieResult.Genres;
 250
 251            if (genres is not null)
 252            {
 253                foreach (var genre in genres.Select(g => g.Name).Trimmed())
 254                {
 255                    movie.AddGenre(genre);
 256                }
 257            }
 258
 259            if (movieResult.Keywords?.Keywords is not null)
 260            {
 261                foreach (var keyword in movieResult.Keywords.Keywords)
 262                {
 263                    var name = keyword.Name;
 264                    if (!string.IsNullOrWhiteSpace(name))
 265                    {
 266                        movie.AddTag(name);
 267                    }
 268                }
 269            }
 270
 271            if (movieResult.Credits?.Cast is not null)
 272            {
 273                var castQuery = movieResult.Credits.Cast.AsEnumerable();
 274
 275                if (config.HideMissingCastMembers)
 276                {
 277                    castQuery = castQuery.Where(a => !string.IsNullOrEmpty(a.ProfilePath));
 278                }
 279
 280                castQuery = castQuery.OrderBy(a => a.Order).Take(config.MaxCastMembers);
 281
 282                foreach (var actor in castQuery)
 283                {
 284                    if (string.IsNullOrWhiteSpace(actor.Name))
 285                    {
 286                        continue;
 287                    }
 288
 289                    var personInfo = new PersonInfo
 290                    {
 291                        Name = actor.Name.Trim(),
 292                        Role = actor.Character?.Trim() ?? string.Empty,
 293                        Type = PersonKind.Actor,
 294                        SortOrder = actor.Order
 295                    };
 296
 297                    if (!string.IsNullOrWhiteSpace(actor.ProfilePath))
 298                    {
 299                        personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(actor.ProfilePath);
 300                    }
 301
 302                    if (actor.Id > 0)
 303                    {
 304                        personInfo.SetProviderId(MetadataProvider.Tmdb, actor.Id.ToString(CultureInfo.InvariantCulture))
 305                    }
 306
 307                    metadataResult.AddPerson(personInfo);
 308                }
 309            }
 310
 311            if (movieResult.Credits?.Crew is not null)
 312            {
 313                var crewQuery = movieResult.Credits.Crew
 314                    .Select(crewMember => new
 315                    {
 316                        CrewMember = crewMember,
 317                        PersonType = TmdbUtils.MapCrewToPersonType(crewMember)
 318                    })
 319                    .Where(entry => TmdbUtils.WantedCrewKinds.Contains(entry.PersonType));
 320
 321                if (config.HideMissingCrewMembers)
 322                {
 323                    crewQuery = crewQuery.Where(entry => !string.IsNullOrEmpty(entry.CrewMember.ProfilePath));
 324                }
 325
 326                crewQuery = crewQuery.Take(config.MaxCrewMembers);
 327
 328                foreach (var entry in crewQuery)
 329                {
 330                    var crewMember = entry.CrewMember;
 331
 332                    if (string.IsNullOrWhiteSpace(crewMember.Name))
 333                    {
 334                        continue;
 335                    }
 336
 337                    var personInfo = new PersonInfo
 338                    {
 339                        Name = crewMember.Name.Trim(),
 340                        Role = crewMember.Job?.Trim() ?? string.Empty,
 341                        Type = entry.PersonType
 342                    };
 343
 344                    if (!string.IsNullOrWhiteSpace(crewMember.ProfilePath))
 345                    {
 346                        personInfo.ImageUrl = _tmdbClientManager.GetProfileUrl(crewMember.ProfilePath);
 347                    }
 348
 349                    if (crewMember.Id > 0)
 350                    {
 351                        personInfo.SetProviderId(MetadataProvider.Tmdb, crewMember.Id.ToString(CultureInfo.InvariantCult
 352                    }
 353
 354                    metadataResult.AddPerson(personInfo);
 355                }
 356            }
 357
 358            if (movieResult.Videos?.Results is not null)
 359            {
 360                var trailers = new List<MediaUrl>();
 361
 362                var sortedVideos = movieResult.Videos.Results
 363                    .OrderByDescending(video => string.Equals(video.Type, "trailer", StringComparison.OrdinalIgnoreCase)
 364
 365                foreach (var video in sortedVideos)
 366                {
 367                    if (!TmdbUtils.IsTrailerType(video))
 368                    {
 369                        continue;
 370                    }
 371
 372                    trailers.Add(new MediaUrl
 373                    {
 374                        Url = string.Format(CultureInfo.InvariantCulture, "https://www.youtube.com/watch?v={0}", video.K
 375                        Name = video.Name
 376                    });
 377                }
 378
 379                movie.RemoteTrailers = trailers;
 380            }
 381
 382            return metadataResult;
 383        }
 384
 385        /// <inheritdoc />
 386        public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
 387        {
 0388            return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken);
 389        }
 390    }
 391}