From 6825cad56221775dfdfc88bbf260cd1399fa4313 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Tue, 11 Mar 2014 22:11:01 -0400 Subject: [PATCH] move collections back under movies. add movie suggestions page. --- MediaBrowser.Api/Movies/MoviesService.cs | 244 +++++++++++++++++- MediaBrowser.Api/SimilarItemsHelper.cs | 10 +- MediaBrowser.Api/TvShowsService.cs | 15 +- MediaBrowser.Controller/Channels/IChannel.cs | 16 +- .../MediaBrowser.Model.Portable.csproj | 3 + .../MediaBrowser.Model.net35.csproj | 3 + MediaBrowser.Model/Dto/RecommendationDto.cs | 29 +++ MediaBrowser.Model/Entities/BaseItemInfo.cs | 24 ++ MediaBrowser.Model/MediaBrowser.Model.csproj | 1 + .../Manager/MetadataService.cs | 22 +- .../Dto/DtoService.cs | 51 ++++ .../Library/LibraryManager.cs | 1 + .../Library/Resolvers/Movies/MovieResolver.cs | 25 +- .../Api/DashboardService.cs | 1 + .../MediaBrowser.WebDashboard.csproj | 6 + MediaBrowser.sln | 2 +- 16 files changed, 408 insertions(+), 45 deletions(-) create mode 100644 MediaBrowser.Model/Dto/RecommendationDto.cs diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 5d97d13e1..667a86fe6 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -1,9 +1,16 @@ -using MediaBrowser.Controller.Dto; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; using ServiceStack; +using System; +using System.Collections.Generic; +using System.Linq; namespace MediaBrowser.Api.Movies { @@ -23,6 +30,32 @@ namespace MediaBrowser.Api.Movies } } + [Route("/Movies/Recommendations", "GET")] + [Api(Description = "Gets movie recommendations")] + public class GetMovieRecommendations : IReturn, IHasItemFields + { + [ApiMember(Name = "CategoryLimit", Description = "The max number of categories to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int CategoryLimit { get; set; } + + [ApiMember(Name = "ItemLimit", Description = "The max number of items to return per category", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int ItemLimit { get; set; } + + /// + /// Gets or sets the user id. + /// + /// The user id. + [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid? UserId { get; set; } + + public GetMovieRecommendations() + { + CategoryLimit = 5; + ItemLimit = 8; + } + + public string Fields { get; set; } + } + /// /// Class MoviesService /// @@ -78,5 +111,214 @@ namespace MediaBrowser.Api.Movies return ToOptimizedSerializedResultUsingCache(result); } + + public object Get(GetMovieRecommendations request) + { + var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; + + var folder = user.RootFolder; + var movies = folder.RecursiveChildren.OfType().ToList(); + + var result = GetRecommendationCategories(user, movies, request.CategoryLimit, request.ItemLimit, request.GetItemFields().ToList()); + + return ToOptimizedResult(result); + } + + private IEnumerable GetRecommendationCategories(User user, List allMovies, int categoryLimit, int itemLimit, List fields) + { + var categories = new List(); + + var recentlyPlayedMovies = allMovies + .Select(i => + { + var userdata = _userDataRepository.GetUserData(user.Id, i.GetUserDataKey()); + return new Tuple(i, userdata.Played, userdata.LastPlayedDate ?? DateTime.MinValue); + }) + .Where(i => i.Item2) + .OrderByDescending(i => i.Item3) + .Select(i => i.Item1) + .ToList(); + + var excludeFromLiked = recentlyPlayedMovies.Take(10); + var likedMovies = allMovies + .Select(i => + { + var score = 0; + var userData = _userDataRepository.GetUserData(user.Id, i.GetUserDataKey()); + + if (userData.IsFavorite) + { + score = 2; + } + else + { + score = userData.Likes.HasValue ? userData.Likes.Value ? 1 : -1 : 0; + } + + return new Tuple(i, score); + }) + .OrderByDescending(i => i.Item2) + .ThenBy(i => Guid.NewGuid()) + .Where(i => i.Item2 > 0) + .Select(i => i.Item1) + .Where(i => !excludeFromLiked.Contains(i)); + + var mostRecentMovies = recentlyPlayedMovies.Take(6).ToList(); + // Get recently played directors + var recentDirectors = GetDirectors(mostRecentMovies) + .OrderBy(i => Guid.NewGuid()) + .ToList(); + + // Get recently played actors + var recentActors = GetActors(mostRecentMovies) + .OrderBy(i => Guid.NewGuid()) + .ToList(); + + var similarToRecentlyPlayed = GetSimilarTo(user, allMovies, recentlyPlayedMovies.Take(10).OrderBy(i => Guid.NewGuid()), itemLimit, fields, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator(); + var similarToLiked = GetSimilarTo(user, allMovies, likedMovies, itemLimit, fields, RecommendationType.SimilarToLikedItem).GetEnumerator(); + + var hasDirectorFromRecentlyPlayed = GetWithDirector(user, allMovies, recentDirectors, itemLimit, fields, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator(); + var hasActorFromRecentlyPlayed = GetWithActor(user, allMovies, recentActors, itemLimit, fields, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator(); + + var categoryTypes = new List> + { + // Give this extra weight + similarToRecentlyPlayed, + similarToRecentlyPlayed, + + // Give this extra weight + similarToLiked, + similarToLiked, + + hasDirectorFromRecentlyPlayed, + hasActorFromRecentlyPlayed + }; + + while (categories.Count < categoryLimit) + { + var allEmpty = true; + + foreach (var category in categoryTypes) + { + if (category.MoveNext()) + { + categories.Add(category.Current); + allEmpty = false; + + if (categories.Count >= categoryLimit) + { + break; + } + } + } + + if (allEmpty) + { + break; + } + } + + //// Get the lead actor for all movies + //var allActors = GetActors(allMovies) + // .ToList(); + + //foreach (var actor in recentActors) + //{ + + //} + + return categories.OrderBy(i => i.RecommendationType).ThenBy(i => Guid.NewGuid()); + } + + private IEnumerable GetWithDirector(User user, List allMovies, IEnumerable directors, int itemLimit, List fields, RecommendationType type) + { + var userId = user.Id; + + foreach (var director in directors) + { + var items = allMovies + .Where(i => !_userDataRepository.GetUserData(userId, i.GetUserDataKey()).Played && i.People.Any(p => string.Equals(p.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase) && string.Equals(p.Name, director, StringComparison.OrdinalIgnoreCase))) + .Take(itemLimit) + .ToList(); + + if (items.Count > 0) + { + yield return new RecommendationDto + { + BaselineItemName = director, + CategoryId = director.GetMD5().ToString("N"), + RecommendationType = type, + Items = items.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray() + }; + } + } + } + + private IEnumerable GetWithActor(User user, List allMovies, IEnumerable names, int itemLimit, List fields, RecommendationType type) + { + var userId = user.Id; + + foreach (var name in names) + { + var items = allMovies + .Where(i => !_userDataRepository.GetUserData(userId, i.GetUserDataKey()).Played && i.People.Any(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase))) + .Take(itemLimit) + .ToList(); + + if (items.Count > 0) + { + yield return new RecommendationDto + { + BaselineItemName = name, + CategoryId = name.GetMD5().ToString("N"), + RecommendationType = type, + Items = items.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray() + }; + } + } + } + + private IEnumerable GetSimilarTo(User user, List allMovies, IEnumerable baselineItems, int itemLimit, List fields, RecommendationType type) + { + var userId = user.Id; + + foreach (var item in baselineItems) + { + var similar = SimilarItemsHelper + .GetSimilaritems(item, allMovies, SimilarItemsHelper.GetSimiliarityScore) + .Where(i => !_userDataRepository.GetUserData(userId, i.GetUserDataKey()).Played) + .Take(itemLimit) + .ToList(); + + if (similar.Count > 0) + { + yield return new RecommendationDto + { + BaselineItemName = item.Name, + CategoryId = item.Id.ToString("N"), + RecommendationType = type, + Items = similar.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray() + }; + } + } + } + + private IEnumerable GetActors(IEnumerable items) + { + // Get the two leading actors for all movies + return items + .SelectMany(i => i.People.Where(p => !string.Equals(PersonType.Director, p.Type, StringComparison.OrdinalIgnoreCase)).Take(2)) + .Select(i => i.Name) + .Distinct(StringComparer.OrdinalIgnoreCase); + } + + private IEnumerable GetDirectors(IEnumerable items) + { + return items + .Select(i => i.People.FirstOrDefault(p => string.Equals(PersonType.Director, p.Type, StringComparison.OrdinalIgnoreCase))) + .Where(i => i != null) + .Select(i => i.Name) + .Distinct(StringComparer.OrdinalIgnoreCase); + } } } diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs index d1dc801bc..abc4ece65 100644 --- a/MediaBrowser.Api/SimilarItemsHelper.cs +++ b/MediaBrowser.Api/SimilarItemsHelper.cs @@ -73,7 +73,7 @@ namespace MediaBrowser.Api var item = string.IsNullOrEmpty(request.Id) ? (request.UserId.HasValue ? user.RootFolder : - (Folder)libraryManager.RootFolder) : dtoService.GetItemByDtoId(request.Id, request.UserId); + libraryManager.RootFolder) : dtoService.GetItemByDtoId(request.Id, request.UserId); var fields = request.GetItemFields().ToList(); @@ -81,7 +81,7 @@ namespace MediaBrowser.Api ? libraryManager.RootFolder.GetRecursiveChildren(i => i.Id != item.Id) : user.RootFolder.GetRecursiveChildren(user, i => i.Id != item.Id); - var items = GetSimilaritems(item, inputItems, includeInSearch, getSimilarityScore) + var items = GetSimilaritems(item, inputItems.Where(includeInSearch), getSimilarityScore) .ToList(); IEnumerable returnItems = items; @@ -106,12 +106,12 @@ namespace MediaBrowser.Api /// /// The item. /// The input items. - /// The include in search. /// The get similarity score. /// IEnumerable{BaseItem}. - internal static IEnumerable GetSimilaritems(BaseItem item, IEnumerable inputItems, Func includeInSearch, Func getSimilarityScore) + internal static IEnumerable GetSimilaritems(BaseItem item, IEnumerable inputItems, Func getSimilarityScore) { - inputItems = inputItems.Where(includeInSearch); + var itemId = item.Id; + inputItems = inputItems.Where(i => i.Id != itemId); return inputItems.Select(i => new Tuple(i, getSimilarityScore(item, i))) .Where(i => i.Item2 > 2) diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 198e1c383..9e58c9f53 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -300,11 +300,13 @@ namespace MediaBrowser.Api return FilterSeries(request, series) .AsParallel() - .Select(i => GetNextUp(i, currentUser, request).Item1) - .Where(i => i != null) + .Select(i => GetNextUp(i, currentUser)) + .Where(i => i.Item1 != null) .OrderByDescending(i => { - var seriesUserData = _userDataManager.GetUserData(user.Id, i.Series.GetUserDataKey()); + var episode = i.Item1; + + var seriesUserData = _userDataManager.GetUserData(user.Id, episode.Series.GetUserDataKey()); if (seriesUserData.IsFavorite) { @@ -318,7 +320,9 @@ namespace MediaBrowser.Api return 0; }) - .ThenByDescending(i => i.PremiereDate ?? DateTime.MinValue); + .ThenByDescending(i =>i.Item2) + .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue) + .Select(i => i.Item1); } /// @@ -326,9 +330,8 @@ namespace MediaBrowser.Api /// /// The series. /// The user. - /// The request. /// Task{Episode}. - private Tuple GetNextUp(Series series, User user, GetNextUpEpisodes request) + private Tuple GetNextUp(Series series, User user) { // Get them in display order, then reverse var allEpisodes = series.GetSeasons(user, true, true) diff --git a/MediaBrowser.Controller/Channels/IChannel.cs b/MediaBrowser.Controller/Channels/IChannel.cs index edb2347c0..ba1bd4083 100644 --- a/MediaBrowser.Controller/Channels/IChannel.cs +++ b/MediaBrowser.Controller/Channels/IChannel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using MediaBrowser.Controller.Entities; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -28,30 +29,31 @@ namespace MediaBrowser.Controller.Channels /// Searches the specified search term. /// /// The search term. + /// The user. /// The cancellation token. /// Task{IEnumerable{ChannelItemInfo}}. - Task> Search(string searchTerm, CancellationToken cancellationToken); - + Task> Search(string searchTerm, User user, CancellationToken cancellationToken); + /// /// Gets the channel items. /// + /// The user. /// The cancellation token. /// Task{IEnumerable{ChannelItem}}. - Task> GetChannelItems(CancellationToken cancellationToken); + Task> GetChannelItems(User user, CancellationToken cancellationToken); /// /// Gets the channel items. /// /// The category identifier. + /// The user. /// The cancellation token. /// Task{IEnumerable{ChannelItem}}. - Task> GetChannelItems(string categoryId, CancellationToken cancellationToken); + Task> GetChannelItems(string categoryId, User user, CancellationToken cancellationToken); } public class ChannelCapabilities { public bool CanSearch { get; set; } - - public bool CanBeIndexed { get; set; } } } diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 8dd8a2a34..c8df3931d 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -134,6 +134,9 @@ Dto\ItemIndex.cs + + Dto\RecommendationDto.cs + Dto\StreamOptions.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 4cdcaae64..6e8f23089 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -121,6 +121,9 @@ Dto\ItemIndex.cs + + Dto\RecommendationDto.cs + Dto\StreamOptions.cs diff --git a/MediaBrowser.Model/Dto/RecommendationDto.cs b/MediaBrowser.Model/Dto/RecommendationDto.cs new file mode 100644 index 000000000..68b71e466 --- /dev/null +++ b/MediaBrowser.Model/Dto/RecommendationDto.cs @@ -0,0 +1,29 @@ + +namespace MediaBrowser.Model.Dto +{ + public class RecommendationDto + { + public BaseItemDto[] Items { get; set; } + + public RecommendationType RecommendationType { get; set; } + + public string BaselineItemName { get; set; } + + public string CategoryId { get; set; } + } + + public enum RecommendationType + { + SimilarToRecentlyPlayed = 0, + + SimilarToLikedItem = 1, + + HasDirectorFromRecentlyPlayed = 2, + + HasActorFromRecentlyPlayed = 3, + + HasLikedDirector = 4, + + HasLikedActor = 5 + } +} diff --git a/MediaBrowser.Model/Entities/BaseItemInfo.cs b/MediaBrowser.Model/Entities/BaseItemInfo.cs index 49f3e2d8f..b704bdb57 100644 --- a/MediaBrowser.Model/Entities/BaseItemInfo.cs +++ b/MediaBrowser.Model/Entities/BaseItemInfo.cs @@ -46,6 +46,30 @@ namespace MediaBrowser.Model.Entities /// The primary image tag. public Guid? PrimaryImageTag { get; set; } + /// + /// Gets or sets the thumb image tag. + /// + /// The thumb image tag. + public Guid? ThumbImageTag { get; set; } + + /// + /// Gets or sets the thumb item identifier. + /// + /// The thumb item identifier. + public string ThumbItemId { get; set; } + + /// + /// Gets or sets the thumb image tag. + /// + /// The thumb image tag. + public Guid? BackdropImageTag { get; set; } + + /// + /// Gets or sets the thumb item identifier. + /// + /// The thumb item identifier. + public string BackdropItemId { get; set; } + /// /// Gets a value indicating whether this instance has primary image. /// diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 42ae7c396..45172da43 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -74,6 +74,7 @@ + diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 2ecc6c9dd..74b54fe2c 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -305,22 +305,20 @@ namespace MediaBrowser.Providers.Manager refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.ImageUpdate; } - if (!string.IsNullOrEmpty(localItem.Item.Name)) + if (string.IsNullOrWhiteSpace(localItem.Item.Name)) { - MergeData(localItem.Item, temp, new List(), !options.ReplaceAllMetadata, true); - refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport; - - // Only one local provider allowed per item - hasLocalMetadata = true; - break; + localItem.Item.Name = item.Name ?? Path.GetFileNameWithoutExtension(item.Path); } - Logger.Error("Invalid local metadata found for: " + item.Path); - } - else - { - Logger.Debug("{0} returned no metadata for {1}", providerName, item.Path ?? item.Name); + MergeData(localItem.Item, temp, new List(), !options.ReplaceAllMetadata, true); + refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport; + + // Only one local provider allowed per item + hasLocalMetadata = true; + break; } + + Logger.Debug("{0} returned no metadata for {1}", providerName, item.Path ?? item.Name); } catch (OperationCanceledException) { diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 641bf0a6a..fadf4c900 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -307,6 +307,57 @@ namespace MediaBrowser.Server.Implementations.Dto info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary); + var backropItem = item.HasImage(ImageType.Backdrop) ? item : null; + + var thumbItem = item.HasImage(ImageType.Thumb) ? item : null; + + if (thumbItem == null) + { + var episode = item as Episode; + + if (episode != null) + { + var series = episode.Series; + + if (series != null && series.HasImage(ImageType.Thumb)) + { + thumbItem = series; + } + } + } + + if (backropItem == null) + { + var episode = item as Episode; + + if (episode != null) + { + var series = episode.Series; + + if (series != null && series.HasImage(ImageType.Backdrop)) + { + backropItem = series; + } + } + } + + if (thumbItem == null) + { + thumbItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Thumb)); + } + + if (thumbItem != null) + { + info.ThumbImageTag = GetImageCacheTag(thumbItem, ImageType.Thumb); + info.ThumbItemId = GetDtoId(thumbItem); + } + + if (thumbItem != null) + { + info.BackdropImageTag = GetImageCacheTag(backropItem, ImageType.Backdrop); + info.BackdropItemId = GetDtoId(backropItem); + } + return info; } diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index b190b8947..0b1947d4c 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -5,6 +5,7 @@ using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index ac9b42efb..a8a4e2237 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -89,34 +89,31 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies // Find movies with their own folders if (isDirectory) { - if (args.Path.IndexOf("[trailers]", StringComparison.OrdinalIgnoreCase) != -1 || - string.Equals(collectionType, CollectionType.Trailers, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(collectionType, CollectionType.Trailers, StringComparison.OrdinalIgnoreCase)) { - return FindMovie(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); + return FindMovie(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false); } - if (args.Path.IndexOf("[musicvideos]", StringComparison.OrdinalIgnoreCase) != -1 || - string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); + return FindMovie(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false); } - if (args.Path.IndexOf("[adultvideos]", StringComparison.OrdinalIgnoreCase) != -1 || - string.Equals(collectionType, CollectionType.AdultVideos, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(collectionType, CollectionType.AdultVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); + return FindMovie(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true); } if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie