瀏覽代碼

Merge branch 'dev' of https://github.com/MediaBrowser/MediaBrowser into dev

Tavares André 10 年之前
父節點
當前提交
27d6135493
共有 100 個文件被更改,包括 2335 次插入1972 次删除
  1. 0 1
      Emby.Drawing/Emby.Drawing.csproj
  2. 8 3
      Emby.Drawing/ImageMagick/ImageMagickEncoder.cs
  3. 38 4
      Emby.Drawing/ImageMagick/PlayedIndicatorDrawer.cs
  4. 5 6
      MediaBrowser.Api/BaseApiService.cs
  5. 5 5
      MediaBrowser.Api/ItemUpdateService.cs
  6. 7 7
      MediaBrowser.Api/Movies/MoviesService.cs
  7. 3 2
      MediaBrowser.Api/Music/AlbumsService.cs
  8. 5 0
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  9. 4 0
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  10. 1134 1214
      MediaBrowser.Api/Reports/ReportsService.cs
  11. 202 261
      MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs
  12. 8 7
      MediaBrowser.Api/SimilarItemsHelper.cs
  13. 3 4
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  14. 1 1
      MediaBrowser.Api/UserLibrary/PersonsService.cs
  15. 9 0
      MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
  16. 9 0
      MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
  17. 6 87
      MediaBrowser.Controller/Entities/BaseItem.cs
  18. 11 0
      MediaBrowser.Controller/Entities/BasePluginFolder.cs
  19. 9 0
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  20. 9 0
      MediaBrowser.Controller/Entities/GameGenre.cs
  21. 9 0
      MediaBrowser.Controller/Entities/GameSystem.cs
  22. 9 0
      MediaBrowser.Controller/Entities/Genre.cs
  23. 6 0
      MediaBrowser.Controller/Entities/IHasMetadata.cs
  24. 100 0
      MediaBrowser.Controller/Entities/PeopleHelper.cs
  25. 10 1
      MediaBrowser.Controller/Entities/Person.cs
  26. 9 0
      MediaBrowser.Controller/Entities/Studio.cs
  27. 9 0
      MediaBrowser.Controller/Entities/User.cs
  28. 11 1
      MediaBrowser.Controller/Entities/UserView.cs
  29. 4 5
      MediaBrowser.Controller/Entities/UserViewBuilder.cs
  30. 9 0
      MediaBrowser.Controller/Entities/Year.cs
  31. 21 0
      MediaBrowser.Controller/Library/ILibraryManager.cs
  32. 5 0
      MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
  33. 5 0
      MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
  34. 1 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  35. 15 0
      MediaBrowser.Controller/Persistence/IItemRepository.cs
  36. 14 12
      MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
  37. 2 8
      MediaBrowser.Controller/Providers/LocalMetadataResult.cs
  38. 10 0
      MediaBrowser.Controller/Providers/MetadataResult.cs
  39. 8 0
      MediaBrowser.Controller/Subtitles/ISubtitleProvider.cs
  40. 5 5
      MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs
  41. 6 2
      MediaBrowser.Dlna/Didl/DidlBuilder.cs
  42. 1 0
      MediaBrowser.Dlna/DlnaManager.cs
  43. 4 0
      MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
  44. 1 1
      MediaBrowser.Dlna/PlayTo/PlayToController.cs
  45. 17 1
      MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs
  46. 20 4
      MediaBrowser.Dlna/Profiles/SonyBravia2011Profile.cs
  47. 17 1
      MediaBrowser.Dlna/Profiles/SonyBravia2012Profile.cs
  48. 17 1
      MediaBrowser.Dlna/Profiles/SonyBravia2013Profile.cs
  49. 260 0
      MediaBrowser.Dlna/Profiles/SonyPs4Profile.cs
  50. 6 1
      MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml
  51. 9 4
      MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml
  52. 6 1
      MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml
  53. 6 1
      MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml
  54. 31 0
      MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 4.xml
  55. 0 1
      MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
  56. 3 3
      MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs
  57. 12 18
      MediaBrowser.LocalMetadata/Parsers/EpisodeXmlParser.cs
  58. 6 4
      MediaBrowser.LocalMetadata/Parsers/GameSystemXmlParser.cs
  59. 6 4
      MediaBrowser.LocalMetadata/Parsers/GameXmlParser.cs
  60. 25 27
      MediaBrowser.LocalMetadata/Parsers/MovieXmlParser.cs
  61. 6 4
      MediaBrowser.LocalMetadata/Parsers/MusicVideoXmlParser.cs
  62. 4 2
      MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs
  63. 5 3
      MediaBrowser.LocalMetadata/Parsers/SeasonXmlParser.cs
  64. 6 4
      MediaBrowser.LocalMetadata/Parsers/SeriesXmlParser.cs
  65. 1 1
      MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs
  66. 1 2
      MediaBrowser.LocalMetadata/Providers/EpisodeXmlProvider.cs
  67. 1 1
      MediaBrowser.LocalMetadata/Providers/FolderXmlProvider.cs
  68. 1 1
      MediaBrowser.LocalMetadata/Providers/GameSystemXmlProvider.cs
  69. 1 1
      MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs
  70. 1 7
      MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs
  71. 1 1
      MediaBrowser.LocalMetadata/Providers/MusicVideoXmlProvider.cs
  72. 1 1
      MediaBrowser.LocalMetadata/Providers/PersonXmlProvider.cs
  73. 1 1
      MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs
  74. 1 1
      MediaBrowser.LocalMetadata/Providers/SeasonXmlProvider.cs
  75. 1 1
      MediaBrowser.LocalMetadata/Providers/SeriesXmlProvider.cs
  76. 4 10
      MediaBrowser.LocalMetadata/Providers/VideoXmlProvider.cs
  77. 4 2
      MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs
  78. 4 2
      MediaBrowser.LocalMetadata/Savers/EpisodeXmlSaver.cs
  79. 4 2
      MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs
  80. 4 2
      MediaBrowser.LocalMetadata/Savers/GameSystemXmlSaver.cs
  81. 5 3
      MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs
  82. 1 1
      MediaBrowser.LocalMetadata/Savers/MovieXmlSaver.cs
  83. 4 2
      MediaBrowser.LocalMetadata/Savers/PersonXmlSaver.cs
  84. 5 3
      MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs
  85. 0 95
      MediaBrowser.LocalMetadata/Savers/SeasonXmlSaver.cs
  86. 5 3
      MediaBrowser.LocalMetadata/Savers/SeriesXmlSaver.cs
  87. 6 3
      MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs
  88. 3 0
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  89. 3 0
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  90. 10 0
      MediaBrowser.Model/Configuration/AutoOnOff.cs
  91. 10 4
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  92. 1 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  93. 4 12
      MediaBrowser.Providers/Books/BookMetadataService.cs
  94. 19 24
      MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs
  95. 2 10
      MediaBrowser.Providers/Channels/AudioChannelItemMetadataService.cs
  96. 2 10
      MediaBrowser.Providers/Channels/ChannelMetadataService.cs
  97. 2 10
      MediaBrowser.Providers/Channels/VideoChannelItemMetadataService.cs
  98. 6 14
      MediaBrowser.Providers/Folders/FolderMetadataService.cs
  99. 2 11
      MediaBrowser.Providers/Folders/UserViewMetadataService.cs
  100. 2 10
      MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs

+ 0 - 1
Emby.Drawing/Emby.Drawing.csproj

@@ -69,7 +69,6 @@
   <ItemGroup>
     <EmbeddedResource Include="ImageMagick\fonts\MontserratLight.otf" />
     <EmbeddedResource Include="ImageMagick\fonts\robotoregular.ttf" />
-    <EmbeddedResource Include="ImageMagick\fonts\webdings.ttf" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">

+ 8 - 3
Emby.Drawing/ImageMagick/ImageMagickEncoder.cs

@@ -1,11 +1,13 @@
-using System.Linq;
+using System.Threading.Tasks;
 using ImageMagickSharp;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Logging;
 using System;
 using System.IO;
+using System.Linq;
 
 namespace Emby.Drawing.ImageMagick
 {
@@ -13,11 +15,13 @@ namespace Emby.Drawing.ImageMagick
     {
         private readonly ILogger _logger;
         private readonly IApplicationPaths _appPaths;
+        private readonly IHttpClient _httpClient;
 
-        public ImageMagickEncoder(ILogger logger, IApplicationPaths appPaths)
+        public ImageMagickEncoder(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient)
         {
             _logger = logger;
             _appPaths = appPaths;
+            _httpClient = httpClient;
 
             LogImageMagickVersion();
         }
@@ -177,7 +181,8 @@ namespace Emby.Drawing.ImageMagick
                 {
                     var currentImageSize = new ImageSize(imageWidth, imageHeight);
 
-                    new PlayedIndicatorDrawer(_appPaths).DrawPlayedIndicator(wand, currentImageSize);
+                    var task = new PlayedIndicatorDrawer(_appPaths, _httpClient).DrawPlayedIndicator(wand, currentImageSize);
+                    Task.WaitAll(task);
                 }
                 else if (options.UnplayedCount.HasValue)
                 {

+ 38 - 4
Emby.Drawing/ImageMagick/PlayedIndicatorDrawer.cs

@@ -1,8 +1,10 @@
 using ImageMagickSharp;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Model.Drawing;
 using System;
 using System.IO;
+using System.Threading.Tasks;
 
 namespace Emby.Drawing.ImageMagick
 {
@@ -12,13 +14,15 @@ namespace Emby.Drawing.ImageMagick
         private const int OffsetFromTopRightCorner = 38;
 
         private readonly IApplicationPaths _appPaths;
+        private readonly IHttpClient _iHttpClient;
 
-        public PlayedIndicatorDrawer(IApplicationPaths appPaths)
+        public PlayedIndicatorDrawer(IApplicationPaths appPaths, IHttpClient iHttpClient)
         {
             _appPaths = appPaths;
+            _iHttpClient = iHttpClient;
         }
 
-        public void DrawPlayedIndicator(MagickWand wand, ImageSize imageSize)
+        public async Task DrawPlayedIndicator(MagickWand wand, ImageSize imageSize)
         {
             var x = imageSize.Width - OffsetFromTopRightCorner;
 
@@ -34,7 +38,7 @@ namespace Emby.Drawing.ImageMagick
                     pixel.Opacity = 0;
                     pixel.Color = "white";
                     draw.FillColor = pixel;
-                    draw.Font = ExtractFont("webdings.ttf", _appPaths);
+                    draw.Font = await DownloadFont("webdings.ttf", "https://github.com/MediaBrowser/Emby.Resources/raw/master/fonts/webdings.ttf", _appPaths, _iHttpClient).ConfigureAwait(false);
                     draw.FontSize = FontSize;
                     draw.FontStyle = FontStyleType.NormalStyle;
                     draw.TextAlignment = TextAlignType.CenterAlign;
@@ -77,7 +81,37 @@ namespace Emby.Drawing.ImageMagick
             }
             catch (IOException)
             {
-                
+
+            }
+
+            return tempPath;
+        }
+
+        internal static async Task<string> DownloadFont(string name, string url, IApplicationPaths paths, IHttpClient httpClient)
+        {
+            var filePath = Path.Combine(paths.ProgramDataPath, "fonts", name);
+
+            if (File.Exists(filePath))
+            {
+                return filePath;
+            }
+
+            var tempPath = await httpClient.GetTempFile(new HttpRequestOptions
+            {
+                Url = url,
+                Progress = new Progress<double>()
+
+            }).ConfigureAwait(false);
+
+            Directory.CreateDirectory(Path.GetDirectoryName(filePath));
+
+            try
+            {
+                File.Copy(tempPath, filePath, false);
+            }
+            catch (IOException)
+            {
+
             }
 
             return tempPath;

+ 5 - 6
MediaBrowser.Api/BaseApiService.cs

@@ -1,5 +1,4 @@
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
@@ -12,6 +11,7 @@ using ServiceStack.Web;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
 
 namespace MediaBrowser.Api
 {
@@ -344,9 +344,7 @@ namespace MediaBrowser.Api
                 return name;
             }
 
-            return libraryManager.RootFolder
-                .GetRecursiveChildren()
-                .SelectMany(i => i.People)
+            return libraryManager.GetAllPeople()
                 .Select(i => i.Name)
                 .DistinctNames()
                 .FirstOrDefault(i =>
@@ -364,7 +362,8 @@ namespace MediaBrowser.Api
             var first = pathInfo.GetArgumentValue<string>(0);
 
             // backwards compatibility
-            if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) ||
+                string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase))
             {
                 index++;
             }

+ 5 - 5
MediaBrowser.Api/ItemUpdateService.cs

@@ -218,6 +218,11 @@ namespace MediaBrowser.Api
 
             await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
+            if (request.People != null)
+            {
+                await _libraryManager.UpdatePeople(item, request.People.Select(x => new PersonInfo { Name = x.Name, Role = x.Role, Type = x.Type }).ToList());
+            }
+
             if (isLockedChanged && item.IsFolder)
             {
                 var folder = (Folder)item;
@@ -303,11 +308,6 @@ namespace MediaBrowser.Api
                 item.Studios = request.Studios.Select(x => x.Name).ToList();
             }
 
-            if (request.People != null)
-            {
-                item.People = request.People.Select(x => new PersonInfo { Name = x.Name, Role = x.Role, Type = x.Type }).ToList();
-            }
-
             if (request.DateCreated.HasValue)
             {
                 item.DateCreated = NormalizeDateTime(request.DateCreated.Value);

+ 7 - 7
MediaBrowser.Api/Movies/MoviesService.cs

@@ -165,7 +165,7 @@ namespace MediaBrowser.Api.Movies
             return ToOptimizedResult(result);
         }
 
-        private async Task<ItemsResult> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, Func<BaseItem, bool> includeInSearch, Func<BaseItem, BaseItem, int> getSimilarityScore)
+        private async Task<ItemsResult> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, Func<BaseItem, bool> includeInSearch, Func<BaseItem, BaseItem, ILibraryManager, int> getSimilarityScore)
         {
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
 
@@ -214,7 +214,7 @@ namespace MediaBrowser.Api.Movies
                 }
             }
 
-            var items = SimilarItemsHelper.GetSimilaritems(item, list, getSimilarityScore).ToList();
+            var items = SimilarItemsHelper.GetSimilaritems(item, _libraryManager, list, getSimilarityScore).ToList();
 
             IEnumerable<BaseItem> returnItems = items;
 
@@ -339,7 +339,7 @@ namespace MediaBrowser.Api.Movies
             foreach (var director in directors)
             {
                 var items = allMovies
-                    .Where(i => i.People.Any(p => string.Equals(p.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase) && string.Equals(p.Name, director, StringComparison.OrdinalIgnoreCase)))
+                    .Where(i => _libraryManager.GetPeople(i).Any(p => string.Equals(p.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase) && string.Equals(p.Name, director, StringComparison.OrdinalIgnoreCase)))
                     .Take(itemLimit)
                     .ToList();
 
@@ -363,7 +363,7 @@ namespace MediaBrowser.Api.Movies
             foreach (var name in names)
             {
                 var items = allMovies
-                    .Where(i => i.People.Any(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)))
+                    .Where(i => _libraryManager.GetPeople(i).Any(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)))
                     .Take(itemLimit)
                     .ToList();
 
@@ -387,7 +387,7 @@ namespace MediaBrowser.Api.Movies
             foreach (var item in baselineItems)
             {
                 var similar = SimilarItemsHelper
-                    .GetSimilaritems(item, allMovies, SimilarItemsHelper.GetSimiliarityScore)
+                    .GetSimilaritems(item, _libraryManager, allMovies, SimilarItemsHelper.GetSimiliarityScore)
                     .Take(itemLimit)
                     .ToList();
 
@@ -408,7 +408,7 @@ namespace MediaBrowser.Api.Movies
         {
             // Get the two leading actors for all movies
             return items
-                .SelectMany(i => i.People.Where(p => !string.Equals(PersonType.Director, p.Type, StringComparison.OrdinalIgnoreCase)).Take(2))
+                .SelectMany(i => _libraryManager.GetPeople(i).Where(p => !string.Equals(PersonType.Director, p.Type, StringComparison.OrdinalIgnoreCase)).Take(2))
                 .Select(i => i.Name)
                 .DistinctNames();
         }
@@ -416,7 +416,7 @@ namespace MediaBrowser.Api.Movies
         private IEnumerable<string> GetDirectors(IEnumerable<BaseItem> items)
         {
             return items
-                .Select(i => i.People.FirstOrDefault(p => string.Equals(PersonType.Director, p.Type, StringComparison.OrdinalIgnoreCase)))
+                .Select(i => _libraryManager.GetPeople(i).FirstOrDefault(p => string.Equals(PersonType.Director, p.Type, StringComparison.OrdinalIgnoreCase)))
                 .Where(i => i != null)
                 .Select(i => i.Name)
                 .DistinctNames();

+ 3 - 2
MediaBrowser.Api/Music/AlbumsService.cs

@@ -69,10 +69,11 @@ namespace MediaBrowser.Api.Music
         /// </summary>
         /// <param name="item1">The item1.</param>
         /// <param name="item2">The item2.</param>
+        /// <param name="libraryManager">The library manager.</param>
         /// <returns>System.Int32.</returns>
-        private int GetAlbumSimilarityScore(BaseItem item1, BaseItem item2)
+        private int GetAlbumSimilarityScore(BaseItem item1, BaseItem item2, ILibraryManager libraryManager)
         {
-            var points = SimilarItemsHelper.GetSimiliarityScore(item1, item2);
+            var points = SimilarItemsHelper.GetSimiliarityScore(item1, item2, libraryManager);
 
             var album1 = (MusicAlbum)item1;
             var album2 = (MusicAlbum)item2;

+ 5 - 0
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -772,6 +772,11 @@ namespace MediaBrowser.Api.Playback
                 ? null
                 : audioStream.Channels;
 
+            if (inputChannels <= 0)
+            {
+                inputChannels = null;
+            }
+
             var codec = outputAudioCodec ?? string.Empty;
 
             if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)

+ 4 - 0
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -294,6 +294,10 @@ namespace MediaBrowser.Api.Playback.Hls
                         }
                     }
                 }
