aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Providers/Manager/MetadataService.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Providers/Manager/MetadataService.cs')
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs311
1 files changed, 165 insertions, 146 deletions
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index 6f473fc07..8af4ed2a8 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -8,7 +8,6 @@ using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
@@ -72,7 +71,7 @@ namespace MediaBrowser.Providers.Manager
}
}
- public async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
+ public virtual async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
{
var itemOfType = (TItemType)item;
@@ -93,10 +92,6 @@ namespace MediaBrowser.Providers.Manager
}
}
- var localImagesFailed = false;
-
- var allImageProviders = ProviderManager.GetImageProviders(item, refreshOptions).ToList();
-
if (refreshOptions.RemoveOldMetadata && refreshOptions.ReplaceAllImages)
{
if (ImageProvider.RemoveImages(item))
@@ -105,24 +100,35 @@ namespace MediaBrowser.Providers.Manager
}
}
- // Start by validating images
- try
+ var localImagesFailed = false;
+ var allImageProviders = ProviderManager.GetImageProviders(item, refreshOptions).ToList();
+
+ // Only validate already registered images if we are replacing and saving locally
+ if (item.IsSaveLocalMetadataEnabled() && refreshOptions.ReplaceAllImages)
{
- // Always validate images and check for new locally stored ones.
- if (ImageProvider.ValidateImages(item, allImageProviders.OfType<ILocalImageProvider>(), refreshOptions))
- {
- updateType |= ItemUpdateType.ImageUpdate;
- }
+ item.ValidateImages();
}
- catch (Exception ex)
+ else
{
- localImagesFailed = true;
- Logger.LogError(ex, "Error validating images for {Item}", item.Path ?? item.Name ?? "Unknown name");
+ // Run full image validation and register new local images
+ try
+ {
+ if (ImageProvider.ValidateImages(item, allImageProviders.OfType<ILocalImageProvider>(), refreshOptions))
+ {
+ updateType |= ItemUpdateType.ImageUpdate;
+ }
+ }
+ catch (Exception ex)
+ {
+ localImagesFailed = true;
+ Logger.LogError(ex, "Error validating images for {Item}", item.Path ?? item.Name ?? "Unknown name");
+ }
}
var metadataResult = new MetadataResult<TItemType>
{
- Item = itemOfType
+ Item = itemOfType,
+ People = LibraryManager.GetPeople(item)
};
bool hasRefreshedMetadata = true;
@@ -154,7 +160,8 @@ namespace MediaBrowser.Providers.Manager
id.IsAutomated = refreshOptions.IsAutomated;
- var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, ImageProvider, cancellationToken).ConfigureAwait(false);
+ var hasMetadataSavers = ProviderManager.GetMetadataSavers(item, libraryOptions).Any();
+ var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, ImageProvider, hasMetadataSavers, cancellationToken).ConfigureAwait(false);
updateType |= result.UpdateType;
if (result.Failures > 0)
@@ -165,7 +172,7 @@ namespace MediaBrowser.Providers.Manager
}
// Next run remote image providers, but only if local image providers didn't throw an exception
- if (!localImagesFailed && refreshOptions.ImageRefreshMode != MetadataRefreshMode.ValidationOnly)
+ if (!localImagesFailed && refreshOptions.ImageRefreshMode > MetadataRefreshMode.ValidationOnly)
{
var providers = GetNonLocalImageProviders(item, allImageProviders, refreshOptions).ToList();
@@ -243,7 +250,7 @@ namespace MediaBrowser.Providers.Manager
protected async Task SaveItemAsync(MetadataResult<TItemType> result, ItemUpdateType reason, CancellationToken cancellationToken)
{
- if (result.Item.SupportsPeople && result.People is not null)
+ if (result.Item.SupportsPeople)
{
var baseItem = result.Item;
@@ -399,7 +406,8 @@ namespace MediaBrowser.Providers.Manager
foreach (var child in children)
{
- if (!child.IsFolder)
+ // Exclude any folders and virtual items since they are only placeholders
+ if (!child.IsFolder && !child.IsVirtualItem)
{
var childDateCreated = child.DateCreated;
if (childDateCreated > dateLastMediaAdded)
@@ -638,6 +646,7 @@ namespace MediaBrowser.Providers.Manager
MetadataRefreshOptions options,
ICollection<IMetadataProvider> providers,
ItemImageProvider imageService,
+ bool isSavingMetadata,
CancellationToken cancellationToken)
{
var refreshResult = new RefreshResult
@@ -655,102 +664,96 @@ namespace MediaBrowser.Providers.Manager
await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwait(false);
}
+ if (item.IsLocked)
+ {
+ return refreshResult;
+ }
+
var temp = new MetadataResult<TItemType>
{
Item = CreateNew()
};
temp.Item.Path = item.Path;
+ temp.Item.Id = item.Id;
+ temp.Item.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
+ temp.Item.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
- // If replacing all metadata, run internet providers first
- if (options.ReplaceAllMetadata)
- {
- var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), cancellationToken)
- .ConfigureAwait(false);
-
- refreshResult.UpdateType |= remoteResult.UpdateType;
- refreshResult.ErrorMessage = remoteResult.ErrorMessage;
- refreshResult.Failures += remoteResult.Failures;
- }
-
- var hasLocalMetadata = false;
var foundImageTypes = new List<ImageType>();
- foreach (var provider in providers.OfType<ILocalMetadataProvider<TItemType>>())
+ // Do not execute local providers if we are identifying or replacing with local metadata saving enabled
+ if (options.SearchResult is null && !(isSavingMetadata && options.ReplaceAllMetadata))
{
- var providerName = provider.GetType().Name;
- Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);
-
- var itemInfo = new ItemInfo(item);
-
- try
+ foreach (var provider in providers.OfType<ILocalMetadataProvider<TItemType>>())
{
- var localItem = await provider.GetMetadata(itemInfo, options.DirectoryService, cancellationToken).ConfigureAwait(false);
+ var providerName = provider.GetType().Name;
+ Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);
+
+ var itemInfo = new ItemInfo(item);
- if (localItem.HasMetadata)
+ try
{
- foreach (var remoteImage in localItem.RemoteImages)
+ var localItem = await provider.GetMetadata(itemInfo, options.DirectoryService, cancellationToken).ConfigureAwait(false);
+
+ if (localItem.HasMetadata)
{
- try
+ foreach (var remoteImage in localItem.RemoteImages)
{
- if (item.ImageInfos.Any(x => x.Type == remoteImage.Type)
- && !options.IsReplacingImage(remoteImage.Type))
+ try
{
- continue;
- }
+ if (item.ImageInfos.Any(x => x.Type == remoteImage.Type)
+ && !options.IsReplacingImage(remoteImage.Type))
+ {
+ continue;
+ }
- await ProviderManager.SaveImage(item, remoteImage.Url, remoteImage.Type, null, cancellationToken).ConfigureAwait(false);
- refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
+ await ProviderManager.SaveImage(item, remoteImage.Url, remoteImage.Type, null, cancellationToken).ConfigureAwait(false);
+ refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
- // remember imagetype that has just been downloaded
- foundImageTypes.Add(remoteImage.Type);
+ // remember imagetype that has just been downloaded
+ foundImageTypes.Add(remoteImage.Type);
+ }
+ catch (HttpRequestException ex)
+ {
+ Logger.LogError(ex, "Could not save {ImageType} image: {Url}", Enum.GetName(remoteImage.Type), remoteImage.Url);
+ }
}
- catch (HttpRequestException ex)
+
+ if (foundImageTypes.Count > 0)
{
- Logger.LogError(ex, "Could not save {ImageType} image: {Url}", Enum.GetName(remoteImage.Type), remoteImage.Url);
+ imageService.UpdateReplaceImages(options, foundImageTypes);
}
- }
- if (foundImageTypes.Count > 0)
- {
- imageService.UpdateReplaceImages(options, foundImageTypes);
- }
-
- if (imageService.MergeImages(item, localItem.Images, options))
- {
- refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
- }
+ if (imageService.MergeImages(item, localItem.Images, options))
+ {
+ refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
+ }
- MergeData(localItem, temp, Array.Empty<MetadataField>(), options.ReplaceAllMetadata, true);
- refreshResult.UpdateType |= ItemUpdateType.MetadataImport;
+ MergeData(localItem, temp, Array.Empty<MetadataField>(), false, true);
+ refreshResult.UpdateType |= ItemUpdateType.MetadataImport;
- // Only one local provider allowed per item
- if (item.IsLocked || localItem.Item.IsLocked || IsFullLocalMetadata(localItem.Item))
- {
- hasLocalMetadata = true;
+ break;
}
- break;
+ Logger.LogDebug("{Provider} returned no metadata for {Item}", providerName, logName);
}
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Error in {Provider}", provider.Name);
- Logger.LogDebug("{Provider} returned no metadata for {Item}", providerName, logName);
- }
- catch (OperationCanceledException)
- {
- throw;
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error in {Provider}", provider.Name);
-
- // If a local provider fails, consider that a failure
- refreshResult.ErrorMessage = ex.Message;
+ // If a local provider fails, consider that a failure
+ refreshResult.ErrorMessage = ex.Message;
+ }
}
}
- // Local metadata is king - if any is found don't run remote providers
- if (!options.ReplaceAllMetadata && (!hasLocalMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || !item.StopRefreshIfLocalMetadataFound))
+ var isLocalLocked = temp.Item.IsLocked;
+ if (!isLocalLocked && (options.ReplaceAllMetadata || options.MetadataRefreshMode > MetadataRefreshMode.ValidationOnly))
{
- var remoteResult = await ExecuteRemoteProviders(temp, logName, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), cancellationToken)
+ var remoteResult = await ExecuteRemoteProviders(temp, logName, false, id, providers.OfType<IRemoteMetadataProvider<TItemType, TIdType>>(), cancellationToken)
.ConfigureAwait(false);
refreshResult.UpdateType |= remoteResult.UpdateType;
@@ -762,19 +765,20 @@ namespace MediaBrowser.Providers.Manager
{
if (refreshResult.UpdateType > ItemUpdateType.None)
{
- if (hasLocalMetadata)
+ if (!options.RemoveOldMetadata)
+ {
+ // Add existing metadata to provider result if it does not exist there
+ MergeData(metadata, temp, Array.Empty<MetadataField>(), false, false);
+ }
+
+ if (isLocalLocked)
{
MergeData(temp, metadata, item.LockedFields, true, true);
}
else
{
- if (!options.RemoveOldMetadata)
- {
- MergeData(metadata, temp, Array.Empty<MetadataField>(), false, false);
- }
-
- // Will always replace all metadata when Scan for new and updated files is used. Else, follow the options.
- MergeData(temp, metadata, item.LockedFields, options.MetadataRefreshMode == MetadataRefreshMode.Default || options.ReplaceAllMetadata, false);
+ var shouldReplace = options.MetadataRefreshMode > MetadataRefreshMode.ValidationOnly || options.ReplaceAllMetadata;
+ MergeData(temp, metadata, item.LockedFields, shouldReplace, true);
}
}
}
@@ -787,16 +791,6 @@ namespace MediaBrowser.Providers.Manager
return refreshResult;
}
- protected virtual bool IsFullLocalMetadata(TItemType item)
- {
- if (string.IsNullOrWhiteSpace(item.Name))
- {
- return false;
- }
-
- return true;
- }
-
private async Task RunCustomProvider(ICustomMetadataProvider<TItemType> provider, TItemType item, string logName, MetadataRefreshOptions options, RefreshResult refreshResult, CancellationToken cancellationToken)
{
Logger.LogDebug("Running {Provider} for {Item}", provider.GetType().Name, logName);
@@ -821,23 +815,20 @@ namespace MediaBrowser.Providers.Manager
return new TItemType();
}
- private async Task<RefreshResult> ExecuteRemoteProviders(MetadataResult<TItemType> temp, string logName, TIdType id, IEnumerable<IRemoteMetadataProvider<TItemType, TIdType>> providers, CancellationToken cancellationToken)
+ private async Task<RefreshResult> ExecuteRemoteProviders(MetadataResult<TItemType> temp, string logName, bool replaceData, TIdType id, IEnumerable<IRemoteMetadataProvider<TItemType, TIdType>> providers, CancellationToken cancellationToken)
{
var refreshResult = new RefreshResult();
- var tmpDataMerged = false;
+ if (id is not null)
+ {
+ MergeNewData(temp.Item, id);
+ }
foreach (var provider in providers)
{
var providerName = provider.GetType().Name;
Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);
- if (id is not null && !tmpDataMerged)
- {
- MergeNewData(temp.Item, id);
- tmpDataMerged = true;
- }
-
try
{
var result = await provider.GetMetadata(id, cancellationToken).ConfigureAwait(false);
@@ -846,7 +837,7 @@ namespace MediaBrowser.Providers.Manager
{
result.Provider = provider.Name;
- MergeData(result, temp, Array.Empty<MetadataField>(), false, false);
+ MergeData(result, temp, Array.Empty<MetadataField>(), replaceData, false);
MergeNewData(temp.Item, id);
refreshResult.UpdateType |= ItemUpdateType.MetadataDownload;
@@ -949,11 +940,7 @@ namespace MediaBrowser.Providers.Manager
if (replaceData || string.IsNullOrEmpty(target.OriginalTitle))
{
- // Safeguard against incoming data having an empty name
- if (!string.IsNullOrWhiteSpace(source.OriginalTitle))
- {
- target.OriginalTitle = source.OriginalTitle;
- }
+ target.OriginalTitle = source.OriginalTitle;
}
if (replaceData || !target.CommunityRating.HasValue)
@@ -1016,7 +1003,7 @@ namespace MediaBrowser.Providers.Manager
{
targetResult.People = sourceResult.People;
}
- else if (targetResult.People is not null && sourceResult.People is not null)
+ else if (sourceResult.People is not null && sourceResult.People.Count > 0)
{
MergePeople(sourceResult.People, targetResult.People);
}
@@ -1049,6 +1036,10 @@ namespace MediaBrowser.Providers.Manager
{
target.Studios = source.Studios;
}
+ else
+ {
+ target.Studios = target.Studios.Concat(source.Studios).Distinct().ToArray();
+ }
}
if (!lockedFields.Contains(MetadataField.Tags))
@@ -1057,6 +1048,10 @@ namespace MediaBrowser.Providers.Manager
{
target.Tags = source.Tags;
}
+ else
+ {
+ target.Tags = target.Tags.Concat(source.Tags).Distinct().ToArray();
+ }
}
if (!lockedFields.Contains(MetadataField.ProductionLocations))
@@ -1065,6 +1060,10 @@ namespace MediaBrowser.Providers.Manager
{
target.ProductionLocations = source.ProductionLocations;
}
+ else
+ {
+ target.ProductionLocations = target.ProductionLocations.Concat(source.ProductionLocations).Distinct().ToArray();
+ }
}
foreach (var id in source.ProviderIds)
@@ -1082,17 +1081,28 @@ namespace MediaBrowser.Providers.Manager
}
}
+ if (replaceData || !target.CriticRating.HasValue)
+ {
+ target.CriticRating = source.CriticRating;
+ }
+
+ if (replaceData || target.RemoteTrailers.Count == 0)
+ {
+ target.RemoteTrailers = source.RemoteTrailers;
+ }
+ else
+ {
+ target.RemoteTrailers = target.RemoteTrailers.Concat(source.RemoteTrailers).DistinctBy(t => t.Url).ToArray();
+ }
+
MergeAlbumArtist(source, target, replaceData);
- MergeCriticRating(source, target, replaceData);
- MergeTrailers(source, target, replaceData);
MergeVideoInfo(source, target, replaceData);
MergeDisplayOrder(source, target, replaceData);
if (replaceData || string.IsNullOrEmpty(target.ForcedSortName))
{
var forcedSortName = source.ForcedSortName;
-
- if (!string.IsNullOrWhiteSpace(forcedSortName))
+ if (!string.IsNullOrEmpty(forcedSortName))
{
target.ForcedSortName = forcedSortName;
}
@@ -1100,22 +1110,44 @@ namespace MediaBrowser.Providers.Manager
if (mergeMetadataSettings)
{
- target.LockedFields = source.LockedFields;
- target.IsLocked = source.IsLocked;
+ if (replaceData || !target.IsLocked)
+ {
+ target.IsLocked = target.IsLocked || source.IsLocked;
+ }
+
+ if (target.LockedFields.Length == 0)
+ {
+ target.LockedFields = source.LockedFields;
+ }
+ else
+ {
+ target.LockedFields = target.LockedFields.Concat(source.LockedFields).Distinct().ToArray();
+ }
- // Grab the value if it's there, but if not then don't overwrite with the default
if (source.DateCreated != default)
{
target.DateCreated = source.DateCreated;
}
- target.PreferredMetadataCountryCode = source.PreferredMetadataCountryCode;
- target.PreferredMetadataLanguage = source.PreferredMetadataLanguage;
+ if (replaceData || string.IsNullOrEmpty(target.PreferredMetadataCountryCode))
+ {
+ target.PreferredMetadataCountryCode = source.PreferredMetadataCountryCode;
+ }
+
+ if (replaceData || string.IsNullOrEmpty(target.PreferredMetadataLanguage))
+ {
+ target.PreferredMetadataLanguage = source.PreferredMetadataLanguage;
+ }
}
}
private static void MergePeople(List<PersonInfo> source, List<PersonInfo> target)
{
+ if (target is null)
+ {
+ target = new List<PersonInfo>();
+ }
+
foreach (var person in target)
{
var normalizedName = person.Name.RemoveDiacritics();
@@ -1144,7 +1176,6 @@ namespace MediaBrowser.Providers.Manager
if (replaceData || string.IsNullOrEmpty(targetHasDisplayOrder.DisplayOrder))
{
var displayOrder = sourceHasDisplayOrder.DisplayOrder;
-
if (!string.IsNullOrWhiteSpace(displayOrder))
{
targetHasDisplayOrder.DisplayOrder = displayOrder;
@@ -1162,22 +1193,10 @@ namespace MediaBrowser.Providers.Manager
{
targetHasAlbumArtist.AlbumArtists = sourceHasAlbumArtist.AlbumArtists;
}
- }
- }
-
- private static void MergeCriticRating(BaseItem source, BaseItem target, bool replaceData)
- {
- if (replaceData || !target.CriticRating.HasValue)
- {
- target.CriticRating = source.CriticRating;
- }
- }
-
- private static void MergeTrailers(BaseItem source, BaseItem target, bool replaceData)
- {
- if (replaceData || target.RemoteTrailers.Count == 0)
- {
- target.RemoteTrailers = source.RemoteTrailers;
+ else if (sourceHasAlbumArtist.AlbumArtists.Count > 0)
+ {
+ targetHasAlbumArtist.AlbumArtists = targetHasAlbumArtist.AlbumArtists.Concat(sourceHasAlbumArtist.AlbumArtists).Distinct().ToArray();
+ }
}
}
@@ -1185,7 +1204,7 @@ namespace MediaBrowser.Providers.Manager
{
if (source is Video sourceCast && target is Video targetCast)
{
- if (replaceData || targetCast.Video3DFormat is null)
+ if (replaceData || !targetCast.Video3DFormat.HasValue)
{
targetCast.Video3DFormat = sourceCast.Video3DFormat;
}