< 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
64%
Covered lines: 514
Uncovered lines: 285
Coverable lines: 799
Total lines: 1702
Line coverage: 64.3%
Branch coverage
46%
Covered branches: 258
Total branches: 558
Branch coverage: 46.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 2/26/2026 - 12:13:28 AM Line coverage: 49.7% (353/710) Branch coverage: 35% (166/473) Total lines: 15114/6/2026 - 12:13:55 AM Line coverage: 49.4% (353/714) Branch coverage: 34.8% (167/479) Total lines: 15205/4/2026 - 12:15:16 AM Line coverage: 53.6% (404/753) Branch coverage: 38.6% (194/502) Total lines: 16125/6/2026 - 12:15:23 AM Line coverage: 53.4% (411/769) Branch coverage: 38.1% (199/522) Total lines: 16505/8/2026 - 12:15:13 AM Line coverage: 53.5% (412/770) Branch coverage: 38.1% (199/522) Total lines: 16525/20/2026 - 12:15:44 AM Line coverage: 53.5% (412/770) Branch coverage: 31.9% (167/522) Total lines: 16526/2/2026 - 12:15:49 AM Line coverage: 63.9% (505/790) Branch coverage: 44.4% (241/542) Total lines: 16876/8/2026 - 12:16:15 AM Line coverage: 64.3% (514/799) Branch coverage: 46.2% (258/558) Total lines: 1702 2/26/2026 - 12:13:28 AM Line coverage: 49.7% (353/710) Branch coverage: 35% (166/473) Total lines: 15114/6/2026 - 12:13:55 AM Line coverage: 49.4% (353/714) Branch coverage: 34.8% (167/479) Total lines: 15205/4/2026 - 12:15:16 AM Line coverage: 53.6% (404/753) Branch coverage: 38.6% (194/502) Total lines: 16125/6/2026 - 12:15:23 AM Line coverage: 53.4% (411/769) Branch coverage: 38.1% (199/522) Total lines: 16505/8/2026 - 12:15:13 AM Line coverage: 53.5% (412/770) Branch coverage: 38.1% (199/522) Total lines: 16525/20/2026 - 12:15:44 AM Line coverage: 53.5% (412/770) Branch coverage: 31.9% (167/522) Total lines: 16526/2/2026 - 12:15:49 AM Line coverage: 63.9% (505/790) Branch coverage: 44.4% (241/542) Total lines: 16876/8/2026 - 12:16:15 AM Line coverage: 64.3% (514/799) Branch coverage: 46.2% (258/558) Total lines: 1702

Coverage delta

