< 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
47%
Covered lines: 353
Uncovered lines: 389
Coverable lines: 742
Total lines: 1541
Line coverage: 47.5%
Branch coverage
35%
Covered branches: 166
Total branches: 473
Branch coverage: 35%
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.Frozen;
 5using System.Collections.Generic;
 6using System.Globalization;
 7using System.IO;
 8using System.Linq;
 9using Jellyfin.Data.Enums;
 10using Jellyfin.Database.Implementations.Entities;
 11using Jellyfin.Extensions;
 12using MediaBrowser.Common;
 13using MediaBrowser.Controller.Channels;
 14using MediaBrowser.Controller.Chapters;
 15using MediaBrowser.Controller.Drawing;
 16using MediaBrowser.Controller.Dto;
 17using MediaBrowser.Controller.Entities;
 18using MediaBrowser.Controller.Entities.Audio;
 19using MediaBrowser.Controller.Library;
 20using MediaBrowser.Controller.LiveTv;
 21using MediaBrowser.Controller.Playlists;
 22using MediaBrowser.Controller.Providers;
 23using MediaBrowser.Controller.Trickplay;
 24using MediaBrowser.Model.Dto;
 25using MediaBrowser.Model.Entities;
 26using MediaBrowser.Model.Querying;
 27using Microsoft.Extensions.Logging;
 28using Book = MediaBrowser.Controller.Entities.Book;
 29using Episode = MediaBrowser.Controller.Entities.TV.Episode;
 30using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
 31using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
 32using Person = MediaBrowser.Controller.Entities.Person;
 33using Photo = MediaBrowser.Controller.Entities.Photo;
 34using Season = MediaBrowser.Controller.Entities.TV.Season;
 35using Series = MediaBrowser.Controller.Entities.TV.Series;
 36
 37namespace Emby.Server.Implementations.Dto
 38{
 39    public class DtoService : IDtoService
 40    {
 141        private static readonly FrozenDictionary<BaseItemKind, BaseItemKind[]> _relatedItemKinds = new Dictionary<BaseIt
 142        {
 143            {
 144                BaseItemKind.Genre, [
 145                    BaseItemKind.Audio,
 146                    BaseItemKind.Episode,
 147                    BaseItemKind.Movie,
 148                    BaseItemKind.LiveTvProgram,
 149                    BaseItemKind.MusicAlbum,
 150                    BaseItemKind.MusicArtist,
 151                    BaseItemKind.MusicVideo,
 152                    BaseItemKind.Series,
 153                    BaseItemKind.Trailer
 154                ]
 155            },
 156            {
 157                BaseItemKind.MusicArtist, [
 158                    BaseItemKind.Audio,
 159                    BaseItemKind.MusicAlbum,
 160                    BaseItemKind.MusicVideo
 161                ]
 162            },
 163            {
 164                BaseItemKind.MusicGenre, [
 165                    BaseItemKind.Audio,
 166                    BaseItemKind.MusicAlbum,
 167                    BaseItemKind.MusicArtist,
 168                    BaseItemKind.MusicVideo
 169                ]
 170            },
 171            {
 172                BaseItemKind.Person, [
 173                    BaseItemKind.Audio,
 174                    BaseItemKind.Episode,
 175                    BaseItemKind.Movie,
 176                    BaseItemKind.LiveTvProgram,
 177                    BaseItemKind.MusicAlbum,
 178                    BaseItemKind.MusicArtist,
 179                    BaseItemKind.MusicVideo,
 180                    BaseItemKind.Series,
 181                    BaseItemKind.Trailer
 182                ]
 183            },
 184            {
 185                BaseItemKind.Studio, [
 186                    BaseItemKind.Audio,
 187                    BaseItemKind.Episode,
 188                    BaseItemKind.Movie,
 189                    BaseItemKind.LiveTvProgram,
 190                    BaseItemKind.MusicAlbum,
 191                    BaseItemKind.MusicArtist,
 192                    BaseItemKind.MusicVideo,
 193                    BaseItemKind.Series,
 194                    BaseItemKind.Trailer
 195                ]
 196            },
 197            {
 198                BaseItemKind.Year, [
 199                    BaseItemKind.Audio,
 1100                    BaseItemKind.Episode,
 1101                    BaseItemKind.Movie,
 1102                    BaseItemKind.LiveTvProgram,
 1103                    BaseItemKind.MusicAlbum,
 1104                    BaseItemKind.MusicArtist,
 1105                    BaseItemKind.MusicVideo,
 1106                    BaseItemKind.Series,
 1107                    BaseItemKind.Trailer
 1108                ]
 1109            }
 1110        }.ToFrozenDictionary();
 111
 112        private readonly ILogger<DtoService> _logger;
 113        private readonly ILibraryManager _libraryManager;
 114        private readonly IUserDataManager _userDataRepository;
 115
 116        private readonly IImageProcessor _imageProcessor;
 117        private readonly IProviderManager _providerManager;
 118        private readonly IRecordingsManager _recordingsManager;
 119
 120        private readonly IApplicationHost _appHost;
 121        private readonly IMediaSourceManager _mediaSourceManager;
 122        private readonly Lazy<ILiveTvManager> _livetvManagerFactory;
 123
 124        private readonly ITrickplayManager _trickplayManager;
 125        private readonly IChapterManager _chapterManager;
 126
 127        public DtoService(
 128            ILogger<DtoService> logger,
 129            ILibraryManager libraryManager,
 130            IUserDataManager userDataRepository,
 131            IImageProcessor imageProcessor,
 132            IProviderManager providerManager,
 133            IRecordingsManager recordingsManager,
 134            IApplicationHost appHost,
 135            IMediaSourceManager mediaSourceManager,
 136            Lazy<ILiveTvManager> livetvManagerFactory,
 137            ITrickplayManager trickplayManager,
 138            IChapterManager chapterManager)
 139        {
 21140            _logger = logger;
 21141            _libraryManager = libraryManager;
 21142            _userDataRepository = userDataRepository;
 21143            _imageProcessor = imageProcessor;
 21144            _providerManager = providerManager;
 21145            _recordingsManager = recordingsManager;
 21146            _appHost = appHost;
 21147            _mediaSourceManager = mediaSourceManager;
 21148            _livetvManagerFactory = livetvManagerFactory;
 21149            _trickplayManager = trickplayManager;
 21150            _chapterManager = chapterManager;
 21151        }
 152
 0153        private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
 154
 155        /// <inheritdoc />
 156        public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User? user 
 157        {
 4158            var accessibleItems = user is null ? items : items.Where(x => x.IsVisible(user)).ToList();
 4159            var returnItems = new BaseItemDto[accessibleItems.Count];
 4160            List<(BaseItem, BaseItemDto)>? programTuples = null;
 4161            List<(BaseItemDto, LiveTvChannel)>? channelTuples = null;
 162
 14163            for (int index = 0; index < accessibleItems.Count; index++)
 164            {
 3165                var item = accessibleItems[index];
 3166                var dto = GetBaseItemDtoInternal(item, options, user, owner);
 167
 3168                if (item is LiveTvChannel tvChannel)
 169                {
 0170                    (channelTuples ??= []).Add((dto, tvChannel));
 171                }
 3172                else if (item is LiveTvProgram)
 173                {
 0174                    (programTuples ??= []).Add((item, dto));
 175                }
 176
 3177                if (options.ContainsField(ItemFields.ItemCounts))
 178                {
 0179                    SetItemByNameInfo(dto, user);
 180                }
 181
 3182                returnItems[index] = dto;
 183            }
 184
 4185            if (programTuples is not null)
 186            {
 0187                LivetvManager.AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
 188            }
 189
 4190            if (channelTuples is not null)
 191            {
 0192                LivetvManager.AddChannelInfo(channelTuples, options, user);
 193            }
 194
 4195            return returnItems;
 196        }
 197
 198        public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner = null)
 199        {
 6200            var dto = GetBaseItemDtoInternal(item, options, user, owner);
 6201            if (item is LiveTvChannel tvChannel)
 202            {
 0203                LivetvManager.AddChannelInfo(new[] { (dto, tvChannel) }, options, user);
 204            }
 6205            else if (item is LiveTvProgram)
 206            {
 0207                LivetvManager.AddInfoToProgramDto(new[] { (item, dto) }, options.Fields, user).GetAwaiter().GetResult();
 208            }
 209
 6210            if (options.ContainsField(ItemFields.ItemCounts))
 211            {
 6212                SetItemByNameInfo(dto, user);
 213            }
 214
 6215            return dto;
 216        }
 217
 218        private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner
 219        {
 9220            var dto = new BaseItemDto
 9221            {
 9222                ServerId = _appHost.SystemId
 9223            };
 224
 9225            if (item.SourceType == SourceType.Channel)
 226            {
 0227                dto.SourceType = item.SourceType.ToString();
 228            }
 229
 9230            if (options.ContainsField(ItemFields.People))
 231            {
 6232                AttachPeople(dto, item, user);
 233            }
 234
 9235            if (options.ContainsField(ItemFields.PrimaryImageAspectRatio))
 236            {
 237                try
 238                {
 6239                    AttachPrimaryImageAspectRatio(dto, item);
 6240                }
 0241                catch (Exception ex)
 242                {
 243                    // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
 0244                    _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {ItemName}", item.Name);
 0245                }
 246            }
 247
 9248            if (options.ContainsField(ItemFields.DisplayPreferencesId))
 249            {
 6250                dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N", CultureInfo.InvariantCulture);
 251            }
 252
 9253            if (user is not null)
 254            {
 9255                AttachUserSpecificInfo(dto, item, user, options);
 256            }
 257
 9258            if (item is IHasMediaSources
 9259                && options.ContainsField(ItemFields.MediaSources))
 260            {
 0261                dto.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray();
 262
 0263                NormalizeMediaSourceContainers(dto);
 264            }
 265
 9266            if (options.ContainsField(ItemFields.Studios))
 267            {
 6268                AttachStudios(dto, item);
 269            }
 270
 9271            AttachBasicFields(dto, item, owner, options);
 272
 9273            if (options.ContainsField(ItemFields.CanDelete))
 274            {
 6275                dto.CanDelete = user is null
 6276                    ? item.CanDelete()
 6277                    : item.CanDelete(user);
 278            }
 279
 9280            if (options.ContainsField(ItemFields.CanDownload))
 281            {
 6282                dto.CanDownload = user is null
 6283                    ? item.CanDownload()
 6284                    : item.CanDownload(user);
 285            }
 286
 9287            if (options.ContainsField(ItemFields.Etag))
 288            {
 6289                dto.Etag = item.GetEtag(user);
 290            }
 291
 9292            var activeRecording = _recordingsManager.GetActiveRecordingInfo(item.Path);
 9293            if (activeRecording is not null)
 294            {
 0295                dto.Type = BaseItemKind.Recording;
 0296                dto.CanDownload = false;
 0297                dto.RunTimeTicks = null;
 298
 0299                if (!string.IsNullOrEmpty(dto.SeriesName))
 300                {
 0301                    dto.EpisodeTitle = dto.Name;
 0302                    dto.Name = dto.SeriesName;
 303                }
 304
 0305                LivetvManager.AddInfoToRecordingDto(item, dto, activeRecording, user);
 306            }
 307
 9308            if (item is Audio audio)
 309            {
 0310                dto.HasLyrics = audio.GetMediaStreams().Any(s => s.Type == MediaStreamType.Lyric);
 311            }
 312
 9313            return dto;
 314        }
 315
 316        private static void NormalizeMediaSourceContainers(BaseItemDto dto)
 317        {
 0318            foreach (var mediaSource in dto.MediaSources)
 319            {
 0320                var container = mediaSource.Container;
 0321                if (string.IsNullOrEmpty(container))
 322                {
 323                    continue;
 324                }
 325
 0326                var containers = container.Split(',');
 0327                if (containers.Length < 2)
 328                {
 329                    continue;
 330                }
 331
 0332                var path = mediaSource.Path;
 0333                string? fileExtensionContainer = null;
 334
 0335                if (!string.IsNullOrEmpty(path))
 336                {
 0337                    path = Path.GetExtension(path);
 0338                    if (!string.IsNullOrEmpty(path))
 339                    {
 0340                        path = Path.GetExtension(path);
 0341                        if (!string.IsNullOrEmpty(path))
 342                        {
 0343                            path = path.TrimStart('.');
 344                        }
 345
 0346                        if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparison.OrdinalIgnoreCase)
 347                        {
 0348                            fileExtensionContainer = path;
 349                        }
 350                    }
 351                }
 352
 0353                mediaSource.Container = fileExtensionContainer ?? containers[0];
 354            }
 0355        }
 356
 357        /// <inheritdoc />
 358        /// TODO refactor this to use the new SetItemByNameInfo.
 359        /// Some callers already have the counts extracted so no reason to retrieve them again.
 360        public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem>? taggedItems, User? user =
 361        {
 0362            var dto = GetBaseItemDtoInternal(item, options, user);
 363
 0364            if (options.ContainsField(ItemFields.ItemCounts)
 0365                && taggedItems is not null
 0366                && taggedItems.Count != 0)
 367            {
 0368                SetItemByNameInfo(item, dto, taggedItems);
 369            }
 370
 0371            return dto;
 372        }
 373
 374        private void SetItemByNameInfo(BaseItemDto dto, User? user)
 375        {
 6376            if (!_relatedItemKinds.TryGetValue(dto.Type, out var relatedItemKinds))
 377            {
 6378                return;
 379            }
 380
 0381            var query = new InternalItemsQuery(user)
 0382            {
 0383                Recursive = true,
 0384                DtoOptions = new DtoOptions(false) { EnableImages = false },
 0385                IncludeItemTypes = relatedItemKinds
 0386            };
 387
 0388            switch (dto.Type)
 389            {
 390                case BaseItemKind.Genre:
 391                case BaseItemKind.MusicGenre:
 0392                    query.GenreIds = [dto.Id];
 0393                    break;
 394                case BaseItemKind.MusicArtist:
 0395                    query.ArtistIds = [dto.Id];
 0396                    break;
 397                case BaseItemKind.Person:
 0398                    query.PersonIds = [dto.Id];
 0399                    break;
 400                case BaseItemKind.Studio:
 0401                    query.StudioIds = [dto.Id];
 0402                    break;
 403                case BaseItemKind.Year
 0404                    when int.TryParse(dto.Name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year):
 0405                    query.Years = [year];
 0406                    break;
 407                default:
 0408                    return;
 409            }
 410
 0411            var counts = _libraryManager.GetItemCounts(query);
 412
 0413            dto.AlbumCount = counts.AlbumCount;
 0414            dto.ArtistCount = counts.ArtistCount;
 0415            dto.EpisodeCount = counts.EpisodeCount;
 0416            dto.MovieCount = counts.MovieCount;
 0417            dto.MusicVideoCount = counts.MusicVideoCount;
 0418            dto.ProgramCount = counts.ProgramCount;
 0419            dto.SeriesCount = counts.SeriesCount;
 0420            dto.SongCount = counts.SongCount;
 0421            dto.TrailerCount = counts.TrailerCount;
 0422            dto.ChildCount = counts.TotalItemCount();
 0423        }
 424
 425        private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IReadOnlyList<BaseItem> taggedItems)
 426        {
 0427            if (item is MusicArtist)
 428            {
 0429                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0430                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0431                dto.SongCount = taggedItems.Count(i => i is Audio);
 432            }
 0433            else if (item is MusicGenre)
 434            {
 0435                dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
 0436                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0437                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0438                dto.SongCount = taggedItems.Count(i => i is Audio);
 439            }
 440            else
 441            {
 442                // This populates them all and covers Genre, Person, Studio, Year
 443
 0444                dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
 0445                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0446                dto.EpisodeCount = taggedItems.Count(i => i is Episode);
 0447                dto.MovieCount = taggedItems.Count(i => i is Movie);
 0448                dto.TrailerCount = taggedItems.Count(i => i is Trailer);
 0449                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0450                dto.SeriesCount = taggedItems.Count(i => i is Series);
 0451                dto.ProgramCount = taggedItems.Count(i => i is LiveTvProgram);
 0452                dto.SongCount = taggedItems.Count(i => i is Audio);
 453            }
 454
 0455            dto.ChildCount = taggedItems.Count;
 0456        }
 457
 458        /// <summary>
 459        /// Attaches the user specific info.
 460        /// </summary>
 461        private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions options)
 462        {
 9463            if (item.IsFolder)
 464            {
 9465                var folder = (Folder)item;
 466
 9467                if (options.EnableUserData)
 468                {
 9469                    dto.UserData = _userDataRepository.GetUserDataDto(item, dto, user, options);
 470                }
 471
 9472                if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library)
 473                {
 474                    // For these types we can try to optimize and assume these values will be equal
 9475                    if (item is MusicAlbum || item is Season || item is Playlist)
 476                    {
 0477                        dto.ChildCount = dto.RecursiveItemCount;
 0478                        var folderChildCount = folder.LinkedChildren.Length;
 479                        // The default is an empty array, so we can't reliably use the count when it's empty
 0480                        if (folderChildCount > 0)
 481                        {
 0482                            dto.ChildCount ??= folderChildCount;
 483                        }
 484                    }
 485
 9486                    if (options.ContainsField(ItemFields.ChildCount))
 487                    {
 6488                        dto.ChildCount ??= GetChildCount(folder, user);
 489                    }
 490                }
 491
 9492                if (options.ContainsField(ItemFields.CumulativeRunTimeTicks))
 493                {
 6494                    dto.CumulativeRunTimeTicks = item.RunTimeTicks;
 495                }
 496
 9497                if (options.ContainsField(ItemFields.DateLastMediaAdded))
 498                {
 6499                    dto.DateLastMediaAdded = folder.DateLastMediaAdded;
 500                }
 501            }
 502            else
 503            {
 0504                if (options.EnableUserData)
 505                {
 0506                    dto.UserData = _userDataRepository.GetUserDataDto(item, user);
 507                }
 508            }
 509
 9510            if (options.ContainsField(ItemFields.PlayAccess))
 511            {
 6512                dto.PlayAccess = item.GetPlayAccess(user);
 513            }
 9514        }
 515
 516        private static int GetChildCount(Folder folder, User user)
 517        {
 518            // Right now this is too slow to calculate for top level folders on a per-user basis
 519            // Just return something so that apps that are expecting a value won't think the folders are empty
 6520            if (folder is ICollectionFolder || folder is UserView)
 521            {
 0522                return Random.Shared.Next(1, 10);
 523            }
 524
 6525            return folder.GetChildCount(user);
 526        }
 527
 528        private static void SetBookProperties(BaseItemDto dto, Book item)
 529        {
 0530            dto.SeriesName = item.SeriesName;
 0531        }
 532
 533        private static void SetPhotoProperties(BaseItemDto dto, Photo item)
 534        {
 0535            dto.CameraMake = item.CameraMake;
 0536            dto.CameraModel = item.CameraModel;
 0537            dto.Software = item.Software;
 0538            dto.ExposureTime = item.ExposureTime;
 0539            dto.FocalLength = item.FocalLength;
 0540            dto.ImageOrientation = item.Orientation;
 0541            dto.Aperture = item.Aperture;
 0542            dto.ShutterSpeed = item.ShutterSpeed;
 543
 0544            dto.Latitude = item.Latitude;
 0545            dto.Longitude = item.Longitude;
 0546            dto.Altitude = item.Altitude;
 0547            dto.IsoSpeedRating = item.IsoSpeedRating;
 548
 0549            var album = item.AlbumEntity;
 550
 0551            if (album is not null)
 552            {
 0553                dto.Album = album.Name;
 0554                dto.AlbumId = album.Id;
 555            }
 0556        }
 557
 558        private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item)
 559        {
 0560            if (!string.IsNullOrEmpty(item.Album))
 561            {
 0562                var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery
 0563                {
 0564                    IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
 0565                    Name = item.Album,
 0566                    Limit = 1
 0567                });
 568
 0569                if (parentAlbumIds.Count > 0)
 570                {
 0571                    dto.AlbumId = parentAlbumIds[0];
 572                }
 573            }
 574
 0575            dto.Album = item.Album;
 0576        }
 577
 578        private string[] GetImageTags(BaseItem item, List<ItemImageInfo> images)
 579        {
 9580            return images
 9581                .Select(p => GetImageCacheTag(item, p))
 9582                .Where(i => i is not null)
 9583                .ToArray()!; // null values got filtered out
 584        }
 585
 586        private string? GetImageCacheTag(BaseItem item, ItemImageInfo image)
 587        {
 588            try
 589            {
 0590                return _imageProcessor.GetImageCacheTag(item, image);
 591            }
 0592            catch (Exception ex)
 593            {
 0594                _logger.LogError(ex, "Error getting {ImageType} image info for {Path}", image.Type, image.Path);
 0595                return null;
 596            }
 0597        }
 598
 599        /// <summary>
 600        /// Attaches People DTO's to a DTOBaseItem.
 601        /// </summary>
 602        /// <param name="dto">The dto.</param>
 603        /// <param name="item">The item.</param>
 604        /// <param name="user">The requesting user.</param>
 605        private void AttachPeople(BaseItemDto dto, BaseItem item, User? user = null)
 606        {
 607            // Ordering by person type to ensure actors and artists are at the front.
 608            // This is taking advantage of the fact that they both begin with A
 609            // This should be improved in the future
 6610            var people = _libraryManager.GetPeople(item).OrderBy(i => i.SortOrder ?? int.MaxValue)
 6611                .ThenBy(i =>
 6612                {
 6613                    if (i.IsType(PersonKind.Actor))
 6614                    {
 6615                        return 0;
 6616                    }
 6617
 6618                    if (i.IsType(PersonKind.GuestStar))
 6619                    {
 6620                        return 1;
 6621                    }
 6622
 6623                    if (i.IsType(PersonKind.Director))
 6624                    {
 6625                        return 2;
 6626                    }
 6627
 6628                    if (i.IsType(PersonKind.Writer))
 6629                    {
 6630                        return 3;
 6631                    }
 6632
 6633                    if (i.IsType(PersonKind.Producer))
 6634                    {
 6635                        return 4;
 6636                    }
 6637
 6638                    if (i.IsType(PersonKind.Composer))
 6639                    {
 6640                        return 4;
 6641                    }
 6642
 6643                    return 10;
 6644                })
 6645                .ToList();
 646
 6647            var list = new List<BaseItemPerson>();
 648
 6649            Dictionary<string, Person> dictionary = people.Select(p => p.Name)
 6650                .Distinct(StringComparer.OrdinalIgnoreCase).Select(c =>
 6651                {
 6652                    try
 6653                    {
 6654                        return _libraryManager.GetPerson(c);
 6655                    }
 6656                    catch (Exception ex)
 6657                    {
 6658                        _logger.LogError(ex, "Error getting person {Name}", c);
 6659                        return null;
 6660                    }
 6661                }).Where(i => i is not null)
 6662                .Where(i => user is null || i!.IsVisible(user))
 6663                .DistinctBy(x => x!.Name, StringComparer.OrdinalIgnoreCase)
 6664                .ToDictionary(i => i!.Name, StringComparer.OrdinalIgnoreCase)!; // null values got filtered out
 665
 12666            for (var i = 0; i < people.Count; i++)
 667            {
 0668                var person = people[i];
 669
 0670                var baseItemPerson = new BaseItemPerson
 0671                {
 0672                    Name = person.Name,
 0673                    Role = person.Role,
 0674                    Type = person.Type
 0675                };
 676
 0677                if (dictionary.TryGetValue(person.Name, out Person? entity))
 678                {
 0679                    baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
 0680                    baseItemPerson.Id = entity.Id;
 0681                    if (dto.ImageBlurHashes is not null)
 682                    {
 683                        // Only add BlurHash for the person's image.
 0684                        baseItemPerson.ImageBlurHashes = [];
 0685                        foreach (var (imageType, blurHash) in dto.ImageBlurHashes)
 686                        {
 0687                            if (blurHash is not null)
 688                            {
 0689                                baseItemPerson.ImageBlurHashes[imageType] = [];
 0690                                foreach (var (imageId, blurHashValue) in blurHash)
 691                                {
 0692                                    if (string.Equals(baseItemPerson.PrimaryImageTag, imageId, StringComparison.OrdinalI
 693                                    {
 0694                                        baseItemPerson.ImageBlurHashes[imageType][imageId] = blurHashValue;
 695                                    }
 696                                }
 697                            }
 698                        }
 699                    }
 700
 0701                    list.Add(baseItemPerson);
 702                }
 703            }
 704
 6705            dto.People = list.ToArray();
 6706        }
 707
 708        /// <summary>
 709        /// Attaches the studios.
 710        /// </summary>
 711        /// <param name="dto">The dto.</param>
 712        /// <param name="item">The item.</param>
 713        private void AttachStudios(BaseItemDto dto, BaseItem item)
 714        {
 6715            dto.Studios = item.Studios
 6716                .Where(i => !string.IsNullOrEmpty(i))
 6717                .Select(i => new NameGuidPair
 6718                {
 6719                    Name = i,
 6720                    Id = _libraryManager.GetStudioId(i)
 6721                })
 6722                .ToArray();
 6723        }
 724
 725        private void AttachGenreItems(BaseItemDto dto, BaseItem item)
 726        {
 6727            dto.GenreItems = item.Genres
 6728                .Where(i => !string.IsNullOrEmpty(i))
 6729                .Select(i => new NameGuidPair
 6730                {
 6731                    Name = i,
 6732                    Id = GetGenreId(i, item)
 6733                })
 6734                .ToArray();
 6735        }
 736
 737        private Guid GetGenreId(string name, BaseItem owner)
 738        {
 0739            if (owner is IHasMusicGenres)
 740            {
 0741                return _libraryManager.GetMusicGenreId(name);
 742            }
 743
 0744            return _libraryManager.GetGenreId(name);
 745        }
 746
 747        private string? GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ImageType imageType, int imageIndex = 0)
 748        {
 0749            var image = item.GetImageInfo(imageType, imageIndex);
 0750            if (image is not null)
 751            {
 0752                return GetTagAndFillBlurhash(dto, item, image);
 753            }
 754
 0755            return null;
 756        }
 757
 758        private string? GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ItemImageInfo image)
 759        {
 0760            var tag = GetImageCacheTag(item, image);
 0761            if (tag is null)
 762            {
 0763                return null;
 764            }
 765
 0766            if (!string.IsNullOrEmpty(image.BlurHash))
 767            {
 0768                dto.ImageBlurHashes ??= [];
 769
 0770                if (!dto.ImageBlurHashes.TryGetValue(image.Type, out var value))
 771                {
 0772                    value = [];
 0773                    dto.ImageBlurHashes[image.Type] = value;
 774                }
 775
 0776                value[tag] = image.BlurHash;
 777            }
 778
 0779            return tag;
 780        }
 781
 782        private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, int limit)
 783        {
 9784            return GetTagsAndFillBlurhashes(dto, item, imageType, item.GetImages(imageType).Take(limit).ToList());
 785        }
 786
 787        private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, List<ItemImageInf
 788        {
 9789            var tags = GetImageTags(item, images);
 9790            var hashes = new Dictionary<string, string>();
 18791            for (int i = 0; i < images.Count; i++)
 792            {
 0793                var img = images[i];
 0794                if (!string.IsNullOrEmpty(img.BlurHash))
 795                {
 0796                    var tag = tags[i];
 0797                    hashes[tag] = img.BlurHash;
 798                }
 799            }
 800
 9801            if (hashes.Count > 0)
 802            {
 0803                dto.ImageBlurHashes ??= [];
 804
 0805                dto.ImageBlurHashes[imageType] = hashes;
 806            }
 807
 9808            return tags;
 809        }
 810
 811        /// <summary>
 812        /// Sets simple property values on a DTOBaseItem.
 813        /// </summary>
 814        /// <param name="dto">The dto.</param>
 815        /// <param name="item">The item.</param>
 816        /// <param name="owner">The owner.</param>
 817        /// <param name="options">The options.</param>
 818        private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem? owner, DtoOptions options)
 819        {
 9820            if (options.ContainsField(ItemFields.DateCreated))
 821            {
 6822                dto.DateCreated = item.DateCreated;
 823            }
 824
 9825            if (options.ContainsField(ItemFields.Settings))
 826            {
 6827                dto.LockedFields = item.LockedFields;
 6828                dto.LockData = item.IsLocked;
 6829                dto.ForcedSortName = item.ForcedSortName;
 830            }
 831
 9832            dto.Container = item.Container;
 9833            dto.EndDate = item.EndDate;
 834
 9835            if (options.ContainsField(ItemFields.ExternalUrls))
 836            {
 6837                dto.ExternalUrls = _providerManager.GetExternalUrls(item).ToArray();
 838            }
 839
 9840            if (options.ContainsField(ItemFields.Tags))
 841            {
 6842                dto.Tags = item.Tags;
 843            }
 844
 9845            if (item is IHasAspectRatio hasAspectRatio)
 846            {
 0847                dto.AspectRatio = hasAspectRatio.AspectRatio;
 848            }
 849
 9850            dto.ImageBlurHashes = [];
 851
 9852            var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
 9853            if (backdropLimit > 0)
 854            {
 9855                dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit);
 856            }
 857
 9858            if (options.ContainsField(ItemFields.Genres))
 859            {
 6860                dto.Genres = item.Genres;
 6861                AttachGenreItems(dto, item);
 862            }
 863
 9864            if (options.EnableImages)
 865            {
 9866                dto.ImageTags = [];
 867
 868                // Prevent implicitly captured closure
 9869                var currentItem = item;
 18870                foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)))
 871                {
 0872                    if (options.GetImageLimit(image.Type) > 0)
 873                    {
 0874                        var tag = GetTagAndFillBlurhash(dto, item, image);
 875
 0876                        if (tag is not null)
 877                        {
 0878                            dto.ImageTags[image.Type] = tag;
 879                        }
 880                    }
 881                }
 882            }
 883
 9884            dto.Id = item.Id;
 9885            dto.IndexNumber = item.IndexNumber;
 9886            dto.ParentIndexNumber = item.ParentIndexNumber;
 887
 9888            if (item.IsFolder)
 889            {
 9890                dto.IsFolder = true;
 891            }
 0892            else if (item is IHasMediaSources)
 893            {
 0894                dto.IsFolder = false;
 895            }
 896
 9897            dto.MediaType = item.MediaType;
 898
 9899            if (item is not LiveTvProgram)
 900            {
 9901                dto.LocationType = item.LocationType;
 902            }
 903
 9904            dto.Audio = item.Audio;
 905
 9906            if (options.ContainsField(ItemFields.Settings))
 907            {
 6908                dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
 6909                dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
 910            }
 911
 9912            dto.CriticRating = item.CriticRating;
 913
 9914            if (item is IHasDisplayOrder hasDisplayOrder)
 915            {
 0916                dto.DisplayOrder = hasDisplayOrder.DisplayOrder;
 917            }
 918
 9919            if (item is IHasCollectionType hasCollectionType)
 920            {
 3921                dto.CollectionType = hasCollectionType.CollectionType;
 922            }
 923
 9924            if (options.ContainsField(ItemFields.RemoteTrailers))
 925            {
 6926                dto.RemoteTrailers = item.RemoteTrailers;
 927            }
 928
 9929            dto.Name = item.Name;
 9930            dto.OfficialRating = item.OfficialRating;
 931
 9932            if (options.ContainsField(ItemFields.Overview))
 933            {
 6934                dto.Overview = item.Overview;
 935            }
 936
 9937            if (options.ContainsField(ItemFields.OriginalTitle))
 938            {
 6939                dto.OriginalTitle = item.OriginalTitle;
 940            }
 941
 9942            if (options.ContainsField(ItemFields.ParentId))
 943            {
 6944                dto.ParentId = item.DisplayParentId;
 945            }
 946
 9947            AddInheritedImages(dto, item, options, owner);
 948
 9949            if (options.ContainsField(ItemFields.Path))
 950            {
 6951                dto.Path = GetMappedPath(item, owner);
 952            }
 953
 9954            if (options.ContainsField(ItemFields.EnableMediaSourceDisplay))
 955            {
 6956                dto.EnableMediaSourceDisplay = item.EnableMediaSourceDisplay;
 957            }
 958
 9959            dto.PremiereDate = item.PremiereDate;
 9960            dto.ProductionYear = item.ProductionYear;
 961
 9962            if (options.ContainsField(ItemFields.ProviderIds))
 963            {
 6964                dto.ProviderIds = item.ProviderIds;
 965            }
 966
 9967            dto.RunTimeTicks = item.RunTimeTicks;
 968
 9969            if (options.ContainsField(ItemFields.SortName))
 970            {
 6971                dto.SortName = item.SortName;
 972            }
 973
 9974            if (options.ContainsField(ItemFields.CustomRating))
 975            {
 6976                dto.CustomRating = item.CustomRating;
 977            }
 978
 9979            if (options.ContainsField(ItemFields.Taglines))
 980            {
 6981                if (!string.IsNullOrEmpty(item.Tagline))
 982                {
 0983                    dto.Taglines = new string[] { item.Tagline };
 984                }
 985
 6986                dto.Taglines ??= Array.Empty<string>();
 987            }
 988
 9989            dto.Type = item.GetBaseItemKind();
 9990            if ((item.CommunityRating ?? 0) > 0)
 991            {
 0992                dto.CommunityRating = item.CommunityRating;
 993            }
 994
 9995            if (item is ISupportsPlaceHolders supportsPlaceHolders && supportsPlaceHolders.IsPlaceHolder)
 996            {
 0997                dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder;
 998            }
 999
 91000            if (item.LUFS.HasValue)
 1001            {
 1002                // -18 LUFS reference, same as ReplayGain 2.0, compatible with ReplayGain 1.0
 01003                dto.NormalizationGain = -18f - item.LUFS;
 1004            }
 91005            else if (item.NormalizationGain.HasValue)
 1006            {
 01007                dto.NormalizationGain = item.NormalizationGain;
 1008            }
 1009
 1010            // Add audio info
 91011            if (item is Audio audio)
 1012            {
 01013                dto.Album = audio.Album;
 01014                dto.ExtraType = audio.ExtraType;
 1015
 01016                var albumParent = audio.AlbumEntity;
 1017
 01018                if (albumParent is not null)
 1019                {
 01020                    dto.AlbumId = albumParent.Id;
 01021                    dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary);
 1022                }
 1023
 1024                // if (options.ContainsField(ItemFields.MediaSourceCount))
 1025                // {
 1026                // Songs always have one
 1027                // }
 1028            }
 1029
 91030            if (item is IHasArtist hasArtist)
 1031            {
 01032                dto.Artists = hasArtist.Artists;
 1033
 1034                // var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
 1035                // {
 1036                //    EnableTotalRecordCount = false,
 1037                //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
 1038                // });
 1039
 1040                // dto.ArtistItems = artistItems.Items
 1041                //    .Select(i =>
 1042                //    {
 1043                //        var artist = i.Item1;
 1044                //        return new NameIdPair
 1045                //        {
 1046                //            Name = artist.Name,
 1047                //            Id = artist.Id.ToString("N", CultureInfo.InvariantCulture)
 1048                //        };
 1049                //    })
 1050                //    .ToList();
 1051
 1052                // Include artists that are not in the database yet, e.g., just added via metadata editor
 1053                // var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
 01054                dto.ArtistItems = hasArtist.Artists
 01055                    // .Except(foundArtists, new DistinctNameComparer())
 01056                    .Select(i =>
 01057                    {
 01058                        // This should not be necessary but we're seeing some cases of it
 01059                        if (string.IsNullOrEmpty(i))
 01060                        {
 01061                            return null;
 01062                        }
 01063
 01064                        var artist = _libraryManager.GetArtist(i, new DtoOptions(false)
 01065                        {
 01066                            EnableImages = false
 01067                        });
 01068                        if (artist is not null)
 01069                        {
 01070                            return new NameGuidPair
 01071                            {
 01072                                Name = artist.Name,
 01073                                Id = artist.Id
 01074                            };
 01075                        }
 01076
 01077                        return null;
 01078                    }).Where(i => i is not null).ToArray();
 1079            }
 1080
 91081            if (item is IHasAlbumArtist hasAlbumArtist)
 1082            {
 01083                dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
 1084
 1085                // var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
 1086                // {
 1087                //    EnableTotalRecordCount = false,
 1088                //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
 1089                // });
 1090
 1091                // dto.AlbumArtists = artistItems.Items
 1092                //    .Select(i =>
 1093                //    {
 1094                //        var artist = i.Item1;
 1095                //        return new NameIdPair
 1096                //        {
 1097                //            Name = artist.Name,
 1098                //            Id = artist.Id.ToString("N", CultureInfo.InvariantCulture)
 1099                //        };
 1100                //    })
 1101                //    .ToList();
 1102
 01103                dto.AlbumArtists = hasAlbumArtist.AlbumArtists
 01104                    // .Except(foundArtists, new DistinctNameComparer())
 01105                    .Select(i =>
 01106                    {
 01107                        // This should not be necessary but we're seeing some cases of it
 01108                        if (string.IsNullOrEmpty(i))
 01109                        {
 01110                            return null;
 01111                        }
 01112
 01113                        var artist = _libraryManager.GetArtist(i, new DtoOptions(false)
 01114                        {
 01115                            EnableImages = false
 01116                        });
 01117                        if (artist is not null)
 01118                        {
 01119                            return new NameGuidPair
 01120                            {
 01121                                Name = artist.Name,
 01122                                Id = artist.Id
 01123                            };
 01124                        }
 01125
 01126                        return null;
 01127                    }).Where(i => i is not null).ToArray();
 1128            }
 1129
 1130            // Add video info
 91131            if (item is Video video)
 1132            {
 01133                dto.VideoType = video.VideoType;
 01134                dto.Video3DFormat = video.Video3DFormat;
 01135                dto.IsoType = video.IsoType;
 1136
 01137                if (video.HasSubtitles)
 1138                {
 01139                    dto.HasSubtitles = video.HasSubtitles;
 1140                }
 1141
 01142                if (video.AdditionalParts.Length != 0)
 1143                {
 01144                    dto.PartCount = video.AdditionalParts.Length + 1;
 1145                }
 1146
 01147                if (options.ContainsField(ItemFields.MediaSourceCount))
 1148                {
 01149                    var mediaSourceCount = video.MediaSourceCount;
 01150                    if (mediaSourceCount != 1)
 1151                    {
 01152                        dto.MediaSourceCount = mediaSourceCount;
 1153                    }
 1154                }
 1155
 01156                if (options.ContainsField(ItemFields.Chapters))
 1157                {
 01158                    dto.Chapters = _chapterManager.GetChapters(item.Id).ToList();
 1159                }
 1160
 01161                if (options.ContainsField(ItemFields.Trickplay))
 1162                {
 01163                    var trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult();
 01164                    dto.Trickplay = trickplay.ToDictionary(
 01165                        mediaStream => mediaStream.Key,
 01166                        mediaStream => mediaStream.Value.ToDictionary(
 01167                            width => width.Key,
 01168                            width => new TrickplayInfoDto(width.Value)));
 1169                }
 1170
 01171                dto.ExtraType = video.ExtraType;
 1172            }
 1173
 91174            if (options.ContainsField(ItemFields.MediaStreams))
 1175            {
 1176                // Add VideoInfo
 61177                if (item is IHasMediaSources)
 1178                {
 1179                    MediaStream[] mediaStreams;
 1180
 01181                    if (dto.MediaSources is not null && dto.MediaSources.Length > 0)
 1182                    {
 01183                        if (item.SourceType == SourceType.Channel)
 1184                        {
 01185                            mediaStreams = dto.MediaSources[0].MediaStreams.ToArray();
 1186                        }
 1187                        else
 1188                        {
 01189                            string id = item.Id.ToString("N", CultureInfo.InvariantCulture);
 01190                            mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, id, StringComparison.OrdinalI
 01191                                .SelectMany(i => i.MediaStreams)
 01192                                .ToArray();
 1193                        }
 1194                    }
 1195                    else
 1196                    {
 01197                        mediaStreams = _mediaSourceManager.GetStaticMediaSources(item, true)[0].MediaStreams.ToArray();
 1198                    }
 1199
 01200                    dto.MediaStreams = mediaStreams;
 1201                }
 1202            }
 1203
 91204            BaseItem[]? allExtras = null;
 1205
 91206            if (options.ContainsField(ItemFields.SpecialFeatureCount))
 1207            {
 61208                allExtras = item.GetExtras().ToArray();
 61209                dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contai
 1210            }
 1211
 91212            if (options.ContainsField(ItemFields.LocalTrailerCount))
 1213            {
 61214                if (item is IHasTrailers hasTrailers)
 1215                {
 01216                    dto.LocalTrailerCount = hasTrailers.LocalTrailers.Count;
 1217                }
 1218                else
 1219                {
 61220                    dto.LocalTrailerCount = (allExtras ?? item.GetExtras()).Count(i => i.ExtraType == ExtraType.Trailer)
 1221                }
 1222            }
 1223
 1224            // Add EpisodeInfo
 91225            if (item is Episode episode)
 1226            {
 01227                dto.IndexNumberEnd = episode.IndexNumberEnd;
 01228                dto.SeriesName = episode.SeriesName;
 1229
 01230                if (options.ContainsField(ItemFields.SpecialEpisodeNumbers))
 1231                {
 01232                    dto.AirsAfterSeasonNumber = episode.AirsAfterSeasonNumber;
 01233                    dto.AirsBeforeEpisodeNumber = episode.AirsBeforeEpisodeNumber;
 01234                    dto.AirsBeforeSeasonNumber = episode.AirsBeforeSeasonNumber;
 1235                }
 1236
 01237                dto.SeasonName = episode.SeasonName;
 01238                dto.SeasonId = episode.SeasonId;
 01239                dto.SeriesId = episode.SeriesId;
 1240
 01241                Series? episodeSeries = null;
 1242
 1243                // this block will add the series poster for episodes without a poster
 1244                // TODO maybe remove the if statement entirely
 1245                // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
 1246                {
 01247                    episodeSeries ??= episode.Series;
 01248                    if (episodeSeries is not null)
 1249                    {
 01250                        dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
 01251                        if (dto.ImageTags is null || !dto.ImageTags.ContainsKey(ImageType.Primary))
 1252                        {
 01253                            AttachPrimaryImageAspectRatio(dto, episodeSeries);
 1254                        }
 1255                    }
 1256                }
 1257
 01258                if (options.ContainsField(ItemFields.SeriesStudio))
 1259                {
 01260                    episodeSeries ??= episode.Series;
 01261                    if (episodeSeries is not null)
 1262                    {
 01263                        dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
 1264                    }
 1265                }
 1266            }
 1267
 1268            // Add SeriesInfo
 1269            Series? series;
 91270            if (item is Series tmp)
 1271            {
 01272                series = tmp;
 01273                dto.AirDays = series.AirDays;
 01274                dto.AirTime = series.AirTime;
 01275                dto.Status = series.Status?.ToString();
 1276            }
 1277
 1278            // Add SeasonInfo
 91279            if (item is Season season)
 1280            {
 01281                dto.SeriesName = season.SeriesName;
 01282                dto.SeriesId = season.SeriesId;
 1283
 01284                series = null;
 1285
 01286                if (options.ContainsField(ItemFields.SeriesStudio))
 1287                {
 01288                    series ??= season.Series;
 01289                    if (series is not null)
 1290                    {
 01291                        dto.SeriesStudio = series.Studios.FirstOrDefault();
 1292                    }
 1293                }
 1294
 1295                // this block will add the series poster for seasons without a poster
 1296                // TODO maybe remove the if statement entirely
 1297                // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
 1298                {
 01299                    series ??= season.Series;
 01300                    if (series is not null)
 1301                    {
 01302                        dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
 01303                        if (dto.ImageTags is null || !dto.ImageTags.ContainsKey(ImageType.Primary))
 1304                        {
 01305                            AttachPrimaryImageAspectRatio(dto, series);
 1306                        }
 1307                    }
 1308                }
 1309            }
 1310
 91311            if (item is MusicVideo musicVideo)
 1312            {
 01313                SetMusicVideoProperties(dto, musicVideo);
 1314            }
 1315
 91316            if (item is Book book)
 1317            {
 01318                SetBookProperties(dto, book);
 1319            }
 1320
 91321            if (options.ContainsField(ItemFields.ProductionLocations))
 1322            {
 61323                if (item.ProductionLocations.Length > 0 || item is Movie)
 1324                {
 01325                    dto.ProductionLocations = item.ProductionLocations;
 1326                }
 1327            }
 1328
 91329            if (options.ContainsField(ItemFields.Width))
 1330            {
 61331                var width = item.Width;
 61332                if (width > 0)
 1333                {
 01334                    dto.Width = width;
 1335                }
 1336            }
 1337
 91338            if (options.ContainsField(ItemFields.Height))
 1339            {
 61340                var height = item.Height;
 61341                if (height > 0)
 1342                {
 01343                    dto.Height = height;
 1344                }
 1345            }
 1346
 91347            if (options.ContainsField(ItemFields.IsHD))
 1348            {
 1349                // Compatibility
 61350                if (item.IsHD)
 1351                {
 01352                    dto.IsHD = true;
 1353                }
 1354            }
 1355
 91356            if (item is Photo photo)
 1357            {
 01358                SetPhotoProperties(dto, photo);
 1359            }
 1360
 91361            dto.ChannelId = item.ChannelId;
 1362
 91363            if (item.SourceType == SourceType.Channel)
 1364            {
 01365                var channel = _libraryManager.GetItemById(item.ChannelId);
 01366                if (channel is not null)
 1367                {
 01368                    dto.ChannelName = channel.Name;
 1369                }
 1370            }
 91371        }
 1372
 1373        private BaseItem? GetImageDisplayParent(BaseItem currentItem, BaseItem originalItem)
 1374        {
 01375            if (currentItem is MusicAlbum musicAlbum)
 1376            {
 01377                var artist = musicAlbum.GetMusicArtist(new DtoOptions(false));
 01378                if (artist is not null)
 1379                {
 01380                    return artist;
 1381                }
 1382            }
 1383
 01384            var parent = currentItem.DisplayParent ?? currentItem.GetOwner() ?? currentItem.GetParent();
 1385
 01386            if (parent is null && originalItem is not UserRootFolder && originalItem is not UserView && originalItem is 
 1387            {
 01388                parent = _libraryManager.GetCollectionFolders(originalItem).FirstOrDefault();
 1389            }
 1390
 01391            return parent;
 1392        }
 1393
 1394        private void AddInheritedImages(BaseItemDto dto, BaseItem item, DtoOptions options, BaseItem? owner)
 1395        {
 91396            if (!item.SupportsInheritedParentImages)
 1397            {
 91398                return;
 1399            }
 1400
 01401            var logoLimit = options.GetImageLimit(ImageType.Logo);
 01402            var artLimit = options.GetImageLimit(ImageType.Art);
 01403            var thumbLimit = options.GetImageLimit(ImageType.Thumb);
 01404            var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
 1405
 1406            // For now. Emby apps are not using this
 01407            artLimit = 0;
 1408
 01409            if (logoLimit == 0 && artLimit == 0 && thumbLimit == 0 && backdropLimit == 0)
 1410            {
 01411                return;
 1412            }
 1413
 01414            BaseItem? parent = null;
 01415            var isFirst = true;
 1416
 01417            var imageTags = dto.ImageTags;
 1418
 01419            while ((!(imageTags is not null && imageTags.ContainsKey(ImageType.Logo)) && logoLimit > 0)
 01420                || (!(imageTags is not null && imageTags.ContainsKey(ImageType.Art)) && artLimit > 0)
 01421                || (!(imageTags is not null && imageTags.ContainsKey(ImageType.Thumb)) && thumbLimit > 0)
 01422                || parent is Series)
 1423            {
 01424                parent ??= isFirst ? GetImageDisplayParent(item, item) ?? owner : parent;
 01425                if (parent is null)
 1426                {
 1427                    break;
 1428                }
 1429
 01430                var allImages = parent.ImageInfos;
 1431
 01432                if (logoLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Logo)) && dto.ParentLogo
 1433                {
 01434                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Logo);
 1435
 01436                    if (image is not null)
 1437                    {
 01438                        dto.ParentLogoItemId = parent.Id;
 01439                        dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1440                    }
 1441                }
 1442
 01443                if (artLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtIte
 1444                {
 01445                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
 1446
 01447                    if (image is not null)
 1448                    {
 01449                        dto.ParentArtItemId = parent.Id;
 01450                        dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1451                    }
 1452                }
 1453
 01454                if (thumbLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentT
 1455                {
 01456                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
 1457
 01458                    if (image is not null)
 1459                    {
 01460                        dto.ParentThumbItemId = parent.Id;
 01461                        dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1462                    }
 1463                }
 1464
 01465                if (backdropLimit > 0 && !((dto.BackdropImageTags is not null && dto.BackdropImageTags.Length > 0) || (d
 1466                {
 01467                    var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
 1468
 01469                    if (images.Count > 0)
 1470                    {
 01471                        dto.ParentBackdropItemId = parent.Id;
 01472                        dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images);
 1473                    }
 1474                }
 1475
 01476                isFirst = false;
 1477
 01478                if (!parent.SupportsInheritedParentImages)
 1479                {
 1480                    break;
 1481                }
 1482
 01483                parent = GetImageDisplayParent(parent, item);
 1484            }
 01485        }
 1486
 1487        private string GetMappedPath(BaseItem item, BaseItem? ownerItem)
 1488        {
 61489            var path = item.Path;
 1490
 61491            if (item.IsFileProtocol)
 1492            {
 61493                path = _libraryManager.GetPathAfterNetworkSubstitution(path, ownerItem ?? item);
 1494            }
 1495
 61496            return path;
 1497        }
 1498
 1499        /// <summary>
 1500        /// Attaches the primary image aspect ratio.
 1501        /// </summary>
 1502        /// <param name="dto">The dto.</param>
 1503        /// <param name="item">The item.</param>
 1504        public void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item)
 1505        {
 61506            dto.PrimaryImageAspectRatio = GetPrimaryImageAspectRatio(item);
 61507        }
 1508
 1509        public double? GetPrimaryImageAspectRatio(BaseItem item)
 1510        {
 61511            var imageInfo = item.GetImageInfo(ImageType.Primary, 0);
 1512
 61513            if (imageInfo is null)
 1514            {
 61515                return null;
 1516            }
 1517
 01518            if (!imageInfo.IsLocalFile)
 1519            {
 01520                return item.GetDefaultPrimaryImageAspectRatio();
 1521            }
 1522
 1523            try
 1524            {
 01525                var size = _imageProcessor.GetImageDimensions(item, imageInfo);
 01526                var width = size.Width;
 01527                var height = size.Height;
 01528                if (width > 0 && height > 0)
 1529                {
 01530                    return (double)width / height;
 1531                }
 01532            }
 01533            catch (Exception ex)
 1534            {
 01535                _logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path);
 01536            }
 1537
 01538            return item.GetDefaultPrimaryImageAspectRatio();
 01539        }
 1540    }
 1541}

Methods/Properties

.cctor()
.ctor(Microsoft.Extensions.Logging.ILogger`1<Emby.Server.Implementations.Dto.DtoService>,MediaBrowser.Controller.Library.ILibraryManager,MediaBrowser.Controller.Library.IUserDataManager,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,MediaBrowser.Controller.Chapters.IChapterManager)
get_LivetvManager()
GetBaseItemDtos(System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>,MediaBrowser.Controller.Dto.DtoOptions,Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Entities.BaseItem)
GetBaseItemDto(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Dto.DtoOptions,Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Entities.BaseItem)
GetBaseItemDtoInternal(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Dto.DtoOptions,Jellyfin.Database.Implementations.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.Database.Implementations.Entities.User)
SetItemByNameInfo(MediaBrowser.Model.Dto.BaseItemDto,Jellyfin.Database.Implementations.Entities.User)
SetItemByNameInfo(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Model.Dto.BaseItemDto,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.BaseItem>)
AttachUserSpecificInfo(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.Entities.BaseItem,Jellyfin.Database.Implementations.Entities.User,MediaBrowser.Controller.Dto.DtoOptions)
GetChildCount(MediaBrowser.Controller.Entities.Folder,Jellyfin.Database.Implementations.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.Database.Implementations.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)