diff options
Diffstat (limited to 'MediaBrowser.Api')
32 files changed, 732 insertions, 272 deletions
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 5fba539fe5..08686b43a8 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -164,7 +164,10 @@ namespace MediaBrowser.Api return name; } - return libraryManager.GetAllArtists() + return libraryManager.RootFolder.RecursiveChildren + .OfType<Audio>() + .SelectMany(i => i.AllArtists) + .Distinct(StringComparer.OrdinalIgnoreCase) .FirstOrDefault(i => { i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); diff --git a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs index afba8c3604..c9c4cbc43f 100644 --- a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs +++ b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs @@ -194,8 +194,10 @@ namespace MediaBrowser.Api.DefaultTheme .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) .ToList(); - var artists = _libraryManager.GetAllArtists(allItems) - .Randomize() + var artists = allItems.OfType<Audio>() + .SelectMany(i => i.AllArtists) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Randomize() .Select(i => { try diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs index eabda673a4..e371791f81 100644 --- a/MediaBrowser.Api/GamesService.cs +++ b/MediaBrowser.Api/GamesService.cs @@ -155,7 +155,7 @@ namespace MediaBrowser.Api var games = items.OfType<Game>().ToList(); - summary.ClientInstalledGameCount = games.Count(i => !i.IsPlaceHolder); + summary.ClientInstalledGameCount = games.Count(i => i.IsPlaceHolder); summary.GameCount = games.Count; diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index 1eaf4acb1b..b15e67ffa7 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -98,7 +98,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); if (dontFetchMetaChanged && item.IsFolder) { @@ -107,7 +107,7 @@ namespace MediaBrowser.Api foreach (var child in folder.RecursiveChildren.ToList()) { child.DontFetchMeta = newLockData; - await _libraryManager.UpdateItem(child, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await child.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } } } @@ -125,7 +125,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } public void Post(UpdateArtist request) @@ -141,7 +141,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } public void Post(UpdateStudio request) @@ -157,7 +157,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } public void Post(UpdateMusicGenre request) @@ -173,7 +173,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } public void Post(UpdateGameGenre request) @@ -189,7 +189,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } public void Post(UpdateGenre request) @@ -205,7 +205,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } private void UpdateItem(BaseItemDto request, BaseItem item) diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index bcc487a5d5..e19fbb967f 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -66,7 +66,8 @@ <Compile Include="..\SharedVersion.cs"> <Link>Properties\SharedVersion.cs</Link> </Compile> - <Compile Include="AlbumsService.cs" /> + <Compile Include="Movies\CollectionService.cs" /> + <Compile Include="Music\AlbumsService.cs" /> <Compile Include="AppThemeService.cs" /> <Compile Include="BaseApiService.cs" /> <Compile Include="ConfigurationService.cs" /> @@ -81,7 +82,7 @@ <Compile Include="Images\ImageRequest.cs" /> <Compile Include="Images\ImageService.cs" /> <Compile Include="Images\ImageWriter.cs" /> - <Compile Include="InstantMixService.cs" /> + <Compile Include="Music\InstantMixService.cs" /> <Compile Include="ItemLookupService.cs" /> <Compile Include="ItemRefreshService.cs" /> <Compile Include="ItemUpdateService.cs" /> @@ -91,7 +92,7 @@ <Compile Include="Library\LibraryStructureService.cs" /> <Compile Include="LiveTv\LiveTvService.cs" /> <Compile Include="LocalizationService.cs" /> - <Compile Include="MoviesService.cs" /> + <Compile Include="Movies\MoviesService.cs" /> <Compile Include="NewsService.cs" /> <Compile Include="NotificationsService.cs" /> <Compile Include="PackageReviewService.cs" /> @@ -118,7 +119,7 @@ <Compile Include="SessionsService.cs" /> <Compile Include="SimilarItemsHelper.cs" /> <Compile Include="SystemService.cs" /> - <Compile Include="TrailersService.cs" /> + <Compile Include="Movies\TrailersService.cs" /> <Compile Include="TvShowsService.cs" /> <Compile Include="UserLibrary\ArtistsService.cs" /> <Compile Include="UserLibrary\BaseItemsByNameService.cs" /> diff --git a/MediaBrowser.Api/Movies/CollectionService.cs b/MediaBrowser.Api/Movies/CollectionService.cs new file mode 100644 index 0000000000..456449b7b6 --- /dev/null +++ b/MediaBrowser.Api/Movies/CollectionService.cs @@ -0,0 +1,80 @@ +using MediaBrowser.Controller.Collections; +using ServiceStack; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Movies +{ + [Route("/Collections", "POST")] + [Api(Description = "Creates a new collection")] + public class CreateCollection : IReturnVoid + { + [ApiMember(Name = "IsLocked", Description = "Whether or not to lock the new collection.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] + public bool IsLocked { get; set; } + + [ApiMember(Name = "Name", Description = "The name of the new collection.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Name { get; set; } + + [ApiMember(Name = "ParentId", Description = "Optional - create the collection within a specific folder", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public Guid? ParentId { get; set; } + } + + [Route("/Collections/{Id}/Items", "POST")] + [Api(Description = "Adds items to a collection")] + public class AddToCollection : IReturnVoid + { + [ApiMember(Name = "Ids", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Ids { get; set; } + + [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public Guid Id { get; set; } + } + + [Route("/Collections/{Id}/Items", "DELETE")] + [Api(Description = "Removes items from a collection")] + public class RemoveFromCollection : IReturnVoid + { + [ApiMember(Name = "Ids", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Ids { get; set; } + + [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + public Guid Id { get; set; } + } + + public class CollectionService : BaseApiService + { + private readonly ICollectionManager _collectionManager; + + public CollectionService(ICollectionManager collectionManager) + { + _collectionManager = collectionManager; + } + + public void Post(CreateCollection request) + { + var task = _collectionManager.CreateCollection(new CollectionCreationOptions + { + IsLocked = request.IsLocked, + Name = request.Name, + ParentId = request.ParentId + }); + + Task.WaitAll(task); + } + + public void Post(AddToCollection request) + { + var task = _collectionManager.AddToCollection(request.Id, request.Ids.Split(',').Select(i => new Guid(i))); + + Task.WaitAll(task); + } + + public void Delete(RemoveFromCollection request) + { + var task = _collectionManager.RemoveFromCollection(request.Id, request.Ids.Split(',').Select(i => new Guid(i))); + + Task.WaitAll(task); + } + } +} diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs new file mode 100644 index 0000000000..3360d97363 --- /dev/null +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -0,0 +1,324 @@ +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 +{ + /// <summary> + /// Class GetSimilarMovies + /// </summary> + [Route("/Movies/{Id}/Similar", "GET")] + [Api(Description = "Finds movies and trailers similar to a given movie.")] + public class GetSimilarMovies : BaseGetSimilarItemsFromItem + { + [ApiMember(Name = "IncludeTrailers", Description = "Whether or not to include trailers within the results. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool IncludeTrailers { get; set; } + + public GetSimilarMovies() + { + IncludeTrailers = true; + } + } + + [Route("/Movies/Recommendations", "GET")] + [Api(Description = "Gets movie recommendations")] + public class GetMovieRecommendations : IReturn<RecommendationDto[]>, 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; } + + /// <summary> + /// Gets or sets the user id. + /// </summary> + /// <value>The user id.</value> + [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; } + } + + /// <summary> + /// Class MoviesService + /// </summary> + public class MoviesService : BaseApiService + { + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// The _user data repository + /// </summary> + private readonly IUserDataManager _userDataRepository; + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + + private readonly IItemRepository _itemRepo; + private readonly IDtoService _dtoService; + + /// <summary> + /// Initializes a new instance of the <see cref="MoviesService"/> class. + /// </summary> + /// <param name="userManager">The user manager.</param> + /// <param name="userDataRepository">The user data repository.</param> + /// <param name="libraryManager">The library manager.</param> + public MoviesService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService) + { + _userManager = userManager; + _userDataRepository = userDataRepository; + _libraryManager = libraryManager; + _itemRepo = itemRepo; + _dtoService = dtoService; + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> + public object Get(GetSimilarMovies request) + { + var result = SimilarItemsHelper.GetSimilarItemsResult(_userManager, + _itemRepo, + _libraryManager, + _userDataRepository, + _dtoService, + Logger, + request, item => item is Movie || (item is Trailer && request.IncludeTrailers), + SimilarItemsHelper.GetSimiliarityScore); + + 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<Movie>().ToList(); + + var result = GetRecommendationCategories(user, movies, request.CategoryLimit, request.ItemLimit, request.GetItemFields().ToList()); + + return ToOptimizedResult(result); + } + + private IEnumerable<RecommendationDto> GetRecommendationCategories(User user, List<Movie> allMovies, int categoryLimit, int itemLimit, List<ItemFields> fields) + { + var categories = new List<RecommendationDto>(); + + var recentlyPlayedMovies = allMovies + .Select(i => + { + var userdata = _userDataRepository.GetUserData(user.Id, i.GetUserDataKey()); + return new Tuple<Movie, bool, DateTime>(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<Movie, int>(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(7).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<IEnumerator<RecommendationDto>> + { + // 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<RecommendationDto> GetWithDirector(User user, List<Movie> allMovies, IEnumerable<string> directors, int itemLimit, List<ItemFields> 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<RecommendationDto> GetWithActor(User user, List<Movie> allMovies, IEnumerable<string> names, int itemLimit, List<ItemFields> 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<RecommendationDto> GetSimilarTo(User user, List<Movie> allMovies, IEnumerable<Movie> baselineItems, int itemLimit, List<ItemFields> 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<string> GetActors(IEnumerable<BaseItem> 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<string> GetDirectors(IEnumerable<BaseItem> 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/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs index ca465b5e32..4e17bc7b50 100644 --- a/MediaBrowser.Api/TrailersService.cs +++ b/MediaBrowser.Api/Movies/TrailersService.cs @@ -5,7 +5,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using ServiceStack; -namespace MediaBrowser.Api +namespace MediaBrowser.Api.Movies { /// <summary> /// Class GetSimilarTrailers diff --git a/MediaBrowser.Api/MoviesService.cs b/MediaBrowser.Api/MoviesService.cs deleted file mode 100644 index 2a99bca8b0..0000000000 --- a/MediaBrowser.Api/MoviesService.cs +++ /dev/null @@ -1,82 +0,0 @@ -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using ServiceStack; - -namespace MediaBrowser.Api -{ - /// <summary> - /// Class GetSimilarMovies - /// </summary> - [Route("/Movies/{Id}/Similar", "GET")] - [Api(Description = "Finds movies and trailers similar to a given movie.")] - public class GetSimilarMovies : BaseGetSimilarItemsFromItem - { - [ApiMember(Name = "IncludeTrailers", Description = "Whether or not to include trailers within the results. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool IncludeTrailers { get; set; } - - public GetSimilarMovies() - { - IncludeTrailers = true; - } - } - - /// <summary> - /// Class MoviesService - /// </summary> - public class MoviesService : BaseApiService - { - /// <summary> - /// The _user manager - /// </summary> - private readonly IUserManager _userManager; - - /// <summary> - /// The _user data repository - /// </summary> - private readonly IUserDataManager _userDataRepository; - /// <summary> - /// The _library manager - /// </summary> - private readonly ILibraryManager _libraryManager; - - private readonly IItemRepository _itemRepo; - private readonly IDtoService _dtoService; - - /// <summary> - /// Initializes a new instance of the <see cref="MoviesService"/> class. - /// </summary> - /// <param name="userManager">The user manager.</param> - /// <param name="userDataRepository">The user data repository.</param> - /// <param name="libraryManager">The library manager.</param> - public MoviesService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService) - { - _userManager = userManager; - _userDataRepository = userDataRepository; - _libraryManager = libraryManager; - _itemRepo = itemRepo; - _dtoService = dtoService; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetSimilarMovies request) - { - var result = SimilarItemsHelper.GetSimilarItemsResult(_userManager, - _itemRepo, - _libraryManager, - _userDataRepository, - _dtoService, - Logger, - request, item => item is Movie || (item is Trailer && request.IncludeTrailers), - SimilarItemsHelper.GetSimiliarityScore); - - return ToOptimizedSerializedResultUsingCache(result); - } - } -} diff --git a/MediaBrowser.Api/AlbumsService.cs b/MediaBrowser.Api/Music/AlbumsService.cs index 5787ad180c..a80dd796a0 100644 --- a/MediaBrowser.Api/AlbumsService.cs +++ b/MediaBrowser.Api/Music/AlbumsService.cs @@ -8,7 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; -namespace MediaBrowser.Api +namespace MediaBrowser.Api.Music { [Route("/Albums/{Id}/Similar", "GET")] [Api(Description = "Finds albums similar to a given album.")] diff --git a/MediaBrowser.Api/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs index 624137677c..a8446a7ef2 100644 --- a/MediaBrowser.Api/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -7,7 +7,7 @@ using System; using System.Collections.Generic; using System.Linq; -namespace MediaBrowser.Api +namespace MediaBrowser.Api.Music { [Route("/Songs/{Id}/InstantMix", "GET")] [Api(Description = "Creates an instant playlist based on a given song")] diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 926dfe9553..3993866cf5 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -279,8 +279,19 @@ namespace MediaBrowser.Api.Playback /// </summary> /// <returns>System.Int32.</returns> /// <exception cref="System.Exception">Unrecognized MediaEncodingQuality value.</exception> - protected int GetNumberOfThreads(bool isWebm) + protected int GetNumberOfThreads(StreamState state, bool isWebm) { + // Use more when this is true. -re will keep cpu usage under control + if (state.ReadInputAtNativeFramerate) + { + if (isWebm) + { + return Math.Max(Environment.ProcessorCount - 1, 1); + } + + return 0; + } + // Webm: http://www.webmproject.org/docs/encoder-parameters/ // The decoder will usually automatically use an appropriate number of threads according to how many cores are available but it can only use multiple threads // for the coefficient data if the encoder selected --token-parts > 0 at encode time. @@ -491,16 +502,16 @@ namespace MediaBrowser.Api.Playback return string.Format("{4} -vf \"{0}scale=trunc({1}/2)*2:trunc({2}/2)*2{3}\"", yadifParam, widthParam, heightParam, assSubtitleParam, copyTsParam); } - - // If Max dimensions were supplied - //this makes my brain hurt. For width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size - if (request.MaxWidth.HasValue && request.MaxHeight.HasValue) - { - var MaxwidthParam = request.MaxWidth.Value.ToString(UsCulture); - var MaxheightParam = request.MaxHeight.Value.ToString(UsCulture); - - return string.Format("{4} -vf \"{0}scale=trunc(min(iw\\,{1})/2)*2:trunc(min((iw/dar)\\,{2})/2)*2{3}\"", yadifParam, MaxwidthParam, MaxheightParam, assSubtitleParam, copyTsParam); - } + + // If Max dimensions were supplied + //this makes my brain hurt. For width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size + if (request.MaxWidth.HasValue && request.MaxHeight.HasValue) + { + var MaxwidthParam = request.MaxWidth.Value.ToString(UsCulture); + var MaxheightParam = request.MaxHeight.Value.ToString(UsCulture); + + return string.Format("{4} -vf \"{0}scale=trunc(min(iw\\,{1})/2)*2:trunc(min((iw/dar)\\,{2})/2)*2{3}\"", yadifParam, MaxwidthParam, MaxheightParam, assSubtitleParam, copyTsParam); + } var isH264Output = outputVideoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase); @@ -603,7 +614,7 @@ namespace MediaBrowser.Api.Playback private string GetExtractedAssPath(StreamState state, bool performConversion) { var path = EncodingManager.GetSubtitleCachePath(state.MediaPath, state.SubtitleStream.Index, ".ass"); - + if (performConversion) { InputType type; @@ -987,20 +998,15 @@ namespace MediaBrowser.Api.Playback if (state.VideoStream != null) { - var isUpscaling = false; - - if (state.VideoRequest.Height.HasValue && state.VideoStream.Height.HasValue && - state.VideoRequest.Height.Value > state.VideoStream.Height.Value) - { - isUpscaling = true; - } + var isUpscaling = state.VideoRequest.Height.HasValue && state.VideoStream.Height.HasValue && + state.VideoRequest.Height.Value > state.VideoStream.Height.Value; if (state.VideoRequest.Width.HasValue && state.VideoStream.Width.HasValue && state.VideoRequest.Width.Value > state.VideoStream.Width.Value) { isUpscaling = true; } - + // Don't allow bitrate increases unless upscaling if (!isUpscaling) { @@ -1199,65 +1205,73 @@ namespace MediaBrowser.Api.Playback } else if (i == 1) { + request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } + else if (i == 2) + { if (videoRequest != null) { videoRequest.VideoCodec = (VideoCodecs)Enum.Parse(typeof(VideoCodecs), val, true); } } - else if (i == 2) + else if (i == 3) { request.AudioCodec = (AudioCodecs)Enum.Parse(typeof(AudioCodecs), val, true); } - else if (i == 3) + else if (i == 4) { if (videoRequest != null) { videoRequest.AudioStreamIndex = int.Parse(val, UsCulture); } } - else if (i == 4) + else if (i == 5) { if (videoRequest != null) { videoRequest.SubtitleStreamIndex = int.Parse(val, UsCulture); } } - else if (i == 5) + else if (i == 6) { if (videoRequest != null) { videoRequest.VideoBitRate = int.Parse(val, UsCulture); } } - else if (i == 6) + else if (i == 7) { request.AudioBitRate = int.Parse(val, UsCulture); } - else if (i == 7) + else if (i == 8) { request.AudioChannels = int.Parse(val, UsCulture); } - else if (i == 8) + else if (i == 9) { if (videoRequest != null) { request.StartTimeTicks = long.Parse(val, UsCulture); } } - else if (i == 9) + else if (i == 10) { if (videoRequest != null) { videoRequest.Profile = val; } } - else if (i == 10) + else if (i == 11) { if (videoRequest != null) { videoRequest.Level = val; } } + else if (i == 12) + { + request.ForcedMimeType = val; + } } } @@ -1309,37 +1323,39 @@ namespace MediaBrowser.Api.Playback state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); state.PlayableStreamFileNames = new List<string>(); - if (!string.IsNullOrEmpty(recording.RecordingInfo.Path) && File.Exists(recording.RecordingInfo.Path)) + var path = recording.RecordingInfo.Path; + var mediaUrl = recording.RecordingInfo.Url; + + if (string.IsNullOrWhiteSpace(path) && string.IsNullOrWhiteSpace(mediaUrl)) { - state.MediaPath = recording.RecordingInfo.Path; + var streamInfo = await LiveTvManager.GetRecordingStream(request.Id, cancellationToken).ConfigureAwait(false); + + state.LiveTvStreamId = streamInfo.Id; + + path = streamInfo.Path; + mediaUrl = streamInfo.Url; + } + + if (!string.IsNullOrEmpty(path) && File.Exists(path)) + { + state.MediaPath = path; state.IsRemote = false; + + state.SendInputOverStandardInput = recording.RecordingInfo.Status == RecordingStatus.InProgress; } - else if (!string.IsNullOrEmpty(recording.RecordingInfo.Url)) + else if (!string.IsNullOrEmpty(mediaUrl)) { - state.MediaPath = recording.RecordingInfo.Url; + state.MediaPath = mediaUrl; state.IsRemote = true; } - else - { - var streamInfo = await LiveTvManager.GetRecordingStream(request.Id, cancellationToken).ConfigureAwait(false); - - state.LiveTvStreamId = streamInfo.Id; - if (!string.IsNullOrEmpty(streamInfo.Path) && File.Exists(streamInfo.Path)) - { - state.MediaPath = streamInfo.Path; - state.IsRemote = false; - } - else if (!string.IsNullOrEmpty(streamInfo.Url)) - { - state.MediaPath = streamInfo.Url; - state.IsRemote = true; - } + //state.RunTimeTicks = recording.RunTimeTicks; + if (recording.RecordingInfo.Status == RecordingStatus.InProgress && !state.IsRemote) + { + await Task.Delay(1000, cancellationToken).ConfigureAwait(false); } - //state.RunTimeTicks = recording.RunTimeTicks; state.ReadInputAtNativeFramerate = recording.RecordingInfo.Status == RecordingStatus.InProgress; - state.SendInputOverStandardInput = recording.RecordingInfo.Status == RecordingStatus.InProgress; state.AudioSync = "1000"; state.DeInterlace = true; } @@ -1359,6 +1375,8 @@ namespace MediaBrowser.Api.Playback { state.MediaPath = streamInfo.Path; state.IsRemote = false; + + await Task.Delay(1000, cancellationToken).ConfigureAwait(false); } else if (!string.IsNullOrEmpty(streamInfo.Url)) { @@ -1366,7 +1384,6 @@ namespace MediaBrowser.Api.Playback state.IsRemote = true; } - state.SendInputOverStandardInput = true; state.ReadInputAtNativeFramerate = true; state.AudioSync = "1000"; state.DeInterlace = true; @@ -1411,6 +1428,11 @@ namespace MediaBrowser.Api.Playback state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false); state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio); + if (state.VideoStream != null && state.VideoStream.IsInterlaced) + { + state.DeInterlace = true; + } + EnforceResolutionLimit(state, videoRequest); } else @@ -1420,8 +1442,8 @@ namespace MediaBrowser.Api.Playback state.HasMediaStreams = mediaStreams.Count > 0; - state.SegmentLength = state.ReadInputAtNativeFramerate ? 3 : 10; - state.HlsListSize = state.ReadInputAtNativeFramerate ? 20 : 1440; + state.SegmentLength = state.ReadInputAtNativeFramerate ? 5 : 10; + state.HlsListSize = state.ReadInputAtNativeFramerate ? 100 : 1440; return state; } diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 5324d2c805..aec271ff20 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -278,7 +278,7 @@ namespace MediaBrowser.Api.Playback.Hls var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds); - var threads = GetNumberOfThreads(false); + var threads = GetNumberOfThreads(state, false); var inputModifier = GetInputModifier(state); diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index 909dd0f40f..4d8d3a5816 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -102,7 +102,7 @@ namespace MediaBrowser.Api.Playback.Progressive const string vn = " -vn"; - var threads = GetNumberOfThreads(false); + var threads = GetNumberOfThreads(state, false); var inputModifier = GetInputModifier(state); diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 023c597308..8ae61b5219 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -214,12 +214,16 @@ namespace MediaBrowser.Api.Playback.Progressive if (request.Static) { - return ResultFactory.GetStaticFileResult(Request, state.MediaPath, FileShare.Read, responseHeaders, isHeadRequest); + var contentType = state.GetMimeType(state.MediaPath); + + return ResultFactory.GetStaticFileResult(Request, state.MediaPath, contentType, FileShare.Read, responseHeaders, isHeadRequest); } if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive)) { - return ResultFactory.GetStaticFileResult(Request, outputPath, FileShare.Read, responseHeaders, isHeadRequest); + var contentType = state.GetMimeType(outputPath); + + return ResultFactory.GetStaticFileResult(Request, outputPath, contentType, FileShare.Read, responseHeaders, isHeadRequest); } return GetStreamResult(state, responseHeaders, isHeadRequest).Result; @@ -287,7 +291,7 @@ namespace MediaBrowser.Api.Playback.Progressive responseHeaders["Accept-Ranges"] = "none"; - var contentType = MimeTypes.GetMimeType(outputPath); + var contentType = state.GetMimeType(outputPath); // Headers only if (isHeadRequest) diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 6deea4ffc6..43dc6f0d49 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -102,7 +102,7 @@ namespace MediaBrowser.Api.Playback.Progressive format = " -f mp4 -movflags frag_keyframe+empty_moov"; } - var threads = GetNumberOfThreads(string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)); + var threads = GetNumberOfThreads(state, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)); var inputModifier = GetInputModifier(state); diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index a73a8f0d90..6b0375e2d2 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -66,6 +66,8 @@ namespace MediaBrowser.Api.Playback public bool ThrowDebugError { get; set; } public string Params { get; set; } + + public string ForcedMimeType { get; set; } } public class VideoStreamRequest : StreamRequest diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 3874fa6036..961ac0a2a2 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Entities; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using System.Collections.Generic; using System.IO; @@ -72,5 +73,20 @@ namespace MediaBrowser.Api.Playback public string InputVideoCodec { get; set; } public string InputAudioCodec { get; set; } + + public string GetMimeType(string outputPath) + { + if (!string.IsNullOrWhiteSpace(Request.ForcedMimeType)) + { + if (VideoRequest == null) + { + return "audio/" + Request.ForcedMimeType; + } + + return "video/" + Request.ForcedMimeType; + } + + return MimeTypes.GetMimeType(outputPath); + } } } diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index 18bd8c6956..f46c6b8e30 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Drawing; +using System; +using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -63,6 +64,9 @@ namespace MediaBrowser.Api [ApiMember(Name = "IncludeArtists", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool IncludeArtists { get; set; } + [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string IncludeItemTypes { get; set; } + public GetSearchHints() { IncludeArtists = true; @@ -130,7 +134,8 @@ namespace MediaBrowser.Api IncludePeople = request.IncludePeople, IncludeStudios = request.IncludeStudios, StartIndex = request.StartIndex, - UserId = request.UserId + UserId = request.UserId, + IncludeItemTypes = (request.IncludeItemTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray() }).ConfigureAwait(false); @@ -206,7 +211,8 @@ namespace MediaBrowser.Api result.SongCount = songs.Count; - result.Artists = _libraryManager.GetAllArtists(songs) + result.Artists = songs.SelectMany(i => i.AllArtists) + .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); result.AlbumArtist = songs.Select(i => i.AlbumArtist).FirstOrDefault(i => !string.IsNullOrEmpty(i)); diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index f662e38170..b8ca70ba5e 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/SessionsService.cs @@ -211,7 +211,7 @@ namespace MediaBrowser.Api [ApiMember(Name = "PlayableMediaTypes", Description = "A list of playable media types, comma delimited. Audio, Video, Book, Game, Photo.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] public string PlayableMediaTypes { get; set; } } - + /// <summary> /// Class SessionsService /// </summary> @@ -368,4 +368,4 @@ namespace MediaBrowser.Api .ToList(); } } -} +}
\ No newline at end of file diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs index d1dc801bcd..1f02a63a09 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<BaseItem> returnItems = items; @@ -106,12 +106,12 @@ namespace MediaBrowser.Api /// </summary> /// <param name="item">The item.</param> /// <param name="inputItems">The input items.</param> - /// <param name="includeInSearch">The include in search.</param> /// <param name="getSimilarityScore">The get similarity score.</param> /// <returns>IEnumerable{BaseItem}.</returns> - internal static IEnumerable<BaseItem> GetSimilaritems(BaseItem item, IEnumerable<BaseItem> inputItems, Func<BaseItem, bool> includeInSearch, Func<BaseItem, BaseItem, int> getSimilarityScore) + internal static IEnumerable<BaseItem> GetSimilaritems(BaseItem item, IEnumerable<BaseItem> inputItems, Func<BaseItem, BaseItem, int> getSimilarityScore) { - inputItems = inputItems.Where(includeInSearch); + var itemId = item.Id; + inputItems = inputItems.Where(i => i.Id != itemId); return inputItems.Select(i => new Tuple<BaseItem, int>(i, getSimilarityScore(item, i))) .Where(i => i.Item2 > 2) @@ -153,7 +153,7 @@ namespace MediaBrowser.Api if (!string.IsNullOrEmpty(item1.OfficialRating) && string.Equals(item1.OfficialRating, item2.OfficialRating, StringComparison.OrdinalIgnoreCase)) { - points += 1; + points += 10; } // Find common genres diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 629f9b2333..9e58c9f53d 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Api /// Class GetNextUpEpisodes /// </summary> [Route("/Shows/NextUp", "GET")] - [Api(("Gets a list of currently installed plugins"))] + [Api(("Gets a list of next up episodes"))] public class GetNextUpEpisodes : IReturn<ItemsResult>, IHasItemFields { /// <summary> @@ -53,6 +53,39 @@ namespace MediaBrowser.Api public string SeriesId { get; set; } } + [Route("/Shows/Upcoming", "GET")] + [Api(("Gets a list of upcoming episodes"))] + public class GetUpcomingEpisodes : IReturn<ItemsResult>, IHasItemFields + { + /// <summary> + /// Gets or sets the user id. + /// </summary> + /// <value>The user id.</value> + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid UserId { get; set; } + + /// <summary> + /// Skips over a given number of items within the results. Use for paging. + /// </summary> + /// <value>The start index.</value> + [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? StartIndex { get; set; } + + /// <summary> + /// The maximum number of items to return + /// </summary> + /// <value>The limit.</value> + [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? Limit { get; set; } + + /// <summary> + /// Fields to return within the items, in addition to basic information + /// </summary> + /// <value>The fields.</value> + [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, OverviewHtml, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Fields { get; set; } + } + [Route("/Shows/{Id}/Similar", "GET")] [Api(Description = "Finds tv shows similar to a given one.")] public class GetSimilarShows : BaseGetSimilarItemsFromItem @@ -85,7 +118,7 @@ namespace MediaBrowser.Api [ApiMember(Name = "SeasonId", Description = "Optional. Filter by season id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string SeasonId { get; set; } - + [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? IsMissing { get; set; } @@ -186,6 +219,39 @@ namespace MediaBrowser.Api return ToOptimizedSerializedResultUsingCache(result); } + public object Get(GetUpcomingEpisodes request) + { + var user = _userManager.GetUserById(request.UserId); + + var items = GetAllLibraryItems(request.UserId, _userManager, _libraryManager) + .OfType<Episode>(); + + var itemsList = _libraryManager.Sort(items, user, new[] { "PremiereDate", "AirTime", "SortName" }, SortOrder.Ascending) + .Cast<Episode>() + .ToList(); + + var unairedEpisodes = itemsList.Where(i => i.IsUnaired).ToList(); + + var minPremiereDate = DateTime.Now.Date.AddDays(-1).ToUniversalTime(); + var previousEpisodes = itemsList.Where(i => !i.IsUnaired && (i.PremiereDate ?? DateTime.MinValue) >= minPremiereDate).ToList(); + + previousEpisodes.AddRange(unairedEpisodes); + + var pagedItems = ApplyPaging(previousEpisodes, request.StartIndex, request.Limit); + + var fields = request.GetItemFields().ToList(); + + var returnItems = pagedItems.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray(); + + var result = new ItemsResult + { + TotalRecordCount = itemsList.Count, + Items = returnItems + }; + + return ToOptimizedSerializedResultUsingCache(result); + } + /// <summary> /// Gets the specified request. /// </summary> @@ -198,7 +264,7 @@ namespace MediaBrowser.Api var itemsList = GetNextUpEpisodes(request) .ToList(); - var pagedItems = ApplyPaging(request, itemsList); + var pagedItems = ApplyPaging(itemsList, request.StartIndex, request.Limit); var fields = request.GetItemFields().ToList(); @@ -234,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) { @@ -252,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); } /// <summary> @@ -260,9 +330,8 @@ namespace MediaBrowser.Api /// </summary> /// <param name="series">The series.</param> /// <param name="user">The user.</param> - /// <param name="request">The request.</param> /// <returns>Task{Episode}.</returns> - private Tuple<Episode, DateTime> GetNextUp(Series series, User user, GetNextUpEpisodes request) + private Tuple<Episode, DateTime> GetNextUp(Series series, User user) { // Get them in display order, then reverse var allEpisodes = series.GetSeasons(user, true, true) @@ -321,21 +390,22 @@ namespace MediaBrowser.Api /// <summary> /// Applies the paging. /// </summary> - /// <param name="request">The request.</param> /// <param name="items">The items.</param> + /// <param name="startIndex">The start index.</param> + /// <param name="limit">The limit.</param> /// <returns>IEnumerable{BaseItem}.</returns> - private IEnumerable<BaseItem> ApplyPaging(GetNextUpEpisodes request, IEnumerable<BaseItem> items) + private IEnumerable<BaseItem> ApplyPaging(IEnumerable<BaseItem> items, int? startIndex, int? limit) { // Start at - if (request.StartIndex.HasValue) + if (startIndex.HasValue) { - items = items.Skip(request.StartIndex.Value); + items = items.Skip(startIndex.Value); } // Return limit - if (request.Limit.HasValue) + if (limit.HasValue) { - items = items.Take(request.Limit.Value); + items = items.Take(limit.Value); } return items; @@ -409,7 +479,7 @@ namespace MediaBrowser.Api return items; } - + public object Get(GetEpisodes request) { var user = _userManager.GetUserById(request.UserId); @@ -435,7 +505,7 @@ namespace MediaBrowser.Api { throw new ResourceNotFoundException("No season exists with Id " + request.SeasonId); } - + episodes = season.GetEpisodes(user); } diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs index a96630efe4..9972ac3eff 100644 --- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs +++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs @@ -85,10 +85,10 @@ namespace MediaBrowser.Api.UserLibrary { var user = UserManager.GetUserById(request.UserId.Value); - return DtoService.GetBaseItemDto(item, fields.ToList(), user); + return DtoService.GetItemByNameDto(item, fields.ToList(), user); } - return DtoService.GetBaseItemDto(item, fields.ToList()); + return DtoService.GetItemByNameDto(item, fields.ToList()); } /// <summary> @@ -111,7 +111,10 @@ namespace MediaBrowser.Api.UserLibrary /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns> protected override IEnumerable<MusicArtist> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items) { - return LibraryManager.GetAllArtists(items) + return items + .OfType<Audio>() + .SelectMany(i => i.AllArtists) + .Distinct(StringComparer.OrdinalIgnoreCase) .Select(name => { try @@ -126,10 +129,5 @@ namespace MediaBrowser.Api.UserLibrary }).Where(i => i != null); } - - protected override IEnumerable<BaseItem> GetLibraryItems(MusicArtist item, IEnumerable<BaseItem> libraryItems) - { - return libraryItems.OfType<IHasArtist>().Where(i => i.HasArtist(item.Name)).Cast<BaseItem>(); - } } } diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 5c2c9967e3..d21014dfed 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -56,15 +56,21 @@ namespace MediaBrowser.Api.UserLibrary { User user = null; BaseItem item; + List<BaseItem> libraryItems; if (request.UserId.HasValue) { user = UserManager.GetUserById(request.UserId.Value); item = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : DtoService.GetItemByDtoId(request.ParentId, user.Id); + + libraryItems = user.RootFolder.GetRecursiveChildren(user).ToList(); + } else { item = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : DtoService.GetItemByDtoId(request.ParentId); + + libraryItems = LibraryManager.RootFolder.RecursiveChildren.ToList(); } IEnumerable<BaseItem> items; @@ -93,7 +99,7 @@ namespace MediaBrowser.Api.UserLibrary var filteredItems = FilterItems(request, extractedItems, user); - filteredItems = FilterByLibraryItems(request, filteredItems, user); + filteredItems = FilterByLibraryItems(request, filteredItems, user, libraryItems); filteredItems = ItemsService.ApplySortOrder(request, filteredItems, user, LibraryManager).Cast<TItemType>(); @@ -122,45 +128,39 @@ namespace MediaBrowser.Api.UserLibrary var fields = request.GetItemFields().ToList(); - var dtos = ibnItems.Select(i => GetDto(i, user, fields)); + var tuples = ibnItems.Select(i => new Tuple<TItemType, List<BaseItem>>(i, i.GetTaggedItems(libraryItems).ToList())); + + var dtos = tuples.Select(i => GetDto(i.Item1, user, fields, i.Item2)); result.Items = dtos.Where(i => i != null).ToArray(); return result; } - private IEnumerable<TItemType> FilterByLibraryItems(GetItemsByName request, IEnumerable<TItemType> items, User user) + private IEnumerable<TItemType> FilterByLibraryItems(GetItemsByName request, IEnumerable<TItemType> items, User user, IEnumerable<BaseItem> libraryItems) { var filters = request.GetFilters().ToList(); if (filters.Contains(ItemFilter.IsPlayed)) { - var libraryItems = user.RootFolder.GetRecursiveChildren(user).ToList(); - - items = items.Where(i => GetLibraryItems(i, libraryItems).All(l => l.IsPlayed(user))); + items = items.Where(i => i.GetTaggedItems(libraryItems).All(l => l.IsPlayed(user))); } if (filters.Contains(ItemFilter.IsUnplayed)) { - var libraryItems = user.RootFolder.GetRecursiveChildren(user).ToList(); - - items = items.Where(i => GetLibraryItems(i, libraryItems).All(l => l.IsUnplayed(user))); + items = items.Where(i => i.GetTaggedItems(libraryItems).All(l => l.IsUnplayed(user))); } if (request.IsPlayed.HasValue) { var val = request.IsPlayed.Value; - var libraryItems = user.RootFolder.GetRecursiveChildren(user).ToList(); - - items = items.Where(i => GetLibraryItems(i, libraryItems).All(l => l.IsPlayed(user)) == val); + items = items.Where(i => i.GetTaggedItems(libraryItems).All(l => l.IsPlayed(user)) == val); } return items; } - protected abstract IEnumerable<BaseItem> GetLibraryItems(TItemType item, IEnumerable<BaseItem> libraryItems); - /// <summary> /// Filters the items. /// </summary> @@ -174,6 +174,10 @@ namespace MediaBrowser.Api.UserLibrary { items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.SortName, StringComparison.CurrentCultureIgnoreCase) < 1); } + if (!string.IsNullOrEmpty(request.NameStartsWith)) + { + items = items.Where(i => string.Compare(request.NameStartsWith, i.SortName.Substring(0, 1), StringComparison.CurrentCultureIgnoreCase) == 0); + } if (!string.IsNullOrEmpty(request.NameLessThan)) { @@ -288,11 +292,11 @@ namespace MediaBrowser.Api.UserLibrary /// <param name="item">The item.</param> /// <param name="user">The user.</param> /// <param name="fields">The fields.</param> + /// <param name="libraryItems">The library items.</param> /// <returns>Task{DtoBaseItem}.</returns> - private BaseItemDto GetDto(TItemType item, User user, List<ItemFields> fields) + private BaseItemDto GetDto(TItemType item, User user, List<ItemFields> fields, List<BaseItem> libraryItems) { - var dto = user == null ? DtoService.GetBaseItemDto(item, fields) : - DtoService.GetBaseItemDto(item, fields, user); + var dto = DtoService.GetItemByNameDto(item, fields, libraryItems, user); return dto; } @@ -313,9 +317,12 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "NameStartsWithOrGreater", Description = "Optional filter by items whose name is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string NameStartsWithOrGreater { get; set; } + [ApiMember(Name = "NameStartsWith", Description = "Optional filter by items whose name is sorted equally than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string NameStartsWith { get; set; } + [ApiMember(Name = "NameLessThan", Description = "Optional filter by items whose name is sorted less than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string NameLessThan { get; set; } - + public GetItemsByName() { Recursive = true; diff --git a/MediaBrowser.Api/UserLibrary/GameGenresService.cs b/MediaBrowser.Api/UserLibrary/GameGenresService.cs index d282ee091d..34eadc3c35 100644 --- a/MediaBrowser.Api/UserLibrary/GameGenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GameGenresService.cs @@ -76,10 +76,10 @@ namespace MediaBrowser.Api.UserLibrary { var user = UserManager.GetUserById(request.UserId.Value); - return DtoService.GetBaseItemDto(item, fields.ToList(), user); + return DtoService.GetItemByNameDto(item, fields.ToList(), user); } - return DtoService.GetBaseItemDto(item, fields.ToList()); + return DtoService.GetItemByNameDto(item, fields.ToList()); } /// <summary> @@ -109,10 +109,5 @@ namespace MediaBrowser.Api.UserLibrary .Distinct(StringComparer.OrdinalIgnoreCase) .Select(name => LibraryManager.GetGameGenre(name)); } - - protected override IEnumerable<BaseItem> GetLibraryItems(GameGenre item, IEnumerable<BaseItem> libraryItems) - { - return libraryItems.Where(i => (i is Game) && i.Genres.Contains(item.Name, StringComparer.OrdinalIgnoreCase)); - } } } diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs index 092c638822..5d362c61a3 100644 --- a/MediaBrowser.Api/UserLibrary/GenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GenresService.cs @@ -81,10 +81,10 @@ namespace MediaBrowser.Api.UserLibrary { var user = UserManager.GetUserById(request.UserId.Value); - return DtoService.GetBaseItemDto(item, fields.ToList(), user); + return DtoService.GetItemByNameDto(item, fields.ToList(), user); } - return DtoService.GetBaseItemDto(item, fields.ToList()); + return DtoService.GetItemByNameDto(item, fields.ToList()); } /// <summary> @@ -112,10 +112,5 @@ namespace MediaBrowser.Api.UserLibrary .Distinct(StringComparer.OrdinalIgnoreCase) .Select(name => LibraryManager.GetGenre(name)); } - - protected override IEnumerable<BaseItem> GetLibraryItems(Genre item, IEnumerable<BaseItem> libraryItems) - { - return libraryItems.Where(i => !(i is Game) && !(i is IHasMusicGenres) && i.Genres.Contains(item.Name, StringComparer.OrdinalIgnoreCase)); - } } } diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 871c9aecb4..b040d3dd81 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -111,6 +111,12 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "NameStartsWithOrGreater", Description = "Optional filter by items whose name is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string NameStartsWithOrGreater { get; set; } + [ApiMember(Name = "NameStartsWith", Description = "Optional filter by items whose name is sorted equally than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string NameStartsWith { get; set; } + + [ApiMember(Name = "NameLessThan", Description = "Optional filter by items whose name is equally or lesser than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string NameLessThan { get; set; } + [ApiMember(Name = "AlbumArtistStartsWithOrGreater", Description = "Optional filter by items whose album artist is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string AlbumArtistStartsWithOrGreater { get; set; } @@ -768,6 +774,15 @@ namespace MediaBrowser.Api.UserLibrary { items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.SortName, StringComparison.CurrentCultureIgnoreCase) < 1); } + if (!string.IsNullOrEmpty(request.NameStartsWith)) + { + items = items.Where(i => string.Compare(request.NameStartsWith, i.SortName.Substring(0, 1), StringComparison.CurrentCultureIgnoreCase) == 0); + } + + if (!string.IsNullOrEmpty(request.NameLessThan)) + { + items = items.Where(i => string.Compare(request.NameLessThan, i.SortName, StringComparison.CurrentCultureIgnoreCase) == 1); + } if (!string.IsNullOrEmpty(request.AlbumArtistStartsWithOrGreater)) { diff --git a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs index 3f960ccbe9..9b7a941d84 100644 --- a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs +++ b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs @@ -76,10 +76,10 @@ namespace MediaBrowser.Api.UserLibrary { var user = UserManager.GetUserById(request.UserId.Value); - return DtoService.GetBaseItemDto(item, fields.ToList(), user); + return DtoService.GetItemByNameDto(item, fields.ToList(), user); } - return DtoService.GetBaseItemDto(item, fields.ToList()); + return DtoService.GetItemByNameDto(item, fields.ToList()); } /// <summary> @@ -109,10 +109,5 @@ namespace MediaBrowser.Api.UserLibrary .Distinct(StringComparer.OrdinalIgnoreCase) .Select(name => LibraryManager.GetMusicGenre(name)); } - - protected override IEnumerable<BaseItem> GetLibraryItems(MusicGenre item, IEnumerable<BaseItem> libraryItems) - { - return libraryItems.Where(i => (i is IHasMusicGenres) && i.Genres.Contains(item.Name, StringComparer.OrdinalIgnoreCase)); - } } } diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs index 32700d21ab..f7ea4198d6 100644 --- a/MediaBrowser.Api/UserLibrary/PersonsService.cs +++ b/MediaBrowser.Api/UserLibrary/PersonsService.cs @@ -93,10 +93,10 @@ namespace MediaBrowser.Api.UserLibrary { var user = UserManager.GetUserById(request.UserId.Value); - return DtoService.GetBaseItemDto(item, fields.ToList(), user); + return DtoService.GetItemByNameDto(item, fields.ToList(), user); } - return DtoService.GetBaseItemDto(item, fields.ToList()); + return DtoService.GetItemByNameDto(item, fields.ToList()); } /// <summary> @@ -163,10 +163,5 @@ namespace MediaBrowser.Api.UserLibrary people.Where(p => personTypes.Contains(p.Type ?? string.Empty, StringComparer.OrdinalIgnoreCase) || personTypes.Contains(p.Role ?? string.Empty, StringComparer.OrdinalIgnoreCase)); } - - protected override IEnumerable<BaseItem> GetLibraryItems(Person item, IEnumerable<BaseItem> libraryItems) - { - return libraryItems.Where(i => i.People.Any(p => string.Equals(p.Name, item.Name, StringComparison.OrdinalIgnoreCase))); - } } } diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs index cf4e333e1b..fc7fdd1600 100644 --- a/MediaBrowser.Api/UserLibrary/StudiosService.cs +++ b/MediaBrowser.Api/UserLibrary/StudiosService.cs @@ -81,10 +81,10 @@ namespace MediaBrowser.Api.UserLibrary { var user = UserManager.GetUserById(request.UserId.Value); - return DtoService.GetBaseItemDto(item, fields.ToList(), user); + return DtoService.GetItemByNameDto(item, fields.ToList(), user); } - return DtoService.GetBaseItemDto(item, fields.ToList()); + return DtoService.GetItemByNameDto(item, fields.ToList()); } /// <summary> @@ -114,10 +114,5 @@ namespace MediaBrowser.Api.UserLibrary .Distinct(StringComparer.OrdinalIgnoreCase) .Select(name => LibraryManager.GetStudio(name)); } - - protected override IEnumerable<BaseItem> GetLibraryItems(Studio item, IEnumerable<BaseItem> libraryItems) - { - return libraryItems.Where(i => i.Studios.Contains(item.Name, StringComparer.OrdinalIgnoreCase)); - } } } diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index c2abc6ad11..e026aec038 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -680,18 +680,34 @@ namespace MediaBrowser.Api.UserLibrary /// <param name="request">The request.</param> public object Post(MarkPlayedItem request) { + var result = MarkPlayed(request).Result; + + return ToOptimizedResult(result); + } + + private async Task<UserItemDataDto> MarkPlayed(MarkPlayedItem request) + { var user = _userManager.GetUserById(request.UserId); DateTime? datePlayed = null; - + if (!string.IsNullOrEmpty(request.DatePlayed)) { datePlayed = DateTime.ParseExact(request.DatePlayed, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); } - var task = UpdatePlayedStatus(user, request.Id, true, datePlayed); + var session = GetSession(); + + var dto = await UpdatePlayedStatus(user, request.Id, true, datePlayed).ConfigureAwait(false); - return ToOptimizedResult(task.Result); + foreach (var additionalUserInfo in session.AdditionalUsers) + { + var additionalUser = _userManager.GetUserById(new Guid(additionalUserInfo.UserId)); + + await UpdatePlayedStatus(additionalUser, request.Id, true, datePlayed).ConfigureAwait(false); + } + + return dto; } private SessionInfo GetSession() @@ -780,11 +796,27 @@ namespace MediaBrowser.Api.UserLibrary /// <param name="request">The request.</param> public object Delete(MarkUnplayedItem request) { + var task = MarkUnplayed(request); + + return ToOptimizedResult(task.Result); + } + + private async Task<UserItemDataDto> MarkUnplayed(MarkUnplayedItem request) + { var user = _userManager.GetUserById(request.UserId); - var task = UpdatePlayedStatus(user, request.Id, false, null); + var session = GetSession(); + + var dto = await UpdatePlayedStatus(user, request.Id, false, null).ConfigureAwait(false); - return ToOptimizedResult(task.Result); + foreach (var additionalUserInfo in session.AdditionalUsers) + { + var additionalUser = _userManager.GetUserById(new Guid(additionalUserInfo.UserId)); + + await UpdatePlayedStatus(additionalUser, request.Id, false, null).ConfigureAwait(false); + } + + return dto; } /// <summary> diff --git a/MediaBrowser.Api/UserLibrary/YearsService.cs b/MediaBrowser.Api/UserLibrary/YearsService.cs index 7024d52563..b8b0aa9e94 100644 --- a/MediaBrowser.Api/UserLibrary/YearsService.cs +++ b/MediaBrowser.Api/UserLibrary/YearsService.cs @@ -7,7 +7,6 @@ using MediaBrowser.Model.Querying; using ServiceStack; using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; namespace MediaBrowser.Api.UserLibrary @@ -81,10 +80,10 @@ namespace MediaBrowser.Api.UserLibrary { var user = UserManager.GetUserById(request.UserId.Value); - return DtoService.GetBaseItemDto(item, fields.ToList(), user); + return DtoService.GetItemByNameDto(item, fields.ToList(), user); } - return DtoService.GetBaseItemDto(item, fields.ToList()); + return DtoService.GetItemByNameDto(item, fields.ToList()); } /// <summary> @@ -115,19 +114,5 @@ namespace MediaBrowser.Api.UserLibrary .Distinct() .Select(year => LibraryManager.GetYear(year)); } - - protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - protected override IEnumerable<BaseItem> GetLibraryItems(Year item, IEnumerable<BaseItem> libraryItems) - { - int year; - - if (!int.TryParse(item.Name, NumberStyles.Integer, UsCulture, out year)) - { - return libraryItems; - } - - return libraryItems.Where(i => i.ProductionYear.HasValue && i.ProductionYear.Value == year); - } } } |