Coverage delta 13 -13

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        {
 26140            _logger = logger;
 26141            _libraryManager = libraryManager;
 26142            _userDataRepository = userDataRepository;
 26143            _imageProcessor = imageProcessor;
 26144            _providerManager = providerManager;
 26145            _recordingsManager = recordingsManager;
 26146            _appHost = appHost;
 26147            _mediaSourceManager = mediaSourceManager;
 26148            _livetvManagerFactory = livetvManagerFactory;
 26149            _trickplayManager = trickplayManager;
 26150            _chapterManager = chapterManager;
 26151        }
 152
 0153        private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
 154
 155        /// <inheritdoc />
 156        public IReadOnlyList<BaseItemDto> GetBaseItemDtos(
 157            IReadOnlyList<BaseItem> items,
 158            DtoOptions options,
 159            User? user = null,
 160            BaseItem? owner = null,
 161            bool skipVisibilityCheck = false)
 162        {
 4163            var accessibleItems = skipVisibilityCheck || user is null ? items : items.Where(x => x.IsVisible(user)).ToLi
 4164            var returnItems = new BaseItemDto[accessibleItems.Count];
 4165            List<(BaseItem, BaseItemDto)>? programTuples = null;
 4166            List<(BaseItemDto, LiveTvChannel)>? channelTuples = null;
 167
 168            // Batch-fetch user data for all items
 4169            Dictionary<Guid, UserItemData>? userDataBatch = null;
 4170            if (user is not null && options.EnableUserData)
 171            {
 4172                userDataBatch = _userDataRepository.GetUserDataBatch(accessibleItems, user);
 173            }
 174
 175            // Pre-compute collection folders once to avoid N+1 queries in CanDelete
 4176            List<Folder>? allCollectionFolders = null;
 4177            if (user is not null && options.ContainsField(ItemFields.CanDelete))
 178            {
 0179                allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList();
 180            }
 181
 182            // Batch-fetch child counts for all folders to avoid N+1 queries
 4183            Dictionary<Guid, int>? childCountBatch = null;
 4184            if (options.ContainsField(ItemFields.ChildCount))
 185            {
 0186                var folderIds = accessibleItems.OfType<Folder>().Select(f => f.Id).ToList();
 0187                if (folderIds.Count > 0)
 188                {
 0189                    childCountBatch = _libraryManager.GetChildCountBatch(folderIds, user?.Id);
 190                }
 191            }
 192
 193            // Batch-fetch played/total counts for all folders to avoid N+1 queries
 4194            Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null;
 4195            if (user is not null && options.EnableUserData)
 196            {
 4197                var folderIds = accessibleItems.OfType<Folder>()
 4198                    .Where(f => f.SupportsUserDataFromChildren && (f.SupportsPlayedStatus || options.ContainsField(ItemF
 4199                    .Select(f => f.Id).ToList();
 4200                if (folderIds.Count > 0)
 201                {
 0202                    playedCountBatch = _libraryManager.GetPlayedAndTotalCountBatch(folderIds, user);
 203                }
 204            }
 205
 206            // Batch-fetch MusicArtist lookups across all items to avoid N+1 queries.
 4207            IReadOnlyDictionary<string, MusicArtist[]>? artistsBatch = null;
 4208            var artistNames = new HashSet<string>(StringComparer.Ordinal);
 14209            foreach (var item in accessibleItems)
 210            {
 3211                if (item is IHasArtist hasArtist)
 212                {
 0213                    foreach (var name in hasArtist.Artists)
 214                    {
 0215                        if (!string.IsNullOrWhiteSpace(name))
 216                        {
 0217                            artistNames.Add(name);
 218                        }
 219                    }
 220                }
 221
 3222                if (item is IHasAlbumArtist hasAlbumArtist)
 223                {
 0224                    foreach (var name in hasAlbumArtist.AlbumArtists)
 225                    {
 0226                        if (!string.IsNullOrWhiteSpace(name))
 227                        {
 0228                            artistNames.Add(name);
 229                        }
 230                    }
 231                }
 232            }
 233
 4234            if (artistNames.Count > 0)
 235            {
 0236                artistsBatch = _libraryManager.GetArtists(artistNames.ToArray());
 237            }
 238
 14239            for (int index = 0; index < accessibleItems.Count; index++)
 240            {
 3241                var item = accessibleItems[index];
 3242                var dto = GetBaseItemDtoInternal(
 3243                    item,
 3244                    options,
 3245                    user,
 3246                    owner,
 3247                    userDataBatch?.GetValueOrDefault(item.Id),
 3248                    allCollectionFolders,
 3249                    childCountBatch,
 3250                    playedCountBatch,
 3251                    artistsBatch);
 252
 3253                if (item is LiveTvChannel tvChannel)
 254                {
 0255                    (channelTuples ??= []).Add((dto, tvChannel));
 256                }
 3257                else if (item is LiveTvProgram)
 258                {
 0259                    (programTuples ??= []).Add((item, dto));
 260                }
 261
 3262                if (options.ContainsField(ItemFields.ItemCounts))
 263                {
 0264                    SetItemByNameInfo(dto, user);
 265                }
 266
 3267                returnItems[index] = dto;
 268            }
 269
 4270            if (programTuples is not null)
 271            {
 0272                LivetvManager.AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
 273            }
 274
 4275            if (channelTuples is not null)
 276            {
 0277                LivetvManager.AddChannelInfo(channelTuples, options, user);
 278            }
 279
 4280            return returnItems;
 281        }
 282
 283        public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner = null)
 284        {
 11285            var dto = GetBaseItemDtoInternal(item, options, user, owner, null);
 11286            if (item is LiveTvChannel tvChannel)
 287            {
 0288                LivetvManager.AddChannelInfo(new[] { (dto, tvChannel) }, options, user);
 289            }
 11290            else if (item is LiveTvProgram)
 291            {
 0292                LivetvManager.AddInfoToProgramDto(new[] { (item, dto) }, options.Fields, user).GetAwaiter().GetResult();
 293            }
 294
 11295            if (options.ContainsField(ItemFields.ItemCounts))
 296            {
 6297                SetItemByNameInfo(dto, user);
 298            }
 299
 11300            return dto;
 301        }
 302
 303        private BaseItemDto GetBaseItemDtoInternal(
 304            BaseItem item,
 305            DtoOptions options,
 306            User? user = null,
 307            BaseItem? owner = null,
 308            UserItemData? userData = null,
 309            List<Folder>? allCollectionFolders = null,
 310            Dictionary<Guid, int>? childCountBatch = null,
 311            Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null,
 312            IReadOnlyDictionary<string, MusicArtist[]>? artistsBatch = null)
 313        {
 14314            var dto = new BaseItemDto
 14315            {
 14316                ServerId = _appHost.SystemId
 14317            };
 318
 14319            if (item.SourceType == SourceType.Channel)
 320            {
 0321                dto.SourceType = item.SourceType.ToString();
 322            }
 323
 14324            if (options.ContainsField(ItemFields.People))
 325            {
 6326                AttachPeople(dto, item, user);
 327            }
 328
 14329            if (options.ContainsField(ItemFields.PrimaryImageAspectRatio))
 330            {
 331                try
 332                {
 6333                    AttachPrimaryImageAspectRatio(dto, item);
 6334                }
 0335                catch (Exception ex)
 336                {
 337                    // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
 0338                    _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {ItemName}", item.Name);
 0339                }
 340            }
 341
 14342            if (options.ContainsField(ItemFields.DisplayPreferencesId))
 343            {
 6344                dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N", CultureInfo.InvariantCulture);
 345            }
 346
 14347            if (user is not null)
 348            {
 9349                AttachUserSpecificInfo(
 9350                    dto,
 9351                    item,
 9352                    user,
 9353                    options,
 9354                    userData,
 9355                    childCountBatch,
 9356                    playedCountBatch);
 357            }
 358
 14359            if (item is IHasMediaSources
 14360                && options.ContainsField(ItemFields.MediaSources))
 361            {
 0362                dto.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray();
 363
 0364                NormalizeMediaSourceContainers(dto);
 365            }
 366
 14367            if (options.ContainsField(ItemFields.Studios))
 368            {
 6369                AttachStudios(dto, item);
 370            }
 371
 14372            AttachBasicFields(dto, item, owner, options, artistsBatch);
 373
 14374            if (options.ContainsField(ItemFields.CanDelete))
 375            {
 6376                dto.CanDelete = user is null
 6377                    ? item.CanDelete()
 6378                    : allCollectionFolders is not null
 6379                        ? item.CanDelete(user, allCollectionFolders)
 6380                        : item.CanDelete(user);
 381            }
 382
 14383            if (options.ContainsField(ItemFields.CanDownload))
 384            {
 6385                dto.CanDownload = user is null
 6386                    ? item.CanDownload()
 6387                    : item.CanDownload(user);
 388            }
 389
 14390            if (options.ContainsField(ItemFields.Etag))
 391            {
 6392                dto.Etag = item.GetEtag(user);
 393            }
 394
 14395            var activeRecording = _recordingsManager.GetActiveRecordingInfo(item.Path);
 14396            if (activeRecording is not null)
 397            {
 0398                dto.Type = BaseItemKind.Recording;
 0399                dto.CanDownload = false;
 0400                dto.RunTimeTicks = null;
 401
 0402                if (!string.IsNullOrEmpty(dto.SeriesName))
 403                {
 0404                    dto.EpisodeTitle = dto.Name;
 0405                    dto.Name = dto.SeriesName;
 406                }
 407
 0408                LivetvManager.AddInfoToRecordingDto(item, dto, activeRecording, user);
 409            }
 410
 14411            if (item is Audio audio)
 412            {
 0413                dto.HasLyrics = audio.GetMediaStreams().Any(s => s.Type == MediaStreamType.Lyric);
 414            }
 415
 14416            return dto;
 417        }
 418
 419        private static void NormalizeMediaSourceContainers(BaseItemDto dto)
 420        {
 0421            foreach (var mediaSource in dto.MediaSources)
 422            {
 0423                var container = mediaSource.Container;
 0424                if (string.IsNullOrEmpty(container))
 425                {
 426                    continue;
 427                }
 428
 0429                var containers = container.Split(',');
 0430                if (containers.Length < 2)
 431                {
 432                    continue;
 433                }
 434
 0435                var path = mediaSource.Path;
 0436                string? fileExtensionContainer = null;
 437
 0438                if (!string.IsNullOrEmpty(path))
 439                {
 0440                    path = Path.GetExtension(path);
 0441                    if (!string.IsNullOrEmpty(path))
 442                    {
 0443                        path = Path.GetExtension(path);
 0444                        if (!string.IsNullOrEmpty(path))
 445                        {
 0446                            path = path.TrimStart('.');
 447                        }
 448
 0449                        if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparison.OrdinalIgnoreCase)
 450                        {
 0451                            fileExtensionContainer = path;
 452                        }
 453                    }
 454                }
 455
 0456                mediaSource.Container = fileExtensionContainer ?? containers[0];
 457            }
 0458        }
 459
 460        /// <inheritdoc />
 461        /// TODO refactor this to use the new SetItemByNameInfo.
 462        /// Some callers already have the counts extracted so no reason to retrieve them again.
 463        public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem>? taggedItems, User? user =
 464        {
 0465            var dto = GetBaseItemDtoInternal(item, options, user);
 466
 0467            if (options.ContainsField(ItemFields.ItemCounts)
 0468                && taggedItems is not null
 0469                && taggedItems.Count != 0)
 470            {
 0471                SetItemByNameInfo(item, dto, taggedItems);
 472            }
 473
 0474            return dto;
 475        }
 476
 477        private void SetItemByNameInfo(BaseItemDto dto, User? user)
 478        {
 6479            if (!_relatedItemKinds.TryGetValue(dto.Type, out var relatedItemKinds))
 480            {
 6481                return;
 482            }
 483
 0484            var counts = _libraryManager.GetItemCountsForNameItem(dto.Type, dto.Id, relatedItemKinds, user);
 485
 0486            dto.AlbumCount = counts.AlbumCount;
 0487            dto.ArtistCount = counts.ArtistCount;
 0488            dto.EpisodeCount = counts.EpisodeCount;
 0489            dto.MovieCount = counts.MovieCount;
 0490            dto.MusicVideoCount = counts.MusicVideoCount;
 0491            dto.ProgramCount = counts.ProgramCount;
 0492            dto.SeriesCount = counts.SeriesCount;
 0493            dto.SongCount = counts.SongCount;
 0494            dto.TrailerCount = counts.TrailerCount;
 0495            dto.ChildCount = counts.TotalItemCount();
 0496        }
 497
 498        private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IReadOnlyList<BaseItem> taggedItems)
 499        {
 0500            if (item is MusicArtist)
 501            {
 0502                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0503                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0504                dto.SongCount = taggedItems.Count(i => i is Audio);
 505            }
 0506            else if (item is MusicGenre)
 507            {
 0508                dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
 0509                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0510                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0511                dto.SongCount = taggedItems.Count(i => i is Audio);
 512            }
 513            else
 514            {
 515                // This populates them all and covers Genre, Person, Studio, Year
 516
 0517                dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
 0518                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0519                dto.EpisodeCount = taggedItems.Count(i => i is Episode);
 0520                dto.MovieCount = taggedItems.Count(i => i is Movie);
 0521                dto.TrailerCount = taggedItems.Count(i => i is Trailer);
 0522                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0523                dto.SeriesCount = taggedItems.Count(i => i is Series);
 0524                dto.ProgramCount = taggedItems.Count(i => i is LiveTvProgram);
 0525                dto.SongCount = taggedItems.Count(i => i is Audio);
 526            }
 527
 0528            dto.ChildCount = taggedItems.Count;
 0529        }
 530
 531        /// <summary>
 532        /// Attaches the user specific info.
 533        /// </summary>
 534        private void AttachUserSpecificInfo(
 535            BaseItemDto dto,
 536            BaseItem item,
 537            User user,
 538            DtoOptions options,
 539            UserItemData? userData = null,
 540            Dictionary<Guid, int>? childCountBatch = null,
 541            Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null)
 542        {
 9543            if (item.IsFolder)
 544            {
 9545                var folder = (Folder)item;
 546
 9547                if (options.EnableUserData)
 548                {
 9549                    if (userData is not null)
 550                    {
 551                        // Use pre-fetched user data
 3552                        dto.UserData = GetUserItemDataDto(userData, item.Id);
 3553                        (int Played, int Total)? precomputed = playedCountBatch is not null
 3554                            && playedCountBatch.TryGetValue(item.Id, out var counts) ? counts : null;
 3555                        item.FillUserDataDtoValues(dto.UserData, userData, dto, user, options, precomputed);
 556                    }
 557                    else
 558                    {
 559                        // Fall back to individual fetch
 6560                        dto.UserData = _userDataRepository.GetUserDataDto(item, dto, user, options);
 561                    }
 562                }
 563
 9564                if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library)
 565                {
 566                    // For these types we can try to optimize and assume these values will be equal
 9567                    if (item is MusicAlbum || item is Season || item is Playlist)
 568                    {
 0569                        dto.ChildCount = dto.RecursiveItemCount;
 0570                        var folderChildCount = folder.LinkedChildren.Length;
 571                        // The default is an empty array, so we can't reliably use the count when it's empty
 0572                        if (folderChildCount > 0)
 573                        {
 0574                            dto.ChildCount ??= folderChildCount;
 575                        }
 576                    }
 577
 9578                    if (options.ContainsField(ItemFields.ChildCount))
 579                    {
 6580                        dto.ChildCount ??= GetChildCount(folder, user, childCountBatch);
 581                    }
 582                }
 583
 9584                if (options.ContainsField(ItemFields.CumulativeRunTimeTicks))
 585                {
 6586                    dto.CumulativeRunTimeTicks = item.RunTimeTicks;
 587                }
 588
 9589                if (options.ContainsField(ItemFields.DateLastMediaAdded))
 590                {
 6591                    dto.DateLastMediaAdded = folder.DateLastMediaAdded;
 592                }
 593            }
 594            else
 595            {
 0596                if (options.EnableUserData)
 597                {
 0598                    if (userData is not null)
 599                    {
 600                        // Use pre-fetched user data
 0601                        dto.UserData = GetUserItemDataDto(userData, item.Id);
 0602                        item.FillUserDataDtoValues(dto.UserData, userData, dto, user, options);
 603                    }
 604                    else
 605                    {
 606                        // Fall back to individual fetch
 0607                        dto.UserData = _userDataRepository.GetUserDataDto(item, user);
 608                    }
 609                }
 610            }
 611
 9612            if (options.ContainsField(ItemFields.PlayAccess))
 613            {
 6614                dto.PlayAccess = item.GetPlayAccess(user);
 615            }
 9616        }
 617
 618        private static UserItemDataDto GetUserItemDataDto(UserItemData data, Guid itemId)
 619        {
 3620            ArgumentNullException.ThrowIfNull(data);
 621
 3622            return new UserItemDataDto
 3623            {
 3624                IsFavorite = data.IsFavorite,
 3625                Likes = data.Likes,
 3626                PlaybackPositionTicks = data.PlaybackPositionTicks,
 3627                PlayCount = data.PlayCount,
 3628                Rating = data.Rating,
 3629                Played = data.Played,
 3630                LastPlayedDate = data.LastPlayedDate,
 3631                ItemId = itemId,
 3632                Key = data.Key
 3633            };
 634        }
 635
 636        private static int GetChildCount(Folder folder, User user, Dictionary<Guid, int>? childCountBatch)
 637        {
 638            // Right now this is too slow to calculate for top level folders on a per-user basis
 639            // Just return something so that apps that are expecting a value won't think the folders are empty
 6640            if (folder is ICollectionFolder || folder is UserView)
 641            {
 0642                return Random.Shared.Next(1, 10);
 643            }
 644
 645            // Use pre-fetched batch data if available
 6646            if (childCountBatch is not null && childCountBatch.TryGetValue(folder.Id, out var count))
 647            {
 0648                return count;
 649            }
 650
 651            // Fall back to individual query for special cases (Series, Season, etc.)
 6652            return folder.GetChildCount(user);
 653        }
 654
 655        private static void SetBookProperties(BaseItemDto dto, Book item)
 656        {
 0657            dto.SeriesName = item.SeriesName;
 0658        }
 659
 660        private static void SetPhotoProperties(BaseItemDto dto, Photo item)
 661        {
 0662            dto.CameraMake = item.CameraMake;
 0663            dto.CameraModel = item.CameraModel;
 0664            dto.Software = item.Software;
 0665            dto.ExposureTime = item.ExposureTime;
 0666            dto.FocalLength = item.FocalLength;
 0667            dto.ImageOrientation = item.Orientation;
 0668            dto.Aperture = item.Aperture;
 0669            dto.ShutterSpeed = item.ShutterSpeed;
 670
 0671            dto.Latitude = item.Latitude;
 0672            dto.Longitude = item.Longitude;
 0673            dto.Altitude = item.Altitude;
 0674            dto.IsoSpeedRating = item.IsoSpeedRating;
 675
 0676            var album = item.AlbumEntity;
 677
 0678            if (album is not null)
 679            {
 0680                dto.Album = album.Name;
 0681                dto.AlbumId = album.Id;
 682            }
 0683        }
 684
 685        private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item)
 686        {
 0687            if (!string.IsNullOrEmpty(item.Album))
 688            {
 0689                var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery
 0690                {
 0691                    IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
 0692                    Name = item.Album,
 0693                    Limit = 1
 0694                });
 695
 0696                if (parentAlbumIds.Count > 0)
 697                {
 0698                    dto.AlbumId = parentAlbumIds[0];
 699                }
 700            }
 701
 0702            dto.Album = item.Album;
 0703        }
 704
 705        private string[] GetImageTags(BaseItem item, List<ItemImageInfo> images)
 706        {
 14707            return images
 14708                .Select(p => GetImageCacheTag(item, p))
 14709                .Where(i => i is not null)
 14710                .ToArray()!; // null values got filtered out
 711        }
 712
 713        private string? GetImageCacheTag(BaseItem item, ItemImageInfo image)
 714        {
 715            try
 716            {
 10717                return _imageProcessor.GetImageCacheTag(item, image);
 718            }
 0719            catch (Exception ex)
 720            {
 0721                _logger.LogError(ex, "Error getting {ImageType} image info for {Path}", image.Type, image.Path);
 0722                return null;
 723            }
 10724        }
 725
 726        /// <summary>
 727        /// Attaches People DTO's to a DTOBaseItem.
 728        /// </summary>
 729        /// <param name="dto">The dto.</param>
 730        /// <param name="item">The item.</param>
 731        /// <param name="user">The requesting user.</param>
 732        private void AttachPeople(BaseItemDto dto, BaseItem item, User? user = null)
 733        {
 734            // Ordering by person type to ensure actors and artists are at the front.
 735            // This is taking advantage of the fact that they both begin with A
 736            // This should be improved in the future
 6737            var people = _libraryManager.GetPeople(item).OrderBy(i => i.SortOrder ?? int.MaxValue)
 6738                .ThenBy(i =>
 6739                {
 6740                    if (i.IsType(PersonKind.Actor))
 6741                    {
 6742                        return 0;
 6743                    }
 6744
 6745                    if (i.IsType(PersonKind.GuestStar))
 6746                    {
 6747                        return 1;
 6748                    }
 6749
 6750                    if (i.IsType(PersonKind.Director))
 6751                    {
 6752                        return 2;
 6753                    }
 6754
 6755                    if (i.IsType(PersonKind.Writer))
 6756                    {
 6757                        return 3;
 6758                    }
 6759
 6760                    if (i.IsType(PersonKind.Producer))
 6761                    {
 6762                        return 4;
 6763                    }
 6764
 6765                    if (i.IsType(PersonKind.Composer))
 6766                    {
 6767                        return 4;
 6768                    }
 6769
 6770                    return 10;
 6771                })
 6772                .ToList();
 773
 6774            var list = new List<BaseItemPerson>();
 775
 6776            Dictionary<string, Person> dictionary = people.Select(p => p.Name)
 6777                .Distinct(StringComparer.OrdinalIgnoreCase).Select(c =>
 6778                {
 6779                    try
 6780                    {
 6781                        return _libraryManager.GetPerson(c);
 6782                    }
 6783                    catch (Exception ex)
 6784                    {
 6785                        _logger.LogError(ex, "Error getting person {Name}", c);
 6786                        return null;
 6787                    }
 6788                }).Where(i => i is not null)
 6789                .Where(i => user is null || i!.IsVisible(user))
 6790                .DistinctBy(x => x!.Name, StringComparer.OrdinalIgnoreCase)
 6791                .ToDictionary(i => i!.Name, StringComparer.OrdinalIgnoreCase)!; // null values got filtered out
 792
 12793            for (var i = 0; i < people.Count; i++)
 794            {
 0795                var person = people[i];
 796
 0797                var baseItemPerson = new BaseItemPerson
 0798                {
 0799                    Name = person.Name,
 0800                    Role = person.Role,
 0801                    Type = person.Type
 0802                };
 803
 0804                if (dictionary.TryGetValue(person.Name, out Person? entity))
 805                {
 0806                    baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
 0807                    baseItemPerson.Id = entity.Id;
 0808                    if (dto.ImageBlurHashes is not null)
 809                    {
 810                        // Only add BlurHash for the person's image.
 0811                        baseItemPerson.ImageBlurHashes = [];
 0812                        foreach (var (imageType, blurHash) in dto.ImageBlurHashes)
 813                        {
 0814                            if (blurHash is not null)
 815                            {
 0816                                baseItemPerson.ImageBlurHashes[imageType] = [];
 0817                                foreach (var (imageId, blurHashValue) in blurHash)
 818                                {
 0819                                    if (string.Equals(baseItemPerson.PrimaryImageTag, imageId, StringComparison.OrdinalI
 820                                    {
 0821                                        baseItemPerson.ImageBlurHashes[imageType][imageId] = blurHashValue;
 822                                    }
 823                                }
 824                            }
 825                        }
 826                    }
 827
 0828                    list.Add(baseItemPerson);
 829                }
 830            }
 831
 6832            dto.People = list.ToArray();
 6833        }
 834
 835        /// <summary>
 836        /// Attaches the studios.
 837        /// </summary>
 838        /// <param name="dto">The dto.</param>
 839        /// <param name="item">The item.</param>
 840        private void AttachStudios(BaseItemDto dto, BaseItem item)
 841        {
 6842            dto.Studios = item.Studios
 6843                .Where(i => !string.IsNullOrEmpty(i))
 6844                .Select(i => new NameGuidPair
 6845                {
 6846                    Name = i,
 6847                    Id = _libraryManager.GetStudioId(i)
 6848                })
 6849                .ToArray();
 6850        }
 851
 852        private void AttachGenreItems(BaseItemDto dto, BaseItem item)
 853        {
 6854            dto.GenreItems = item.Genres
 6855                .Where(i => !string.IsNullOrEmpty(i))
 6856                .Select(i => new NameGuidPair
 6857                {
 6858                    Name = i,
 6859                    Id = GetGenreId(i, item)
 6860                })
 6861                .ToArray();
 6862        }
 863
 864        private Guid GetGenreId(string name, BaseItem owner)
 865        {
 0866            if (owner is IHasMusicGenres)
 867            {
 0868                return _libraryManager.GetMusicGenreId(name);
 869            }
 870
 0871            return _libraryManager.GetGenreId(name);
 872        }
 873
 874        private string? GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ImageType imageType, int imageIndex = 0)
 875        {
 5876            var image = item.GetImageInfo(imageType, imageIndex);
 5877            if (image is not null)
 878            {
 4879                return GetTagAndFillBlurhash(dto, item, image);
 880            }
 881
 1882            return null;
 883        }
 884
 885        private string? GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ItemImageInfo image)
 886        {
 10887            var tag = GetImageCacheTag(item, image);
 10888            if (tag is null)
 889            {
 0890                return null;
 891            }
 892
 10893            if (!string.IsNullOrEmpty(image.BlurHash))
 894            {
 0895                dto.ImageBlurHashes ??= [];
 896
 0897                if (!dto.ImageBlurHashes.TryGetValue(image.Type, out var value))
 898                {
 0899                    value = [];
 0900                    dto.ImageBlurHashes[image.Type] = value;
 901                }
 902
 0903                value[tag] = image.BlurHash;
 904            }
 905
 10906            return tag;
 907        }
 908
 909        private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, int limit)
 910        {
 14911            return GetTagsAndFillBlurhashes(dto, item, imageType, item.GetImages(imageType).Take(limit).ToList());
 912        }
 913
 914        private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, List<ItemImageInf
 915        {
 14916            var tags = GetImageTags(item, images);
 14917            var hashes = new Dictionary<string, string>();
 28918            for (int i = 0; i < images.Count; i++)
 919            {
 0920                var img = images[i];
 0921                if (!string.IsNullOrEmpty(img.BlurHash))
 922                {
 0923                    var tag = tags[i];
 0924                    hashes[tag] = img.BlurHash;
 925                }
 926            }
 927
 14928            if (hashes.Count > 0)
 929            {
 0930                dto.ImageBlurHashes ??= [];
 931
 0932                dto.ImageBlurHashes[imageType] = hashes;
 933            }
 934
 14935            return tags;
 936        }
 937
 938        /// <summary>
 939        /// Sets simple property values on a DTOBaseItem.
 940        /// </summary>
 941        /// <param name="dto">The dto.</param>
 942        /// <param name="item">The item.</param>
 943        /// <param name="owner">The owner.</param>
 944        /// <param name="options">The options.</param>
 945        /// <param name="artistsBatch">Optional pre-fetched artist lookup shared across a batch of items.</param>
 946        private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem? owner, DtoOptions options, IReadOnlyDic
 947        {
 14948            if (options.ContainsField(ItemFields.DateCreated))
 949            {
 6950                dto.DateCreated = item.DateCreated;
 951            }
 952
 14953            if (options.ContainsField(ItemFields.Settings))
 954            {
 6955                dto.LockedFields = item.LockedFields;
 6956                dto.LockData = item.IsLocked;
 6957                dto.ForcedSortName = item.ForcedSortName;
 958            }
 959
 14960            dto.Container = item.Container;
 14961            dto.EndDate = item.EndDate;
 962
 14963            if (options.ContainsField(ItemFields.ExternalUrls))
 964            {
 6965                dto.ExternalUrls = _providerManager.GetExternalUrls(item).ToArray();
 966            }
 967
 14968            if (options.ContainsField(ItemFields.Tags))
 969            {
 6970                dto.Tags = item.Tags;
 971            }
 972
 14973            if (item is IHasAspectRatio hasAspectRatio)
 974            {
 3975                dto.AspectRatio = hasAspectRatio.AspectRatio;
 976            }
 977
 14978            dto.ImageBlurHashes = [];
 979
 14980            var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
 14981            if (backdropLimit > 0)
 982            {
 14983                dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit);
 984            }
 985
 14986            if (options.ContainsField(ItemFields.Genres))
 987            {
 6988                dto.Genres = item.Genres;
 6989                AttachGenreItems(dto, item);
 990            }
 991
 14992            if (options.EnableImages)
 993            {
 14994                dto.ImageTags = [];
 995
 996                // Prevent implicitly captured closure
 14997                var currentItem = item;
 38998                foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)))
 999                {
 51000                    if (options.GetImageLimit(image.Type) > 0)
 1001                    {
 51002                        var tag = GetTagAndFillBlurhash(dto, item, image);
 1003
 51004                        if (tag is not null)
 1005                        {
 51006                            dto.ImageTags[image.Type] = tag;
 1007                        }
 1008                    }
 1009                }
 1010            }
 1011
 141012            dto.Id = item.Id;
 141013            dto.IndexNumber = item.IndexNumber;
 141014            dto.ParentIndexNumber = item.ParentIndexNumber;
 1015
 141016            if (item.IsFolder)
 1017            {
 111018                dto.IsFolder = true;
 1019            }
 31020            else if (item is IHasMediaSources)
 1021            {
 31022                dto.IsFolder = false;
 1023            }
 1024
 141025            dto.MediaType = item.MediaType;
 1026
 141027            if (item is not LiveTvProgram)
 1028            {
 141029                dto.LocationType = item.LocationType;
 1030            }
 1031
 141032            dto.Audio = item.Audio;
 1033
 141034            if (options.ContainsField(ItemFields.Settings))
 1035            {
 61036                dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
 61037                dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
 1038            }
 1039
 141040            dto.CriticRating = item.CriticRating;
 1041
 141042            if (item is IHasDisplayOrder hasDisplayOrder)
 1043            {
 01044                dto.DisplayOrder = hasDisplayOrder.DisplayOrder;
 1045            }
 1046
 141047            if (item is IHasCollectionType hasCollectionType)
 1048            {
 51049                dto.CollectionType = hasCollectionType.CollectionType;
 1050            }
 1051
 141052            if (options.ContainsField(ItemFields.RemoteTrailers))
 1053            {
 61054                dto.RemoteTrailers = item.RemoteTrailers;
 1055            }
 1056
 141057            dto.Name = item.Name;
 141058            dto.OfficialRating = item.OfficialRating;
 1059
 141060            if (options.ContainsField(ItemFields.Overview))
 1061            {
 61062                dto.Overview = item.Overview;
 1063            }
 1064
 141065            if (options.ContainsField(ItemFields.OriginalTitle))
 1066            {
 61067                dto.OriginalTitle = item.OriginalTitle;
 1068            }
 1069
 141070            dto.OriginalLanguage = item.OriginalLanguage;
 1071
 141072            if (options.ContainsField(ItemFields.ParentId))
 1073            {
 61074                dto.ParentId = item.DisplayParentId;
 1075            }
 1076
 141077            AddInheritedImages(dto, item, options, owner);
 1078
 141079            if (options.ContainsField(ItemFields.Path))
 1080            {
 61081                dto.Path = GetMappedPath(item, owner);
 1082            }
 1083
 141084            if (options.ContainsField(ItemFields.EnableMediaSourceDisplay))
 1085            {
 61086                dto.EnableMediaSourceDisplay = item.EnableMediaSourceDisplay;
 1087            }
 1088
 141089            dto.PremiereDate = item.PremiereDate;
 141090            dto.ProductionYear = item.ProductionYear;
 1091
 141092            if (options.ContainsField(ItemFields.ProviderIds))
 1093            {
 61094                dto.ProviderIds = item.ProviderIds;
 1095            }
 1096
 141097            dto.RunTimeTicks = item.RunTimeTicks;
 1098
 141099            if (options.ContainsField(ItemFields.SortName))
 1100            {
 61101                dto.SortName = item.SortName;
 1102            }
 1103
 141104            if (options.ContainsField(ItemFields.CustomRating))
 1105            {
 61106                dto.CustomRating = item.CustomRating;
 1107            }
 1108
 141109            if (options.ContainsField(ItemFields.Taglines))
 1110            {
 61111                if (!string.IsNullOrEmpty(item.Tagline))
 1112                {
 01113                    dto.Taglines = new string[] { item.Tagline };
 1114                }
 1115
 61116                dto.Taglines ??= Array.Empty<string>();
 1117            }
 1118
 141119            dto.Type = item.GetBaseItemKind();
 141120            if ((item.CommunityRating ?? 0) > 0)
 1121            {
 01122                dto.CommunityRating = item.CommunityRating;
 1123            }
 1124
 141125            if (item is ISupportsPlaceHolders supportsPlaceHolders && supportsPlaceHolders.IsPlaceHolder)
 1126            {
 01127                dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder;
 1128            }
 1129
 141130            if (item.LUFS.HasValue)
 1131            {
 1132                // -18 LUFS reference, same as ReplayGain 2.0, compatible with ReplayGain 1.0
 01133                dto.NormalizationGain = -18f - item.LUFS;
 1134            }
 141135            else if (item.NormalizationGain.HasValue)
 1136            {
 01137                dto.NormalizationGain = item.NormalizationGain;
 1138            }
 1139
 1140            // Add audio info
 141141            if (item is Audio audio)
 1142            {
 01143                dto.Album = audio.Album;
 01144                dto.ExtraType = audio.ExtraType;
 1145
 01146                var albumParent = audio.AlbumEntity;
 1147
 01148                if (albumParent is not null)
 1149                {
 01150                    dto.AlbumId = albumParent.Id;
 01151                    dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary);
 01152                    if (albumParent.LUFS.HasValue)
 1153                    {
 1154                        // -18 LUFS reference, same as ReplayGain 2.0, compatible with ReplayGain 1.0
 01155                        dto.AlbumNormalizationGain = -18f - albumParent.LUFS;
 1156                    }
 01157                    else if (albumParent.NormalizationGain.HasValue)
 1158                    {
 01159                        dto.AlbumNormalizationGain = albumParent.NormalizationGain;
 1160                    }
 1161                }
 1162
 1163                // if (options.ContainsField(ItemFields.MediaSourceCount))
 1164                // {
 1165                // Songs always have one
 1166                // }
 1167            }
 1168
 141169            if (item is IHasArtist hasArtist)
 1170            {
 01171                dto.Artists = hasArtist.Artists;
 1172
 1173                // var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
 1174                // {
 1175                //    EnableTotalRecordCount = false,
 1176                //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
 1177                // });
 1178
 1179                // dto.ArtistItems = artistItems.Items
 1180                //    .Select(i =>
 1181                //    {
 1182                //        var artist = i.Item1;
 1183                //        return new NameIdPair
 1184                //        {
 1185                //            Name = artist.Name,
 1186                //            Id = artist.Id.ToString("N", CultureInfo.InvariantCulture)
 1187                //        };
 1188                //    })
 1189                //    .ToList();
 1190
 1191                // Include artists that are not in the database yet, e.g., just added via metadata editor
 1192                // var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
 01193                var artistsLookup = artistsBatch
 01194                    ?? _libraryManager.GetArtists([.. hasArtist.Artists.Where(e => !string.IsNullOrWhiteSpace(e))]);
 1195
 01196                dto.ArtistItems = hasArtist.Artists
 01197                    .Where(name => !string.IsNullOrWhiteSpace(name))
 01198                    .Distinct()
 01199                    .Select(name => artistsLookup.TryGetValue(name, out var artists) && artists.Length > 0
 01200                        ? new NameGuidPair { Name = name, Id = artists[0].Id }
 01201                        : null)
 01202                    .Where(item => item is not null)
 01203                    .ToArray();
 1204            }
 1205
 141206            if (item is IHasAlbumArtist hasAlbumArtist)
 1207            {
 01208                dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
 1209
 1210                // var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
 1211                // {
 1212                //    EnableTotalRecordCount = false,
 1213                //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
 1214                // });
 1215
 1216                // dto.AlbumArtists = artistItems.Items
 1217                //    .Select(i =>
 1218                //    {
 1219                //        var artist = i.Item1;
 1220                //        return new NameIdPair
 1221                //        {
 1222                //            Name = artist.Name,
 1223                //            Id = artist.Id.ToString("N", CultureInfo.InvariantCulture)
 1224                //        };
 1225                //    })
 1226                //    .ToList();
 1227
 01228                var albumArtistsLookup = artistsBatch
 01229                    ?? _libraryManager.GetArtists([.. hasAlbumArtist.AlbumArtists.Where(e => !string.IsNullOrWhiteSpace(
 1230
 01231                dto.AlbumArtists = hasAlbumArtist.AlbumArtists
 01232                    .Where(name => !string.IsNullOrWhiteSpace(name))
 01233                    .Distinct()
 01234                    .Select(name => albumArtistsLookup.TryGetValue(name, out var albumArtists) && albumArtists.Length > 
 01235                        ? new NameGuidPair { Name = name, Id = albumArtists[0].Id }
 01236                        : null)
 01237                    .Where(item => item is not null)
 01238                    .ToArray();
 1239            }
 1240
 1241            // Add video info
 141242            if (item is Video video)
 1243            {
 31244                dto.VideoType = video.VideoType;
 31245                dto.Video3DFormat = video.Video3DFormat;
 31246                dto.IsoType = video.IsoType;
 1247
 31248                if (video.HasSubtitles)
 1249                {
 01250                    dto.HasSubtitles = video.HasSubtitles;
 1251                }
 1252
 31253                if (video.AdditionalParts.Length != 0)
 1254                {
 01255                    dto.PartCount = video.AdditionalParts.Length + 1;
 1256                }
 1257
 31258                if (options.ContainsField(ItemFields.MediaSourceCount))
 1259                {
 01260                    var mediaSourceCount = video.MediaSourceCount;
 01261                    if (mediaSourceCount != 1)
 1262                    {
 01263                        dto.MediaSourceCount = mediaSourceCount;
 1264                    }
 1265                }
 1266
 31267                if (options.ContainsField(ItemFields.Trickplay))
 1268                {
 01269                    var trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult();
 01270                    dto.Trickplay = trickplay.ToDictionary(
 01271                        mediaStream => mediaStream.Key,
 01272                        mediaStream => mediaStream.Value.ToDictionary(
 01273                            width => width.Key,
 01274                            width => new TrickplayInfoDto(width.Value)));
 1275                }
 1276
 31277                dto.ExtraType = video.ExtraType;
 1278            }
 1279
 141280            if (options.ContainsField(ItemFields.Chapters))
 1281            {
 61282                dto.Chapters = _chapterManager.GetChapters(item.Id).ToList();
 1283            }
 1284
 141285            if (options.ContainsField(ItemFields.MediaStreams))
 1286            {
 1287                // Add VideoInfo
 61288                if (item is IHasMediaSources)
 1289                {
 1290                    MediaStream[] mediaStreams;
 1291
 01292                    if (dto.MediaSources is not null && dto.MediaSources.Length > 0)
 1293                    {
 01294                        if (item.SourceType == SourceType.Channel)
 1295                        {
 01296                            mediaStreams = dto.MediaSources[0].MediaStreams.ToArray();
 1297                        }
 1298                        else
 1299                        {
 01300                            string id = item.Id.ToString("N", CultureInfo.InvariantCulture);
 01301                            mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, id, StringComparison.OrdinalI
 01302                                .SelectMany(i => i.MediaStreams)
 01303                                .ToArray();
 1304                        }
 1305                    }
 1306                    else
 1307                    {
 01308                        mediaStreams = _mediaSourceManager.GetStaticMediaSources(item, true)[0].MediaStreams.ToArray();
 1309                    }
 1310
 01311                    dto.MediaStreams = mediaStreams;
 1312                }
 1313            }
 1314
 141315            BaseItem[]? allExtras = null;
 1316
 141317            if (options.ContainsField(ItemFields.SpecialFeatureCount))
 1318            {
 61319                allExtras = item.GetExtras().ToArray();
 61320                dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contai
 1321            }
 1322
 141323            if (options.ContainsField(ItemFields.LocalTrailerCount))
 1324            {
 61325                if (item is IHasTrailers hasTrailers)
 1326                {
 01327                    dto.LocalTrailerCount = hasTrailers.LocalTrailers.Count;
 1328                }
 1329                else
 1330                {
 61331                    dto.LocalTrailerCount = (allExtras ?? item.GetExtras()).Count(i => i.ExtraType == ExtraType.Trailer)
 1332                }
 1333            }
 1334
 1335            // Add EpisodeInfo
 141336            if (item is Episode episode)
 1337            {
 31338                dto.IndexNumberEnd = episode.IndexNumberEnd;
 31339                dto.SeriesName = episode.SeriesName;
 1340
 31341                if (options.ContainsField(ItemFields.SpecialEpisodeNumbers))
 1342                {
 01343                    dto.AirsAfterSeasonNumber = episode.AirsAfterSeasonNumber;
 01344                    dto.AirsBeforeEpisodeNumber = episode.AirsBeforeEpisodeNumber;
 01345                    dto.AirsBeforeSeasonNumber = episode.AirsBeforeSeasonNumber;
 1346                }
 1347
 31348                dto.SeasonName = episode.SeasonName;
 31349                dto.SeasonId = episode.SeasonId;
 31350                dto.SeriesId = episode.SeriesId;
 1351
 31352                Series? episodeSeries = null;
 1353
 1354                // this block will add the series poster for episodes without a poster
 1355                // TODO maybe remove the if statement entirely
 1356                // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
 1357                {
 31358                    episodeSeries ??= episode.Series;
 31359                    if (episodeSeries is not null)
 1360                    {
 31361                        dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
 31362                        if (dto.ImageTags is null || !dto.ImageTags.ContainsKey(ImageType.Primary))
 1363                        {
 01364                            AttachPrimaryImageAspectRatio(dto, episodeSeries);
 1365                        }
 1366                    }
 1367                }
 1368
 31369                if (options.PreferEpisodeParentPoster)
 1370                {
 21371                    var episodeSeason = episode.Season;
 21372                    var seasonPrimaryTag = episodeSeason is not null
 21373                        ? GetTagAndFillBlurhash(dto, episodeSeason, ImageType.Primary)
 21374                        : null;
 1375
 21376                    BaseItem? posterParent = null;
 21377                    if (seasonPrimaryTag is not null)
 1378                    {
 11379                        dto.ParentPrimaryImageItemId = episodeSeason!.Id;
 11380                        dto.ParentPrimaryImageTag = seasonPrimaryTag;
 11381                        posterParent = episodeSeason;
 1382                    }
 11383                    else if (episodeSeries is not null && dto.SeriesPrimaryImageTag is not null)
 1384                    {
 11385                        dto.ParentPrimaryImageItemId = episodeSeries.Id;
 11386                        dto.ParentPrimaryImageTag = dto.SeriesPrimaryImageTag;
 11387                        posterParent = episodeSeries;
 1388                    }
 1389
 21390                    if (posterParent is not null)
 1391                    {
 21392                        if (dto.ImageTags is not null && dto.ImageTags.Remove(ImageType.Primary, out var ownPrimaryTag))
 1393                        {
 1394                            // Only drop the episode's own primary blurhash; keep the poster parent's.
 21395                            dto.ImageBlurHashes?.GetValueOrDefault(ImageType.Primary)?.Remove(ownPrimaryTag);
 1396                        }
 1397
 21398                        dto.SeriesPrimaryImageTag = null;
 21399                        dto.PrimaryImageAspectRatio = null;
 21400                        AttachPrimaryImageAspectRatio(dto, posterParent);
 1401                    }
 1402                }
 1403
 31404                if (options.ContainsField(ItemFields.SeriesStudio))
 1405                {
 01406                    episodeSeries ??= episode.Series;
 01407                    if (episodeSeries is not null)
 1408                    {
 01409                        dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
 1410                    }
 1411                }
 1412            }
 1413
 1414            // Add SeriesInfo
 1415            Series? series;
 141416            if (item is Series tmp)
 1417            {
 01418                series = tmp;
 01419                dto.AirDays = series.AirDays;
 01420                dto.AirTime = series.AirTime;
 01421                dto.Status = series.Status?.ToString();
 1422            }
 1423
 1424            // Add SeasonInfo
 141425            if (item is Season season)
 1426            {
 01427                dto.SeriesName = season.SeriesName;
 01428                dto.SeriesId = season.SeriesId;
 1429
 01430                series = null;
 1431
 01432                if (options.ContainsField(ItemFields.SeriesStudio))
 1433                {
 01434                    series ??= season.Series;
 01435                    if (series is not null)
 1436                    {
 01437                        dto.SeriesStudio = series.Studios.FirstOrDefault();
 1438                    }
 1439                }
 1440
 1441                // this block will add the series poster for seasons without a poster
 1442                // TODO maybe remove the if statement entirely
 1443                // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
 1444                {
 01445                    series ??= season.Series;
 01446                    if (series is not null)
 1447                    {
 01448                        dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
 01449                        if (dto.ImageTags is null || !dto.ImageTags.ContainsKey(ImageType.Primary))
 1450                        {
 01451                            AttachPrimaryImageAspectRatio(dto, series);
 1452                        }
 1453                    }
 1454                }
 1455            }
 1456
 141457            if (item is MusicVideo musicVideo)
 1458            {
 01459                SetMusicVideoProperties(dto, musicVideo);
 1460            }
 1461
 141462            if (item is Book book)
 1463            {
 01464                SetBookProperties(dto, book);
 1465            }
 1466
 141467            if (options.ContainsField(ItemFields.ProductionLocations))
 1468            {
 61469                if (item.ProductionLocations.Length > 0 || item is Movie)
 1470                {
 01471                    dto.ProductionLocations = item.ProductionLocations;
 1472                }
 1473            }
 1474
 141475            if (options.ContainsField(ItemFields.Width))
 1476            {
 61477                var width = item.Width;
 61478                if (width > 0)
 1479                {
 01480                    dto.Width = width;
 1481                }
 1482            }
 1483
 141484            if (options.ContainsField(ItemFields.Height))
 1485            {
 61486                var height = item.Height;
 61487                if (height > 0)
 1488                {
 01489                    dto.Height = height;
 1490                }
 1491            }
 1492
 141493            if (options.ContainsField(ItemFields.IsHD))
 1494            {
 1495                // Compatibility
 61496                if (item.IsHD)
 1497                {
 01498                    dto.IsHD = true;
 1499                }
 1500            }
 1501
 141502            if (item is Photo photo)
 1503            {
 01504                SetPhotoProperties(dto, photo);
 1505            }
 1506
 141507            dto.ChannelId = item.ChannelId;
 1508
 141509            if (item.SourceType == SourceType.Channel)
 1510            {
 01511                var channel = _libraryManager.GetItemById(item.ChannelId);
 01512                if (channel is not null)
 1513                {
 01514                    dto.ChannelName = channel.Name;
 1515                }
 1516            }
 141517        }
 1518
 1519        private BaseItem? GetImageDisplayParent(BaseItem currentItem, BaseItem originalItem)
 1520        {
 61521            if (currentItem is MusicAlbum musicAlbum)
 1522            {
 01523                var artist = musicAlbum.GetMusicArtist(new DtoOptions(false));
 01524                if (artist is not null)
 1525                {
 01526                    return artist;
 1527                }
 1528            }
 1529
 61530            var parent = currentItem.DisplayParent ?? currentItem.GetOwner() ?? currentItem.GetParent();
 1531
 61532            if (parent is null && originalItem is not UserRootFolder && originalItem is not UserView && originalItem is 
 1533            {
 01534                parent = _libraryManager.GetCollectionFolders(originalItem).FirstOrDefault();
 1535            }
 1536
 61537            return parent;
 1538        }
 1539
 1540        private void AddInheritedImages(BaseItemDto dto, BaseItem item, DtoOptions options, BaseItem? owner)
 1541        {
 141542            if (item is UserView { ViewType: CollectionType.playlists } playlistsView
 141543                && options.GetImageLimit(ImageType.Primary) > 0
 141544                && !playlistsView.DisplayParentId.IsEmpty())
 1545            {
 21546                var displayParent = _libraryManager.GetItemById(playlistsView.DisplayParentId);
 21547                var displayParentPrimaryImage = displayParent?.GetImageInfo(ImageType.Primary, 0);
 1548
 21549                if (displayParentPrimaryImage is not null)
 1550                {
 11551                    dto.ImageTags?.Remove(ImageType.Primary);
 11552                    dto.ParentPrimaryImageItemId = displayParent!.Id;
 11553                    dto.ParentPrimaryImageTag = GetTagAndFillBlurhash(dto, displayParent, displayParentPrimaryImage);
 1554                }
 1555            }
 1556
 141557            if (!item.SupportsInheritedParentImages)
 1558            {
 111559                return;
 1560            }
 1561
 31562            var logoLimit = options.GetImageLimit(ImageType.Logo);
 31563            var artLimit = options.GetImageLimit(ImageType.Art);
 31564            var thumbLimit = options.GetImageLimit(ImageType.Thumb);
 31565            var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
 1566
 1567            // For now. Emby apps are not using this
 31568            artLimit = 0;
 1569
 31570            if (logoLimit == 0 && artLimit == 0 && thumbLimit == 0 && backdropLimit == 0)
 1571            {
 01572                return;
 1573            }
 1574
 31575            BaseItem? parent = null;
 31576            var isFirst = true;
 1577
 31578            var imageTags = dto.ImageTags;
 1579
 61580            while ((!(imageTags is not null && imageTags.ContainsKey(ImageType.Logo)) && logoLimit > 0)
 61581                || (!(imageTags is not null && imageTags.ContainsKey(ImageType.Art)) && artLimit > 0)
 61582                || (!(imageTags is not null && imageTags.ContainsKey(ImageType.Thumb)) && thumbLimit > 0)
 61583                || parent is Series)
 1584            {
 61585                parent ??= isFirst ? GetImageDisplayParent(item, item) ?? owner : parent;
 61586                if (parent is null)
 1587                {
 1588                    break;
 1589                }
 1590
 61591                var allImages = parent.ImageInfos;
 1592
 61593                if (logoLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Logo)) && dto.ParentLogo
 1594                {
 61595                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Logo);
 1596
 61597                    if (image is not null)
 1598                    {
 01599                        dto.ParentLogoItemId = parent.Id;
 01600                        dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1601                    }
 1602                }
 1603
 61604                if (artLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtIte
 1605                {
 01606                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
 1607
 01608                    if (image is not null)
 1609                    {
 01610                        dto.ParentArtItemId = parent.Id;
 01611                        dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1612                    }
 1613                }
 1614
 61615                if (thumbLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentT
 1616                {
 61617                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
 1618
 61619                    if (image is not null)
 1620                    {
 01621                        dto.ParentThumbItemId = parent.Id;
 01622                        dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1623                    }
 1624                }
 1625
 61626                if (backdropLimit > 0 && !((dto.BackdropImageTags is not null && dto.BackdropImageTags.Length > 0) || (d
 1627                {
 61628                    var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
 1629
 61630                    if (images.Count > 0)
 1631                    {
 01632                        dto.ParentBackdropItemId = parent.Id;
 01633                        dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images);
 1634                    }
 1635                }
 1636
 61637                isFirst = false;
 1638
 61639                if (!parent.SupportsInheritedParentImages)
 1640                {
 1641                    break;
 1642                }
 1643
 31644                parent = GetImageDisplayParent(parent, item);
 1645            }
 31646        }
 1647
 1648        private string GetMappedPath(BaseItem item, BaseItem? ownerItem)
 1649        {
 61650            var path = item.Path;
 1651
 61652            if (item.IsFileProtocol)
 1653            {
 61654                path = _libraryManager.GetPathAfterNetworkSubstitution(path, ownerItem ?? item);
 1655            }
 1656
 61657            return path;
 1658        }
 1659
 1660        /// <summary>
 1661        /// Attaches the primary image aspect ratio.
 1662        /// </summary>
 1663        /// <param name="dto">The dto.</param>
 1664        /// <param name="item">The item.</param>
 1665        public void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item)
 1666        {
 81667            dto.PrimaryImageAspectRatio = GetPrimaryImageAspectRatio(item);
 81668        }
 1669
 1670        public double? GetPrimaryImageAspectRatio(BaseItem item)
 1671        {
 81672            var imageInfo = item.GetImageInfo(ImageType.Primary, 0);
 1673
 81674            if (imageInfo is null)
 1675            {
 61676                return null;
 1677            }
 1678
 21679            if (!imageInfo.IsLocalFile)
 1680            {
 21681                return item.GetDefaultPrimaryImageAspectRatio();
 1682            }
 1683
 1684            try
 1685            {
 01686                var size = _imageProcessor.GetImageDimensions(item, imageInfo);
 01687                var width = size.Width;
 01688                var height = size.Height;
 01689                if (width > 0 && height > 0)
 1690                {
 01691                    return (double)width / height;
 1692                }
 01693            }
 01694            catch (Exception ex)
 1695            {
 01696                _logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path);
 01697            }
 1698
 01699            return item.GetDefaultPrimaryImageAspectRatio();
 01700        }
 1701    }
 1702}

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,System.Boolean)
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,MediaBrowser.Controller.Entities.UserItemData,System.Collections.Generic.List`1<MediaBrowser.Controller.Entities.Folder>,System.Collections.Generic.Dictionary`2<System.Guid,System.Int32>,System.Collections.Generic.Dictionary`2<System.Guid,System.ValueTuple`2<System.Int32,System.Int32>>,System.Collections.Generic.IReadOnlyDictionary`2<System.String,MediaBrowser.Controller.Entities.Audio.MusicArtist[]>)
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,MediaBrowser.Controller.Entities.UserItemData,System.Collections.Generic.Dictionary`2<System.Guid,System.Int32>,System.Collections.Generic.Dictionary`2<System.Guid,System.ValueTuple`2<System.Int32,System.Int32>>)
GetUserItemDataDto(MediaBrowser.Controller.Entities.UserItemData,System.Guid)
GetChildCount(MediaBrowser.Controller.Entities.Folder,Jellyfin.Database.Implementations.Entities.User,System.Collections.Generic.Dictionary`2<System.Guid,System.Int32>)
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,System.Collections.Generic.IReadOnlyDictionary`2<System.String,MediaBrowser.Controller.Entities.Audio.MusicArtist[]>)
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)