< 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: 380
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 7/22/2025 - 12:11:20 AM Line coverage: 57.1% (4/7) Total lines: 3709/12/2025 - 12:11:36 AM Line coverage: 57.1% (4/7) Total lines: 3739/13/2025 - 12:11:04 AM Line coverage: 57.1% (4/7) Total lines: 37610/14/2025 - 12:11:23 AM Line coverage: 57.1% (4/7) Total lines: 380

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