< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Plugins.Tmdb.TmdbUtils
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
Line coverage
29%
Covered lines: 19
Uncovered lines: 46
Coverable lines: 65
Total lines: 227
Line coverage: 29.2%
Branch coverage
37%
Covered branches: 18
Total branches: 48
Branch coverage: 37.5%
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: 32.2% (19/59) Branch coverage: 39.5% (19/48) Total lines: 2165/20/2026 - 12:15:44 AM Line coverage: 32.2% (19/59) Branch coverage: 37.5% (18/48) Total lines: 2167/16/2026 - 12:13:45 AM Line coverage: 29.2% (19/65) Branch coverage: 37.5% (18/48) Total lines: 227 4/8/2026 - 12:11:47 AM Line coverage: 32.2% (19/59) Branch coverage: 39.5% (19/48) Total lines: 2165/20/2026 - 12:15:44 AM Line coverage: 32.2% (19/59) Branch coverage: 37.5% (18/48) Total lines: 2167/16/2026 - 12:13:45 AM Line coverage: 29.2% (19/65) Branch coverage: 37.5% (18/48) Total lines: 227

Coverage delta

Coverage delta 3 -3

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%210%
CleanName(...)100%210%
MapCrewToPersonType(...)0%210140%
IsTrailerType(...)0%2040%
GetImageLanguagesParam(...)0%2040%
NormalizeLanguage(...)58.33%141275%
AdjustImageLanguage(...)91.66%1212100%
BuildParentalRating(...)0%620%

File(s)

/srv/git/jellyfin/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs

