< Summary - Jellyfin

Information
Class: MediaBrowser.LocalMetadata.Images.LocalImageProvider
Assembly: MediaBrowser.LocalMetadata
File(s): /srv/git/jellyfin/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
Line coverage
69%
Covered lines: 144
Uncovered lines: 64
Coverable lines: 208
Total lines: 495
Line coverage: 69.2%
Branch coverage
66%
Covered branches: 100
Total branches: 150
Branch coverage: 66.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
get_Name()100%11100%
get_Order()100%11100%
Supports(...)25%1041630%
GetFiles(...)50%4480%
GetImages(...)100%11100%
GetImages(...)100%210%
GetImages(...)100%11100%
PopulateImages(...)84%935074.28%
PopulatePrimaryImages(...)64.28%572866.66%
PopulateBackdrops(...)87.5%8893.33%
PopulateBackdropsFromExtraFanart(...)100%210%
PopulateBackdrops(...)100%1414100%
PopulateSeasonImagesFromSeriesFolder(...)0%110100%
AddImage(...)75%4480%
AddImage(...)50%3233.33%
GetImage(...)64.28%391450%

File(s)

/srv/git/jellyfin/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Globalization;
 4using System.IO;
 5using System.Linq;
 6using Jellyfin.Extensions;
 7using MediaBrowser.Controller.Entities;
 8using MediaBrowser.Controller.Entities.Audio;
 9using MediaBrowser.Controller.Entities.Movies;
 10using MediaBrowser.Controller.Entities.TV;
 11using MediaBrowser.Controller.Providers;
 12using MediaBrowser.Model.Entities;
 13using MediaBrowser.Model.IO;
 14
 15namespace MediaBrowser.LocalMetadata.Images
 16{
 17    /// <summary>
 18    /// Local image provider.
 19    /// </summary>
 20    public class LocalImageProvider : ILocalImageProvider, IHasOrder
 21    {
 122        private static readonly string[] _commonImageFileNames =
 123        {
 124            "poster",
 125            "folder",
 126            "cover",
 127            "default"
 128        };
 29
 130        private static readonly string[] _musicImageFileNames =
 131        {
 132            "folder",
 133            "poster",
 134            "cover",
 135            "jacket",
 136            "default",
 137            "albumart"
 138        };
 39
 140        private static readonly string[] _personImageFileNames =
 141        {
 142            "folder",
 143            "poster"
 144        };
 45
 146        private static readonly string[] _seriesImageFileNames =
 147        {
 148            "poster",
 149            "folder",
 150            "cover",
 151            "default",
 152            "show"
 153        };
 54
 155        private static readonly string[] _videoImageFileNames =
 156        {
 157            "poster",
 158            "folder",
 159            "cover",
 160            "default",
 161            "movie"
 162        };
 63
 64        private readonly IFileSystem _fileSystem;
 65
 66        /// <summary>
 67        /// Initializes a new instance of the <see cref="LocalImageProvider"/> class.
 68        /// </summary>
 69        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
 70        public LocalImageProvider(IFileSystem fileSystem)
 71        {
 3472            _fileSystem = fileSystem;
 3473        }
 74
 75        /// <inheritdoc />
 4976        public string Name => "Local Images";
 77
 78        /// <inheritdoc />
 4979        public int Order => 0;
 80
 81        /// <inheritdoc />
 82        public bool Supports(BaseItem item)
 83        {
 4984            if (item.SupportsLocalMetadata)
 85            {
 86                // Episode has its own provider
 4987                if (item is Episode || item is Audio || item is Photo)
 88                {
 089                    return false;
 90                }
 91
 4992                return true;
 93            }
 94
 095            if (item.LocationType == LocationType.Virtual)
 96            {
 097                var season = item as Season;
 098                var series = season?.Series;
 099                if (series is not null && series.IsFileProtocol)
 100                {
 0101                    return true;
 102                }
 103            }
 104
 0105            return false;
 106        }
 107
 108        private static IEnumerable<FileSystemMetadata> GetFiles(BaseItem item, bool includeDirectories, IDirectoryServic
 109        {
 49110            if (!item.IsFileProtocol)
 111            {
 0112                return Enumerable.Empty<FileSystemMetadata>();
 113            }
 114
 49115            var path = item.ContainingFolderPath;
 116
 117            // Exit if the cache dir does not exist, alternative solution is to create it, but that's a lot of empty dir
 49118            if (!Directory.Exists(path))
 119            {
 0120                return Enumerable.Empty<FileSystemMetadata>();
 121            }
 122
 49123            return directoryService.GetFileSystemEntries(path)
 49124                .Where(i =>
 49125                    (includeDirectories && i.IsDirectory)
 49126                    || BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparison.OrdinalIgnoreCase))
 49127                .OrderBy(i => Array.IndexOf(BaseItem.SupportedImageExtensions, i.Extension ?? string.Empty));
 128        }
 129
 130        /// <inheritdoc />
 131        public IEnumerable<LocalImageInfo> GetImages(BaseItem item, IDirectoryService directoryService)
 132        {
 49133            var files = GetFiles(item, true, directoryService).ToList();
 134
 49135            var list = new List<LocalImageInfo>();
 136
 49137            PopulateImages(item, list, files, true, directoryService);
 138
 49139            return list;
 140        }
 141
 142        /// <summary>
 143        /// Get images for item.
 144        /// </summary>
 145        /// <param name="item">The item.</param>
 146        /// <param name="path">The images path.</param>
 147        /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
 148        /// <returns>The local image info.</returns>
 149        public IEnumerable<LocalImageInfo> GetImages(BaseItem item, string path, IDirectoryService directoryService)
 150        {
 0151            return GetImages(item, new[] { path }, directoryService);
 152        }
 153
 154        /// <summary>
 155        /// Get images for item from multiple paths.
 156        /// </summary>
 157        /// <param name="item">The item.</param>
 158        /// <param name="paths">The image paths.</param>
 159        /// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
 160        /// <returns>The local image info.</returns>
 161        public IEnumerable<LocalImageInfo> GetImages(BaseItem item, IEnumerable<string> paths, IDirectoryService directo
 162        {
 13163            IEnumerable<FileSystemMetadata> files = paths.SelectMany(i => _fileSystem.GetFiles(i, BaseItem.SupportedImag
 164
 13165            files = files
 13166                .OrderBy(i => Array.IndexOf(BaseItem.SupportedImageExtensions, i.Extension ?? string.Empty));
 167
 13168            var list = new List<LocalImageInfo>();
 169
 13170            PopulateImages(item, list, files.ToList(), false, directoryService);
 171
 13172            return list;
 173        }
 174
 175        private void PopulateImages(BaseItem item, List<LocalImageInfo> images, List<FileSystemMetadata> files, bool sup
 176        {
 62177            if (supportParentSeriesFiles)
 178            {
 49179                if (item is Season season)
 180                {
 0181                    PopulateSeasonImagesFromSeriesFolder(season, images, directoryService);
 182                }
 183            }
 184
 62185            var imagePrefix = item.FileNameWithoutExtension + "-";
 62186            var isInMixedFolder = item.IsInMixedFolder;
 187
 62188            PopulatePrimaryImages(item, images, files, imagePrefix, isInMixedFolder);
 189
 62190            var added = false;
 62191            var isEpisode = item is Episode;
 62192            var isSong = item.GetType() == typeof(Audio);
 62193            var isPerson = item is Person;
 194
 195            // Logo
 62196            if (!isEpisode && !isSong && !isPerson)
 197            {
 62198                added = AddImage(files, images, "logo", imagePrefix, isInMixedFolder, ImageType.Logo);
 62199                if (!added)
 200                {
 62201                    AddImage(files, images, "clearlogo", imagePrefix, isInMixedFolder, ImageType.Logo);
 202                }
 203            }
 204
 205            // Art
 62206            if (!isEpisode && !isSong && !isPerson)
 207            {
 62208                AddImage(files, images, "clearart", imagePrefix, isInMixedFolder, ImageType.Art);
 209            }
 210
 211            // For music albums, prefer cdart before disc
 62212            if (item is MusicAlbum)
 213            {
 0214                added = AddImage(files, images, "cdart", imagePrefix, isInMixedFolder, ImageType.Disc);
 215
 0216                if (!added)
 217                {
 0218                    AddImage(files, images, "disc", imagePrefix, isInMixedFolder, ImageType.Disc);
 219                }
 220            }
 62221            else if (item is Video || item is BoxSet)
 222            {
 0223                added = AddImage(files, images, "disc", imagePrefix, isInMixedFolder, ImageType.Disc);
 224
 0225                if (!added)
 226                {
 0227                    added = AddImage(files, images, "cdart", imagePrefix, isInMixedFolder, ImageType.Disc);
 228                }
 229
 0230                if (!added)
 231                {
 0232                    AddImage(files, images, "discart", imagePrefix, isInMixedFolder, ImageType.Disc);
 233                }
 234            }
 235
 236            // Banner
 62237            if (!isEpisode && !isSong && !isPerson)
 238            {
 62239                AddImage(files, images, "banner", imagePrefix, isInMixedFolder, ImageType.Banner);
 240            }
 241
 242            // Thumb
 62243            if (!isEpisode && !isSong && !isPerson)
 244            {
 62245                added = AddImage(files, images, "landscape", imagePrefix, isInMixedFolder, ImageType.Thumb);
 62246                if (!added)
 247                {
 62248                    AddImage(files, images, "thumb", imagePrefix, isInMixedFolder, ImageType.Thumb);
 249                }
 250            }
 251
 62252            if (!isEpisode && !isSong && !isPerson)
 253            {
 62254                PopulateBackdrops(item, images, files, imagePrefix, isInMixedFolder);
 255            }
 62256        }
 257
 258        private void PopulatePrimaryImages(BaseItem item, List<LocalImageInfo> images, List<FileSystemMetadata> files, s
 259        {
 260            string[] imageFileNames;
 261
 62262            if (item is MusicAlbum || item is MusicArtist || item is PhotoAlbum)
 263            {
 264                // these prefer folder
 0265                imageFileNames = _musicImageFileNames;
 266            }
 62267            else if (item is Person)
 268            {
 269                // these prefer folder
 0270                imageFileNames = _personImageFileNames;
 271            }
 62272            else if (item is Series)
 273            {
 0274                imageFileNames = _seriesImageFileNames;
 275            }
 62276            else if (item is Video && item is not Episode)
 277            {
 0278                imageFileNames = _videoImageFileNames;
 279            }
 280            else
 281            {
 62282                imageFileNames = _commonImageFileNames;
 283            }
 284
 62285            var fileNameWithoutExtension = item.FileNameWithoutExtension;
 62286            if (!string.IsNullOrEmpty(fileNameWithoutExtension))
 287            {
 62288                if (AddImage(files, images, fileNameWithoutExtension, ImageType.Primary))
 289                {
 0290                    return;
 291                }
 292            }
 293
 620294            foreach (var name in imageFileNames)
 295            {
 248296                if (AddImage(files, images, name, ImageType.Primary, imagePrefix))
 297                {
 0298                    return;
 299                }
 300            }
 301
 62302            if (!isInMixedFolder)
 303            {
 620304                foreach (var name in imageFileNames)
 305                {
 248306                    if (AddImage(files, images, name, ImageType.Primary))
 307                    {
 0308                        return;
 309                    }
 310                }
 311            }
 62312        }
 313
 314        private void PopulateBackdrops(BaseItem item, List<LocalImageInfo> images, List<FileSystemMetadata> files, strin
 315        {
 62316            if (!string.IsNullOrEmpty(item.Path))
 317            {
 62318                var name = item.FileNameWithoutExtension;
 319
 62320                if (!string.IsNullOrEmpty(name))
 321                {
 62322                    AddImage(files, images, name + "-fanart", ImageType.Backdrop, imagePrefix);
 323
 324                    // Support without the prefix if it's in its own folder
 62325                    if (!isInMixedFolder)
 326                    {
 62327                        AddImage(files, images, name + "-fanart", ImageType.Backdrop);
 328                    }
 329                }
 330            }
 331
 62332            PopulateBackdrops(images, files, imagePrefix, "fanart", "fanart-", isInMixedFolder, ImageType.Backdrop);
 62333            PopulateBackdrops(images, files, imagePrefix, "background", "background-", isInMixedFolder, ImageType.Backdr
 62334            PopulateBackdrops(images, files, imagePrefix, "art", "art-", isInMixedFolder, ImageType.Backdrop);
 335
 62336            var extraFanartFolder = files
 62337                .FirstOrDefault(i => string.Equals(i.Name, "extrafanart", StringComparison.OrdinalIgnoreCase));
 338
 62339            if (extraFanartFolder is not null)
 340            {
 0341                PopulateBackdropsFromExtraFanart(extraFanartFolder.FullName, images);
 342            }
 343
 62344            PopulateBackdrops(images, files, imagePrefix, "backdrop", "backdrop", isInMixedFolder, ImageType.Backdrop);
 62345        }
 346
 347        private void PopulateBackdropsFromExtraFanart(string path, List<LocalImageInfo> images)
 348        {
 0349            var imageFiles = _fileSystem.GetFiles(path, BaseItem.SupportedImageExtensions, false, false);
 350
 0351            images.AddRange(imageFiles.Where(i => i.Length > 0).Select(i => new LocalImageInfo
 0352            {
 0353                FileInfo = i,
 0354                Type = ImageType.Backdrop
 0355            }));
 0356        }
 357
 358        private void PopulateBackdrops(List<LocalImageInfo> images, List<FileSystemMetadata> files, string imagePrefix, 
 359        {
 248360            AddImage(files, images, imagePrefix + firstFileName, type);
 361
 248362            var unfound = 0;
 1488363            for (var i = 1; i <= 20; i++)
 364            {
 365                // Screenshot Image
 744366                var found = AddImage(files, images, imagePrefix + subsequentFileNamePrefix + i, type);
 367
 744368                if (!found)
 369                {
 744370                    unfound++;
 371
 744372                    if (unfound >= 3)
 373                    {
 374                        break;
 375                    }
 376                }
 377            }
 378
 379            // Support without the prefix
 248380            if (!isInMixedFolder)
 381            {
 248382                AddImage(files, images, firstFileName, type);
 383
 248384                unfound = 0;
 1488385                for (var i = 1; i <= 20; i++)
 386                {
 387                    // Screenshot Image
 744388                    var found = AddImage(files, images, subsequentFileNamePrefix + i, type);
 389
 744390                    if (!found)
 391                    {
 744392                        unfound++;
 393
 744394                        if (unfound >= 3)
 395                        {
 396                            break;
 397                        }
 398                    }
 399                }
 400            }
 248401        }
 402
 403        private void PopulateSeasonImagesFromSeriesFolder(Season season, List<LocalImageInfo> images, IDirectoryService 
 404        {
 0405            var seasonNumber = season.IndexNumber;
 406
 0407            var series = season.Series;
 0408            if (!seasonNumber.HasValue || !series.IsFileProtocol)
 409            {
 0410                return;
 411            }
 412
 0413            var seriesFiles = GetFiles(series, false, directoryService).ToList();
 414
 415            // Try using the season name
 0416            var prefix = season.Name.Replace(" ", string.Empty, StringComparison.Ordinal).ToLowerInvariant();
 417
 0418            var filenamePrefixes = new List<string> { prefix };
 419
 0420            var seasonMarker = seasonNumber.Value == 0
 0421                                   ? "-specials"
 0422                                   : seasonNumber.Value.ToString("00", CultureInfo.InvariantCulture);
 423
 424            // Get this one directly from the file system since we have to go up a level
 0425            if (!string.Equals(prefix, seasonMarker, StringComparison.OrdinalIgnoreCase))
 426            {
 0427                filenamePrefixes.Add("season" + seasonMarker);
 428            }
 429
 0430            foreach (var filename in filenamePrefixes)
 431            {
 0432                AddImage(seriesFiles, images, filename + "-poster", ImageType.Primary);
 0433                AddImage(seriesFiles, images, filename + "-fanart", ImageType.Backdrop);
 0434                AddImage(seriesFiles, images, filename + "-banner", ImageType.Banner);
 0435                AddImage(seriesFiles, images, filename + "-landscape", ImageType.Thumb);
 436            }
 0437        }
 438
 439        private bool AddImage(List<FileSystemMetadata> files, List<LocalImageInfo> images, string name, string imagePref
 440        {
 372441            var added = AddImage(files, images, name, type, imagePrefix);
 442
 372443            if (!isInMixedFolder)
 444            {
 372445                if (AddImage(files, images, name, type))
 446                {
 0447                    added = true;
 448                }
 449            }
 450
 372451            return added;
 452        }
 453
 454        private static bool AddImage(IReadOnlyList<FileSystemMetadata> files, List<LocalImageInfo> images, string name, 
 455        {
 3410456            var image = GetImage(files, name, prefix);
 457
 3410458            if (image is null)
 459            {
 3410460                return false;
 461            }
 462
 0463            images.Add(new LocalImageInfo
 0464            {
 0465                FileInfo = image,
 0466                Type = type
 0467            });
 468
 0469            return true;
 470        }
 471
 472        private static FileSystemMetadata? GetImage(IReadOnlyList<FileSystemMetadata> files, string name, string? prefix
 473        {
 3410474            var fileNameLength = name.Length + (prefix?.Length ?? 0);
 9900475            for (var i = 0; i < files.Count; i++)
 476            {
 1540477                var file = files[i];
 1540478                if (file.IsDirectory || file.Length <= 0)
 479                {
 480                    continue;
 481                }
 482
 0483                var fileName = Path.GetFileNameWithoutExtension(file.FullName.AsSpan());
 0484                if (fileName.Length == fileNameLength
 0485                    && fileName.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
 0486                    && fileName.EndsWith(name, StringComparison.OrdinalIgnoreCase))
 487                {
 0488                    return file;
 489                }
 490            }
 491
 3410492            return null;
 493        }
 494    }
 495}

Methods/Properties

.cctor()
.ctor(MediaBrowser.Model.IO.IFileSystem)
get_Name()
get_Order()
Supports(MediaBrowser.Controller.Entities.BaseItem)
GetFiles(MediaBrowser.Controller.Entities.BaseItem,System.Boolean,MediaBrowser.Controller.Providers.IDirectoryService)
GetImages(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Providers.IDirectoryService)
GetImages(MediaBrowser.Controller.Entities.BaseItem,System.String,MediaBrowser.Controller.Providers.IDirectoryService)
GetImages(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.IEnumerable`1<System.String>,MediaBrowser.Controller.Providers.IDirectoryService)
PopulateImages(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.List`1<MediaBrowser.Controller.Providers.LocalImageInfo>,System.Collections.Generic.List`1<MediaBrowser.Model.IO.FileSystemMetadata>,System.Boolean,MediaBrowser.Controller.Providers.IDirectoryService)
PopulatePrimaryImages(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.List`1<MediaBrowser.Controller.Providers.LocalImageInfo>,System.Collections.Generic.List`1<MediaBrowser.Model.IO.FileSystemMetadata>,System.String,System.Boolean)
PopulateBackdrops(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.List`1<MediaBrowser.Controller.Providers.LocalImageInfo>,System.Collections.Generic.List`1<MediaBrowser.Model.IO.FileSystemMetadata>,System.String,System.Boolean)
PopulateBackdropsFromExtraFanart(System.String,System.Collections.Generic.List`1<MediaBrowser.Controller.Providers.LocalImageInfo>)
PopulateBackdrops(System.Collections.Generic.List`1<MediaBrowser.Controller.Providers.LocalImageInfo>,System.Collections.Generic.List`1<MediaBrowser.Model.IO.FileSystemMetadata>,System.String,System.String,System.String,System.Boolean,MediaBrowser.Model.Entities.ImageType)
PopulateSeasonImagesFromSeriesFolder(MediaBrowser.Controller.Entities.TV.Season,System.Collections.Generic.List`1<MediaBrowser.Controller.Providers.LocalImageInfo>,MediaBrowser.Controller.Providers.IDirectoryService)
AddImage(System.Collections.Generic.List`1<MediaBrowser.Model.IO.FileSystemMetadata>,System.Collections.Generic.List`1<MediaBrowser.Controller.Providers.LocalImageInfo>,System.String,System.String,System.Boolean,MediaBrowser.Model.Entities.ImageType)
AddImage(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Model.IO.FileSystemMetadata>,System.Collections.Generic.List`1<MediaBrowser.Controller.Providers.LocalImageInfo>,System.String,MediaBrowser.Model.Entities.ImageType,System.String)
GetImage(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Model.IO.FileSystemMetadata>,System.String,System.String)