aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Providers
diff options
context:
space:
mode:
authorTommaso Stocchi <tommasostocchi@outlook.com>2021-06-03 17:15:32 +0200
committerGitHub <noreply@github.com>2021-06-03 17:15:32 +0200
commit2b232df07ff1e6b82005deb9e2797260fdd48b8b (patch)
treebafa3828f2299d8e2ff23faef415871d7818ad3a /MediaBrowser.Providers
parentdc261b815f4ce5fbace33e787902636c43618881 (diff)
parentb060d9d0f1b3dac523288a3aaf182f7e35cf875c (diff)
Merge branch 'master' into bug/authorization-header-issue
Diffstat (limited to 'MediaBrowser.Providers')
-rw-r--r--MediaBrowser.Providers/Manager/ImageSaver.cs13
-rw-r--r--MediaBrowser.Providers/Manager/ItemImageProvider.cs1
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs84
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs53
-rw-r--r--MediaBrowser.Providers/Manager/ProviderUtils.cs2
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj11
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs4
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs5
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs16
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs4
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs135
-rw-r--r--MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs12
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs14
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs12
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs14
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs82
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs175
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs8
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs9
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs20
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs82
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/Plugin.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs11
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs130
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonImageProvider.cs48
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/People/TmdbPersonProvider.cs21
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs29
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs1
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs91
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs81
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs45
-rw-r--r--MediaBrowser.Providers/Properties/AssemblyInfo.cs2
-rw-r--r--MediaBrowser.Providers/Studios/StudiosImageProvider.cs27
-rw-r--r--MediaBrowser.Providers/Subtitles/SubtitleManager.cs64
-rw-r--r--MediaBrowser.Providers/TV/SeriesMetadataService.cs133
43 files changed, 836 insertions, 630 deletions
diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs
index 19a42d506c..fb1d4f4906 100644
--- a/MediaBrowser.Providers/Manager/ImageSaver.cs
+++ b/MediaBrowser.Providers/Manager/ImageSaver.cs
@@ -102,10 +102,8 @@ namespace MediaBrowser.Providers.Manager
{
saveLocally = false;
- var season = item as Season;
-
// If season is virtual under a physical series, save locally if using compatible convention
- if (season != null && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Compatible)
+ if (item is Season season && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Compatible)
{
var series = season.Series;
@@ -138,7 +136,7 @@ namespace MediaBrowser.Providers.Manager
var memoryStream = new MemoryStream();
await using (source.ConfigureAwait(false))
{
- await source.CopyToAsync(memoryStream).ConfigureAwait(false);
+ await source.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false);
}
source = memoryStream;
@@ -174,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;
@@ -263,7 +263,8 @@ namespace MediaBrowser.Providers.Manager
_fileSystem.SetAttributes(path, false, false);
- await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
+ // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
+ await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
{
await source.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index ffc6889fa2..4471a25b2f 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 8b3ca17ca1..401c7e99f2 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 a20c47cf2e..dd497845d1 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
@@ -242,6 +241,7 @@ namespace MediaBrowser.Providers.Manager
languages.Add(preferredLanguage);
}
+ // TODO include [query.IncludeAllLanguages] as an argument to the providers
var tasks = providers.Select(i => GetImages(item, i, languages, cancellationToken, query.ImageType));
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
@@ -869,14 +869,14 @@ namespace MediaBrowser.Providers.Manager
}
}
}
- catch (Exception)
+#pragma warning disable CA1031 // do not catch general exception types
+ catch (Exception ex)
+#pragma warning restore CA1031 // do not catch general exception types
{
- // Logged at lower levels
+ _logger.LogError(ex, "Provider {ProviderName} failed to retrieve search results", provider.Name);
}
}
- // _logger.LogDebug("Returning search results {0}", _json.SerializeToString(resultList));
-
return resultList;
}
@@ -960,13 +960,11 @@ namespace MediaBrowser.Providers.Manager
public IEnumerable<ExternalIdInfo> GetExternalIdInfos(IHasProviderIds item)
{
return GetExternalIds(item)
- .Select(i => new ExternalIdInfo
- {
- Name = i.ProviderName,
- Key = i.Key,
- Type = i.Type,
- UrlFormatString = i.UrlFormatString
- });
+ .Select(i => new ExternalIdInfo(
+ name: i.ProviderName,
+ key: i.Key,
+ type: i.Type,
+ urlFormatString: i.UrlFormatString));
}
/// <inheritdoc/>
@@ -1023,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)
{
@@ -1075,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/Manager/ProviderUtils.cs b/MediaBrowser.Providers/Manager/ProviderUtils.cs
index 5621d2b86c..e5aa64b281 100644
--- a/MediaBrowser.Providers/Manager/ProviderUtils.cs
+++ b/MediaBrowser.Providers/Manager/ProviderUtils.cs
@@ -55,7 +55,7 @@ namespace MediaBrowser.Providers.Manager
}
}
- if (replaceData || !target.CommunityRating.HasValue || (source.CommunityRating.HasValue && string.Equals(sourceResult.Provider, "The Open Movie Database", StringComparison.OrdinalIgnoreCase)))
+ if (replaceData || !target.CommunityRating.HasValue)
{
target.CommunityRating = source.CommunityRating;
}
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index accdea36e4..cdb07a15da 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="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="TvDbSharper" Version="3.2.2" />
+ <PackageReference Include="TMDbLib" Version="1.8.1" />
</ItemGroup>
<PropertyGroup>
@@ -30,20 +30,17 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'">true</TreatWarningsAsErrors>
+ <AnalysisMode Condition=" '$(Configuration)' == 'Debug'">AllEnabledByDefault</AnalysisMode>
+ <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
- </PropertyGroup>
-
<ItemGroup>
<None Remove="Plugins\AudioDb\Configuration\config.html" />
<EmbeddedResource Include="Plugins\AudioDb\Configuration\config.html" />
diff --git a/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs b/MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
index 64ad1bddfe..03e45fb869 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/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
index 9454636669..cf271e7db1 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
@@ -111,6 +111,11 @@ namespace MediaBrowser.Providers.MediaInfo
audio.Name = data.Name;
}
+ if (!string.IsNullOrEmpty(data.ForcedSortName))
+ {
+ audio.ForcedSortName = data.ForcedSortName;
+ }
+
if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast))
{
var people = new List<PersonInfo>();
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index 74849a5221..36b56f20f9 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);
@@ -394,6 +391,12 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
+ if (video is MusicVideo musicVideo)
+ {
+ musicVideo.Album = data.Album;
+ musicVideo.Artists = data.Artists;
+ }
+
if (data.ProductionYear.HasValue)
{
if (!video.ProductionYear.HasValue || isFullRefresh)
@@ -436,6 +439,11 @@ namespace MediaBrowser.Providers.MediaInfo
video.Name = data.Name;
}
}
+
+ if (!string.IsNullOrWhiteSpace(data.ForcedSortName))
+ {
+ video.ForcedSortName = data.ForcedSortName;
+ }
}
// If we don't have a ProductionYear try and get it from PremiereDate
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
index 912aedb0db..44ab5aa5b9 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 e9f999c6d0..b086ef07bb 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 c36c3af6a7..30af6710ab 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 293087da7a..85a28747f5 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumImageProvider.cs
@@ -1,9 +1,12 @@
#pragma warning disable CS1591
using System.Collections.Generic;
+using System.IO;
using System.Net.Http;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
@@ -11,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
{
@@ -19,13 +21,12 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
{
private readonly IServerConfigurationManager _config;
private readonly IHttpClientFactory _httpClientFactory;
- private readonly IJsonSerializer _json;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
- public AudioDbAlbumImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IJsonSerializer json)
+ public AudioDbAlbumImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory)
{
_config = config;
_httpClientFactory = httpClientFactory;
- _json = json;
}
/// <inheritdoc />
@@ -56,7 +57,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
var path = AudioDbAlbumProvider.GetAlbumInfoPath(_config.ApplicationPaths, id);
- var obj = _json.DeserializeFromFile<AudioDbAlbumProvider.RootObject>(path);
+ await using FileStream jsonStream = File.OpenRead(path);
+ var obj = await JsonSerializer.DeserializeAsync<AudioDbAlbumProvider.RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (obj != null && obj.album != null && obj.album.Count > 0)
{
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
index 97bba10baa..25bb3f9ce3 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AlbumProvider.cs
@@ -6,10 +6,12 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
@@ -17,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
@@ -27,16 +28,15 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly IHttpClientFactory _httpClientFactory;
- private readonly IJsonSerializer _json;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
public static AudioDbAlbumProvider Current;
- public AudioDbAlbumProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory, IJsonSerializer json)
+ public AudioDbAlbumProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory)
{
_config = config;
_fileSystem = fileSystem;
_httpClientFactory = httpClientFactory;
- _json = json;
Current = this;
}
@@ -64,7 +64,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
var path = GetAlbumInfoPath(_config.ApplicationPaths, id);
- var obj = _json.DeserializeFromFile<RootObject>(path);
+ await using FileStream jsonStream = File.OpenRead(path);
+ var obj = await JsonSerializer.DeserializeAsync<RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (obj != null && obj.album != null && obj.album.Count > 0)
{
@@ -169,7 +170,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
+ // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
+ await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true);
await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
index d250acfa84..db8536cc92 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistImageProvider.cs
@@ -1,9 +1,12 @@
#pragma warning disable CS1591
using System.Collections.Generic;
+using System.IO;
using System.Net.Http;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
@@ -11,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
{
@@ -19,12 +21,11 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
{
private readonly IServerConfigurationManager _config;
private readonly IHttpClientFactory _httpClientFactory;
- private readonly IJsonSerializer _json;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
- public AudioDbArtistImageProvider(IServerConfigurationManager config, IJsonSerializer json, IHttpClientFactory httpClientFactory)
+ public AudioDbArtistImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory)
{
_config = config;
- _json = json;
_httpClientFactory = httpClientFactory;
}
@@ -58,7 +59,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
var path = AudioDbArtistProvider.GetArtistInfoPath(_config.ApplicationPaths, id);
- var obj = _json.DeserializeFromFile<AudioDbArtistProvider.RootObject>(path);
+ await using FileStream jsonStream = File.OpenRead(path);
+ var obj = await JsonSerializer.DeserializeAsync<AudioDbArtistProvider.RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (obj != null && obj.artists != null && obj.artists.Count > 0)
{
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
index a2a03e1f9a..cbb61fa353 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/ArtistProvider.cs
@@ -5,10 +5,12 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Json;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
@@ -16,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
@@ -29,14 +30,13 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly IHttpClientFactory _httpClientFactory;
- private readonly IJsonSerializer _json;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
- public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory, IJsonSerializer json)
+ public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory)
{
_config = config;
_fileSystem = fileSystem;
_httpClientFactory = httpClientFactory;
- _json = json;
Current = this;
}
@@ -65,7 +65,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
var path = GetArtistInfoPath(_config.ApplicationPaths, id);
- var obj = _json.DeserializeFromFile<RootObject>(path);
+ await using FileStream jsonStream = File.OpenRead(path);
+ var obj = await JsonSerializer.DeserializeAsync<RootObject>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (obj != null && obj.artists != null && obj.artists.Count > 0)
{
@@ -153,7 +154,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
Directory.CreateDirectory(Path.GetDirectoryName(path));
- await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
+ // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
+ await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, true);
await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs
index b5bd72ff0d..ba0d7b5697 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/Plugin.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/ArtistProvider.cs
index ce93924026..2eab95294a 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 ef7933b1a6..0023d59594 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)
@@ -756,7 +738,10 @@ namespace MediaBrowser.Providers.Music
_stopWatchMusicBrainz.Restart();
using var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
- response = await _httpClientFactory.CreateClient(NamedClient.MusicBrainz).SendAsync(request).ConfigureAwait(false);
+ response = await _httpClientFactory
+ .CreateClient(NamedClient.MusicBrainz)
+ .SendAsync(request, cancellationToken)
+ .ConfigureAwait(false);
// We retry a finite number of times, and only whilst MB is indicating 503 (throttling).
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
index 90266e4409..43bd3a472f 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
index bfc840ea59..24ef80a35d 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbEpisodeProvider.cs
@@ -12,13 +12,11 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Plugins.Omdb
{
public class OmdbEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>, IHasOrder
{
- private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClientFactory _httpClientFactory;
private readonly OmdbItemProvider _itemProvider;
private readonly IFileSystem _fileSystem;
@@ -26,19 +24,17 @@ namespace MediaBrowser.Providers.Plugins.Omdb
private readonly IApplicationHost _appHost;
public OmdbEpisodeProvider(
- IJsonSerializer jsonSerializer,
IApplicationHost appHost,
IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager,
IFileSystem fileSystem,
IServerConfigurationManager configurationManager)
{
- _jsonSerializer = jsonSerializer;
_httpClientFactory = httpClientFactory;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_appHost = appHost;
- _itemProvider = new OmdbItemProvider(jsonSerializer, _appHost, httpClientFactory, libraryManager, fileSystem, configurationManager);
+ _itemProvider = new OmdbItemProvider(_appHost, httpClientFactory, libraryManager, fileSystem, configurationManager);
}
// After TheTvDb
@@ -69,7 +65,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
{
if (info.IndexNumber.HasValue && info.ParentIndexNumber.HasValue)
{
- result.HasMetadata = await new OmdbProvider(_jsonSerializer, _httpClientFactory, _fileSystem, _appHost, _configurationManager)
+ result.HasMetadata = await new OmdbProvider(_httpClientFactory, _fileSystem, _appHost, _configurationManager)
.FetchEpisodeData(result, info.IndexNumber.Value, info.ParentIndexNumber.Value, info.GetProviderId(MetadataProvider.Imdb), seriesImdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
}
}
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
index 8f4240dc11..df67aff31c 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbImageProvider.cs
@@ -1,8 +1,8 @@
#pragma warning disable CS1591
using System.Collections.Generic;
-using System.Net.Http;
using System.Globalization;
+using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
@@ -15,21 +15,18 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Plugins.Omdb
{
public class OmdbImageProvider : IRemoteImageProvider, IHasOrder
{
private readonly IHttpClientFactory _httpClientFactory;
- private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly IApplicationHost _appHost;
- public OmdbImageProvider(IJsonSerializer jsonSerializer, IApplicationHost appHost, IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IServerConfigurationManager configurationManager)
+ public OmdbImageProvider(IApplicationHost appHost, IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IServerConfigurationManager configurationManager)
{
- _jsonSerializer = jsonSerializer;
_httpClientFactory = httpClientFactory;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
@@ -56,7 +53,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
var list = new List<RemoteImageInfo>();
- var provider = new OmdbProvider(_jsonSerializer, _httpClientFactory, _fileSystem, _appHost, _configurationManager);
+ var provider = new OmdbProvider(_httpClientFactory, _fileSystem, _appHost, _configurationManager);
if (!string.IsNullOrWhiteSpace(imdbId))
{
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
index 43d8af75f0..428b0ded11 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs
@@ -6,9 +6,12 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
+using MediaBrowser.Common.Json;
+using MediaBrowser.Common.Json.Converters;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
@@ -19,34 +22,35 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Plugins.Omdb
{
public class OmdbItemProvider : IRemoteMetadataProvider<Series, SeriesInfo>,
IRemoteMetadataProvider<Movie, MovieInfo>, IRemoteMetadataProvider<Trailer, TrailerInfo>, IHasOrder
{
- private readonly IJsonSerializer _jsonSerializer;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly IApplicationHost _appHost;
+ private readonly JsonSerializerOptions _jsonOptions;
public OmdbItemProvider(
- IJsonSerializer jsonSerializer,
IApplicationHost appHost,
IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager,
IFileSystem fileSystem,
IServerConfigurationManager configurationManager)
{
- _jsonSerializer = jsonSerializer;
_httpClientFactory = httpClientFactory;
_libraryManager = libraryManager;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_appHost = appHost;
+
+ _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options);
+ _jsonOptions.Converters.Add(new JsonOmdbNotAvailableStringConverter());
+ _jsonOptions.Converters.Add(new JsonOmdbNotAvailableInt32Converter());
}
public string Name => "The Open Movie Database";
@@ -138,7 +142,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
if (isSearch)
{
- var searchResultList = await _jsonSerializer.DeserializeFromStreamAsync<SearchResultList>(stream).ConfigureAwait(false);
+ var searchResultList = await JsonSerializer.DeserializeAsync<SearchResultList>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (searchResultList != null && searchResultList.Search != null)
{
resultList.AddRange(searchResultList.Search);
@@ -146,7 +150,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
}
else
{
- var result = await _jsonSerializer.DeserializeFromStreamAsync<SearchResult>(stream).ConfigureAwait(false);
+ var result = await JsonSerializer.DeserializeAsync<SearchResult>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (string.Equals(result.Response, "true", StringComparison.OrdinalIgnoreCase))
{
resultList.Add(result);
@@ -221,7 +225,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
result.Item.SetProviderId(MetadataProvider.Imdb, imdbId);
result.HasMetadata = true;
- await new OmdbProvider(_jsonSerializer, _httpClientFactory, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
+ await new OmdbProvider(_httpClientFactory, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
}
return result;
@@ -253,7 +257,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
result.Item.SetProviderId(MetadataProvider.Imdb, imdbId);
result.HasMetadata = true;
- await new OmdbProvider(_jsonSerializer, _httpClientFactory, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
+ await new OmdbProvider(_httpClientFactory, _fileSystem, _appHost, _configurationManager).Fetch(result, imdbId, info.MetadataLanguage, info.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
}
return result;
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
index e6c6050720..46d3038905 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
@@ -6,36 +6,40 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
-using System.Text;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
+using MediaBrowser.Common.Json;
+using MediaBrowser.Common.Json.Converters;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Plugins.Omdb
{
public class OmdbProvider
{
- private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _configurationManager;
private readonly IHttpClientFactory _httpClientFactory;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IApplicationHost _appHost;
+ private readonly JsonSerializerOptions _jsonOptions;
- public OmdbProvider(IJsonSerializer jsonSerializer, IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IApplicationHost appHost, IServerConfigurationManager configurationManager)
+ public OmdbProvider(IHttpClientFactory httpClientFactory, IFileSystem fileSystem, IApplicationHost appHost, IServerConfigurationManager configurationManager)
{
- _jsonSerializer = jsonSerializer;
_httpClientFactory = httpClientFactory;
_fileSystem = fileSystem;
_configurationManager = configurationManager;
_appHost = appHost;
+
+ _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options);
+ _jsonOptions.Converters.Add(new JsonOmdbNotAvailableStringConverter());
+ _jsonOptions.Converters.Add(new JsonOmdbNotAvailableInt32Converter());
}
public async Task Fetch<T>(MetadataResult<T> itemResult, string imdbId, string language, string country, CancellationToken cancellationToken)
@@ -208,39 +212,15 @@ namespace MediaBrowser.Providers.Plugins.Omdb
internal async Task<RootObject> GetRootObject(string imdbId, CancellationToken cancellationToken)
{
var path = await EnsureItemInfo(imdbId, cancellationToken).ConfigureAwait(false);
-
- string resultString;
-
- using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
- {
- using (var reader = new StreamReader(stream, new UTF8Encoding(false)))
- {
- resultString = reader.ReadToEnd();
- resultString = resultString.Replace("\"N/A\"", "\"\"");
- }
- }
-
- var result = _jsonSerializer.DeserializeFromString<RootObject>(resultString);
- return result;
+ await using var stream = File.OpenRead(path);
+ return await JsonSerializer.DeserializeAsync<RootObject>(stream, _jsonOptions, cancellationToken);
}
internal async Task<SeasonRootObject> GetSeasonRootObject(string imdbId, int seasonId, CancellationToken cancellationToken)
{
var path = await EnsureSeasonInfo(imdbId, seasonId, cancellationToken).ConfigureAwait(false);
-
- string resultString;
-
- using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
- {
- using (var reader = new StreamReader(stream, new UTF8Encoding(false)))
- {
- resultString = reader.ReadToEnd();
- resultString = resultString.Replace("\"N/A\"", "\"\"");
- }
- }
-
- var result = _jsonSerializer.DeserializeFromString<SeasonRootObject>(resultString);
- return result;
+ await using var stream = File.OpenRead(path);
+ return await JsonSerializer.DeserializeAsync<SeasonRootObject>(stream, _jsonOptions, cancellationToken);
}
internal static bool IsValidSeries(Dictionary<string, string> seriesProviderIds)
@@ -290,6 +270,10 @@ namespace MediaBrowser.Providers.Plugins.Omdb
return path;
}
}
+ else
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
+ }
var url = GetOmdbUrl(
string.Format(
@@ -297,11 +281,9 @@ namespace MediaBrowser.Providers.Plugins.Omdb
"i={0}&plot=short&tomatoes=true&r=json",
imdbParam));
- using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<RootObject>(stream).ConfigureAwait(false);
- Directory.CreateDirectory(Path.GetDirectoryName(path));
- _jsonSerializer.SerializeToFile(rootObject, path);
+ var rootObject = await GetDeserializedOmdbResponse<RootObject>(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
+ await using FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
+ await JsonSerializer.SerializeAsync(jsonFileStream, rootObject, _jsonOptions, cancellationToken).ConfigureAwait(false);
return path;
}
@@ -327,6 +309,10 @@ namespace MediaBrowser.Providers.Plugins.Omdb
return path;
}
}
+ else
+ {
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
+ }
var url = GetOmdbUrl(
string.Format(
@@ -335,15 +321,21 @@ namespace MediaBrowser.Providers.Plugins.Omdb
imdbParam,
seasonId));
- using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var rootObject = await _jsonSerializer.DeserializeFromStreamAsync<SeasonRootObject>(stream).ConfigureAwait(false);
- Directory.CreateDirectory(Path.GetDirectoryName(path));
- _jsonSerializer.SerializeToFile(rootObject, path);
+ var rootObject = await GetDeserializedOmdbResponse<SeasonRootObject>(_httpClientFactory.CreateClient(NamedClient.Default), url, cancellationToken).ConfigureAwait(false);
+ await using FileStream jsonFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
+ await JsonSerializer.SerializeAsync(jsonFileStream, rootObject, _jsonOptions, cancellationToken).ConfigureAwait(false);
return path;
}
+ public async Task<T> GetDeserializedOmdbResponse<T>(HttpClient httpClient, string url, CancellationToken cancellationToken)
+ {
+ using var response = await GetOmdbResponse(httpClient, url, cancellationToken).ConfigureAwait(false);
+ await using Stream content = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+
+ return await JsonSerializer.DeserializeAsync<T>(content, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ }
+
public static Task<HttpResponseMessage> GetOmdbResponse(HttpClient httpClient, string url, CancellationToken cancellationToken)
{
return httpClient.GetAsync(url, cancellationToken);
@@ -465,7 +457,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
public string seriesID { get; set; }
- public int Season { get; set; }
+ public int? Season { get; set; }
public int? totalSeasons { get; set; }
@@ -526,7 +518,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
public string Response { get; set; }
- public int Episode { get; set; }
+ public int? Episode { get; set; }
public float? GetRottenTomatoScore()
{
diff --git a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs
index 41ca561643..d7f6781e50 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/Plugin.cs
@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CS1591
using System;
using System.Collections.Generic;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
index df1e12240d..5ad61c567f 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetImageProvider.cs
@@ -58,7 +58,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
var language = item.GetPreferredMetadataLanguage();
- var collection = await _tmdbClientManager.GetCollectionAsync(tmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken).ConfigureAwait(false);
+ // 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)
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
index fcd8e614c1..ca1af6c499 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/BoxSets/TmdbBoxSetProvider.cs
@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
@@ -19,11 +20,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly TmdbClientManager _tmdbClientManager;
+ private readonly ILibraryManager _libraryManager;
- public TmdbBoxSetProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager)
+ public TmdbBoxSetProvider(IHttpClientFactory httpClientFactory, TmdbClientManager tmdbClientManager, ILibraryManager libraryManager)
{
_httpClientFactory = httpClientFactory;
_tmdbClientManager = tmdbClientManager;
+ _libraryManager = libraryManager;
}
public string Name => TmdbUtils.ProviderName;
@@ -83,7 +86,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
// We don't already have an Id, need to fetch it
if (tmdbId <= 0)
{
- var searchResults = await _tmdbClientManager.SearchCollectionAsync(id.Name, language, cancellationToken).ConfigureAwait(false);
+ // ParseName is required here.
+ // Caller provides the filename with extension stripped and NOT the parsed filename
+ var parsedName = _libraryManager.ParseName(id.Name);
+ var cleanedName = TmdbUtils.CleanName(parsedName.Name);
+ var searchResults = await _tmdbClientManager.SearchCollectionAsync(cleanedName, language, cancellationToken).ConfigureAwait(false);
if (searchResults != null && searchResults.Count > 0)
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs
index dac9e961c6..f34d689c1a 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieImageProvider.cs
@@ -73,8 +73,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
return Enumerable.Empty<RemoteImageInfo>();
}
+ // TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here
var movie = await _tmdbClientManager
- .GetMovieAsync(movieTmdbId, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken)
+ .GetMovieAsync(movieTmdbId, null, null, cancellationToken)
.ConfigureAwait(false);
if (movie?.Images == null)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
index bcf9459ef8..4a0884c079 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
@@ -14,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
{
@@ -43,63 +45,89 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(MovieInfo searchInfo, CancellationToken cancellationToken)
{
- var tmdbId = Convert.ToInt32(searchInfo.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
-
- if (tmdbId == 0)
+ if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var id))
{
- var movieResults = await _tmdbClientManager
- .SearchMovieAsync(searchInfo.Name, searchInfo.MetadataLanguage, cancellationToken)
+ var movie = await _tmdbClientManager
+ .GetMovieAsync(
+ int.Parse(id, CultureInfo.InvariantCulture),
+ searchInfo.MetadataLanguage,
+ TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
+ cancellationToken)
.ConfigureAwait(false);
- var remoteSearchResults = new List<RemoteSearchResult>();
- for (var i = 0; i < movieResults.Count; i++)
+
+ var remoteResult = new RemoteSearchResult
{
- var movieResult = movieResults[i];
- var remoteSearchResult = new RemoteSearchResult
- {
- Name = movieResult.Title ?? movieResult.OriginalTitle,
- ImageUrl = _tmdbClientManager.GetPosterUrl(movieResult.PosterPath),
- Overview = movieResult.Overview,
- SearchProviderName = Name
- };
+ 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;
+ }
- var releaseDate = movieResult.ReleaseDate?.ToUniversalTime();
- remoteSearchResult.PremiereDate = releaseDate;
- remoteSearchResult.ProductionYear = releaseDate?.Year;
+ remoteResult.SetProviderId(MetadataProvider.Tmdb, movie.Id.ToString(CultureInfo.InvariantCulture));
- remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, movieResult.Id.ToString(CultureInfo.InvariantCulture));
- remoteSearchResults.Add(remoteSearchResult);
+ if (!string.IsNullOrWhiteSpace(movie.ImdbId))
+ {
+ remoteResult.SetProviderId(MetadataProvider.Imdb, movie.ImdbId);
}
- return remoteSearchResults;
+ return new[] { remoteResult };
}
- var movie = await _tmdbClientManager
- .GetMovieAsync(tmdbId, searchInfo.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage), cancellationToken)
- .ConfigureAwait(false);
-
- var remoteResult = new RemoteSearchResult
+ IReadOnlyList<SearchMovie> movieResults;
+ if (searchInfo.TryGetProviderId(MetadataProvider.Imdb, out id))
{
- Name = movie.Title ?? movie.OriginalTitle,
- SearchProviderName = Name,
- ImageUrl = _tmdbClientManager.GetPosterUrl(movie.PosterPath),
- Overview = movie.Overview
- };
-
- if (movie.ReleaseDate != null)
+ var result = await _tmdbClientManager.FindByExternalIdAsync(
+ id,
+ FindExternalSource.Imdb,
+ TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
+ cancellationToken).ConfigureAwait(false);
+ movieResults = result.MovieResults;
+ }
+ else if (searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out id))
{
- var releaseDate = movie.ReleaseDate.Value.ToUniversalTime();
- remoteResult.PremiereDate = releaseDate;
- remoteResult.ProductionYear = releaseDate.Year;
+ var result = await _tmdbClientManager.FindByExternalIdAsync(
+ id,
+ FindExternalSource.TvDb,
+ TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
+ cancellationToken).ConfigureAwait(false);
+ movieResults = result.MovieResults;
+ }
+ else
+ {
+ movieResults = await _tmdbClientManager
+ .SearchMovieAsync(searchInfo.Name, searchInfo.Year ?? 0, searchInfo.MetadataLanguage, cancellationToken)
+ .ConfigureAwait(false);
}
- remoteResult.SetProviderId(MetadataProvider.Tmdb, movie.Id.ToString(CultureInfo.InvariantCulture));
-
- if (!string.IsNullOrWhiteSpace(movie.ImdbId))
+ var len = movieResults.Count;
+ var remoteSearchResults = new RemoteSearchResult[len];
+ for (var i = 0; i < len; i++)
{
- remoteResult.SetProviderId(MetadataProvider.Imdb, movie.ImdbId);
+ var movieResult = movieResults[i];
+ var remoteSearchResult = new RemoteSearchResult
+ {
+ Name = movieResult.Title ?? movieResult.OriginalTitle,
+ ImageUrl = _tmdbClientManager.GetPosterUrl(movieResult.PosterPath),
+ Overview = movieResult.Overview,
+ SearchProviderName = Name
+ };
+
+ var releaseDate = movieResult.ReleaseDate?.ToUniversalTime();
+ remoteSearchResult.PremiereDate = releaseDate;
+ remoteSearchResult.ProductionYear = releaseDate?.Year;
+
+ remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, movieResult.Id.ToString(CultureInfo.InvariantCulture));
+ remoteSearchResults[i] = remoteSearchResult;
}
- return new[] { remoteResult };
+ return remoteSearchResults;
}
public async Task<MetadataResult<Movie>> GetMetadata(MovieInfo info, CancellationToken cancellationToken)
@@ -112,7 +140,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
// ParseName is required here.
// Caller provides the filename with extension stripped and NOT the parsed filename
var parsedName = _libraryManager.ParseName(info.Name);
- var searchResults = await _tmdbClientManager.SearchMovieAsync(parsedName.Name, parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+ var cleanedName = TmdbUtils.CleanName(parsedName.Name);
+ var searchResults = await _tmdbClientManager.SearchMovieAsync(cleanedName, info.Year ?? parsedName.Year ?? 0, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
if (searchResults.Count > 0)
{
@@ -120,6 +149,15 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
}
}
+ if (string.IsNullOrEmpty(tmdbId) && !string.IsNullOrEmpty(imdbId))
+ {
+ var movieResultFromImdbId = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+ if (movieResultFromImdbId?.MovieResults.Count > 0)
+ {
+ tmdbId = movieResultFromImdbId.MovieResults[0].Id.ToString();
+ }
+ }
+
if (string.IsNullOrEmpty(tmdbId))
{
return new MetadataResult<Movie>();
@@ -137,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()
@@ -167,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 3f57c4bc4c..e4c908a62c 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 4384c203e5..6db550b1d0 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 3b7a0b254e..ba18c542fe 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
@@ -63,8 +63,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var language = item.GetPreferredMetadataLanguage();
+ // 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, language, TmdbUtils.GetImageLanguagesParam(language), 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 93998a1102..8ec8f64641 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)
@@ -111,24 +111,31 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var item = new Episode
{
- Name = info.Name,
IndexNumber = info.IndexNumber,
ParentIndexNumber = info.ParentIndexNumber,
- IndexNumberEnd = info.IndexNumberEnd
+ IndexNumberEnd = info.IndexNumberEnd,
+ Name = episodeResult.Name,
+ PremiereDate = episodeResult.AirDate,
+ ProductionYear = episodeResult.AirDate?.Year,
+ Overview = episodeResult.Overview,
+ 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);
}
- item.PremiereDate = episodeResult.AirDate;
- item.ProductionYear = episodeResult.AirDate?.Year;
-
- item.Name = episodeResult.Name;
- item.Overview = episodeResult.Overview;
+ if (!string.IsNullOrEmpty(externalIds?.ImdbId))
+ {
+ item.SetProviderId(MetadataProvider.Imdb, externalIds.ImdbId);
+ }
- item.CommunityRating = Convert.ToSingle(episodeResult.VoteAverage);
+ if (!string.IsNullOrEmpty(externalIds?.TvrageId))
+ {
+ item.SetProviderId(MetadataProvider.TvRage, externalIds.TvrageId);
+ }
if (episodeResult.Videos?.Results != null)
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
index f4ed480aef..0d23c7872f 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonImageProvider.cs
@@ -52,8 +52,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var language = item.GetPreferredMetadataLanguage();
+ // TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here
var seasonResult = await _tmdbClientManager
- .GetSeasonAsync(seriesTmdbId, season.IndexNumber.Value, language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken)
+ .GetSeasonAsync(seriesTmdbId, season.IndexNumber.Value, null, null, cancellationToken)
.ConfigureAwait(false);
var posters = seasonResult?.Images?.Posters;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
index 6ca462474a..4c1f697632 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs
@@ -54,7 +54,6 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
result.HasMetadata = true;
result.Item = new Season
{
- Name = info.Name,
IndexNumber = seasonNumber,
Overview = seasonResult?.Overview
};
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
index d0c6b8b886..326c116b3b 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesImageProvider.cs
@@ -54,13 +54,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
if (string.IsNullOrEmpty(tmdbId))
{
- return null;
+ return Enumerable.Empty<RemoteImageInfo>();
}
var language = item.GetPreferredMetadataLanguage();
+ // TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here
var series = await _tmdbClientManager
- .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), language, TmdbUtils.GetImageLanguagesParam(language), cancellationToken)
+ .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), null, null, cancellationToken)
.ConfigureAwait(false);
if (series?.Images == null)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
index 942c85b90d..da76345b5b 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
@@ -10,6 +10,7 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
@@ -22,15 +23,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
public class TmdbSeriesProvider : IRemoteMetadataProvider<Series, SeriesInfo>, IHasOrder
{
private readonly IHttpClientFactory _httpClientFactory;
+ private readonly ILibraryManager _libraryManager;
private readonly TmdbClientManager _tmdbClientManager;
public TmdbSeriesProvider(
+ ILibraryManager libraryManager,
IHttpClientFactory httpClientFactory,
TmdbClientManager tmdbClientManager)
{
+ _libraryManager = libraryManager;
_httpClientFactory = httpClientFactory;
_tmdbClientManager = tmdbClientManager;
- Current = this;
}
public string Name => TmdbUtils.ProviderName;
@@ -38,13 +41,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
// After TheTVDB
public int Order => 1;
- internal static TmdbSeriesProvider Current { get; private set; }
-
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken)
{
- var tmdbId = searchInfo.GetProviderId(MetadataProvider.Tmdb);
-
- if (!string.IsNullOrEmpty(tmdbId))
+ if (searchInfo.TryGetProviderId(MetadataProvider.Tmdb, out var tmdbId))
{
var series = await _tmdbClientManager
.GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), searchInfo.MetadataLanguage, searchInfo.MetadataLanguage, cancellationToken)
@@ -58,9 +57,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
}
}
- var imdbId = searchInfo.GetProviderId(MetadataProvider.Imdb);
-
- if (!string.IsNullOrEmpty(imdbId))
+ if (searchInfo.TryGetProviderId(MetadataProvider.Imdb, out var imdbId))
{
var findResult = await _tmdbClientManager
.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, searchInfo.MetadataLanguage, cancellationToken)
@@ -81,9 +78,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
}
}
- var tvdbId = searchInfo.GetProviderId(MetadataProvider.Tvdb);
-
- if (!string.IsNullOrEmpty(tvdbId))
+ if (searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out var tvdbId))
{
var findResult = await _tmdbClientManager
.FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, searchInfo.MetadataLanguage, cancellationToken)
@@ -104,7 +99,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
}
}
- var tvSearchResults = await _tmdbClientManager.SearchSeriesAsync(searchInfo.Name, searchInfo.MetadataLanguage, cancellationToken)
+ var tvSearchResults = await _tmdbClientManager.SearchSeriesAsync(searchInfo.Name, searchInfo.MetadataLanguage, cancellationToken: cancellationToken)
.ConfigureAwait(false);
var remoteResults = new RemoteSearchResult[tvSearchResults.Count];
@@ -170,40 +165,32 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
var tmdbId = info.GetProviderId(MetadataProvider.Tmdb);
- if (string.IsNullOrEmpty(tmdbId))
+ if (string.IsNullOrEmpty(tmdbId) && info.TryGetProviderId(MetadataProvider.Imdb, out var imdbId))
{
- var imdbId = info.GetProviderId(MetadataProvider.Imdb);
-
- if (!string.IsNullOrEmpty(imdbId))
+ var searchResult = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+ if (searchResult?.TvResults.Count > 0)
{
- var searchResult = await _tmdbClientManager.FindByExternalIdAsync(imdbId, FindExternalSource.Imdb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
-
- if (searchResult != null)
- {
- tmdbId = searchResult.TvResults.FirstOrDefault()?.Id.ToString(CultureInfo.InvariantCulture);
- }
+ tmdbId = searchResult.TvResults[0].Id.ToString(CultureInfo.InvariantCulture);
}
}
- if (string.IsNullOrEmpty(tmdbId))
+ if (string.IsNullOrEmpty(tmdbId) && info.TryGetProviderId(MetadataProvider.Tvdb, out var tvdbId))
{
- var tvdbId = info.GetProviderId(MetadataProvider.Tvdb);
-
- if (!string.IsNullOrEmpty(tvdbId))
+ var searchResult = await _tmdbClientManager.FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+ if (searchResult?.TvResults.Count > 0)
{
- var searchResult = await _tmdbClientManager.FindByExternalIdAsync(tvdbId, FindExternalSource.TvDb, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
-
- if (searchResult != null)
- {
- tmdbId = searchResult.TvResults.FirstOrDefault()?.Id.ToString(CultureInfo.InvariantCulture);
- }
+ tmdbId = searchResult.TvResults[0].Id.ToString(CultureInfo.InvariantCulture);
}
}
if (string.IsNullOrEmpty(tmdbId))
{
result.QueriedById = false;
- var searchResults = await _tmdbClientManager.SearchSeriesAsync(info.Name, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+ // ParseName is required here.
+ // 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.SearchSeriesAsync(cleanedName, info.MetadataLanguage, info.Year ?? parsedName.Year ?? 0, cancellationToken).ConfigureAwait(false);
if (searchResults.Count > 0)
{
@@ -211,32 +198,34 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
}
}
- if (!string.IsNullOrEmpty(tmdbId))
+ if (string.IsNullOrEmpty(tmdbId))
{
- cancellationToken.ThrowIfCancellationRequested();
+ return result;
+ }
- var tvShow = await _tmdbClientManager
- .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
- .ConfigureAwait(false);
+ cancellationToken.ThrowIfCancellationRequested();
- result = new MetadataResult<Series>
- {
- Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode),
- ResultLanguage = info.MetadataLanguage ?? tvShow.OriginalLanguage
- };
+ var tvShow = await _tmdbClientManager
+ .GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
+ .ConfigureAwait(false);
- foreach (var person in GetPersons(tvShow))
- {
- result.AddPerson(person);
- }
+ result = new MetadataResult<Series>
+ {
+ Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode),
+ ResultLanguage = info.MetadataLanguage ?? tvShow.OriginalLanguage
+ };
- result.HasMetadata = result.Item != null;
+ foreach (var person in GetPersons(tvShow))
+ {
+ result.AddPerson(person);
}
+ result.HasMetadata = result.Item != null;
+
return result;
}
- private Series MapTvShowToSeries(TvShow seriesResult, string preferredCountryCode)
+ private static Series MapTvShowToSeries(TvShow seriesResult, string preferredCountryCode)
{
var series = new Series
{
@@ -311,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 2dc5cd55da..79ec6139d1 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);
@@ -278,9 +344,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// </summary>
/// <param name="name">The name of the tv show.</param>
/// <param name="language">The tv show's language.</param>
+ /// <param name="year">The year the tv show first aired.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb tv show information.</returns>
- public async Task<IReadOnlyList<SearchTv>> SearchSeriesAsync(string name, string language, CancellationToken cancellationToken)
+ 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))
@@ -291,7 +358,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
await EnsureClientConfigAsync().ConfigureAwait(false);
var searchResults = await _tmDbClient
- .SearchTvShowAsync(name, TmdbUtils.NormalizeLanguage(language), cancellationToken: cancellationToken)
+ .SearchTvShowAsync(name, TmdbUtils.NormalizeLanguage(language), firstAirDateYear: year, cancellationToken: cancellationToken)
.ConfigureAwait(false);
if (searchResults.Results.Count > 0)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
index 0e8a5baab6..b713736a0f 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Text.RegularExpressions;
using MediaBrowser.Model.Entities;
using TMDbLib.Objects.General;
@@ -12,6 +13,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// </summary>
public static class TmdbUtils
{
+ private static readonly Regex _nonWords = new (@"[\W_]+", RegexOptions.Compiled);
+
/// <summary>
/// URL of the TMDB instance to use.
/// </summary>
@@ -43,25 +46,36 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
};
/// <summary>
+ /// Cleans the name according to TMDb requirements.
+ /// </summary>
+ /// <param name="name">The name of the entity.</param>
+ /// <returns>The cleaned name.</returns>
+ public static string CleanName(string name)
+ {
+ // TMDb expects a space separated list of words make sure that is the case
+ return _nonWords.Replace(name, " ");
+ }
+
+ /// <summary>
/// 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)
{
- 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;
}
@@ -134,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();
}
@@ -159,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/Properties/AssemblyInfo.cs b/MediaBrowser.Providers/Properties/AssemblyInfo.cs
index f1c46899ce..fe4749c799 100644
--- a/MediaBrowser.Providers/Properties/AssemblyInfo.cs
+++ b/MediaBrowser.Providers/Properties/AssemblyInfo.cs
@@ -1,5 +1,6 @@
using System.Reflection;
using System.Resources;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
@@ -14,6 +15,7 @@ using System.Runtime.InteropServices;
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
+[assembly: InternalsVisibleTo("Jellyfin.Common.Tests")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
index 90e13f12f8..f6153dd532 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;
@@ -144,7 +145,7 @@ namespace MediaBrowser.Providers.Studios
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
Directory.CreateDirectory(Path.GetDirectoryName(file));
- await using var response = await httpClient.GetStreamAsync(url).ConfigureAwait(false);
+ await using var response = await httpClient.GetStreamAsync(url, cancellationToken).ConfigureAwait(false);
await using var fileStream = new FileStream(file, FileMode.Create);
await response.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
@@ -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 47e9d5ee8c..6aacaa15de 100644
--- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
+++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
@@ -187,36 +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))
{
- savePaths.Add(Path.Combine(video.ContainingFolderPath, saveFileName));
+ savePaths.Add(mediaFolderPath);
}
+ }
- savePaths.Add(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(), 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.");
+ }
}
private async Task TrySaveToFiles(Stream stream, List<string> savePaths)
{
- Exception exceptionToThrow = null;
+ List<Exception> exs = null;
foreach (var savePath in savePaths)
{
@@ -228,19 +244,15 @@ namespace MediaBrowser.Providers.Subtitles
{
Directory.CreateDirectory(Path.GetDirectoryName(savePath));
- using (var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.Read, FileStreamBufferSize, true))
- {
- await stream.CopyToAsync(fs).ConfigureAwait(false);
- }
+ // 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);
return;
}
catch (Exception ex)
{
- if (exceptionToThrow == null)
- {
- exceptionToThrow = ex;
- }
+ (exs ??= new List<Exception>()).Add(ex);
}
finally
{
@@ -250,9 +262,9 @@ namespace MediaBrowser.Providers.Subtitles
stream.Position = 0;
}
- if (exceptionToThrow != null)
+ if (exs != null)
{
- throw exceptionToThrow;
+ throw new AggregateException(exs);
}
}
diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
index c8fc568a22..9679081975 100644
--- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
@@ -1,10 +1,16 @@
#pragma warning disable CS1591
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Providers.Manager;
using Microsoft.Extensions.Logging;
@@ -13,14 +19,27 @@ namespace MediaBrowser.Providers.TV
{
public class SeriesMetadataService : MetadataService<Series, SeriesInfo>
{
+ private readonly ILocalizationManager _localizationManager;
+
public SeriesMetadataService(
IServerConfigurationManager serverConfigurationManager,
ILogger<SeriesMetadataService> logger,
IProviderManager providerManager,
IFileSystem fileSystem,
- ILibraryManager libraryManager)
+ ILibraryManager libraryManager,
+ ILocalizationManager localizationManager)
: base(serverConfigurationManager, logger, providerManager, fileSystem, libraryManager)
{
+ _localizationManager = localizationManager;
+ }
+
+ /// <inheritdoc />
+ protected override async Task AfterMetadataRefresh(Series item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
+ {
+ await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false);
+
+ RemoveObsoleteSeasons(item);
+ await FillInMissingSeasonsAsync(item, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
@@ -62,5 +81,117 @@ namespace MediaBrowser.Providers.TV
targetItem.AirDays = sourceItem.AirDays;
}
}
+
+ private void RemoveObsoleteSeasons(Series series)
+ {
+ // TODO Legacy. It's not really "physical" seasons as any virtual seasons are always converted to non-virtual in FillInMissingSeasonsAsync.
+ var physicalSeasonNumbers = new HashSet<int>();
+ var virtualSeasons = new List<Season>();
+ foreach (var existingSeason in series.Children.OfType<Season>())
+ {
+ if (existingSeason.LocationType != LocationType.Virtual && existingSeason.IndexNumber.HasValue)
+ {
+ physicalSeasonNumbers.Add(existingSeason.IndexNumber.Value);
+ }
+ else if (existingSeason.LocationType == LocationType.Virtual)
+ {
+ virtualSeasons.Add(existingSeason);
+ }
+ }
+
+ foreach (var virtualSeason in virtualSeasons)
+ {
+ var seasonNumber = virtualSeason.IndexNumber;
+ // If there's a physical season with the same number or no episodes in the season, delete it
+ if ((seasonNumber.HasValue && physicalSeasonNumbers.Contains(seasonNumber.Value))
+ || !virtualSeason.GetEpisodes().Any())
+ {
+ Logger.LogInformation("Removing virtual season {SeasonNumber} in series {SeriesName}", virtualSeason.IndexNumber, series.Name);
+
+ LibraryManager.DeleteItem(
+ virtualSeason,
+ new DeleteOptions
+ {
+ DeleteFileLocation = true
+ },
+ false);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Creates seasons for all episodes that aren't in a season folder.
+ /// If no season number can be determined, a dummy season will be created.
+ /// </summary>
+ /// <param name="series">The series.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>The async task.</returns>
+ private async Task FillInMissingSeasonsAsync(Series series, CancellationToken cancellationToken)
+ {
+ var episodesInSeriesFolder = series.GetRecursiveChildren(i => i is Episode)
+ .Cast<Episode>()
+ .Where(i => !i.IsInSeasonFolder);
+
+ List<Season> seasons = series.Children.OfType<Season>().ToList();
+
+ // Loop through the unique season numbers
+ foreach (var episode in episodesInSeriesFolder)
+ {
+ // Null season numbers will have a 'dummy' season created because seasons are always required.
+ var seasonNumber = episode.ParentIndexNumber >= 0 ? episode.ParentIndexNumber : null;
+ var existingSeason = seasons.FirstOrDefault(i => i.IndexNumber == seasonNumber);
+
+ if (existingSeason == null)
+ {
+ var season = await CreateSeasonAsync(series, seasonNumber, cancellationToken).ConfigureAwait(false);
+ seasons.Add(season);
+ }
+ else if (existingSeason.IsVirtualItem)
+ {
+ existingSeason.IsVirtualItem = false;
+ await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Creates a new season, adds it to the database by linking it to the [series] and refreshes the metadata.
+ /// </summary>
+ /// <param name="series">The series.</param>
+ /// <param name="seasonNumber">The season number.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>The newly created season.</returns>
+ private async Task<Season> CreateSeasonAsync(
+ Series series,
+ int? seasonNumber,
+ CancellationToken cancellationToken)
+ {
+ string seasonName = seasonNumber switch
+ {
+ null => _localizationManager.GetLocalizedString("NameSeasonUnknown"),
+ 0 => LibraryManager.GetLibraryOptions(series).SeasonZeroDisplayName,
+ _ => string.Format(CultureInfo.InvariantCulture, _localizationManager.GetLocalizedString("NameSeasonNumber"), seasonNumber.Value)
+ };
+
+ Logger.LogInformation("Creating Season {SeasonName} entry for {SeriesName}", seasonName, series.Name);
+
+ var season = new Season
+ {
+ Name = seasonName,
+ IndexNumber = seasonNumber,
+ Id = LibraryManager.GetNewItemId(
+ series.Id + (seasonNumber ?? -1).ToString(CultureInfo.InvariantCulture) + seasonName,
+ typeof(Season)),
+ IsVirtualItem = false,
+ SeriesId = series.Id,
+ SeriesName = series.Name
+ };
+
+ series.AddChild(season, cancellationToken);
+
+ await season.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellationToken).ConfigureAwait(false);
+
+ return season;
+ }
}
}