#LineLine coverage
 1using System;
 2using System.Collections.Frozen;
 3using System.Collections.Generic;
 4using System.Diagnostics.CodeAnalysis;
 5using System.Text.RegularExpressions;
 6using Jellyfin.Data.Enums;
 7using MediaBrowser.Model.Entities;
 8using TMDbLib.Objects.General;
 9
 10namespace MediaBrowser.Providers.Plugins.Tmdb
 11{
 12    /// <summary>
 13    /// Utilities for the TMDb provider.
 14    /// </summary>
 15    public static partial class TmdbUtils
 16    {
 17        /// <summary>
 18        /// URL of the TMDb instance to use.
 19        /// </summary>
 20        public const string BaseTmdbUrl = "https://www.themoviedb.org/";
 21
 22        /// <summary>
 23        /// Name of the provider.
 24        /// </summary>
 25        public const string ProviderName = "TheMovieDb";
 26
 27        /// <summary>
 28        /// API key to use when performing an API call.
 29        /// </summary>
 30        public const string ApiKey = "4219e299c89411838049ab0dab19ebd5";
 31
 32        /// <summary>
 33        /// The crew types to keep.
 34        /// </summary>
 035        public static readonly string[] WantedCrewTypes =
 036        {
 037            PersonType.Director,
 038            PersonType.Writer,
 039            PersonType.Producer
 040        };
 41
 42        /// <summary>
 43        /// The crew kinds to keep.
 44        /// </summary>
 045        public static readonly PersonKind[] WantedCrewKinds =
 046        {
 047            PersonKind.Director,
 048            PersonKind.Writer,
 049            PersonKind.Producer
 050        };
 51
 52        /// <summary>
 53        /// Writing jobs to keep.
 54        /// </summary>
 055        private static readonly FrozenSet<string> _writerJobs = new[]
 056        {
 057            "writer",
 058            "screenplay",
 059            "novel"
 060        }.ToFrozenSet(StringComparer.OrdinalIgnoreCase);
 61
 62        [GeneratedRegex(@"[\W_-[·]]+")]
 63        private static partial Regex NonWordRegex();
 64
 65        /// <summary>
 66        /// Cleans the name according to TMDb requirements.
 67        /// </summary>
 68        /// <param name="name">The name of the entity.</param>
 69        /// <returns>The cleaned name.</returns>
 70        public static string CleanName(string name)
 71        {
 72            // TMDb expects a space separated list of words make sure that is the case
 073            return NonWordRegex().Replace(name, " ");
 74        }
 75
 76        /// <summary>
 77        /// Maps the TMDb provided roles for crew members to Jellyfin roles.
 78        /// </summary>
 79        /// <param name="crew">Crew member to map against the Jellyfin person types.</param>
 80        /// <returns>The Jellyfin person type.</returns>
 81        public static PersonKind MapCrewToPersonType(Crew crew)
 82        {
 083            if (string.Equals(crew.Department, "directing", StringComparison.OrdinalIgnoreCase)
 084                && string.Equals(crew.Job, "director", StringComparison.OrdinalIgnoreCase))
 85            {
 086                return PersonKind.Director;
 87            }
 88
 089            if (string.Equals(crew.Department, "production", StringComparison.OrdinalIgnoreCase)
 090                && string.Equals(crew.Job, "producer", StringComparison.OrdinalIgnoreCase))
 91            {
 092                return PersonKind.Producer;
 93            }
 94
 095            if (string.Equals(crew.Department, "writing", StringComparison.OrdinalIgnoreCase)
 096                && crew.Job is not null && _writerJobs.Contains(crew.Job))
 97            {
 098                return PersonKind.Writer;
 99            }
 100
 0101            return PersonKind.Unknown;
 102        }
 103
 104        /// <summary>
 105        /// Determines whether a video is a trailer.
 106        /// </summary>
 107        /// <param name="video">The TMDb video.</param>
 108        /// <returns>A boolean indicating whether the video is a trailer.</returns>
 109        public static bool IsTrailerType(Video video)
 110        {
 0111            return string.Equals(video.Site, "youtube", StringComparison.OrdinalIgnoreCase)
 0112                   && (string.Equals(video.Type, "trailer", StringComparison.OrdinalIgnoreCase)
 0113                       || string.Equals(video.Type, "teaser", StringComparison.OrdinalIgnoreCase));
 114        }
 115
 116        /// <summary>
 117        /// Normalizes a language string for use with TMDb's include image language parameter.
 118        /// </summary>
 119        /// <param name="preferredLanguage">The preferred language as either a 2 letter code with or without country cod
 120        /// <param name="countryCode">The country code, ISO 3166-1.</param>
 121        /// <returns>The comma separated language string.</returns>
 122        public static string GetImageLanguagesParam(string preferredLanguage, string? countryCode = null)
 123        {
 0124            var languages = new List<string>();
 125
 0126            if (!string.IsNullOrEmpty(preferredLanguage))
 127            {
 0128                preferredLanguage = NormalizeLanguage(preferredLanguage, countryCode);
 129
 0130                languages.Add(preferredLanguage);
 131            }
 132
 0133            languages.Add("null");
 134
 135            // Always add English as fallback language
 0136            if (!string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase))
 137            {
 0138                languages.Add("en");
 139            }
 140
 0141            return string.Join(',', languages);
 142        }
 143
 144        /// <summary>
 145        /// Normalizes a language string for use with TMDb's language parameter.
 146        /// </summary>
 147        /// <param name="language">The language code.</param>
 148        /// <param name="countryCode">The country code.</param>
 149        /// <returns>The normalized language code.</returns>
 150        [return: NotNullIfNotNull(nameof(language))]
 151        public static string? NormalizeLanguage(string? language, string? countryCode = null)
 152        {
 7153            if (string.IsNullOrEmpty(language))
 154            {
 2155                return language;
 156            }
 157
 158            // Handle es-419 (Latin American Spanish) by converting to regional variant
 5159            if (string.Equals(language, "es-419", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(countryCo
 160            {
 0161                language = string.Equals(countryCode, "AR", StringComparison.OrdinalIgnoreCase)
 0162                    ? "es-AR"
 0163                    : "es-MX";
 164            }
 165
 166            // TMDb requires this to be uppercase
 167            // Everything after the hyphen must be written in uppercase due to a way TMDb wrote their API.
 168            // See here: https://www.themoviedb.org/talk/5119221d760ee36c642af4ad?page=3#56e372a0c3a3685a9e0019ab
 5169            var parts = language.Split('-');
 170
 5171            if (parts.Length == 2)
 172            {
 173                // TMDb doesn't support Switzerland (de-CH, it-CH or fr-CH) so use the language (de, it or fr) without c
 3174                if (string.Equals(parts[1], "CH", StringComparison.OrdinalIgnoreCase))
 175                {
 1176                    return parts[0];
 177                }
 178
 2179                language = parts[0] + "-" + parts[1].ToUpperInvariant();
 180            }
 181
 4182            return language;
 183        }
 184
 185        /// <summary>
 186        /// Adjusts the image's language code preferring the 5 letter language code eg. en-US.
 187        /// </summary>
 188        /// <param name="imageLanguage">The image's actual language code.</param>
 189        /// <param name="requestLanguage">The requested language code.</param>
 190        /// <returns>The language code.</returns>
 191        public static string AdjustImageLanguage(string? imageLanguage, string requestLanguage)
 192        {
 5193            if (string.IsNullOrEmpty(imageLanguage))
 194            {
 1195                return string.Empty;
 196            }
 197
 4198            if (!string.IsNullOrEmpty(requestLanguage)
 4199                && requestLanguage.Length > 2
 4200                && imageLanguage.Length == 2
 4201                && requestLanguage.StartsWith(imageLanguage, StringComparison.OrdinalIgnoreCase))
 202            {
 1203                return requestLanguage;
 204            }
 205
 206            // TMDb now returns xx for no language instead of an empty string.
 3207            return string.Equals(imageLanguage, "xx", StringComparison.OrdinalIgnoreCase)
 3208                ? string.Empty
 3209                : imageLanguage;
 210        }
 211
 212        /// <summary>
 213        /// Combines the metadata country code and the parental rating from the API into the value we store in our datab
 214        /// </summary>
 215        /// <param name="countryCode">The ISO 3166-1 country code of the rating country.</param>
 216        /// <param name="ratingValue">The rating value returned by the TMDb API.</param>
 217        /// <returns>The combined parental rating of country code+rating value.</returns>
 218        public static string BuildParentalRating(string countryCode, string ratingValue)
 219        {
 220            // Exclude US because we store US values as TV-14 without the country code.
 0221            var ratingPrefix = string.Equals(countryCode, "US", StringComparison.OrdinalIgnoreCase) ? string.Empty : cou
 0222            var newRating = ratingPrefix + ratingValue;
 223
 0224            return newRating.Replace("DE-", "FSK-", StringComparison.OrdinalIgnoreCase);
 225        }
 226    }
 227}