< Summary - Jellyfin

Information
Class: Jellyfin.Api.Controllers.MoviesController
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Controllers/MoviesController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 178
Coverable lines: 178
Total lines: 327
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 34
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 1/16/2026 - 12:12:27 AM Line coverage: 0% (0/99) Branch coverage: 0% (0/16) Total lines: 3264/19/2026 - 12:14:27 AM Line coverage: 0% (0/178) Branch coverage: 0% (0/34) Total lines: 3264/27/2026 - 12:15:04 AM Line coverage: 0% (0/178) Branch coverage: 0% (0/34) Total lines: 327 1/16/2026 - 12:12:27 AM Line coverage: 0% (0/99) Branch coverage: 0% (0/16) Total lines: 3264/19/2026 - 12:14:27 AM Line coverage: 0% (0/178) Branch coverage: 0% (0/34) Total lines: 3264/27/2026 - 12:15:04 AM Line coverage: 0% (0/178) Branch coverage: 0% (0/34) Total lines: 327

Coverage delta

Coverage delta 1 -1

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
GetMovieRecommendations(...)0%272160%
GetWithDirector()0%4260%
GetWithActor()0%4260%
GetSimilarTo()0%4260%
GetActors(...)100%210%
GetDirectors(...)100%210%

File(s)

/srv/git/jellyfin/Jellyfin.Api/Controllers/MoviesController.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Globalization;
 4using System.Linq;
 5using Jellyfin.Api.Helpers;
 6using Jellyfin.Api.ModelBinders;
 7using Jellyfin.Data.Enums;
 8using Jellyfin.Database.Implementations.Entities;
 9using Jellyfin.Database.Implementations.Enums;
 10using Jellyfin.Extensions;
 11using MediaBrowser.Common.Extensions;
 12using MediaBrowser.Controller.Configuration;
 13using MediaBrowser.Controller.Dto;
 14using MediaBrowser.Controller.Entities;
 15using MediaBrowser.Controller.Library;
 16using MediaBrowser.Model.Dto;
 17using MediaBrowser.Model.Entities;
 18using MediaBrowser.Model.Querying;
 19using Microsoft.AspNetCore.Authorization;
 20using Microsoft.AspNetCore.Http;
 21using Microsoft.AspNetCore.Mvc;
 22
 23namespace Jellyfin.Api.Controllers;
 24
 25/// <summary>
 26/// Movies controller.
 27/// </summary>
 28[Authorize]
 29[Tags("Movie")]
 30public class MoviesController : BaseJellyfinApiController
 31{
 32    private readonly IUserManager _userManager;
 33    private readonly ILibraryManager _libraryManager;
 34    private readonly IDtoService _dtoService;
 35    private readonly IServerConfigurationManager _serverConfigurationManager;
 36
 37    /// <summary>
 38    /// Initializes a new instance of the <see cref="MoviesController"/> class.
 39    /// </summary>
 40    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 41    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 42    /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 43    /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</p
 044    public MoviesController(
 045        IUserManager userManager,
 046        ILibraryManager libraryManager,
 047        IDtoService dtoService,
 048        IServerConfigurationManager serverConfigurationManager)
 49    {
 050        _userManager = userManager;
 051        _libraryManager = libraryManager;
 052        _dtoService = dtoService;
 053        _serverConfigurationManager = serverConfigurationManager;
 054    }
 55
 56    /// <summary>
 57    /// Gets movie recommendations.
 58    /// </summary>
 59    /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 60    /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</
 61    /// <param name="fields">Optional. The fields to return.</param>
 62    /// <param name="categoryLimit">The max number of categories to return.</param>
 63    /// <param name="itemLimit">The max number of items to return per category.</param>
 64    /// <response code="200">Movie recommendations returned.</response>
 65    /// <returns>The list of movie recommendations.</returns>
 66    [HttpGet("Recommendations")]
 67    public ActionResult<IEnumerable<RecommendationDto>> GetMovieRecommendations(
 68        [FromQuery] Guid? userId,
 69        [FromQuery] Guid? parentId,
 70        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
 71        [FromQuery] int categoryLimit = 5,
 72        [FromQuery] int itemLimit = 8)
 73    {
 074        userId = RequestHelpers.GetUserId(User, userId);
 075        var user = userId.IsNullOrEmpty()
 076            ? null
 077            : _userManager.GetUserById(userId.Value);
 078        var dtoOptions = new DtoOptions { Fields = fields };
 79
 080        var categories = new List<RecommendationDto>();
 81
 082        var parentIdGuid = parentId ?? Guid.Empty;
 83
 084        var query = new InternalItemsQuery(user)
 085        {
 086            IncludeItemTypes = new[]
 087            {
 088                BaseItemKind.Movie,
 089                // nameof(Trailer),
 090                // nameof(LiveTvProgram)
 091            },
 092            // IsMovie = true
 093            OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.Random, SortOrder.Descending) }
 094            Limit = 7,
 095            ParentId = parentIdGuid,
 096            Recursive = true,
 097            IsPlayed = true,
 098            DtoOptions = dtoOptions
 099        };
 100
 0101        var recentlyPlayedMovies = _libraryManager.GetItemList(query);
 102
 0103        var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
 0104        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 105        {
 0106            itemTypes.Add(BaseItemKind.Trailer);
 0107            itemTypes.Add(BaseItemKind.LiveTvProgram);
 108        }
 109
 0110        var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user)
 0111        {
 0112            IncludeItemTypes = itemTypes.ToArray(),
 0113            IsMovie = true,
 0114            OrderBy = new[] { (ItemSortBy.Random, SortOrder.Descending) },
 0115            Limit = 10,
 0116            IsFavoriteOrLiked = true,
 0117            ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id).ToArray(),
 0118            EnableGroupByMetadataKey = true,
 0119            ParentId = parentIdGuid,
 0120            Recursive = true,
 0121            DtoOptions = dtoOptions
 0122        });
 123
 0124        var mostRecentMovies = recentlyPlayedMovies.Take(Math.Min(recentlyPlayedMovies.Count, 6)).ToList();
 125        // Get recently played directors
 0126        var recentDirectors = GetDirectors(mostRecentMovies)
 0127            .ToList();
 128
 129        // Get recently played actors
 0130        var recentActors = GetActors(mostRecentMovies)
 0131            .ToList();
 132
 0133        var similarToRecentlyPlayed = GetSimilarTo(user, recentlyPlayedMovies, itemLimit, dtoOptions, RecommendationType
 0134        var similarToLiked = GetSimilarTo(user, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedIte
 135
 0136        var hasDirectorFromRecentlyPlayed = GetWithDirector(user, recentDirectors, itemLimit, dtoOptions, Recommendation
 0137        var hasActorFromRecentlyPlayed = GetWithActor(user, recentActors, itemLimit, dtoOptions, RecommendationType.HasA
 138
 0139        var categoryTypes = new List<IEnumerator<RecommendationDto>>
 0140            {
 0141                // Give this extra weight
 0142                similarToRecentlyPlayed,
 0143                similarToRecentlyPlayed,
 0144
 0145                // Give this extra weight
 0146                similarToLiked,
 0147                similarToLiked,
 0148                hasDirectorFromRecentlyPlayed,
 0149                hasActorFromRecentlyPlayed
 0150            };
 151
 0152        while (categories.Count < categoryLimit)
 153        {
 0154            var allEmpty = true;
 155
 0156            foreach (var category in categoryTypes)
 157            {
 0158                if (category.MoveNext())
 159                {
 0160                    categories.Add(category.Current);
 0161                    allEmpty = false;
 162
 0163                    if (categories.Count >= categoryLimit)
 164                    {
 0165                        break;
 166                    }
 167                }
 168            }
 169
 0170            if (allEmpty)
 171            {
 172                break;
 173            }
 174        }
 175
 0176        return Ok(categories.OrderBy(i => i.RecommendationType).AsEnumerable());
 177    }
 178
 179    private IEnumerable<RecommendationDto> GetWithDirector(
 180        User? user,
 181        IEnumerable<string> names,
 182        int itemLimit,
 183        DtoOptions dtoOptions,
 184        RecommendationType type)
 185    {
 0186        var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
 0187        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 188        {
 0189            itemTypes.Add(BaseItemKind.Trailer);
 0190            itemTypes.Add(BaseItemKind.LiveTvProgram);
 191        }
 192
 0193        foreach (var name in names)
 194        {
 0195            var items = _libraryManager.GetItemList(
 0196                new InternalItemsQuery(user)
 0197                {
 0198                    Person = name,
 0199                    // Account for duplicates by IMDb id, since the database doesn't support this yet
 0200                    Limit = itemLimit + 2,
 0201                    PersonTypes = new[] { PersonType.Director },
 0202                    IncludeItemTypes = itemTypes.ToArray(),
 0203                    IsMovie = true,
 0204                    EnableGroupByMetadataKey = true,
 0205                    DtoOptions = dtoOptions
 0206                }).DistinctBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().
 0207                .Take(itemLimit)
 0208                .ToList();
 209
 0210            if (items.Count > 0)
 211            {
 0212                var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user);
 213
 0214                yield return new RecommendationDto
 0215                {
 0216                    BaselineItemName = name,
 0217                    CategoryId = name.GetMD5(),
 0218                    RecommendationType = type,
 0219                    Items = returnItems
 0220                };
 221            }
 222        }
 0223    }
 224
 225    private IEnumerable<RecommendationDto> GetWithActor(User? user, IEnumerable<string> names, int itemLimit, DtoOptions
 226    {
 0227        var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
 0228        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 229        {
 0230            itemTypes.Add(BaseItemKind.Trailer);
 0231            itemTypes.Add(BaseItemKind.LiveTvProgram);
 232        }
 233
 0234        foreach (var name in names)
 235        {
 0236            var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
 0237            {
 0238                Person = name,
 0239                // Account for duplicates by IMDb id, since the database doesn't support this yet
 0240                Limit = itemLimit + 2,
 0241                IncludeItemTypes = itemTypes.ToArray(),
 0242                IsMovie = true,
 0243                EnableGroupByMetadataKey = true,
 0244                DtoOptions = dtoOptions
 0245            }).DistinctBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().ToSt
 0246                .Take(itemLimit)
 0247                .ToList();
 248
 0249            if (items.Count > 0)
 250            {
 0251                var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user);
 252
 0253                yield return new RecommendationDto
 0254                {
 0255                    BaselineItemName = name,
 0256                    CategoryId = name.GetMD5(),
 0257                    RecommendationType = type,
 0258                    Items = returnItems
 0259                };
 260            }
 261        }
 0262    }
 263
 264    private IEnumerable<RecommendationDto> GetSimilarTo(User? user, IEnumerable<BaseItem> baselineItems, int itemLimit, 
 265    {
 0266        var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
 0267        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 268        {
 0269            itemTypes.Add(BaseItemKind.Trailer);
 0270            itemTypes.Add(BaseItemKind.LiveTvProgram);
 271        }
 272
 0273        foreach (var item in baselineItems)
 274        {
 0275            var similar = _libraryManager.GetItemList(new InternalItemsQuery(user)
 0276            {
 0277                Limit = itemLimit,
 0278                IncludeItemTypes = itemTypes.ToArray(),
 0279                IsMovie = true,
 0280                EnableGroupByMetadataKey = true,
 0281                DtoOptions = dtoOptions
 0282            });
 283
 0284            if (similar.Count > 0)
 285            {
 0286                var returnItems = _dtoService.GetBaseItemDtos(similar, dtoOptions, user);
 287
 0288                yield return new RecommendationDto
 0289                {
 0290                    BaselineItemName = item.Name,
 0291                    CategoryId = item.Id,
 0292                    RecommendationType = type,
 0293                    Items = returnItems
 0294                };
 295            }
 296        }
 0297    }
 298
 299    private IEnumerable<string> GetActors(IEnumerable<BaseItem> items)
 300    {
 0301        var people = _libraryManager.GetPeople(new InternalPeopleQuery(Array.Empty<string>(), new[] { PersonType.Directo
 0302        {
 0303            MaxListOrder = 3
 0304        });
 305
 0306        var itemIds = items.Select(i => i.Id).ToList();
 307
 0308        return people
 0309            .Where(i => itemIds.Contains(i.ItemId))
 0310            .Select(i => i.Name)
 0311            .DistinctNames();
 312    }
 313
 314    private IEnumerable<string> GetDirectors(IEnumerable<BaseItem> items)
 315    {
 0316        var people = _libraryManager.GetPeople(new InternalPeopleQuery(
 0317            new[] { PersonType.Director },
 0318            Array.Empty<string>()));
 319
 0320        var itemIds = items.Select(i => i.Id).ToList();
 321
 0322        return people
 0323            .Where(i => itemIds.Contains(i.ItemId))
 0324            .Select(i => i.Name)
 0325            .DistinctNames();
 326    }
 327}