< 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.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 }
 078            .AddClientFields(User);
 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    {
 186        var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
 187        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 188        {
 189            itemTypes.Add(BaseItemKind.Trailer);
 190            itemTypes.Add(BaseItemKind.LiveTvProgram);
 191        }
 192
 193        foreach (var name in names)
 194        {
 195            var items = _libraryManager.GetItemList(
 196                new InternalItemsQuery(user)
 197                {
 198                    Person = name,
 199                    // Account for duplicates by IMDb id, since the database doesn't support this yet
 200                    Limit = itemLimit + 2,
 201                    PersonTypes = new[] { PersonType.Director },
 202                    IncludeItemTypes = itemTypes.ToArray(),
 203                    IsMovie = true,
 204                    EnableGroupByMetadataKey = true,
 205                    DtoOptions = dtoOptions
 206                }).DistinctBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().
 207                .Take(itemLimit)
 208                .ToList();
 209
 210            if (items.Count > 0)
 211            {
 212                var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user);
 213
 214                yield return new RecommendationDto
 215                {
 216                    BaselineItemName = name,
 217                    CategoryId = name.GetMD5(),
 218                    RecommendationType = type,
 219                    Items = returnItems
 220                };
 221            }
 222        }
 223    }
 224
 225    private IEnumerable<RecommendationDto> GetWithActor(User? user, IEnumerable<string> names, int itemLimit, DtoOptions
 226    {
 227        var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
 228        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 229        {
 230            itemTypes.Add(BaseItemKind.Trailer);
 231            itemTypes.Add(BaseItemKind.LiveTvProgram);
 232        }
 233
 234        foreach (var name in names)
 235        {
 236            var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
 237            {
 238                Person = name,
 239                // Account for duplicates by IMDb id, since the database doesn't support this yet
 240                Limit = itemLimit + 2,
 241                IncludeItemTypes = itemTypes.ToArray(),
 242                IsMovie = true,
 243                EnableGroupByMetadataKey = true,
 244                DtoOptions = dtoOptions
 245            }).DistinctBy(i => i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb) ?? Guid.NewGuid().ToSt
 246                .Take(itemLimit)
 247                .ToList();
 248
 249            if (items.Count > 0)
 250            {
 251                var returnItems = _dtoService.GetBaseItemDtos(items, dtoOptions, user);
 252
 253                yield return new RecommendationDto
 254                {
 255                    BaselineItemName = name,
 256                    CategoryId = name.GetMD5(),
 257                    RecommendationType = type,
 258                    Items = returnItems
 259                };
 260            }
 261        }
 262    }
 263
 264    private IEnumerable<RecommendationDto> GetSimilarTo(User? user, IEnumerable<BaseItem> baselineItems, int itemLimit, 
 265    {
 266        var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
 267        if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
 268        {
 269            itemTypes.Add(BaseItemKind.Trailer);
 270            itemTypes.Add(BaseItemKind.LiveTvProgram);
 271        }
 272
 273        foreach (var item in baselineItems)
 274        {
 275            var similar = _libraryManager.GetItemList(new InternalItemsQuery(user)
 276            {
 277                Limit = itemLimit,
 278                IncludeItemTypes = itemTypes.ToArray(),
 279                IsMovie = true,
 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}