< Summary - Jellyfin

Information
Class: Emby.Server.Implementations.Dto.DtoService
Assembly: Emby.Server.Implementations
File(s): /srv/git/jellyfin/Emby.Server.Implementations/Dto/DtoService.cs
Line coverage
42%
Covered lines: 281
Uncovered lines: 376
Coverable lines: 657
Total lines: 1441
Line coverage: 42.7%
Branch coverage
36%
Covered branches: 166
Total branches: 458
Branch coverage: 36.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

File(s)

/srv/git/jellyfin/Emby.Server.Implementations/Dto/DtoService.cs

#LineLine coverage
 1#pragma warning disable CS1591
 2
 3using System;
 4using System.Collections.Generic;
 5using System.Globalization;
 6using System.IO;
 7using System.Linq;
 8using Jellyfin.Data.Entities;
 9using Jellyfin.Data.Enums;
 10using Jellyfin.Extensions;
 11using MediaBrowser.Common;
 12using MediaBrowser.Controller.Channels;
 13using MediaBrowser.Controller.Drawing;
 14using MediaBrowser.Controller.Dto;
 15using MediaBrowser.Controller.Entities;
 16using MediaBrowser.Controller.Entities.Audio;
 17using MediaBrowser.Controller.Library;
 18using MediaBrowser.Controller.LiveTv;
 19using MediaBrowser.Controller.Persistence;
 20using MediaBrowser.Controller.Playlists;
 21using MediaBrowser.Controller.Providers;
 22using MediaBrowser.Controller.Trickplay;
 23using MediaBrowser.Model.Dto;
 24using MediaBrowser.Model.Entities;
 25using MediaBrowser.Model.Querying;
 26using Microsoft.Extensions.Logging;
 27using Book = MediaBrowser.Controller.Entities.Book;
 28using Episode = MediaBrowser.Controller.Entities.TV.Episode;
 29using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
 30using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
 31using Person = MediaBrowser.Controller.Entities.Person;
 32using Photo = MediaBrowser.Controller.Entities.Photo;
 33using Season = MediaBrowser.Controller.Entities.TV.Season;
 34using Series = MediaBrowser.Controller.Entities.TV.Series;
 35
 36namespace Emby.Server.Implementations.Dto
 37{
 38    public class DtoService : IDtoService
 39    {
 40        private readonly ILogger<DtoService> _logger;
 41        private readonly ILibraryManager _libraryManager;
 42        private readonly IUserDataManager _userDataRepository;
 43        private readonly IItemRepository _itemRepo;
 44
 45        private readonly IImageProcessor _imageProcessor;
 46        private readonly IProviderManager _providerManager;
 47        private readonly IRecordingsManager _recordingsManager;
 48
 49        private readonly IApplicationHost _appHost;
 50        private readonly IMediaSourceManager _mediaSourceManager;
 51        private readonly Lazy<ILiveTvManager> _livetvManagerFactory;
 52
 53        private readonly ITrickplayManager _trickplayManager;
 54
 55        public DtoService(
 56            ILogger<DtoService> logger,
 57            ILibraryManager libraryManager,
 58            IUserDataManager userDataRepository,
 59            IItemRepository itemRepo,
 60            IImageProcessor imageProcessor,
 61            IProviderManager providerManager,
 62            IRecordingsManager recordingsManager,
 63            IApplicationHost appHost,
 64            IMediaSourceManager mediaSourceManager,
 65            Lazy<ILiveTvManager> livetvManagerFactory,
 66            ITrickplayManager trickplayManager)
 67        {
 2268            _logger = logger;
 2269            _libraryManager = libraryManager;
 2270            _userDataRepository = userDataRepository;
 2271            _itemRepo = itemRepo;
 2272            _imageProcessor = imageProcessor;
 2273            _providerManager = providerManager;
 2274            _recordingsManager = recordingsManager;
 2275            _appHost = appHost;
 2276            _mediaSourceManager = mediaSourceManager;
 2277            _livetvManagerFactory = livetvManagerFactory;
 2278            _trickplayManager = trickplayManager;
 2279        }
 80
 081        private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
 82
 83        /// <inheritdoc />
 84        public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User? user 
 85        {
 486            var accessibleItems = user is null ? items : items.Where(x => x.IsVisible(user)).ToList();
 487            var returnItems = new BaseItemDto[accessibleItems.Count];
 488            List<(BaseItem, BaseItemDto)>? programTuples = null;
 489            List<(BaseItemDto, LiveTvChannel)>? channelTuples = null;
 90
 1491            for (int index = 0; index < accessibleItems.Count; index++)
 92            {
 393                var item = accessibleItems[index];
 394                var dto = GetBaseItemDtoInternal(item, options, user, owner);
 95
 396                if (item is LiveTvChannel tvChannel)
 97                {
 098                    (channelTuples ??= new()).Add((dto, tvChannel));
 99                }
 3100                else if (item is LiveTvProgram)
 101                {
 0102                    (programTuples ??= new()).Add((item, dto));
 103                }
 104
 3105                if (item is IItemByName byName)
 106                {
 0107                    if (options.ContainsField(ItemFields.ItemCounts))
 108                    {
 0109                        var libraryItems = byName.GetTaggedItems(new InternalItemsQuery(user)
 0110                        {
 0111                            Recursive = true,
 0112                            DtoOptions = new DtoOptions(false)
 0113                            {
 0114                                EnableImages = false
 0115                            }
 0116                        });
 117
 0118                        SetItemByNameInfo(item, dto, libraryItems);
 119                    }
 120                }
 121
 3122                returnItems[index] = dto;
 123            }
 124
 4125            if (programTuples is not null)
 126            {
 0127                LivetvManager.AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
 128            }
 129
 4130            if (channelTuples is not null)
 131            {
 0132                LivetvManager.AddChannelInfo(channelTuples, options, user);
 133            }
 134
 4135            return returnItems;
 136        }
 137
 138        public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner = null)
 139        {
 11140            var dto = GetBaseItemDtoInternal(item, options, user, owner);
 11141            if (item is LiveTvChannel tvChannel)
 142            {
 0143                LivetvManager.AddChannelInfo(new[] { (dto, tvChannel) }, options, user);
 144            }
 11145            else if (item is LiveTvProgram)
 146            {
 0147                LivetvManager.AddInfoToProgramDto(new[] { (item, dto) }, options.Fields, user).GetAwaiter().GetResult();
 148            }
 149
 11150            if (item is IItemByName itemByName
 11151                && options.ContainsField(ItemFields.ItemCounts))
 152            {
 0153                SetItemByNameInfo(
 0154                    item,
 0155                    dto,
 0156                    GetTaggedItems(
 0157                        itemByName,
 0158                        user,
 0159                        new DtoOptions(false)
 0160                        {
 0161                            EnableImages = false
 0162                        }));
 163            }
 164
 11165            return dto;
 166        }
 167
 168        private static IList<BaseItem> GetTaggedItems(IItemByName byName, User? user, DtoOptions options)
 169        {
 0170            return byName.GetTaggedItems(
 0171                new InternalItemsQuery(user)
 0172                {
 0173                    Recursive = true,
 0174                    DtoOptions = options
 0175                });
 176        }
 177
 178        private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner
 179        {
 14180            var dto = new BaseItemDto
 14181            {
 14182                ServerId = _appHost.SystemId
 14183            };
 184
 14185            if (item.SourceType == SourceType.Channel)
 186            {
 0187                dto.SourceType = item.SourceType.ToString();
 188            }
 189
 14190            if (options.ContainsField(ItemFields.People))
 191            {
 11192                AttachPeople(dto, item, user);
 193            }
 194
 14195            if (options.ContainsField(ItemFields.PrimaryImageAspectRatio))
 196            {
 197                try
 198                {
 11199                    AttachPrimaryImageAspectRatio(dto, item);
 11200                }
 0201                catch (Exception ex)
 202                {
 203                    // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
 0204                    _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {ItemName}", item.Name);
 0205                }
 206            }
 207
 14208            if (options.ContainsField(ItemFields.DisplayPreferencesId))
 209            {
 11210                dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N", CultureInfo.InvariantCulture);
 211            }
 212
 14213            if (user is not null)
 214            {
 14215                AttachUserSpecificInfo(dto, item, user, options);
 216            }
 217
 14218            if (item is IHasMediaSources
 14219                && options.ContainsField(ItemFields.MediaSources))
 220            {
 0221                dto.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray();
 222
 0223                NormalizeMediaSourceContainers(dto);
 224            }
 225
 14226            if (options.ContainsField(ItemFields.Studios))
 227            {
 11228                AttachStudios(dto, item);
 229            }
 230
 14231            AttachBasicFields(dto, item, owner, options);
 232
 14233            if (options.ContainsField(ItemFields.CanDelete))
 234            {
 11235                dto.CanDelete = user is null
 11236                    ? item.CanDelete()
 11237                    : item.CanDelete(user);
 238            }
 239
 14240            if (options.ContainsField(ItemFields.CanDownload))
 241            {
 11242                dto.CanDownload = user is null
 11243                    ? item.CanDownload()
 11244                    : item.CanDownload(user);
 245            }
 246
 14247            if (options.ContainsField(ItemFields.Etag))
 248            {
 11249                dto.Etag = item.GetEtag(user);
 250            }
 251
 14252            var activeRecording = _recordingsManager.GetActiveRecordingInfo(item.Path);
 14253            if (activeRecording is not null)
 254            {
 0255                dto.Type = BaseItemKind.Recording;
 0256                dto.CanDownload = false;
 0257                dto.RunTimeTicks = null;
 258
 0259                if (!string.IsNullOrEmpty(dto.SeriesName))
 260                {
 0261                    dto.EpisodeTitle = dto.Name;
 0262                    dto.Name = dto.SeriesName;
 263                }
 264
 0265                LivetvManager.AddInfoToRecordingDto(item, dto, activeRecording, user);
 266            }
 267
 14268            if (item is Audio audio)
 269            {
 0270                dto.HasLyrics = audio.GetMediaStreams().Any(s => s.Type == MediaStreamType.Lyric);
 271            }
 272
 14273            return dto;
 274        }
 275
 276        private static void NormalizeMediaSourceContainers(BaseItemDto dto)
 277        {
 0278            foreach (var mediaSource in dto.MediaSources)
 279            {
 0280                var container = mediaSource.Container;
 0281                if (string.IsNullOrEmpty(container))
 282                {
 283                    continue;
 284                }
 285
 0286                var containers = container.Split(',');
 0287                if (containers.Length < 2)
 288                {
 289                    continue;
 290                }
 291
 0292                var path = mediaSource.Path;
 0293                string? fileExtensionContainer = null;
 294
 0295                if (!string.IsNullOrEmpty(path))
 296                {
 0297                    path = Path.GetExtension(path);
 0298                    if (!string.IsNullOrEmpty(path))
 299                    {
 0300                        path = Path.GetExtension(path);
 0301                        if (!string.IsNullOrEmpty(path))
 302                        {
 0303                            path = path.TrimStart('.');
 304                        }
 305
 0306                        if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparison.OrdinalIgnoreCase)
 307                        {
 0308                            fileExtensionContainer = path;
 309                        }
 310                    }
 311                }
 312
 0313                mediaSource.Container = fileExtensionContainer ?? containers[0];
 314            }
 0315        }
 316
 317        /// <inheritdoc />
 318        public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem>? taggedItems, User? user =
 319        {
 0320            var dto = GetBaseItemDtoInternal(item, options, user);
 321
 0322            if (taggedItems is not null && options.ContainsField(ItemFields.ItemCounts))
 323            {
 0324                SetItemByNameInfo(item, dto, taggedItems);
 325            }
 326
 0327            return dto;
 328        }
 329
 330        private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList<BaseItem> taggedItems)
 331        {
 0332            if (item is MusicArtist)
 333            {
 0334                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0335                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0336                dto.SongCount = taggedItems.Count(i => i is Audio);
 337            }
 0338            else if (item is MusicGenre)
 339            {
 0340                dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
 0341                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0342                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0343                dto.SongCount = taggedItems.Count(i => i is Audio);
 344            }
 345            else
 346            {
 347                // This populates them all and covers Genre, Person, Studio, Year
 348
 0349                dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
 0350                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0351                dto.EpisodeCount = taggedItems.Count(i => i is Episode);
 0352                dto.MovieCount = taggedItems.Count(i => i is Movie);
 0353                dto.TrailerCount = taggedItems.Count(i => i is Trailer);
 0354                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0355                dto.SeriesCount = taggedItems.Count(i => i is Series);
 0356                dto.ProgramCount = taggedItems.Count(i => i is LiveTvProgram);
 0357                dto.SongCount = taggedItems.Count(i => i is Audio);
 358            }
 359
 0360            dto.ChildCount = taggedItems.Count;
 0361        }
 362
 363        /// <summary>
 364        /// Attaches the user specific info.
 365        /// </summary>
 366        private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions options)
 367        {
 14368            if (item.IsFolder)
 369            {
 14370                var folder = (Folder)item;
 371
 14372                if (options.EnableUserData)
 373                {
 14374                    dto.UserData = _userDataRepository.GetUserDataDto(item, dto, user, options);
 375                }
 376
 14377                if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library)
 378                {
 379                    // For these types we can try to optimize and assume these values will be equal
 14380                    if (item is MusicAlbum || item is Season || item is Playlist)
 381                    {
 0382                        dto.ChildCount = dto.RecursiveItemCount;
 0383                        var folderChildCount = folder.LinkedChildren.Length;
 384                        // The default is an empty array, so we can't reliably use the count when it's empty
 0385                        if (folderChildCount > 0)
 386                        {
 0387                            dto.ChildCount ??= folderChildCount;
 388                        }
 389                    }
 390
 14391                    if (options.ContainsField(ItemFields.ChildCount))
 392                    {
 11393                        dto.ChildCount ??= GetChildCount(folder, user);
 394                    }
 395                }
 396
 14397                if (options.ContainsField(ItemFields.CumulativeRunTimeTicks))
 398                {
 11399                    dto.CumulativeRunTimeTicks = item.RunTimeTicks;
 400                }
 401
 14402                if (options.ContainsField(ItemFields.DateLastMediaAdded))
 403                {
 11404                    dto.DateLastMediaAdded = folder.DateLastMediaAdded;
 405                }
 406            }
 407            else
 408            {
 0409                if (options.EnableUserData)
 410                {
 0411                    dto.UserData = _userDataRepository.GetUserDataDto(item, user);
 412                }
 413            }
 414
 14415            if (options.ContainsField(ItemFields.PlayAccess))
 416            {
 11417                dto.PlayAccess = item.GetPlayAccess(user);
 418            }
 14419        }
 420
 421        private static int GetChildCount(Folder folder, User user)
 422        {
 423            // Right now this is too slow to calculate for top level folders on a per-user basis
 424            // Just return something so that apps that are expecting a value won't think the folders are empty
 11425            if (folder is ICollectionFolder || folder is UserView)
 426            {
 0427                return Random.Shared.Next(1, 10);
 428            }
 429
 11430            return folder.GetChildCount(user);
 431        }
 432
 433        private static void SetBookProperties(BaseItemDto dto, Book item)
 434        {
 0435            dto.SeriesName = item.SeriesName;
 0436        }
 437
 438        private static void SetPhotoProperties(BaseItemDto dto, Photo item)
 439        {
 0440            dto.CameraMake = item.CameraMake;
 0441            dto.CameraModel = item.CameraModel;
 0442            dto.Software = item.Software;
 0443            dto.ExposureTime = item.ExposureTime;
 0444            dto.FocalLength = item.FocalLength;
 0445            dto.ImageOrientation = item.Orientation;
 0446            dto.Aperture = item.Aperture;
 0447            dto.ShutterSpeed = item.ShutterSpeed;
 448
 0449            dto.Latitude = item.Latitude;
 0450            dto.Longitude = item.Longitude;
 0451            dto.Altitude = item.Altitude;
 0452            dto.IsoSpeedRating = item.IsoSpeedRating;
 453
 0454            var album = item.AlbumEntity;
 455
 0456            if (album is not null)
 457            {
 0458                dto.Album = album.Name;
 0459                dto.AlbumId = album.Id;
 460            }
 0461        }
 462
 463        private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item)
 464        {
 0465            if (!string.IsNullOrEmpty(item.Album))
 466            {
 0467                var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery
 0468                {
 0469                    IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
 0470                    Name = item.Album,
 0471                    Limit = 1
 0472                });
 473
 0474                if (parentAlbumIds.Count > 0)
 475                {
 0476                    dto.AlbumId = parentAlbumIds[0];
 477                }
 478            }
 479
 0480            dto.Album = item.Album;
 0481        }
 482
 483        private string[] GetImageTags(BaseItem item, List<ItemImageInfo> images)
 484        {
 14485            return images
 14486                .Select(p => GetImageCacheTag(item, p))
 14487                .Where(i => i is not null)
 14488                .ToArray()!; // null values got filtered out
 489        }
 490
 491        private string? GetImageCacheTag(BaseItem item, ItemImageInfo image)
 492        {
 493            try
 494            {
 0495                return _imageProcessor.GetImageCacheTag(item, image);
 496            }
 0497            catch (Exception ex)
 498            {
 0499                _logger.LogError(ex, "Error getting {ImageType} image info for {Path}", image.Type, image.Path);
 0500                return null;
 501            }
 0502        }
 503
 504        /// <summary>
 505        /// Attaches People DTO's to a DTOBaseItem.
 506        /// </summary>
 507        /// <param name="dto">The dto.</param>
 508        /// <param name="item">The item.</param>
 509        /// <param name="user">The requesting user.</param>
 510        private void AttachPeople(BaseItemDto dto, BaseItem item, User? user = null)
 511        {
 512            // Ordering by person type to ensure actors and artists are at the front.
 513            // This is taking advantage of the fact that they both begin with A
 514            // This should be improved in the future
 11515            var people = _libraryManager.GetPeople(item).OrderBy(i => i.SortOrder ?? int.MaxValue)
 11516                .ThenBy(i =>
 11517                {
 11518                    if (i.IsType(PersonKind.Actor))
 11519                    {
 11520                        return 0;
 11521                    }
 11522
 11523                    if (i.IsType(PersonKind.GuestStar))
 11524                    {
 11525                        return 1;
 11526                    }
 11527
 11528                    if (i.IsType(PersonKind.Director))
 11529                    {
 11530                        return 2;
 11531                    }
 11532
 11533                    if (i.IsType(PersonKind.Writer))
 11534                    {
 11535                        return 3;
 11536                    }
 11537
 11538                    if (i.IsType(PersonKind.Producer))
 11539                    {
 11540                        return 4;
 11541                    }
 11542
 11543                    if (i.IsType(PersonKind.Composer))
 11544                    {
 11545                        return 4;
 11546                    }
 11547
 11548                    return 10;
 11549                })
 11550                .ToList();
 551
 11552            var list = new List<BaseItemPerson>();
 553
 11554            Dictionary<string, Person> dictionary = people.Select(p => p.Name)
 11555                .Distinct(StringComparer.OrdinalIgnoreCase).Select(c =>
 11556                {
 11557                    try
 11558                    {
 11559                        return _libraryManager.GetPerson(c);
 11560                    }
 11561                    catch (Exception ex)
 11562                    {
 11563                        _logger.LogError(ex, "Error getting person {Name}", c);
 11564                        return null;
 11565                    }
 11566                }).Where(i => i is not null)
 11567                .Where(i => user is null || i!.IsVisible(user))
 11568                .DistinctBy(x => x!.Name, StringComparer.OrdinalIgnoreCase)
 11569                .ToDictionary(i => i!.Name, StringComparer.OrdinalIgnoreCase)!; // null values got filtered out
 570
 22571            for (var i = 0; i < people.Count; i++)
 572            {
 0573                var person = people[i];
 574
 0575                var baseItemPerson = new BaseItemPerson
 0576                {
 0577                    Name = person.Name,
 0578                    Role = person.Role,
 0579                    Type = person.Type
 0580                };
 581
 0582                if (dictionary.TryGetValue(person.Name, out Person? entity))
 583                {
 0584                    baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
 0585                    baseItemPerson.Id = entity.Id;
 0586                    if (dto.ImageBlurHashes is not null)
 587                    {
 588                        // Only add BlurHash for the person's image.
 0589                        baseItemPerson.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
 0590                        foreach (var (imageType, blurHash) in dto.ImageBlurHashes)
 591                        {
 0592                            if (blurHash is not null)
 593                            {
 0594                                baseItemPerson.ImageBlurHashes[imageType] = new Dictionary<string, string>();
 0595                                foreach (var (imageId, blurHashValue) in blurHash)
 596                                {
 0597                                    if (string.Equals(baseItemPerson.PrimaryImageTag, imageId, StringComparison.OrdinalI
 598                                    {
 0599                                        baseItemPerson.ImageBlurHashes[imageType][imageId] = blurHashValue;
 600                                    }
 601                                }
 602                            }
 603                        }
 604                    }
 605
 0606                    list.Add(baseItemPerson);
 607                }
 608            }
 609
 11610            dto.People = list.ToArray();
 11611        }
 612
 613        /// <summary>
 614        /// Attaches the studios.
 615        /// </summary>
 616        /// <param name="dto">The dto.</param>
 617        /// <param name="item">The item.</param>
 618        private void AttachStudios(BaseItemDto dto, BaseItem item)
 619        {
 11620            dto.Studios = item.Studios
 11621                .Where(i => !string.IsNullOrEmpty(i))
 11622                .Select(i => new NameGuidPair
 11623                {
 11624                    Name = i,
 11625                    Id = _libraryManager.GetStudioId(i)
 11626                })
 11627                .ToArray();
 11628        }
 629
 630        private void AttachGenreItems(BaseItemDto dto, BaseItem item)
 631        {
 11632            dto.GenreItems = item.Genres
 11633                .Where(i => !string.IsNullOrEmpty(i))
 11634                .Select(i => new NameGuidPair
 11635                {
 11636                    Name = i,
 11637                    Id = GetGenreId(i, item)
 11638                })
 11639                .ToArray();
 11640        }
 641
 642        private Guid GetGenreId(string name, BaseItem owner)
 643        {
 0644            if (owner is IHasMusicGenres)
 645            {
 0646                return _libraryManager.GetMusicGenreId(name);
 647            }
 648
 0649            return _libraryManager.GetGenreId(name);
 650        }
 651
 652        private string? GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ImageType imageType, int imageIndex = 0)
 653        {
 0654            var image = item.GetImageInfo(imageType, imageIndex);
 0655            if (image is not null)
 656            {
 0657                return GetTagAndFillBlurhash(dto, item, image);
 658            }
 659
 0660            return null;
 661        }
 662
 663        private string? GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ItemImageInfo image)
 664        {
 0665            var tag = GetImageCacheTag(item, image);
 0666            if (tag is null)
 667            {
 0668                return null;
 669            }
 670
 0671            if (!string.IsNullOrEmpty(image.BlurHash))
 672            {
 0673                dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>();
 674
 0675                if (!dto.ImageBlurHashes.TryGetValue(image.Type, out var value))
 676                {
 0677                    value = new Dictionary<string, string>();
 0678                    dto.ImageBlurHashes[image.Type] = value;
 679                }
 680
 0681                value[tag] = image.BlurHash;
 682            }
 683
 0684            return tag;
 685        }
 686
 687        private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, int limit)
 688        {
 14689            return GetTagsAndFillBlurhashes(dto, item, imageType, item.GetImages(imageType).Take(limit).ToList());
 690        }
 691
 692        private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, List<ItemImageInf
 693        {
 14694            var tags = GetImageTags(item, images);
 14695            var hashes = new Dictionary<string, string>();
 28696            for (int i = 0; i < images.Count; i++)
 697            {
 0698                var img = images[i];
 0699                if (!string.IsNullOrEmpty(img.BlurHash))
 700                {
 0701                    var tag = tags[i];
 0702                    hashes[tag] = img.BlurHash;
 703                }
 704            }
 705
 14706            if (hashes.Count > 0)
 707            {
 0708                dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>();
 709
 0710                dto.ImageBlurHashes[imageType] = hashes;
 711            }
 712
 14713            return tags;
 714        }
 715
 716        /// <summary>
 717        /// Sets simple property values on a DTOBaseItem.
 718        /// </summary>
 719        /// <param name="dto">The dto.</param>
 720        /// <param name="item">The item.</param>
 721        /// <param name="owner">The owner.</param>
 722        /// <param name="options">The options.</param>
 723        private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem? owner, DtoOptions options)
 724        {
 14725            if (options.ContainsField(ItemFields.DateCreated))
 726            {
 11727                dto.DateCreated = item.DateCreated;
 728            }
 729
 14730            if (options.ContainsField(ItemFields.Settings))
 731            {
 11732                dto.LockedFields = item.LockedFields;
 11733                dto.LockData = item.IsLocked;
 11734                dto.ForcedSortName = item.ForcedSortName;
 735            }
 736
 14737            dto.Container = item.Container;
 14738            dto.EndDate = item.EndDate;
 739
 14740            if (options.ContainsField(ItemFields.ExternalUrls))
 741            {
 11742                dto.ExternalUrls = _providerManager.GetExternalUrls(item).ToArray();
 743            }
 744
 14745            if (options.ContainsField(ItemFields.Tags))
 746            {
 11747                dto.Tags = item.Tags;
 748            }
 749
 14750            if (item is IHasAspectRatio hasAspectRatio)
 751            {
 0752                dto.AspectRatio = hasAspectRatio.AspectRatio;
 753            }
 754
 14755            dto.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
 756
 14757            var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
 14758            if (backdropLimit > 0)
 759            {
 14760                dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit);
 761            }
 762
 14763            if (options.ContainsField(ItemFields.Genres))
 764            {
 11765                dto.Genres = item.Genres;
 11766                AttachGenreItems(dto, item);
 767            }
 768
 14769            if (options.EnableImages)
 770            {
 14771                dto.ImageTags = new Dictionary<ImageType, string>();
 772
 773                // Prevent implicitly captured closure
 14774                var currentItem = item;
 28775                foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)))
 776                {
 0777                    if (options.GetImageLimit(image.Type) > 0)
 778                    {
 0779                        var tag = GetTagAndFillBlurhash(dto, item, image);
 780
 0781                        if (tag is not null)
 782                        {
 0783                            dto.ImageTags[image.Type] = tag;
 784                        }
 785                    }
 786                }
 787            }
 788
 14789            dto.Id = item.Id;
 14790            dto.IndexNumber = item.IndexNumber;
 14791            dto.ParentIndexNumber = item.ParentIndexNumber;
 792
 14793            if (item.IsFolder)
 794            {
 14795                dto.IsFolder = true;
 796            }
 0797            else if (item is IHasMediaSources)
 798            {
 0799                dto.IsFolder = false;
 800            }
 801
 14802            dto.MediaType = item.MediaType;
 803
 14804            if (item is not LiveTvProgram)
 805            {
 14806                dto.LocationType = item.LocationType;
 807            }
 808
 14809            dto.Audio = item.Audio;
 810
 14811            if (options.ContainsField(ItemFields.Settings))
 812            {
 11813                dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
 11814                dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
 815            }
 816
 14817            dto.CriticRating = item.CriticRating;
 818
 14819            if (item is IHasDisplayOrder hasDisplayOrder)
 820            {
 0821                dto.DisplayOrder = hasDisplayOrder.DisplayOrder;
 822            }
 823
 14824            if (item is IHasCollectionType hasCollectionType)
 825            {
 3826                dto.CollectionType = hasCollectionType.CollectionType;
 827            }
 828
 14829            if (options.ContainsField(ItemFields.RemoteTrailers))
 830            {
 11831                dto.RemoteTrailers = item.RemoteTrailers;
 832            }
 833
 14834            dto.Name = item.Name;
 14835            dto.OfficialRating = item.OfficialRating;
 836
 14837            if (options.ContainsField(ItemFields.Overview))
 838            {
 11839                dto.Overview = item.Overview;
 840            }
 841
 14842            if (options.ContainsField(ItemFields.OriginalTitle))
 843            {
 11844                dto.OriginalTitle = item.OriginalTitle;
 845            }
 846
 14847            if (options.ContainsField(ItemFields.ParentId))
 848            {
 11849                dto.ParentId = item.DisplayParentId;
 850            }
 851
 14852            AddInheritedImages(dto, item, options, owner);
 853
 14854            if (options.ContainsField(ItemFields.Path))
 855            {
 11856                dto.Path = GetMappedPath(item, owner);
 857            }
 858
 14859            if (options.ContainsField(ItemFields.EnableMediaSourceDisplay))
 860            {
 11861                dto.EnableMediaSourceDisplay = item.EnableMediaSourceDisplay;
 862            }
 863
 14864            dto.PremiereDate = item.PremiereDate;
 14865            dto.ProductionYear = item.ProductionYear;
 866
 14867            if (options.ContainsField(ItemFields.ProviderIds))
 868            {
 11869                dto.ProviderIds = item.ProviderIds;
 870            }
 871
 14872            dto.RunTimeTicks = item.RunTimeTicks;
 873
 14874            if (options.ContainsField(ItemFields.SortName))
 875            {
 11876                dto.SortName = item.SortName;
 877            }
 878
 14879            if (options.ContainsField(ItemFields.CustomRating))
 880            {
 11881                dto.CustomRating = item.CustomRating;
 882            }
 883
 14884            if (options.ContainsField(ItemFields.Taglines))
 885            {
 11886                if (!string.IsNullOrEmpty(item.Tagline))
 887                {
 0888                    dto.Taglines = new string[] { item.Tagline };
 889                }
 890
 11891                dto.Taglines ??= Array.Empty<string>();
 892            }
 893
 14894            dto.Type = item.GetBaseItemKind();
 14895            if ((item.CommunityRating ?? 0) > 0)
 896            {
 0897                dto.CommunityRating = item.CommunityRating;
 898            }
 899
 14900            if (item is ISupportsPlaceHolders supportsPlaceHolders && supportsPlaceHolders.IsPlaceHolder)
 901            {
 0902                dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder;
 903            }
 904
 14905            if (item.LUFS.HasValue)
 906            {
 907                // -18 LUFS reference, same as ReplayGain 2.0, compatible with ReplayGain 1.0
 0908                dto.NormalizationGain = -18f - item.LUFS;
 909            }
 14910            else if (item.NormalizationGain.HasValue)
 911            {
 0912                dto.NormalizationGain = item.NormalizationGain;
 913            }
 914
 915            // Add audio info
 14916            if (item is Audio audio)
 917            {
 0918                dto.Album = audio.Album;
 0919                dto.ExtraType = audio.ExtraType;
 920
 0921                var albumParent = audio.AlbumEntity;
 922
 0923                if (albumParent is not null)
 924                {
 0925                    dto.AlbumId = albumParent.Id;
 0926                    dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary);
 927                }
 928
 929                // if (options.ContainsField(ItemFields.MediaSourceCount))
 930                // {
 931                // Songs always have one
 932                // }
 933            }
 934
 14935            if (item is IHasArtist hasArtist)
 936            {
 0937                dto.Artists = hasArtist.Artists;
 938
 939                // var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
 940                // {
 941                //    EnableTotalRecordCount = false,
 942                //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
 943                // });
 944
 945                // dto.ArtistItems = artistItems.Items
 946                //    .Select(i =>
 947                //    {
 948                //        var artist = i.Item1;
 949                //        return new NameIdPair
 950                //        {
 951                //            Name = artist.Name,
 952                //            Id = artist.Id.ToString("N", CultureInfo.InvariantCulture)
 953                //        };
 954                //    })
 955                //    .ToList();
 956
 957                // Include artists that are not in the database yet, e.g., just added via metadata editor
 958                // var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
 0959                dto.ArtistItems = hasArtist.Artists
 0960                    // .Except(foundArtists, new DistinctNameComparer())
 0961                    .Select(i =>
 0962                    {
 0963                        // This should not be necessary but we're seeing some cases of it
 0964                        if (string.IsNullOrEmpty(i))
 0965                        {
 0966                            return null;
 0967                        }
 0968
 0969                        var artist = _libraryManager.GetArtist(i, new DtoOptions(false)
 0970                        {
 0971                            EnableImages = false
 0972                        });
 0973                        if (artist is not null)
 0974                        {
 0975                            return new NameGuidPair
 0976                            {
 0977                                Name = artist.Name,
 0978                                Id = artist.Id
 0979                            };
 0980                        }
 0981
 0982                        return null;
 0983                    }).Where(i => i is not null).ToArray();
 984            }
 985
 14986            if (item is IHasAlbumArtist hasAlbumArtist)
 987            {
 0988                dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
 989
 990                // var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
 991                // {
 992                //    EnableTotalRecordCount = false,
 993                //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
 994                // });
 995
 996                // dto.AlbumArtists = artistItems.Items
 997                //    .Select(i =>
 998                //    {
 999                //        var artist = i.Item1;
 1000                //        return new NameIdPair
 1001                //        {
 1002                //            Name = artist.Name,
 1003                //            Id = artist.Id.ToString("N", CultureInfo.InvariantCulture)
 1004                //        };
 1005                //    })
 1006                //    .ToList();
 1007
 01008                dto.AlbumArtists = hasAlbumArtist.AlbumArtists
 01009                    // .Except(foundArtists, new DistinctNameComparer())
 01010                    .Select(i =>
 01011                    {
 01012                        // This should not be necessary but we're seeing some cases of it
 01013                        if (string.IsNullOrEmpty(i))
 01014                        {
 01015                            return null;
 01016                        }
 01017
 01018                        var artist = _libraryManager.GetArtist(i, new DtoOptions(false)
 01019                        {
 01020                            EnableImages = false
 01021                        });
 01022                        if (artist is not null)
 01023                        {
 01024                            return new NameGuidPair
 01025                            {
 01026                                Name = artist.Name,
 01027                                Id = artist.Id
 01028                            };
 01029                        }
 01030
 01031                        return null;
 01032                    }).Where(i => i is not null).ToArray();
 1033            }
 1034
 1035            // Add video info
 141036            if (item is Video video)
 1037            {
 01038                dto.VideoType = video.VideoType;
 01039                dto.Video3DFormat = video.Video3DFormat;
 01040                dto.IsoType = video.IsoType;
 1041
 01042                if (video.HasSubtitles)
 1043                {
 01044                    dto.HasSubtitles = video.HasSubtitles;
 1045                }
 1046
 01047                if (video.AdditionalParts.Length != 0)
 1048                {
 01049                    dto.PartCount = video.AdditionalParts.Length + 1;
 1050                }
 1051
 01052                if (options.ContainsField(ItemFields.MediaSourceCount))
 1053                {
 01054                    var mediaSourceCount = video.MediaSourceCount;
 01055                    if (mediaSourceCount != 1)
 1056                    {
 01057                        dto.MediaSourceCount = mediaSourceCount;
 1058                    }
 1059                }
 1060
 01061                if (options.ContainsField(ItemFields.Chapters))
 1062                {
 01063                    dto.Chapters = _itemRepo.GetChapters(item);
 1064                }
 1065
 01066                if (options.ContainsField(ItemFields.Trickplay))
 1067                {
 01068                    dto.Trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult();
 1069                }
 1070
 01071                dto.ExtraType = video.ExtraType;
 1072            }
 1073
 141074            if (options.ContainsField(ItemFields.MediaStreams))
 1075            {
 1076                // Add VideoInfo
 111077                if (item is IHasMediaSources)
 1078                {
 1079                    MediaStream[] mediaStreams;
 1080
 01081                    if (dto.MediaSources is not null && dto.MediaSources.Length > 0)
 1082                    {
 01083                        if (item.SourceType == SourceType.Channel)
 1084                        {
 01085                            mediaStreams = dto.MediaSources[0].MediaStreams.ToArray();
 1086                        }
 1087                        else
 1088                        {
 01089                            string id = item.Id.ToString("N", CultureInfo.InvariantCulture);
 01090                            mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, id, StringComparison.OrdinalI
 01091                                .SelectMany(i => i.MediaStreams)
 01092                                .ToArray();
 1093                        }
 1094                    }
 1095                    else
 1096                    {
 01097                        mediaStreams = _mediaSourceManager.GetStaticMediaSources(item, true)[0].MediaStreams.ToArray();
 1098                    }
 1099
 01100                    dto.MediaStreams = mediaStreams;
 1101                }
 1102            }
 1103
 141104            BaseItem[]? allExtras = null;
 1105
 141106            if (options.ContainsField(ItemFields.SpecialFeatureCount))
 1107            {
 111108                allExtras = item.GetExtras().ToArray();
 111109                dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contai
 1110            }
 1111
 141112            if (options.ContainsField(ItemFields.LocalTrailerCount))
 1113            {
 111114                if (item is IHasTrailers hasTrailers)
 1115                {
 01116                    dto.LocalTrailerCount = hasTrailers.LocalTrailers.Count;
 1117                }
 1118                else
 1119                {
 111120                    dto.LocalTrailerCount = (allExtras ?? item.GetExtras()).Count(i => i.ExtraType == ExtraType.Trailer)
 1121                }
 1122            }
 1123
 1124            // Add EpisodeInfo
 141125            if (item is Episode episode)
 1126            {
 01127                dto.IndexNumberEnd = episode.IndexNumberEnd;
 01128                dto.SeriesName = episode.SeriesName;
 1129
 01130                if (options.ContainsField(ItemFields.SpecialEpisodeNumbers))
 1131                {
 01132                    dto.AirsAfterSeasonNumber = episode.AirsAfterSeasonNumber;
 01133                    dto.AirsBeforeEpisodeNumber = episode.AirsBeforeEpisodeNumber;
 01134                    dto.AirsBeforeSeasonNumber = episode.AirsBeforeSeasonNumber;
 1135                }
 1136
 01137                dto.SeasonName = episode.SeasonName;
 01138                dto.SeasonId = episode.SeasonId;
 01139                dto.SeriesId = episode.SeriesId;
 1140
 01141                Series? episodeSeries = null;
 1142
 1143                // this block will add the series poster for episodes without a poster
 1144                // TODO maybe remove the if statement entirely
 1145                // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
 1146                {
 01147                    episodeSeries ??= episode.Series;
 01148                    if (episodeSeries is not null)
 1149                    {
 01150                        dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
 01151                        if (dto.ImageTags is null || !dto.ImageTags.ContainsKey(ImageType.Primary))
 1152                        {
 01153                            AttachPrimaryImageAspectRatio(dto, episodeSeries);
 1154                        }
 1155                    }
 1156                }
 1157
 01158                if (options.ContainsField(ItemFields.SeriesStudio))
 1159                {
 01160                    episodeSeries ??= episode.Series;
 01161                    if (episodeSeries is not null)
 1162                    {
 01163                        dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
 1164                    }
 1165                }
 1166            }
 1167
 1168            // Add SeriesInfo
 1169            Series? series;
 141170            if (item is Series tmp)
 1171            {
 01172                series = tmp;
 01173                dto.AirDays = series.AirDays;
 01174                dto.AirTime = series.AirTime;
 01175                dto.Status = series.Status?.ToString();
 1176            }
 1177
 1178            // Add SeasonInfo
 141179            if (item is Season season)
 1180            {
 01181                dto.SeriesName = season.SeriesName;
 01182                dto.SeriesId = season.SeriesId;
 1183
 01184                series = null;
 1185
 01186                if (options.ContainsField(ItemFields.SeriesStudio))
 1187                {
 01188                    series ??= season.Series;
 01189                    if (series is not null)
 1190                    {
 01191                        dto.SeriesStudio = series.Studios.FirstOrDefault();
 1192                    }
 1193                }
 1194
 1195                // this block will add the series poster for seasons without a poster
 1196                // TODO maybe remove the if statement entirely
 1197                // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
 1198                {
 01199                    series ??= season.Series;
 01200                    if (series is not null)
 1201                    {
 01202                        dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
 01203                        if (dto.ImageTags is null || !dto.ImageTags.ContainsKey(ImageType.Primary))
 1204                        {
 01205                            AttachPrimaryImageAspectRatio(dto, series);
 1206                        }
 1207                    }
 1208                }
 1209            }
 1210
 141211            if (item is MusicVideo musicVideo)
 1212            {
 01213                SetMusicVideoProperties(dto, musicVideo);
 1214            }
 1215
 141216            if (item is Book book)
 1217            {
 01218                SetBookProperties(dto, book);
 1219            }
 1220
 141221            if (options.ContainsField(ItemFields.ProductionLocations))
 1222            {
 111223                if (item.ProductionLocations.Length > 0 || item is Movie)
 1224                {
 01225                    dto.ProductionLocations = item.ProductionLocations;
 1226                }
 1227            }
 1228
 141229            if (options.ContainsField(ItemFields.Width))
 1230            {
 111231                var width = item.Width;
 111232                if (width > 0)
 1233                {
 01234                    dto.Width = width;
 1235                }
 1236            }
 1237
 141238            if (options.ContainsField(ItemFields.Height))
 1239            {
 111240                var height = item.Height;
 111241                if (height > 0)
 1242                {
 01243                    dto.Height = height;
 1244                }
 1245            }
 1246
 141247            if (options.ContainsField(ItemFields.IsHD))
 1248            {
 1249                // Compatibility
 111250                if (item.IsHD)
 1251                {
 01252                    dto.IsHD = true;
 1253                }
 1254            }
 1255
 141256            if (item is Photo photo)
 1257            {
 01258                SetPhotoProperties(dto, photo);
 1259            }
 1260
 141261            dto.ChannelId = item.ChannelId;
 1262
 141263            if (item.SourceType == SourceType.Channel)
 1264            {
 01265                var channel = _libraryManager.GetItemById(item.ChannelId);
 01266                if (channel is not null)
 1267                {
 01268                    dto.ChannelName = channel.Name;
 1269                }
 1270            }
 141271        }
 1272
 1273        private BaseItem? GetImageDisplayParent(BaseItem currentItem, BaseItem originalItem)
 1274        {
 01275            if (currentItem is MusicAlbum musicAlbum)
 1276            {
 01277                var artist = musicAlbum.GetMusicArtist(new DtoOptions(false));
 01278                if (artist is not null)
 1279                {
 01280                    return artist;
 1281                }
 1282            }
 1283
 01284            var parent = currentItem.DisplayParent ?? currentItem.GetOwner() ?? currentItem.GetParent();
 1285
 01286            if (parent is null && originalItem is not UserRootFolder && originalItem is not UserView && originalItem is 
 1287            {
 01288                parent = _libraryManager.GetCollectionFolders(originalItem).FirstOrDefault();
 1289            }
 1290
 01291            return parent;
 1292        }
 1293
 1294        private void AddInheritedImages(BaseItemDto dto, BaseItem item, DtoOptions options, BaseItem? owner)
 1295        {
 141296            if (!item.SupportsInheritedParentImages)
 1297            {
 141298                return;
 1299            }
 1300
 01301            var logoLimit = options.GetImageLimit(ImageType.Logo);
 01302            var artLimit = options.GetImageLimit(ImageType.Art);
 01303            var thumbLimit = options.GetImageLimit(ImageType.Thumb);
 01304            var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
 1305
 1306            // For now. Emby apps are not using this
 01307            artLimit = 0;
 1308
 01309            if (logoLimit == 0 && artLimit == 0 && thumbLimit == 0 && backdropLimit == 0)
 1310            {
 01311                return;
 1312            }
 1313
 01314            BaseItem? parent = null;
 01315            var isFirst = true;
 1316
 01317            var imageTags = dto.ImageTags;
 1318
 01319            while ((!(imageTags is not null && imageTags.ContainsKey(ImageType.Logo)) && logoLimit > 0)
 01320                || (!(imageTags is not null && imageTags.ContainsKey(ImageType.Art)) && artLimit > 0)
 01321                || (!(imageTags is not null && imageTags.ContainsKey(ImageType.Thumb)) && thumbLimit > 0)
 01322                || parent is Series)
 1323            {
 01324                parent ??= isFirst ? GetImageDisplayParent(item, item) ?? owner : parent;
 01325                if (parent is null)
 1326                {
 1327                    break;
 1328                }
 1329
 01330                var allImages = parent.ImageInfos;
 1331
 01332                if (logoLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Logo)) && dto.ParentLogo
 1333                {
 01334                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Logo);
 1335
 01336                    if (image is not null)
 1337                    {
 01338                        dto.ParentLogoItemId = parent.Id;
 01339                        dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1340                    }
 1341                }
 1342
 01343                if (artLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtIte
 1344                {
 01345                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
 1346
 01347                    if (image is not null)
 1348                    {
 01349                        dto.ParentArtItemId = parent.Id;
 01350                        dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1351                    }
 1352                }
 1353
 01354                if (thumbLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentT
 1355                {
 01356                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
 1357
 01358                    if (image is not null)
 1359                    {
 01360                        dto.ParentThumbItemId = parent.Id;
 01361                        dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1362                    }
 1363                }
 1364
 01365                if (backdropLimit > 0 && !((dto.BackdropImageTags is not null && dto.BackdropImageTags.Length > 0) || (d
 1366                {
 01367                    var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
 1368
 01369                    if (images.Count > 0)
 1370                    {
 01371                        dto.ParentBackdropItemId = parent.Id;
 01372                        dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images);
 1373                    }
 1374                }
 1375
 01376                isFirst = false;
 1377
 01378                if (!parent.SupportsInheritedParentImages)
 1379                {
 1380                    break;
 1381                }
 1382
 01383                parent = GetImageDisplayParent(parent, item);
 1384            }
 01385        }
 1386
 1387        private string GetMappedPath(BaseItem item, BaseItem? ownerItem)
 1388        {
 111389            var path = item.Path;
 1390
 111391            if (item.IsFileProtocol)
 1392            {
 111393                path = _libraryManager.GetPathAfterNetworkSubstitution(path, ownerItem ?? item);
 1394            }
 1395
 111396            return path;
 1397        }
 1398
 1399        /// <summary>
 1400        /// Attaches the primary image aspect ratio.
 1401        /// </summary>
 1402        /// <param name="dto">The dto.</param>
 1403        /// <param name="item">The item.</param>
 1404        public void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item)
 1405        {
 111406            dto.PrimaryImageAspectRatio = GetPrimaryImageAspectRatio(item);
 111407        }
 1408
 1409        public double? GetPrimaryImageAspectRatio(BaseItem item)
 1410        {
 111411            var imageInfo = item.GetImageInfo(ImageType.Primary, 0);
 1412
 111413            if (imageInfo is null)
 1414            {
 111415                return null;
 1416            }
 1417
 01418            if (!imageInfo.IsLocalFile)
 1419            {
 01420                return item.GetDefaultPrimaryImageAspectRatio();
 1421            }
 1422
 1423            try
 1424            {
 01425                var size = _imageProcessor.GetImageDimensions(item, imageInfo);
 01426                var width = size.Width;
 01427                var height = size.Height;
 01428                if (width > 0 && height > 0)
 1429                {
 01430                    return (double)width / height;
 1431                }
 01432            }
 01433            catch (Exception ex)
 1434            {
 01435                _logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path);
 01436            }
 1437
 01438            return item.GetDefaultPrimaryImageAspectRatio();
 01439        }
 1440    }
 1441}

