< 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: 520
Uncovered lines: 289
Coverable lines: 809
Total lines: 1721
Line coverage: 64.2%
Branch coverage
45%
Covered branches: 259
Total branches: 570
Branch coverage: 45.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 3/26/2026 - 12:14:14 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: 17026/30/2026 - 12:15:32 AM Line coverage: 64.4% (516/801) Branch coverage: 46.2% (258/558) Total lines: 17047/6/2026 - 12:16:28 AM Line coverage: 64.2% (520/809) Branch coverage: 45.4% (259/570) Total lines: 1721 3/26/2026 - 12:14:14 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: 17026/30/2026 - 12:15:32 AM Line coverage: 64.4% (516/801) Branch coverage: 46.2% (258/558) Total lines: 17047/6/2026 - 12:16:28 AM Line coverage: 64.2% (520/809) Branch coverage: 45.4% (259/570) Total lines: 1721

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.AudioBook,
 175                    BaseItemKind.Book,
 176                    BaseItemKind.Episode,
 177                    BaseItemKind.Movie,
 178                    BaseItemKind.LiveTvProgram,
 179                    BaseItemKind.MusicAlbum,
 180                    BaseItemKind.MusicArtist,
 181                    BaseItemKind.MusicVideo,
 182                    BaseItemKind.Series,
 183                    BaseItemKind.Trailer
 184                ]
 185            },
 186            {
 187                BaseItemKind.Studio, [
 188                    BaseItemKind.Audio,
 189                    BaseItemKind.Episode,
 190                    BaseItemKind.Movie,
 191                    BaseItemKind.LiveTvProgram,
 192                    BaseItemKind.MusicAlbum,
 193                    BaseItemKind.MusicArtist,
 194                    BaseItemKind.MusicVideo,
 195                    BaseItemKind.Series,
 196                    BaseItemKind.Trailer
 197                ]
 198            },
 199            {
 1100                BaseItemKind.Year, [
 1101                    BaseItemKind.Audio,
 1102                    BaseItemKind.Episode,
 1103                    BaseItemKind.Movie,
 1104                    BaseItemKind.LiveTvProgram,
 1105                    BaseItemKind.MusicAlbum,
 1106                    BaseItemKind.MusicArtist,
 1107                    BaseItemKind.MusicVideo,
 1108                    BaseItemKind.Series,
 1109                    BaseItemKind.Trailer
 1110                ]
 1111            }
 1112        }.ToFrozenDictionary();
 113
 114        private readonly ILogger<DtoService> _logger;
 115        private readonly ILibraryManager _libraryManager;
 116        private readonly IUserDataManager _userDataRepository;
 117
 118        private readonly IImageProcessor _imageProcessor;
 119        private readonly IProviderManager _providerManager;
 120        private readonly IRecordingsManager _recordingsManager;
 121
 122        private readonly IApplicationHost _appHost;
 123        private readonly IMediaSourceManager _mediaSourceManager;
 124        private readonly Lazy<ILiveTvManager> _livetvManagerFactory;
 125
 126        private readonly ITrickplayManager _trickplayManager;
 127        private readonly IChapterManager _chapterManager;
 128
 129        public DtoService(
 130            ILogger<DtoService> logger,
 131            ILibraryManager libraryManager,
 132            IUserDataManager userDataRepository,
 133            IImageProcessor imageProcessor,
 134            IProviderManager providerManager,
 135            IRecordingsManager recordingsManager,
 136            IApplicationHost appHost,
 137            IMediaSourceManager mediaSourceManager,
 138            Lazy<ILiveTvManager> livetvManagerFactory,
 139            ITrickplayManager trickplayManager,
 140            IChapterManager chapterManager)
 141        {
 27142            _logger = logger;
 27143            _libraryManager = libraryManager;
 27144            _userDataRepository = userDataRepository;
 27145            _imageProcessor = imageProcessor;
 27146            _providerManager = providerManager;
 27147            _recordingsManager = recordingsManager;
 27148            _appHost = appHost;
 27149            _mediaSourceManager = mediaSourceManager;
 27150            _livetvManagerFactory = livetvManagerFactory;
 27151            _trickplayManager = trickplayManager;
 27152            _chapterManager = chapterManager;
 27153        }
 154
 0155        private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
 156
 157        /// <inheritdoc />
 158        public IReadOnlyList<BaseItemDto> GetBaseItemDtos(
 159            IReadOnlyList<BaseItem> items,
 160            DtoOptions options,
 161            User? user = null,
 162            BaseItem? owner = null,
 163            bool skipVisibilityCheck = false)
 164        {
 4165            var accessibleItems = skipVisibilityCheck || user is null ? items : items.Where(x => x.IsVisible(user)).ToLi
 4166            var returnItems = new BaseItemDto[accessibleItems.Count];
 4167            List<(BaseItem, BaseItemDto)>? programTuples = null;
 4168            List<(BaseItemDto, LiveTvChannel)>? channelTuples = null;
 169
 170            // Batch-fetch user data for all items
 4171            Dictionary<Guid, UserItemData>? userDataBatch = null;
 4172            IReadOnlyDictionary<Guid, VersionResumeData>? resumeDataBatch = null;
 4173            if (user is not null && options.EnableUserData)
 174            {
 4175                userDataBatch = _userDataRepository.GetUserDataBatch(accessibleItems, user);
 176
 177                // For items with alternate versions, the most recently played version drives resume.
 4178                resumeDataBatch = _userDataRepository.GetResumeUserDataBatch(accessibleItems, user);
 179            }
 180
 181            // Pre-compute collection folders once to avoid N+1 queries in CanDelete
 4182            List<Folder>? allCollectionFolders = null;
 4183            if (user is not null && options.ContainsField(ItemFields.CanDelete))
 184            {
 0185                allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList();
 186            }
 187
 188            // Batch-fetch child counts for all folders to avoid N+1 queries
 4189            Dictionary<Guid, int>? childCountBatch = null;
 4190            if (options.ContainsField(ItemFields.ChildCount))
 191            {
 0192                var folderIds = accessibleItems.OfType<Folder>().Select(f => f.Id).ToList();
 0193                if (folderIds.Count > 0)
 194                {
 0195                    childCountBatch = _libraryManager.GetChildCountBatch(folderIds, user?.Id);
 196                }
 197            }
 198
 199            // Batch-fetch played/total counts for all folders to avoid N+1 queries
 4200            Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null;
 4201            if (user is not null && options.EnableUserData)
 202            {
 4203                var folderIds = accessibleItems.OfType<Folder>()
 4204                    .Where(f => f.SupportsUserDataFromChildren && (f.SupportsPlayedStatus || options.ContainsField(ItemF
 4205                    .Select(f => f.Id).ToList();
 4206                if (folderIds.Count > 0)
 207                {
 0208                    playedCountBatch = _libraryManager.GetPlayedAndTotalCountBatch(folderIds, user);
 209                }
 210            }
 211
 212            // Batch-fetch MusicArtist lookups across all items to avoid N+1 queries.
 4213            IReadOnlyDictionary<string, MusicArtist[]>? artistsBatch = null;
 4214            var artistNames = new HashSet<string>(StringComparer.Ordinal);
 14215            foreach (var item in accessibleItems)
 216            {
 3217                if (item is IHasArtist hasArtist)
 218                {
 0219                    foreach (var name in hasArtist.Artists)
 220                    {
 0221                        if (!string.IsNullOrWhiteSpace(name))
 222                        {
 0223                            artistNames.Add(name);
 224                        }
 225                    }
 226                }
 227
 3228                if (item is IHasAlbumArtist hasAlbumArtist)
 229                {
 0230                    foreach (var name in hasAlbumArtist.AlbumArtists)
 231                    {
 0232                        if (!string.IsNullOrWhiteSpace(name))
 233                        {
 0234                            artistNames.Add(name);
 235                        }
 236                    }
 237                }
 238            }
 239
 4240            if (artistNames.Count > 0)
 241            {
 0242                artistsBatch = _libraryManager.GetArtists(artistNames.ToArray());
 243            }
 244
 14245            for (int index = 0; index < accessibleItems.Count; index++)
 246            {
 3247                var item = accessibleItems[index];
 3248                var dto = GetBaseItemDtoInternal(
 3249                    item,
 3250                    options,
 3251                    user,
 3252                    owner,
 3253                    userDataBatch?.GetValueOrDefault(item.Id),
 3254                    allCollectionFolders,
 3255                    childCountBatch,
 3256                    playedCountBatch,
 3257                    artistsBatch,
 3258                    resumeDataBatch?.GetValueOrDefault(item.Id));
 259
 3260                if (item is LiveTvChannel tvChannel)
 261                {
 0262                    (channelTuples ??= []).Add((dto, tvChannel));
 263                }
 3264                else if (item is LiveTvProgram)
 265                {
 0266                    (programTuples ??= []).Add((item, dto));
 267                }
 268
 3269                if (options.ContainsField(ItemFields.ItemCounts))
 270                {
 0271                    SetItemByNameInfo(dto, user);
 272                }
 273
 3274                returnItems[index] = dto;
 275            }
 276
 4277            if (programTuples is not null)
 278            {
 0279                LivetvManager.AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
 280            }
 281
 4282            if (channelTuples is not null)
 283            {
 0284                LivetvManager.AddChannelInfo(channelTuples, options, user);
 285            }
 286
 4287            return returnItems;
 288        }
 289
 290        public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner = null)
 291        {
 11292            var dto = GetBaseItemDtoInternal(item, options, user, owner, null);
 11293            if (item is LiveTvChannel tvChannel)
 294            {
 0295                LivetvManager.AddChannelInfo(new[] { (dto, tvChannel) }, options, user);
 296            }
 11297            else if (item is LiveTvProgram)
 298            {
 0299                LivetvManager.AddInfoToProgramDto(new[] { (item, dto) }, options.Fields, user).GetAwaiter().GetResult();
 300            }
 301
 11302            if (options.ContainsField(ItemFields.ItemCounts))
 303            {
 6304                SetItemByNameInfo(dto, user);
 305            }
 306
 11307            return dto;
 308        }
 309
 310        private BaseItemDto GetBaseItemDtoInternal(
 311            BaseItem item,
 312            DtoOptions options,
 313            User? user = null,
 314            BaseItem? owner = null,
 315            UserItemData? userData = null,
 316            List<Folder>? allCollectionFolders = null,
 317            Dictionary<Guid, int>? childCountBatch = null,
 318            Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null,
 319            IReadOnlyDictionary<string, MusicArtist[]>? artistsBatch = null,
 320            VersionResumeData? resumeData = null)
 321        {
 14322            var dto = new BaseItemDto
 14323            {
 14324                ServerId = _appHost.SystemId
 14325            };
 326
 14327            if (item.SourceType == SourceType.Channel)
 328            {
 0329                dto.SourceType = item.SourceType.ToString();
 330            }
 331
 14332            if (options.ContainsField(ItemFields.People))
 333            {
 6334                AttachPeople(dto, item, user);
 335            }
 336
 14337            if (options.ContainsField(ItemFields.PrimaryImageAspectRatio))
 338            {
 339                try
 340                {
 6341                    AttachPrimaryImageAspectRatio(dto, item);
 6342                }
 0343                catch (Exception ex)
 344                {
 345                    // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
 0346                    _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {ItemName}", item.Name);
 0347                }
 348            }
 349
 14350            if (options.ContainsField(ItemFields.DisplayPreferencesId))
 351            {
 6352                dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N", CultureInfo.InvariantCulture);
 353            }
 354
 14355            if (user is not null)
 356            {
 9357                AttachUserSpecificInfo(
 9358                    dto,
 9359                    item,
 9360                    user,
 9361                    options,
 9362                    userData,
 9363                    childCountBatch,
 9364                    playedCountBatch,
 9365                    resumeData);
 366            }
 367
 14368            if (item is IHasMediaSources
 14369                && options.ContainsField(ItemFields.MediaSources))
 370            {
 0371                dto.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray();
 372
 0373                NormalizeMediaSourceContainers(dto);
 374            }
 375
 14376            if (options.ContainsField(ItemFields.Studios))
 377            {
 6378                AttachStudios(dto, item);
 379            }
 380
 14381            AttachBasicFields(dto, item, owner, options, artistsBatch, user);
 382
 14383            if (options.ContainsField(ItemFields.CanDelete))
 384            {
 6385                dto.CanDelete = user is null
 6386                    ? item.CanDelete()
 6387                    : allCollectionFolders is not null
 6388                        ? item.CanDelete(user, allCollectionFolders)
 6389                        : item.CanDelete(user);
 390            }
 391
 14392            if (options.ContainsField(ItemFields.CanDownload))
 393            {
 6394                dto.CanDownload = user is null
 6395                    ? item.CanDownload()
 6396                    : item.CanDownload(user);
 397            }
 398
 14399            if (options.ContainsField(ItemFields.Etag))
 400            {
 6401                dto.Etag = item.GetEtag(user);
 402            }
 403
 14404            var activeRecording = _recordingsManager.GetActiveRecordingInfo(item.Path);
 14405            if (activeRecording is not null)
 406            {
 0407                dto.Type = BaseItemKind.Recording;
 0408                dto.CanDownload = false;
 0409                dto.RunTimeTicks = null;
 410
 0411                if (!string.IsNullOrEmpty(dto.SeriesName))
 412                {
 0413                    dto.EpisodeTitle = dto.Name;
 0414                    dto.Name = dto.SeriesName;
 415                }
 416
 0417                LivetvManager.AddInfoToRecordingDto(item, dto, activeRecording, user);
 418            }
 419
 14420            if (item is Audio audio)
 421            {
 0422                dto.HasLyrics = audio.GetMediaStreams().Any(s => s.Type == MediaStreamType.Lyric);
 423            }
 424
 14425            return dto;
 426        }
 427
 428        private static void NormalizeMediaSourceContainers(BaseItemDto dto)
 429        {
 0430            foreach (var mediaSource in dto.MediaSources)
 431            {
 0432                var container = mediaSource.Container;
 0433                if (string.IsNullOrEmpty(container))
 434                {
 435                    continue;
 436                }
 437
 0438                var containers = container.Split(',');
 0439                if (containers.Length < 2)
 440                {
 441                    continue;
 442                }
 443
 0444                var path = mediaSource.Path;
 0445                string? fileExtensionContainer = null;
 446
 0447                if (!string.IsNullOrEmpty(path))
 448                {
 0449                    path = Path.GetExtension(path);
 0450                    if (!string.IsNullOrEmpty(path))
 451                    {
 0452                        path = Path.GetExtension(path);
 0453                        if (!string.IsNullOrEmpty(path))
 454                        {
 0455                            path = path.TrimStart('.');
 456                        }
 457
 0458                        if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparison.OrdinalIgnoreCase)
 459                        {
 0460                            fileExtensionContainer = path;
 461                        }
 462                    }
 463                }
 464
 0465                mediaSource.Container = fileExtensionContainer ?? containers[0];
 466            }
 0467        }
 468
 469        /// <inheritdoc />
 470        /// TODO refactor this to use the new SetItemByNameInfo.
 471        /// Some callers already have the counts extracted so no reason to retrieve them again.
 472        public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem>? taggedItems, User? user =
 473        {
 0474            var dto = GetBaseItemDtoInternal(item, options, user);
 475
 0476            if (options.ContainsField(ItemFields.ItemCounts)
 0477                && taggedItems is not null
 0478                && taggedItems.Count != 0)
 479            {
 0480                SetItemByNameInfo(item, dto, taggedItems);
 481            }
 482
 0483            return dto;
 484        }
 485
 486        private void SetItemByNameInfo(BaseItemDto dto, User? user)
 487        {
 6488            if (!_relatedItemKinds.TryGetValue(dto.Type, out var relatedItemKinds))
 489            {
 6490                return;
 491            }
 492
 0493            var counts = _libraryManager.GetItemCountsForNameItem(dto.Type, dto.Id, relatedItemKinds, user);
 494
 0495            dto.AlbumCount = counts.AlbumCount;
 0496            dto.ArtistCount = counts.ArtistCount;
 0497            dto.EpisodeCount = counts.EpisodeCount;
 0498            dto.MovieCount = counts.MovieCount;
 0499            dto.MusicVideoCount = counts.MusicVideoCount;
 0500            dto.ProgramCount = counts.ProgramCount;
 0501            dto.SeriesCount = counts.SeriesCount;
 0502            dto.SongCount = counts.SongCount;
 0503            dto.TrailerCount = counts.TrailerCount;
 0504            dto.ChildCount = counts.TotalItemCount();
 0505        }
 506
 507        private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IReadOnlyList<BaseItem> taggedItems)
 508        {
 0509            if (item is MusicArtist)
 510            {
 0511                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0512                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0513                dto.SongCount = taggedItems.Count(i => i is Audio);
 514            }
 0515            else if (item is MusicGenre)
 516            {
 0517                dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
 0518                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0519                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0520                dto.SongCount = taggedItems.Count(i => i is Audio);
 521            }
 522            else
 523            {
 524                // This populates them all and covers Genre, Person, Studio, Year
 525
 0526                dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
 0527                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0528                dto.EpisodeCount = taggedItems.Count(i => i is Episode);
 0529                dto.MovieCount = taggedItems.Count(i => i is Movie);
 0530                dto.TrailerCount = taggedItems.Count(i => i is Trailer);
 0531                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0532                dto.SeriesCount = taggedItems.Count(i => i is Series);
 0533                dto.ProgramCount = taggedItems.Count(i => i is LiveTvProgram);
 0534                dto.SongCount = taggedItems.Count(i => i is Audio);
 535            }
 536
 0537            dto.ChildCount = taggedItems.Count;
 0538        }
 539
 540        /// <summary>
 541        /// Attaches the user specific info.
 542        /// </summary>
 543        private void AttachUserSpecificInfo(
 544            BaseItemDto dto,
 545            BaseItem item,
 546            User user,
 547            DtoOptions options,
 548            UserItemData? userData = null,
 549            Dictionary<Guid, int>? childCountBatch = null,
 550            Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null,
 551            VersionResumeData? resumeData = null)
 552        {
 9553            if (item.IsFolder)
 554            {
 9555                var folder = (Folder)item;
 556
 9557                if (options.EnableUserData)
 558                {
 9559                    if (userData is not null)
 560                    {
 561                        // Use pre-fetched user data
 3562                        dto.UserData = GetUserItemDataDto(userData, item.Id);
 3563                        (int Played, int Total)? precomputed = playedCountBatch is not null
 3564                            && playedCountBatch.TryGetValue(item.Id, out var counts) ? counts : null;
 3565                        item.FillUserDataDtoValues(dto.UserData, userData, dto, user, options, precomputed);
 566                    }
 567                    else
 568                    {
 569                        // Fall back to individual fetch
 6570                        dto.UserData = _userDataRepository.GetUserDataDto(item, dto, user, options);
 571                    }
 572                }
 573
 9574                if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library)
 575                {
 576                    // For these types we can try to optimize and assume these values will be equal
 9577                    if (item is MusicAlbum || item is Season || item is Playlist)
 578                    {
 0579                        dto.ChildCount = dto.RecursiveItemCount;
 0580                        var folderChildCount = folder.LinkedChildren.Length;
 581                        // The default is an empty array, so we can't reliably use the count when it's empty
 0582                        if (folderChildCount > 0)
 583                        {
 0584                            dto.ChildCount ??= folderChildCount;
 585                        }
 586                    }
 587
 9588                    if (options.ContainsField(ItemFields.ChildCount))
 589                    {
 6590                        dto.ChildCount ??= GetChildCount(folder, user, childCountBatch);
 591                    }
 592                }
 593
 9594                if (options.ContainsField(ItemFields.CumulativeRunTimeTicks))
 595                {
 6596                    dto.CumulativeRunTimeTicks = item.RunTimeTicks;
 597                }
 598
 9599                if (options.ContainsField(ItemFields.DateLastMediaAdded))
 600                {
 6601                    dto.DateLastMediaAdded = folder.DateLastMediaAdded;
 602                }
 603            }
 604            else
 605            {
 0606                if (options.EnableUserData)
 607                {
 0608                    if (userData is not null)
 609                    {
 610                        // Use pre-fetched user data
 0611                        dto.UserData = GetUserItemDataDto(userData, item.Id);
 0612                        item.FillUserDataDtoValues(dto.UserData, userData, dto, user, options);
 613
 614                        // For items with alternate versions, the most recently played version drives resume.
 0615                        resumeData?.ApplyTo(dto.UserData);
 616                    }
 617                    else
 618                    {
 619                        // Fall back to individual fetch
 0620                        dto.UserData = _userDataRepository.GetUserDataDto(item, user);
 621                    }
 622                }
 623            }
 624
 9625            if (options.ContainsField(ItemFields.PlayAccess))
 626            {
 6627                dto.PlayAccess = item.GetPlayAccess(user);
 628            }
 9629        }
 630
 631        private static UserItemDataDto GetUserItemDataDto(UserItemData data, Guid itemId)
 632        {
 3633            ArgumentNullException.ThrowIfNull(data);
 634
 3635            return new UserItemDataDto
 3636            {
 3637                IsFavorite = data.IsFavorite,
 3638                Likes = data.Likes,
 3639                PlaybackPositionTicks = data.PlaybackPositionTicks,
 3640                PlayCount = data.PlayCount,
 3641                Rating = data.Rating,
 3642                Played = data.Played,
 3643                LastPlayedDate = data.LastPlayedDate,
 3644                ItemId = itemId,
 3645                Key = data.Key
 3646            };
 647        }
 648
 649        private static int GetChildCount(Folder folder, User user, Dictionary<Guid, int>? childCountBatch)
 650        {
 651            // Right now this is too slow to calculate for top level folders on a per-user basis
 652            // Just return something so that apps that are expecting a value won't think the folders are empty
 6653            if (folder is ICollectionFolder || folder is UserView)
 654            {
 0655                return Random.Shared.Next(1, 10);
 656            }
 657
 658            // Use pre-fetched batch data if available
 6659            if (childCountBatch is not null && childCountBatch.TryGetValue(folder.Id, out var count))
 660            {
 0661                return count;
 662            }
 663
 664            // Fall back to individual query for special cases (Series, Season, etc.)
 6665            return folder.GetChildCount(user);
 666        }
 667
 668        private static void SetBookProperties(BaseItemDto dto, Book item)
 669        {
 0670            dto.SeriesName = item.SeriesName;
 0671        }
 672
 673        private static void SetPhotoProperties(BaseItemDto dto, Photo item)
 674        {
 0675            dto.CameraMake = item.CameraMake;
 0676            dto.CameraModel = item.CameraModel;
 0677            dto.Software = item.Software;
 0678            dto.ExposureTime = item.ExposureTime;
 0679            dto.FocalLength = item.FocalLength;
 0680            dto.ImageOrientation = item.Orientation;
 0681            dto.Aperture = item.Aperture;
 0682            dto.ShutterSpeed = item.ShutterSpeed;
 683
 0684            dto.Latitude = item.Latitude;
 0685            dto.Longitude = item.Longitude;
 0686            dto.Altitude = item.Altitude;
 0687            dto.IsoSpeedRating = item.IsoSpeedRating;
 688
 0689            var album = item.AlbumEntity;
 690
 0691            if (album is not null)
 692            {
 0693                dto.Album = album.Name;
 0694                dto.AlbumId = album.Id;
 695            }
 0696        }
 697
 698        private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item)
 699        {
 0700            if (!string.IsNullOrEmpty(item.Album))
 701            {
 0702                var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery
 0703                {
 0704                    IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
 0705                    Name = item.Album,
 0706                    Limit = 1
 0707                });
 708
 0709                if (parentAlbumIds.Count > 0)
 710                {
 0711                    dto.AlbumId = parentAlbumIds[0];
 712                }
 713            }
 714
 0715            dto.Album = item.Album;
 0716        }
 717
 718        private string[] GetImageTags(BaseItem item, List<ItemImageInfo> images)
 719        {
 14720            return images
 14721                .Select(p => GetImageCacheTag(item, p))
 14722                .Where(i => i is not null)
 14723                .ToArray()!; // null values got filtered out
 724        }
 725
 726        private string? GetImageCacheTag(BaseItem item, ItemImageInfo image)
 727        {
 728            try
 729            {
 10730                return _imageProcessor.GetImageCacheTag(item, image);
 731            }
 0732            catch (Exception ex)
 733            {
 0734                _logger.LogError(ex, "Error getting {ImageType} image info for {Path}", image.Type, image.Path);
 0735                return null;
 736            }
 10737        }
 738
 739        /// <summary>
 740        /// Attaches People DTO's to a DTOBaseItem.
 741        /// </summary>
 742        /// <param name="dto">The dto.</param>
 743        /// <param name="item">The item.</param>
 744        /// <param name="user">The requesting user.</param>
 745        private void AttachPeople(BaseItemDto dto, BaseItem item, User? user = null)
 746        {
 747            // Ordering by person type to ensure actors and artists are at the front.
 748            // This is taking advantage of the fact that they both begin with A
 749            // This should be improved in the future
 6750            var people = _libraryManager.GetPeople(item).OrderBy(i => i.SortOrder ?? int.MaxValue)
 6751                .ThenBy(i =>
 6752                {
 6753                    if (i.IsType(PersonKind.Actor))
 6754                    {
 6755                        return 0;
 6756                    }
 6757
 6758                    if (i.IsType(PersonKind.GuestStar))
 6759                    {
 6760                        return 1;
 6761                    }
 6762
 6763                    if (i.IsType(PersonKind.Director))
 6764                    {
 6765                        return 2;
 6766                    }
 6767
 6768                    if (i.IsType(PersonKind.Writer))
 6769                    {
 6770                        return 3;
 6771                    }
 6772
 6773                    if (i.IsType(PersonKind.Producer))
 6774                    {
 6775                        return 4;
 6776                    }
 6777
 6778                    if (i.IsType(PersonKind.Composer))
 6779                    {
 6780                        return 4;
 6781                    }
 6782
 6783                    return 10;
 6784                })
 6785                .ToList();
 786
 6787            var list = new List<BaseItemPerson>();
 788
 6789            Dictionary<string, Person> dictionary = people.Select(p => p.Name)
 6790                .Distinct(StringComparer.OrdinalIgnoreCase).Select(c =>
 6791                {
 6792                    try
 6793                    {
 6794                        return _libraryManager.GetPerson(c);
 6795                    }
 6796                    catch (Exception ex)
 6797                    {
 6798                        _logger.LogError(ex, "Error getting person {Name}", c);
 6799                        return null;
 6800                    }
 6801                }).Where(i => i is not null)
 6802                .Where(i => user is null || i!.IsVisible(user))
 6803                .DistinctBy(x => x!.Name, StringComparer.OrdinalIgnoreCase)
 6804                .ToDictionary(i => i!.Name, StringComparer.OrdinalIgnoreCase)!; // null values got filtered out
 805
 12806            for (var i = 0; i < people.Count; i++)
 807            {
 0808                var person = people[i];
 809
 0810                var baseItemPerson = new BaseItemPerson
 0811                {
 0812                    Name = person.Name,
 0813                    Role = person.Role,
 0814                    Type = person.Type
 0815                };
 816
 0817                if (dictionary.TryGetValue(person.Name, out Person? entity))
 818                {
 0819                    baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
 0820                    baseItemPerson.Id = entity.Id;
 0821                    if (dto.ImageBlurHashes is not null)
 822                    {
 823                        // Only add BlurHash for the person's image.
 0824                        baseItemPerson.ImageBlurHashes = [];
 0825                        foreach (var (imageType, blurHash) in dto.ImageBlurHashes)
 826                        {
 0827                            if (blurHash is not null)
 828                            {
 0829                                baseItemPerson.ImageBlurHashes[imageType] = [];
 0830                                foreach (var (imageId, blurHashValue) in blurHash)
 831                                {
 0832                                    if (string.Equals(baseItemPerson.PrimaryImageTag, imageId, StringComparison.OrdinalI
 833                                    {
 0834                                        baseItemPerson.ImageBlurHashes[imageType][imageId] = blurHashValue;
 835                                    }
 836                                }
 837                            }
 838                        }
 839                    }
 840
 0841                    list.Add(baseItemPerson);
 842                }
 843            }
 844
 6845            dto.People = list.ToArray();
 6846        }
 847
 848        /// <summary>
 849        /// Attaches the studios.
 850        /// </summary>
 851        /// <param name="dto">The dto.</param>
 852        /// <param name="item">The item.</param>
 853        private void AttachStudios(BaseItemDto dto, BaseItem item)
 854        {
 6855            dto.Studios = item.Studios
 6856                .Where(i => !string.IsNullOrEmpty(i))
 6857                .Select(i => new NameGuidPair
 6858                {
 6859                    Name = i,
 6860                    Id = _libraryManager.GetStudioId(i)
 6861                })
 6862                .ToArray();
 6863        }
 864
 865        private void AttachGenreItems(BaseItemDto dto, BaseItem item)
 866        {
 6867            dto.GenreItems = item.Genres
 6868                .Where(i => !string.IsNullOrEmpty(i))
 6869                .Select(i => new NameGuidPair
 6870                {
 6871                    Name = i,
 6872                    Id = GetGenreId(i, item)
 6873                })
 6874                .ToArray();
 6875        }
 876
 877        private Guid GetGenreId(string name, BaseItem owner)
 878        {
 0879            if (owner is IHasMusicGenres)
 880            {
 0881                return _libraryManager.GetMusicGenreId(name);
 882            }
 883
 0884            return _libraryManager.GetGenreId(name);
 885        }
 886
 887        private string? GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ImageType imageType, int imageIndex = 0)
 888        {
 5889            var image = item.GetImageInfo(imageType, imageIndex);
 5890            if (image is not null)
 891            {
 4892                return GetTagAndFillBlurhash(dto, item, image);
 893            }
 894
 1895            return null;
 896        }
 897
 898        private string? GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ItemImageInfo image)
 899        {
 10900            var tag = GetImageCacheTag(item, image);
 10901            if (tag is null)
 902            {
 0903                return null;
 904            }
 905
 10906            if (!string.IsNullOrEmpty(image.BlurHash))
 907            {
 0908                dto.ImageBlurHashes ??= [];
 909
 0910                if (!dto.ImageBlurHashes.TryGetValue(image.Type, out var value))
 911                {
 0912                    value = [];
 0913                    dto.ImageBlurHashes[image.Type] = value;
 914                }
 915
 0916                value[tag] = image.BlurHash;
 917            }
 918
 10919            return tag;
 920        }
 921
 922        private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, int limit)
 923        {
 14924            return GetTagsAndFillBlurhashes(dto, item, imageType, item.GetImages(imageType).Take(limit).ToList());
 925        }
 926
 927        private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, List<ItemImageInf
 928        {
 14929            var tags = GetImageTags(item, images);
 14930            var hashes = new Dictionary<string, string>();
 28931            for (int i = 0; i < images.Count; i++)
 932            {
 0933                var img = images[i];
 0934                if (!string.IsNullOrEmpty(img.BlurHash))
 935                {
 0936                    var tag = tags[i];
 0937                    hashes[tag] = img.BlurHash;
 938                }
 939            }
 940
 14941            if (hashes.Count > 0)
 942            {
 0943                dto.ImageBlurHashes ??= [];
 944
 0945                dto.ImageBlurHashes[imageType] = hashes;
 946            }
 947
 14948            return tags;
 949        }
 950
 951        /// <summary>
 952        /// Sets simple property values on a DTOBaseItem.
 953        /// </summary>
 954        /// <param name="dto">The dto.</param>
 955        /// <param name="item">The item.</param>
 956        /// <param name="owner">The owner.</param>
 957        /// <param name="options">The options.</param>
 958        /// <param name="artistsBatch">Optional pre-fetched artist lookup shared across a batch of items.</param>
 959        /// <param name="user">The user, for per-user values such as the accessible media source count.</param>
 960        private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem? owner, DtoOptions options, IReadOnlyDic
 961        {
 14962            if (options.ContainsField(ItemFields.DateCreated))
 963            {
 6964                dto.DateCreated = item.DateCreated;
 965            }
 966
 14967            if (options.ContainsField(ItemFields.Settings))
 968            {
 6969                dto.LockedFields = item.LockedFields;
 6970                dto.LockData = item.IsLocked;
 6971                dto.ForcedSortName = item.ForcedSortName;
 972            }
 973
 14974            dto.Container = item.Container;
 14975            dto.EndDate = item.EndDate;
 976
 14977            if (options.ContainsField(ItemFields.ExternalUrls))
 978            {
 6979                dto.ExternalUrls = _providerManager.GetExternalUrls(item).ToArray();
 980            }
 981
 14982            if (options.ContainsField(ItemFields.Tags))
 983            {
 6984                dto.Tags = item.Tags;
 985            }
 986
 14987            if (item is IHasAspectRatio hasAspectRatio)
 988            {
 3989                dto.AspectRatio = hasAspectRatio.AspectRatio;
 990            }
 991
 14992            dto.ImageBlurHashes = [];
 993
 14994            var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
 14995            if (backdropLimit > 0)
 996            {
 14997                dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit);
 998            }
 999
 141000            if (options.ContainsField(ItemFields.Genres))
 1001            {
 61002                dto.Genres = item.Genres;
 61003                AttachGenreItems(dto, item);
 1004            }
 1005
 141006            if (options.EnableImages)
 1007            {
 141008                dto.ImageTags = [];
 1009
 1010                // Prevent implicitly captured closure
 141011                var currentItem = item;
 381012                foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)))
 1013                {
 51014                    if (options.GetImageLimit(image.Type) > 0)
 1015                    {
 51016                        var tag = GetTagAndFillBlurhash(dto, item, image);
 1017
 51018                        if (tag is not null)
 1019                        {
 51020                            dto.ImageTags[image.Type] = tag;
 1021                        }
 1022                    }
 1023                }
 1024            }
 1025
 141026            dto.Id = item.Id;
 141027            dto.IndexNumber = item.IndexNumber;
 141028            dto.ParentIndexNumber = item.ParentIndexNumber;
 1029
 141030            if (item.IsFolder)
 1031            {
 111032                dto.IsFolder = true;
 1033            }
 31034            else if (item is IHasMediaSources)
 1035            {
 31036                dto.IsFolder = false;
 1037            }
 1038
 141039            dto.MediaType = item.MediaType;
 1040
 141041            if (item is not LiveTvProgram)
 1042            {
 141043                dto.LocationType = item.LocationType;
 1044            }
 1045
 141046            dto.Audio = item.Audio;
 1047
 141048            if (options.ContainsField(ItemFields.Settings))
 1049            {
 61050                dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
 61051                dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
 1052            }
 1053
 141054            dto.CriticRating = item.CriticRating;
 1055
 141056            if (item is IHasDisplayOrder hasDisplayOrder)
 1057            {
 01058                dto.DisplayOrder = hasDisplayOrder.DisplayOrder;
 1059            }
 1060
 141061            if (item is IHasCollectionType hasCollectionType)
 1062            {
 51063                dto.CollectionType = hasCollectionType.CollectionType;
 1064            }
 1065
 141066            if (options.ContainsField(ItemFields.RemoteTrailers))
 1067            {
 61068                dto.RemoteTrailers = item.RemoteTrailers;
 1069            }
 1070
 141071            dto.Name = item.Name;
 141072            dto.OfficialRating = item.OfficialRating;
 1073
 141074            if (options.ContainsField(ItemFields.Overview))
 1075            {
 61076                dto.Overview = item.Overview;
 1077            }
 1078
 141079            if (options.ContainsField(ItemFields.OriginalTitle))
 1080            {
 61081                dto.OriginalTitle = item.OriginalTitle;
 1082            }
 1083
 141084            dto.OriginalLanguage = item.OriginalLanguage;
 1085
 141086            if (options.ContainsField(ItemFields.ParentId))
 1087            {
 61088                dto.ParentId = item.DisplayParentId;
 1089            }
 1090
 141091            AddInheritedImages(dto, item, options, owner);
 1092
 141093            if (options.ContainsField(ItemFields.Path))
 1094            {
 61095                dto.Path = GetMappedPath(item, owner);
 1096            }
 1097
 141098            if (options.ContainsField(ItemFields.EnableMediaSourceDisplay))
 1099            {
 61100                dto.EnableMediaSourceDisplay = item.EnableMediaSourceDisplay;
 1101            }
 1102
 141103            dto.PremiereDate = item.PremiereDate;
 141104            dto.ProductionYear = item.ProductionYear;
 1105
 141106            if (options.ContainsField(ItemFields.ProviderIds))
 1107            {
 61108                dto.ProviderIds = item.ProviderIds;
 1109            }
 1110
 141111            dto.RunTimeTicks = item.RunTimeTicks;
 1112
 141113            if (options.ContainsField(ItemFields.SortName))
 1114            {
 61115                dto.SortName = item.SortName;
 1116            }
 1117
 141118            if (options.ContainsField(ItemFields.CustomRating))
 1119            {
 61120                dto.CustomRating = item.CustomRating;
 1121            }
 1122
 141123            if (options.ContainsField(ItemFields.Taglines))
 1124            {
 61125                if (!string.IsNullOrEmpty(item.Tagline))
 1126                {
 01127                    dto.Taglines = new string[] { item.Tagline };
 1128                }
 1129
 61130                dto.Taglines ??= Array.Empty<string>();
 1131            }
 1132
 141133            dto.Type = item.GetBaseItemKind();
 141134            if ((item.CommunityRating ?? 0) > 0)
 1135            {
 01136                dto.CommunityRating = item.CommunityRating;
 1137            }
 1138
 141139            if (item is ISupportsPlaceHolders supportsPlaceHolders && supportsPlaceHolders.IsPlaceHolder)
 1140            {
 01141                dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder;
 1142            }
 1143
 141144            if (item.LUFS.HasValue)
 1145            {
 1146                // -18 LUFS reference, same as ReplayGain 2.0, compatible with ReplayGain 1.0
 01147                dto.NormalizationGain = -18f - item.LUFS;
 1148            }
 141149            else if (item.NormalizationGain.HasValue)
 1150            {
 01151                dto.NormalizationGain = item.NormalizationGain;
 1152            }
 1153
 1154            // Add audio info
 141155            if (item is Audio audio)
 1156            {
 01157                dto.Album = audio.Album;
 01158                dto.ExtraType = audio.ExtraType;
 1159
 01160                var albumParent = audio.AlbumEntity;
 1161
 01162                if (albumParent is not null)
 1163                {
 01164                    dto.AlbumId = albumParent.Id;
 01165                    dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary);
 01166                    if (albumParent.LUFS.HasValue)
 1167                    {
 1168                        // -18 LUFS reference, same as ReplayGain 2.0, compatible with ReplayGain 1.0
 01169                        dto.AlbumNormalizationGain = -18f - albumParent.LUFS;
 1170                    }
 01171                    else if (albumParent.NormalizationGain.HasValue)
 1172                    {
 01173                        dto.AlbumNormalizationGain = albumParent.NormalizationGain;
 1174                    }
 1175                }
 1176
 1177                // if (options.ContainsField(ItemFields.MediaSourceCount))
 1178                // {
 1179                // Songs always have one
 1180                // }
 1181            }
 1182
 141183            if (item is IHasArtist hasArtist)
 1184            {
 01185                dto.Artists = hasArtist.Artists;
 1186
 1187                // var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
 1188                // {
 1189                //    EnableTotalRecordCount = false,
 1190                //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
 1191                // });
 1192
 1193                // dto.ArtistItems = artistItems.Items
 1194                //    .Select(i =>
 1195                //    {
 1196                //        var artist = i.Item1;
 1197                //        return new NameIdPair
 1198                //        {
 1199                //            Name = artist.Name,
 1200                //            Id = artist.Id.ToString("N", CultureInfo.InvariantCulture)
 1201                //        };
 1202                //    })
 1203                //    .ToList();
 1204
 1205                // Include artists that are not in the database yet, e.g., just added via metadata editor
 1206                // var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
 01207                var artistsLookup = artistsBatch
 01208                    ?? _libraryManager.GetArtists([.. hasArtist.Artists.Where(e => !string.IsNullOrWhiteSpace(e))]);
 1209
 01210                dto.ArtistItems = hasArtist.Artists
 01211                    .Where(name => !string.IsNullOrWhiteSpace(name))
 01212                    .Distinct()
 01213                    .Select(name => artistsLookup.TryGetValue(name, out var artists) && artists.Length > 0
 01214                        ? new NameGuidPair { Name = name, Id = artists[0].Id }
 01215                        : null)
 01216                    .Where(item => item is not null)
 01217                    .ToArray();
 1218            }
 1219
 141220            if (item is IHasAlbumArtist hasAlbumArtist)
 1221            {
 01222                dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
 1223
 1224                // var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
 1225                // {
 1226                //    EnableTotalRecordCount = false,
 1227                //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
 1228                // });
 1229
 1230                // dto.AlbumArtists = artistItems.Items
 1231                //    .Select(i =>
 1232                //    {
 1233                //        var artist = i.Item1;
 1234                //        return new NameIdPair
 1235                //        {
 1236                //            Name = artist.Name,
 1237                //            Id = artist.Id.ToString("N", CultureInfo.InvariantCulture)
 1238                //        };
 1239                //    })
 1240                //    .ToList();
 1241
 01242                var albumArtistsLookup = artistsBatch
 01243                    ?? _libraryManager.GetArtists([.. hasAlbumArtist.AlbumArtists.Where(e => !string.IsNullOrWhiteSpace(
 1244
 01245                dto.AlbumArtists = hasAlbumArtist.AlbumArtists
 01246                    .Where(name => !string.IsNullOrWhiteSpace(name))
 01247                    .Distinct()
 01248                    .Select(name => albumArtistsLookup.TryGetValue(name, out var albumArtists) && albumArtists.Length > 
 01249                        ? new NameGuidPair { Name = name, Id = albumArtists[0].Id }
 01250                        : null)
 01251                    .Where(item => item is not null)
 01252                    .ToArray();
 1253            }
 1254
 1255            // Add video info
 141256            if (item is Video video)
 1257            {
 31258                dto.VideoType = video.VideoType;
 31259                dto.Video3DFormat = video.Video3DFormat;
 31260                dto.IsoType = video.IsoType;
 1261
 31262                if (video.HasSubtitles)
 1263                {
 01264                    dto.HasSubtitles = video.HasSubtitles;
 1265                }
 1266
 31267                if (video.AdditionalParts.Length != 0)
 1268                {
 01269                    dto.PartCount = video.AdditionalParts.Length + 1;
 1270                }
 1271
 31272                if (options.ContainsField(ItemFields.MediaSourceCount))
 1273                {
 1274                    // Match the per-user filtering of the media sources: versions the user cannot
 1275                    // access are not selectable, so they must not count towards the badge either.
 01276                    var mediaSourceCount = user is null
 01277                        || (!video.PrimaryVersionId.HasValue && video.LinkedAlternateVersions.Length == 0 && !video.HasL
 01278                            ? video.MediaSourceCount
 01279                            : video.GetAllVersions().Count(v => v.Id.Equals(video.Id) || v.IsVisibleStandalone(user));
 01280                    if (mediaSourceCount != 1)
 1281                    {
 01282                        dto.MediaSourceCount = mediaSourceCount;
 1283                    }
 1284                }
 1285
 31286                if (options.ContainsField(ItemFields.Trickplay))
 1287                {
 01288                    var trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult();
 01289                    dto.Trickplay = trickplay.ToDictionary(
 01290                        mediaStream => mediaStream.Key,
 01291                        mediaStream => mediaStream.Value.ToDictionary(
 01292                            width => width.Key,
 01293                            width => new TrickplayInfoDto(width.Value)));
 1294                }
 1295
 31296                dto.ExtraType = video.ExtraType;
 1297            }
 1298
 141299            if (options.ContainsField(ItemFields.Chapters))
 1300            {
 61301                dto.Chapters = _chapterManager.GetChapters(item.Id).ToList();
 1302            }
 1303
 141304            if (options.ContainsField(ItemFields.MediaStreams))
 1305            {
 1306                // Add VideoInfo
 61307                if (item is IHasMediaSources)
 1308                {
 1309                    MediaStream[] mediaStreams;
 1310
 01311                    if (dto.MediaSources is not null && dto.MediaSources.Length > 0)
 1312                    {
 01313                        if (item.SourceType == SourceType.Channel)
 1314                        {
 01315                            mediaStreams = dto.MediaSources[0].MediaStreams.ToArray();
 1316                        }
 1317                        else
 1318                        {
 01319                            string id = item.Id.ToString("N", CultureInfo.InvariantCulture);
 01320                            mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, id, StringComparison.OrdinalI
 01321                                .SelectMany(i => i.MediaStreams)
 01322                                .ToArray();
 1323                        }
 1324                    }
 1325                    else
 1326                    {
 01327                        mediaStreams = _mediaSourceManager.GetStaticMediaSources(item, true)[0].MediaStreams.ToArray();
 1328                    }
 1329
 01330                    dto.MediaStreams = mediaStreams;
 1331                }
 1332            }
 1333
 141334            BaseItem[]? allExtras = null;
 1335
 141336            if (options.ContainsField(ItemFields.SpecialFeatureCount))
 1337            {
 61338                allExtras = item.GetExtras().ToArray();
 61339                dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contai
 1340            }
 1341
 141342            if (options.ContainsField(ItemFields.LocalTrailerCount))
 1343            {
 61344                if (item is IHasTrailers hasTrailers)
 1345                {
 01346                    dto.LocalTrailerCount = hasTrailers.LocalTrailers.Count;
 1347                }
 1348                else
 1349                {
 61350                    dto.LocalTrailerCount = (allExtras ?? item.GetExtras()).Count(i => i.ExtraType == ExtraType.Trailer)
 1351                }
 1352            }
 1353
 1354            // Add EpisodeInfo
 141355            if (item is Episode episode)
 1356            {
 31357                dto.IndexNumberEnd = episode.IndexNumberEnd;
 31358                dto.SeriesName = episode.SeriesName;
 1359
 31360                if (options.ContainsField(ItemFields.SpecialEpisodeNumbers))
 1361                {
 01362                    dto.AirsAfterSeasonNumber = episode.AirsAfterSeasonNumber;
 01363                    dto.AirsBeforeEpisodeNumber = episode.AirsBeforeEpisodeNumber;
 01364                    dto.AirsBeforeSeasonNumber = episode.AirsBeforeSeasonNumber;
 1365                }
 1366
 31367                dto.SeasonName = episode.SeasonName;
 31368                dto.SeasonId = episode.SeasonId;
 31369                dto.SeriesId = episode.SeriesId;
 1370
 31371                Series? episodeSeries = null;
 1372
 1373                // this block will add the series poster for episodes without a poster
 1374                // TODO maybe remove the if statement entirely
 1375                // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
 1376                {
 31377                    episodeSeries ??= episode.Series;
 31378                    if (episodeSeries is not null)
 1379                    {
 31380                        dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
 31381                        if (dto.ImageTags is null || !dto.ImageTags.ContainsKey(ImageType.Primary))
 1382                        {
 01383                            AttachPrimaryImageAspectRatio(dto, episodeSeries);
 1384                        }
 1385                    }
 1386                }
 1387
 31388                if (options.PreferEpisodeParentPoster)
 1389                {
 21390                    var episodeSeason = episode.Season;
 21391                    var seasonPrimaryTag = episodeSeason is not null
 21392                        ? GetTagAndFillBlurhash(dto, episodeSeason, ImageType.Primary)
 21393                        : null;
 1394
 21395                    BaseItem? posterParent = null;
 21396                    if (seasonPrimaryTag is not null)
 1397                    {
 11398                        dto.ParentPrimaryImageItemId = episodeSeason!.Id;
 11399                        dto.ParentPrimaryImageTag = seasonPrimaryTag;
 11400                        posterParent = episodeSeason;
 1401                    }
 11402                    else if (episodeSeries is not null && dto.SeriesPrimaryImageTag is not null)
 1403                    {
 11404                        dto.ParentPrimaryImageItemId = episodeSeries.Id;
 11405                        dto.ParentPrimaryImageTag = dto.SeriesPrimaryImageTag;
 11406                        posterParent = episodeSeries;
 1407                    }
 1408
 21409                    if (posterParent is not null)
 1410                    {
 21411                        if (dto.ImageTags is not null && dto.ImageTags.Remove(ImageType.Primary, out var ownPrimaryTag))
 1412                        {
 1413                            // Only drop the episode's own primary blurhash; keep the poster parent's.
 21414                            dto.ImageBlurHashes?.GetValueOrDefault(ImageType.Primary)?.Remove(ownPrimaryTag);
 1415                        }
 1416
 21417                        dto.SeriesPrimaryImageTag = null;
 21418                        dto.PrimaryImageAspectRatio = null;
 21419                        AttachPrimaryImageAspectRatio(dto, posterParent);
 1420                    }
 1421                }
 1422
 31423                if (options.ContainsField(ItemFields.SeriesStudio))
 1424                {
 01425                    episodeSeries ??= episode.Series;
 01426                    if (episodeSeries is not null)
 1427                    {
 01428                        dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
 1429                    }
 1430                }
 1431            }
 1432
 1433            // Add SeriesInfo
 1434            Series? series;
 141435            if (item is Series tmp)
 1436            {
 01437                series = tmp;
 01438                dto.AirDays = series.AirDays;
 01439                dto.AirTime = series.AirTime;
 01440                dto.Status = series.Status?.ToString();
 1441            }
 1442
 1443            // Add SeasonInfo
 141444            if (item is Season season)
 1445            {
 01446                dto.SeriesName = season.SeriesName;
 01447                dto.SeriesId = season.SeriesId;
 1448
 01449                series = null;
 1450
 01451                if (options.ContainsField(ItemFields.SeriesStudio))
 1452                {
 01453                    series ??= season.Series;
 01454                    if (series is not null)
 1455                    {
 01456                        dto.SeriesStudio = series.Studios.FirstOrDefault();
 1457                    }
 1458                }
 1459
 1460                // this block will add the series poster for seasons without a poster
 1461                // TODO maybe remove the if statement entirely
 1462                // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
 1463                {
 01464                    series ??= season.Series;
 01465                    if (series is not null)
 1466                    {
 01467                        dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
 01468                        if (dto.ImageTags is null || !dto.ImageTags.ContainsKey(ImageType.Primary))
 1469                        {
 01470                            AttachPrimaryImageAspectRatio(dto, series);
 1471                        }
 1472                    }
 1473                }
 1474            }
 1475
 141476            if (item is MusicVideo musicVideo)
 1477            {
 01478                SetMusicVideoProperties(dto, musicVideo);
 1479            }
 1480
 141481            if (item is Book book)
 1482            {
 01483                SetBookProperties(dto, book);
 1484            }
 1485
 141486            if (options.ContainsField(ItemFields.ProductionLocations))
 1487            {
 61488                if (item.ProductionLocations.Length > 0 || item is Movie)
 1489                {
 01490                    dto.ProductionLocations = item.ProductionLocations;
 1491                }
 1492            }
 1493
 141494            if (options.ContainsField(ItemFields.Width))
 1495            {
 61496                var width = item.Width;
 61497                if (width > 0)
 1498                {
 01499                    dto.Width = width;
 1500                }
 1501            }
 1502
 141503            if (options.ContainsField(ItemFields.Height))
 1504            {
 61505                var height = item.Height;
 61506                if (height > 0)
 1507                {
 01508                    dto.Height = height;
 1509                }
 1510            }
 1511
 141512            if (options.ContainsField(ItemFields.IsHD))
 1513            {
 1514                // Compatibility
 61515                if (item.IsHD)
 1516                {
 01517                    dto.IsHD = true;
 1518                }
 1519            }
 1520
 141521            if (item is Photo photo)
 1522            {
 01523                SetPhotoProperties(dto, photo);
 1524            }
 1525
 141526            dto.ChannelId = item.ChannelId;
 1527
 141528            if (item.SourceType == SourceType.Channel)
 1529            {
 01530                var channel = _libraryManager.GetItemById(item.ChannelId);
 01531                if (channel is not null)
 1532                {
 01533                    dto.ChannelName = channel.Name;
 1534                }
 1535            }
 141536        }
 1537
 1538        private BaseItem? GetImageDisplayParent(BaseItem currentItem, BaseItem originalItem)
 1539        {
 61540            if (currentItem is MusicAlbum musicAlbum)
 1541            {
 01542                var artist = musicAlbum.GetMusicArtist(new DtoOptions(false));
 01543                if (artist is not null)
 1544                {
 01545                    return artist;
 1546                }
 1547            }
 1548
 61549            var parent = currentItem.DisplayParent ?? currentItem.GetOwner() ?? currentItem.GetParent();
 1550
 61551            if (parent is null && originalItem is not UserRootFolder && originalItem is not UserView && originalItem is 
 1552            {
 01553                parent = _libraryManager.GetCollectionFolders(originalItem).FirstOrDefault();
 1554            }
 1555
 61556            return parent;
 1557        }
 1558
 1559        private void AddInheritedImages(BaseItemDto dto, BaseItem item, DtoOptions options, BaseItem? owner)
 1560        {
 141561            if (item is UserView { ViewType: CollectionType.playlists } playlistsView
 141562                && options.GetImageLimit(ImageType.Primary) > 0
 141563                && !playlistsView.DisplayParentId.IsEmpty())
 1564            {
 21565                var displayParent = _libraryManager.GetItemById(playlistsView.DisplayParentId);
 21566                var displayParentPrimaryImage = displayParent?.GetImageInfo(ImageType.Primary, 0);
 1567
 21568                if (displayParentPrimaryImage is not null)
 1569                {
 11570                    dto.ImageTags?.Remove(ImageType.Primary);
 11571                    dto.ParentPrimaryImageItemId = displayParent!.Id;
 11572                    dto.ParentPrimaryImageTag = GetTagAndFillBlurhash(dto, displayParent, displayParentPrimaryImage);
 1573                }
 1574            }
 1575
 141576            if (!item.SupportsInheritedParentImages)
 1577            {
 111578                return;
 1579            }
 1580
 31581            var logoLimit = options.GetImageLimit(ImageType.Logo);
 31582            var artLimit = options.GetImageLimit(ImageType.Art);
 31583            var thumbLimit = options.GetImageLimit(ImageType.Thumb);
 31584            var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
 1585
 1586            // For now. Emby apps are not using this
 31587            artLimit = 0;
 1588
 31589            if (logoLimit == 0 && artLimit == 0 && thumbLimit == 0 && backdropLimit == 0)
 1590            {
 01591                return;
 1592            }
 1593
 31594            BaseItem? parent = null;
 31595            var isFirst = true;
 1596
 31597            var imageTags = dto.ImageTags;
 1598
 61599            while ((!(imageTags is not null && imageTags.ContainsKey(ImageType.Logo)) && logoLimit > 0)
 61600                || (!(imageTags is not null && imageTags.ContainsKey(ImageType.Art)) && artLimit > 0)
 61601                || (!(imageTags is not null && imageTags.ContainsKey(ImageType.Thumb)) && thumbLimit > 0)
 61602                || parent is Series)
 1603            {
 61604                parent ??= isFirst ? GetImageDisplayParent(item, item) ?? owner : parent;
 61605                if (parent is null)
 1606                {
 1607                    break;
 1608                }
 1609
 61610                var allImages = parent.ImageInfos;
 1611
 61612                if (logoLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Logo)) && dto.ParentLogo
 1613                {
 61614                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Logo);
 1615
 61616                    if (image is not null)
 1617                    {
 01618                        dto.ParentLogoItemId = parent.Id;
 01619                        dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1620                    }
 1621                }
 1622
 61623                if (artLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtIte
 1624                {
 01625                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
 1626
 01627                    if (image is not null)
 1628                    {
 01629                        dto.ParentArtItemId = parent.Id;
 01630                        dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1631                    }
 1632                }
 1633
 61634                if (thumbLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentT
 1635                {
 61636                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
 1637
 61638                    if (image is not null)
 1639                    {
 01640                        dto.ParentThumbItemId = parent.Id;
 01641                        dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1642                    }
 1643                }
 1644
 61645                if (backdropLimit > 0 && !((dto.BackdropImageTags is not null && dto.BackdropImageTags.Length > 0) || (d
 1646                {
 61647                    var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
 1648
 61649                    if (images.Count > 0)
 1650                    {
 01651                        dto.ParentBackdropItemId = parent.Id;
 01652                        dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images);
 1653                    }
 1654                }
 1655
 61656                isFirst = false;
 1657
 61658                if (!parent.SupportsInheritedParentImages)
 1659                {
 1660                    break;
 1661                }
 1662
 31663                parent = GetImageDisplayParent(parent, item);
 1664            }
 31665        }
 1666
 1667        private string GetMappedPath(BaseItem item, BaseItem? ownerItem)
 1668        {
 61669            var path = item.Path;
 1670
 61671            if (item.IsFileProtocol)
 1672            {
 61673                path = _libraryManager.GetPathAfterNetworkSubstitution(path, ownerItem ?? item);
 1674            }
 1675
 61676            return path;
 1677        }
 1678
 1679        /// <summary>
 1680        /// Attaches the primary image aspect ratio.
 1681        /// </summary>
 1682        /// <param name="dto">The dto.</param>
 1683        /// <param name="item">The item.</param>
 1684        public void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item)
 1685        {
 81686            dto.PrimaryImageAspectRatio = GetPrimaryImageAspectRatio(item);
 81687        }
 1688
 1689        public double? GetPrimaryImageAspectRatio(BaseItem item)
 1690        {
 81691            var imageInfo = item.GetImageInfo(ImageType.Primary, 0);
 1692
 81693            if (imageInfo is null)
 1694            {
 61695                return null;
 1696            }
 1697
 21698            if (!imageInfo.IsLocalFile)
 1699            {
 21700                return item.GetDefaultPrimaryImageAspectRatio();
 1701            }
 1702
 1703            try
 1704            {
 01705                var size = _imageProcessor.GetImageDimensions(item, imageInfo);
 01706                var width = size.Width;
 01707                var height = size.Height;
 01708                if (width > 0 && height > 0)
 1709                {
 01710                    return (double)width / height;
 1711                }
 01712            }
 01713            catch (Exception ex)
 1714            {
 01715                _logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path);
 01716            }
 1717
 01718            return item.GetDefaultPrimaryImageAspectRatio();
 01719        }
 1720    }
 1721}

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[]>,MediaBrowser.Controller.Library.VersionResumeData)
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>>,MediaBrowser.Controller.Library.VersionResumeData)
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[]>,Jellyfin.Database.Implementations.Entities.User)
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)