+            }
+            catch (DirectoryNotFoundException)
+            {
+
             }
             catch (FileNotFoundException)
             {

+ 1134 - 1214
MediaBrowser.Api/Reports/ReportsService.cs

@@ -24,1219 +24,1139 @@ using System.Text;
 
 namespace MediaBrowser.Api.Reports
 {
-    /// <summary> The reports service. </summary>
-    /// <seealso cref="T:MediaBrowser.Api.BaseApiService"/>
-    public class ReportsService : BaseApiService
-    {
-        #region [Constructors]
-
-        /// <summary>
-        /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportsService class. </summary>
-        /// <param name="userManager"> Manager for user. </param>
-        /// <param name="libraryManager"> Manager for library. </param>
-        /// <param name="localization"> The localization. </param>
-        /// <param name="activityManager"> Manager for activity. </param>
-        public ReportsService(IUserManager userManager, ILibraryManager libraryManager, ILocalizationManager localization, IActivityManager activityManager, IActivityRepository repo)
-        {
-            _userManager = userManager;
-            _libraryManager = libraryManager;
-            _localization = localization;
-            _activityManager = activityManager;
-            _repo = repo;
-        }
-
-        #endregion
-
-        #region [Private Fields]
-
-        private readonly IActivityManager _activityManager; ///< Manager for activity
-
-        /// <summary> Manager for library. </summary>
-        private readonly ILibraryManager _libraryManager;   ///< Manager for library
-        /// <summary> The localization. </summary>
-
-        private readonly ILocalizationManager _localization;    ///< The localization
-
-        private readonly IActivityRepository _repo;
-
-        /// <summary> Manager for user. </summary>
-        private readonly IUserManager _userManager; ///< Manager for user
-
-        #endregion
-
-        #region [Public Methods]
-
-        /// <summary> Gets the given request. </summary>
-        /// <param name="request"> The request. </param>
-        /// <returns> A Task&lt;object&gt; </returns>
-        public async Task<object> Get(GetActivityLogs request)
-        {
-            ReportResult result = await GetReportActivities(request).ConfigureAwait(false);
-            return ToOptimizedResult(result);
-        }
-
-        /// <summary> Gets the given request. </summary>
-        /// <param name="request"> The request. </param>
-        /// <returns> A Task&lt;object&gt; </returns>
-        public async Task<object> Get(GetReportHeaders request)
-        {
-            if (string.IsNullOrEmpty(request.IncludeItemTypes))
-                return null;
-
-            ReportViewType reportViewType = ReportHelper.GetReportViewType(request.ReportView);
-            ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
-
-            List<ReportHeader> result = new List<ReportHeader>();
-            switch (reportViewType)
-            {
-                case ReportViewType.ReportData:
-                    ReportBuilder dataBuilder = new ReportBuilder(_libraryManager);
-                    result = dataBuilder.GetHeaders(request);
-                    break;
-                case ReportViewType.ReportStatistics:
-                    break;
-                case ReportViewType.ReportActivities:
-                    ReportActivitiesBuilder activityBuilder = new ReportActivitiesBuilder(_libraryManager, _userManager);
-                    result = activityBuilder.GetHeaders(request);
-                    break;
-            }
-
-            return ToOptimizedResult(result);
-
-        }
-
-        /// <summary> Gets the given request. </summary>
-        /// <param name="request"> The request. </param>
-        /// <returns> A Task&lt;object&gt; </returns>
-        public async Task<object> Get(GetItemReport request)
-        {
-            if (string.IsNullOrEmpty(request.IncludeItemTypes))
-                return null;
-
-            var reportResult = await GetReportResult(request);
-
-            return ToOptimizedResult(reportResult);
-        }
-
-        /// <summary> Gets the given request. </summary>
-        /// <param name="request"> The request. </param>
-        /// <returns> A Task&lt;object&gt; </returns>
-        public async Task<object> Get(GetReportStatistics request)
-        {
-            if (string.IsNullOrEmpty(request.IncludeItemTypes))
-                return null;
-            var reportResult = await GetReportStatistic(request);
-
-            return ToOptimizedResult(reportResult);
-        }
-
-        /// <summary> Gets the given request. </summary>
-        /// <param name="request"> The request. </param>
-        /// <returns> A Task&lt;object&gt; </returns>
-        public async Task<object> Get(GetReportDownload request)
-        {
-            if (string.IsNullOrEmpty(request.IncludeItemTypes))
-                return null;
-
-            ReportViewType reportViewType = ReportHelper.GetReportViewType(request.ReportView);
-            var headers = new Dictionary<string, string>();
-            string fileExtension = "csv";
-            string contentType = "text/plain;charset='utf-8'";
-
-            switch (request.ExportType)
-            {
-                case ReportExportType.CSV:
-                    break;
-                case ReportExportType.Excel:
-                    contentType = "application/vnd.ms-excel";
-                    fileExtension = "xls";
-                    break;
-            }
-
-            var filename = "ReportExport." + fileExtension;
-            headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename);
-            headers["Content-Encoding"] = "UTF-8";
-
-            ReportResult result = null;
-            switch (reportViewType)
-            {
-                case ReportViewType.ReportStatistics:
-                case ReportViewType.ReportData:
-                    ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
-                    ReportBuilder dataBuilder = new ReportBuilder(_libraryManager);
-                    QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
-                    result = dataBuilder.GetResult(queryResult.Items, request);
-                    result.TotalRecordCount = queryResult.TotalRecordCount;
-                    break;
-                case ReportViewType.ReportActivities:
-                    result = await GetReportActivities(request).ConfigureAwait(false);
-                    break;
-            }
-
-            string returnResult = string.Empty;
-            switch (request.ExportType)
-            {
-                case ReportExportType.CSV:
-                    returnResult = new ReportExport().ExportToCsv(result);
-                    break;
-                case ReportExportType.Excel:
-                    returnResult = new ReportExport().ExportToExcel(result);
-                    break;
-            }
-
-            object ro = ResultFactory.GetResult(returnResult, contentType, headers);
-            return ro;
-        }
-
-        #endregion
-
-        #region [Internal Methods]
-
-        /// <summary> Applies filtering. </summary>
-        /// <param name="items"> The items. </param>
-        /// <param name="filter"> The filter. </param>
-        /// <param name="user"> The user. </param>
-        /// <param name="repository"> The repository. </param>
-        /// <returns> IEnumerable{BaseItem}. </returns>
-        internal static IEnumerable<BaseItem> ApplyFilter(IEnumerable<BaseItem> items, ItemFilter filter, User user, IUserDataManager repository)
-        {
-            // Avoid implicitly captured closure
-            var currentUser = user;
-
-            switch (filter)
-            {
-                case ItemFilter.IsFavoriteOrLikes:
-                    return items.Where(item =>
-                    {
-                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
-
-                        if (userdata == null)
-                        {
-                            return false;
-                        }
-
-                        var likes = userdata.Likes ?? false;
-                        var favorite = userdata.IsFavorite;
-
-                        return likes || favorite;
-                    });
-
-                case ItemFilter.Likes:
-                    return items.Where(item =>
-                    {
-                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
-
-                        return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value;
-                    });
-
-                case ItemFilter.Dislikes:
-                    return items.Where(item =>
-                    {
-                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
-
-                        return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value;
-                    });
-
-                case ItemFilter.IsFavorite:
-                    return items.Where(item =>
-                    {
-                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
-
-                        return userdata != null && userdata.IsFavorite;
-                    });
-
-                case ItemFilter.IsResumable:
-                    return items.Where(item =>
-                    {
-                        var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
-
-                        return userdata != null && userdata.PlaybackPositionTicks > 0;
-                    });
-
-                case ItemFilter.IsPlayed:
-                    return items.Where(item => item.IsPlayed(currentUser));
-
-                case ItemFilter.IsUnplayed:
-                    return items.Where(item => item.IsUnplayed(currentUser));
-
-                case ItemFilter.IsFolder:
-                    return items.Where(item => item.IsFolder);
-
-                case ItemFilter.IsNotFolder:
-                    return items.Where(item => !item.IsFolder);
-
-                case ItemFilter.IsRecentlyAdded:
-                    return items.Where(item => (DateTime.UtcNow - item.DateCreated).TotalDays <= 10);
-            }
-
-            return items;
-        }
-
-        #endregion
-
-        #region [Private Methods]
-
-        /// <summary> Applies the additional filters. </summary>
-        /// <param name="request"> The request. </param>
-        /// <param name="i"> Zero-based index of the. </param>
-        /// <param name="user"> The user. </param>
-        /// <param name="isPreFiltered"> true if this object is pre filtered. </param>
-        /// <param name="libraryManager"> Manager for library. </param>
-        /// <returns> true if it succeeds, false if it fails. </returns>
+	/// <summary> The reports service. </summary>
+	/// <seealso cref="T:MediaBrowser.Api.BaseApiService"/>
+	public class ReportsService : BaseApiService
+	{
+
+
+		/// <summary> Manager for user. </summary>
+		private readonly IUserManager _userManager;
+
+		/// <summary> Manager for library. </summary>
+		private readonly ILibraryManager _libraryManager;
+		/// <summary> The localization. </summary>
+		private readonly ILocalizationManager _localization;
+
+		/// <summary>
+		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportsService class. </summary>
+		/// <param name="userManager"> Manager for user. </param>
+		/// <param name="libraryManager"> Manager for library. </param>
+		/// <param name="localization"> The localization. </param>
+		public ReportsService(IUserManager userManager, ILibraryManager libraryManager, ILocalizationManager localization)
+		{
+			_userManager = userManager;
+			_libraryManager = libraryManager;
+			_localization = localization;
+		}
+
+		/// <summary> Gets the given request. </summary>
+		/// <param name="request"> The request. </param>
+		/// <returns> A Task&lt;object&gt; </returns>
+		public async Task<object> Get(GetReportHeaders request)
+		{
+			if (string.IsNullOrEmpty(request.IncludeItemTypes))
+				return null;
+
+			ReportViewType reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
+			ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
+			var reportResult = reportBuilder.GetReportHeaders(reportRowType, request);
+
+			return ToOptimizedResult(reportResult);
+
+		}
+
+		/// <summary> Gets the given request. </summary>
+		/// <param name="request"> The request. </param>
+		/// <returns> A Task&lt;object&gt; </returns>
+		public async Task<object> Get(GetItemReport request)
+		{
+			if (string.IsNullOrEmpty(request.IncludeItemTypes))
+				return null;
+
+			var reportResult = await GetReportResult(request);
+
+			return ToOptimizedResult(reportResult);
+		}
+
+		/// <summary> Gets the given request. </summary>
+		/// <param name="request"> The request. </param>
+		/// <returns> A Task&lt;object&gt; </returns>
+		public async Task<object> Get(GetReportDownload request)
+		{
+			if (string.IsNullOrEmpty(request.IncludeItemTypes))
+				return null;
+
+			var headers = new Dictionary<string, string>();
+			string fileExtension = "csv";
+			string contentType = "text/plain;charset='utf-8'";
+
+			switch (request.ExportType)
+			{
+				case ReportExportType.CSV:
+					break;
+				case ReportExportType.Excel:
+					contentType = "application/vnd.ms-excel";
+					fileExtension = "xls";
+					break;
+			}
+
+			var filename = "ReportExport." + fileExtension;
+			headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename);
+			headers["Content-Encoding"] = "UTF-8";
+
+			ReportViewType reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
+			ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
+			QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+			ReportResult reportResult = reportBuilder.GetReportResult(queryResult.Items, reportRowType, request);
+
+			reportResult.TotalRecordCount = queryResult.TotalRecordCount;
+
+			string result = string.Empty;
+			switch (request.ExportType)
+			{
+				case ReportExportType.CSV:
+					result = new ReportExport().ExportToCsv(reportResult);
+					break;
+				case ReportExportType.Excel:
+					result = new ReportExport().ExportToExcel(reportResult);
+					break;
+			}
+
+			object ro = ResultFactory.GetResult(result, contentType, headers);
+			return ro;
+		}
+
+		/// <summary> Gets the given request. </summary>
+		/// <param name="request"> The request. </param>
+		/// <returns> A Task&lt;object&gt; </returns>
+		public async Task<object> Get(GetReportStatistics request)
+		{
+			if (string.IsNullOrEmpty(request.IncludeItemTypes))
+				return null;
+			var reportResult = await GetReportStatistic(request);
+
+			return ToOptimizedResult(reportResult);
+		}
+
+		/// <summary> Gets report statistic. </summary>
+		/// <param name="request"> The request. </param>
+		/// <returns> The report statistic. </returns>
+		private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request)
+		{
+			ReportViewType reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
+			QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+
+			ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager);
+			ReportStatResult reportResult = reportBuilder.GetReportStatResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5);
+			reportResult.TotalRecordCount = reportResult.Groups.Count();
+			return reportResult;
+		}
+
+		/// <summary> Gets report result. </summary>
+		/// <param name="request"> The request. </param>
+		/// <returns> The report result. </returns>
+		private async Task<ReportResult> GetReportResult(GetItemReport request)
+		{
+
+			ReportViewType reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
+			ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
+			QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+			ReportResult reportResult = reportBuilder.GetReportResult(queryResult.Items, reportRowType, request);
+			reportResult.TotalRecordCount = queryResult.TotalRecordCount;
+
+			return reportResult;
+		}
+
+		/// <summary> Gets query result. </summary>
+		/// <param name="request"> The request. </param>
+		/// <returns> The query result. </returns>
+		private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request)
+		{
+			// Placeholder in case needed later
+			request.Recursive = true;
+			var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+			request.Fields = "MediaSources,DateCreated,Settings,Studios,SyncInfo,ItemCounts";
+
+			var parentItem = string.IsNullOrEmpty(request.ParentId) ?
+				(user == null ? _libraryManager.RootFolder : user.RootFolder) :
+				_libraryManager.GetItemById(request.ParentId);
+
+			var item = string.IsNullOrEmpty(request.ParentId) ?
+				user == null ? _libraryManager.RootFolder : user.RootFolder :
+				parentItem;
+
+			IEnumerable<BaseItem> items;
+
+			if (request.Recursive)
+			{
+				var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+				return result;
+			}
+			else
+			{
+				if (user == null)
+				{
+					var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
+					return result;
+				}
+
+				var userRoot = item as UserRootFolder;
+
+				if (userRoot == null)
+				{
+					var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+
+					return result;
+				}
+
+				items = ((Folder)item).GetChildren(user, true);
+			}
+
+			return new QueryResult<BaseItem> { Items = items.ToArray() };
+
+		}
+
+		/// <summary> Gets items query. </summary>
+		/// <param name="request"> The request. </param>
+		/// <param name="user"> The user. </param>
+		/// <returns> The items query. </returns>
+		private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user)
+		{
+			var query = new InternalItemsQuery
+			{
+				User = user,
+				IsPlayed = request.IsPlayed,
+				MediaTypes = request.GetMediaTypes(),
+				IncludeItemTypes = request.GetIncludeItemTypes(),
+				ExcludeItemTypes = request.GetExcludeItemTypes(),
+				Recursive = true,
+				SortBy = request.GetOrderBy(),
+				SortOrder = request.SortOrder ?? SortOrder.Ascending,
+
+				Filter = i => ApplyAdditionalFilters(request, i, user, true, _libraryManager),
+				StartIndex = request.StartIndex,
+				IsMissing = request.IsMissing,
+				IsVirtualUnaired = request.IsVirtualUnaired,
+				IsUnaired = request.IsUnaired,
+				CollapseBoxSetItems = request.CollapseBoxSetItems,
+				NameLessThan = request.NameLessThan,
+				NameStartsWith = request.NameStartsWith,
+				NameStartsWithOrGreater = request.NameStartsWithOrGreater,
+				HasImdbId = request.HasImdbId,
+				IsYearMismatched = request.IsYearMismatched,
+				IsUnidentified = request.IsUnidentified,
+				IsPlaceHolder = request.IsPlaceHolder,
+				IsLocked = request.IsLocked,
+				IsInBoxSet = request.IsInBoxSet,
+				IsHD = request.IsHD,
+				Is3D = request.Is3D,
+				HasTvdbId = request.HasTvdbId,
+				HasTmdbId = request.HasTmdbId,
+				HasOverview = request.HasOverview,
+				HasOfficialRating = request.HasOfficialRating,
+				HasParentalRating = request.HasParentalRating,
+				HasSpecialFeature = request.HasSpecialFeature,
+				HasSubtitles = request.HasSubtitles,
+				HasThemeSong = request.HasThemeSong,
+				HasThemeVideo = request.HasThemeVideo,
+				HasTrailer = request.HasTrailer,
+				Tags = request.GetTags(),
+				OfficialRatings = request.GetOfficialRatings(),
+				Genres = request.GetGenres(),
+				Studios = request.GetStudios(),
+				StudioIds = request.GetStudioIds(),
+				Person = request.Person,
+				PersonIds = request.GetPersonIds(),
+				PersonTypes = request.GetPersonTypes(),
+				Years = request.GetYears(),
+				ImageTypes = request.GetImageTypes().ToArray(),
+				VideoTypes = request.GetVideoTypes().ToArray(),
+				AdjacentTo = request.AdjacentTo
+			};
+
+			if (!string.IsNullOrWhiteSpace(request.Ids))
+			{
+				query.CollapseBoxSetItems = false;
+			}
+
+			foreach (var filter in request.GetFilters())
+			{
+				switch (filter)
+				{
+					case ItemFilter.Dislikes:
+						query.IsLiked = false;
+						break;
+					case ItemFilter.IsFavorite:
+						query.IsFavorite = true;
+						break;
+					case ItemFilter.IsFavoriteOrLikes:
+						query.IsFavoriteOrLiked = true;
+						break;
+					case ItemFilter.IsFolder:
+						query.IsFolder = true;
+						break;
+					case ItemFilter.IsNotFolder:
+						query.IsFolder = false;
+						break;
+					case ItemFilter.IsPlayed:
+						query.IsPlayed = true;
+						break;
+					case ItemFilter.IsRecentlyAdded:
+						break;
+					case ItemFilter.IsResumable:
+						query.IsResumable = true;
+						break;
+					case ItemFilter.IsUnplayed:
+						query.IsPlayed = false;
+						break;
+					case ItemFilter.Likes:
+						query.IsLiked = true;
+						break;
+				}
+			}
+
+			if (request.HasQueryLimit)
+				query.Limit = request.Limit;
+			return query;
+		}
+
+		/// <summary> Applies filtering. </summary>
+		/// <param name="items"> The items. </param>
+		/// <param name="filter"> The filter. </param>
+		/// <param name="user"> The user. </param>
+		/// <param name="repository"> The repository. </param>
+		/// <returns> IEnumerable{BaseItem}. </returns>
+		internal static IEnumerable<BaseItem> ApplyFilter(IEnumerable<BaseItem> items, ItemFilter filter, User user, IUserDataManager repository)
+		{
+			// Avoid implicitly captured closure
+			var currentUser = user;
+
+			switch (filter)
+			{
+				case ItemFilter.IsFavoriteOrLikes:
+					return items.Where(item =>
+					{
+						var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
+
+						if (userdata == null)
+						{
+							return false;
+						}
+
+						var likes = userdata.Likes ?? false;
+						var favorite = userdata.IsFavorite;
+
+						return likes || favorite;
+					});
+
+				case ItemFilter.Likes:
+					return items.Where(item =>
+					{
+						var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
+
+						return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value;
+					});
+
+				case ItemFilter.Dislikes:
+					return items.Where(item =>
+					{
+						var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
+
+						return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value;
+					});
+
+				case ItemFilter.IsFavorite:
+					return items.Where(item =>
+					{
+						var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
+
+						return userdata != null && userdata.IsFavorite;
+					});
+
+				case ItemFilter.IsResumable:
+					return items.Where(item =>
+					{
+						var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
+
+						return userdata != null && userdata.PlaybackPositionTicks > 0;
+					});
+
+				case ItemFilter.IsPlayed:
+					return items.Where(item => item.IsPlayed(currentUser));
+
+				case ItemFilter.IsUnplayed:
+					return items.Where(item => item.IsUnplayed(currentUser));
+
+				case ItemFilter.IsFolder:
+					return items.Where(item => item.IsFolder);
+
+				case ItemFilter.IsNotFolder:
+					return items.Where(item => !item.IsFolder);
+
+				case ItemFilter.IsRecentlyAdded:
+					return items.Where(item => (DateTime.UtcNow - item.DateCreated).TotalDays <= 10);
+			}
+
+			return items;
+		}
+
+		/// <summary> Applies the additional filters. </summary>
+		/// <param name="request"> The request. </param>
+		/// <param name="i"> Zero-based index of the. </param>
+		/// <param name="user"> The user. </param>
+		/// <param name="isPreFiltered"> true if this object is pre filtered. </param>
+		/// <param name="libraryManager"> Manager for library. </param>
+		/// <returns> true if it succeeds, false if it fails. </returns>
         private bool ApplyAdditionalFilters(BaseReportRequest request, BaseItem i, User user, bool isPreFiltered, ILibraryManager libraryManager)
