< 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
71%
Covered lines: 588
Uncovered lines: 238
Coverable lines: 826
Total lines: 1772
Line coverage: 71.1%
Branch coverage
52%
Covered branches: 310
Total branches: 592
Branch coverage: 52.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 5/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: 17458/11/2026 - 12:17:00 AM Line coverage: 71.1% (588/826) Branch coverage: 52.3% (310/592) Total lines: 1772 5/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: 17458/11/2026 - 12:17:00 AM Line coverage: 71.1% (588/826) Branch coverage: 52.3% (310/592) Total lines: 1772

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        {
 31142            _logger = logger;
 31143            _libraryManager = libraryManager;
 31144            _userDataRepository = userDataRepository;
 31145            _imageProcessor = imageProcessor;
 31146            _providerManager = providerManager;
 31147            _recordingsManager = recordingsManager;
 31148            _appHost = appHost;
 31149            _mediaSourceManager = mediaSourceManager;
 31150            _livetvManagerFactory = livetvManagerFactory;
 31151            _trickplayManager = trickplayManager;
 31152            _chapterManager = chapterManager;
 31153        }
 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        {
 8165            var accessibleItems = skipVisibilityCheck || user is null ? items : items.Where(x => x.IsVisible(user)).ToLi
 8166            var returnItems = new BaseItemDto[accessibleItems.Count];
 8167            List<(BaseItem, BaseItemDto)>? programTuples = null;
 8168            List<(BaseItemDto, LiveTvChannel)>? channelTuples = null;
 169
 170            // Batch-fetch user data for all items
 8171            Dictionary<Guid, UserItemData>? userDataBatch = null;
 8172            IReadOnlyDictionary<Guid, VersionResumeData>? resumeDataBatch = null;
 8173            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
 8182            List<Folder>? allCollectionFolders = null;
 8183            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
 8189            Dictionary<Guid, int>? childCountBatch = null;
 8190            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
 8200            Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null;
 8201            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.
 8213            IReadOnlyDictionary<string, MusicArtist[]>? artistsBatch = null;
 8214            var artistNames = new HashSet<string>(StringComparer.Ordinal);
 36215            foreach (var item in accessibleItems)
 216            {
 10217                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
 10228                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
 8240            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.
 8246            IReadOnlyDictionary<Guid, IReadOnlyList<PersonInfo>>? peopleBatch = null;
 8247            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
 256            // Batch-detect which videos own alternate versions to avoid the per-item alternate-version
 257            // queries in MediaSourceCount. Videos absent from this set have a single media source.
 8258            IReadOnlySet<Guid>? alternateVersionItemIds = null;
 8259            if (options.ContainsField(ItemFields.MediaSourceCount))
 260            {
 2261                var versionItemIds = accessibleItems.OfType<Video>().Select(i => i.Id).ToList();
 2262                if (versionItemIds.Count > 0)
 263                {
 2264                    alternateVersionItemIds = _libraryManager.GetItemIdsWithAlternateVersions(versionItemIds);
 265                }
 266            }
 267
 36268            for (int index = 0; index < accessibleItems.Count; index++)
 269            {
 10270                var item = accessibleItems[index];
 10271                var dto = GetBaseItemDtoInternal(
 10272                    item,
 10273                    options,
 10274                    user,
 10275                    owner,
 10276                    userDataBatch?.GetValueOrDefault(item.Id),
 10277                    allCollectionFolders,
 10278                    childCountBatch,
 10279                    playedCountBatch,
 10280                    artistsBatch,
 10281                    resumeDataBatch?.GetValueOrDefault(item.Id),
 10282                    peopleBatch,
 10283                    alternateVersionItemIds);
 284
 10285                if (item is LiveTvChannel tvChannel)
 286                {
 0287                    (channelTuples ??= []).Add((dto, tvChannel));
 288                }
 10289                else if (item is LiveTvProgram)
 290                {
 0291                    (programTuples ??= []).Add((item, dto));
 292                }
 293
 10294                if (options.ContainsField(ItemFields.ItemCounts))
 295                {
 0296                    SetItemByNameInfo(dto, user);
 297                }
 298
 10299                returnItems[index] = dto;
 300            }
 301
 8302            if (programTuples is not null)
 303            {
 0304                LivetvManager.AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
 305            }
 306
 8307            if (channelTuples is not null)
 308            {
 0309                LivetvManager.AddChannelInfo(channelTuples, options, user);
 310            }
 311
 8312            return returnItems;
 313        }
 314
 315        public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner = null)
 316        {
 11317            var dto = GetBaseItemDtoInternal(item, options, user, owner, null);
 11318            if (item is LiveTvChannel tvChannel)
 319            {
 0320                LivetvManager.AddChannelInfo(new[] { (dto, tvChannel) }, options, user);
 321            }
 11322            else if (item is LiveTvProgram)
 323            {
 0324                LivetvManager.AddInfoToProgramDto(new[] { (item, dto) }, options.Fields, user).GetAwaiter().GetResult();
 325            }
 326
 11327            if (options.ContainsField(ItemFields.ItemCounts))
 328            {
 6329                SetItemByNameInfo(dto, user);
 330            }
 331
 11332            return dto;
 333        }
 334
 335        private BaseItemDto GetBaseItemDtoInternal(
 336            BaseItem item,
 337            DtoOptions options,
 338            User? user = null,
 339            BaseItem? owner = null,
 340            UserItemData? userData = null,
 341            List<Folder>? allCollectionFolders = null,
 342            Dictionary<Guid, int>? childCountBatch = null,
 343            Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null,
 344            IReadOnlyDictionary<string, MusicArtist[]>? artistsBatch = null,
 345            VersionResumeData? resumeData = null,
 346            IReadOnlyDictionary<Guid, IReadOnlyList<PersonInfo>>? peopleBatch = null,
 347            IReadOnlySet<Guid>? alternateVersionItemIds = null)
 348        {
 21349            var dto = new BaseItemDto
 21350            {
 21351                ServerId = _appHost.SystemId
 21352            };
 353
 21354            if (item.SourceType == SourceType.Channel)
 355            {
 0356                dto.SourceType = item.SourceType.ToString();
 357            }
 358
 21359            if (options.ContainsField(ItemFields.People))
 360            {
 8361                IReadOnlyList<PersonInfo>? prefetchedPeople = null;
 8362                if (peopleBatch is not null)
 363                {
 364                    // The batch omits items with no people, so a miss means "no people",
 365                    // not "not fetched". Use an empty list to skip the per-item query.
 2366                    prefetchedPeople = peopleBatch.GetValueOrDefault(item.Id) ?? [];
 367                }
 368
 8369                AttachPeople(dto, item, user, prefetchedPeople);
 370            }
 371
 21372            if (options.ContainsField(ItemFields.PrimaryImageAspectRatio))
 373            {
 374                try
 375                {
 7376                    AttachPrimaryImageAspectRatio(dto, item);
 7377                }
 0378                catch (Exception ex)
 379                {
 380                    // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions
 0381                    _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {ItemName}", item.Name);
 0382                }
 383            }
 384
 21385            if (options.ContainsField(ItemFields.DisplayPreferencesId))
 386            {
 6387                dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N", CultureInfo.InvariantCulture);
 388            }
 389
 21390            if (user is not null)
 391            {
 9392                AttachUserSpecificInfo(
 9393                    dto,
 9394                    item,
 9395                    user,
 9396                    options,
 9397                    userData,
 9398                    childCountBatch,
 9399                    playedCountBatch,
 9400                    resumeData);
 401            }
 402
 21403            if (item is IHasMediaSources
 21404                && options.ContainsField(ItemFields.MediaSources))
 405            {
 0406                dto.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray();
 407
 0408                NormalizeMediaSourceContainers(dto);
 409            }
 410
 21411            if (options.ContainsField(ItemFields.Studios))
 412            {
 6413                AttachStudios(dto, item);
 414            }
 415
 21416            AttachBasicFields(dto, item, owner, options, artistsBatch, user, alternateVersionItemIds);
 417
 21418            if (options.ContainsField(ItemFields.CanDelete))
 419            {
 6420                dto.CanDelete = user is null
 6421                    ? item.CanDelete()
 6422                    : allCollectionFolders is not null
 6423                        ? item.CanDelete(user, allCollectionFolders)
 6424                        : item.CanDelete(user);
 425            }
 426
 21427            if (options.ContainsField(ItemFields.CanDownload))
 428            {
 6429                dto.CanDownload = user is null
 6430                    ? item.CanDownload()
 6431                    : item.CanDownload(user);
 432            }
 433
 21434            if (options.ContainsField(ItemFields.Etag))
 435            {
 6436                dto.Etag = item.GetEtag(user);
 437            }
 438
 21439            var activeRecording = _recordingsManager.GetActiveRecordingInfo(item.Path);
 21440            if (activeRecording is not null)
 441            {
 0442                dto.Type = BaseItemKind.Recording;
 0443                dto.CanDownload = false;
 0444                dto.RunTimeTicks = null;
 445
 0446                if (!string.IsNullOrEmpty(dto.SeriesName))
 447                {
 0448                    dto.EpisodeTitle = dto.Name;
 0449                    dto.Name = dto.SeriesName;
 450                }
 451
 0452                LivetvManager.AddInfoToRecordingDto(item, dto, activeRecording, user);
 453            }
 454
 21455            if (item is Audio audio)
 456            {
 0457                dto.HasLyrics = audio.GetMediaStreams().Any(s => s.Type == MediaStreamType.Lyric);
 458            }
 459
 21460            return dto;
 461        }
 462
 463        private static void NormalizeMediaSourceContainers(BaseItemDto dto)
 464        {
 0465            foreach (var mediaSource in dto.MediaSources)
 466            {
 0467                var container = mediaSource.Container;
 0468                if (string.IsNullOrEmpty(container))
 469                {
 470                    continue;
 471                }
 472
 0473                var containers = container.Split(',');
 0474                if (containers.Length < 2)
 475                {
 476                    continue;
 477                }
 478
 0479                var path = mediaSource.Path;
 0480                string? fileExtensionContainer = null;
 481
 0482                if (!string.IsNullOrEmpty(path))
 483                {
 0484                    path = Path.GetExtension(path);
 0485                    if (!string.IsNullOrEmpty(path))
 486                    {
 0487                        path = Path.GetExtension(path);
 0488                        if (!string.IsNullOrEmpty(path))
 489                        {
 0490                            path = path.TrimStart('.');
 491                        }
 492
 0493                        if (!string.IsNullOrEmpty(path) && containers.Contains(path, StringComparison.OrdinalIgnoreCase)
 494                        {
 0495                            fileExtensionContainer = path;
 496                        }
 497                    }
 498                }
 499
 0500                mediaSource.Container = fileExtensionContainer ?? containers[0];
 501            }
 0502        }
 503
 504        /// <inheritdoc />
 505        /// TODO refactor this to use the new SetItemByNameInfo.
 506        /// Some callers already have the counts extracted so no reason to retrieve them again.
 507        public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem>? taggedItems, User? user =
 508        {
 0509            var dto = GetBaseItemDtoInternal(item, options, user);
 510
 0511            if (options.ContainsField(ItemFields.ItemCounts)
 0512                && taggedItems is not null
 0513                && taggedItems.Count != 0)
 514            {
 0515                SetItemByNameInfo(item, dto, taggedItems);
 516            }
 517
 0518            return dto;
 519        }
 520
 521        private void SetItemByNameInfo(BaseItemDto dto, User? user)
 522        {
 6523            if (!_relatedItemKinds.TryGetValue(dto.Type, out var relatedItemKinds))
 524            {
 6525                return;
 526            }
 527
 0528            var counts = _libraryManager.GetItemCountsForNameItem(dto.Type, dto.Id, relatedItemKinds, user);
 529
 0530            dto.AlbumCount = counts.AlbumCount;
 0531            dto.ArtistCount = counts.ArtistCount;
 0532            dto.EpisodeCount = counts.EpisodeCount;
 0533            dto.MovieCount = counts.MovieCount;
 0534            dto.MusicVideoCount = counts.MusicVideoCount;
 0535            dto.ProgramCount = counts.ProgramCount;
 0536            dto.SeriesCount = counts.SeriesCount;
 0537            dto.SongCount = counts.SongCount;
 0538            dto.TrailerCount = counts.TrailerCount;
 0539            dto.ChildCount = counts.TotalItemCount();
 0540        }
 541
 542        private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IReadOnlyList<BaseItem> taggedItems)
 543        {
 0544            if (item is MusicArtist)
 545            {
 0546                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0547                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0548                dto.SongCount = taggedItems.Count(i => i is Audio);
 549            }
 0550            else if (item is MusicGenre)
 551            {
 0552                dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
 0553                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0554                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0555                dto.SongCount = taggedItems.Count(i => i is Audio);
 556            }
 557            else
 558            {
 559                // This populates them all and covers Genre, Person, Studio, Year
 560
 0561                dto.ArtistCount = taggedItems.Count(i => i is MusicArtist);
 0562                dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum);
 0563                dto.EpisodeCount = taggedItems.Count(i => i is Episode);
 0564                dto.MovieCount = taggedItems.Count(i => i is Movie);
 0565                dto.TrailerCount = taggedItems.Count(i => i is Trailer);
 0566                dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
 0567                dto.SeriesCount = taggedItems.Count(i => i is Series);
 0568                dto.ProgramCount = taggedItems.Count(i => i is LiveTvProgram);
 0569                dto.SongCount = taggedItems.Count(i => i is Audio);
 570            }
 571
 0572            dto.ChildCount = taggedItems.Count;
 0573        }
 574
 575        /// <summary>
 576        /// Attaches the user specific info.
 577        /// </summary>
 578        private void AttachUserSpecificInfo(
 579            BaseItemDto dto,
 580            BaseItem item,
 581            User user,
 582            DtoOptions options,
 583            UserItemData? userData = null,
 584            Dictionary<Guid, int>? childCountBatch = null,
 585            Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null,
 586            VersionResumeData? resumeData = null)
 587        {
 9588            if (item.IsFolder)
 589            {
 9590                var folder = (Folder)item;
 591
 9592                if (options.EnableUserData)
 593                {
 9594                    if (userData is not null)
 595                    {
 596                        // Use pre-fetched user data
 3597                        dto.UserData = GetUserItemDataDto(userData, item.Id);
 3598                        (int Played, int Total)? precomputed = playedCountBatch is not null
 3599                            && playedCountBatch.TryGetValue(item.Id, out var counts) ? counts : null;
 3600                        item.FillUserDataDtoValues(dto.UserData, userData, dto, user, options, precomputed);
 601                    }
 602                    else
 603                    {
 604                        // Fall back to individual fetch
 6605                        dto.UserData = _userDataRepository.GetUserDataDto(item, dto, user, options);
 606                    }
 607                }
 608
 9609                if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library)
 610                {
 611                    // For these types we can try to optimize and assume these values will be equal
 9612                    if (item is MusicAlbum || item is Season || item is Playlist)
 613                    {
 0614                        dto.ChildCount = dto.RecursiveItemCount;
 0615                        var folderChildCount = folder.LinkedChildren.Length;
 616                        // The default is an empty array, so we can't reliably use the count when it's empty
 0617                        if (folderChildCount > 0)
 618                        {
 0619                            dto.ChildCount ??= folderChildCount;
 620                        }
 621                    }
 622
 9623                    if (options.ContainsField(ItemFields.ChildCount))
 624                    {
 6625                        dto.ChildCount ??= GetChildCount(folder, user, childCountBatch);
 626                    }
 627                }
 628
 9629                if (options.ContainsField(ItemFields.CumulativeRunTimeTicks))
 630                {
 6631                    dto.CumulativeRunTimeTicks = item.RunTimeTicks;
 632                }
 633
 9634                if (options.ContainsField(ItemFields.DateLastMediaAdded))
 635                {
 6636                    dto.DateLastMediaAdded = folder.DateLastMediaAdded;
 637                }
 638            }
 639            else
 640            {
 0641                if (options.EnableUserData)
 642                {
 0643                    if (userData is not null)
 644                    {
 645                        // Use pre-fetched user data
 0646                        dto.UserData = GetUserItemDataDto(userData, item.Id);
 0647                        item.FillUserDataDtoValues(dto.UserData, userData, dto, user, options);
 648
 649                        // For items with alternate versions, the most recently played version drives resume.
 0650                        resumeData?.ApplyTo(dto.UserData);
 651                    }
 652                    else
 653                    {
 654                        // Fall back to individual fetch
 0655                        dto.UserData = _userDataRepository.GetUserDataDto(item, user);
 656                    }
 657                }
 658            }
 659
 9660            if (options.ContainsField(ItemFields.PlayAccess))
 661            {
 6662                dto.PlayAccess = item.GetPlayAccess(user);
 663            }
 9664        }
 665
 666        private static UserItemDataDto GetUserItemDataDto(UserItemData data, Guid itemId)
 667        {
 3668            ArgumentNullException.ThrowIfNull(data);
 669
 3670            return new UserItemDataDto
 3671            {
 3672                IsFavorite = data.IsFavorite,
 3673                Likes = data.Likes,
 3674                PlaybackPositionTicks = data.PlaybackPositionTicks,
 3675                PlayCount = data.PlayCount,
 3676                Rating = data.Rating,
 3677                Played = data.Played,
 3678                LastPlayedDate = data.LastPlayedDate,
 3679                ItemId = itemId,
 3680                Key = data.Key
 3681            };
 682        }
 683
 684        private static int GetChildCount(Folder folder, User user, Dictionary<Guid, int>? childCountBatch)
 685        {
 686            // Right now this is too slow to calculate for top level folders on a per-user basis
 687            // Just return something so that apps that are expecting a value won't think the folders are empty
 6688            if (folder is ICollectionFolder || folder is UserView)
 689            {
 0690                return Random.Shared.Next(1, 10);
 691            }
 692
 693            // Use pre-fetched batch data if available
 6694            if (childCountBatch is not null && childCountBatch.TryGetValue(folder.Id, out var count))
 695            {
 0696                return count;
 697            }
 698
 699            // Fall back to individual query for special cases (Series, Season, etc.)
 6700            return folder.GetChildCount(user);
 701        }
 702
 703        private static void SetBookProperties(BaseItemDto dto, Book item)
 704        {
 0705            dto.SeriesName = item.SeriesName;
 0706        }
 707
 708        private static void SetPhotoProperties(BaseItemDto dto, Photo item)
 709        {
 0710            dto.CameraMake = item.CameraMake;
 0711            dto.CameraModel = item.CameraModel;
 0712            dto.Software = item.Software;
 0713            dto.ExposureTime = item.ExposureTime;
 0714            dto.FocalLength = item.FocalLength;
 0715            dto.ImageOrientation = item.Orientation;
 0716            dto.Aperture = item.Aperture;
 0717            dto.ShutterSpeed = item.ShutterSpeed;
 718
 0719            dto.Latitude = item.Latitude;
 0720            dto.Longitude = item.Longitude;
 0721            dto.Altitude = item.Altitude;
 0722            dto.IsoSpeedRating = item.IsoSpeedRating;
 723
 0724            var album = item.AlbumEntity;
 725
 0726            if (album is not null)
 727            {
 0728                dto.Album = album.Name;
 0729                dto.AlbumId = album.Id;
 730            }
 0731        }
 732
 733        private void SetMusicVideoProperties(BaseItemDto dto, MusicVideo item)
 734        {
 0735            if (!string.IsNullOrEmpty(item.Album))
 736            {
 0737                var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery
 0738                {
 0739                    IncludeItemTypes = new[] { BaseItemKind.MusicAlbum },
 0740                    Name = item.Album,
 0741                    Limit = 1
 0742                });
 743
 0744                if (parentAlbumIds.Count > 0)
 745                {
 0746                    dto.AlbumId = parentAlbumIds[0];
 747                }
 748            }
 749
 0750            dto.Album = item.Album;
 0751        }
 752
 753        private string[] GetImageTags(BaseItem item, List<ItemImageInfo> images)
 754        {
 21755            return images
 21756                .Select(p => GetImageCacheTag(item, p))
 21757                .Where(i => i is not null)
 21758                .ToArray()!; // null values got filtered out
 759        }
 760
 761        private string? GetImageCacheTag(BaseItem item, ItemImageInfo image)
 762        {
 763            try
 764            {
 11765                return _imageProcessor.GetImageCacheTag(item, image);
 766            }
 0767            catch (Exception ex)
 768            {
 0769                _logger.LogError(ex, "Error getting {ImageType} image info for {Path}", image.Type, image.Path);
 0770                return null;
 771            }
 11772        }
 773
 774        /// <summary>
 775        /// Attaches People DTO's to a DTOBaseItem.
 776        /// </summary>
 777        /// <param name="dto">The dto.</param>
 778        /// <param name="item">The item.</param>
 779        /// <param name="user">The requesting user.</param>
 780        /// <param name="prefetchedPeople">People fetched in batch by the caller; when null the people are queried per i
 781        private void AttachPeople(BaseItemDto dto, BaseItem item, User? user = null, IReadOnlyList<PersonInfo>? prefetch
 782        {
 783            // When rendering a page of items the caller batch-fetches people for every item up
 784            // front and passes them in, avoiding one GetPeople query per item. Fall back to the
 785            // per-item query for the single item path where no batch is available.
 8786            var source = prefetchedPeople ?? _libraryManager.GetPeople(item);
 787
 788            // Ordering by person type to ensure actors and artists are at the front.
 789            // This is taking advantage of the fact that they both begin with A
 790            // This should be improved in the future
 8791            var people = source.OrderBy(i => i.SortOrder ?? int.MaxValue)
 8792                .ThenBy(i =>
 8793                {
 8794                    if (i.IsType(PersonKind.Actor))
 8795                    {
 8796                        return 0;
 8797                    }
 8798
 8799                    if (i.IsType(PersonKind.GuestStar))
 8800                    {
 8801                        return 1;
 8802                    }
 8803
 8804                    if (i.IsType(PersonKind.Director))
 8805                    {
 8806                        return 2;
 8807                    }
 8808
 8809                    if (i.IsType(PersonKind.Writer))
 8810                    {
 8811                        return 3;
 8812                    }
 8813
 8814                    if (i.IsType(PersonKind.Producer))
 8815                    {
 8816                        return 4;
 8817                    }
 8818
 8819                    if (i.IsType(PersonKind.Composer))
 8820                    {
 8821                        return 4;
 8822                    }
 8823
 8824                    return 10;
 8825                })
 8826                .ToList();
 827
 8828            var list = new List<BaseItemPerson>();
 829
 8830            Dictionary<string, Person> dictionary = people.Select(p => p.Name)
 8831                .Distinct(StringComparer.OrdinalIgnoreCase).Select(c =>
 8832                {
 8833                    try
 8834                    {
 8835                        return _libraryManager.GetPerson(c);
 8836                    }
 8837                    catch (Exception ex)
 8838                    {
 8839                        _logger.LogError(ex, "Error getting person {Name}", c);
 8840                        return null;
 8841                    }
 8842                }).Where(i => i is not null)
 8843                .Where(i => user is null || i!.IsVisible(user))
 8844                .DistinctBy(x => x!.Name, StringComparer.OrdinalIgnoreCase)
 8845                .ToDictionary(i => i!.Name, StringComparer.OrdinalIgnoreCase)!; // null values got filtered out
 846
 20847            for (var i = 0; i < people.Count; i++)
 848            {
 2849                var person = people[i];
 850
 2851                var baseItemPerson = new BaseItemPerson
 2852                {
 2853                    Name = person.Name,
 2854                    Role = person.Role,
 2855                    Type = person.Type
 2856                };
 857
 2858                if (dictionary.TryGetValue(person.Name, out Person? entity))
 859                {
 2860                    baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
 2861                    baseItemPerson.Id = entity.Id;
 2862                    if (dto.ImageBlurHashes is not null)
 863                    {
 864                        // Only add BlurHash for the person's image.
 0865                        baseItemPerson.ImageBlurHashes = [];
 0866                        foreach (var (imageType, blurHash) in dto.ImageBlurHashes)
 867                        {
 0868                            if (blurHash is not null)
 869                            {
 0870                                baseItemPerson.ImageBlurHashes[imageType] = [];
 0871                                foreach (var (imageId, blurHashValue) in blurHash)
 872                                {
 0873                                    if (string.Equals(baseItemPerson.PrimaryImageTag, imageId, StringComparison.OrdinalI
 874                                    {
 0875                                        baseItemPerson.ImageBlurHashes[imageType][imageId] = blurHashValue;
 876                                    }
 877                                }
 878                            }
 879                        }
 880                    }
 881
 2882                    list.Add(baseItemPerson);
 883                }
 884            }
 885
 8886            dto.People = list.ToArray();
 8887        }
 888
 889        /// <summary>
 890        /// Attaches the studios.
 891        /// </summary>
 892        /// <param name="dto">The dto.</param>
 893        /// <param name="item">The item.</param>
 894        private void AttachStudios(BaseItemDto dto, BaseItem item)
 895        {
 6896            dto.Studios = item.Studios
 6897                .Where(i => !string.IsNullOrEmpty(i))
 6898                .Select(i => new NameGuidPair
 6899                {
 6900                    Name = i,
 6901                    Id = _libraryManager.GetStudioId(i)
 6902                })
 6903                .ToArray();
 6904        }
 905
 906        private void AttachGenreItems(BaseItemDto dto, BaseItem item)
 907        {
 6908            dto.GenreItems = item.Genres
 6909                .Where(i => !string.IsNullOrEmpty(i))
 6910                .Select(i => new NameGuidPair
 6911                {
 6912                    Name = i,
 6913                    Id = GetGenreId(i, item)
 6914                })
 6915                .ToArray();
 6916        }
 917
 918        private Guid GetGenreId(string name, BaseItem owner)
 919        {
 0920            if (owner is IHasMusicGenres)
 921            {
 0922                return _libraryManager.GetMusicGenreId(name);
 923            }
 924
 0925            return _libraryManager.GetGenreId(name);
 926        }
 927
 928        private string? GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ImageType imageType, int imageIndex = 0)
 929        {
 8930            var image = item.GetImageInfo(imageType, imageIndex);
 8931            if (image is not null)
 932            {
 3933                return GetTagAndFillBlurhash(dto, item, image);
 934            }
 935
 5936            return null;
 937        }
 938
 939        private string? GetTagAndFillBlurhash(BaseItemDto dto, BaseItem item, ItemImageInfo image)
 940        {
 11941            var tag = GetImageCacheTag(item, image);
 11942            if (tag is null)
 943            {
 0944                return null;
 945            }
 946
 11947            if (!string.IsNullOrEmpty(image.BlurHash))
 948            {
 0949                dto.ImageBlurHashes ??= [];
 950
 0951                if (!dto.ImageBlurHashes.TryGetValue(image.Type, out var value))
 952                {
 0953                    value = [];
 0954                    dto.ImageBlurHashes[image.Type] = value;
 955                }
 956
 0957                value[tag] = image.BlurHash;
 958            }
 959
 11960            return tag;
 961        }
 962
 963        private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, int limit)
 964        {
 21965            return GetTagsAndFillBlurhashes(dto, item, imageType, item.GetImages(imageType).Take(limit).ToList());
 966        }
 967
 968        private string[] GetTagsAndFillBlurhashes(BaseItemDto dto, BaseItem item, ImageType imageType, List<ItemImageInf
 969        {
 21970            var tags = GetImageTags(item, images);
 21971            var hashes = new Dictionary<string, string>();
 42972            for (int i = 0; i < images.Count; i++)
 973            {
 0974                var img = images[i];
 0975                if (!string.IsNullOrEmpty(img.BlurHash))
 976                {
 0977                    var tag = tags[i];
 0978                    hashes[tag] = img.BlurHash;
 979                }
 980            }
 981
 21982            if (hashes.Count > 0)
 983            {
 0984                dto.ImageBlurHashes ??= [];
 985
 0986                dto.ImageBlurHashes[imageType] = hashes;
 987            }
 988
 21989            return tags;
 990        }
 991
 992        /// <summary>
 993        /// Sets simple property values on a DTOBaseItem.
 994        /// </summary>
 995        /// <param name="dto">The dto.</param>
 996        /// <param name="item">The item.</param>
 997        /// <param name="owner">The owner.</param>
 998        /// <param name="options">The options.</param>
 999        /// <param name="artistsBatch">Optional pre-fetched artist lookup shared across a batch of items.</param>
 1000        /// <param name="user">The user, for per-user values such as the accessible media source count.</param>
 1001        /// <param name="alternateVersionItemIds">Optional pre-fetched set of item IDs that own alternate versions, shar
 1002        private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem? owner, DtoOptions options, IReadOnlyDic
 1003        {
 211004            if (options.ContainsField(ItemFields.DateCreated))
 1005            {
 61006                dto.DateCreated = item.DateCreated;
 1007            }
 1008
 211009            if (options.ContainsField(ItemFields.Settings))
 1010            {
 61011                dto.LockedFields = item.LockedFields;
 61012                dto.LockData = item.IsLocked;
 61013                dto.ForcedSortName = item.ForcedSortName;
 1014            }
 1015
 211016            dto.Container = item.Container;
 211017            dto.EndDate = item.EndDate;
 1018
 211019            if (options.ContainsField(ItemFields.ExternalUrls))
 1020            {
 61021                dto.ExternalUrls = _providerManager.GetExternalUrls(item).ToArray();
 1022            }
 1023
 211024            if (options.ContainsField(ItemFields.Tags))
 1025            {
 61026                dto.Tags = item.Tags;
 1027            }
 1028
 211029            if (item is IHasAspectRatio hasAspectRatio)
 1030            {
 61031                dto.AspectRatio = hasAspectRatio.AspectRatio;
 1032            }
 1033
 211034            dto.ImageBlurHashes = [];
 1035
 211036            var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
 211037            if (backdropLimit > 0)
 1038            {
 211039                dto.BackdropImageTags = GetTagsAndFillBlurhashes(dto, item, ImageType.Backdrop, backdropLimit);
 1040            }
 1041
 211042            if (options.ContainsField(ItemFields.Genres))
 1043            {
 61044                dto.Genres = item.Genres;
 61045                AttachGenreItems(dto, item);
 1046            }
 1047
 211048            if (options.EnableImages)
 1049            {
 211050                dto.ImageTags = [];
 1051
 1052                // Prevent implicitly captured closure
 211053                var currentItem = item;
 521054                foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)))
 1055                {
 51056                    if (options.GetImageLimit(image.Type) > 0)
 1057                    {
 51058                        var tag = GetTagAndFillBlurhash(dto, item, image);
 1059
 51060                        if (tag is not null)
 1061                        {
 51062                            dto.ImageTags[image.Type] = tag;
 1063                        }
 1064                    }
 1065                }
 1066            }
 1067
 211068            dto.Id = item.Id;
 211069            dto.IndexNumber = item.IndexNumber;
 211070            dto.ParentIndexNumber = item.ParentIndexNumber;
 1071
 211072            if (item.IsFolder)
 1073            {
 151074                dto.IsFolder = true;
 1075            }
 61076            else if (item is IHasMediaSources)
 1077            {
 61078                dto.IsFolder = false;
 1079            }
 1080
 211081            dto.MediaType = item.MediaType;
 1082
 211083            if (item is not LiveTvProgram)
 1084            {
 211085                dto.LocationType = item.LocationType;
 1086            }
 1087
 211088            dto.Audio = item.Audio;
 1089
 211090            if (options.ContainsField(ItemFields.Settings))
 1091            {
 61092                dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
 61093                dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
 1094            }
 1095
 211096            dto.CriticRating = item.CriticRating;
 1097
 211098            if (item is IHasDisplayOrder hasDisplayOrder)
 1099            {
 01100                dto.DisplayOrder = hasDisplayOrder.DisplayOrder;
 1101            }
 1102
 211103            if (item is IHasCollectionType hasCollectionType)
 1104            {
 51105                dto.CollectionType = hasCollectionType.CollectionType;
 1106            }
 1107
 211108            if (options.ContainsField(ItemFields.RemoteTrailers))
 1109            {
 61110                dto.RemoteTrailers = item.RemoteTrailers;
 1111            }
 1112
 211113            dto.Name = item.Name;
 211114            dto.OfficialRating = item.OfficialRating;
 1115
 211116            if (options.ContainsField(ItemFields.Overview))
 1117            {
 61118                dto.Overview = item.Overview;
 1119            }
 1120
 211121            if (options.ContainsField(ItemFields.OriginalTitle))
 1122            {
 61123                dto.OriginalTitle = item.OriginalTitle;
 1124            }
 1125
 211126            dto.OriginalLanguage = item.OriginalLanguage;
 1127
 211128            if (options.ContainsField(ItemFields.ParentId))
 1129            {
 61130                dto.ParentId = item.DisplayParentId;
 1131            }
 1132
 211133            AddInheritedImages(dto, item, options, owner, artistsBatch);
 1134
 211135            if (options.ContainsField(ItemFields.Path))
 1136            {
 61137                dto.Path = GetMappedPath(item, owner);
 1138            }
 1139
 211140            if (options.ContainsField(ItemFields.EnableMediaSourceDisplay))
 1141            {
 61142                dto.EnableMediaSourceDisplay = item.EnableMediaSourceDisplay;
 1143            }
 1144
 211145            dto.PremiereDate = item.PremiereDate;
 211146            dto.ProductionYear = item.ProductionYear;
 1147
 211148            if (options.ContainsField(ItemFields.ProviderIds))
 1149            {
 61150                dto.ProviderIds = item.ProviderIds;
 1151            }
 1152
 211153            dto.RunTimeTicks = item.RunTimeTicks;
 1154
 211155            if (options.ContainsField(ItemFields.SortName))
 1156            {
 61157                dto.SortName = item.SortName;
 1158            }
 1159
 211160            if (options.ContainsField(ItemFields.CustomRating))
 1161            {
 61162                dto.CustomRating = item.CustomRating;
 1163            }
 1164
 211165            if (options.ContainsField(ItemFields.Taglines))
 1166            {
 61167                if (!string.IsNullOrEmpty(item.Tagline))
 1168                {
 01169                    dto.Taglines = new string[] { item.Tagline };
 1170                }
 1171
 61172                dto.Taglines ??= Array.Empty<string>();
 1173            }
 1174
 211175            dto.Type = item.GetBaseItemKind();
 211176            if ((item.CommunityRating ?? 0) > 0)
 1177            {
 01178                dto.CommunityRating = item.CommunityRating;
 1179            }
 1180
 211181            if (item is ISupportsPlaceHolders supportsPlaceHolders && supportsPlaceHolders.IsPlaceHolder)
 1182            {
 01183                dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder;
 1184            }
 1185
 211186            if (item.LUFS.HasValue)
 1187            {
 1188                // -18 LUFS reference, same as ReplayGain 2.0, compatible with ReplayGain 1.0
 01189                dto.NormalizationGain = -18f - item.LUFS;
 1190            }
 211191            else if (item.NormalizationGain.HasValue)
 1192            {
 01193                dto.NormalizationGain = item.NormalizationGain;
 1194            }
 1195
 1196            // Add audio info
 211197            if (item is Audio audio)
 1198            {
 01199                dto.Album = audio.Album;
 01200                dto.ExtraType = audio.ExtraType;
 1201
 01202                var albumParent = audio.AlbumEntity;
 1203
 01204                if (albumParent is not null)
 1205                {
 01206                    dto.AlbumId = albumParent.Id;
 01207                    dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary);
 01208                    if (albumParent.LUFS.HasValue)
 1209                    {
 1210                        // -18 LUFS reference, same as ReplayGain 2.0, compatible with ReplayGain 1.0
 01211                        dto.AlbumNormalizationGain = -18f - albumParent.LUFS;
 1212                    }
 01213                    else if (albumParent.NormalizationGain.HasValue)
 1214                    {
 01215                        dto.AlbumNormalizationGain = albumParent.NormalizationGain;
 1216                    }
 1217                }
 1218
 1219                // if (options.ContainsField(ItemFields.MediaSourceCount))
 1220                // {
 1221                // Songs always have one
 1222                // }
 1223            }
 1224
 211225            if (item is IHasArtist hasArtist)
 1226            {
 41227                dto.Artists = hasArtist.Artists;
 1228
 1229                // var artistItems = _libraryManager.GetArtists(new InternalItemsQuery
 1230                // {
 1231                //    EnableTotalRecordCount = false,
 1232                //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
 1233                // });
 1234
 1235                // dto.ArtistItems = artistItems.Items
 1236                //    .Select(i =>
 1237                //    {
 1238                //        var artist = i.Item1;
 1239                //        return new NameIdPair
 1240                //        {
 1241                //            Name = artist.Name,
 1242                //            Id = artist.Id.ToString("N", CultureInfo.InvariantCulture)
 1243                //        };
 1244                //    })
 1245                //    .ToList();
 1246
 1247                // Include artists that are not in the database yet, e.g., just added via metadata editor
 1248                // var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
 41249                var artistsLookup = artistsBatch
 41250                    ?? _libraryManager.GetArtists([.. hasArtist.Artists.Where(e => !string.IsNullOrWhiteSpace(e))]);
 1251
 41252                dto.ArtistItems = hasArtist.Artists
 41253                    .Where(name => !string.IsNullOrWhiteSpace(name))
 41254                    .Distinct()
 41255                    .Select(name => artistsLookup.TryGetValue(name, out var artists) && artists.Length > 0
 41256                        ? new NameGuidPair { Name = name, Id = artists[0].Id }
 41257                        : null)
 41258                    .Where(item => item is not null)
 41259                    .ToArray();
 1260            }
 1261
 211262            if (item is IHasAlbumArtist hasAlbumArtist)
 1263            {
 41264                dto.AlbumArtist = hasAlbumArtist.AlbumArtists.FirstOrDefault();
 1265
 1266                // var artistItems = _libraryManager.GetAlbumArtists(new InternalItemsQuery
 1267                // {
 1268                //    EnableTotalRecordCount = false,
 1269                //    ItemIds = new[] { item.Id.ToString("N", CultureInfo.InvariantCulture) }
 1270                // });
 1271
 1272                // dto.AlbumArtists = artistItems.Items
 1273                //    .Select(i =>
 1274                //    {
 1275                //        var artist = i.Item1;
 1276                //        return new NameIdPair
 1277                //        {
 1278                //            Name = artist.Name,
 1279                //            Id = artist.Id.ToString("N", CultureInfo.InvariantCulture)
 1280                //        };
 1281                //    })
 1282                //    .ToList();
 1283
 41284                var albumArtistsLookup = artistsBatch
 41285                    ?? _libraryManager.GetArtists([.. hasAlbumArtist.AlbumArtists.Where(e => !string.IsNullOrWhiteSpace(
 1286
 41287                dto.AlbumArtists = hasAlbumArtist.AlbumArtists
 41288                    .Where(name => !string.IsNullOrWhiteSpace(name))
 41289                    .Distinct()
 41290                    .Select(name => albumArtistsLookup.TryGetValue(name, out var albumArtists) && albumArtists.Length > 
 41291                        ? new NameGuidPair { Name = name, Id = albumArtists[0].Id }
 41292                        : null)
 41293                    .Where(item => item is not null)
 41294                    .ToArray();
 1295            }
 1296
 1297            // Add video info
 211298            if (item is Video video)
 1299            {
 61300                dto.VideoType = video.VideoType;
 61301                dto.Video3DFormat = video.Video3DFormat;
 61302                dto.IsoType = video.IsoType;
 1303
 61304                if (video.HasSubtitles)
 1305                {
 01306                    dto.HasSubtitles = video.HasSubtitles;
 1307                }
 1308
 61309                if (video.AdditionalParts.Length != 0)
 1310                {
 01311                    dto.PartCount = video.AdditionalParts.Length + 1;
 1312                }
 1313
 61314                if (options.ContainsField(ItemFields.MediaSourceCount))
 1315                {
 1316                    // A video with no primary version and no alternate versions always has a single
 1317                    // media source. Only compute the count for videos that might have more: a primary
 1318                    // version, or membership in the batch's set of items that own alternate versions.
 1319                    // Without the batch we can't rule it out, so fall back to computing (the single-item
 1320                    // path). Everything else is the common case and keeps the default count of one.
 31321                    var mayHaveAlternateVersions = alternateVersionItemIds is null
 31322                        || video.PrimaryVersionId.HasValue
 31323                        || alternateVersionItemIds.Contains(video.Id);
 1324
 31325                    if (mayHaveAlternateVersions)
 1326                    {
 1327                        // Match the per-user filtering of the media sources: versions the user cannot
 1328                        // access are not selectable, so they must not count towards the badge either.
 11329                        var mediaSourceCount = user is null
 11330                            || (!video.PrimaryVersionId.HasValue && video.LinkedAlternateVersions.Length == 0 && !video.
 11331                                ? video.MediaSourceCount
 11332                                : video.GetAllVersions().Count(v => v.Id.Equals(video.Id) || v.IsVisibleStandalone(user)
 11333                        if (mediaSourceCount != 1)
 1334                        {
 11335                            dto.MediaSourceCount = mediaSourceCount;
 1336                        }
 1337                    }
 1338                }
 1339
 61340                if (options.ContainsField(ItemFields.Trickplay))
 1341                {
 01342                    var trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult();
 01343                    dto.Trickplay = trickplay.ToDictionary(
 01344                        mediaStream => mediaStream.Key,
 01345                        mediaStream => mediaStream.Value.ToDictionary(
 01346                            width => width.Key,
 01347                            width => new TrickplayInfoDto(width.Value)));
 1348                }
 1349
 61350                dto.ExtraType = video.ExtraType;
 1351            }
 1352
 211353            if (options.ContainsField(ItemFields.Chapters))
 1354            {
 61355                dto.Chapters = _chapterManager.GetChapters(item.Id).ToList();
 1356            }
 1357
 211358            if (options.ContainsField(ItemFields.MediaStreams))
 1359            {
 1360                // Add VideoInfo
 61361                if (item is IHasMediaSources)
 1362                {
 1363                    MediaStream[] mediaStreams;
 1364
 01365                    if (dto.MediaSources is not null && dto.MediaSources.Length > 0)
 1366                    {
 01367                        if (item.SourceType == SourceType.Channel)
 1368                        {
 01369                            mediaStreams = dto.MediaSources[0].MediaStreams.ToArray();
 1370                        }
 1371                        else
 1372                        {
 01373                            string id = item.Id.ToString("N", CultureInfo.InvariantCulture);
 01374                            mediaStreams = dto.MediaSources.Where(i => string.Equals(i.Id, id, StringComparison.OrdinalI
 01375                                .SelectMany(i => i.MediaStreams)
 01376                                .ToArray();
 1377                        }
 1378                    }
 1379                    else
 1380                    {
 01381                        mediaStreams = _mediaSourceManager.GetStaticMediaSources(item, true)[0].MediaStreams.ToArray();
 1382                    }
 1383
 01384                    dto.MediaStreams = mediaStreams;
 1385                }
 1386            }
 1387
 211388            BaseItem[]? allExtras = null;
 1389
 211390            if (options.ContainsField(ItemFields.SpecialFeatureCount))
 1391            {
 61392                allExtras = item.GetExtras().ToArray();
 61393                dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contai
 1394            }
 1395
 211396            if (options.ContainsField(ItemFields.LocalTrailerCount))
 1397            {
 61398                if (item is IHasTrailers hasTrailers)
 1399                {
 01400                    dto.LocalTrailerCount = hasTrailers.LocalTrailers.Count;
 1401                }
 1402                else
 1403                {
 61404                    dto.LocalTrailerCount = (allExtras ?? item.GetExtras()).Count(i => i.ExtraType == ExtraType.Trailer)
 1405                }
 1406            }
 1407
 1408            // Add EpisodeInfo
 211409            if (item is Episode episode)
 1410            {
 31411                dto.IndexNumberEnd = episode.IndexNumberEnd;
 31412                dto.SeriesName = episode.SeriesName;
 1413
 31414                if (options.ContainsField(ItemFields.SpecialEpisodeNumbers))
 1415                {
 01416                    dto.AirsAfterSeasonNumber = episode.AirsAfterSeasonNumber;
 01417                    dto.AirsBeforeEpisodeNumber = episode.AirsBeforeEpisodeNumber;
 01418                    dto.AirsBeforeSeasonNumber = episode.AirsBeforeSeasonNumber;
 1419                }
 1420
 31421                dto.SeasonName = episode.SeasonName;
 31422                dto.SeasonId = episode.SeasonId;
 31423                dto.SeriesId = episode.SeriesId;
 1424
 31425                Series? episodeSeries = null;
 1426
 1427                // this block will add the series poster for episodes without a poster
 1428                // TODO maybe remove the if statement entirely
 1429                // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
 1430                {
 31431                    episodeSeries ??= episode.Series;
 31432                    if (episodeSeries is not null)
 1433                    {
 31434                        dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
 31435                        if (dto.ImageTags is null || !dto.ImageTags.ContainsKey(ImageType.Primary))
 1436                        {
 01437                            AttachPrimaryImageAspectRatio(dto, episodeSeries);
 1438                        }
 1439                    }
 1440                }
 1441
 31442                if (options.GetImageLimit(ImageType.Primary) > 0)
 1443                {
 31444                    var episodeSeason = episode.Season;
 31445                    var seasonPrimaryTag = episodeSeason is not null
 31446                        ? GetTagAndFillBlurhash(dto, episodeSeason, ImageType.Primary)
 31447                        : null;
 1448
 31449                    if (seasonPrimaryTag is not null)
 1450                    {
 11451                        dto.ParentPrimaryImageItemId = episodeSeason!.Id;
 11452                        dto.ParentPrimaryImageTag = seasonPrimaryTag;
 1453                    }
 21454                    else if (episodeSeries is not null && dto.SeriesPrimaryImageTag is not null)
 1455                    {
 11456                        dto.ParentPrimaryImageItemId = episodeSeries.Id;
 11457                        dto.ParentPrimaryImageTag = dto.SeriesPrimaryImageTag;
 1458                    }
 1459                }
 1460
 31461                if (options.ContainsField(ItemFields.SeriesStudio))
 1462                {
 01463                    episodeSeries ??= episode.Series;
 01464                    if (episodeSeries is not null)
 1465                    {
 01466                        dto.SeriesStudio = episodeSeries.Studios.FirstOrDefault();
 1467                    }
 1468                }
 1469            }
 1470
 1471            // Add SeriesInfo
 1472            Series? series;
 211473            if (item is Series tmp)
 1474            {
 01475                series = tmp;
 01476                dto.AirDays = series.AirDays;
 01477                dto.AirTime = series.AirTime;
 01478                dto.Status = series.Status?.ToString();
 1479            }
 1480
 1481            // Add SeasonInfo
 211482            if (item is Season season)
 1483            {
 01484                dto.SeriesName = season.SeriesName;
 01485                dto.SeriesId = season.SeriesId;
 1486
 01487                series = null;
 1488
 01489                if (options.ContainsField(ItemFields.SeriesStudio))
 1490                {
 01491                    series ??= season.Series;
 01492                    if (series is not null)
 1493                    {
 01494                        dto.SeriesStudio = series.Studios.FirstOrDefault();
 1495                    }
 1496                }
 1497
 1498                // this block will add the series poster for seasons without a poster
 1499                // TODO maybe remove the if statement entirely
 1500                // if (options.ContainsField(ItemFields.SeriesPrimaryImage))
 1501                {
 01502                    series ??= season.Series;
 01503                    if (series is not null)
 1504                    {
 01505                        dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
 01506                        if (dto.ImageTags is null || !dto.ImageTags.ContainsKey(ImageType.Primary))
 1507                        {
 01508                            AttachPrimaryImageAspectRatio(dto, series);
 1509                        }
 1510                    }
 1511                }
 1512            }
 1513
 211514            if (item is MusicVideo musicVideo)
 1515            {
 01516                SetMusicVideoProperties(dto, musicVideo);
 1517            }
 1518
 211519            if (item is Book book)
 1520            {
 01521                SetBookProperties(dto, book);
 1522            }
 1523
 211524            if (options.ContainsField(ItemFields.ProductionLocations))
 1525            {
 61526                if (item.ProductionLocations.Length > 0 || item is Movie)
 1527                {
 01528                    dto.ProductionLocations = item.ProductionLocations;
 1529                }
 1530            }
 1531
 211532            if (options.ContainsField(ItemFields.Width))
 1533            {
 61534                var width = item.Width;
 61535                if (width > 0)
 1536                {
 01537                    dto.Width = width;
 1538                }
 1539            }
 1540
 211541            if (options.ContainsField(ItemFields.Height))
 1542            {
 61543                var height = item.Height;
 61544                if (height > 0)
 1545                {
 01546                    dto.Height = height;
 1547                }
 1548            }
 1549
 211550            if (options.ContainsField(ItemFields.IsHD))
 1551            {
 1552                // Compatibility
 61553                if (item.IsHD)
 1554                {
 01555                    dto.IsHD = true;
 1556                }
 1557            }
 1558
 211559            if (item is Photo photo)
 1560            {
 01561                SetPhotoProperties(dto, photo);
 1562            }
 1563
 211564            dto.ChannelId = item.ChannelId;
 1565
 211566            if (item.SourceType == SourceType.Channel)
 1567            {
 01568                var channel = _libraryManager.GetItemById(item.ChannelId);
 01569                if (channel is not null)
 1570                {
 01571                    dto.ChannelName = channel.Name;
 1572                }
 1573            }
 211574        }
 1575
 1576        private BaseItem? GetImageDisplayParent(BaseItem currentItem, BaseItem originalItem, IReadOnlyDictionary<string,
 1577        {
 131578            if (currentItem is MusicAlbum musicAlbum)
 1579            {
 41580                var artist = GetBatchedAlbumArtist(musicAlbum, artistsBatch) ?? musicAlbum.GetMusicArtist(new DtoOptions
 41581                if (artist is not null)
 1582                {
 21583                    return artist;
 1584                }
 1585            }
 1586
 111587            var parent = currentItem.DisplayParent ?? currentItem.GetOwner() ?? currentItem.GetParent();
 1588
 111589            if (parent is null && originalItem is not UserRootFolder && originalItem is not UserView && originalItem is 
 1590            {
 51591                parent = _libraryManager.GetCollectionFolders(originalItem).FirstOrDefault();
 1592            }
 1593
 111594            return parent;
 1595        }
 1596
 1597        private static MusicArtist? GetBatchedAlbumArtist(MusicAlbum album, IReadOnlyDictionary<string, MusicArtist[]>? 
 1598        {
 41599            if (artistsBatch is null)
 1600            {
 21601                return null;
 1602            }
 1603
 21604            var name = album.AlbumArtists.Count > 0 ? album.AlbumArtists[0] : null;
 21605            return !string.IsNullOrEmpty(name) && artistsBatch.TryGetValue(name, out var artists) && artists.Length > 0
 21606                ? artists[0]
 21607                : null;
 1608        }
 1609
 1610        private void AddInheritedImages(BaseItemDto dto, BaseItem item, DtoOptions options, BaseItem? owner, IReadOnlyDi
 1611        {
 211612            if (item is UserView { ViewType: CollectionType.playlists } playlistsView
 211613                && options.GetImageLimit(ImageType.Primary) > 0
 211614                && !playlistsView.DisplayParentId.IsEmpty())
 1615            {
 21616                var displayParent = _libraryManager.GetItemById(playlistsView.DisplayParentId);
 21617                var displayParentPrimaryImage = displayParent?.GetImageInfo(ImageType.Primary, 0);
 1618
 21619                if (displayParentPrimaryImage is not null)
 1620                {
 11621                    dto.ImageTags?.Remove(ImageType.Primary);
 11622                    dto.ParentPrimaryImageItemId = displayParent!.Id;
 11623                    dto.ParentPrimaryImageTag = GetTagAndFillBlurhash(dto, displayParent, displayParentPrimaryImage);
 1624                }
 1625            }
 1626
 211627            if (!item.SupportsInheritedParentImages)
 1628            {
 111629                return;
 1630            }
 1631
 101632            var logoLimit = options.GetImageLimit(ImageType.Logo);
 101633            var artLimit = options.GetImageLimit(ImageType.Art);
 101634            var thumbLimit = options.GetImageLimit(ImageType.Thumb);
 101635            var backdropLimit = options.GetImageLimit(ImageType.Backdrop);
 1636
 1637            // For now. Emby apps are not using this
 101638            artLimit = 0;
 1639
 101640            if (logoLimit == 0 && artLimit == 0 && thumbLimit == 0 && backdropLimit == 0)
 1641            {
 01642                return;
 1643            }
 1644
 101645            BaseItem? parent = null;
 101646            var isFirst = true;
 1647
 101648            var imageTags = dto.ImageTags;
 1649
 131650            while ((!(imageTags is not null && imageTags.ContainsKey(ImageType.Logo)) && logoLimit > 0)
 131651                || (!(imageTags is not null && imageTags.ContainsKey(ImageType.Art)) && artLimit > 0)
 131652                || (!(imageTags is not null && imageTags.ContainsKey(ImageType.Thumb)) && thumbLimit > 0)
 131653                || parent is Series)
 1654            {
 131655                parent ??= isFirst ? GetImageDisplayParent(item, item, artistsBatch) ?? owner : parent;
 131656                if (parent is null)
 1657                {
 1658                    break;
 1659                }
 1660
 81661                var allImages = parent.ImageInfos;
 1662
 81663                if (logoLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Logo)) && dto.ParentLogo
 1664                {
 81665                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Logo);
 1666
 81667                    if (image is not null)
 1668                    {
 01669                        dto.ParentLogoItemId = parent.Id;
 01670                        dto.ParentLogoImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1671                    }
 1672                }
 1673
 81674                if (artLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Art)) && dto.ParentArtIte
 1675                {
 01676                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Art);
 1677
 01678                    if (image is not null)
 1679                    {
 01680                        dto.ParentArtItemId = parent.Id;
 01681                        dto.ParentArtImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1682                    }
 1683                }
 1684
 81685                if (thumbLimit > 0 && !(imageTags is not null && imageTags.ContainsKey(ImageType.Thumb)) && (dto.ParentT
 1686                {
 81687                    var image = allImages.FirstOrDefault(i => i.Type == ImageType.Thumb);
 1688
 81689                    if (image is not null)
 1690                    {
 21691                        dto.ParentThumbItemId = parent.Id;
 21692                        dto.ParentThumbImageTag = GetTagAndFillBlurhash(dto, parent, image);
 1693                    }
 1694                }
 1695
 81696                if (backdropLimit > 0 && !((dto.BackdropImageTags is not null && dto.BackdropImageTags.Length > 0) || (d
 1697                {
 81698                    var images = allImages.Where(i => i.Type == ImageType.Backdrop).Take(backdropLimit).ToList();
 1699
 81700                    if (images.Count > 0)
 1701                    {
 01702                        dto.ParentBackdropItemId = parent.Id;
 01703                        dto.ParentBackdropImageTags = GetTagsAndFillBlurhashes(dto, parent, ImageType.Backdrop, images);
 1704                    }
 1705                }
 1706
 81707                isFirst = false;
 1708
 81709                if (!parent.SupportsInheritedParentImages)
 1710                {
 1711                    break;
 1712                }
 1713
 31714                parent = GetImageDisplayParent(parent, item, artistsBatch);
 1715            }
 101716        }
 1717
 1718        private string GetMappedPath(BaseItem item, BaseItem? ownerItem)
 1719        {
 61720            var path = item.Path;
 1721
 61722            if (item.IsFileProtocol)
 1723            {
 61724                path = _libraryManager.GetPathAfterNetworkSubstitution(path, ownerItem ?? item);
 1725            }
 1726
 61727            return path;
 1728        }
 1729
 1730        /// <summary>
 1731        /// Attaches the primary image aspect ratio.
 1732        /// </summary>
 1733        /// <param name="dto">The dto.</param>
 1734        /// <param name="item">The item.</param>
 1735        public void AttachPrimaryImageAspectRatio(IItemDto dto, BaseItem item)
 1736        {
 71737            dto.PrimaryImageAspectRatio = GetPrimaryImageAspectRatio(item);
 71738        }
 1739
 1740        public double? GetPrimaryImageAspectRatio(BaseItem item)
 1741        {
 71742            var imageInfo = item.GetImageInfo(ImageType.Primary, 0);
 1743
 71744            if (imageInfo is null)
 1745            {
 61746                return null;
 1747            }
 1748
 11749            if (!imageInfo.IsLocalFile)
 1750            {
 11751                return item.GetDefaultPrimaryImageAspectRatio();
 1752            }
 1753
 1754            try
 1755            {
 01756                var size = _imageProcessor.GetImageDimensions(item, imageInfo);
 01757                var width = size.Width;
 01758                var height = size.Height;
 01759                if (width > 0 && height > 0)
 1760                {
 01761                    return (double)width / height;
 1762                }
 01763            }
 01764            catch (Exception ex)
 1765            {
 01766                _logger.LogError(ex, "Failed to determine primary image aspect ratio for {ImagePath}", imageInfo.Path);
 01767            }
 1768
 01769            return item.GetDefaultPrimaryImageAspectRatio();
 01770        }
 1771    }
 1772}

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>>,System.Collections.Generic.IReadOnlySet`1<System.Guid>)
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,System.Collections.Generic.IReadOnlySet`1<System.Guid>)
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)