< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Manager.ItemImageProvider
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Manager/ItemImageProvider.cs
Line coverage
82%
Covered lines: 114
Uncovered lines: 25
Coverable lines: 139
Total lines: 749
Line coverage: 82%
Branch coverage
76%
Covered branches: 78
Total branches: 102
Branch coverage: 76.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 10/5/2025 - 12:11:27 AM Line coverage: 86.5% (109/126) Branch coverage: 80.9% (68/84) Total lines: 71512/29/2025 - 12:13:19 AM Line coverage: 82% (114/139) Branch coverage: 76.4% (78/102) Total lines: 749 10/5/2025 - 12:11:27 AM Line coverage: 86.5% (109/126) Branch coverage: 80.9% (68/84) Total lines: 71512/29/2025 - 12:13:19 AM Line coverage: 82% (114/139) Branch coverage: 76.4% (78/102) Total lines: 749

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
RemoveImages(...)85.71%1414100%
ValidateImages(...)83.33%66100%
ContainsImages(...)100%1212100%
PruneImages(...)66.66%411241.17%
UpdateReplaceImages(...)0%2040%
MergeImages(...)79.41%383485.29%
GetFirstLocalImageInfoByType(...)100%44100%
UpdateMultiImages(...)100%22100%
EnableImageStub(...)50%251255.55%
SaveImageStub(...)100%22100%
SaveImageStub(...)100%11100%

File(s)

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

