Browse Source

Merge branch 'beta'

Luke Pulverenti 9 năm trước cách đây
mục cha
commit
daa0b6cd0e
100 tập tin đã thay đổi với 1775 bổ sung1036 xóa
  1. 0 44
      MediaBrowser.Api/BaseApiService.cs
  2. 16 5
      MediaBrowser.Api/GamesService.cs
  3. 47 1
      MediaBrowser.Api/Library/FileOrganizationService.cs
  4. 56 14
      MediaBrowser.Api/Library/LibraryService.cs
  5. 1 0
      MediaBrowser.Api/MediaBrowser.Api.csproj
  6. 25 20
      MediaBrowser.Api/Movies/MoviesService.cs
  7. 1 1
      MediaBrowser.Api/PackageReviewService.cs
  8. 202 0
      MediaBrowser.Api/PinLoginService.cs
  9. 6 12
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  10. 1 1
      MediaBrowser.Api/Playback/Dash/MpegDashService.cs
  11. 1 1
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  12. 1 1
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  13. 1 1
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  14. 1 1
      MediaBrowser.Api/SearchService.cs
  15. 0 4
      MediaBrowser.Api/StartupWizardService.cs
  16. 16 17
      MediaBrowser.Api/TvShowsService.cs
  17. 7 0
      MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
  18. 10 9
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  19. 19 4
      MediaBrowser.Api/UserLibrary/UserViewsService.cs
  20. 3 3
      MediaBrowser.Common.Implementations/BaseApplicationHost.cs
  21. 52 1
      MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs
  22. 5 0
      MediaBrowser.Common/ScheduledTasks/ITaskManager.cs
  23. 2 2
      MediaBrowser.Controller/Channels/ChannelAudioItem.cs
  24. 6 0
      MediaBrowser.Controller/Channels/ChannelFolderItem.cs
  25. 2 2
      MediaBrowser.Controller/Channels/ChannelVideoItem.cs
  26. 4 3
      MediaBrowser.Controller/Dto/IDtoService.cs
  27. 16 18
      MediaBrowser.Controller/Entities/Audio/Audio.cs
  28. 7 2
      MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
  29. 5 0
      MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
  30. 187 77
      MediaBrowser.Controller/Entities/BaseItem.cs
  31. 3 14
      MediaBrowser.Controller/Entities/Book.cs
  32. 8 7
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  33. 55 77
      MediaBrowser.Controller/Entities/Folder.cs
  34. 2 9
      MediaBrowser.Controller/Entities/Game.cs
  35. 5 0
      MediaBrowser.Controller/Entities/GameSystem.cs
  36. 5 0
      MediaBrowser.Controller/Entities/ICollectionFolder.cs
  37. 18 0
      MediaBrowser.Controller/Entities/IHiddenFromDisplay.cs
  38. 36 0
      MediaBrowser.Controller/Entities/InternalItemsQuery.cs
  39. 6 31
      MediaBrowser.Controller/Entities/Movies/BoxSet.cs
  40. 3 3
      MediaBrowser.Controller/Entities/Movies/Movie.cs
  41. 2 2
      MediaBrowser.Controller/Entities/MusicVideo.cs
  42. 9 0
      MediaBrowser.Controller/Entities/Person.cs
  43. 6 8
      MediaBrowser.Controller/Entities/Photo.cs
  44. 2 27
      MediaBrowser.Controller/Entities/PhotoAlbum.cs
  45. 0 7
      MediaBrowser.Controller/Entities/Studio.cs
  46. 17 16
      MediaBrowser.Controller/Entities/TV/Episode.cs
  47. 7 14
      MediaBrowser.Controller/Entities/TV/Season.cs
  48. 5 0
      MediaBrowser.Controller/Entities/TV/Series.cs
  49. 3 3
      MediaBrowser.Controller/Entities/Trailer.cs
  50. 20 1
      MediaBrowser.Controller/Entities/User.cs
  51. 9 1
      MediaBrowser.Controller/Entities/UserRootFolder.cs
  52. 73 20
      MediaBrowser.Controller/Entities/UserView.cs
  53. 89 179
      MediaBrowser.Controller/Entities/UserViewBuilder.cs
  54. 1 7
      MediaBrowser.Controller/Entities/Video.cs
  55. 14 0
      MediaBrowser.Controller/FileOrganization/IFileOrganizationService.cs
  56. 24 4
      MediaBrowser.Controller/Library/ILibraryManager.cs
  57. 1 1
      MediaBrowser.Controller/Library/ItemResolveArgs.cs
  58. 0 8
      MediaBrowser.Controller/Library/LibraryManagerExtensions.cs
  59. 9 2
      MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
  60. 14 3
      MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
  61. 2 2
      MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
  62. 23 25
      MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
  63. 14 3
      MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
  64. 6 0
      MediaBrowser.Controller/LiveTv/RecordingGroup.cs
  65. 1 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  66. 14 0
      MediaBrowser.Controller/Persistence/IItemRepository.cs
  67. 1 0
      MediaBrowser.Controller/Playlists/Playlist.cs
  68. 4 0
      MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
  69. 0 32
      MediaBrowser.Controller/Providers/MetadataStatus.cs
  70. 3 2
      MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs
  71. 5 2
      MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs
  72. 37 24
      MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs
  73. 1 1
      MediaBrowser.Dlna/Didl/DidlBuilder.cs
  74. 8 2
      MediaBrowser.Dlna/DlnaManager.cs
  75. 1 1
      MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs
  76. 1 1
      MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs
  77. 1 1
      MediaBrowser.Dlna/Service/BaseControlHandler.cs
  78. 1 1
      MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
  79. 145 29
      MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
  80. 1 1
      MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs
  81. 3 0
      MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs
  82. 13 11
      MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
  83. 138 77
      MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
  84. 107 83
      MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
  85. 2 0
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  86. 26 18
      MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
  87. 20 3
      MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
  88. 3 0
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  89. 3 0
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  90. 8 1
      MediaBrowser.Model/ApiClient/IApiClient.cs
  91. 1 0
      MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs
  92. 1 1
      MediaBrowser.Model/Configuration/DlnaOptions.cs
  93. 6 25
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  94. 1 0
      MediaBrowser.Model/Dto/BaseItemDto.cs
  95. 1 1
      MediaBrowser.Model/Dto/MediaSourceInfo.cs
  96. 2 1
      MediaBrowser.Model/Entities/Video3DFormat.cs
  97. 7 0
      MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs
  98. 16 0
      MediaBrowser.Model/FileOrganization/SmartMatchInfo.cs
  99. 1 0
      MediaBrowser.Model/LiveTv/LiveTvOptions.cs
  100. 1 0
      MediaBrowser.Model/MediaBrowser.Model.csproj

+ 0 - 44
MediaBrowser.Api/BaseApiService.cs

@@ -183,50 +183,6 @@ namespace MediaBrowser.Api
             return libraryManager.GetPerson(DeSlugPersonName(name, libraryManager));
         }
 
-        protected IList<BaseItem> GetAllLibraryItems(string userId, IUserManager userManager, ILibraryManager libraryManager, string parentId, Func<BaseItem,bool> filter)
-        {
-            if (!string.IsNullOrEmpty(parentId))
-            {
-                var folder = (Folder)libraryManager.GetItemById(new Guid(parentId));
-
-                if (!string.IsNullOrWhiteSpace(userId))
-                {
-                    var user = userManager.GetUserById(userId);
-
-                    if (user == null)
-                    {
-                        throw new ArgumentException("User not found");
-                    }
-
-                    return folder
-                        .GetRecursiveChildren(user, filter)
-                        .ToList();
-                }
-
-                return folder
-                    .GetRecursiveChildren(filter);
-            }
-            if (!string.IsNullOrWhiteSpace(userId))
-            {
-                var user = userManager.GetUserById(userId);
-
-                if (user == null)
-                {
-                    throw new ArgumentException("User not found");
-                }
-
-                return userManager
-                    .GetUserById(userId)
-                    .RootFolder
-                    .GetRecursiveChildren(user, filter)
-                    .ToList();
-            }
-
-            return libraryManager
-                .RootFolder
-                .GetRecursiveChildren(filter);
-        }
-
         /// <summary>
         /// Deslugs an artist name by finding the correct entry in the library
         /// </summary>

+ 16 - 5
MediaBrowser.Api/GamesService.cs

@@ -102,12 +102,16 @@ namespace MediaBrowser.Api
         /// <returns>System.Object.</returns>
         public object Get(GetGameSystemSummaries request)
         {
-            var gameSystems = GetAllLibraryItems(request.UserId, _userManager, _libraryManager, null, i => i is GameSystem)
+            var user = request.UserId == null ? null : _userManager.GetUserById(request.UserId);
+            var query = new InternalItemsQuery(user)
+            {
+                IncludeItemTypes = new[] { typeof(GameSystem).Name }
+            };
+            var parentIds = new string[] { } ;
+            var gameSystems = _libraryManager.GetItems(query, parentIds)
                 .Cast<GameSystem>()
                 .ToList();
 
-            var user = request.UserId == null ? null : _userManager.GetUserById(request.UserId);
-
             var result = gameSystems
                 .Select(i => GetSummary(i, user))
                 .ToList();
@@ -119,8 +123,15 @@ namespace MediaBrowser.Api
 
         public object Get(GetPlayerIndex request)
         {
-            var games = GetAllLibraryItems(request.UserId, _userManager, _libraryManager, null, i => i is Game)
-                .Cast<Game>();
+            var user = request.UserId == null ? null : _userManager.GetUserById(request.UserId);
+            var query = new InternalItemsQuery(user)
+            {
+                IncludeItemTypes = new[] { typeof(Game).Name }
+            };
+            var parentIds = new string[] { };
+            var games = _libraryManager.GetItems(query, parentIds)
+                .Cast<Game>()
+                .ToList();
 
             var lookup = games
                 .ToLookup(i => i.PlayersSupported ?? -1)

+ 47 - 1
MediaBrowser.Api/Library/FileOrganizationService.cs

@@ -1,9 +1,11 @@
-using MediaBrowser.Controller.FileOrganization;
+using System.Collections.Generic;
+using MediaBrowser.Controller.FileOrganization;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.FileOrganization;
 using MediaBrowser.Model.Querying;
 using ServiceStack;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Dto;
 
 namespace MediaBrowser.Api.Library
 {
@@ -74,6 +76,31 @@ namespace MediaBrowser.Api.Library
         public bool RememberCorrection { get; set; }
     }
 
+    [Route("/Library/FileOrganizations/SmartMatches", "GET", Summary = "Gets smart match entries")]
+    public class GetSmartMatchInfos : IReturn<QueryResult<SmartMatchInfo>>
+    {
+        /// <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; }
+    }
+
+    [Route("/Library/FileOrganizations/SmartMatches/Delete", "POST", Summary = "Deletes a smart match entry")]
+    public class DeleteSmartMatchEntry
+    {
+        [ApiMember(Name = "Entries", Description = "SmartMatch Entry", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public List<NameValuePair> Entries { get; set; }
+    }
+
     [Authenticated(Roles = "Admin")]
     public class FileOrganizationService : BaseApiService
     {
@@ -130,5 +157,24 @@ namespace MediaBrowser.Api.Library
 
             Task.WaitAll(task);
         }
+
+        public object Get(GetSmartMatchInfos request)
+        {
+            var result = _iFileOrganizationService.GetSmartMatchInfos(new FileOrganizationResultQuery
+            {
+                Limit = request.Limit,
+                StartIndex = request.StartIndex
+            });
+
+            return ToOptimizedSerializedResultUsingCache(result);
+        }
+
+        public void Post(DeleteSmartMatchEntry request)
+        {
+            request.Entries.ForEach(entry =>
+            {
+                _iFileOrganizationService.DeleteSmartMatchEntry(entry.Name, entry.Value);
+            });
+        }
     }
 }

+ 56 - 14
MediaBrowser.Api/Library/LibraryService.cs

@@ -424,7 +424,7 @@ namespace MediaBrowser.Api.Library
 
         public object Get(GetMediaFolders request)
         {
-            var items = _libraryManager.GetUserRootFolder().Children.OrderBy(i => i.SortName).ToList();
+            var items = _libraryManager.GetUserRootFolder().Children.Concat(_libraryManager.RootFolder.VirtualChildren).OrderBy(i => i.SortName).ToList();
 
             if (request.IsHidden.HasValue)
             {
@@ -569,7 +569,7 @@ namespace MediaBrowser.Api.Library
             {
                 throw new ArgumentException("This command cannot be used for remote or virtual items.");
             }
-			if (_fileSystem.DirectoryExists(item.Path))
+            if (_fileSystem.DirectoryExists(item.Path))
             {
                 throw new ArgumentException("This command cannot be used for directories.");
             }
@@ -618,7 +618,7 @@ namespace MediaBrowser.Api.Library
 
             var dtoOptions = GetDtoOptions(request);
 
-            BaseItem parent = item.Parent;
+            BaseItem parent = item.GetParent();
 
             while (parent != null)
             {
@@ -629,7 +629,7 @@ namespace MediaBrowser.Api.Library
 
                 baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user));
 
-                parent = parent.Parent;
+                parent = parent.GetParent();
             }
 
             return baseItemDtos.ToList();
@@ -637,7 +637,7 @@ namespace MediaBrowser.Api.Library
 
         private BaseItem TranslateParentItem(BaseItem item, User user)
         {
-            if (item.Parent is AggregateFolder)
+            if (item.GetParent() is AggregateFolder)
             {
                 return user.RootFolder.GetChildren(user, true).FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path));
             }
@@ -685,6 +685,50 @@ namespace MediaBrowser.Api.Library
             return ToOptimizedSerializedResultUsingCache(counts);
         }
 
+        private IList<BaseItem> GetAllLibraryItems(string userId, IUserManager userManager, ILibraryManager libraryManager, string parentId, Func<BaseItem, bool> filter)
+        {
+            if (!string.IsNullOrEmpty(parentId))
+            {
+                var folder = (Folder)libraryManager.GetItemById(new Guid(parentId));
+
+                if (!string.IsNullOrWhiteSpace(userId))
+                {
+                    var user = userManager.GetUserById(userId);
+
+                    if (user == null)
+                    {
+                        throw new ArgumentException("User not found");
+                    }
+
+                    return folder
+                        .GetRecursiveChildren(user, filter)
+                        .ToList();
+                }
+
+                return folder
+                    .GetRecursiveChildren(filter);
+            }
+            if (!string.IsNullOrWhiteSpace(userId))
+            {
+                var user = userManager.GetUserById(userId);
+
+                if (user == null)
+                {
+                    throw new ArgumentException("User not found");
+                }
+
+                return userManager
+                    .GetUserById(userId)
+                    .RootFolder
+                    .GetRecursiveChildren(user, filter)
+                    .ToList();
+            }
+
+            return libraryManager
+                .RootFolder
+                .GetRecursiveChildren(filter);
+        }
+
         private bool FilterItem(BaseItem item, GetItemCounts request, string userId)
         {
             if (!string.IsNullOrWhiteSpace(userId))
@@ -745,12 +789,10 @@ namespace MediaBrowser.Api.Library
                     return Task.FromResult(true);
                 }
 
-                if (item is ILiveTvRecording)
+                return item.Delete(new DeleteOptions
                 {
-                    return _liveTv.DeleteRecording(i);
-                }
-
-                return _libraryManager.DeleteItem(item);
+                    DeleteFileLocation = true
+                });
             }).ToArray();
 
             Task.WaitAll(tasks);
@@ -847,9 +889,9 @@ namespace MediaBrowser.Api.Library
                                   : (Folder)_libraryManager.RootFolder)
                            : _libraryManager.GetItemById(request.Id);
 
-            while (GetThemeSongIds(item).Count == 0 && request.InheritFromParent && item.Parent != null)
+            while (GetThemeSongIds(item).Count == 0 && request.InheritFromParent && item.GetParent() != null)
             {
-                item = item.Parent;
+                item = item.GetParent();
             }
 
             var dtoOptions = GetDtoOptions(request);
@@ -890,9 +932,9 @@ namespace MediaBrowser.Api.Library
                                   : (Folder)_libraryManager.RootFolder)
                            : _libraryManager.GetItemById(request.Id);
 
-            while (GetThemeVideoIds(item).Count == 0 && request.InheritFromParent && item.Parent != null)
+            while (GetThemeVideoIds(item).Count == 0 && request.InheritFromParent && item.GetParent() != null)
             {
-                item = item.Parent;
+                item = item.GetParent();
             }
 
             var dtoOptions = GetDtoOptions(request);

+ 1 - 0
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -80,6 +80,7 @@
     <Compile Include="FilterService.cs" />
     <Compile Include="IHasDtoOptions.cs" />
     <Compile Include="Library\ChapterService.cs" />
+    <Compile Include="PinLoginService.cs" />
     <Compile Include="Playback\Dash\ManifestBuilder.cs" />
     <Compile Include="Playback\Dash\MpegDashService.cs" />
     <Compile Include="Playback\MediaInfoService.cs" />

+ 25 - 20
MediaBrowser.Api/Movies/MoviesService.cs