Methods/Properties

.ctor(Microsoft.Extensions.Logging.ILogger`1<Emby.Server.Implementations.Dto.DtoService>,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.Library.IUserDataManager,MediaBrowser.Controller.Persistence.IItemRepository,MediaBrowser.Controller.Drawing.IImageProcessor,MediaBrowser.Controller.Providers.IProviderManager,MediaBrowser.Controller.LiveTv.IRecordingsManager,MediaBrowser.Common.IApplicationHost,MediaBrowser.Controller.Library.IMediaSourceManager,System.Lazy`1<MediaBrowser.Controller.LiveTv.ILiveTvManager>,MediaBrowser.Controller.Trickplay.ITrickplayManager)
get_LivetvManager()
GetBaseItemDtos(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>,MediaBrowser.Controller.Dto.DtoOptions,Jellyfin.Data.Entities.User,MediaBrowser.Controller.Entities.BaseItem)
GetBaseItemDto(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Dto.DtoOptions,Jellyfin.Data.Entities.User,MediaBrowser.Controller.Entities.BaseItem)
GetTaggedItems(MediaBrowser.Controller.Entities.IItemByName,Jellyfin.Data.Entities.User,MediaBrowser.Controller.Dto.DtoOptions)
GetBaseItemDtoInternal(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Dto.DtoOptions,Jellyfin.Data.Entities.User,MediaBrowser.Controller.Entities.BaseItem)
NormalizeMediaSourceContainers(MediaBrowser.Model.Dto.BaseItemDto)
GetItemByNameDto(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Dto.DtoOptions,System.Collections.Generic.List`1<MediaBrowser.Controller.Entities.BaseItem>,Jellyfin.Data.Entities.User)
SetItemByNameInfo(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Dto.BaseItemDto,System.Collections.Generic.IList`1<MediaBrowser.Controller.Entities.BaseItem>)
AttachUserSpecificInfo(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.Entities.BaseItem,Jellyfin.Data.Entities.User,MediaBrowser.Controller.Dto.DtoOptions)
GetChildCount(MediaBrowser.Controller.Entities.Folder,Jellyfin.Data.Entities.User)
SetBookProperties(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.Entities.Book)
SetPhotoProperties(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.Entities.Photo)
SetMusicVideoProperties(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.Entities.MusicVideo)
GetImageTags(MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.List`1<MediaBrowser.Controller.Entities.ItemImageInfo>)
GetImageCacheTag(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Entities.ItemImageInfo)
AttachPeople(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.Entities.BaseItem,Jellyfin.Data.Entities.User)
AttachStudios(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.Entities.BaseItem)
AttachGenreItems(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.Entities.BaseItem)
GetGenreId(System.String,MediaBrowser.Controller.Entities.BaseItem)
GetTagAndFillBlurhash(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Entities.ImageType,System.Int32)
GetTagAndFillBlurhash(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Entities.ItemImageInfo)
GetTagsAndFillBlurhashes(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Entities.ImageType,System.Int32)
GetTagsAndFillBlurhashes(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Entities.ImageType,System.Collections.Generic.List`1<MediaBrowser.Controller.Entities.ItemImageInfo>)
AttachBasicFields(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Dto.DtoOptions)
GetImageDisplayParent(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Entities.BaseItem)
AddInheritedImages(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Dto.DtoOptions,MediaBrowser.Controller.Entities.BaseItem)
GetMappedPath(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Entities.BaseItem)
AttachPrimaryImageAspectRatio(MediaBrowser.Model.Dto.IItemDto,MediaBrowser.Controller.Entities.BaseItem)
GetPrimaryImageAspectRatio(MediaBrowser.Controller.Entities.BaseItem)