< 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: 326
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/10/2026 - 12:12:36 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: 326 1/10/2026 - 12:12:36 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: 326

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.Extensions;
 6using Jellyfin.Api.Helpers;
 7using Jellyfin.Api.ModelBinders;
 8using Jellyfin.Data.Enums;
 9using Jellyfin.Database.Implementations.Entities;
 10using Jellyfin.Database.Implementations.Enums;
 11using Jellyfin.Extensions;
 12using MediaBrowser.Common.Extensions;
 13using MediaBrowser.Controller.Configuration;
 14using MediaBrowser.Controller.Dto;
 15using MediaBrowser.Controller.Entities;
 16using MediaBrowser.Controller.Library;
 17using MediaBrowser.Model.Dto;
 18using MediaBrowser.Model.Entities;
 19using MediaBrowser.Model.Querying;
 20using Microsoft.AspNetCore.Authorization;
 21using Microsoft.AspNetCore.Mvc;
 22
 23namespace Jellyfin.Api.Controllers;
 24
 25/// <summary>
 26/// Movies controller.
 27/// </summary>
 28[Authorize]
 29public class MoviesController : BaseJellyfinApiController
 30{
 31    private readonly IUserManager _userManager;
 32    private readonly ILibraryManager _libraryManager;
 33    private readonly IDtoService _dtoService;
 34    private readonly IServerConfigurationManager _serverConfigurationManager;
 35
 36    /// <summary>
 37    /// Initializes a new instance of the <see cref="MoviesController"/> class.
 38    /// </summary>
 39    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 40    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 41    /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 42    /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</p
 043    public MoviesController(
 044        IUserManager userManager,
 045        ILibraryManager libraryManager,
 046        IDtoService dtoService,
 047        IServerConfigurationManager serverConfigurationManager)
 48    {
 049        _userManager = userManager;
 050        _libraryManager = libraryManager;
 051        _dtoService = dtoService;
 052        _serverConfigurationManager = serverConfigurationManager;
 053    }
 54
 55    /// <summary>
 56    /// Gets movie recommendations.
 57    /// </summary>
 58    /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 59    /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</
 60    /// <param name="fields">Optional. The fields to return.</param>
 61    /// <param name="categoryLimit">The max number of categories to return.</param>
 62    /// <param name="itemLimit">The max number of items to return per category.</param>
 63    /// <response code="200">Movie recommendations returned.</response>
 64    /// <returns>The list of movie recommendations.</returns>
 65    [HttpGet("Recommendations")]
 66    public ActionResult<IEnumerable<RecommendationDto>> GetMovieRecommendations(
 67        [FromQuery] Guid? userId,
 68        [FromQuery] Guid? parentId,
 69        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
 70        [FromQuery] int categoryLimit = 5,
 71        [FromQuery] int itemLimit = 8)
 72    {
 073        userId = RequestHelpers.GetUserId(User, userId);
 074        var user = userId.IsNullOrEmpty()
 075            ? null
 076            : _userManager.GetUserById(userId.Value);
 077        var dtoOptions = new DtoOptions { Fields = fields };
 78
 079        var categories = new List<RecommendationDto>();
 80
 081        var parentIdGuid = parentId ?? Guid.Empty;
 82
 083        var query = new InternalItemsQuery(user)
 084        {
 085            IncludeItemTypes = new[]
 086            {
 087                BaseItemKind.Movie,
 088                // nameof(Trailer),
 089                // nameof(LiveTvProgram)
 090            },
 091            // IsMovie = true
 092            OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.Random, SortOrder.Descending) }
 093            Limit = 7,
 094            ParentId = parentIdGuid,
 095            Recursive = true,
 096            IsPlayed = true,
 097            DtoOptions = dtoOptions
 098        };
 99
 0100        var recentlyPlayedMovies = _libraryManager.GetItemList(query);
 101
 0102        var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
 0103        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 104        {
 0105            itemTypes.Add(BaseItemKind.Trailer);
 0106            itemTypes.Add(BaseItemKind.LiveTvProgram);
 107        }
 108
 0109        var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user)
 0110        {
 0111            IncludeItemTypes = itemTypes.ToArray(),
 0112            IsMovie = true,
 0113            OrderBy = new[] { (ItemSortBy.Random, SortOrder.Descending) },
 0114            Limit = 10,
 0115            IsFavoriteOrLiked = true,
 0116            ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id).ToArray(),
 0117            EnableGroupByMetadataKey = true,
 0118            ParentId = parentIdGuid,
 0119            Recursive = true,
 0120            DtoOptions = dtoOptions
 0121        });
 122
 0123        var mostRecentMovies = recentlyPlayedMovies.Take(Math.Min(recentlyPlayedMovies.Count, 6)).ToList();
 124        // Get recently played directors
 0125        var recentDirectors = GetDirectors(mostRecentMovies)
 0126            .ToList();
 127
 128        // Get recently played actors
 0129        var recentActors = GetActors(mostRecentMovies)
 0130            .ToList();
 131
 0132        var similarToRecentlyPlayed = GetSimilarTo(user, recentlyPlayedMovies, itemLimit, dtoOptions, RecommendationType
 0133        var similarToLiked = GetSimilarTo(user, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedIte
 134
 0135        var hasDirectorFromRecentlyPlayed = GetWithDirector(user, recentDirectors, itemLimit, dtoOptions, Recommendation
 0136        var hasActorFromRecentlyPlayed = GetWithActor(user, recentActors, itemLimit, dtoOptions, RecommendationType.HasA
 137
 0138        var categoryTypes = new List<IEnumerator<RecommendationDto>>
 0139            {
 0140                // Give this extra weight
 0141                similarToRecentlyPlayed,
 0142                similarToRecentlyPlayed,
 0143
 0144                // Give this extra weight
 0145                similarToLiked,
 0146                similarToLiked,
 0147                hasDirectorFromRecentlyPlayed,
 0148                hasActorFromRecentlyPlayed
 0149            };
 150
 0151        while (categories.Count < categoryLimit)
 152        {
 0153            var allEmpty = true;
 154
 0155            foreach (var category in categoryTypes)
 156            {
 0157                if (category.MoveNext())
 158                {
 0159                    categories.Add(category.Current);
 0160                    allEmpty = false;
 161
 0162                    if (categories.Count >= categoryLimit)
 163                    {
 0164                        break;
 165                    }
 166                }
 167            }
 168
 0169            if (allEmpty)
 170            {
 171                break;
 172            }
 173        }
 174
 0175        return Ok(categories.OrderBy(i => i.RecommendationType).AsEnumerable());
 176    }
 177
 178    private IEnumerable<RecommendationDto> GetWithDirector(
 179        User? user,
 180        IEnumerable<string> names,
 181        int itemLimit,
 182        DtoOptions dtoOptions,
 183        RecommendationType type)
 184    {
 0185        var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
 0186        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 187        {
 0188            itemTypes.Add(BaseItemKind.Trailer);
 0189            itemTypes.Add(BaseItemKind.LiveTvProgram);
 190        }
 191
 0192        foreach (var name in names)
 193        {
 0194            var items = _libraryManager.GetItemList(
 0195                new InternalItemsQuery(user)
 0196                {
 0197                    Person = name,
 0198                    // Account for duplicates by IMDb id, since the database doesn't support this yet
 0199                    Limit = itemLimit + 2,
 0200                    PersonTypes = new[] { PersonType.Director },
 0201                    IncludeItemTypes = itemTypes.ToArray(),
 0202                    IsMovie = true,
 0203                    EnableGroupByMetadataKey = true,
 0204                    DtoOptions = dtoOptions
 0205                }).DistinctBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().
 0206                .Take(itemLimit)
 0207                .ToList();
 208
 0209            if (items.Count > 0)
 210            {
 0211                var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user);
 212
 0213                yield return new RecommendationDto
 0214                {
 0215                    BaselineItemName = name,
 0216                    CategoryId = name.GetMD5(),
 0217                    RecommendationType = type,
 0218                    Items = returnItems
 0219                };
 220            }
 221        }
 0222    }
 223
 224    private IEnumerable<RecommendationDto> GetWithActor(User? user, IEnumerable<string> names, int itemLimit, DtoOptions
 225    {
 0226        var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
 0227        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 228        {
 0229            itemTypes.Add(BaseItemKind.Trailer);
 0230            itemTypes.Add(BaseItemKind.LiveTvProgram);
 231        }
 232
 0233        foreach (var name in names)
 234        {
 0235            var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
 0236            {
 0237                Person = name,
 0238                // Account for duplicates by IMDb id, since the database doesn't support this yet
 0239                Limit = itemLimit + 2,
 0240                IncludeItemTypes = itemTypes.ToArray(),
 0241                IsMovie = true,
 0242                EnableGroupByMetadataKey = true,
 0243                DtoOptions = dtoOptions
 0244            }).DistinctBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().ToSt
 0245                .Take(itemLimit)
 0246                .ToList();
 247
 0248            if (items.Count > 0)
 249            {
 0250                var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user);
 251
 0252                yield return new RecommendationDto
 0253                {
 0254                    BaselineItemName = name,
 0255                    CategoryId = name.GetMD5(),
 0256                    RecommendationType = type,
 0257                    Items = returnItems
 0258                };
 259            }
 260        }
 0261    }
 262
 263    private IEnumerable<RecommendationDto> GetSimilarTo(User? user, IEnumerable<BaseItem> baselineItems, int itemLimit, 
 264    {
 0265        var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
 0266        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 267        {
 0268            itemTypes.Add(BaseItemKind.Trailer);
 0269            itemTypes.Add(BaseItemKind.LiveTvProgram);
 270        }
 271
 0272        foreach (var item in baselineItems)
 273        {
 0274            var similar = _libraryManager.GetItemList(new InternalItemsQuery(user)
 0275            {
 0276                Limit = itemLimit,
 0277                IncludeItemTypes = itemTypes.ToArray(),
 0278                IsMovie = true,
 0279                EnableGroupByMetadataKey = true,
 0280                DtoOptions = dtoOptions
 0281            });
 282
 0283            if (similar.Count > 0)
 284            {
 0285                var returnItems = _dtoService.GetBaseItemDtos(similar, dtoOptions, user);
 286
 0287                yield return new RecommendationDto
 0288                {
 0289                    BaselineItemName = item.Name,
 0290                    CategoryId = item.Id,
 0291                    RecommendationType = type,
 0292                    Items = returnItems
 0293                };
 294            }
 295        }
 0296    }
 297
 298    private IEnumerable<string> GetActors(IEnumerable<BaseItem> items)
 299    {
 0300        var people = _libraryManager.GetPeople(new InternalPeopleQuery(Array.Empty<string>(), new[] { PersonType.Directo
 0301        {
 0302            MaxListOrder = 3
 0303        });
 304
 0305        var itemIds = items.Select(i => i.Id).ToList();
 306
 0307        return people
 0308            .Where(i => itemIds.Contains(i.ItemId))
 0309            .Select(i => i.Name)
 0310            .DistinctNames();
 311    }
 312
 313    private IEnumerable<string> GetDirectors(IEnumerable<BaseItem> items)
 314    {
 0315        var people = _libraryManager.GetPeople(new InternalPeopleQuery(
 0316            new[] { PersonType.Director },
 0317            Array.Empty<string>()));
 318
 0319        var itemIds = items.Select(i => i.Id).ToList();
 320
 0321        return people
 0322            .Where(i => itemIds.Contains(i.ItemId))
 0323            .Select(i => i.Name)
 0324            .DistinctNames();
 325    }
 326}