@@ -117,10 +117,7 @@ namespace MediaBrowser.Api.Movies
         public async Task<object> Get(GetSimilarMovies request)
         {
             var result = await GetSimilarItemsResult(
-                // Strip out secondary versions
-                request, item => (item is Movie) && !((Video)item).PrimaryVersionId.HasValue,
-
-                SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
+                request, SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
 
             return ToOptimizedSerializedResultUsingCache(result);
         }
@@ -128,10 +125,7 @@ namespace MediaBrowser.Api.Movies
         public async Task<object> Get(GetSimilarTrailers request)
         {
             var result = await GetSimilarItemsResult(
-                // Strip out secondary versions
-                request, item => (item is Movie) && !((Video)item).PrimaryVersionId.HasValue,
-
-                SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
+                request, SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
 
             return ToOptimizedSerializedResultUsingCache(result);
         }
@@ -140,8 +134,12 @@ namespace MediaBrowser.Api.Movies
         {
             var user = _userManager.GetUserById(request.UserId);
 
-            IEnumerable<BaseItem> movies = GetAllLibraryItems(request.UserId, _userManager, _libraryManager, request.ParentId, i => i is Movie);
-
+            var query = new InternalItemsQuery(user)
+            {
+                IncludeItemTypes = new[] { typeof(Movie).Name }
+            };
+            var parentIds = string.IsNullOrWhiteSpace(request.ParentId) ? new string[] { } : new[] { request.ParentId };
+            var movies = _libraryManager.GetItems(query, parentIds);
             movies = _libraryManager.ReplaceVideosWithPrimaryVersions(movies);
 
             var listEligibleForCategories = new List<BaseItem>();
@@ -184,21 +182,27 @@ namespace MediaBrowser.Api.Movies
             return ToOptimizedResult(result);
         }
 
-        private async Task<ItemsResult> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, Func<BaseItem, bool> includeInSearch, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
+        private async Task<ItemsResult> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
         {
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
 
             var item = string.IsNullOrEmpty(request.Id) ?
                 (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
                 _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
-
-            Func<BaseItem, bool> filter = i => i.Id != item.Id && includeInSearch(i);
-
-            var inputItems = user == null
-                                 ? _libraryManager.RootFolder.GetRecursiveChildren(filter)
-                                 : user.RootFolder.GetRecursiveChildren(user, filter);
-
-            var list = inputItems.ToList();
+            
+            var query = new InternalItemsQuery(user)
+            {
+                IncludeItemTypes = new[] { typeof(Movie).Name }
+            };
+            var parentIds = new string[] { };
+            var list = _libraryManager.GetItems(query, parentIds)
+                .Where(i =>
+                {
+                    // Strip out secondary versions
+                    var v = i as Video;
+                    return v != null && !v.PrimaryVersionId.HasValue;
+                })
+                .ToList();
 
             if (user != null && user.Configuration.IncludeTrailersInSuggestions)
             {
@@ -379,9 +383,10 @@ namespace MediaBrowser.Api.Movies
         {
             foreach (var name in names)
             {
-                var itemsWithActor = _libraryManager.GetItemIds(new InternalItemsQuery
+                var itemsWithActor = _libraryManager.GetItemIds(new InternalItemsQuery(user)
                 {
                     Person = name
+
                 });
 
                 var items = allMovies

+ 1 - 1
MediaBrowser.Api/PackageReviewService.cs

@@ -102,7 +102,7 @@ namespace MediaBrowser.Api
     {
         private readonly IHttpClient _httpClient;
         private readonly IJsonSerializer _serializer;
-        private const string MbAdminUrl = "http://www.mb3admin.com/admin/";
+        private const string MbAdminUrl = "https://www.mb3admin.com/admin/";
         private readonly IServerApplicationHost _appHost;
 
         public PackageReviewService(IHttpClient httpClient, IJsonSerializer serializer, IServerApplicationHost appHost)

+ 202 - 0
MediaBrowser.Api/PinLoginService.cs

@@ -0,0 +1,202 @@
+using System;
+using System.Collections.Concurrent;
+using System.Globalization;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Connect;
+using ServiceStack;
+
+namespace MediaBrowser.Api
+{
+    [Route("/Auth/Pin", "POST", Summary = "Creates a pin request")]
+    public class CreatePinRequest : IReturn<PinCreationResult>
+    {
+        [ApiMember(Name = "DeviceId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string DeviceId { get; set; }
+    }
+
+    [Route("/Auth/Pin", "GET", Summary = "Gets pin status")]
+    public class GetPinStatusRequest : IReturn<PinStatusResult>
+    {
+        [ApiMember(Name = "DeviceId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string DeviceId { get; set; }
+        [ApiMember(Name = "Pin", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string Pin { get; set; }
+    }
+
+    [Route("/Auth/Pin/Exchange", "POST", Summary = "Exchanges a pin")]
+    public class ExchangePinRequest : IReturn<PinExchangeResult>
+    {
+        [ApiMember(Name = "DeviceId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string DeviceId { get; set; }
+        [ApiMember(Name = "Pin", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string Pin { get; set; }
+    }
+
+    [Route("/Auth/Pin/Validate", "POST", Summary = "Validates a pin")]
+    [Authenticated]
+    public class ValidatePinRequest : IReturnVoid
+    {
+        [ApiMember(Name = "Pin", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string Pin { get; set; }
+    }
+
+    public class PinLoginService : BaseApiService
+    {
+        private readonly ConcurrentDictionary<string, MyPinStatus> _activeRequests = new ConcurrentDictionary<string, MyPinStatus>(StringComparer.OrdinalIgnoreCase);
+
+        public object Post(CreatePinRequest request)
+        {
+            var pin = GetNewPin();
+
+            var value = new MyPinStatus
+            {
+                CreationTimeUtc = DateTime.UtcNow,
+                IsConfirmed = false,
+                IsExpired = false,
+                Pin = pin,
+                DeviceId = request.DeviceId
+            };
+
+            _activeRequests.AddOrUpdate(pin, value, (k, v) => value);
+
+            return ToOptimizedResult(new PinCreationResult
+            {
+                DeviceId = request.DeviceId,
+                IsConfirmed = false,
+                IsExpired = false,
+                Pin = pin
+            });
+        }
+
+        public object Get(GetPinStatusRequest request)
+        {
+            MyPinStatus status;
+
+            if (!_activeRequests.TryGetValue(request.Pin, out status))
+            {
+                throw new ResourceNotFoundException();
+            }
+
+            EnsureValid(request.DeviceId, status);
+
+            return ToOptimizedResult(new PinStatusResult
+            {
+                Pin = status.Pin,
+                IsConfirmed = status.IsConfirmed,
+                IsExpired = status.IsExpired
+            });
+        }
+
+        public object Post(ExchangePinRequest request)
+        {
+            MyPinStatus status;
+
+            if (!_activeRequests.TryGetValue(request.Pin, out status))
+            {
+                throw new ResourceNotFoundException();
+            }
+
+            EnsureValid(request.DeviceId, status);
+
+            if (!status.IsConfirmed)
+            {
+                throw new ResourceNotFoundException();
+            }
+
+            return ToOptimizedResult(new PinExchangeResult
+            {
+                // TODO: Add access token
+                UserId = status.UserId
+            });
+        }
+
+        public void Post(ValidatePinRequest request)
+        {
+            MyPinStatus status;
+
+            if (!_activeRequests.TryGetValue(request.Pin, out status))
+            {
+                throw new ResourceNotFoundException();
+            }
+
+            EnsureValid(status);
+
+            status.IsConfirmed = true;
+            status.UserId = AuthorizationContext.GetAuthorizationInfo(Request).UserId;
+        }
+
+        private void EnsureValid(string requestedDeviceId, MyPinStatus status)
+        {
+            if (!string.Equals(requestedDeviceId, status.DeviceId, StringComparison.OrdinalIgnoreCase))
+            {
+                throw new ResourceNotFoundException();
+            }
+
+            EnsureValid(status);
+        }
+
+        private void EnsureValid(MyPinStatus status)
+        {
+            if ((DateTime.UtcNow - status.CreationTimeUtc).TotalMinutes > 10)
+            {
+                status.IsExpired = true;
+            }
+
+            if (status.IsExpired)
+            {
+                throw new ResourceNotFoundException();
+            }
+        }
+
+        private string GetNewPin()
+        {
+            var pin = GetNewPinInternal();
+
+            while (IsPinActive(pin))
+            {
+                pin = GetNewPinInternal();
+            }
+
+            return pin;
+        }
+
+        private string GetNewPinInternal()
+        {
+            var length = 5;
+            var pin = string.Empty;
+
+            while (pin.Length < length)
+            {
+                var digit = new Random().Next(0, 9);
+                pin += digit.ToString(CultureInfo.InvariantCulture);
+            }
+
+            return pin;
+        }
+
+        private bool IsPinActive(string pin)
+        {
+            MyPinStatus status;
+
+            if (!_activeRequests.TryGetValue(pin, out status))
+            {
+                return true;
+            }
+
+            if (status.IsExpired)
+            {
+                return true;
+            }
+
+            return false;
+        }
+
+        public class MyPinStatus : PinStatusResult
+        {
+            public DateTime CreationTimeUtc { get; set; }
+            public string DeviceId { get; set; }
+            public string UserId { get; set; }
+        }
+    }
+}

+ 6 - 12
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -305,9 +305,8 @@ namespace MediaBrowser.Api.Playback
         /// </summary>
         /// <param name="state">The state.</param>
         /// <param name="videoCodec">The video codec.</param>
-        /// <param name="isHls">if set to <c>true</c> [is HLS].</param>
         /// <returns>System.String.</returns>
-        protected string GetVideoQualityParam(StreamState state, string videoCodec, bool isHls)
+        protected string GetVideoQualityParam(StreamState state, string videoCodec)
         {
             var param = string.Empty;
 
@@ -385,7 +384,7 @@ namespace MediaBrowser.Api.Playback
                 param = "-mbd 2";
             }
 
-            param += GetVideoBitrateParam(state, videoCodec, isHls);
+            param += GetVideoBitrateParam(state, videoCodec);
 
             var framerate = GetFramerateParam(state);
             if (framerate.HasValue)
@@ -1190,7 +1189,7 @@ namespace MediaBrowser.Api.Playback
             return bitrate;
         }
 
-        protected string GetVideoBitrateParam(StreamState state, string videoCodec, bool isHls)
+        protected string GetVideoBitrateParam(StreamState state, string videoCodec)
         {
             var bitrate = state.OutputVideoBitrate;
 
@@ -1209,14 +1208,9 @@ namespace MediaBrowser.Api.Playback
                 }
 
                 // h264
-                if (isHls)
-                {
-                    return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
-                        bitrate.Value.ToString(UsCulture),
-                        (bitrate.Value * 2).ToString(UsCulture));
-                }
-
-                return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
+                return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
+                    bitrate.Value.ToString(UsCulture),
+                    (bitrate.Value * 2).ToString(UsCulture));
             }
 
             return string.Empty;

+ 1 - 1
MediaBrowser.Api/Playback/Dash/MpegDashService.cs

@@ -430,7 +430,7 @@ namespace MediaBrowser.Api.Playback.Dash
 
             var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
 
-            args += " " + GetVideoQualityParam(state, GetH264Encoder(state), true) + keyFrameArg;
+            args += " " + GetVideoQualityParam(state, GetH264Encoder(state)) + keyFrameArg;
 
             // Add resolution params, if specified
             if (!hasGraphicalSubs)

+ 1 - 1
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -822,7 +822,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
                 var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
 
-                args += " " + GetVideoQualityParam(state, GetH264Encoder(state), true) + keyFrameArg;
+                args += " " + GetVideoQualityParam(state, GetH264Encoder(state)) + keyFrameArg;
 
                 //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
 

+ 1 - 1
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -106,7 +106,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
 
-            args += " " + GetVideoQualityParam(state, GetH264Encoder(state), true) + keyFrameArg;
+            args += " " + GetVideoQualityParam(state, GetH264Encoder(state)) + keyFrameArg;
 
             // Add resolution params, if specified
             if (!hasGraphicalSubs)

+ 1 - 1
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -166,7 +166,7 @@ namespace MediaBrowser.Api.Playback.Progressive
                 args += GetOutputSizeParam(state, videoCodec);
             }
 
-            var qualityParam = GetVideoQualityParam(state, videoCodec, false);
+            var qualityParam = GetVideoQualityParam(state, videoCodec);
 
             if (!string.IsNullOrEmpty(qualityParam))
             {

+ 1 - 1
MediaBrowser.Api/SearchService.cs

@@ -284,7 +284,7 @@ namespace MediaBrowser.Api
         private T GetParentWithImage<T>(BaseItem item, ImageType type)
             where T : BaseItem
         {
-            return item.Parents.OfType<T>().FirstOrDefault(i => i.HasImage(type));
+            return item.GetParents().OfType<T>().FirstOrDefault(i => i.HasImage(type));
         }
     }
 }

+ 0 - 4
MediaBrowser.Api/StartupWizardService.cs

@@ -66,12 +66,8 @@ namespace MediaBrowser.Api
         {
             _config.Configuration.IsStartupWizardCompleted = true;
             _config.Configuration.EnableLocalizedGuids = true;
-            _config.Configuration.EnableLibraryMetadataSubFolder = true;
             _config.Configuration.EnableCustomPathSubFolders = true;
-            _config.Configuration.DisableStartupScan = true;
-            _config.Configuration.EnableUserViews = true;
             _config.Configuration.EnableDateLastRefresh = true;
-            _config.Configuration.MergeMetadataAndImagesByName = true;
             _config.SaveConfiguration();
         }
 

+ 16 - 17
MediaBrowser.Api/TvShowsService.cs

@@ -159,7 +159,7 @@ namespace MediaBrowser.Api
 
         [ApiMember(Name = "StartItemId", Description = "Optional. Skip through the list until a given item is found.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string StartItemId { get; set; }
-        
+
         /// <summary>
         /// Skips over a given number of items within the results. Use for paging.
         /// </summary>
@@ -273,29 +273,28 @@ namespace MediaBrowser.Api
         {
             var user = _userManager.GetUserById(request.UserId);
 
-            var items = GetAllLibraryItems(request.UserId, _userManager, _libraryManager, request.ParentId, i => i is 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 parentIds = string.IsNullOrWhiteSpace(request.ParentId) ? new string[] { } : new[] { request.ParentId };
 
-            var pagedItems = ApplyPaging(previousEpisodes, request.StartIndex, request.Limit);
+            var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
+            {
+                IncludeItemTypes = new[] { typeof(Episode).Name },
+                SortBy = new[] { "PremiereDate", "AirTime", "SortName" },
+                SortOrder = SortOrder.Ascending,
+                MinPremiereDate = minPremiereDate,
+                StartIndex = request.StartIndex,
+                Limit = request.Limit
+
+            }, parentIds);
 
             var options = GetDtoOptions(request);
 
-            var returnItems = _dtoService.GetBaseItemDtos(pagedItems, options, user).ToArray();
+            var returnItems = _dtoService.GetBaseItemDtos(itemsResult.Items, options, user).ToArray();
 
             var result = new ItemsResult
             {
-                TotalRecordCount = itemsList.Count,
+                TotalRecordCount = itemsResult.TotalRecordCount,
                 Items = returnItems
             };
 
@@ -440,7 +439,7 @@ namespace MediaBrowser.Api
                 }
 
                 episodes = season.GetEpisodes(user);
-            } 
+            }
             else if (request.Season.HasValue)
             {
                 var series = _libraryManager.GetItemById(request.Id) as Series;
@@ -495,7 +494,7 @@ namespace MediaBrowser.Api
                 .ToList();
 
             var pagedItems = ApplyPaging(returnList, request.StartIndex, request.Limit);
-            
+
             var dtoOptions = GetDtoOptions(request);
 
             var dtos = _dtoService.GetBaseItemDtos(pagedItems, dtoOptions, user)

+ 7 - 0
MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs

@@ -206,6 +206,8 @@ namespace MediaBrowser.Api.UserLibrary
         [ApiMember(Name = "Genres", Description = "Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Genres { get; set; }
 
+        public string GenreIds { get; set; }
+        
         [ApiMember(Name = "OfficialRatings", Description = "Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string OfficialRatings { get; set; }
 
@@ -385,6 +387,11 @@ namespace MediaBrowser.Api.UserLibrary
             return (StudioIds ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
         }
 
+        public string[] GetGenreIds()
+        {
+            return (GenreIds ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
+        }
+
         public string[] GetPersonTypes()
         {
             return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

+ 10 - 9
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -112,6 +112,15 @@ namespace MediaBrowser.Api.UserLibrary
                 user == null ? _libraryManager.RootFolder : user.RootFolder :
                 parentItem;
 
+            if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase))
+            {
+                item = user == null ? _libraryManager.RootFolder : user.RootFolder;
+            }
+            else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase))
+            {
+                item = user == null ? _libraryManager.RootFolder : user.RootFolder;
+            }
+
             // Default list type = children
 
             if (!string.IsNullOrEmpty(request.Ids))
@@ -211,6 +220,7 @@ namespace MediaBrowser.Api.UserLibrary
                 Tags = request.GetTags(),
                 OfficialRatings = request.GetOfficialRatings(),
                 Genres = request.GetGenres(),
+                GenreIds = request.GetGenreIds(),
                 Studios = request.GetStudios(),
                 StudioIds = request.GetStudioIds(),
                 Person = request.Person,
@@ -423,15 +433,6 @@ namespace MediaBrowser.Api.UserLibrary
                 return false;
             }
 
-            // Min index number
-            if (request.MinIndexNumber.HasValue)
-            {
-                if (!(i.IndexNumber.HasValue && i.IndexNumber.Value >= request.MinIndexNumber.Value))
-                {
-                    return false;
-                }
-            }
-
             // Min official rating
             if (!string.IsNullOrEmpty(request.MinOfficialRating))
             {

+ 19 - 4
MediaBrowser.Api/UserLibrary/UserViewsService.cs

@@ -26,6 +26,8 @@ namespace MediaBrowser.Api.UserLibrary
 
         [ApiMember(Name = "IncludeExternalContent", Description = "Whether or not to include external views such as channels or live tv", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "POST")]
         public bool? IncludeExternalContent { get; set; }
+
+        public string PresetViews { get; set; }
     }
 
     [Route("/Users/{UserId}/SpecialViewOptions", "GET")]
@@ -75,9 +77,24 @@ namespace MediaBrowser.Api.UserLibrary
                 query.IncludeExternalContent = request.IncludeExternalContent.Value;
             }
 
+            if (!string.IsNullOrWhiteSpace(request.PresetViews))
+            {
+                query.PresetViews = request.PresetViews.Split(',');
+            }
+
+            var app = AuthorizationContext.GetAuthorizationInfo(Request).Client ?? string.Empty;
+            if (app.IndexOf("emby rt", StringComparison.OrdinalIgnoreCase) != -1)
+            {
+                query.PresetViews = new[] { CollectionType.Music, CollectionType.Movies, CollectionType.TvShows };
+            }
+            //query.PresetViews = new[] { CollectionType.Music, CollectionType.Movies, CollectionType.TvShows };
+
             var folders = await _userViewManager.GetUserViews(query, CancellationToken.None).ConfigureAwait(false);
 
             var dtoOptions = GetDtoOptions(request);
+            dtoOptions.Fields = new List<ItemFields>();
+            dtoOptions.Fields.Add(ItemFields.PrimaryImageAspectRatio);
+            dtoOptions.Fields.Add(ItemFields.DisplayPreferencesId);
 
             var user = _userManager.GetUserById(request.UserId);
 
@@ -123,7 +140,7 @@ namespace MediaBrowser.Api.UserLibrary
             var views = user.RootFolder
                 .GetChildren(user, true)
                 .OfType<Folder>()
-                .Where(i => !UserView.IsExcludedFromGrouping(i))
+                .Where(UserView.IsEligibleForGrouping)
                 .ToList();
 
             var list = views
@@ -141,9 +158,7 @@ namespace MediaBrowser.Api.UserLibrary
 
         private bool IsEligibleForSpecialView(ICollectionFolder view)
         {
-            var types = new[] { CollectionType.Movies, CollectionType.TvShows, CollectionType.Games, CollectionType.Music, CollectionType.Photos };
-
-            return types.Contains(view.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+            return UserView.IsEligibleForEnhancedView(view.CollectionType);
         }
     }
 

+ 3 - 3
MediaBrowser.Common.Implementations/BaseApplicationHost.cs

@@ -453,7 +453,7 @@ namespace MediaBrowser.Common.Implementations
 
 			RegisterSingleInstance<IApplicationPaths>(ApplicationPaths);
 
-			TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, Logger, FileSystemManager);
+			TaskManager = new TaskManager(ApplicationPaths, JsonSerializer, LogManager.GetLogger("TaskManager"), FileSystemManager);
 
 			RegisterSingleInstance(JsonSerializer);
 			RegisterSingleInstance(XmlSerializer);
@@ -465,7 +465,7 @@ namespace MediaBrowser.Common.Implementations
 
 			RegisterSingleInstance(FileSystemManager);
 
-			HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger, FileSystemManager);
+            HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, LogManager.GetLogger("HttpClient"), FileSystemManager);
 			RegisterSingleInstance(HttpClient);
 
 			NetworkManager = CreateNetworkManager(LogManager.GetLogger("NetworkManager"));
@@ -474,7 +474,7 @@ namespace MediaBrowser.Common.Implementations
 			SecurityManager = new PluginSecurityManager(this, HttpClient, JsonSerializer, ApplicationPaths, LogManager);
 			RegisterSingleInstance(SecurityManager);
 
-			InstallationManager = new InstallationManager(Logger, this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, ConfigurationManager, FileSystemManager);
+            InstallationManager = new InstallationManager(LogManager.GetLogger("InstallationManager"), this, ApplicationPaths, HttpClient, JsonSerializer, SecurityManager, ConfigurationManager, FileSystemManager);
 			RegisterSingleInstance(InstallationManager);
 
 			ZipClient = new ZipClient(FileSystemManager);

+ 52 - 1
MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs

@@ -55,6 +55,25 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         private ILogger Logger { get; set; }
         private readonly IFileSystem _fileSystem;
 
+        private bool _suspendTriggers;
+
+        public bool SuspendTriggers
+        {
+            get { return _suspendTriggers; }
+            set
+            {
+                Logger.Info("Setting SuspendTriggers to {0}", value);
+                var executeQueued = _suspendTriggers && !value;
+
+                _suspendTriggers = value;
+
+                if (executeQueued)
+                {
+                    ExecuteQueuedTasks();
+                }
+            }
+        }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="TaskManager" /> class.
         /// </summary>
@@ -151,6 +170,31 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
             QueueScheduledTask<T>(new TaskExecutionOptions());
         }
 
+        public void Execute<T>()
+            where T : IScheduledTask
+        {
+            var scheduledTask = ScheduledTasks.FirstOrDefault(t => t.ScheduledTask.GetType() == typeof(T));
+
+            if (scheduledTask == null)
+            {
+                Logger.Error("Unable to find scheduled task of type {0} in Execute.", typeof(T).Name);
+            }
+            else
+            {
+                var type = scheduledTask.ScheduledTask.GetType();
+
+                Logger.Info("Queueing task {0}", type.Name);
+
+                lock (_taskQueue)
+                {
+                    if (scheduledTask.State == TaskState.Idle)
+                    {
+                        Execute(scheduledTask, new TaskExecutionOptions());
+                    }
+                }
+            }
+        }
+
         /// <summary>
         /// Queues the scheduled task.
         /// </summary>
@@ -183,7 +227,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
 
             lock (_taskQueue)
             {
-                if (task.State == TaskState.Idle)
+                if (task.State == TaskState.Idle && !SuspendTriggers)
                 {
                     Execute(task, options);
                     return;
@@ -273,6 +317,13 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         /// </summary>
         private void ExecuteQueuedTasks()
         {
+            if (SuspendTriggers)
+            {
+                return;
+            }
+
+            Logger.Info("ExecuteQueuedTasks");
+
             // Execute queued tasks
             lock (_taskQueue)
             {

+ 5 - 0
MediaBrowser.Common/ScheduledTasks/ITaskManager.cs

@@ -66,7 +66,12 @@ namespace MediaBrowser.Common.ScheduledTasks
         void Cancel(IScheduledTaskWorker task);
         Task Execute(IScheduledTaskWorker task, TaskExecutionOptions options = null);
 
+        void Execute<T>()
+            where T : IScheduledTask;
+        
         event EventHandler<GenericEventArgs<IScheduledTaskWorker>> TaskExecuting;
         event EventHandler<TaskCompletionEventArgs> TaskCompleted;
+
+        bool SuspendTriggers { get; set; }
     }
 }

+ 2 - 2
MediaBrowser.Controller/Channels/ChannelAudioItem.cs

@@ -18,9 +18,9 @@ namespace MediaBrowser.Controller.Channels
 
         public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.ChannelContent);
+            return UnratedItem.ChannelContent;
         }
 
         protected override string CreateUserDataKey()

+ 6 - 0
MediaBrowser.Controller/Channels/ChannelFolderItem.cs

@@ -6,6 +6,7 @@ using System;
 using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Controller.Channels
@@ -20,6 +21,11 @@ namespace MediaBrowser.Controller.Channels
             return false;
         }
 
+        public override UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.ChannelContent;
+        }
+
         [IgnoreDataMember]
         public override bool SupportsLocalMetadata
         {

+ 2 - 2
MediaBrowser.Controller/Channels/ChannelVideoItem.cs

@@ -42,9 +42,9 @@ namespace MediaBrowser.Controller.Channels
             return ExternalId;
         }
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.ChannelContent);
+            return UnratedItem.ChannelContent;
         }
 
         [IgnoreDataMember]

+ 4 - 3
MediaBrowser.Controller/Dto/IDtoService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Entities;
+using System;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Querying;
 using System.Collections.Generic;
@@ -44,10 +45,10 @@ namespace MediaBrowser.Controller.Dto
         /// <summary>
         /// Fills the synchronize information.
         /// </summary>
-        /// <param name="dtos">The dtos.</param>
+        /// <param name="tuples">The tuples.</param>
         /// <param name="options">The options.</param>
         /// <param name="user">The user.</param>
-        void FillSyncInfo(IEnumerable<IHasSyncInfo> dtos, DtoOptions options, User user);
+        void FillSyncInfo(IEnumerable<Tuple<BaseItem, BaseItemDto>> tuples, DtoOptions options, User user);
 
         /// <summary>
         /// Gets the base item dto.

+ 16 - 18
MediaBrowser.Controller/Entities/Audio/Audio.cs

@@ -27,9 +27,22 @@ namespace MediaBrowser.Controller.Entities.Audio
         public long? Size { get; set; }
         public string Container { get; set; }
         public int? TotalBitrate { get; set; }
-        public List<string> Tags { get; set; }
         public ExtraType? ExtraType { get; set; }
 
+        /// <summary>
+        /// Gets or sets the artist.
+        /// </summary>
+        /// <value>The artist.</value>
+        public List<string> Artists { get; set; }
+
+        public List<string> AlbumArtists { get; set; }
+
+        /// <summary>
+        /// Gets or sets the album.
+        /// </summary>
+        /// <value>The album.</value>
+        public string Album { get; set; }
+
         [IgnoreDataMember]
         public bool IsThemeMedia
         {
@@ -43,7 +56,6 @@ namespace MediaBrowser.Controller.Entities.Audio
         {
             Artists = new List<string>();
             AlbumArtists = new List<string>();
-            Tags = new List<string>();
         }
 
         [IgnoreDataMember]
@@ -92,14 +104,6 @@ namespace MediaBrowser.Controller.Entities.Audio
                    locationType != LocationType.Virtual;
         }
 
-        /// <summary>
-        /// Gets or sets the artist.
-        /// </summary>
-        /// <value>The artist.</value>
-        public List<string> Artists { get; set; }
-
-        public List<string> AlbumArtists { get; set; }
-        
         [IgnoreDataMember]
         public List<string> AllArtists
         {
@@ -114,12 +118,6 @@ namespace MediaBrowser.Controller.Entities.Audio
             }
         }
 
-        /// <summary>
-        /// Gets or sets the album.
-        /// </summary>
-        /// <value>The album.</value>
-        public string Album { get; set; }
-
         [IgnoreDataMember]
         public MusicAlbum AlbumEntity
         {
@@ -173,9 +171,9 @@ namespace MediaBrowser.Controller.Entities.Audio
             return base.CreateUserDataKey();
         }
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.Music);
+            return UnratedItem.Music;
         }
 
         public SongInfo GetLookupInfo()

+ 7 - 2
MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs

@@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Entities.Audio
         {
             get
             {
-                return Parents.OfType<MusicArtist>().FirstOrDefault();
+                return GetParents().OfType<MusicArtist>().FirstOrDefault();
             }
         }
 
@@ -114,13 +114,18 @@ namespace MediaBrowser.Controller.Entities.Audio
             return config.BlockUnratedItems.Contains(UnratedItem.Music);
         }
 
+        public override UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.Music;
+        }
+
         public AlbumInfo GetLookupInfo()
         {
             var id = GetItemLookupInfo<AlbumInfo>();
 
             id.AlbumArtists = AlbumArtists;
 
-            var artist = Parents.OfType<MusicArtist>().FirstOrDefault();
+            var artist = GetParents().OfType<MusicArtist>().FirstOrDefault();
 
             if (artist != null)
             {

+ 5 - 0
MediaBrowser.Controller/Entities/Audio/MusicArtist.cs

@@ -138,6 +138,11 @@ namespace MediaBrowser.Controller.Entities.Audio
             return config.BlockUnratedItems.Contains(UnratedItem.Music);
         }
 
+        public override UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.Music;
+        }
+
         public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
         {
             var items = GetRecursiveChildren().ToList();

+ 187 - 77
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -1,5 +1,4 @@
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Collections;
 using MediaBrowser.Controller.Configuration;
@@ -24,6 +23,7 @@ using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
 using CommonIO;
+using MediaBrowser.Model.LiveTv;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -34,6 +34,7 @@ namespace MediaBrowser.Controller.Entities
     {
         protected BaseItem()
         {
+            Tags = new List<string>();
             Genres = new List<string>();
             Studios = new List<string>();
             ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@@ -44,7 +45,7 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// The supported image extensions
         /// </summary>
-        public static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg" };
+        public static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg", ".tbn", ".gif" };
 
         public static readonly List<string> SupportedImageExtensionsList = SupportedImageExtensions.ToList();
 
@@ -103,7 +104,8 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the name.
         /// </summary>
         /// <value>The name.</value>
-        public string Name
+        [IgnoreDataMember]
+        public virtual string Name
         {
             get
             {
@@ -122,14 +124,23 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the id.
         /// </summary>
         /// <value>The id.</value>
+        [IgnoreDataMember]
         public Guid Id { get; set; }
 
         /// <summary>
         /// Gets or sets a value indicating whether this instance is hd.
         /// </summary>
         /// <value><c>true</c> if this instance is hd; otherwise, <c>false</c>.</value>
+        [IgnoreDataMember]
         public bool? IsHD { get; set; }
 
+        /// <summary>
+        /// Gets or sets the audio.
+        /// </summary>
+        /// <value>The audio.</value>
+        [IgnoreDataMember]
+        public ProgramAudio? Audio { get; set; }
+
         /// <summary>
         /// Return the id that should be used to key display prefs for this item.
         /// Default is based on the type for everything except actual generic folders.
@@ -149,6 +160,7 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the path.
         /// </summary>
         /// <value>The path.</value>
+        [IgnoreDataMember]
         public virtual string Path { get; set; }
 
         [IgnoreDataMember]
@@ -173,7 +185,7 @@ namespace MediaBrowser.Controller.Entities
         }
 
         /// <summary>
-        /// Id of the program.
+        /// If this content came from an external service, the id of the content on that service
         /// </summary>
         [IgnoreDataMember]
         public string ExternalId
@@ -201,11 +213,6 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
-        public virtual bool IsHiddenFromUser(User user)
-        {
-            return false;
-        }
-
         [IgnoreDataMember]
         public virtual bool IsOwnedItem
         {
@@ -325,12 +332,14 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the date created.
         /// </summary>
         /// <value>The date created.</value>
+        [IgnoreDataMember]
         public DateTime DateCreated { get; set; }
 
         /// <summary>
         /// Gets or sets the date modified.
         /// </summary>
         /// <value>The date modified.</value>
+        [IgnoreDataMember]
         public DateTime DateModified { get; set; }
 
         public DateTime DateLastSaved { get; set; }
@@ -407,6 +416,7 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the name of the forced sort.
         /// </summary>
         /// <value>The name of the forced sort.</value>
+        [IgnoreDataMember]
         public string ForcedSortName
         {
             get { return _forcedSortName; }
@@ -447,10 +457,7 @@ namespace MediaBrowser.Controller.Entities
         {
             var idString = Id.ToString("N");
 
-            if (ConfigurationManager.Configuration.EnableLibraryMetadataSubFolder)
-            {
-                basePath = System.IO.Path.Combine(basePath, "library");
-            }
+            basePath = System.IO.Path.Combine(basePath, "library");
 
             return System.IO.Path.Combine(basePath, idString.Substring(0, 2), idString);
         }
@@ -493,6 +500,7 @@ namespace MediaBrowser.Controller.Entities
             return sortable;
         }
 
+        [IgnoreDataMember]
         public Guid ParentId { get; set; }
 
         /// <summary>
@@ -502,15 +510,7 @@ namespace MediaBrowser.Controller.Entities
         [IgnoreDataMember]
         public Folder Parent
         {
-            get
-            {
-                if (ParentId != Guid.Empty)
-                {
-                    return LibraryManager.GetItemById(ParentId) as Folder;
-                }
-
-                return null;
-            }
+            get { return GetParent() as Folder; }
             set
             {
 
@@ -525,16 +525,28 @@ namespace MediaBrowser.Controller.Entities
         [IgnoreDataMember]
         public IEnumerable<Folder> Parents
         {
-            get
+            get { return GetParents().OfType<Folder>(); }
+        }
+
+        public BaseItem GetParent()
+        {
+            if (ParentId != Guid.Empty)
             {
-                var parent = Parent;
+                return LibraryManager.GetItemById(ParentId);
+            }
 
-                while (parent != null)
-                {
-                    yield return parent;
+            return null;
+        }
 
-                    parent = parent.Parent;
-                }
+        public IEnumerable<BaseItem> GetParents()
+        {
+            var parent = GetParent();
+
+            while (parent != null)
+            {
+                yield return parent;
+
+                parent = parent.GetParent();
             }
         }
 
@@ -546,19 +558,20 @@ namespace MediaBrowser.Controller.Entities
         public T FindParent<T>()
             where T : Folder
         {
-            return Parents.OfType<T>().FirstOrDefault();
+            return GetParents().OfType<T>().FirstOrDefault();
         }
 
         [IgnoreDataMember]
         public virtual BaseItem DisplayParent
         {
-            get { return Parent; }
+            get { return GetParent(); }
         }
 
         /// <summary>
         /// When the item first debuted. For movies this could be premiere date, episodes would be first aired
         /// </summary>
         /// <value>The premiere date.</value>
+        [IgnoreDataMember]
         public DateTime? PremiereDate { get; set; }
 
         /// <summary>
@@ -572,31 +585,35 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the display type of the media.
         /// </summary>
         /// <value>The display type of the media.</value>
+        [IgnoreDataMember]
         public string DisplayMediaType { get; set; }
 
         /// <summary>
         /// Gets or sets the official rating.
         /// </summary>
         /// <value>The official rating.</value>
+        [IgnoreDataMember]
         public string OfficialRating { get; set; }
 
         /// <summary>
         /// Gets or sets the official rating description.
         /// </summary>
         /// <value>The official rating description.</value>
+        [IgnoreDataMember]
         public string OfficialRatingDescription { get; set; }
 
         /// <summary>
         /// Gets or sets the custom rating.
         /// </summary>
         /// <value>The custom rating.</value>
-        //[IgnoreDataMember]
+        [IgnoreDataMember]
         public string CustomRating { get; set; }
 
         /// <summary>
         /// Gets or sets the overview.
         /// </summary>
         /// <value>The overview.</value>
+        [IgnoreDataMember]
         public string Overview { get; set; }
 
         /// <summary>
@@ -609,37 +626,48 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the genres.
         /// </summary>
         /// <value>The genres.</value>
+        [IgnoreDataMember]
         public List<string> Genres { get; set; }
 
+        /// <summary>
+        /// Gets or sets the tags.
+        /// </summary>
+        /// <value>The tags.</value>
+        public List<string> Tags { get; set; }
+
         /// <summary>
         /// Gets or sets the home page URL.
         /// </summary>
         /// <value>The home page URL.</value>
+        [IgnoreDataMember]
         public string HomePageUrl { get; set; }
 
         /// <summary>
         /// Gets or sets the community rating.
         /// </summary>
         /// <value>The community rating.</value>
-        //[IgnoreDataMember]
+        [IgnoreDataMember]
         public float? CommunityRating { get; set; }
 
         /// <summary>
         /// Gets or sets the community rating vote count.
         /// </summary>
         /// <value>The community rating vote count.</value>
+        [IgnoreDataMember]
         public int? VoteCount { get; set; }
 
         /// <summary>
         /// Gets or sets the run time ticks.
         /// </summary>
         /// <value>The run time ticks.</value>
+        [IgnoreDataMember]
         public long? RunTimeTicks { get; set; }
 
         /// <summary>
         /// Gets or sets the production year.
         /// </summary>
         /// <value>The production year.</value>
+        [IgnoreDataMember]
         public int? ProductionYear { get; set; }
 
         /// <summary>
@@ -647,19 +675,34 @@ namespace MediaBrowser.Controller.Entities
         /// This could be episode number, album track number, etc.
         /// </summary>
         /// <value>The index number.</value>
-        //[IgnoreDataMember]
+        [IgnoreDataMember]
         public int? IndexNumber { get; set; }
 
         /// <summary>
         /// For an episode this could be the season number, or for a song this could be the disc number.
         /// </summary>
         /// <value>The parent index number.</value>
+        [IgnoreDataMember]
         public int? ParentIndexNumber { get; set; }
 
         [IgnoreDataMember]
-        public virtual string OfficialRatingForComparison
+        public string OfficialRatingForComparison
         {
-            get { return OfficialRating; }
+            get
+            {
+                if (!string.IsNullOrWhiteSpace(OfficialRating))
+                {
+                    return OfficialRating;
+                }
+
+                var parent = DisplayParent;
+                if (parent != null)
+                {
+                    return parent.OfficialRatingForComparison;
+                }
+
+                return null;
+            }
         }
 
         [IgnoreDataMember]
@@ -721,21 +764,21 @@ namespace MediaBrowser.Controller.Entities
             return LibraryManager.ResolvePaths(files, directoryService, null)
                 .OfType<Audio.Audio>()
                 .Select(audio =>
-            {
-                // Try to retrieve it from the db. If we don't find it, use the resolved version
-                var dbItem = LibraryManager.GetItemById(audio.Id) as Audio.Audio;
-
-                if (dbItem != null)
                 {
-                    audio = dbItem;
-                }
+                    // Try to retrieve it from the db. If we don't find it, use the resolved version
+                    var dbItem = LibraryManager.GetItemById(audio.Id) as Audio.Audio;
 
-                audio.ExtraType = ExtraType.ThemeSong;
+                    if (dbItem != null)
+                    {
+                        audio = dbItem;
+                    }
+
+                    audio.ExtraType = ExtraType.ThemeSong;
 
-                return audio;
+                    return audio;
 
-                // Sort them so that the list can be easily compared for changes
-            }).OrderBy(i => i.Path).ToList();
+                    // Sort them so that the list can be easily compared for changes
+                }).OrderBy(i => i.Path).ToList();
         }
 
         /// <summary>
@@ -751,21 +794,21 @@ namespace MediaBrowser.Controller.Entities
             return LibraryManager.ResolvePaths(files, directoryService, null)
                 .OfType<Video>()
                 .Select(item =>
-            {
-                // Try to retrieve it from the db. If we don't find it, use the resolved version
-                var dbItem = LibraryManager.GetItemById(item.Id) as Video;
-
-                if (dbItem != null)
                 {
-                    item = dbItem;
-                }
+                    // Try to retrieve it from the db. If we don't find it, use the resolved version
+                    var dbItem = LibraryManager.GetItemById(item.Id) as Video;
 
-                item.ExtraType = ExtraType.ThemeVideo;
+                    if (dbItem != null)
+                    {
+                        item = dbItem;
+                    }
 
-                return item;
+                    item.ExtraType = ExtraType.ThemeVideo;
 
-                // Sort them so that the list can be easily compared for changes
-            }).OrderBy(i => i.Path).ToList();
+                    return item;
+
+                    // Sort them so that the list can be easily compared for changes
+                }).OrderBy(i => i.Path).ToList();
         }
 
         public Task RefreshMetadata(CancellationToken cancellationToken)
@@ -821,7 +864,7 @@ namespace MediaBrowser.Controller.Entities
         [IgnoreDataMember]
         protected virtual bool SupportsOwnedItems
         {
-            get { return IsFolder || Parent != null; }
+            get { return IsFolder || GetParent() != null; }
         }
 
         [IgnoreDataMember]
@@ -846,7 +889,7 @@ namespace MediaBrowser.Controller.Entities
 
             var localTrailersChanged = false;
 
-            if (LocationType == LocationType.FileSystem && Parent != null)
+            if (LocationType == LocationType.FileSystem && GetParent() != null)
             {
                 var hasThemeMedia = this as IHasThemeMedia;
                 if (hasThemeMedia != null)
@@ -1008,7 +1051,7 @@ namespace MediaBrowser.Controller.Entities
 
             if (string.IsNullOrWhiteSpace(lang))
             {
-                lang = Parents
+                lang = GetParents()
                     .Select(i => i.PreferredMetadataLanguage)
                     .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
             }
@@ -1038,7 +1081,7 @@ namespace MediaBrowser.Controller.Entities
 
             if (string.IsNullOrWhiteSpace(lang))
             {
-                lang = Parents
+                lang = GetParents()
                     .Select(i => i.PreferredMetadataCountryCode)
                     .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
             }
@@ -1119,6 +1162,23 @@ namespace MediaBrowser.Controller.Entities
         }
 
         public int? GetParentalRatingValue()
+        {
+            var rating = CustomRating;
+
+            if (string.IsNullOrWhiteSpace(rating))
+            {
+                rating = OfficialRating;
+            }
+
+            if (string.IsNullOrWhiteSpace(rating))
+            {
+                return null;
+            }
+
+            return LocalizationManager.GetRatingLevel(rating);
+        }
+
+        public int? GetInheritedParentalRatingValue()
         {
             var rating = CustomRatingForComparison;
 
@@ -1156,6 +1216,11 @@ namespace MediaBrowser.Controller.Entities
             return true;
         }
 
+        public virtual UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.Other;
+        }
+
         /// <summary>
         /// Gets the block unrated value.
         /// </summary>
@@ -1174,7 +1239,7 @@ namespace MediaBrowser.Controller.Entities
                 return false;
             }
 
-            return config.BlockUnratedItems.Contains(UnratedItem.Other);
+            return config.BlockUnratedItems.Contains(GetBlockUnratedType());
         }
 
         /// <summary>
@@ -1206,14 +1271,14 @@ namespace MediaBrowser.Controller.Entities
                 return false;
             }
 
