Merge branch 'master' into using-declarations

This commit is contained in:
Patrick Barron
2021-05-10 09:05:12 -04:00
committed by GitHub
743 changed files with 10493 additions and 5526 deletions

View File

@@ -172,7 +172,9 @@ namespace MediaBrowser.Providers.Manager
SetImagePath(item, type, imageIndex, savedPaths[0]);
// Delete the current path
if (currentImageIsLocalFile && !savedPaths.Contains(currentImagePath, StringComparer.OrdinalIgnoreCase))
if (currentImageIsLocalFile
&& !savedPaths.Contains(currentImagePath, StringComparer.OrdinalIgnoreCase)
&& (saveLocally || currentImagePath.Contains(_config.ApplicationPaths.InternalMetadataPath, StringComparison.OrdinalIgnoreCase)))
{
var currentPath = currentImagePath;
@@ -261,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);
}

View File

@@ -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(

View File

@@ -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
{

View File

@@ -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;
}
@@ -1021,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)
{
@@ -1073,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);
var task = item is MusicArtist artist
? RefreshArtist(artist, refreshItem.Item2, cancellationToken)
: RefreshItem(item, refreshItem.Item2, cancellationToken);
await task.ConfigureAwait(false);
continue;
}
var task = item is MusicArtist artist
? RefreshArtist(artist, refreshItem.Item2, cancellationToken)
: RefreshItem(item, refreshItem.Item2, cancellationToken);
await task.ConfigureAwait(false);
}
catch (OperationCanceledException)
{

View File

@@ -19,10 +19,10 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="OptimizedPriorityQueue" Version="5.0.0" />
<PackageReference Include="PlaylistsNET" Version="1.1.3" />
<PackageReference Include="TMDbLib" Version="1.7.3-alpha" />
<PackageReference Include="TMDbLib" Version="1.8.1" />
</ItemGroup>
<PropertyGroup>
@@ -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" />

View File

@@ -137,9 +137,7 @@ namespace MediaBrowser.Providers.MediaInfo
return false;
}
var audio = item as Audio;
return audio != null;
return item is Audio;
}
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -14,7 +14,6 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Plugins.AudioDb
{
@@ -22,7 +21,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
{
private readonly IServerConfigurationManager _config;
private readonly IHttpClientFactory _httpClientFactory;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
public AudioDbAlbumImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory)
{

View File

@@ -19,7 +19,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Providers.Music;
namespace MediaBrowser.Providers.Plugins.AudioDb
@@ -29,7 +28,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly IHttpClientFactory _httpClientFactory;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
public static AudioDbAlbumProvider Current;
@@ -171,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);
}

View File

@@ -14,7 +14,6 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Providers.Plugins.AudioDb
{
@@ -22,7 +21,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
{
private readonly IServerConfigurationManager _config;
private readonly IHttpClientFactory _httpClientFactory;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
public AudioDbArtistImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory)
{

View File

@@ -18,7 +18,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Providers.Music;
namespace MediaBrowser.Providers.Plugins.AudioDb
@@ -31,7 +30,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly IHttpClientFactory _httpClientFactory;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
public AudioDbArtistProvider(IServerConfigurationManager config, IFileSystem fileSystem, IHttpClientFactory httpClientFactory)
{
@@ -155,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);
}

View File

@@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
_configurationManager = configurationManager;
_appHost = appHost;
_jsonOptions = new JsonSerializerOptions(JsonDefaults.GetOptions());
_jsonOptions = new JsonSerializerOptions(JsonDefaults.Options);
_jsonOptions.Converters.Add(new JsonOmdbNotAvailableStringConverter());
_jsonOptions.Converters.Add(new JsonOmdbNotAvailableInt32Converter());
}

View File

@@ -6,9 +6,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common;
@@ -39,7 +37,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
_configurationManager = configurationManager;
_appHost = appHost;
_jsonOptions = new JsonSerializerOptions(JsonDefaults.GetOptions());
_jsonOptions = new JsonSerializerOptions(JsonDefaults.Options);
_jsonOptions.Converters.Add(new JsonOmdbNotAvailableStringConverter());
_jsonOptions.Converters.Add(new JsonOmdbNotAvailableInt32Converter());
}

View File

@@ -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)
{

View File

@@ -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)
{

View File

@@ -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)

View File

@@ -7,8 +7,6 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using TMDbLib.Objects.Find;
using TMDbLib.Objects.Search;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
@@ -16,6 +14,8 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
using TMDbLib.Objects.Find;
using TMDbLib.Objects.Search;
namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{
@@ -140,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)
{
@@ -148,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>();
@@ -165,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()

View File

@@ -49,37 +49,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>();
}
var remoteImages = new List<RemoteImageInfo>();
var language = item.GetPreferredMetadataLanguage();
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)
});
}
return remoteImages.OrderByLanguageDescending(language);
return Enumerable.Empty<RemoteImageInfo>();
}
return Enumerable.Empty<RemoteImageInfo>();
var personResult = await _tmdbClientManager.GetPersonAsync(int.Parse(personTmdbId, CultureInfo.InvariantCulture), cancellationToken).ConfigureAwait(false);
if (personResult?.Images?.Profiles == null)
{
return Enumerable.Empty<RemoteImageInfo>();
}
var remoteImages = new RemoteImageInfo[personResult.Images.Profiles.Count];
var language = item.GetPreferredMetadataLanguage();
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 remoteImages.OrderByLanguageDescending(language);
}
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)