#LineLine coverage
 1#nullable disable
 2
 3using System;
 4using System.Collections.Generic;
 5using System.IO;
 6using System.Linq;
 7using System.Net;
 8using System.Net.Http;
 9using System.Net.Mime;
 10using System.Threading;
 11using System.Threading.Tasks;
 12using MediaBrowser.Controller.Entities;
 13using MediaBrowser.Controller.Entities.Audio;
 14using MediaBrowser.Controller.Entities.TV;
 15using MediaBrowser.Controller.Library;
 16using MediaBrowser.Controller.LiveTv;
 17using MediaBrowser.Controller.Providers;
 18using MediaBrowser.Model.Configuration;
 19using MediaBrowser.Model.Drawing;
 20using MediaBrowser.Model.Entities;
 21using MediaBrowser.Model.IO;
 22using MediaBrowser.Model.MediaInfo;
 23using MediaBrowser.Model.Net;
 24using MediaBrowser.Model.Providers;
 25using Microsoft.Extensions.Logging;
 26
 27namespace MediaBrowser.Providers.Manager
 28{
 29    /// <summary>
 30    /// Utilities for managing images attached to items.
 31    /// </summary>
 32    public class ItemImageProvider
 33    {
 34        private readonly ILogger _logger;
 35        private readonly IProviderManager _providerManager;
 36        private readonly IFileSystem _fileSystem;
 237        private static readonly ImageType[] AllImageTypes = Enum.GetValues<ImageType>();
 38
 39        /// <summary>
 40        /// Image types that are only one per item.
 41        /// </summary>
 242        private static readonly ImageType[] _singularImages =
 243        {
 244            ImageType.Primary,
 245            ImageType.Art,
 246            ImageType.Banner,
 247            ImageType.Box,
 248            ImageType.BoxRear,
 249            ImageType.Disc,
 250            ImageType.Logo,
 251            ImageType.Menu,
 252            ImageType.Thumb
 253        };
 54
 55        /// <summary>
 56        /// Initializes a new instance of the <see cref="ItemImageProvider"/> class.
 57        /// </summary>
 58        /// <param name="logger">The logger.</param>
 59        /// <param name="providerManager">The provider manager for interacting with provider image references.</param>
 60        /// <param name="fileSystem">The filesystem.</param>
 61        public ItemImageProvider(ILogger logger, IProviderManager providerManager, IFileSystem fileSystem)
 62        {
 59163            _logger = logger;
 59164            _providerManager = providerManager;
 59165            _fileSystem = fileSystem;
 59166        }
 67
 68        /// <summary>
 69        /// Removes all existing images from the provided item.
 70        /// </summary>
 71        /// <param name="item">The <see cref="BaseItem"/> to remove images from.</param>
 72        /// <param name="canDeleteLocal">Whether removing images outside metadata folder is allowed.</param>
 73        /// <returns><c>true</c> if changes were made to the item; otherwise <c>false</c>.</returns>
 74        public bool RemoveImages(BaseItem item, bool canDeleteLocal = false)
 75        {
 376            var singular = new List<ItemImageInfo>();
 377            var itemMetadataPath = item.GetInternalMetadataPath();
 6078            for (var i = 0; i < _singularImages.Length; i++)
 79            {
 2780                var currentImage = item.GetImageInfo(_singularImages[i], 0);
 2781                if (currentImage is not null)
 82                {
 183                    var imageInMetadataFolder = currentImage.Path.StartsWith(itemMetadataPath, StringComparison.OrdinalI
 184                    if (imageInMetadataFolder || canDeleteLocal || item.IsSaveLocalMetadataEnabled())
 85                    {
 186                        singular.Add(currentImage);
 87                    }
 88                }
 89            }
 90
 1091            foreach (var backdrop in item.GetImages(ImageType.Backdrop))
 92            {
 293                var imageInMetadataFolder = backdrop.Path.StartsWith(itemMetadataPath, StringComparison.OrdinalIgnoreCas
 294                if (imageInMetadataFolder || canDeleteLocal || item.IsSaveLocalMetadataEnabled())
 95                {
 296                    singular.Add(backdrop);
 97                }
 98            }
 99
 3100            PruneImages(item, singular);
 101
 3102            return singular.Count > 0;
 103        }
 104
 105        /// <summary>
 106        /// Verifies existing images have valid paths and adds any new local images provided.
 107        /// </summary>
 108        /// <param name="item">The <see cref="BaseItem"/> to validate images for.</param>
 109        /// <param name="providers">The providers to use, must include <see cref="ILocalImageProvider"/>(s) for local sc
 110        /// <param name="refreshOptions">The refresh options.</param>
 111        /// <returns><c>true</c> if changes were made to the item; otherwise <c>false</c>.</returns>
 112        public bool ValidateImages(BaseItem item, IEnumerable<IImageProvider> providers, ImageRefreshOptions refreshOpti
 113        {
 64114            var hasChanges = false;
 64115            var directoryService = refreshOptions?.DirectoryService;
 116
 64117            if (item is not Photo)
 118            {
 63119                var images = providers.OfType<ILocalImageProvider>()
 63120                    .SelectMany(i => i.GetImages(item, directoryService))
 63121                    .ToList();
 122
 63123                if (MergeImages(item, images, refreshOptions))
 124                {
 4125                    hasChanges = true;
 126                }
 127            }
 128
 64129            return hasChanges;
 130        }
 131
 132        /// <summary>
 133        /// Refreshes from the providers according to the given options.
 134        /// </summary>
 135        /// <param name="item">The <see cref="BaseItem"/> to gather images for.</param>
 136        /// <param name="libraryOptions">The library options.</param>
 137        /// <param name="providers">The providers to query for images.</param>
 138        /// <param name="refreshOptions">The refresh options.</param>
 139        /// <param name="cancellationToken">The cancellation token.</param>
 140        /// <returns>The refresh result.</returns>
 141        public async Task<RefreshResult> RefreshImages(
 142            BaseItem item,
 143            LibraryOptions libraryOptions,
 144            IEnumerable<IImageProvider> providers,
 145            ImageRefreshOptions refreshOptions,
 146            CancellationToken cancellationToken)
 147        {
 148            var oldBackdropImages = Array.Empty<ItemImageInfo>();
 149            if (refreshOptions.IsReplacingImage(ImageType.Backdrop))
 150            {
 151                oldBackdropImages = item.GetImages(ImageType.Backdrop).ToArray();
 152            }
 153
 154            var result = new RefreshResult { UpdateType = ItemUpdateType.None };
 155
 156            var typeName = item.GetType().Name;
 157            var typeOptions = libraryOptions.GetTypeOptions(typeName) ?? new TypeOptions { Type = typeName };
 158
 159            // track library limits, adding buffer to allow lazy replacing of current images
 160            var backdropLimit = typeOptions.GetLimit(ImageType.Backdrop) + oldBackdropImages.Length;
 161            var downloadedImages = new List<ImageType>();
 162
 163            foreach (var provider in providers)
 164            {
 165                if (provider is IRemoteImageProvider remoteProvider)
 166                {
 167                    await RefreshFromProvider(item, remoteProvider, refreshOptions, typeOptions, backdropLimit, download
 168                    continue;
 169                }
 170
 171                if (provider is IDynamicImageProvider dynamicImageProvider)
 172                {
 173                    await RefreshFromProvider(item, dynamicImageProvider, refreshOptions, typeOptions, downloadedImages,
 174                }
 175            }
 176
 177            // Only delete existing multi-images if new ones were added
 178            if (oldBackdropImages.Length > 0 && oldBackdropImages.Length < item.GetImages(ImageType.Backdrop).Count())
 179            {
 180                PruneImages(item, oldBackdropImages);
 181            }
 182
 183            return result;
 184        }
 185
 186        /// <summary>
 187        /// Refreshes from a dynamic provider.
 188        /// </summary>
 189        private async Task RefreshFromProvider(
 190            BaseItem item,
 191            IDynamicImageProvider provider,
 192            ImageRefreshOptions refreshOptions,
 193            TypeOptions savedOptions,
 194            List<ImageType> downloadedImages,
 195            RefreshResult result,
 196            CancellationToken cancellationToken)
 197        {
 198            try
 199            {
 200                var images = provider.GetSupportedImages(item);
 201
 202                foreach (var imageType in images)
 203                {
 204                    if (!savedOptions.IsEnabled(imageType))
 205                    {
 206                        continue;
 207                    }
 208
 209                    if (!item.HasImage(imageType) || (refreshOptions.IsReplacingImage(imageType) && !downloadedImages.Co
 210                    {
 211                        _logger.LogDebug("Running {Provider} for {Item}", provider.GetType().Name, item.Path ?? item.Nam
 212
 213                        var response = await provider.GetImage(item, imageType, cancellationToken).ConfigureAwait(false)
 214
 215                        if (response.HasImage)
 216                        {
 217                            if (string.IsNullOrEmpty(response.Path))
 218                            {
 219                                var mimeType = response.Format.GetMimeType();
 220
 221                                await _providerManager.SaveImage(item, response.Stream, mimeType, imageType, null, cance
 222                            }
 223                            else
 224                            {
 225                                if (response.Protocol == MediaProtocol.Http)
 226                                {
 227                                    _logger.LogDebug("Setting image url into item {Item}", item.Id);
 228                                    var index = item.AllowsMultipleImages(imageType) ? item.GetImages(imageType).Count()
 229                                    item.SetImage(
 230                                        new ItemImageInfo
 231                                        {
 232                                            Path = response.Path,
 233                                            Type = imageType
 234                                        },
 235                                        index);
 236                                }
 237                                else
 238                                {
 239                                    var mimeType = MimeTypes.GetMimeType(response.Path);
 240
 241                                    await _providerManager.SaveImage(item, response.Path, mimeType, imageType, null, nul
 242                                }
 243                            }
 244
 245                            downloadedImages.Add(imageType);
 246                            result.UpdateType |= ItemUpdateType.ImageUpdate;
 247                        }
 248                    }
 249                }
 250            }
 251            catch (OperationCanceledException)
 252            {
 253                throw;
 254            }
 255            catch (Exception ex)
 256            {
 257                result.ErrorMessage = ex.Message;
 258                _logger.LogError(ex, "Error in {Provider}", provider.Name);
 259            }
 260        }
 261
 262        /// <summary>
 263        /// Refreshes from a remote provider.
 264        /// </summary>
 265        /// <param name="item">The item.</param>
 266        /// <param name="provider">The provider.</param>
 267        /// <param name="refreshOptions">The refresh options.</param>
 268        /// <param name="savedOptions">The saved options.</param>
 269        /// <param name="backdropLimit">The backdrop limit.</param>
 270        /// <param name="downloadedImages">The downloaded images.</param>
 271        /// <param name="result">The result.</param>
 272        /// <param name="cancellationToken">The cancellation token.</param>
 273        /// <returns>Task.</returns>
 274        private async Task RefreshFromProvider(
 275            BaseItem item,
 276            IRemoteImageProvider provider,
 277            ImageRefreshOptions refreshOptions,
 278            TypeOptions savedOptions,
 279            int backdropLimit,
 280            List<ImageType> downloadedImages,
 281            RefreshResult result,
 282            CancellationToken cancellationToken)
 283        {
 284            try
 285            {
 286                if (!item.SupportsRemoteImageDownloading)
 287                {
 288                    return;
 289                }
 290
 291                if (!refreshOptions.ReplaceAllImages &&
 292                    refreshOptions.ReplaceImages.Count == 0 &&
 293                    ContainsImages(item, provider.GetSupportedImages(item).ToList(), savedOptions, backdropLimit))
 294                {
 295                    return;
 296                }
 297
 298                _logger.LogDebug("Running {Provider} for {Item}", provider.GetType().Name, item.Path ?? item.Name);
 299
 300                var images = await _providerManager.GetAvailableRemoteImages(
 301                    item,
 302                    new RemoteImageQuery(provider.Name)
 303                    {
 304                        IncludeAllLanguages = true,
 305                        IncludeDisabledProviders = false,
 306                    },
 307                    cancellationToken).ConfigureAwait(false);
 308
 309                var list = images.ToList();
 310                int minWidth;
 311
 312                foreach (var imageType in _singularImages)
 313                {
 314                    if (!savedOptions.IsEnabled(imageType))
 315                    {
 316                        continue;
 317                    }
 318
 319                    if (!item.HasImage(imageType) || (refreshOptions.IsReplacingImage(imageType) && !downloadedImages.Co
 320                    {
 321                        minWidth = savedOptions.GetMinWidth(imageType);
 322                        var downloaded = await DownloadImage(item, provider, result, list, minWidth, imageType, cancella
 323
 324                        if (downloaded)
 325                        {
 326                            downloadedImages.Add(imageType);
 327                        }
 328                    }
 329                }
 330
 331                minWidth = savedOptions.GetMinWidth(ImageType.Backdrop);
 332                var listWithNoLangFirst = list.OrderByDescending(i => string.IsNullOrEmpty(i.Language));
 333                await DownloadMultiImages(item, ImageType.Backdrop, refreshOptions, backdropLimit, provider, result, lis
 334            }
 335            catch (OperationCanceledException)
 336            {
 337                throw;
 338            }
 339            catch (Exception ex)
 340            {
 341                result.ErrorMessage = ex.Message;
 342                _logger.LogError(ex, "Error in {Provider}", provider.Name);
 343            }
 344        }
 345
 346        /// <summary>
 347        /// Determines if an item already contains the given images.
 348        /// </summary>
 349        /// <param name="item">The item.</param>
 350        /// <param name="images">The images.</param>
 351        /// <param name="savedOptions">The saved options.</param>
 352        /// <param name="backdropLimit">The backdrop limit.</param>
 353        /// <returns><c>true</c> if the specified item contains images; otherwise, <c>false</c>.</returns>
 354        private bool ContainsImages(BaseItem item, List<ImageType> images, TypeOptions savedOptions, int backdropLimit)
 355        {
 356            // Using .Any causes the creation of a DisplayClass aka. variable capture
 130357            for (var i = 0; i < _singularImages.Length; i++)
 358            {
 59359                var type = _singularImages[i];
 59360                if (images.Contains(type) && !item.HasImage(type) && savedOptions.GetLimit(type) > 0)
 361                {
 5362                    return false;
 363                }
 364            }
 365
 6366            if (images.Contains(ImageType.Backdrop) && item.GetImages(ImageType.Backdrop).Count() < backdropLimit)
 367            {
 3368                return false;
 369            }
 370
 3371            return true;
 372        }
 373
 374        private void PruneImages(BaseItem item, IReadOnlyList<ItemImageInfo> images)
 375        {
 32376            foreach (var image in images)
 377            {
 9378                if (image.IsLocalFile)
 379                {
 380                    try
 381                    {
 9382                        _fileSystem.DeleteFile(image.Path);
 9383                    }
 0384                    catch (FileNotFoundException)
 385                    {
 386                        // Nothing to do, already gone
 0387                    }
 0388                    catch (DirectoryNotFoundException)
 389                    {
 390                        // Nothing to do, already gone
 0391                    }
 0392                    catch (UnauthorizedAccessException ex)
 393                    {
 0394                        _logger.LogWarning(ex, "Unable to delete {Image}", image.Path);
 0395                    }
 396                }
 397            }
 398
 7399            item.RemoveImages(images);
 400
 401            // Cleanup old metadata directory for episodes if empty, as long as it's not a virtual item
 7402            if (item is Episode && !item.IsVirtualItem)
 403            {
 0404                var oldLocalMetadataDirectory = Path.Combine(item.ContainingFolderPath, "metadata");
 0405                if (_fileSystem.DirectoryExists(oldLocalMetadataDirectory) && !_fileSystem.GetFiles(oldLocalMetadataDire
 406                {
 0407                    Directory.Delete(oldLocalMetadataDirectory);
 408                }
 409            }
 7410        }
 411
 412        /// <summary>
 413        /// Merges a list of images into the provided item, validating existing images and replacing them or adding new 
 414        /// </summary>
 415        /// <param name="refreshOptions">The refresh options.</param>
 416        /// <param name="dontReplaceImages">List of imageTypes to remove from ReplaceImages.</param>
 417        public void UpdateReplaceImages(ImageRefreshOptions refreshOptions, ICollection<ImageType> dontReplaceImages)
 418        {
 0419            if (refreshOptions is not null)
 420            {
 0421                if (refreshOptions.ReplaceAllImages)
 422                {
 0423                    refreshOptions.ReplaceAllImages = false;
 0424                    refreshOptions.ReplaceImages = AllImageTypes.ToList();
 425                }
 426
 0427                refreshOptions.ReplaceImages = refreshOptions.ReplaceImages.Except(dontReplaceImages).ToList();
 428            }
 0429        }
 430
 431        /// <summary>
 432        /// Merges a list of images into the provided item, validating existing images and replacing them or adding new 
 433        /// </summary>
 434        /// <param name="item">The <see cref="BaseItem"/> to modify.</param>
 435        /// <param name="images">The new images to place in <c>item</c>.</param>
 436        /// <param name="refreshOptions">The refresh options.</param>
 437        /// <returns><c>true</c> if changes were made to the item; otherwise <c>false</c>.</returns>
 438        public bool MergeImages(BaseItem item, IReadOnlyList<LocalImageInfo> images, ImageRefreshOptions refreshOptions)
 439        {
 70440            var changed = item.ValidateImages();
 70441            var foundImageTypes = new List<ImageType>();
 1400442            for (var i = 0; i < _singularImages.Length; i++)
 443            {
 630444                var type = _singularImages[i];
 630445                var image = GetFirstLocalImageInfoByType(images, type);
 630446                if (image is not null)
 447                {
 4448                    var currentImage = item.GetImageInfo(type, 0);
 449                    // if image file is stored with media, don't replace that later
 4450                    if (item.ContainingFolderPath is not null && item.ContainingFolderPath.Contains(Path.GetDirectoryNam
 451                    {
 0452                        foundImageTypes.Add(type);
 453                    }
 454
 4455                    if (currentImage is null || !string.Equals(currentImage.Path, image.FileInfo.FullName, StringCompari
 456                    {
 2457                        item.SetImagePath(type, image.FileInfo);
 2458                        changed = true;
 459                    }
 460                    else
 461                    {
 2462                        var newDateModified = _fileSystem.GetLastWriteTimeUtc(image.FileInfo);
 463
 464                        // If date changed then we need to reset saved image dimensions
 2465                        if (currentImage.DateModified != newDateModified && (currentImage.Width > 0 || currentImage.Heig
 466                        {
 1467                            currentImage.Width = 0;
 1468                            currentImage.Height = 0;
 1469                            changed = true;
 470                        }
 471
 2472                        currentImage.DateModified = newDateModified;
 473                    }
 474                }
 475            }
 476
 70477            bool hasBackdrop = false;
 70478            bool backdropStoredWithMedia = false;
 479
 164480            foreach (var image in images)
 481            {
 12482                if (image.Type != ImageType.Backdrop)
 483                {
 484                    continue;
 485                }
 486
 8487                hasBackdrop = true;
 488
 8489                if (item.ContainingFolderPath is not null && item.ContainingFolderPath.Contains(Path.GetDirectoryName(im
 490                {
 0491                    backdropStoredWithMedia = true;
 0492                    break;
 493                }
 494            }
 495
 70496            if (hasBackdrop)
 497            {
 4498                if (UpdateMultiImages(item, images, ImageType.Backdrop))
 499                {
 3500                    changed = true;
 501                }
 502
 4503                if (backdropStoredWithMedia)
 504                {
 0505                    foundImageTypes.Add(ImageType.Backdrop);
 506                }
 507            }
 508
 70509            if (foundImageTypes.Count > 0)
 510            {
 0511                UpdateReplaceImages(refreshOptions, foundImageTypes);
 512            }
 513
 70514            return changed;
 515        }
 516
 517        private static LocalImageInfo GetFirstLocalImageInfoByType(IReadOnlyList<LocalImageInfo> images, ImageType type)
 518        {
 630519            var len = images.Count;
 1468520            for (var i = 0; i < len; i++)
 521            {
 108522                var image = images[i];
 108523                if (image.Type == type)
 524                {
 4525                    return image;
 526                }
 527            }
 528
 626529            return null;
 530        }
 531
 532        private bool UpdateMultiImages(BaseItem item, IReadOnlyList<LocalImageInfo> images, ImageType type)
 533        {
 4534            var changed = false;
 535
 4536            var newImageFileInfos = images
 4537                .Where(i => i.Type == type)
 4538                .Select(i => i.FileInfo)
 4539                .ToList();
 540
 4541            if (item.AddImages(type, newImageFileInfos))
 542            {
 3543                changed = true;
 544            }
 545
 4546            return changed;
 547        }
 548
 549        private async Task<bool> DownloadImage(
 550            BaseItem item,
 551            IRemoteImageProvider provider,
 552            RefreshResult result,
 553            IEnumerable<RemoteImageInfo> images,
 554            int minWidth,
 555            ImageType type,
 556            CancellationToken cancellationToken)
 557        {
 558            var eligibleImages = images
 559                .Where(i => i.Type == type && (i.Width is null || i.Width >= minWidth))
 560                .ToList();
 561
 562            if (EnableImageStub(item) && eligibleImages.Count > 0)
 563            {
 564                SaveImageStub(item, type, eligibleImages.Select(i => i.Url));
 565                result.UpdateType |= ItemUpdateType.ImageUpdate;
 566                return true;
 567            }
 568
 569            foreach (var image in eligibleImages)
 570            {
 571                var url = image.Url;
 572
 573                try
 574                {
 575                    using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
 576
 577                    // Sometimes providers send back bad urls. Just move to the next image
 578                    if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Forbidde
 579                    {
 580                        _logger.LogDebug("{Url} returned {StatusCode}, ignoring", url, response.StatusCode);
 581                        continue;
 582                    }
 583
 584                    if (!response.IsSuccessStatusCode)
 585                    {
 586                        _logger.LogWarning("{Url} returned {StatusCode}, skipping all remaining requests", url, response
 587                        break;
 588                    }
 589
 590                    var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
 591                    await using (stream.ConfigureAwait(false))
 592                    {
 593                        var mimetype = response.Content.Headers.ContentType?.MediaType;
 594                        if (mimetype is null || mimetype.Equals(MediaTypeNames.Application.Octet, StringComparison.Ordin
 595                        {
 596                            mimetype = MimeTypes.GetMimeType(response.RequestMessage.RequestUri.GetLeftPart(UriPartial.P
 597                        }
 598
 599                        await _providerManager.SaveImage(
 600                            item,
 601                            stream,
 602                            mimetype,
 603                            type,
 604                            null,
 605                            cancellationToken).ConfigureAwait(false);
 606                    }
 607
 608                    result.UpdateType |= ItemUpdateType.ImageUpdate;
 609                    return true;
 610                }
 611                catch (HttpRequestException)
 612                {
 613                    break;
 614                }
 615            }
 616
 617            return false;
 618        }
 619
 620        private bool EnableImageStub(BaseItem item)
 621        {
 134622            if (item is LiveTvProgram)
 623            {
 0624                return true;
 625            }
 626
 134627            if (!item.IsFileProtocol)
 628            {
 95629                return true;
 630            }
 631
 39632            if (item is IItemByName and not MusicArtist)
 633            {
 0634                var hasDualAccess = item as IHasDualAccess;
 0635                if (hasDualAccess is null || hasDualAccess.IsAccessedByName)
 636                {
 0637                    return true;
 638                }
 639            }
 640
 641            // We always want to use prefetched images
 39642            return false;
 643        }
 644
 645        private void SaveImageStub(BaseItem item, ImageType imageType, IEnumerable<string> urls)
 646        {
 9647            var newIndex = item.AllowsMultipleImages(imageType) ? item.GetImages(imageType).Count() : 0;
 648
 9649            SaveImageStub(item, imageType, urls, newIndex);
 9650        }
 651
 652        private void SaveImageStub(BaseItem item, ImageType imageType, IEnumerable<string> urls, int newIndex)
 653        {
 9654            var path = string.Join('|', urls.Take(1));
 655
 9656            item.SetImage(
 9657                new ItemImageInfo
 9658                {
 9659                    Path = path,
 9660                    Type = imageType
 9661                },
 9662                newIndex);
 9663        }
 664
 665        private async Task DownloadMultiImages(BaseItem item, ImageType imageType, ImageRefreshOptions refreshOptions, i
 666        {
 667            foreach (var image in images.Where(i => i.Type == imageType))
 668            {
 669                if (item.GetImages(imageType).Count() >= limit)
 670                {
 671                    break;
 672                }
 673
 674                if (image.Width.HasValue && image.Width.Value < minWidth)
 675                {
 676                    continue;
 677                }
 678
 679                var url = image.Url;
 680
 681                if (EnableImageStub(item))
 682                {
 683                    SaveImageStub(item, imageType, new[] { url });
 684                    result.UpdateType |= ItemUpdateType.ImageUpdate;
 685                    continue;
 686                }
 687
 688                try
 689                {
 690                    using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
 691
 692                    // Sometimes providers send back bad urls. Just move to the next image
 693                    if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Forbidde
 694                    {
 695                        _logger.LogDebug("{Url} returned {StatusCode}, ignoring", url, response.StatusCode);
 696                        continue;
 697                    }
 698
 699                    if (!response.IsSuccessStatusCode)
 700                    {
 701                        _logger.LogWarning("{Url} returned {StatusCode}, skipping all remaining requests", url, response
 702                        break;
 703                    }
 704
 705                    // If there's already an image of the same file size, skip it unless doing a full refresh
 706                    if (response.Content.Headers.ContentLength.HasValue && !refreshOptions.IsReplacingImage(imageType))
 707                    {
 708                        try
 709                        {
 710                            if (item.GetImages(imageType).Any(i => _fileSystem.GetFileInfo(i.Path).Length == response.Co
 711                            {
 712                                response.Content.Dispose();
 713                                continue;
 714                            }
 715                        }
 716                        catch (IOException ex)
 717                        {
 718                            _logger.LogError(ex, "Error examining images");
 719                        }
 720                    }
 721
 722                    var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
 723                    await using (stream.ConfigureAwait(false))
 724                    {
 725                        var mimetype = response.Content.Headers.ContentType?.MediaType;
 726                        if (mimetype is null || mimetype.Equals(MediaTypeNames.Application.Octet, StringComparison.Ordin
 727                        {
 728                            mimetype = MimeTypes.GetMimeType(response.RequestMessage.RequestUri.GetLeftPart(UriPartial.P
 729                        }
 730
 731                        await _providerManager.SaveImage(
 732                            item,
 733                            stream,
 734                            mimetype,
 735                            imageType,
 736                            null,
 737                            cancellationToken).ConfigureAwait(false);
 738                    }
 739
 740                    result.UpdateType |= ItemUpdateType.ImageUpdate;
 741                }
 742                catch (HttpRequestException)
 743                {
 744                    break;
 745                }
 746            }
 747        }
 748    }
 749}

Methods/Properties

.cctor()
.ctor(Microsoft.Extensions.Logging.ILogger,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Model.IO.IFileSystem)
RemoveImages(MediaBrowser.Controller.Entities.BaseItem,System.Boolean)
ValidateImages(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.IEnumerable`1<MediaBrowser.Controller.Providers.IImageProvider>,MediaBrowser.Controller.Providers.ImageRefreshOptions)
ContainsImages(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.List`1<MediaBrowser.Model.Entities.ImageType>,MediaBrowser.Model.Configuration.TypeOptions,System.Int32)
PruneImages(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.ItemImageInfo>)
UpdateReplaceImages(MediaBrowser.Controller.Providers.ImageRefreshOptions,System.Collections.Generic.ICollection`1<MediaBrowser.Model.Entities.ImageType>)
MergeImages(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Providers.LocalImageInfo>,MediaBrowser.Controller.Providers.ImageRefreshOptions)
GetFirstLocalImageInfoByType(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Providers.LocalImageInfo>,MediaBrowser.Model.Entities.ImageType)
UpdateMultiImages(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Providers.LocalImageInfo>,MediaBrowser.Model.Entities.ImageType)
EnableImageStub(MediaBrowser.Controller.Entities.BaseItem)
SaveImageStub(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Entities.ImageType,System.Collections.Generic.IEnumerable`1<System.String>)
SaveImageStub(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Entities.ImageType,System.Collections.Generic.IEnumerable`1<System.String>,System.Int32)