-        {
-            var video = i as Video;
-
-            if (!isPreFiltered)
-            {
-                var mediaTypes = request.GetMediaTypes();
-                if (mediaTypes.Length > 0)
-                {
-                    if (!(!string.IsNullOrEmpty(i.MediaType) && mediaTypes.Contains(i.MediaType, StringComparer.OrdinalIgnoreCase)))
-                    {
-                        return false;
-                    }
-                }
-
-                if (request.IsPlayed.HasValue)
-                {
-                    var val = request.IsPlayed.Value;
-                    if (i.IsPlayed(user) != val)
-                    {
-                        return false;
-                    }
-                }
-
-                // Exclude item types
-                var excluteItemTypes = request.GetExcludeItemTypes();
-                if (excluteItemTypes.Length > 0 && excluteItemTypes.Contains(i.GetType().Name, StringComparer.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
-
-                // Include item types
-                var includeItemTypes = request.GetIncludeItemTypes();
-                if (includeItemTypes.Length > 0 && !includeItemTypes.Contains(i.GetType().Name, StringComparer.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
-
-                if (request.IsInBoxSet.HasValue)
-                {
-                    var val = request.IsInBoxSet.Value;
-                    if (i.Parents.OfType<BoxSet>().Any() != val)
-                    {
-                        return false;
-                    }
-                }
-
-                // Filter by Video3DFormat
-                if (request.Is3D.HasValue)
-                {
-                    var val = request.Is3D.Value;
-
-                    if (video == null || val != video.Video3DFormat.HasValue)
-                    {
-                        return false;
-                    }
-                }
-
-                if (request.IsHD.HasValue)
-                {
-                    var val = request.IsHD.Value;
-
-                    if (video == null || val != video.IsHD)
-                    {
-                        return false;
-                    }
-                }
-
-                if (request.IsUnidentified.HasValue)
-                {
-                    var val = request.IsUnidentified.Value;
-                    if (i.IsUnidentified != val)
-                    {
-                        return false;
-                    }
-                }
-
-                if (request.IsLocked.HasValue)
-                {
-                    var val = request.IsLocked.Value;
-                    if (i.IsLocked != val)
-                    {
-                        return false;
-                    }
-                }
-
-                if (request.HasOverview.HasValue)
-                {
-                    var filterValue = request.HasOverview.Value;
-
-                    var hasValue = !string.IsNullOrEmpty(i.Overview);
-
-                    if (hasValue != filterValue)
-                    {
-                        return false;
-                    }
-                }
-
-                if (request.HasImdbId.HasValue)
-                {
-                    var filterValue = request.HasImdbId.Value;
-
-                    var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Imdb));
-
-                    if (hasValue != filterValue)
-                    {
-                        return false;
-                    }
-                }
-
-                if (request.HasTmdbId.HasValue)
-                {
-                    var filterValue = request.HasTmdbId.Value;
-
-                    var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tmdb));
-
-                    if (hasValue != filterValue)
-                    {
-                        return false;
-                    }
-                }
-
-                if (request.HasTvdbId.HasValue)
-                {
-                    var filterValue = request.HasTvdbId.Value;
-
-                    var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb));
-
-                    if (hasValue != filterValue)
-                    {
-                        return false;
-                    }
-                }
-
-                if (request.IsYearMismatched.HasValue)
-                {
-                    var filterValue = request.IsYearMismatched.Value;
-
-                    if (UserViewBuilder.IsYearMismatched(i, libraryManager) != filterValue)
-                    {
-                        return false;
-                    }
-                }
-
-                if (request.HasOfficialRating.HasValue)
-                {
-                    var filterValue = request.HasOfficialRating.Value;
-
-                    var hasValue = !string.IsNullOrEmpty(i.OfficialRating);
-
-                    if (hasValue != filterValue)
-                    {
-                        return false;
-                    }
-                }
-
-                if (request.IsPlaceHolder.HasValue)
-                {
-                    var filterValue = request.IsPlaceHolder.Value;
-
-                    var isPlaceHolder = false;
-
-                    var hasPlaceHolder = i as ISupportsPlaceHolders;
-
-                    if (hasPlaceHolder != null)
-                    {
-                        isPlaceHolder = hasPlaceHolder.IsPlaceHolder;
-                    }
-
-                    if (isPlaceHolder != filterValue)
-                    {
-                        return false;
-                    }
-                }
-
-                if (request.HasSpecialFeature.HasValue)
-                {
-                    var filterValue = request.HasSpecialFeature.Value;
-
-                    var movie = i as IHasSpecialFeatures;
-
-                    if (movie != null)
-                    {
-                        var ok = filterValue
-                            ? movie.SpecialFeatureIds.Count > 0
-                            : movie.SpecialFeatureIds.Count == 0;
-
-                        if (!ok)
-                        {
-                            return false;
-                        }
-                    }
-                    else
-                    {
-                        return false;
-                    }
-                }
-
-                if (request.HasSubtitles.HasValue)
-                {
-                    var val = request.HasSubtitles.Value;
-
-                    if (video == null || val != video.HasSubtitles)
-                    {
-                        return false;
-                    }
-                }
-
-                if (request.HasParentalRating.HasValue)
-                {
-                    var val = request.HasParentalRating.Value;
-
-                    var rating = i.CustomRating;
-
-                    if (string.IsNullOrEmpty(rating))
-                    {
-                        rating = i.OfficialRating;
-                    }
-
-                    if (val)
-                    {
-                        if (string.IsNullOrEmpty(rating))
-                        {
-                            return false;
-                        }
-                    }
-                    else
-                    {
-                        if (!string.IsNullOrEmpty(rating))
-                        {
-                            return false;
-                        }
-                    }
-                }
-
-                if (request.HasTrailer.HasValue)
-                {
-                    var val = request.HasTrailer.Value;
-                    var trailerCount = 0;
-
-                    var hasTrailers = i as IHasTrailers;
-                    if (hasTrailers != null)
-                    {
-                        trailerCount = hasTrailers.GetTrailerIds().Count;
-                    }
-
-                    var ok = val ? trailerCount > 0 : trailerCount == 0;
-
-                    if (!ok)
-                    {
-                        return false;
-                    }
-                }
-
-                if (request.HasThemeSong.HasValue)
-                {
-                    var filterValue = request.HasThemeSong.Value;
-
-                    var themeCount = 0;
-                    var iHasThemeMedia = i as IHasThemeMedia;
-
-                    if (iHasThemeMedia != null)
-                    {
-                        themeCount = iHasThemeMedia.ThemeSongIds.Count;
-                    }
-                    var ok = filterValue ? themeCount > 0 : themeCount == 0;
-
-                    if (!ok)
-                    {
-                        return false;
-                    }
-                }
-
-                if (request.HasThemeVideo.HasValue)
-                {
-                    var filterValue = request.HasThemeVideo.Value;
-
-                    var themeCount = 0;
-                    var iHasThemeMedia = i as IHasThemeMedia;
-
-                    if (iHasThemeMedia != null)
-                    {
-                        themeCount = iHasThemeMedia.ThemeVideoIds.Count;
-                    }
-                    var ok = filterValue ? themeCount > 0 : themeCount == 0;
-
-                    if (!ok)
-                    {
-                        return false;
-                    }
-                }
-
-                // Apply tag filter
-                var tags = request.GetTags();
-                if (tags.Length > 0)
-                {
-                    var hasTags = i as IHasTags;
-                    if (hasTags == null)
-                    {
-                        return false;
-                    }
-                    if (!(tags.Any(v => hasTags.Tags.Contains(v, StringComparer.OrdinalIgnoreCase))))
-                    {
-                        return false;
-                    }
-                }
-
-                // Apply official rating filter
-                var officialRatings = request.GetOfficialRatings();
-                if (officialRatings.Length > 0 && !officialRatings.Contains(i.OfficialRating ?? string.Empty))
-                {
-                    return false;
-                }
-
-                // Apply genre filter
-                var genres = request.GetGenres();
-                if (genres.Length > 0 && !(genres.Any(v => i.Genres.Contains(v, StringComparer.OrdinalIgnoreCase))))
-                {
-                    return false;
-                }
-
-                // Filter by VideoType
-                var videoTypes = request.GetVideoTypes();
-                if (videoTypes.Length > 0 && (video == null || !videoTypes.Contains(video.VideoType)))
-                {
-                    return false;
-                }
-
-                var imageTypes = request.GetImageTypes().ToList();
-                if (imageTypes.Count > 0)
-                {
-                    if (!(imageTypes.Any(i.HasImage)))
-                    {
-                        return false;
-                    }
-                }
-
-                // Apply studio filter
-                var studios = request.GetStudios();
-                if (studios.Length > 0 && !studios.Any(v => i.Studios.Contains(v, StringComparer.OrdinalIgnoreCase)))
-                {
-                    return false;
-                }
-
-                // Apply studio filter
-                var studioIds = request.GetStudioIds();
-                if (studioIds.Length > 0 && !studioIds.Any(id =>
-                {
-                    var studioItem = libraryManager.GetItemById(id);
-                    return studioItem != null && i.Studios.Contains(studioItem.Name, StringComparer.OrdinalIgnoreCase);
-                }))
-                {
-                    return false;
-                }
-
-                // Apply year filter
-                var years = request.GetYears();
-                if (years.Length > 0 && !(i.ProductionYear.HasValue && years.Contains(i.ProductionYear.Value)))
-                {
-                    return false;
-                }
-
-                // Apply person filter
-                var personIds = request.GetPersonIds();
-                if (personIds.Length > 0)
-                {
-                    var names = personIds
-                        .Select(libraryManager.GetItemById)
-                        .Select(p => p == null ? "-1" : p.Name)
-                        .ToList();
-
-                    if (!(names.Any(v => i.People.Select(p => p.Name).Contains(v, StringComparer.OrdinalIgnoreCase))))
-                    {
-                        return false;
-                    }
-                }
-
-                // Apply person filter
-                if (!string.IsNullOrEmpty(request.Person))
-                {
-                    var personTypes = request.GetPersonTypes();
-
-                    if (personTypes.Length == 0)
-                    {
-                        if (!(i.People.Any(p => string.Equals(p.Name, request.Person, StringComparison.OrdinalIgnoreCase))))
-                        {
-                            return false;
-                        }
-                    }
-                    else
-                    {
-                        var types = personTypes;
-
-                        var ok = new[] { i }.Any(item =>
-                                item.People != null &&
-                                item.People.Any(p =>
-                                    p.Name.Equals(request.Person, StringComparison.OrdinalIgnoreCase) && (types.Contains(p.Type, StringComparer.OrdinalIgnoreCase) || types.Contains(p.Role, StringComparer.OrdinalIgnoreCase))));
-
-                        if (!ok)
-                        {
-                            return false;
-                        }
-                    }
-                }
-            }
-
-            if (request.MinCommunityRating.HasValue)
-            {
-                var val = request.MinCommunityRating.Value;
-
-                if (!(i.CommunityRating.HasValue && i.CommunityRating >= val))
-                {
-                    return false;
-                }
-            }
-
-            if (request.MinCriticRating.HasValue)
-            {
-                var val = request.MinCriticRating.Value;
-
-                var hasCriticRating = i as IHasCriticRating;
-
-                if (hasCriticRating != null)
-                {
-                    if (!(hasCriticRating.CriticRating.HasValue && hasCriticRating.CriticRating >= val))
-                    {
-                        return false;
-                    }
-                }
-                else
-                {
-                    return false;
-                }
-            }
-
-            // Artists
-            if (!string.IsNullOrEmpty(request.ArtistIds))
-            {
-                var artistIds = request.ArtistIds.Split('|');
-
-                var audio = i as IHasArtist;
-
-                if (!(audio != null && artistIds.Any(id =>
-                {
-                    var artistItem = libraryManager.GetItemById(id);
-                    return artistItem != null && audio.HasAnyArtist(artistItem.Name);
-                })))
-                {
-                    return false;
-                }
-            }
-
-            // Artists
-            if (!string.IsNullOrEmpty(request.Artists))
-            {
-                var artists = request.Artists.Split('|');
-
-                var audio = i as IHasArtist;
-
-                if (!(audio != null && artists.Any(audio.HasAnyArtist)))
-                {
-                    return false;
-                }
-            }
-
-            // Albums
-            if (!string.IsNullOrEmpty(request.Albums))
-            {
-                var albums = request.Albums.Split('|');
-
-                var audio = i as Audio;
-
-                if (audio != null)
-                {
-                    if (!albums.Any(a => string.Equals(a, audio.Album, StringComparison.OrdinalIgnoreCase)))
-                    {
-                        return false;
-                    }
-                }
-
-                var album = i as MusicAlbum;
-
-                if (album != null)
-                {
-                    if (!albums.Any(a => string.Equals(a, album.Name, StringComparison.OrdinalIgnoreCase)))
-                    {
-                        return false;
-                    }
-                }
-
-                var musicVideo = i as MusicVideo;
-
-                if (musicVideo != null)
-                {
-                    if (!albums.Any(a => string.Equals(a, musicVideo.Album, StringComparison.OrdinalIgnoreCase)))
-                    {
-                        return false;
-                    }
-                }
-
-                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))
-            {
-                var level = _localization.GetRatingLevel(request.MinOfficialRating);
-
-                if (level.HasValue)
-                {
-                    var rating = i.CustomRating;
-
-                    if (string.IsNullOrEmpty(rating))
-                    {
-                        rating = i.OfficialRating;
-                    }
-
-                    if (!string.IsNullOrEmpty(rating))
-                    {
-                        var itemLevel = _localization.GetRatingLevel(rating);
-
-                        if (!(!itemLevel.HasValue || itemLevel.Value >= level.Value))
-                        {
-                            return false;
-                        }
-                    }
-                }
-            }
-
-            // Max official rating
-            if (!string.IsNullOrEmpty(request.MaxOfficialRating))
-            {
-                var level = _localization.GetRatingLevel(request.MaxOfficialRating);
-
-                if (level.HasValue)
-                {
-                    var rating = i.CustomRating;
-
-                    if (string.IsNullOrEmpty(rating))
-                    {
-                        rating = i.OfficialRating;
-                    }
-
-                    if (!string.IsNullOrEmpty(rating))
-                    {
-                        var itemLevel = _localization.GetRatingLevel(rating);
-
-                        if (!(!itemLevel.HasValue || itemLevel.Value <= level.Value))
-                        {
-                            return false;
-                        }
-                    }
-                }
-            }
-
-            // LocationTypes
-            if (!string.IsNullOrEmpty(request.LocationTypes))
-            {
-                var vals = request.LocationTypes.Split(',');
-                if (!vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
-            }
-
-            // ExcludeLocationTypes
-            if (!string.IsNullOrEmpty(request.ExcludeLocationTypes))
-            {
-                var vals = request.ExcludeLocationTypes.Split(',');
-                if (vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
-            }
-
-            if (!string.IsNullOrEmpty(request.AlbumArtistStartsWithOrGreater))
-            {
-                var ok = new[] { i }.OfType<IHasAlbumArtist>()
-                    .Any(p => string.Compare(request.AlbumArtistStartsWithOrGreater, p.AlbumArtists.FirstOrDefault(), StringComparison.CurrentCultureIgnoreCase) < 1);
-
-                if (!ok)
-                {
-                    return false;
-                }
-            }
-
-            // Filter by Series Status
-            if (!string.IsNullOrEmpty(request.SeriesStatus))
-            {
-                var vals = request.SeriesStatus.Split(',');
-
-                var ok = new[] { i }.OfType<Series>().Any(p => p.Status.HasValue && vals.Contains(p.Status.Value.ToString(), StringComparer.OrdinalIgnoreCase));
-
-                if (!ok)
-                {
-                    return false;
-                }
-            }
-
-            // Filter by Series AirDays
-            if (!string.IsNullOrEmpty(request.AirDays))
-            {
-                var days = request.AirDays.Split(',').Select(d => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), d, true));
-
-                var ok = new[] { i }.OfType<Series>().Any(p => p.AirDays != null && days.Any(d => p.AirDays.Contains(d)));
-
-                if (!ok)
-                {
-                    return false;
-                }
-            }
-
-            if (request.MinPlayers.HasValue)
-            {
-                var filterValue = request.MinPlayers.Value;
-
-                var game = i as Game;
-
-                if (game != null)
-                {
-                    var players = game.PlayersSupported ?? 1;
-
-                    var ok = players >= filterValue;
-
-                    if (!ok)
-                    {
-                        return false;
-                    }
-                }
-                else
-                {
-                    return false;
-                }
-            }
-
-            if (request.MaxPlayers.HasValue)
-            {
-                var filterValue = request.MaxPlayers.Value;
-
-                var game = i as Game;
-
-                if (game != null)
-                {
-                    var players = game.PlayersSupported ?? 1;
-
-                    var ok = players <= filterValue;
-
-                    if (!ok)
-                    {
-                        return false;
-                    }
-                }
-                else
-                {
-                    return false;
-                }
-            }
-
-            if (request.ParentIndexNumber.HasValue)
-            {
-                var filterValue = request.ParentIndexNumber.Value;
-
-                var episode = i as Episode;
-
-                if (episode != null)
-                {
-                    if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value != filterValue)
-                    {
-                        return false;
-                    }
-                }
-
-                var song = i as Audio;
-
-                if (song != null)
-                {
-                    if (song.ParentIndexNumber.HasValue && song.ParentIndexNumber.Value != filterValue)
-                    {
-                        return false;
-                    }
-                }
-            }
-
-            if (request.AiredDuringSeason.HasValue)
-            {
-                var episode = i as Episode;
-
-                if (episode == null)
-                {
-                    return false;
-                }
-
-                if (!Series.FilterEpisodesBySeason(new[] { episode }, request.AiredDuringSeason.Value, true).Any())
-                {
-                    return false;
-                }
-            }
-
-            if (!string.IsNullOrEmpty(request.MinPremiereDate))
-            {
-                var date = DateTime.Parse(request.MinPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
-
-                if (!(i.PremiereDate.HasValue && i.PremiereDate.Value >= date))
-                {
-                    return false;
-                }
-            }
-
-            if (!string.IsNullOrEmpty(request.MaxPremiereDate))
-            {
-                var date = DateTime.Parse(request.MaxPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
-
-                if (!(i.PremiereDate.HasValue && i.PremiereDate.Value <= date))
-                {
-                    return false;
-                }
-            }
-
-            return true;
-        }
-
-        /// <summary> Applies the paging. </summary>
-        /// <param name="request"> The request. </param>
-        /// <param name="items"> The items. </param>
-        /// <returns> IEnumerable{BaseItem}. </returns>
-        private IEnumerable<BaseItem> ApplyPaging(GetItems request, IEnumerable<BaseItem> items)
-        {
-            // Start at
-            if (request.StartIndex.HasValue)
-            {
-                items = items.Skip(request.StartIndex.Value);
-            }
-
-            // Return limit
-            if (request.Limit.HasValue)
-            {
-                items = items.Take(request.Limit.Value);
-            }
-
-            return items;
-        }
-
-        /// <summary> Gets items query. </summary>
-        /// <param name="request"> The request. </param>
-        /// <param name="user"> The user. </param>
-        /// <returns> The items query. </returns>
-        private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user)
-        {
-            var query = new InternalItemsQuery
-            {
-                User = user,
-                IsPlayed = request.IsPlayed,
-                MediaTypes = request.GetMediaTypes(),
-                IncludeItemTypes = request.GetIncludeItemTypes(),
-                ExcludeItemTypes = request.GetExcludeItemTypes(),
-                Recursive = true,
-                SortBy = request.GetOrderBy(),
-                SortOrder = request.SortOrder ?? SortOrder.Ascending,
-
-                Filter = i => ApplyAdditionalFilters(request, i, user, true, _libraryManager),
-                StartIndex = request.StartIndex,
-                IsMissing = request.IsMissing,
-                IsVirtualUnaired = request.IsVirtualUnaired,
-                IsUnaired = request.IsUnaired,
-                CollapseBoxSetItems = request.CollapseBoxSetItems,
-                NameLessThan = request.NameLessThan,
-                NameStartsWith = request.NameStartsWith,
-                NameStartsWithOrGreater = request.NameStartsWithOrGreater,
-                HasImdbId = request.HasImdbId,
-                IsYearMismatched = request.IsYearMismatched,
-                IsUnidentified = request.IsUnidentified,
-                IsPlaceHolder = request.IsPlaceHolder,
-                IsLocked = request.IsLocked,
-                IsInBoxSet = request.IsInBoxSet,
-                IsHD = request.IsHD,
-                Is3D = request.Is3D,
-                HasTvdbId = request.HasTvdbId,
-                HasTmdbId = request.HasTmdbId,
-                HasOverview = request.HasOverview,
-                HasOfficialRating = request.HasOfficialRating,
-                HasParentalRating = request.HasParentalRating,
-                HasSpecialFeature = request.HasSpecialFeature,
-                HasSubtitles = request.HasSubtitles,
-                HasThemeSong = request.HasThemeSong,
-                HasThemeVideo = request.HasThemeVideo,
-                HasTrailer = request.HasTrailer,
-                Tags = request.GetTags(),
-                OfficialRatings = request.GetOfficialRatings(),
-                Genres = request.GetGenres(),
-                Studios = request.GetStudios(),
-                StudioIds = request.GetStudioIds(),
-                Person = request.Person,
-                PersonIds = request.GetPersonIds(),
-                PersonTypes = request.GetPersonTypes(),
-                Years = request.GetYears(),
-                ImageTypes = request.GetImageTypes().ToArray(),
-                VideoTypes = request.GetVideoTypes().ToArray(),
-                AdjacentTo = request.AdjacentTo
-            };
-
-            if (!string.IsNullOrWhiteSpace(request.Ids))
-            {
-                query.CollapseBoxSetItems = false;
-            }
-
-            foreach (var filter in request.GetFilters())
-            {
-                switch (filter)
-                {
-                    case ItemFilter.Dislikes:
-                        query.IsLiked = false;
-                        break;
-                    case ItemFilter.IsFavorite:
-                        query.IsFavorite = true;
-                        break;
-                    case ItemFilter.IsFavoriteOrLikes:
-                        query.IsFavoriteOrLiked = true;
-                        break;
-                    case ItemFilter.IsFolder:
-                        query.IsFolder = true;
-                        break;
-                    case ItemFilter.IsNotFolder:
-                        query.IsFolder = false;
-                        break;
-                    case ItemFilter.IsPlayed:
-                        query.IsPlayed = true;
-                        break;
-                    case ItemFilter.IsRecentlyAdded:
-                        break;
-                    case ItemFilter.IsResumable:
-                        query.IsResumable = true;
-                        break;
-                    case ItemFilter.IsUnplayed:
-                        query.IsPlayed = false;
-                        break;
-                    case ItemFilter.Likes:
-                        query.IsLiked = true;
-                        break;
-                }
-            }
-
-            if (request.HasQueryLimit)
-                query.Limit = request.Limit;
-            return query;
-        }
-
-        /// <summary> Gets query result. </summary>
-        /// <param name="request"> The request. </param>
-        /// <returns> The query result. </returns>
-        private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request)
-        {
-            // Placeholder in case needed later
-            request.Recursive = true;
-            var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
-            request.Fields = "MediaSources,DateCreated,Settings,Studios,SyncInfo,ItemCounts";
-
-            var parentItem = string.IsNullOrEmpty(request.ParentId) ?
-                (user == null ? _libraryManager.RootFolder : user.RootFolder) :
-                _libraryManager.GetItemById(request.ParentId);
-
-            var item = string.IsNullOrEmpty(request.ParentId) ?
-                user == null ? _libraryManager.RootFolder : user.RootFolder :
-                parentItem;
-
-            IEnumerable<BaseItem> items;
-
-            if (request.Recursive)
-            {
-                var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
-                return result;
-            }
-            else
-            {
-                if (user == null)
-                {
-                    var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
-                    return result;
-                }
-
-                var userRoot = item as UserRootFolder;
-
-                if (userRoot == null)
-                {
-                    var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
-
-                    return result;
-                }
-
-                items = ((Folder)item).GetChildren(user, true);
-            }
-
-            return new QueryResult<BaseItem> { Items = items.ToArray() };
-
-        }
-
-        /// <summary> Gets report activities. </summary>
-        /// <param name="request"> The request. </param>
-        /// <returns> The report activities. </returns>
-        private Task<ReportResult> GetReportActivities(IReportsDownload request)
-        {
-            return Task<ReportResult>.Run(() =>
-            {
-                DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
-                (DateTime?)null :
-                DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
-                var queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
-                //var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
-
-                ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager);
-                var result = builder.GetResult(queryResult, request);
-                result.TotalRecordCount = queryResult.TotalRecordCount;
-                return result;
-
-            });
-
-        }
-
-        /// <summary> Gets report result. </summary>
-        /// <param name="request"> The request. </param>
-        /// <returns> The report result. </returns>
-        private async Task<ReportResult> GetReportResult(GetItemReport request)
-        {
-            ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
-            QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
-            ReportResult reportResult = reportBuilder.GetResult(queryResult.Items, request);
-            reportResult.TotalRecordCount = queryResult.TotalRecordCount;
-
-            return reportResult;
-        }
-
-        /// <summary> Gets report statistic. </summary>
-        /// <param name="request"> The request. </param>
-        /// <returns> The report statistic. </returns>
-        private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request)
-        {
-            ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
-            QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
-
-            ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager);
-            ReportStatResult reportResult = reportBuilder.GetResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5);
-            reportResult.TotalRecordCount = reportResult.Groups.Count();
-            return reportResult;
-        }
-
-        #endregion
-
-    }
+		{
+			var video = i as Video;
+
+			if (!isPreFiltered)
+			{
+				var mediaTypes = request.GetMediaTypes();
+				if (mediaTypes.Length > 0)
+				{
+					if (!(!string.IsNullOrEmpty(i.MediaType) && mediaTypes.Contains(i.MediaType, StringComparer.OrdinalIgnoreCase)))
+					{
+						return false;
+					}
+				}
+
+				if (request.IsPlayed.HasValue)
+				{
+					var val = request.IsPlayed.Value;
+					if (i.IsPlayed(user) != val)
+					{
+						return false;
+					}
+				}
+
+				// Exclude item types
+				var excluteItemTypes = request.GetExcludeItemTypes();
+				if (excluteItemTypes.Length > 0 && excluteItemTypes.Contains(i.GetType().Name, StringComparer.OrdinalIgnoreCase))
+				{
+					return false;
+				}
+
+				// Include item types
+				var includeItemTypes = request.GetIncludeItemTypes();
+				if (includeItemTypes.Length > 0 && !includeItemTypes.Contains(i.GetType().Name, StringComparer.OrdinalIgnoreCase))
+				{
+					return false;
+				}
+
+				if (request.IsInBoxSet.HasValue)
+				{
+					var val = request.IsInBoxSet.Value;
+					if (i.Parents.OfType<BoxSet>().Any() != val)
+					{
+						return false;
+					}
+				}
+
+				// Filter by Video3DFormat
+				if (request.Is3D.HasValue)
+				{
+					var val = request.Is3D.Value;
+
+					if (video == null || val != video.Video3DFormat.HasValue)
+					{
+						return false;
+					}
+				}
+
+				if (request.IsHD.HasValue)
+				{
+					var val = request.IsHD.Value;
+
+					if (video == null || val != video.IsHD)
+					{
+						return false;
+					}
+				}
+
+				if (request.IsUnidentified.HasValue)
+				{
+					var val = request.IsUnidentified.Value;
+					if (i.IsUnidentified != val)
+					{
+						return false;
+					}
+				}
+
+				if (request.IsLocked.HasValue)
+				{
+					var val = request.IsLocked.Value;
+					if (i.IsLocked != val)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasOverview.HasValue)
+				{
+					var filterValue = request.HasOverview.Value;
+
+					var hasValue = !string.IsNullOrEmpty(i.Overview);
+
+					if (hasValue != filterValue)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasImdbId.HasValue)
+				{
+					var filterValue = request.HasImdbId.Value;
+
+					var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Imdb));
+
+					if (hasValue != filterValue)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasTmdbId.HasValue)
+				{
+					var filterValue = request.HasTmdbId.Value;
+
+					var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tmdb));
+
+					if (hasValue != filterValue)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasTvdbId.HasValue)
+				{
+					var filterValue = request.HasTvdbId.Value;
+
+					var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb));
+
+					if (hasValue != filterValue)
+					{
+						return false;
+					}
+				}
+
+				if (request.IsYearMismatched.HasValue)
+				{
+					var filterValue = request.IsYearMismatched.Value;
+
+					if (UserViewBuilder.IsYearMismatched(i, libraryManager) != filterValue)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasOfficialRating.HasValue)
+				{
+					var filterValue = request.HasOfficialRating.Value;
+
+					var hasValue = !string.IsNullOrEmpty(i.OfficialRating);
+
+					if (hasValue != filterValue)
+					{
+						return false;
+					}
+				}
+
+				if (request.IsPlaceHolder.HasValue)
+				{
+					var filterValue = request.IsPlaceHolder.Value;
+
+					var isPlaceHolder = false;
+
+					var hasPlaceHolder = i as ISupportsPlaceHolders;
+
+					if (hasPlaceHolder != null)
+					{
+						isPlaceHolder = hasPlaceHolder.IsPlaceHolder;
+					}
+
+					if (isPlaceHolder != filterValue)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasSpecialFeature.HasValue)
+				{
+					var filterValue = request.HasSpecialFeature.Value;
+
+					var movie = i as IHasSpecialFeatures;
+
+					if (movie != null)
+					{
+						var ok = filterValue
+							? movie.SpecialFeatureIds.Count > 0
+							: movie.SpecialFeatureIds.Count == 0;
+
+						if (!ok)
+						{
+							return false;
+						}
+					}
+					else
+					{
+						return false;
+					}
+				}
+
+				if (request.HasSubtitles.HasValue)
+				{
+					var val = request.HasSubtitles.Value;
+
+					if (video == null || val != video.HasSubtitles)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasParentalRating.HasValue)
+				{
+					var val = request.HasParentalRating.Value;
+
+					var rating = i.CustomRating;
+
+					if (string.IsNullOrEmpty(rating))
+					{
+						rating = i.OfficialRating;
+					}
+
+					if (val)
+					{
+						if (string.IsNullOrEmpty(rating))
+						{
+							return false;
+						}
+					}
+					else
+					{
+						if (!string.IsNullOrEmpty(rating))
+						{
+							return false;
+						}
+					}
+				}
+
+				if (request.HasTrailer.HasValue)
+				{
+					var val = request.HasTrailer.Value;
+					var trailerCount = 0;
+
+					var hasTrailers = i as IHasTrailers;
+					if (hasTrailers != null)
+					{
+						trailerCount = hasTrailers.GetTrailerIds().Count;
+					}
+
+					var ok = val ? trailerCount > 0 : trailerCount == 0;
+
+					if (!ok)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasThemeSong.HasValue)
+				{
+					var filterValue = request.HasThemeSong.Value;
+
+					var themeCount = 0;
+					var iHasThemeMedia = i as IHasThemeMedia;
+
+					if (iHasThemeMedia != null)
+					{
+						themeCount = iHasThemeMedia.ThemeSongIds.Count;
+					}
+					var ok = filterValue ? themeCount > 0 : themeCount == 0;
+
+					if (!ok)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasThemeVideo.HasValue)
+				{
+					var filterValue = request.HasThemeVideo.Value;
+
+					var themeCount = 0;
+					var iHasThemeMedia = i as IHasThemeMedia;
+
+					if (iHasThemeMedia != null)
+					{
+						themeCount = iHasThemeMedia.ThemeVideoIds.Count;
+					}
+					var ok = filterValue ? themeCount > 0 : themeCount == 0;
+
+					if (!ok)
+					{
+						return false;
+					}
+				}
+
+				// Apply tag filter
+				var tags = request.GetTags();
+				if (tags.Length > 0)
+				{
+					var hasTags = i as IHasTags;
+					if (hasTags == null)
+					{
+						return false;
+					}
+					if (!(tags.Any(v => hasTags.Tags.Contains(v, StringComparer.OrdinalIgnoreCase))))
+					{
+						return false;
+					}
+				}
+
+				// Apply official rating filter
+				var officialRatings = request.GetOfficialRatings();
+				if (officialRatings.Length > 0 && !officialRatings.Contains(i.OfficialRating ?? string.Empty))
+				{
+					return false;
+				}
+
+				// Apply genre filter
+				var genres = request.GetGenres();
+				if (genres.Length > 0 && !(genres.Any(v => i.Genres.Contains(v, StringComparer.OrdinalIgnoreCase))))
+				{
+					return false;
+				}
+
+				// Filter by VideoType
+				var videoTypes = request.GetVideoTypes();
+				if (videoTypes.Length > 0 && (video == null || !videoTypes.Contains(video.VideoType)))
+				{
+					return false;
+				}
+
+				var imageTypes = request.GetImageTypes().ToList();
+				if (imageTypes.Count > 0)
+				{
+					if (!(imageTypes.Any(i.HasImage)))
+					{
+						return false;
+					}
+				}
+
+				// Apply studio filter
+				var studios = request.GetStudios();
+				if (studios.Length > 0 && !studios.Any(v => i.Studios.Contains(v, StringComparer.OrdinalIgnoreCase)))
+				{
+					return false;
+				}
+
+				// Apply studio filter
+				var studioIds = request.GetStudioIds();
+				if (studioIds.Length > 0 && !studioIds.Any(id =>
+				{
+					var studioItem = libraryManager.GetItemById(id);
+					return studioItem != null && i.Studios.Contains(studioItem.Name, StringComparer.OrdinalIgnoreCase);
+				}))
+				{
+					return false;
+				}
+
+				// Apply year filter
+				var years = request.GetYears();
+				if (years.Length > 0 && !(i.ProductionYear.HasValue && years.Contains(i.ProductionYear.Value)))
+				{
+					return false;
+				}
+
+				// Apply person filter
+				var personIds = request.GetPersonIds();
+				if (personIds.Length > 0)
+				{
+					var names = personIds
+						.Select(libraryManager.GetItemById)
+						.Select(p => p == null ? "-1" : p.Name)
+						.ToList();
+
+					if (!(names.Any(v => _libraryManager.GetPeople(i).Select(p => p.Name).Contains(v, StringComparer.OrdinalIgnoreCase))))
+					{
+						return false;
+					}
+				}
+
+				// Apply person filter
+				if (!string.IsNullOrEmpty(request.Person))
+				{
+					var personTypes = request.GetPersonTypes();
+
+					if (personTypes.Length == 0)
+					{
+                        if (!(_libraryManager.GetPeople(i).Any(p => string.Equals(p.Name, request.Person, StringComparison.OrdinalIgnoreCase))))
+						{
+							return false;
+						}
+					}
+					else
+					{
+						var types = personTypes;
+
+						var ok = new[] { i }.Any(item =>
+                                _libraryManager.GetPeople(i).Any(p =>
+									p.Name.Equals(request.Person, StringComparison.OrdinalIgnoreCase) && (types.Contains(p.Type, StringComparer.OrdinalIgnoreCase) || types.Contains(p.Role, StringComparer.OrdinalIgnoreCase))));
+
+						if (!ok)
+						{
+							return false;
+						}
+					}
+				}
+			}
+
+			if (request.MinCommunityRating.HasValue)
+			{
+				var val = request.MinCommunityRating.Value;
+
+				if (!(i.CommunityRating.HasValue && i.CommunityRating >= val))
+				{
+					return false;
+				}
+			}
+
+			if (request.MinCriticRating.HasValue)
+			{
+				var val = request.MinCriticRating.Value;
+
+				var hasCriticRating = i as IHasCriticRating;
+
+				if (hasCriticRating != null)
+				{
+					if (!(hasCriticRating.CriticRating.HasValue && hasCriticRating.CriticRating >= val))
+					{
+						return false;
+					}
+				}
+				else
+				{
+					return false;
+				}
+			}
+
+			// Artists
+			if (!string.IsNullOrEmpty(request.ArtistIds))
+			{
+				var artistIds = request.ArtistIds.Split('|');
+
+				var audio = i as IHasArtist;
+
+				if (!(audio != null && artistIds.Any(id =>
+				{
+					var artistItem = libraryManager.GetItemById(id);
+					return artistItem != null && audio.HasAnyArtist(artistItem.Name);
+				})))
+				{
+					return false;
+				}
+			}
+
+			// Artists
+			if (!string.IsNullOrEmpty(request.Artists))
+			{
+				var artists = request.Artists.Split('|');
+
+				var audio = i as IHasArtist;
+
+				if (!(audio != null && artists.Any(audio.HasAnyArtist)))
+				{
+					return false;
+				}
+			}
+
+			// Albums
+			if (!string.IsNullOrEmpty(request.Albums))
+			{
+				var albums = request.Albums.Split('|');
+
+				var audio = i as Audio;
+
+				if (audio != null)
+				{
+					if (!albums.Any(a => string.Equals(a, audio.Album, StringComparison.OrdinalIgnoreCase)))
+					{
+						return false;
+					}
+				}
+
+				var album = i as MusicAlbum;
+
+				if (album != null)
+				{
+					if (!albums.Any(a => string.Equals(a, album.Name, StringComparison.OrdinalIgnoreCase)))
+					{
+						return false;
+					}
+				}
+
+				var musicVideo = i as MusicVideo;
+
+				if (musicVideo != null)
+				{
+					if (!albums.Any(a => string.Equals(a, musicVideo.Album, StringComparison.OrdinalIgnoreCase)))
+					{
+						return false;
+					}
+				}
+
+				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))
+			{
+				var level = _localization.GetRatingLevel(request.MinOfficialRating);
+
+				if (level.HasValue)
+				{
+					var rating = i.CustomRating;
+
+					if (string.IsNullOrEmpty(rating))
+					{
+						rating = i.OfficialRating;
+					}
+
+					if (!string.IsNullOrEmpty(rating))
+					{
+						var itemLevel = _localization.GetRatingLevel(rating);
+
+						if (!(!itemLevel.HasValue || itemLevel.Value >= level.Value))
+						{
+							return false;
+						}
+					}
+				}
+			}
+
+			// Max official rating
+			if (!string.IsNullOrEmpty(request.MaxOfficialRating))
+			{
+				var level = _localization.GetRatingLevel(request.MaxOfficialRating);
+
+				if (level.HasValue)
+				{
+					var rating = i.CustomRating;
+
+					if (string.IsNullOrEmpty(rating))
+					{
+						rating = i.OfficialRating;
+					}
+
+					if (!string.IsNullOrEmpty(rating))
+					{
+						var itemLevel = _localization.GetRatingLevel(rating);
+
+						if (!(!itemLevel.HasValue || itemLevel.Value <= level.Value))
+						{
+							return false;
+						}
+					}
+				}
+			}
+
+			// LocationTypes
+			if (!string.IsNullOrEmpty(request.LocationTypes))
+			{
+				var vals = request.LocationTypes.Split(',');
+				if (!vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase))
+				{
+					return false;
+				}
+			}
+
+			// ExcludeLocationTypes
+			if (!string.IsNullOrEmpty(request.ExcludeLocationTypes))
+			{
+				var vals = request.ExcludeLocationTypes.Split(',');
+				if (vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase))
+				{
+					return false;
+				}
+			}
+
+			if (!string.IsNullOrEmpty(request.AlbumArtistStartsWithOrGreater))
+			{
+				var ok = new[] { i }.OfType<IHasAlbumArtist>()
+					.Any(p => string.Compare(request.AlbumArtistStartsWithOrGreater, p.AlbumArtists.FirstOrDefault(), StringComparison.CurrentCultureIgnoreCase) < 1);
+
+				if (!ok)
+				{
+					return false;
+				}
+			}
+
+			// Filter by Series Status
+			if (!string.IsNullOrEmpty(request.SeriesStatus))
+			{
+				var vals = request.SeriesStatus.Split(',');
+
+				var ok = new[] { i }.OfType<Series>().Any(p => p.Status.HasValue && vals.Contains(p.Status.Value.ToString(), StringComparer.OrdinalIgnoreCase));
+
+				if (!ok)
+				{
+					return false;
+				}
+			}
+
+			// Filter by Series AirDays
+			if (!string.IsNullOrEmpty(request.AirDays))
+			{
+				var days = request.AirDays.Split(',').Select(d => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), d, true));
+
+				var ok = new[] { i }.OfType<Series>().Any(p => p.AirDays != null && days.Any(d => p.AirDays.Contains(d)));
+
+				if (!ok)
+				{
+					return false;
+				}
+			}
+
+			if (request.MinPlayers.HasValue)
+			{
+				var filterValue = request.MinPlayers.Value;
+
+				var game = i as Game;
+
+				if (game != null)
+				{
+					var players = game.PlayersSupported ?? 1;
+
+					var ok = players >= filterValue;
+
+					if (!ok)
+					{
+						return false;
+					}
+				}
+				else
+				{
+					return false;
+				}
+			}
+
+			if (request.MaxPlayers.HasValue)
+			{
+				var filterValue = request.MaxPlayers.Value;
+
+				var game = i as Game;
+
+				if (game != null)
+				{
+					var players = game.PlayersSupported ?? 1;
+
+					var ok = players <= filterValue;
+
+					if (!ok)
+					{
+						return false;
+					}
+				}
+				else
+				{
+					return false;
+				}
+			}
+
+			if (request.ParentIndexNumber.HasValue)
+			{
+				var filterValue = request.ParentIndexNumber.Value;
+
+				var episode = i as Episode;
+
+				if (episode != null)
+				{
+					if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value != filterValue)
+					{
+						return false;
+					}
+				}
+
+				var song = i as Audio;
+
+				if (song != null)
+				{
+					if (song.ParentIndexNumber.HasValue && song.ParentIndexNumber.Value != filterValue)
+					{
+						return false;
+					}
+				}
+			}
+
+			if (request.AiredDuringSeason.HasValue)
+			{
+				var episode = i as Episode;
+
+				if (episode == null)
+				{
+					return false;
+				}
+
+				if (!Series.FilterEpisodesBySeason(new[] { episode }, request.AiredDuringSeason.Value, true).Any())
+				{
+					return false;
+				}
+			}
+
+			if (!string.IsNullOrEmpty(request.MinPremiereDate))
+			{
+				var date = DateTime.Parse(request.MinPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
+
+				if (!(i.PremiereDate.HasValue && i.PremiereDate.Value >= date))
+				{
+					return false;
+				}
+			}
+
+			if (!string.IsNullOrEmpty(request.MaxPremiereDate))
+			{
+				var date = DateTime.Parse(request.MaxPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
+
+				if (!(i.PremiereDate.HasValue && i.PremiereDate.Value <= date))
+				{
+					return false;
+				}
+			}
+
+			return true;
+		}
+
+		/// <summary> Applies the paging. </summary>
+		/// <param name="request"> The request. </param>
+		/// <param name="items"> The items. </param>
+		/// <returns> IEnumerable{BaseItem}. </returns>
+		private IEnumerable<BaseItem> ApplyPaging(GetItems request, IEnumerable<BaseItem> items)
+		{
+			// Start at
+			if (request.StartIndex.HasValue)
+			{
+				items = items.Skip(request.StartIndex.Value);
+			}
+
+			// Return limit
+			if (request.Limit.HasValue)
+			{
+				items = items.Take(request.Limit.Value);
+			}
+
+			return items;
+		}
+
+	}
 }