View File

@@ -30,11 +30,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), cancellationToken).ConfigureAwait(false);
if (personResult != null)
{
@@ -51,19 +49,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>();

View File

@@ -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;

View File

@@ -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;
if (!string.IsNullOrEmpty(externalIds?.ImdbId))
{
item.SetProviderId(MetadataProvider.Imdb, externalIds.ImdbId);
}
item.Name = episodeResult.Name;
item.Overview = episodeResult.Overview;
item.CommunityRating = Convert.ToSingle(episodeResult.VoteAverage);
if (!string.IsNullOrEmpty(externalIds?.TvrageId))
{
item.SetProviderId(MetadataProvider.TvRage, externalIds.TvrageId);
}
if (episodeResult.Videos?.Results != null)
{

View File

@@ -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;

View File

@@ -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)

View File

@@ -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();
var tvShow = await _tmdbClientManager
.GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
.ConfigureAwait(false);
result = new MetadataResult<Series>
{
Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode),
ResultLanguage = info.MetadataLanguage ?? tvShow.OriginalLanguage
};
foreach (var person in GetPersons(tvShow))
{
result.AddPerson(person);
}
result.HasMetadata = result.Item != null;
return result;
}
cancellationToken.ThrowIfCancellationRequested();
var tvShow = await _tmdbClientManager
.GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
.ConfigureAwait(false);
result = new MetadataResult<Series>
{
Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode),
ResultLanguage = info.MetadataLanguage ?? tvShow.OriginalLanguage
};
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
{

View File

@@ -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)
@@ -136,6 +136,56 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
return series;
}
/// <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>
@@ -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,
@@ -278,9 +342,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 +356,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)

View File

@@ -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>
@@ -42,6 +45,17 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
PersonType.Producer
};
/// <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>
@@ -49,19 +63,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <returns>The Jellyfin person type.</returns>
public static string MapCrewToPersonType(Crew crew)
{
if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase)
&& crew.Job.Contains("director", StringComparison.InvariantCultureIgnoreCase))
if (crew.Department.Equals("production", StringComparison.OrdinalIgnoreCase)
&& crew.Job.Contains("director", StringComparison.OrdinalIgnoreCase))
{
return PersonType.Director;
}
if (crew.Department.Equals("production", StringComparison.InvariantCultureIgnoreCase)
&& crew.Job.Contains("producer", StringComparison.InvariantCultureIgnoreCase))
if (crew.Department.Equals("production", StringComparison.OrdinalIgnoreCase)
&& crew.Job.Contains("producer", StringComparison.OrdinalIgnoreCase))
{
return PersonType.Producer;
}
if (crew.Department.Equals("writing", StringComparison.InvariantCultureIgnoreCase))
if (crew.Department.Equals("writing", StringComparison.OrdinalIgnoreCase))
{
return PersonType.Writer;
}

View File

@@ -8,6 +8,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
@@ -171,20 +172,23 @@ namespace MediaBrowser.Providers.Studios
public IEnumerable<string> GetAvailableImages(string file)
{
using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
using var reader = new StreamReader(fileStream);
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read));
using (var reader = new StreamReader(fileStream));
var lines = new List<string>();
while (!reader.EndOfStream)
foreach (var line in reader.ReadAllLines())
{
var text = reader.ReadLine();
if (!string.IsNullOrWhiteSpace(text))
if (!string.IsNullOrWhiteSpace(line))
{
lines.Add(text);
lines.Add(line);
}
}
if (!string.IsNullOrWhiteSpace(text))
{
lines.Add(text);
}
return lines;
}
}

View File

@@ -204,17 +204,35 @@ namespace MediaBrowser.Providers.Subtitles
if (saveInMediaFolder)
{
savePaths.Add(Path.Combine(video.ContainingFolderPath, saveFileName));
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(mediaFolderPath);
}
}
savePaths.Add(Path.Combine(video.GetInternalMetadataPath(), saveFileName));
var internalPath = Path.GetFullPath(Path.Combine(video.GetInternalMetadataPath(), saveFileName));
await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false);
// 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)
{
@@ -226,17 +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);
// 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
{
@@ -246,9 +262,9 @@ namespace MediaBrowser.Providers.Subtitles
stream.Position = 0;
}
if (exceptionToThrow != null)
if (exs != null)
{
throw exceptionToThrow;
throw new AggregateException(exs);
}
}