diff options
Diffstat (limited to 'MediaBrowser.Providers/Plugins')
38 files changed, 706 insertions, 453 deletions
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs index ad0247fb2..7f73afc53 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumImageProvider.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net.Http; using System.Text.Json; using System.Threading; @@ -42,11 +43,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb /// <inheritdoc /> public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { - return new List<ImageType> - { - ImageType.Primary, - ImageType.Disc - }; + yield return ImageType.Primary; + yield return ImageType.Disc; } /// <inheritdoc /> @@ -60,16 +58,19 @@ namespace MediaBrowser.Providers.Plugins.AudioDb var path = AudioDbAlbumProvider.GetAlbumInfoPath(_config.ApplicationPaths, id); - await using FileStream jsonStream = AsyncFile.OpenRead(path); - var obj = await JsonSerializer.DeserializeAsync<AudioDbAlbumProvider.RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); - - if (obj != null && obj.album != null && obj.album.Count > 0) + FileStream jsonStream = AsyncFile.OpenRead(path); + await using (jsonStream.ConfigureAwait(false)) { - return GetImages(obj.album[0]); + var obj = await JsonSerializer.DeserializeAsync<AudioDbAlbumProvider.RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + + if (obj is not null && obj.album is not null && obj.album.Count > 0) + { + return GetImages(obj.album[0]); + } } } - return new List<RemoteImageInfo>(); + return Enumerable.Empty<RemoteImageInfo>(); } private IEnumerable<RemoteImageInfo> GetImages(AudioDbAlbumProvider.Album item) diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs index 43f30824b..55e2474a5 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs @@ -68,14 +68,17 @@ namespace MediaBrowser.Providers.Plugins.AudioDb var path = GetAlbumInfoPath(_config.ApplicationPaths, id); - await using FileStream jsonStream = AsyncFile.OpenRead(path); - var obj = await JsonSerializer.DeserializeAsync<RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); - - if (obj != null && obj.album != null && obj.album.Count > 0) + FileStream jsonStream = AsyncFile.OpenRead(path); + await using (jsonStream.ConfigureAwait(false)) { - result.Item = new MusicAlbum(); - result.HasMetadata = true; - ProcessResult(result.Item, obj.album[0], info.MetadataLanguage); + var obj = await JsonSerializer.DeserializeAsync<RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + + if (obj is not null && obj.album is not null && obj.album.Count > 0) + { + result.Item = new MusicAlbum(); + result.HasMetadata = true; + ProcessResult(result.Item, obj.album[0], info.MetadataLanguage); + } } } @@ -173,13 +176,18 @@ namespace MediaBrowser.Providers.Plugins.AudioDb Directory.CreateDirectory(Path.GetDirectoryName(path)); using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - - var fileStreamOptions = AsyncFile.WriteOptions; - fileStreamOptions.Mode = FileMode.Create; - fileStreamOptions.PreallocationSize = stream.Length; - await using var xmlFileStream = new FileStream(path, fileStreamOptions); - await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); + var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + await using (stream.ConfigureAwait(false)) + { + var fileStreamOptions = AsyncFile.WriteOptions; + fileStreamOptions.Mode = FileMode.Create; + fileStreamOptions.PreallocationSize = stream.Length; + var xmlFileStream = new FileStream(path, fileStreamOptions); + await using (xmlFileStream.ConfigureAwait(false)) + { + await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); + } + } } private static string GetAlbumDataPath(IApplicationPaths appPaths, string musicBrainzReleaseGroupId) diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs index 9c2447660..2232dfa0d 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistImageProvider.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net.Http; using System.Text.Json; using System.Threading; @@ -42,7 +43,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb /// <inheritdoc /> public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { - return new List<ImageType> + return new ImageType[] { ImageType.Primary, ImageType.Logo, @@ -62,16 +63,19 @@ namespace MediaBrowser.Providers.Plugins.AudioDb var path = AudioDbArtistProvider.GetArtistInfoPath(_config.ApplicationPaths, id); - await using FileStream jsonStream = AsyncFile.OpenRead(path); - var obj = await JsonSerializer.DeserializeAsync<AudioDbArtistProvider.RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); - - if (obj != null && obj.artists != null && obj.artists.Count > 0) + FileStream jsonStream = AsyncFile.OpenRead(path); + await using (jsonStream.ConfigureAwait(false)) { - return GetImages(obj.artists[0]); + var obj = await JsonSerializer.DeserializeAsync<AudioDbArtistProvider.RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + + if (obj is not null && obj.artists is not null && obj.artists.Count > 0) + { + return GetImages(obj.artists[0]); + } } } - return new List<RemoteImageInfo>(); + return Enumerable.Empty<RemoteImageInfo>(); } private IEnumerable<RemoteImageInfo> GetImages(AudioDbArtistProvider.Artist item) diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs index 538dc67c4..f3385b3a9 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs @@ -67,14 +67,17 @@ namespace MediaBrowser.Providers.Plugins.AudioDb var path = GetArtistInfoPath(_config.ApplicationPaths, id); - await using FileStream jsonStream = AsyncFile.OpenRead(path); - var obj = await JsonSerializer.DeserializeAsync<RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); - - if (obj != null && obj.artists != null && obj.artists.Count > 0) + FileStream jsonStream = AsyncFile.OpenRead(path); + await using (jsonStream.ConfigureAwait(false)) { - result.Item = new MusicArtist(); - result.HasMetadata = true; - ProcessResult(result.Item, obj.artists[0], info.MetadataLanguage); + var obj = await JsonSerializer.DeserializeAsync<RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false); + + if (obj is not null && obj.artists is not null && obj.artists.Count > 0) + { + result.Item = new MusicArtist(); + result.HasMetadata = true; + ProcessResult(result.Item, obj.artists[0], info.MetadataLanguage); + } } } @@ -149,18 +152,23 @@ namespace MediaBrowser.Providers.Plugins.AudioDb var url = BaseUrl + "/artist-mb.php?i=" + musicBrainzId; - var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId); - using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - var fileStreamOptions = AsyncFile.WriteOptions; - fileStreamOptions.Mode = FileMode.Create; - fileStreamOptions.PreallocationSize = stream.Length; - await using var xmlFileStream = new FileStream(path, fileStreamOptions); - await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + await using (stream.ConfigureAwait(false)) + { + var path = GetArtistInfoPath(_config.ApplicationPaths, musicBrainzId); + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + var fileStreamOptions = AsyncFile.WriteOptions; + fileStreamOptions.Mode = FileMode.Create; + fileStreamOptions.PreallocationSize = stream.Length; + var xmlFileStream = new FileStream(path, fileStreamOptions); + await using (xmlFileStream.ConfigureAwait(false)) + { + await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false); + } + } } /// <summary> diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html index eab252005..2093effca 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html @@ -1,12 +1,13 @@ <!DOCTYPE html> <html> <head> - <title>AudioDB</title> + <title>TheAudioDB</title> </head> <body> - <div data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox"> + <div id="configPage" data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox"> <div data-role="content"> <div class="content-primary"> + <h1>TheAudioDB</h1> <form class="configForm"> <label class="checkboxContainer"> <input is="emby-checkbox" type="checkbox" id="replaceAlbumName" /> diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs index 22229e377..21a15c58c 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs @@ -1,5 +1,4 @@ using MediaBrowser.Model.Plugins; -using MetaBrainz.MusicBrainz; namespace MediaBrowser.Providers.Plugins.MusicBrainz.Configuration; @@ -8,16 +7,22 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz.Configuration; /// </summary> public class PluginConfiguration : BasePluginConfiguration { - private const string DefaultServer = "musicbrainz.org"; + /// <summary> + /// The default server URL. + /// </summary> + public const string DefaultServer = "https://musicbrainz.org"; - private const double DefaultRateLimit = 1.0; + /// <summary> + /// The default rate limit. + /// </summary> + public const double DefaultRateLimit = 1.0; private string _server = DefaultServer; private double _rateLimit = DefaultRateLimit; /// <summary> - /// Gets or sets the server url. + /// Gets or sets the server URL. /// </summary> public string Server { diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html index 6f1296bb7..24f2ac0ca 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html @@ -4,17 +4,18 @@ <title>MusicBrainz</title> </head> <body> - <div data-role="page" class="page type-interior pluginConfigurationPage musicBrainzConfigPage" data-require="emby-input,emby-button,emby-checkbox"> + <div id="configPage" data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox"> <div data-role="content"> <div class="content-primary"> - <form class="musicBrainzConfigForm"> + <h1>MusicBrainz</h1> + <form class="configForm"> <div class="inputContainer"> <input is="emby-input" type="text" id="server" required label="Server" /> <div class="fieldDescription">This can be a mirror of the official server or even a custom server.</div> </div> <div class="inputContainer"> - <input is="emby-input" type="number" id="rateLimit" pattern="[0-9]*" required min="0" max="10000" label="Rate Limit" /> - <div class="fieldDescription">Span of time between requests in milliseconds. The official server is limited to one request every two seconds.</div> + <input is="emby-input" type="number" id="rateLimit" required pattern="[0-9]*" min="0" max="10" step=".01" label="Rate Limit" /> + <div class="fieldDescription">Span of time between requests in seconds. The official server is limited to one request every seconds.</div> </div> <label class="checkboxContainer"> <input is="emby-checkbox" type="checkbox" id="replaceArtistName" /> @@ -32,7 +33,7 @@ uniquePluginId: "8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a" }; - document.querySelector('.musicBrainzConfigPage') + document.querySelector('.configPage') .addEventListener('pageshow', function () { Dashboard.showLoadingMsg(); ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) { @@ -49,14 +50,14 @@ bubbles: true, cancelable: false })); - + document.querySelector('#replaceArtistName').checked = config.ReplaceArtistName; Dashboard.hideLoadingMsg(); }); }); - document.querySelector('.musicBrainzConfigForm') + document.querySelector('.configForm') .addEventListener('submit', function (e) { Dashboard.showLoadingMsg(); diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index 4d9feca6d..d0bd7d609 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -8,11 +8,14 @@ using Jellyfin.Extensions; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Providers; using MediaBrowser.Providers.Music; +using MediaBrowser.Providers.Plugins.MusicBrainz.Configuration; using MetaBrainz.MusicBrainz; using MetaBrainz.MusicBrainz.Interfaces.Entities; using MetaBrainz.MusicBrainz.Interfaces.Searches; +using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Plugins.MusicBrainz; @@ -21,20 +24,19 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz; /// </summary> public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, AlbumInfo>, IHasOrder, IDisposable { - private readonly Query _musicBrainzQuery; + private readonly ILogger<MusicBrainzAlbumProvider> _logger; + private Query _musicBrainzQuery; /// <summary> /// Initializes a new instance of the <see cref="MusicBrainzAlbumProvider"/> class. /// </summary> - public MusicBrainzAlbumProvider() + /// <param name="logger">The logger.</param> + public MusicBrainzAlbumProvider(ILogger<MusicBrainzAlbumProvider> logger) { - MusicBrainz.Plugin.Instance!.ConfigurationChanged += (_, _) => - { - Query.DefaultServer = MusicBrainz.Plugin.Instance.Configuration.Server; - Query.DelayBetweenRequests = MusicBrainz.Plugin.Instance.Configuration.RateLimit; - }; - + _logger = logger; _musicBrainzQuery = new Query(); + ReloadConfig(null, MusicBrainz.Plugin.Instance!.Configuration); + MusicBrainz.Plugin.Instance!.ConfigurationChanged += ReloadConfig; } /// <inheritdoc /> @@ -43,6 +45,29 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu /// <inheritdoc /> public int Order => 0; + private void ReloadConfig(object? sender, BasePluginConfiguration e) + { + var configuration = (PluginConfiguration)e; + if (Uri.TryCreate(configuration.Server, UriKind.Absolute, out var server)) + { + Query.DefaultServer = server.DnsSafeHost; + Query.DefaultPort = server.Port; + Query.DefaultUrlScheme = server.Scheme; + } + else + { + // Fallback to official server + _logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server"); + var defaultServer = new Uri(PluginConfiguration.DefaultServer); + Query.DefaultServer = defaultServer.Host; + Query.DefaultPort = defaultServer.Port; + Query.DefaultUrlScheme = defaultServer.Scheme; + } + + Query.DelayBetweenRequests = configuration.RateLimit; + _musicBrainzQuery = new Query(); + } + /// <inheritdoc /> public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken) { @@ -51,13 +76,13 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu if (!string.IsNullOrEmpty(releaseId)) { - var releaseResult = await _musicBrainzQuery.LookupReleaseAsync(new Guid(releaseId), Include.ReleaseGroups, cancellationToken).ConfigureAwait(false); + var releaseResult = await _musicBrainzQuery.LookupReleaseAsync(new Guid(releaseId), Include.Artists | Include.ReleaseGroups, cancellationToken).ConfigureAwait(false); return GetReleaseResult(releaseResult).SingleItemAsEnumerable(); } if (!string.IsNullOrEmpty(releaseGroupId)) { - var releaseGroupResult = await _musicBrainzQuery.LookupReleaseGroupAsync(new Guid(releaseGroupId), Include.None, null, cancellationToken).ConfigureAwait(false); + var releaseGroupResult = await _musicBrainzQuery.LookupReleaseGroupAsync(new Guid(releaseGroupId), Include.Releases, null, cancellationToken).ConfigureAwait(false); return GetReleaseGroupResult(releaseGroupResult.Releases); } @@ -112,7 +137,9 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu foreach (var result in releaseSearchResults) { - yield return GetReleaseResult(result); + // Fetch full release info, otherwise artists are missing + var fullResult = _musicBrainzQuery.LookupRelease(result.Id, Include.Artists | Include.ReleaseGroups); + yield return GetReleaseResult(fullResult); } } @@ -122,21 +149,33 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu { Name = releaseSearchResult.Title, ProductionYear = releaseSearchResult.Date?.Year, - PremiereDate = releaseSearchResult.Date?.NearestDate + PremiereDate = releaseSearchResult.Date?.NearestDate, + SearchProviderName = Name }; - if (releaseSearchResult.ArtistCredit?.Count > 0) + // Add artists and use first as album artist + var artists = releaseSearchResult.ArtistCredit; + if (artists is not null && artists.Count > 0) { - searchResult.AlbumArtist = new RemoteSearchResult + var artistResults = new RemoteSearchResult[artists.Count]; + for (int i = 0; i < artists.Count; i++) { - SearchProviderName = Name, - Name = releaseSearchResult.ArtistCredit[0].Name - }; + var artist = artists[i]; + var artistResult = new RemoteSearchResult + { + Name = artist.Name + }; - if (releaseSearchResult.ArtistCredit[0].Artist?.Id is not null) - { - searchResult.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, releaseSearchResult.ArtistCredit[0].Artist!.Id.ToString()); + if (artist.Artist?.Id is not null) + { + artistResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Artist!.Id.ToString()); + } + + artistResults[i] = artistResult; } + + searchResult.AlbumArtist = artistResults[0]; + searchResult.Artists = artistResults; } searchResult.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseSearchResult.Id.ToString()); @@ -167,7 +206,7 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu // TODO: Actually try to match the release. Simply taking the first result is stupid. var releaseGroup = await _musicBrainzQuery.LookupReleaseGroupAsync(new Guid(releaseGroupId), Include.None, null, cancellationToken).ConfigureAwait(false); var release = releaseGroup.Releases?.Count > 0 ? releaseGroup.Releases[0] : null; - if (release != null) + if (release is not null) { releaseId = release.Id.ToString(); result.HasMetadata = true; @@ -193,7 +232,7 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu releaseResult = releaseSearchResults.Results.Count > 0 ? releaseSearchResults.Results[0].Item : null; } - if (releaseResult != null) + if (releaseResult is not null) { releaseId = releaseResult.Id.ToString(); diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs index 2cc3a13be..1323d2604 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs @@ -4,16 +4,18 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using System.Xml; using Jellyfin.Extensions; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Providers; using MediaBrowser.Providers.Music; +using MediaBrowser.Providers.Plugins.MusicBrainz.Configuration; using MetaBrainz.MusicBrainz; using MetaBrainz.MusicBrainz.Interfaces.Entities; using MetaBrainz.MusicBrainz.Interfaces.Searches; +using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Plugins.MusicBrainz; @@ -22,25 +24,47 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz; /// </summary> public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IDisposable { - private readonly Query _musicBrainzQuery; + private readonly ILogger<MusicBrainzArtistProvider> _logger; + private Query _musicBrainzQuery; /// <summary> /// Initializes a new instance of the <see cref="MusicBrainzArtistProvider"/> class. /// </summary> - public MusicBrainzArtistProvider() + /// <param name="logger">The logger.</param> + public MusicBrainzArtistProvider(ILogger<MusicBrainzArtistProvider> logger) { - MusicBrainz.Plugin.Instance!.ConfigurationChanged += (_, _) => - { - Query.DefaultServer = MusicBrainz.Plugin.Instance.Configuration.Server; - Query.DelayBetweenRequests = MusicBrainz.Plugin.Instance.Configuration.RateLimit; - }; - + _logger = logger; _musicBrainzQuery = new Query(); + ReloadConfig(null, MusicBrainz.Plugin.Instance!.Configuration); + MusicBrainz.Plugin.Instance!.ConfigurationChanged += ReloadConfig; } /// <inheritdoc /> public string Name => "MusicBrainz"; + private void ReloadConfig(object? sender, BasePluginConfiguration e) + { + var configuration = (PluginConfiguration)e; + if (Uri.TryCreate(configuration.Server, UriKind.Absolute, out var server)) + { + Query.DefaultServer = server.DnsSafeHost; + Query.DefaultPort = server.Port; + Query.DefaultUrlScheme = server.Scheme; + } + else + { + // Fallback to official server + _logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server"); + var defaultServer = new Uri(PluginConfiguration.DefaultServer); + Query.DefaultServer = defaultServer.Host; + Query.DefaultPort = defaultServer.Port; + Query.DefaultUrlScheme = defaultServer.Scheme; + } + + Query.DelayBetweenRequests = configuration.RateLimit; + _musicBrainzQuery = new Query(); + } + /// <inheritdoc /> public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken) { @@ -92,7 +116,8 @@ public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, Ar { Name = artist.Name, ProductionYear = artist.LifeSpan?.Begin?.Year, - PremiereDate = artist.LifeSpan?.Begin?.NearestDate + PremiereDate = artist.LifeSpan?.Begin?.NearestDate, + SearchProviderName = Name, }; searchResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Id.ToString()); @@ -113,7 +138,7 @@ public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, Ar var singleResult = searchResults.FirstOrDefault(); - if (singleResult != null) + if (singleResult is not null) { musicBrainzId = singleResult.GetProviderId(MetadataProvider.MusicBrainzArtist); result.Item.Overview = singleResult.Overview; diff --git a/MediaBrowser.Providers/Plugins/Omdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Omdb/Configuration/config.html index f4375b3cb..d00c1f9f8 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/Omdb/Configuration/config.html @@ -4,9 +4,10 @@ <title>OMDb</title> </head> <body> - <div data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox"> + <div id="configPage" data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox"> <div data-role="content"> <div class="content-primary"> + <h1>OMDb</h1> <form class="configForm"> <label class="checkboxContainer"> <input is="emby-checkbox" type="checkbox" id="castAndCrew" /> @@ -33,16 +34,16 @@ }); }); - + document.querySelector('.configForm') .addEventListener('submit', function (e) { Dashboard.showLoadingMsg(); - + ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) { config.CastAndCrew = document.querySelector('#castAndCrew').checked; ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult); }); - + e.preventDefault(); return false; }); diff --git a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs index 8bfdc461e..38379ae5d 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableInt32Converter.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb if (reader.TokenType == JsonTokenType.String) { var str = reader.GetString(); - if (str == null || str.Equals("N/A", StringComparison.OrdinalIgnoreCase)) + if (str is null || str.Equals("N/A", StringComparison.OrdinalIgnoreCase)) { return null; } diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs index 60b373483..140a64f52 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs @@ -38,10 +38,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { - return new List<ImageType> - { - ImageType.Primary - }; + yield return ImageType.Primary; } public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs index e5753b2b5..e4bb4eaea 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs @@ -90,7 +90,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb var imdbId = searchInfo.GetProviderId(MetadataProvider.Imdb); var urlQuery = new StringBuilder("plot=full&r=json"); - if (episodeSearchInfo != null) + if (episodeSearchInfo is not null) { episodeSearchInfo.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out imdbId); if (searchInfo.IndexNumber.HasValue) @@ -137,29 +137,31 @@ namespace MediaBrowser.Providers.Plugins.Omdb var url = OmdbProvider.GetOmdbUrl(urlQuery.ToString()); using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false); - await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); - - if (isSearch) + var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); + await using (stream.ConfigureAwait(false)) { - var searchResultList = await JsonSerializer.DeserializeAsync<SearchResultList>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); - if (searchResultList?.Search != null) + if (isSearch) { - var resultCount = searchResultList.Search.Count; - var result = new RemoteSearchResult[resultCount]; - for (var i = 0; i < resultCount; i++) + var searchResultList = await JsonSerializer.DeserializeAsync<SearchResultList>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); + if (searchResultList?.Search is not null) { - result[i] = ResultToMetadataResult(searchResultList.Search[i], searchInfo, indexNumberEnd); + var resultCount = searchResultList.Search.Count; + var result = new RemoteSearchResult[resultCount]; + for (var i = 0; i < resultCount; i++) + { + result[i] = ResultToMetadataResult(searchResultList.Search[i], searchInfo, indexNumberEnd); + } + + return result; } - - return result; } - } - else - { - var result = await JsonSerializer.DeserializeAsync<SearchResult>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); - if (string.Equals(result?.Response, "true", StringComparison.OrdinalIgnoreCase)) + else { - return new[] { ResultToMetadataResult(result, searchInfo, indexNumberEnd) }; + var result = await JsonSerializer.DeserializeAsync<SearchResult>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); + if (string.Equals(result?.Response, "true", StringComparison.OrdinalIgnoreCase)) + { + return new[] { ResultToMetadataResult(result, searchInfo, indexNumberEnd) }; + } } } diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index 10077e5c8..3fd4ae1fc 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -13,6 +13,7 @@ using System.Net.Http.Json; using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Extensions.Json; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -98,8 +99,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb // item.VoteCount = voteCount; } - if (!string.IsNullOrEmpty(result.imdbRating) - && float.TryParse(result.imdbRating, NumberStyles.Any, CultureInfo.InvariantCulture, out var imdbRating) + if (float.TryParse(result.imdbRating, CultureInfo.InvariantCulture, out var imdbRating) && imdbRating >= 0) { item.CommunityRating = imdbRating; @@ -141,7 +141,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb var seasonResult = await GetSeasonRootObject(seriesImdbId, seasonNumber, cancellationToken).ConfigureAwait(false); - if (seasonResult?.Episodes == null) + if (seasonResult?.Episodes is null) { return false; } @@ -161,7 +161,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb } // finally, search by numbers - if (result == null) + if (result is null) { foreach (var episode in seasonResult.Episodes) { @@ -173,7 +173,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb } } - if (result == null) + if (result is null) { return false; } @@ -209,8 +209,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb // item.VoteCount = voteCount; } - if (!string.IsNullOrEmpty(result.imdbRating) - && float.TryParse(result.imdbRating, NumberStyles.Any, CultureInfo.InvariantCulture, out var imdbRating) + if (float.TryParse(result.imdbRating, CultureInfo.InvariantCulture, out var imdbRating) && imdbRating >= 0) { item.CommunityRating = imdbRating; @@ -234,15 +233,21 @@ namespace MediaBrowser.Providers.Plugins.Omdb internal async Task<RootObject> GetRootObject(string imdbId, CancellationToken cancellationToken) { var path = await EnsureItemInfo(imdbId, cancellationToken).ConfigureAwait(false); - await using var stream = AsyncFile.OpenRead(path); - return await JsonSerializer.DeserializeAsync<RootObject>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); + var stream = AsyncFile.OpenRead(path); + await using (stream.ConfigureAwait(false)) + { + return await JsonSerializer.DeserializeAsync<RootObject>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); + } } internal async Task<SeasonRootObject> GetSeasonRootObject(string imdbId, int seasonId, CancellationToken cancellationToken) { var path = await EnsureSeasonInfo(imdbId, seasonId, cancellationToken).ConfigureAwait(false); - await using var stream = AsyncFile.OpenRead(path); - return await JsonSerializer.DeserializeAsync<SeasonRootObject>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); + var stream = AsyncFile.OpenRead(path); + await using (stream.ConfigureAwait(false)) + { + return await JsonSerializer.DeserializeAsync<SeasonRootObject>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false); + } } /// <summary>Gets OMDB URL.</summary> @@ -317,8 +322,11 @@ namespace MediaBrowser.Providers.Plugins.Omdb imdbParam)); var rootObject = await _httpClientFactory.CreateClient(NamedClient.Default).GetFromJsonAsync<RootObject>(url, _jsonOptions, cancellationToken).ConfigureAwait(false); - await using FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); - await JsonSerializer.SerializeAsync(jsonFileStream, rootObject, _jsonOptions, cancellationToken).ConfigureAwait(false); + FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); + await using (jsonFileStream.ConfigureAwait(false)) + { + await JsonSerializer.SerializeAsync(jsonFileStream, rootObject, _jsonOptions, cancellationToken).ConfigureAwait(false); + } return path; } @@ -357,18 +365,18 @@ namespace MediaBrowser.Providers.Plugins.Omdb seasonId)); var rootObject = await _httpClientFactory.CreateClient(NamedClient.Default).GetFromJsonAsync<SeasonRootObject>(url, _jsonOptions, cancellationToken).ConfigureAwait(false); - await using FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); - await JsonSerializer.SerializeAsync(jsonFileStream, rootObject, _jsonOptions, cancellationToken).ConfigureAwait(false); + FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); + await using (jsonFileStream.ConfigureAwait(false)) + { + await JsonSerializer.SerializeAsync(jsonFileStream, rootObject, _jsonOptions, cancellationToken).ConfigureAwait(false); + } return path; } internal string GetDataFilePath(string imdbId) { - if (string.IsNullOrEmpty(imdbId)) - { - throw new ArgumentNullException(nameof(imdbId)); - } + ArgumentException.ThrowIfNullOrEmpty(imdbId); var dataPath = Path.Combine(_configurationManager.ApplicationPaths.CachePath, "omdb"); @@ -379,10 +387,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb internal string GetSeasonFilePath(string imdbId, int seasonId) { - if (string.IsNullOrEmpty(imdbId)) - { - throw new ArgumentNullException(nameof(imdbId)); - } + ArgumentException.ThrowIfNullOrEmpty(imdbId); var dataPath = Path.Combine(_configurationManager.ApplicationPaths.CachePath, "omdb"); @@ -420,7 +425,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb var person = new PersonInfo { Name = result.Director, - Type = PersonType.Director + Type = PersonKind.Director }; itemResult.AddPerson(person); @@ -431,7 +436,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb var person = new PersonInfo { Name = result.Writer, - Type = PersonType.Writer + Type = PersonKind.Writer }; itemResult.AddPerson(person); @@ -450,7 +455,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb var person = new PersonInfo { Name = actor, - Type = PersonType.Actor + Type = PersonKind.Actor }; itemResult.AddPerson(person); @@ -540,13 +545,13 @@ namespace MediaBrowser.Providers.Plugins.Omdb public float? GetRottenTomatoScore() { - if (Ratings != null) + if (Ratings is not null) { var rating = Ratings.FirstOrDefault(i => string.Equals(i.Source, "Rotten Tomatoes", StringComparison.OrdinalIgnoreCase)); - if (rating?.Value != null) + if (rating?.Value is not null) { var value = rating.Value.TrimEnd('%'); - if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var score)) + if (float.TryParse(value, CultureInfo.InvariantCulture, out var score)) { return score; } diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs index cb422ef3d..0bfab9824 100644 --- a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/PluginConfiguration.cs @@ -1,13 +1,17 @@ -#pragma warning disable CS1591 - -using MediaBrowser.Model.Plugins; +using MediaBrowser.Model.Plugins; namespace MediaBrowser.Providers.Plugins.StudioImages.Configuration { + /// <summary> + /// Plugin configuration class for the studio image provider. + /// </summary> public class PluginConfiguration : BasePluginConfiguration { private string _repository = Plugin.DefaultServer; + /// <summary> + /// Gets or sets the studio image repository URL. + /// </summary> public string RepositoryUrl { get diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html index 63750dbcd..85ebe443f 100644 --- a/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/StudioImages/Configuration/config.html @@ -4,9 +4,10 @@ <title>Studio Images</title> </head> <body> - <div data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox"> + <div id="configPage" data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox"> <div data-role="content"> <div class="content-primary"> + <h1>Studio Images</h1> <form class="configForm"> <div class="inputContainer"> <input is="emby-input" type="text" id="repository" label="Repository" /> diff --git a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs index 5e653d039..78150153a 100644 --- a/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs +++ b/MediaBrowser.Providers/Plugins/StudioImages/Plugin.cs @@ -1,5 +1,4 @@ #nullable disable -#pragma warning disable CS1591 using System; using System.Collections.Generic; @@ -11,27 +10,47 @@ using MediaBrowser.Providers.Plugins.StudioImages.Configuration; namespace MediaBrowser.Providers.Plugins.StudioImages { + /// <summary> + /// Artwork Plugin class. + /// </summary> public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages { + /// <summary> + /// Artwork repository URL. + /// </summary> public const string DefaultServer = "https://raw.github.com/jellyfin/emby-artwork/master/studios"; + /// <summary> + /// Initializes a new instance of the <see cref="Plugin"/> class. + /// </summary> + /// <param name="applicationPaths">application paths.</param> + /// <param name="xmlSerializer">xml serializer.</param> public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer) : base(applicationPaths, xmlSerializer) { Instance = this; } + /// <summary> + /// Gets the instance of Artwork plugin. + /// </summary> public static Plugin Instance { get; private set; } + /// <inheritdoc/> public override Guid Id => new Guid("872a7849-1171-458d-a6fb-3de3d442ad30"); + /// <inheritdoc/> public override string Name => "Studio Images"; + /// <inheritdoc/> public override string Description => "Get artwork for studios from any Jellyfin-compatible repository."; // TODO remove when plugin removed from server. + + /// <inheritdoc/> public override string ConfigurationFileName => "Jellyfin.Plugin.StudioImages.xml"; + /// <inheritdoc/> public IEnumerable<PluginPageInfo> GetPages() { yield return new PluginPageInfo diff --git a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs index ef822a22a..ae244da19 100644 --- a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs @@ -1,7 +1,5 @@ #nullable disable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -21,12 +19,21 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.StudioImages { + /// <summary> + /// Studio image provider. + /// </summary> public class StudiosImageProvider : IRemoteImageProvider { private readonly IServerConfigurationManager _config; private readonly IHttpClientFactory _httpClientFactory; private readonly IFileSystem _fileSystem; + /// <summary> + /// Initializes a new instance of the <see cref="StudiosImageProvider"/> class. + /// </summary> + /// <param name="config">The <see cref="IServerConfigurationManager"/>.</param> + /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param> + /// <param name="fileSystem">The <see cref="IFileSystem"/>.</param> public StudiosImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IFileSystem fileSystem) { _config = config; @@ -34,21 +41,25 @@ namespace MediaBrowser.Providers.Plugins.StudioImages _fileSystem = fileSystem; } + /// <inheritdoc /> public string Name => "Artwork Repository"; + /// <inheritdoc /> public bool Supports(BaseItem item) { return item is Studio; } + /// <inheritdoc /> public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { - return new List<ImageType> + return new ImageType[] { ImageType.Thumb }; } + /// <inheritdoc /> public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var thumbsPath = Path.Combine(_config.ApplicationPaths.CachePath, "imagesbyname", "remotestudiothumbs.txt"); @@ -59,7 +70,7 @@ namespace MediaBrowser.Providers.Plugins.StudioImages var imageInfo = GetImage(item, thumbsPath, ImageType.Thumb, "thumb"); - if (imageInfo == null) + if (imageInfo is null) { return Enumerable.Empty<RemoteImageInfo>(); } @@ -103,6 +114,7 @@ namespace MediaBrowser.Providers.Plugins.StudioImages return EnsureList(url, file, _fileSystem, cancellationToken); } + /// <inheritdoc /> public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) { var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); @@ -110,13 +122,13 @@ namespace MediaBrowser.Providers.Plugins.StudioImages } /// <summary> - /// Ensures the list. + /// Ensures the existence of a file listing. /// </summary> /// <param name="url">The URL.</param> /// <param name="file">The file.</param> /// <param name="fileSystem">The file system.</param> /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> + /// <returns>A Task to ensure existence of a file listing.</returns> public async Task<string> EnsureList(string url, string file, IFileSystem fileSystem, CancellationToken cancellationToken) { var fileInfo = fileSystem.GetFileInfo(file); @@ -126,14 +138,26 @@ namespace MediaBrowser.Providers.Plugins.StudioImages var httpClient = _httpClientFactory.CreateClient(NamedClient.Default); Directory.CreateDirectory(Path.GetDirectoryName(file)); - await using var response = await httpClient.GetStreamAsync(url, cancellationToken).ConfigureAwait(false); - await using var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); - await response.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); + var response = await httpClient.GetStreamAsync(url, cancellationToken).ConfigureAwait(false); + await using (response.ConfigureAwait(false)) + { + var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); + await using (fileStream.ConfigureAwait(false)) + { + await response.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false); + } + } } return file; } + /// <summary> + /// Get matching image for an item. + /// </summary> + /// <param name="item">The <see cref="BaseItem"/>.</param> + /// <param name="images">The enumerable of image strings.</param> + /// <returns>The matching image string.</returns> public string FindMatch(BaseItem item, IEnumerable<string> images) { var name = GetComparableName(item.Name); @@ -151,6 +175,11 @@ namespace MediaBrowser.Providers.Plugins.StudioImages .Replace("/", string.Empty, StringComparison.Ordinal); } + /// <summary> + /// Get available image strings for a file. + /// </summary> + /// <param name="file">The file.</param> + /// <returns>All images strings of a file.</returns> public IEnumerable<string> GetAvailableImages(string file) { using var fileStream = File.OpenRead(file); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs b/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs index 0bab7c3ca..450ee2a33 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Api/TmdbController.cs @@ -8,10 +8,10 @@ using TMDbLib.Objects.General; namespace MediaBrowser.Providers.Plugins.Tmdb.Api { /// <summary> - /// The TMDb api controller. + /// The TMDb API controller. /// </summary> [ApiController] - [Authorize(Policy = "DefaultAuthorization")] + [Authorize] [Route("[controller]")] [Produces(MediaTypeNames.Application.Json)] public class TmdbController : ControllerBase diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs index 3217ac2f1..0e768bb83 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetExternalId.cs @@ -7,7 +7,7 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets { /// <summary> - /// External ID for a TMDB box set. + /// External id for a TMDb box set. /// </summary> public class TmdbBoxSetExternalId : IExternalId { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs index 29a557c31..a4c6cb47d 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs @@ -1,7 +1,3 @@ -#nullable disable - -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -18,35 +14,48 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets { + /// <summary> + /// BoxSet image provider powered by TMDb. + /// </summary> public class TmdbBoxSetImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// <summary> + /// Initializes a new instance of the <see cref="TmdbBoxSetImageProvider"/> class. + /// </summary> + /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param> + /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param> public TmdbBoxSetImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } + /// <inheritdoc /> public string Name => TmdbUtils.ProviderName; + /// <inheritdoc /> public int Order => 0; + /// <inheritdoc /> public bool Supports(BaseItem item) { return item is BoxSet; } + /// <inheritdoc /> public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { - return new List<ImageType> + return new ImageType[] { ImageType.Primary, ImageType.Backdrop }; } + /// <inheritdoc /> public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var tmdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); @@ -61,7 +70,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets // TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, null, null, cancellationToken).ConfigureAwait(false); - if (collection?.Images == null) + if (collection?.Images is null) { return Enumerable.Empty<RemoteImageInfo>(); } @@ -70,12 +79,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets var backdrops = collection.Images.Backdrops; var remoteImages = new List<RemoteImageInfo>(posters.Count + backdrops.Count); - _tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language, remoteImages); - _tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language, remoteImages); + remoteImages.AddRange(_tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language)); + remoteImages.AddRange(_tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language)); return remoteImages; } + /// <inheritdoc /> public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs index 62bc9c65f..c2018d820 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs @@ -1,7 +1,3 @@ -#nullable disable - -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -18,12 +14,21 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets { + /// <summary> + /// BoxSet provider powered by TMDb. + /// </summary> public class TmdbBoxSetProvider : IRemoteMetadataProvider<BoxSet, BoxSetInfo> { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; private readonly ILibraryManager _libraryManager; + /// <summary> + /// Initializes a new instance of the <see cref="TmdbBoxSetProvider"/> class. + /// </summary> + /// <param name="libraryManager">The <see cref="ILibraryManager"/>.</param> + /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param> + /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param> public TmdbBoxSetProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager, ILibraryManager libraryManager) { _httpClientFactory = httpClientFactory; @@ -31,8 +36,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets _libraryManager = libraryManager; } + /// <inheritdoc /> public string Name => TmdbUtils.ProviderName; + /// <inheritdoc /> public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(BoxSetInfo searchInfo, CancellationToken cancellationToken) { var tmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); @@ -42,7 +49,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets { var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false); - if (collection == null) + if (collection is null) { return Enumerable.Empty<RemoteSearchResult>(); } @@ -53,7 +60,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets SearchProviderName = Name }; - if (collection.Images != null) + if (collection.Images is not null) { result.ImageUrl = _tmdbClientManager.GetPosterUrl(collection.PosterPath); } @@ -65,7 +72,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets var collectionSearchResults = await _tmdbClientManager.SearchCollectionAsync(searchInfo.Name, language, cancellationToken).ConfigureAwait(false); - var collections = new List<RemoteSearchResult>(); + var collections = new RemoteSearchResult[collectionSearchResults.Count]; for (var i = 0; i < collectionSearchResults.Count; i++) { var collection = new RemoteSearchResult @@ -75,12 +82,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets }; collection.SetProviderId(MetadataProvider.Tmdb, collectionSearchResults[i].Id.ToString(CultureInfo.InvariantCulture)); - collections.Add(collection); + collections[i] = collection; } return collections; } + /// <inheritdoc /> public async Task<MetadataResult<BoxSet>> GetMetadata(BoxSetInfo info, CancellationToken cancellationToken) { var tmdbId = Convert.ToInt32(info.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); @@ -94,7 +102,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets var cleanedName = TmdbUtils.CleanName(parsedName.Name); var searchResults = await _tmdbClientManager.SearchCollectionAsync(cleanedName, language, cancellationToken).ConfigureAwait(false); - if (searchResults != null && searchResults.Count > 0) + if (searchResults is not null && searchResults.Count > 0) { tmdbId = searchResults[0].Id; } @@ -106,7 +114,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets { var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false); - if (collection != null) + if (collection is not null) { var item = new BoxSet { @@ -124,6 +132,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets return result; } + /// <inheritdoc /> public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html index 48ec0535c..cd21516f9 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html +++ b/MediaBrowser.Providers/Plugins/Tmdb/Configuration/config.html @@ -4,9 +4,10 @@ <title>TMDb</title> </head> <body> - <div data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox"> + <div id="configPage" data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox"> <div data-role="content"> <div class="content-primary"> + <h1>TMDb</h1> <form class="configForm"> <label class="checkboxContainer"> <input is="emby-checkbox" type="checkbox" id="includeAdult" /> diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs index 31310a8d4..38d2c5c69 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieExternalId.cs @@ -7,7 +7,7 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.Movies { /// <summary> - /// External ID for a TMBD movie. + /// External id for a TMDb movie. /// </summary> public class TmdbMovieExternalId : IExternalId { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs index 16f0089f8..bfec48e7c 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs @@ -1,7 +1,3 @@ -#nullable disable - -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -19,29 +15,41 @@ using TMDbLib.Objects.Find; namespace MediaBrowser.Providers.Plugins.Tmdb.Movies { + /// <summary> + /// Movie image provider powered by TMDb. + /// </summary> public class TmdbMovieImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// <summary> + /// Initializes a new instance of the <see cref="TmdbMovieImageProvider"/> class. + /// </summary> + /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param> + /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param> public TmdbMovieImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } + /// <inheritdoc /> public int Order => 0; + /// <inheritdoc /> public string Name => TmdbUtils.ProviderName; + /// <inheritdoc /> public bool Supports(BaseItem item) { return item is Movie || item is Trailer; } + /// <inheritdoc /> public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { - return new List<ImageType> + return new ImageType[] { ImageType.Primary, ImageType.Backdrop, @@ -49,6 +57,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies }; } + /// <inheritdoc /> public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var language = item.GetPreferredMetadataLanguage(); @@ -63,7 +72,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies } var movieResult = await _tmdbClientManager.FindByExternalIdAsync(movieImdbId, FindExternalSource.Imdb, language, cancellationToken).ConfigureAwait(false); - if (movieResult?.MovieResults != null && movieResult.MovieResults.Count > 0) + if (movieResult?.MovieResults is not null && movieResult.MovieResults.Count > 0) { movieTmdbId = movieResult.MovieResults[0].Id; } @@ -79,7 +88,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies .GetMovieAsync(movieTmdbId, null, null, cancellationToken) .ConfigureAwait(false); - if (movie?.Images == null) + if (movie?.Images is null) { return Enumerable.Empty<RemoteImageInfo>(); } @@ -89,13 +98,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies var logos = movie.Images.Logos; var remoteImages = new List<RemoteImageInfo>(posters.Count + backdrops.Count + logos.Count); - _tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language, remoteImages); - _tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language, remoteImages); - _tmdbClientManager.ConvertLogosToRemoteImageInfo(logos, language, remoteImages); + remoteImages.AddRange(_tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language)); + remoteImages.AddRange(_tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language)); + remoteImages.AddRange(_tmdbClientManager.ConvertLogosToRemoteImageInfo(logos, language)); return remoteImages; } + /// <inheritdoc /> public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index f14f31858..2f62e117e 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -1,7 +1,3 @@ -#nullable disable - -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -9,6 +5,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; @@ -23,7 +20,7 @@ using TMDbLib.Objects.Search; namespace MediaBrowser.Providers.Plugins.Tmdb.Movies { /// <summary> - /// Class MovieDbProvider. + /// Movie provider powered by TMDb. /// </summary> public class TmdbMovieProvider : IRemoteMetadataProvider<Movie, MovieInfo>, IHasOrder { @@ -31,6 +28,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies private readonly ILibraryManager _libraryManager; private readonly TmdbClientManager _tmdbClientManager; + /// <summary> + /// Initializes a new instance of the <see cref="TmdbMovieProvider"/> class. + /// </summary> + /// <param name="libraryManager">The <see cref="ILibraryManager"/>.</param> + /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param> + /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param> public TmdbMovieProvider( ILibraryManager libraryManager, TmdbClientManager tmdbClientManager, @@ -41,11 +44,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies _httpClientFactory = httpClientFactory; } - public string Name => TmdbUtils.ProviderName; - /// <inheritdoc /> public int Order => 1; + /// <inheritdoc /> + public string Name => TmdbUtils.ProviderName; + + /// <inheritdoc /> public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken) { if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var id)) @@ -58,32 +63,35 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies cancellationToken) .ConfigureAwait(false); - var remoteResult = new RemoteSearchResult + if (movie is not null) { - Name = movie.Title ?? movie.OriginalTitle, - SearchProviderName = Name, - ImageUrl = _tmdbClientManager.GetPosterUrl(movie.PosterPath), - Overview = movie.Overview - }; + var remoteResult = new RemoteSearchResult + { + Name = movie.Title ?? movie.OriginalTitle, + SearchProviderName = Name, + ImageUrl = _tmdbClientManager.GetPosterUrl(movie.PosterPath), + Overview = movie.Overview + }; - if (movie.ReleaseDate != null) - { - var releaseDate = movie.ReleaseDate.Value.ToUniversalTime(); - remoteResult.PremiereDate = releaseDate; - remoteResult.ProductionYear = releaseDate.Year; - } + if (movie.ReleaseDate is not null) + { + var releaseDate = movie.ReleaseDate.Value.ToUniversalTime(); + remoteResult.PremiereDate = releaseDate; + remoteResult.ProductionYear = releaseDate.Year; + } - remoteResult.SetProviderId(MetadataProvider.Tmdb, movie.Id.ToString(CultureInfo.InvariantCulture)); + remoteResult.SetProviderId(MetadataProvider.Tmdb, movie.Id.ToString(CultureInfo.InvariantCulture)); - if (!string.IsNullOrWhiteSpace(movie.ImdbId)) - { - remoteResult.SetProviderId(MetadataProvider.Imdb, movie.ImdbId); - } + if (!string.IsNullOrWhiteSpace(movie.ImdbId)) + { + remoteResult.SetProviderId(MetadataProvider.Imdb, movie.ImdbId); + } - return new[] { remoteResult }; + return new[] { remoteResult }; + } } - IReadOnlyList<SearchMovie> movieResults; + IReadOnlyList<SearchMovie>? movieResults = null; if (searchInfo.TryGetProviderId(MetadataProvider.Imdb, out id)) { var result = await _tmdbClientManager.FindByExternalIdAsync( @@ -91,18 +99,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies FindExternalSource.Imdb, TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage), cancellationToken).ConfigureAwait(false); - movieResults = result.MovieResults; + movieResults = result?.MovieResults; } - else if (searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out id)) + + if (movieResults is null && searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out id)) { var result = await _tmdbClientManager.FindByExternalIdAsync( id, FindExternalSource.TvDb, TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage), cancellationToken).ConfigureAwait(false); - movieResults = result.MovieResults; + movieResults = result?.MovieResults; } - else + + if (movieResults is null) { movieResults = await _tmdbClientManager .SearchMovieAsync(searchInfo.Name, searchInfo.Year ?? 0, searchInfo.MetadataLanguage, cancellationToken) @@ -133,6 +143,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies return remoteSearchResults; } + /// <inheritdoc /> public async Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken) { var tmdbId = info.GetProviderId(MetadataProvider.Tmdb); @@ -144,7 +155,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies // Caller provides the filename with extension stripped and NOT the parsed filename var parsedName = _libraryManager.ParseName(info.Name); var cleanedName = TmdbUtils.CleanName(parsedName.Name); - var searchResults = await _tmdbClientManager.SearchMovieAsync(cleanedName, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); + var searchResults = await _tmdbClientManager.SearchMovieAsync(cleanedName, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); if (searchResults.Count > 0) { @@ -170,7 +181,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies .GetMovieAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) .ConfigureAwait(false); - if (movieResult == null) + if (movieResult is null) { return new MetadataResult<Movie>(); } @@ -192,7 +203,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies movie.SetProviderId(MetadataProvider.Tmdb, tmdbId); movie.SetProviderId(MetadataProvider.Imdb, movieResult.ImdbId); - if (movieResult.BelongsToCollection != null) + if (movieResult.BelongsToCollection is not null) { movie.SetProviderId(MetadataProvider.TmdbCollection, movieResult.BelongsToCollection.Id.ToString(CultureInfo.InvariantCulture)); movie.CollectionName = movieResult.BelongsToCollection.Name; @@ -200,18 +211,18 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies movie.CommunityRating = Convert.ToSingle(movieResult.VoteAverage); - if (movieResult.Releases?.Countries != null) + if (movieResult.Releases?.Countries is not null) { var releases = movieResult.Releases.Countries.Where(i => !string.IsNullOrWhiteSpace(i.Certification)).ToList(); var ourRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, info.MetadataCountryCode, StringComparison.OrdinalIgnoreCase)); var usRelease = releases.FirstOrDefault(c => string.Equals(c.Iso_3166_1, "US", StringComparison.OrdinalIgnoreCase)); - if (ourRelease != null) + if (ourRelease is not null) { movie.OfficialRating = TmdbUtils.BuildParentalRating(ourRelease.Iso_3166_1, ourRelease.Certification); } - else if (usRelease != null) + else if (usRelease is not null) { movie.OfficialRating = usRelease.Certification; } @@ -220,7 +231,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies movie.PremiereDate = movieResult.ReleaseDate; movie.ProductionYear = movieResult.ReleaseDate?.Year; - if (movieResult.ProductionCompanies != null) + if (movieResult.ProductionCompanies is not null) { movie.SetStudios(movieResult.ProductionCompanies.Select(c => c.Name)); } @@ -232,7 +243,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies movie.AddGenre(genre); } - if (movieResult.Keywords?.Keywords != null) + if (movieResult.Keywords?.Keywords is not null) { for (var i = 0; i < movieResult.Keywords.Keywords.Count; i++) { @@ -240,7 +251,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies } } - if (movieResult.Credits?.Cast != null) + if (movieResult.Credits?.Cast is not null) { foreach (var actor in movieResult.Credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) { @@ -248,7 +259,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies { Name = actor.Name.Trim(), Role = actor.Character, - Type = PersonType.Actor, + Type = PersonKind.Actor, SortOrder = actor.Order }; @@ -266,22 +277,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies } } - if (movieResult.Credits?.Crew != null) + if (movieResult.Credits?.Crew is not null) { - var keepTypes = new[] - { - PersonType.Director, - PersonType.Writer, - PersonType.Producer - }; - foreach (var person in movieResult.Credits.Crew) { // Normalize this var type = TmdbUtils.MapCrewToPersonType(person); - if (!keepTypes.Contains(type, StringComparison.OrdinalIgnoreCase) && - !keepTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + if (!TmdbUtils.WantedCrewKinds.Contains(type) + && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { continue; } @@ -307,7 +311,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies } } - if (movieResult.Videos?.Results != null) + if (movieResult.Videos?.Results is not null) { var trailers = new List<MediaUrl>(); for (var i = 0; i < movieResult.Videos.Results.Count; i++) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs index 9804d60bd..027399aec 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonExternalId.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.People { /// <summary> - /// External ID for a TMDB person. + /// External id for a TMDb person. /// </summary> public class TmdbPersonExternalId : IExternalId { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs index 7ce4cfe67..9e5404b32 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -14,11 +12,19 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.People { + /// <summary> + /// Person image provider powered by TMDb. + /// </summary> public class TmdbPersonImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// <summary> + /// Initializes a new instance of the <see cref="TmdbPersonImageProvider"/> class. + /// </summary> + /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param> + /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param> public TmdbPersonImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; @@ -31,19 +37,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People /// <inheritdoc /> public int Order => 0; + /// <inheritdoc /> public bool Supports(BaseItem item) { return item is Person; } + /// <inheritdoc /> public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { - return new List<ImageType> - { - ImageType.Primary - }; + yield return ImageType.Primary; } + /// <inheritdoc /> public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var person = (Person)item; @@ -55,19 +61,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People var language = item.GetPreferredMetadataLanguage(); var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), language, cancellationToken).ConfigureAwait(false); - if (personResult?.Images?.Profiles == null) + if (personResult?.Images?.Profiles is null) { return Enumerable.Empty<RemoteImageInfo>(); } - var profiles = personResult.Images.Profiles; - var remoteImages = new List<RemoteImageInfo>(profiles.Count); - - _tmdbClientManager.ConvertProfilesToRemoteImageInfo(profiles, language, remoteImages); - - return remoteImages; + return _tmdbClientManager.ConvertProfilesToRemoteImageInfo(personResult.Images.Profiles, language); } + /// <inheritdoc /> public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs index 8790e3759..5c6e71fd8 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs @@ -1,7 +1,3 @@ -#nullable disable - -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -16,26 +12,36 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.People { + /// <summary> + /// Person image provider powered by TMDb. + /// </summary> public class TmdbPersonProvider : IRemoteMetadataProvider<Person, PersonLookupInfo> { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// <summary> + /// Initializes a new instance of the <see cref="TmdbPersonProvider"/> class. + /// </summary> + /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param> + /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param> public TmdbPersonProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } + /// <inheritdoc /> public string Name => TmdbUtils.ProviderName; + /// <inheritdoc /> public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken) { if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId)) { var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); - if (personResult != null) + if (personResult is not null) { var result = new RemoteSearchResult { @@ -44,7 +50,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People Overview = personResult.Biography }; - if (personResult.Images?.Profiles != null && personResult.Images.Profiles.Count > 0) + if (personResult.Images?.Profiles is not null && personResult.Images.Profiles.Count > 0) { result.ImageUrl = _tmdbClientManager.GetProfileUrl(personResult.Images.Profiles[0].FilePath); } @@ -61,7 +67,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People var personSearchResult = await _tmdbClientManager.SearchPersonAsync(searchInfo.Name, cancellationToken).ConfigureAwait(false); - var remoteSearchResults = new List<RemoteSearchResult>(); + var remoteSearchResults = new RemoteSearchResult[personSearchResult.Count]; for (var i = 0; i < personSearchResult.Count; i++) { var person = personSearchResult[i]; @@ -73,12 +79,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People }; remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture)); - remoteSearchResults.Add(remoteSearchResult); + remoteSearchResults[i] = remoteSearchResult; } return remoteSearchResults; } + /// <inheritdoc /> public async Task<MetadataResult<Person>> GetMetadata(PersonLookupInfo info, CancellationToken cancellationToken) { var personTmdbId = Convert.ToInt32(info.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); @@ -98,6 +105,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People if (personTmdbId > 0) { var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, info.MetadataLanguage, cancellationToken).ConfigureAwait(false); + if (person is null) + { + return result; + } result.HasMetadata = true; @@ -131,6 +142,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People return result; } + /// <inheritdoc /> public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs index 5eec776b5..d1fec7cb1 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs @@ -1,7 +1,3 @@ -#nullable disable - -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -17,30 +13,44 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { + /// <summary> + /// TV episode image provider powered by TheMovieDb. + /// </summary> public class TmdbEpisodeImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// <summary> + /// Initializes a new instance of the <see cref="TmdbEpisodeImageProvider"/> class. + /// </summary> + /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param> + /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param> public TmdbEpisodeImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } - // After TheTvDb + /// <inheritdoc /> public int Order => 1; + /// <inheritdoc /> public string Name => TmdbUtils.ProviderName; + /// <inheritdoc /> + public bool Supports(BaseItem item) + { + return item is Controller.Entities.TV.Episode; + } + + /// <inheritdoc /> public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { - return new List<ImageType> - { - ImageType.Primary - }; + yield return ImageType.Primary; } + /// <inheritdoc /> public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var episode = (Controller.Entities.TV.Episode)item; @@ -48,7 +58,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var seriesTmdbId = Convert.ToInt32(series?.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); - if (seriesTmdbId <= 0) + if (series is null || seriesTmdbId <= 0) { return Enumerable.Empty<RemoteImageInfo>(); } @@ -69,26 +79,18 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV .ConfigureAwait(false); var stills = episodeResult?.Images?.Stills; - if (stills == null) + if (stills is null) { return Enumerable.Empty<RemoteImageInfo>(); } - var remoteImages = new List<RemoteImageInfo>(stills.Count); - - _tmdbClientManager.ConvertStillsToRemoteImageInfo(stills, language, remoteImages); - - return remoteImages; + return _tmdbClientManager.ConvertStillsToRemoteImageInfo(stills, language); } + /// <inheritdoc /> public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } - - public bool Supports(BaseItem item) - { - return item is Controller.Entities.TV.Episode; - } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index f50f15877..f18575aa9 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs @@ -1,7 +1,3 @@ -#nullable disable - -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -9,6 +5,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; @@ -19,22 +16,32 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { + /// <summary> + /// TV episode provider powered by TheMovieDb. + /// </summary> public class TmdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// <summary> + /// Initializes a new instance of the <see cref="TmdbEpisodeProvider"/> class. + /// </summary> + /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param> + /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param> public TmdbEpisodeProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } - // After TheTvDb + /// <inheritdoc /> public int Order => 1; + /// <inheritdoc /> public string Name => TmdbUtils.ProviderName; + /// <inheritdoc /> public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) { // The search query must either provide an episode number or date @@ -68,6 +75,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV }; } + /// <inheritdoc /> public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken) { var metadataResult = new MetadataResult<Episode>(); @@ -78,7 +86,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return metadataResult; } - info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string tmdbId); + info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string? tmdbId); var seriesTmdbId = Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture); if (seriesTmdbId <= 0) @@ -98,7 +106,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) .ConfigureAwait(false); - if (episodeResult == null) + if (episodeResult is null) { return metadataResult; } @@ -140,7 +148,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV item.SetProviderId(MetadataProvider.TvRage, externalIds.TvrageId); } - if (episodeResult.Videos?.Results != null) + if (episodeResult.Videos?.Results is not null) { foreach (var video in episodeResult.Videos.Results) { @@ -153,7 +161,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var credits = episodeResult.Credits; - if (credits?.Cast != null) + if (credits?.Cast is not null) { foreach (var actor in credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) { @@ -161,13 +169,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV { Name = actor.Name.Trim(), Role = actor.Character, - Type = PersonType.Actor, + Type = PersonKind.Actor, SortOrder = actor.Order }); } } - if (credits?.GuestStars != null) + if (credits?.GuestStars is not null) { foreach (var guest in credits.GuestStars.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) { @@ -175,21 +183,21 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV { Name = guest.Name.Trim(), Role = guest.Character, - Type = PersonType.GuestStar, + Type = PersonKind.GuestStar, SortOrder = guest.Order }); } } // and the rest from crew - if (credits?.Crew != null) + if (credits?.Crew is not null) { foreach (var person in credits.Crew) { // Normalize this var type = TmdbUtils.MapCrewToPersonType(person); - if (!TmdbUtils.WantedCrewTypes.Contains(type, StringComparison.OrdinalIgnoreCase) + if (!TmdbUtils.WantedCrewKinds.Contains(type) && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { continue; @@ -209,6 +217,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return metadataResult; } + /// <inheritdoc /> public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs index 4446fa966..a743601ed 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -16,26 +14,44 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { + /// <summary> + /// TV season image provider powered by TheMovieDb. + /// </summary> public class TmdbSeasonImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// <summary> + /// Initializes a new instance of the <see cref="TmdbSeasonImageProvider"/> class. + /// </summary> + /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param> + /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param> public TmdbSeasonImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } + /// <inheritdoc/> public int Order => 1; + /// <inheritdoc/> public string Name => TmdbUtils.ProviderName; - public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) + /// <inheritdoc /> + public bool Supports(BaseItem item) { - return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); + return item is Season; } + /// <inheritdoc /> + public IEnumerable<ImageType> GetSupportedImages(BaseItem item) + { + yield return ImageType.Primary; + } + + /// <inheritdoc /> public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var season = (Season)item; @@ -43,7 +59,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var seriesTmdbId = Convert.ToInt32(series?.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); - if (seriesTmdbId <= 0 || season?.IndexNumber == null) + if (seriesTmdbId <= 0 || season?.IndexNumber is null) { return Enumerable.Empty<RemoteImageInfo>(); } @@ -56,29 +72,18 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV .ConfigureAwait(false); var posters = seasonResult?.Images?.Posters; - if (posters == null) + if (posters is null) { return Enumerable.Empty<RemoteImageInfo>(); } - var remoteImages = new List<RemoteImageInfo>(posters.Count); - - _tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language, remoteImages); - - return remoteImages; - } - - public IEnumerable<ImageType> GetSupportedImages(BaseItem item) - { - return new List<ImageType> - { - ImageType.Primary - }; + return _tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language); } - public bool Supports(BaseItem item) + /// <inheritdoc /> + public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) { - return item is Season; + return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); } } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs index 64ed3f408..10efb68b9 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -7,6 +5,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; @@ -17,19 +16,29 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { + /// <summary> + /// TV season provider powered by TheMovieDb. + /// </summary> public class TmdbSeasonProvider : IRemoteMetadataProvider<Season, SeasonInfo> { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// <summary> + /// Initializes a new instance of the <see cref="TmdbSeasonProvider"/> class. + /// </summary> + /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param> + /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param> public TmdbSeasonProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } + /// <inheritdoc /> public string Name => TmdbUtils.ProviderName; + /// <inheritdoc /> public async Task<MetadataResult<Season>> GetMetadata(SeasonInfo info, CancellationToken cancellationToken) { var result = new MetadataResult<Season>(); @@ -47,7 +56,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV .GetSeasonAsync(Convert.ToInt32(seriesTmdbId, CultureInfo.InvariantCulture), seasonNumber.Value, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) .ConfigureAwait(false); - if (seasonResult == null) + if (seasonResult is null) { return result; } @@ -71,7 +80,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV // TODO why was this disabled? var credits = seasonResult.Credits; - if (credits?.Cast != null) + if (credits?.Cast is not null) { var cast = credits.Cast.OrderBy(c => c.Order).Take(Plugin.Instance.Configuration.MaxCastMembers).ToList(); for (var i = 0; i < cast.Count; i++) @@ -80,20 +89,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV { Name = cast[i].Name.Trim(), Role = cast[i].Character, - Type = PersonType.Actor, + Type = PersonKind.Actor, SortOrder = cast[i].Order }); } } - if (credits?.Crew != null) + if (credits?.Crew is not null) { foreach (var person in credits.Crew) { // Normalize this var type = TmdbUtils.MapCrewToPersonType(person); - if (!TmdbUtils.WantedCrewTypes.Contains(type, StringComparison.OrdinalIgnoreCase) + if (!TmdbUtils.WantedCrewKinds.Contains(type) && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { continue; @@ -114,11 +123,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return result; } + /// <inheritdoc /> public Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeasonInfo searchInfo, CancellationToken cancellationToken) { return Task.FromResult(Enumerable.Empty<RemoteSearchResult>()); } + /// <inheritdoc /> public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs index 8a2be80cd..df04cb2e7 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesExternalId.cs @@ -6,7 +6,7 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { /// <summary> - /// External ID for a TMDB series. + /// External id for a TMDb series. /// </summary> public class TmdbSeriesExternalId : IExternalId { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs index 130d6ce44..192fb052d 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -16,30 +14,41 @@ using MediaBrowser.Model.Providers; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { + /// <summary> + /// TV series image provider powered by TheMovieDb. + /// </summary> public class TmdbSeriesImageProvider : IRemoteImageProvider, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; private readonly TmdbClientManager _tmdbClientManager; + /// <summary> + /// Initializes a new instance of the <see cref="TmdbSeriesImageProvider"/> class. + /// </summary> + /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param> + /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param> public TmdbSeriesImageProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager) { _httpClientFactory = httpClientFactory; _tmdbClientManager = tmdbClientManager; } + /// <inheritdoc /> public string Name => TmdbUtils.ProviderName; - // After tvdb and fanart + /// <inheritdoc /> public int Order => 2; + /// <inheritdoc /> public bool Supports(BaseItem item) { return item is Series; } + /// <inheritdoc /> public IEnumerable<ImageType> GetSupportedImages(BaseItem item) { - return new List<ImageType> + return new ImageType[] { ImageType.Primary, ImageType.Backdrop, @@ -47,6 +56,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV }; } + /// <inheritdoc /> public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var tmdbId = item.GetProviderId(MetadataProvider.Tmdb); @@ -63,7 +73,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), null, null, cancellationToken) .ConfigureAwait(false); - if (series?.Images == null) + if (series?.Images is null) { return Enumerable.Empty<RemoteImageInfo>(); } @@ -73,13 +83,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var logos = series.Images.Logos; var remoteImages = new List<RemoteImageInfo>(posters.Count + backdrops.Count + logos.Count); - _tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language, remoteImages); - _tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language, remoteImages); - _tmdbClientManager.ConvertLogosToRemoteImageInfo(logos, language, remoteImages); + remoteImages.AddRange(_tmdbClientManager.ConvertPostersToRemoteImageInfo(posters, language)); + remoteImages.AddRange(_tmdbClientManager.ConvertBackdropsToRemoteImageInfo(backdrops, language)); + remoteImages.AddRange(_tmdbClientManager.ConvertLogosToRemoteImageInfo(logos, language)); return remoteImages; } + /// <inheritdoc /> public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index 4d26052fa..8dc2d6938 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -1,7 +1,3 @@ -#nullable disable - -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Globalization; @@ -9,6 +5,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; @@ -23,12 +20,21 @@ using TMDbLib.Objects.TvShows; namespace MediaBrowser.Providers.Plugins.Tmdb.TV { + /// <summary> + /// TV series provider powered by TheMovieDb. + /// </summary> public class TmdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder { private readonly IHttpClientFactory _httpClientFactory; private readonly ILibraryManager _libraryManager; private readonly TmdbClientManager _tmdbClientManager; + /// <summary> + /// Initializes a new instance of the <see cref="TmdbSeriesProvider"/> class. + /// </summary> + /// <param name="libraryManager">The <see cref="ILibraryManager"/>.</param> + /// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param> + /// <param name="tmdbClientManager">The <see cref="TmdbClientManager"/>.</param> public TmdbSeriesProvider( ILibraryManager libraryManager, IHttpClientFactory httpClientFactory, @@ -39,11 +45,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV _tmdbClientManager = tmdbClientManager; } + /// <inheritdoc /> public string Name => TmdbUtils.ProviderName; - // After TheTVDB + /// <inheritdoc /> public int Order => 1; + /// <inheritdoc /> public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken) { if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var tmdbId)) @@ -52,7 +60,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, searchInfo.MetadataLanguage, cancellationToken) .ConfigureAwait(false); - if (series != null) + if (series is not null) { var remoteResult = MapTvShowToRemoteSearchResult(series); @@ -67,7 +75,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV .ConfigureAwait(false); var tvResults = findResult?.TvResults; - if (tvResults != null) + if (tvResults is not null) { var imdbIdResults = new RemoteSearchResult[tvResults.Count]; for (var i = 0; i < tvResults.Count; i++) @@ -88,7 +96,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV .ConfigureAwait(false); var tvResults = findResult?.TvResults; - if (tvResults != null) + if (tvResults is not null) { var tvIdResults = new RemoteSearchResult[tvResults.Count]; for (var i = 0; i < tvResults.Count; i++) @@ -125,7 +133,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV }; remoteResult.SetProviderId(MetadataProvider.Tmdb, series.Id.ToString(CultureInfo.InvariantCulture)); - if (series.ExternalIds != null) + if (series.ExternalIds is not null) { if (!string.IsNullOrEmpty(series.ExternalIds.ImdbId)) { @@ -159,6 +167,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV return remoteResult; } + /// <inheritdoc /> public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo info, CancellationToken cancellationToken) { var result = new MetadataResult<Series> @@ -201,7 +210,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV } } - if (string.IsNullOrEmpty(tmdbId)) + if (!int.TryParse(tmdbId, CultureInfo.InvariantCulture, out int tmdbIdInt)) { return result; } @@ -209,9 +218,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV cancellationToken.ThrowIfCancellationRequested(); var tvShow = await _tmdbClientManager - .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) + .GetSeriesAsync(tmdbIdInt, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) .ConfigureAwait(false); + if (tvShow is null) + { + return result; + } + result = new MetadataResult<Series> { Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode), @@ -223,7 +237,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV result.AddPerson(person); } - result.HasMetadata = result.Item != null; + result.HasMetadata = result.Item is not null; return result; } @@ -242,17 +256,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV series.Overview = seriesResult.Overview; - if (seriesResult.Networks != null) + if (seriesResult.Networks is not null) { series.Studios = seriesResult.Networks.Select(i => i.Name).ToArray(); } - if (seriesResult.Genres != null) + if (seriesResult.Genres is not null) { series.Genres = seriesResult.Genres.Select(i => i.Name).ToArray(); } - if (seriesResult.Keywords?.Results != null) + if (seriesResult.Keywords?.Results is not null) { for (var i = 0; i < seriesResult.Keywords.Results.Count; i++) { @@ -278,7 +292,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV series.PremiereDate = seriesResult.FirstAirDate; var ids = seriesResult.ExternalIds; - if (ids != null) + if (ids is not null) { if (!string.IsNullOrWhiteSpace(ids.ImdbId)) { @@ -302,20 +316,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV var usRelease = contentRatings.FirstOrDefault(c => string.Equals(c.Iso_3166_1, "US", StringComparison.OrdinalIgnoreCase)); var minimumRelease = contentRatings.FirstOrDefault(); - if (ourRelease != null) + if (ourRelease is not null) { series.OfficialRating = TmdbUtils.BuildParentalRating(ourRelease.Iso_3166_1, ourRelease.Rating); } - else if (usRelease != null) + else if (usRelease is not null) { series.OfficialRating = usRelease.Rating; } - else if (minimumRelease != null) + else if (minimumRelease is not null) { series.OfficialRating = minimumRelease.Rating; } - if (seriesResult.Videos?.Results != null) + if (seriesResult.Videos?.Results is not null) { foreach (var video in seriesResult.Videos.Results) { @@ -331,7 +345,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV private IEnumerable<PersonInfo> GetPersons(TvShow seriesResult) { - if (seriesResult.Credits?.Cast != null) + if (seriesResult.Credits?.Cast is not null) { foreach (var actor in seriesResult.Credits.Cast.OrderBy(a => a.Order).Take(Plugin.Instance.Configuration.MaxCastMembers)) { @@ -339,7 +353,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV { Name = actor.Name.Trim(), Role = actor.Character, - Type = PersonType.Actor, + Type = PersonKind.Actor, SortOrder = actor.Order, ImageUrl = _tmdbClientManager.GetPosterUrl(actor.ProfilePath) }; @@ -353,7 +367,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV } } - if (seriesResult.Credits?.Crew != null) + if (seriesResult.Credits?.Crew is not null) { var keepTypes = new[] { @@ -367,8 +381,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV // Normalize this var type = TmdbUtils.MapCrewToPersonType(person); - if (!keepTypes.Contains(type, StringComparison.OrdinalIgnoreCase) - && !keepTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + if (!TmdbUtils.WantedCrewKinds.Contains(type) + && !TmdbUtils.WantedCrewTypes.Contains(person.Job ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { continue; } @@ -383,6 +397,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV } } + /// <inheritdoc /> public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) { return _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index 7d7733407..500ebaf71 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -1,6 +1,4 @@ -#nullable disable - -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Threading; @@ -50,10 +48,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="imageLanguages">A comma-separated list of image languages.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The TMDb movie or null if not found.</returns> - public async Task<Movie> GetMovieAsync(int tmdbId, string language, string imageLanguages, CancellationToken cancellationToken) + public async Task<Movie?> GetMovieAsync(int tmdbId, string? language, string? imageLanguages, CancellationToken cancellationToken) { var key = $"movie-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}"; - if (_memoryCache.TryGetValue(key, out Movie movie)) + if (_memoryCache.TryGetValue(key, out Movie? movie)) { return movie; } @@ -73,7 +71,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb extraMethods, cancellationToken).ConfigureAwait(false); - if (movie != null) + if (movie is not null) { _memoryCache.Set(key, movie, TimeSpan.FromHours(CacheDurationInHours)); } @@ -89,10 +87,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="imageLanguages">A comma-separated list of image languages.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The TMDb collection or null if not found.</returns> - public async Task<Collection> GetCollectionAsync(int tmdbId, string language, string imageLanguages, CancellationToken cancellationToken) + public async Task<Collection?> GetCollectionAsync(int tmdbId, string? language, string? imageLanguages, CancellationToken cancellationToken) { var key = $"collection-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}"; - if (_memoryCache.TryGetValue(key, out Collection collection)) + if (_memoryCache.TryGetValue(key, out Collection? collection)) { return collection; } @@ -106,7 +104,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb CollectionMethods.Images, cancellationToken).ConfigureAwait(false); - if (collection != null) + if (collection is not null) { _memoryCache.Set(key, collection, TimeSpan.FromHours(CacheDurationInHours)); } @@ -122,10 +120,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="imageLanguages">A comma-separated list of image languages.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The TMDb tv show information or null if not found.</returns> - public async Task<TvShow> GetSeriesAsync(int tmdbId, string language, string imageLanguages, CancellationToken cancellationToken) + public async Task<TvShow?> GetSeriesAsync(int tmdbId, string? language, string? imageLanguages, CancellationToken cancellationToken) { var key = $"series-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}"; - if (_memoryCache.TryGetValue(key, out TvShow series)) + if (_memoryCache.TryGetValue(key, out TvShow? series)) { return series; } @@ -145,7 +143,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb extraMethods: extraMethods, cancellationToken: cancellationToken).ConfigureAwait(false); - if (series != null) + if (series is not null) { _memoryCache.Set(key, series, TimeSpan.FromHours(CacheDurationInHours)); } @@ -162,7 +160,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="imageLanguages">A comma-separated list of image languages.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The TMDb tv show episode group information or null if not found.</returns> - private async Task<TvGroupCollection> GetSeriesGroupAsync(int tvShowId, string displayOrder, string language, string imageLanguages, CancellationToken cancellationToken) + private async Task<TvGroupCollection?> GetSeriesGroupAsync(int tvShowId, string displayOrder, string? language, string? imageLanguages, CancellationToken cancellationToken) { TvGroupType? groupType = string.Equals(displayOrder, "originalAirDate", StringComparison.Ordinal) ? TvGroupType.OriginalAirDate : @@ -174,13 +172,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb string.Equals(displayOrder, "tv", StringComparison.Ordinal) ? TvGroupType.TV : null; - if (groupType == null) + if (groupType is null) { return null; } var key = $"group-{tvShowId.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}"; - if (_memoryCache.TryGetValue(key, out TvGroupCollection group)) + if (_memoryCache.TryGetValue(key, out TvGroupCollection? group)) { return group; } @@ -190,7 +188,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb var series = await GetSeriesAsync(tvShowId, language, imageLanguages, cancellationToken).ConfigureAwait(false); var episodeGroupId = series?.EpisodeGroups.Results.Find(g => g.Type == groupType)?.Id; - if (episodeGroupId == null) + if (episodeGroupId is null) { return null; } @@ -200,7 +198,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb language: TmdbUtils.NormalizeLanguage(language), cancellationToken: cancellationToken).ConfigureAwait(false); - if (group != null) + if (group is not null) { _memoryCache.Set(key, group, TimeSpan.FromHours(CacheDurationInHours)); } @@ -217,10 +215,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="imageLanguages">A comma-separated list of image languages.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The TMDb tv season information or null if not found.</returns> - public async Task<TvSeason> GetSeasonAsync(int tvShowId, int seasonNumber, string language, string imageLanguages, CancellationToken cancellationToken) + public async Task<TvSeason?> GetSeasonAsync(int tvShowId, int seasonNumber, string? language, string? imageLanguages, CancellationToken cancellationToken) { var key = $"season-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}-{language}"; - if (_memoryCache.TryGetValue(key, out TvSeason season)) + if (_memoryCache.TryGetValue(key, out TvSeason? season)) { return season; } @@ -235,7 +233,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb extraMethods: TvSeasonMethods.Credits | TvSeasonMethods.Images | TvSeasonMethods.ExternalIds | TvSeasonMethods.Videos, cancellationToken: cancellationToken).ConfigureAwait(false); - if (season != null) + if (season is not null) { _memoryCache.Set(key, season, TimeSpan.FromHours(CacheDurationInHours)); } @@ -254,10 +252,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="imageLanguages">A comma-separated list of image languages.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The TMDb tv episode information or null if not found.</returns> - public async Task<TvEpisode> GetEpisodeAsync(int tvShowId, int seasonNumber, int episodeNumber, string displayOrder, string language, string imageLanguages, CancellationToken cancellationToken) + public async Task<TvEpisode?> GetEpisodeAsync(int tvShowId, int seasonNumber, int episodeNumber, string displayOrder, string? language, string? imageLanguages, CancellationToken cancellationToken) { var key = $"episode-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}e{episodeNumber.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}"; - if (_memoryCache.TryGetValue(key, out TvEpisode episode)) + if (_memoryCache.TryGetValue(key, out TvEpisode? episode)) { return episode; } @@ -265,12 +263,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb await EnsureClientConfigAsync().ConfigureAwait(false); var group = await GetSeriesGroupAsync(tvShowId, displayOrder, language, imageLanguages, cancellationToken).ConfigureAwait(false); - if (group != null) + if (group is not null) { var season = group.Groups.Find(s => s.Order == seasonNumber); // Episode order starts at 0 var ep = season?.Episodes.Find(e => e.Order == episodeNumber - 1); - if (ep != null) + if (ep is not null) { seasonNumber = ep.SeasonNumber; episodeNumber = ep.EpisodeNumber; @@ -286,7 +284,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb extraMethods: TvEpisodeMethods.Credits | TvEpisodeMethods.Images | TvEpisodeMethods.ExternalIds | TvEpisodeMethods.Videos, cancellationToken: cancellationToken).ConfigureAwait(false); - if (episode != null) + if (episode is not null) { _memoryCache.Set(key, episode, TimeSpan.FromHours(CacheDurationInHours)); } @@ -301,10 +299,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="language">The episode's language.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The TMDb person information or null if not found.</returns> - public async Task<Person> GetPersonAsync(int personTmdbId, string language, CancellationToken cancellationToken) + public async Task<Person?> GetPersonAsync(int personTmdbId, string language, CancellationToken cancellationToken) { var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}-{language}"; - if (_memoryCache.TryGetValue(key, out Person person)) + if (_memoryCache.TryGetValue(key, out Person? person)) { return person; } @@ -317,7 +315,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb PersonMethods.TvCredits | PersonMethods.MovieCredits | PersonMethods.Images | PersonMethods.ExternalIds, cancellationToken).ConfigureAwait(false); - if (person != null) + if (person is not null) { _memoryCache.Set(key, person, TimeSpan.FromHours(CacheDurationInHours)); } @@ -333,14 +331,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="language">The item's language.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The TMDb item or null if not found.</returns> - public async Task<FindContainer> FindByExternalIdAsync( + public async Task<FindContainer?> FindByExternalIdAsync( string externalId, FindExternalSource source, string language, CancellationToken cancellationToken) { var key = $"find-{source.ToString()}-{externalId.ToString(CultureInfo.InvariantCulture)}-{language}"; - if (_memoryCache.TryGetValue(key, out FindContainer result)) + if (_memoryCache.TryGetValue(key, out FindContainer? result)) { return result; } @@ -353,7 +351,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb TmdbUtils.NormalizeLanguage(language), cancellationToken).ConfigureAwait(false); - if (result != null) + if (result is not null) { _memoryCache.Set(key, result, TimeSpan.FromHours(CacheDurationInHours)); } @@ -372,7 +370,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb public async Task<IReadOnlyList<SearchTv>> SearchSeriesAsync(string name, string language, int year = 0, CancellationToken cancellationToken = default) { var key = $"searchseries-{name}-{language}"; - if (_memoryCache.TryGetValue(key, out SearchContainer<SearchTv> series)) + if (_memoryCache.TryGetValue(key, out SearchContainer<SearchTv>? series) && series is not null) { return series.Results; } @@ -400,7 +398,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb public async Task<IReadOnlyList<SearchPerson>> SearchPersonAsync(string name, CancellationToken cancellationToken) { var key = $"searchperson-{name}"; - if (_memoryCache.TryGetValue(key, out SearchContainer<SearchPerson> person)) + if (_memoryCache.TryGetValue(key, out SearchContainer<SearchPerson>? person) && person is not null) { return person.Results; } @@ -442,7 +440,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb public async Task<IReadOnlyList<SearchMovie>> SearchMovieAsync(string name, int year, string language, CancellationToken cancellationToken) { var key = $"moviesearch-{name}-{year.ToString(CultureInfo.InvariantCulture)}-{language}"; - if (_memoryCache.TryGetValue(key, out SearchContainer<SearchMovie> movies)) + if (_memoryCache.TryGetValue(key, out SearchContainer<SearchMovie>? movies) && movies is not null) { return movies.Results; } @@ -471,7 +469,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb public async Task<IReadOnlyList<SearchCollection>> SearchCollectionAsync(string name, string language, CancellationToken cancellationToken) { var key = $"collectionsearch-{name}-{language}"; - if (_memoryCache.TryGetValue(key, out SearchContainer<SearchCollection> collections)) + if (_memoryCache.TryGetValue(key, out SearchContainer<SearchCollection>? collections) && collections is not null) { return collections.Results; } @@ -496,7 +494,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="size">The image size to fetch.</param> /// <param name="path">The relative URL of the image.</param> /// <returns>The absolute URL.</returns> - private string GetUrl(string size, string path) + private string? GetUrl(string? size, string path) { if (string.IsNullOrEmpty(path)) { @@ -511,7 +509,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// </summary> /// <param name="posterPath">The relative URL of the poster.</param> /// <returns>The absolute URL.</returns> - public string GetPosterUrl(string posterPath) + public string? GetPosterUrl(string posterPath) { return GetUrl(Plugin.Instance.Configuration.PosterSize, posterPath); } @@ -521,7 +519,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// </summary> /// <param name="actorProfilePath">The relative URL of the profile image.</param> /// <returns>The absolute URL.</returns> - public string GetProfileUrl(string actorProfilePath) + public string? GetProfileUrl(string actorProfilePath) { return GetUrl(Plugin.Instance.Configuration.ProfileSize, actorProfilePath); } @@ -531,55 +529,45 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// </summary> /// <param name="images">The input images.</param> /// <param name="requestLanguage">The requested language.</param> - /// <param name="results">The collection to add the remote images into.</param> - public void ConvertPostersToRemoteImageInfo(List<ImageData> images, string requestLanguage, List<RemoteImageInfo> results) - { - ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.PosterSize, ImageType.Primary, requestLanguage, results); - } + /// <returns>The remote images.</returns> + public IEnumerable<RemoteImageInfo> ConvertPostersToRemoteImageInfo(IReadOnlyList<ImageData> images, string requestLanguage) + => ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.PosterSize, ImageType.Primary, requestLanguage); /// <summary> /// Converts backdrop <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s. /// </summary> /// <param name="images">The input images.</param> /// <param name="requestLanguage">The requested language.</param> - /// <param name="results">The collection to add the remote images into.</param> - public void ConvertBackdropsToRemoteImageInfo(List<ImageData> images, string requestLanguage, List<RemoteImageInfo> results) - { - ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.BackdropSize, ImageType.Backdrop, requestLanguage, results); - } + /// <returns>The remote images.</returns> + public IEnumerable<RemoteImageInfo> ConvertBackdropsToRemoteImageInfo(IReadOnlyList<ImageData> images, string requestLanguage) + => ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.BackdropSize, ImageType.Backdrop, requestLanguage); /// <summary> /// Converts logo <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s. /// </summary> /// <param name="images">The input images.</param> /// <param name="requestLanguage">The requested language.</param> - /// <param name="results">The collection to add the remote images into.</param> - public void ConvertLogosToRemoteImageInfo(List<ImageData> images, string requestLanguage, List<RemoteImageInfo> results) - { - ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.LogoSize, ImageType.Logo, requestLanguage, results); - } + /// <returns>The remote images.</returns> + public IEnumerable<RemoteImageInfo> ConvertLogosToRemoteImageInfo(IReadOnlyList<ImageData> images, string requestLanguage) + => ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.LogoSize, ImageType.Logo, requestLanguage); /// <summary> /// Converts profile <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s. /// </summary> /// <param name="images">The input images.</param> /// <param name="requestLanguage">The requested language.</param> - /// <param name="results">The collection to add the remote images into.</param> - public void ConvertProfilesToRemoteImageInfo(List<ImageData> images, string requestLanguage, List<RemoteImageInfo> results) - { - ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.ProfileSize, ImageType.Primary, requestLanguage, results); - } + /// <returns>The remote images.</returns> + public IEnumerable<RemoteImageInfo> ConvertProfilesToRemoteImageInfo(IReadOnlyList<ImageData> images, string requestLanguage) + => ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.ProfileSize, ImageType.Primary, requestLanguage); /// <summary> /// Converts still <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s. /// </summary> /// <param name="images">The input images.</param> /// <param name="requestLanguage">The requested language.</param> - /// <param name="results">The collection to add the remote images into.</param> - public void ConvertStillsToRemoteImageInfo(List<ImageData> images, string requestLanguage, List<RemoteImageInfo> results) - { - ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.StillSize, ImageType.Primary, requestLanguage, results); - } + /// <returns>The remote images.</returns> + public IEnumerable<RemoteImageInfo> ConvertStillsToRemoteImageInfo(IReadOnlyList<ImageData> images, string requestLanguage) + => ConvertToRemoteImageInfo(images, Plugin.Instance.Configuration.StillSize, ImageType.Primary, requestLanguage); /// <summary> /// Converts <see cref="ImageData"/>s into <see cref="RemoteImageInfo"/>s. @@ -588,8 +576,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="size">The size of the image to fetch.</param> /// <param name="type">The type of the image.</param> /// <param name="requestLanguage">The requested language.</param> - /// <param name="results">The collection to add the remote images into.</param> - private void ConvertToRemoteImageInfo(List<ImageData> images, string size, ImageType type, string requestLanguage, List<RemoteImageInfo> results) + /// <returns>The remote images.</returns> + private IEnumerable<RemoteImageInfo> ConvertToRemoteImageInfo(IReadOnlyList<ImageData> images, string? size, ImageType type, string requestLanguage) { // sizes provided are for original resolution, don't store them when downloading scaled images var scaleImage = !string.Equals(size, "original", StringComparison.OrdinalIgnoreCase); @@ -598,7 +586,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb { var image = images[i]; - results.Add(new RemoteImageInfo + yield return new RemoteImageInfo { Url = GetUrl(size, image.FilePath), CommunityRating = image.VoteAverage, @@ -609,7 +597,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb ProviderName = TmdbUtils.ProviderName, Type = type, RatingType = RatingType.Score - }); + }; } } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index 685eb222f..516eee758 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Entities; using TMDbLib.Objects.General; @@ -14,7 +16,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb private static readonly Regex _nonWords = new(@"[\W_]+", RegexOptions.Compiled); /// <summary> - /// URL of the TMDB instance to use. + /// URL of the TMDb instance to use. /// </summary> public const string BaseTmdbUrl = "https://www.themoviedb.org/"; @@ -39,6 +41,16 @@ namespace MediaBrowser.Providers.Plugins.Tmdb }; /// <summary> + /// The crew kinds to keep. + /// </summary> + public static readonly PersonKind[] WantedCrewKinds = + { + PersonKind.Director, + PersonKind.Writer, + PersonKind.Producer + }; + + /// <summary> /// Cleans the name according to TMDb requirements. /// </summary> /// <param name="name">The name of the entity.</param> @@ -50,30 +62,30 @@ namespace MediaBrowser.Providers.Plugins.Tmdb } /// <summary> - /// Maps the TMDB provided roles for crew members to Jellyfin roles. + /// Maps the TMDb provided roles for crew members to Jellyfin roles. /// </summary> /// <param name="crew">Crew member to map against the Jellyfin person types.</param> /// <returns>The Jellyfin person type.</returns> - public static string MapCrewToPersonType(Crew crew) + public static PersonKind MapCrewToPersonType(Crew crew) { if (crew.Department.Equals("production", StringComparison.OrdinalIgnoreCase) && crew.Job.Contains("director", StringComparison.OrdinalIgnoreCase)) { - return PersonType.Director; + return PersonKind.Director; } if (crew.Department.Equals("production", StringComparison.OrdinalIgnoreCase) && crew.Job.Contains("producer", StringComparison.OrdinalIgnoreCase)) { - return PersonType.Producer; + return PersonKind.Producer; } if (crew.Department.Equals("writing", StringComparison.OrdinalIgnoreCase)) { - return PersonType.Writer; + return PersonKind.Writer; } - return string.Empty; + return PersonKind.Unknown; } /// <summary> @@ -103,9 +115,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb languages.Add(preferredLanguage); - if (preferredLanguage.Length == 5) // like en-US + if (preferredLanguage.Length == 5) // Like en-US { - // Currently, TMDB supports 2-letter language codes only + // Currently, TMDb supports 2-letter language codes only. // They are planning to change this in the future, thus we're // supplying both codes if we're having a 5-letter code. languages.Add(preferredLanguage.Substring(0, 2)); @@ -114,6 +126,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb languages.Add("null"); + // Always add English as fallback language if (!string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase)) { languages.Add("en"); @@ -127,21 +140,22 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// </summary> /// <param name="language">The language code.</param> /// <returns>The normalized language code.</returns> - public static string NormalizeLanguage(string language) + [return: NotNullIfNotNull(nameof(language))] + public static string? NormalizeLanguage(string? language) { if (string.IsNullOrEmpty(language)) { return language; } - // They require this to be uppercase - // Everything after the hyphen must be written in uppercase due to a way TMDB wrote their api. + // TMDb requires this to be uppercase + // Everything after the hyphen must be written in uppercase due to a way TMDb wrote their API. // See here: https://www.themoviedb.org/talk/5119221d760ee36c642af4ad?page=3#56e372a0c3a3685a9e0019ab var parts = language.Split('-'); if (parts.Length == 2) { - // TMDB doesn't support Switzerland (de-CH, it-CH or fr-CH) so use the language (de, it or fr) without country code + // TMDb doesn't support Switzerland (de-CH, it-CH or fr-CH) so use the language (de, it or fr) without country code if (string.Equals(parts[1], "CH", StringComparison.OrdinalIgnoreCase)) { return parts[0]; @@ -174,14 +188,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb } /// <summary> - /// Combines the metadata country code and the parental rating from the Api into the value we store in our database. + /// Combines the metadata country code and the parental rating from the API into the value we store in our database. /// </summary> - /// <param name="countryCode">The Iso 3166-1 country code of the rating country.</param> - /// <param name="ratingValue">The rating value returned by the Tmdb Api.</param> + /// <param name="countryCode">The ISO 3166-1 country code of the rating country.</param> + /// <param name="ratingValue">The rating value returned by the TMDb API.</param> /// <returns>The combined parental rating of country code+rating value.</returns> public static string BuildParentalRating(string countryCode, string ratingValue) { - // exclude US because we store us values as TV-14 without the country code. + // Exclude US because we store US values as TV-14 without the country code. var ratingPrefix = string.Equals(countryCode, "US", StringComparison.OrdinalIgnoreCase) ? string.Empty : countryCode + "-"; var newRating = ratingPrefix + ratingValue; |
