< 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: 100
Coverable lines: 100
Total lines: 327
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 16
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
GetMovieRecommendations(...)0%272160%
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.Entities;
 9using Jellyfin.Data.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.Mvc;
 21
 22namespace Jellyfin.Api.Controllers;
 23
 24/// <summary>
 25/// Movies controller.
 26/// </summary>
 27[Authorize]
 28public class MoviesController : BaseJellyfinApiController
 29{
 30    private readonly IUserManager _userManager;
 31    private readonly ILibraryManager _libraryManager;
 32    private readonly IDtoService _dtoService;
 33    private readonly IServerConfigurationManager _serverConfigurationManager;
 34
 35    /// <summary>
 36    /// Initializes a new instance of the <see cref="MoviesController"/> class.
 37    /// </summary>
 38    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 39    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 40    /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 41    /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</p
 042    public MoviesController(
 043        IUserManager userManager,
 044        ILibraryManager libraryManager,
 045        IDtoService dtoService,
 046        IServerConfigurationManager serverConfigurationManager)
 47    {
 048        _userManager = userManager;
 049        _libraryManager = libraryManager;
 050        _dtoService = dtoService;
 051        _serverConfigurationManager = serverConfigurationManager;
 052    }
 53
 54    /// <summary>
 55    /// Gets movie recommendations.
 56    /// </summary>
 57    /// <param name="userId">Optional. Filter by user id, and attach user data.</param>
 58    /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</
 59    /// <param name="fields">Optional. The fields to return.</param>
 60    /// <param name="categoryLimit">The max number of categories to return.</param>
 61    /// <param name="itemLimit">The max number of items to return per category.</param>
 62    /// <response code="200">Movie recommendations returned.</response>
 63    /// <returns>The list of movie recommendations.</returns>
 64    [HttpGet("Recommendations")]
 65    public ActionResult<IEnumerable<RecommendationDto>> GetMovieRecommendations(
 66        [FromQuery] Guid? userId,
 67        [FromQuery] Guid? parentId,
 68        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
 69        [FromQuery] int categoryLimit = 5,
 70        [FromQuery] int itemLimit = 8)
 71    {
 072        userId = RequestHelpers.GetUserId(User, userId);
 073        var user = userId.IsNullOrEmpty()
 074            ? null
 075            : _userManager.GetUserById(userId.Value);
 076        var dtoOptions = new DtoOptions { Fields = fields }
 077            .AddClientFields(User);
 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.GetRange(0, Math.Min(recentlyPlayedMovies.Count, 6));
 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    {
 185        var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
 186        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 187        {
 188            itemTypes.Add(BaseItemKind.Trailer);
 189            itemTypes.Add(BaseItemKind.LiveTvProgram);
 190        }
 191
 192        foreach (var name in names)
 193        {
 194            var items = _libraryManager.GetItemList(
 195                new InternalItemsQuery(user)
 196                {
 197                    Person = name,
 198                    // Account for duplicates by IMDb id, since the database doesn't support this yet
 199                    Limit = itemLimit + 2,
 200                    PersonTypes = new[] { PersonType.Director },
 201                    IncludeItemTypes = itemTypes.ToArray(),
 202                    IsMovie = true,
 203                    EnableGroupByMetadataKey = true,
 204                    DtoOptions = dtoOptions
 205                }).DistinctBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().
 206                .Take(itemLimit)
 207                .ToList();
 208
 209            if (items.Count > 0)
 210            {
 211                var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user);
 212
 213                yield return new RecommendationDto
 214                {
 215                    BaselineItemName = name,
 216                    CategoryId = name.GetMD5(),
 217                    RecommendationType = type,
 218                    Items = returnItems
 219                };
 220            }
 221        }
 222    }
 223
 224    private IEnumerable<RecommendationDto> GetWithActor(User? user, IEnumerable<string> names, int itemLimit, DtoOptions
 225    {
 226        var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
 227        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 228        {
 229            itemTypes.Add(BaseItemKind.Trailer);
 230            itemTypes.Add(BaseItemKind.LiveTvProgram);
 231        }
 232
 233        foreach (var name in names)
 234        {
 235            var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
 236            {
 237                Person = name,
 238                // Account for duplicates by IMDb id, since the database doesn't support this yet
 239                Limit = itemLimit + 2,
 240                IncludeItemTypes = itemTypes.ToArray(),
 241                IsMovie = true,
 242                EnableGroupByMetadataKey = true,
 243                DtoOptions = dtoOptions
 244            }).DistinctBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().ToSt
 245                .Take(itemLimit)
 246                .ToList();
 247
 248            if (items.Count > 0)
 249            {
 250                var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user);
 251
 252                yield return new RecommendationDto
 253                {
 254                    BaselineItemName = name,
 255                    CategoryId = name.GetMD5(),
 256                    RecommendationType = type,
 257                    Items = returnItems
 258                };
 259            }
 260        }
 261    }
 262
 263    private IEnumerable<RecommendationDto> GetSimilarTo(User? user, IEnumerable<BaseItem> baselineItems, int itemLimit, 
 264    {
 265        var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
 266        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 267        {
 268            itemTypes.Add(BaseItemKind.Trailer);
 269            itemTypes.Add(BaseItemKind.LiveTvProgram);
 270        }
 271
 272        foreach (var item in baselineItems)
 273        {
 274            var similar = _libraryManager.GetItemList(new InternalItemsQuery(user)
 275            {
 276                Limit = itemLimit,
 277                IncludeItemTypes = itemTypes.ToArray(),
 278                IsMovie = true,
 279                SimilarTo = item,
 280                EnableGroupByMetadataKey = true,
 281                DtoOptions = dtoOptions
 282            });
 283
 284            if (similar.Count > 0)
 285            {
 286                var returnItems = _dtoService.GetBaseItemDtos(similar, dtoOptions, user);
 287
 288                yield return new RecommendationDto
 289                {
 290                    BaselineItemName = item.Name,
 291                    CategoryId = item.Id,
 292                    RecommendationType = type,
 293                    Items = returnItems
 294                };
 295            }
 296        }
 297    }
 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}