< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.Manager.ItemImageProvider
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/Manager/ItemImageProvider.cs
Line coverage
84%
Covered lines: 253
Uncovered lines: 47
Coverable lines: 300
Total lines: 752
Line coverage: 84.3%
Branch coverage
79%
Covered branches: 171
Total branches: 214
Branch coverage: 79.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/16/2026 - 12:15:55 AM Line coverage: 81.5% (243/298) Branch coverage: 79.9% (171/214) Total lines: 7495/20/2026 - 12:15:44 AM Line coverage: 81.5% (243/298) Branch coverage: 77.1% (165/214) Total lines: 74910/29/2026 - 12:17:11 AM Line coverage: 84.3% (253/300) Branch coverage: 79.9% (171/214) Total lines: 752 5/16/2026 - 12:15:55 AM Line coverage: 81.5% (243/298) Branch coverage: 79.9% (171/214) Total lines: 7495/20/2026 - 12:15:44 AM Line coverage: 81.5% (243/298) Branch coverage: 77.1% (165/214) Total lines: 74910/29/2026 - 12:17:11 AM Line coverage: 84.3% (253/300) Branch coverage: 79.9% (171/214) Total lines: 752

Coverage delta

Coverage delta 3 -3

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
RemoveImages(...)78.57%1414100%
ValidateImages(...)100%66100%
RefreshImages()100%1414100%
RefreshFromProvider()100%222293.93%
RefreshFromProvider()95.83%242491.42%
ContainsImages(...)100%1212100%
PruneImages(...)41.66%411241.17%
UpdateReplaceImages(...)0%2040%
MergeImages(...)73.52%383485.29%
GetFirstLocalImageInfoByType(...)100%44100%
UpdateMultiImages(...)100%22100%
DownloadImage()70%232079.41%
EnableImageStub(...)50%251255.55%
SaveImageStub(...)100%22100%
SaveImageStub(...)100%11100%
DownloadMultiImages()78.12%473275.6%

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        {
 77463            _logger = logger;
 77464            _providerManager = providerManager;
 77465            _fileSystem = fileSystem;
 77466        }
 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        {
 122114            var hasChanges = false;
 122115            var directoryService = refreshOptions?.DirectoryService;
 116
 122117            if (item is not Photo)
 118            {
 121119                var images = providers.OfType<ILocalImageProvider>()
 121120                    .SelectMany(i => i.GetImages(item, directoryService))
 121121                    .ToList();
 122
 120123                if (MergeImages(item, images, refreshOptions))
 124                {
 4125                    hasChanges = true;
 126                }
 127            }
 128
 121129            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        {
 31148            var oldBackdropImages = Array.Empty<ItemImageInfo>();
 31149            if (refreshOptions.IsReplacingImage(ImageType.Backdrop))
 150            {
 8151                oldBackdropImages = item.GetImages(ImageType.Backdrop).ToArray();
 152            }
 153
 31154            var result = new RefreshResult { UpdateType = ItemUpdateType.None };
 155
 31156            var typeName = item.GetType().Name;
 31157            var typeOptions = libraryOptions.GetTypeOptions(typeName) ?? new TypeOptions { Type = typeName };
 158
 159            // track library limits, adding buffer to allow lazy replacing of current images
 31160            var backdropLimit = typeOptions.GetLimit(ImageType.Backdrop) + oldBackdropImages.Length;
 31161            var downloadedImages = new List<ImageType>();
 162
 124163            foreach (var provider in providers)
 164            {
 31165                if (provider is IRemoteImageProvider remoteProvider)
 166                {
 18167                    await RefreshFromProvider(item, remoteProvider, refreshOptions, typeOptions, backdropLimit, download
 18168                    continue;
 169                }
 170
 13171                if (provider is IDynamicImageProvider dynamicImageProvider)
 172                {
 13173                    await RefreshFromProvider(item, dynamicImageProvider, refreshOptions, typeOptions, downloadedImages,
 174                }
 175            }
 176
 177            // Only delete existing multi-images if new ones were added
 31178            if (oldBackdropImages.Length > 0 && oldBackdropImages.Length < item.GetImages(ImageType.Backdrop).Count())
 179            {
 4180                PruneImages(item, oldBackdropImages);
 181            }
 182
 31183            return result;
 31184        }
 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            {
 13200                var images = provider.GetSupportedImages(item);
 201
 50202                foreach (var imageType in images)
 203                {
 13204                    if (!savedOptions.IsEnabled(imageType))
 205                    {
 206                        continue;
 207                    }
 208
 13209                    if (!item.HasImage(imageType) || (refreshOptions.IsReplacingImage(imageType) && !downloadedImages.Co
 210                    {
 11211                        _logger.LogDebug("Running {Provider} for {Item}", provider.GetType().Name, item.Path ?? item.Nam
 212
 11213                        var response = await provider.GetImage(item, imageType, cancellationToken).ConfigureAwait(false)
 214
 9215                        if (response.HasImage)
 216                        {
 8217                            if (string.IsNullOrEmpty(response.Path))
 218                            {
 2219                                var mimeType = response.Format.GetMimeType();
 220
 2221                                await _providerManager.SaveImage(item, response.Stream, mimeType, imageType, null, cance
 222                            }
 223                            else
 224                            {
 6225                                if (response.Protocol == MediaProtocol.Http)
 226                                {
 4227                                    _logger.LogDebug("Setting image url into item {Item}", item.Id);
 4228                                    var index = item.AllowsMultipleImages(imageType) ? item.GetImages(imageType).Count()
 4229                                    item.SetImage(
 4230                                        new ItemImageInfo
 4231                                        {
 4232                                            Path = response.Path,
 4233                                            Type = imageType
 4234                                        },
 4235                                        index);
 236                                }
 237                                else
 238                                {
 2239                                    var mimeType = MimeTypes.GetMimeType(response.Path);
 240
 2241                                    await _providerManager.SaveImage(item, response.Path, mimeType, imageType, null, nul
 242                                }
 243                            }
 244
 8245                            downloadedImages.Add(imageType);
 8246                            result.UpdateType |= ItemUpdateType.ImageUpdate;
 247                        }
 248                    }
 249                }
 11250            }
 0251            catch (OperationCanceledException)
 252            {
 0253                throw;
 254            }
 2255            catch (Exception ex)
 256            {
 2257                result.Failures++;
 2258                result.ErrorMessage = ex.Message;
 2259                _logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, item.Path ?? item.Name);
 2260            }
 13261        }
 262
 263        /// <summary>
 264        /// Refreshes from a remote provider.
 265        /// </summary>
 266        /// <param name="item">The item.</param>
 267        /// <param name="provider">The provider.</param>
 268        /// <param name="refreshOptions">The refresh options.</param>
 269        /// <param name="savedOptions">The saved options.</param>
 270        /// <param name="backdropLimit">The backdrop limit.</param>
 271        /// <param name="downloadedImages">The downloaded images.</param>
 272        /// <param name="result">The result.</param>
 273        /// <param name="cancellationToken">The cancellation token.</param>
 274        /// <returns>Task.</returns>
 275        private async Task RefreshFromProvider(
 276            BaseItem item,
 277            IRemoteImageProvider provider,
 278            ImageRefreshOptions refreshOptions,
 279            TypeOptions savedOptions,
 280            int backdropLimit,
 281            List<ImageType> downloadedImages,
 282            RefreshResult result,
 283            CancellationToken cancellationToken)
 284        {
 285            try
 286            {
 18287                if (!item.SupportsRemoteImageDownloading)
 288                {
 0289                    return;
 290                }
 291
 18292                if (!refreshOptions.ReplaceAllImages &&
 18293                    refreshOptions.ReplaceImages.Count == 0 &&
 18294                    ContainsImages(item, provider.GetSupportedImages(item).ToList(), savedOptions, backdropLimit))
 295                {
 3296                    return;
 297                }
 298
 15299                _logger.LogDebug("Running {Provider} for {Item}", provider.GetType().Name, item.Path ?? item.Name);
 300
 15301                var images = await _providerManager.GetAvailableRemoteImages(
 15302                    item,
 15303                    new RemoteImageQuery(provider.Name)
 15304                    {
 15305                        IncludeAllLanguages = true,
 15306                        IncludeDisabledProviders = false,
 15307                    },
 15308                    cancellationToken).ConfigureAwait(false);
 309
 14310                var list = images.ToList();
 311                int minWidth;
 312
 280313                foreach (var imageType in _singularImages)
 314                {
 126315                    if (!savedOptions.IsEnabled(imageType))
 316                    {
 317                        continue;
 318                    }
 319
 126320                    if (!item.HasImage(imageType) || (refreshOptions.IsReplacingImage(imageType) && !downloadedImages.Co
 321                    {
 126322                        minWidth = savedOptions.GetMinWidth(imageType);
 126323                        var downloaded = await DownloadImage(item, provider, result, list, minWidth, imageType, cancella
 324
 126325                        if (downloaded)
 326                        {
 5327                            downloadedImages.Add(imageType);
 328                        }
 329                    }
 330                }
 331
 14332                minWidth = savedOptions.GetMinWidth(ImageType.Backdrop);
 14333                var listWithNoLangFirst = list.OrderByDescending(i => string.IsNullOrEmpty(i.Language));
 14334                await DownloadMultiImages(item, ImageType.Backdrop, refreshOptions, backdropLimit, provider, result, lis
 14335            }
 0336            catch (OperationCanceledException)
 337            {
 0338                throw;
 339            }
 1340            catch (Exception ex)
 341            {
 1342                result.Failures++;
 1343                result.ErrorMessage = ex.Message;
 1344                _logger.LogError(ex, "Error in {Provider} for {Item}", provider.Name, item.Path ?? item.Name);
 1345            }
 18346        }
 347
 348        /// <summary>
 349        /// Determines if an item already contains the given images.
 350        /// </summary>
 351        /// <param name="item">The item.</param>
 352        /// <param name="images">The images.</param>
 353        /// <param name="savedOptions">The saved options.</param>
 354        /// <param name="backdropLimit">The backdrop limit.</param>
 355        /// <returns><c>true</c> if the specified item contains images; otherwise, <c>false</c>.</returns>
 356        private bool ContainsImages(BaseItem item, List<ImageType> images, TypeOptions savedOptions, int backdropLimit)
 357        {
 358            // Using .Any causes the creation of a DisplayClass aka. variable capture
 132359            for (var i = 0; i < _singularImages.Length; i++)
 360            {
 60361                var type = _singularImages[i];
 60362                if (images.Contains(type) && !item.HasImage(type) && savedOptions.GetLimit(type) > 0)
 363                {
 6364                    return false;
 365                }
 366            }
 367
 6368            if (images.Contains(ImageType.Backdrop) && item.GetImages(ImageType.Backdrop).Count() < backdropLimit)
 369            {
 3370                return false;
 371            }
 372
 3373            return true;
 374        }
 375
 376        private void PruneImages(BaseItem item, IReadOnlyList<ItemImageInfo> images)
 377        {
 32378            foreach (var image in images)
 379            {
 9380                if (image.IsLocalFile)
 381                {
 382                    try
 383                    {
 9384                        _fileSystem.DeleteFile(image.Path);
 9385                    }
 0386                    catch (FileNotFoundException)
 387                    {
 388                        // Nothing to do, already gone
 0389                    }
 0390                    catch (DirectoryNotFoundException)
 391                    {
 392                        // Nothing to do, already gone
 0393                    }
 0394                    catch (UnauthorizedAccessException ex)
 395                    {
 0396                        _logger.LogWarning(ex, "Unable to delete {Image}", image.Path);
 0397                    }
 398                }
 399            }
 400
 7401            item.RemoveImages(images);
 402
 403            // Cleanup old metadata directory for episodes if empty, as long as it's not a virtual item
 7404            if (item is Episode && !item.IsVirtualItem)
 405            {
 0406                var oldLocalMetadataDirectory = Path.Combine(item.ContainingFolderPath, "metadata");
 0407                if (_fileSystem.DirectoryExists(oldLocalMetadataDirectory) && !_fileSystem.GetFiles(oldLocalMetadataDire
 408                {
 0409                    Directory.Delete(oldLocalMetadataDirectory);
 410                }
 411            }
 7412        }
 413
 414        /// <summary>
 415        /// Merges a list of images into the provided item, validating existing images and replacing them or adding new 
 416        /// </summary>
 417        /// <param name="refreshOptions">The refresh options.</param>
 418        /// <param name="dontReplaceImages">List of imageTypes to remove from ReplaceImages.</param>
 419        public void UpdateReplaceImages(ImageRefreshOptions refreshOptions, ICollection<ImageType> dontReplaceImages)
 420        {
 0421            if (refreshOptions is not null)
 422            {
 0423                if (refreshOptions.ReplaceAllImages)
 424                {
 0425                    refreshOptions.ReplaceAllImages = false;
 0426                    refreshOptions.ReplaceImages = AllImageTypes.ToList();
 427                }
 428
 0429                refreshOptions.ReplaceImages = refreshOptions.ReplaceImages.Except(dontReplaceImages).ToList();
 430            }
 0431        }
 432
 433        /// <summary>
 434        /// Merges a list of images into the provided item, validating existing images and replacing them or adding new 
 435        /// </summary>
 436        /// <param name="item">The <see cref="BaseItem"/> to modify.</param>
 437        /// <param name="images">The new images to place in <c>item</c>.</param>
 438        /// <param name="refreshOptions">The refresh options.</param>
 439        /// <returns><c>true</c> if changes were made to the item; otherwise <c>false</c>.</returns>
 440        public bool MergeImages(BaseItem item, IReadOnlyList<LocalImageInfo> images, ImageRefreshOptions refreshOptions)
 441        {
 130442            var changed = item.ValidateImages();
 130443            var foundImageTypes = new List<ImageType>();
 2600444            for (var i = 0; i < _singularImages.Length; i++)
 445            {
 1170446                var type = _singularImages[i];
 1170447                var image = GetFirstLocalImageInfoByType(images, type);
 1170448                if (image is not null)
 449                {
 5450                    var currentImage = item.GetImageInfo(type, 0);
 451                    // if image file is stored with media, don't replace that later
 5452                    if (item.ContainingFolderPath is not null && item.ContainingFolderPath.Contains(Path.GetDirectoryNam
 453                    {
 0454                        foundImageTypes.Add(type);
 455                    }
 456
 5457                    if (currentImage is null || !string.Equals(currentImage.Path, image.FileInfo.FullName, StringCompari
 458                    {
 2459                        item.SetImagePath(type, image.FileInfo);
 2460                        changed = true;
 461                    }
 462                    else
 463                    {
 3464                        var newDateModified = _fileSystem.GetLastWriteTimeUtc(image.FileInfo);
 465
 466                        // If date changed then we need to reset saved image dimensions. Stores such as PostgreSQL keep
 467                        // only microseconds, so an exact comparison would treat every unchanged image as modified.
 3468                        if (Math.Abs((currentImage.DateModified - newDateModified).TotalSeconds) > 1 && (currentImage.Wi
 469                        {
 1470                            currentImage.Width = 0;
 1471                            currentImage.Height = 0;
 1472                            changed = true;
 473                        }
 474
 3475                        currentImage.DateModified = newDateModified;
 476                    }
 477                }
 478            }
 479
 130480            bool hasBackdrop = false;
 130481            bool backdropStoredWithMedia = false;
 482
 290483            foreach (var image in images)
 484            {
 15485                if (image.Type != ImageType.Backdrop)
 486                {
 487                    continue;
 488                }
 489
 10490                hasBackdrop = true;
 491
 10492                if (item.ContainingFolderPath is not null && item.ContainingFolderPath.Contains(Path.GetDirectoryName(im
 493                {
 0494                    backdropStoredWithMedia = true;
 0495                    break;
 496                }
 497            }
 498
 130499            if (hasBackdrop)
 500            {
 5501                if (UpdateMultiImages(item, images, ImageType.Backdrop))
 502                {
 3503                    changed = true;
 504                }
 505
 5506                if (backdropStoredWithMedia)
 507                {
 0508                    foundImageTypes.Add(ImageType.Backdrop);
 509                }
 510            }
 511
 130512            if (foundImageTypes.Count > 0)
 513            {
 0514                UpdateReplaceImages(refreshOptions, foundImageTypes);
 515            }
 516
 130517            return changed;
 518        }
 519
 520        private static LocalImageInfo GetFirstLocalImageInfoByType(IReadOnlyList<LocalImageInfo> images, ImageType type)
 521        {
 1170522            var len = images.Count;
 2600523            for (var i = 0; i < len; i++)
 524            {
 135525                var image = images[i];
 135526                if (image.Type == type)
 527                {
 5528                    return image;
 529                }
 530            }
 531
 1165532            return null;
 533        }
 534
 535        private bool UpdateMultiImages(BaseItem item, IReadOnlyList<LocalImageInfo> images, ImageType type)
 536        {
 5537            var changed = false;
 538
 5539            var newImageFileInfos = images
 5540                .Where(i => i.Type == type)
 5541                .Select(i => i.FileInfo)
 5542                .ToList();
 543
 5544            if (item.AddImages(type, newImageFileInfos))
 545            {
 3546                changed = true;
 547            }
 548
 5549            return changed;
 550        }
 551
 552        private async Task<bool> DownloadImage(
 553            BaseItem item,
 554            IRemoteImageProvider provider,
 555            RefreshResult result,
 556            IEnumerable<RemoteImageInfo> images,
 557            int minWidth,
 558            ImageType type,
 559            CancellationToken cancellationToken)
 560        {
 126561            var eligibleImages = images
 126562                .Where(i => i.Type == type && (i.Width is null || i.Width >= minWidth))
 126563                .ToList();
 564
 126565            if (EnableImageStub(item) && eligibleImages.Count > 0)
 566            {
 4567                SaveImageStub(item, type, eligibleImages.Select(i => i.Url));
 4568                result.UpdateType |= ItemUpdateType.ImageUpdate;
 4569                return true;
 570            }
 571
 245572            foreach (var image in eligibleImages)
 573            {
 1574                var url = image.Url;
 575
 576                try
 577                {
 1578                    using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
 579
 580                    // Sometimes providers send back bad urls. Just move to the next image
 1581                    if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Forbidde
 582                    {
 0583                        _logger.LogDebug("{Url} returned {StatusCode}, ignoring", url, response.StatusCode);
 0584                        continue;
 585                    }
 586
 1587                    if (!response.IsSuccessStatusCode)
 588                    {
 0589                        _logger.LogWarning("{Url} returned {StatusCode}, skipping all remaining requests", url, response
 0590                        break;
 591                    }
 592
 1593                    var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
 1594                    await using (stream.ConfigureAwait(false))
 595                    {
 1596                        var mimetype = response.Content.Headers.ContentType?.MediaType;
 1597                        if (mimetype is null || mimetype.Equals(MediaTypeNames.Application.Octet, StringComparison.Ordin
 598                        {
 0599                            mimetype = MimeTypes.GetMimeType(response.RequestMessage.RequestUri.GetLeftPart(UriPartial.P
 600                        }
 601
 1602                        await _providerManager.SaveImage(
 1603                            item,
 1604                            stream,
 1605                            mimetype,
 1606                            type,
 1607                            null,
 1608                            cancellationToken).ConfigureAwait(false);
 609                    }
 610
 1611                    result.UpdateType |= ItemUpdateType.ImageUpdate;
 1612                    return true;
 613                }
 0614                catch (HttpRequestException)
 615                {
 0616                    break;
 617                }
 618            }
 619
 121620            return false;
 126621        }
 622
 623        private bool EnableImageStub(BaseItem item)
 624        {
 134625            if (item is LiveTvProgram)
 626            {
 0627                return true;
 628            }
 629
 134630            if (!item.IsFileProtocol)
 631            {
 95632                return true;
 633            }
 634
 39635            if (item is IItemByName and not MusicArtist)
 636            {
 0637                var hasDualAccess = item as IHasDualAccess;
 0638                if (hasDualAccess is null || hasDualAccess.IsAccessedByName)
 639                {
 0640                    return true;
 641                }
 642            }
 643
 644            // We always want to use prefetched images
 39645            return false;
 646        }
 647
 648        private void SaveImageStub(BaseItem item, ImageType imageType, IEnumerable<string> urls)
 649        {
 9650            var newIndex = item.AllowsMultipleImages(imageType) ? item.GetImages(imageType).Count() : 0;
 651
 9652            SaveImageStub(item, imageType, urls, newIndex);
 9653        }
 654
 655        private void SaveImageStub(BaseItem item, ImageType imageType, IEnumerable<string> urls, int newIndex)
 656        {
 9657            var path = string.Join('|', urls.Take(1));
 658
 9659            item.SetImage(
 9660                new ItemImageInfo
 9661                {
 9662                    Path = path,
 9663                    Type = imageType
 9664                },
 9665                newIndex);
 9666        }
 667
 668        private async Task DownloadMultiImages(BaseItem item, ImageType imageType, ImageRefreshOptions refreshOptions, i
 669        {
 45670            foreach (var image in images.Where(i => i.Type == imageType))
 671            {
 9672                if (item.GetImages(imageType).Count() >= limit)
 673                {
 674                    break;
 675                }
 676
 8677                if (image.Width.HasValue && image.Width.Value < minWidth)
 678                {
 679                    continue;
 680                }
 681
 8682                var url = image.Url;
 683
 8684                if (EnableImageStub(item))
 685                {
 5686                    SaveImageStub(item, imageType, new[] { url });
 5687                    result.UpdateType |= ItemUpdateType.ImageUpdate;
 5688                    continue;
 689                }
 690
 691                try
 692                {
 3693                    using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
 694
 695                    // Sometimes providers send back bad urls. Just move to the next image
 3696                    if (response.StatusCode == HttpStatusCode.NotFound || response.StatusCode == HttpStatusCode.Forbidde
 697                    {
 0698                        _logger.LogDebug("{Url} returned {StatusCode}, ignoring", url, response.StatusCode);
 0699                        continue;
 700                    }
 701
 3702                    if (!response.IsSuccessStatusCode)
 703                    {
 0704                        _logger.LogWarning("{Url} returned {StatusCode}, skipping all remaining requests", url, response
 0705                        break;
 706                    }
 707
 708                    // If there's already an image of the same file size, skip it unless doing a full refresh
 3709                    if (response.Content.Headers.ContentLength.HasValue && !refreshOptions.IsReplacingImage(imageType))
 710                    {
 711                        try
 712                        {
 2713                            if (item.GetImages(imageType).Any(i => _fileSystem.GetFileInfo(i.Path).Length == response.Co
 714                            {
 1715                                response.Content.Dispose();
 1716                                continue;
 717                            }
 1718                        }
 0719                        catch (IOException ex)
 720                        {
 0721                            _logger.LogError(ex, "Error examining images");
 0722                        }
 723                    }
 724
 2725                    var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
 2726                    await using (stream.ConfigureAwait(false))
 727                    {
 2728                        var mimetype = response.Content.Headers.ContentType?.MediaType;
 2729                        if (mimetype is null || mimetype.Equals(MediaTypeNames.Application.Octet, StringComparison.Ordin
 730                        {
 0731                            mimetype = MimeTypes.GetMimeType(response.RequestMessage.RequestUri.GetLeftPart(UriPartial.P
 732                        }
 733
 2734                        await _providerManager.SaveImage(
 2735                            item,
 2736                            stream,
 2737                            mimetype,
 2738                            imageType,
 2739                            null,
 2740                            cancellationToken).ConfigureAwait(false);
 741                    }
 742
 2743                    result.UpdateType |= ItemUpdateType.ImageUpdate;
 2744                }
 0745                catch (HttpRequestException)
 746                {
 0747                    break;
 748                }
 2749            }
 14750        }
 751    }
 752}

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)
RefreshImages()
RefreshFromProvider()
RefreshFromProvider()
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)
DownloadImage()
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)
DownloadMultiImages()