+ 202 - 261
MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs

@@ -9,265 +9,206 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Api.Reports
 {
-    /// <summary> A report stat builder. </summary>
-    /// <seealso cref="T:MediaBrowser.Api.Reports.ReportBuilderBase"/>
-    public class ReportStatBuilder : ReportBuilderBase
-    {
-        #region [Constructors]
-
-        /// <summary>
-        /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportStatBuilder class. </summary>
-        /// <param name="libraryManager"> Manager for library. </param>
-        public ReportStatBuilder(ILibraryManager libraryManager)
-            : base(libraryManager)
-        {
-        }
-
-        #endregion
-
-        #region [Public Methods]
-        
-        /// <summary> Gets report stat result. </summary>
-        /// <param name="items"> The items. </param>
-        /// <param name="reportIncludeItemTypes"> List of types of the report include items. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <returns> The report stat result. </returns>
-        public ReportStatResult GetResult(BaseItem[] items, ReportIncludeItemTypes reportIncludeItemTypes, int topItem = 5)
-        {
-            ReportStatResult result = new ReportStatResult();
-            result = this.GetResultGenres(result, items, topItem);
-            result = this.GetResultStudios(result, items, topItem);
-            result = this.GetResultPersons(result, items, topItem);
-            result = this.GetResultProductionYears(result, items, topItem);
-            result = this.GetResulProductionLocations(result, items, topItem);
-            result = this.GetResultCommunityRatings(result, items, topItem);
-            result = this.GetResultParentalRatings(result, items, topItem);
-
-            switch (reportIncludeItemTypes)
-            {
-                case ReportIncludeItemTypes.Season:
-                case ReportIncludeItemTypes.Series:
-                case ReportIncludeItemTypes.MusicAlbum:
-                case ReportIncludeItemTypes.MusicArtist:
-                case ReportIncludeItemTypes.Game:
-                    break;
-                case ReportIncludeItemTypes.Movie:
-                case ReportIncludeItemTypes.BoxSet:
-
-                    break;
-                case ReportIncludeItemTypes.Book:
-                case ReportIncludeItemTypes.Episode:
-                case ReportIncludeItemTypes.Video:
-                case ReportIncludeItemTypes.MusicVideo:
-                case ReportIncludeItemTypes.Trailer:
-                case ReportIncludeItemTypes.Audio:
-                case ReportIncludeItemTypes.BaseItem:
-                default:
-                    break;
-            }
-
-            result.Groups = result.Groups.OrderByDescending(n => n.Items.Count()).ToList();
-
-            return result;
-        }
-
-        #endregion
-
-        #region [Protected Internal Methods]
-        /// <summary> Gets the headers. </summary>
-        /// <typeparam name="H"> Type of the header. </typeparam>
-        /// <param name="request"> The request. </param>
-        /// <returns> The headers. </returns>
-        /// <seealso cref="M:MediaBrowser.Api.Reports.ReportBuilderBase.GetHeaders{H}(H)"/>
-        protected internal override List<ReportHeader> GetHeaders<H>(H request)
-        {
-            throw new NotImplementedException();
-        }
-
-        #endregion
-
-        #region [Private Methods]
-
-        /// <summary> Gets the groups. </summary>
-        /// <param name="result"> The result. </param>
-        /// <param name="header"> The header. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <param name="top"> The top. </param>
-        private void GetGroups(ReportStatResult result, string header, int topItem, IEnumerable<ReportStatItem> top)
-        {
-            if (top.Count() > 0)
-            {
-                var group = new ReportStatGroup { Header = ReportStatGroup.FormatedHeader(header, topItem) };
-                group.Items.AddRange(top);
-                result.Groups.Add(group);
-            }
-        }
-
-        /// <summary> Gets resul production locations. </summary>
-        /// <param name="result"> The result. </param>
-        /// <param name="items"> The items. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <returns> The resul production locations. </returns>
-        private ReportStatResult GetResulProductionLocations(ReportStatResult result, BaseItem[] items, int topItem = 5)
-        {
-            this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderCountries"), topItem,
-                        items.OfType<IHasProductionLocations>()
-                        .Where(x => x.ProductionLocations != null)
-                        .SelectMany(x => x.ProductionLocations)
-                        .GroupBy(x => x)
-                        .OrderByDescending(x => x.Count())
-                        .Take(topItem)
-                        .Select(x => new ReportStatItem
-                        {
-                            Name = x.Key.ToString(),
-                            Value = x.Count().ToString()
-                        })
-            );
-
-            return result;
-        }
-
-        /// <summary> Gets result community ratings. </summary>
-        /// <param name="result"> The result. </param>
-        /// <param name="items"> The items. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <returns> The result community ratings. </returns>
-        private ReportStatResult GetResultCommunityRatings(ReportStatResult result, BaseItem[] items, int topItem = 5)
-        {
-            this.GetGroups(result, ReportHelper.GetServerLocalizedString("LabelCommunityRating"), topItem,
-                       items.Where(x => x.CommunityRating != null && x.CommunityRating > 0)
-                           .GroupBy(x => x.CommunityRating)
-                           .OrderByDescending(x => x.Count())
-                           .Take(topItem)
-                           .Select(x => new ReportStatItem
-                           {
-                               Name = x.Key.ToString(),
-                               Value = x.Count().ToString()
-                           })
-               );
-
-            return result;
-        }
-
-        /// <summary> Gets result genres. </summary>
-        /// <param name="result"> The result. </param>
-        /// <param name="items"> The items. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <returns> The result genres. </returns>
-        private ReportStatResult GetResultGenres(ReportStatResult result, BaseItem[] items, int topItem = 5)
-        {
-            this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderGenres"), topItem,
-                            items.SelectMany(x => x.Genres)
-                                .GroupBy(x => x)
-                                .OrderByDescending(x => x.Count())
-                                .Take(topItem)
-                                .Select(x => new ReportStatItem
-                                {
-                                    Name = x.Key,
-                                    Value = x.Count().ToString(),
-                                    Id = GetGenreID(x.Key)
-                                }));
-            return result;
-
-        }
-
-        /// <summary> Gets result parental ratings. </summary>
-        /// <param name="result"> The result. </param>
-        /// <param name="items"> The items. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <returns> The result parental ratings. </returns>
-        private ReportStatResult GetResultParentalRatings(ReportStatResult result, BaseItem[] items, int topItem = 5)
-        {
-            this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderParentalRatings"), topItem,
-                       items.Where(x => x.OfficialRating != null)
-                           .GroupBy(x => x.OfficialRating)
-                           .OrderByDescending(x => x.Count())
-                           .Take(topItem)
-                           .Select(x => new ReportStatItem
-                           {
-                               Name = x.Key.ToString(),
-                               Value = x.Count().ToString()
-                           })
-               );
-
-            return result;
-        }
-
-        /// <summary> Gets result persons. </summary>
-        /// <param name="result"> The result. </param>
-        /// <param name="items"> The items. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <returns> The result persons. </returns>
-        private ReportStatResult GetResultPersons(ReportStatResult result, BaseItem[] items, int topItem = 5)
-        {
-            List<string> t = new List<string> { PersonType.Actor, PersonType.Composer, PersonType.Director, PersonType.GuestStar, PersonType.Producer, PersonType.Writer, "Artist", "AlbumArtist" };
-            foreach (var item in t)
-            {
-                this.GetGroups(result, ReportHelper.GetServerLocalizedString("Option" + item), topItem,
-                        items.SelectMany(x => x.People)
-                                .Where(n => n.Type == item)
-                                .GroupBy(x => x.Name)
-                                .OrderByDescending(x => x.Count())
-                                .Take(topItem)
-                                .Select(x => new ReportStatItem
-                                {
-                                    Name = x.Key,
-                                    Value = x.Count().ToString(),
-                                    Id = GetPersonID(x.Key)
-                                })
-                );
-            }
-
-            return result;
-        }
-
-        /// <summary> Gets result production years. </summary>
-        /// <param name="result"> The result. </param>
-        /// <param name="items"> The items. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <returns> The result production years. </returns>
-        private ReportStatResult GetResultProductionYears(ReportStatResult result, BaseItem[] items, int topItem = 5)
-        {
-            this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderYears"), topItem,
-                    items.Where(x => x.ProductionYear != null && x.ProductionYear > 0)
-                        .GroupBy(x => x.ProductionYear)
-                        .OrderByDescending(x => x.Count())
-                        .Take(topItem)
-                        .Select(x => new ReportStatItem
-                        {
-                            Name = x.Key.ToString(),
-                            Value = x.Count().ToString()
-                        })
-            );
-
-            return result;
-        }
-
-        /// <summary> Gets result studios. </summary>
-        /// <param name="result"> The result. </param>
-        /// <param name="items"> The items. </param>
-        /// <param name="topItem"> The top item. </param>
-        /// <returns> The result studios. </returns>
-        private ReportStatResult GetResultStudios(ReportStatResult result, BaseItem[] items, int topItem = 5)
-        {
-            this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderStudios"), topItem,
-                                    items.SelectMany(x => x.Studios)
-                                        .GroupBy(x => x)
-                                        .OrderByDescending(x => x.Count())
-                                        .Take(topItem)
-                                        .Select(x => new ReportStatItem
-                                        {
-                                            Name = x.Key,
-                                            Value = x.Count().ToString(),
-                                            Id = GetStudioID(x.Key)
-                                        })
-                    );
-
-            return result;
-
-        }
-
-        #endregion
-
-    }
+	/// <summary> A report stat builder. </summary>
+	/// <seealso cref="T:MediaBrowser.Api.Reports.ReportBuilderBase"/>
+	public class ReportStatBuilder : ReportBuilderBase
+	{
+		/// <summary>
+		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportStatBuilder class. </summary>
+		/// <param name="libraryManager"> Manager for library. </param>
+		public ReportStatBuilder(ILibraryManager libraryManager)
+			: base(libraryManager)
+		{
+		}
+
+		/// <summary> Gets report stat result. </summary>
+		/// <param name="items"> The items. </param>
+		/// <param name="reportRowType"> Type of the report row. </param>
+		/// <param name="topItem"> The top item. </param>
+		/// <returns> The report stat result. </returns>
+		public ReportStatResult GetReportStatResult(BaseItem[] items, ReportViewType reportRowType, int topItem = 5)
+		{
+			ReportStatResult result = new ReportStatResult();
+			result = this.GetResultGenres(result, items, topItem);
+			result = this.GetResultStudios(result, items, topItem);
+			result = this.GetResultPersons(result, items, topItem);
+			result = this.GetResultProductionYears(result, items, topItem);
+			result = this.GetResulProductionLocations(result, items, topItem);
+			result = this.GetResultCommunityRatings(result, items, topItem);
+			result = this.GetResultParentalRatings(result, items, topItem);
+
+			switch (reportRowType)
+			{
+				case ReportViewType.Season:
+				case ReportViewType.Series:
+				case ReportViewType.MusicAlbum:
+				case ReportViewType.MusicArtist:
+				case ReportViewType.Game:
+					break;
+				case ReportViewType.Movie:
+				case ReportViewType.BoxSet:
+
+					break;
+				case ReportViewType.Book:
+				case ReportViewType.Episode:
+				case ReportViewType.Video:
+				case ReportViewType.MusicVideo:
+				case ReportViewType.Trailer:
+				case ReportViewType.Audio:
+				case ReportViewType.BaseItem:
+				default:
+					break;
+			}
+
+			result.Groups = result.Groups.OrderByDescending(n => n.Items.Count()).ToList();
+
+			return result;
+		}
+
+		private ReportStatResult GetResultGenres(ReportStatResult result, BaseItem[] items, int topItem = 5)
+		{
+			this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderGenres"), topItem,
+							items.SelectMany(x => x.Genres)
+								.GroupBy(x => x)
+								.OrderByDescending(x => x.Count())
+								.Take(topItem)
+								.Select(x => new ReportStatItem
+								{
+									Name = x.Key,
+									Value = x.Count().ToString(),
+									Id = GetGenreID(x.Key)
+								}));
+			return result;
+
+		}
+
+		private ReportStatResult GetResultStudios(ReportStatResult result, BaseItem[] items, int topItem = 5)
+		{
+			this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderStudios"), topItem,
+									items.SelectMany(x => x.Studios)
+										.GroupBy(x => x)
+										.OrderByDescending(x => x.Count())
+										.Take(topItem)
+										.Select(x => new ReportStatItem
+										{
+											Name = x.Key,
+											Value = x.Count().ToString(),
+											Id = GetStudioID(x.Key)
+										})
+					);
+
+			return result;
+
+		}
+
+		private ReportStatResult GetResultPersons(ReportStatResult result, BaseItem[] items, int topItem = 5)
+		{
+			List<string> t = new List<string> { PersonType.Actor, PersonType.Composer, PersonType.Director, PersonType.GuestStar, PersonType.Producer, PersonType.Writer, "Artist", "AlbumArtist" };
+			foreach (var item in t)
+			{
+				this.GetGroups(result, ReportHelper.GetServerLocalizedString("Option" + item), topItem,
+						items.SelectMany(x => _libraryManager.GetPeople(x))
+								.Where(n => n.Type == item)
+								.GroupBy(x => x.Name)
+								.OrderByDescending(x => x.Count())
+								.Take(topItem)
+								.Select(x => new ReportStatItem
+								{
+									Name = x.Key,
+									Value = x.Count().ToString(),
+									Id = GetPersonID(x.Key)
+								})
+				);
+			}
+
+			return result;
+		}
+
+		private ReportStatResult GetResultCommunityRatings(ReportStatResult result, BaseItem[] items, int topItem = 5)
+		{
+			this.GetGroups(result, ReportHelper.GetServerLocalizedString("LabelCommunityRating"), topItem,
+					   items.Where(x => x.CommunityRating != null && x.CommunityRating > 0)
+						   .GroupBy(x => x.CommunityRating)
+						   .OrderByDescending(x => x.Count())
+						   .Take(topItem)
+						   .Select(x => new ReportStatItem
+						   {
+							   Name = x.Key.ToString(),
+							   Value = x.Count().ToString()
+						   })
+			   );
+
+			return result;
+		}
+
+		private ReportStatResult GetResultParentalRatings(ReportStatResult result, BaseItem[] items, int topItem = 5)
+		{
+			this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderParentalRatings"), topItem,
+					   items.Where(x => x.OfficialRating != null)
+						   .GroupBy(x => x.OfficialRating)
+						   .OrderByDescending(x => x.Count())
+						   .Take(topItem)
+						   .Select(x => new ReportStatItem
+						   {
+							   Name = x.Key.ToString(),
+							   Value = x.Count().ToString()
+						   })
+			   );
+
+			return result;
+		}
+
+
+		private ReportStatResult GetResultProductionYears(ReportStatResult result, BaseItem[] items, int topItem = 5)
+		{
+			this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderYears"), topItem,
+					items.Where(x => x.ProductionYear != null && x.ProductionYear > 0)
+						.GroupBy(x => x.ProductionYear)
+						.OrderByDescending(x => x.Count())
+						.Take(topItem)
+						.Select(x => new ReportStatItem
+						{
+							Name = x.Key.ToString(),
+							Value = x.Count().ToString()
+						})
+			);
+
+			return result;
+		}
+
+		private ReportStatResult GetResulProductionLocations(ReportStatResult result, BaseItem[] items, int topItem = 5)
+		{
+			this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderCountries"), topItem,
+						items.OfType<IHasProductionLocations>()
+						.Where(x => x.ProductionLocations != null)
+						.SelectMany(x => x.ProductionLocations)
+						.GroupBy(x => x)
+						.OrderByDescending(x => x.Count())
+						.Take(topItem)
+						.Select(x => new ReportStatItem
+						{
+							Name = x.Key.ToString(),
+							Value = x.Count().ToString()
+						})
+			);
+
+			return result;
+		}
+
+
+		/// <summary> Gets the groups. </summary>
+		/// <param name="result"> The result. </param>
+		/// <param name="header"> The header. </param>
+		/// <param name="topItem"> The top item. </param>
+		/// <param name="top"> The top. </param>
+		private void GetGroups(ReportStatResult result, string header, int topItem, IEnumerable<ReportStatItem> top)
+		{
+			if (top.Count() > 0)
+			{
+				var group = new ReportStatGroup { Header = ReportStatGroup.FormatedHeader(header, topItem) };
+				group.Items.AddRange(top);
+				result.Groups.Add(group);
+			}
+		}
+	}
 }