-            if (Parents.Any(i => !i.IsVisible(user)))
+            if (GetParents().Any(i => !i.IsVisible(user)))
             {
                 return false;
             }
 
             if (checkFolders)
             {
-                var topParent = Parents.LastOrDefault() ?? this;
+                var topParent = GetParents().LastOrDefault() ?? this;
 
                 if (string.IsNullOrWhiteSpace(topParent.Path))
                 {
@@ -1307,15 +1372,6 @@ namespace MediaBrowser.Controller.Entities
             return null;
         }
 
-        /// <summary>
-        /// Adds a person to the item
-        /// </summary>
-        /// <param name="person">The person.</param>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        public void AddPerson(PersonInfo person)
-        {
-        }
-
         /// <summary>
         /// Adds a studio to the item
         /// </summary>
@@ -1779,8 +1835,8 @@ namespace MediaBrowser.Controller.Entities
                 ProviderIds = ProviderIds,
                 IndexNumber = IndexNumber,
                 ParentIndexNumber = ParentIndexNumber,
-                Year = ProductionYear,
-                PremiereDate = PremiereDate
+				Year = ProductionYear,
+				PremiereDate = PremiereDate
             };
         }
 
@@ -1875,5 +1931,59 @@ namespace MediaBrowser.Controller.Entities
                 DateLastSaved.Ticks.ToString(CultureInfo.InvariantCulture)
             };
         }
+
+        public virtual IEnumerable<Guid> GetAncestorIds()
+        {
+            return GetParents().Select(i => i.Id).Concat(LibraryManager.GetCollectionFolders(this).Select(i => i.Id));
+        }
+
+        public BaseItem GetTopParent()
+        {
+            if (IsTopParent)
+            {
+                return this;
+            }
+
+            return GetParents().FirstOrDefault(i => i.IsTopParent);
+        }
+
+        [IgnoreDataMember]
+        public virtual bool IsTopParent
+        {
+            get
+            {
+                if (GetParent() is AggregateFolder || this is Channel || this is BasePluginFolder)
+                {
+                    return true;
+                }
+
+                var view = this as UserView;
+                if (view != null && string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
+                {
+                    return true;
+                }
+
+                return false;
+            }
+        }
+
+        [IgnoreDataMember]
+        public virtual bool SupportsAncestors
+        {
+            get
+            {
+                return true;
+            }
+        }
+
+        public virtual IEnumerable<Guid> GetIdsForAncestorQuery()
+        {
+            return new[] { Id };
+        }
+
+        public virtual Task Delete(DeleteOptions options)
+        {
+            return LibraryManager.DeleteItem(this, options);
+        }
     }
-}
+}

+ 3 - 14
MediaBrowser.Controller/Entities/Book.cs

@@ -17,19 +17,8 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
-        /// <summary>
-        /// Gets or sets the tags.
-        /// </summary>
-        /// <value>The tags.</value>
-        public List<string> Tags { get; set; }
-
         public string SeriesName { get; set; }
 
-        public Book()
-        {
-            Tags = new List<string>();
-        }
-
         public override bool CanDownload()
         {
             var locationType = LocationType;
@@ -37,9 +26,9 @@ namespace MediaBrowser.Controller.Entities
                    locationType != LocationType.Virtual;
         }
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.Book);
+            return UnratedItem.Book;
         }
 
         public BookInfo GetLookupInfo()
@@ -48,7 +37,7 @@ namespace MediaBrowser.Controller.Entities
 
             if (string.IsNullOrEmpty(SeriesName))
             {
-                info.SeriesName = Parents.Select(i => i.Name).FirstOrDefault();
+                info.SeriesName = GetParents().Select(i => i.Name).FirstOrDefault();
             }
             else
             {

+ 8 - 7
MediaBrowser.Controller/Entities/CollectionFolder.cs

@@ -181,9 +181,7 @@ namespace MediaBrowser.Controller.Entities
         }
         private List<LinkedChild> GetLinkedChildrenInternal()
         {
-            return LibraryManager.RootFolder.Children
-                .OfType<Folder>()
-                .Where(i => i.Path != null && PhysicalLocations.Contains(i.Path, StringComparer.OrdinalIgnoreCase))
+            return GetPhysicalParents()
                 .SelectMany(c => c.LinkedChildren)
                 .ToList();
         }
@@ -199,11 +197,14 @@ namespace MediaBrowser.Controller.Entities
 
         private IEnumerable<BaseItem> GetActualChildren()
         {
-            return
-                LibraryManager.RootFolder.Children
+            return GetPhysicalParents().SelectMany(c => c.Children);
+        }
+
+        public IEnumerable<Folder> GetPhysicalParents()
+        {
+            return LibraryManager.RootFolder.Children
                 .OfType<Folder>()
-                .Where(i => i.Path != null && PhysicalLocations.Contains(i.Path, StringComparer.OrdinalIgnoreCase))
-                .SelectMany(c => c.Children);
+                .Where(i => i.Path != null && PhysicalLocations.Contains(i.Path, StringComparer.OrdinalIgnoreCase));
         }
 
         [IgnoreDataMember]

+ 55 - 77
MediaBrowser.Controller/Entities/Folder.cs

@@ -28,7 +28,6 @@ namespace MediaBrowser.Controller.Entities
 
         public List<Guid> ThemeSongIds { get; set; }
         public List<Guid> ThemeVideoIds { get; set; }
-        public List<string> Tags { get; set; }
 
         public Folder()
         {
@@ -36,7 +35,6 @@ namespace MediaBrowser.Controller.Entities
 
             ThemeSongIds = new List<Guid>();
             ThemeVideoIds = new List<Guid>();
-            Tags = new List<string>();
         }
 
         [IgnoreDataMember]
@@ -151,7 +149,15 @@ namespace MediaBrowser.Controller.Entities
 
             await LibraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
 
-            await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
+            if (!EnableNewFolderQuerying())
+            {
+                await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
+            }
+        }
+
+        private static bool EnableNewFolderQuerying()
+        {
+            return ConfigurationManager.Configuration.MigrationVersion >= 1;
         }
 
         protected void AddChildrenInternal(IEnumerable<BaseItem> children)
@@ -196,21 +202,6 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
-        [IgnoreDataMember]
-        public override string OfficialRatingForComparison
-        {
-            get
-            {
-                // Never want folders to be blocked by "BlockNotRated"
-                if (this is Series)
-                {
-                    return base.OfficialRatingForComparison;
-                }
-
-                return !string.IsNullOrWhiteSpace(base.OfficialRatingForComparison) ? base.OfficialRatingForComparison : "None";
-            }
-        }
-
         /// <summary>
         /// Removes the child.
         /// </summary>
@@ -224,7 +215,12 @@ namespace MediaBrowser.Controller.Entities
 
             item.SetParent(null);
 
-            return ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken);
+            if (!EnableNewFolderQuerying())
+            {
+                return ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken);
+            }
+
+            return Task.FromResult(true);
         }
 
         /// <summary>
@@ -457,32 +453,25 @@ namespace MediaBrowser.Controller.Entities
                 {
                     BaseItem currentChild;
 
-                    if (currentChildren.TryGetValue(child.Id, out currentChild))
+                    if (currentChildren.TryGetValue(child.Id, out currentChild) && IsValidFromResolver(currentChild, child))
                     {
-                        if (IsValidFromResolver(currentChild, child))
+                        var currentChildLocationType = currentChild.LocationType;
+                        if (currentChildLocationType != LocationType.Remote &&
+                            currentChildLocationType != LocationType.Virtual)
                         {
-                            var currentChildLocationType = currentChild.LocationType;
-                            if (currentChildLocationType != LocationType.Remote &&
-                                currentChildLocationType != LocationType.Virtual)
-                            {
-                                currentChild.DateModified = child.DateModified;
-                            }
-
-                            await UpdateIsOffline(currentChild, false).ConfigureAwait(false);
-                            validChildren.Add(currentChild);
-                        }
-                        else
-                        {
-                            newItems.Add(child);
-                            validChildren.Add(child);
+                            currentChild.DateModified = child.DateModified;
                         }
+
+                        await UpdateIsOffline(currentChild, false).ConfigureAwait(false);
+                        validChildren.Add(currentChild);
+
+                        continue;
                     }
-                    else
-                    {
-                        // Brand new item - needs to be added
-                        newItems.Add(child);
-                        validChildren.Add(child);
-                    }
+
+                    // Brand new item - needs to be added
+                    child.SetParent(this);
+                    newItems.Add(child);
+                    validChildren.Add(child);
                 }
 
                 // If any items were added or removed....
