< 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
70%
Covered lines: 572
Uncovered lines: 244
Coverable lines: 816
Total lines: 1745
Line coverage: 70%
Branch coverage
51%
Covered branches: 299
Total branches: 582
Branch coverage: 51.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/2/2026 - 12:12:50 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: 17217/18/2026 - 12:15:19 AM Line coverage: 63.8% (511/800) Branch coverage: 45.3% (254/560) Total lines: 17057/27/2026 - 12:16:14 AM Line coverage: 67.9% (548/806) Branch coverage: 48% (275/572) Total lines: 17188/9/2026 - 12:16:58 AM Line coverage: 70% (572/816) Branch coverage: 51.3% (299/582) Total lines: 1745 5/2/2026 - 12:12:50 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: 17217/18/2026 - 12:15:19 AM Line coverage: 63.8% (511/800) Branch coverage: 45.3% (254/560) Total lines: 17057/27/2026 - 12:16:14 AM Line coverage: 67.9% (548/806) Branch coverage: 48% (275/572) Total lines: 17188/9/2026 - 12:16:58 AM Line coverage: 70% (572/816) Branch coverage: 51.3% (299/582) Total lines: 1745

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        {
 29142            _logger = logger;
 29143            _libraryManager = libraryManager;
 29144            _userDataRepository = userDataRepository;
 29145            _imageProcessor = imageProcessor;
 29146            _providerManager = providerManager;
 29147            _recordingsManager = recordingsManager;
 29148            _appHost = appHost;
 29149            _mediaSourceManager = mediaSourceManager;
 29150            _livetvManagerFactory = livetvManagerFactory;
 29151            _trickplayManager = trickplayManager;
 29152            _chapterManager = chapterManager;
 29153        }
 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        {
 6165            var accessibleItems = skipVisibilityCheck || user is null ? items : items.Where(x => x.IsVisible(user)).ToLi
 6166            var returnItems = new BaseItemDto[accessibleItems.Count];
 6167            List<(BaseItem, BaseItemDto)>? programTuples = null;
 6168            List<(BaseItemDto, LiveTvChannel)>? channelTuples = null;
 169
 170            // Batch-fetch user data for all items
 6171            Dictionary<Guid, UserItemData>? userDataBatch = null;
 6172            IReadOnlyDictionary<Guid, VersionResumeData>? resumeDataBatch = null;
 6173            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
 6182            List<Folder>? allCollectionFolders = null;
 6183            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
 6189            Dictionary<Guid, int>? childCountBatch = null;
 6190            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
 6200            Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null;
 6201            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.
 6213            IReadOnlyDictionary<string, MusicArtist[]>? artistsBatch = null;
 6214            var artistNames = new HashSet<string>(StringComparer.Ordinal);
 26215            foreach (var item in accessibleItems)
 216            {
 7217                if (item is IHasArtist hasArtist)
 218                {
 8219                    foreach (var name in hasArtist.Artists)
 220                    {
 0221                        if (!string.IsNullOrWhiteSpace(name))
 222                        {
 0223                            artistNames.Add(name);
 224                        }
 225                    }
 226                }
 227
 7228                if (item is IHasAlbumArtist hasAlbumArtist)
 229                {
 12230                    foreach (var name in hasAlbumArtist.AlbumArtists)
 231                    {
 2232                        if (!string.IsNullOrWhiteSpace(name))
 233                        {
 2234                            artistNames.Add(name);
 235                        }
 236                    }
 237                }
 238            }
 239
 6240            if (artistNames.Count > 0)
 241            {
 1242                artistsBatch = _libraryManager.GetArtists(artistNames.ToArray());
 243            }
 244
 245            // Batch-fetch people across all items to avoid one GetPeople query per item.
 6246            IReadOnlyDictionary<Guid, IReadOnlyList<PersonInfo>>? peopleBatch = null;
 6247            if (options.ContainsField(ItemFields.People))
 248            {
 1249                var peopleItemIds = accessibleItems.Where(i => i.SupportsPeople).Select(i => i.Id).ToList();
 1250                if (peopleItemIds.Count > 0)
 251                {
 1252                    peopleBatch = _libraryManager.GetPeopleByItems(peopleItemIds);
 253                }
 254            }
 255
 26256            for (int index = 0; index < accessibleItems.Count; index++)
 257            {
 7258                var item = accessibleItems[index];
 7259                var dto = GetBaseItemDtoInternal(
 7260                    item,
 7261                    options,
 7262                    user,
 7263                    owner,
 7264                    userDataBatch?.GetValueOrDefault(item.Id),
 7265                    allCollectionFolders,
 7266                    childCountBatch,
 7267                    playedCountBatch,
 7268                    artistsBatch,
 7269                    resumeDataBatch?.GetValueOrDefault(item.Id),
 7270                    peopleBatch);
 271
 7272                if (item is LiveTvChannel tvChannel)
 273                {
 0274                    (channelTuples ??= []).Add((dto, tvChannel));
 275                }
 7276                else if (item is LiveTvProgram)
 277                {
 0278                    (programTuples ??= []).Add((item, dto));
 279                }
 280
 7281                if (options.ContainsField(ItemFields.ItemCounts))
 282                {
 0283                    SetItemByNameInfo(dto, user);
 284                }
 285
 7286                returnItems[index] = dto;
 287            }
 288
 6289            if (programTuples is not null)
 290            {
 0291                LivetvManager.AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
 292            }
 293
 6294            if (channelTuples is not null)
 295            {
 0296                LivetvManager.AddChannelInfo(channelTuples, options, user);
 297            }
 298
 6299            return returnItems;
 300        }
 301
 302        public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner = null)
 303        {
 11304            var dto = GetBaseItemDtoInternal(item, options, user, owner, null);
 11305            if (item is LiveTvChannel tvChannel)
 306            {
 0307                LivetvManager.AddChannelInfo(new[] { (dto, tvChannel) }, options, user);
 308            }
 11309            else if (item is LiveTvProgram)
 310            {
 0311                LivetvManager.AddInfoToProgramDto(new[] { (item, dto) }, options.Fields, user).GetAwaiter().GetResult();
 312            }
 313
 11314            if (options.ContainsField(ItemFields.ItemCounts))
 315            {
 6316                SetItemByNameInfo(dto, user);
 317            }
 318
 11319            return dto;
 320        }
 321
 322        private BaseItemDto GetBaseItemDtoInternal(
 323            BaseItem item,
 324            DtoOptions options,
 325            User? user = null,
 326            BaseItem? owner = null,
 327            UserItemData? userData = null,
 328            List<Folder>? allCollectionFolders = null,
 329            Dictionary<Guid, int>? childCountBatch = null,
 330            Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null,
 331            IReadOnlyDictionary<string, MusicArtist[]>? artistsBatch = null,
 332            VersionResumeData? resumeData = null,
 333            IReadOnlyDictionary<Guid, IReadOnlyList<PersonInfo>>? peopleBatch = null)
 334        {
 18335            var dto = new BaseItemDto
 18336            {
 18337                ServerId = _appHost.SystemId
 18338            };
 339
 18340            if (item.SourceType == SourceType.Channel)
 341            {
 0342                dto.SourceType = item.SourceType.ToString();
 343            }
 344
 18345            if (options.ContainsField(ItemFields.People))
 346            {
 8347                IReadOnlyList<PersonInfo>? prefetchedPeople = null;
 8348                if (peopleBatch is not null)
 349                {
 350                    // The batch omits items with no people, so a miss means "no people",
 351                    // not "not fetched". Use an empty list to skip the per-item query.
 2352                    prefetchedPeople = peopleBatch.GetValueOrDefault(item.Id) ?? [];
 353                }
 354
 8355                AttachPeople(dto, item, user, prefetchedPeople);
 356            }
 357
 18358            if (options.ContainsField(ItemFields.PrimaryImageAspectRatio))
 359            {
 360                try
 361                {
 7362                    AttachPrimaryImageAspectRatio(dto, item);
 7363                }
 0364                catch (Exception ex)
 365                {
 366                    // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
 0367                    _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {ItemName}", item.Name);
 0368                }
 369            }
 370
 18371            if (options.ContainsField(ItemFields.DisplayPreferencesId))
 372            {
 6373                dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N", CultureInfo.InvariantCulture);
 374            }
 375
 18376            if (user is not null)
 377            {
 9378                AttachUserSpecificInfo(
 9379                    dto,
 9380                    item,
 9381                    user,
 9382                    options,
 9383                    userData,
 9384                    childCountBatch,
 9385                    playedCountBatch,
 9386                    resumeData);
 387            }
 388
 18389            if (item is IHasMediaSources
 18390                && options.ContainsField(ItemFields.MediaSources))
 391            {
 0392                dto.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray();
 393
 0394                NormalizeMediaSourceContainers(dto);
 395            }
 396
 18397            if (options.ContainsField(ItemFields.Studios))
 398            {
 6399                AttachStudios(dto, item);
 400            }
 401
 18402            AttachBasicFields(dto, item, owner, options, artistsBatch, user);
 403
 18404            if (options.ContainsField(ItemFields.CanDelete))
 405            {
 6406                dto.CanDelete = user is null
 6407                    ? item.CanDelete()
 6408                    : allCollectionFolders is not null
 6409                        ? item.CanDelete(user, allCollectionFolders)
 6410                        : item.CanDelete(user);
 411            }
 412
 18413            if (options.ContainsField(ItemFields.CanDownload))
 414            {
 6415                dto.CanDownload = user is null
 6416                    ? item.CanDownload()
 6417                    : item.CanDownload(user);
 418            }
 419
 18420            if (options.ContainsField(ItemFields.Etag))
 421            {
 6422                dto.Etag = item.GetEtag(user);
 423            }
 424
 18425            var activeRecording = _recordingsManager.GetActiveRecordingInfo(item.Path);
 18426            if (activeRecording is not null)
 427            {
 0428                dto.Type = BaseItemKind.Recording;
 0429                dto.CanDownload = false;
 0430                dto.RunTimeTicks = null;
 431
 0432                if (!string.IsNullOrEmpty(dto.SeriesName))
 433                {
 0434                    dto.EpisodeTitle = dto.Name;
 0435                    dto.Name = dto.SeriesName;
 436                }
 437
 0438                LivetvManager.AddInfoToRecordingDto(item, dto, activeRecording, user);
 439            }
 440
 18441            if (item is Audio audio)
 442            {
 0443                dto.HasLyrics = audio.GetMediaStreams().Any(s => s.Type == MediaStreamType.Lyric);
 444            }
 445
 18446            return dto;
 447        }
 448
 449        private static void NormalizeMediaSourceContainers(BaseItemDto dto)
 450        {
 0451            foreach (var mediaSource in dto.MediaSources)
 452            {
 0453                var container = mediaSource.Container;
 0454                if (string.IsNullOrEmpty(container))
 455                {
 456                    continue;
 457                }
 458
 0459                var containers = container.Split(',');
 0460                if (containers.Length < 2)
 461                {
 462                    continue;
 463                }
 464
 0465                var path = mediaSource.Path;
 0466                string? fileExtensionContainer = null;
 467
 0468                if (!string.IsNullOrEmpty(path))
 469                {
 0470                    path = Path.GetExtension(path);
 0471                    if (!string.IsNullOrEmpty(path))
 472                    {
 0473                        path = Path.GetExtension(path);
 0474                        if (!string.IsNullOrEmpty(path))
 475                        {
 0476                            path = path.TrimStart('.');
 477                        }
 478
 0479                        if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparison.OrdinalIgnoreCase)
 480                        {
 0481                            fileExtensionContainer = path;
 482                        }
 483                    }
 484                }
 485
 0486                mediaSource.Container = fileExtensionContainer ?? containers[0];
 487            }
 0488        }
 489
 490        /// <inheritdoc />
 491        /// TODO refactor this to use the new SetItemByNameInfo.
 492        /// Some callers already have the counts extracted so no reason to retrieve them again.
 493        public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem>? taggedItems, User? user =
 494        {
 0495            var dto = GetBaseItemDtoInternal(item, options, user);
 496
 0497            if (options.ContainsField(ItemFields.ItemCounts)
 0498                && taggedItems is not null
 0499                && taggedItems.Count != 0)
 500            {
 0501                SetItemByNameInfo(item, dto, taggedItems);
 502            }
 503
 0504            return dto;
 505        }
 506
 507        private void SetItemByNameInfo(BaseItemDto dto, User? user)
 508        {
 6509            if (!_relatedItemKinds.TryGetValue(dto.Type, out var relatedItemKinds))
 510            {
 6511                return;
 512            }
 513
 0514            var counts = _libraryManager.GetItemCountsForNameItem(dto.Type, dto.Id, relatedItemKinds, user);
 515
 0516            dto.AlbumCount = counts.AlbumCount;
 0517            dto.ArtistCount = counts.ArtistCount;
 0518            dto.EpisodeCount = counts.EpisodeCount;
 0519            dto.MovieCount = counts.MovieCount;
 0520            dto.MusicVideoCount = counts.MusicVideoCount;
 0521            dto.ProgramCount = counts.ProgramCount;
 0522            dto.SeriesCount = counts.SeriesCount;
 0523            dto.SongCount = counts.SongCount;
 0524            dto.TrailerCount = counts.TrailerCount;
 0525            dto.ChildCount = counts.TotalItemCount();
 0526        }
 527
 528        private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IReadOnlyList<BaseItem> taggedItems)
 529        {
 0530            if (item is MusicArtist)
 531            {
 0532                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0533                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0534                dto.SongCount = taggedItems.Count(i => i is Audio);
 535            }
 0536            else if (item is MusicGenre)
 537            {
 0538                dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
 0539                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0540                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0541                dto.SongCount = taggedItems.Count(i => i is Audio);
 542            }
 543            else
 544            {
 545                // This populates them all and covers Genre, Person, Studio, Year
 546
 0547                dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
 0548                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0549                dto.EpisodeCount = taggedItems.Count(i => i is Episode);
 0550                dto.MovieCount = taggedItems.Count(i => i is Movie);
 0551                dto.TrailerCount = taggedItems.Count(i => i is Trailer);
 0552                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0553                dto.SeriesCount = taggedItems.Count(i => i is Series);
 0554                dto.ProgramCount = taggedItems.Count(i => i is LiveTvProgram);
 0555                dto.SongCount = taggedItems.Count(i => i is Audio);
 556            }
 557
 0558            dto.ChildCount = taggedItems.Count;
 0559        }
 560
 561        /// <summary>
 562        /// Attaches the user specific info.
 563        /// </summary>
 564        private void AttachUserSpecificInfo(
 565            BaseItemDto dto,
 566            BaseItem item,
 567            User user,
 568            DtoOptions options,
 569            UserItemData? userData = null,
 570            Dictionary<Guid, int>? childCountBatch = null,
 571            Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null,
 572            VersionResumeData? resumeData = null)
 573        {
 9574            if (item.IsFolder)
 575            {
 9576                var folder = (Folder)item;
 577
 9578                if (options.EnableUserData)
 579                {
 9580                    if (userData is not null)
 581                    {
 582                        // Use pre-fetched user data
 3583                        dto.UserData = GetUserItemDataDto(userData, item.Id);
 3584                        (int Played, int Total)? precomputed = playedCountBatch is not null
 3585                            && playedCountBatch.TryGetValue(item.Id, out var counts) ? counts : null;
 3586                        item.FillUserDataDtoValues(dto.UserData, userData, dto, user, options, precomputed);
 587                    }
 588                    else
 589                    {
 590                        // Fall back to individual fetch
 6591                        dto.UserData = _userDataRepository.GetUserDataDto(item, dto, user, options);
 592                    }
 593                }
 594
 9595                if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library)
 596                {
 597                    // For these types we can try to optimize and assume these values will be equal
 9598                    if (item is MusicAlbum || item is Season || item is Playlist)
 599                    {
 0600                        dto.ChildCount = dto.RecursiveItemCount;
 0601                        var folderChildCount = folder.LinkedChildren.Length;
 602                        // The default is an empty array, so we can't reliably use the count when it's empty
 0603                        if (folderChildCount > 0)
 604                        {
 0605                            dto.ChildCount ??= folderChildCount;
 606                        }
 607                    }
 608
 9609                    if (options.ContainsField(ItemFields.ChildCount))
 610                    {
 6611                        dto.ChildCount ??= GetChildCount(folder, user, childCountBatch);
 612                    }
 613                }
 614
 9615                if (options.ContainsField(ItemFields.CumulativeRunTimeTicks))
 616                {
 6617                    dto.CumulativeRunTimeTicks = item.RunTimeTicks;
 618                }
 619
 9620                if (options.ContainsField(ItemFields.DateLastMediaAdded))
 621                {
 6622                    dto.DateLastMediaAdded = folder.DateLastMediaAdded;
 623                }
 624            }
 625            else
 626            {
 0627                if (options.EnableUserData)
 628                {
 0629                    if (userData is not null)
 630                    {
 631                        // Use pre-fetched user data
 0632                        dto.UserData = GetUserItemDataDto(userData, item.Id);
 0633                        item.FillUserDataDtoValues(dto.UserData, userData, dto, user, options);
 634
 635                        // For items with alternate versions, the most recently played version drives resume.
 0636                        resumeData?.ApplyTo(dto.UserData);
 637                    }
 638                    else
 639                    {
 640                        // Fall back to individual fetch
 0641                        dto.UserData = _userDataRepository.GetUserDataDto(item, user);
 642                    }
 643                }
 644            }
 645
 9646            if (options.ContainsField(ItemFields.PlayAccess))
 647            {
 6648                dto.PlayAccess = item.GetPlayAccess(user);
 649            }
 9650        }
 651
 652        private static UserItemDataDto GetUserItemDataDto(UserItemData data, Guid itemId)
 653        {
 3654            ArgumentNullException.ThrowIfNull(data);
 655
 3656            return new UserItemDataDto
 3657            {
 3658                IsFavorite = data.IsFavorite,
 3659                Likes = data.Likes,
 3660                PlaybackPositionTicks = data.PlaybackPositionTicks,
 3661                PlayCount = data.PlayCount,
 3662                Rating = data.Rating,
 3663                Played = data.Played,
 3664                LastPlayedDate = data.LastPlayedDate,
 3665                ItemId = itemId,
 3666                Key = data.Key
 3667            };
 668        }
 669
 670        private static int GetChildCount(Folder folder, User user, Dictionary<Guid, int>? childCountBatch)
 671        {
 672            // Right now this is too slow to calculate for top level folders on a per-user basis
 673            // Just return something so that apps that are expecting a value won't think the folders are empty
 6674            if (folder is ICollectionFolder || folder is UserView)
 675            {
 0676                return Random.Shared.Next(1, 10);
 677            }
 678
 679            // Use pre-fetched batch data if available
 6680            if (childCountBatch is not null && childCountBatch.TryGetValue(folder.Id, out var count))
 681            {
 0682                return count;
 683            }
 684
 685            // Fall back to individual query for special cases (Series, Season, etc.)
 6686            return folder.GetChildCount(user);
 687        }
 688
 689        private static void SetBookProperties(BaseItemDto dto, Book item)
 690        {
 0691            dto.SeriesName = item.SeriesName;
 0692        }
 693
 694        private static void SetPhotoProperties(BaseItemDto dto, Photo item)
 695        {
 0696            dto.CameraMake = item.CameraMake;
 0697            dto.CameraModel = item.CameraModel;
 0698            dto.Software = item.Software;
 0699            dto.ExposureTime = item.ExposureTime;
 0700            dto.FocalLength = item.FocalLength;
 0701            dto.ImageOrientation = item.Orientation;
 0702            dto.Aperture = item.Aperture;
 0703            dto.ShutterSpeed = item.ShutterSpeed;
 704
 0705            dto.Latitude = item.Latitude;
 0706            dto.Longitude = item.Longitude;
 0707            dto.Altitude = item.Altitude;
 0708            dto.IsoSpeedRating = item.IsoSpeedRating;
 709
 0710            var album = item.AlbumEntity;
 711
 0712            if (album is not null)
 713            {
 0714                dto.Album = album.Name;
 0715                dto.AlbumId = album.Id;
 716            }
 0717        }
 718
 719        private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item)
 720        {
 0721            if (!string.IsNullOrEmpty(item.Album))
 722            {
 0723                var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery
 0724                {
 0725                    IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
 0726                    Name = item.Album,
 0727                    Limit = 1
 0728                });
 729
 0730                if (parentAlbumIds.Count > 0)
 731                {
 0732                    dto.AlbumId = parentAlbumIds[0];
 733                }
 734            }
 735
 0736            dto.Album = item.Album;
 0737        }
 738
 739        private string[] GetImageTags(BaseItem item, List<ItemImageInfo> images)
 740        {
 18741            return images
 18742                .Select(p => GetImageCacheTag(item, p))
 18743                .Where(i => i is not null)
 18744                .ToArray()!; // null values got filtered out
 745        }
 746
 747        private string? GetImageCacheTag(BaseItem item, ItemImageInfo image)
 748        {
 749            try
 750            {
 11751                return _imageProcessor.GetImageCacheTag(item, image);
 752            }
 0753            catch (Exception ex)
 754            {
 0755                _logger.LogError(ex, "Error getting {ImageType} image info for {Path}", image.Type, image.Path);
 0756                return null;
 757            }
 11758        }
 759
 760        /// <summary>
 761        /// Attaches People DTO's to a DTOBaseItem.
 762        /// </summary>
 763        /// <param name="dto">The dto.</param>
 764        /// <param name="item">The item.</param>
 765        /// <param name="user">The requesting user.</param>
 766        /// <param name="prefetchedPeople">People fetched in batch by the caller; when null the people are queried per i
 767        private void AttachPeople(BaseItemDto dto, BaseItem item, User? user = null, IReadOnlyList<PersonInfo>? prefetch
 768        {
 769            // When rendering a page of items the caller batch-fetches people for every item up
 770            // front and passes them in, avoiding one GetPeople query per item. Fall back to the
 771            // per-item query for the single item path where no batch is available.
 8772            var source = prefetchedPeople ?? _libraryManager.GetPeople(item);
 773
 774            // Ordering by person type to ensure actors and artists are at the front.
 775            // This is taking advantage of the fact that they both begin with A
 776            // This should be improved in the future
 8777            var people = source.OrderBy(i => i.SortOrder ?? int.MaxValue)
 8778                .ThenBy(i =>
 8779                {
 8780                    if (i.IsType(PersonKind.Actor))
 8781                    {
 8782                        return 0;
 8783                    }
 8784
 8785                    if (i.IsType(PersonKind.GuestStar))
 8786                    {
 8787                        return 1;
 8788                    }
 8789
 8790                    if (i.IsType(PersonKind.Director))
 8791                    {
 8792                        return 2;
 8793                    }
 8794
 8795                    if (i.IsType(PersonKind.Writer))
 8796                    {
 8797                        return 3;
 8798                    }
 8799
 8800                    if (i.IsType(PersonKind.Producer))
 8801                    {
 8802                        return 4;
 8803                    }
 8804
 8805                    if (i.IsType(PersonKind.Composer))
 8806                    {
 8807                        return 4;
 8808                    }
 8809
 8810                    return 10;
 8811                })
 8812                .ToList();
 813
 8814            var list = new List<BaseItemPerson>();
 815
 8816            Dictionary<string, Person> dictionary = people.Select(p => p.Name)
 8817                .Distinct(StringComparer.OrdinalIgnoreCase).Select(c =>
 8818                {
 8819                    try
 8820                    {
 8821                        return _libraryManager.GetPerson(c);
 8822                    }
 8823                    catch (Exception ex)
 8824                    {
 8825                        _logger.LogError(ex, "Error getting person {Name}", c);
 8826                        return null;
 8827                    }
 8828                }).Where(i => i is not null)
 8829                .Where(i => user is null || i!.IsVisible(user))
 8830                .DistinctBy(x => x!.Name, StringComparer.OrdinalIgnoreCase)
 8831                .ToDictionary(i => i!.Name, StringComparer.OrdinalIgnoreCase)!; // null values got filtered out
 832
 20833            for (var i = 0; i < people.Count; i++)
 834            {
 2835                var person = people[i];
 836
 2837                var baseItemPerson = new BaseItemPerson
 2838                {
 2839                    Name = person.Name,
 2840                    Role = person.Role,
 2841                    Type = person.Type
 2842                };
 843
 2844                if (dictionary.TryGetValue(person.Name, out Person? entity))
 845                {
 2846                    baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
 2847                    baseItemPerson.Id = entity.Id;
 2848                    if (dto.ImageBlurHashes is not null)
 849                    {
 850                        // Only add BlurHash for the person's image.
 0851                        baseItemPerson.ImageBlurHashes = [];
 0852                        foreach (var (imageType, blurHash) in dto.ImageBlurHashes)
 853                        {
 0854                            if (blurHash is not null)
 855                            {
 0856                                baseItemPerson.ImageBlurHashes[imageType] = [];
 0857                                foreach (var (imageId, blurHashValue) in blurHash)
 858                                {
 0859                                    if (string.Equals(baseItemPerson.PrimaryImageTag, imageId, StringComparison.OrdinalI
 860                                    {
 0861                                        baseItemPerson.ImageBlurHashes[imageType][imageId] = blurHashValue;
 862                                    }
 863                                }
 864                            }
 865                        }
 866                    }
 867
 2868                    list.Add(baseItemPerson);
 869                }
 870            }
 871
 8872            dto.People = list.ToArray();
 8873        }
 874
 875        /// <summary>
 876        /// Attaches the studios.
 877        /// </summary>
 878        /// <param name="dto">The dto.</param>
 879        /// <param name="item">The item.</param>
 880        private void AttachStudios(BaseItemDto dto, BaseItem item)
 881        {
 6882            dto.Studios = item.Studios
 6883                .Where(i => !string.IsNullOrEmpty(i))
 6884                .Select(i => new NameGuidPair
 6885                {
 6886                    Name = i,
 6887                    Id = _libraryManager.GetStudioId(i)
 6888                })
 6889                .ToArray();
 6890        }
 891
 892        private void AttachGenreItems(BaseItemDto dto, BaseItem item)
 893        {
 6894            dto.GenreItems = item.Genres
 6895                .Where(i => !string.IsNullOrEmpty(i))
 6896                .Select(i => new NameGuidPair
 6897                {
 6898                    Name = i,
 6899                    Id = GetGenreId(i, item)
 6900                })
 6901                .ToArray();
 6902        }
 903
 904        private Guid GetGenreId(string name, BaseItem owner)
 905        {
 0906            if (owner is IHasMusicGenres)
 907            {
 0908                return _libraryManager.GetMusicGenreId(name);
 909            }
 910
 0911            return _libraryManager.GetGenreId(name);
 912        }
 913
 914        private string? GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ImageType imageType, int imageIndex = 0)
 915        {
 8916            var image = item.GetImageInfo(imageType, imageIndex);
 8917            if (image is not null)
 918            {
 3919                return GetTagAndFillBlurhash(dto, item, image);
 920            }
 921
 5922            return null;
 923        }
 924
 925        private string? GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ItemImageInfo image)
 926        {
 11927            var tag = GetImageCacheTag(item, image);
 11928            if (tag is null)
 929            {
 0930                return null;
 931            }
 932
 11933            if (!string.IsNullOrEmpty(image.BlurHash))
 934            {
 0935                dto.ImageBlurHashes ??= [];
 936
 0937                if (!dto.ImageBlurHashes.TryGetValue(image.Type, out var value))
 938                {
 0939                    value = [];
 0940                    dto.ImageBlurHashes[image.Type] = value;
 941                }
 942
 0943                value[tag] = image.BlurHash;
 944            }
 945
 11946            return tag;
 947        }
 948
 949        private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, int limit)
 950        {
 18951            return GetTagsAndFillBlurhashes(dto, item, imageType, item.GetImages(imageType).Take(limit).ToList());
 952        }
 953
 954        private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, List<ItemImageInf
 955        {
 18956            var tags = GetImageTags(item, images);
 18957            var hashes = new Dictionary<string, string>();
 36958            for (int i = 0; i < images.Count; i++)
 959            {
 0960                var img = images[i];
 0961                if (!string.IsNullOrEmpty(img.BlurHash))
 962                {
 0963                    var tag = tags[i];
 0964                    hashes[tag] = img.BlurHash;
 965                }
 966            }
 967
 18968            if (hashes.Count > 0)
 969            {
 0970                dto.ImageBlurHashes ??= [];
 971
 0972                dto.ImageBlurHashes[imageType] = hashes;
 973            }
 974
 18975            return tags;
 976        }
 977
 978        /// <summary>
 979        /// Sets simple property values on a DTOBaseItem.
 980        /// </summary>
 981        /// <param name="dto">The dto.</param>
 982        /// <param name="item">The item.</param>
 983        /// <param name="owner">The owner.</param>
 984        /// <param name="options">The options.</param>
 985        /// <param name="artistsBatch">Optional pre-fetched artist lookup shared across a batch of items.</param>
 986        /// <param name="user">The user, for per-user values such as the accessible media source count.</param>
 987        private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem? owner, DtoOptions options, IReadOnlyDic
 988        {
 18989            if (options.ContainsField(ItemFields.DateCreated))
 990            {
 6991                dto.DateCreated = item.DateCreated;
 992            }
 993
 18994            if (options.ContainsField(ItemFields.Settings))
 995            {
 6996                dto.LockedFields = item.LockedFields;
 6997                dto.LockData = item.IsLocked;
 6998                dto.ForcedSortName = item.ForcedSortName;
 999            }
 1000
 181001            dto.Container = item.Container;
 181002            dto.EndDate = item.EndDate;
 1003
 181004            if (options.ContainsField(ItemFields.ExternalUrls))
 1005            {
 61006                dto.ExternalUrls = _providerManager.GetExternalUrls(item).ToArray();
 1007            }
 1008
 181009            if (options.ContainsField(ItemFields.Tags))
 1010            {
 61011                dto.Tags = item.Tags;
 1012            }
 1013
 181014            if (item is IHasAspectRatio hasAspectRatio)
 1015            {
 31016                dto.AspectRatio = hasAspectRatio.AspectRatio;
 1017            }
 1018
 181019            dto.ImageBlurHashes = [];
 1020
 181021            var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
 181022            if (backdropLimit > 0)
 1023            {
 181024                dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit);
 1025            }
 1026
 181027            if (options.ContainsField(ItemFields.Genres))
 1028            {
 61029                dto.Genres = item.Genres;
 61030                AttachGenreItems(dto, item);
 1031            }
 1032
 181033            if (options.EnableImages)
 1034            {
 181035                dto.ImageTags = [];
 1036
 1037                // Prevent implicitly captured closure
 181038                var currentItem = item;
 461039                foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)))
 1040                {
 51041                    if (options.GetImageLimit(image.Type) > 0)
 1042                    {
 51043                        var tag = GetTagAndFillBlurhash(dto, item, image);
 1044
 51045                        if (tag is not null)
 1046                        {
 51047                            dto.ImageTags[image.Type] = tag;
 1048                        }
 1049                    }
 1050                }
 1051            }
 1052
 181053            dto.Id = item.Id;
 181054            dto.IndexNumber = item.IndexNumber;
 181055            dto.ParentIndexNumber = item.ParentIndexNumber;
 1056
 181057            if (item.IsFolder)
 1058            {
 151059                dto.IsFolder = true;
 1060            }
 31061            else if (item is IHasMediaSources)
 1062            {
 31063                dto.IsFolder = false;
 1064            }
 1065
 181066            dto.MediaType = item.MediaType;
 1067
 181068            if (item is not LiveTvProgram)
 1069            {
 181070                dto.LocationType = item.LocationType;
 1071            }
 1072
 181073            dto.Audio = item.Audio;
 1074
 181075            if (options.ContainsField(ItemFields.Settings))
 1076            {
 61077                dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
 61078                dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
 1079            }
 1080
 181081            dto.CriticRating = item.CriticRating;
 1082
 181083            if (item is IHasDisplayOrder hasDisplayOrder)
 1084            {
 01085                dto.DisplayOrder = hasDisplayOrder.DisplayOrder;
 1086            }
 1087
 181088            if (item is IHasCollectionType hasCollectionType)
 1089            {
 51090                dto.CollectionType = hasCollectionType.CollectionType;
 1091            }
 1092
 181093            if (options.ContainsField(ItemFields.RemoteTrailers))
 1094            {
 61095                dto.RemoteTrailers = item.RemoteTrailers;
 1096            }
 1097
 181098            dto.Name = item.Name;
 181099            dto.OfficialRating = item.OfficialRating;
 1100
 181101            if (options.ContainsField(ItemFields.Overview))
 1102            {
 61103                dto.Overview = item.Overview;
 1104            }
 1105
 181106            if (options.ContainsField(ItemFields.OriginalTitle))
 1107            {
 61108                dto.OriginalTitle = item.OriginalTitle;
 1109            }
 1110
 181111            dto.OriginalLanguage = item.OriginalLanguage;
 1112
 181113            if (options.ContainsField(ItemFields.ParentId))
 1114            {
 61115                dto.ParentId = item.DisplayParentId;
 1116            }
 1117
 181118            AddInheritedImages(dto, item, options, owner, artistsBatch);
 1119
 181120            if (options.ContainsField(ItemFields.Path))
 1121            {
 61122                dto.Path = GetMappedPath(item, owner);
 1123            }
 1124
 181125            if (options.ContainsField(ItemFields.EnableMediaSourceDisplay))
 1126            {
 61127                dto.EnableMediaSourceDisplay = item.EnableMediaSourceDisplay;
 1128            }
 1129
 181130            dto.PremiereDate = item.PremiereDate;
 181131            dto.ProductionYear = item.ProductionYear;
 1132
 181133            if (options.ContainsField(ItemFields.ProviderIds))
 1134            {
 61135                dto.ProviderIds = item.ProviderIds;
 1136            }
 1137
 181138            dto.RunTimeTicks = item.RunTimeTicks;
 1139
 181140            if (options.ContainsField(ItemFields.SortName))
 1141            {
 61142                dto.SortName = item.SortName;
 1143            }
 1144
 181145            if (options.ContainsField(ItemFields.CustomRating))
 1146            {
 61147                dto.CustomRating = item.CustomRating;
 1148            }
 1149
 181150            if (options.ContainsField(ItemFields.Taglines))
 1151            {
 61152                if (!string.IsNullOrEmpty(item.Tagline))
 1153                {
 01154                    dto.Taglines = new string[] { item.Tagline };
 1155                }
 1156
 61157                dto.Taglines ??= Array.Empty<string>();
 1158            }
 1159
 181160            dto.Type = item.GetBaseItemKind();
 181161            if ((item.CommunityRating ?? 0) > 0)
 1162            {
 01163                dto.CommunityRating = item.CommunityRating;
 1164            }
 1165
 181166            if (item is ISupportsPlaceHolders supportsPlaceHolders && supportsPlaceHolders.IsPlaceHolder)
 1167            {
 01168                dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder;
 1169            }
 1170
 181171            if (item.LUFS.HasValue)
 1172            {
 1173                // -18 LUFS reference, same as ReplayGain 2.0, compatible with ReplayGain 1.0
 01174                dto.NormalizationGain = -18f - item.LUFS;
 1175            }
 181176            else if (item.NormalizationGain.HasValue)
 1177            {
 01178                dto.NormalizationGain = item.NormalizationGain;
 1179            }
 1180
 1181            // Add audio info
 181182            if (item is Audio audio)
 1183            {
 01184                dto.Album = audio.Album;
 01185                dto.ExtraType = audio.ExtraType;
 1186
 01187                var albumParent = audio.AlbumEntity;
 1188
 01189                if (albumParent is not null)
 1190                {
 01191                    dto.AlbumId = albumParent.Id;
 01192                    dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary);
 01193                    if (albumParent.LUFS.HasValue)
 1194                    {
 1195                        // -18 LUFS reference, same as ReplayGain 2.0, compatible with ReplayGain 1.0
 01196                        dto.AlbumNormalizationGain = -18f - albumParent.LUFS;
 1197                    }
 01198                    else if (albumParent.NormalizationGain.HasValue)
 1199                    {
 01200                        dto.AlbumNormalizationGain = albumParent.NormalizationGain;
 1201                    }
 1202                }
 1203
 1204                // if (options.ContainsField(ItemFields.MediaSourceCount))
 1205                // {
 1206                // Songs always have one
 1207                // }
 1208            }
 1209
 181210            if (item is IHasArtist hasArtist)
 1211            {
 41212                dto.Artists = hasArtist.Artists;
 1213
 1214                // var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
 1215                // {
 1216                //    EnableTotalRecordCount = false,
 1217                //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
 1218                // });
 1219
 1220                // dto.ArtistItems = artistItems.Items
 1221                //    .Select(i =>
 1222                //    {
 1223                //        var artist = i.Item1;
 1224                //        return new NameIdPair
 1225                //        {
 1226                //            Name = artist.Name,
 1227                //            Id = artist.Id.ToString("N", CultureInfo.InvariantCulture)
 1228                //        };
 1229                //    })
 1230                //    .ToList();
 1231
 1232                // Include artists that are not in the database yet, e.g., just added via metadata editor
 1233                // var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
 41234                var artistsLookup = artistsBatch
 41235                    ?? _libraryManager.GetArtists([.. hasArtist.Artists.Where(e => !string.IsNullOrWhiteSpace(e))]);
 1236
 41237                dto.ArtistItems = hasArtist.Artists
 41238                    .Where(name => !string.IsNullOrWhiteSpace(name))
 41239                    .Distinct()
 41240                    .Select(name => artistsLookup.TryGetValue(name, out var artists) && artists.Length > 0
 41241                        ? new NameGuidPair { Name = name, Id = artists[0].Id }
 41242                        : null)
 41243                    .Where(item => item is not null)
 41244                    .ToArray();
 1245            }
 1246
 181247            if (item is IHasAlbumArtist hasAlbumArtist)
 1248            {
 41249                dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
 1250
 1251                // var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
 1252                // {
 1253                //    EnableTotalRecordCount = false,
 1254                //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
 1255                // });
 1256
 1257                // dto.AlbumArtists = artistItems.Items
 1258                //    .Select(i =>
 1259                //    {
 1260                //        var artist = i.Item1;
 1261                //        return new NameIdPair
 1262                //        {
 1263                //            Name = artist.Name,
 1264                //            Id = artist.Id.ToString("N", CultureInfo.InvariantCulture)
 1265                //        };
 1266                //    })
 1267                //    .ToList();
 1268
 41269                var albumArtistsLookup = artistsBatch
 41270                    ?? _libraryManager.GetArtists([.. hasAlbumArtist.AlbumArtists.Where(e => !string.IsNullOrWhiteSpace(
 1271
 41272                dto.AlbumArtists = hasAlbumArtist.AlbumArtists
 41273                    .Where(name => !string.IsNullOrWhiteSpace(name))
 41274                    .Distinct()
 41275                    .Select(name => albumArtistsLookup.TryGetValue(name, out var albumArtists) && albumArtists.Length > 
 41276                        ? new NameGuidPair { Name = name, Id = albumArtists[0].Id }
 41277                        : null)
 41278                    .Where(item => item is not null)
 41279                    .ToArray();
 1280            }
 1281
 1282            // Add video info
 181283            if (item is Video video)
 1284            {
 31285                dto.VideoType = video.VideoType;
 31286                dto.Video3DFormat = video.Video3DFormat;
 31287                dto.IsoType = video.IsoType;
 1288
 31289                if (video.HasSubtitles)
 1290                {
 01291                    dto.HasSubtitles = video.HasSubtitles;
 1292                }
 1293
 31294                if (video.AdditionalParts.Length != 0)
 1295                {
 01296                    dto.PartCount = video.AdditionalParts.Length + 1;
 1297                }
 1298
 31299                if (options.ContainsField(ItemFields.MediaSourceCount))
 1300                {
 1301                    // Match the per-user filtering of the media sources: versions the user cannot
 1302                    // access are not selectable, so they must not count towards the badge either.
 01303                    var mediaSourceCount = user is null
 01304                        || (!video.PrimaryVersionId.HasValue && video.LinkedAlternateVersions.Length == 0 && !video.HasL
 01305                            ? video.MediaSourceCount
 01306                            : video.GetAllVersions().Count(v => v.Id.Equals(video.Id) || v.IsVisibleStandalone(user));
 01307                    if (mediaSourceCount != 1)
 1308                    {
 01309                        dto.MediaSourceCount = mediaSourceCount;
 1310                    }
 1311                }
 1312
 31313                if (options.ContainsField(ItemFields.Trickplay))
 1314                {
 01315                    var trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult();
 01316                    dto.Trickplay = trickplay.ToDictionary(
 01317                        mediaStream => mediaStream.Key,
 01318                        mediaStream => mediaStream.Value.ToDictionary(
 01319                            width => width.Key,
 01320                            width => new TrickplayInfoDto(width.Value)));
 1321                }
 1322
 31323                dto.ExtraType = video.ExtraType;
 1324            }
 1325
 181326            if (options.ContainsField(ItemFields.Chapters))
 1327            {
 61328                dto.Chapters = _chapterManager.GetChapters(item.Id).ToList();
 1329            }
 1330
 181331            if (options.ContainsField(ItemFields.MediaStreams))
 1332            {
 1333                // Add VideoInfo
 61334                if (item is IHasMediaSources)
 1335                {
 1336                    MediaStream[] mediaStreams;
 1337
 01338                    if (dto.MediaSources is not null && dto.MediaSources.Length > 0)
 1339                    {
 01340                        if (item.SourceType == SourceType.Channel)
 1341                        {
 01342                            mediaStreams = dto.MediaSources[0].MediaStreams.ToArray();
 1343                        }
 1344                        else
 1345                        {
 01346                            string id = item.Id.ToString("N", CultureInfo.InvariantCulture);
 01347                            mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, id, StringComparison.OrdinalI
 01348                                .SelectMany(i => i.MediaStreams)
 01349                                .ToArray();
 1350                        }
 1351                    }
 1352                    else
 1353                    {
 01354                        mediaStreams = _mediaSourceManager.GetStaticMediaSources(item, true)[0].MediaStreams.ToArray();
 1355                    }
 1356
 01357                    dto.MediaStreams = mediaStreams;
 1358                }
 1359            }
 1360
 181361            BaseItem[]? allExtras = null;
 1362
 181363            if (options.ContainsField(ItemFields.SpecialFeatureCount))
 1364            {
 61365                allExtras = item.GetExtras().ToArray();
 61366                dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contai
 1367            }
 1368
 181369            if (options.ContainsField(ItemFields.LocalTrailerCount))
 1370            {
 61371                if (item is IHasTrailers hasTrailers)
 1372                {
 01373                    dto.LocalTrailerCount = hasTrailers.LocalTrailers.Count;
 1374                }
 1375                else
 1376                {
 61377                    dto.LocalTrailerCount = (allExtras ?? item.GetExtras()).Count(i => i.ExtraType == ExtraType.Trailer)
 1378                }
 1379            }
 1380
 1381            // Add EpisodeInfo
 181382            if (item is Episode episode)
 1383            {
 31384                dto.IndexNumberEnd = episode.IndexNumberEnd;
 31385                dto.SeriesName = episode.SeriesName;
 1386
 31387                if (options.ContainsField(ItemFields.SpecialEpisodeNumbers))
 1388                {
 01389                    dto.AirsAfterSeasonNumber = episode.AirsAfterSeasonNumber;
 01390                    dto.AirsBeforeEpisodeNumber = episode.AirsBeforeEpisodeNumber;
 01391                    dto.AirsBeforeSeasonNumber = episode.AirsBeforeSeasonNumber;
 1392                }
 1393
 31394                dto.SeasonName = episode.SeasonName;
 31395                dto.SeasonId = episode.SeasonId;
 31396                dto.SeriesId = episode.SeriesId;
 1397
 31398                Series? episodeSeries = null;
 1399
 1400                // this block will add the series poster for episodes without a poster
 1401                // TODO maybe remove the if statement entirely
 1402                // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
 1403                {
 31404                    episodeSeries ??= episode.Series;
 31405                    if (episodeSeries is not null)
 1406                    {
 31407                        dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
 31408                        if (dto.ImageTags is null || !dto.ImageTags.ContainsKey(ImageType.Primary))
 1409                        {
 01410                            AttachPrimaryImageAspectRatio(dto, episodeSeries);
 1411                        }
 1412                    }
 1413                }
 1414
 31415                if (options.GetImageLimit(ImageType.Primary) > 0)
 1416                {
 31417                    var episodeSeason = episode.Season;
 31418                    var seasonPrimaryTag = episodeSeason is not null
 31419                        ? GetTagAndFillBlurhash(dto, episodeSeason, ImageType.Primary)
 31420                        : null;
 1421
 31422                    if (seasonPrimaryTag is not null)
 1423                    {
 11424                        dto.ParentPrimaryImageItemId = episodeSeason!.Id;
 11425                        dto.ParentPrimaryImageTag = seasonPrimaryTag;
 1426                    }
 21427                    else if (episodeSeries is not null && dto.SeriesPrimaryImageTag is not null)
 1428                    {
 11429                        dto.ParentPrimaryImageItemId = episodeSeries.Id;
 11430                        dto.ParentPrimaryImageTag = dto.SeriesPrimaryImageTag;
 1431                    }
 1432                }
 1433
 31434                if (options.ContainsField(ItemFields.SeriesStudio))
 1435                {
 01436                    episodeSeries ??= episode.Series;
 01437                    if (episodeSeries is not null)
 1438                    {
 01439                        dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
 1440                    }
 1441                }
 1442            }
 1443
 1444            // Add SeriesInfo
 1445            Series? series;
 181446            if (item is Series tmp)
 1447            {
 01448                series = tmp;
 01449                dto.AirDays = series.AirDays;
 01450                dto.AirTime = series.AirTime;
 01451                dto.Status = series.Status?.ToString();
 1452            }
 1453
 1454            // Add SeasonInfo
 181455            if (item is Season season)
 1456            {
 01457                dto.SeriesName = season.SeriesName;
 01458                dto.SeriesId = season.SeriesId;
 1459
 01460                series = null;
 1461
 01462                if (options.ContainsField(ItemFields.SeriesStudio))
 1463                {
 01464                    series ??= season.Series;
 01465                    if (series is not null)
 1466                    {
 01467                        dto.SeriesStudio = series.Studios.FirstOrDefault();
 1468                    }
 1469                }
 1470
 1471                // this block will add the series poster for seasons without a poster
 1472                // TODO maybe remove the if statement entirely
 1473                // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
 1474                {
 01475                    series ??= season.Series;
 01476                    if (series is not null)
 1477                    {
 01478                        dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
 01479                        if (dto.ImageTags is null || !dto.ImageTags.ContainsKey(ImageType.Primary))
 1480                        {
 01481                            AttachPrimaryImageAspectRatio(dto, series);
 1482                        }
 1483                    }
 1484                }
 1485            }
 1486
 181487            if (item is MusicVideo musicVideo)
 1488            {
 01489                SetMusicVideoProperties(dto, musicVideo);
 1490            }
 1491
 181492            if (item is Book book)
 1493            {
 01494                SetBookProperties(dto, book);
 1495            }
 1496
 181497            if (options.ContainsField(ItemFields.ProductionLocations))
 1498            {
 61499                if (item.ProductionLocations.Length > 0 || item is Movie)
 1500                {
 01501                    dto.ProductionLocations = item.ProductionLocations;
 1502                }
 1503            }
 1504
 181505            if (options.ContainsField(ItemFields.Width))
 1506            {
 61507                var width = item.Width;
 61508                if (width > 0)
 1509                {
 01510                    dto.Width = width;
 1511                }
 1512            }
 1513
 181514            if (options.ContainsField(ItemFields.Height))
 1515            {
 61516                var height = item.Height;
 61517                if (height > 0)
 1518                {
 01519                    dto.Height = height;
 1520                }
 1521            }
 1522
 181523            if (options.ContainsField(ItemFields.IsHD))
 1524            {
 1525                // Compatibility
 61526                if (item.IsHD)
 1527                {
 01528                    dto.IsHD = true;
 1529                }
 1530            }
 1531
 181532            if (item is Photo photo)
 1533            {
 01534                SetPhotoProperties(dto, photo);
 1535            }
 1536
 181537            dto.ChannelId = item.ChannelId;
 1538
 181539            if (item.SourceType == SourceType.Channel)
 1540            {
 01541                var channel = _libraryManager.GetItemById(item.ChannelId);
 01542                if (channel is not null)
 1543                {
 01544                    dto.ChannelName = channel.Name;
 1545                }
 1546            }
 181547        }
 1548
 1549        private BaseItem? GetImageDisplayParent(BaseItem currentItem, BaseItem originalItem, IReadOnlyDictionary<string,
 1550        {
 101551            if (currentItem is MusicAlbum musicAlbum)
 1552            {
 41553                var artist = GetBatchedAlbumArtist(musicAlbum, artistsBatch) ?? musicAlbum.GetMusicArtist(new DtoOptions
 41554                if (artist is not null)
 1555                {
 21556                    return artist;
 1557                }
 1558            }
 1559
 81560            var parent = currentItem.DisplayParent ?? currentItem.GetOwner() ?? currentItem.GetParent();
 1561
 81562            if (parent is null && originalItem is not UserRootFolder && originalItem is not UserView && originalItem is 
 1563            {
 21564                parent = _libraryManager.GetCollectionFolders(originalItem).FirstOrDefault();
 1565            }
 1566
 81567            return parent;
 1568        }
 1569
 1570        private static MusicArtist? GetBatchedAlbumArtist(MusicAlbum album, IReadOnlyDictionary<string, MusicArtist[]>? 
 1571        {
 41572            if (artistsBatch is null)
 1573            {
 21574                return null;
 1575            }
 1576
 21577            var name = album.AlbumArtists.Count > 0 ? album.AlbumArtists[0] : null;
 21578            return !string.IsNullOrEmpty(name) && artistsBatch.TryGetValue(name, out var artists) && artists.Length > 0
 21579                ? artists[0]
 21580                : null;
 1581        }
 1582
 1583        private void AddInheritedImages(BaseItemDto dto, BaseItem item, DtoOptions options, BaseItem? owner, IReadOnlyDi
 1584        {
 181585            if (item is UserView { ViewType: CollectionType.playlists } playlistsView
 181586                && options.GetImageLimit(ImageType.Primary) > 0
 181587                && !playlistsView.DisplayParentId.IsEmpty())
 1588            {
 21589                var displayParent = _libraryManager.GetItemById(playlistsView.DisplayParentId);
 21590                var displayParentPrimaryImage = displayParent?.GetImageInfo(ImageType.Primary, 0);
 1591
 21592                if (displayParentPrimaryImage is not null)
 1593                {
 11594                    dto.ImageTags?.Remove(ImageType.Primary);
 11595                    dto.ParentPrimaryImageItemId = displayParent!.Id;
 11596                    dto.ParentPrimaryImageTag = GetTagAndFillBlurhash(dto, displayParent, displayParentPrimaryImage);
 1597                }
 1598            }
 1599
 181600            if (!item.SupportsInheritedParentImages)
 1601            {
 111602                return;
 1603            }
 1604
 71605            var logoLimit = options.GetImageLimit(ImageType.Logo);
 71606            var artLimit = options.GetImageLimit(ImageType.Art);
 71607            var thumbLimit = options.GetImageLimit(ImageType.Thumb);
 71608            var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
 1609
 1610            // For now. Emby apps are not using this
 71611            artLimit = 0;
 1612
 71613            if (logoLimit == 0 && artLimit == 0 && thumbLimit == 0 && backdropLimit == 0)
 1614            {
 01615                return;
 1616            }
 1617
 71618            BaseItem? parent = null;
 71619            var isFirst = true;
 1620
 71621            var imageTags = dto.ImageTags;
 1622
 101623            while ((!(imageTags is not null && imageTags.ContainsKey(ImageType.Logo)) && logoLimit > 0)
 101624                || (!(imageTags is not null && imageTags.ContainsKey(ImageType.Art)) && artLimit > 0)
 101625                || (!(imageTags is not null && imageTags.ContainsKey(ImageType.Thumb)) && thumbLimit > 0)
 101626                || parent is Series)
 1627            {
 101628                parent ??= isFirst ? GetImageDisplayParent(item, item, artistsBatch) ?? owner : parent;
 101629                if (parent is null)
 1630                {
 1631                    break;
 1632                }
 1633
 81634                var allImages = parent.ImageInfos;
 1635
 81636                if (logoLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Logo)) && dto.ParentLogo
 1637                {
 81638                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Logo);
 1639
 81640                    if (image is not null)
 1641                    {
 01642                        dto.ParentLogoItemId = parent.Id;
 01643                        dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1644                    }
 1645                }
 1646
 81647                if (artLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtIte
 1648                {
 01649                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
 1650
 01651                    if (image is not null)
 1652                    {
 01653                        dto.ParentArtItemId = parent.Id;
 01654                        dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1655                    }
 1656                }
 1657
 81658                if (thumbLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentT
 1659                {
 81660                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
 1661
 81662                    if (image is not null)
 1663                    {
 21664                        dto.ParentThumbItemId = parent.Id;
 21665                        dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1666                    }
 1667                }
 1668
 81669                if (backdropLimit > 0 && !((dto.BackdropImageTags is not null && dto.BackdropImageTags.Length > 0) || (d
 1670                {
 81671                    var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
 1672
 81673                    if (images.Count > 0)
 1674                    {
 01675                        dto.ParentBackdropItemId = parent.Id;
 01676                        dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images);
 1677                    }
 1678                }
 1679
 81680                isFirst = false;
 1681
 81682                if (!parent.SupportsInheritedParentImages)
 1683                {
 1684                    break;
 1685                }
 1686
 31687                parent = GetImageDisplayParent(parent, item, artistsBatch);
 1688            }
 71689        }
 1690
 1691        private string GetMappedPath(BaseItem item, BaseItem? ownerItem)
 1692        {
 61693            var path = item.Path;
 1694
 61695            if (item.IsFileProtocol)
 1696            {
 61697                path = _libraryManager.GetPathAfterNetworkSubstitution(path, ownerItem ?? item);
 1698            }
 1699
 61700            return path;
 1701        }
 1702
 1703        /// <summary>
 1704        /// Attaches the primary image aspect ratio.
 1705        /// </summary>
 1706        /// <param name="dto">The dto.</param>
 1707        /// <param name="item">The item.</param>
 1708        public void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item)
 1709        {
 71710            dto.PrimaryImageAspectRatio = GetPrimaryImageAspectRatio(item);
 71711        }
 1712
 1713        public double? GetPrimaryImageAspectRatio(BaseItem item)
 1714        {
 71715            var imageInfo = item.GetImageInfo(ImageType.Primary, 0);
 1716
 71717            if (imageInfo is null)
 1718            {
 61719                return null;
 1720            }
 1721
 11722            if (!imageInfo.IsLocalFile)
 1723            {
 11724                return item.GetDefaultPrimaryImageAspectRatio();
 1725            }
 1726
 1727            try
 1728            {
 01729                var size = _imageProcessor.GetImageDimensions(item, imageInfo);
 01730                var width = size.Width;
 01731                var height = size.Height;
 01732                if (width > 0 && height > 0)
 1733                {
 01734                    return (double)width / height;
 1735                }
 01736            }
 01737            catch (Exception ex)
 1738            {
 01739                _logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path);
 01740            }
 1741
 01742            return item.GetDefaultPrimaryImageAspectRatio();
 01743        }
 1744    }
 1745}

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,System.Collections.Generic.IReadOnlyDictionary`2<System.Guid,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.PersonInfo>>)
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,System.Collections.Generic.IReadOnlyList`1<MediaBrowser.Controller.Entities.PersonInfo>)
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,System.Collections.Generic.IReadOnlyDictionary`2<System.String,MediaBrowser.Controller.Entities.Audio.MusicArtist[]>)
GetBatchedAlbumArtist(MediaBrowser.Controller.Entities.Audio.MusicAlbum,System.Collections.Generic.IReadOnlyDictionary`2<System.String,MediaBrowser.Controller.Entities.Audio.MusicArtist[]>)
AddInheritedImages(MediaBrowser.Model.Dto.BaseItemDto,MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Dto.DtoOptions,MediaBrowser.Controller.Entities.BaseItem,System.Collections.Generic.IReadOnlyDictionary`2<System.String,MediaBrowser.Controller.Entities.Audio.MusicArtist[]>)
GetMappedPath(MediaBrowser.Controller.Entities.BaseItem,MediaBrowser.Controller.Entities.BaseItem)
AttachPrimaryImageAspectRatio(MediaBrowser.Model.Dto.IItemDto,MediaBrowser.Controller.Entities.BaseItem)
GetPrimaryImageAspectRatio(MediaBrowser.Controller.Entities.BaseItem)