< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Manager.MetadataService<T1, T2>
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Manager/MetadataService.cs
Line coverage
67%
Covered lines: 222
Uncovered lines: 108
Coverable lines: 330
Total lines: 1234
Line coverage: 67.2%
Branch coverage
66%
Covered branches: 223
Total branches: 336
Branch coverage: 66.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

File(s)

/srv/git/jellyfin/MediaBrowser.Providers/Manager/MetadataService.cs

#LineLine coverage
 1#nullable disable
 2
 3#pragma warning disable CS1591
 4
 5using System;
 6using System.Collections.Generic;
 7using System.Linq;
 8using System.Net.Http;
 9using System.Threading;
 10using System.Threading.Tasks;
 11using Jellyfin.Extensions;
 12using MediaBrowser.Controller.Configuration;
 13using MediaBrowser.Controller.Entities;
 14using MediaBrowser.Controller.Entities.Audio;
 15using MediaBrowser.Controller.Library;
 16using MediaBrowser.Controller.Providers;
 17using MediaBrowser.Model.Configuration;
 18using MediaBrowser.Model.Entities;
 19using MediaBrowser.Model.IO;
 20using MediaBrowser.Model.Providers;
 21using Microsoft.Extensions.Logging;
 22
 23namespace MediaBrowser.Providers.Manager
 24{
 25    public abstract class MetadataService<TItemType, TIdType> : IMetadataService
 26        where TItemType : BaseItem, IHasLookupInfo<TIdType>, new()
 27        where TIdType : ItemLookupInfo, new()
 28    {
 29        protected MetadataService(IServerConfigurationManager serverConfigurationManager, ILogger<MetadataService<TItemT
 30        {
 54631            ServerConfigurationManager = serverConfigurationManager;
 54632            Logger = logger;
 54633            ProviderManager = providerManager;
 54634            FileSystem = fileSystem;
 54635            LibraryManager = libraryManager;
 54636            ImageProvider = new ItemImageProvider(Logger, ProviderManager, FileSystem);
 54637        }
 38
 39        protected ItemImageProvider ImageProvider { get; }
 40
 41        protected IServerConfigurationManager ServerConfigurationManager { get; }
 42
 43        protected ILogger<MetadataService<TItemType, TIdType>> Logger { get; }
 44
 45        protected IProviderManager ProviderManager { get; }
 46
 47        protected IFileSystem FileSystem { get; }
 48
 49        protected ILibraryManager LibraryManager { get; }
 50
 2951        protected virtual bool EnableUpdatingPremiereDateFromChildren => false;
 52
 2953        protected virtual bool EnableUpdatingGenresFromChildren => false;
 54
 2955        protected virtual bool EnableUpdatingStudiosFromChildren => false;
 56
 2957        protected virtual bool EnableUpdatingOfficialRatingFromChildren => false;
 58
 50459        public virtual int Order => 0;
 60
 61        private FileSystemMetadata TryGetFile(string path, IDirectoryService directoryService)
 62        {
 63            try
 64            {
 5865                return directoryService.GetFile(path);
 66            }
 067            catch (Exception ex)
 68            {
 069                Logger.LogError(ex, "Error getting file {Path}", path);
 070                return null;
 71            }
 5872        }
 73
 74        public virtual async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, 
 75        {
 76            var itemOfType = (TItemType)item;
 77            var updateType = ItemUpdateType.None;
 78            var libraryOptions = LibraryManager.GetLibraryOptions(item);
 79            var isFirstRefresh = item.DateLastRefreshed == default;
 80            var hasRefreshedMetadata = true;
 81            var hasRefreshedImages = true;
 82
 83            var requiresRefresh = libraryOptions.AutomaticRefreshIntervalDays > 0 && (DateTime.UtcNow - item.DateLastRef
 84
 85            if (!requiresRefresh && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
 86            {
 87                // TODO: If this returns true, should we instead just change metadata refresh mode to Full?
 88                requiresRefresh = item.RequiresRefresh();
 89
 90                if (requiresRefresh)
 91                {
 92                    Logger.LogDebug("Refreshing {Type} {Item} because item.RequiresRefresh() returned true", typeof(TIte
 93                }
 94            }
 95
 96            if (refreshOptions.RemoveOldMetadata && refreshOptions.ReplaceAllImages)
 97            {
 98                if (ImageProvider.RemoveImages(item))
 99                {
 100                    updateType |= ItemUpdateType.ImageUpdate;
 101                }
 102            }
 103
 104            var localImagesFailed = false;
 105            var allImageProviders = ProviderManager.GetImageProviders(item, refreshOptions).ToList();
 106
 107            // Only validate already registered images if we are replacing and saving locally
 108            if (item.IsSaveLocalMetadataEnabled() && refreshOptions.ReplaceAllImages)
 109            {
 110                item.ValidateImages();
 111            }
 112            else
 113            {
 114                // Run full image validation and register new local images
 115                try
 116                {
 117                    if (ImageProvider.ValidateImages(item, allImageProviders.OfType<ILocalImageProvider>(), refreshOptio
 118                    {
 119                        updateType |= ItemUpdateType.ImageUpdate;
 120                    }
 121                }
 122                catch (Exception ex)
 123                {
 124                    localImagesFailed = true;
 125                    Logger.LogError(ex, "Error validating images for {Item}", item.Path ?? item.Name ?? "Unknown name");
 126                }
 127            }
 128
 129            var metadataResult = new MetadataResult<TItemType>
 130            {
 131                Item = itemOfType,
 132                People = LibraryManager.GetPeople(item)
 133            };
 134
 135            var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refresh
 136            updateType |= beforeSaveResult;
 137
 138            updateType = await SaveInternal(item, refreshOptions, updateType, isFirstRefresh, requiresRefresh, metadataR
 139
 140            // Next run metadata providers
 141            if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
 142            {
 143                var providers = GetProviders(item, libraryOptions, refreshOptions, isFirstRefresh, requiresRefresh)
 144                    .ToList();
 145
 146                if (providers.Count > 0 || isFirstRefresh || requiresRefresh)
 147                {
 148                    if (item.BeforeMetadataRefresh(refreshOptions.ReplaceAllMetadata))
 149                    {
 150                        updateType |= ItemUpdateType.MetadataImport;
 151                    }
 152                }
 153
 154                if (providers.Count > 0)
 155                {
 156                    var id = itemOfType.GetLookupInfo();
 157
 158                    if (refreshOptions.SearchResult is not null)
 159                    {
 160                        ApplySearchResult(id, refreshOptions.SearchResult);
 161                    }
 162
 163                    id.IsAutomated = refreshOptions.IsAutomated;
 164
 165                    var hasMetadataSavers = ProviderManager.GetMetadataSavers(item, libraryOptions).Any();
 166                    var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, ImageProvider
 167
 168                    updateType |= result.UpdateType;
 169                    if (result.Failures > 0)
 170                    {
 171                        hasRefreshedMetadata = false;
 172                    }
 173                }
 174            }
 175
 176            // Next run remote image providers, but only if local image providers didn't throw an exception
 177            if (!localImagesFailed && refreshOptions.ImageRefreshMode > MetadataRefreshMode.ValidationOnly)
 178            {
 179                var providers = GetNonLocalImageProviders(item, allImageProviders, refreshOptions).ToList();
 180
 181                if (providers.Count > 0)
 182                {
 183                    var result = await ImageProvider.RefreshImages(itemOfType, libraryOptions, providers, refreshOptions
 184
 185                    updateType |= result.UpdateType;
 186                    if (result.Failures > 0)
 187                    {
 188                        hasRefreshedImages = false;
 189                    }
 190                }
 191            }
 192
 193            if (hasRefreshedMetadata && hasRefreshedImages)
 194            {
 195                item.DateLastRefreshed = DateTime.UtcNow;
 196                updateType |= item.OnMetadataChanged();
 197            }
 198
 199            updateType = await SaveInternal(item, refreshOptions, updateType, isFirstRefresh, requiresRefresh, metadataR
 200
 201            await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false);
 202
 203            return updateType;
 204
 205            async Task<ItemUpdateType> SaveInternal(BaseItem item, MetadataRefreshOptions refreshOptions, ItemUpdateType
 206            {
 207                // Save if changes were made, or it's never been saved before
 208                if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.Rep
 209                {
 210                    if (item.IsFileProtocol)
 211                    {
 212                        var file = TryGetFile(item.Path, refreshOptions.DirectoryService);
 213                        if (file is not null)
 214                        {
 215                            item.DateModified = file.LastWriteTimeUtc;
 216                        }
 217                    }
 218
 219                    // If any of these properties are set then make sure the updateType is not None, just to force every
 220                    if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata)
 221                    {
 222                        updateType |= ItemUpdateType.MetadataDownload;
 223                    }
 224
 225                    // Save to database
 226                    await SaveItemAsync(metadataResult, updateType, cancellationToken).ConfigureAwait(false);
 227                }
 228
 229                return updateType;
 230            }
 231        }
 232
 233        private void ApplySearchResult(ItemLookupInfo lookupInfo, RemoteSearchResult result)
 234        {
 235            // Episode and Season do not support Identify, so the search results are the Series'
 236            switch (lookupInfo)
 237            {
 238                case EpisodeInfo episodeInfo:
 0239                    episodeInfo.SeriesProviderIds = result.ProviderIds;
 0240                    episodeInfo.ProviderIds.Clear();
 0241                    break;
 242                case SeasonInfo seasonInfo:
 0243                    seasonInfo.SeriesProviderIds = result.ProviderIds;
 0244                    seasonInfo.ProviderIds.Clear();
 0245                    break;
 246                default:
 0247                    lookupInfo.ProviderIds = result.ProviderIds;
 0248                    lookupInfo.Name = result.Name;
 0249                    lookupInfo.Year = result.ProductionYear;
 250                    break;
 251            }
 0252        }
 253
 254        protected async Task SaveItemAsync(MetadataResult<TItemType> result, ItemUpdateType reason, CancellationToken ca
 255        {
 256            if (result.Item.SupportsPeople)
 257            {
 258                var baseItem = result.Item;
 259
 260                await LibraryManager.UpdatePeopleAsync(baseItem, result.People, cancellationToken).ConfigureAwait(false)
 261            }
 262
 263            await result.Item.UpdateToRepositoryAsync(reason, cancellationToken).ConfigureAwait(false);
 264        }
 265
 266        protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationT
 267        {
 52268            item.AfterMetadataRefresh();
 52269            return Task.CompletedTask;
 270        }
 271
 272        /// <summary>
 273        /// Before the save.
 274        /// </summary>
 275        /// <param name="item">The item.</param>
 276        /// <param name="isFullRefresh">if set to <c>true</c> [is full refresh].</param>
 277        /// <param name="currentUpdateType">Type of the current update.</param>
 278        /// <returns>ItemUpdateType.</returns>
 279        private ItemUpdateType BeforeSave(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType)
 280        {
 53281            var updateType = BeforeSaveInternal(item, isFullRefresh, currentUpdateType);
 282
 53283            updateType |= item.OnMetadataChanged();
 284
 53285            return updateType;
 286        }
 287
 288        protected virtual ItemUpdateType BeforeSaveInternal(TItemType item, bool isFullRefresh, ItemUpdateType updateTyp
 289        {
 53290            if (EnableUpdateMetadataFromChildren(item, isFullRefresh, updateType))
 291            {
 0292                if (isFullRefresh || updateType > ItemUpdateType.None)
 293                {
 0294                    var children = GetChildrenForMetadataUpdates(item);
 295
 0296                    updateType = UpdateMetadataFromChildren(item, children, isFullRefresh, updateType);
 297                }
 298            }
 299
 53300            var presentationUniqueKey = item.CreatePresentationUniqueKey();
 53301            if (!string.Equals(item.PresentationUniqueKey, presentationUniqueKey, StringComparison.Ordinal))
 302            {
 28303                item.PresentationUniqueKey = presentationUniqueKey;
 28304                updateType |= ItemUpdateType.MetadataImport;
 305            }
 306
 53307            return updateType;
 308        }
 309
 310        protected virtual bool EnableUpdateMetadataFromChildren(TItemType item, bool isFullRefresh, ItemUpdateType curre
 311        {
 53312            if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
 313            {
 29314                if (EnableUpdatingPremiereDateFromChildren || EnableUpdatingGenresFromChildren || EnableUpdatingStudiosF
 315                {
 0316                    return true;
 317                }
 318
 29319                if (item is Folder folder)
 320                {
 29321                    return folder.SupportsDateLastMediaAdded || folder.SupportsCumulativeRunTimeTicks;
 322                }
 323            }
 324
 24325            return false;
 326        }
 327
 328        protected virtual IReadOnlyList<BaseItem> GetChildrenForMetadataUpdates(TItemType item)
 329        {
 0330            if (item is Folder folder)
 331            {
 0332                return folder.GetRecursiveChildren();
 333            }
 334
 0335            return [];
 336        }
 337
 338        protected virtual ItemUpdateType UpdateMetadataFromChildren(TItemType item, IReadOnlyList<BaseItem> children, bo
 339        {
 0340            var updateType = ItemUpdateType.None;
 341
 0342            if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
 343            {
 0344                updateType |= UpdateCumulativeRunTimeTicks(item, children);
 0345                updateType |= UpdateDateLastMediaAdded(item, children);
 346
 347                // don't update user-changeable metadata for locked items
 0348                if (item.IsLocked)
 349                {
 0350                    return updateType;
 351                }
 352
 0353                if (EnableUpdatingPremiereDateFromChildren)
 354                {
 0355                    updateType |= UpdatePremiereDate(item, children);
 356                }
 357
 0358                if (EnableUpdatingGenresFromChildren)
 359                {
 0360                    updateType |= UpdateGenres(item, children);
 361                }
 362
 0363                if (EnableUpdatingStudiosFromChildren)
 364                {
 0365                    updateType |= UpdateStudios(item, children);
 366                }
 367
 0368                if (EnableUpdatingOfficialRatingFromChildren)
 369                {
 0370                    updateType |= UpdateOfficialRating(item, children);
 371                }
 372            }
 373
 0374            return updateType;
 375        }
 376
 377        private ItemUpdateType UpdateCumulativeRunTimeTicks(TItemType item, IReadOnlyList<BaseItem> children)
 378        {
 0379            if (item is Folder folder && folder.SupportsCumulativeRunTimeTicks)
 380            {
 0381                long ticks = 0;
 382
 0383                foreach (var child in children)
 384                {
 0385                    if (!child.IsFolder)
 386                    {
 0387                        ticks += child.RunTimeTicks ?? 0;
 388                    }
 389                }
 390
 0391                if (!folder.RunTimeTicks.HasValue || folder.RunTimeTicks.Value != ticks)
 392                {
 0393                    folder.RunTimeTicks = ticks;
 0394                    return ItemUpdateType.MetadataImport;
 395                }
 396            }
 397
 0398            return ItemUpdateType.None;
 399        }
 400
 401        private ItemUpdateType UpdateDateLastMediaAdded(TItemType item, IReadOnlyList<BaseItem> children)
 402        {
 0403            var updateType = ItemUpdateType.None;
 404
 0405            if (item is Folder folder && folder.SupportsDateLastMediaAdded)
 406            {
 0407                var dateLastMediaAdded = DateTime.MinValue;
 0408                var any = false;
 409
 0410                foreach (var child in children)
 411                {
 412                    // Exclude any folders and virtual items since they are only placeholders
 0413                    if (!child.IsFolder && !child.IsVirtualItem)
 414                    {
 0415                        var childDateCreated = child.DateCreated;
 0416                        if (childDateCreated > dateLastMediaAdded)
 417                        {
 0418                            dateLastMediaAdded = childDateCreated;
 419                        }
 420
 0421                        any = true;
 422                    }
 423                }
 424
 0425                if ((!folder.DateLastMediaAdded.HasValue && any) || folder.DateLastMediaAdded != dateLastMediaAdded)
 426                {
 0427                    folder.DateLastMediaAdded = dateLastMediaAdded;
 0428                    updateType = ItemUpdateType.MetadataImport;
 429                }
 430            }
 431
 0432            return updateType;
 433        }
 434
 435        private ItemUpdateType UpdatePremiereDate(TItemType item, IReadOnlyList<BaseItem> children)
 436        {
 0437            var updateType = ItemUpdateType.None;
 438
 0439            if (children.Count == 0)
 440            {
 0441                return updateType;
 442            }
 443
 0444            var date = children.Select(i => i.PremiereDate ?? DateTime.MaxValue).Min();
 445
 0446            var originalPremiereDate = item.PremiereDate;
 0447            var originalProductionYear = item.ProductionYear;
 448
 0449            if (date > DateTime.MinValue && date < DateTime.MaxValue)
 450            {
 0451                item.PremiereDate = date;
 0452                item.ProductionYear = date.Year;
 453            }
 454            else
 455            {
 0456                var year = children.Select(i => i.ProductionYear ?? 0).Min();
 457
 0458                if (year > 0)
 459                {
 0460                    item.ProductionYear = year;
 461                }
 462            }
 463
 0464            if ((originalPremiereDate ?? DateTime.MinValue) != (item.PremiereDate ?? DateTime.MinValue)
 0465                || (originalProductionYear ?? -1) != (item.ProductionYear ?? -1))
 466            {
 0467                updateType |= ItemUpdateType.MetadataEdit;
 468            }
 469
 0470            return updateType;
 471        }
 472
 473        private ItemUpdateType UpdateGenres(TItemType item, IReadOnlyList<BaseItem> children)
 474        {
 0475            var updateType = ItemUpdateType.None;
 476
 0477            if (!item.LockedFields.Contains(MetadataField.Genres))
 478            {
 0479                var currentList = item.Genres;
 480
 0481                item.Genres = children.SelectMany(i => i.Genres)
 0482                    .Distinct(StringComparer.OrdinalIgnoreCase)
 0483                    .ToArray();
 484
 0485                if (currentList.Length != item.Genres.Length || !currentList.Order().SequenceEqual(item.Genres.Order(), 
 486                {
 0487                    updateType |= ItemUpdateType.MetadataEdit;
 488                }
 489            }
 490
 0491            return updateType;
 492        }
 493
 494        private ItemUpdateType UpdateStudios(TItemType item, IReadOnlyList<BaseItem> children)
 495        {
 0496            var updateType = ItemUpdateType.None;
 497
 0498            if (!item.LockedFields.Contains(MetadataField.Studios))
 499            {
 0500                var currentList = item.Studios;
 501
 0502                item.Studios = children.SelectMany(i => i.Studios)
 0503                    .Distinct(StringComparer.OrdinalIgnoreCase)
 0504                    .ToArray();
 505
 0506                if (currentList.Length != item.Studios.Length || !currentList.Order().SequenceEqual(item.Studios.Order()
 507                {
 0508                    updateType |= ItemUpdateType.MetadataEdit;
 509                }
 510            }
 511
 0512            return updateType;
 513        }
 514
 515        private ItemUpdateType UpdateOfficialRating(TItemType item, IReadOnlyList<BaseItem> children)
 516        {
 0517            var updateType = ItemUpdateType.None;
 518
 0519            if (!item.LockedFields.Contains(MetadataField.OfficialRating))
 520            {
 0521                if (item.UpdateRatingToItems(children))
 522                {
 0523                    updateType |= ItemUpdateType.MetadataEdit;
 524                }
 525            }
 526
 0527            return updateType;
 528        }
 529
 530        /// <summary>
 531        /// Gets the providers.
 532        /// </summary>
 533        /// <param name="item">A media item.</param>
 534        /// <param name="libraryOptions">The LibraryOptions to use.</param>
 535        /// <param name="options">The MetadataRefreshOptions to use.</param>
 536        /// <param name="isFirstRefresh">Specifies first refresh mode.</param>
 537        /// <param name="requiresRefresh">Specifies refresh mode.</param>
 538        /// <returns>IEnumerable{`0}.</returns>
 539        protected IEnumerable<IMetadataProvider> GetProviders(BaseItem item, LibraryOptions libraryOptions, MetadataRefr
 540        {
 541            // Get providers to refresh
 53542            var providers = ProviderManager.GetMetadataProviders<TItemType>(item, libraryOptions).ToList();
 543
 53544            var metadataRefreshMode = options.MetadataRefreshMode;
 545
 546            // Run all if either of these flags are true
 53547            var runAllProviders = options.ReplaceAllMetadata ||
 53548                metadataRefreshMode == MetadataRefreshMode.FullRefresh ||
 53549                (isFirstRefresh && metadataRefreshMode >= MetadataRefreshMode.Default) ||
 53550                (requiresRefresh && metadataRefreshMode >= MetadataRefreshMode.Default);
 551
 53552            if (!runAllProviders)
 553            {
 24554                var providersWithChanges = providers
 24555                    .Where(i =>
 24556                    {
 24557                        if (i is IHasItemChangeMonitor hasFileChangeMonitor)
 24558                        {
 24559                            return HasChanged(item, hasFileChangeMonitor, options.DirectoryService);
 24560                        }
 24561
 24562                        return false;
 24563                    })
 24564                    .ToList();
 565
 24566                if (providersWithChanges.Count == 0)
 567                {
 0568                    providers = new List<IMetadataProvider<TItemType>>();
 569                }
 570                else
 571                {
 24572                    var anyRemoteProvidersChanged = providersWithChanges.OfType<IRemoteMetadataProvider>()
 24573                        .Any();
 574
 24575                    var anyLocalProvidersChanged = providersWithChanges.OfType<ILocalMetadataProvider>()
 24576                        .Any();
 577
 24578                    var anyLocalPreRefreshProvidersChanged = providersWithChanges.OfType<IPreRefreshProvider>()
 24579                        .Any();
 580
 24581                    providers = providers.Where(i =>
 24582                    {
 24583                        // If any provider reports a change, always run local ones as well
 24584                        if (i is ILocalMetadataProvider)
 24585                        {
 24586                            return anyRemoteProvidersChanged || anyLocalProvidersChanged || anyLocalPreRefreshProvidersC
 24587                        }
 24588
 24589                        // If any remote providers changed, run them all so that priorities can be honored
 24590                        if (i is IRemoteMetadataProvider)
 24591                        {
 24592                            if (options.MetadataRefreshMode == MetadataRefreshMode.ValidationOnly)
 24593                            {
 24594                                return false;
 24595                            }
 24596
 24597                            return anyRemoteProvidersChanged;
 24598                        }
 24599
 24600                        // Run custom refresh providers if they report a change or any remote providers change
 24601                        return anyRemoteProvidersChanged || providersWithChanges.Contains(i);
 24602                    }).ToList();
 603                }
 604            }
 605
 53606            return providers;
 607        }
 608
 609        protected virtual IEnumerable<IImageProvider> GetNonLocalImageProviders(BaseItem item, IEnumerable<IImageProvide
 610        {
 611            // Get providers to refresh
 53612            var providers = allImageProviders.Where(i => i is not ILocalImageProvider);
 613
 53614            var dateLastImageRefresh = item.DateLastRefreshed;
 615
 616            // Run all if either of these flags are true
 53617            var runAllProviders = options.ImageRefreshMode == MetadataRefreshMode.FullRefresh || dateLastImageRefresh ==
 618
 53619            if (!runAllProviders)
 620            {
 25621                providers = providers
 25622                    .Where(i =>
 25623                    {
 25624                        if (i is IHasItemChangeMonitor hasFileChangeMonitor)
 25625                        {
 25626                            return HasChanged(item, hasFileChangeMonitor, options.DirectoryService);
 25627                        }
 25628
 25629                        return false;
 25630                    });
 631            }
 632
 53633            return providers;
 634        }
 635
 636        public bool CanRefresh(BaseItem item)
 637        {
 962638            return item is TItemType;
 639        }
 640
 641        public bool CanRefreshPrimary(Type type)
 642        {
 1266643            return type == typeof(TItemType);
 644        }
 645
 646        protected virtual async Task<RefreshResult> RefreshWithProviders(
 647            MetadataResult<TItemType> metadata,
 648            TIdType id,
 649            MetadataRefreshOptions options,
 650            ICollection<IMetadataProvider> providers,
 651            ItemImageProvider imageService,
 652            bool isSavingMetadata,
 653            CancellationToken cancellationToken)
 654        {
 655            var refreshResult = new RefreshResult
 656            {
 657                UpdateType = ItemUpdateType.None
 658            };
 659
 660            var item = metadata.Item;
 661
 662            var customProviders = providers.OfType<ICustomMetadataProvider<TItemType>>().ToList();
 663            var logName = !item.IsFileProtocol ? item.Name ?? item.Path : item.Path ?? item.Name;
 664
 665            foreach (var provider in customProviders.Where(i => i is IPreRefreshProvider))
 666            {
 667                await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwa
 668            }
 669
 670            if (item.IsLocked)
 671            {
 672                return refreshResult;
 673            }
 674
 675            var temp = new MetadataResult<TItemType>
 676            {
 677                Item = CreateNew()
 678            };
 679            temp.Item.Path = item.Path;
 680            temp.Item.Id = item.Id;
 681            temp.Item.ParentIndexNumber = item.ParentIndexNumber;
 682            temp.Item.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
 683            temp.Item.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
 684
 685            var foundImageTypes = new List<ImageType>();
 686
 687            // Do not execute local providers if we are identifying or replacing with local metadata saving enabled
 688            if (options.SearchResult is null && !(isSavingMetadata && options.ReplaceAllMetadata))
 689            {
 690                foreach (var provider in providers.OfType<ILocalMetadataProvider<TItemType>>())
 691                {
 692                    var providerName = provider.GetType().Name;
 693                    Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);
 694
 695                    var itemInfo = new ItemInfo(item);
 696
 697                    try
 698                    {
 699                        var localItem = await provider.GetMetadata(itemInfo, options.DirectoryService, cancellationToken
 700
 701                        if (localItem.HasMetadata)
 702                        {
 703                            foreach (var remoteImage in localItem.RemoteImages)
 704                            {
 705                                try
 706                                {
 707                                    if (item.ImageInfos.Any(x => x.Type == remoteImage.Type)
 708                                        && !options.IsReplacingImage(remoteImage.Type))
 709                                    {
 710                                        continue;
 711                                    }
 712
 713                                    await ProviderManager.SaveImage(item, remoteImage.Url, remoteImage.Type, null, cance
 714                                    refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
 715
 716                                    // remember imagetype that has just been downloaded
 717                                    foundImageTypes.Add(remoteImage.Type);
 718                                }
 719                                catch (HttpRequestException ex)
 720                                {
 721                                    Logger.LogError(ex, "Could not save {ImageType} image: {Url}", Enum.GetName(remoteIm
 722                                }
 723                            }
 724
 725                            if (foundImageTypes.Count > 0)
 726                            {
 727                                imageService.UpdateReplaceImages(options, foundImageTypes);
 728                            }
 729
 730                            if (imageService.MergeImages(item, localItem.Images, options))
 731                            {
 732                                refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
 733                            }
 734
 735                            MergeData(localItem, temp, [], false, true);
 736                            refreshResult.UpdateType |= ItemUpdateType.MetadataImport;
 737
 738                            break;
 739                        }
 740
 741                        Logger.LogDebug("{Provider} returned no metadata for {Item}", providerName, logName);
 742                    }
 743                    catch (OperationCanceledException)
 744                    {
 745                        throw;
 746                    }
 747                    catch (Exception ex)
 748                    {
 749                        Logger.LogError(ex, "Error in {Provider}", provider.Name);
 750
 751                        // If a local provider fails, consider that a failure
 752                        refreshResult.ErrorMessage = ex.Message;
 753                    }
 754                }
 755            }
 756
 757            var isLocalLocked = temp.Item.IsLocked;
 758            if (!isLocalLocked && (options.ReplaceAllMetadata || options.MetadataRefreshMode > MetadataRefreshMode.Valid
 759            {
 760                var remoteResult = await ExecuteRemoteProviders(temp, logName, false, id, providers.OfType<IRemoteMetada
 761                    .ConfigureAwait(false);
 762
 763                refreshResult.UpdateType |= remoteResult.UpdateType;
 764                refreshResult.ErrorMessage = remoteResult.ErrorMessage;
 765                refreshResult.Failures += remoteResult.Failures;
 766            }
 767
 768            if (providers.Any(i => i is not ICustomMetadataProvider))
 769            {
 770                if (refreshResult.UpdateType > ItemUpdateType.None)
 771                {
 772                    if (!options.RemoveOldMetadata)
 773                    {
 774                        // Add existing metadata to provider result if it does not exist there
 775                        MergeData(metadata, temp, [], false, false);
 776                    }
 777
 778                    if (isLocalLocked)
 779                    {
 780                        MergeData(temp, metadata, item.LockedFields, true, true);
 781                    }
 782                    else
 783                    {
 784                        var shouldReplace = options.MetadataRefreshMode > MetadataRefreshMode.ValidationOnly || options.
 785                        MergeData(temp, metadata, item.LockedFields, shouldReplace, true);
 786                    }
 787                }
 788            }
 789
 790            foreach (var provider in customProviders.Where(i => i is not IPreRefreshProvider))
 791            {
 792                await RunCustomProvider(provider, item, logName, options, refreshResult, cancellationToken).ConfigureAwa
 793            }
 794
 795            return refreshResult;
 796        }
 797
 798        private async Task RunCustomProvider(ICustomMetadataProvider<TItemType> provider, TItemType item, string logName
 799        {
 800            Logger.LogDebug("Running {Provider} for {Item}", provider.GetType().Name, logName);
 801
 802            try
 803            {
 804                refreshResult.UpdateType |= await provider.FetchAsync(item, options, cancellationToken).ConfigureAwait(f
 805            }
 806            catch (OperationCanceledException)
 807            {
 808                throw;
 809            }
 810            catch (Exception ex)
 811            {
 812                refreshResult.ErrorMessage = ex.Message;
 813                Logger.LogError(ex, "Error in {Provider}", provider.Name);
 814            }
 815        }
 816
 817        protected virtual TItemType CreateNew()
 818        {
 53819            return new TItemType();
 820        }
 821
 822        private async Task<RefreshResult> ExecuteRemoteProviders(MetadataResult<TItemType> temp, string logName, bool re
 823        {
 824            var refreshResult = new RefreshResult();
 825
 826            if (id is not null)
 827            {
 828                MergeNewData(temp.Item, id);
 829            }
 830
 831            foreach (var provider in providers)
 832            {
 833                var providerName = provider.GetType().Name;
 834                Logger.LogDebug("Running {Provider} for {Item}", providerName, logName);
 835
 836                try
 837                {
 838                    var result = await provider.GetMetadata(id, cancellationToken).ConfigureAwait(false);
 839
 840                    if (result.HasMetadata)
 841                    {
 842                        result.Provider = provider.Name;
 843
 844                        MergeData(result, temp, [], replaceData, false);
 845                        MergeNewData(temp.Item, id);
 846
 847                        refreshResult.UpdateType |= ItemUpdateType.MetadataDownload;
 848                    }
 849                    else
 850                    {
 851                        Logger.LogDebug("{Provider} returned no metadata for {Item}", providerName, logName);
 852                    }
 853                }
 854                catch (OperationCanceledException)
 855                {
 856                    throw;
 857                }
 858                catch (Exception ex)
 859                {
 860                    refreshResult.Failures++;
 861                    refreshResult.ErrorMessage = ex.Message;
 862                    Logger.LogError(ex, "Error in {Provider}", provider.Name);
 863                }
 864            }
 865
 866            return refreshResult;
 867        }
 868
 869        private void MergeNewData(TItemType source, TIdType lookupInfo)
 870        {
 871            // Copy new provider id's that may have been obtained
 106872            foreach (var providerId in source.ProviderIds)
 873            {
 0874                var key = providerId.Key;
 875
 876                // Don't replace existing Id's.
 0877                lookupInfo.ProviderIds.TryAdd(key, providerId.Value);
 878            }
 53879        }
 880
 881        private bool HasChanged(BaseItem item, IHasItemChangeMonitor changeMonitor, IDirectoryService directoryService)
 882        {
 883            try
 884            {
 24885                var hasChanged = changeMonitor.HasChanged(item, directoryService);
 886
 24887                if (hasChanged)
 888                {
 24889                    Logger.LogDebug("{Monitor} reports change to {Item}", changeMonitor.GetType().Name, item.Path ?? ite
 890                }
 891
 24892                return hasChanged;
 893            }
 0894            catch (Exception ex)
 895            {
 0896                Logger.LogError(ex, "Error in {Monitor}.HasChanged", changeMonitor.GetType().Name);
 0897                return false;
 898            }
 24899        }
 900
 901        /// <summary>
 902        /// Merges metadata from source into target.
 903        /// </summary>
 904        /// <param name="source">The source for new metadata.</param>
 905        /// <param name="target">The target to insert new metadata into.</param>
 906        /// <param name="lockedFields">The fields that are locked and should not be updated.</param>
 907        /// <param name="replaceData"><c>true</c> if existing data should be replaced.</param>
 908        /// <param name="mergeMetadataSettings"><c>true</c> if the metadata settings in target should be updated to matc
 909        /// <exception cref="ArgumentException">Thrown if source or target are null.</exception>
 910        protected virtual void MergeData(
 911            MetadataResult<TItemType> source,
 912            MetadataResult<TItemType> target,
 913            MetadataField[] lockedFields,
 914            bool replaceData,
 915            bool mergeMetadataSettings)
 916        {
 0917            MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
 0918        }
 919
 920        internal static void MergeBaseItemData(
 921            MetadataResult<TItemType> sourceResult,
 922            MetadataResult<TItemType> targetResult,
 923            MetadataField[] lockedFields,
 924            bool replaceData,
 925            bool mergeMetadataSettings)
 926        {
 129927            var source = sourceResult.Item;
 129928            var target = targetResult.Item;
 929
 129930            ArgumentNullException.ThrowIfNull(sourceResult);
 129931            ArgumentNullException.ThrowIfNull(targetResult);
 932
 129933            if (!lockedFields.Contains(MetadataField.Name))
 934            {
 126935                if (replaceData || string.IsNullOrEmpty(target.Name))
 936                {
 937                    // Safeguard against incoming data having an empty name
 125938                    if (!string.IsNullOrWhiteSpace(source.Name))
 939                    {
 3940                        target.Name = source.Name;
 941                    }
 942                }
 943            }
 944
 129945            if (replaceData || string.IsNullOrEmpty(target.OriginalTitle))
 946            {
 128947                target.OriginalTitle = source.OriginalTitle;
 948            }
 949
 129950            if (replaceData || !target.CommunityRating.HasValue)
 951            {
 128952                target.CommunityRating = source.CommunityRating;
 953            }
 954
 129955            if (replaceData || !target.EndDate.HasValue)
 956            {
 128957                target.EndDate = source.EndDate;
 958            }
 959
 129960            if (!lockedFields.Contains(MetadataField.Genres))
 961            {
 127962                if (replaceData || target.Genres.Length == 0)
 963                {
 126964                    target.Genres = source.Genres;
 965                }
 966            }
 967
 129968            if (replaceData || !target.IndexNumber.HasValue)
 969            {
 128970                target.IndexNumber = source.IndexNumber;
 971            }
 972
 129973            if (!lockedFields.Contains(MetadataField.OfficialRating))
 974            {
 126975                if (replaceData || string.IsNullOrEmpty(target.OfficialRating))
 976                {
 125977                    target.OfficialRating = source.OfficialRating;
 978                }
 979            }
 980
 129981            if (replaceData || string.IsNullOrEmpty(target.CustomRating))
 982            {
 128983                target.CustomRating = source.CustomRating;
 984            }
 985
 129986            if (replaceData || string.IsNullOrEmpty(target.Tagline))
 987            {
 128988                target.Tagline = source.Tagline;
 989            }
 990
 129991            if (!lockedFields.Contains(MetadataField.Overview))
 992            {
 126993                if (replaceData || string.IsNullOrEmpty(target.Overview))
 994                {
 125995                    target.Overview = source.Overview;
 996                }
 997            }
 998
 129999            if (replaceData || !target.ParentIndexNumber.HasValue)
 1000            {
 1281001                target.ParentIndexNumber = source.ParentIndexNumber;
 1002            }
 1003
 1291004            if (!lockedFields.Contains(MetadataField.Cast))
 1005            {
 1281006                if (replaceData || targetResult.People is null || targetResult.People.Count == 0)
 1007                {
 1241008                    targetResult.People = sourceResult.People;
 1009                }
 41010                else if (sourceResult.People is not null && sourceResult.People.Count > 0)
 1011                {
 41012                    MergePeople(sourceResult.People, targetResult.People);
 1013                }
 1014            }
 1015
 1291016            if (replaceData || !target.PremiereDate.HasValue)
 1017            {
 1281018                target.PremiereDate = source.PremiereDate;
 1019            }
 1020
 1291021            if (replaceData || !target.ProductionYear.HasValue)
 1022            {
 1281023                target.ProductionYear = source.ProductionYear;
 1024            }
 1025
 1291026            if (!lockedFields.Contains(MetadataField.Runtime))
 1027            {
 1291028                if (replaceData || !target.RunTimeTicks.HasValue)
 1029                {
 1291030                    if (target is not Audio && target is not Video)
 1031                    {
 491032                        target.RunTimeTicks = source.RunTimeTicks;
 1033                    }
 1034                }
 1035            }
 1036
 1291037            if (!lockedFields.Contains(MetadataField.Studios))
 1038            {
 1271039                if (replaceData || target.Studios.Length == 0)
 1040                {
 1261041                    target.Studios = source.Studios;
 1042                }
 1043                else
 1044                {
 11045                    target.Studios = target.Studios.Concat(source.Studios).Distinct().ToArray();
 1046                }
 1047            }
 1048
 1291049            if (!lockedFields.Contains(MetadataField.Tags))
 1050            {
 1271051                if (replaceData || target.Tags.Length == 0)
 1052                {
 1261053                    target.Tags = source.Tags;
 1054                }
 1055                else
 1056                {
 11057                    target.Tags = target.Tags.Concat(source.Tags).Distinct().ToArray();
 1058                }
 1059            }
 1060
 1291061            if (!lockedFields.Contains(MetadataField.ProductionLocations))
 1062            {
 1271063                if (replaceData || target.ProductionLocations.Length == 0)
 1064                {
 1261065                    target.ProductionLocations = source.ProductionLocations;
 1066                }
 1067                else
 1068                {
 11069                    target.ProductionLocations = target.ProductionLocations.Concat(source.ProductionLocations).Distinct(
 1070                }
 1071            }
 1072
 2661073            foreach (var id in source.ProviderIds)
 1074            {
 41075                var key = id.Key;
 1076
 1077                // Don't replace existing Id's.
 41078                if (replaceData)
 1079                {
 11080                    target.ProviderIds[key] = id.Value;
 1081                }
 1082                else
 1083                {
 31084                    target.ProviderIds.TryAdd(key, id.Value);
 1085                }
 1086            }
 1087
 1291088            if (replaceData || !target.CriticRating.HasValue)
 1089            {
 1281090                target.CriticRating = source.CriticRating;
 1091            }
 1092
 1291093            if (replaceData || target.RemoteTrailers.Count == 0)
 1094            {
 1281095                target.RemoteTrailers = source.RemoteTrailers;
 1096            }
 1097            else
 1098            {
 11099                target.RemoteTrailers = target.RemoteTrailers.Concat(source.RemoteTrailers).DistinctBy(t => t.Url).ToArr
 1100            }
 1101
 1291102            MergeAlbumArtist(source, target, replaceData);
 1291103            MergeVideoInfo(source, target, replaceData);
 1291104            MergeDisplayOrder(source, target, replaceData);
 1105
 1291106            if (replaceData || string.IsNullOrEmpty(target.ForcedSortName))
 1107            {
 1281108                var forcedSortName = source.ForcedSortName;
 1281109                if (!string.IsNullOrEmpty(forcedSortName))
 1110                {
 31111                    target.ForcedSortName = forcedSortName;
 1112                }
 1113            }
 1114
 1291115            if (mergeMetadataSettings)
 1116            {
 21117                if (replaceData || !target.IsLocked)
 1118                {
 21119                    target.IsLocked = target.IsLocked || source.IsLocked;
 1120                }
 1121
 21122                if (target.LockedFields.Length == 0)
 1123                {
 01124                    target.LockedFields = source.LockedFields;
 1125                }
 1126                else
 1127                {
 21128                    target.LockedFields = target.LockedFields.Concat(source.LockedFields).Distinct().ToArray();
 1129                }
 1130
 21131                if (source.DateCreated != default)
 1132                {
 11133                    target.DateCreated = source.DateCreated;
 1134                }
 1135
 21136                if (replaceData || string.IsNullOrEmpty(target.PreferredMetadataCountryCode))
 1137                {
 21138                    target.PreferredMetadataCountryCode = source.PreferredMetadataCountryCode;
 1139                }
 1140
 21141                if (replaceData || string.IsNullOrEmpty(target.PreferredMetadataLanguage))
 1142                {
 21143                    target.PreferredMetadataLanguage = source.PreferredMetadataLanguage;
 1144                }
 1145            }
 1291146        }
 1147
 1148        private static void MergePeople(IReadOnlyList<PersonInfo> source, IReadOnlyList<PersonInfo> target)
 1149        {
 41150            var sourceByName = source.ToLookup(p => p.Name.RemoveDiacritics(), StringComparer.OrdinalIgnoreCase);
 41151            var targetByName = target.ToLookup(p => p.Name.RemoveDiacritics(), StringComparer.OrdinalIgnoreCase);
 1152
 161153            foreach (var name in targetByName.Select(g => g.Key))
 1154            {
 41155                var targetPeople = targetByName[name].ToArray();
 41156                var sourcePeople = sourceByName[name].ToArray();
 1157
 41158                if (sourcePeople.Length == 0)
 1159                {
 1160                    continue;
 1161                }
 1162
 121163                for (int i = 0; i < targetPeople.Length; i++)
 1164                {
 31165                    var person = targetPeople[i];
 31166                    var personInSource = i < sourcePeople.Length ? sourcePeople[i] : sourcePeople[0];
 1167
 101168                    foreach (var providerId in personInSource.ProviderIds)
 1169                    {
 21170                        person.ProviderIds.TryAdd(providerId.Key, providerId.Value);
 1171                    }
 1172
 31173                    if (string.IsNullOrWhiteSpace(person.ImageUrl))
 1174                    {
 21175                        person.ImageUrl = personInSource.ImageUrl;
 1176                    }
 1177
 31178                    if (!string.IsNullOrWhiteSpace(personInSource.Role) && string.IsNullOrWhiteSpace(person.Role))
 1179                    {
 01180                        person.Role = personInSource.Role;
 1181                    }
 1182
 31183                    if (personInSource.SortOrder.HasValue && !person.SortOrder.HasValue)
 1184                    {
 01185                        person.SortOrder = personInSource.SortOrder;
 1186                    }
 1187                }
 1188            }
 41189        }
 1190
 1191        private static void MergeDisplayOrder(BaseItem source, BaseItem target, bool replaceData)
 1192        {
 1291193            if (source is IHasDisplayOrder sourceHasDisplayOrder
 1291194                && target is IHasDisplayOrder targetHasDisplayOrder)
 1195            {
 491196                if (replaceData || string.IsNullOrEmpty(targetHasDisplayOrder.DisplayOrder))
 1197                {
 481198                    var displayOrder = sourceHasDisplayOrder.DisplayOrder;
 481199                    if (!string.IsNullOrWhiteSpace(displayOrder))
 1200                    {
 31201                        targetHasDisplayOrder.DisplayOrder = displayOrder;
 1202                    }
 1203                }
 1204            }
 1291205        }
 1206
 1207        private static void MergeAlbumArtist(BaseItem source, BaseItem target, bool replaceData)
 1208        {
 1291209            if (source is IHasAlbumArtist sourceHasAlbumArtist
 1291210                && target is IHasAlbumArtist targetHasAlbumArtist)
 1211            {
 281212                if (replaceData || targetHasAlbumArtist.AlbumArtists.Count == 0)
 1213                {
 271214                    targetHasAlbumArtist.AlbumArtists = sourceHasAlbumArtist.AlbumArtists;
 1215                }
 11216                else if (sourceHasAlbumArtist.AlbumArtists.Count > 0)
 1217                {
 11218                    targetHasAlbumArtist.AlbumArtists = targetHasAlbumArtist.AlbumArtists.Concat(sourceHasAlbumArtist.Al
 1219                }
 1220            }
 1021221        }
 1222
 1223        private static void MergeVideoInfo(BaseItem source, BaseItem target, bool replaceData)
 1224        {
 1291225            if (source is Video sourceCast && target is Video targetCast)
 1226            {
 521227                if (replaceData || !targetCast.Video3DFormat.HasValue)
 1228                {
 511229                    targetCast.Video3DFormat = sourceCast.Video3DFormat;
 1230                }
 1231            }
 1291232        }
 1233    }
 1234}

Methods/Properties

.ctor(MediaBrowser.Controller.Configuration.IServerConfigurationManager,Microsoft.Extensions.Logging.ILogger`1<MediaBrowser.Providers.Manager.MetadataService`2<TItemType,TIdType>>,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Model.IO.IFileSystem,MediaBrowser.Controller.Library.ILibraryManager)
get_EnableUpdatingPremiereDateFromChildren()
get_EnableUpdatingGenresFromChildren()
get_EnableUpdatingStudiosFromChildren()
get_EnableUpdatingOfficialRatingFromChildren()
get_Order()
TryGetFile(System.String,MediaBrowser.Controller.Providers.IDirectoryService)
ApplySearchResult(MediaBrowser.Controller.Providers.ItemLookupInfo,MediaBrowser.Model.Providers.RemoteSearchResult)
AfterMetadataRefresh(TItemType,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Threading.CancellationToken)
BeforeSave(TItemType,System.Boolean,MediaBrowser.Controller.Library.ItemUpdateType)
BeforeSaveInternal(TItemType,System.Boolean,MediaBrowser.Controller.Library.ItemUpdateType)
EnableUpdateMetadataFromChildren(TItemType,System.Boolean,MediaBrowser.Controller.Library.ItemUpdateType)
GetChildrenForMetadataUpdates(TItemType)
UpdateMetadataFromChildren(TItemType,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>,System.Boolean,MediaBrowser.Controller.Library.ItemUpdateType)
UpdateCumulativeRunTimeTicks(TItemType,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>)
UpdateDateLastMediaAdded(TItemType,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>)
UpdatePremiereDate(TItemType,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>)
UpdateGenres(TItemType,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>)
UpdateStudios(TItemType,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>)
UpdateOfficialRating(TItemType,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>)
GetProviders(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Configuration.LibraryOptions,MediaBrowser.Controller.Providers.MetadataRefreshOptions,System.Boolean,System.Boolean)
GetNonLocalImageProviders(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Providers.IImageProvider>,MediaBrowser.Controller.Providers.ImageRefreshOptions)
CanRefresh(MediaBrowser.Controller.Entities.BaseItem)
CanRefreshPrimary(System.Type)
CreateNew()
MergeNewData(TItemType,TIdType)
HasChanged(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Providers.IHasItemChangeMonitor,MediaBrowser.Controller.Providers.IDirectoryService)
MergeData(MediaBrowser.Controller.Providers.MetadataResult`1<TItemType>,MediaBrowser.Controller.Providers.MetadataResult`1<TItemType>,MediaBrowser.Model.Entities.MetadataField[],System.Boolean,System.Boolean)
MergeBaseItemData(MediaBrowser.Controller.Providers.MetadataResult`1<TItemType>,MediaBrowser.Controller.Providers.MetadataResult`1<TItemType>,MediaBrowser.Model.Entities.MetadataField[],System.Boolean,System.Boolean)
MergePeople(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.PersonInfo>,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.PersonInfo>)
MergeDisplayOrder(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Entities.BaseItem,System.Boolean)
MergeAlbumArtist(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Entities.BaseItem,System.Boolean)
MergeVideoInfo(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Entities.BaseItem,System.Boolean)