@@ -508,7 +497,6 @@ namespace MediaBrowser.Controller.Entities
                         }
                         else
                         {
-                            await UpdateIsOffline(item, false).ConfigureAwait(false);
                             actualRemovals.Add(item);
                         }
                     }
@@ -519,6 +507,11 @@ namespace MediaBrowser.Controller.Entities
 
                         foreach (var item in actualRemovals)
                         {
+                            Logger.Debug("Removed item: " + item.Path);
+
+                            item.SetParent(null);
+                            item.IsOffline = false;
+                            await LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }).ConfigureAwait(false);
                             LibraryManager.ReportItemRemoved(item);
                         }
                     }
@@ -527,7 +520,10 @@ namespace MediaBrowser.Controller.Entities
 
                     AddChildrenInternal(newItems);
 
-                    await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
+                    if (!EnableNewFolderQuerying())
+                    {
+                        await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
+                    }
                 }
             }
 
@@ -721,7 +717,7 @@ namespace MediaBrowser.Controller.Entities
                 return true;
             }
 
-            return ContainsPath(LibraryManager.GetVirtualFolders(), originalPath);
+            return false;
         }
 
         /// <summary>
@@ -757,19 +753,16 @@ namespace MediaBrowser.Controller.Entities
         /// <returns>IEnumerable{BaseItem}.</returns>
         protected IEnumerable<BaseItem> GetCachedChildren()
         {
-            if (ConfigurationManager.Configuration.DisableStartupScan)
+            if (EnableNewFolderQuerying())
             {
-                return ItemRepository.GetChildrenItems(Id).Select(RetrieveChild).Where(i => i != null);
-                //return ItemRepository.GetItems(new InternalItemsQuery
-                //{
-                //    ParentId = Id
+                return ItemRepository.GetItemList(new InternalItemsQuery
+                {
+                    ParentId = Id
 
-                //}).Items.Select(RetrieveChild).Where(i => i != null);
-            }
-            else
-            {
-                return ItemRepository.GetChildrenItems(Id).Select(RetrieveChild).Where(i => i != null);
+                }).Select(RetrieveChild).Where(i => i != null);
             }
+
+            return ItemRepository.GetChildrenItems(Id).Select(RetrieveChild).Where(i => i != null);
         }
 
         private BaseItem RetrieveChild(BaseItem child)
@@ -832,19 +825,7 @@ namespace MediaBrowser.Controller.Entities
             return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager);
         }
 
-        /// <summary>
-        /// Gets allowed children of an item
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
-        /// <returns>IEnumerable{BaseItem}.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
         public virtual IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
-        {
-            return GetChildren(user, includeLinkedChildren, false);
-        }
-
-        internal IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren, bool includeHidden)
         {
             if (user == null)
             {
@@ -856,7 +837,7 @@ namespace MediaBrowser.Controller.Entities
 
             var result = new Dictionary<Guid, BaseItem>();
 
-            AddChildren(user, includeLinkedChildren, result, includeHidden, false, null);
+            AddChildren(user, includeLinkedChildren, result, false, null);
 
             return result.Values;
         }
@@ -872,29 +853,25 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="user">The user.</param>
         /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
         /// <param name="result">The result.</param>
-        /// <param name="includeHidden">if set to <c>true</c> [include hidden].</param>
         /// <param name="recursive">if set to <c>true</c> [recursive].</param>
         /// <param name="filter">The filter.</param>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        private void AddChildren(User user, bool includeLinkedChildren, Dictionary<Guid, BaseItem> result, bool includeHidden, bool recursive, Func<BaseItem, bool> filter)
+        private void AddChildren(User user, bool includeLinkedChildren, Dictionary<Guid, BaseItem> result, bool recursive, Func<BaseItem, bool> filter)
         {
             foreach (var child in GetEligibleChildrenForRecursiveChildren(user))
             {
                 if (child.IsVisible(user))
                 {
-                    if (includeHidden || !child.IsHiddenFromUser(user))
+                    if (filter == null || filter(child))
                     {
-                        if (filter == null || filter(child))
-                        {
-                            result[child.Id] = child;
-                        }
+                        result[child.Id] = child;
                     }
 
                     if (recursive && child.IsFolder)
                     {
                         var folder = (Folder)child;
 
-                        folder.AddChildren(user, includeLinkedChildren, result, includeHidden, true, filter);
+                        folder.AddChildren(user, includeLinkedChildren, result, true, filter);
                     }
                 }
             }
@@ -935,7 +912,7 @@ namespace MediaBrowser.Controller.Entities
 
             var result = new Dictionary<Guid, BaseItem>();
 
-            AddChildren(user, true, result, false, true, filter);
+            AddChildren(user, true, result, true, filter);
 
             return result.Values;
         }
@@ -1184,6 +1161,7 @@ namespace MediaBrowser.Controller.Entities
                 Recursive = true,
                 IsFolder = false,
                 IsUnaired = false
+
             };
 
             if (!user.Configuration.DisplayMissingEpisodes)
@@ -1322,4 +1300,4 @@ namespace MediaBrowser.Controller.Entities
             }
         }
     }
-}
+}

+ 2 - 9
MediaBrowser.Controller/Entities/Game.cs

@@ -21,7 +21,6 @@ namespace MediaBrowser.Controller.Entities
             RemoteTrailerIds = new List<Guid>();
             ThemeSongIds = new List<Guid>();
             ThemeVideoIds = new List<Guid>();
-            Tags = new List<string>();
         }
 
         public List<Guid> LocalTrailerIds { get; set; }
@@ -34,12 +33,6 @@ namespace MediaBrowser.Controller.Entities
                    locationType != LocationType.Virtual;
         }
 
-        /// <summary>
-        /// Gets or sets the tags.
-        /// </summary>
-        /// <value>The tags.</value>
-        public List<string> Tags { get; set; }
-
         /// <summary>
         /// Gets or sets the remote trailers.
         /// </summary>
@@ -105,9 +98,9 @@ namespace MediaBrowser.Controller.Entities
             return base.GetDeletePaths();
         }
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.Game);
+            return UnratedItem.Game;
         }
 
         public GameInfo GetLookupInfo()

+ 5 - 0
MediaBrowser.Controller/Entities/GameSystem.cs

@@ -50,6 +50,11 @@ namespace MediaBrowser.Controller.Entities
             return false;
         }
 
+        public override UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.Game;
+        }
+
         public GameSystemInfo GetLookupInfo()
         {
             var id = GetItemLookupInfo<GameSystemInfo>();

+ 5 - 0
MediaBrowser.Controller/Entities/ICollectionFolder.cs

@@ -16,6 +16,11 @@ namespace MediaBrowser.Controller.Entities
         IEnumerable<string> PhysicalLocations { get; }
     }
 
+    public interface ISupportsUserSpecificView
+    {
+        bool EnableUserSpecificView { get; }
+    }
+
     public static class CollectionFolderExtensions
     {
         public static string GetViewType(this ICollectionFolder folder, User user)

+ 18 - 0
MediaBrowser.Controller/Entities/IHiddenFromDisplay.cs

@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Entities
+{
+    public interface IHiddenFromDisplay
+    {
+        /// <summary>
+        /// Determines whether the specified user is hidden.
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <returns><c>true</c> if the specified user is hidden; otherwise, <c>false</c>.</returns>
+        bool IsHiddenFromUser(User user);
+    }
+}

+ 36 - 0
MediaBrowser.Controller/Entities/InternalItemsQuery.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Model.Entities;
 using System;
 using System.Collections.Generic;
+using MediaBrowser.Model.Configuration;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -26,10 +27,12 @@ namespace MediaBrowser.Controller.Entities
         public bool? IsLiked { get; set; }
         public bool? IsPlayed { get; set; }
         public bool? IsResumable { get; set; }
+        public bool? IncludeItemsByName { get; set; }
 
         public string[] MediaTypes { get; set; }
         public string[] IncludeItemTypes { get; set; }
         public string[] ExcludeItemTypes { get; set; }
+        public string[] ExcludeTags { get; set; }
         public string[] Genres { get; set; }
 
         public bool? IsMissing { get; set; }
@@ -69,12 +72,15 @@ namespace MediaBrowser.Controller.Entities
 
         public string[] Studios { get; set; }
         public string[] StudioIds { get; set; }
+        public string[] GenreIds { get; set; }
         public ImageType[] ImageTypes { get; set; }
         public VideoType[] VideoTypes { get; set; }
+        public UnratedItem[] BlockUnratedItems { get; set; }
         public int[] Years { get; set; }
         public string[] Tags { get; set; }
         public string[] OfficialRatings { get; set; }
 
+        public DateTime? MinPremiereDate { get; set; }
         public DateTime? MinStartDate { get; set; }
         public DateTime? MaxStartDate { get; set; }
         public DateTime? MinEndDate { get; set; }
@@ -87,6 +93,7 @@ namespace MediaBrowser.Controller.Entities
 
         public int? MinPlayers { get; set; }
         public int? MaxPlayers { get; set; }
+        public int? MinIndexNumber { get; set; }
         public double? MinCriticRating { get; set; }
         public double? MinCommunityRating { get; set; }
        
@@ -101,9 +108,14 @@ namespace MediaBrowser.Controller.Entities
         public LocationType? LocationType { get; set; }
 
         public Guid? ParentId { get; set; }
+        public string[] AncestorIds { get; set; }
+        public string[] TopParentIds { get; set; }
+
+        public LocationType[] ExcludeLocationTypes { get; set; }
         
         public InternalItemsQuery()
         {
+            BlockUnratedItems = new UnratedItem[] { };
             Tags = new string[] { };
             OfficialRatings = new string[] { };
             SortBy = new string[] { };
@@ -113,6 +125,7 @@ namespace MediaBrowser.Controller.Entities
             Genres = new string[] { };
             Studios = new string[] { };
             StudioIds = new string[] { };
+            GenreIds = new string[] { };
             ImageTypes = new ImageType[] { };
             VideoTypes = new VideoType[] { };
             Years = new int[] { };
@@ -120,6 +133,29 @@ namespace MediaBrowser.Controller.Entities
             PersonIds = new string[] { };
             ChannelIds = new string[] { };
             ItemIds = new string[] { };
+            AncestorIds = new string[] { };
+            TopParentIds = new string[] { };
+            ExcludeTags = new string[] { };
+            ExcludeLocationTypes = new LocationType[] { };
+        }
+
+        public InternalItemsQuery(User user)
+            : this()
+        {
+            if (user != null)
+            {
+                var policy = user.Policy;
+                MaxParentalRating = policy.MaxParentalRating;
+
+                if (policy.MaxParentalRating.HasValue)
+                {
+                    BlockUnratedItems = policy.BlockUnratedItems;
+                }
+
+                ExcludeTags = policy.BlockedTags;
+                
+                User = user;
+            }
         }
     }
 }

+ 6 - 31
MediaBrowser.Controller/Entities/Movies/BoxSet.cs

@@ -8,15 +8,13 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
-using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Entities.Movies
 {
     /// <summary>
     /// Class BoxSet
     /// </summary>
-    public class BoxSet : Folder, IHasTrailers, IHasKeywords, IHasDisplayOrder, IHasLookupInfo<BoxSetInfo>, IMetadataContainer, IHasShares
+    public class BoxSet : Folder, IHasTrailers, IHasKeywords, IHasDisplayOrder, IHasLookupInfo<BoxSetInfo>, IHasShares
     {
         public List<Share> Shares { get; set; }
 
@@ -65,6 +63,11 @@ namespace MediaBrowser.Controller.Entities.Movies
             return config.BlockUnratedItems.Contains(UnratedItem.Movie);
         }
 
+        public override UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.Movie;
+        }
+
         [IgnoreDataMember]
         public override bool IsPreSorted
         {
@@ -154,34 +157,6 @@ namespace MediaBrowser.Controller.Entities.Movies
             return GetItemLookupInfo<BoxSetInfo>();
         }
 
-        public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            // Refresh bottom up, children first, then the boxset
-            // By then hopefully the  movies within will have Tmdb collection values
-            var items = GetRecursiveChildren().ToList();
-
-            var totalItems = items.Count;
-            var numComplete = 0;
-
-            // Refresh songs
-            foreach (var item in items)
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
-
-                numComplete++;
-                double percent = numComplete;
-                percent /= totalItems;
-                progress.Report(percent * 100);
-            }
-
-            // Refresh current item
-            await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
-
-            progress.Report(100);
-        }
-
         public override bool IsVisible(User user)
         {
             var userId = user.Id.ToString("N");

+ 3 - 3
MediaBrowser.Controller/Entities/Movies/Movie.cs

@@ -130,7 +130,7 @@ namespace MediaBrowser.Controller.Entities.Movies
 
             // Must have a parent to have special features
             // In other words, it must be part of the Parent/Child tree
-            if (LocationType == LocationType.FileSystem && Parent != null && !IsInMixedFolder)
+            if (LocationType == LocationType.FileSystem && GetParent() != null && !IsInMixedFolder)
             {
                 var specialFeaturesChanged = await RefreshSpecialFeatures(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
 
@@ -159,9 +159,9 @@ namespace MediaBrowser.Controller.Entities.Movies
             return itemsChanged;
         }
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.Movie);
+            return UnratedItem.Movie;
         }
 
         public MovieInfo GetLookupInfo()

+ 2 - 2
MediaBrowser.Controller/Entities/MusicVideo.cs

@@ -56,9 +56,9 @@ namespace MediaBrowser.Controller.Entities
             return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.CreateUserDataKey();
         }
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.Music);
+            return UnratedItem.Music;
         }
 
         public MusicVideoInfo GetLookupInfo()

+ 9 - 0
MediaBrowser.Controller/Entities/Person.cs

@@ -102,6 +102,15 @@ namespace MediaBrowser.Controller.Entities
                 return false;
             }
         }
+
+        [IgnoreDataMember]
+        public override bool SupportsAncestors
+        {
+            get
+            {
+                return false;
+            }
+        }
     }
 
     /// <summary>

+ 6 - 8
MediaBrowser.Controller/Entities/Photo.cs

@@ -9,12 +9,10 @@ namespace MediaBrowser.Controller.Entities
 {
     public class Photo : BaseItem, IHasTags, IHasTaglines
     {
-        public List<string> Tags { get; set; }
         public List<string> Taglines { get; set; }
 
         public Photo()
         {
-            Tags = new List<string>();
             Taglines = new List<string>();
         }
 
@@ -51,10 +49,15 @@ namespace MediaBrowser.Controller.Entities
         {
             get
             {
-                return Parents.OfType<PhotoAlbum>().FirstOrDefault();
+                return GetParents().OfType<PhotoAlbum>().FirstOrDefault();
             }
         }
 
+        public override bool CanDownload()
+        {
+            return true;
+        }
+
         public int? Width { get; set; }
         public int? Height { get; set; }
         public string CameraMake { get; set; }
@@ -70,10 +73,5 @@ namespace MediaBrowser.Controller.Entities
         public double? Longitude { get; set; }
         public double? Altitude { get; set; }
         public int? IsoSpeedRating { get; set; }
-
-        protected override bool GetBlockUnratedValue(UserPolicy config)
-        {
-            return config.BlockUnratedItems.Contains(UnratedItem.Other);
-        }
     }
 }

+ 2 - 27
MediaBrowser.Controller/Entities/PhotoAlbum.cs

@@ -9,8 +9,9 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Entities
 {
-    public class PhotoAlbum : Folder, IMetadataContainer
+    public class PhotoAlbum : Folder
     {
+        [IgnoreDataMember]
         public override bool SupportsLocalMetadata
         {
             get
@@ -32,31 +33,5 @@ namespace MediaBrowser.Controller.Entities
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Other);
         }
-
-        public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            var items = GetRecursiveChildren().ToList();
-
-            var totalItems = items.Count;
-            var numComplete = 0;
-
-            // Refresh songs
-            foreach (var item in items)
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
-
-                numComplete++;
-                double percent = numComplete;
-                percent /= totalItems;
-                progress.Report(percent * 100);
-            }
-
-            // Refresh current item
-            await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
-
-            progress.Report(100);
-        }
     }
 }

+ 0 - 7
MediaBrowser.Controller/Entities/Studio.cs

@@ -10,13 +10,6 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     public class Studio : BaseItem, IItemByName, IHasTags
     {
-        public List<string> Tags { get; set; }
-
-        public Studio()
-        {
-            Tags = new List<string>();
-        }
-
         /// <summary>
         /// Gets the user data key.
         /// </summary>

+ 17 - 16
MediaBrowser.Controller/Entities/TV/Episode.cs

@@ -95,7 +95,7 @@ namespace MediaBrowser.Controller.Entities.TV
         {
             get
             {
-                return Season ?? Parent;
+                return Season ?? GetParent();
             }
         }
 
@@ -115,19 +115,6 @@ namespace MediaBrowser.Controller.Entities.TV
             return base.CreateUserDataKey();
         }
 
-        /// <summary>
-        /// Our rating comes from our series
-        /// </summary>
-        [IgnoreDataMember]
-        public override string OfficialRatingForComparison
-        {
-            get
-            {
-                var series = Series;
-                return series != null ? series.OfficialRatingForComparison : base.OfficialRatingForComparison;
-            }
-        }
-
         /// <summary>
         /// This Episode's Series Instance
         /// </summary>
@@ -265,14 +252,28 @@ namespace MediaBrowser.Controller.Entities.TV
             }
         }
 
+        public override IEnumerable<Guid> GetAncestorIds()
+        {
+            var list = base.GetAncestorIds().ToList();
+
+            var seasonId = SeasonId;
+
+            if (seasonId.HasValue && !list.Contains(seasonId.Value))
+            {
+                list.Add(seasonId.Value);
+            }
+
+            return list;
+        }
+
         public override IEnumerable<string> GetDeletePaths()
         {
             return new[] { Path };
         }
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.Series);
+            return UnratedItem.Series;
         }
 
         public EpisodeInfo GetLookupInfo()

+ 7 - 14
MediaBrowser.Controller/Entities/TV/Season.cs

@@ -6,6 +6,7 @@ using MoreLinq;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
+using MediaBrowser.Model.Configuration;
 
 namespace MediaBrowser.Controller.Entities.TV
 {
@@ -32,7 +33,7 @@ namespace MediaBrowser.Controller.Entities.TV
         [IgnoreDataMember]
         public override BaseItem DisplayParent
         {
-            get { return Series ?? Parent; }
+            get { return Series ?? GetParent(); }
         }
 
         // Genre, Rating and Stuido will all be the same
@@ -87,19 +88,6 @@ namespace MediaBrowser.Controller.Entities.TV
             }
         }
 
-        /// <summary>
-        /// Our rating comes from our series
-        /// </summary>
-        [IgnoreDataMember]
-        public override string OfficialRatingForComparison
-        {
-            get
-            {
-                var series = Series;
-                return series != null ? series.OfficialRatingForComparison : base.OfficialRatingForComparison;
-            }
-        }
-
         /// <summary>
         /// Creates the name of the sort.
         /// </summary>
@@ -234,6 +222,11 @@ namespace MediaBrowser.Controller.Entities.TV
             return false;
         }
 
+        public override UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.Series;
+        }
+
         [IgnoreDataMember]
         public string SeriesName
         {

+ 5 - 0
MediaBrowser.Controller/Entities/TV/Series.cs

@@ -333,6 +333,11 @@ namespace MediaBrowser.Controller.Entities.TV
             return config.BlockUnratedItems.Contains(UnratedItem.Series);
         }
 
+        public override UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.Series;
+        }
+
         public SeriesInfo GetLookupInfo()
         {
             var info = GetItemLookupInfo<SeriesInfo>();

+ 3 - 3
MediaBrowser.Controller/Entities/Trailer.cs

@@ -73,7 +73,7 @@ namespace MediaBrowser.Controller.Entities
             get
             {
                 // Local trailers are not part of children
-                return Parent == null;
+                return GetParent() == null;
             }
         }
 
@@ -97,9 +97,9 @@ namespace MediaBrowser.Controller.Entities
             return base.CreateUserDataKey();
         }
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.Trailer);
+            return UnratedItem.Trailer;
         }
 
         public TrailerInfo GetLookupInfo()

+ 20 - 1
MediaBrowser.Controller/Entities/User.cs

@@ -20,7 +20,6 @@ namespace MediaBrowser.Controller.Entities
     {
         public static IUserManager UserManager { get; set; }
         public static IXmlSerializer XmlSerializer { get; set; }
-        public bool EnableUserViews { get; set; }
 
         /// <summary>
         /// From now on all user paths will be Id-based. 
@@ -58,6 +57,26 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
+        private string _name;
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public override string Name
+        {
+            get
+            {
+                return _name;
+            }
+            set
+            {
+                _name = value;
+
+                // lazy load this again
+                SortName = null;
+            }
+        }
+
         /// <summary>
         /// Returns the folder containing the item.
         /// If the item is a folder, it returns the folder itself

+ 9 - 1
MediaBrowser.Controller/Entities/UserRootFolder.cs

@@ -55,13 +55,21 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
+        protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
+        {
+            var list = base.GetEligibleChildrenForRecursiveChildren(user).ToList();
+            list.AddRange(LibraryManager.RootFolder.VirtualChildren);
+
+            return list;
+        }
+
         /// <summary>
         /// Get the children of this folder from the actual file system
         /// </summary>
         /// <returns>IEnumerable{BaseItem}.</returns>
         protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
         {
-            return base.GetNonCachedChildren(directoryService).Concat(LibraryManager.RootFolder.VirtualChildren);
+            return base.GetNonCachedChildren(directoryService);
         }
 
         public override bool BeforeMetadataRefresh()

+ 73 - 20
MediaBrowser.Controller/Entities/UserView.cs

@@ -6,6 +6,7 @@ using System;
 using System.Collections.Generic;
 using System.Runtime.Serialization;
 using System.Threading.Tasks;
+using System.Linq;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -16,7 +17,7 @@ namespace MediaBrowser.Controller.Entities
         public Guid DisplayParentId { get; set; }
 
         public Guid? UserId { get; set; }
-        
+
         public static ITVSeriesManager TVSeriesManager;
         public static IPlaylistManager PlaylistManager;
 
@@ -24,7 +25,26 @@ namespace MediaBrowser.Controller.Entities
         {
             return true;
         }
-        
+
+        public override IEnumerable<Guid> GetIdsForAncestorQuery()
+        {
+            var list = new List<Guid>();
+
+            if (DisplayParentId != Guid.Empty)
+            {
+                list.Add(DisplayParentId);
+            }
+            else if (ParentId != Guid.Empty)
+            {
+                list.Add(ParentId);
+            }
+            else
+            {
+                list.Add(Id);
+            }
+            return list;
+        }
+
         public override Task<QueryResult<BaseItem>> GetItems(InternalItemsQuery query)
         {
             var parent = this as Folder;
@@ -81,16 +101,11 @@ namespace MediaBrowser.Controller.Entities
             return GetChildren(user, false);
         }
 
-        public static bool IsExcludedFromGrouping(Folder folder)
+        public static bool IsUserSpecific(Folder folder)
         {
             var standaloneTypes = new List<string>
             {
-                CollectionType.Books,
-                CollectionType.HomeVideos,
-                CollectionType.Photos,
-                CollectionType.Playlists,
-                CollectionType.BoxSets,
-                CollectionType.MusicVideos
+                CollectionType.Playlists
             };
 
             var collectionFolder = folder as ICollectionFolder;
@@ -100,25 +115,63 @@ namespace MediaBrowser.Controller.Entities
                 return false;
             }
 
+            var supportsUserSpecific = folder as ISupportsUserSpecificView;
+            if (supportsUserSpecific != null && supportsUserSpecific.EnableUserSpecificView)
+            {
+                return true;
+            }
+
             return standaloneTypes.Contains(collectionFolder.CollectionType ?? string.Empty);
         }
 