+ 8 - 7
MediaBrowser.Api/SimilarItemsHelper.cs

@@ -68,7 +68,7 @@ namespace MediaBrowser.Api
         /// <param name="includeInSearch">The include in search.</param>
         /// <param name="getSimilarityScore">The get similarity score.</param>
         /// <returns>ItemsResult.</returns>
-        internal static ItemsResult GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, ILogger logger, BaseGetSimilarItemsFromItem request, Func<BaseItem, bool> includeInSearch, Func<BaseItem, BaseItem, int> getSimilarityScore)
+        internal static ItemsResult GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, ILogger logger, BaseGetSimilarItemsFromItem request, Func<BaseItem, bool> includeInSearch, Func<BaseItem, BaseItem, ILibraryManager, int> getSimilarityScore)
         {
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? userManager.GetUserById(request.UserId) : null;
 
@@ -82,7 +82,7 @@ namespace MediaBrowser.Api
                                  ? libraryManager.RootFolder.GetRecursiveChildren(filter)
                                  : user.RootFolder.GetRecursiveChildren(user, filter);
 
-            var items = GetSimilaritems(item, inputItems, getSimilarityScore)
+            var items = GetSimilaritems(item, libraryManager, inputItems, getSimilarityScore)
                 .ToList();
 
             IEnumerable<BaseItem> returnItems = items;
@@ -106,15 +106,16 @@ namespace MediaBrowser.Api
         /// Gets the similaritems.
         /// </summary>
         /// <param name="item">The item.</param>
+        /// <param name="libraryManager">The library manager.</param>
         /// <param name="inputItems">The input items.</param>
         /// <param name="getSimilarityScore">The get similarity score.</param>
         /// <returns>IEnumerable{BaseItem}.</returns>
-        internal static IEnumerable<BaseItem> GetSimilaritems(BaseItem item, IEnumerable<BaseItem> inputItems, Func<BaseItem, BaseItem, int> getSimilarityScore)
+        internal static IEnumerable<BaseItem> GetSimilaritems(BaseItem item, ILibraryManager libraryManager, IEnumerable<BaseItem> inputItems, Func<BaseItem, BaseItem, ILibraryManager, int> getSimilarityScore)
         {
             var itemId = item.Id;
             inputItems = inputItems.Where(i => i.Id != itemId);
 
-            return inputItems.Select(i => new Tuple<BaseItem, int>(i, getSimilarityScore(item, i)))
+            return inputItems.Select(i => new Tuple<BaseItem, int>(i, getSimilarityScore(item, i, libraryManager)))
                 .Where(i => i.Item2 > 2)
                 .OrderByDescending(i => i.Item2)
                 .Select(i => i.Item1);
@@ -148,7 +149,7 @@ namespace MediaBrowser.Api
         /// <param name="item1">The item1.</param>
         /// <param name="item2">The item2.</param>
         /// <returns>System.Int32.</returns>
-        internal static int GetSimiliarityScore(BaseItem item1, BaseItem item2)
+        internal static int GetSimiliarityScore(BaseItem item1, BaseItem item2, ILibraryManager libraryManager)
         {
             var points = 0;
 
@@ -169,11 +170,11 @@ namespace MediaBrowser.Api
             // Find common studios
             points += item1.Studios.Where(i => item2.Studios.Contains(i, StringComparer.OrdinalIgnoreCase)).Sum(i => 3);
 
-            var item2PeopleNames = item2.People.Select(i => i.Name)
+            var item2PeopleNames = libraryManager.GetPeople(item2).Select(i => i.Name)
                 .DistinctNames()
                 .ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
 
-            points += item1.People.Where(i => item2PeopleNames.ContainsKey(i.Name)).Sum(i =>
+            points += libraryManager.GetPeople(item1).Where(i => item2PeopleNames.ContainsKey(i.Name)).Sum(i =>
             {
                 if (string.Equals(i.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Director, StringComparison.OrdinalIgnoreCase))
                 {

+ 3 - 4
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -990,7 +990,7 @@ namespace MediaBrowser.Api.UserLibrary
                         .Select(p => p == null ? "-1" : p.Name)
                         .ToList();
 
-                    if (!(names.Any(v => i.People.Select(p => p.Name).Contains(v, StringComparer.OrdinalIgnoreCase))))
+                    if (!(names.Any(v => libraryManager.GetPeople(i).Select(p => p.Name).Contains(v, StringComparer.OrdinalIgnoreCase))))
                     {
                         return false;
                     }
@@ -1003,7 +1003,7 @@ namespace MediaBrowser.Api.UserLibrary
 
                     if (personTypes.Length == 0)
                     {
-                        if (!(i.People.Any(p => string.Equals(p.Name, request.Person, StringComparison.OrdinalIgnoreCase))))
+                        if (!(libraryManager.GetPeople(i).Any(p => string.Equals(p.Name, request.Person, StringComparison.OrdinalIgnoreCase))))
                         {
                             return false;
                         }
@@ -1013,8 +1013,7 @@ namespace MediaBrowser.Api.UserLibrary
                         var types = personTypes;
 
                         var ok = new[] { i }.Any(item =>
-                                item.People != null &&
-                                item.People.Any(p =>
+                                libraryManager.GetPeople(item).Any(p =>
                                     p.Name.Equals(request.Person, StringComparison.OrdinalIgnoreCase) && (types.Contains(p.Type, StringComparer.OrdinalIgnoreCase) || types.Contains(p.Role, StringComparer.OrdinalIgnoreCase))));
 
                         if (!ok)

+ 1 - 1
MediaBrowser.Api/UserLibrary/PersonsService.cs

@@ -153,7 +153,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>IEnumerable{PersonInfo}.</returns>
         private IEnumerable<PersonInfo> GetAllPeople(IEnumerable<BaseItem> itemsList, string[] personTypes)
         {
-            var people = itemsList.SelectMany(i => i.People.OrderBy(p => p.SortOrder ?? int.MaxValue).ThenBy(p => p.Type));
+            var people = itemsList.SelectMany(i => LibraryManager.GetPeople(i).OrderBy(p => p.SortOrder ?? int.MaxValue).ThenBy(p => p.Type));
 
             if (personTypes.Length > 0)
             {

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

@@ -216,5 +216,14 @@ namespace MediaBrowser.Controller.Entities.Audio
                 return hasArtist != null && hasArtist.HasAnyArtist(Name);
             };
         }
+
+        [IgnoreDataMember]
+        public override bool SupportsPeople
+        {
+            get
+            {
+                return false;
+            }
+        }
     }
 }

+ 9 - 0
MediaBrowser.Controller/Entities/Audio/MusicGenre.cs

@@ -71,5 +71,14 @@ namespace MediaBrowser.Controller.Entities.Audio
         {
             return i => (i is IHasMusicGenres) && i.Genres.Contains(Name, StringComparer.OrdinalIgnoreCase);
         }
+
+        [IgnoreDataMember]
+        public override bool SupportsPeople
+        {
+            get
+            {
+                return false;
+            }
+        }
     }
 }

+ 6 - 87
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -35,7 +35,6 @@ namespace MediaBrowser.Controller.Entities
         {
             Genres = new List<string>();
             Studios = new List<string>();
-            People = new List<PersonInfo>();
             ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
             LockedFields = new List<MetadataFields>();
             ImageInfos = new List<ItemImageInfo>();
@@ -413,15 +412,6 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
-        public bool ContainsPerson(string name)
-        {
-            if (string.IsNullOrWhiteSpace(name))
-            {
-                throw new ArgumentNullException("name");
-            }
-            return People.Any(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase));
-        }
-
         public string GetInternalMetadataPath()
         {
             var basePath = ConfigurationManager.ApplicationPaths.InternalMetadataPath;
@@ -785,6 +775,12 @@ namespace MediaBrowser.Controller.Entities
             get { return IsFolder || Parent != null; }
         }
 
+        [IgnoreDataMember]
+        public virtual bool SupportsPeople
+        {
+            get { return true; }
+        }
+
         /// <summary>
         /// Refreshes owned items such as trailers, theme videos, special features, etc.
         /// Returns true or false indicating if changes were found.
@@ -1248,83 +1244,6 @@ namespace MediaBrowser.Controller.Entities
         /// <exception cref="System.ArgumentNullException"></exception>
         public void AddPerson(PersonInfo person)
         {
-            if (person == null)
-            {
-                throw new ArgumentNullException("person");
-            }
-
-            if (string.IsNullOrWhiteSpace(person.Name))
-            {
-                throw new ArgumentNullException();
-            }
-
-            // Normalize
-            if (string.Equals(person.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))
-            {
-                person.Type = PersonType.GuestStar;
-            }
-            else if (string.Equals(person.Role, PersonType.Director, StringComparison.OrdinalIgnoreCase))
-            {
-                person.Type = PersonType.Director;
-            }
-            else if (string.Equals(person.Role, PersonType.Producer, StringComparison.OrdinalIgnoreCase))
-            {
-                person.Type = PersonType.Producer;
-            }
-            else if (string.Equals(person.Role, PersonType.Writer, StringComparison.OrdinalIgnoreCase))
-            {
-                person.Type = PersonType.Writer;
-            }
-
-            // If the type is GuestStar and there's already an Actor entry, then update it to avoid dupes
-            if (string.Equals(person.Type, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))
-            {
-                var existing = People.FirstOrDefault(p => p.Name.Equals(person.Name, StringComparison.OrdinalIgnoreCase) && p.Type.Equals(PersonType.Actor, StringComparison.OrdinalIgnoreCase));
-
-                if (existing != null)
-                {
-                    existing.Type = PersonType.GuestStar;
-                    existing.SortOrder = person.SortOrder ?? existing.SortOrder;
-                    return;
-                }
-            }
-
-            if (string.Equals(person.Type, PersonType.Actor, StringComparison.OrdinalIgnoreCase))
-            {
-                // If the actor already exists without a role and we have one, fill it in
-                var existing = People.FirstOrDefault(p => p.Name.Equals(person.Name, StringComparison.OrdinalIgnoreCase) && (p.Type.Equals(PersonType.Actor, StringComparison.OrdinalIgnoreCase) || p.Type.Equals(PersonType.GuestStar, StringComparison.OrdinalIgnoreCase)));
-                if (existing == null)
-                {
-                    // Wasn't there - add it
-                    People.Add(person);
-                }
-                else
-                {
-                    // Was there, if no role and we have one - fill it in
-                    if (string.IsNullOrWhiteSpace(existing.Role) && !string.IsNullOrWhiteSpace(person.Role))
-                    {
-                        existing.Role = person.Role;
-                    }
-
-                    existing.SortOrder = person.SortOrder ?? existing.SortOrder;
-                }
-            }
-            else
-            {
-                var existing = People.FirstOrDefault(p =>
-                            string.Equals(p.Name, person.Name, StringComparison.OrdinalIgnoreCase) &&
-                            string.Equals(p.Type, person.Type, StringComparison.OrdinalIgnoreCase));
-
-                // Check for dupes based on the combination of Name and Type
-                if (existing == null)
-                {
-                    People.Add(person);
-                }
-                else
-                {
-                    existing.SortOrder = person.SortOrder ?? existing.SortOrder;
-                }
-            }
         }
 
         /// <summary>

+ 11 - 0
MediaBrowser.Controller/Entities/BasePluginFolder.cs

@@ -1,4 +1,6 @@
 
+using System.Runtime.Serialization;
+
 namespace MediaBrowser.Controller.Entities
 {
     /// <summary>
@@ -21,5 +23,14 @@ namespace MediaBrowser.Controller.Entities
         {
             return true;
         }
+
+        [IgnoreDataMember]
+        public override bool SupportsPeople
+        {
+            get
+            {
+                return false;
+            }
+        }
     }
 }

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

@@ -194,5 +194,14 @@ namespace MediaBrowser.Controller.Entities
                 .Where(i => i.Path != null && PhysicalLocations.Contains(i.Path, StringComparer.OrdinalIgnoreCase))
                 .SelectMany(c => c.Children);
         }
+
+        [IgnoreDataMember]
+        public override bool SupportsPeople
+        {
+            get
+            {
+                return false;
+            }
+        }
     }
 }

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

@@ -62,5 +62,14 @@ namespace MediaBrowser.Controller.Entities
         {
             return i => (i is Game) && i.Genres.Contains(Name, StringComparer.OrdinalIgnoreCase);
         }
+
+        [IgnoreDataMember]
+        public override bool SupportsPeople
+        {
+            get
+            {
+                return false;
+            }
+        }
     }
 }

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

@@ -58,5 +58,14 @@ namespace MediaBrowser.Controller.Entities
 
             return id;
         }
+
+        [IgnoreDataMember]
+        public override bool SupportsPeople
+        {
+            get
+            {
+                return false;
+            }
+        }
     }
 }

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

@@ -66,5 +66,14 @@ namespace MediaBrowser.Controller.Entities
         {
             return i => !(i is Game) && !(i is IHasMusicGenres) && i.Genres.Contains(Name, StringComparer.OrdinalIgnoreCase);
         }
+
+        [IgnoreDataMember]
+        public override bool SupportsPeople
+        {
+            get
+            {
+                return false;
+            }
+        }
     }
 }

+ 6 - 0
MediaBrowser.Controller/Entities/IHasMetadata.cs

@@ -59,5 +59,11 @@ namespace MediaBrowser.Controller.Entities
         /// Afters the metadata refresh.
         /// </summary>
         void AfterMetadataRefresh();
+
+        /// <summary>
+        /// Gets a value indicating whether [supports people].
+        /// </summary>
+        /// <value><c>true</c> if [supports people]; otherwise, <c>false</c>.</value>
+        bool SupportsPeople { get; }
     }
 }

+ 100 - 0
MediaBrowser.Controller/Entities/PeopleHelper.cs

@@ -0,0 +1,100 @@
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Controller.Entities
+{
+    public static class PeopleHelper
+    {
+        public static void AddPerson(List<PersonInfo> people, PersonInfo person)
+        {
+            if (person == null)
+            {
+                throw new ArgumentNullException("person");
+            }
+
+            if (string.IsNullOrWhiteSpace(person.Name))
+            {
+                throw new ArgumentNullException();
+            }
+
+            // Normalize
+            if (string.Equals(person.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))
+            {
+                person.Type = PersonType.GuestStar;
+            }
+            else if (string.Equals(person.Role, PersonType.Director, StringComparison.OrdinalIgnoreCase))
+            {
+                person.Type = PersonType.Director;
+            }
+            else if (string.Equals(person.Role, PersonType.Producer, StringComparison.OrdinalIgnoreCase))
+            {
+                person.Type = PersonType.Producer;
+            }
+            else if (string.Equals(person.Role, PersonType.Writer, StringComparison.OrdinalIgnoreCase))
+            {
+                person.Type = PersonType.Writer;
+            }
+
+            // If the type is GuestStar and there's already an Actor entry, then update it to avoid dupes
+            if (string.Equals(person.Type, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))
+            {
+                var existing = people.FirstOrDefault(p => p.Name.Equals(person.Name, StringComparison.OrdinalIgnoreCase) && p.Type.Equals(PersonType.Actor, StringComparison.OrdinalIgnoreCase));
+
+                if (existing != null)
+                {
+                    existing.Type = PersonType.GuestStar;
+                    existing.SortOrder = person.SortOrder ?? existing.SortOrder;
+                    return;
+                }
+            }
+
+            if (string.Equals(person.Type, PersonType.Actor, StringComparison.OrdinalIgnoreCase))
+            {
+                // If the actor already exists without a role and we have one, fill it in
+                var existing = people.FirstOrDefault(p => p.Name.Equals(person.Name, StringComparison.OrdinalIgnoreCase) && (p.Type.Equals(PersonType.Actor, StringComparison.OrdinalIgnoreCase) || p.Type.Equals(PersonType.GuestStar, StringComparison.OrdinalIgnoreCase)));
+                if (existing == null)
+                {
+                    // Wasn't there - add it
+                    people.Add(person);
+                }
+                else
+                {
+                    // Was there, if no role and we have one - fill it in
+                    if (string.IsNullOrWhiteSpace(existing.Role) && !string.IsNullOrWhiteSpace(person.Role))
+                    {
+                        existing.Role = person.Role;
+                    }
+
+                    existing.SortOrder = person.SortOrder ?? existing.SortOrder;
+                }
+            }
+            else
+            {
+                var existing = people.FirstOrDefault(p =>
+                            string.Equals(p.Name, person.Name, StringComparison.OrdinalIgnoreCase) &&
+                            string.Equals(p.Type, person.Type, StringComparison.OrdinalIgnoreCase));
+
+                // Check for dupes based on the combination of Name and Type
+                if (existing == null)
+                {
+                    people.Add(person);
+                }
+                else
+                {
+                    existing.SortOrder = person.SortOrder ?? existing.SortOrder;
+                }
+            }
+        }
+
+        public static bool ContainsPerson(List<PersonInfo> people, string name)
+        {
+            if (string.IsNullOrWhiteSpace(name))
+            {
+                throw new ArgumentNullException("name");
+            }
+            return people.Any(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase));
+        }
+    }
+}

+ 10 - 1
MediaBrowser.Controller/Entities/Person.cs

@@ -76,7 +76,16 @@ namespace MediaBrowser.Controller.Entities
 
         public Func<BaseItem, bool> GetItemFilter()
         {
-            return i => i.People.Any(p => string.Equals(p.Name, Name, StringComparison.OrdinalIgnoreCase));
+            return i => LibraryManager.GetPeople(i).Any(p => string.Equals(p.Name, Name, StringComparison.OrdinalIgnoreCase));
+        }
+
+        [IgnoreDataMember]
+        public override bool SupportsPeople
+        {
+            get
+            {
+                return false;
+            }
         }
     }
 

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

@@ -72,5 +72,14 @@ namespace MediaBrowser.Controller.Entities
         {
             return i => i.Studios.Contains(Name, StringComparer.OrdinalIgnoreCase);
         }
+
+        [IgnoreDataMember]
+        public override bool SupportsPeople
+        {
+            get
+            {
+                return false;
+            }
+        }
     }
 }

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

@@ -295,5 +295,14 @@ namespace MediaBrowser.Controller.Entities
 
             return config.GroupedFolders.Select(i => new Guid(i)).Contains(id);
         }
+
+        [IgnoreDataMember]
+        public override bool SupportsPeople
+        {
+            get
+            {
+                return false;
+            }
+        }
     }
 }

+ 11 - 1
MediaBrowser.Controller/Entities/UserView.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Playlists;
+using System.Runtime.Serialization;
+using MediaBrowser.Controller.Playlists;
 using MediaBrowser.Controller.TV;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
@@ -93,5 +94,14 @@ namespace MediaBrowser.Controller.Entities
 
             return standaloneTypes.Contains(collectionFolder.CollectionType ?? string.Empty);
         }
+
+        [IgnoreDataMember]
+        public override bool SupportsPeople
+        {
+            get
+            {
+                return false;
+            }
+        }
     }
 }

+ 4 - 5
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -1698,9 +1698,9 @@ namespace MediaBrowser.Controller.Entities
                     .Select(libraryManager.GetItemById)
                     .Select(i => i == null ? "-1" : i.Name)
                     .ToList();
-
+               
                 if (!(names.Any(
-                        v => item.People.Select(i => i.Name).Contains(v, StringComparer.OrdinalIgnoreCase))))
+                        v => libraryManager.GetPeople(item).Select(i => i.Name).Contains(v, StringComparer.OrdinalIgnoreCase))))
                 {
                     return false;
                 }
