aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Providers
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Providers')
-rw-r--r--MediaBrowser.Providers/Manager/ImageSaver.cs85
-rw-r--r--MediaBrowser.Providers/Manager/ItemImageProvider.cs23
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs311
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs91
-rw-r--r--MediaBrowser.Providers/MediaInfo/AudioFileProber.cs418
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs11
-rw-r--r--MediaBrowser.Providers/MediaInfo/ProbeProvider.cs27
-rw-r--r--MediaBrowser.Providers/Movies/MovieMetadataService.cs16
-rw-r--r--MediaBrowser.Providers/Movies/TrailerMetadataService.cs21
-rw-r--r--MediaBrowser.Providers/Music/AlbumMetadataService.cs4
-rw-r--r--MediaBrowser.Providers/Music/AudioMetadataService.cs5
-rw-r--r--MediaBrowser.Providers/Music/MusicVideoMetadataService.cs5
-rw-r--r--MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs249
-rw-r--r--MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs21
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs11
-rw-r--r--MediaBrowser.Providers/Subtitles/SubtitleManager.cs79
-rw-r--r--MediaBrowser.Providers/TV/SeriesMetadataService.cs88
-rw-r--r--MediaBrowser.Providers/Trickplay/TrickplayProvider.cs2
20 files changed, 778 insertions, 696 deletions
diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs
index d82716831..9a676cb2e 100644
--- a/MediaBrowser.Providers/Manager/ImageSaver.cs
+++ b/MediaBrowser.Providers/Manager/ImageSaver.cs
@@ -14,6 +14,7 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
@@ -100,8 +101,8 @@ namespace MediaBrowser.Providers.Manager
{
saveLocally = false;
- // If season is virtual under a physical series, save locally if using compatible convention
- if (item is Season season && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Compatible)
+ // If season is virtual under a physical series, save locally
+ if (item is Season season)
{
var series = season.Series;
@@ -126,7 +127,11 @@ namespace MediaBrowser.Providers.Manager
var paths = GetSavePaths(item, type, imageIndex, mimeType, saveLocally);
- var retryPaths = GetSavePaths(item, type, imageIndex, mimeType, false);
+ string[] retryPaths = [];
+ if (saveLocally)
+ {
+ retryPaths = GetSavePaths(item, type, imageIndex, mimeType, false);
+ }
// If there are more than one output paths, the stream will need to be seekable
if (paths.Length > 1 && !source.CanSeek)
@@ -183,6 +188,29 @@ namespace MediaBrowser.Providers.Manager
try
{
_fileSystem.DeleteFile(currentPath);
+
+ // Remove local episode metadata directory if it exists and is empty
+ var directory = Path.GetDirectoryName(currentPath);
+ if (item is Episode && directory.Equals("metadata", StringComparison.Ordinal))
+ {
+ var parentDirectoryPath = Directory.GetParent(currentPath).FullName;
+ if (_fileSystem.DirectoryExists(parentDirectoryPath) && !_fileSystem.GetFiles(parentDirectoryPath).Any())
+ {
+ try
+ {
+ _logger.LogInformation("Deleting empty local metadata folder {Folder}", parentDirectoryPath);
+ Directory.Delete(parentDirectoryPath);
+ }
+ catch (UnauthorizedAccessException ex)
+ {
+ _logger.LogError(ex, "Error deleting directory {Path}", parentDirectoryPath);
+ }
+ catch (IOException ex)
+ {
+ _logger.LogError(ex, "Error deleting directory {Path}", parentDirectoryPath);
+ }
+ }
+ }
}
catch (FileNotFoundException)
{
@@ -374,6 +402,47 @@ namespace MediaBrowser.Providers.Manager
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Unable to determine image file extension from mime type {0}", mimeType));
}
+ if (string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase))
+ {
+ extension = ".jpg";
+ }
+
+ extension = extension.ToLowerInvariant();
+
+ if (type == ImageType.Primary && saveLocally)
+ {
+ if (season is not null && season.IndexNumber.HasValue)
+ {
+ var seriesFolder = season.SeriesPath;
+
+ var seasonMarker = season.IndexNumber.Value == 0
+ ? "-specials"
+ : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
+
+ var imageFilename = "season" + seasonMarker + "-poster" + extension;
+
+ return Path.Combine(seriesFolder, imageFilename);
+ }
+ }
+
+ if (type == ImageType.Backdrop && saveLocally)
+ {
+ if (season is not null
+ && season.IndexNumber.HasValue
+ && (imageIndex is null || imageIndex == 0))
+ {
+ var seriesFolder = season.SeriesPath;
+
+ var seasonMarker = season.IndexNumber.Value == 0
+ ? "-specials"
+ : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
+
+ var imageFilename = "season" + seasonMarker + "-fanart" + extension;
+
+ return Path.Combine(seriesFolder, imageFilename);
+ }
+ }
+
if (type == ImageType.Thumb && saveLocally)
{
if (season is not null && season.IndexNumber.HasValue)
@@ -447,20 +516,12 @@ namespace MediaBrowser.Providers.Manager
break;
}
- if (string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase))
- {
- extension = ".jpg";
- }
-
- extension = extension.ToLowerInvariant();
-
string path = null;
-
if (saveLocally)
{
if (type == ImageType.Primary && item is Episode)
{
- path = Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename + extension);
+ path = Path.Combine(Path.GetDirectoryName(item.Path), filename + "-thumb" + extension);
}
else if (item.IsInMixedFolder)
{
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index 1a5dbd7a5..1bb7ffcce 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Providers;
@@ -96,7 +97,7 @@ namespace MediaBrowser.Providers.Manager
public bool ValidateImages(BaseItem item, IEnumerable<IImageProvider> providers, ImageRefreshOptions refreshOptions)
{
var hasChanges = false;
- IDirectoryService directoryService = refreshOptions?.DirectoryService;
+ var directoryService = refreshOptions?.DirectoryService;
if (item is not Photo)
{
@@ -158,7 +159,7 @@ namespace MediaBrowser.Providers.Manager
}
}
- // only delete existing multi-images if new ones were added
+ // Only delete existing multi-images if new ones were added
if (oldBackdropImages.Length > 0 && oldBackdropImages.Length < item.GetImages(ImageType.Backdrop).Count())
{
PruneImages(item, oldBackdropImages);
@@ -359,10 +360,8 @@ namespace MediaBrowser.Providers.Manager
private void PruneImages(BaseItem item, IReadOnlyList<ItemImageInfo> images)
{
- for (var i = 0; i < images.Count; i++)
+ foreach (var image in images)
{
- var image = images[i];
-
if (image.IsLocalFile)
{
try
@@ -371,7 +370,7 @@ namespace MediaBrowser.Providers.Manager
}
catch (FileNotFoundException)
{
- // nothing to do, already gone
+ // Nothing to do, already gone
}
catch (UnauthorizedAccessException ex)
{
@@ -381,6 +380,16 @@ namespace MediaBrowser.Providers.Manager
}
item.RemoveImages(images);
+
+ // Cleanup old metadata directory for episodes if empty
+ if (item is Episode)
+ {
+ var oldLocalMetadataDirectory = Path.Combine(item.ContainingFolderPath, "metadata");
+ if (_fileSystem.DirectoryExists(oldLocalMetadataDirectory) && !_fileSystem.GetFiles(oldLocalMetadataDirectory).Any())
+ {
+ Directory.Delete(oldLocalMetadataDirectory);
+ }
+ }
}
/// <summary>
@@ -413,12 +422,10 @@ namespace MediaBrowser.Providers.Manager
{
var changed = item.ValidateImages();
var foundImageTypes = new List<ImageType>();
-
for (var i = 0; i < _singularImages.Length; i++)
{
var type = _singularImages[i];
var image = GetFirstLocalImageInfoByType(images, type);
-
if (image is not null)
{
var currentImage = item.GetImageInfo(type, 0);
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;
}
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index f34034964..60d89a51b 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -69,11 +69,12 @@ namespace MediaBrowser.Providers.Manager
o.PoolInitialFill = 1;
});
- private IImageProvider[] _imageProviders = Array.Empty<IImageProvider>();
- private IMetadataService[] _metadataServices = Array.Empty<IMetadataService>();
- private IMetadataProvider[] _metadataProviders = Array.Empty<IMetadataProvider>();
- private IMetadataSaver[] _savers = Array.Empty<IMetadataSaver>();
- private IExternalId[] _externalIds = Array.Empty<IExternalId>();
+ private IImageProvider[] _imageProviders = [];
+ private IMetadataService[] _metadataServices = [];
+ private IMetadataProvider[] _metadataProviders = [];
+ private IMetadataSaver[] _savers = [];
+ private IExternalId[] _externalIds = [];
+ private IExternalUrlProvider[] _externalUrlProviders = [];
private bool _isProcessingRefreshQueue;
private bool _disposed;
@@ -132,12 +133,14 @@ namespace MediaBrowser.Providers.Manager
IEnumerable<IMetadataService> metadataServices,
IEnumerable<IMetadataProvider> metadataProviders,
IEnumerable<IMetadataSaver> metadataSavers,
- IEnumerable<IExternalId> externalIds)
+ IEnumerable<IExternalId> externalIds,
+ IEnumerable<IExternalUrlProvider> externalUrlProviders)
{
_imageProviders = imageProviders.ToArray();
_metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
_metadataProviders = metadataProviders.ToArray();
_externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray();
+ _externalUrlProviders = externalUrlProviders.OrderBy(i => i.Name).ToArray();
_savers = metadataSavers.ToArray();
}
@@ -286,7 +289,7 @@ namespace MediaBrowser.Providers.Manager
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
- return results.SelectMany(i => i.ToList());
+ return results.SelectMany(i => i);
}
/// <summary>
@@ -418,6 +421,12 @@ namespace MediaBrowser.Providers.Manager
return GetMetadataProvidersInternal<T>(item, libraryOptions, globalMetadataOptions, false, false);
}
+ /// <inheritdoc />
+ public IEnumerable<IMetadataSaver> GetMetadataSavers(BaseItem item, LibraryOptions libraryOptions)
+ {
+ return _savers.Where(i => IsSaverEnabledForItem(i, item, libraryOptions, ItemUpdateType.MetadataEdit, false));
+ }
+
private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(BaseItem item, LibraryOptions libraryOptions, MetadataOptions globalMetadataOptions, bool includeDisabled, bool forceEnableInternetMetadata)
where T : BaseItem
{
@@ -871,31 +880,35 @@ namespace MediaBrowser.Providers.Manager
/// <inheritdoc/>
public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
{
- return GetExternalIds(item)
+#pragma warning disable CS0618 // Type or member is obsolete - Remove 10.11
+ var legacyExternalIdUrls = GetExternalIds(item)
.Select(i =>
- {
- if (string.IsNullOrEmpty(i.UrlFormatString))
{
- return null;
- }
+ var urlFormatString = i.UrlFormatString;
+ if (string.IsNullOrEmpty(urlFormatString)
+ || !item.TryGetProviderId(i.Key, out var providerId))
+ {
+ return null;
+ }
- var value = item.GetProviderId(i.Key);
+ return new ExternalUrl
+ {
+ Name = i.ProviderName,
+ Url = string.Format(
+ CultureInfo.InvariantCulture,
+ urlFormatString,
+ providerId)
+ };
+ })
+ .OfType<ExternalUrl>();
+#pragma warning restore CS0618 // Type or member is obsolete
- if (string.IsNullOrEmpty(value))
- {
- return null;
- }
+ var externalUrls = _externalUrlProviders
+ .SelectMany(p => p
+ .GetExternalUrls(item)
+ .Select(externalUrl => new ExternalUrl { Name = p.Name, Url = externalUrl }));
- return new ExternalUrl
- {
- Name = i.ProviderName,
- Url = string.Format(
- CultureInfo.InvariantCulture,
- i.UrlFormatString,
- value)
- };
- }).Where(i => i is not null)
- .Concat(item.GetRelatedUrls())!; // We just filtered out all the nulls
+ return legacyExternalIdUrls.Concat(externalUrls).OrderBy(u => u.Name);
}
/// <inheritdoc/>
@@ -906,7 +919,9 @@ namespace MediaBrowser.Providers.Manager
name: i.ProviderName,
key: i.Key,
type: i.Type,
+#pragma warning disable CS0618 // Type or member is obsolete - Remove 10.11
urlFormatString: i.UrlFormatString));
+#pragma warning restore CS0618 // Type or member is obsolete
}
/// <inheritdoc/>
@@ -968,16 +983,13 @@ namespace MediaBrowser.Providers.Manager
var id = item.Id;
_logger.LogDebug("OnRefreshProgress {Id:N} {Progress}", id, progress);
- // TODO: Need to hunt down the conditions for this happening
- _activeRefreshes.AddOrUpdate(
- id,
- _ => 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);
+ if (!_activeRefreshes.TryGetValue(id, out var current)
+ || progress <= current
+ || !_activeRefreshes.TryUpdate(id, progress, current))
+ {
+ // Item isn't currently refreshing, or update was received out-of-order, so don't trigger event.
+ return;
+ }
try
{
@@ -1106,9 +1118,10 @@ namespace MediaBrowser.Providers.Manager
var musicArtists = albums
.Select(i => i.MusicArtist)
- .Where(i => i is not null);
+ .Where(i => i is not null)
+ .Distinct();
- var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new Progress<double>(), options, true, cancellationToken));
+ var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new Progress<double>(), options, true, false, cancellationToken));
await Task.WhenAll(musicArtistRefreshTasks).ConfigureAwait(false);
diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
index c9fe4c9b6..0083d4f75 100644
--- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
+++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs
@@ -1,12 +1,11 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Globalization;
using System.Linq;
-using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
@@ -26,15 +25,12 @@ namespace MediaBrowser.Providers.MediaInfo
/// <summary>
/// Probes audio files for metadata.
/// </summary>
- public partial class AudioFileProber
+ public class AudioFileProber
{
- // Default LUFS value for use with the web interface, at -18db gain will be 1(no db gain).
- private const float DefaultLUFSValue = -18;
-
- private readonly ILogger<AudioFileProber> _logger;
private readonly IMediaEncoder _mediaEncoder;
private readonly IItemRepository _itemRepo;
private readonly ILibraryManager _libraryManager;
+ private readonly ILogger<AudioFileProber> _logger;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly LyricResolver _lyricResolver;
private readonly ILyricManager _lyricManager;
@@ -58,21 +54,15 @@ namespace MediaBrowser.Providers.MediaInfo
LyricResolver lyricResolver,
ILyricManager lyricManager)
{
- _logger = logger;
_mediaEncoder = mediaEncoder;
_itemRepo = itemRepo;
_libraryManager = libraryManager;
+ _logger = logger;
_mediaSourceManager = mediaSourceManager;
_lyricResolver = lyricResolver;
_lyricManager = lyricManager;
}
- [GeneratedRegex(@"I:\s+(.*?)\s+LUFS")]
- private static partial Regex LUFSRegex();
-
- [GeneratedRegex(@"REPLAYGAIN_TRACK_GAIN:\s+-?([0-9.]+)\s+dB")]
- private static partial Regex ReplayGainTagRegex();
-
/// <summary>
/// Probes the specified item for metadata.
/// </summary>
@@ -115,97 +105,6 @@ namespace MediaBrowser.Providers.MediaInfo
await FetchAsync(item, result, options, cancellationToken).ConfigureAwait(false);
}
- var libraryOptions = _libraryManager.GetLibraryOptions(item);
- bool foundLUFSValue = false;
-
- if (libraryOptions.UseReplayGainTags)
- {
- using (var process = new Process()
- {
- StartInfo = new ProcessStartInfo
- {
- FileName = _mediaEncoder.ProbePath,
- Arguments = $"-hide_banner -i \"{path}\"",
- RedirectStandardOutput = false,
- RedirectStandardError = true
- },
- })
- {
- try
- {
- process.Start();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error starting ffmpeg");
-
- throw;
- }
-
- using var reader = process.StandardError;
- var output = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
- cancellationToken.ThrowIfCancellationRequested();
- Match split = ReplayGainTagRegex().Match(output);
-
- if (split.Success)
- {
- item.LUFS = DefaultLUFSValue - float.Parse(split.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
- foundLUFSValue = true;
- }
- else
- {
- item.LUFS = DefaultLUFSValue;
- }
- }
- }
-
- if (libraryOptions.EnableLUFSScan && !foundLUFSValue)
- {
- using (var process = new Process()
- {
- StartInfo = new ProcessStartInfo
- {
- FileName = _mediaEncoder.EncoderPath,
- Arguments = $"-hide_banner -i \"{path}\" -af ebur128=framelog=verbose -f null -",
- RedirectStandardOutput = false,
- RedirectStandardError = true
- },
- })
- {
- try
- {
- process.Start();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error starting ffmpeg");
-
- throw;
- }
-
- using var reader = process.StandardError;
- var output = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
- cancellationToken.ThrowIfCancellationRequested();
- MatchCollection split = LUFSRegex().Matches(output);
-
- if (split.Count != 0)
- {
- item.LUFS = float.Parse(split[0].Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
- }
- else
- {
- item.LUFS = DefaultLUFSValue;
- }
- }
- }
-
- if (!libraryOptions.EnableLUFSScan && !libraryOptions.UseReplayGainTags)
- {
- item.LUFS = DefaultLUFSValue;
- }
-
- _logger.LogDebug("LUFS for {ItemName} is {LUFS}.", item.Name, item.LUFS);
-
return ItemUpdateType.MetadataImport;
}
@@ -230,14 +129,20 @@ namespace MediaBrowser.Providers.MediaInfo
audio.Size = mediaInfo.Size;
audio.PremiereDate = mediaInfo.PremiereDate;
+ // Add external lyrics first to prevent the lrc file get overwritten on first scan
+ var mediaStreams = new List<MediaStream>(mediaInfo.MediaStreams);
+ AddExternalLyrics(audio, mediaStreams, options);
+ var tryExtractEmbeddedLyrics = mediaStreams.All(s => s.Type != MediaStreamType.Lyric);
+
if (!audio.IsLocked)
{
- await FetchDataFromTags(audio, options).ConfigureAwait(false);
+ await FetchDataFromTags(audio, mediaInfo, options, tryExtractEmbeddedLyrics).ConfigureAwait(false);
+ if (tryExtractEmbeddedLyrics)
+ {
+ AddExternalLyrics(audio, mediaStreams, options);
+ }
}
- var mediaStreams = new List<MediaStream>(mediaInfo.MediaStreams);
- AddExternalLyrics(audio, mediaStreams, options);
-
audio.HasLyrics = mediaStreams.Any(s => s.Type == MediaStreamType.Lyric);
_itemRepo.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken);
@@ -247,177 +152,221 @@ namespace MediaBrowser.Providers.MediaInfo
/// Fetches data from the tags.
/// </summary>
/// <param name="audio">The <see cref="Audio"/>.</param>
+ /// <param name="mediaInfo">The <see cref="Model.MediaInfo.MediaInfo"/>.</param>
/// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param>
- private async Task FetchDataFromTags(Audio audio, MetadataRefreshOptions options)
+ /// <param name="tryExtractEmbeddedLyrics">Whether to extract embedded lyrics to lrc file. </param>
+ private async Task FetchDataFromTags(Audio audio, Model.MediaInfo.MediaInfo mediaInfo, MetadataRefreshOptions options, bool tryExtractEmbeddedLyrics)
{
- using var file = TagLib.File.Create(audio.Path);
- var tagTypes = file.TagTypesOnDisk;
Tag? tags = null;
-
- if (tagTypes.HasFlag(TagTypes.Id3v2))
- {
- tags = file.GetTag(TagTypes.Id3v2);
- }
- else if (tagTypes.HasFlag(TagTypes.Ape))
- {
- tags = file.GetTag(TagTypes.Ape);
- }
- else if (tagTypes.HasFlag(TagTypes.FlacMetadata))
- {
- tags = file.GetTag(TagTypes.FlacMetadata);
- }
- else if (tagTypes.HasFlag(TagTypes.Apple))
+ try
{
- tags = file.GetTag(TagTypes.Apple);
- }
- else if (tagTypes.HasFlag(TagTypes.Xiph))
- {
- tags = file.GetTag(TagTypes.Xiph);
- }
- else if (tagTypes.HasFlag(TagTypes.AudibleMetadata))
- {
- tags = file.GetTag(TagTypes.AudibleMetadata);
+ using var file = TagLib.File.Create(audio.Path);
+ var tagTypes = file.TagTypesOnDisk;
+
+ if (tagTypes.HasFlag(TagTypes.Id3v2))
+ {
+ tags = file.GetTag(TagTypes.Id3v2);
+ }
+ else if (tagTypes.HasFlag(TagTypes.Ape))
+ {
+ tags = file.GetTag(TagTypes.Ape);
+ }
+ else if (tagTypes.HasFlag(TagTypes.FlacMetadata))
+ {
+ tags = file.GetTag(TagTypes.FlacMetadata);
+ }
+ else if (tagTypes.HasFlag(TagTypes.Apple))
+ {
+ tags = file.GetTag(TagTypes.Apple);
+ }
+ else if (tagTypes.HasFlag(TagTypes.Xiph))
+ {
+ tags = file.GetTag(TagTypes.Xiph);
+ }
+ else if (tagTypes.HasFlag(TagTypes.AudibleMetadata))
+ {
+ tags = file.GetTag(TagTypes.AudibleMetadata);
+ }
+ else if (tagTypes.HasFlag(TagTypes.Id3v1))
+ {
+ tags = file.GetTag(TagTypes.Id3v1);
+ }
}
- else if (tagTypes.HasFlag(TagTypes.Id3v1))
+ catch (Exception e)
{
- tags = file.GetTag(TagTypes.Id3v1);
+ _logger.LogWarning(e, "TagLib-Sharp does not support this audio");
}
- if (tags is not null)
+ tags ??= new TagLib.Id3v2.Tag();
+ tags.AlbumArtists ??= mediaInfo.AlbumArtists;
+ tags.Album ??= mediaInfo.Album;
+ tags.Title ??= mediaInfo.Name;
+ tags.Year = tags.Year == 0U ? Convert.ToUInt32(mediaInfo.ProductionYear, CultureInfo.InvariantCulture) : tags.Year;
+ tags.Performers ??= mediaInfo.Artists;
+ tags.Genres ??= mediaInfo.Genres;
+ tags.Track = tags.Track == 0U ? Convert.ToUInt32(mediaInfo.IndexNumber, CultureInfo.InvariantCulture) : tags.Track;
+ tags.Disc = tags.Disc == 0U ? Convert.ToUInt32(mediaInfo.ParentIndexNumber, CultureInfo.InvariantCulture) : tags.Disc;
+
+ if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast))
{
- if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast))
+ var people = new List<PersonInfo>();
+ var albumArtists = tags.AlbumArtists;
+ foreach (var albumArtist in albumArtists)
{
- var people = new List<PersonInfo>();
- var albumArtists = tags.AlbumArtists;
- foreach (var albumArtist in albumArtists)
+ if (!string.IsNullOrEmpty(albumArtist))
{
- if (!string.IsNullOrEmpty(albumArtist))
+ PeopleHelper.AddPerson(people, new PersonInfo
{
- PeopleHelper.AddPerson(people, new PersonInfo
- {
- Name = albumArtist,
- Type = PersonKind.AlbumArtist
- });
- }
+ Name = albumArtist,
+ Type = PersonKind.AlbumArtist
+ });
}
+ }
- var performers = tags.Performers;
- foreach (var performer in performers)
+ var performers = tags.Performers;
+ foreach (var performer in performers)
+ {
+ if (!string.IsNullOrEmpty(performer))
{
- if (!string.IsNullOrEmpty(performer))
+ PeopleHelper.AddPerson(people, new PersonInfo
{
- PeopleHelper.AddPerson(people, new PersonInfo
- {
- Name = performer,
- Type = PersonKind.Artist
- });
- }
+ Name = performer,
+ Type = PersonKind.Artist
+ });
}
+ }
- foreach (var composer in tags.Composers)
+ foreach (var composer in tags.Composers)
+ {
+ if (!string.IsNullOrEmpty(composer))
{
- if (!string.IsNullOrEmpty(composer))
+ PeopleHelper.AddPerson(people, new PersonInfo
{
- PeopleHelper.AddPerson(people, new PersonInfo
- {
- Name = composer,
- Type = PersonKind.Composer
- });
- }
+ Name = composer,
+ Type = PersonKind.Composer
+ });
}
+ }
- _libraryManager.UpdatePeople(audio, people);
-
- if (options.ReplaceAllMetadata && performers.Length != 0)
- {
- audio.Artists = performers;
- }
- else if (!options.ReplaceAllMetadata
- && (audio.Artists is null || audio.Artists.Count == 0))
- {
- audio.Artists = performers;
- }
+ _libraryManager.UpdatePeople(audio, people);
- if (options.ReplaceAllMetadata && albumArtists.Length != 0)
- {
- audio.AlbumArtists = albumArtists;
- }
- else if (!options.ReplaceAllMetadata
- && (audio.AlbumArtists is null || audio.AlbumArtists.Count == 0))
- {
- audio.AlbumArtists = albumArtists;
- }
+ if (options.ReplaceAllMetadata && performers.Length != 0)
+ {
+ audio.Artists = performers;
+ }
+ else if (!options.ReplaceAllMetadata
+ && (audio.Artists is null || audio.Artists.Count == 0))
+ {
+ audio.Artists = performers;
}
- if (!audio.LockedFields.Contains(MetadataField.Name) && !string.IsNullOrEmpty(tags.Title))
+ if (albumArtists.Length == 0)
{
- audio.Name = tags.Title;
+ // Album artists not provided, fall back to performers (artists).
+ albumArtists = performers;
}
- if (options.ReplaceAllMetadata)
+ if (options.ReplaceAllMetadata && albumArtists.Length != 0)
{
- audio.Album = tags.Album;
- audio.IndexNumber = Convert.ToInt32(tags.Track);
- audio.ParentIndexNumber = Convert.ToInt32(tags.Disc);
+ audio.AlbumArtists = albumArtists;
}
- else
+ else if (!options.ReplaceAllMetadata
+ && (audio.AlbumArtists is null || audio.AlbumArtists.Count == 0))
{
- audio.Album ??= tags.Album;
- audio.IndexNumber ??= Convert.ToInt32(tags.Track);
- audio.ParentIndexNumber ??= Convert.ToInt32(tags.Disc);
+ audio.AlbumArtists = albumArtists;
}
+ }
- if (tags.Year != 0)
- {
- var year = Convert.ToInt32(tags.Year);
- audio.ProductionYear = year;
+ if (!audio.LockedFields.Contains(MetadataField.Name) && !string.IsNullOrEmpty(tags.Title))
+ {
+ audio.Name = tags.Title;
+ }
+
+ if (options.ReplaceAllMetadata)
+ {
+ audio.Album = tags.Album;
+ audio.IndexNumber = Convert.ToInt32(tags.Track);
+ audio.ParentIndexNumber = Convert.ToInt32(tags.Disc);
+ }
+ else
+ {
+ audio.Album ??= tags.Album;
+ audio.IndexNumber ??= Convert.ToInt32(tags.Track);
+ audio.ParentIndexNumber ??= Convert.ToInt32(tags.Disc);
+ }
+
+ if (tags.Year != 0)
+ {
+ var year = Convert.ToInt32(tags.Year);
+ audio.ProductionYear = year;
- if (!audio.PremiereDate.HasValue)
+ if (!audio.PremiereDate.HasValue)
+ {
+ try
{
audio.PremiereDate = new DateTime(year, 01, 01);
}
+ catch (ArgumentOutOfRangeException ex)
+ {
+ _logger.LogError(ex, "Error parsing YEAR tag in {File}. '{TagValue}' is an invalid year", audio.Path, tags.Year);
+ }
}
+ }
- if (!audio.LockedFields.Contains(MetadataField.Genres))
- {
- audio.Genres = options.ReplaceAllMetadata || audio.Genres == null || audio.Genres.Length == 0
- ? tags.Genres.Distinct(StringComparer.OrdinalIgnoreCase).ToArray()
- : audio.Genres;
- }
+ if (!audio.LockedFields.Contains(MetadataField.Genres))
+ {
+ audio.Genres = options.ReplaceAllMetadata || audio.Genres == null || audio.Genres.Length == 0
+ ? tags.Genres.Distinct(StringComparer.OrdinalIgnoreCase).ToArray()
+ : audio.Genres;
+ }
- if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out _))
- {
- audio.SetProviderId(MetadataProvider.MusicBrainzArtist, tags.MusicBrainzArtistId);
- }
+ if (!double.IsNaN(tags.ReplayGainTrackGain))
+ {
+ audio.NormalizationGain = (float)tags.ReplayGainTrackGain;
+ }
- if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbumArtist, out _))
- {
- audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, tags.MusicBrainzReleaseArtistId);
- }
+ if ((options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out _))
+ && !string.IsNullOrEmpty(tags.MusicBrainzArtistId))
+ {
+ audio.SetProviderId(MetadataProvider.MusicBrainzArtist, tags.MusicBrainzArtistId);
+ }
- if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbum, out _))
- {
- audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, tags.MusicBrainzReleaseId);
- }
+ if ((options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbumArtist, out _))
+ && !string.IsNullOrEmpty(tags.MusicBrainzReleaseArtistId))
+ {
+ audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, tags.MusicBrainzReleaseArtistId);
+ }
- if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzReleaseGroup, out _))
- {
- audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, tags.MusicBrainzReleaseGroupId);
- }
+ if ((options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbum, out _))
+ && !string.IsNullOrEmpty(tags.MusicBrainzReleaseId))
+ {
+ audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, tags.MusicBrainzReleaseId);
+ }
- if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzTrack, out _))
- {
- audio.SetProviderId(MetadataProvider.MusicBrainzTrack, tags.MusicBrainzTrackId);
- }
+ if ((options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzReleaseGroup, out _))
+ && !string.IsNullOrEmpty(tags.MusicBrainzReleaseGroupId))
+ {
+ audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, tags.MusicBrainzReleaseGroupId);
+ }
- // Save extracted lyrics if they exist,
- // and if we are replacing all metadata or the audio doesn't yet have lyrics.
- if (!string.IsNullOrWhiteSpace(tags.Lyrics)
- && (options.ReplaceAllMetadata || audio.GetMediaStreams().All(s => s.Type != MediaStreamType.Lyric)))
+ if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzTrack, out _))
+ {
+ // Fallback to ffprobe as TagLib incorrectly provides recording MBID in `tags.MusicBrainzTrackId`.
+ // See https://github.com/mono/taglib-sharp/issues/304
+ var trackMbId = mediaInfo.GetProviderId(MetadataProvider.MusicBrainzTrack);
+ if (trackMbId is not null)
{
- await _lyricManager.SaveLyricAsync(audio, "lrc", tags.Lyrics).ConfigureAwait(false);
+ audio.SetProviderId(MetadataProvider.MusicBrainzTrack, trackMbId);
}
}
+
+ // Save extracted lyrics if they exist,
+ // and if the audio doesn't yet have lyrics.
+ if (!string.IsNullOrWhiteSpace(tags.Lyrics)
+ && tryExtractEmbeddedLyrics)
+ {
+ await _lyricManager.SaveLyricAsync(audio, "lrc", tags.Lyrics).ConfigureAwait(false);
+ }
}
private void AddExternalLyrics(
@@ -429,7 +378,10 @@ namespace MediaBrowser.Providers.MediaInfo
var externalLyricFiles = _lyricResolver.GetExternalStreams(audio, startIndex, options.DirectoryService, false);
audio.LyricFiles = externalLyricFiles.Select(i => i.Path).Distinct().ToArray();
- currentStreams.AddRange(externalLyricFiles);
+ if (externalLyricFiles.Count > 0)
+ {
+ currentStreams.Add(externalLyricFiles[0]);
+ }
}
}
}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index 5d0fccbe1..246ba2733 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -124,11 +124,8 @@ namespace MediaBrowser.Providers.MediaInfo
// Get BD disc information
blurayDiscInfo = GetBDInfo(item.Path);
- // Get playable .m2ts files
- var m2ts = _mediaEncoder.GetPrimaryPlaylistM2tsFiles(item.Path);
-
// Return if no playable .m2ts files are found
- if (blurayDiscInfo is null || blurayDiscInfo.Files.Length == 0 || m2ts.Count == 0)
+ if (blurayDiscInfo is null || blurayDiscInfo.Files.Length == 0)
{
_logger.LogError("No playable .m2ts files found in Blu-ray structure, skipping FFprobe.");
return ItemUpdateType.MetadataImport;
@@ -138,7 +135,7 @@ namespace MediaBrowser.Providers.MediaInfo
mediaInfoResult = await GetMediaInfo(
new Video
{
- Path = m2ts[0]
+ Path = blurayDiscInfo.Files[0]
},
cancellationToken).ConfigureAwait(false);
}
@@ -358,6 +355,10 @@ namespace MediaBrowser.Providers.MediaInfo
blurayVideoStream.BitRate = blurayVideoStream.BitRate.GetValueOrDefault() == 0 ? ffmpegVideoStream.BitRate : blurayVideoStream.BitRate;
blurayVideoStream.Width = blurayVideoStream.Width.GetValueOrDefault() == 0 ? ffmpegVideoStream.Width : blurayVideoStream.Width;
blurayVideoStream.Height = blurayVideoStream.Height.GetValueOrDefault() == 0 ? ffmpegVideoStream.Width : blurayVideoStream.Height;
+ blurayVideoStream.ColorRange = ffmpegVideoStream.ColorRange;
+ blurayVideoStream.ColorSpace = ffmpegVideoStream.ColorSpace;
+ blurayVideoStream.ColorTransfer = ffmpegVideoStream.ColorTransfer;
+ blurayVideoStream.ColorPrimaries = ffmpegVideoStream.ColorPrimaries;
}
}
diff --git a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs
index 8bb8d5bb4..04da8fb88 100644
--- a/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/ProbeProvider.cs
@@ -1,6 +1,7 @@
#nullable disable
using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
@@ -141,19 +142,15 @@ namespace MediaBrowser.Providers.MediaInfo
&& item.SupportsLocalMetadata
&& !video.IsPlaceHolder)
{
- if (!video.SubtitleFiles.SequenceEqual(
- _subtitleResolver.GetExternalFiles(video, directoryService, false)
- .Select(info => info.Path).ToList(),
- StringComparer.Ordinal))
+ var externalFiles = new HashSet<string>(_subtitleResolver.GetExternalFiles(video, directoryService, false).Select(info => info.Path), StringComparer.OrdinalIgnoreCase);
+ if (!new HashSet<string>(video.SubtitleFiles, StringComparer.Ordinal).SetEquals(externalFiles))
{
_logger.LogDebug("Refreshing {ItemPath} due to external subtitles change.", item.Path);
return true;
}
- if (!video.AudioFiles.SequenceEqual(
- _audioResolver.GetExternalFiles(video, directoryService, false)
- .Select(info => info.Path).ToList(),
- StringComparer.Ordinal))
+ externalFiles = new HashSet<string>(_audioResolver.GetExternalFiles(video, directoryService, false).Select(info => info.Path), StringComparer.OrdinalIgnoreCase);
+ if (!new HashSet<string>(video.AudioFiles, StringComparer.Ordinal).SetEquals(externalFiles))
{
_logger.LogDebug("Refreshing {ItemPath} due to external audio change.", item.Path);
return true;
@@ -161,14 +158,14 @@ namespace MediaBrowser.Providers.MediaInfo
}
if (item is Audio audio
- && item.SupportsLocalMetadata
- && !audio.LyricFiles.SequenceEqual(
- _lyricResolver.GetExternalFiles(audio, directoryService, false)
- .Select(info => info.Path).ToList(),
- StringComparer.Ordinal))
+ && item.SupportsLocalMetadata)
{
- _logger.LogDebug("Refreshing {ItemPath} due to external lyrics change.", item.Path);
- return true;
+ var externalFiles = new HashSet<string>(_lyricResolver.GetExternalFiles(audio, directoryService, false).Select(info => info.Path), StringComparer.OrdinalIgnoreCase);
+ if (!new HashSet<string>(audio.LyricFiles, StringComparer.Ordinal).SetEquals(externalFiles))
+ {
+ _logger.LogDebug("Refreshing {ItemPath} due to external lyrics change.", item.Path);
+ return true;
+ }
}
return false;
diff --git a/MediaBrowser.Providers/Movies/MovieMetadataService.cs b/MediaBrowser.Providers/Movies/MovieMetadataService.cs
index 984a3c122..8997ddc64 100644
--- a/MediaBrowser.Providers/Movies/MovieMetadataService.cs
+++ b/MediaBrowser.Providers/Movies/MovieMetadataService.cs
@@ -24,22 +24,6 @@ namespace MediaBrowser.Providers.Movies
}
/// <inheritdoc />
- protected override bool IsFullLocalMetadata(Movie item)
- {
- if (string.IsNullOrWhiteSpace(item.Overview))
- {
- return false;
- }
-
- if (!item.ProductionYear.HasValue)
- {
- return false;
- }
-
- return base.IsFullLocalMetadata(item);
- }
-
- /// <inheritdoc />
protected override void MergeData(MetadataResult<Movie> source, MetadataResult<Movie> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
{
base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings);
diff --git a/MediaBrowser.Providers/Movies/TrailerMetadataService.cs b/MediaBrowser.Providers/Movies/TrailerMetadataService.cs
index ad0c5aaa7..e77d2fa8a 100644
--- a/MediaBrowser.Providers/Movies/TrailerMetadataService.cs
+++ b/MediaBrowser.Providers/Movies/TrailerMetadataService.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System.Linq;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -24,22 +25,6 @@ namespace MediaBrowser.Providers.Movies
}
/// <inheritdoc />
- protected override bool IsFullLocalMetadata(Trailer item)
- {
- if (string.IsNullOrWhiteSpace(item.Overview))
- {
- return false;
- }
-
- if (!item.ProductionYear.HasValue)
- {
- return false;
- }
-
- return base.IsFullLocalMetadata(item);
- }
-
- /// <inheritdoc />
protected override void MergeData(MetadataResult<Trailer> source, MetadataResult<Trailer> target, MetadataField[] lockedFields, bool replaceData, bool mergeMetadataSettings)
{
base.MergeData(source, target, lockedFields, replaceData, mergeMetadataSettings);
@@ -48,6 +33,10 @@ namespace MediaBrowser.Providers.Movies
{
target.Item.TrailerTypes = source.Item.TrailerTypes;
}
+ else
+ {
+ target.Item.TrailerTypes = target.Item.TrailerTypes.Concat(source.Item.TrailerTypes).Distinct().ToArray();
+ }
}
}
}
diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
index e4f34776b..a39bd16ce 100644
--- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs
+++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
@@ -225,6 +225,10 @@ namespace MediaBrowser.Providers.Music
{
targetItem.Artists = sourceItem.Artists;
}
+ else
+ {
+ targetItem.Artists = targetItem.Artists.Concat(sourceItem.Artists).Distinct().ToArray();
+ }
if (replaceData || string.IsNullOrEmpty(targetItem.GetProviderId(MetadataProvider.MusicBrainzAlbumArtist)))
{
diff --git a/MediaBrowser.Providers/Music/AudioMetadataService.cs b/MediaBrowser.Providers/Music/AudioMetadataService.cs
index a5b7cb895..7b25bc0e4 100644
--- a/MediaBrowser.Providers/Music/AudioMetadataService.cs
+++ b/MediaBrowser.Providers/Music/AudioMetadataService.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
@@ -60,6 +61,10 @@ namespace MediaBrowser.Providers.Music
{
targetItem.Artists = sourceItem.Artists;
}
+ else
+ {
+ targetItem.Artists = targetItem.Artists.Concat(sourceItem.Artists).Distinct().ToArray();
+ }
if (replaceData || string.IsNullOrEmpty(targetItem.Album))
{
diff --git a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
index b97b76630..24c4b5501 100644
--- a/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
+++ b/MediaBrowser.Providers/Music/MusicVideoMetadataService.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System.Linq;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -45,6 +46,10 @@ namespace MediaBrowser.Providers.Music
{
targetItem.Artists = sourceItem.Artists;
}
+ else
+ {
+ targetItem.Artists = targetItem.Artists.Concat(sourceItem.Artists).Distinct().ToArray();
+ }
}
}
}
diff --git a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs
index 9bd36f25c..51a3ba0c7 100644
--- a/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs
+++ b/MediaBrowser.Providers/Playlists/PlaylistItemsProvider.cs
@@ -1,170 +1,227 @@
#nullable disable
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
using PlaylistsNET.Content;
-namespace MediaBrowser.Providers.Playlists
+namespace MediaBrowser.Providers.Playlists;
+
+/// <summary>
+/// Local playlist provider.
+/// </summary>
+public class PlaylistItemsProvider : ILocalMetadataProvider<Playlist>,
+ IHasOrder,
+ IForcedProvider,
+ IHasItemChangeMonitor
{
- public class PlaylistItemsProvider : ICustomMetadataProvider<Playlist>,
- IHasOrder,
- IForcedProvider,
- IPreRefreshProvider,
- IHasItemChangeMonitor
+ private readonly IFileSystem _fileSystem;
+ private readonly ILibraryManager _libraryManager;
+ private readonly ILogger<PlaylistItemsProvider> _logger;
+ private readonly CollectionType[] _ignoredCollections = [CollectionType.livetv, CollectionType.boxsets, CollectionType.playlists];
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PlaylistItemsProvider"/> class.
+ /// </summary>
+ /// <param name="logger">Instance of the <see cref="ILogger{PlaylistItemsProvider}"/> interface.</param>
+ /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
+ /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+ public PlaylistItemsProvider(ILogger<PlaylistItemsProvider> logger, ILibraryManager libraryManager, IFileSystem fileSystem)
{
- private readonly ILogger<PlaylistItemsProvider> _logger;
-
- public PlaylistItemsProvider(ILogger<PlaylistItemsProvider> logger)
- {
- _logger = logger;
- }
+ _logger = logger;
+ _libraryManager = libraryManager;
+ _fileSystem = fileSystem;
+ }
- public string Name => "Playlist Reader";
+ /// <inheritdoc />
+ public string Name => "Playlist Item Provider";
- // Run last
- public int Order => 100;
+ /// <inheritdoc />
+ public int Order => 100;
- public Task<ItemUpdateType> FetchAsync(Playlist item, MetadataRefreshOptions options, CancellationToken cancellationToken)
+ /// <inheritdoc />
+ public Task<MetadataResult<Playlist>> GetMetadata(
+ ItemInfo info,
+ IDirectoryService directoryService,
+ CancellationToken cancellationToken)
+ {
+ var result = new MetadataResult<Playlist>()
{
- var path = item.Path;
- if (!Playlist.IsPlaylistFile(path))
+ Item = new Playlist
{
- return Task.FromResult(ItemUpdateType.None);
+ Path = info.Path
}
+ };
+ Fetch(result);
- var extension = Path.GetExtension(path);
- if (!Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparison.OrdinalIgnoreCase))
- {
- return Task.FromResult(ItemUpdateType.None);
- }
+ return Task.FromResult(result);
+ }
- using (var stream = File.OpenRead(path))
- {
- var items = GetItems(stream, extension).ToArray();
+ private void Fetch(MetadataResult<Playlist> result)
+ {
+ var item = result.Item;
+ var path = item.Path;
+ if (!Playlist.IsPlaylistFile(path))
+ {
+ return;
+ }
- item.LinkedChildren = items;
- }
+ var extension = Path.GetExtension(path);
+ if (!Playlist.SupportedExtensions.Contains(extension ?? string.Empty, StringComparison.OrdinalIgnoreCase))
+ {
+ return;
+ }
- return Task.FromResult(ItemUpdateType.None);
+ var items = GetItems(path, extension).ToArray();
+ if (items.Length > 0)
+ {
+ result.HasMetadata = true;
+ item.LinkedChildren = items;
}
- private IEnumerable<LinkedChild> GetItems(Stream stream, string extension)
+ return;
+ }
+
+ private IEnumerable<LinkedChild> GetItems(string path, string extension)
+ {
+ var libraryRoots = _libraryManager.GetUserRootFolder().Children
+ .OfType<CollectionFolder>()
+ .Where(f => f.CollectionType.HasValue && !_ignoredCollections.Contains(f.CollectionType.Value))
+ .SelectMany(f => f.PhysicalLocations)
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToList();
+
+ using (var stream = File.OpenRead(path))
{
if (string.Equals(".wpl", extension, StringComparison.OrdinalIgnoreCase))
{
- return GetWplItems(stream);
+ return GetWplItems(stream, path, libraryRoots);
}
if (string.Equals(".zpl", extension, StringComparison.OrdinalIgnoreCase))
{
- return GetZplItems(stream);
+ return GetZplItems(stream, path, libraryRoots);
}
if (string.Equals(".m3u", extension, StringComparison.OrdinalIgnoreCase))
{
- return GetM3uItems(stream);
+ return GetM3uItems(stream, path, libraryRoots);
}
if (string.Equals(".m3u8", extension, StringComparison.OrdinalIgnoreCase))
{
- return GetM3u8Items(stream);
+ return GetM3uItems(stream, path, libraryRoots);
}
if (string.Equals(".pls", extension, StringComparison.OrdinalIgnoreCase))
{
- return GetPlsItems(stream);
+ return GetPlsItems(stream, path, libraryRoots);
}
-
- return Enumerable.Empty<LinkedChild>();
}
- private IEnumerable<LinkedChild> GetPlsItems(Stream stream)
- {
- var content = new PlsContent();
- var playlist = content.GetFromStream(stream);
+ return Enumerable.Empty<LinkedChild>();
+ }
- return playlist.PlaylistEntries.Select(i => new LinkedChild
- {
- Path = i.Path,
- Type = LinkedChildType.Manual
- });
- }
+ private IEnumerable<LinkedChild> GetPlsItems(Stream stream, string playlistPath, List<string> libraryRoots)
+ {
+ var content = new PlsContent();
+ var playlist = content.GetFromStream(stream);
- private IEnumerable<LinkedChild> GetM3u8Items(Stream stream)
- {
- var content = new M3uContent();
- var playlist = content.GetFromStream(stream);
+ return playlist.PlaylistEntries
+ .Select(i => GetLinkedChild(i.Path, playlistPath, libraryRoots))
+ .Where(i => i is not null);
+ }
- return playlist.PlaylistEntries.Select(i => new LinkedChild
- {
- Path = i.Path,
- Type = LinkedChildType.Manual
- });
- }
+ private IEnumerable<LinkedChild> GetM3uItems(Stream stream, string playlistPath, List<string> libraryRoots)
+ {
+ var content = new M3uContent();
+ var playlist = content.GetFromStream(stream);
- private IEnumerable<LinkedChild> GetM3uItems(Stream stream)
- {
- var content = new M3uContent();
- var playlist = content.GetFromStream(stream);
+ return playlist.PlaylistEntries
+ .Select(i => GetLinkedChild(i.Path, playlistPath, libraryRoots))
+ .Where(i => i is not null);
+ }
- return playlist.PlaylistEntries.Select(i => new LinkedChild
- {
- Path = i.Path,
- Type = LinkedChildType.Manual
- });
- }
+ private IEnumerable<LinkedChild> GetZplItems(Stream stream, string playlistPath, List<string> libraryRoots)
+ {
+ var content = new ZplContent();
+ var playlist = content.GetFromStream(stream);
- private IEnumerable<LinkedChild> GetZplItems(Stream stream)
- {
- var content = new ZplContent();
- var playlist = content.GetFromStream(stream);
+ return playlist.PlaylistEntries
+ .Select(i => GetLinkedChild(i.Path, playlistPath, libraryRoots))
+ .Where(i => i is not null);
+ }
+
+ private IEnumerable<LinkedChild> GetWplItems(Stream stream, string playlistPath, List<string> libraryRoots)
+ {
+ var content = new WplContent();
+ var playlist = content.GetFromStream(stream);
- return playlist.PlaylistEntries.Select(i => new LinkedChild
+ return playlist.PlaylistEntries
+ .Select(i => GetLinkedChild(i.Path, playlistPath, libraryRoots))
+ .Where(i => i is not null);
+ }
+
+ private LinkedChild GetLinkedChild(string itemPath, string playlistPath, List<string> libraryRoots)
+ {
+ if (TryGetPlaylistItemPath(itemPath, playlistPath, libraryRoots, out var parsedPath))
+ {
+ return new LinkedChild
{
- Path = i.Path,
+ Path = parsedPath,
Type = LinkedChildType.Manual
- });
+ };
}
- private IEnumerable<LinkedChild> GetWplItems(Stream stream)
+ return null;
+ }
+
+ private bool TryGetPlaylistItemPath(string itemPath, string playlistPath, List<string> libraryPaths, out string path)
+ {
+ path = null;
+ string pathToCheck = _fileSystem.MakeAbsolutePath(Path.GetDirectoryName(playlistPath), itemPath);
+ if (!File.Exists(pathToCheck))
{
- var content = new WplContent();
- var playlist = content.GetFromStream(stream);
+ return false;
+ }
- return playlist.PlaylistEntries.Select(i => new LinkedChild
+ foreach (var libraryPath in libraryPaths)
+ {
+ if (pathToCheck.StartsWith(libraryPath, StringComparison.OrdinalIgnoreCase))
{
- Path = i.Path,
- Type = LinkedChildType.Manual
- });
+ path = pathToCheck;
+ return true;
+ }
}
- public bool HasChanged(BaseItem item, IDirectoryService directoryService)
- {
- var path = item.Path;
+ return false;
+ }
- if (!string.IsNullOrWhiteSpace(path) && item.IsFileProtocol)
+ /// <inheritdoc />
+ public bool HasChanged(BaseItem item, IDirectoryService directoryService)
+ {
+ var path = item.Path;
+ if (!string.IsNullOrWhiteSpace(path) && item.IsFileProtocol)
+ {
+ var file = directoryService.GetFile(path);
+ if (file is not null && file.LastWriteTimeUtc != item.DateModified)
{
- var file = directoryService.GetFile(path);
- if (file is not null && file.LastWriteTimeUtc != item.DateModified)
- {
- _logger.LogDebug("Refreshing {0} due to date modified timestamp change.", path);
- return true;
- }
+ _logger.LogDebug("Refreshing {Path} due to date modified timestamp change.", path);
+ return true;
}
-
- return false;
}
+
+ return false;
}
}
diff --git a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
index 1bd000a48..43889bfbf 100644
--- a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
+++ b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs
@@ -1,6 +1,7 @@
#pragma warning disable CS1591
using System.Collections.Generic;
+using System.Linq;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -49,8 +50,24 @@ namespace MediaBrowser.Providers.Playlists
if (mergeMetadataSettings)
{
targetItem.PlaylistMediaType = sourceItem.PlaylistMediaType;
- targetItem.LinkedChildren = sourceItem.LinkedChildren;
- targetItem.Shares = sourceItem.Shares;
+
+ if (replaceData || targetItem.LinkedChildren.Length == 0)
+ {
+ targetItem.LinkedChildren = sourceItem.LinkedChildren;
+ }
+ else
+ {
+ targetItem.LinkedChildren = sourceItem.LinkedChildren.Concat(targetItem.LinkedChildren).Distinct().ToArray();
+ }
+
+ if (replaceData || targetItem.Shares.Count == 0)
+ {
+ targetItem.Shares = sourceItem.Shares;
+ }
+ else
+ {
+ targetItem.Shares = sourceItem.Shares.Concat(targetItem.Shares).DistinctBy(s => s.UserId).ToArray();
+ }
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
index d0bd7d609..c35324746 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
@@ -250,7 +250,7 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu
// If we have a release ID but not a release group ID, lookup the release group
if (!string.IsNullOrWhiteSpace(releaseId) && string.IsNullOrWhiteSpace(releaseGroupId))
{
- var release = await _musicBrainzQuery.LookupReleaseAsync(new Guid(releaseId), Include.Releases, cancellationToken).ConfigureAwait(false);
+ var release = await _musicBrainzQuery.LookupReleaseAsync(new Guid(releaseId), Include.ReleaseGroups, cancellationToken).ConfigureAwait(false);
releaseGroupId = release.ReleaseGroup?.Id.ToString();
result.HasMetadata = true;
}
diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
index 3fd4ae1fc..c750caa1c 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs
@@ -447,11 +447,6 @@ namespace MediaBrowser.Providers.Plugins.Omdb
var actorList = result.Actors.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
foreach (var actor in actorList)
{
- if (string.IsNullOrWhiteSpace(actor))
- {
- continue;
- }
-
var person = new PersonInfo
{
Name = actor,
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
index 8dc2d6938..d8476bd47 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs
@@ -278,17 +278,12 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
series.RunTimeTicks = seriesResult.EpisodeRunTime.Select(i => TimeSpan.FromMinutes(i).Ticks).FirstOrDefault();
- if (string.Equals(seriesResult.Status, "Ended", StringComparison.OrdinalIgnoreCase)
- || string.Equals(seriesResult.Status, "Canceled", StringComparison.OrdinalIgnoreCase))
+ if (Emby.Naming.TV.TvParserHelpers.TryParseSeriesStatus(seriesResult.Status, out var seriesStatus))
{
- series.Status = SeriesStatus.Ended;
- series.EndDate = seriesResult.LastAirDate;
- }
- else
- {
- series.Status = SeriesStatus.Continuing;
+ series.Status = seriesStatus;
}
+ series.EndDate = seriesResult.LastAirDate;
series.PremiereDate = seriesResult.FirstAirDate;
var ids = seriesResult.ExternalIds;
diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
index f68b3cee6..ae5e1090a 100644
--- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
+++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
@@ -117,7 +117,7 @@ namespace MediaBrowser.Providers.Subtitles
}
catch (Exception ex)
{
- _logger.LogError(ex, "Error downloading subtitles from {0}", i.Name);
+ _logger.LogError(ex, "Error downloading subtitles from {Name}", i.Name);
return Array.Empty<RemoteSubtitleInfo>();
}
});
@@ -205,72 +205,71 @@ namespace MediaBrowser.Providers.Subtitles
saveFileName += ".sdh";
}
- saveFileName += "." + response.Format.ToLowerInvariant();
-
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(mediaFolderPath);
- }
+ savePaths.Add(mediaFolderPath);
}
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);
- }
+ 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.");
- }
+ await TrySaveToFiles(memoryStream, savePaths, video, response.Format.ToLowerInvariant()).ConfigureAwait(false);
}
}
- private async Task TrySaveToFiles(Stream stream, List<string> savePaths)
+ private async Task TrySaveToFiles(Stream stream, List<string> savePaths, Video video, string extension)
{
List<Exception>? exs = null;
foreach (var savePath in savePaths)
{
- _logger.LogInformation("Saving subtitles to {SavePath}", savePath);
-
- _monitor.ReportFileSystemChangeBeginning(savePath);
-
+ var path = savePath + "." + extension;
try
{
- Directory.CreateDirectory(Path.GetDirectoryName(savePath) ?? throw new InvalidOperationException("Path can't be a root directory."));
+ if (path.StartsWith(video.ContainingFolderPath, StringComparison.Ordinal)
+ || path.StartsWith(video.GetInternalMetadataPath(), StringComparison.Ordinal))
+ {
+ var fileExists = File.Exists(path);
+ var counter = 0;
+
+ while (fileExists)
+ {
+ path = string.Format(CultureInfo.InvariantCulture, "{0}.{1}.{2}", savePath, counter, extension);
+ fileExists = File.Exists(path);
+ counter++;
+ }
+
+ _logger.LogInformation("Saving subtitles to {SavePath}", path);
+ _monitor.ReportFileSystemChangeBeginning(path);
+
+ Directory.CreateDirectory(Path.GetDirectoryName(path) ?? throw new InvalidOperationException("Path can't be a root directory."));
- var fileOptions = AsyncFile.WriteOptions;
- fileOptions.Mode = FileMode.CreateNew;
- fileOptions.PreallocationSize = stream.Length;
- var fs = new FileStream(savePath, fileOptions);
- await using (fs.ConfigureAwait(false))
+ var fileOptions = AsyncFile.WriteOptions;
+ fileOptions.Mode = FileMode.CreateNew;
+ fileOptions.PreallocationSize = stream.Length;
+ var fs = new FileStream(path, fileOptions);
+ await using (fs.ConfigureAwait(false))
+ {
+ await stream.CopyToAsync(fs).ConfigureAwait(false);
+ }
+
+ return;
+ }
+ else
{
- await stream.CopyToAsync(fs).ConfigureAwait(false);
+ // TODO: Add some error handling to the API user: return BadRequest("Could not save subtitle, bad path.");
+ _logger.LogError("An uploaded subtitle could not be saved because the resulting path was invalid.");
}
-
- return;
}
catch (Exception ex)
{
-// Bug in analyzer -- https://github.com/dotnet/roslyn-analyzers/issues/5160
-#pragma warning disable CA1508
- (exs ??= new List<Exception>()).Add(ex);
-#pragma warning restore CA1508
+ (exs ??= []).Add(ex);
}
finally
{
- _monitor.ReportFileSystemChangeComplete(savePath, false);
+ _monitor.ReportFileSystemChangeComplete(path, false);
}
stream.Position = 0;
diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
index 01c07d633..b03d6ffb5 100644
--- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs
+++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs
@@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -35,30 +36,33 @@ namespace MediaBrowser.Providers.TV
_localizationManager = localizationManager;
}
- /// <inheritdoc />
- protected override async Task AfterMetadataRefresh(Series item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
+ public override async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
{
- await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false);
+ if (item is Series series)
+ {
+ var seasons = series.GetRecursiveChildren(i => i is Season).ToList();
- RemoveObsoleteEpisodes(item);
- RemoveObsoleteSeasons(item);
- await UpdateAndCreateSeasonsAsync(item, cancellationToken).ConfigureAwait(false);
+ foreach (var season in seasons)
+ {
+ var hasUpdate = refreshOptions != null && season.BeforeMetadataRefresh(refreshOptions.ReplaceAllMetadata);
+ if (hasUpdate)
+ {
+ await season.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ }
+
+ return await base.RefreshMetadata(item, refreshOptions, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
- protected override bool IsFullLocalMetadata(Series item)
+ protected override async Task AfterMetadataRefresh(Series item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
{
- if (string.IsNullOrWhiteSpace(item.Overview))
- {
- return false;
- }
-
- if (!item.ProductionYear.HasValue)
- {
- return false;
- }
+ await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false);
- return base.IsFullLocalMetadata(item);
+ RemoveObsoleteEpisodes(item);
+ await CreateSeasonsAsync(item, cancellationToken).ConfigureAwait(false);
+ RemoveObsoleteSeasons(item);
}
/// <inheritdoc />
@@ -68,20 +72,6 @@ namespace MediaBrowser.Providers.TV
var sourceItem = source.Item;
var targetItem = target.Item;
- var sourceSeasonNames = sourceItem.SeasonNames;
- var targetSeasonNames = targetItem.SeasonNames;
-
- if (replaceData || targetSeasonNames.Count == 0)
- {
- targetItem.SeasonNames = sourceSeasonNames;
- }
- else if (targetSeasonNames.Count != sourceSeasonNames.Count || !sourceSeasonNames.Keys.All(targetSeasonNames.ContainsKey))
- {
- foreach (var (number, name) in sourceSeasonNames)
- {
- targetSeasonNames.TryAdd(number, name);
- }
- }
if (replaceData || string.IsNullOrEmpty(targetItem.AirTime))
{
@@ -101,7 +91,7 @@ namespace MediaBrowser.Providers.TV
private void RemoveObsoleteSeasons(Series series)
{
- // TODO Legacy. It's not really "physical" seasons as any virtual seasons are always converted to non-virtual in UpdateAndCreateSeasonsAsync.
+ // TODO Legacy. It's not really "physical" seasons as any virtual seasons are always converted to non-virtual in CreateSeasonsAsync.
var physicalSeasonNumbers = new HashSet<int>();
var virtualSeasons = new List<Season>();
foreach (var existingSeason in series.Children.OfType<Season>())
@@ -129,7 +119,8 @@ namespace MediaBrowser.Providers.TV
virtualSeason,
new DeleteOptions
{
- DeleteFileLocation = true
+ // Internal metadata paths are removed regardless of this.
+ DeleteFileLocation = false
},
false);
}
@@ -138,7 +129,7 @@ namespace MediaBrowser.Providers.TV
private void RemoveObsoleteEpisodes(Series series)
{
- var episodes = series.GetEpisodes(null, new DtoOptions()).OfType<Episode>().ToList();
+ var episodes = series.GetEpisodes(null, new DtoOptions(), true).OfType<Episode>().ToList();
var numberOfEpisodes = episodes.Count;
// TODO: O(n^2), but can it be done faster without overcomplicating it?
for (var i = 0; i < numberOfEpisodes; i++)
@@ -186,7 +177,8 @@ namespace MediaBrowser.Providers.TV
episode,
new DeleteOptions
{
- DeleteFileLocation = true
+ // Internal metadata paths are removed regardless of this.
+ DeleteFileLocation = false
},
false);
}
@@ -194,14 +186,12 @@ namespace MediaBrowser.Providers.TV
/// <summary>
/// Creates seasons for all episodes if they don't exist.
/// If no season number can be determined, a dummy season will be created.
- /// Updates seasons names.
/// </summary>
/// <param name="series">The series.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The async task.</returns>
- private async Task UpdateAndCreateSeasonsAsync(Series series, CancellationToken cancellationToken)
+ private async Task CreateSeasonsAsync(Series series, CancellationToken cancellationToken)
{
- var seasonNames = series.SeasonNames;
var seriesChildren = series.GetRecursiveChildren(i => i is Episode || i is Season);
var seasons = seriesChildren.OfType<Season>().ToList();
var uniqueSeasonNumbers = seriesChildren
@@ -214,20 +204,14 @@ namespace MediaBrowser.Providers.TV
{
// Null season numbers will have a 'dummy' season created because seasons are always required.
var existingSeason = seasons.FirstOrDefault(i => i.IndexNumber == seasonNumber);
-
- if (!seasonNumber.HasValue || !seasonNames.TryGetValue(seasonNumber.Value, out var seasonName))
- {
- seasonName = GetValidSeasonNameForSeries(series, null, seasonNumber);
- }
-
if (existingSeason is null)
{
- var season = await CreateSeasonAsync(series, seasonName, seasonNumber, cancellationToken).ConfigureAwait(false);
- series.AddChild(season);
+ var seasonName = GetValidSeasonNameForSeries(series, null, seasonNumber);
+ await CreateSeasonAsync(series, seasonName, seasonNumber, cancellationToken).ConfigureAwait(false);
}
- else if (!string.Equals(existingSeason.Name, seasonName, StringComparison.Ordinal))
+ else if (existingSeason.IsVirtualItem)
{
- existingSeason.Name = seasonName;
+ existingSeason.IsVirtualItem = false;
await existingSeason.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
}
}
@@ -241,7 +225,7 @@ namespace MediaBrowser.Providers.TV
/// <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(
+ private async Task CreateSeasonAsync(
Series series,
string? seasonName,
int? seasonNumber,
@@ -258,14 +242,12 @@ namespace MediaBrowser.Providers.TV
typeof(Season)),
IsVirtualItem = false,
SeriesId = series.Id,
- SeriesName = series.Name
+ SeriesName = series.Name,
+ SeriesPresentationUniqueKey = series.GetPresentationUniqueKey()
};
series.AddChild(season);
-
await season.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellationToken).ConfigureAwait(false);
-
- return season;
}
private string GetValidSeasonNameForSeries(Series series, string? seasonName, int? seasonNumber)
diff --git a/MediaBrowser.Providers/Trickplay/TrickplayProvider.cs b/MediaBrowser.Providers/Trickplay/TrickplayProvider.cs
index f6dcde4f6..9dc4446fc 100644
--- a/MediaBrowser.Providers/Trickplay/TrickplayProvider.cs
+++ b/MediaBrowser.Providers/Trickplay/TrickplayProvider.cs
@@ -101,7 +101,7 @@ public class TrickplayProvider : ICustomMetadataProvider<Episode>,
bool? enableDuringScan = libraryOptions?.ExtractTrickplayImagesDuringLibraryScan;
bool replace = options.ReplaceAllImages;
- if (options.IsAutomated && !enableDuringScan.GetValueOrDefault(false))
+ if (!enableDuringScan.GetValueOrDefault(false))
{
return ItemUpdateType.None;
}