-        public static bool IsUserSpecific(Folder folder)
+        public static bool IsEligibleForGrouping(Folder folder)
         {
-            var standaloneTypes = new List<string>
-            {
-                CollectionType.Playlists,
-                CollectionType.BoxSets
+            var collectionFolder = folder as ICollectionFolder;
+            return collectionFolder != null && IsEligibleForGrouping(collectionFolder.CollectionType);
+        }
+
+        public static bool IsEligibleForGrouping(string viewType)
+        {
+            var types = new[] 
+            { 
+                CollectionType.Movies, 
+                CollectionType.TvShows,
+                string.Empty
             };
 
-            var collectionFolder = folder as ICollectionFolder;
+            return types.Contains(viewType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+        }
 
-            if (collectionFolder == null)
-            {
-                return false;
-            }
+        public static bool IsEligibleForEnhancedView(string viewType)
+        {
+            var types = new[] 
+            { 
+                CollectionType.Movies, 
+                CollectionType.TvShows 
+            };
 
-            return standaloneTypes.Contains(collectionFolder.CollectionType ?? string.Empty);
+            return types.Contains(viewType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+        }
+
+        public static bool EnableOriginalFolder(string viewType)
+        {
+            var types = new[] 
+            { 
+                CollectionType.Games, 
+                CollectionType.Books, 
+                CollectionType.MusicVideos, 
+                CollectionType.HomeVideos, 
+                CollectionType.Photos, 
+                CollectionType.Music, 
+                CollectionType.BoxSets
+            };
+
+            return types.Contains(viewType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+        }
+
+        protected override Task ValidateChildrenInternal(IProgress<double> progress, System.Threading.CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, Providers.MetadataRefreshOptions refreshOptions, Providers.IDirectoryService directoryService)
+        {
+            return Task.FromResult(true);
         }
 
         [IgnoreDataMember]

+ 89 - 179
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -120,59 +120,34 @@ namespace MediaBrowser.Controller.Entities
                         return await GetLiveTvView(queryParent, user, query).ConfigureAwait(false);
                     }
 
+                case CollectionType.Photos:
                 case CollectionType.Books:
                 case CollectionType.HomeVideos:
+                case CollectionType.Games:
                 case CollectionType.MusicVideos:
+                {
+                    if (query.Recursive)
+                    {
+                        return GetResult(queryParent.GetRecursiveChildren(user, true), queryParent, query);
+                    }
                     return GetResult(queryParent.GetChildren(user, true), queryParent, query);
+                }
 
                 case CollectionType.Folders:
                     return GetResult(user.RootFolder.GetChildren(user, true), queryParent, query);
 
-                case CollectionType.Games:
-                    return await GetGameView(user, queryParent, query).ConfigureAwait(false);
-
                 case CollectionType.Playlists:
                     return await GetPlaylistsView(queryParent, user, query).ConfigureAwait(false);
 
                 case CollectionType.BoxSets:
                     return await GetBoxsetView(queryParent, user, query).ConfigureAwait(false);
 
-                case CollectionType.Photos:
-                    return await GetPhotosView(queryParent, user, query).ConfigureAwait(false);
-
                 case CollectionType.TvShows:
                     return await GetTvView(queryParent, user, query).ConfigureAwait(false);
 
-                case CollectionType.Music:
-                    return await GetMusicFolders(queryParent, user, query).ConfigureAwait(false);
-
                 case CollectionType.Movies:
                     return await GetMovieFolders(queryParent, user, query).ConfigureAwait(false);
 
-                case SpecialFolder.MusicGenres:
-                    return await GetMusicGenres(queryParent, user, query).ConfigureAwait(false);
-
-                case SpecialFolder.MusicGenre:
-                    return await GetMusicGenreItems(queryParent, displayParent, user, query).ConfigureAwait(false);
-
-                case SpecialFolder.GameGenres:
-                    return await GetGameGenres(queryParent, user, query).ConfigureAwait(false);
-
-                case SpecialFolder.GameGenre:
-                    return await GetGameGenreItems(queryParent, displayParent, user, query).ConfigureAwait(false);
-
-                case SpecialFolder.GameSystems:
-                    return GetGameSystems(queryParent, user, query);
-
-                case SpecialFolder.LatestGames:
-                    return GetLatestGames(queryParent, user, query);
-
-                case SpecialFolder.RecentlyPlayedGames:
-                    return GetRecentlyPlayedGames(queryParent, user, query);
-
-                case SpecialFolder.GameFavorites:
-                    return GetFavoriteGames(queryParent, user, query);
-
                 case SpecialFolder.TvShowSeries:
                     return GetTvSeries(queryParent, user, query);
 
@@ -212,6 +187,21 @@ namespace MediaBrowser.Controller.Entities
                 case SpecialFolder.MovieCollections:
                     return GetMovieCollections(queryParent, user, query);
 
+                case SpecialFolder.TvFavoriteEpisodes:
+                    return GetFavoriteEpisodes(queryParent, user, query);
+
+                case SpecialFolder.TvFavoriteSeries:
+                    return GetFavoriteSeries(queryParent, user, query);
+
+                case CollectionType.Music:
+                    return await GetMusicFolders(queryParent, user, query).ConfigureAwait(false);
+
+                case SpecialFolder.MusicGenres:
+                    return await GetMusicGenres(queryParent, user, query).ConfigureAwait(false);
+
+                case SpecialFolder.MusicGenre:
+                    return await GetMusicGenreItems(queryParent, displayParent, user, query).ConfigureAwait(false);
+
                 case SpecialFolder.MusicLatest:
                     return GetMusicLatest(queryParent, user, query);
 
@@ -230,12 +220,6 @@ namespace MediaBrowser.Controller.Entities
                 case SpecialFolder.MusicSongs:
                     return GetMusicSongs(queryParent, user, query);
 
-                case SpecialFolder.TvFavoriteEpisodes:
-                    return GetFavoriteEpisodes(queryParent, user, query);
-
-                case SpecialFolder.TvFavoriteSeries:
-                    return GetFavoriteSeries(queryParent, user, query);
-
                 case SpecialFolder.MusicFavorites:
                     return await GetMusicFavorites(queryParent, user, query).ConfigureAwait(false);
 
@@ -262,18 +246,6 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
-        private async Task<QueryResult<BaseItem>> FindPlaylists(Folder parent, User user, InternalItemsQuery query)
-        {
-            var list = _playlistManager.GetPlaylists(user.Id.ToString("N"));
-
-            return GetResult(list, parent, query);
-        }
-
-        private int GetSpecialItemsLimit()
-        {
-            return 50;
-        }
-
         private async Task<QueryResult<BaseItem>> GetMusicFolders(Folder parent, User user, InternalItemsQuery query)
         {
             if (query.Recursive)
@@ -289,7 +261,7 @@ namespace MediaBrowser.Controller.Entities
             list.Add(await GetUserView(SpecialFolder.MusicPlaylists, "1", parent).ConfigureAwait(false));
             list.Add(await GetUserView(SpecialFolder.MusicAlbums, "2", parent).ConfigureAwait(false));
             list.Add(await GetUserView(SpecialFolder.MusicAlbumArtists, "3", parent).ConfigureAwait(false));
-            //list.Add(await GetUserView(SpecialFolder.MusicArtists, user, "4", parent).ConfigureAwait(false));
+            list.Add(await GetUserView(SpecialFolder.MusicArtists, "4", parent).ConfigureAwait(false));
             list.Add(await GetUserView(SpecialFolder.MusicSongs, "5", parent).ConfigureAwait(false));
             list.Add(await GetUserView(SpecialFolder.MusicGenres, "6", parent).ConfigureAwait(false));
             list.Add(await GetUserView(SpecialFolder.MusicFavorites, "7", parent).ConfigureAwait(false));
@@ -422,6 +394,36 @@ namespace MediaBrowser.Controller.Entities
             return PostFilterAndSort(items, parent, null, query);
         }
 
+        private QueryResult<BaseItem> GetFavoriteSongs(Folder parent, User user, InternalItemsQuery query)
+        {
+            query.IsFavorite = true;
+
+            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music }, i => (i is Audio.Audio) && FilterItem(i, query));
+
+            return PostFilterAndSort(items, parent, null, query);
+        }
+
+        private QueryResult<BaseItem> GetFavoriteAlbums(Folder parent, User user, InternalItemsQuery query)
+        {
+            query.IsFavorite = true;
+
+            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music }, i => (i is MusicAlbum) && FilterItem(i, query));
+
+            return PostFilterAndSort(items, parent, null, query);
+        }
+
+        private async Task<QueryResult<BaseItem>> FindPlaylists(Folder parent, User user, InternalItemsQuery query)
+        {
+            var list = _playlistManager.GetPlaylists(user.Id.ToString("N"));
+
+            return GetResult(list, parent, query);
+        }
+
+        private int GetSpecialItemsLimit()
+        {
+            return 50;
+        }
+
         private async Task<QueryResult<BaseItem>> GetMovieFolders(Folder parent, User user, InternalItemsQuery query)
         {
             if (query.Recursive)
@@ -480,24 +482,6 @@ namespace MediaBrowser.Controller.Entities
             return PostFilterAndSort(items, parent, null, query);
         }
 
-        private QueryResult<BaseItem> GetFavoriteSongs(Folder parent, User user, InternalItemsQuery query)
-        {
-            query.IsFavorite = true;
-
-            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music }, i => (i is Audio.Audio) && FilterItem(i, query));
-
-            return PostFilterAndSort(items, parent, null, query);
-        }
-
-        private QueryResult<BaseItem> GetFavoriteAlbums(Folder parent, User user, InternalItemsQuery query)
-        {
-            query.IsFavorite = true;
-
-            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music }, i => (i is MusicAlbum) && FilterItem(i, query));
-
-            return PostFilterAndSort(items, parent, null, query);
-        }
-
         private QueryResult<BaseItem> GetMovieMovies(Folder parent, User user, InternalItemsQuery query)
         {
             var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }, i => (i is Movie) && FilterItem(i, query));
@@ -582,19 +566,6 @@ namespace MediaBrowser.Controller.Entities
             return GetResult(collections, parent, query);
         }
 
-        private async Task<QueryResult<BaseItem>> GetPhotosView(Folder queryParent, User user, InternalItemsQuery query)
-        {
-            if (query.Recursive)
-            {
-                var mediaTypes = new[] { MediaType.Video, MediaType.Photo };
-                var items = GetRecursiveChildren(queryParent, user, new[] { CollectionType.Photos, string.Empty }, i => (i is PhotoAlbum || mediaTypes.Contains(i.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) && FilterItem(i, query));
-
-                return PostFilterAndSort(items, queryParent, null, query);
-            }
-
-            return GetResult(queryParent.GetChildren(user, true), queryParent, query);
-        }
-
         private async Task<QueryResult<BaseItem>> GetTvView(Folder parent, User user, InternalItemsQuery query)
         {
             if (query.Recursive)
@@ -617,54 +588,6 @@ namespace MediaBrowser.Controller.Entities
             return GetResult(list, parent, query);
         }
 
-        private async Task<QueryResult<BaseItem>> GetGameView(User user, Folder parent, InternalItemsQuery query)
-        {
-            if (query.Recursive)
-            {
-                var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Games }, i => FilterItem(i, query));
-                return PostFilterAndSort(items, parent, null, query);
-            }
-
-            var list = new List<BaseItem>();
-
-            list.Add(await GetUserView(SpecialFolder.LatestGames, "0", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.RecentlyPlayedGames, "1", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.GameFavorites, "2", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.GameSystems, "3", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.GameGenres, "4", parent).ConfigureAwait(false));
-
-            return GetResult(list, parent, query);
-        }
-
-        private QueryResult<BaseItem> GetLatestGames(Folder parent, User user, InternalItemsQuery query)
-        {
-            query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName };
-            query.SortOrder = SortOrder.Descending;
-
-            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Games }, i => i is Game && FilterItem(i, query));
-
-            return PostFilterAndSort(items, parent, GetSpecialItemsLimit(), query);
-        }
-
-        private QueryResult<BaseItem> GetRecentlyPlayedGames(Folder parent, User user, InternalItemsQuery query)
-        {
-            query.IsPlayed = true;
-            query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName };
-            query.SortOrder = SortOrder.Descending;
-
-            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Games }, i => i is Game && FilterItem(i, query));
-
-            return PostFilterAndSort(items, parent, GetSpecialItemsLimit(), query);
-        }
-
-        private QueryResult<BaseItem> GetFavoriteGames(Folder parent, User user, InternalItemsQuery query)
-        {
-            query.IsFavorite = true;
-
-            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Games }, i => i is Game && FilterItem(i, query));
-            return PostFilterAndSort(items, parent, null, query);
-        }
-
         private QueryResult<BaseItem> GetTvLatest(Folder parent, User user, InternalItemsQuery query)
         {
             query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName };
@@ -745,49 +668,6 @@ namespace MediaBrowser.Controller.Entities
             return GetResult(items, queryParent, query);
         }
 
-        private QueryResult<BaseItem> GetGameSystems(Folder parent, User user, InternalItemsQuery query)
-        {
-            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Games }, i => i is GameSystem && FilterItem(i, query));
-
-            return PostFilterAndSort(items, parent, null, query);
-        }
-
-        private async Task<QueryResult<BaseItem>> GetGameGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
-        {
-            var items = GetRecursiveChildren(queryParent, user, new[] { CollectionType.Games },
-                i => i is Game && i.Genres.Contains(displayParent.Name, StringComparer.OrdinalIgnoreCase));
-
-            return GetResult(items, queryParent, query);
-        }
-
-        private async Task<QueryResult<BaseItem>> GetGameGenres(Folder parent, User user, InternalItemsQuery query)
-        {
-            var tasks = GetRecursiveChildren(parent, user, new[] { CollectionType.Games })
-                .OfType<Game>()
-                .SelectMany(i => i.Genres)
-                .DistinctNames()
-                .Select(i =>
-                {
-                    try
-                    {
-                        return _libraryManager.GetGameGenre(i);
-                    }
-                    catch
-                    {
-                        // Full exception logged at lower levels
-                        _logger.Error("Error getting game genre");
-                        return null;
-                    }
-
-                })
-                .Where(i => i != null)
-                .Select(i => GetUserView(i.Name, SpecialFolder.GameGenre, i.SortName, parent));
-
-            var genres = await Task.WhenAll(tasks).ConfigureAwait(false);
-
-            return GetResult(genres, parent, query);
-        }
-
         private QueryResult<BaseItem> GetResult<T>(QueryResult<T> result)
             where T : BaseItem
         {
@@ -1061,6 +941,11 @@ namespace MediaBrowser.Controller.Entities
                 return false;
             }
 
+            if (request.GenreIds.Length > 0)
+            {
+                return false;
+            }
+
             if (request.VideoTypes.Length > 0)
             {
                 return false;
@@ -1101,10 +986,15 @@ namespace MediaBrowser.Controller.Entities
                 return false;
             }
 
+            if (request.MinIndexNumber.HasValue)
+            {
+                return false;
+            }
+
             return true;
         }
 
-        public static IEnumerable<BaseItem> FilterVirtualEpisodes(
+        private static IEnumerable<BaseItem> FilterVirtualEpisodes(
             IEnumerable<BaseItem> items,
             bool? isMissing,
             bool? isVirtualUnaired,
@@ -1374,7 +1264,7 @@ namespace MediaBrowser.Controller.Entities
             if (query.IsInBoxSet.HasValue)
             {
                 var val = query.IsInBoxSet.Value;
-                if (item.Parents.OfType<BoxSet>().Any() != val)
+                if (item.GetParents().OfType<BoxSet>().Any() != val)
                 {
                     return false;
                 }
@@ -1657,6 +1547,16 @@ namespace MediaBrowser.Controller.Entities
                 return false;
             }
 
+            // Apply genre filter
+            if (query.GenreIds.Length > 0 && !query.GenreIds.Any(id =>
+            {
+                var genreItem = libraryManager.GetItemById(id);
+                return genreItem != null && item.Genres.Contains(genreItem.Name, StringComparer.OrdinalIgnoreCase);
+            }))
+            {
+                return false;
+            }
+
             // Apply year filter
             if (query.Years.Length > 0)
             {
@@ -1779,6 +1679,16 @@ namespace MediaBrowser.Controller.Entities
                 }
             }
 
+            if (query.MinIndexNumber.HasValue)
+            {
+                var val = query.MinIndexNumber.Value;
+
+                if (!(item.IndexNumber.HasValue && item.IndexNumber.Value >= val))
+                {
+                    return false;
+                }
+            }
+
             return true;
         }
 
@@ -1789,12 +1699,12 @@ namespace MediaBrowser.Controller.Entities
                 return _libraryManager.RootFolder
                     .Children
                     .OfType<Folder>()
-                    .Where(i => !UserView.IsExcludedFromGrouping(i));
+                    .Where(UserView.IsEligibleForGrouping);
             }
             return user.RootFolder
-                .GetChildren(user, true, true)
+                .GetChildren(user, true)
                 .OfType<Folder>()
-                .Where(i => user.IsFolderGrouped(i.Id) && !UserView.IsExcludedFromGrouping(i));
+                .Where(i => user.IsFolderGrouped(i.Id) && UserView.IsEligibleForGrouping(i));
         }
 
         private IEnumerable<Folder> GetMediaFolders(User user, IEnumerable<string> viewTypes)

+ 1 - 7
MediaBrowser.Controller/Entities/Video.cs

@@ -185,12 +185,6 @@ namespace MediaBrowser.Controller.Entities
         public bool IsShortcut { get; set; }
         public string ShortcutPath { get; set; }
 
-        /// <summary>
-        /// Gets or sets the tags.
-        /// </summary>
-        /// <value>The tags.</value>
-        public List<string> Tags { get; set; }
-
         /// <summary>
         /// Gets or sets the video bit rate.
         /// </summary>
@@ -356,7 +350,7 @@ namespace MediaBrowser.Controller.Entities
             // Must have a parent to have additional parts or alternate versions
             // In other words, it must be part of the Parent/Child tree
             // The additional parts won't have additional parts themselves
-            if (LocationType == LocationType.FileSystem && Parent != null)
+            if (LocationType == LocationType.FileSystem && GetParent() != null)
             {
                 if (!IsStacked)
                 {

+ 14 - 0
MediaBrowser.Controller/FileOrganization/IFileOrganizationService.cs

@@ -67,5 +67,19 @@ namespace MediaBrowser.Controller.FileOrganization
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         Task SaveResult(FileOrganizationResult result, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Returns a list of smart match entries
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <returns>IEnumerable{SmartMatchInfo}.</returns>
+        QueryResult<SmartMatchInfo> GetSmartMatchInfos(FileOrganizationResultQuery query);
+
+        /// <summary>
+        /// Deletes a smart match entry.
+        /// </summary>
+        /// <param name="ItemName">Item name.</param>
+        /// <param name="matchString">The match string to delete.</param>
+        void DeleteSmartMatchEntry(string ItemName, string matchString);
     }
 }

+ 24 - 4
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -329,7 +329,6 @@ namespace MediaBrowser.Controller.Library
         /// <param name="parentId">The parent identifier.</param>
         /// <param name="viewType">Type of the view.</param>
         /// <param name="sortName">Name of the sort.</param>
-        /// <param name="uniqueId">The unique identifier.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task&lt;UserView&gt;.</returns>
         Task<UserView> GetNamedView(User user,
@@ -337,7 +336,6 @@ namespace MediaBrowser.Controller.Library
             string parentId,
             string viewType, 
             string sortName, 
-            string uniqueId,
             CancellationToken cancellationToken);
 
         /// <summary>
@@ -391,13 +389,11 @@ namespace MediaBrowser.Controller.Library
         /// <param name="parent">The parent.</param>
         /// <param name="viewType">Type of the view.</param>
         /// <param name="sortName">Name of the sort.</param>
-        /// <param name="uniqueId">The unique identifier.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task&lt;UserView&gt;.</returns>
         Task<UserView> GetShadowView(BaseItem parent,
           string viewType,
           string sortName,
-          string uniqueId,
           CancellationToken cancellationToken);
         
         /// <summary>
@@ -543,5 +539,29 @@ namespace MediaBrowser.Controller.Library
         /// <param name="imageIndex">Index of the image.</param>
         /// <returns>Task.</returns>
         Task<ItemImageInfo> ConvertImageToLocal(IHasImages item, ItemImageInfo image, int imageIndex);
+
+        /// <summary>
+        /// Gets the items.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <param name="parentIds">The parent ids.</param>
+        /// <returns>List&lt;BaseItem&gt;.</returns>
+        IEnumerable<BaseItem> GetItems(InternalItemsQuery query, IEnumerable<string> parentIds);
+
+        /// <summary>
+        /// Gets the items result.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <param name="parentIds">The parent ids.</param>
+        /// <returns>QueryResult&lt;BaseItem&gt;.</returns>
+        QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query, IEnumerable<string> parentIds);
+
+        /// <summary>
+        /// Ignores the file.
+        /// </summary>
+        /// <param name="file">The file.</param>
+        /// <param name="parent">The parent.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+        bool IgnoreFile(FileSystemMetadata file, BaseItem parent);
     }
 }

+ 1 - 1
MediaBrowser.Controller/Library/ItemResolveArgs.cs

@@ -155,7 +155,7 @@ namespace MediaBrowser.Controller.Library
                 // Not officially supported but in some cases we can handle it.
                 if (item == null)
                 {
-                    item = parent.Parents.OfType<T>().FirstOrDefault();
+                    item = parent.GetParents().OfType<T>().FirstOrDefault();
                 }
 
                 return item != null;

+ 0 - 8
MediaBrowser.Controller/Library/LibraryManagerExtensions.cs