@@ -1713,7 +1713,7 @@ namespace MediaBrowser.Controller.Entities
 
                 if (personTypes.Length == 0)
                 {
-                    if (!(item.People.Any(p => string.Equals(p.Name, query.Person, StringComparison.OrdinalIgnoreCase))))
+                    if (!(libraryManager.GetPeople(item).Any(p => string.Equals(p.Name, query.Person, StringComparison.OrdinalIgnoreCase))))
                     {
                         return false;
                     }
@@ -1723,8 +1723,7 @@ namespace MediaBrowser.Controller.Entities
                     var types = personTypes;
 
                     var ok = new[] { item }.Any(i =>
-                            i.People != null &&
-                            i.People.Any(p =>
+                            libraryManager.GetPeople(i).Any(p =>
                                 string.Equals(p.Name, query.Person, StringComparison.OrdinalIgnoreCase) && (types.Contains(p.Type ?? string.Empty, StringComparer.OrdinalIgnoreCase) || types.Contains(p.Role ?? string.Empty, StringComparer.OrdinalIgnoreCase))));
 
                     if (!ok)

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

@@ -88,5 +88,14 @@ namespace MediaBrowser.Controller.Entities
             var val = GetYearValue();
             return i => i.ProductionYear.HasValue && val.HasValue && i.ProductionYear.Value == val.Value;
         }
+
+        [IgnoreDataMember]
+        public override bool SupportsPeople
+        {
+            get
+            {
+                return false;
+            }
+        }
     }
 }

+ 21 - 0
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -412,5 +412,26 @@ namespace MediaBrowser.Controller.Library
         /// <param name="item">The item.</param>
         /// <returns>IEnumerable&lt;Folder&gt;.</returns>
         IEnumerable<Folder> GetCollectionFolders(BaseItem item);
+
+        /// <summary>
+        /// Gets the people.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns>List&lt;PersonInfo&gt;.</returns>
+        List<PersonInfo> GetPeople(BaseItem item);
+
+        /// <summary>
+        /// Gets all people names.
+        /// </summary>
+        /// <returns>List&lt;System.String&gt;.</returns>
+        List<PersonInfo> GetAllPeople();
+
+        /// <summary>
+        /// Updates the people.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="people">The people.</param>
+        /// <returns>Task.</returns>
+        Task UpdatePeople(BaseItem item, List<PersonInfo> people);
     }
 }

+ 5 - 0
MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs

@@ -118,6 +118,11 @@ namespace MediaBrowser.Controller.LiveTv
             return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N"));
         }
 
+        public override bool CanDelete()
+        {
+            return true;
+        }
+
         public override bool IsAuthorizedToDelete(User user)
         {
             return user.Policy.EnableLiveTvManagement;

+ 5 - 0
MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs

@@ -116,6 +116,11 @@ namespace MediaBrowser.Controller.LiveTv
             return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N"));
         }
 
+        public override bool CanDelete()
+        {
+            return true;
+        }
+
         public override bool IsAuthorizedToDelete(User user)
         {
             return user.Policy.EnableLiveTvManagement;

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

@@ -173,6 +173,7 @@
     <Compile Include="Entities\LinkedChild.cs" />
     <Compile Include="Entities\MusicVideo.cs" />
     <Compile Include="Entities\IHasAwards.cs" />
+    <Compile Include="Entities\PeopleHelper.cs" />
     <Compile Include="Entities\Photo.cs" />
     <Compile Include="Entities\PhotoAlbum.cs" />
     <Compile Include="Entities\Share.cs" />

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

@@ -147,6 +147,21 @@ namespace MediaBrowser.Controller.Persistence
         /// <param name="query">The query.</param>
         /// <returns>List&lt;Guid&gt;.</returns>
         List<Guid> GetItemIdsList(InternalItemsQuery query);
+
+        /// <summary>
+        /// Gets the people.
+        /// </summary>
+        /// <param name="itemId">The item identifier.</param>
+        /// <returns>List&lt;PersonInfo&gt;.</returns>
+        List<PersonInfo> GetPeople(Guid itemId);
+
+        /// <summary>
+        /// Updates the people.
+        /// </summary>
+        /// <param name="itemId">The item identifier.</param>
+        /// <param name="people">The people.</param>
+        /// <returns>Task.</returns>
+        Task UpdatePeople(Guid itemId, List<PersonInfo> people);
     }
 }
 

+ 14 - 12
MediaBrowser.Controller/Providers/BaseItemXmlParser.cs

@@ -40,7 +40,7 @@ namespace MediaBrowser.Controller.Providers
         /// <param name="metadataFile">The metadata file.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <exception cref="System.ArgumentNullException"></exception>
-        public void Fetch(T item, string metadataFile, CancellationToken cancellationToken)
+        public void Fetch(MetadataResult<T> item, string metadataFile, CancellationToken cancellationToken)
         {
             if (item == null)
             {
@@ -72,7 +72,7 @@ namespace MediaBrowser.Controller.Providers
         /// <param name="settings">The settings.</param>
         /// <param name="encoding">The encoding.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        private void Fetch(T item, string metadataFile, XmlReaderSettings settings, Encoding encoding, CancellationToken cancellationToken)
+        private void Fetch(MetadataResult<T> item, string metadataFile, XmlReaderSettings settings, Encoding encoding, CancellationToken cancellationToken)
         {
             using (var streamReader = new StreamReader(metadataFile, encoding))
             {
@@ -101,9 +101,11 @@ namespace MediaBrowser.Controller.Providers
         /// Fetches metadata from one Xml Element
         /// </summary>
         /// <param name="reader">The reader.</param>
-        /// <param name="item">The item.</param>
-        protected virtual void FetchDataFromXmlNode(XmlReader reader, T item)
+        /// <param name="itemResult">The item result.</param>
+        protected virtual void FetchDataFromXmlNode(XmlReader reader, MetadataResult<T> itemResult)
         {
+            var item = itemResult.Item;
+
             switch (reader.Name)
             {
                 // DateCreated
@@ -490,7 +492,7 @@ namespace MediaBrowser.Controller.Providers
                             {
                                 continue;
                             }
-                            item.AddPerson(p);
+                            PeopleHelper.AddPerson(itemResult.People, p);
                         }
                         break;
                     }
@@ -502,7 +504,7 @@ namespace MediaBrowser.Controller.Providers
                             {
                                 continue;
                             }
-                            item.AddPerson(p);
+                            PeopleHelper.AddPerson(itemResult.People, p);
                         }
                         break;
                     }
@@ -516,7 +518,7 @@ namespace MediaBrowser.Controller.Providers
                         {
                             // This is one of the mis-named "Actors" full nodes created by MB2
                             // Create a reader and pass it to the persons node processor
-                            FetchDataFromPersonsNode(new XmlTextReader(new StringReader("<Persons>" + actors + "</Persons>")), item);
+                            FetchDataFromPersonsNode(new XmlTextReader(new StringReader("<Persons>" + actors + "</Persons>")), itemResult);
                         }
                         else
                         {
@@ -527,7 +529,7 @@ namespace MediaBrowser.Controller.Providers
                                 {
                                     continue;
                                 }
-                                item.AddPerson(p);
+                                PeopleHelper.AddPerson(itemResult.People, p);
                             }
                         }
                         break;
@@ -541,7 +543,7 @@ namespace MediaBrowser.Controller.Providers
                             {
                                 continue;
                             }
-                            item.AddPerson(p);
+                            PeopleHelper.AddPerson(itemResult.People, p);
                         }
                         break;
                     }
@@ -833,7 +835,7 @@ namespace MediaBrowser.Controller.Providers
                     {
                         using (var subtree = reader.ReadSubtree())
                         {
-                            FetchDataFromPersonsNode(subtree, item);
+                            FetchDataFromPersonsNode(subtree, itemResult);
                         }
                         break;
                     }
@@ -1133,7 +1135,7 @@ namespace MediaBrowser.Controller.Providers
         /// </summary>
         /// <param name="reader">The reader.</param>
         /// <param name="item">The item.</param>
-        private void FetchDataFromPersonsNode(XmlReader reader, T item)
+        private void FetchDataFromPersonsNode(XmlReader reader, MetadataResult<T> item)
         {
             reader.MoveToContent();
 
@@ -1154,7 +1156,7 @@ namespace MediaBrowser.Controller.Providers
                                         {
                                             continue;
                                         }
-                                        item.AddPerson(person);
+                                        PeopleHelper.AddPerson(item.People, person);
                                     }
                                 }
                                 break;

+ 2 - 8
MediaBrowser.Controller/Providers/LocalMetadataResult.cs

@@ -1,23 +1,17 @@
-using System.Collections.Generic;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Entities;
+using System.Collections.Generic;
 
 namespace MediaBrowser.Controller.Providers
 {
-    public class LocalMetadataResult<T>
+    public class LocalMetadataResult<T> : MetadataResult<T>
         where T : IHasMetadata
     {
-        public bool HasMetadata { get; set; }
-        public T Item { get; set; }
-        
         public List<LocalImageInfo> Images { get; set; }
-        public List<ChapterInfo> Chapters { get; set; }
         public List<UserItemData> UserDataLIst { get; set; }
 
         public LocalMetadataResult()
         {
             Images = new List<LocalImageInfo>();
-            Chapters = new List<ChapterInfo>();
             UserDataLIst = new List<UserItemData>();
         }
     }

+ 10 - 0
MediaBrowser.Controller/Providers/MetadataResult.cs

@@ -1,8 +1,18 @@
+using MediaBrowser.Controller.Entities;
+using System.Collections.Generic;
+
 namespace MediaBrowser.Controller.Providers
 {
     public class MetadataResult<T>
     {
+        public List<PersonInfo> People { get; set; }
+
         public bool HasMetadata { get; set; }
         public T Item { get; set; }
+
+        public MetadataResult()
+        {
+            People = new List<PersonInfo>();
+        }
     }
 }

+ 8 - 0
MediaBrowser.Controller/Subtitles/ISubtitleProvider.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Providers;
 using System.Collections.Generic;
 using System.Threading;
@@ -35,5 +36,12 @@ namespace MediaBrowser.Controller.Subtitles
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{SubtitleResponse}.</returns>
         Task<SubtitleResponse> GetSubtitles(string id, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the supported languages.
+        /// </summary>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;IEnumerable&lt;NameIdPair&gt;&gt;.</returns>
+        Task<IEnumerable<NameIdPair>> GetSupportedLanguages(CancellationToken cancellationToken);
     }
 }

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

@@ -58,7 +58,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
             _profile = profile;
             _config = config;
 
-            _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger);
+            _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, libraryManager);
         }
 
         protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams)
@@ -410,7 +410,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
             {
                 if (stubType.Value == StubType.People)
                 {
-                    var items = item.People.Select(i =>
+                    var items = _libraryManager.GetPeople(item).Select(i =>
                     {
                         try
                         {
@@ -488,7 +488,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
 
         private async Task<QueryResult<ServerItem>> GetItemsFromPerson(Person person, User user, int? startIndex, int? limit)
         {
-            var items = user.RootFolder.GetRecursiveChildren(user, i => i is Movie || i is Series && i.ContainsPerson(person.Name))
+            var items = user.RootFolder.GetRecursiveChildren(user, i => i is Movie || i is Series && PeopleHelper.ContainsPerson(_libraryManager.GetPeople(i), person.Name))
                 .ToList();
 
             var trailerResult = await _channelManager.GetAllMediaInternal(new AllChannelMediaQuery
@@ -503,7 +503,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
                 .ToList();
 
             var trailersToAdd = trailerResult.Items
-                .Where(i => i.ContainsPerson(person.Name))
+                .Where(i => PeopleHelper.ContainsPerson(_libraryManager.GetPeople(i), person.Name))
                 .Where(i =>
                 {
                     // Try to filter out dupes using imdb id
@@ -569,7 +569,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
 
         private bool EnablePeopleDisplay(BaseItem item)
         {
-            if (item.People.Count > 0)
+            if (_libraryManager.GetPeople(item).Count > 0)
             {
                 return item is Movie;
             }

+ 6 - 2
MediaBrowser.Dlna/Didl/DidlBuilder.cs

@@ -40,8 +40,9 @@ namespace MediaBrowser.Dlna.Didl
         private readonly ILocalizationManager _localization;
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly ILogger _logger;
+        private readonly ILibraryManager _libraryManager;
 
-        public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress, string accessToken, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, ILogger logger)
+        public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress, string accessToken, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, ILogger logger, ILibraryManager libraryManager)
         {
             _profile = profile;
             _imageProcessor = imageProcessor;
@@ -50,6 +51,7 @@ namespace MediaBrowser.Dlna.Didl
             _localization = localization;
             _mediaSourceManager = mediaSourceManager;
             _logger = logger;
+            _libraryManager = libraryManager;
             _accessToken = accessToken;
             _user = user;
         }
@@ -654,7 +656,9 @@ namespace MediaBrowser.Dlna.Didl
         {
             var types = new[] { PersonType.Director, PersonType.Writer, PersonType.Producer, PersonType.Composer, "Creator" };
 
-            foreach (var actor in item.People)
+            var people = _libraryManager.GetPeople(item);
+
+            foreach (var actor in people)
             {
                 var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
                     ?? PersonType.Actor;

+ 1 - 0
MediaBrowser.Dlna/DlnaManager.cs

@@ -525,6 +525,7 @@ namespace MediaBrowser.Dlna
                 new Xbox360Profile(),
                 new XboxOneProfile(),
                 new SonyPs3Profile(),
+                new SonyPs4Profile(),
                 new SonyBravia2010Profile(),
                 new SonyBravia2011Profile(),
                 new SonyBravia2012Profile(),

+ 4 - 0
MediaBrowser.Dlna/MediaBrowser.Dlna.csproj

@@ -82,6 +82,7 @@
     <Compile Include="Profiles\DirectTvProfile.cs" />
     <Compile Include="Profiles\DishHopperJoeyProfile.cs" />
     <Compile Include="Profiles\PopcornHourProfile.cs" />
+    <Compile Include="Profiles\SonyPs4Profile.cs" />
     <Compile Include="Profiles\VlcProfile.cs" />
     <Compile Include="Ssdp\DeviceDiscoveryInfo.cs" />
     <Compile Include="Ssdp\Extensions.cs" />
@@ -210,6 +211,9 @@
     <EmbeddedResource Include="Profiles\Xml\BubbleUPnp.xml" />
     <EmbeddedResource Include="Profiles\Xml\Vlc.xml" />
   </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Profiles\Xml\Sony PlayStation 4.xml" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.

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

@@ -478,7 +478,7 @@ namespace MediaBrowser.Dlna.PlayTo
 
             playlistItem.StreamUrl = playlistItem.StreamInfo.ToDlnaUrl(_serverAddress, _accessToken);
 
-            var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger)
+            var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _libraryManager)
                 .GetItemDidl(item, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
 
             playlistItem.Didl = itemXml;

+ 17 - 1
MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs

@@ -54,7 +54,7 @@ namespace MediaBrowser.Dlna.Profiles
                 {
                     Container = "ts",
                     VideoCodec = "h264",
-                    AudioCodec = "aac",
+                    AudioCodec = "ac3",
                     Type = DlnaProfileType.Video,
                     EnableMpegtsM2TsMode = true
                 },
@@ -333,6 +333,22 @@ namespace MediaBrowser.Dlna.Profiles
                             Value = "he-aac"
                         }
                     }
+                },
+
+                new CodecProfile
+                {
+                    Type = CodecType.VideoAudio,
+                    Codec = "mp3,mp2",
+
+                    Conditions = new []
+                    {
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.AudioChannels,
+                            Value = "2"
+                        }
+                    }
                 }
             };
         }

+ 20 - 4
MediaBrowser.Dlna/Profiles/SonyBravia2011Profile.cs

@@ -51,7 +51,7 @@ namespace MediaBrowser.Dlna.Profiles
                 {
                     Container = "ts",
                     VideoCodec = "h264",
-                    AudioCodec = "aac",
+                    AudioCodec = "ac3",
                     Type = DlnaProfileType.Video,
                     EnableMpegtsM2TsMode = true
                 },
@@ -75,21 +75,21 @@ namespace MediaBrowser.Dlna.Profiles
                 {
                     Container = "ts",
                     VideoCodec = "mpeg2video",
-                    AudioCodec = "mp3,mp2",
+                    AudioCodec = "mp3",
                     Type = DlnaProfileType.Video
                 },
                 new DirectPlayProfile
                 {
                     Container = "mp4",
                     VideoCodec = "h264,mpeg4",
-                    AudioCodec = "ac3,aac,mp3,mp2",
+                    AudioCodec = "ac3,aac,mp3",
                     Type = DlnaProfileType.Video
                 },
                 new DirectPlayProfile
                 {
                     Container = "mpeg",
                     VideoCodec = "mpeg2video,mpeg1video",
-                    AudioCodec = "mp3,mp2",
+                    AudioCodec = "mp3",
                     Type = DlnaProfileType.Video
                 },
                 new DirectPlayProfile
@@ -350,6 +350,22 @@ namespace MediaBrowser.Dlna.Profiles
                             Value = "he-aac"
                         }
                     }
+                },
+
+                new CodecProfile
+                {
+                    Type = CodecType.VideoAudio,
+                    Codec = "mp3,mp2",
+
+                    Conditions = new []
+                    {
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.AudioChannels,
+                            Value = "2"
+                        }
+                    }
                 }
             };
         }

+ 17 - 1
MediaBrowser.Dlna/Profiles/SonyBravia2012Profile.cs

@@ -51,7 +51,7 @@ namespace MediaBrowser.Dlna.Profiles
                 {
                     Container = "ts",
                     VideoCodec = "h264",
-                    AudioCodec = "aac",
+                    AudioCodec = "ac3",
                     Type = DlnaProfileType.Video,
                     EnableMpegtsM2TsMode = true
                 },
@@ -268,6 +268,22 @@ namespace MediaBrowser.Dlna.Profiles
                             Value = "6"
                         }
                     }
+                },
+
+                new CodecProfile
+                {
+                    Type = CodecType.VideoAudio,
+                    Codec = "mp3,mp2",
+
+                    Conditions = new []
+                    {
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.AudioChannels,
+                            Value = "2"
+                        }
+                    }
                 }
             };
         }

+ 17 - 1
MediaBrowser.Dlna/Profiles/SonyBravia2013Profile.cs

@@ -50,7 +50,7 @@ namespace MediaBrowser.Dlna.Profiles
                 {
                     Container = "ts",
                     VideoCodec = "h264",
-                    AudioCodec = "aac",
+                    AudioCodec = "ac3",
                     Type = DlnaProfileType.Video,
                     EnableMpegtsM2TsMode = true
                 },
@@ -286,6 +286,22 @@ namespace MediaBrowser.Dlna.Profiles
                             Value = "30"
                         }
                     }
+                },
+
+                new CodecProfile
+                {
+                    Type = CodecType.VideoAudio,
+                    Codec = "mp3,mp2",
+
+                    Conditions = new []
+                    {
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.AudioChannels,
+                            Value = "2"
+                        }
+                    }
                 }
             };
         }

+ 260 - 0
MediaBrowser.Dlna/Profiles/SonyPs4Profile.cs

@@ -0,0 +1,260 @@
+using MediaBrowser.Model.Dlna;
+using System.Xml.Serialization;
+
+namespace MediaBrowser.Dlna.Profiles
+{
+    [XmlRoot("Profile")]
+    public class SonyPs4Profile : DefaultProfile
+    {
+        public SonyPs4Profile()
+        {
+            Name = "Sony PlayStation 4";
+
+            Identification = new DeviceIdentification
+            {
+                FriendlyName = "PLAYSTATION 4",
+
+                Headers = new[]
+                {
+                    new HttpHeaderInfo
+                    {
+                        Name = "User-Agent",
+                        Value = @"PLAYSTATION 4",
+                        Match = HeaderMatchType.Substring
+                    },
+
+                    new HttpHeaderInfo
+                    {
+                        Name = "X-AV-Client-Info",
+                        Value = @"PLAYSTATION 4",
+                        Match = HeaderMatchType.Substring
+                    }
+                }
+            };
+
+            AlbumArtPn = "JPEG_TN";
+
+            SonyAggregationFlags = "10";
+            XDlnaDoc = "DMS-1.50";
+            EnableSingleAlbumArtLimit = true;
+
+            DirectPlayProfiles = new[]
+            {
+                new DirectPlayProfile
+                {
+                    Container = "avi",
+                    Type = DlnaProfileType.Video,
+                    VideoCodec = "mpeg4",
+                    AudioCodec = "mp2,mp3"
+                },
+                new DirectPlayProfile
+                {
+                    Container = "ts",
+                    Type = DlnaProfileType.Video,
+                    VideoCodec = "mpeg1video,mpeg2video,h264",
+                    AudioCodec = "ac3,mp2,mp3,aac"
+                },
+                new DirectPlayProfile
+                {
+                    Container = "mpeg",
+                    Type = DlnaProfileType.Video,
+                    VideoCodec = "mpeg1video,mpeg2video",
+                    AudioCodec = "mp2"
+                },
+                new DirectPlayProfile
+                {
+                    Container = "mp4,mkv",
+                    Type = DlnaProfileType.Video,
+                    VideoCodec = "h264,mpeg4",
+                    AudioCodec = "aac,ac3"
+                },
+                new DirectPlayProfile
+                {
+                    Container = "aac,mp3,wav",
+                    Type = DlnaProfileType.Audio
+                },
+                new DirectPlayProfile
+                {
+                    Container = "jpeg,png,gif,bmp,tiff",
+                    Type = DlnaProfileType.Photo
+                }
+            };
+
+            TranscodingProfiles = new[]
+            {
+                new TranscodingProfile
+                {
+                    Container = "mp3",
+                    AudioCodec = "mp3",
+                    Type = DlnaProfileType.Audio
+                },
+                new TranscodingProfile
+                {
+                    Container = "ts",
+                    VideoCodec = "h264",
+                    AudioCodec = "mp3",
+                    Type = DlnaProfileType.Video
+                },
+                new TranscodingProfile
+                {
+                    Container = "jpeg",
+                    Type = DlnaProfileType.Photo
+                }
+            };
+
+            ContainerProfiles = new[]
+            {
+                new ContainerProfile
+                {
+                    Type = DlnaProfileType.Photo,
+
+                    Conditions = new []
+                    {
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.Width,
+                            Value = "1920"
+                        },
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.Height,
+                            Value = "1080"
+                        }
+                    }
+                }
+            };
+
+            CodecProfiles = new[]
+            {
+                new CodecProfile
+                {
+                    Type = CodecType.Video,
+                    Codec = "h264",
+
+                    Conditions = new []
+                    {
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.Width,
+                            Value = "1920"
+                        },
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.Height,
+                            Value = "1080"
+                        },
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.VideoFramerate,
+                            Value = "30",
+                            IsRequired = false
+                        },
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.VideoBitrate,
+                            Value = "15360000",
+                            IsRequired = false
+                        },
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.VideoLevel,
+                            Value = "41",
+                            IsRequired = false
+                        }
+                    }
+                },
+
+                new CodecProfile
+                {
+                    Type = CodecType.VideoAudio,
+                    Codec = "ac3",
+
+                    Conditions = new []
+                    {
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.AudioChannels,
+                            Value = "6",
+                            IsRequired = false
+                        },
+
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.AudioBitrate,
+                            Value = "640000",
+                            IsRequired = false
+                        }
+                    }
+                },
+
+                new CodecProfile
+                {
+                    Type = CodecType.VideoAudio,
+                    Codec = "wmapro",
+
+                    Conditions = new []
+                    {
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.LessThanEqual,
+                            Property = ProfileConditionValue.AudioChannels,
+                            Value = "2"
+                        }
+                    }
+                },
+
+                new CodecProfile
+                {
+                    Type = CodecType.VideoAudio,
+                    Codec = "aac",
+
+                    Conditions = new []
+                    {
+                        new ProfileCondition
+                        {
+                            Condition = ProfileConditionType.NotEquals,
+                            Property = ProfileConditionValue.AudioProfile,
+                            Value = "he-aac",
+                            IsRequired = false
+                        }
+                    }
+                }
+            };
+
+            ResponseProfiles = new[]
+            {
+                new ResponseProfile
+                {
+                    Container = "mp4,mov",
+                    AudioCodec="aac",
+                    MimeType = "video/mp4",
+                    Type = DlnaProfileType.Video
+                },
+
+                new ResponseProfile
+                {
+                    Container = "avi",
+                    MimeType = "video/divx",
+                    OrgPn="AVI",
+                    Type = DlnaProfileType.Video
+                },
+
+                new ResponseProfile
+                {
+                    Container = "wav",
+                    MimeType = "audio/wav",
+                    Type = DlnaProfileType.Audio
+                }
+            };
+        }
+    }
+}

