diff options
Diffstat (limited to 'MediaBrowser.Providers')
28 files changed, 420 insertions, 432 deletions
diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 5bb85be7b..fb1d4f490 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -172,7 +172,9 @@ namespace MediaBrowser.Providers.Manager SetImagePath(item, type, imageIndex, savedPaths[0]); // Delete the current path - if (currentImageIsLocalFile && !savedPaths.Contains(currentImagePath, StringComparer.OrdinalIgnoreCase)) + if (currentImageIsLocalFile + && !savedPaths.Contains(currentImagePath, StringComparer.OrdinalIgnoreCase) + && (saveLocally || currentImagePath.Contains(_config.ApplicationPaths.InternalMetadataPath, StringComparison.OrdinalIgnoreCase))) { var currentPath = currentImagePath; diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index ffc6889fa..4471a25b2 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -469,6 +469,7 @@ namespace MediaBrowser.Providers.Manager try { using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await _providerManager.SaveImage( diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 8b3ca17ca..401c7e99f 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -201,7 +201,7 @@ namespace MediaBrowser.Providers.Manager } // Save to database - await SaveItemAsync(metadataResult, libraryOptions, updateType, cancellationToken).ConfigureAwait(false); + await SaveItemAsync(metadataResult, updateType, cancellationToken).ConfigureAwait(false); } await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false); @@ -216,66 +216,18 @@ namespace MediaBrowser.Providers.Manager lookupInfo.Year = result.ProductionYear; } - protected async Task SaveItemAsync(MetadataResult<TItemType> result, LibraryOptions libraryOptions, ItemUpdateType reason, CancellationToken cancellationToken) + protected async Task SaveItemAsync(MetadataResult<TItemType> result, ItemUpdateType reason, CancellationToken cancellationToken) { if (result.Item.SupportsPeople && result.People != null) { var baseItem = result.Item; - LibraryManager.UpdatePeople(baseItem, result.People); - await SavePeopleMetadataAsync(result.People, libraryOptions, cancellationToken).ConfigureAwait(false); + await LibraryManager.UpdatePeopleAsync(baseItem, result.People, cancellationToken).ConfigureAwait(false); } await result.Item.UpdateToRepositoryAsync(reason, cancellationToken).ConfigureAwait(false); } - private async Task SavePeopleMetadataAsync(List<PersonInfo> people, LibraryOptions libraryOptions, CancellationToken cancellationToken) - { - var personsToSave = new List<BaseItem>(); - - foreach (var person in people) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (person.ProviderIds.Count > 0 || !string.IsNullOrWhiteSpace(person.ImageUrl)) - { - var itemUpdateType = ItemUpdateType.MetadataDownload; - var saveEntity = false; - var personEntity = LibraryManager.GetPerson(person.Name); - foreach (var id in person.ProviderIds) - { - if (!string.Equals(personEntity.GetProviderId(id.Key), id.Value, StringComparison.OrdinalIgnoreCase)) - { - personEntity.SetProviderId(id.Key, id.Value); - saveEntity = true; - } - } - - if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary)) - { - personEntity.SetImage( - new ItemImageInfo - { - Path = person.ImageUrl, - Type = ImageType.Primary - }, - 0); - - saveEntity = true; - itemUpdateType = ItemUpdateType.ImageUpdate; - } - - if (saveEntity) - { - personsToSave.Add(personEntity); - await LibraryManager.RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false); - } - } - } - - LibraryManager.CreateItems(personsToSave, null, CancellationToken.None); - } - protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) { item.AfterMetadataRefresh(); @@ -329,8 +281,7 @@ namespace MediaBrowser.Providers.Manager return true; } - var folder = item as Folder; - if (folder != null) + if (item is Folder folder) { return folder.SupportsDateLastMediaAdded || folder.SupportsCumulativeRunTimeTicks; } @@ -384,8 +335,7 @@ namespace MediaBrowser.Providers.Manager private ItemUpdateType UpdateCumulativeRunTimeTicks(TItemType item, IList<BaseItem> children) { - var folder = item as Folder; - if (folder != null && folder.SupportsCumulativeRunTimeTicks) + if (item is Folder folder && folder.SupportsCumulativeRunTimeTicks) { long ticks = 0; @@ -473,7 +423,7 @@ namespace MediaBrowser.Providers.Manager if ((originalPremiereDate ?? DateTime.MinValue) != (item.PremiereDate ?? DateTime.MinValue) || (originalProductionYear ?? -1) != (item.ProductionYear ?? -1)) { - updateType = updateType | ItemUpdateType.MetadataEdit; + updateType |= ItemUpdateType.MetadataEdit; } return updateType; @@ -493,7 +443,7 @@ namespace MediaBrowser.Providers.Manager if (currentList.Length != item.Genres.Length || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase)) { - updateType = updateType | ItemUpdateType.MetadataEdit; + updateType |= ItemUpdateType.MetadataEdit; } } @@ -514,7 +464,7 @@ namespace MediaBrowser.Providers.Manager if (currentList.Length != item.Studios.Length || !currentList.OrderBy(i => i).SequenceEqual(item.Studios.OrderBy(i => i), StringComparer.OrdinalIgnoreCase)) { - updateType = updateType | ItemUpdateType.MetadataEdit; + updateType |= ItemUpdateType.MetadataEdit; } } @@ -529,7 +479,7 @@ namespace MediaBrowser.Providers.Manager { if (item.UpdateRatingToItems(children)) { - updateType = updateType | ItemUpdateType.MetadataEdit; + updateType |= ItemUpdateType.MetadataEdit; } } @@ -686,7 +636,7 @@ namespace MediaBrowser.Providers.Manager var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), cancellationToken) .ConfigureAwait(false); - refreshResult.UpdateType = refreshResult.UpdateType | remoteResult.UpdateType; + refreshResult.UpdateType |= remoteResult.UpdateType; refreshResult.ErrorMessage = remoteResult.ErrorMessage; refreshResult.Failures += remoteResult.Failures; } @@ -706,9 +656,15 @@ namespace MediaBrowser.Providers.Manager if (localItem.HasMetadata) { + foreach (var remoteImage in localItem.RemoteImages) + { + await ProviderManager.SaveImage(item, remoteImage.url, remoteImage.type, null, cancellationToken).ConfigureAwait(false); + refreshResult.UpdateType |= ItemUpdateType.ImageUpdate; + } + if (imageService.MergeImages(item, localItem.Images)) { - refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.ImageUpdate; + refreshResult.UpdateType |= ItemUpdateType.ImageUpdate; } if (localItem.UserDataList != null) @@ -717,7 +673,7 @@ namespace MediaBrowser.Providers.Manager } MergeData(localItem, temp, Array.Empty<MetadataField>(), !options.ReplaceAllMetadata, true); - refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport; + refreshResult.UpdateType |= ItemUpdateType.MetadataImport; // Only one local provider allowed per item if (item.IsLocked || localItem.Item.IsLocked || IsFullLocalMetadata(localItem.Item)) @@ -749,7 +705,7 @@ namespace MediaBrowser.Providers.Manager var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), cancellationToken) .ConfigureAwait(false); - refreshResult.UpdateType = refreshResult.UpdateType | remoteResult.UpdateType; + refreshResult.UpdateType |= remoteResult.UpdateType; refreshResult.ErrorMessage = remoteResult.ErrorMessage; refreshResult.Failures += remoteResult.Failures; } @@ -845,7 +801,7 @@ namespace MediaBrowser.Providers.Manager MergeData(result, temp, Array.Empty<MetadataField>(), false, false); MergeNewData(temp.Item, id); - refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataDownload; + refreshResult.UpdateType |= ItemUpdateType.MetadataDownload; } else { diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index d581dd434..dd497845d 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -25,7 +25,6 @@ using MediaBrowser.Controller.Subtitles; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; using MediaBrowser.Model.Providers; using Microsoft.Extensions.Logging; using Priority_Queue; @@ -60,8 +59,8 @@ namespace MediaBrowser.Providers.Manager private IMetadataService[] _metadataServices = Array.Empty<IMetadataService>(); private IMetadataProvider[] _metadataProviders = Array.Empty<IMetadataProvider>(); - private IEnumerable<IMetadataSaver> _savers; - private IExternalId[] _externalIds; + private IMetadataSaver[] _savers = Array.Empty<IMetadataSaver>(); + private IExternalId[] _externalIds = Array.Empty<IExternalId>(); private bool _isProcessingRefreshQueue; private bool _disposed; @@ -125,7 +124,7 @@ namespace MediaBrowser.Providers.Manager _externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray(); _savers = metadataSavers - .Where(i => !(i is IConfigurableProvider configurable) || configurable.IsEnabled) + .Where(i => i is not IConfigurableProvider configurable || configurable.IsEnabled) .ToArray(); } @@ -168,7 +167,7 @@ namespace MediaBrowser.Providers.Manager throw new HttpRequestException("Invalid image received.", null, response.StatusCode); } - var contentType = response.Content.Headers.ContentType.MediaType; + var contentType = response.Content.Headers.ContentType?.MediaType; // Workaround for tvheadend channel icons // TODO: Isolate this hack into the tvh plugin @@ -1022,26 +1021,26 @@ namespace MediaBrowser.Providers.Manager // TODO: Need to hunt down the conditions for this happening _activeRefreshes.AddOrUpdate( id, - (_) => throw new Exception( + (_) => throw new InvalidOperationException( string.Format( CultureInfo.InvariantCulture, "Cannot update refresh progress of item '{0}' ({1}) because a refresh for this item is not running", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture))), - (_, __) => progress); + (_, _) => progress); RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(item, progress))); } /// <inheritdoc/> - public void QueueRefresh(Guid id, MetadataRefreshOptions options, RefreshPriority priority) + public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority) { if (_disposed) { return; } - _refreshQueue.Enqueue(new Tuple<Guid, MetadataRefreshOptions>(id, options), (int)priority); + _refreshQueue.Enqueue(new Tuple<Guid, MetadataRefreshOptions>(itemId, options), (int)priority); lock (_refreshQueueLock) { @@ -1074,17 +1073,16 @@ namespace MediaBrowser.Providers.Manager try { var item = libraryManager.GetItemById(refreshItem.Item1); - if (item != null) + if (item == null) { - // Try to throttle this a little bit. - await Task.Delay(100, cancellationToken).ConfigureAwait(false); + continue; + } - var task = item is MusicArtist artist - ? RefreshArtist(artist, refreshItem.Item2, cancellationToken) - : RefreshItem(item, refreshItem.Item2, cancellationToken); + var task = item is MusicArtist artist + ? RefreshArtist(artist, refreshItem.Item2, cancellationToken) + : RefreshItem(item, refreshItem.Item2, cancellationToken); - await task.ConfigureAwait(false); - } + await task.ConfigureAwait(false); } catch (OperationCanceledException) { diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 152ea664a..cdb07a15d 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -19,10 +19,10 @@ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" /> - <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> + <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="OptimizedPriorityQueue" Version="5.0.0" /> <PackageReference Include="PlaylistsNET" Version="1.1.3" /> - <PackageReference Include="TMDbLib" Version="1.7.3-alpha" /> + <PackageReference Include="TMDbLib" Version="1.8.1" /> </ItemGroup> <PropertyGroup> diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs index 64ad1bddf..03e45fb86 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs @@ -137,9 +137,7 @@ namespace MediaBrowser.Providers.MediaInfo return false; } - var audio = item as Audio; - - return audio != null; + return item is Audio; } } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 74849a522..f049cc81f 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -111,10 +111,7 @@ namespace MediaBrowser.Providers.MediaInfo } } - if (streamFileNames == null) - { - streamFileNames = Array.Empty<string>(); - } + streamFileNames ??= Array.Empty<string>(); mediaInfoResult = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs index 912aedb0d..44ab5aa5b 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs @@ -172,9 +172,7 @@ namespace MediaBrowser.Providers.MediaInfo SubtitleFetcherOrder = subtitleFetcherOrder }; - var episode = video as Episode; - - if (episode != null) + if (video is Episode episode) { request.IndexNumberEnd = episode.IndexNumberEnd; request.SeriesName = episode.SeriesName; diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index e9f999c6d..b086ef07b 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -15,17 +15,6 @@ namespace MediaBrowser.Providers.MediaInfo { private readonly ILocalizationManager _localization; - private static readonly HashSet<string> SubtitleExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) - { - ".srt", - ".ssa", - ".ass", - ".sub", - ".smi", - ".sami", - ".vtt" - }; - public SubtitleResolver(ILocalizationManager localization) { _localization = localization; @@ -88,80 +77,65 @@ namespace MediaBrowser.Providers.MediaInfo return list; } - private void AddExternalSubtitleStreams( - List<MediaStream> streams, - string folder, - string videoPath, - int startIndex, - IDirectoryService directoryService, - bool clearCache) - { - var files = directoryService.GetFilePaths(folder, clearCache).OrderBy(i => i).ToArray(); - - AddExternalSubtitleStreams(streams, videoPath, startIndex, files); - } - public void AddExternalSubtitleStreams( List<MediaStream> streams, string videoPath, int startIndex, string[] files) { - var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(videoPath); - videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoFileNameWithoutExtension); + var videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoPath); foreach (var fullName in files) { - var extension = Path.GetExtension(fullName); - - if (!SubtitleExtensions.Contains(extension)) + var extension = Path.GetExtension(fullName.AsSpan()); + if (!IsSubtitleExtension(extension)) { continue; } - var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName); - fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fileNameWithoutExtension); + var fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fullName); - if (!string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) && - !fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - var codec = Path.GetExtension(fullName).ToLowerInvariant().TrimStart('.'); - - if (string.Equals(codec, "txt", StringComparison.OrdinalIgnoreCase)) - { - codec = "srt"; - } + MediaStream mediaStream; - // If the subtitle file matches the video file name - if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) + // The subtitle filename must either be equal to the video filename or start with the video filename followed by a dot + if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) { - streams.Add(new MediaStream + mediaStream = new MediaStream { Index = startIndex++, Type = MediaStreamType.Subtitle, IsExternal = true, - Path = fullName, - Codec = codec - }); + Path = fullName + }; } - else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase)) + else if (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length + && fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.' + && fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) { - var isForced = fullName.IndexOf(".forced.", StringComparison.OrdinalIgnoreCase) != -1 || - fullName.IndexOf(".foreign.", StringComparison.OrdinalIgnoreCase) != -1; + var isForced = fullName.Contains(".forced.", StringComparison.OrdinalIgnoreCase) + || fullName.Contains(".foreign.", StringComparison.OrdinalIgnoreCase); - var isDefault = fullName.IndexOf(".default.", StringComparison.OrdinalIgnoreCase) != -1; + var isDefault = fullName.Contains(".default.", StringComparison.OrdinalIgnoreCase); // Support xbmc naming conventions - 300.spanish.srt - var language = fileNameWithoutExtension - .Replace(".forced", string.Empty, StringComparison.OrdinalIgnoreCase) - .Replace(".foreign", string.Empty, StringComparison.OrdinalIgnoreCase) - .Replace(".default", string.Empty, StringComparison.OrdinalIgnoreCase) - .Split('.') - .LastOrDefault(); + var languageSpan = fileNameWithoutExtension; + while (languageSpan.Length > 0) + { + var lastDot = languageSpan.LastIndexOf('.'); + var currentSlice = languageSpan[lastDot..]; + if (currentSlice.Equals(".default", StringComparison.OrdinalIgnoreCase) + || currentSlice.Equals(".forced", StringComparison.OrdinalIgnoreCase) + || currentSlice.Equals(".foreign", StringComparison.OrdinalIgnoreCase)) + { + languageSpan = languageSpan[..lastDot]; + continue; + } + + languageSpan = languageSpan[(lastDot + 1)..]; + break; + } + var language = languageSpan.ToString(); // Try to translate to three character code // Be flexible and check against both the full and three character versions var culture = _localization.FindLanguageInfo(language); @@ -171,33 +145,58 @@ namespace MediaBrowser.Providers.MediaInfo language = culture.ThreeLetterISOLanguageName; } - streams.Add(new MediaStream + mediaStream = new MediaStream { Index = startIndex++, Type = MediaStreamType.Subtitle, IsExternal = true, Path = fullName, - Codec = codec, Language = language, IsForced = isForced, IsDefault = isDefault - }); + }; + } + else + { + continue; } + + mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant(); + + streams.Add(mediaStream); } } - private string NormalizeFilenameForSubtitleComparison(string filename) + private static bool IsSubtitleExtension(ReadOnlySpan<char> extension) + { + return extension.Equals(".srt", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".ssa", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".ass", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".sub", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".vtt", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".smi", StringComparison.OrdinalIgnoreCase) + || extension.Equals(".sami", StringComparison.OrdinalIgnoreCase); + } + + private static ReadOnlySpan<char> NormalizeFilenameForSubtitleComparison(string filename) { // Try to account for sloppy file naming filename = filename.Replace("_", string.Empty, StringComparison.Ordinal); filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal); + return Path.GetFileNameWithoutExtension(filename.AsSpan()); + } - // can't normalize this due to languages such as pt-br - // filename = filename.Replace("-", string.Empty); - - // filename = filename.Replace(".", string.Empty); + private void AddExternalSubtitleStreams( + List<MediaStream> streams, + string folder, + string videoPath, + int startIndex, + IDirectoryService directoryService, + bool clearCache) + { + var files = directoryService.GetFilePaths(folder, clearCache).OrderBy(i => i).ToArray(); - return filename; + AddExternalSubtitleStreams(streams, videoPath, startIndex, files); } } } diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index c36c3af6a..30af6710a 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -154,9 +154,7 @@ namespace MediaBrowser.Providers.MediaInfo return false; } - var video = item as Video; - - if (video != null && !video.IsPlaceHolder && video.IsCompleteMedia) + if (item is Video video && !video.IsPlaceHolder && video.IsCompleteMedia) { return true; } diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs index 2adb11908..85a28747f 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs @@ -14,7 +14,6 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Providers.Plugins.AudioDb { diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs index 00feeec1f..25bb3f9ce 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs @@ -19,7 +19,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Music; namespace MediaBrowser.Providers.Plugins.AudioDb diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs index b8095ff04..db8536cc9 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs @@ -14,7 +14,6 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Providers.Plugins.AudioDb { diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs index 59ecbc017..cbb61fa35 100644 --- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs @@ -18,7 +18,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; using MediaBrowser.Providers.Music; namespace MediaBrowser.Providers.Plugins.AudioDb diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs index ce9392402..2eab95294 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs @@ -69,58 +69,52 @@ namespace MediaBrowser.Providers.Music private IEnumerable<RemoteSearchResult> GetResultsFromResponse(Stream stream) { - using (var oReader = new StreamReader(stream, Encoding.UTF8)) + using var oReader = new StreamReader(stream, Encoding.UTF8); + var settings = new XmlReaderSettings() { - var settings = new XmlReaderSettings() - { - ValidationType = ValidationType.None, - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true - }; + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; - using (var reader = XmlReader.Create(oReader, settings)) - { - reader.MoveToContent(); - reader.Read(); + using var reader = XmlReader.Create(oReader, settings); + reader.MoveToContent(); + reader.Read(); - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) { - if (reader.NodeType == XmlNodeType.Element) + case "artist-list": { - switch (reader.Name) + if (reader.IsEmptyElement) { - case "artist-list": - { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - - using (var subReader = reader.ReadSubtree()) - { - return ParseArtistList(subReader).ToList(); - } - } - - default: - { - reader.Skip(); - break; - } + reader.Read(); + continue; } + + using var subReader = reader.ReadSubtree(); + return ParseArtistList(subReader).ToList(); } - else + + default: { - reader.Read(); + reader.Skip(); + break; } } - - return Enumerable.Empty<RemoteSearchResult>(); + } + else + { + reader.Read(); } } + + return Enumerable.Empty<RemoteSearchResult>(); } private IEnumerable<RemoteSearchResult> ParseArtistList(XmlReader reader) @@ -145,13 +139,11 @@ namespace MediaBrowser.Providers.Music var mbzId = reader.GetAttribute("id"); - using (var subReader = reader.ReadSubtree()) + using var subReader = reader.ReadSubtree(); + var artist = ParseArtist(subReader, mbzId); + if (artist != null) { - var artist = ParseArtist(subReader, mbzId); - if (artist != null) - { - yield return artist; - } + yield return artist; } break; diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs index e5ad0f3e0..0023d5959 100644 --- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs @@ -128,53 +128,49 @@ namespace MediaBrowser.Providers.Music private IEnumerable<RemoteSearchResult> GetResultsFromResponse(Stream stream) { - using (var oReader = new StreamReader(stream, Encoding.UTF8)) + using var oReader = new StreamReader(stream, Encoding.UTF8); + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + + using var reader = XmlReader.Create(oReader, settings); + var results = ReleaseResult.Parse(reader); + + return results.Select(i => { - var settings = new XmlReaderSettings() + var result = new RemoteSearchResult { - ValidationType = ValidationType.None, - CheckCharacters = false, - IgnoreProcessingInstructions = true, - IgnoreComments = true + Name = i.Title, + ProductionYear = i.Year }; - using (var reader = XmlReader.Create(oReader, settings)) + if (i.Artists.Count > 0) { - var results = ReleaseResult.Parse(reader); - - return results.Select(i => + result.AlbumArtist = new RemoteSearchResult { - var result = new RemoteSearchResult - { - Name = i.Title, - ProductionYear = i.Year - }; - - if (i.Artists.Count > 0) - { - result.AlbumArtist = new RemoteSearchResult - { - SearchProviderName = Name, - Name = i.Artists[0].Item1 - }; + SearchProviderName = Name, + Name = i.Artists[0].Item1 + }; - result.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, i.Artists[0].Item2); - } - - if (!string.IsNullOrWhiteSpace(i.ReleaseId)) - { - result.SetProviderId(MetadataProvider.MusicBrainzAlbum, i.ReleaseId); - } + result.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, i.Artists[0].Item2); + } - if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId)) - { - result.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, i.ReleaseGroupId); - } + if (!string.IsNullOrWhiteSpace(i.ReleaseId)) + { + result.SetProviderId(MetadataProvider.MusicBrainzAlbum, i.ReleaseId); + } - return result; - }); + if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId)) + { + result.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, i.ReleaseGroupId); } - } + + return result; + }); } /// <inheritdoc /> @@ -339,10 +335,8 @@ namespace MediaBrowser.Providers.Music continue; } - using (var subReader = reader.ReadSubtree()) - { - return ParseReleaseList(subReader).ToList(); - } + using var subReader = reader.ReadSubtree(); + return ParseReleaseList(subReader).ToList(); } default: @@ -383,13 +377,11 @@ namespace MediaBrowser.Providers.Music var releaseId = reader.GetAttribute("id"); - using (var subReader = reader.ReadSubtree()) + using var subReader = reader.ReadSubtree(); + var release = ParseRelease(subReader, releaseId); + if (release != null) { - var release = ParseRelease(subReader, releaseId); - if (release != null) - { - yield return release; - } + yield return release; } break; @@ -460,14 +452,12 @@ namespace MediaBrowser.Providers.Music case "artist-credit": { - using (var subReader = reader.ReadSubtree()) - { - var artist = ParseArtistCredit(subReader); + using var subReader = reader.ReadSubtree(); + var artist = ParseArtistCredit(subReader); - if (!string.IsNullOrEmpty(artist.Item1)) - { - result.Artists.Add(artist); - } + if (!string.IsNullOrEmpty(artist.Item1)) + { + result.Artists.Add(artist); } break; @@ -505,12 +495,10 @@ namespace MediaBrowser.Providers.Music switch (reader.Name) { case "name-credit": - { - using (var subReader = reader.ReadSubtree()) - { - return ParseArtistNameCredit(subReader); - } - } + { + using var subReader = reader.ReadSubtree(); + return ParseArtistNameCredit(subReader); + } default: { @@ -545,10 +533,8 @@ namespace MediaBrowser.Providers.Music case "artist": { var id = reader.GetAttribute("id"); - using (var subReader = reader.ReadSubtree()) - { - return ParseArtistArtistCredit(subReader, id); - } + using var subReader = reader.ReadSubtree(); + return ParseArtistArtistCredit(subReader, id); } default: @@ -647,47 +633,43 @@ namespace MediaBrowser.Providers.Music IgnoreComments = true }; - using (var reader = XmlReader.Create(oReader, settings)) - { - reader.MoveToContent(); - reader.Read(); + using var reader = XmlReader.Create(oReader, settings); + reader.MoveToContent(); + reader.Read(); - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) { - if (reader.NodeType == XmlNodeType.Element) + switch (reader.Name) { - switch (reader.Name) + case "release-group-list": { - case "release-group-list": + if (reader.IsEmptyElement) { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - - using (var subReader = reader.ReadSubtree()) - { - return GetFirstReleaseGroupId(subReader); - } + reader.Read(); + continue; } - default: - { - reader.Skip(); - break; - } + using var subReader = reader.ReadSubtree(); + return GetFirstReleaseGroupId(subReader); + } + + default: + { + reader.Skip(); + break; } - } - else - { - reader.Read(); } } - - return null; + else + { + reader.Read(); + } } + + return null; } private string GetFirstReleaseGroupId(XmlReader reader) diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index d35805a84..46d303890 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -6,9 +6,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Net.Http; -using System.Text; using System.Text.Json; -using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index 4963777bc..4a0884c07 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -7,8 +7,6 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using TMDbLib.Objects.Find; -using TMDbLib.Objects.Search; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; @@ -16,6 +14,8 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; +using TMDbLib.Objects.Find; +using TMDbLib.Objects.Search; namespace MediaBrowser.Providers.Plugins.Tmdb.Movies { @@ -175,6 +175,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies var movie = new Movie { Name = movieResult.Title ?? movieResult.OriginalTitle, + OriginalTitle = movieResult.OriginalTitle, Overview = movieResult.Overview?.Replace("\n\n", "\n", StringComparison.InvariantCulture), Tagline = movieResult.Tagline, ProductionLocations = movieResult.ProductionCountries.Select(pc => pc.Name).ToArray() @@ -205,12 +206,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies if (ourRelease != null) { - var ratingPrefix = string.Equals(info.MetadataCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? string.Empty : info.MetadataCountryCode + "-"; - var newRating = ratingPrefix + ourRelease.Certification; - - newRating = newRating.Replace("de-", "FSK-", StringComparison.OrdinalIgnoreCase); - - movie.OfficialRating = newRating; + movie.OfficialRating = TmdbUtils.BuildParentalRating(ourRelease.Iso_3166_1, ourRelease.Certification); } else if (usRelease != null) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs index 3f57c4bc4..e4c908a62 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs @@ -1,6 +1,5 @@ #pragma warning disable CS1591 -using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -49,37 +48,36 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken) { var person = (Person)item; - var personTmdbId = Convert.ToInt32(person.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); - if (personTmdbId > 0) + if (!person.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId)) { - var personResult = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false); - if (personResult?.Images?.Profiles == null) - { - return Enumerable.Empty<RemoteImageInfo>(); - } + return Enumerable.Empty<RemoteImageInfo>(); + } - var remoteImages = new List<RemoteImageInfo>(); - var language = item.GetPreferredMetadataLanguage(); + var language = item.GetPreferredMetadataLanguage(); + var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), language, cancellationToken).ConfigureAwait(false); + if (personResult?.Images?.Profiles == null) + { + return Enumerable.Empty<RemoteImageInfo>(); + } - for (var i = 0; i < personResult.Images.Profiles.Count; i++) - { - var image = personResult.Images.Profiles[i]; - remoteImages.Add(new RemoteImageInfo - { - ProviderName = Name, - Type = ImageType.Primary, - Width = image.Width, - Height = image.Height, - Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, language), - Url = _tmdbClientManager.GetProfileUrl(image.FilePath) - }); - } + var remoteImages = new RemoteImageInfo[personResult.Images.Profiles.Count]; - return remoteImages.OrderByLanguageDescending(language); + for (var i = 0; i < personResult.Images.Profiles.Count; i++) + { + var image = personResult.Images.Profiles[i]; + remoteImages[i] = new RemoteImageInfo + { + ProviderName = Name, + Type = ImageType.Primary, + Width = image.Width, + Height = image.Height, + Language = TmdbUtils.AdjustImageLanguage(image.Iso_639_1, language), + Url = _tmdbClientManager.GetProfileUrl(image.FilePath) + }; } - return Enumerable.Empty<RemoteImageInfo>(); + return remoteImages.OrderByLanguageDescending(language); } public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs index 4384c203e..6db550b1d 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -30,11 +29,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken) { - var personTmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture); - - if (personTmdbId <= 0) + if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var personTmdbId)) { - var personResult = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false); + var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); if (personResult != null) { @@ -51,19 +48,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People } result.SetProviderId(MetadataProvider.Tmdb, personResult.Id.ToString(CultureInfo.InvariantCulture)); - result.SetProviderId(MetadataProvider.Imdb, personResult.ExternalIds.ImdbId); + if (!string.IsNullOrEmpty(personResult.ExternalIds.ImdbId)) + { + result.SetProviderId(MetadataProvider.Imdb, personResult.ExternalIds.ImdbId); + } return new[] { result }; } } - // TODO why? Because of the old rate limit? - if (searchInfo.IsAutomated) - { - // Don't hammer moviedb searching by name - return Enumerable.Empty<RemoteSearchResult>(); - } - var personSearchResult = await _tmdbClientManager.SearchPersonAsync(searchInfo.Name, cancellationToken).ConfigureAwait(false); var remoteSearchResults = new List<RemoteSearchResult>(); @@ -102,7 +95,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People if (personTmdbId > 0) { - var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, cancellationToken).ConfigureAwait(false); + var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, id.MetadataLanguage, cancellationToken).ConfigureAwait(false); result.HasMetadata = true; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs index d92336624..ba18c542f 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs @@ -65,7 +65,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV // TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here var episodeResult = await _tmdbClientManager - .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, null, null, cancellationToken) + .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, series.DisplayOrder, null, null, cancellationToken) .ConfigureAwait(false); var stills = episodeResult?.Images?.Stills; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index b455e5634..8ec8f6464 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV } var episodeResult = await _tmdbClientManager - .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) + .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken) .ConfigureAwait(false); if (episodeResult == null) @@ -121,9 +121,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV CommunityRating = Convert.ToSingle(episodeResult.VoteAverage) }; - if (!string.IsNullOrEmpty(episodeResult.ExternalIds?.TvdbId)) + var externalIds = episodeResult.ExternalIds; + if (!string.IsNullOrEmpty(externalIds?.TvdbId)) { - item.SetProviderId(MetadataProvider.Tvdb, episodeResult.ExternalIds.TvdbId); + item.SetProviderId(MetadataProvider.Tvdb, externalIds.TvdbId); + } + + if (!string.IsNullOrEmpty(externalIds?.ImdbId)) + { + item.SetProviderId(MetadataProvider.Imdb, externalIds.ImdbId); + } + + if (!string.IsNullOrEmpty(externalIds?.TvrageId)) + { + item.SetProviderId(MetadataProvider.TvRage, externalIds.TvrageId); } if (episodeResult.Videos?.Results != null) diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs index a96fc8ed6..326c116b3 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs @@ -54,7 +54,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV if (string.IsNullOrEmpty(tmdbId)) { - return null; + return Enumerable.Empty<RemoteImageInfo>(); } var language = item.GetPreferredMetadataLanguage(); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index 496e1ae25..da76345b5 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs @@ -300,7 +300,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV if (ourRelease != null) { - series.OfficialRating = ourRelease.Rating; + series.OfficialRating = TmdbUtils.BuildParentalRating(ourRelease.Iso_3166_1, ourRelease.Rating); } else if (usRelease != null) { diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs index bf0f027fc..79ec6139d 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs @@ -125,7 +125,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb tmdbId, language: TmdbUtils.NormalizeLanguage(language), includeImageLanguage: imageLanguages, - extraMethods: TvShowMethods.Credits | TvShowMethods.Images | TvShowMethods.Keywords | TvShowMethods.ExternalIds | TvShowMethods.Videos | TvShowMethods.ContentRatings, + extraMethods: TvShowMethods.Credits | TvShowMethods.Images | TvShowMethods.Keywords | TvShowMethods.ExternalIds | TvShowMethods.Videos | TvShowMethods.ContentRatings | TvShowMethods.EpisodeGroups, cancellationToken: cancellationToken).ConfigureAwait(false); if (series != null) @@ -137,6 +137,56 @@ namespace MediaBrowser.Providers.Plugins.Tmdb } /// <summary> + /// Gets a tv show episode group from the TMDb API based on the show id and the display order. + /// </summary> + /// <param name="tvShowId">The tv show's TMDb id.</param> + /// <param name="displayOrder">The display order.</param> + /// <param name="language">The tv show's language.</param> + /// <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) + { + TvGroupType? groupType = + string.Equals(displayOrder, "absolute", StringComparison.Ordinal) ? TvGroupType.Absolute : + string.Equals(displayOrder, "dvd", StringComparison.Ordinal) ? TvGroupType.DVD : + null; + + if (groupType == null) + { + return null; + } + + var key = $"group-{tvShowId.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}"; + if (_memoryCache.TryGetValue(key, out TvGroupCollection group)) + { + return group; + } + + await EnsureClientConfigAsync().ConfigureAwait(false); + + var series = await GetSeriesAsync(tvShowId, language, imageLanguages, cancellationToken).ConfigureAwait(false); + var episodeGroupId = series?.EpisodeGroups.Results.Find(g => g.Type == groupType)?.Id; + + if (episodeGroupId == null) + { + return null; + } + + group = await _tmDbClient.GetTvEpisodeGroupsAsync( + episodeGroupId, + language: TmdbUtils.NormalizeLanguage(language), + cancellationToken: cancellationToken).ConfigureAwait(false); + + if (group != null) + { + _memoryCache.Set(key, group, TimeSpan.FromHours(CacheDurationInHours)); + } + + return group; + } + + /// <summary> /// Gets a tv season from the TMDb API based on the tv show's TMDb id. /// </summary> /// <param name="tvShowId">The tv season's TMDb id.</param> @@ -177,13 +227,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <param name="tvShowId">The tv show's TMDb id.</param> /// <param name="seasonNumber">The season number.</param> /// <param name="episodeNumber">The episode number.</param> + /// <param name="displayOrder">The display order.</param> /// <param name="language">The episode's language.</param> /// <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 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)}-{language}"; + 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)) { return episode; @@ -191,6 +242,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb await EnsureClientConfigAsync().ConfigureAwait(false); + var group = await GetSeriesGroupAsync(tvShowId, displayOrder, language, imageLanguages, cancellationToken); + if (group != 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) + { + seasonNumber = ep.SeasonNumber; + episodeNumber = ep.EpisodeNumber; + } + } + episode = await _tmDbClient.GetTvEpisodeAsync( tvShowId, seasonNumber, @@ -212,11 +276,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// Gets a person eg. cast or crew member from the TMDb API based on its TMDb id. /// </summary> /// <param name="personTmdbId">The person's TMDb id.</param> + /// <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, CancellationToken cancellationToken) + public async Task<Person> GetPersonAsync(int personTmdbId, string language, CancellationToken cancellationToken) { - var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}"; + var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}-{language}"; if (_memoryCache.TryGetValue(key, out Person person)) { return person; @@ -226,6 +291,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb person = await _tmDbClient.GetPersonAsync( personTmdbId, + TmdbUtils.NormalizeLanguage(language), PersonMethods.TvCredits | PersonMethods.MovieCredits | PersonMethods.Images | PersonMethods.ExternalIds, cancellationToken).ConfigureAwait(false); diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs index 15a44c7ed..b713736a0 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs @@ -63,19 +63,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb /// <returns>The Jellyfin person type.</returns> public static string MapCrewToPersonType(Crew crew) { - if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase) - && crew.Job.Contains("director", StringComparison.InvariantCultureIgnoreCase)) + if (crew.Department.Equals("production", StringComparison.OrdinalIgnoreCase) + && crew.Job.Contains("director", StringComparison.OrdinalIgnoreCase)) { return PersonType.Director; } - if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase) - && crew.Job.Contains("producer", StringComparison.InvariantCultureIgnoreCase)) + if (crew.Department.Equals("production", StringComparison.OrdinalIgnoreCase) + && crew.Job.Contains("producer", StringComparison.OrdinalIgnoreCase)) { return PersonType.Producer; } - if (crew.Department.Equals("writing", StringComparison.InvariantCultureIgnoreCase)) + if (crew.Department.Equals("writing", StringComparison.OrdinalIgnoreCase)) { return PersonType.Writer; } @@ -148,6 +148,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb 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 + if (string.Equals(parts[1], "CH", StringComparison.OrdinalIgnoreCase)) + { + return parts[0]; + } + language = parts[0] + "-" + parts[1].ToUpperInvariant(); } @@ -173,5 +179,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb return imageLanguage; } + + /// <summary> + /// 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> + /// <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. + var ratingPrefix = string.Equals(countryCode, "US", StringComparison.OrdinalIgnoreCase) ? string.Empty : countryCode + "-"; + var newRating = ratingPrefix + ratingValue; + + return newRating.Replace("DE-", "FSK-", StringComparison.OrdinalIgnoreCase); + } } } diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs index 5fcf6d9aa..f6153dd53 100644 --- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs +++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -171,25 +172,19 @@ namespace MediaBrowser.Providers.Studios public IEnumerable<string> GetAvailableImages(string file) { - using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)) + using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read); + using var reader = new StreamReader(fileStream); + var lines = new List<string>(); + + foreach (var line in reader.ReadAllLines()) { - using (var reader = new StreamReader(fileStream)) + if (!string.IsNullOrWhiteSpace(line)) { - var lines = new List<string>(); - - while (!reader.EndOfStream) - { - var text = reader.ReadLine(); - - if (!string.IsNullOrWhiteSpace(text)) - { - lines.Add(text); - } - } - - return lines; + lines.Add(line); } } + + return lines; } } } diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 1f3d9acff..6aacaa15d 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -187,54 +187,52 @@ namespace MediaBrowser.Providers.Subtitles { var saveInMediaFolder = libraryOptions.SaveSubtitlesWithMedia; - using (var stream = response.Stream) - using (var memoryStream = new MemoryStream()) - { - await stream.CopyToAsync(memoryStream).ConfigureAwait(false); - memoryStream.Position = 0; + using var stream = response.Stream; + using var memoryStream = new MemoryStream(); + await stream.CopyToAsync(memoryStream).ConfigureAwait(false); + memoryStream.Position = 0; - var savePaths = new List<string>(); - var saveFileName = Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLowerInvariant(); + var savePaths = new List<string>(); + var saveFileName = Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLowerInvariant(); - if (response.IsForced) - { - saveFileName += ".forced"; - } + if (response.IsForced) + { + saveFileName += ".forced"; + } - saveFileName += "." + response.Format.ToLowerInvariant(); + saveFileName += "." + response.Format.ToLowerInvariant(); - if (saveInMediaFolder) + if (saveInMediaFolder) + { + var mediaFolderPath = Path.GetFullPath(Path.Combine(video.ContainingFolderPath, saveFileName)); + // TODO: Add some error handling to the API user: return BadRequest("Could not save subtitle, bad path."); + if (mediaFolderPath.StartsWith(video.ContainingFolderPath, StringComparison.Ordinal)) { - var mediaFolderPath = Path.GetFullPath(Path.Combine(video.ContainingFolderPath, saveFileName)); - // TODO: Add some error handling to the API user: return BadRequest("Could not save subtitle, bad path."); - if (mediaFolderPath.StartsWith(video.ContainingFolderPath)) - { - savePaths.Add(mediaFolderPath); - } + savePaths.Add(mediaFolderPath); } + } - var internalPath = Path.GetFullPath(Path.Combine(video.GetInternalMetadataPath(), saveFileName)); + var internalPath = Path.GetFullPath(Path.Combine(video.GetInternalMetadataPath(), saveFileName)); - // TODO: Add some error to the user: return BadRequest("Could not save subtitle, bad path."); - if (internalPath.StartsWith(video.GetInternalMetadataPath())) - { - savePaths.Add(internalPath); - } + // TODO: Add some error to the user: return BadRequest("Could not save subtitle, bad path."); + if (internalPath.StartsWith(video.GetInternalMetadataPath(), StringComparison.Ordinal)) + { + savePaths.Add(internalPath); + } - if (savePaths.Count > 0) - { - await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false); - } - else - { - _logger.LogError("An uploaded subtitle could not be saved because the resulting paths were invalid."); - } + if (savePaths.Count > 0) + { + await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false); + } + else + { + _logger.LogError("An uploaded subtitle could not be saved because the resulting paths were invalid."); } } private async Task TrySaveToFiles(Stream stream, List<string> savePaths) { - Exception exceptionToThrow = null; + List<Exception> exs = null; foreach (var savePath in savePaths) { @@ -247,19 +245,14 @@ namespace MediaBrowser.Providers.Subtitles Directory.CreateDirectory(Path.GetDirectoryName(savePath)); // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . - using (var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, FileStreamBufferSize, true)) - { - await stream.CopyToAsync(fs).ConfigureAwait(false); - } + using var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, FileStreamBufferSize, true); + await stream.CopyToAsync(fs).ConfigureAwait(false); return; } catch (Exception ex) { - if (exceptionToThrow == null) - { - exceptionToThrow = ex; - } + (exs ??= new List<Exception>()).Add(ex); } finally { @@ -269,9 +262,9 @@ namespace MediaBrowser.Providers.Subtitles stream.Position = 0; } - if (exceptionToThrow != null) + if (exs != null) { - throw exceptionToThrow; + throw new AggregateException(exs); } } |