@@ -6,14 +6,6 @@ namespace MediaBrowser.Controller.Library
 {
     public static class LibraryManagerExtensions
     {
-        public static Task DeleteItem(this ILibraryManager manager, BaseItem item)
-        {
-            return manager.DeleteItem(item, new DeleteOptions
-            {
-                DeleteFileLocation = true
-            });
-        }
-
         public static BaseItem GetItemById(this ILibraryManager manager, string id)
         {
             return manager.GetItemById(new Guid(id));

+ 9 - 2
MediaBrowser.Controller/LiveTv/ILiveTvManager.cs

@@ -44,6 +44,13 @@ namespace MediaBrowser.Controller.LiveTv
         /// <returns>Task.</returns>
         Task DeleteRecording(string id);
 
+        /// <summary>
+        /// Deletes the recording.
+        /// </summary>
+        /// <param name="recording">The recording.</param>
+        /// <returns>Task.</returns>
+        Task DeleteRecording(ILiveTvRecording recording);
+        
         /// <summary>
         /// Cancels the timer.
         /// </summary>
@@ -338,9 +345,9 @@ namespace MediaBrowser.Controller.LiveTv
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="dto">The dto.</param>
-        /// <param name="addChannelInfo">if set to <c>true</c> [add channel information].</param>
+        /// <param name="fields">The fields.</param>
         /// <param name="user">The user.</param>
-        void AddInfoToProgramDto(BaseItem item, BaseItemDto dto, bool addChannelInfo, User user = null);
+        void AddInfoToProgramDto(BaseItem item, BaseItemDto dto, List<ItemFields> fields, User user = null);
         /// <summary>
         /// Saves the tuner host.
         /// </summary>

+ 14 - 3
MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs

@@ -9,6 +9,8 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Library;
 
 namespace MediaBrowser.Controller.LiveTv
 {
@@ -36,7 +38,6 @@ namespace MediaBrowser.Controller.LiveTv
         public bool IsLive { get; set; }
         [IgnoreDataMember]
         public bool IsPremiere { get; set; }
-        public ProgramAudio? Audio { get; set; }
 
         /// <summary>
         /// Gets the user data key.
@@ -106,9 +107,9 @@ namespace MediaBrowser.Controller.LiveTv
             }
         }
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.LiveTvProgram);
+            return UnratedItem.LiveTvProgram;
         }
 
         protected override string GetInternalMetadataPath(string basePath)
@@ -140,5 +141,15 @@ namespace MediaBrowser.Controller.LiveTv
 
             return list;
         }
+
+        public override bool IsVisibleStandalone(User user)
+        {
+            return IsVisible(user);
+        }
+
+        public override Task Delete(DeleteOptions options)
+        {
+            return LiveTvManager.DeleteRecording(this);
+        }
     }
 }

+ 2 - 2
MediaBrowser.Controller/LiveTv/LiveTvChannel.cs

@@ -22,9 +22,9 @@ namespace MediaBrowser.Controller.LiveTv
             return GetClientTypeName() + "-" + Name;
         }
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.LiveTvChannel);
+            return UnratedItem.LiveTvChannel;
         }
 
         /// <summary>

+ 23 - 25
MediaBrowser.Controller/LiveTv/LiveTvProgram.cs

@@ -40,10 +40,11 @@ namespace MediaBrowser.Controller.LiveTv
         }
 
         /// <summary>
-        /// Gets or sets the type of the channel.
+        /// Gets or sets the name.
         /// </summary>
-        /// <value>The type of the channel.</value>
-        public ChannelType ChannelType { get; set; }
+        /// <value>The name.</value>
+        [IgnoreDataMember]
+        public string ServiceName { get; set; }
 
         /// <summary>
         /// The start date of the program, in UTC.
@@ -51,12 +52,6 @@ namespace MediaBrowser.Controller.LiveTv
         [IgnoreDataMember]
         public DateTime StartDate { get; set; }
 
-        /// <summary>
-        /// Gets or sets the audio.
-        /// </summary>
-        /// <value>The audio.</value>
-        public ProgramAudio? Audio { get; set; }
-
         /// <summary>
         /// Gets or sets a value indicating whether this instance is repeat.
         /// </summary>
@@ -71,12 +66,6 @@ namespace MediaBrowser.Controller.LiveTv
         [IgnoreDataMember]
         public string EpisodeTitle { get; set; }
 
-        /// <summary>
-        /// Gets or sets the name of the service.
-        /// </summary>
-        /// <value>The name of the service.</value>
-        public string ServiceName { get; set; }
-
         /// <summary>
         /// Gets or sets a value indicating whether this instance is movie.
         /// </summary>
@@ -153,14 +142,14 @@ namespace MediaBrowser.Controller.LiveTv
             }
         }
 
-        [IgnoreDataMember]
-        public override string MediaType
-        {
-            get
-            {
-                return ChannelType == ChannelType.TV ? Model.Entities.MediaType.Video : Model.Entities.MediaType.Audio;
-            }
-        }
+        //[IgnoreDataMember]
+        //public override string MediaType
+        //{
+        //    get
+        //    {
+        //        return ChannelType == ChannelType.TV ? Model.Entities.MediaType.Video : Model.Entities.MediaType.Audio;
+        //    }
+        //}
 
         [IgnoreDataMember]
         public bool IsAiring
@@ -189,9 +178,9 @@ namespace MediaBrowser.Controller.LiveTv
             return "Program";
         }
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.LiveTvProgram);
+            return UnratedItem.LiveTvProgram;
         }
 
         protected override string GetInternalMetadataPath(string basePath)
@@ -236,5 +225,14 @@ namespace MediaBrowser.Controller.LiveTv
                 return base.SupportsPeople;
             }
         }
+
+        [IgnoreDataMember]
+        public override bool SupportsAncestors
+        {
+            get
+            {
+                return false;
+            }
+        }
     }
 }

+ 14 - 3
MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs

@@ -9,6 +9,8 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Library;
 
 namespace MediaBrowser.Controller.LiveTv
 {
@@ -36,7 +38,6 @@ namespace MediaBrowser.Controller.LiveTv
         public bool IsLive { get; set; }
         [IgnoreDataMember]
         public bool IsPremiere { get; set; }
-        public ProgramAudio? Audio { get; set; }
 
         /// <summary>
         /// Gets the user data key.
@@ -121,9 +122,9 @@ namespace MediaBrowser.Controller.LiveTv
             }
         }
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.LiveTvProgram);
+            return UnratedItem.LiveTvProgram;
         }
 
         protected override string GetInternalMetadataPath(string basePath)
@@ -155,5 +156,15 @@ namespace MediaBrowser.Controller.LiveTv
 
             return list;
         }
+
+        public override bool IsVisibleStandalone(User user)
+        {
+            return IsVisible(user);
+        }
+
+        public override Task Delete(DeleteOptions options)
+        {
+            return LiveTvManager.DeleteRecording(this);
+        }
     }
 }

+ 6 - 0
MediaBrowser.Controller/LiveTv/RecordingGroup.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.LiveTv
@@ -11,6 +12,11 @@ namespace MediaBrowser.Controller.LiveTv
             return false;
         }
 
+        public override UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.LiveTvProgram;
+        }
+
         public override bool SupportsLocalMetadata
         {
             get

+ 1 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -162,6 +162,7 @@
     <Compile Include="Entities\IHasThemeMedia.cs" />
     <Compile Include="Entities\IHasTrailers.cs" />
     <Compile Include="Entities\IHasUserData.cs" />
+    <Compile Include="Entities\IHiddenFromDisplay.cs" />
     <Compile Include="Entities\IItemByName.cs" />
     <Compile Include="Entities\ILibraryItem.cs" />
     <Compile Include="Entities\ImageSourceInfo.cs" />

+ 14 - 0
MediaBrowser.Controller/Persistence/IItemRepository.cs

@@ -176,6 +176,20 @@ namespace MediaBrowser.Controller.Persistence
         /// <param name="query">The query.</param>
         /// <returns>QueryResult&lt;Tuple&lt;Guid, System.String&gt;&gt;.</returns>
         QueryResult<Tuple<Guid, string>> GetItemIdsWithPath(InternalItemsQuery query);
+
+        /// <summary>
+        /// Gets the item list.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <returns>List&lt;BaseItem&gt;.</returns>
+        IEnumerable<BaseItem> GetItemList(InternalItemsQuery query);
+
+        /// <summary>
+        /// Updates the inherited values.
+        /// </summary>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task UpdateInheritedValues(CancellationToken cancellationToken);
     }
 }
 

+ 1 - 0
MediaBrowser.Controller/Playlists/Playlist.cs

@@ -144,6 +144,7 @@ namespace MediaBrowser.Controller.Playlists
 
         public string PlaylistMediaType { get; set; }
 
+        [IgnoreDataMember]
         public override string MediaType
         {
             get

+ 4 - 0
MediaBrowser.Controller/Providers/BaseItemXmlParser.cs

@@ -889,6 +889,10 @@ namespace MediaBrowser.Controller.Providers
                             {
                                 video.Video3DFormat = Video3DFormat.FullSideBySide;
                             }
+                            else if (string.Equals("MVC", val, StringComparison.OrdinalIgnoreCase))
+                            {
+                                video.Video3DFormat = Video3DFormat.MVC;
+                            }
                         }
                         break;
                     }

+ 0 - 32
MediaBrowser.Controller/Providers/MetadataStatus.cs

@@ -10,24 +10,6 @@ namespace MediaBrowser.Controller.Providers
         /// <value>The item identifier.</value>
         public Guid ItemId { get; set; }
 
-        /// <summary>
-        /// Gets or sets the name of the item.
-        /// </summary>
-        /// <value>The name of the item.</value>
-        public string ItemName { get; set; }
-
-        /// <summary>
-        /// Gets or sets the type of the item.
-        /// </summary>
-        /// <value>The type of the item.</value>
-        public string ItemType { get; set; }
-        
-        /// <summary>
-        /// Gets or sets the name of the series.
-        /// </summary>
-        /// <value>The name of the series.</value>
-        public string SeriesName { get; set; }
-
         /// <summary>
         /// Gets or sets the date last metadata refresh.
         /// </summary>
@@ -40,22 +22,8 @@ namespace MediaBrowser.Controller.Providers
         /// <value>The date last images refresh.</value>
         public DateTime? DateLastImagesRefresh { get; set; }
 
-        /// <summary>
-        /// Gets or sets the last result error message.
-        /// </summary>
-        /// <value>The last result error message.</value>
-        public string LastErrorMessage { get; set; }
-
         public DateTime? ItemDateModified { get; set; }
 
-        public void AddStatus(string errorMessage)
-        {
-            if (string.IsNullOrEmpty(LastErrorMessage))
-            {
-                LastErrorMessage = errorMessage;
-            }
-        }
-
         public bool IsDirty { get; private set; }
 
         public void SetDateLastMetadataRefresh(DateTime? date)

+ 3 - 2
MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Library;
+using CommonIO;
+using MediaBrowser.Controller.Entities;
 
 namespace MediaBrowser.Controller.Resolvers
 {
@@ -7,6 +8,6 @@ namespace MediaBrowser.Controller.Resolvers
     /// </summary>
     public interface IResolverIgnoreRule
     {
-        bool ShouldIgnore(ItemResolveArgs args);
+        bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent);
     }
 }

+ 5 - 2
MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs

@@ -26,6 +26,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
         private readonly ILocalizationManager _localization;
         private readonly IChannelManager _channelManager;
         private readonly IMediaSourceManager _mediaSourceManager;
+        private readonly IUserViewManager _userViewManager;
 
         public ContentDirectory(IDlnaManager dlna,
             IUserDataManager userDataManager,
@@ -34,7 +35,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
             IServerConfigurationManager config,
             IUserManager userManager,
             ILogger logger,
-            IHttpClient httpClient, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager)
+            IHttpClient httpClient, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager)
             : base(logger, httpClient)
         {
             _dlna = dlna;
@@ -46,6 +47,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
             _localization = localization;
             _channelManager = channelManager;
             _mediaSourceManager = mediaSourceManager;
+            _userViewManager = userViewManager;
         }
 
         private int SystemUpdateId
@@ -86,7 +88,8 @@ namespace MediaBrowser.Dlna.ContentDirectory
                 _config,
                 _localization,
                 _channelManager,
-                _mediaSourceManager)
+                _mediaSourceManager,
+                _userViewManager)
                 .ProcessControlRequest(request);
         }
 

+ 37 - 24
MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs

@@ -24,6 +24,7 @@ using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Xml;
+using MediaBrowser.Model.Library;
 
 namespace MediaBrowser.Dlna.ContentDirectory
 {
@@ -34,6 +35,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
         private readonly IUserDataManager _userDataManager;
         private readonly IServerConfigurationManager _config;
         private readonly User _user;
+        private readonly IUserViewManager _userViewManager;
 
         private const string NS_DC = "http://purl.org/dc/elements/1.1/";
         private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
@@ -47,7 +49,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
 
         private readonly DeviceProfile _profile;
 
-        public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager)
+        public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager)
             : base(config, logger)
         {
             _libraryManager = libraryManager;
@@ -55,6 +57,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
             _user = user;
             _systemUpdateId = systemUpdateId;
             _channelManager = channelManager;
+            _userViewManager = userViewManager;
             _profile = profile;
             _config = config;
 
@@ -450,16 +453,32 @@ namespace MediaBrowser.Dlna.ContentDirectory
                 sortOrders.Add(ItemSortBy.SortName);
             }
 
-            var queryResult = await folder.GetItems(new InternalItemsQuery
+            QueryResult<BaseItem> queryResult;
+
+            if (folder is UserRootFolder)
             {
-                Limit = limit,
-                StartIndex = startIndex,
-                SortBy = sortOrders.ToArray(),
-                SortOrder = sort.SortOrder,
-                User = user,
-                Filter = FilterUnsupportedContent
+                var views = await _userViewManager.GetUserViews(new UserViewQuery { UserId = user.Id.ToString("N"), PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows, CollectionType.Music } }, CancellationToken.None)
+                            .ConfigureAwait(false);
+
+                queryResult = new QueryResult<BaseItem>
+                {
+                    Items = views.Cast<BaseItem>().ToArray()
+                };
+                queryResult.TotalRecordCount = queryResult.Items.Length;
+            }
+            else
+            {
+                queryResult = await folder.GetItems(new InternalItemsQuery
+               {
+                   Limit = limit,
+                   StartIndex = startIndex,
+                   SortBy = sortOrders.ToArray(),
+                   SortOrder = sort.SortOrder,
+                   User = user,
+                   Filter = FilterUnsupportedContent
 
-            }).ConfigureAwait(false);
+               }).ConfigureAwait(false);
+            }
 
             var options = _config.GetDlnaConfiguration();
 
@@ -481,23 +500,17 @@ namespace MediaBrowser.Dlna.ContentDirectory
 
         private QueryResult<ServerItem> GetItemsFromPerson(Person person, User user, int? startIndex, int? limit)
         {
-            var itemsWithPerson = _libraryManager.GetItems(new InternalItemsQuery
+            var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
             {
-                Person = person.Name
-
-            }).Items;
-
-            var items = itemsWithPerson
-                .Where(i => i is Movie || i is Series || i is IChannelItem)
-                .Where(i => i.IsVisibleStandalone(user))
-                .ToList();
+                Person = person.Name,
+                IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name, typeof(ChannelVideoItem).Name },
+                SortBy = new[] { ItemSortBy.SortName },
+                Limit = limit,
+                StartIndex = startIndex
 
-            items = _libraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending)
-                .Skip(startIndex ?? 0)
-                .Take(limit ?? int.MaxValue)
-                .ToList();
+            }, new string[] { });
 
-            var serverItems = items.Select(i => new ServerItem
+            var serverItems = itemsResult.Items.Select(i => new ServerItem
             {
                 Item = i,
                 StubType = null
@@ -506,7 +519,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
 
             return new QueryResult<ServerItem>
             {
-                TotalRecordCount = serverItems.Length,
+                TotalRecordCount = itemsResult.TotalRecordCount,
                 Items = serverItems
             };
         }

+ 1 - 1
MediaBrowser.Dlna/Didl/DidlBuilder.cs

@@ -966,7 +966,7 @@ namespace MediaBrowser.Dlna.Didl
                 }
             }
 
-            item = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Primary));
+            item = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary));
 
             if (item != null)
             {

+ 8 - 2
MediaBrowser.Dlna/DlnaManager.cs

@@ -210,6 +210,10 @@ namespace MediaBrowser.Dlna
                 throw new ArgumentNullException("headers");
             }
 
+            //_logger.Debug("GetProfile. Headers: " + _jsonSerializer.SerializeToString(headers));
+            // Convert to case insensitive
+            headers = new Dictionary<string, string>(headers, StringComparer.OrdinalIgnoreCase);
+
             var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
 
             if (profile != null)
@@ -221,7 +225,7 @@ namespace MediaBrowser.Dlna
                 string userAgent = null;
                 headers.TryGetValue("User-Agent", out userAgent);
 
-                var msg = "No matching device profile found. The default will be used. ";
+                var msg = "No matching device profile via headers found. The default will be used. ";
                 if (!string.IsNullOrEmpty(userAgent))
                 {
                     msg += "User-agent: " + userAgent + ". ";
@@ -249,7 +253,9 @@ namespace MediaBrowser.Dlna
                     case HeaderMatchType.Equals:
                         return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
                     case HeaderMatchType.Substring:
-                        return value.IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
+                        var isMatch = value.IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
+                        //_logger.Debug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
+                        return isMatch;
                     case HeaderMatchType.Regex:
                         // Reports of IgnoreCase not working on linux so try it a couple different ways.
                         return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase) || Regex.IsMatch(value.ToUpper(), header.Value.ToUpper(), RegexOptions.IgnoreCase);

+ 1 - 1
MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs

@@ -114,7 +114,7 @@ namespace MediaBrowser.Dlna.PlayTo
             {
                 Url = url,
                 UserAgent = USERAGENT,
-                LogRequest = logRequest || _config.GetDlnaConfiguration().EnableDebugLogging,
+                LogRequest = logRequest || _config.GetDlnaConfiguration().EnableDebugLog,
                 LogErrorResponseBody = true
             };
 

+ 1 - 1
MediaBrowser.Dlna/Server/DescriptionXmlBuilder.cs

@@ -122,7 +122,7 @@ namespace MediaBrowser.Dlna.Server
                 builder.Append("<serialNumber>" + SecurityElement.Escape(_profile.SerialNumber) + "</serialNumber>");
             }
 
-            builder.Append("<UDN>uuid:" + SecurityElement.Escape(_serverUdn) + "</UDN>");
+            builder.Append("<UDN>uuid:" + SecurityElement.Escape(_serverId) + "</UDN>");
             builder.Append("<presentationURL>" + SecurityElement.Escape(_serverAddress) + "</presentationURL>");
 
             if (!EnableAbsoluteUrls)

+ 1 - 1
MediaBrowser.Dlna/Service/BaseControlHandler.cs