+ 6 - 1
MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2010).xml

@@ -47,7 +47,7 @@
   </DirectPlayProfiles>
   <TranscodingProfiles>
     <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" context="Streaming" />
-    <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" context="Streaming" />
+    <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" context="Streaming" />
     <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" context="Streaming" />
   </TranscodingProfiles>
   <ContainerProfiles>
@@ -94,6 +94,11 @@
         <ProfileCondition condition="NotEquals" property="AudioProfile" value="he-aac" isRequired="true" />
       </Conditions>
     </CodecProfile>
+    <CodecProfile type="VideoAudio" codec="mp3,mp2">
+      <Conditions>
+        <ProfileCondition condition="LessThanEqual" property="AudioChannels" value="2" isRequired="true" />
+      </Conditions>
+    </CodecProfile>
   </CodecProfiles>
   <ResponseProfiles>
     <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T" mimeType="video/vnd.dlna.mpeg-tts">

+ 9 - 4
MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2011).xml

@@ -41,16 +41,16 @@
   </XmlRootAttributes>
   <DirectPlayProfiles>
     <DirectPlayProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" />
-    <DirectPlayProfile container="ts" audioCodec="mp3,mp2" videoCodec="mpeg2video" type="Video" />
-    <DirectPlayProfile container="mp4" audioCodec="ac3,aac,mp3,mp2" videoCodec="h264,mpeg4" type="Video" />
-    <DirectPlayProfile container="mpeg" audioCodec="mp3,mp2" videoCodec="mpeg2video,mpeg1video" type="Video" />
+    <DirectPlayProfile container="ts" audioCodec="mp3" videoCodec="mpeg2video" type="Video" />
+    <DirectPlayProfile container="mp4" audioCodec="ac3,aac,mp3" videoCodec="h264,mpeg4" type="Video" />
+    <DirectPlayProfile container="mpeg" audioCodec="mp3" videoCodec="mpeg2video,mpeg1video" type="Video" />
     <DirectPlayProfile container="asf" audioCodec="wmav2,wmapro,wmavoice" videoCodec="wmv2,wmv3,vc1" type="Video" />
     <DirectPlayProfile container="mp3" audioCodec="mp3" type="Audio" />
     <DirectPlayProfile container="asf" audioCodec="wmav2,wmapro,wmavoice" type="Audio" />
   </DirectPlayProfiles>
   <TranscodingProfiles>
     <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" context="Streaming" />
-    <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" context="Streaming" />
+    <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" context="Streaming" />
     <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" context="Streaming" />
   </TranscodingProfiles>
   <ContainerProfiles>
@@ -97,6 +97,11 @@
         <ProfileCondition condition="NotEquals" property="AudioProfile" value="he-aac" isRequired="true" />
       </Conditions>
     </CodecProfile>
+    <CodecProfile type="VideoAudio" codec="mp3,mp2">
+      <Conditions>
+        <ProfileCondition condition="LessThanEqual" property="AudioChannels" value="2" isRequired="true" />
+      </Conditions>
+    </CodecProfile>
   </CodecProfiles>
   <ResponseProfiles>
     <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T" mimeType="video/vnd.dlna.mpeg-tts">

+ 6 - 1
MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2012).xml

@@ -52,7 +52,7 @@
   </DirectPlayProfiles>
   <TranscodingProfiles>
     <TranscodingProfile container="mp3" type="Audio" audioCodec="mp3" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" context="Streaming" />
-    <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" context="Streaming" />
+    <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" context="Streaming" />
     <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" context="Streaming" />
   </TranscodingProfiles>
   <ContainerProfiles>
@@ -76,6 +76,11 @@
         <ProfileCondition condition="LessThanEqual" property="AudioChannels" value="6" isRequired="true" />
       </Conditions>
     </CodecProfile>
+    <CodecProfile type="VideoAudio" codec="mp3,mp2">
+      <Conditions>
+        <ProfileCondition condition="LessThanEqual" property="AudioChannels" value="2" isRequired="true" />
+      </Conditions>
+    </CodecProfile>
   </CodecProfiles>
   <ResponseProfiles>
     <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T" mimeType="video/vnd.dlna.mpeg-tts">

+ 6 - 1
MediaBrowser.Dlna/Profiles/Xml/Sony Bravia (2013).xml

@@ -57,7 +57,7 @@
   </DirectPlayProfiles>
   <TranscodingProfiles>
     <TranscodingProfile container="mp3" type="Audio" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" context="Streaming" />
-    <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="aac" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" context="Streaming" />
+    <TranscodingProfile container="ts" type="Video" videoCodec="h264" audioCodec="ac3" estimateContentLength="false" enableMpegtsM2TsMode="true" transcodeSeekInfo="Auto" context="Streaming" />
     <TranscodingProfile container="jpeg" type="Photo" estimateContentLength="false" enableMpegtsM2TsMode="false" transcodeSeekInfo="Auto" context="Streaming" />
   </TranscodingProfiles>
   <ContainerProfiles>
@@ -76,6 +76,11 @@
         <ProfileCondition condition="LessThanEqual" property="VideoFramerate" value="30" isRequired="true" />
       </Conditions>
     </CodecProfile>
+    <CodecProfile type="VideoAudio" codec="mp3,mp2">
+      <Conditions>
+        <ProfileCondition condition="LessThanEqual" property="AudioChannels" value="2" isRequired="true" />
+      </Conditions>
+    </CodecProfile>
   </CodecProfiles>
   <ResponseProfiles>
     <ResponseProfile container="ts" audioCodec="ac3,aac,mp3" videoCodec="h264" type="Video" orgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T" mimeType="video/vnd.dlna.mpeg-tts">

文件差異過大導致無法顯示
+ 31 - 0
MediaBrowser.Dlna/Profiles/Xml/Sony PlayStation 4.xml


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

@@ -78,7 +78,6 @@
     <Compile Include="Savers\MovieXmlSaver.cs" />
     <Compile Include="Savers\PersonXmlSaver.cs" />
     <Compile Include="Savers\PlaylistXmlSaver.cs" />
-    <Compile Include="Savers\SeasonXmlSaver.cs" />
     <Compile Include="Savers\SeriesXmlSaver.cs" />
     <Compile Include="Savers\XmlSaverHelpers.cs" />
   </ItemGroup>

+ 3 - 3
MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs

@@ -14,7 +14,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
         {
         }
 
-        protected override void FetchDataFromXmlNode(XmlReader reader, BoxSet item)
+        protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<BoxSet> item)
         {
             switch (reader.Name)
             {
@@ -32,7 +32,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
             }
         }
 
-        private void FetchFromCollectionItemsNode(XmlReader reader, BoxSet item)
+        private void FetchFromCollectionItemsNode(XmlReader reader, MetadataResult<BoxSet> item)
         {
             reader.MoveToContent();
 
@@ -66,7 +66,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
                 }
             }
 
-            item.LinkedChildren = list;
+            item.Item.LinkedChildren = list;
         }
     }
 }

+ 12 - 18
MediaBrowser.LocalMetadata/Parsers/EpisodeXmlParser.cs

@@ -1,13 +1,13 @@
-using System;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Threading;
 using System.Xml;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
 
 namespace MediaBrowser.LocalMetadata.Parsers
 {
@@ -17,7 +17,6 @@ namespace MediaBrowser.LocalMetadata.Parsers
     public class EpisodeXmlParser : BaseItemXmlParser<Episode>
     {
         private List<LocalImageInfo> _imagesFound;
-        private List<ChapterInfo> _chaptersFound;
 
         public EpisodeXmlParser(ILogger logger)
             : base(logger)
@@ -26,14 +25,12 @@ namespace MediaBrowser.LocalMetadata.Parsers
 
         private string _xmlPath;
 
-        public void Fetch(Episode item, 
+        public void Fetch(MetadataResult<Episode> item, 
             List<LocalImageInfo> images,
-            List<ChapterInfo> chapters, 
             string metadataFile, 
             CancellationToken cancellationToken)
         {
             _imagesFound = images;
-            _chaptersFound = chapters;
             _xmlPath = metadataFile;
 
             Fetch(item, metadataFile, cancellationToken);
@@ -45,16 +42,13 @@ namespace MediaBrowser.LocalMetadata.Parsers
         /// Fetches the data from XML node.
         /// </summary>
         /// <param name="reader">The reader.</param>
-        /// <param name="item">The item.</param>
-        protected override void FetchDataFromXmlNode(XmlReader reader, Episode item)
+        /// <param name="result">The result.</param>
+        protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<Episode> result)
         {
+            var item = result.Item;
+
             switch (reader.Name)
             {
-                case "Chapters":
-
-                    _chaptersFound.AddRange(FetchChaptersFromXmlNode(item, reader.ReadSubtree()));
-                    break;
-
                 case "Episode":
 
                     //MB generated metadata is within an "Episode" node
@@ -67,7 +61,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
                         {
                             if (subTree.NodeType == XmlNodeType.Element)
                             {
-                                FetchDataFromXmlNode(subTree, item);
+                                FetchDataFromXmlNode(subTree, result);
                             }
                         }
 
@@ -263,7 +257,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
 
 
                 default:
-                    base.FetchDataFromXmlNode(reader, item);
+                    base.FetchDataFromXmlNode(reader, result);
                     break;
             }
         }

+ 6 - 4
MediaBrowser.LocalMetadata/Parsers/GameSystemXmlParser.cs

@@ -16,7 +16,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
         }
 
         private readonly Task _cachedTask = Task.FromResult(true);
-        public Task FetchAsync(GameSystem item, string metadataFile, CancellationToken cancellationToken)
+        public Task FetchAsync(MetadataResult<GameSystem> item, string metadataFile, CancellationToken cancellationToken)
         {
             Fetch(item, metadataFile, cancellationToken);
 
@@ -29,9 +29,11 @@ namespace MediaBrowser.LocalMetadata.Parsers
         /// Fetches the data from XML node.
         /// </summary>
         /// <param name="reader">The reader.</param>
-        /// <param name="item">The item.</param>
-        protected override void FetchDataFromXmlNode(XmlReader reader, GameSystem item)
+        /// <param name="result">The result.</param>
+        protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<GameSystem> result)
         {
+            var item = result.Item;
+
             switch (reader.Name)
             {
                 case "GameSystem":
@@ -56,7 +58,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
 
 
                 default:
-                    base.FetchDataFromXmlNode(reader, item);
+                    base.FetchDataFromXmlNode(reader, result);
                     break;
             }
         }

+ 6 - 4
MediaBrowser.LocalMetadata/Parsers/GameXmlParser.cs

@@ -22,7 +22,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
         }
 
         private readonly Task _cachedTask = Task.FromResult(true);
-        public Task FetchAsync(Game item, string metadataFile, CancellationToken cancellationToken)
+        public Task FetchAsync(MetadataResult<Game> item, string metadataFile, CancellationToken cancellationToken)
         {
             Fetch(item, metadataFile, cancellationToken);
 
@@ -35,9 +35,11 @@ namespace MediaBrowser.LocalMetadata.Parsers
         /// Fetches the data from XML node.
         /// </summary>
         /// <param name="reader">The reader.</param>
-        /// <param name="item">The item.</param>
-        protected override void FetchDataFromXmlNode(XmlReader reader, Game item)
+        /// <param name="result">The result.</param>
+        protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<Game> result)
         {
+            var item = result.Item;
+
             switch (reader.Name)
             {
                 case "GameSystem":
@@ -97,7 +99,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
 
 
                 default:
-                    base.FetchDataFromXmlNode(reader, item);
+                    base.FetchDataFromXmlNode(reader, result);
                     break;
             }
         }

+ 25 - 27
MediaBrowser.LocalMetadata/Parsers/MovieXmlParser.cs

@@ -1,43 +1,31 @@
-using System.Collections.Generic;
-using System.Threading;
-using System.Xml;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
+using System.Xml;
 
 namespace MediaBrowser.LocalMetadata.Parsers
 {
     /// <summary>
     /// Class EpisodeXmlParser
     /// </summary>
-    public class MovieXmlParser : BaseItemXmlParser<Video>
+    public class BaseVideoXmlParser<T> : BaseItemXmlParser<T>
+        where T : Video
     {
-        private List<ChapterInfo> _chaptersFound;
-
-        public MovieXmlParser(ILogger logger)
+        public BaseVideoXmlParser(ILogger logger)
             : base(logger)
         {
         }
 
-        public void Fetch(Video item, 
-            List<ChapterInfo> chapters, 
-            string metadataFile, 
-            CancellationToken cancellationToken)
-        {
-            _chaptersFound = chapters;
-
-            Fetch(item, metadataFile, cancellationToken);
-        }
-
         /// <summary>
         /// Fetches the data from XML node.
         /// </summary>
         /// <param name="reader">The reader.</param>
-        /// <param name="item">The item.</param>
-        protected override void FetchDataFromXmlNode(XmlReader reader, Video item)
+        /// <param name="result">The result.</param>
+        protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<T> result)
         {
+            var item = result.Item;
+
             switch (reader.Name)
             {
                 case "TmdbCollectionName":
@@ -53,15 +41,25 @@ namespace MediaBrowser.LocalMetadata.Parsers
                         break;
                     }
 
-                case "Chapters":
-
-                    _chaptersFound.AddRange(FetchChaptersFromXmlNode(item, reader.ReadSubtree()));
-                    break;
-
                 default:
-                    base.FetchDataFromXmlNode(reader, item);
+                    base.FetchDataFromXmlNode(reader, result);
                     break;
             }
         }
     }
+
+    public class MovieXmlParser : BaseVideoXmlParser<Movie>
+    {
+        public MovieXmlParser(ILogger logger) : base(logger)
+        {
+        }
+    }
+
+    public class VideoXmlParser : BaseVideoXmlParser<Video>
+    {
+        public VideoXmlParser(ILogger logger)
+            : base(logger)
+        {
+        }
+    }
 }

+ 6 - 4
MediaBrowser.LocalMetadata/Parsers/MusicVideoXmlParser.cs

@@ -6,7 +6,7 @@ using System.Xml;
 
 namespace MediaBrowser.LocalMetadata.Parsers
 {
-    public class MusicVideoXmlParser : BaseItemXmlParser<MusicVideo>
+    public class MusicVideoXmlParser : BaseVideoXmlParser<MusicVideo>
     {
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseItemXmlParser{T}" /> class.
@@ -21,9 +21,11 @@ namespace MediaBrowser.LocalMetadata.Parsers
         /// Fetches the data from XML node.
         /// </summary>
         /// <param name="reader">The reader.</param>
-        /// <param name="item">The item.</param>
-        protected override void FetchDataFromXmlNode(XmlReader reader, MusicVideo item)
+        /// <param name="result">The result.</param>
+        protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<MusicVideo> result)
         {
+            var item = result.Item;
+
             switch (reader.Name)
             {
                 case "Artist":
@@ -44,7 +46,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
                     break;
 
                 default:
-                    base.FetchDataFromXmlNode(reader, item);
+                    base.FetchDataFromXmlNode(reader, result);
                     break;
             }
         }

+ 4 - 2
MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs

@@ -16,8 +16,10 @@ namespace MediaBrowser.LocalMetadata.Parsers
         {
         }
 
-        protected override void FetchDataFromXmlNode(XmlReader reader, Playlist item)
+        protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<Playlist> result)
         {
+            var item = result.Item;
+
             switch (reader.Name)
             {
                 case "OwnerUserId":
@@ -59,7 +61,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
                     break;
 
                 default:
-                    base.FetchDataFromXmlNode(reader, item);
+                    base.FetchDataFromXmlNode(reader, result);
                     break;
             }
         }

+ 5 - 3
MediaBrowser.LocalMetadata/Parsers/SeasonXmlParser.cs

@@ -16,9 +16,11 @@ namespace MediaBrowser.LocalMetadata.Parsers
         /// Fetches the data from XML node.
         /// </summary>
         /// <param name="reader">The reader.</param>
-        /// <param name="item">The item.</param>
-        protected override void FetchDataFromXmlNode(XmlReader reader, Season item)
+        /// <param name="result">The result.</param>
+        protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<Season> result)
         {
+            var item = result.Item;
+
             switch (reader.Name)
             {
                 case "SeasonNumber":
@@ -38,7 +40,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
                     }
 
                 default:
-                    base.FetchDataFromXmlNode(reader, item);
+                    base.FetchDataFromXmlNode(reader, result);
                     break;
             }
         }

+ 6 - 4
MediaBrowser.LocalMetadata/Parsers/SeriesXmlParser.cs

@@ -26,9 +26,11 @@ namespace MediaBrowser.LocalMetadata.Parsers
         /// Fetches the data from XML node.
         /// </summary>
         /// <param name="reader">The reader.</param>
-        /// <param name="item">The item.</param>
-        protected override void FetchDataFromXmlNode(XmlReader reader, Series item)
+        /// <param name="result">The result.</param>
+        protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<Series> result)
         {
+            var item = result.Item;
+
             switch (reader.Name)
             {
                 case "Series":
@@ -42,7 +44,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
                         {
                             if (subTree.NodeType == XmlNodeType.Element)
                             {
-                                FetchDataFromXmlNode(subTree, item);
+                                FetchDataFromXmlNode(subTree, result);
                             }
                         }
 
@@ -110,7 +112,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
                     }
 
                 default:
-                    base.FetchDataFromXmlNode(reader, item);
+                    base.FetchDataFromXmlNode(reader, result);
                     break;
             }
         }

+ 1 - 1
MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs

@@ -23,7 +23,7 @@ namespace MediaBrowser.LocalMetadata.Providers
 
         protected override void Fetch(LocalMetadataResult<BoxSet> result, string path, CancellationToken cancellationToken)
         {
-            new BoxSetXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
+            new BoxSetXmlParser(_logger).Fetch(result, path, cancellationToken);
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 1 - 2
MediaBrowser.LocalMetadata/Providers/EpisodeXmlProvider.cs

@@ -25,10 +25,9 @@ namespace MediaBrowser.LocalMetadata.Providers
             var images = new List<LocalImageInfo>();
             var chapters = new List<ChapterInfo>();
 
-            new EpisodeXmlParser(_logger).Fetch(result.Item, images, chapters, path, cancellationToken);
+            new EpisodeXmlParser(_logger).Fetch(result, images, path, cancellationToken);
 
             result.Images = images;
-            result.Chapters = chapters;
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 1 - 1
MediaBrowser.LocalMetadata/Providers/FolderXmlProvider.cs

@@ -22,7 +22,7 @@ namespace MediaBrowser.LocalMetadata.Providers
 
         protected override void Fetch(LocalMetadataResult<Folder> result, string path, CancellationToken cancellationToken)
         {
-            new BaseItemXmlParser<Folder>(_logger).Fetch(result.Item, path, cancellationToken);
+            new BaseItemXmlParser<Folder>(_logger).Fetch(result, path, cancellationToken);
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 1 - 1
MediaBrowser.LocalMetadata/Providers/GameSystemXmlProvider.cs

@@ -20,7 +20,7 @@ namespace MediaBrowser.LocalMetadata.Providers
 
         protected override void Fetch(LocalMetadataResult<GameSystem> result, string path, CancellationToken cancellationToken)
         {
-            new GameSystemXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
+            new GameSystemXmlParser(_logger).Fetch(result, path, cancellationToken);
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 1 - 1
MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs

@@ -20,7 +20,7 @@ namespace MediaBrowser.LocalMetadata.Providers
 
         protected override void Fetch(LocalMetadataResult<Game> result, string path, CancellationToken cancellationToken)
         {
-            new GameXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
+            new GameXmlParser(_logger).Fetch(result, path, cancellationToken);
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 1 - 7
MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs

@@ -2,9 +2,7 @@
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.LocalMetadata.Parsers;
-using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
-using System.Collections.Generic;
 using System.IO;
 using System.Threading;
 
@@ -22,11 +20,7 @@ namespace MediaBrowser.LocalMetadata.Providers
 
         protected override void Fetch(LocalMetadataResult<Movie> result, string path, CancellationToken cancellationToken)
         {
-            var chapters = new List<ChapterInfo>();
-
-            new MovieXmlParser(_logger).Fetch(result.Item, chapters, path, cancellationToken);
-
-            result.Chapters = chapters;
+            new MovieXmlParser(_logger).Fetch(result, path, cancellationToken);
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 1 - 1
MediaBrowser.LocalMetadata/Providers/MusicVideoXmlProvider.cs

@@ -20,7 +20,7 @@ namespace MediaBrowser.LocalMetadata.Providers
 
         protected override void Fetch(LocalMetadataResult<MusicVideo> result, string path, CancellationToken cancellationToken)
         {
-            new MusicVideoXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
+            new MusicVideoXmlParser(_logger).Fetch(result, path, cancellationToken);
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 1 - 1
MediaBrowser.LocalMetadata/Providers/PersonXmlProvider.cs

@@ -19,7 +19,7 @@ namespace MediaBrowser.LocalMetadata.Providers
 
         protected override void Fetch(LocalMetadataResult<Person> result, string path, CancellationToken cancellationToken)
         {
-            new BaseItemXmlParser<Person>(_logger).Fetch(result.Item, path, cancellationToken);
+            new BaseItemXmlParser<Person>(_logger).Fetch(result, path, cancellationToken);
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 1 - 1
MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs

@@ -20,7 +20,7 @@ namespace MediaBrowser.LocalMetadata.Providers
 
         protected override void Fetch(LocalMetadataResult<Playlist> result, string path, CancellationToken cancellationToken)
         {
-            new PlaylistXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
+            new PlaylistXmlParser(_logger).Fetch(result, path, cancellationToken);
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 1 - 1
MediaBrowser.LocalMetadata/Providers/SeasonXmlProvider.cs

@@ -23,7 +23,7 @@ namespace MediaBrowser.LocalMetadata.Providers
 
         protected override void Fetch(LocalMetadataResult<Season> result, string path, CancellationToken cancellationToken)
         {
-            new SeasonXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
+            new SeasonXmlParser(_logger).Fetch(result, path, cancellationToken);
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 1 - 1
MediaBrowser.LocalMetadata/Providers/SeriesXmlProvider.cs

@@ -23,7 +23,7 @@ namespace MediaBrowser.LocalMetadata.Providers
 
         protected override void Fetch(LocalMetadataResult<Series> result, string path, CancellationToken cancellationToken)
         {
-            new SeriesXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
+            new SeriesXmlParser(_logger).Fetch(result, path, cancellationToken);
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 4 - 10
MediaBrowser.LocalMetadata/Providers/VideoXmlProvider.cs

@@ -1,12 +1,10 @@
-using System.Collections.Generic;
-using System.IO;
-using System.Threading;
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.LocalMetadata.Parsers;
-using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
+using System.IO;
+using System.Threading;
 
 namespace MediaBrowser.LocalMetadata.Providers
 {
@@ -22,11 +20,7 @@ namespace MediaBrowser.LocalMetadata.Providers
 
         protected override void Fetch(LocalMetadataResult<Video> result, string path, CancellationToken cancellationToken)
         {
-            var chapters = new List<ChapterInfo>();
-
-            new MovieXmlParser(_logger).Fetch(result.Item, chapters, path, cancellationToken);
-
-            result.Chapters = chapters;
+            new VideoXmlParser(_logger).Fetch(result, path, cancellationToken);
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 4 - 2
MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs

@@ -20,10 +20,12 @@ namespace MediaBrowser.LocalMetadata.Savers
         }
 
         private readonly IServerConfigurationManager _config;
+        private readonly ILibraryManager _libraryManager;
 
-        public BoxSetXmlSaver(IServerConfigurationManager config)
+        public BoxSetXmlSaver(IServerConfigurationManager config, ILibraryManager libraryManager)
         {
             _config = config;
+            _libraryManager = libraryManager;
         }
 
         /// <summary>
@@ -54,7 +56,7 @@ namespace MediaBrowser.LocalMetadata.Savers
 
             builder.Append("<Item>");
 
-            XmlSaverHelpers.AddCommonNodes((BoxSet)item, builder);
+            XmlSaverHelpers.AddCommonNodes((BoxSet)item, _libraryManager, builder);
 
             builder.Append("</Item>");
 

+ 4 - 2
MediaBrowser.LocalMetadata/Savers/EpisodeXmlSaver.cs

@@ -18,11 +18,13 @@ namespace MediaBrowser.LocalMetadata.Savers
 
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
         private readonly IServerConfigurationManager _config;
+        private readonly ILibraryManager _libraryManager;
 
-        public EpisodeXmlSaver(IItemRepository itemRepository, IServerConfigurationManager config)
+        public EpisodeXmlSaver(IItemRepository itemRepository, IServerConfigurationManager config, ILibraryManager libraryManager)
         {
             _itemRepository = itemRepository;
             _config = config;
+            _libraryManager = libraryManager;
         }
 
         /// <summary>
@@ -116,7 +118,7 @@ namespace MediaBrowser.LocalMetadata.Savers
                 builder.Append("<FirstAired>" + SecurityElement.Escape(episode.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd")) + "</FirstAired>");
             }
 
-            XmlSaverHelpers.AddCommonNodes(episode, builder);
+            XmlSaverHelpers.AddCommonNodes(episode, _libraryManager, builder);
             XmlSaverHelpers.AddMediaInfo(episode, builder, _itemRepository);
 
             builder.Append("</Item>");

+ 4 - 2
MediaBrowser.LocalMetadata/Savers/FolderXmlSaver.cs

@@ -23,10 +23,12 @@ namespace MediaBrowser.LocalMetadata.Savers
         }
 
         private readonly IServerConfigurationManager _config;
+        private readonly ILibraryManager _libraryManager;
 
-        public FolderXmlSaver(IServerConfigurationManager config)
+        public FolderXmlSaver(IServerConfigurationManager config, ILibraryManager libraryManager)
         {
             _config = config;
+            _libraryManager = libraryManager;
         }
 
         /// <summary>
@@ -68,7 +70,7 @@ namespace MediaBrowser.LocalMetadata.Savers
 
             builder.Append("<Item>");
 
-            XmlSaverHelpers.AddCommonNodes((Folder)item, builder);
+            XmlSaverHelpers.AddCommonNodes((Folder)item, _libraryManager, builder);
 
             builder.Append("</Item>");
 

+ 4 - 2
MediaBrowser.LocalMetadata/Savers/GameSystemXmlSaver.cs

@@ -20,10 +20,12 @@ namespace MediaBrowser.LocalMetadata.Savers
         }
 
         private readonly IServerConfigurationManager _config;
+        private readonly ILibraryManager _libraryManager;
 
-        public GameSystemXmlSaver(IServerConfigurationManager config)
+        public GameSystemXmlSaver(IServerConfigurationManager config, ILibraryManager libraryManager)
         {
             _config = config;
+            _libraryManager = libraryManager;
         }
 
         /// <summary>
@@ -61,7 +63,7 @@ namespace MediaBrowser.LocalMetadata.Savers
                 builder.Append("<GameSystem>" + SecurityElement.Escape(gameSystem.GameSystemName) + "</GameSystem>");
             }
 
-            XmlSaverHelpers.AddCommonNodes(gameSystem, builder);
+            XmlSaverHelpers.AddCommonNodes(gameSystem, _libraryManager, builder);
 
             builder.Append("</Item>");
 

+ 5 - 3
MediaBrowser.LocalMetadata/Savers/GameXmlSaver.cs

@@ -25,12 +25,14 @@ namespace MediaBrowser.LocalMetadata.Savers
         }
 
         private readonly IServerConfigurationManager _config;
+        private readonly ILibraryManager _libraryManager;
 
-        public GameXmlSaver(IServerConfigurationManager config)
+        public GameXmlSaver(IServerConfigurationManager config, ILibraryManager libraryManager)
         {
             _config = config;
+            _libraryManager = libraryManager;
         }
-        
+
         /// <summary>
         /// Determines whether [is enabled for] [the specified item].
         /// </summary>
@@ -87,7 +89,7 @@ namespace MediaBrowser.LocalMetadata.Savers
                 builder.Append("<NesBoxRom>" + SecurityElement.Escape(val) + "</NesBoxRom>");
             }
 
-            XmlSaverHelpers.AddCommonNodes(game, builder);
+            XmlSaverHelpers.AddCommonNodes(game, _libraryManager, builder);
 
             builder.Append("</Item>");
 

+ 1 - 1
MediaBrowser.LocalMetadata/Savers/MovieXmlSaver.cs

@@ -74,7 +74,7 @@ namespace MediaBrowser.LocalMetadata.Savers
 
             builder.Append("<Title>");
 
-            XmlSaverHelpers.AddCommonNodes(video, builder);
+            XmlSaverHelpers.AddCommonNodes(video, _libraryManager, builder);
 
             var musicVideo = item as MusicVideo;
 

+ 4 - 2
MediaBrowser.LocalMetadata/Savers/PersonXmlSaver.cs

@@ -23,10 +23,12 @@ namespace MediaBrowser.LocalMetadata.Savers
         }
 
         private readonly IServerConfigurationManager _config;
+        private readonly ILibraryManager _libraryManager;
 
-        public PersonXmlSaver(IServerConfigurationManager config)
+        public PersonXmlSaver(IServerConfigurationManager config, ILibraryManager libraryManager)
         {
             _config = config;
+            _libraryManager = libraryManager;
         }
 
         /// <summary>
@@ -59,7 +61,7 @@ namespace MediaBrowser.LocalMetadata.Savers
 
             builder.Append("<Item>");
 
-            XmlSaverHelpers.AddCommonNodes(person, builder);
+            XmlSaverHelpers.AddCommonNodes(person, _libraryManager, builder);
 
             if (!string.IsNullOrEmpty(person.PlaceOfBirth))
             {

+ 5 - 3
MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs

@@ -21,10 +21,12 @@ namespace MediaBrowser.LocalMetadata.Savers
         }
 
         private readonly IServerConfigurationManager _config;
+        private readonly ILibraryManager _libraryManager;
 
-        public PlaylistXmlSaver(IServerConfigurationManager config)
+        public PlaylistXmlSaver(IServerConfigurationManager config, ILibraryManager libraryManager)
         {
             _config = config;
+            _libraryManager = libraryManager;
         }
 
         /// <summary>
@@ -61,8 +63,8 @@ namespace MediaBrowser.LocalMetadata.Savers
             {
                 builder.Append("<PlaylistMediaType>" + SecurityElement.Escape(playlist.PlaylistMediaType) + "</PlaylistMediaType>");
             }
-            
-            XmlSaverHelpers.AddCommonNodes(playlist, builder);
+
+            XmlSaverHelpers.AddCommonNodes(playlist, _libraryManager, builder);
 
             builder.Append("</Item>");
 

+ 0 - 95
MediaBrowser.LocalMetadata/Savers/SeasonXmlSaver.cs

@@ -1,95 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Security;
-using System.Text;
-using System.Threading;
-
-namespace MediaBrowser.LocalMetadata.Savers
-{
-    public class SeasonXmlSaver
-    {
-        public string Name
-        {
-            get
-            {
-                return XmlProviderUtils.Name;
-            }
-        }
-
-        private readonly IServerConfigurationManager _config;
-
-        public SeasonXmlSaver(IServerConfigurationManager config)
-        {
-            _config = config;
-        }
-        
-        /// <summary>
-        /// Determines whether [is enabled for] [the specified item].
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="updateType">Type of the update.</param>
-        /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
-        {
-            if (!item.SupportsLocalMetadata)
-            {
-                return false;
-            }
-
-            if (!(item is Season))
-            {
-                return false;
-            }
-
-            return updateType >= ItemUpdateType.MetadataDownload || (updateType >= ItemUpdateType.MetadataImport && File.Exists(GetSavePath(item)));
-        }
-
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
-        /// <summary>
-        /// Saves the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public void Save(IHasMetadata item, CancellationToken cancellationToken)
-        {
-            var builder = new StringBuilder();
-
-            builder.Append("<Item>");
-
-            var season = (Season)item;
-
-            if (season.IndexNumber.HasValue)
-            {
-                builder.Append("<SeasonNumber>" + SecurityElement.Escape(season.IndexNumber.Value.ToString(_usCulture)) + "</SeasonNumber>");
-            }
-            
-            XmlSaverHelpers.AddCommonNodes((Season)item, builder);
-
-            builder.Append("</Item>");
-
-            var xmlFilePath = GetSavePath(item);
-
-            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string>
-            {
-                "SeasonNumber"
-            }, _config);
-        }
-
-        /// <summary>
-        /// Gets the save path.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>System.String.</returns>
-        public string GetSavePath(IHasMetadata item)
-        {
-            return Path.Combine(item.Path, "season.xml");
-        }
-    }
-}

+ 5 - 3
MediaBrowser.LocalMetadata/Savers/SeriesXmlSaver.cs

@@ -15,12 +15,14 @@ namespace MediaBrowser.LocalMetadata.Savers
     public class SeriesXmlSaver : IMetadataFileSaver
     {
         private readonly IServerConfigurationManager _config;
+        private readonly ILibraryManager _libraryManager;
 
-        public SeriesXmlSaver(IServerConfigurationManager config)
+        public SeriesXmlSaver(IServerConfigurationManager config, ILibraryManager libraryManager)
         {
             _config = config;
+            _libraryManager = libraryManager;
         }
-        
+
         public string Name
         {
             get
@@ -105,7 +107,7 @@ namespace MediaBrowser.LocalMetadata.Savers
                 builder.Append("<AnimeSeriesIndex>" + SecurityElement.Escape(series.AnimeSeriesIndex.Value.ToString(UsCulture)) + "</AnimeSeriesIndex>");
             }
 
-            XmlSaverHelpers.AddCommonNodes(series, builder);
+            XmlSaverHelpers.AddCommonNodes(series, _libraryManager, builder);
 
             builder.Append("</Series>");
 

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

@@ -2,6 +2,7 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Playlists;
 using MediaBrowser.Model.Entities;
@@ -230,7 +231,7 @@ namespace MediaBrowser.LocalMetadata.Savers
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="builder">The builder.</param>
-        public static void AddCommonNodes(BaseItem item, StringBuilder builder)
+        public static void AddCommonNodes(BaseItem item, ILibraryManager libraryManager, StringBuilder builder)
         {
             if (!string.IsNullOrEmpty(item.OfficialRating))
             {
@@ -627,11 +628,13 @@ namespace MediaBrowser.LocalMetadata.Savers
                 }
             }
 
-            if (item.People.Count > 0)
+            var people = libraryManager.GetPeople(item);
+
+            if (people.Count > 0)
             {
                 builder.Append("<Persons>");
 
-                foreach (var person in item.People)
+                foreach (var person in people)
                 {
                     builder.Append("<Person>");
                     builder.Append("<Name>" + SecurityElement.Escape(person.Name) + "</Name>");

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

@@ -182,6 +182,9 @@
     <Compile Include="..\MediaBrowser.Model\Configuration\AccessSchedule.cs">
       <Link>Configuration\AccessSchedule.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Configuration\AutoOnOff.cs">
+      <Link>Configuration\AutoOnOff.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
       <Link>Configuration\BaseApplicationConfiguration.cs</Link>
     </Compile>

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

@@ -147,6 +147,9 @@
     <Compile Include="..\MediaBrowser.Model\Configuration\AccessSchedule.cs">
       <Link>Configuration\AccessSchedule.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Configuration\AutoOnOff.cs">
+      <Link>Configuration\AutoOnOff.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
       <Link>Configuration\BaseApplicationConfiguration.cs</Link>
     </Compile>

+ 10 - 0
MediaBrowser.Model/Configuration/AutoOnOff.cs

@@ -0,0 +1,10 @@
+
+namespace MediaBrowser.Model.Configuration
+{
+    public enum AutoOnOff
+    {
+        Auto,
+        Enabled,
+        Disabled
+    }
+}

+ 10 - 4
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -187,7 +187,6 @@ namespace MediaBrowser.Model.Configuration
 
         public bool EnableAutomaticRestart { get; set; }
 
-        public bool EnableRealtimeMonitor { get; set; }
         public PathSubstitution[] PathSubstitutions { get; set; }
 
         public string ServerName { get; set; }
@@ -208,6 +207,10 @@ namespace MediaBrowser.Model.Configuration
         public bool EnableVideoArchiveFiles { get; set; }
         public int RemoteClientBitrateLimit { get; set; }
 
+        public bool DenyIFrameEmbedding { get; set; }
+
+        public AutoOnOff EnableLibraryMonitor { get; set; }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
         /// </summary>
@@ -224,6 +227,7 @@ namespace MediaBrowser.Model.Configuration
             EnableDashboardResourceMinification = true;
 
             EnableAutomaticRestart = true;
+            DenyIFrameEmbedding = true;
 
             EnableUPnP = true;
 
@@ -233,6 +237,7 @@ namespace MediaBrowser.Model.Configuration
             // 5 minutes
             MinResumeDurationSeconds = 300;
 
+            EnableLibraryMonitor = AutoOnOff.Auto;
             RealtimeLibraryMonitorDelay = 40;
 
             EnableInternetProviders = true;
@@ -250,8 +255,6 @@ namespace MediaBrowser.Model.Configuration
 
             SeasonZeroDisplayName = "Specials";
 
-            EnableRealtimeMonitor = true;
-
             UICulture = "en-us";
 
             PeopleMetadataOptions = new PeopleMetadataOptions();
@@ -426,7 +429,10 @@ namespace MediaBrowser.Model.Configuration
                     }
                 },
 
-                new MetadataOptions(0, 1280) {ItemType = "Season"}
+                new MetadataOptions(0, 1280)
+                {
+                    ItemType = "Season"
+                }
             };
         }
     }

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

@@ -96,6 +96,7 @@
     <Compile Include="Chapters\RemoteChapterResult.cs" />
     <Compile Include="Collections\CollectionCreationResult.cs" />
     <Compile Include="Configuration\AccessSchedule.cs" />
+    <Compile Include="Configuration\AutoOnOff.cs" />
     <Compile Include="Configuration\ChannelOptions.cs" />
     <Compile Include="Configuration\ChapterOptions.cs" />
     <Compile Include="Configuration\CinemaModeConfiguration.cs" />

+ 4 - 12
MediaBrowser.Providers/Books/BookMetadataService.cs

@@ -12,25 +12,17 @@ namespace MediaBrowser.Providers.Books
 {
     public class BookMetadataService : MetadataService<Book, BookInfo>
     {
-        public BookMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager)
+        public BookMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
         {
         }
 
-        /// <summary>
-        /// Merges the specified source.
-        /// </summary>
-        /// <param name="source">The source.</param>
-        /// <param name="target">The target.</param>
-        /// <param name="lockedFields">The locked fields.</param>
-        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
-        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
-        protected override void MergeData(Book source, Book target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        protected override void MergeData(MetadataResult<Book> source, MetadataResult<Book> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
         {
             ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
 
-            if (replaceData || string.IsNullOrEmpty(target.SeriesName))
+            if (replaceData || string.IsNullOrEmpty(target.Item.SeriesName))
             {
-                target.SeriesName = source.SeriesName;
+                target.Item.SeriesName = source.Item.SeriesName;
             }
         }
     }

+ 19 - 24
MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs

@@ -15,33 +15,10 @@ namespace MediaBrowser.Providers.BoxSets
 {
     public class BoxSetMetadataService : MetadataService<BoxSet, BoxSetInfo>
     {
-        public BoxSetMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager)
+        public BoxSetMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
         {
         }
 
-        /// <summary>
-        /// Merges the specified source.
-        /// </summary>
-        /// <param name="source">The source.</param>
-        /// <param name="target">The target.</param>
-        /// <param name="lockedFields">The locked fields.</param>
-        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
-        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
-        protected override void MergeData(BoxSet source, BoxSet target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
-        {
-            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
-
-            if (mergeMetadataSettings)
-            {
-                var list = source.LinkedChildren.Where(i => i.Type != LinkedChildType.Manual).ToList();
-
-                list.AddRange(target.LinkedChildren.Where(i => i.Type == LinkedChildType.Manual));
-
-                target.LinkedChildren = list;
-                target.Shares = source.Shares;
-            }
-        }
-
         protected override async Task<ItemUpdateType> BeforeSave(BoxSet item, bool isFullRefresh, ItemUpdateType currentUpdateType)
         {
             var updateType = await base.BeforeSave(item, isFullRefresh, currentUpdateType).ConfigureAwait(false);
@@ -59,5 +36,23 @@ namespace MediaBrowser.Providers.BoxSets
 
             return updateType;
         }
+
+        protected override void MergeData(MetadataResult<BoxSet> source, MetadataResult<BoxSet> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        {
+            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+
+            var sourceItem = source.Item;
+            var targetItem = target.Item;
+
+            if (mergeMetadataSettings)
+            {
+                var list = sourceItem.LinkedChildren.Where(i => i.Type != LinkedChildType.Manual).ToList();
+
+                list.AddRange(targetItem.LinkedChildren.Where(i => i.Type == LinkedChildType.Manual));
+
+                targetItem.LinkedChildren = list;
+                targetItem.Shares = sourceItem.Shares;
+            }
+        }
     }
 }

+ 2 - 10
MediaBrowser.Providers/Channels/AudioChannelItemMetadataService.cs

@@ -12,19 +12,11 @@ namespace MediaBrowser.Providers.Channels
 {
     public class AudioChannelItemMetadataService : MetadataService<ChannelAudioItem, ItemLookupInfo>
     {
-        public AudioChannelItemMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager)
+        public AudioChannelItemMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
         {
         }
 
-        /// <summary>
-        /// Merges the specified source.
-        /// </summary>
-        /// <param name="source">The source.</param>
-        /// <param name="target">The target.</param>
-        /// <param name="lockedFields">The locked fields.</param>
-        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
-        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
-        protected override void MergeData(ChannelAudioItem source, ChannelAudioItem target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        protected override void MergeData(MetadataResult<ChannelAudioItem> source, MetadataResult<ChannelAudioItem> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
         {
             ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
         }

+ 2 - 10
MediaBrowser.Providers/Channels/ChannelMetadataService.cs

@@ -12,19 +12,11 @@ namespace MediaBrowser.Providers.Channels
 {
     public class ChannelMetadataService : MetadataService<Channel, ItemLookupInfo>
     {
-        public ChannelMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager)
+        public ChannelMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
         {
         }
 
-        /// <summary>
-        /// Merges the specified source.
-        /// </summary>
-        /// <param name="source">The source.</param>
-        /// <param name="target">The target.</param>
-        /// <param name="lockedFields">The locked fields.</param>
-        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
-        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
-        protected override void MergeData(Channel source, Channel target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        protected override void MergeData(MetadataResult<Channel> source, MetadataResult<Channel> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
         {
             ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
         }

+ 2 - 10
MediaBrowser.Providers/Channels/VideoChannelItemMetadataService.cs

@@ -12,19 +12,11 @@ namespace MediaBrowser.Providers.Channels
 {
     public class VideoChannelItemMetadataService : MetadataService<ChannelVideoItem, ChannelItemLookupInfo>
     {
-        public VideoChannelItemMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager)
+        public VideoChannelItemMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
         {
         }
 
-        /// <summary>
-        /// Merges the specified source.
-        /// </summary>
-        /// <param name="source">The source.</param>
-        /// <param name="target">The target.</param>
-        /// <param name="lockedFields">The locked fields.</param>
-        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
-        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
-        protected override void MergeData(ChannelVideoItem source, ChannelVideoItem target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        protected override void MergeData(MetadataResult<ChannelVideoItem> source, MetadataResult<ChannelVideoItem> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
         {
             ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
         }

+ 6 - 14
MediaBrowser.Providers/Folders/FolderMetadataService.cs

@@ -12,23 +12,10 @@ namespace MediaBrowser.Providers.Folders
 {
     public class FolderMetadataService : MetadataService<Folder, ItemLookupInfo>
     {
-        public FolderMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager)
+        public FolderMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
         {
         }
 
-        /// <summary>
-        /// Merges the specified source.
-        /// </summary>
-        /// <param name="source">The source.</param>
-        /// <param name="target">The target.</param>
-        /// <param name="lockedFields">The locked fields.</param>
-        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
-        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
-        protected override void MergeData(Folder source, Folder target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
-        {
-            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
-        }
-
         public override int Order
         {
             get
@@ -37,5 +24,10 @@ namespace MediaBrowser.Providers.Folders
                 return 10;
             }
         }
+
+        protected override void MergeData(MetadataResult<Folder> source, MetadataResult<Folder> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        {
+            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+        }
     }
 }

+ 2 - 11
MediaBrowser.Providers/Folders/UserViewMetadataService.cs

@@ -12,20 +12,11 @@ namespace MediaBrowser.Providers.Folders
 {
     public class UserViewMetadataService : MetadataService<UserView, ItemLookupInfo>
     {
-        public UserViewMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager)
-            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager)
+        public UserViewMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
         {
         }
 
-        /// <summary>
-        /// Merges the specified source.
-        /// </summary>
-        /// <param name="source">The source.</param>
-        /// <param name="target">The target.</param>
-        /// <param name="lockedFields">The locked fields.</param>
-        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
-        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
-        protected override void MergeData(UserView source, UserView target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        protected override void MergeData(MetadataResult<UserView> source, MetadataResult<UserView> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
         {
             ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
         }

+ 2 - 10
MediaBrowser.Providers/GameGenres/GameGenreMetadataService.cs

@@ -12,19 +12,11 @@ namespace MediaBrowser.Providers.GameGenres
 {
     public class GameGenreMetadataService : MetadataService<GameGenre, ItemLookupInfo>
     {
-        public GameGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager)
+        public GameGenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem, userDataManager, libraryManager)
         {
         }
 
-        /// <summary>
-        /// Merges the specified source.
-        /// </summary>
-        /// <param name="source">The source.</param>
-        /// <param name="target">The target.</param>
-        /// <param name="lockedFields">The locked fields.</param>
-        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
-        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
-        protected override void MergeData(GameGenre source, GameGenre target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        protected override void MergeData(MetadataResult<GameGenre> source, MetadataResult<GameGenre> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
         {
             ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
         }

部分文件因文件數量過多而無法顯示