@@ -27,7 +27,7 @@ namespace MediaBrowser.Dlna.Service
         {
             try
             {
-                var enableDebugLogging = Config.GetDlnaConfiguration().EnableDebugLogging;
+                var enableDebugLogging = Config.GetDlnaConfiguration().EnableDebugLog;
 
                 if (enableDebugLogging)
                 {

+ 1 - 1
MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs

@@ -217,7 +217,7 @@ namespace MediaBrowser.Dlna.Ssdp
                 return;
             }
 
-            if (_config.GetDlnaConfiguration().EnableDebugLogging)
+            if (_config.GetDlnaConfiguration().EnableDebugLog)
             {
                 var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
                 var headerText = string.Join(",", headerTexts.ToArray());

+ 145 - 29
MediaBrowser.Dlna/Ssdp/SsdpHandler.cs

@@ -21,7 +21,7 @@ namespace MediaBrowser.Dlna.Ssdp
 {
     public class SsdpHandler : IDisposable, ISsdpHandler
     {
-        private Socket _socket;
+        private Socket _multicastSocket;
 
         private readonly ILogger _logger;
         private readonly IServerConfigurationManager _config;
@@ -40,6 +40,9 @@ namespace MediaBrowser.Dlna.Ssdp
 
         private readonly IApplicationHost _appHost;
 
+        private readonly int _unicastPort = 1901;
+        private UdpClient _unicastClient;
+
         public SsdpHandler(ILogger logger, IServerConfigurationManager config, IApplicationHost appHost)
         {
             _logger = logger;
@@ -92,7 +95,7 @@ namespace MediaBrowser.Dlna.Ssdp
             {
                 TimeSpan delay = GetSearchDelay(headers);
 
-                if (_config.GetDlnaConfiguration().EnableDebugLogging)
+                if (_config.GetDlnaConfiguration().EnableDebugLog)
                 {
                     _logger.Debug("Delaying search response by {0} seconds", delay.TotalSeconds);
                 }
@@ -123,6 +126,8 @@ namespace MediaBrowser.Dlna.Ssdp
             RestartSocketListener();
             ReloadAliveNotifier();
 
+            //CreateUnicastClient();
+
             SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
             SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
         }
@@ -150,26 +155,29 @@ namespace MediaBrowser.Dlna.Ssdp
             // Seconds to delay response
             values["MX"] = "3";
 
+            var header = "M-SEARCH * HTTP/1.1";
+
+            var msg = new SsdpMessageBuilder().BuildMessage(header, values);
+
             // UDP is unreliable, so send 3 requests at a time (per Upnp spec, sec 1.1.2)
-            SendDatagram("M-SEARCH * HTTP/1.1", values, _ssdpEndp, localIp, true, 2);
+            SendDatagram(msg, _ssdpEndp, localIp, true);
+
+            //SendUnicastRequest(msg);
         }
 
-        public async void SendDatagram(string header,
-            Dictionary<string, string> values,
+        public async void SendDatagram(string msg,
             EndPoint endpoint,
             EndPoint localAddress,
             bool isBroadcast,
-            int sendCount)
+            int sendCount = 3)
         {
-            var msg = new SsdpMessageBuilder().BuildMessage(header, values);
-
-            var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLogging;
+            var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
 
             for (var i = 0; i < sendCount; i++)
             {
                 if (i > 0)
                 {
-                    await Task.Delay(500).ConfigureAwait(false);
+                    await Task.Delay(200).ConfigureAwait(false);
                 }
 
                 var dgram = new Datagram(endpoint, localAddress, _logger, msg, isBroadcast, enableDebugLogging);
@@ -202,7 +210,7 @@ namespace MediaBrowser.Dlna.Ssdp
 
         private void RespondToSearch(EndPoint endpoint, string deviceType)
         {
-            var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLogging;
+            var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
 
             var isLogged = false;
 
@@ -232,8 +240,10 @@ namespace MediaBrowser.Dlna.Ssdp
                     values["ST"] = d.Type;
                     values["USN"] = d.USN;
 
-                    SendDatagram(header, values, endpoint, null, false, 1);
-                    SendDatagram(header, values, endpoint, new IPEndPoint(d.Address, 0), false, 1);
+                    var msg = new SsdpMessageBuilder().BuildMessage(header, values);
+
+                    SendDatagram(msg, endpoint, null, false, 1);
+                    SendDatagram(msg, endpoint, new IPEndPoint(d.Address, 0), false, 1);
                     //SendDatagram(header, values, endpoint, null, true);
 
                     if (enableDebugLogging)
@@ -253,7 +263,7 @@ namespace MediaBrowser.Dlna.Ssdp
 
             try
             {
-                _socket = CreateMulticastSocket();
+                _multicastSocket = CreateMulticastSocket();
 
                 _logger.Info("MultiCast socket created");
 
@@ -274,8 +284,7 @@ namespace MediaBrowser.Dlna.Ssdp
 
                 EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort);
 
-                _socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref endpoint, ReceiveCallback,
-                    buffer);
+                _multicastSocket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref endpoint, ReceiveCallback, buffer);
             }
             catch (ObjectDisposedException)
             {
@@ -301,11 +310,11 @@ namespace MediaBrowser.Dlna.Ssdp
             {
                 EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort);
 
-                var length = _socket.EndReceiveFrom(result, ref endpoint);
+                var length = _multicastSocket.EndReceiveFrom(result, ref endpoint);
 
                 var received = (byte[])result.AsyncState;
 
-                var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLogging;
+                var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
 
                 if (enableDebugLogging)
                 {
@@ -325,7 +334,7 @@ namespace MediaBrowser.Dlna.Ssdp
                     var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
                     var headerText = string.Join(",", headerTexts.ToArray());
 
-                    _logger.Debug("{0} message received from {1} on {3}. Headers: {2}", args.Method, args.EndPoint, headerText, _socket.LocalEndPoint);
+                    _logger.Debug("{0} message received from {1} on {3}. Headers: {2}", args.Method, args.EndPoint, headerText, _multicastSocket.LocalEndPoint);
                 }
 
                 OnMessageReceived(args);
@@ -342,7 +351,7 @@ namespace MediaBrowser.Dlna.Ssdp
                 _logger.ErrorException("Failed to read SSDP message", ex);
             }
 
-            if (_socket != null)
+            if (_multicastSocket != null)
             {
                 Receive();
             }
@@ -375,17 +384,18 @@ namespace MediaBrowser.Dlna.Ssdp
 
             _isDisposed = true;
 
+            DisposeUnicastClient();
             DisposeSocket();
             StopAliveNotifier();
         }
 
         private void DisposeSocket()
         {
-            if (_socket != null)
+            if (_multicastSocket != null)
             {
-                _socket.Close();
-                _socket.Dispose();
-                _socket = null;
+                _multicastSocket.Close();
+                _multicastSocket.Dispose();
+                _multicastSocket = null;
             }
         }
 
@@ -404,7 +414,7 @@ namespace MediaBrowser.Dlna.Ssdp
 
         private void NotifyAll()
         {
-            var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLogging;
+            var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
 
             if (enableDebugLogging)
             {
@@ -412,11 +422,11 @@ namespace MediaBrowser.Dlna.Ssdp
             }
             foreach (var d in RegisteredDevices)
             {
-                NotifyDevice(d, "alive", 1, enableDebugLogging);
+                NotifyDevice(d, "alive", enableDebugLogging);
             }
         }
 
-        private void NotifyDevice(UpnpDevice dev, string type, int sendCount, bool logMessage)
+        private void NotifyDevice(UpnpDevice dev, string type, bool logMessage)
         {
             const string header = "NOTIFY * HTTP/1.1";
 
@@ -436,7 +446,9 @@ namespace MediaBrowser.Dlna.Ssdp
                 _logger.Debug("{0} said {1}", dev.USN, type);
             }
 
-            SendDatagram(header, values, _ssdpEndp, new IPEndPoint(dev.Address, 0), true, sendCount);
+            var msg = new SsdpMessageBuilder().BuildMessage(header, values);
+
+            SendDatagram(msg, _ssdpEndp, new IPEndPoint(dev.Address, 0), true);
         }
 
         public void RegisterNotification(Guid uuid, Uri descriptionUri, IPAddress address, IEnumerable<string> services)
@@ -457,13 +469,111 @@ namespace MediaBrowser.Dlna.Ssdp
 
                 foreach (var d in dl.ToList())
                 {
-                    NotifyDevice(d, "byebye", 2, true);
+                    NotifyDevice(d, "byebye", true);
                 }
 
                 _logger.Debug("Unregistered mount {0}", uuid);
             }
         }
 
+        private void CreateUnicastClient()
+        {
+            if (_unicastClient == null)
+            {
+                try
+                {
+                    _unicastClient = new UdpClient(_unicastPort);
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error creating unicast client", ex);
+                }
+
+                try
+                {
+                    UnicastSetBeginReceive();
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error in UnicastSetBeginReceive", ex);
+                }
+            }
+        }
+
+        private void DisposeUnicastClient()
+        {
+            if (_unicastClient != null)
+            {
+                try
+                {
+                    _unicastClient.Close();
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error closing unicast client", ex);
+                }
+
+                _unicastClient = null;
+            }
+        }
+
+        /// <summary>
+        /// Listen for Unicast SSDP Responses
+        /// </summary>
+        private void UnicastSetBeginReceive()
+        {
+            var ipRxEnd = new IPEndPoint(IPAddress.Any, _unicastPort);
+            var udpListener = new UdpState { EndPoint = ipRxEnd };
+
+            udpListener.UdpClient = _unicastClient;
+            _unicastClient.BeginReceive(UnicastReceiveCallback, udpListener);
+        }
+
+        /// <summary>
+        /// The UnicastReceiveCallback receives Http Responses 
+        /// and Fired the SatIpDeviceFound Event for adding the SatIpDevice  
+        /// </summary>
+        /// <param name="ar"></param>
+        private void UnicastReceiveCallback(IAsyncResult ar)
+        {
+            var udpClient = ((UdpState)(ar.AsyncState)).UdpClient;
+            var endpoint = ((UdpState)(ar.AsyncState)).EndPoint;
+            if (udpClient.Client != null)
+            {
+                var responseBytes = udpClient.EndReceive(ar, ref endpoint);
+                var args = SsdpHelper.ParseSsdpResponse(responseBytes);
+
+                args.EndPoint = endpoint;
+
+                OnMessageReceived(args);
+
+                UnicastSetBeginReceive();
+            }
+        }
+
+        private async void SendUnicastRequest(string request)
+        {
+            if (_unicastClient == null)
+            {
+                return;
+            }
+
+            _logger.Debug("Sending unicast search request");
+
+            byte[] req = Encoding.ASCII.GetBytes(request);
+            var ipSsdp = IPAddress.Parse(SSDPAddr);
+            var ipTxEnd = new IPEndPoint(ipSsdp, SSDPPort);
+
+            for (var i = 0; i < 3; i++)
+            {
+                if (i > 0)
+                {
+                    await Task.Delay(50).ConfigureAwait(false);
+                }
+                _unicastClient.Send(req, req.Length, ipTxEnd);
+            }
+        }
+
         private readonly object _notificationTimerSyncLock = new object();
         private int _aliveNotifierIntervalMs;
         private void ReloadAliveNotifier()
@@ -511,5 +621,11 @@ namespace MediaBrowser.Dlna.Ssdp
                 }
             }
         }
+
+        public class UdpState
+        {
+            public UdpClient UdpClient;
+            public IPEndPoint EndPoint;
+        }
     }
 }

+ 1 - 1
MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs

@@ -176,7 +176,7 @@ namespace MediaBrowser.LocalMetadata.Images
                 "default"
             };
 
-            if (item is MusicAlbum || item is MusicArtist)
+            if (item is MusicAlbum || item is MusicArtist || item is Photo)
             {
                 // these prefer folder
                 names.Insert(0, "poster");

+ 3 - 0
MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs

@@ -736,6 +736,9 @@ namespace MediaBrowser.LocalMetadata.Savers
                         case Video3DFormat.HalfTopAndBottom:
                             builder.Append("<Format3D>HTAB</Format3D>");
                             break;
+                        case Video3DFormat.MVC:
+                            builder.Append("<Format3D>MVC</Format3D>");
+                            break;
                     }
                 }
             }

+ 13 - 11
MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs

@@ -19,38 +19,40 @@ namespace MediaBrowser.MediaEncoding.Encoder
         {
         }
 
-        protected override string GetCommandLineArguments(EncodingJob job)
+        protected override string GetCommandLineArguments(EncodingJob state)
         {
             var audioTranscodeParams = new List<string>();
 
-            var bitrate = job.OutputAudioBitrate;
+            var bitrate = state.OutputAudioBitrate;
 
             if (bitrate.HasValue)
             {
                 audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(UsCulture));
             }
 
-            if (job.OutputAudioChannels.HasValue)
+            if (state.OutputAudioChannels.HasValue)
             {
-                audioTranscodeParams.Add("-ac " + job.OutputAudioChannels.Value.ToString(UsCulture));
+                audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(UsCulture));
             }
 
-            if (job.OutputAudioSampleRate.HasValue)
+            if (state.OutputAudioSampleRate.HasValue)
             {
-                audioTranscodeParams.Add("-ar " + job.OutputAudioSampleRate.Value.ToString(UsCulture));
+                audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(UsCulture));
             }
 
-            var threads = GetNumberOfThreads(job, false);
+            const string vn = " -vn";
 
-            var inputModifier = GetInputModifier(job);
+            var threads = GetNumberOfThreads(state, false);
+
+            var inputModifier = GetInputModifier(state);
 
             return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
                 inputModifier,
-                GetInputArgument(job),
+                GetInputArgument(state),
                 threads,
-                " -vn",
+                vn,
                 string.Join(" ", audioTranscodeParams.ToArray()),
-                job.OutputFilePath).Trim();
+                state.OutputFilePath).Trim();
         }
 
         protected override string GetOutputFileExtension(EncodingJob state)

+ 138 - 77
MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs

@@ -303,15 +303,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
             return job.Options.CpuCoreLimit ?? 0;
         }
 
-        protected string GetInputModifier(EncodingJob job, bool genPts = true)
+        protected string GetInputModifier(EncodingJob state, bool genPts = true)
         {
             var inputModifier = string.Empty;
 
-            var probeSize = GetProbeSizeArgument(job);
+            var probeSize = GetProbeSizeArgument(state);
             inputModifier += " " + probeSize;
             inputModifier = inputModifier.Trim();
 
-            var userAgentParam = GetUserAgentParam(job);
+            var userAgentParam = GetUserAgentParam(state);
 
             if (!string.IsNullOrWhiteSpace(userAgentParam))
             {
@@ -320,35 +320,43 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
             inputModifier = inputModifier.Trim();
 
-            inputModifier += " " + GetFastSeekCommandLineParameter(job.Options);
+            inputModifier += " " + GetFastSeekCommandLineParameter(state.Options);
             inputModifier = inputModifier.Trim();
 
-            if (job.IsVideoRequest && genPts)
+            if (state.IsVideoRequest && genPts)
             {
                 inputModifier += " -fflags +genpts";
             }
 
-            if (!string.IsNullOrEmpty(job.InputAudioSync))
+            if (!string.IsNullOrEmpty(state.InputAudioSync))
             {
-                inputModifier += " -async " + job.InputAudioSync;
+                inputModifier += " -async " + state.InputAudioSync;
             }
 
-            if (!string.IsNullOrEmpty(job.InputVideoSync))
+            if (!string.IsNullOrEmpty(state.InputVideoSync))
             {
-                inputModifier += " -vsync " + job.InputVideoSync;
+                inputModifier += " -vsync " + state.InputVideoSync;
             }
 
-            if (job.ReadInputAtNativeFramerate)
+            if (state.ReadInputAtNativeFramerate)
             {
                 inputModifier += " -re";
             }
 
-            var videoDecoder = GetVideoDecoder(job);
+            var videoDecoder = GetVideoDecoder(state);
             if (!string.IsNullOrWhiteSpace(videoDecoder))
             {
                 inputModifier += " " + videoDecoder;
             }
 
+            //if (state.IsVideoRequest)
+            //{
+            //    if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase))
+            //    {
+            //        //inputModifier += " -noaccurate_seek";
+            //    }
+            //}
+
             return inputModifier;
         }
 
@@ -392,11 +400,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
             return null;
         }
 
-        private string GetUserAgentParam(EncodingJob job)
+        private string GetUserAgentParam(EncodingJob state)
         {
             string useragent = null;
 
-            job.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
+            state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
 
             if (!string.IsNullOrWhiteSpace(useragent))
             {
@@ -409,31 +417,31 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// <summary>
         /// Gets the probe size argument.
         /// </summary>
-        /// <param name="job">The job.</param>
+        /// <param name="state">The state.</param>
         /// <returns>System.String.</returns>
-        private string GetProbeSizeArgument(EncodingJob job)
+        private string GetProbeSizeArgument(EncodingJob state)
         {
-            if (job.PlayableStreamFileNames.Count > 0)
+            if (state.PlayableStreamFileNames.Count > 0)
             {
-                return MediaEncoder.GetProbeSizeArgument(job.PlayableStreamFileNames.ToArray(), job.InputProtocol);
+                return MediaEncoder.GetProbeSizeArgument(state.PlayableStreamFileNames.ToArray(), state.InputProtocol);
             }
 
-            return MediaEncoder.GetProbeSizeArgument(new[] { job.MediaPath }, job.InputProtocol);
+            return MediaEncoder.GetProbeSizeArgument(new[] { state.MediaPath }, state.InputProtocol);
         }
 
         /// <summary>
         /// Gets the fast seek command line parameter.
         /// </summary>
-        /// <param name="options">The options.</param>
+        /// <param name="request">The request.</param>
         /// <returns>System.String.</returns>
         /// <value>The fast seek command line parameter.</value>
-        protected string GetFastSeekCommandLineParameter(EncodingJobOptions options)
+        protected string GetFastSeekCommandLineParameter(EncodingJobOptions request)
         {
-            var time = options.StartTimeTicks;
+            var time = request.StartTimeTicks ?? 0;
 
-            if (time.HasValue && time.Value > 0)
+            if (time > 0)
             {
-                return string.Format("-ss {0}", MediaEncoder.GetTimeParameter(time.Value));
+                return string.Format("-ss {0}", MediaEncoder.GetTimeParameter(time));
             }
 
             return string.Empty;
@@ -442,34 +450,35 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// <summary>
         /// Gets the input argument.
         /// </summary>
-        /// <param name="job">The job.</param>
+        /// <param name="state">The state.</param>
         /// <returns>System.String.</returns>
-        protected string GetInputArgument(EncodingJob job)
+        protected string GetInputArgument(EncodingJob state)
         {
-            var arg = "-i " + GetInputPathArgument(job);
+            var arg = string.Format("-i {0}", GetInputPathArgument(state));
 
-            if (job.SubtitleStream != null)
+            if (state.SubtitleStream != null)
             {
-                if (job.SubtitleStream.IsExternal && !job.SubtitleStream.IsTextSubtitleStream)
+                if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
                 {
-                    arg += " -i \"" + job.SubtitleStream.Path + "\"";
+                    arg += " -i \"" + state.SubtitleStream.Path + "\"";
                 }
             }
 
-            return arg;
+            return arg.Trim();
         }
 
-        private string GetInputPathArgument(EncodingJob job)
+        private string GetInputPathArgument(EncodingJob state)
         {
-            var protocol = job.InputProtocol;
+            var protocol = state.InputProtocol;
+            var mediaPath = state.MediaPath ?? string.Empty;
 
-            var inputPath = new[] { job.MediaPath };
+            var inputPath = new[] { mediaPath };
 
-            if (job.IsInputVideo)
+            if (state.IsInputVideo)
             {
-                if (!(job.VideoType == VideoType.Iso && job.IsoMount == null))
+                if (!(state.VideoType == VideoType.Iso && state.IsoMount == null))
                 {
-                    inputPath = MediaEncoderHelpers.GetInputArgument(FileSystem, job.MediaPath, job.InputProtocol, job.IsoMount, job.PlayableStreamFileNames);
+                    inputPath = MediaEncoderHelpers.GetInputArgument(FileSystem, mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames);
                 }
             }
 
@@ -491,7 +500,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
                 }, false, cancellationToken).ConfigureAwait(false);
 
-                AttachMediaStreamInfo(state, liveStreamResponse.MediaSource, state.Options);
+                AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.Options);
 
                 if (state.IsVideoRequest)
                 {
@@ -505,11 +514,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
             }
         }
 
-        private void AttachMediaStreamInfo(EncodingJob state,
+        private void AttachMediaSourceInfo(EncodingJob state,
           MediaSourceInfo mediaSource,
           EncodingJobOptions videoRequest)
         {
-            EncodingJobFactory.AttachMediaStreamInfo(state, mediaSource, videoRequest);
+            EncodingJobFactory.AttachMediaSourceInfo(state, mediaSource, videoRequest);
         }
 
         /// <summary>
@@ -559,9 +568,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// </summary>
         /// <param name="state">The state.</param>
         /// <param name="videoCodec">The video codec.</param>
-        /// <param name="isHls">if set to <c>true</c> [is HLS].</param>
         /// <returns>System.String.</returns>
-        protected string GetVideoQualityParam(EncodingJob state, string videoCodec, bool isHls)
+        protected string GetVideoQualityParam(EncodingJob state, string videoCodec)
         {
             var param = string.Empty;
 
@@ -572,7 +580,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             {
                 param = "-preset superfast";
 
-                param += " -crf 28";
+                param += " -crf 23";
             }
 
             else if (string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
@@ -582,6 +590,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 param += " -crf 28";
             }
 
+            // h264 (h264_qsv)
+            else if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+            {
+                param = "-preset 7 -look_ahead 0";
+
+            }
+
+            // h264 (libnvenc)
+            else if (string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase))
+            {
+                param = "-preset high-performance";
+            }
+
             // webm
             else if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
             {
@@ -626,7 +647,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 param = "-mbd 2";
             }
 
-            param += GetVideoBitrateParam(state, videoCodec, isHls);
+            param += GetVideoBitrateParam(state, videoCodec);
 
             var framerate = GetFramerateParam(state);
             if (framerate.HasValue)
@@ -644,29 +665,66 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 param += " -profile:v " + state.Options.Profile;
             }
 
-            if (state.Options.Level.HasValue)
+            var levelString = state.Options.Level.HasValue ? state.Options.Level.Value.ToString(CultureInfo.InvariantCulture) : null;
+
+            if (!string.IsNullOrEmpty(levelString))
             {
-                param += " -level " + state.Options.Level.Value.ToString(UsCulture);
+                var h264Encoder = EncodingJobFactory.GetH264Encoder(state, GetEncodingOptions());
+
+                // h264_qsv and libnvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
+                if (String.Equals(h264Encoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || String.Equals(h264Encoder, "libnvenc", StringComparison.OrdinalIgnoreCase))
+                {
+                    switch (levelString)
+                    {
+                        case "30":
+                            param += " -level 3";
+                            break;
+                        case "31":
+                            param += " -level 3.1";
+                            break;
+                        case "32":
+                            param += " -level 3.2";
+                            break;
+                        case "40":
+                            param += " -level 4";
+                            break;
+                        case "41":
+                            param += " -level 4.1";
+                            break;
+                        case "42":
+                            param += " -level 4.2";
+                            break;
+                        case "50":
+                            param += " -level 5";
+                            break;
+                        case "51":
+                            param += " -level 5.1";
+                            break;
+                        case "52":
+                            param += " -level 5.2";
+                            break;
+                        default:
+                            param += " -level " + levelString;
+                            break;
+                    }
+                }
+                else
+                {
+                    param += " -level " + levelString;
+                }
             }
 
             return "-pix_fmt yuv420p " + param;
         }
 
-        protected string GetVideoBitrateParam(EncodingJob state, string videoCodec, bool isHls)
+        protected string GetVideoBitrateParam(EncodingJob state, string videoCodec)
         {
             var bitrate = state.OutputVideoBitrate;
 
             if (bitrate.HasValue)
             {
-                var hasFixedResolution = state.Options.HasFixedResolution;
-
                 if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
                 {
-                    if (hasFixedResolution)
-                    {
-                        return string.Format(" -minrate:v ({0}*.90) -maxrate:v ({0}*1.10) -bufsize:v {0} -b:v {0}", bitrate.Value.ToString(UsCulture));
-                    }
-
                     // With vpx when crf is used, b:v becomes a max rate
                     // https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up.
                     return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture));
@@ -677,18 +735,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
                     return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
                 }
 
-                // H264
-                if (hasFixedResolution)
-                {
-                    if (isHls)
-                    {
-                        return string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture));
-                    }
-
-                    return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
-                }
-
-                return string.Format(" -maxrate {0} -bufsize {1}",
+                // h264
+                return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
                     bitrate.Value.ToString(UsCulture),
                     (bitrate.Value * 2).ToString(UsCulture));
             }
@@ -698,20 +746,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
         protected double? GetFramerateParam(EncodingJob state)
         {
-            if (state.Options.Framerate.HasValue)
+            if (state.Options != null)
             {
-                return state.Options.Framerate.Value;
-            }
+                if (state.Options.Framerate.HasValue)
+                {
+                    return state.Options.Framerate.Value;
+                }
 
-            var maxrate = state.Options.MaxFramerate;
+                var maxrate = state.Options.MaxFramerate;
 
-            if (maxrate.HasValue && state.VideoStream != null)
-            {
-                var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate;
-
-                if (contentRate.HasValue && contentRate.Value > maxrate.Value)
+                if (maxrate.HasValue && state.VideoStream != null)
                 {
-                    return maxrate;
+                    var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate;
+
+                    if (contentRate.HasValue && contentRate.Value > maxrate.Value)
+                    {
+                        return maxrate;
+                    }
                 }
             }
 
@@ -852,7 +903,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             {
                 var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
 
-                filters.Add(string.Format("scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam));
+                filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam));
             }
 
             // If a max height was requested
@@ -863,6 +914,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(ih\\,{0})", maxHeightParam));
             }
 
+            if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+            {
+                if (filters.Count > 1)
+                {
+                    //filters[filters.Count - 1] += ":flags=fast_bilinear";
+                }
+            }
+
             var output = string.Empty;
 
             if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream)
@@ -917,8 +976,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
                     seconds.ToString(UsCulture));
             }
 
+            var mediaPath = state.MediaPath ?? string.Empty;
+
             return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB",
-                MediaEncoder.EscapeSubtitleFilterPath(state.MediaPath),
+                MediaEncoder.EscapeSubtitleFilterPath(mediaPath),
                 state.InternalSubtitleStreamOffset.ToString(UsCulture),
                 seconds.ToString(UsCulture));
         }

+ 107 - 83
MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs

@@ -10,6 +10,7 @@ using MediaBrowser.Model.MediaInfo;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
+using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
@@ -66,38 +67,56 @@ namespace MediaBrowser.MediaEncoding.Encoder
                ? mediaSources.First()
                : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
 
-            AttachMediaStreamInfo(state, mediaSource, options);
+            var videoRequest = state.Options;
 
-            state.OutputAudioBitrate = GetAudioBitrateParam(request, state.AudioStream);
+            AttachMediaSourceInfo(state, mediaSource, videoRequest);
+
+            //var container = Path.GetExtension(state.RequestedUrl);
+
+            //if (string.IsNullOrEmpty(container))
+            //{
+            //    container = request.Static ?
+            //        state.InputContainer :
+            //        (Path.GetExtension(GetOutputFilePath(state)) ?? string.Empty).TrimStart('.');
+            //}
+
+            //state.OutputContainer = (container ?? string.Empty).TrimStart('.');
+
+            state.OutputAudioBitrate = GetAudioBitrateParam(state.Options, state.AudioStream);
             state.OutputAudioSampleRate = request.AudioSampleRate;
 
-            state.OutputAudioCodec = GetAudioCodec(request);
+            state.OutputAudioCodec = state.Options.AudioCodec;
 
-            state.OutputAudioChannels = GetNumAudioChannelsParam(request, state.AudioStream, state.OutputAudioCodec);
+            state.OutputAudioChannels = GetNumAudioChannelsParam(state.Options, state.AudioStream, state.OutputAudioCodec);
 
-            if (isVideoRequest)
+            if (videoRequest != null)
             {
-                state.OutputVideoCodec = GetVideoCodec(request);
-                state.OutputVideoBitrate = GetVideoBitrateParamValue(request, state.VideoStream);
+                state.OutputVideoCodec = state.Options.VideoCodec;
+                state.OutputVideoBitrate = GetVideoBitrateParamValue(state.Options, state.VideoStream);
 
                 if (state.OutputVideoBitrate.HasValue)
                 {
                     var resolution = ResolutionNormalizer.Normalize(
-						state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
-						state.OutputVideoBitrate.Value,
-						state.VideoStream == null ? null : state.VideoStream.Codec,
+                        state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
+                        state.OutputVideoBitrate.Value,
+                        state.VideoStream == null ? null : state.VideoStream.Codec,
                         state.OutputVideoCodec,
-                        request.MaxWidth,
-                        request.MaxHeight);
+                        videoRequest.MaxWidth,
+                        videoRequest.MaxHeight);
 
-                    request.MaxWidth = resolution.MaxWidth;
-                    request.MaxHeight = resolution.MaxHeight;
+                    videoRequest.MaxWidth = resolution.MaxWidth;
+                    videoRequest.MaxHeight = resolution.MaxHeight;
                 }
             }
 
             ApplyDeviceProfileSettings(state);
 
-            TryStreamCopy(state, request);
+            if (videoRequest != null)
+            {
+                TryStreamCopy(state, videoRequest);
+            }
+
+            //state.OutputFilePath = GetOutputFilePath(state);
 
             return state;
         }
@@ -119,7 +138,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             }
         }
 
-        internal static void AttachMediaStreamInfo(EncodingJob state,
+        internal static void AttachMediaSourceInfo(EncodingJob state,
             MediaSourceInfo mediaSource,
             EncodingJobOptions videoRequest)
         {
@@ -131,11 +150,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
             state.RunTimeTicks = mediaSource.RunTimeTicks;
             state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
 
-            if (mediaSource.ReadAtNativeFramerate)
-            {
-                state.ReadInputAtNativeFramerate = true;
-            }
-
             if (mediaSource.VideoType.HasValue)
             {
                 state.VideoType = mediaSource.VideoType.Value;
@@ -156,6 +170,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders;
             state.InputBitrate = mediaSource.Bitrate;
             state.InputFileSize = mediaSource.Size;
+            state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
 
             if (state.ReadInputAtNativeFramerate ||
                 mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase))
@@ -165,6 +180,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 state.InputAudioSync = "1";
             }
 
+            if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase))
+            {
+                // Seeing some stuttering when transcoding wma to audio-only HLS
+                state.InputAudioSync = "1";
+            }
+
             var mediaStreams = mediaSource.MediaStreams;
 
             if (videoRequest != null)
@@ -210,19 +231,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// <returns>System.Nullable{VideoCodecs}.</returns>
         private static string InferVideoCodec(string container)
         {
-            if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase))
+            var ext = "." + (container ?? string.Empty);
+
+            if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase))
             {
                 return "wmv";
             }
-            if (string.Equals(container, "webm", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
             {
                 return "vpx";
             }
-            if (string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
             {
                 return "theora";
             }
-            if (string.Equals(container, "m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase))
             {
                 return "h264";
             }
@@ -232,35 +255,37 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
         private string InferAudioCodec(string container)
         {
-            if (string.Equals(container, "mp3", StringComparison.OrdinalIgnoreCase))
+            var ext = "." + (container ?? string.Empty);
+
+            if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase))
             {
                 return "mp3";
             }
-            if (string.Equals(container, "aac", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase))
             {
                 return "aac";
             }
-            if (string.Equals(container, "wma", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase))
             {
                 return "wma";
             }
-            if (string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase))
             {
                 return "vorbis";
             }
-            if (string.Equals(container, "oga", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase))
             {
                 return "vorbis";
             }
-            if (string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
             {
                 return "vorbis";
             }
-            if (string.Equals(container, "webm", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
             {
                 return "vorbis";
             }
-            if (string.Equals(container, "webma", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase))
             {
                 return "vorbis";
             }
@@ -330,8 +355,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
         private int? GetNumAudioChannelsParam(EncodingJobOptions request, MediaStream audioStream, string outputAudioCodec)
         {
             var inputChannels = audioStream == null
-                            ? null
-                            : audioStream.Channels;
+                ? null
+                : audioStream.Channels;
 
             if (inputChannels <= 0)
             {
@@ -348,15 +373,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
             if (request.MaxAudioChannels.HasValue)
             {
+                var channelLimit = codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1
+                   ? 2
+                   : 6;
+
                 if (inputChannels.HasValue)
                 {
-                    return Math.Min(request.MaxAudioChannels.Value, inputChannels.Value);
+                    channelLimit = Math.Min(channelLimit, inputChannels.Value);
                 }
 
-                var channelLimit = codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1
-                    ? 2
-                    : 6;
-
                 // If we don't have any media info then limit it to 5 to prevent encoding errors due to asking for too many channels
                 return Math.Min(request.MaxAudioChannels.Value, channelLimit);
             }
@@ -398,15 +423,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
             if (bitrate.HasValue)
             {
-                var hasFixedResolution = state.Options.HasFixedResolution;
-
                 if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
                 {
-                    if (hasFixedResolution)
-                    {
-                        return string.Format(" -minrate:v ({0}*.90) -maxrate:v ({0}*1.10) -bufsize:v {0} -b:v {0}", bitrate.Value.ToString(UsCulture));
-                    }
-
                     // With vpx when crf is used, b:v becomes a max rate
                     // https://trac.ffmpeg.org/wiki/vpxEncodingGuide. But higher bitrate source files -b:v causes judder so limite the bitrate but dont allow it to "saturate" the bitrate. So dont contrain it down just up.
                     return string.Format(" -maxrate:v {0} -bufsize:v ({0}*2) -b:v {0}", bitrate.Value.ToString(UsCulture));
@@ -417,18 +435,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
                     return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
                 }
 
-                // H264
-                if (hasFixedResolution)
-                {
-                    if (isHls)
-                    {
-                        return string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture));
-                    }
-
-                    return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
-                }
-
-                return string.Format(" -maxrate {0} -bufsize {1}",
+                // h264
+                return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}",
                     bitrate.Value.ToString(UsCulture),
                     (bitrate.Value * 2).ToString(UsCulture));
             }
@@ -466,11 +474,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// <summary>
         /// Gets the name of the output audio codec
         /// </summary>
-        /// <param name="request">The request.</param>
+        /// <param name="state">The state.</param>
         /// <returns>System.String.</returns>
-        private string GetAudioCodec(EncodingJobOptions request)
+        internal static string GetAudioEncoder(EncodingJob state)
         {
-            var codec = request.AudioCodec;
+            var codec = state.OutputAudioCodec;
 
             if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
             {
@@ -489,40 +497,56 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 return "wmav2";
             }
 
-            return (codec ?? string.Empty).ToLower();
+            return codec.ToLower();
         }
 
         /// <summary>
         /// Gets the name of the output video codec
         /// </summary>
-        /// <param name="request">The request.</param>
+        /// <param name="state">The state.</param>
+        /// <param name="options">The options.</param>
         /// <returns>System.String.</returns>
-        private string GetVideoCodec(EncodingJobOptions request)
+        internal static string GetVideoEncoder(EncodingJob state, EncodingOptions options)
         {
-            var codec = request.VideoCodec;
+            var codec = state.OutputVideoCodec;
 
-            if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
+            if (!string.IsNullOrEmpty(codec))
             {
-                return "libx264";
-            }
-            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
-            {
-                return "libx265";
-            }
-            if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase))
-            {
-                return "libvpx";
-            }
-            if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase))
-            {
-                return "wmv2";
+                if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
+                {
+                    return GetH264Encoder(state, options);
+                }
+                if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase))
+                {
+                    return "libvpx";
+                }
+                if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase))
+                {
+                    return "wmv2";
+                }
+                if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase))
+                {
+                    return "libtheora";
+                }
+
+                return codec.ToLower();
             }
-            if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase))
+
+            return "copy";
+        }
+
+        internal static string GetH264Encoder(EncodingJob state, EncodingOptions options)
+        {
+            if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
             {
-                return "libtheora";
+                // It's currently failing on live tv
+                if (state.RunTimeTicks.HasValue)
+                {
+                    return "h264_qsv";
+                }
             }
 
-            return (codec ?? string.Empty).ToLower();
+            return "libx264";
         }
 
         internal static bool CanStreamCopyVideo(EncodingJobOptions request, MediaStream videoStream)

+ 2 - 0
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -557,6 +557,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
                         vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2";
                         // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made the scale width to 600
                         break;
+                    default:
+                        break;
                 }
             }
 

+ 26 - 18
MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs

@@ -21,7 +21,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
         protected override string GetCommandLineArguments(EncodingJob state)
         {
             // Get the output codec name
-            var videoCodec = state.OutputVideoCodec;
+            var videoCodec = EncodingJobFactory.GetVideoEncoder(state, GetEncodingOptions());
 
             var format = string.Empty;
             var keyFrame = string.Empty;
@@ -29,6 +29,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             if (string.Equals(Path.GetExtension(state.OutputFilePath), ".mp4", StringComparison.OrdinalIgnoreCase) &&
                 state.Options.Context == EncodingContext.Streaming)
             {
+                // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
                 format = " -f mp4 -movflags frag_keyframe+empty_moov";
             }
 
@@ -53,42 +54,49 @@ namespace MediaBrowser.MediaEncoding.Encoder
         /// Gets video arguments to pass to ffmpeg
         /// </summary>
         /// <param name="state">The state.</param>
-        /// <param name="codec">The video codec.</param>
+        /// <param name="videoCodec">The video codec.</param>
         /// <returns>System.String.</returns>
-        private string GetVideoArguments(EncodingJob state, string codec)
+        private string GetVideoArguments(EncodingJob state, string videoCodec)
         {
-            var args = "-codec:v:0 " + codec;
+            var args = "-codec:v:0 " + videoCodec;
 
             if (state.EnableMpegtsM2TsMode)
             {
                 args += " -mpegts_m2ts_mode 1";
             }
 
-            // See if we can save come cpu cycles by avoiding encoding
-            if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+            var isOutputMkv = string.Equals(state.Options.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase);
+
+            if (state.RunTimeTicks.HasValue)
             {
-                return state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.Options.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) ?
-                    args + " -bsf:v h264_mp4toannexb" :
-                    args;
+                //args += " -copyts -avoid_negative_ts disabled -start_at_zero";
             }
 
-            if (state.Options.Context == EncodingContext.Streaming)
+            if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
             {
-                var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
-                    5.ToString(UsCulture));
+                if (state.VideoStream != null && IsH264(state.VideoStream) &&
+                    (string.Equals(state.Options.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) || isOutputMkv))
+                {
+                    args += " -bsf:v h264_mp4toannexb";
+                }
 
-                args += keyFrameArg;
+                return args;
             }
 
+            var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
+                5.ToString(UsCulture));
+
+            args += keyFrameArg;
+
             var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
 
             // Add resolution params, if specified
             if (!hasGraphicalSubs)
             {
-                args += GetOutputSizeParam(state, codec);
+                args += GetOutputSizeParam(state, videoCodec);
             }
 
-            var qualityParam = GetVideoQualityParam(state, codec, false);
+            var qualityParam = GetVideoQualityParam(state, videoCodec);
 
             if (!string.IsNullOrEmpty(qualityParam))
             {
@@ -98,7 +106,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             // This is for internal graphical subs
             if (hasGraphicalSubs)
             {
-                args += GetGraphicalSubtitleParam(state, codec);
+                args += GetGraphicalSubtitleParam(state, videoCodec);
             }
 
             return args;
@@ -118,11 +126,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
             }
 
             // Get the output codec name
-            var codec = state.OutputAudioCodec;
+            var codec = EncodingJobFactory.GetAudioEncoder(state);
 
             var args = "-codec:a:0 " + codec;
 
-            if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
             {
                 return args;
             }

+ 20 - 3
MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs

@@ -122,10 +122,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
                         .ConfigureAwait(false);
 
-            using (var stream = subtitle.Item1)
+            var inputFormat = subtitle.Item2;
+
+            if (string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase) && TryGetWriter(outputFormat) == null)
             {
-                var inputFormat = subtitle.Item2;
+                return subtitle.Item1;
+            }
 
+            using (var stream = subtitle.Item1)
+            {
                 return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, cancellationToken).ConfigureAwait(false);
             }
         }
@@ -288,7 +293,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             return null;
         }
 
-        private ISubtitleWriter GetWriter(string format)
+        private ISubtitleWriter TryGetWriter(string format)
         {
             if (string.IsNullOrEmpty(format))
             {
@@ -312,6 +317,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                 return new TtmlWriter();
             }
 
+            return null;
+        }
+
+        private ISubtitleWriter GetWriter(string format)
+        {
+            var writer = TryGetWriter(format);
+
+            if (writer != null)
+            {
+                return writer;
+            }
+
             throw new ArgumentException("Unsupported format: " + format);
         }
 

+ 3 - 0
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -668,6 +668,9 @@
     <Compile Include="..\MediaBrowser.Model\FileOrganization\FileSortingStatus.cs">
       <Link>FileOrganization\FileSortingStatus.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\FileOrganization\SmartMatchInfo.cs">
+      <Link>FileOrganization\SmartMatchInfo.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\FileOrganization\TvFileOrganizationOptions.cs">
       <Link>FileOrganization\TvFileOrganizationOptions.cs</Link>
     </Compile>

+ 3 - 0
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -633,6 +633,9 @@
     <Compile Include="..\MediaBrowser.Model\FileOrganization\FileSortingStatus.cs">
       <Link>FileOrganization\FileSortingStatus.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\FileOrganization\SmartMatchInfo.cs">
+      <Link>FileOrganization\SmartMatchInfo.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\FileOrganization\TvFileOrganizationOptions.cs">
       <Link>FileOrganization\TvFileOrganizationOptions.cs</Link>
     </Compile>

+ 8 - 1
MediaBrowser.Model/ApiClient/IApiClient.cs

@@ -1413,6 +1413,13 @@ namespace MediaBrowser.Model.ApiClient
         /// </summary>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task&lt;System.Int32&gt;.</returns>
-        Task<int> GetSupportedBitrate(CancellationToken cancellationToken);
+        Task<int> DetectMaxBitrate(CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the end point information.
+        /// </summary>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>System.Threading.Tasks.Task&lt;MediaBrowser.Model.Net.EndPointInfo&gt;.</returns>
+        Task<EndPointInfo> GetEndPointInfo(CancellationToken cancellationToken);
     }
 }

+ 1 - 0
MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs

@@ -11,6 +11,7 @@ namespace MediaBrowser.Model.Configuration
         public bool EnableIntrosParentalControl { get; set; }
         public bool EnableIntrosFromSimilarMovies { get; set; }
         public string CustomIntroPath { get; set; }
+        public string MediaInfoIntroPath { get; set; }
         public bool EnableIntrosFromUpcomingDvdMovies { get; set; }
         public bool EnableIntrosFromUpcomingStreamingMovies { get; set; }
 

+ 1 - 1
MediaBrowser.Model/Configuration/DlnaOptions.cs

@@ -5,7 +5,7 @@ namespace MediaBrowser.Model.Configuration
     {
         public bool EnablePlayTo { get; set; }
         public bool EnableServer { get; set; }
-        public bool EnableDebugLogging { get; set; }
+        public bool EnableDebugLog { get; set; }
         public bool BlastAliveMessages { get; set; }
         public int ClientDiscoveryIntervalSeconds { get; set; }
         public int BlastAliveMessageIntervalSeconds { get; set; }

+ 6 - 25
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -92,24 +92,6 @@ namespace MediaBrowser.Model.Configuration
         /// <value><c>true</c> if [enable localized guids]; otherwise, <c>false</c>.</value>
         public bool EnableLocalizedGuids { get; set; }
 
-        /// <summary>
-        /// Gets or sets a value indicating whether [disable startup scan].
-        /// </summary>
-        /// <value><c>true</c> if [disable startup scan]; otherwise, <c>false</c>.</value>
-        public bool DisableStartupScan { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether [enable user views].
-        /// </summary>
-        /// <value><c>true</c> if [enable user views]; otherwise, <c>false</c>.</value>
-        public bool EnableUserViews { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether [enable library metadata sub folder].
-        /// </summary>
-        /// <value><c>true</c> if [enable library metadata sub folder]; otherwise, <c>false</c>.</value>
-        public bool EnableLibraryMetadataSubFolder { get; set; }
-
         /// <summary>
         /// Gets or sets the preferred metadata language.
         /// </summary>
@@ -179,9 +161,7 @@ namespace MediaBrowser.Model.Configuration
         /// </summary>
         /// <value>The dashboard source path.</value>
         public string DashboardSourcePath { get; set; }
-
-        public bool MergeMetadataAndImagesByName { get; set; }
-
+        
         /// <summary>
         /// Gets or sets the image saving convention.
         /// </summary>
@@ -220,18 +200,18 @@ namespace MediaBrowser.Model.Configuration
 
         public bool EnableWindowsShortcuts { get; set; }
 
-        public bool EnableVideoFrameByFrameAnalysis { get; set; }
-
         public bool EnableDateLastRefresh { get; set; }
 
         public string[] Migrations { get; set; }
 
+        public int MigrationVersion { get; set; }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
         /// </summary>
         public ServerConfiguration()
         {
-            Migrations = new string[] {};
+            Migrations = new string[] { };
 
             ImageSavingConvention = ImageSavingConvention.Compatible;
             PublicPort = 8096;
@@ -576,7 +556,7 @@ namespace MediaBrowser.Model.Configuration
                             Type = ImageType.Thumb
                         }
                     },
-                    DisabledMetadataFetchers = new []{ "TheMovieDb" }
+                    DisabledMetadataFetchers = new []{ "The Open Movie Database", "TheMovieDb" }
                 },
 
                 new MetadataOptions(0, 1280)
@@ -597,6 +577,7 @@ namespace MediaBrowser.Model.Configuration
                             Type = ImageType.Primary
                         }
                     },
+                    DisabledMetadataFetchers = new []{ "The Open Movie Database" },
                     DisabledImageFetchers = new []{ "TheMovieDb" }
                 }
             };

+ 1 - 0
MediaBrowser.Model/Dto/BaseItemDto.cs

@@ -192,6 +192,7 @@ namespace MediaBrowser.Model.Dto
         /// <value>The channel identifier.</value>
         public string ChannelId { get; set; }
         public string ChannelName { get; set; }
+        public string ServiceName { get; set; }
 
         /// <summary>
         /// Gets or sets the overview.

+ 1 - 1
MediaBrowser.Model/Dto/MediaSourceInfo.cs

@@ -51,7 +51,7 @@ namespace MediaBrowser.Model.Dto
         public string TranscodingUrl { get; set; }
         public string TranscodingSubProtocol { get; set; }
         public string TranscodingContainer { get; set; }
-
+        
         public MediaSourceInfo()
         {
             Formats = new List<string>();

+ 2 - 1
MediaBrowser.Model/Entities/Video3DFormat.cs

@@ -6,6 +6,7 @@ namespace MediaBrowser.Model.Entities
         HalfSideBySide,
         FullSideBySide,
         FullTopAndBottom,
-        HalfTopAndBottom
+        HalfTopAndBottom,
+        MVC
     }
 }

+ 7 - 0
MediaBrowser.Model/FileOrganization/AutoOrganizeOptions.cs

@@ -9,9 +9,16 @@ namespace MediaBrowser.Model.FileOrganization
         /// <value>The tv options.</value>
         public TvFileOrganizationOptions TvOptions { get; set; }
 
+        /// <summary>
+        /// Gets or sets a list of smart match entries.
+        /// </summary>
+        /// <value>The smart match entries.</value>
+        public SmartMatchInfo[] SmartMatchInfos { get; set; }
+
         public AutoOrganizeOptions()
         {
             TvOptions = new TvFileOrganizationOptions();
+            SmartMatchInfos = new SmartMatchInfo[]{};
         }
     }
 }

+ 16 - 0
MediaBrowser.Model/FileOrganization/SmartMatchInfo.cs

@@ -0,0 +1,16 @@
+
+namespace MediaBrowser.Model.FileOrganization
+{
+    public class SmartMatchInfo
+    {
+        public string ItemName { get; set; }
+        public string DisplayName { get; set; }
+        public FileOrganizerType OrganizerType { get; set; }
+        public string[] MatchStrings { get; set; }
+
+        public SmartMatchInfo()
+        {
+            MatchStrings = new string[] { };
+        }
+    }
+}

+ 1 - 0
MediaBrowser.Model/LiveTv/LiveTvOptions.cs

@@ -8,6 +8,7 @@ namespace MediaBrowser.Model.LiveTv
         public bool EnableMovieProviders { get; set; }
         public string RecordingPath { get; set; }
         public bool EnableAutoOrganize { get; set; }
+        public bool EnableRecordingEncoding { get; set; }
 
         public List<TunerHostInfo> TunerHosts { get; set; }
         public List<ListingsProviderInfo> ListingProviders { get; set; }

+ 1 - 0
MediaBrowser.Model/MediaBrowser.Model.csproj

@@ -137,6 +137,7 @@
     <Compile Include="Dto\MetadataEditorInfo.cs" />
     <Compile Include="Dto\NameIdPair.cs" />
     <Compile Include="Dto\NameValuePair.cs" />
+    <Compile Include="FileOrganization\SmartMatchInfo.cs" />
     <Compile Include="MediaInfo\LiveStreamRequest.cs" />
     <Compile Include="MediaInfo\LiveStreamResponse.cs" />
     <Compile Include="MediaInfo\PlaybackInfoRequest.cs" />

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác