소스 검색

Merge pull request #1899 from MediaBrowser/beta

Beta
Luke 9 년 전
부모
커밋
2708df6cc2
100개의 변경된 파일1485개의 추가작업 그리고 1592개의 파일을 삭제
  1. 0 1
      Emby.Drawing/Emby.Drawing.csproj
  2. 3 14
      Emby.Drawing/ImageMagick/ImageMagickEncoder.cs
  3. 26 325
      Emby.Drawing/ImageMagick/StripCollageBuilder.cs
  4. 1 1
      MediaBrowser.Api/BaseApiService.cs
  5. 21 1
      MediaBrowser.Api/ConfigurationService.cs
  6. 1 1
      MediaBrowser.Api/FilterService.cs
  7. 36 14
      MediaBrowser.Api/GamesService.cs
  8. 5 5
      MediaBrowser.Api/Images/ImageService.cs
  9. 5 5
      MediaBrowser.Api/Images/RemoteImageService.cs
  10. 30 19
      MediaBrowser.Api/ItemLookupService.cs
  11. 13 31
      MediaBrowser.Api/ItemUpdateService.cs
  12. 3 3
      MediaBrowser.Api/Library/LibraryService.cs
  13. 99 12
      MediaBrowser.Api/LiveTv/LiveTvService.cs
  14. 116 143
      MediaBrowser.Api/Movies/MoviesService.cs
  15. 1 1
      MediaBrowser.Api/Movies/TrailersService.cs
  16. 7 6
      MediaBrowser.Api/Music/AlbumsService.cs
  17. 11 10
      MediaBrowser.Api/Music/InstantMixService.cs
  18. 7 5
      MediaBrowser.Api/PackageReviewService.cs
  19. 51 28
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  20. 2 2
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  21. 1 1
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  22. 5 4
      MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
  23. 3 2
      MediaBrowser.Api/Playback/Progressive/AudioService.cs
  24. 15 13
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  25. 3 2
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  26. 1 1
      MediaBrowser.Api/Playback/StreamState.cs
  27. 2 2
      MediaBrowser.Api/PlaylistService.cs
  28. 0 1
      MediaBrowser.Api/Reports/ReportsService.cs
  29. 9 19
      MediaBrowser.Api/SimilarItemsHelper.cs
  30. 11 6
      MediaBrowser.Api/StartupWizardService.cs
  31. 26 10
      MediaBrowser.Api/Subtitles/SubtitleService.cs
  32. 9 10
      MediaBrowser.Api/Sync/SyncService.cs
  33. 1 1
      MediaBrowser.Api/System/SystemInfoWebSocketListener.cs
  34. 8 8
      MediaBrowser.Api/System/SystemService.cs
  35. 47 21
      MediaBrowser.Api/TvShowsService.cs
  36. 27 13
      MediaBrowser.Api/UserLibrary/ArtistsService.cs
  37. 134 7
      MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
  38. 0 3
      MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
  39. 8 21
      MediaBrowser.Api/UserLibrary/GameGenresService.cs
  40. 17 44
      MediaBrowser.Api/UserLibrary/GenresService.cs
  41. 54 16
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  42. 11 10
      MediaBrowser.Api/UserLibrary/MusicGenresService.cs
  43. 3 3
      MediaBrowser.Api/UserLibrary/PlaystateService.cs
  44. 9 2
      MediaBrowser.Api/UserLibrary/StudiosService.cs
  45. 6 6
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  46. 7 2
      MediaBrowser.Api/UserService.cs
  47. 4 4
      MediaBrowser.Common.Implementations/BaseApplicationHost.cs
  48. 10 10
      MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
  49. 3 3
      MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
  50. 18 21
      MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  51. 6 2
      MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs
  52. 9 5
      MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs
  53. 2 2
      MediaBrowser.Common.Implementations/packages.config
  54. 3 0
      MediaBrowser.Common/Net/HttpRequestOptions.cs
  55. 8 0
      MediaBrowser.Controller/Channels/ChannelItemInfo.cs
  56. 1 1
      MediaBrowser.Controller/Chapters/IChapterManager.cs
  57. 0 5
      MediaBrowser.Controller/Drawing/ImageCollageOptions.cs
  58. 2 1
      MediaBrowser.Controller/Dto/IDtoService.cs
  59. 0 3
      MediaBrowser.Controller/Entities/Audio/Audio.cs
  60. 3 20
      MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
  61. 18 8
      MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
  62. 10 1
      MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
  63. 25 13
      MediaBrowser.Controller/Entities/BaseItem.cs
  64. 1 1
      MediaBrowser.Controller/Entities/Book.cs
  65. 63 272
      MediaBrowser.Controller/Entities/Folder.cs
  66. 1 1
      MediaBrowser.Controller/Entities/Game.cs
  67. 10 1
      MediaBrowser.Controller/Entities/GameGenre.cs
  68. 10 1
      MediaBrowser.Controller/Entities/Genre.cs
  69. 2 1
      MediaBrowser.Controller/Entities/IHasUserData.cs
  70. 13 2
      MediaBrowser.Controller/Entities/InternalItemsQuery.cs
  71. 1 11
      MediaBrowser.Controller/Entities/KeywordExtensions.cs
  72. 1 8
      MediaBrowser.Controller/Entities/Movies/BoxSet.cs
  73. 19 3
      MediaBrowser.Controller/Entities/Movies/Movie.cs
  74. 10 1
      MediaBrowser.Controller/Entities/Person.cs
  75. 1 1
      MediaBrowser.Controller/Entities/Photo.cs
  76. 11 2
      MediaBrowser.Controller/Entities/Studio.cs
  77. 26 8
      MediaBrowser.Controller/Entities/TV/Episode.cs
  78. 14 67
      MediaBrowser.Controller/Entities/TV/Season.cs
  79. 109 112
      MediaBrowser.Controller/Entities/TV/Series.cs
  80. 1 14
      MediaBrowser.Controller/Entities/TagExtensions.cs
  81. 19 3
      MediaBrowser.Controller/Entities/Trailer.cs
  82. 5 0
      MediaBrowser.Controller/Entities/UserRootFolder.cs
  83. 6 1
      MediaBrowser.Controller/Entities/UserView.cs
  84. 46 75
      MediaBrowser.Controller/Entities/UserViewBuilder.cs
  85. 0 4
      MediaBrowser.Controller/Entities/Video.cs
  86. 4 3
      MediaBrowser.Controller/IServerApplicationHost.cs
  87. 8 7
      MediaBrowser.Controller/Library/ILibraryManager.cs
  88. 3 1
      MediaBrowser.Controller/Library/IUserDataManager.cs
  89. 1 0
      MediaBrowser.Controller/LiveTv/IListingsProvider.cs
  90. 18 0
      MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
  91. 19 0
      MediaBrowser.Controller/LiveTv/ILiveTvService.cs
  92. 21 0
      MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
  93. 14 0
      MediaBrowser.Controller/LiveTv/TimerEventInfo.cs
  94. 16 0
      MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs
  95. 4 10
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  96. 3 2
      MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
  97. 12 12
      MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
  98. 1 7
      MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs
  99. 4 4
      MediaBrowser.Controller/Net/IHttpResultFactory.cs
  100. 11 3
      MediaBrowser.Controller/Persistence/IItemRepository.cs

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

@@ -80,7 +80,6 @@
     <Compile Include="ImageMagick\UnplayedCountIndicator.cs" />
   </ItemGroup>
   <ItemGroup>
-    <EmbeddedResource Include="ImageMagick\fonts\MontserratLight.otf" />
     <EmbeddedResource Include="ImageMagick\fonts\robotoregular.ttf" />
   </ItemGroup>
   <ItemGroup>

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

@@ -111,7 +111,6 @@ namespace Emby.Drawing.ImageMagick
                 wand.CurrentImage.TrimImage(10);
                 wand.SaveImage(outputPath);
             }
-            SaveDelay();
         }
 
         public ImageSize GetImageSize(string path)
@@ -189,7 +188,6 @@ namespace Emby.Drawing.ImageMagick
                     }
                 }
             }
-            SaveDelay();
         }
 
         private void AddForegroundLayer(MagickWand wand, ImageProcessingOptions options)
@@ -284,25 +282,16 @@ namespace Emby.Drawing.ImageMagick
 
             if (ratio >= 1.4)
             {
-                new StripCollageBuilder(_appPaths, _fileSystem).BuildThumbCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height, options.Text);
+                new StripCollageBuilder(_appPaths, _fileSystem).BuildThumbCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height);
             }
             else if (ratio >= .9)
             {
-                new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height, options.Text);
+                new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height);
             }
             else
             {
-                new StripCollageBuilder(_appPaths, _fileSystem).BuildPosterCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height, options.Text);
+                new StripCollageBuilder(_appPaths, _fileSystem).BuildPosterCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height);
             }
-
-            SaveDelay();
-        }
-
-        private void SaveDelay()
-        {
-            // For some reason the images are not always getting released right away
-            //var task = Task.Delay(300);
-            //Task.WaitAll(task);
         }
 
         public string Name

+ 26 - 325
Emby.Drawing/ImageMagick/StripCollageBuilder.cs

@@ -9,140 +9,35 @@ namespace Emby.Drawing.ImageMagick
     public class StripCollageBuilder
     {
         private readonly IApplicationPaths _appPaths;
-		private readonly IFileSystem _fileSystem;
+        private readonly IFileSystem _fileSystem;
 
-		public StripCollageBuilder(IApplicationPaths appPaths, IFileSystem fileSystem)
+        public StripCollageBuilder(IApplicationPaths appPaths, IFileSystem fileSystem)
         {
             _appPaths = appPaths;
-			_fileSystem = fileSystem;
+            _fileSystem = fileSystem;
         }
 
-        public void BuildPosterCollage(List<string> paths, string outputPath, int width, int height, string text)
+        public void BuildPosterCollage(List<string> paths, string outputPath, int width, int height)
         {
-            if (!string.IsNullOrWhiteSpace(text))
+            using (var wand = BuildPosterCollageWand(paths, width, height))
             {
-                using (var wand = BuildPosterCollageWandWithText(paths, text, width, height))
-                {
-                    wand.SaveImage(outputPath);
-                }
-            }
-            else
-            {
-                using (var wand = BuildPosterCollageWand(paths, width, height))
-                {
-                    wand.SaveImage(outputPath);
-                }
+                wand.SaveImage(outputPath);
             }
         }
 
-        public void BuildSquareCollage(List<string> paths, string outputPath, int width, int height, string text)
+        public void BuildSquareCollage(List<string> paths, string outputPath, int width, int height)
         {
-            if (!string.IsNullOrWhiteSpace(text))
+            using (var wand = BuildSquareCollageWand(paths, width, height))
             {
-                using (var wand = BuildSquareCollageWandWithText(paths, text, width, height))
-                {
-                    wand.SaveImage(outputPath);
-                }
-            }
-            else
-            {
-                using (var wand = BuildSquareCollageWand(paths, width, height))
-                {
-                    wand.SaveImage(outputPath);
-                }
+                wand.SaveImage(outputPath);
             }
         }
 
-        public void BuildThumbCollage(List<string> paths, string outputPath, int width, int height, string text)
+        public void BuildThumbCollage(List<string> paths, string outputPath, int width, int height)
         {
-            if (!string.IsNullOrWhiteSpace(text))
+            using (var wand = BuildThumbCollageWand(paths, width, height))
             {
-                using (var wand = BuildThumbCollageWandWithText(paths, text, width, height))
-                {
-                    wand.SaveImage(outputPath);
-                }
-            }
-            else
-            {
-                using (var wand = BuildThumbCollageWand(paths, width, height))
-                {
-                    wand.SaveImage(outputPath);
-                }
-            }
-        }
-
-        private MagickWand BuildThumbCollageWandWithText(List<string> paths, string text, int width, int height)
-        {
-            var inputPaths = ImageHelpers.ProjectPaths(paths, 8);
-            using (var wandImages = new MagickWand(inputPaths.ToArray()))
-            {
-                var wand = new MagickWand(width, height);
-                wand.OpenImage("gradient:#111111-#111111");
-                using (var draw = new DrawingWand())
-                {
-                    using (var fcolor = new PixelWand(ColorName.White))
-                    {
-                        draw.FillColor = fcolor;
-                        draw.Font = MontserratLightFont;
-                        draw.FontSize = 60;
-                        draw.FontWeight = FontWeightType.LightStyle;
-                        draw.TextAntialias = true;
-                    }
-
-                    var fontMetrics = wand.QueryFontMetrics(draw, text);
-                    var textContainerY = Convert.ToInt32(height * .165);
-                    wand.CurrentImage.AnnotateImage(draw, (width - fontMetrics.TextWidth) / 2, textContainerY, 0.0, text);
-
-                    var iSlice = Convert.ToInt32(width * .1166666667);
-                    int iTrans = Convert.ToInt32(height * 0.2);
-                    int iHeight = Convert.ToInt32(height * 0.46296296296296296296296296296296);
-                    var horizontalImagePadding = Convert.ToInt32(width * 0.0125);
-
-                    foreach (var element in wandImages.ImageList)
-                    {
-                        int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height);
-                        element.Gravity = GravityType.CenterGravity;
-                        element.BackgroundColor = new PixelWand("none", 1);
-                        element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter);
-                        int ix = (int)Math.Abs((iWidth - iSlice) / 2);
-                        element.CropImage(iSlice, iHeight, ix, 0);
-
-                        element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0);
-                    }
-
-                    wandImages.SetFirstIterator();
-                    using (var wandList = wandImages.AppendImages())
-                    {
-                        wandList.CurrentImage.TrimImage(1);
-                        using (var mwr = wandList.CloneMagickWand())
-                        {
-                            using (var blackPixelWand = new PixelWand(ColorName.Black))
-                            {
-                                using (var greyPixelWand = new PixelWand(ColorName.Grey70))
-                                {
-                                    mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1);
-                                    mwr.CurrentImage.FlipImage();
-
-                                    mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel;
-                                    mwr.CurrentImage.ColorizeImage(blackPixelWand, greyPixelWand);
-
-                                    using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans))
-                                    {
-                                        mwg.OpenImage("gradient:black-none");
-                                        var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111);
-                                        mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.DstInCompositeOp, 0, verticalSpacing);
-
-                                        wandList.AddImage(mwr);
-                                        int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2;
-                                        wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * 0.26851851851851851851851851851852));
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-
-                return wand;
+                wand.SaveImage(outputPath);
             }
         }
 
@@ -211,81 +106,6 @@ namespace Emby.Drawing.ImageMagick
             }
         }
 
-        private MagickWand BuildPosterCollageWandWithText(List<string> paths, string label, int width, int height)
-        {
-            var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
-            using (var wandImages = new MagickWand(inputPaths.ToArray()))
-            {
-                var wand = new MagickWand(width, height);
-                wand.OpenImage("gradient:#111111-#111111");
-                using (var draw = new DrawingWand())
-                {
-                    using (var fcolor = new PixelWand(ColorName.White))
-                    {
-                        draw.FillColor = fcolor;
-                        draw.Font = MontserratLightFont;
-                        draw.FontSize = 60;
-                        draw.FontWeight = FontWeightType.LightStyle;
-                        draw.TextAntialias = true;
-                    }
-
-                    var fontMetrics = wand.QueryFontMetrics(draw, label);
-                    var textContainerY = Convert.ToInt32(height * .165);
-                    wand.CurrentImage.AnnotateImage(draw, (width - fontMetrics.TextWidth) / 2, textContainerY, 0.0, label);
-
-                    var iSlice = Convert.ToInt32(width * 0.225);
-                    int iTrans = Convert.ToInt32(height * 0.2);
-                    int iHeight = Convert.ToInt32(height * 0.46296296296296296296296296296296);
-                    var horizontalImagePadding = Convert.ToInt32(width * 0.0275);
-
-                    foreach (var element in wandImages.ImageList)
-                    {
-                        int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height);
-                        element.Gravity = GravityType.CenterGravity;
-                        element.BackgroundColor = new PixelWand("none", 1);
-                        element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter);
-                        int ix = (int)Math.Abs((iWidth - iSlice) / 2);
-                        element.CropImage(iSlice, iHeight, ix, 0);
-
-                        element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0);
-                    }
-
-                    wandImages.SetFirstIterator();
-                    using (var wandList = wandImages.AppendImages())
-                    {
-                        wandList.CurrentImage.TrimImage(1);
-                        using (var mwr = wandList.CloneMagickWand())
-                        {
-                            using (var blackPixelWand = new PixelWand(ColorName.Black))
-                            {
-                                using (var greyPixelWand = new PixelWand(ColorName.Grey70))
-                                {
-                                    mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1);
-                                    mwr.CurrentImage.FlipImage();
-
-                                    mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel;
-                                    mwr.CurrentImage.ColorizeImage(blackPixelWand, greyPixelWand);
-
-                                    using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans))
-                                    {
-                                        mwg.OpenImage("gradient:black-none");
-                                        var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111);
-                                        mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.DstInCompositeOp, 0, verticalSpacing);
-
-                                        wandList.AddImage(mwr);
-                                        int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2;
-                                        wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * 0.26851851851851851851851851851852));
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-
-                return wand;
-            }
-        }
-
         private MagickWand BuildThumbCollageWand(List<string> paths, int width, int height)
         {
             var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
@@ -352,148 +172,29 @@ namespace Emby.Drawing.ImageMagick
         }
 
         private MagickWand BuildSquareCollageWand(List<string> paths, int width, int height)
-        {
-            var inputPaths = ImageHelpers.ProjectPaths(paths, 3);
-            using (var wandImages = new MagickWand(inputPaths.ToArray()))
-            {
-                var wand = new MagickWand(width, height);
-                wand.OpenImage("gradient:#111111-#111111");
-                using (var draw = new DrawingWand())
-                {
-                    var iSlice = Convert.ToInt32(width * .32);
-                    int iTrans = Convert.ToInt32(height * .25);
-                    int iHeight = Convert.ToInt32(height * .68);
-                    var horizontalImagePadding = Convert.ToInt32(width * 0.02);
-
-                    foreach (var element in wandImages.ImageList)
-                    {
-                        using (var blackPixelWand = new PixelWand(ColorName.Black))
-                        {
-                            int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height);
-                            element.Gravity = GravityType.CenterGravity;
-                            element.BackgroundColor = blackPixelWand;
-                            element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter);
-                            int ix = (int)Math.Abs((iWidth - iSlice) / 2);
-                            element.CropImage(iSlice, iHeight, ix, 0);
-
-                            element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0);
-                        } 
-                    }
-
-                    wandImages.SetFirstIterator();
-                    using (var wandList = wandImages.AppendImages())
-                    {
-                        wandList.CurrentImage.TrimImage(1);
-                        using (var mwr = wandList.CloneMagickWand())
-                        {
-                            using (var blackPixelWand = new PixelWand(ColorName.Black))
-                            {
-                                using (var greyPixelWand = new PixelWand(ColorName.Grey70))
-                                {
-                                    mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1);
-                                    mwr.CurrentImage.FlipImage();
-
-                                    mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel;
-                                    mwr.CurrentImage.ColorizeImage(blackPixelWand, greyPixelWand);
-
-                                    using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans))
-                                    {
-                                        mwg.OpenImage("gradient:black-none");
-                                        var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111);
-                                        mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.CopyOpacityCompositeOp, 0, verticalSpacing);
-
-                                        wandList.AddImage(mwr);
-                                        int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2;
-                                        wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .03));
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-
-                return wand;
-            }
-        }
-
-        private MagickWand BuildSquareCollageWandWithText(List<string> paths, string label, int width, int height)
         {
             var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
-            using (var wandImages = new MagickWand(inputPaths.ToArray()))
+            var outputWand = new MagickWand(width, height, new PixelWand("none", 1));
+            var imageIndex = 0;
+            var cellWidth = width/2;
+            var cellHeight = height/2;
+            for (var x = 0; x < 2; x++)
             {
-                var wand = new MagickWand(width, height);
-                wand.OpenImage("gradient:#111111-#111111");
-                using (var draw = new DrawingWand())
+                for (var y = 0; y < 2; y++)
                 {
-                    using (var fcolor = new PixelWand(ColorName.White))
-                    {
-                        draw.FillColor = fcolor;
-                        draw.Font = MontserratLightFont;
-                        draw.FontSize = 60;
-                        draw.FontWeight = FontWeightType.LightStyle;
-                        draw.TextAntialias = true;
-                    }
-
-                    var fontMetrics = wand.QueryFontMetrics(draw, label);
-                    var textContainerY = Convert.ToInt32(height * .165);
-                    wand.CurrentImage.AnnotateImage(draw, (width - fontMetrics.TextWidth) / 2, textContainerY, 0.0, label);
-
-                    var iSlice = Convert.ToInt32(width * .225);
-                    int iTrans = Convert.ToInt32(height * 0.2);
-                    int iHeight = Convert.ToInt32(height * 0.46296296296296296296296296296296);
-                    var horizontalImagePadding = Convert.ToInt32(width * 0.02);
-
-                    foreach (var element in wandImages.ImageList)
-                    {
-                        int iWidth = (int)Math.Abs(iHeight * element.Width / element.Height);
-                        element.Gravity = GravityType.CenterGravity;
-                        element.BackgroundColor = new PixelWand("none", 1);
-                        element.ResizeImage(iWidth, iHeight, FilterTypes.LanczosFilter);
-                        int ix = (int)Math.Abs((iWidth - iSlice) / 2);
-                        element.CropImage(iSlice, iHeight, ix, 0);
-
-                        element.ExtentImage(iSlice, iHeight, 0 - horizontalImagePadding, 0);
-                    }
-
-                    wandImages.SetFirstIterator();
-                    using (var wandList = wandImages.AppendImages())
+                    using (var temp = new MagickWand(inputPaths[imageIndex]))
                     {
-                        wandList.CurrentImage.TrimImage(1);
-                        using (var mwr = wandList.CloneMagickWand())
-                        {
-                            using (var blackPixelWand = new PixelWand(ColorName.Black))
-                            {
-                                using (var greyPixelWand = new PixelWand(ColorName.Grey70))
-                                {
-                                    mwr.CurrentImage.ResizeImage(wandList.CurrentImage.Width, (wandList.CurrentImage.Height / 2), FilterTypes.LanczosFilter, 1);
-                                    mwr.CurrentImage.FlipImage();
-
-                                    mwr.CurrentImage.AlphaChannel = AlphaChannelType.DeactivateAlphaChannel;
-                                    mwr.CurrentImage.ColorizeImage(blackPixelWand, greyPixelWand);
-
-                                    using (var mwg = new MagickWand(wandList.CurrentImage.Width, iTrans))
-                                    {
-                                        mwg.OpenImage("gradient:black-none");
-                                        var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111);
-                                        mwr.CurrentImage.CompositeImage(mwg, CompositeOperator.DstInCompositeOp, 0, verticalSpacing);
-
-                                        wandList.AddImage(mwr);
-                                        int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2;
-                                        wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * 0.26851851851851851851851851851852));
-                                    }
-                                }
-                            }
-                        }
+                        temp.CurrentImage.ScaleImage(cellWidth, cellHeight);
+                        // draw this image into the strip at the next position
+                        var xPos = x*cellWidth;
+                        var yPos = y*cellHeight;
+                        outputWand.CurrentImage.CompositeImage(temp, CompositeOperator.OverCompositeOp, xPos, yPos);
                     }
+                    imageIndex++;
                 }
-
-                return wand;
             }
-        }
 
-        private string MontserratLightFont
-        {
-			get { return PlayedIndicatorDrawer.ExtractFont("MontserratLight.otf", _appPaths, _fileSystem); }
+            return outputWand;
         }
     }
 }

+ 1 - 1
MediaBrowser.Api/BaseApiService.cs

@@ -115,7 +115,7 @@ namespace MediaBrowser.Api
         /// <returns>System.Object.</returns>
         protected object ToStaticFileResult(string path)
         {
-            return ResultFactory.GetStaticFileResult(Request, path);
+            return ResultFactory.GetStaticFileResult(Request, path).Result;
         }
 
         protected DtoOptions GetDtoOptions(object request)

+ 21 - 1
MediaBrowser.Api/ConfigurationService.cs

@@ -9,7 +9,9 @@ using ServiceStack.Web;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
+using System.Threading.Tasks;
 using CommonIO;
+using MediaBrowser.Controller.MediaEncoding;
 
 namespace MediaBrowser.Api
 {
@@ -71,6 +73,16 @@ namespace MediaBrowser.Api
 
     }
 
+    [Route("/System/MediaEncoder/Path", "POST", Summary = "Updates the path to the media encoder")]
+    [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)]
+    public class UpdateMediaEncoderPath : IReturnVoid
+    {
+        [ApiMember(Name = "Path", Description = "Path", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Path { get; set; }
+        [ApiMember(Name = "PathType", Description = "PathType", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string PathType { get; set; }
+    }
+
     public class ConfigurationService : BaseApiService
     {
         /// <summary>
@@ -86,14 +98,22 @@ namespace MediaBrowser.Api
         private readonly IFileSystem _fileSystem;
         private readonly IProviderManager _providerManager;
         private readonly ILibraryManager _libraryManager;
+        private readonly IMediaEncoder _mediaEncoder;
 
-        public ConfigurationService(IJsonSerializer jsonSerializer, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IProviderManager providerManager, ILibraryManager libraryManager)
+        public ConfigurationService(IJsonSerializer jsonSerializer, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IProviderManager providerManager, ILibraryManager libraryManager, IMediaEncoder mediaEncoder)
         {
             _jsonSerializer = jsonSerializer;
             _configurationManager = configurationManager;
             _fileSystem = fileSystem;
             _providerManager = providerManager;
             _libraryManager = libraryManager;
+            _mediaEncoder = mediaEncoder;
+        }
+
+        public void Post(UpdateMediaEncoderPath request)
+        {
+            var task = _mediaEncoder.UpdateEncoderPath(request.Path, request.PathType);
+            Task.WaitAll(task);
         }
 
         /// <summary>

+ 1 - 1
MediaBrowser.Api/FilterService.cs

@@ -80,7 +80,7 @@ namespace MediaBrowser.Api
                 .OrderBy(i => i)
                 .ToArray();
 
-            result.Tags = items.OfType<IHasTags>()
+            result.Tags = items
                 .SelectMany(i => i.Tags)
                 .Distinct(StringComparer.OrdinalIgnoreCase)
                 .OrderBy(i => i)

+ 36 - 14
MediaBrowser.Api/GamesService.cs

@@ -10,6 +10,8 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Querying;
 
 namespace MediaBrowser.Api
 {
@@ -107,8 +109,7 @@ namespace MediaBrowser.Api
             {
                 IncludeItemTypes = new[] { typeof(GameSystem).Name }
             };
-            var parentIds = new string[] { } ;
-            var gameSystems = _libraryManager.GetItemList(query, parentIds)
+            var gameSystems = _libraryManager.GetItemList(query)
                 .Cast<GameSystem>()
                 .ToList();
 
@@ -128,8 +129,7 @@ namespace MediaBrowser.Api
             {
                 IncludeItemTypes = new[] { typeof(Game).Name }
             };
-            var parentIds = new string[] { };
-            var games = _libraryManager.GetItemList(query, parentIds)
+            var games = _libraryManager.GetItemList(query)
                 .Cast<Game>()
                 .ToList();
 
@@ -185,20 +185,42 @@ namespace MediaBrowser.Api
         /// </summary>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
-        public object Get(GetSimilarGames request)
+        public async Task<object> Get(GetSimilarGames request)
         {
+            var result = await GetSimilarItemsResult(request).ConfigureAwait(false);
+
+            return ToOptimizedSerializedResultUsingCache(result);
+        }
+
+        private async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
+        {
+            var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+
+            var item = string.IsNullOrEmpty(request.Id) ?
+                (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
+                _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
+
+            var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
+            {
+                Limit = request.Limit,
+                IncludeItemTypes = new[]
+                {
+                        typeof(Game).Name
+                },
+                SimilarTo = item
+
+            }).ToList();
+
             var dtoOptions = GetDtoOptions(request);
 
-            var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
-                _itemRepo,
-                _libraryManager,
-                _userDataRepository,
-                _dtoService,
-                Logger,
-                request, new[] { typeof(Game) },
-                SimilarItemsHelper.GetSimiliarityScore);
+            var result = new QueryResult<BaseItemDto>
+            {
+                Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(),
 
-            return ToOptimizedSerializedResultUsingCache(result);
+                TotalRecordCount = itemsResult.Count
+            };
+
+            return result;
         }
     }
 }

+ 5 - 5
MediaBrowser.Api/Images/ImageService.cs

@@ -514,7 +514,7 @@ namespace MediaBrowser.Api.Images
         /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
         /// <returns>System.Object.</returns>
         /// <exception cref="ResourceNotFoundException"></exception>
-        public object GetImage(ImageRequest request, IHasImages item, bool isHeadRequest)
+        public Task<object> GetImage(ImageRequest request, IHasImages item, bool isHeadRequest)
         {
             if (request.PercentPlayed.HasValue)
             {
@@ -594,8 +594,7 @@ namespace MediaBrowser.Api.Images
                 supportedImageEnhancers,
                 cacheDuration,
                 responseHeaders,
-                isHeadRequest)
-                .Result;
+                isHeadRequest);
         }
 
         private async Task<object> GetImageResult(IHasImages item,
@@ -632,7 +631,7 @@ namespace MediaBrowser.Api.Images
 
             headers["Vary"] = "Accept";
 
-            return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
+            return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
             {
                 CacheDuration = cacheDuration,
                 ResponseHeaders = headers,
@@ -643,7 +642,8 @@ namespace MediaBrowser.Api.Images
                 // Sometimes imagemagick keeps a hold on the file briefly even after it's done writing to it.
                 // I'd rather do this than add a delay after saving the file
                 FileShare = FileShare.ReadWrite
-            });
+
+            }).ConfigureAwait(false);
         }
 
         private List<ImageFormat> GetOutputFormats(ImageRequest request, ItemImageInfo image, bool cropwhitespace, List<IImageEnhancer> enhancers)

+ 5 - 5
MediaBrowser.Api/Images/RemoteImageService.cs

@@ -238,9 +238,9 @@ namespace MediaBrowser.Api.Images
                 }
 
 				if (_fileSystem.FileExists(contentPath))
-                {
-                    return ToStaticFileResult(contentPath);
-                }
+				{
+				    return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false);
+				}
             }
             catch (DirectoryNotFoundException)
             {
@@ -259,9 +259,9 @@ namespace MediaBrowser.Api.Images
                 contentPath = await reader.ReadToEndAsync().ConfigureAwait(false);
             }
 
-            return ToStaticFileResult(contentPath);
+            return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false);
         }
-        
+
         /// <summary>
         /// Downloads the image.
         /// </summary>

+ 30 - 19
MediaBrowser.Api/ItemLookupService.cs

@@ -38,6 +38,12 @@ namespace MediaBrowser.Api
     {
     }
 
+    [Route("/Items/RemoteSearch/Trailer", "POST")]
+    [Authenticated]
+    public class GetTrailerRemoteSearchResults : RemoteSearchQuery<TrailerInfo>, IReturn<List<RemoteSearchResult>>
+    {
+    }
+
     [Route("/Items/RemoteSearch/AdultVideo", "POST")]
     [Authenticated]
     public class GetAdultVideoRemoteSearchResults : RemoteSearchQuery<ItemLookupInfo>, IReturn<List<RemoteSearchResult>>
@@ -132,60 +138,65 @@ namespace MediaBrowser.Api
             return ToOptimizedResult(infos);
         }
 
-        public object Post(GetMovieRemoteSearchResults request)
+        public async Task<object> Post(GetTrailerRemoteSearchResults request)
         {
-            var result = _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(request, CancellationToken.None).Result;
+            var result = await _providerManager.GetRemoteSearchResults<Trailer, TrailerInfo>(request, CancellationToken.None).ConfigureAwait(false);
 
             return ToOptimizedResult(result);
         }
 
-        public object Post(GetSeriesRemoteSearchResults request)
+        public async Task<object> Post(GetMovieRemoteSearchResults request)
         {
-            var result = _providerManager.GetRemoteSearchResults<Series, SeriesInfo>(request, CancellationToken.None).Result;
+            var result = await _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(request, CancellationToken.None).ConfigureAwait(false);
 
             return ToOptimizedResult(result);
         }
 
-        public object Post(GetGameRemoteSearchResults request)
+        public async Task<object> Post(GetSeriesRemoteSearchResults request)
         {
-            var result = _providerManager.GetRemoteSearchResults<Game, GameInfo>(request, CancellationToken.None).Result;
+            var result = await _providerManager.GetRemoteSearchResults<Series, SeriesInfo>(request, CancellationToken.None).ConfigureAwait(false);
 
             return ToOptimizedResult(result);
         }
 
-        public object Post(GetBoxSetRemoteSearchResults request)
+        public async Task<object> Post(GetGameRemoteSearchResults request)
         {
-            var result = _providerManager.GetRemoteSearchResults<BoxSet, BoxSetInfo>(request, CancellationToken.None).Result;
+            var result = await _providerManager.GetRemoteSearchResults<Game, GameInfo>(request, CancellationToken.None).ConfigureAwait(false);
 
             return ToOptimizedResult(result);
         }
 
-        public object Post(GetPersonRemoteSearchResults request)
+        public async Task<object> Post(GetBoxSetRemoteSearchResults request)
         {
-            var result = _providerManager.GetRemoteSearchResults<Person, PersonLookupInfo>(request, CancellationToken.None).Result;
+            var result = await _providerManager.GetRemoteSearchResults<BoxSet, BoxSetInfo>(request, CancellationToken.None).ConfigureAwait(false);
 
             return ToOptimizedResult(result);
         }
 
-        public object Post(GetMusicAlbumRemoteSearchResults request)
+        public async Task<object> Post(GetPersonRemoteSearchResults request)
         {
-            var result = _providerManager.GetRemoteSearchResults<MusicAlbum, AlbumInfo>(request, CancellationToken.None).Result;
+            var result = await _providerManager.GetRemoteSearchResults<Person, PersonLookupInfo>(request, CancellationToken.None).ConfigureAwait(false);
 
             return ToOptimizedResult(result);
         }
 
-        public object Post(GetMusicArtistRemoteSearchResults request)
+        public async Task<object> Post(GetMusicAlbumRemoteSearchResults request)
         {
-            var result = _providerManager.GetRemoteSearchResults<MusicArtist, ArtistInfo>(request, CancellationToken.None).Result;
+            var result = await _providerManager.GetRemoteSearchResults<MusicAlbum, AlbumInfo>(request, CancellationToken.None).ConfigureAwait(false);
 
             return ToOptimizedResult(result);
         }
 
-        public object Get(GetRemoteSearchImage request)
+        public async Task<object> Post(GetMusicArtistRemoteSearchResults request)
         {
-            var result = GetRemoteImage(request).Result;
+            var result = await _providerManager.GetRemoteSearchResults<MusicArtist, ArtistInfo>(request, CancellationToken.None).ConfigureAwait(false);
 
-            return result;
+            return ToOptimizedResult(result);
+        }
+
+        public Task<object> Get(GetRemoteSearchImage request)
+        {
+            return GetRemoteImage(request);
         }
 
         public void Post(ApplySearchCriteria request)
@@ -241,7 +252,7 @@ namespace MediaBrowser.Api
 
                 if (_fileSystem.FileExists(contentPath))
                 {
-                    return ToStaticFileResult(contentPath);
+                    return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false);
                 }
             }
             catch (DirectoryNotFoundException)
@@ -261,7 +272,7 @@ namespace MediaBrowser.Api
                 contentPath = await reader.ReadToEndAsync().ConfigureAwait(false);
             }
 
-            return ToStaticFileResult(contentPath);
+            return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false);
         }
 
         /// <summary>

+ 13 - 31
MediaBrowser.Api/ItemUpdateService.cs

@@ -70,26 +70,21 @@ namespace MediaBrowser.Api
                 Cultures = _localizationManager.GetCultures().ToList()
             };
 
-            var locationType = item.LocationType;
-            if (locationType == LocationType.FileSystem ||
-                locationType == LocationType.Offline)
+            if (!item.IsVirtualItem && !(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName))
             {
-                if (!(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName))
+                var inheritedContentType = _libraryManager.GetInheritedContentType(item);
+                var configuredContentType = _libraryManager.GetConfiguredContentType(item);
+
+                if (string.IsNullOrWhiteSpace(inheritedContentType) || string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || !string.IsNullOrWhiteSpace(configuredContentType))
                 {
-                    var inheritedContentType = _libraryManager.GetInheritedContentType(item);
-                    var configuredContentType = _libraryManager.GetConfiguredContentType(item);
+                    info.ContentTypeOptions = GetContentTypeOptions(true);
+                    info.ContentType = configuredContentType;
 
-                    if (string.IsNullOrWhiteSpace(inheritedContentType) || string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || !string.IsNullOrWhiteSpace(configuredContentType))
+                    if (string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
                     {
-                        info.ContentTypeOptions = GetContentTypeOptions(true);
-                        info.ContentType = configuredContentType;
-
-                        if (string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
-                        {
-                            info.ContentTypeOptions = info.ContentTypeOptions
-                                .Where(i => string.IsNullOrWhiteSpace(i.Value) || string.Equals(i.Value, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
-                                .ToList();
-                        }
+                        info.ContentTypeOptions = info.ContentTypeOptions
+                            .Where(i => string.IsNullOrWhiteSpace(i.Value) || string.Equals(i.Value, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
+                            .ToList();
                     }
                 }
             }
@@ -280,11 +275,7 @@ namespace MediaBrowser.Api
                 episode.AbsoluteEpisodeNumber = request.AbsoluteEpisodeNumber;
             }
 
-            var hasTags = item as IHasTags;
-            if (hasTags != null)
-            {
-                hasTags.Tags = request.Tags;
-            }
+            item.Tags = request.Tags;
 
             var hasTaglines = item as IHasTaglines;
             if (hasTaglines != null)
@@ -298,11 +289,7 @@ namespace MediaBrowser.Api
                 hasShortOverview.ShortOverview = request.ShortOverview;
             }
 
-            var hasKeywords = item as IHasKeywords;
-            if (hasKeywords != null)
-            {
-                hasKeywords.Keywords = request.Keywords;
-            }
+            item.Keywords = request.Keywords;
 
             if (request.Studios != null)
             {
@@ -427,11 +414,6 @@ namespace MediaBrowser.Api
                 series.Status = request.SeriesStatus;
                 series.AirDays = request.AirDays;
                 series.AirTime = request.AirTime;
-
-                if (request.DisplaySpecialsWithSeasons.HasValue)
-                {
-                    series.DisplaySpecialsWithSeasons = request.DisplaySpecialsWithSeasons.Value;
-                }
             }
         }
 

+ 3 - 3
MediaBrowser.Api/Library/LibraryService.cs

@@ -493,7 +493,7 @@ namespace MediaBrowser.Api.Library
             }
         }
 
-        public object Get(GetDownload request)
+        public Task<object> Get(GetDownload request)
         {
             var item = _libraryManager.GetItemById(request.Id);
             var auth = _authContext.GetAuthorizationInfo(Request);
@@ -552,7 +552,7 @@ namespace MediaBrowser.Api.Library
             }
         }
 
-        public object Get(GetFile request)
+        public Task<object> Get(GetFile request)
         {
             var item = _libraryManager.GetItemById(request.Id);
             var locationType = item.LocationType;
@@ -565,7 +565,7 @@ namespace MediaBrowser.Api.Library
                 throw new ArgumentException("This command cannot be used for directories.");
             }
 
-            return ToStaticFileResult(item.Path);
+            return ResultFactory.GetStaticFileResult(Request, item.Path);
         }
 
         /// <summary>

+ 99 - 12
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -146,6 +146,13 @@ namespace MediaBrowser.Api.LiveTv
         /// <value>The fields.</value>
         [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
+
+        public bool EnableTotalRecordCount { get; set; }
+
+        public GetRecordings()
+        {
+            EnableTotalRecordCount = true;
+        }
     }
 
     [Route("/LiveTv/Recordings/Groups", "GET", Summary = "Gets live tv recording groups")]
@@ -200,6 +207,8 @@ namespace MediaBrowser.Api.LiveTv
 
         [ApiMember(Name = "SeriesTimerId", Description = "Optional filter by timers belonging to a series timer", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string SeriesTimerId { get; set; }
+
+        public bool? IsActive { get; set; }
     }
 
     [Route("/LiveTv/Programs", "GET,POST", Summary = "Gets available live tv epgs..")]
@@ -439,6 +448,12 @@ namespace MediaBrowser.Api.LiveTv
         public string Id { get; set; }
     }
 
+    [Route("/LiveTv/ListingProviders/Default", "GET")]
+    [Authenticated(AllowBeforeStartupWizard = true)]
+    public class GetDefaultListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
+    {
+    }
+
     [Route("/LiveTv/ListingProviders", "POST", Summary = "Adds a listing provider")]
     [Authenticated(AllowBeforeStartupWizard = true)]
     public class AddListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
@@ -478,6 +493,32 @@ namespace MediaBrowser.Api.LiveTv
     {
     }
 
+    [Route("/LiveTv/ChannelMappingOptions")]
+    [Authenticated(AllowBeforeStartupWizard = true)]
+    public class GetChannelMappingOptions
+    {
+        [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ProviderId { get; set; }
+    }
+
+    [Route("/LiveTv/ChannelMappings")]
+    [Authenticated(AllowBeforeStartupWizard = true)]
+    public class SetChannelMapping
+    {
+        [ApiMember(Name = "Id", Description = "Provider id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ProviderId { get; set; }
+        public string TunerChannelNumber { get; set; }
+        public string ProviderChannelNumber { get; set; }
+    }
+
+    public class ChannelMappingOptions
+    {
+        public List<TunerChannelMapping> TunerChannels { get; set; }
+        public List<NameIdPair> ProviderChannels { get; set; }
+        public List<NameValuePair> Mappings { get; set; }
+        public string ProviderName { get; set; }
+    }
+
     [Route("/LiveTv/Registration", "GET")]
     [Authenticated]
     public class GetLiveTvRegistrationInfo : IReturn<MBRegistrationRecord>
@@ -525,6 +566,11 @@ namespace MediaBrowser.Api.LiveTv
             _dtoService = dtoService;
         }
 
+        public object Get(GetDefaultListingProvider request)
+        {
+            return ToOptimizedResult(new ListingsProviderInfo());
+        }
+
         public async Task<object> Get(GetSatChannnelScanResult request)
         {
             var result = await _liveTvManager.GetSatChannelScanResult(request, CancellationToken.None).ConfigureAwait(false);
@@ -539,6 +585,46 @@ namespace MediaBrowser.Api.LiveTv
             return ToOptimizedResult(result);
         }
 
+        public async Task<object> Post(SetChannelMapping request)
+        {
+            return await _liveTvManager.SetChannelMapping(request.ProviderId, request.TunerChannelNumber, request.ProviderChannelNumber).ConfigureAwait(false);
+        }
+
+        public async Task<object> Get(GetChannelMappingOptions request)
+        {
+            var config = GetConfiguration();
+
+            var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(request.ProviderId, i.Id, StringComparison.OrdinalIgnoreCase));
+
+            var listingsProviderName = _liveTvManager.ListingProviders.First(i => string.Equals(i.Type, listingsProviderInfo.Type, StringComparison.OrdinalIgnoreCase)).Name;
+
+            var tunerChannels = await _liveTvManager.GetChannelsForListingsProvider(request.ProviderId, CancellationToken.None)
+                        .ConfigureAwait(false);
+
+            var providerChannels = await _liveTvManager.GetChannelsFromListingsProviderData(request.ProviderId, CancellationToken.None)
+                     .ConfigureAwait(false);
+
+            var mappings = listingsProviderInfo.ChannelMappings.ToList();
+
+            var result = new ChannelMappingOptions
+            {
+                TunerChannels = tunerChannels.Select(i => _liveTvManager.GetTunerChannelMapping(i, mappings, providerChannels)).ToList(),
+
+                ProviderChannels = providerChannels.Select(i => new NameIdPair
+                {
+                    Name = i.Name,
+                    Id = i.Number
+
+                }).ToList(),
+
+                Mappings = mappings,
+
+                ProviderName = listingsProviderName
+            };
+
+            return ToOptimizedResult(result);
+        }
+
         public object Get(GetSatIniMappings request)
         {
             return ToOptimizedResult(_liveTvManager.GetSatIniMappings());
@@ -550,9 +636,7 @@ namespace MediaBrowser.Api.LiveTv
 
             var response = await _httpClient.Get(new HttpRequestOptions
             {
-                Url = "https://json.schedulesdirect.org/20141201/available/countries",
-                CacheLength = TimeSpan.FromDays(1),
-                CacheMode = CacheMode.Unconditional
+                Url = "https://json.schedulesdirect.org/20141201/available/countries"
 
             }).ConfigureAwait(false);
 
@@ -582,11 +666,7 @@ namespace MediaBrowser.Api.LiveTv
 
         public void Delete(DeleteListingProvider request)
         {
-            var config = GetConfiguration();
-
-            config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(request.Id, i.Id, StringComparison.OrdinalIgnoreCase)).ToList();
-
-            _config.SaveConfiguration("livetv", config);
+            _liveTvManager.DeleteListingsProvider(request.Id);
         }
 
         public async Task<object> Post(AddTunerHost request)
@@ -609,6 +689,11 @@ namespace MediaBrowser.Api.LiveTv
             return _config.GetConfiguration<LiveTvOptions>("livetv");
         }
 
+        private void UpdateConfiguration(LiveTvOptions options)
+        {
+            _config.SaveConfiguration("livetv", options);
+        }
+
         public async Task<object> Get(GetLineups request)
         {
             var info = await _liveTvManager.GetLineups(request.Type, request.Id, request.Country, request.Location).ConfigureAwait(false);
@@ -641,14 +726,14 @@ namespace MediaBrowser.Api.LiveTv
 
             var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(request.UserId);
 
-            var returnArray = _dtoService.GetBaseItemDtos(channelResult.Items, GetDtoOptions(Request), user).ToArray();
+            var returnArray = (await _dtoService.GetBaseItemDtos(channelResult.Items, GetDtoOptions(Request), user).ConfigureAwait(false)).ToArray();
 
             var result = new QueryResult<BaseItemDto>
             {
                 Items = returnArray,
                 TotalRecordCount = channelResult.TotalRecordCount
             };
-            
+
             return ToOptimizedSerializedResultUsingCache(result);
         }
 
@@ -752,7 +837,8 @@ namespace MediaBrowser.Api.LiveTv
                 Limit = request.Limit,
                 Status = request.Status,
                 SeriesTimerId = request.SeriesTimerId,
-                IsInProgress = request.IsInProgress
+                IsInProgress = request.IsInProgress,
+                EnableTotalRecordCount = request.EnableTotalRecordCount
 
             }, options, CancellationToken.None).ConfigureAwait(false);
 
@@ -783,7 +869,8 @@ namespace MediaBrowser.Api.LiveTv
             var result = await _liveTvManager.GetTimers(new TimerQuery
             {
                 ChannelId = request.ChannelId,
-                SeriesTimerId = request.SeriesTimerId
+                SeriesTimerId = request.SeriesTimerId,
+                IsActive = request.IsActive
 
             }, CancellationToken.None).ConfigureAwait(false);
 

+ 116 - 143
MediaBrowser.Api/Movies/MoviesService.cs

@@ -14,6 +14,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
+using MediaBrowser.Controller.LiveTv;
 
 namespace MediaBrowser.Api.Movies
 {
@@ -112,16 +113,14 @@ namespace MediaBrowser.Api.Movies
         /// <returns>System.Object.</returns>
         public async Task<object> Get(GetSimilarMovies request)
         {
-            var result = await GetSimilarItemsResult(
-                request, SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
+            var result = await GetSimilarItemsResult(request).ConfigureAwait(false);
 
             return ToOptimizedSerializedResultUsingCache(result);
         }
 
         public async Task<object> Get(GetSimilarTrailers request)
         {
-            var result = await GetSimilarItemsResult(
-                request, SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
+            var result = await GetSimilarItemsResult(request).ConfigureAwait(false);
 
             return ToOptimizedSerializedResultUsingCache(result);
         }
@@ -130,148 +129,93 @@ namespace MediaBrowser.Api.Movies
         {
             var user = _userManager.GetUserById(request.UserId);
 
-            var query = new InternalItemsQuery(user)
-            {
-                IncludeItemTypes = new[] { typeof(Movie).Name }
-            };
-
-            if (user.Configuration.IncludeTrailersInSuggestions)
-            {
-                var includeList = query.IncludeItemTypes.ToList();
-                includeList.Add(typeof(Trailer).Name);
-                query.IncludeItemTypes = includeList.ToArray();
-            }
-
-            var parentIds = string.IsNullOrWhiteSpace(request.ParentId) ? new string[] { } : new[] { request.ParentId };
-            var movies = _libraryManager.GetItemList(query, parentIds)
-                .OrderBy(i => (int)i.SourceType);
-
-            var listEligibleForCategories = new List<BaseItem>();
-            var listEligibleForSuggestion = new List<BaseItem>();
-
-            var list = movies.ToList();
-
-            listEligibleForCategories.AddRange(list);
-            listEligibleForSuggestion.AddRange(list);
-
-            listEligibleForCategories = listEligibleForCategories
-                // Exclude trailers from the suggestion categories
-                .Where(i => i is Movie)
-                .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
-                .DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString(), StringComparer.OrdinalIgnoreCase)
-                .ToList();
-
-            listEligibleForSuggestion = listEligibleForSuggestion
-                .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
-                .DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString(), StringComparer.OrdinalIgnoreCase)
-                .ToList();
-
             var dtoOptions = GetDtoOptions(request);
 
             dtoOptions.Fields = request.GetItemFields().ToList();
 
-            var result = GetRecommendationCategories(user, listEligibleForCategories, listEligibleForSuggestion, request.CategoryLimit, request.ItemLimit, dtoOptions);
+            var result = GetRecommendationCategories(user, request.ParentId, request.CategoryLimit, request.ItemLimit, dtoOptions);
 
             return ToOptimizedResult(result);
         }
 
-        private async Task<ItemsResult> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
+        private async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
         {
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
 
             var item = string.IsNullOrEmpty(request.Id) ?
                 (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
                 _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
-            
-            var query = new InternalItemsQuery(user)
-            {
-                IncludeItemTypes = new[] { typeof(Movie).Name }
-            };
-
-            if (user == null || user.Configuration.IncludeTrailersInSuggestions)
-            {
-                var includeList = query.IncludeItemTypes.ToList();
-                includeList.Add(typeof(Trailer).Name);
-                query.IncludeItemTypes = includeList.ToArray();
-            }
-
-            var list = _libraryManager.GetItemList(query)
-                .OrderBy(i => (int)i.SourceType)
-                .DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N"))
-                .ToList();
 
-            if (item is Video)
+            var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
             {
-                var imdbId = item.GetProviderId(MetadataProviders.Imdb);
-
-                // Use imdb id to try to filter duplicates of the same item
-                if (!string.IsNullOrWhiteSpace(imdbId))
+                Limit = request.Limit,
+                IncludeItemTypes = new[]
                 {
-                    list = list
-                        .Where(i => !string.Equals(imdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase))
-                        .ToList();
-                }
-            }
-
-            var items = SimilarItemsHelper.GetSimilaritems(item, _libraryManager, list, getSimilarityScore).ToList();
-
-            IEnumerable<BaseItem> returnItems = items;
+                        typeof(Movie).Name,
+                        typeof(Trailer).Name,
+                        typeof(LiveTvProgram).Name
+                },
+                IsMovie = true,
+                SimilarTo = item,
+                EnableGroupByMetadataKey = true
 
-            if (request.Limit.HasValue)
-            {
-                returnItems = returnItems.Take(request.Limit.Value);
-            }
+            }).ToList();
 
             var dtoOptions = GetDtoOptions(request);
 
-            var result = new ItemsResult
+            var result = new QueryResult<BaseItemDto>
             {
-                Items = _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user).ToArray(),
+                Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(),
 
-                TotalRecordCount = items.Count
+                TotalRecordCount = itemsResult.Count
             };
 
             return result;
         }
 
-        private IEnumerable<RecommendationDto> GetRecommendationCategories(User user, List<BaseItem> allMoviesForCategories, List<BaseItem> allMovies, int categoryLimit, int itemLimit, DtoOptions dtoOptions)
+        private IEnumerable<RecommendationDto> GetRecommendationCategories(User user, string parentId, int categoryLimit, int itemLimit, DtoOptions dtoOptions)
         {
             var categories = new List<RecommendationDto>();
 
-            var recentlyPlayedMovies = allMoviesForCategories
-                .Select(i =>
-                {
-                    var userdata = _userDataRepository.GetUserData(user, i);
-                    return new Tuple<BaseItem, bool, DateTime>(i, userdata.Played, userdata.LastPlayedDate ?? DateTime.MinValue);
-                })
-                .Where(i => i.Item2)
-                .OrderByDescending(i => i.Item3)
-                .Select(i => i.Item1)
-                .ToList();
+            var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? (Guid?)null : new Guid(parentId);
 
-            var excludeFromLiked = recentlyPlayedMovies.Take(10);
-            var likedMovies = allMovies
-                .Select(i =>
+            var query = new InternalItemsQuery(user)
+            {
+                IncludeItemTypes = new[]
                 {
-                    var score = 0;
-                    var userData = _userDataRepository.GetUserData(user, i);
+                    typeof(Movie).Name,
+                    //typeof(Trailer).Name,
+                    //typeof(LiveTvProgram).Name
+                },
+                // IsMovie = true
+                SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.Random },
+                SortOrder = SortOrder.Descending,
+                Limit = 7,
+                ParentId = parentIdGuid,
+                Recursive = true
+            };
 
-                    if (userData.IsFavorite)
-                    {
-                        score = 2;
-                    }
-                    else
-                    {
-                        score = userData.Likes.HasValue ? userData.Likes.Value ? 1 : -1 : 0;
-                    }
+            var recentlyPlayedMovies = _libraryManager.GetItemList(query).ToList();
 
-                    return new Tuple<BaseItem, int>(i, score);
-                })
-                .OrderByDescending(i => i.Item2)
-                .ThenBy(i => Guid.NewGuid())
-                .Where(i => i.Item2 > 0)
-                .Select(i => i.Item1)
-                .Where(i => !excludeFromLiked.Contains(i));
+            var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user)
+            {
+                IncludeItemTypes = new[]
+                {
+                   typeof(Movie).Name,
+                   typeof(Trailer).Name,
+                   typeof(LiveTvProgram).Name
+                },
+                IsMovie = true,
+                SortBy = new[] { ItemSortBy.Random },
+                SortOrder = SortOrder.Descending,
+                Limit = 10,
+                IsFavoriteOrLiked = true,
+                ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id.ToString("N")).ToArray(),
+                EnableGroupByMetadataKey = true,
+                ParentId = parentIdGuid,
+                Recursive = true
+
+            }).ToList();
 
             var mostRecentMovies = recentlyPlayedMovies.Take(6).ToList();
             // Get recently played directors
@@ -284,11 +228,11 @@ namespace MediaBrowser.Api.Movies
                 .OrderBy(i => Guid.NewGuid())
                 .ToList();
 
-            var similarToRecentlyPlayed = GetSimilarTo(user, allMovies, recentlyPlayedMovies.Take(7).OrderBy(i => Guid.NewGuid()), itemLimit, dtoOptions, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator();
-            var similarToLiked = GetSimilarTo(user, allMovies, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedItem).GetEnumerator();
+            var similarToRecentlyPlayed = GetSimilarTo(user, recentlyPlayedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator();
+            var similarToLiked = GetSimilarTo(user, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedItem).GetEnumerator();
 
-            var hasDirectorFromRecentlyPlayed = GetWithDirector(user, allMovies, recentDirectors, itemLimit, dtoOptions, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator();
-            var hasActorFromRecentlyPlayed = GetWithActor(user, allMovies, recentActors, itemLimit, dtoOptions, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator();
+            var hasDirectorFromRecentlyPlayed = GetWithDirector(user, recentDirectors, itemLimit, dtoOptions, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator();
+            var hasActorFromRecentlyPlayed = GetWithActor(user, recentActors, itemLimit, dtoOptions, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator();
 
             var categoryTypes = new List<IEnumerator<RecommendationDto>>
             {
@@ -331,44 +275,63 @@ namespace MediaBrowser.Api.Movies
             return categories.OrderBy(i => i.RecommendationType).ThenBy(i => Guid.NewGuid());
         }
 
-        private IEnumerable<RecommendationDto> GetWithDirector(User user, List<BaseItem> allMovies, IEnumerable<string> directors, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
+        private IEnumerable<RecommendationDto> GetWithDirector(User user, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
         {
-            var userId = user.Id;
-
-            foreach (var director in directors)
+            foreach (var name in names)
             {
-                var items = allMovies
-                    .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();
+                var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
+                {
+                    Person = name,
+                    // Account for duplicates by imdb id, since the database doesn't support this yet
+                    Limit = itemLimit + 2,
+                    PersonTypes = new[] { PersonType.Director },
+                    IncludeItemTypes = new[]
+                    {
+                        typeof(Movie).Name,
+                        typeof(Trailer).Name,
+                        typeof(LiveTvProgram).Name
+                    },
+                    IsMovie = true,
+                    EnableGroupByMetadataKey = true
+
+                }).DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N"))
+                .Take(itemLimit)
+                .ToList();
 
                 if (items.Count > 0)
                 {
                     yield return new RecommendationDto
                     {
-                        BaselineItemName = director,
-                        CategoryId = director.GetMD5().ToString("N"),
+                        BaselineItemName = name,
+                        CategoryId = name.GetMD5().ToString("N"),
                         RecommendationType = type,
-                        Items = _dtoService.GetBaseItemDtos(items, dtoOptions, user).ToArray()
+                        Items = _dtoService.GetBaseItemDtos(items, dtoOptions, user).Result.ToArray()
                     };
                 }
             }
         }
 
-        private IEnumerable<RecommendationDto> GetWithActor(User user, List<BaseItem> allMovies, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
+        private IEnumerable<RecommendationDto> GetWithActor(User user, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
         {
             foreach (var name in names)
             {
-                var itemsWithActor = _libraryManager.GetItemIds(new InternalItemsQuery(user)
+                var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
                 {
-                    Person = name
-
-                });
-
-                var items = allMovies
-                    .Where(i => itemsWithActor.Contains(i.Id))
-                    .Take(itemLimit)
-                    .ToList();
+                    Person = name,
+                    // Account for duplicates by imdb id, since the database doesn't support this yet
+                    Limit = itemLimit + 2,
+                    IncludeItemTypes = new[]
+                    {
+                        typeof(Movie).Name,
+                        typeof(Trailer).Name,
+                        typeof(LiveTvProgram).Name
+                    },
+                    IsMovie = true,
+                    EnableGroupByMetadataKey = true
+
+                }).DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N"))
+                .Take(itemLimit)
+                .ToList();
 
                 if (items.Count > 0)
                 {
@@ -377,20 +340,30 @@ namespace MediaBrowser.Api.Movies
                         BaselineItemName = name,
                         CategoryId = name.GetMD5().ToString("N"),
                         RecommendationType = type,
-                        Items = _dtoService.GetBaseItemDtos(items, dtoOptions, user).ToArray()
+                        Items = _dtoService.GetBaseItemDtos(items, dtoOptions, user).Result.ToArray()
                     };
                 }
             }
         }
 
-        private IEnumerable<RecommendationDto> GetSimilarTo(User user, List<BaseItem> allMovies, IEnumerable<BaseItem> baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
+        private IEnumerable<RecommendationDto> GetSimilarTo(User user, List<BaseItem> baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
         {
             foreach (var item in baselineItems)
             {
-                var similar = SimilarItemsHelper
-                    .GetSimilaritems(item, _libraryManager, allMovies, SimilarItemsHelper.GetSimiliarityScore)
-                    .Take(itemLimit)
-                    .ToList();
+                var similar = _libraryManager.GetItemList(new InternalItemsQuery(user)
+                {
+                    Limit = itemLimit,
+                    IncludeItemTypes = new[]
+                    {
+                        typeof(Movie).Name,
+                        typeof(Trailer).Name,
+                        typeof(LiveTvProgram).Name
+                    },
+                    IsMovie = true,
+                    SimilarTo = item,
+                    EnableGroupByMetadataKey = true
+
+                }).ToList();
 
                 if (similar.Count > 0)
                 {
@@ -399,7 +372,7 @@ namespace MediaBrowser.Api.Movies
                         BaselineItemName = item.Name,
                         CategoryId = item.Id.ToString("N"),
                         RecommendationType = type,
-                        Items = _dtoService.GetBaseItemDtos(similar, dtoOptions, user).ToArray()
+                        Items = _dtoService.GetBaseItemDtos(similar, dtoOptions, user).Result.ToArray()
                     };
                 }
             }

+ 1 - 1
MediaBrowser.Api/Movies/TrailersService.cs

@@ -58,7 +58,7 @@ namespace MediaBrowser.Api.Movies
 
             getItems.IncludeItemTypes = "Trailer";
 
-            return new ItemsService(_userManager, _libraryManager, _userDataRepository, _localizationManager, _dtoService, _collectionManager)
+            return new ItemsService(_userManager, _libraryManager, _localizationManager, _dtoService)
             {
                 AuthorizationContext = AuthorizationContext,
                 Logger = Logger,

+ 7 - 6
MediaBrowser.Api/Music/AlbumsService.cs

@@ -8,6 +8,7 @@ using ServiceStack;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
 
 namespace MediaBrowser.Api.Music
 {
@@ -49,18 +50,18 @@ namespace MediaBrowser.Api.Music
             _dtoService = dtoService;
         }
 
-        public object Get(GetSimilarArtists request)
+        public async Task<object> Get(GetSimilarArtists request)
         {
             var dtoOptions = GetDtoOptions(request);
 
-            var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
+            var result = await SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
                 _itemRepo,
                 _libraryManager,
                 _userDataRepository,
                 _dtoService,
                 Logger,
                 request, new[] { typeof(MusicArtist) },
-                SimilarItemsHelper.GetSimiliarityScore);
+                SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
 
             return ToOptimizedSerializedResultUsingCache(result);
         }
@@ -70,18 +71,18 @@ namespace MediaBrowser.Api.Music
         /// </summary>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
-        public object Get(GetSimilarAlbums request)
+        public async Task<object> Get(GetSimilarAlbums request)
         {
             var dtoOptions = GetDtoOptions(request);
 
-            var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
+            var result = await SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
                 _itemRepo,
                 _libraryManager,
                 _userDataRepository,
                 _dtoService,
                 Logger,
                 request, new[] { typeof(MusicAlbum) },
-                GetAlbumSimilarityScore);
+                GetAlbumSimilarityScore).ConfigureAwait(false);
 
             return ToOptimizedSerializedResultUsingCache(result);
         }

+ 11 - 10
MediaBrowser.Api/Music/InstantMixService.cs

@@ -8,6 +8,7 @@ using MediaBrowser.Model.Querying;
 using ServiceStack;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
 
 namespace MediaBrowser.Api.Music
 {
@@ -76,7 +77,7 @@ namespace MediaBrowser.Api.Music
             _libraryManager = libraryManager;
         }
 
-        public object Get(GetInstantMixFromItem request)
+        public Task<object> Get(GetInstantMixFromItem request)
         {
             var item = _libraryManager.GetItemById(request.Id);
 
@@ -87,7 +88,7 @@ namespace MediaBrowser.Api.Music
             return GetResult(items, user, request);
         }
 
-        public object Get(GetInstantMixFromArtistId request)
+        public Task<object> Get(GetInstantMixFromArtistId request)
         {
             var item = _libraryManager.GetItemById(request.Id);
 
@@ -98,7 +99,7 @@ namespace MediaBrowser.Api.Music
             return GetResult(items, user, request);
         }
 
-        public object Get(GetInstantMixFromMusicGenreId request)
+        public Task<object> Get(GetInstantMixFromMusicGenreId request)
         {
             var item = _libraryManager.GetItemById(request.Id);
 
@@ -109,7 +110,7 @@ namespace MediaBrowser.Api.Music
             return GetResult(items, user, request);
         }
 
-        public object Get(GetInstantMixFromSong request)
+        public Task<object> Get(GetInstantMixFromSong request)
         {
             var item = _libraryManager.GetItemById(request.Id);
 
@@ -120,7 +121,7 @@ namespace MediaBrowser.Api.Music
             return GetResult(items, user, request);
         }
 
-        public object Get(GetInstantMixFromAlbum request)
+        public Task<object> Get(GetInstantMixFromAlbum request)
         {
             var album = _libraryManager.GetItemById(request.Id);
 
@@ -131,7 +132,7 @@ namespace MediaBrowser.Api.Music
             return GetResult(items, user, request);
         }
 
-        public object Get(GetInstantMixFromPlaylist request)
+        public Task<object> Get(GetInstantMixFromPlaylist request)
         {
             var playlist = (Playlist)_libraryManager.GetItemById(request.Id);
 
@@ -142,7 +143,7 @@ namespace MediaBrowser.Api.Music
             return GetResult(items, user, request);
         }
 
-        public object Get(GetInstantMixFromMusicGenre request)
+        public Task<object> Get(GetInstantMixFromMusicGenre request)
         {
             var user = _userManager.GetUserById(request.UserId);
 
@@ -151,7 +152,7 @@ namespace MediaBrowser.Api.Music
             return GetResult(items, user, request);
         }
 
-        public object Get(GetInstantMixFromArtist request)
+        public Task<object> Get(GetInstantMixFromArtist request)
         {
             var user = _userManager.GetUserById(request.UserId);
             var artist = _libraryManager.GetArtist(request.Name);
@@ -161,7 +162,7 @@ namespace MediaBrowser.Api.Music
             return GetResult(items, user, request);
         }
 
-        private object GetResult(IEnumerable<Audio> items, User user, BaseGetSimilarItems request)
+        private async Task<object> GetResult(IEnumerable<Audio> items, User user, BaseGetSimilarItems request)
         {
             var list = items.ToList();
 
@@ -172,7 +173,7 @@ namespace MediaBrowser.Api.Music
 
             var dtoOptions = GetDtoOptions(request);
 
-            result.Items = _dtoService.GetBaseItemDtos(list.Take(request.Limit ?? list.Count), dtoOptions, user).ToArray();
+            result.Items = (await _dtoService.GetBaseItemDtos(list.Take(request.Limit ?? list.Count), dtoOptions, user).ConfigureAwait(false)).ToArray();
 
             return ToOptimizedResult(result);
         }

+ 7 - 5
MediaBrowser.Api/PackageReviewService.cs

@@ -112,7 +112,7 @@ namespace MediaBrowser.Api
             _appHost = appHost;
         }
 
-        public object Get(ReviewRequest request)
+        public async Task<object> Get(ReviewRequest request)
         {
             var parms = "?id=" + request.Id;
 
@@ -133,11 +133,13 @@ namespace MediaBrowser.Api
                 parms += "&title=true";
             }
 
-            var result = _httpClient.Get(MbAdminUrl + "/service/packageReview/retrieve" + parms, CancellationToken.None).Result;
-
-            var reviews = _serializer.DeserializeFromStream<List<PackageReviewInfo>>(result);
+            using (var result = await _httpClient.Get(MbAdminUrl + "/service/packageReview/retrieve" + parms, CancellationToken.None)
+                            .ConfigureAwait(false))
+            {
+                var reviews = _serializer.DeserializeFromStream<List<PackageReviewInfo>>(result);
 
-            return ToOptimizedResult(reviews);
+                return ToOptimizedResult(reviews);
+            }
         }
 
         public void Post(CreateReviewRequest request)

+ 51 - 28
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -286,11 +286,19 @@ namespace MediaBrowser.Api.Playback
 
         protected string GetH264Encoder(StreamState state)
         {
-            if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
+                string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
             {
-
                 return "h264_qsv";
+            }
 
+            if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "libnvenc", StringComparison.OrdinalIgnoreCase))
+            {
+                return "libnvenc";
+            }
+            if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
+            {
+                return "h264_omx";
             }
 
             return "libx264";
@@ -395,15 +403,18 @@ namespace MediaBrowser.Api.Playback
 
             if (!string.IsNullOrEmpty(state.VideoRequest.Profile))
             {
-                param += " -profile:v " + state.VideoRequest.Profile;
+                if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
+                {
+                    // not supported by h264_omx
+                    param += " -profile:v " + state.VideoRequest.Profile;
+                }
             }
 
             if (!string.IsNullOrEmpty(state.VideoRequest.Level))
             {
-                var h264Encoder = GetH264Encoder(state);
-
                 // h264_qsv and libnvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
-                if (String.Equals(h264Encoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || String.Equals(h264Encoder, "libnvenc", StringComparison.OrdinalIgnoreCase))
+                if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
+                    string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase))
                 {
                     switch (state.VideoRequest.Level)
                     {
@@ -438,16 +449,21 @@ namespace MediaBrowser.Api.Playback
                             param += " -level " + state.VideoRequest.Level;
                             break;
                     }
-
-                    return param;
                 }
-                else
+                else if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
                 {
                     param += " -level " + state.VideoRequest.Level;
                 }
             }
 
-            return "-pix_fmt yuv420p " + param;
+            if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
+                !string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
+                !string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase))
+            {
+                param = "-pix_fmt yuv420p " + param;
+            }
+
+            return param;
         }
 
         protected string GetAudioFilterParam(StreamState state, bool isHls)
@@ -563,14 +579,6 @@ namespace MediaBrowser.Api.Playback
                 filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(ih\\,{0})", maxHeightParam));
             }
 
-            if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
-            {
-                if (filters.Count > 1)
-                {
-                    //filters[filters.Count - 1] += ":flags=fast_bilinear";
-                }
-            }
-
             var output = string.Empty;
 
             if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode)
@@ -846,7 +854,7 @@ namespace MediaBrowser.Api.Playback
                             if (MediaEncoder.SupportsDecoder("h264_qsv"))
                             {
                                 // Seeing stalls and failures with decoding. Not worth it compared to encoding.
-                                //return "-c:v h264_qsv ";
+                                return "-c:v h264_qsv ";
                             }
                             break;
                         case "mpeg2video":
@@ -980,11 +988,6 @@ namespace MediaBrowser.Api.Playback
             var transcodingId = Guid.NewGuid().ToString("N");
             var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
 
-            if (ApiEntryPoint.Instance.GetEncodingOptions().EnableDebugLogging)
-            {
-                commandLineArgs = "-loglevel debug " + commandLineArgs;
-            }
-
             var process = new Process
             {
                 StartInfo = new ProcessStartInfo
@@ -1212,7 +1215,7 @@ namespace MediaBrowser.Api.Playback
             }
         }
 
-        private int? GetVideoBitrateParamValue(VideoStreamRequest request, MediaStream videoStream)
+        private int? GetVideoBitrateParamValue(VideoStreamRequest request, MediaStream videoStream, string outputVideoCodec)
         {
             var bitrate = request.VideoBitRate;
 
@@ -1237,6 +1240,18 @@ namespace MediaBrowser.Api.Playback
                 }
             }
 
+            if (bitrate.HasValue)
+            {
+                var inputVideoCodec = videoStream == null ? null : videoStream.Codec;
+                bitrate = ResolutionNormalizer.ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
+
+                // If a max bitrate was requested, don't let the scaled bitrate exceed it
+                if (request.VideoBitRate.HasValue)
+                {
+                    bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
+                }
+            }
+
             return bitrate;
         }
 
@@ -1518,6 +1533,13 @@ namespace MediaBrowser.Api.Playback
                     }
                 }
                 else if (i == 25)
+                {
+                    if (videoRequest != null)
+                    {
+                        videoRequest.ForceLiveStream = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+                    }
+                }
+                else if (i == 26)
                 {
                     if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
                     {
@@ -1528,7 +1550,7 @@ namespace MediaBrowser.Api.Playback
                         }
                     }
                 }
-                else if (i == 26)
+                else if (i == 27)
                 {
                     request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
                 }
@@ -1636,7 +1658,8 @@ namespace MediaBrowser.Api.Playback
             if (!string.IsNullOrWhiteSpace(request.AudioCodec))
             {
                 state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
-                state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault();
+                state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToAudioCodec(i))
+                    ?? state.SupportedAudioCodecs.FirstOrDefault();
             }
 
             var item = LibraryManager.GetItemById(request.Id);
@@ -1690,7 +1713,7 @@ namespace MediaBrowser.Api.Playback
             if (videoRequest != null)
             {
                 state.OutputVideoCodec = state.VideoRequest.VideoCodec;
-                state.OutputVideoBitrate = GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream);
+                state.OutputVideoBitrate = GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
 
                 if (state.OutputVideoBitrate.HasValue)
                 {

+ 2 - 2
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -63,9 +63,9 @@ namespace MediaBrowser.Api.Playback.Hls
         /// <param name="request">The request.</param>
         /// <param name="isLive">if set to <c>true</c> [is live].</param>
         /// <returns>System.Object.</returns>
-        protected object ProcessRequest(StreamRequest request, bool isLive)
+        protected async Task<object> ProcessRequest(StreamRequest request, bool isLive)
         {
-            return ProcessRequestAsync(request, isLive).Result;
+            return await ProcessRequestAsync(request, isLive).ConfigureAwait(false);
         }
 
         /// <summary>

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

@@ -475,7 +475,7 @@ namespace MediaBrowser.Api.Playback.Hls
                         ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob);
                     }
                 }
-            });
+            }).Result;
         }
 
         private async Task<object> GetMasterPlaylistInternal(StreamRequest request, string method)

+ 5 - 4
MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs

@@ -5,6 +5,7 @@ using ServiceStack;
 using System;
 using System.IO;
 using System.Linq;
+using System.Threading.Tasks;
 
 namespace MediaBrowser.Api.Playback.Hls
 {
@@ -89,7 +90,7 @@ namespace MediaBrowser.Api.Playback.Hls
             _config = config;
         }
 
-        public object Get(GetHlsPlaylistLegacy request)
+        public Task<object> Get(GetHlsPlaylistLegacy request)
         {
             var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
             file = Path.Combine(_appPaths.TranscodingTempPath, file);
@@ -107,7 +108,7 @@ namespace MediaBrowser.Api.Playback.Hls
         /// </summary>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
-        public object Get(GetHlsVideoSegmentLegacy request)
+        public Task<object> Get(GetHlsVideoSegmentLegacy request)
         {
             var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
             file = Path.Combine(_config.ApplicationPaths.TranscodingTempPath, file);
@@ -131,10 +132,10 @@ namespace MediaBrowser.Api.Playback.Hls
             var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
             file = Path.Combine(_appPaths.TranscodingTempPath, file);
 
-            return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite);
+            return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite).Result;
         }
 
-        private object GetFileResult(string path, string playlistPath)
+        private Task<object> GetFileResult(string path, string playlistPath)
         {
             var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
 

+ 3 - 2
MediaBrowser.Api/Playback/Progressive/AudioService.cs

@@ -9,6 +9,7 @@ using MediaBrowser.Model.IO;
 using MediaBrowser.Model.Serialization;
 using ServiceStack;
 using System.Collections.Generic;
+using System.Threading.Tasks;
 using CommonIO;
 
 namespace MediaBrowser.Api.Playback.Progressive
@@ -40,7 +41,7 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// </summary>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
-        public object Get(GetAudioStream request)
+        public Task<object> Get(GetAudioStream request)
         {
             return ProcessRequest(request, false);
         }
@@ -50,7 +51,7 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// </summary>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
-        public object Head(GetAudioStream request)
+        public Task<object> Head(GetAudioStream request)
         {
             return ProcessRequest(request, true);
         }

+ 15 - 13
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -113,11 +113,11 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// <param name="request">The request.</param>
         /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
         /// <returns>Task.</returns>
-        protected object ProcessRequest(StreamRequest request, bool isHeadRequest)
+        protected async Task<object> ProcessRequest(StreamRequest request, bool isHeadRequest)
         {
             var cancellationTokenSource = new CancellationTokenSource();
 
-            var state = GetState(request, cancellationTokenSource.Token).Result;
+            var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false);
 
             var responseHeaders = new Dictionary<string, string>();
 
@@ -128,7 +128,8 @@ namespace MediaBrowser.Api.Playback.Progressive
 
                 using (state)
                 {
-                    return GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).Result;
+                    return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource)
+                                .ConfigureAwait(false);
                 }
             }
 
@@ -138,7 +139,7 @@ namespace MediaBrowser.Api.Playback.Progressive
             }
 
             var outputPath = state.OutputFilePath;
-			var outputPathExists = FileSystem.FileExists(outputPath);
+            var outputPathExists = FileSystem.FileExists(outputPath);
 
             var isTranscodeCached = outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive);
 
@@ -151,13 +152,13 @@ namespace MediaBrowser.Api.Playback.Progressive
 
                 using (state)
                 {
-                    return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
+                    return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
                     {
                         ResponseHeaders = responseHeaders,
                         ContentType = contentType,
                         IsHeadRequest = isHeadRequest,
                         Path = state.MediaPath
-                    });
+                    }).ConfigureAwait(false);
                 }
             }
 
@@ -168,13 +169,13 @@ namespace MediaBrowser.Api.Playback.Progressive
 
                 try
                 {
-                    return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
+                    return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
                     {
                         ResponseHeaders = responseHeaders,
                         ContentType = contentType,
                         IsHeadRequest = isHeadRequest,
                         Path = outputPath
-                    });
+                    }).ConfigureAwait(false);
                 }
                 finally
                 {
@@ -185,7 +186,8 @@ namespace MediaBrowser.Api.Playback.Progressive
             // Need to start ffmpeg
             try
             {
-                return GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).Result;
+                return await GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource)
+                            .ConfigureAwait(false);
             }
             catch
             {
@@ -229,7 +231,7 @@ namespace MediaBrowser.Api.Playback.Progressive
 
             if (trySupportSeek)
             {
-                foreach (var name in new[] {"Content-Range", "Accept-Ranges"})
+                foreach (var name in new[] { "Content-Range", "Accept-Ranges" })
                 {
                     var val = response.Headers[name];
                     if (!string.IsNullOrWhiteSpace(val))
@@ -242,12 +244,12 @@ namespace MediaBrowser.Api.Playback.Progressive
             {
                 responseHeaders["Accept-Ranges"] = "none";
             }
-            
+
             if (response.ContentLength.HasValue)
             {
                 responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture);
             }
-            
+
             if (isHeadRequest)
             {
                 using (response)
@@ -324,7 +326,7 @@ namespace MediaBrowser.Api.Playback.Progressive
             {
                 TranscodingJob job;
 
-				if (!FileSystem.FileExists(outputPath))
+                if (!FileSystem.FileExists(outputPath))
                 {
                     job = await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false);
                 }

+ 3 - 2
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -10,6 +10,7 @@ using MediaBrowser.Model.Serialization;
 using ServiceStack;
 using System;
 using System.IO;
+using System.Threading.Tasks;
 using CommonIO;
 using MediaBrowser.Model.Dlna;
 
@@ -76,7 +77,7 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// </summary>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
-        public object Get(GetVideoStream request)
+        public Task<object> Get(GetVideoStream request)
         {
             return ProcessRequest(request, false);
         }
@@ -86,7 +87,7 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// </summary>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
-        public object Head(GetVideoStream request)
+        public Task<object> Head(GetVideoStream request)
         {
             return ProcessRequest(request, true);
         }

+ 1 - 1
MediaBrowser.Api/Playback/StreamState.cs

@@ -96,7 +96,7 @@ namespace MediaBrowser.Api.Playback
         public long? InputFileSize { get; set; }
 
         public string OutputAudioSync = "1";
-        public string OutputVideoSync = "vfr";
+        public string OutputVideoSync = "-1";
 
         public List<string> SupportedAudioCodecs { get; set; }
 

+ 2 - 2
MediaBrowser.Api/PlaylistService.cs

@@ -157,7 +157,7 @@ namespace MediaBrowser.Api
             Task.WaitAll(task);
         }
 
-        public object Get(GetPlaylistItems request)
+        public async Task<object> Get(GetPlaylistItems request)
         {
             var playlist = (Playlist)_libraryManager.GetItemById(request.Id);
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
@@ -178,7 +178,7 @@ namespace MediaBrowser.Api
 
             var dtoOptions = GetDtoOptions(request);
 
-            var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2), dtoOptions, user)
+            var dtos = (await _dtoService.GetBaseItemDtos(items.Select(i => i.Item2), dtoOptions, user).ConfigureAwait(false))
                    .ToArray();
 
             var index = 0;

+ 0 - 1
MediaBrowser.Api/Reports/ReportsService.cs

@@ -213,7 +213,6 @@ namespace MediaBrowser.Api.Reports
                 NameStartsWith = request.NameStartsWith,
                 NameStartsWithOrGreater = request.NameStartsWithOrGreater,
                 HasImdbId = request.HasImdbId,
-                IsYearMismatched = request.IsYearMismatched,
                 IsPlaceHolder = request.IsPlaceHolder,
                 IsLocked = request.IsLocked,
                 IsInBoxSet = request.IsInBoxSet,

+ 9 - 19
MediaBrowser.Api/SimilarItemsHelper.cs

@@ -9,6 +9,8 @@ using ServiceStack;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Dto;
 
 namespace MediaBrowser.Api
 {
@@ -54,7 +56,7 @@ namespace MediaBrowser.Api
     /// </summary>
     public static class SimilarItemsHelper
     {
-        internal static ItemsResult GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, ILogger logger, BaseGetSimilarItemsFromItem request, Type[] includeTypes, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
+        internal static async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(DtoOptions dtoOptions, IUserManager userManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserDataManager userDataRepository, IDtoService dtoService, ILogger logger, BaseGetSimilarItemsFromItem request, Type[] includeTypes, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
         {
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? userManager.GetUserById(request.UserId) : null;
 
@@ -80,14 +82,14 @@ namespace MediaBrowser.Api
                 returnItems = returnItems.Take(request.Limit.Value);
             }
 
-            var result = new ItemsResult
+            var dtos = await dtoService.GetBaseItemDtos(returnItems, dtoOptions, user).ConfigureAwait(false);
+
+            return new QueryResult<BaseItemDto>
             {
-                Items = dtoService.GetBaseItemDtos(returnItems, dtoOptions, user).ToArray(),
+                Items = dtos.ToArray(),
 
                 TotalRecordCount = items.Count
             };
-
-            return result;
         }
 
         /// <summary>
@@ -116,24 +118,12 @@ namespace MediaBrowser.Api
 
         private static IEnumerable<string> GetTags(BaseItem item)
         {
-            var hasTags = item as IHasTags;
-            if (hasTags != null)
-            {
-                return hasTags.Tags;
-            }
-
-            return new List<string>();
+            return item.Tags;
         }
 
         private static IEnumerable<string> GetKeywords(BaseItem item)
         {
-            var hasTags = item as IHasKeywords;
-            if (hasTags != null)
-            {
-                return hasTags.Keywords;
-            }
-
-            return new List<string>();
+            return item.Keywords;
         }
 
         /// <summary>

+ 11 - 6
MediaBrowser.Api/StartupWizardService.cs

@@ -11,6 +11,7 @@ using ServiceStack;
 using System;
 using System.Linq;
 using System.Threading.Tasks;
+using MediaBrowser.Controller.MediaEncoding;
 
 namespace MediaBrowser.Api
 {
@@ -52,14 +53,16 @@ namespace MediaBrowser.Api
         private readonly IUserManager _userManager;
         private readonly IConnectManager _connectManager;
         private readonly ILiveTvManager _liveTvManager;
+        private readonly IMediaEncoder _mediaEncoder;
 
-        public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, ILiveTvManager liveTvManager)
+        public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, ILiveTvManager liveTvManager, IMediaEncoder mediaEncoder)
         {
             _config = config;
             _appHost = appHost;
             _userManager = userManager;
             _connectManager = connectManager;
             _liveTvManager = liveTvManager;
+            _mediaEncoder = mediaEncoder;
         }
 
         public void Post(ReportStartupWizardComplete request)
@@ -69,13 +72,14 @@ namespace MediaBrowser.Api
             _config.SaveConfiguration();
         }
 
-        public object Get(GetStartupInfo request)
+        public async Task<object> Get(GetStartupInfo request)
         {
-            var info = _appHost.GetSystemInfo();
+            var info = await _appHost.GetSystemInfo().ConfigureAwait(false);
 
             return new StartupInfo
             {
-                SupportsRunningAsService = info.SupportsRunningAsService
+                SupportsRunningAsService = info.SupportsRunningAsService,
+                HasMediaEncoder = !string.IsNullOrWhiteSpace(_mediaEncoder.EncoderPath)
             };
         }
 
@@ -111,10 +115,10 @@ namespace MediaBrowser.Api
         {
             config.EnableLocalizedGuids = true;
             config.EnableCustomPathSubFolders = true;
-            config.EnableDateLastRefresh = true;
             config.EnableStandaloneMusicKeys = true;
             config.EnableCaseSensitiveItemIds = true;
-            config.SchemaVersion = 79;
+            config.EnableFolderView = true;
+            config.SchemaVersion = 97;
         }
 
         public void Post(UpdateStartupConfiguration request)
@@ -231,6 +235,7 @@ namespace MediaBrowser.Api
     public class StartupInfo
     {
         public bool SupportsRunningAsService { get; set; }
+        public bool HasMediaEncoder { get; set; }
     }
 
     public class StartupUser

+ 26 - 10
MediaBrowser.Api/Subtitles/SubtitleService.cs

@@ -98,6 +98,10 @@ namespace MediaBrowser.Api.Subtitles
 
         [ApiMember(Name = "EndPositionTicks", Description = "EndPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public long? EndPositionTicks { get; set; }
+
+        [ApiMember(Name = "CopyTimestamps", Description = "CopyTimestamps", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool CopyTimestamps { get; set; }
+        public bool AddVttTimeMap { get; set; }
     }
 
     [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/subtitles.m3u8", "GET", Summary = "Gets an HLS subtitle playlist.")]
@@ -175,7 +179,7 @@ namespace MediaBrowser.Api.Subtitles
 
                 var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks);
 
-                var url = string.Format("stream.vtt?StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}",
+                var url = string.Format("stream.vtt?CopyTimestamps=true&AddVttTimeMap=true&StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}",
                     positionTicks.ToString(CultureInfo.InvariantCulture),
                     endPositionTicks.ToString(CultureInfo.InvariantCulture),
                     accessToken);
@@ -190,7 +194,7 @@ namespace MediaBrowser.Api.Subtitles
             return ResultFactory.GetResult(builder.ToString(), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
         }
 
-        public object Get(GetSubtitle request)
+        public async Task<object> Get(GetSubtitle request)
         {
             if (string.Equals(request.Format, "js", StringComparison.OrdinalIgnoreCase))
             {
@@ -206,23 +210,35 @@ namespace MediaBrowser.Api.Subtitles
                 var subtitleStream = mediaSource.MediaStreams
                     .First(i => i.Type == MediaStreamType.Subtitle && i.Index == request.Index);
 
-                return ToStaticFileResult(subtitleStream.Path);
+                return await ResultFactory.GetStaticFileResult(Request, subtitleStream.Path).ConfigureAwait(false);
             }
 
-            var stream = GetSubtitles(request).Result;
+            using (var stream = await GetSubtitles(request).ConfigureAwait(false))
+            {
+                using (var reader = new StreamReader(stream))
+                {
+                    var text = reader.ReadToEnd();
+
+                    if (string.Equals(request.Format, "vtt", StringComparison.OrdinalIgnoreCase) && request.AddVttTimeMap)
+                    {
+                        text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000");
+                    }
 
-            return ResultFactory.GetResult(stream, MimeTypes.GetMimeType("file." + request.Format));
+                    return ResultFactory.GetResult(text, MimeTypes.GetMimeType("file." + request.Format));
+                }
+            }
         }
 
-        private async Task<Stream> GetSubtitles(GetSubtitle request)
+        private Task<Stream> GetSubtitles(GetSubtitle request)
         {
-            return await _subtitleEncoder.GetSubtitles(request.Id,
+            return _subtitleEncoder.GetSubtitles(request.Id,
                 request.MediaSourceId,
                 request.Index,
                 request.Format,
                 request.StartPositionTicks,
                 request.EndPositionTicks,
-                CancellationToken.None).ConfigureAwait(false);
+                request.CopyTimestamps,
+                CancellationToken.None);
         }
 
         public object Get(SearchRemoteSubtitles request)
@@ -248,9 +264,9 @@ namespace MediaBrowser.Api.Subtitles
             return ToOptimizedResult(result);
         }
 
-        public object Get(GetRemoteSubtitles request)
+        public async Task<object> Get(GetRemoteSubtitles request)
         {
-            var result = _subtitleManager.GetRemoteSubtitles(request.Id, CancellationToken.None).Result;
+            var result = await _subtitleManager.GetRemoteSubtitles(request.Id, CancellationToken.None).ConfigureAwait(false);
 
             return ResultFactory.GetResult(result.Stream, MimeTypes.GetMimeType("file." + result.Format));
         }

+ 9 - 10
MediaBrowser.Api/Sync/SyncService.cs

@@ -227,7 +227,7 @@ namespace MediaBrowser.Api.Sync
             Task.WaitAll(task);
         }
 
-        public object Get(GetSyncJobItemFile request)
+        public async Task<object> Get(GetSyncJobItemFile request)
         {
             var jobItem = _syncManager.GetJobItem(request.Id);
 
@@ -241,10 +241,9 @@ namespace MediaBrowser.Api.Sync
                 throw new ArgumentException("The job item is not yet ready for transfer.");
             }
 
-            var task = _syncManager.ReportSyncJobItemTransferBeginning(request.Id);
-            Task.WaitAll(task);
+            await _syncManager.ReportSyncJobItemTransferBeginning(request.Id).ConfigureAwait(false);
 
-            return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
+            return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
             {
                 Path = jobItem.OutputPath,
                 OnError = () =>
@@ -252,10 +251,11 @@ namespace MediaBrowser.Api.Sync
                     var failedTask = _syncManager.ReportSyncJobItemTransferFailed(request.Id);
                     Task.WaitAll(failedTask);
                 }
-            });
+
+            }).ConfigureAwait(false);
         }
 
-        public object Get(GetSyncDialogOptions request)
+        public async Task<object> Get(GetSyncDialogOptions request)
         {
             var result = new SyncDialogOptions();
 
@@ -298,8 +298,7 @@ namespace MediaBrowser.Api.Sync
                     .Select(_libraryManager.GetItemById)
                     .Where(i => i != null);
 
-                var dtos = _dtoService.GetBaseItemDtos(items, dtoOptions, authenticatedUser)
-                    .ToList();
+                var dtos = (await _dtoService.GetBaseItemDtos(items, dtoOptions, authenticatedUser).ConfigureAwait(false));
 
                 result.Options = SyncHelper.GetSyncOptions(dtos);
             }
@@ -343,7 +342,7 @@ namespace MediaBrowser.Api.Sync
             Task.WaitAll(task);
         }
 
-        public object Get(GetSyncJobItemAdditionalFile request)
+        public Task<object> Get(GetSyncJobItemAdditionalFile request)
         {
             var jobItem = _syncManager.GetJobItem(request.Id);
 
@@ -359,7 +358,7 @@ namespace MediaBrowser.Api.Sync
                 throw new ArgumentException("Sync job additional file not found.");
             }
 
-            return ToStaticFileResult(file.Path);
+            return ResultFactory.GetStaticFileResult(Request, file.Path);
         }
 
         public void Post(EnableSyncJobItem request)

+ 1 - 1
MediaBrowser.Api/System/SystemInfoWebSocketListener.cs

@@ -43,7 +43,7 @@ namespace MediaBrowser.Api.System
         /// <returns>Task{SystemInfo}.</returns>
         protected override Task<SystemInfo> GetDataToSend(WebSocketListenerState state)
         {
-            return Task.FromResult(_appHost.GetSystemInfo());
+            return _appHost.GetSystemInfo();
         }
     }
 }

+ 8 - 8
MediaBrowser.Api/System/SystemService.cs

@@ -19,7 +19,7 @@ namespace MediaBrowser.Api.System
     /// Class GetSystemInfo
     /// </summary>
     [Route("/System/Info", "GET", Summary = "Gets information about the server")]
-    [Authenticated(EscapeParentalControl = true)]
+    [Authenticated(EscapeParentalControl = true, AllowBeforeStartupWizard = true)]
     public class GetSystemInfo : IReturn<SystemInfo>
     {
 
@@ -120,7 +120,7 @@ namespace MediaBrowser.Api.System
 
             try
             {
-				files = _fileSystem.GetFiles(_appPaths.LogDirectoryPath)
+                files = _fileSystem.GetFiles(_appPaths.LogDirectoryPath)
                     .Where(i => string.Equals(i.Extension, ".txt", StringComparison.OrdinalIgnoreCase))
                     .ToList();
             }
@@ -144,9 +144,9 @@ namespace MediaBrowser.Api.System
             return ToOptimizedResult(result);
         }
 
-        public object Get(GetLogFile request)
+        public Task<object> Get(GetLogFile request)
         {
-			var file = _fileSystem.GetFiles(_appPaths.LogDirectoryPath)
+            var file = _fileSystem.GetFiles(_appPaths.LogDirectoryPath)
                 .First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase));
 
             return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite);
@@ -157,16 +157,16 @@ namespace MediaBrowser.Api.System
         /// </summary>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
-        public object Get(GetSystemInfo request)
+        public async Task<object> Get(GetSystemInfo request)
         {
-            var result = _appHost.GetSystemInfo();
+            var result = await _appHost.GetSystemInfo().ConfigureAwait(false);
 
             return ToOptimizedResult(result);
         }
 
-        public object Get(GetPublicSystemInfo request)
+        public async Task<object> Get(GetPublicSystemInfo request)
         {
-            var result = _appHost.GetSystemInfo();
+            var result = await _appHost.GetSystemInfo().ConfigureAwait(false);
 
             var publicInfo = new PublicSystemInfo
             {

+ 47 - 21
MediaBrowser.Api/TvShowsService.cs

@@ -12,6 +12,8 @@ using ServiceStack;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Dto;
 
 namespace MediaBrowser.Api
 {
@@ -271,29 +273,51 @@ namespace MediaBrowser.Api
         /// </summary>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
-        public object Get(GetSimilarShows request)
+        public async Task<object> Get(GetSimilarShows request)
         {
+            var result = await GetSimilarItemsResult(request).ConfigureAwait(false);
+
+            return ToOptimizedSerializedResultUsingCache(result);
+        }
+
+        private async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
+        {
+            var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+
+            var item = string.IsNullOrEmpty(request.Id) ?
+                (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
+                _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
+
+            var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
+            {
+                Limit = request.Limit,
+                IncludeItemTypes = new[]
+                {
+                        typeof(Series).Name
+                },
+                SimilarTo = item
+
+            }).ToList();
+
             var dtoOptions = GetDtoOptions(request);
 
-            var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
-                _itemRepo,
-                _libraryManager,
-                _userDataManager,
-                _dtoService,
-                Logger,
-                request, new[] { typeof(Series) },
-                SimilarItemsHelper.GetSimiliarityScore);
+            var result = new QueryResult<BaseItemDto>
+            {
+                Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(),
 
-            return ToOptimizedSerializedResultUsingCache(result);
+                TotalRecordCount = itemsResult.Count
+            };
+
+            return result;
         }
 
-        public object Get(GetUpcomingEpisodes request)
+        public async Task<object> Get(GetUpcomingEpisodes request)
         {
             var user = _userManager.GetUserById(request.UserId);
 
             var minPremiereDate = DateTime.Now.Date.ToUniversalTime().AddDays(-1);
 
-            var parentIds = string.IsNullOrWhiteSpace(request.ParentId) ? new string[] { } : new[] { request.ParentId };
+            var parentIdGuid = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId);
 
             var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
             {
@@ -302,13 +326,15 @@ namespace MediaBrowser.Api
                 SortOrder = SortOrder.Ascending,
                 MinPremiereDate = minPremiereDate,
                 StartIndex = request.StartIndex,
-                Limit = request.Limit
+                Limit = request.Limit,
+                ParentId = parentIdGuid,
+                Recursive = true
 
-            }, parentIds).ToList();
+            }).ToList();
 
             var options = GetDtoOptions(request);
 
-            var returnItems = _dtoService.GetBaseItemDtos(itemsResult, options, user).ToArray();
+            var returnItems = (await _dtoService.GetBaseItemDtos(itemsResult, options, user).ConfigureAwait(false)).ToArray();
 
             var result = new ItemsResult
             {
@@ -324,7 +350,7 @@ namespace MediaBrowser.Api
         /// </summary>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
-        public object Get(GetNextUpEpisodes request)
+        public async Task<object> Get(GetNextUpEpisodes request)
         {
             var result = _tvSeriesManager.GetNextUp(new NextUpQuery
             {
@@ -339,7 +365,7 @@ namespace MediaBrowser.Api
 
             var options = GetDtoOptions(request);
 
-            var returnItems = _dtoService.GetBaseItemDtos(result.Items, options, user).ToArray();
+            var returnItems = (await _dtoService.GetBaseItemDtos(result.Items, options, user).ConfigureAwait(false)).ToArray();
 
             return ToOptimizedSerializedResultUsingCache(new ItemsResult
             {
@@ -372,7 +398,7 @@ namespace MediaBrowser.Api
             return items;
         }
 
-        public object Get(GetSeasons request)
+        public async Task<object> Get(GetSeasons request)
         {
             var user = _userManager.GetUserById(request.UserId);
 
@@ -403,7 +429,7 @@ namespace MediaBrowser.Api
 
             var dtoOptions = GetDtoOptions(request);
 
-            var returnItems = _dtoService.GetBaseItemDtos(seasons, dtoOptions, user)
+            var returnItems = (await _dtoService.GetBaseItemDtos(seasons, dtoOptions, user).ConfigureAwait(false))
                 .ToArray();
 
             return new ItemsResult
@@ -430,7 +456,7 @@ namespace MediaBrowser.Api
             return items;
         }
 
-        public object Get(GetEpisodes request)
+        public async Task<object> Get(GetEpisodes request)
         {
             var user = _userManager.GetUserById(request.UserId);
 
@@ -512,7 +538,7 @@ namespace MediaBrowser.Api
 
             var dtoOptions = GetDtoOptions(request);
 
-            var dtos = _dtoService.GetBaseItemDtos(pagedItems, dtoOptions, user)
+            var dtos = (await _dtoService.GetBaseItemDtos(pagedItems, dtoOptions, user).ConfigureAwait(false))
                 .ToArray();
 
             return new ItemsResult

+ 27 - 13
MediaBrowser.Api/UserLibrary/ArtistsService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Dto;
+using System;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
@@ -8,6 +9,8 @@ using MediaBrowser.Model.Dto;
 using ServiceStack;
 using System.Collections.Generic;
 using System.Linq;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
 
 namespace MediaBrowser.Api.UserLibrary
 {
@@ -100,7 +103,12 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         public object Get(GetArtists request)
         {
-            var result = GetResult(request);
+            if (string.IsNullOrWhiteSpace(request.IncludeItemTypes))
+            {
+                //request.IncludeItemTypes = "Audio,MusicVideo";
+            }
+
+            var result = GetResultSlim(request);
 
             return ToOptimizedResult(result);
         }
@@ -112,11 +120,26 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         public object Get(GetAlbumArtists request)
         {
-            var result = GetResult(request);
+            if (string.IsNullOrWhiteSpace(request.IncludeItemTypes))
+            {
+                //request.IncludeItemTypes = "Audio,MusicVideo";
+            }
+
+            var result = GetResultSlim(request);
 
             return ToOptimizedResult(result);
         }
 
+        protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+        {
+            if (request is GetAlbumArtists)
+            {
+                return LibraryManager.GetAlbumArtists(query);
+            }
+
+            return LibraryManager.GetArtists(query);
+        }
+
         /// <summary>
         /// Gets all items.
         /// </summary>
@@ -125,16 +148,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
         protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
         {
-            if (request is GetAlbumArtists)
-            {
-                return LibraryManager.GetAlbumArtists(items
-                   .Where(i => !i.IsFolder)
-                   .OfType<IHasAlbumArtist>());
-            }
-
-            return LibraryManager.GetArtists(items
-                .Where(i => !i.IsFolder)
-                .OfType<IHasArtist>());
+            throw new NotImplementedException();
         }
     }
 }

+ 134 - 7
MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs

@@ -8,6 +8,7 @@ using ServiceStack;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using MediaBrowser.Model.Dto;
 
 namespace MediaBrowser.Api.UserLibrary
 {
@@ -83,6 +84,137 @@ namespace MediaBrowser.Api.UserLibrary
             return null;
         }
 
+        protected ItemsResult GetResultSlim(GetItemsByName request)
+        {
+            var dtoOptions = GetDtoOptions(request);
+
+            User user = null;
+            BaseItem parentItem;
+
+            if (!string.IsNullOrWhiteSpace(request.UserId))
+            {
+                user = UserManager.GetUserById(request.UserId);
+                parentItem = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : LibraryManager.GetItemById(request.ParentId);
+            }
+            else
+            {
+                parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : LibraryManager.GetItemById(request.ParentId);
+            }
+
+            var excludeItemTypes = request.GetExcludeItemTypes();
+            var includeItemTypes = request.GetIncludeItemTypes();
+            var mediaTypes = request.GetMediaTypes();
+
+            var query = new InternalItemsQuery(user)
+            {
+                ExcludeItemTypes = excludeItemTypes,
+                IncludeItemTypes = includeItemTypes,
+                MediaTypes = mediaTypes,
+                StartIndex = request.StartIndex,
+                Limit = request.Limit,
+                IsFavorite = request.IsFavorite,
+                NameLessThan = request.NameLessThan,
+                NameStartsWith = request.NameStartsWith,
+                NameStartsWithOrGreater = request.NameStartsWithOrGreater,
+                AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
+                Tags = request.GetTags(),
+                OfficialRatings = request.GetOfficialRatings(),
+                Genres = request.GetGenres(),
+                GenreIds = request.GetGenreIds(),
+                Studios = request.GetStudios(),
+                StudioIds = request.GetStudioIds(),
+                Person = request.Person,
+                PersonIds = request.GetPersonIds(),
+                PersonTypes = request.GetPersonTypes(),
+                Years = request.GetYears(),
+                MinCommunityRating = request.MinCommunityRating
+            };
+
+            if (!string.IsNullOrWhiteSpace(request.ParentId))
+            {
+                if (parentItem is Folder)
+                {
+                    query.AncestorIds = new[] { request.ParentId };
+                }
+                else
+                {
+                    query.ItemIds = new[] { request.ParentId };
+                }
+            }
+
+            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;
+                }
+            }
+
+            var result = GetItems(request, query);
+
+            var dtos = result.Items.Select(i =>
+            {
+                var dto = DtoService.GetItemByNameDto(i.Item1, dtoOptions, null, user);
+
+                if (!string.IsNullOrWhiteSpace(request.IncludeItemTypes))
+                {
+                    SetItemCounts(dto, i.Item2);
+                }
+                return dto;
+            });
+
+            return new ItemsResult
+            {
+                Items = dtos.ToArray(),
+                TotalRecordCount = result.TotalRecordCount
+            };
+        }
+
+        protected virtual QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+        {
+            return new QueryResult<Tuple<BaseItem, ItemCounts>>();
+        }
+
+        private void SetItemCounts(BaseItemDto dto, ItemCounts counts)
+        {
+            dto.ChildCount = counts.ItemCount;
+            dto.SeriesCount = counts.SeriesCount;
+            dto.EpisodeCount = counts.EpisodeCount;
+            dto.MovieCount = counts.MovieCount;
+            dto.TrailerCount = counts.TrailerCount;
+            dto.AlbumCount = counts.AlbumCount;
+            dto.SongCount = counts.SongCount;
+            dto.GameCount = counts.GameCount;
+        }
+
         /// <summary>
         /// Gets the specified request.
         /// </summary>
@@ -333,12 +465,7 @@ namespace MediaBrowser.Api.UserLibrary
                 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)))
+                    if (!tags.Any(v => i.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))
                     {
                         return false;
                     }
@@ -379,7 +506,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="includeItemTypes">The include item types.</param>
         /// <param name="mediaTypes">The media types.</param>
         /// <returns>IEnumerable{BaseItem}.</returns>
-        protected bool FilterItem(GetItemsByName request, BaseItem f, string[] excludeItemTypes, string[] includeItemTypes, string[] mediaTypes)
+        private bool FilterItem(GetItemsByName request, BaseItem f, string[] excludeItemTypes, string[] includeItemTypes, string[] mediaTypes)
         {
             // Exclude item types
             if (excludeItemTypes.Length > 0)

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

@@ -100,9 +100,6 @@ namespace MediaBrowser.Api.UserLibrary
         [ApiMember(Name = "HasTvdbId", Description = "Optional filter by items that have a tvdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         public bool? HasTvdbId { get; set; }
 
-        [ApiMember(Name = "IsYearMismatched", Description = "Optional filter by items that are potentially misidentified.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? IsYearMismatched { get; set; }
-
         [ApiMember(Name = "IsInBoxSet", Description = "Optional filter by items that are in boxsets, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         public bool? IsInBoxSet { get; set; }
 

+ 8 - 21
MediaBrowser.Api/UserLibrary/GameGenresService.cs

@@ -9,16 +9,13 @@ using ServiceStack;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using MediaBrowser.Model.Querying;
 
 namespace MediaBrowser.Api.UserLibrary
 {
     [Route("/GameGenres", "GET", Summary = "Gets all Game genres from a given item, folder, or the entire library")]
     public class GetGameGenres : GetItemsByName
     {
-        public GetGameGenres()
-        {
-            MediaTypes = MediaType.Game;
-        }
     }
 
     [Route("/GameGenres/{Name}", "GET", Summary = "Gets a Game genre, by name")]
@@ -87,11 +84,16 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         public object Get(GetGameGenres request)
         {
-            var result = GetResult(request);
+            var result = GetResultSlim(request);
 
             return ToOptimizedSerializedResultUsingCache(result);
         }
 
+        protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+        {
+            return LibraryManager.GetGameGenres(query);
+        }
+
         /// <summary>
         /// Gets all items.
         /// </summary>
@@ -100,22 +102,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
         protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
         {
-            return items
-                .SelectMany(i => i.Genres)
-                .DistinctNames()
-                .Select(name =>
-                {
-                    try
-                    {
-                        return LibraryManager.GetGameGenre(name);
-                    }
-                    catch (Exception ex)
-                    {
-                        Logger.ErrorException("Error getting genre {0}", ex, name);
-                        return null;
-                    }
-                })
-                .Where(i => i != null);
+            throw new NotImplementedException();
         }
     }
 }

+ 17 - 44
MediaBrowser.Api/UserLibrary/GenresService.cs

@@ -9,6 +9,7 @@ using ServiceStack;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using MediaBrowser.Model.Querying;
 
 namespace MediaBrowser.Api.UserLibrary
 {
@@ -92,65 +93,37 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         public object Get(GetGenres request)
         {
-            var result = GetResult(request);
+            var result = GetResultSlim(request);
 
             return ToOptimizedSerializedResultUsingCache(result);
         }
 
-        /// <summary>
-        /// Gets all items.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <param name="items">The items.</param>
-        /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
-        protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+        protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
         {
             var viewType = GetParentItemViewType(request);
 
             if (string.Equals(viewType, CollectionType.Music) || string.Equals(viewType, CollectionType.MusicVideos))
             {
-                return items
-                    .SelectMany(i => i.Genres)
-                    .DistinctNames()
-                    .Select(name => LibraryManager.GetMusicGenre(name));
+                return LibraryManager.GetMusicGenres(query);
             }
 
             if (string.Equals(viewType, CollectionType.Games))
             {
-                return items
-                    .SelectMany(i => i.Genres)
-                    .DistinctNames()
-                    .Select(name =>
-                    {
-                        try
-                        {
-                            return LibraryManager.GetGameGenre(name);
-                        }
-                        catch (Exception ex)
-                        {
-                            Logger.ErrorException("Error getting genre {0}", ex, name);
-                            return null;
-                        }
-                    })
-                    .Where(i => i != null);
+                return LibraryManager.GetGameGenres(query);
             }
 
-            return items
-                .SelectMany(i => i.Genres)
-                .DistinctNames()
-                .Select(name =>
-                {
-                    try
-                    {
-                        return LibraryManager.GetGenre(name);
-                    }
-                    catch (Exception ex)
-                    {
-                        Logger.ErrorException("Error getting genre {0}", ex, name);
-                        return null;
-                    }
-                })
-                .Where(i => i != null);
+            return LibraryManager.GetGenres(query);
+        }
+
+        /// <summary>
+        /// Gets all items.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <param name="items">The items.</param>
+        /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
+        protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
+        {
+            throw new NotImplementedException();
         }
     }
 }

+ 54 - 16
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -34,7 +34,6 @@ namespace MediaBrowser.Api.UserLibrary
         /// The _user manager
         /// </summary>
         private readonly IUserManager _userManager;
-        private readonly IUserDataManager _userDataRepository;
 
         /// <summary>
         /// The _library manager
@@ -43,25 +42,37 @@ namespace MediaBrowser.Api.UserLibrary
         private readonly ILocalizationManager _localization;
 
         private readonly IDtoService _dtoService;
-        private readonly ICollectionManager _collectionManager;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ItemsService" /> class.
         /// </summary>
         /// <param name="userManager">The user manager.</param>
         /// <param name="libraryManager">The library manager.</param>
-        /// <param name="userDataRepository">The user data repository.</param>
         /// <param name="localization">The localization.</param>
         /// <param name="dtoService">The dto service.</param>
-        /// <param name="collectionManager">The collection manager.</param>
-        public ItemsService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, ILocalizationManager localization, IDtoService dtoService, ICollectionManager collectionManager)
+        public ItemsService(IUserManager userManager, ILibraryManager libraryManager, ILocalizationManager localization, IDtoService dtoService)
         {
+            if (userManager == null)
+            {
+                throw new ArgumentNullException("userManager");
+            }
+            if (libraryManager == null)
+            {
+                throw new ArgumentNullException("libraryManager");
+            }
+            if (localization == null)
+            {
+                throw new ArgumentNullException("localization");
+            }
+            if (dtoService == null)
+            {
+                throw new ArgumentNullException("dtoService");
+            }
+
             _userManager = userManager;
             _libraryManager = libraryManager;
-            _userDataRepository = userDataRepository;
             _localization = localization;
             _dtoService = dtoService;
-            _collectionManager = collectionManager;
         }
 
         /// <summary>
@@ -71,6 +82,11 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         public async Task<object> Get(GetItems request)
         {
+            if (request == null)
+            {
+                throw new ArgumentNullException("request");
+            }
+
             var result = await GetItems(request).ConfigureAwait(false);
 
             return ToOptimizedSerializedResultUsingCache(result);
@@ -84,15 +100,32 @@ namespace MediaBrowser.Api.UserLibrary
         private async Task<ItemsResult> GetItems(GetItems request)
         {
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
-
+        
             var result = await GetItemsToSerialize(request, user).ConfigureAwait(false);
 
+            if (result == null)
+            {
+                throw new InvalidOperationException("GetItemsToSerialize returned null");
+            }
+
+            if (result.Items == null)
+            {
+                throw new InvalidOperationException("GetItemsToSerialize result.Items returned null");
+            }
+
             var dtoOptions = GetDtoOptions(request);
 
+            var dtoList = await _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user).ConfigureAwait(false);
+
+            if (dtoList == null)
+            {
+                throw new InvalidOperationException("GetBaseItemDtos returned null");
+            }
+
             return new ItemsResult
             {
                 TotalRecordCount = result.TotalRecordCount,
-                Items = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user).ToArray()
+                Items = dtoList.ToArray()
             };
         }
 
@@ -119,11 +152,17 @@ namespace MediaBrowser.Api.UserLibrary
 
             // Default list type = children
 
+            var folder = item as Folder;
+            if (folder == null)
+            {
+                folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder();
+            }
+
             if (!string.IsNullOrEmpty(request.Ids))
             {
                 request.Recursive = true;
                 var query = GetItemsQuery(request, user);
-                var result = await ((Folder)item).GetItems(query).ConfigureAwait(false);
+                var result = await folder.GetItems(query).ConfigureAwait(false);
 
                 if (string.IsNullOrWhiteSpace(request.SortBy))
                 {
@@ -138,22 +177,22 @@ namespace MediaBrowser.Api.UserLibrary
 
             if (request.Recursive)
             {
-                return await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+                return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
             }
 
             if (user == null)
             {
-                return await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
+                return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
             }
 
             var userRoot = item as UserRootFolder;
 
             if (userRoot == null)
             {
-                return await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+                return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
             }
 
-            IEnumerable<BaseItem> items = ((Folder)item).GetChildren(user, true);
+            IEnumerable<BaseItem> items = folder.GetChildren(user, true);
 
             var itemsArray = items.ToArray();
 
@@ -187,7 +226,6 @@ namespace MediaBrowser.Api.UserLibrary
                 NameStartsWith = request.NameStartsWith,
                 NameStartsWithOrGreater = request.NameStartsWithOrGreater,
                 HasImdbId = request.HasImdbId,
-                IsYearMismatched = request.IsYearMismatched,
                 IsPlaceHolder = request.IsPlaceHolder,
                 IsLocked = request.IsLocked,
                 IsInBoxSet = request.IsInBoxSet,
@@ -301,7 +339,7 @@ namespace MediaBrowser.Api.UserLibrary
             {
                 query.LocationTypes = request.LocationTypes.Split(',').Select(d => (LocationType)Enum.Parse(typeof(LocationType), d, true)).ToArray();
             }
-            
+
             // Min official rating
             if (!string.IsNullOrWhiteSpace(request.MinOfficialRating))
             {

+ 11 - 10
MediaBrowser.Api/UserLibrary/MusicGenresService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Dto;
+using System;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
@@ -8,16 +9,14 @@ using MediaBrowser.Model.Dto;
 using ServiceStack;
 using System.Collections.Generic;
 using System.Linq;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
 
 namespace MediaBrowser.Api.UserLibrary
 {
     [Route("/MusicGenres", "GET", Summary = "Gets all music genres from a given item, folder, or the entire library")]
     public class GetMusicGenres : GetItemsByName
     {
-        public GetMusicGenres()
-        {
-            IncludeItemTypes = typeof(Audio).Name;
-        }
     }
 
     [Route("/MusicGenres/{Name}", "GET", Summary = "Gets a music genre, by name")]
@@ -86,11 +85,16 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         public object Get(GetMusicGenres request)
         {
-            var result = GetResult(request);
+            var result = GetResultSlim(request);
 
             return ToOptimizedSerializedResultUsingCache(result);
         }
 
+        protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+        {
+            return LibraryManager.GetMusicGenres(query);
+        }
+
         /// <summary>
         /// Gets all items.
         /// </summary>
@@ -99,10 +103,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
         protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
         {
-            return items
-                .SelectMany(i => i.Genres)
-                .DistinctNames()
-                .Select(name => LibraryManager.GetMusicGenre(name));
+            throw new NotImplementedException();
         }
     }
 }

+ 3 - 3
MediaBrowser.Api/UserLibrary/PlaystateService.cs

@@ -247,9 +247,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// Posts the specified request.
         /// </summary>
         /// <param name="request">The request.</param>
-        public object Post(MarkPlayedItem request)
+        public async Task<object> Post(MarkPlayedItem request)
         {
-            var result = MarkPlayed(request).Result;
+            var result = await MarkPlayed(request).ConfigureAwait(false);
 
             return ToOptimizedResult(result);
         }
@@ -429,7 +429,7 @@ namespace MediaBrowser.Api.UserLibrary
                 await item.MarkUnplayed(user).ConfigureAwait(false);
             }
 
-            return _userDataRepository.GetUserDataDto(item, user);
+            return await _userDataRepository.GetUserDataDto(item, user).ConfigureAwait(false);
         }
     }
 }

+ 9 - 2
MediaBrowser.Api/UserLibrary/StudiosService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Dto;
+using System;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
@@ -7,6 +8,7 @@ using MediaBrowser.Model.Dto;
 using ServiceStack;
 using System.Collections.Generic;
 using System.Linq;
+using MediaBrowser.Model.Querying;
 
 namespace MediaBrowser.Api.UserLibrary
 {
@@ -90,11 +92,16 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         public object Get(GetStudios request)
         {
-            var result = GetResult(request);
+            var result = GetResultSlim(request);
 
             return ToOptimizedSerializedResultUsingCache(result);
         }
 
+        protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+        {
+            return LibraryManager.GetStudios(query);
+        }
+
         /// <summary>
         /// Gets all items.
         /// </summary>

+ 6 - 6
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -488,9 +488,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// Posts the specified request.
         /// </summary>
         /// <param name="request">The request.</param>
-        public object Post(MarkFavoriteItem request)
+        public async Task<object> Post(MarkFavoriteItem request)
         {
-            var dto = MarkFavorite(request.UserId, request.Id, true).Result;
+            var dto = await MarkFavorite(request.UserId, request.Id, true).ConfigureAwait(false);
 
             return ToOptimizedResult(dto);
         }
@@ -527,7 +527,7 @@ namespace MediaBrowser.Api.UserLibrary
 
             await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false);
 
-            return _userDataRepository.GetUserDataDto(item, user);
+            return await _userDataRepository.GetUserDataDto(item, user).ConfigureAwait(false);
         }
 
         /// <summary>
@@ -545,9 +545,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// Posts the specified request.
         /// </summary>
         /// <param name="request">The request.</param>
-        public object Post(UpdateUserItemRating request)
+        public async Task<object> Post(UpdateUserItemRating request)
         {
-            var dto = UpdateUserItemRating(request.UserId, request.Id, request.Likes).Result;
+            var dto = await UpdateUserItemRating(request.UserId, request.Id, request.Likes).ConfigureAwait(false);
 
             return ToOptimizedResult(dto);
         }
@@ -572,7 +572,7 @@ namespace MediaBrowser.Api.UserLibrary
 
             await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false);
 
-            return _userDataRepository.GetUserDataDto(item, user);
+            return await _userDataRepository.GetUserDataDto(item, user).ConfigureAwait(false);
         }
     }
 }

+ 7 - 2
MediaBrowser.Api/UserService.cs

@@ -385,7 +385,7 @@ namespace MediaBrowser.Api
                 throw new ResourceNotFoundException("User not found");
             }
 
-            await _sessionMananger.RevokeUserTokens(user.Id.ToString("N")).ConfigureAwait(false);
+            await _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), null).ConfigureAwait(false);
 
             await _userManager.DeleteUser(user).ConfigureAwait(false);
         }
@@ -465,6 +465,10 @@ namespace MediaBrowser.Api
                 }
 
                 await _userManager.ChangePassword(user, request.NewPassword).ConfigureAwait(false);
+
+                var currentToken = AuthorizationContext.GetAuthorizationInfo(Request).Token;
+
+                await _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), currentToken).ConfigureAwait(false);
             }
         }
 
@@ -602,7 +606,8 @@ namespace MediaBrowser.Api
                     throw new ArgumentException("There must be at least one enabled user in the system.");
                 }
 
-                await _sessionMananger.RevokeUserTokens(user.Id.ToString("N")).ConfigureAwait(false);
+                var currentToken = AuthorizationContext.GetAuthorizationInfo(Request).Token;
+                await _sessionMananger.RevokeUserTokens(user.Id.ToString("N"), currentToken).ConfigureAwait(false);
             }
 
             await _userManager.UpdateUserPolicy(request.Id, request).ConfigureAwait(false);

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

@@ -199,7 +199,7 @@ namespace MediaBrowser.Common.Implementations
             ILogManager logManager, 
             IFileSystem fileSystem)
         {
-			XmlSerializer = new MediaBrowser.Common.Implementations.Serialization.XmlSerializer (fileSystem);
+			XmlSerializer = new XmlSerializer (fileSystem, logManager.GetLogger("XmlSerializer"));
             FailedAssemblies = new List<string>();
 
             ApplicationPaths = applicationPaths;
@@ -321,7 +321,7 @@ namespace MediaBrowser.Common.Implementations
 
         protected virtual IJsonSerializer CreateJsonSerializer()
         {
-            return new JsonSerializer(FileSystemManager);
+            return new JsonSerializer(FileSystemManager, LogManager.GetLogger("JsonSerializer"));
         }
 
         private void SetHttpLimit()
@@ -552,7 +552,7 @@ namespace MediaBrowser.Common.Implementations
             }
             catch (Exception ex)
             {
-                Logger.Error("Error creating {0}", ex, type.Name);
+                Logger.ErrorException("Error creating {0}", ex, type.Name);
 
                 throw;
             }
@@ -571,7 +571,7 @@ namespace MediaBrowser.Common.Implementations
             }
             catch (Exception ex)
             {
-                Logger.Error("Error creating {0}", ex, type.Name);
+                Logger.ErrorException("Error creating {0}", ex, type.Name);
                 // Don't blow up in release mode
                 return null;
             }

+ 10 - 10
MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs

@@ -128,11 +128,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
         private void AddIpv4Option(HttpWebRequest request, HttpRequestOptions options)
         {
-            if (!options.PreferIpv4)
-            {
-                return;
-            }
-
             request.ServicePoint.BindIPEndPointDelegate = (servicePount, remoteEndPoint, retryCount) =>
             {
                 if (remoteEndPoint.AddressFamily == AddressFamily.InterNetwork)
@@ -143,18 +138,23 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
             };
         }
 
-        private WebRequest GetRequest(HttpRequestOptions options, string method, bool enableHttpCompression)
+        private WebRequest GetRequest(HttpRequestOptions options, string method)
         {
             var request = CreateWebRequest(options.Url);
             var httpWebRequest = request as HttpWebRequest;
 
             if (httpWebRequest != null)
             {
-                AddIpv4Option(httpWebRequest, options);
+                if (options.PreferIpv4)
+                {
+                    AddIpv4Option(httpWebRequest, options);
+                }
 
                 AddRequestHeaders(httpWebRequest, options);
 
-                httpWebRequest.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None;
+                httpWebRequest.AutomaticDecompression = options.EnableHttpCompression ? 
+                    (options.DecompressionMethod ?? DecompressionMethods.Deflate) : 
+                    DecompressionMethods.None;
             }
 
             request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
@@ -366,7 +366,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
                 };
             }
 
-            var httpWebRequest = GetRequest(options, httpMethod, options.EnableHttpCompression);
+            var httpWebRequest = GetRequest(options, httpMethod);
 
             if (options.RequestContentBytes != null ||
                 !string.IsNullOrEmpty(options.RequestContent) ||
@@ -556,7 +556,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
             options.CancellationToken.ThrowIfCancellationRequested();
 
-            var httpWebRequest = GetRequest(options, "GET", options.EnableHttpCompression);
+            var httpWebRequest = GetRequest(options, "GET");
 
             if (options.ResourcePool != null)
             {

+ 3 - 3
MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj

@@ -55,7 +55,7 @@
       <HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
     </Reference>
     <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
-      <HintPath>..\packages\NLog.4.3.4\lib\net45\NLog.dll</HintPath>
+      <HintPath>..\packages\NLog.4.3.5\lib\net45\NLog.dll</HintPath>
       <Private>True</Private>
     </Reference>
     <Reference Include="Patterns.Logging">
@@ -65,8 +65,8 @@
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\ThirdParty\SharpCompress\SharpCompress.dll</HintPath>
     </Reference>
-    <Reference Include="SimpleInjector, Version=3.1.4.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
-      <HintPath>..\packages\SimpleInjector.3.1.4\lib\net45\SimpleInjector.dll</HintPath>
+    <Reference Include="SimpleInjector, Version=3.2.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
+      <HintPath>..\packages\SimpleInjector.3.2.0\lib\net45\SimpleInjector.dll</HintPath>
       <Private>True</Private>
     </Reference>
     <Reference Include="System" />

+ 18 - 21
MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs

@@ -122,30 +122,27 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         {
             get
             {
-                if (_lastExecutionResult == null)
-                {
-                    var path = GetHistoryFilePath();
+                var path = GetHistoryFilePath();
 
-                    lock (_lastExecutionResultSyncLock)
+                lock (_lastExecutionResultSyncLock)
+                {
+                    if (_lastExecutionResult == null)
                     {
-                        if (_lastExecutionResult == null)
+                        try
+                        {
+                            _lastExecutionResult = JsonSerializer.DeserializeFromFile<TaskResult>(path);
+                        }
+                        catch (DirectoryNotFoundException)
+                        {
+                            // File doesn't exist. No biggie
+                        }
+                        catch (FileNotFoundException)
+                        {
+                            // File doesn't exist. No biggie
+                        }
+                        catch (Exception ex)
                         {
-                            try
-                            {
-                                return JsonSerializer.DeserializeFromFile<TaskResult>(path);
-                            }
-                            catch (DirectoryNotFoundException)
-                            {
-                                // File doesn't exist. No biggie
-                            }
-                            catch (FileNotFoundException)
-                            {
-                                // File doesn't exist. No biggie
-                            }
-                            catch (Exception ex)
-                            {
-                                Logger.ErrorException("Error deserializing {0}", ex, path);
-                            }
+                            Logger.ErrorException("Error deserializing {0}", ex, path);
                         }
                     }
                 }

+ 6 - 2
MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs

@@ -2,6 +2,7 @@
 using System;
 using System.IO;
 using CommonIO;
+using MediaBrowser.Model.Logging;
 
 namespace MediaBrowser.Common.Implementations.Serialization
 {
@@ -11,10 +12,12 @@ namespace MediaBrowser.Common.Implementations.Serialization
     public class JsonSerializer : IJsonSerializer
     {
         private readonly IFileSystem _fileSystem;
-        
-        public JsonSerializer(IFileSystem fileSystem)
+        private readonly ILogger _logger;
+
+        public JsonSerializer(IFileSystem fileSystem, ILogger logger)
         {
             _fileSystem = fileSystem;
+            _logger = logger;
             Configure();
         }
 
@@ -65,6 +68,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
 
         private Stream OpenFile(string path)
         {
+            _logger.Debug("Deserializing file {0}", path);
             return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 131072);
         }
 

+ 9 - 5
MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs

@@ -4,6 +4,7 @@ using System.Collections.Concurrent;
 using System.IO;
 using System.Xml;
 using CommonIO;
+using MediaBrowser.Model.Logging;
 
 namespace MediaBrowser.Common.Implementations.Serialization
 {
@@ -12,12 +13,14 @@ namespace MediaBrowser.Common.Implementations.Serialization
     /// </summary>
     public class XmlSerializer : IXmlSerializer
     {
-		private IFileSystem _fileSystem;
+		private readonly IFileSystem _fileSystem;
+        private readonly ILogger _logger;
 
-		public XmlSerializer(IFileSystem fileSystem) 
-		{
-			_fileSystem = fileSystem;
-		}
+        public XmlSerializer(IFileSystem fileSystem, ILogger logger)
+        {
+            _fileSystem = fileSystem;
+            _logger = logger;
+        }
 
         // Need to cache these
         // http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
@@ -91,6 +94,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
         /// <returns>System.Object.</returns>
         public object DeserializeFromFile(Type type, string file)
         {
+            _logger.Debug("Deserializing file {0}", file);
             using (var stream = _fileSystem.OpenRead(file))
             {
                 return DeserializeFromStream(type, stream);

+ 2 - 2
MediaBrowser.Common.Implementations/packages.config

@@ -2,7 +2,7 @@
 <packages>
   <package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
   <package id="morelinq" version="1.4.0" targetFramework="net45" />
-  <package id="NLog" version="4.3.4" targetFramework="net45" />
+  <package id="NLog" version="4.3.5" targetFramework="net45" />
   <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
-  <package id="SimpleInjector" version="3.1.4" targetFramework="net45" />
+  <package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
 </packages>

+ 3 - 0
MediaBrowser.Common/Net/HttpRequestOptions.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Net;
 using System.Threading;
 
 namespace MediaBrowser.Common.Net
@@ -16,6 +17,8 @@ namespace MediaBrowser.Common.Net
         /// <value>The URL.</value>
         public string Url { get; set; }
 
+        public DecompressionMethods? DecompressionMethod { get; set; }
+
         /// <summary>
         /// Gets or sets the accept header.
         /// </summary>

+ 8 - 0
MediaBrowser.Controller/Channels/ChannelItemInfo.cs

@@ -53,6 +53,12 @@ namespace MediaBrowser.Controller.Channels
 
         public bool IsInfiniteStream { get; set; }
 
+        public string HomePageUrl { get; set; }
+
+        public List<string> Artists { get; set; }
+
+        public List<string> AlbumArtists { get; set; }
+
         public ChannelItemInfo()
         {
             MediaSources = new List<ChannelMediaInfo>();
@@ -62,6 +68,8 @@ namespace MediaBrowser.Controller.Channels
             People = new List<PersonInfo>();
             Tags = new List<string>();
             ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+            Artists = new List<string>();
+            AlbumArtists = new List<string>();
         }
     }
 }

+ 1 - 1
MediaBrowser.Controller/Chapters/IChapterManager.cs

@@ -33,7 +33,7 @@ namespace MediaBrowser.Controller.Chapters
         /// <param name="chapters">The chapters.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        Task SaveChapters(string itemId, IEnumerable<ChapterInfo> chapters, CancellationToken cancellationToken);
+        Task SaveChapters(string itemId, List<ChapterInfo> chapters, CancellationToken cancellationToken);
 
         /// <summary>
         /// Searches the specified video.

+ 0 - 5
MediaBrowser.Controller/Drawing/ImageCollageOptions.cs

@@ -23,10 +23,5 @@ namespace MediaBrowser.Controller.Drawing
         /// </summary>
         /// <value>The height.</value>
         public int Height { get; set; }
-        /// <summary>
-        /// Gets or sets the text.
-        /// </summary>
-        /// <value>The text.</value>
-        public string Text { get; set; }
     }
 }

+ 2 - 1
MediaBrowser.Controller/Dto/IDtoService.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Querying;
 using System.Collections.Generic;
+using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Dto
 {
@@ -68,7 +69,7 @@ namespace MediaBrowser.Controller.Dto
         /// <param name="user">The user.</param>
         /// <param name="owner">The owner.</param>
         /// <returns>IEnumerable&lt;BaseItemDto&gt;.</returns>
-        IEnumerable<BaseItemDto> GetBaseItemDtos(IEnumerable<BaseItem> items, DtoOptions options, User user = null,
+        Task<List<BaseItemDto>> GetBaseItemDtos(IEnumerable<BaseItem> items, DtoOptions options, User user = null,
             BaseItem owner = null);
         
         /// <summary>

+ 0 - 3
MediaBrowser.Controller/Entities/Audio/Audio.cs

@@ -20,15 +20,12 @@ namespace MediaBrowser.Controller.Entities.Audio
         IHasArtist,
         IHasMusicGenres,
         IHasLookupInfo<SongInfo>,
-        IHasTags,
         IHasMediaSources,
         IThemeMedia,
         IArchivable
     {
         public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
 
-        public long? Size { get; set; }
-        public string Container { get; set; }
         public int? TotalBitrate { get; set; }
         public ExtraType? ExtraType { get; set; }
 

+ 3 - 20
MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs

@@ -179,17 +179,13 @@ namespace MediaBrowser.Controller.Entities.Audio
         {
             var items = GetRecursiveChildren().ToList();
 
-            var songs = items.OfType<Audio>().ToList();
-
-            var others = items.Except(songs).ToList();
-
-            var totalItems = songs.Count + others.Count;
+            var totalItems = items.Count;
             var numComplete = 0;
 
             var childUpdateType = ItemUpdateType.None;
 
             // Refresh songs
-            foreach (var item in songs)
+            foreach (var item in items)
             {
                 cancellationToken.ThrowIfCancellationRequested();
 
@@ -199,7 +195,7 @@ namespace MediaBrowser.Controller.Entities.Audio
                 numComplete++;
                 double percent = numComplete;
                 percent /= totalItems;
-                progress.Report(percent * 100);
+                progress.Report(percent * 95);
             }
 
             var parentRefreshOptions = refreshOptions;
@@ -212,19 +208,6 @@ namespace MediaBrowser.Controller.Entities.Audio
             // Refresh current item
             await RefreshMetadata(parentRefreshOptions, cancellationToken).ConfigureAwait(false);
 
-            // Refresh all non-songs
-            foreach (var item in others)
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                var updateType = await item.RefreshMetadata(parentRefreshOptions, cancellationToken).ConfigureAwait(false);
-
-                numComplete++;
-                double percent = numComplete;
-                percent /= totalItems;
-                progress.Report(percent * 100);
-            }
-
             progress.Report(100);
         }
     }

+ 18 - 8
MediaBrowser.Controller/Entities/Audio/MusicArtist.cs

@@ -9,6 +9,7 @@ using System.Linq;
 using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
 
 namespace MediaBrowser.Controller.Entities.Audio
 {
@@ -62,13 +63,6 @@ namespace MediaBrowser.Controller.Entities.Audio
                 query.ArtistNames = new[] { Name };
             }
 
-            // Need this for now since the artist filter isn't yet supported by the db
-            if (ConfigurationManager.Configuration.SchemaVersion < 79)
-            {
-                var filter = GetItemFilter();
-                return LibraryManager.GetItemList(query).Where(filter);
-            }
-
             return LibraryManager.GetItemList(query);
         }
 
@@ -86,6 +80,15 @@ namespace MediaBrowser.Controller.Entities.Audio
             }
         }
 
+        public override int GetChildCount(User user)
+        {
+            if (IsAccessedByName)
+            {
+                return 0;
+            }
+            return base.GetChildCount(user);
+        }
+
         public override bool IsSaveLocalMetadataEnabled()
         {
             if (IsAccessedByName)
@@ -163,10 +166,17 @@ namespace MediaBrowser.Controller.Entities.Audio
                 list.Add("Artist-Musicbrainz-" + id);
             }
 
-            list.Add("Artist-" + item.Name);
+            list.Add("Artist-" + (item.Name ?? string.Empty).RemoveDiacritics());
             return list;
         }
 
+        public override string PresentationUniqueKey
+        {
+            get
+            {
+                return "Artist-" + (Name ?? string.Empty).RemoveDiacritics();
+            }
+        }
         protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Music);

+ 10 - 1
MediaBrowser.Controller/Entities/Audio/MusicGenre.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
+using MediaBrowser.Common.Extensions;
 
 namespace MediaBrowser.Controller.Entities.Audio
 {
@@ -14,10 +15,18 @@ namespace MediaBrowser.Controller.Entities.Audio
         {
             var list = base.GetUserDataKeys();
 
-            list.Insert(0, "MusicGenre-" + Name);
+            list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
             return list;
         }
 
+        public override string PresentationUniqueKey
+        {
+            get
+            {
+                return GetUserDataKeys()[0];
+            }
+        }
+
         [IgnoreDataMember]
         public override bool SupportsAddingToPlaylist
         {

+ 25 - 13
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -26,6 +26,7 @@ using System.Threading.Tasks;
 using CommonIO;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.LiveTv;
+using MediaBrowser.Model.Providers;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -36,6 +37,7 @@ namespace MediaBrowser.Controller.Entities
     {
         protected BaseItem()
         {
+            Keywords = new List<string>();
             Tags = new List<string>();
             Genres = new List<string>();
             Studios = new List<string>();
@@ -67,12 +69,20 @@ namespace MediaBrowser.Controller.Entities
         [IgnoreDataMember]
         public string PreferredMetadataLanguage { get; set; }
 
+        public long? Size { get; set; }
+        public string Container { get; set; }
+        public string ShortOverview { get; set; }
+
         public List<ItemImageInfo> ImageInfos { get; set; }
 
+        [IgnoreDataMember]
+        public bool IsVirtualItem { get; set; }
+
         /// <summary>
         /// Gets or sets the album.
         /// </summary>
         /// <value>The album.</value>
+        [IgnoreDataMember]
         public string Album { get; set; }
 
         /// <summary>
@@ -810,6 +820,8 @@ namespace MediaBrowser.Controller.Entities
         [IgnoreDataMember]
         public List<string> Tags { get; set; }
 
+        public List<string> Keywords { get; set; }
+
         /// <summary>
         /// Gets or sets the home page URL.
         /// </summary>
@@ -1031,9 +1043,7 @@ namespace MediaBrowser.Controller.Entities
                 }
                 : options;
 
-            var result = await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
-
-            return result;
+            return await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
         }
 
         [IgnoreDataMember]
@@ -1394,15 +1404,10 @@ namespace MediaBrowser.Controller.Entities
 
         private bool IsVisibleViaTags(User user)
         {
-            var hasTags = this as IHasTags;
-
-            if (hasTags != null)
+            var policy = user.Policy;
+            if (policy.BlockedTags.Any(i => Tags.Contains(i, StringComparer.OrdinalIgnoreCase)))
             {
-                var policy = user.Policy;
-                if (policy.BlockedTags.Any(i => hasTags.Tags.Contains(i, StringComparer.OrdinalIgnoreCase)))
-                {
-                    return false;
-                }
+                return false;
             }
 
             return true;
@@ -2088,7 +2093,7 @@ namespace MediaBrowser.Controller.Entities
             return path;
         }
 
-        public virtual void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user)
+        public virtual Task FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user)
         {
             if (RunTimeTicks.HasValue)
             {
@@ -2104,6 +2109,8 @@ namespace MediaBrowser.Controller.Entities
                     }
                 }
             }
+
+            return Task.FromResult(true);
         }
 
         protected Task RefreshMetadataForOwnedVideo(MetadataRefreshOptions options, string path, CancellationToken cancellationToken)
@@ -2168,7 +2175,7 @@ namespace MediaBrowser.Controller.Entities
         {
             get
             {
-                if (GetParent() is AggregateFolder || this is BasePluginFolder)
+                if (GetParent() is AggregateFolder || this is BasePluginFolder || this is Channel)
                 {
                     return true;
                 }
@@ -2214,5 +2221,10 @@ namespace MediaBrowser.Controller.Entities
                 DeleteFileLocation = false
             });
         }
+
+        public virtual List<ExternalUrl> GetRelatedUrls()
+        {
+            return new List<ExternalUrl>();
+        }
     }
 }

+ 1 - 1
MediaBrowser.Controller/Entities/Book.cs

@@ -6,7 +6,7 @@ using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Controller.Entities
 {
-    public class Book : BaseItem, IHasTags, IHasLookupInfo<BookInfo>, IHasSeries
+    public class Book : BaseItem, IHasLookupInfo<BookInfo>, IHasSeries
     {
         [IgnoreDataMember]
         public override string MediaType

+ 63 - 272
MediaBrowser.Controller/Entities/Folder.cs

@@ -13,6 +13,7 @@ using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
 using CommonIO;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Model.Channels;
 
 namespace MediaBrowser.Controller.Entities
@@ -20,7 +21,7 @@ namespace MediaBrowser.Controller.Entities
     /// <summary>
     /// Class Folder
     /// </summary>
-    public class Folder : BaseItem, IHasThemeMedia, IHasTags
+    public class Folder : BaseItem, IHasThemeMedia
     {
         public static IUserManager UserManager { get; set; }
         public static IUserViewManager UserViewManager { get; set; }
@@ -164,49 +165,15 @@ namespace MediaBrowser.Controller.Entities
                 item.DateModified = DateTime.UtcNow;
             }
 
-            AddChildInternal(item.Id);
-
             await LibraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
         }
 
-        protected void AddChildrenInternal(List<Guid> children)
-        {
-            lock (_childrenSyncLock)
-            {
-                var newChildren = ChildIds.ToList();
-                newChildren.AddRange(children);
-                _children = newChildren.ToList();
-            }
-        }
-        protected void AddChildInternal(Guid child)
-        {
-            lock (_childrenSyncLock)
-            {
-                var childIds = ChildIds.ToList();
-                if (!childIds.Contains(child))
-                {
-                    childIds.Add(child);
-                    _children = childIds.ToList();
-                }
-            }
-        }
-
-        protected void RemoveChildrenInternal(List<Guid> children)
-        {
-            lock (_childrenSyncLock)
-            {
-                _children = ChildIds.Except(children).ToList();
-            }
-        }
-
         /// <summary>
         /// Removes the child.
         /// </summary>
         /// <param name="item">The item.</param>
         public void RemoveChild(BaseItem item)
         {
-            RemoveChildrenInternal(new[] { item.Id }.ToList());
-
             item.SetParent(null);
         }
 
@@ -241,33 +208,6 @@ namespace MediaBrowser.Controller.Entities
 
         #endregion
 
-        /// <summary>
-        /// The children
-        /// </summary>
-        private IReadOnlyList<Guid> _children;
-        /// <summary>
-        /// The _children sync lock
-        /// </summary>
-        private readonly object _childrenSyncLock = new object();
-        /// <summary>
-        /// Gets or sets the actual children.
-        /// </summary>
-        /// <value>The actual children.</value>
-        protected virtual IEnumerable<Guid> ChildIds
-        {
-            get
-            {
-                lock (_childrenSyncLock)
-                {
-                    if (_children == null)
-                    {
-                        _children = LoadChildren().ToList();
-                    }
-                    return _children.ToList();
-                }
-            }
-        }
-
         /// <summary>
         /// Gets the actual children.
         /// </summary>
@@ -277,7 +217,7 @@ namespace MediaBrowser.Controller.Entities
         {
             get
             {
-                return ChildIds.Select(LibraryManager.GetItemById).Where(i => i != null);
+                return LoadChildren();
             }
         }
 
@@ -331,7 +271,7 @@ namespace MediaBrowser.Controller.Entities
         /// Loads our children.  Validation will occur externally.
         /// We want this sychronous.
         /// </summary>
-        protected virtual IEnumerable<Guid> LoadChildren()
+        protected virtual IEnumerable<BaseItem> LoadChildren()
         {
             //just load our children from the repo - the library will be validated and maintained in other processes
             return GetCachedChildren();
@@ -461,17 +401,15 @@ namespace MediaBrowser.Controller.Entities
 
                     foreach (var item in itemsRemoved)
                     {
-                        if (item.LocationType == LocationType.Virtual ||
-                            item.LocationType == LocationType.Remote)
+                        var itemLocationType = item.LocationType;
+                        if (itemLocationType == LocationType.Virtual ||
+                            itemLocationType == LocationType.Remote)
                         {
-                            // Don't remove these because there's no way to accurately validate them.
-                            validChildren.Add(item);
                         }
 
                         else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
                         {
                             await UpdateIsOffline(item, true).ConfigureAwait(false);
-                            validChildren.Add(item);
                         }
                         else
                         {
@@ -481,8 +419,6 @@ namespace MediaBrowser.Controller.Entities
 
                     if (actualRemovals.Count > 0)
                     {
-                        RemoveChildrenInternal(actualRemovals.Select(i => i.Id).ToList());
-
                         foreach (var item in actualRemovals)
                         {
                             Logger.Debug("Removed item: " + item.Path);
@@ -495,8 +431,6 @@ namespace MediaBrowser.Controller.Entities
                     }
 
                     await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false);
-
-                    AddChildrenInternal(newItems.Select(i => i.Id).ToList());
                 }
             }
 
@@ -724,15 +658,36 @@ namespace MediaBrowser.Controller.Entities
         /// Get our children from the repo - stubbed for now
         /// </summary>
         /// <returns>IEnumerable{BaseItem}.</returns>
-        protected IEnumerable<Guid> GetCachedChildren()
+        protected IEnumerable<BaseItem> GetCachedChildren()
         {
-            return ItemRepository.GetItemIdsList(new InternalItemsQuery
+            return ItemRepository.GetItemList(new InternalItemsQuery
             {
                 ParentId = Id,
                 GroupByPresentationUniqueKey = false
             });
         }
 
+        public virtual int GetChildCount(User user)
+        {
+            if (LinkedChildren.Count > 0)
+            {
+                if (!(this is ICollectionFolder))
+                {
+                    return GetChildren(user, true).Count();
+                }
+            }
+
+            var result = GetItems(new InternalItemsQuery(user)
+            {
+                Recursive = false,
+                Limit = 0,
+                ParentId = Id
+
+            }).Result;
+
+            return result.TotalRecordCount;
+        }
+
         public QueryResult<BaseItem> QueryRecursive(InternalItemsQuery query)
         {
             var user = query.User;
@@ -768,58 +723,13 @@ namespace MediaBrowser.Controller.Entities
             {
                 if (!(this is ICollectionFolder))
                 {
-                    Logger.Debug("Query requires post-filtering due to LinkedChildren");
+                    Logger.Debug("Query requires post-filtering due to LinkedChildren. Type: " + GetType().Name);
                     return true;
                 }
             }
 
-            var supportsUserDataQueries = ConfigurationManager.Configuration.SchemaVersion >= 76;
-
             if (query.SortBy != null && query.SortBy.Length > 0)
             {
-                if (!supportsUserDataQueries)
-                {
-                    if (query.SortBy.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase))
-                    {
-                        Logger.Debug("Query requires post-filtering due to ItemSortBy.IsFavoriteOrLiked");
-                        return true;
-                    }
-                    if (query.SortBy.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase))
-                    {
-                        Logger.Debug("Query requires post-filtering due to ItemSortBy.PlayCount");
-                        return true;
-                    }
-                    if (query.SortBy.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase))
-                    {
-                        Logger.Debug("Query requires post-filtering due to ItemSortBy.IsFavoriteOrLiked");
-                        return true;
-                    }
-                    if (query.SortBy.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase))
-                    {
-                        Logger.Debug("Query requires post-filtering due to ItemSortBy.IsPlayed");
-                        return true;
-                    }
-                    if (query.SortBy.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase))
-                    {
-                        Logger.Debug("Query requires post-filtering due to ItemSortBy.IsUnplayed");
-                        return true;
-                    }
-                }
-
-                if (ConfigurationManager.Configuration.SchemaVersion < 79)
-                {
-                    if (query.SortBy.Contains(ItemSortBy.AlbumArtist, StringComparer.OrdinalIgnoreCase))
-                    {
-                        Logger.Debug("Query requires post-filtering due to ItemSortBy.AlbumArtist");
-                        return true;
-                    }
-                    if (query.SortBy.Contains(ItemSortBy.Artist, StringComparer.OrdinalIgnoreCase))
-                    {
-                        Logger.Debug("Query requires post-filtering due to ItemSortBy.Artist");
-                        return true;
-                    }
-                }
-
                 if (query.SortBy.Contains(ItemSortBy.AiredEpisodeOrder, StringComparer.OrdinalIgnoreCase))
                 {
                     Logger.Debug("Query requires post-filtering due to ItemSortBy.AiredEpisodeOrder");
@@ -840,11 +750,6 @@ namespace MediaBrowser.Controller.Entities
                     Logger.Debug("Query requires post-filtering due to ItemSortBy.Metascore");
                     return true;
                 }
-                if (query.SortBy.Contains(ItemSortBy.OfficialRating, StringComparer.OrdinalIgnoreCase))
-                {
-                    Logger.Debug("Query requires post-filtering due to ItemSortBy.OfficialRating");
-                    return true;
-                }
                 if (query.SortBy.Contains(ItemSortBy.Players, StringComparer.OrdinalIgnoreCase))
                 {
                     Logger.Debug("Query requires post-filtering due to ItemSortBy.Players");
@@ -860,11 +765,6 @@ namespace MediaBrowser.Controller.Entities
                     Logger.Debug("Query requires post-filtering due to ItemSortBy.SeriesSortName");
                     return true;
                 }
-                if (query.SortBy.Contains(ItemSortBy.Studio, StringComparer.OrdinalIgnoreCase))
-                {
-                    Logger.Debug("Query requires post-filtering due to ItemSortBy.Studio");
-                    return true;
-                }
                 if (query.SortBy.Contains(ItemSortBy.VideoBitRate, StringComparer.OrdinalIgnoreCase))
                 {
                     Logger.Debug("Query requires post-filtering due to ItemSortBy.VideoBitRate");
@@ -878,45 +778,6 @@ namespace MediaBrowser.Controller.Entities
                 return true;
             }
 
-            if (query.PersonIds.Length > 0)
-            {
-                Logger.Debug("Query requires post-filtering due to PersonIds");
-                return true;
-            }
-
-            if (!supportsUserDataQueries)
-            {
-                if (query.IsLiked.HasValue)
-                {
-                    Logger.Debug("Query requires post-filtering due to IsLiked");
-                    return true;
-                }
-
-                if (query.IsFavoriteOrLiked.HasValue)
-                {
-                    Logger.Debug("Query requires post-filtering due to IsFavoriteOrLiked");
-                    return true;
-                }
-
-                if (query.IsFavorite.HasValue)
-                {
-                    Logger.Debug("Query requires post-filtering due to IsFavorite");
-                    return true;
-                }
-
-                if (query.IsResumable.HasValue)
-                {
-                    Logger.Debug("Query requires post-filtering due to IsResumable");
-                    return true;
-                }
-
-                if (query.IsPlayed.HasValue)
-                {
-                    Logger.Debug("Query requires post-filtering due to IsPlayed");
-                    return true;
-                }
-            }
-
             if (query.IsInBoxSet.HasValue)
             {
                 Logger.Debug("Query requires post-filtering due to IsInBoxSet");
@@ -930,30 +791,6 @@ namespace MediaBrowser.Controller.Entities
                 return true;
             }
 
-            if (query.HasImdbId.HasValue)
-            {
-                Logger.Debug("Query requires post-filtering due to HasImdbId");
-                return true;
-            }
-
-            if (query.HasTmdbId.HasValue)
-            {
-                Logger.Debug("Query requires post-filtering due to HasTmdbId");
-                return true;
-            }
-
-            if (query.HasTvdbId.HasValue)
-            {
-                Logger.Debug("Query requires post-filtering due to HasTvdbId");
-                return true;
-            }
-
-            if (query.IsYearMismatched.HasValue)
-            {
-                Logger.Debug("Query requires post-filtering due to IsYearMismatched");
-                return true;
-            }
-
             if (query.HasOfficialRating.HasValue)
             {
                 Logger.Debug("Query requires post-filtering due to HasOfficialRating");
@@ -1003,26 +840,6 @@ namespace MediaBrowser.Controller.Entities
                 return true;
             }
 
-            if (query.ImageTypes.Length > 0)
-            {
-                Logger.Debug("Query requires post-filtering due to ImageTypes");
-                return true;
-            }
-
-            // Apply studio filter
-            if (query.StudioIds.Length > 0)
-            {
-                Logger.Debug("Query requires post-filtering due to StudioIds");
-                return true;
-            }
-
-            // Apply genre filter
-            if (query.GenreIds.Length > 0)
-            {
-                Logger.Debug("Query requires post-filtering due to GenreIds");
-                return true;
-            }
-
             // Apply person filter
             if (query.ItemIdsFromPersonFilters != null)
             {
@@ -1042,12 +859,6 @@ namespace MediaBrowser.Controller.Entities
                 return true;
             }
 
-            if (query.OfficialRatings.Length > 0)
-            {
-                Logger.Debug("Query requires post-filtering due to OfficialRatings");
-                return true;
-            }
-
             if (query.IsMissing.HasValue)
             {
                 Logger.Debug("Query requires post-filtering due to IsMissing");
@@ -1066,7 +877,7 @@ namespace MediaBrowser.Controller.Entities
                 return true;
             }
 
-            if (UserViewBuilder.CollapseBoxSetItems(query, this, query.User))
+            if (UserViewBuilder.CollapseBoxSetItems(query, this, query.User, ConfigurationManager))
             {
                 Logger.Debug("Query requires post-filtering due to CollapseBoxSetItems");
                 return true;
@@ -1102,15 +913,6 @@ namespace MediaBrowser.Controller.Entities
                 return true;
             }
 
-            if (ConfigurationManager.Configuration.SchemaVersion < 79)
-            {
-                if (query.ArtistNames.Length > 0)
-                {
-                    Logger.Debug("Query requires post-filtering due to ArtistNames");
-                    return true;
-                }
-            }
-
             return false;
         }
 
@@ -1183,7 +985,7 @@ namespace MediaBrowser.Controller.Entities
 
         protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query)
         {
-            return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager);
+            return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager, ConfigurationManager);
         }
 
         public virtual IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
@@ -1601,72 +1403,61 @@ namespace MediaBrowser.Controller.Entities
                 {
                     return false;
                 }
+                if (this is Channel)
+                {
+                    return false;
+                }
+                if (SourceType != SourceType.Library)
+                {
+                    return false;
+                }
 
                 return true;
             }
         }
 
-        public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user)
+        public override async Task FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user)
         {
             if (!SupportsUserDataFromChildren)
             {
                 return;
             }
 
-            var recursiveItemCount = 0;
-            var unplayed = 0;
-
-            double totalPercentPlayed = 0;
-
-            var itemsResult = GetItems(new InternalItemsQuery(user)
+            var unplayedQueryResult = await GetItems(new InternalItemsQuery(user)
             {
                 Recursive = true,
                 IsFolder = false,
-                ExcludeLocationTypes = new[] { LocationType.Virtual },
-                EnableTotalRecordCount = false
-
-            }).Result;
+                IsVirtualItem = false,
+                EnableTotalRecordCount = true,
+                Limit = 0,
+                IsPlayed = false
 
-            var children = itemsResult.Items;
+            }).ConfigureAwait(false);
 
-            // Loop through each recursive child
-            foreach (var child in children)
+            var allItemsQueryResult = await GetItems(new InternalItemsQuery(user)
             {
-                recursiveItemCount++;
-
-                var isUnplayed = true;
-
-                var itemUserData = UserDataManager.GetUserData(user, child);
-
-                // Incrememt totalPercentPlayed
-                if (itemUserData != null)
-                {
-                    if (itemUserData.Played)
-                    {
-                        totalPercentPlayed += 100;
+                Recursive = true,
+                IsFolder = false,
+                IsVirtualItem = false,
+                EnableTotalRecordCount = true,
+                Limit = 0
 
-                        isUnplayed = false;
-                    }
-                    else if (itemUserData.PlaybackPositionTicks > 0 && child.RunTimeTicks.HasValue && child.RunTimeTicks.Value > 0)
-                    {
-                        double itemPercent = itemUserData.PlaybackPositionTicks;
-                        itemPercent /= child.RunTimeTicks.Value;
-                        totalPercentPlayed += itemPercent;
-                    }
-                }
+            }).ConfigureAwait(false);
 
-                if (isUnplayed)
-                {
-                    unplayed++;
-                }
+            if (itemDto != null)
+            {
+                itemDto.RecursiveItemCount = allItemsQueryResult.TotalRecordCount;
             }
 
-            dto.UnplayedItemCount = unplayed;
+            double recursiveItemCount = allItemsQueryResult.TotalRecordCount;
+            double unplayedCount = unplayedQueryResult.TotalRecordCount;
 
             if (recursiveItemCount > 0)
             {
-                dto.PlayedPercentage = totalPercentPlayed / recursiveItemCount;
+                var unplayedPercentage = (unplayedCount / recursiveItemCount) * 100;
+                dto.PlayedPercentage = 100 - unplayedPercentage;
                 dto.Played = dto.PlayedPercentage.Value >= 100;
+                dto.UnplayedItemCount = unplayedQueryResult.TotalRecordCount;
             }
         }
     }

+ 1 - 1
MediaBrowser.Controller/Entities/Game.cs

@@ -7,7 +7,7 @@ using System.Linq;
 
 namespace MediaBrowser.Controller.Entities
 {
-    public class Game : BaseItem, IHasTrailers, IHasThemeMedia, IHasTags, IHasScreenshots, ISupportsPlaceHolders, IHasLookupInfo<GameInfo>
+    public class Game : BaseItem, IHasTrailers, IHasThemeMedia, IHasScreenshots, ISupportsPlaceHolders, IHasLookupInfo<GameInfo>
     {
         public List<Guid> ThemeSongIds { get; set; }
         public List<Guid> ThemeVideoIds { get; set; }

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

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
+using MediaBrowser.Common.Extensions;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -11,10 +12,18 @@ namespace MediaBrowser.Controller.Entities
         {
             var list = base.GetUserDataKeys();
 
-            list.Insert(0, "GameGenre-" + Name);
+            list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
             return list;
         }
 
+        public override string PresentationUniqueKey
+        {
+            get
+            {
+                return GetUserDataKeys()[0];
+            }
+        }
+
         /// <summary>
         /// Returns the folder containing the item.
         /// If the item is a folder, it returns the folder itself

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

@@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities.Audio;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using MediaBrowser.Common.Extensions;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -15,10 +16,18 @@ namespace MediaBrowser.Controller.Entities
         {
             var list = base.GetUserDataKeys();
 
-            list.Insert(0, "Genre-" + Name);
+            list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
             return list;
         }
 
+        public override string PresentationUniqueKey
+        {
+            get
+            {
+                return GetUserDataKeys()[0];
+            }
+        }
+
         /// <summary>
         /// Returns the folder containing the item.
         /// If the item is a folder, it returns the folder itself

+ 2 - 1
MediaBrowser.Controller/Entities/IHasUserData.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
+using System.Threading.Tasks;
 using MediaBrowser.Model.Dto;
 
 namespace MediaBrowser.Controller.Entities
@@ -16,7 +17,7 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="dto">The dto.</param>
         /// <param name="userData">The user data.</param>
         /// <param name="user">The user.</param>
-        void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, User user);
+        Task FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user);
 
         bool EnableRememberingTrackSelections { get; }
     }

+ 13 - 2
MediaBrowser.Controller/Entities/InternalItemsQuery.cs

@@ -19,6 +19,8 @@ namespace MediaBrowser.Controller.Entities
 
         public User User { get; set; }
 
+        public BaseItem SimilarTo { get; set; }
+
         public bool? IsFolder { get; set; }
         public bool? IsFavorite { get; set; }
         public bool? IsFavoriteOrLiked { get; set; }
@@ -33,6 +35,7 @@ namespace MediaBrowser.Controller.Entities
         public string[] ExcludeTags { get; set; }
         public string[] ExcludeInheritedTags { get; set; }
         public string[] Genres { get; set; }
+        public string[] Keywords { get; set; }
 
         public bool? IsMissing { get; set; }
         public bool? IsUnaired { get; set; }
@@ -43,6 +46,7 @@ namespace MediaBrowser.Controller.Entities
         public string NameStartsWith { get; set; }
         public string NameLessThan { get; set; }
         public string NameContains { get; set; }
+        public string MinSortName { get; set; }
 
         public string PresentationUniqueKey { get; set; }
         public string Path { get; set; }
@@ -52,6 +56,7 @@ namespace MediaBrowser.Controller.Entities
         public string Person { get; set; }
         public string[] PersonIds { get; set; }
         public string[] ItemIds { get; set; }
+        public string[] ExcludeItemIds { get; set; }
         public string AdjacentTo { get; set; }
         public string[] PersonTypes { get; set; }
 
@@ -60,7 +65,6 @@ namespace MediaBrowser.Controller.Entities
         public bool? IsInBoxSet { get; set; }
         public bool? IsLocked { get; set; }
         public bool? IsPlaceHolder { get; set; }
-        public bool? IsYearMismatched { get; set; }
 
         public bool? HasImdbId { get; set; }
         public bool? HasOverview { get; set; }
@@ -107,6 +111,7 @@ namespace MediaBrowser.Controller.Entities
 
         internal List<Guid> ItemIdsFromPersonFilters { get; set; }
         public int? ParentIndexNumber { get; set; }
+        public int? ParentIndexNumberNotEquals { get; set; }
         public int? IndexNumber { get; set; }
         public int? MinParentalRating { get; set; }
         public int? MaxParentalRating { get; set; }
@@ -114,6 +119,7 @@ namespace MediaBrowser.Controller.Entities
         public bool? IsCurrentSchema { get; set; }
         public bool? HasDeadParentId { get; set; }
         public bool? IsOffline { get; set; }
+        public bool? IsVirtualItem { get; set; }
 
         public Guid? ParentId { get; set; }
         public string[] AncestorIds { get; set; }
@@ -137,6 +143,8 @@ namespace MediaBrowser.Controller.Entities
         public bool GroupByPresentationUniqueKey { get; set; }
         public bool EnableTotalRecordCount { get; set; }
         public bool ForceDirect { get; set; }
+        public Dictionary<string, string> ExcludeProviderIds { get; set; }
+        public bool EnableGroupByMetadataKey { get; set; }
 
         public InternalItemsQuery()
         {
@@ -145,12 +153,14 @@ namespace MediaBrowser.Controller.Entities
 
             AlbumNames = new string[] { };
             ArtistNames = new string[] { };
-            
+            ExcludeProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
             BlockUnratedItems = new UnratedItem[] { };
             Tags = new string[] { };
             OfficialRatings = new string[] { };
             SortBy = new string[] { };
             MediaTypes = new string[] { };
+            Keywords = new string[] { };
             IncludeItemTypes = new string[] { };
             ExcludeItemTypes = new string[] { };
             Genres = new string[] { };
@@ -164,6 +174,7 @@ namespace MediaBrowser.Controller.Entities
             PersonIds = new string[] { };
             ChannelIds = new string[] { };
             ItemIds = new string[] { };
+            ExcludeItemIds = new string[] { };
             AncestorIds = new string[] { };
             TopParentIds = new string[] { };
             ExcludeTags = new string[] { };

+ 1 - 11
MediaBrowser.Controller/Entities/IHasKeywords.cs → MediaBrowser.Controller/Entities/KeywordExtensions.cs

@@ -1,21 +1,11 @@
 using System;
-using System.Collections.Generic;
 using System.Linq;
 
 namespace MediaBrowser.Controller.Entities
 {
-    public interface IHasKeywords
-    {
-        /// <summary>
-        /// Gets or sets the keywords.
-        /// </summary>
-        /// <value>The keywords.</value>
-        List<string> Keywords { get; set; }
-    }
-
     public static class KeywordExtensions
     {
-        public static void AddKeyword(this IHasKeywords item, string name)
+        public static void AddKeyword(this BaseItem item, string name)
         {
             if (string.IsNullOrWhiteSpace(name))
             {

+ 1 - 8
MediaBrowser.Controller/Entities/Movies/BoxSet.cs

@@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Entities.Movies
     /// <summary>
     /// Class BoxSet
     /// </summary>
-    public class BoxSet : Folder, IHasTrailers, IHasKeywords, IHasDisplayOrder, IHasLookupInfo<BoxSetInfo>, IHasShares
+    public class BoxSet : Folder, IHasTrailers, IHasDisplayOrder, IHasLookupInfo<BoxSetInfo>, IHasShares
     {
         public List<Share> Shares { get; set; }
 
@@ -26,7 +26,6 @@ namespace MediaBrowser.Controller.Entities.Movies
             RemoteTrailerIds = new List<Guid>();
 
             DisplayOrder = ItemSortBy.PremiereDate;
-            Keywords = new List<string>();
             Shares = new List<Share>();
         }
 
@@ -47,12 +46,6 @@ namespace MediaBrowser.Controller.Entities.Movies
         /// <value>The remote trailers.</value>
         public List<MediaUrl> RemoteTrailers { get; set; }
 
-        /// <summary>
-        /// Gets or sets the tags.
-        /// </summary>
-        /// <value>The tags.</value>
-        public List<string> Keywords { get; set; }
-
         /// <summary>
         /// Gets or sets the display order.
         /// </summary>

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

@@ -8,13 +8,14 @@ using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
 using CommonIO;
+using MediaBrowser.Model.Providers;
 
 namespace MediaBrowser.Controller.Entities.Movies
 {
     /// <summary>
     /// Class Movie
     /// </summary>
-    public class Movie : Video, IHasCriticRating, IHasSpecialFeatures, IHasProductionLocations, IHasBudget, IHasKeywords, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping, IHasOriginalTitle
+    public class Movie : Video, IHasCriticRating, IHasSpecialFeatures, IHasProductionLocations, IHasBudget, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping, IHasOriginalTitle
     {
         public List<Guid> SpecialFeatureIds { get; set; }
 
@@ -31,7 +32,6 @@ namespace MediaBrowser.Controller.Entities.Movies
             ThemeSongIds = new List<Guid>();
             ThemeVideoIds = new List<Guid>();
             Taglines = new List<string>();
-            Keywords = new List<string>();
             ProductionLocations = new List<string>();
         }
 
@@ -41,7 +41,6 @@ namespace MediaBrowser.Controller.Entities.Movies
 
         public List<Guid> LocalTrailerIds { get; set; }
         public List<Guid> RemoteTrailerIds { get; set; }
-        public List<string> Keywords { get; set; }
 
         public List<MediaUrl> RemoteTrailers { get; set; }
 
@@ -163,5 +162,22 @@ namespace MediaBrowser.Controller.Entities.Movies
 
             return hasChanges;
         }
+
+        public override List<ExternalUrl> GetRelatedUrls()
+        {
+            var list = base.GetRelatedUrls();
+
+            var imdbId = this.GetProviderId(MetadataProviders.Imdb);
+            if (!string.IsNullOrWhiteSpace(imdbId))
+            {
+                list.Add(new ExternalUrl
+                {
+                    Name = "Trakt",
+                    Url = string.Format("https://trakt.tv/movies/{0}", imdbId)
+                });
+            }
+
+            return list;
+        }
     }
 }

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

@@ -3,6 +3,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Controller.Entities
@@ -22,10 +23,18 @@ namespace MediaBrowser.Controller.Entities
         {
             var list = base.GetUserDataKeys();
 
-            list.Insert(0, "Person-" + Name);
+            list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
             return list;
         }
 
+        public override string PresentationUniqueKey
+        {
+            get
+            {
+                return GetUserDataKeys()[0];
+            }
+        }
+
         public PersonLookupInfo GetLookupInfo()
         {
             return GetItemLookupInfo<PersonLookupInfo>();

+ 1 - 1
MediaBrowser.Controller/Entities/Photo.cs

@@ -5,7 +5,7 @@ using System.Runtime.Serialization;
 
 namespace MediaBrowser.Controller.Entities
 {
-    public class Photo : BaseItem, IHasTags, IHasTaglines
+    public class Photo : BaseItem, IHasTaglines
     {
         public List<string> Taglines { get; set; }
 

+ 11 - 2
MediaBrowser.Controller/Entities/Studio.cs

@@ -2,22 +2,31 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
+using MediaBrowser.Common.Extensions;
 
 namespace MediaBrowser.Controller.Entities
 {
     /// <summary>
     /// Class Studio
     /// </summary>
-    public class Studio : BaseItem, IItemByName, IHasTags
+    public class Studio : BaseItem, IItemByName
     {
         public override List<string> GetUserDataKeys()
         {
             var list = base.GetUserDataKeys();
 
-            list.Insert(0, "Studio-" + Name);
+            list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
             return list;
         }
 
+        public override string PresentationUniqueKey
+        {
+            get
+            {
+                return GetUserDataKeys()[0];
+            }
+        }
+
         /// <summary>
         /// Returns the folder containing the item.
         /// If the item is a folder, it returns the folder itself

+ 26 - 8
MediaBrowser.Controller/Entities/TV/Episode.cs

@@ -11,13 +11,25 @@ namespace MediaBrowser.Controller.Entities.TV
     /// <summary>
     /// Class Episode
     /// </summary>
-    public class Episode : Video, IHasLookupInfo<EpisodeInfo>, IHasSeries
-    {
-        /// <summary>
-        /// Gets the season in which it aired.
-        /// </summary>
-        /// <value>The aired season.</value>
-        public int? AirsBeforeSeasonNumber { get; set; }
+    public class Episode : Video, IHasTrailers, IHasLookupInfo<EpisodeInfo>, IHasSeries
+    {
+
+        public Episode()
+        {
+            RemoteTrailers = new List<MediaUrl>();
+            LocalTrailerIds = new List<Guid>();
+            RemoteTrailerIds = new List<Guid>();
+        }
+
+        public List<Guid> LocalTrailerIds { get; set; }
+        public List<Guid> RemoteTrailerIds { get; set; }
+        public List<MediaUrl> RemoteTrailers { get; set; }
+
+    /// <summary>
+    /// Gets the season in which it aired.
+    /// </summary>
+    /// <value>The aired season.</value>
+    public int? AirsBeforeSeasonNumber { get; set; }
         public int? AirsAfterSeasonNumber { get; set; }
         public int? AirsBeforeEpisodeNumber { get; set; }
 
@@ -96,7 +108,13 @@ namespace MediaBrowser.Controller.Entities.TV
             var series = Series;
             if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
             {
-                list.InsertRange(0, series.GetUserDataKeys().Select(i => i + ParentIndexNumber.Value.ToString("000") + IndexNumber.Value.ToString("000")));
+                var seriesUserDataKeys = series.GetUserDataKeys();
+                var take = seriesUserDataKeys.Count;
+                if (seriesUserDataKeys.Count > 1)
+                {
+                    take--;
+                }
+                list.InsertRange(0, seriesUserDataKeys.Take(take).Select(i => i + ParentIndexNumber.Value.ToString("000") + IndexNumber.Value.ToString("000")));
             }
 
             return list;

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

@@ -75,6 +75,11 @@ namespace MediaBrowser.Controller.Entities.TV
             return list;
         }
 
+        public override int GetChildCount(User user)
+        {
+            return GetChildren(user, true).Count();
+        }
+
         /// <summary>
         /// This Episode's Series Instance
         /// </summary>
@@ -128,39 +133,16 @@ namespace MediaBrowser.Controller.Entities.TV
             return IndexNumber != null ? IndexNumber.Value.ToString("0000") : Name;
         }
 
-        public override bool RequiresRefresh()
-        {
-            var result = base.RequiresRefresh();
-
-            if (!result)
-            {
-                if (!IsVirtualItem.HasValue)
-                {
-                    return true;
-                }
-            }
-
-            return result;
-        }
-
-        [IgnoreDataMember]
-        public bool? IsVirtualItem { get; set; }
-
         [IgnoreDataMember]
         public bool IsMissingSeason
         {
-            get { return (IsVirtualItem ?? DetectIsVirtualItem()) && !IsUnaired; }
+            get { return (IsVirtualItem) && !IsUnaired; }
         }
 
         [IgnoreDataMember]
         public bool IsVirtualUnaired
         {
-            get { return (IsVirtualItem ?? DetectIsVirtualItem()) && IsUnaired; }
-        }
-
-        private bool DetectIsVirtualItem()
-        {
-            return LocationType == LocationType.Virtual && GetEpisodes().All(i => i.LocationType == LocationType.Virtual);
+            get { return (IsVirtualItem) && IsUnaired; }
         }
 
         [IgnoreDataMember]
@@ -196,52 +178,17 @@ namespace MediaBrowser.Controller.Entities.TV
         {
             var config = user.Configuration;
 
-            return GetEpisodes(user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
+            return GetEpisodes(Series, user, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
         }
 
-        public IEnumerable<Episode> GetEpisodes(User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
+        public IEnumerable<Episode> GetEpisodes(Series series, User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
         {
-            var series = Series;
-
-            if (IndexNumber.HasValue && series != null)
-            {
-                return series.GetEpisodes(user, this, includeMissingEpisodes, includeVirtualUnairedEpisodes);
-            }
-
-            var episodes = GetRecursiveChildren(user)
-                .OfType<Episode>();
-
-            if (series != null && series.ContainsEpisodesWithoutSeasonFolders)
-            {
-                var seasonNumber = IndexNumber;
-                var list = episodes.ToList();
-
-                if (seasonNumber.HasValue)
-                {
-                    list.AddRange(series.GetRecursiveChildren(user).OfType<Episode>()
-                        .Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == seasonNumber.Value));
-                }
-                else
-                {
-                    list.AddRange(series.GetRecursiveChildren(user).OfType<Episode>()
-                        .Where(i => !i.ParentIndexNumber.HasValue));
-                }
-
-                episodes = list.DistinctBy(i => i.Id);
-            }
-
-            if (!includeMissingEpisodes)
-            {
-                episodes = episodes.Where(i => !i.IsMissingEpisode);
-            }
-            if (!includeVirtualUnairedEpisodes)
-            {
-                episodes = episodes.Where(i => !i.IsVirtualUnaired);
-            }
+            return GetEpisodes(series, user, includeMissingEpisodes, includeVirtualUnairedEpisodes, null);
+        }
 
-            return LibraryManager
-                .Sort(episodes, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending)
-                .Cast<Episode>();
+        public IEnumerable<Episode> GetEpisodes(Series series, User user, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> allSeriesEpisodes)
+        {
+            return series.GetEpisodes(user, this, includeMissingEpisodes, includeVirtualUnairedEpisodes, allSeriesEpisodes);
         }
 
         public IEnumerable<Episode> GetEpisodes()

+ 109 - 112
MediaBrowser.Controller/Entities/TV/Series.cs

@@ -9,6 +9,7 @@ using System.Linq;
 using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Providers;
 using MoreLinq;
 
 namespace MediaBrowser.Controller.Entities.TV
@@ -30,7 +31,6 @@ namespace MediaBrowser.Controller.Entities.TV
             RemoteTrailers = new List<MediaUrl>();
             LocalTrailerIds = new List<Guid>();
             RemoteTrailerIds = new List<Guid>();
-            DisplaySpecialsWithSeasons = true;
         }
 
         [IgnoreDataMember]
@@ -57,8 +57,6 @@ namespace MediaBrowser.Controller.Entities.TV
             }
         }
 
-        public bool DisplaySpecialsWithSeasons { get; set; }
-
         public List<Guid> LocalTrailerIds { get; set; }
         public List<Guid> RemoteTrailerIds { get; set; }
 
@@ -94,10 +92,7 @@ namespace MediaBrowser.Controller.Entities.TV
         {
             get
             {
-                return GetRecursiveChildren(i => i is Episode)
-                        .Select(i => i.DateCreated)
-                        .OrderByDescending(i => i)
-                        .FirstOrDefault();
+                return DateLastMediaAdded ?? DateTime.MinValue;
             }
         }
 
@@ -106,14 +101,30 @@ namespace MediaBrowser.Controller.Entities.TV
         {
             get
             {
-                if (EnablePooling())
+                var userdatakeys = GetUserDataKeys();
+
+                if (userdatakeys.Count > 1)
                 {
-                    return GetUserDataKeys().First();
+                    return userdatakeys[0];
                 }
                 return base.PresentationUniqueKey;
             }
         }
 
+        public override int GetChildCount(User user)
+        {
+            var result = LibraryManager.GetItemsResult(new InternalItemsQuery(user)
+            {
+                AncestorWithPresentationUniqueKey = PresentationUniqueKey,
+                IncludeItemTypes = new[] { typeof(Season).Name },
+                SortBy = new[] { ItemSortBy.SortName },
+                IsVirtualItem = false,
+                Limit = 0
+            });
+
+            return result.TotalRecordCount;
+        }
+
         /// <summary>
         /// Gets the user data key.
         /// </summary>
@@ -182,27 +193,32 @@ namespace MediaBrowser.Controller.Entities.TV
 
         protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
         {
-            var user = query.User;
-
-            Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
-
-            IEnumerable<BaseItem> items;
-
             if (query.User == null)
             {
-                items = query.Recursive
-                   ? GetRecursiveChildren(filter)
-                   : Children.Where(filter);
+                return base.GetItemsInternal(query);
             }
-            else
+
+            var user = query.User;
+
+            if (query.Recursive)
             {
-                items = query.Recursive
-                   ? GetSeasons(user).Cast<BaseItem>().Concat(GetEpisodes(user)).Where(filter)
-                   : GetSeasons(user).Where(filter);
+                query.AncestorWithPresentationUniqueKey = PresentationUniqueKey;
+                if (query.SortBy.Length == 0)
+                {
+                    query.SortBy = new[] { ItemSortBy.SortName };
+                }
+                if (query.IncludeItemTypes.Length == 0)
+                {
+                    query.IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name };
+                }
+                query.IsVirtualItem = false;
+                return Task.FromResult(LibraryManager.GetItemsResult(query));
             }
 
-            var result = PostFilterAndSort(items, query);
+            Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
 
+            var items = GetSeasons(user).Where(filter);
+            var result = PostFilterAndSort(items, query);
             return Task.FromResult(result);
         }
 
@@ -210,33 +226,13 @@ namespace MediaBrowser.Controller.Entities.TV
         {
             IEnumerable<Season> seasons;
 
-            if (EnablePooling())
+            seasons = LibraryManager.GetItemList(new InternalItemsQuery(user)
             {
-                var seriesIds = LibraryManager.GetItemIds(new InternalItemsQuery(user)
-                {
-                    PresentationUniqueKey = PresentationUniqueKey,
-                    IncludeItemTypes = new[] { typeof(Series).Name }
-                });
+                AncestorWithPresentationUniqueKey = PresentationUniqueKey,
+                IncludeItemTypes = new[] { typeof(Season).Name },
+                SortBy = new[] { ItemSortBy.SortName }
 
-                if (seriesIds.Count > 1)
-                {
-                    seasons = LibraryManager.GetItemList(new InternalItemsQuery(user)
-                    {
-                        AncestorIds = seriesIds.Select(i => i.ToString("N")).ToArray(),
-                        IncludeItemTypes = new[] { typeof(Season).Name },
-                        SortBy = new[] { ItemSortBy.SortName }
-
-                    }).Cast<Season>();
-                }
-                else
-                {
-                    seasons = LibraryManager.Sort(base.GetChildren(user, true), user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).OfType<Season>();
-                }
-            }
-            else
-            {
-                seasons = LibraryManager.Sort(base.GetChildren(user, true), user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).OfType<Season>();
-            }
+            }).Cast<Season>();
 
             if (!includeMissingSeasons)
             {
@@ -259,8 +255,18 @@ namespace MediaBrowser.Controller.Entities.TV
 
         public IEnumerable<Episode> GetEpisodes(User user, bool includeMissing, bool includeVirtualUnaired)
         {
-            var allEpisodes = GetSeasons(user, true, true)
-                .SelectMany(i => i.GetEpisodes(user, includeMissing, includeVirtualUnaired))
+            var allItems = LibraryManager.GetItemList(new InternalItemsQuery(user)
+            {
+                AncestorWithPresentationUniqueKey = PresentationUniqueKey,
+                IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name },
+                SortBy = new[] { ItemSortBy.SortName }
+
+            }).ToList();
+
+            var allSeriesEpisodes = allItems.OfType<Episode>().ToList();
+
+            var allEpisodes = allItems.OfType<Season>()
+                .SelectMany(i => i.GetEpisodes(this, user, includeMissing, includeVirtualUnaired, allSeriesEpisodes))
                 .Reverse()
                 .ToList();
 
@@ -283,9 +289,6 @@ namespace MediaBrowser.Controller.Entities.TV
             var totalItems = seasons.Count + otherItems.Count;
             var numComplete = 0;
 
-            refreshOptions = new MetadataRefreshOptions(refreshOptions);
-            refreshOptions.IsPostRecursiveRefresh = true;
-
             // Refresh current item
             await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
 
@@ -315,7 +318,7 @@ namespace MediaBrowser.Controller.Entities.TV
                     && refreshOptions.MetadataRefreshMode != MetadataRefreshMode.FullRefresh
                     && !refreshOptions.ReplaceAllMetadata
                     && episode.IsMissingEpisode
-                    && episode.LocationType == Model.Entities.LocationType.Virtual
+                    && episode.LocationType == LocationType.Virtual
                     && episode.PremiereDate.HasValue
                     && (DateTime.UtcNow - episode.PremiereDate.Value).TotalDays > 30)
                 {
@@ -333,6 +336,8 @@ namespace MediaBrowser.Controller.Entities.TV
                 progress.Report(percent * 100);
             }
 
+            refreshOptions = new MetadataRefreshOptions(refreshOptions);
+            refreshOptions.IsPostRecursiveRefresh = true;
             await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
 
             progress.Report(100);
@@ -345,50 +350,32 @@ namespace MediaBrowser.Controller.Entities.TV
             return GetEpisodes(user, season, config.DisplayMissingEpisodes, config.DisplayUnairedEpisodes);
         }
 
-        private bool EnablePooling()
+        private IEnumerable<Episode> GetAllEpisodes(User user)
         {
-            return false;
+            return LibraryManager.GetItemList(new InternalItemsQuery(user)
+            {
+                AncestorWithPresentationUniqueKey = PresentationUniqueKey,
+                IncludeItemTypes = new[] { typeof(Episode).Name },
+                SortBy = new[] { ItemSortBy.SortName }
+
+            }).Cast<Episode>();
         }
 
         public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes)
         {
-            IEnumerable<Episode> episodes;
-
-            if (EnablePooling())
-            {
-                var seriesIds = LibraryManager.GetItemIds(new InternalItemsQuery(user)
-                {
-                    PresentationUniqueKey = PresentationUniqueKey,
-                    IncludeItemTypes = new[] { typeof(Series).Name }
-                });
+            IEnumerable<Episode> episodes = GetAllEpisodes(user);
 
-                if (seriesIds.Count > 1)
-                {
-                    episodes = LibraryManager.GetItemList(new InternalItemsQuery(user)
-                    {
-                        AncestorIds = seriesIds.Select(i => i.ToString("N")).ToArray(),
-                        IncludeItemTypes = new[] { typeof(Episode).Name },
-                        SortBy = new[] { ItemSortBy.SortName }
+            return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes);
+        }
 
-                    }).Cast<Episode>();
-                }
-                else
-                {
-                    episodes = GetRecursiveChildren(user, new InternalItemsQuery(user)
-                    {
-                        IncludeItemTypes = new[] { typeof(Episode).Name }
-                    }).Cast<Episode>();
-                }
-            }
-            else
+        public IEnumerable<Episode> GetEpisodes(User user, Season parentSeason, bool includeMissingEpisodes, bool includeVirtualUnairedEpisodes, IEnumerable<Episode> allSeriesEpisodes)
+        {
+            if (allSeriesEpisodes == null)
             {
-                episodes = GetRecursiveChildren(user, new InternalItemsQuery(user)
-                {
-                    IncludeItemTypes = new[] { typeof(Episode).Name }
-                }).Cast<Episode>();
+                return GetEpisodes(user, parentSeason, includeMissingEpisodes, includeVirtualUnairedEpisodes);
             }
 
-            episodes = FilterEpisodesBySeason(episodes, parentSeason, DisplaySpecialsWithSeasons);
+            var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons);
 
             if (!includeMissingEpisodes)
             {
@@ -436,38 +423,31 @@ namespace MediaBrowser.Controller.Entities.TV
         public static IEnumerable<Episode> FilterEpisodesBySeason(IEnumerable<Episode> episodes, Season parentSeason, bool includeSpecials)
         {
             var seasonNumber = parentSeason.IndexNumber;
-            if (!includeSpecials || (seasonNumber.HasValue && seasonNumber.Value == 0))
-            {
-                var seasonPresentationKey = parentSeason.PresentationUniqueKey;
+            var seasonPresentationKey = parentSeason.PresentationUniqueKey;
 
-                return episodes.Where(i =>
-                {
-                    if ((i.ParentIndexNumber ?? -1) == seasonNumber)
-                    {
-                        return true;
-                    }
+            var supportSpecialsInSeason = includeSpecials && seasonNumber.HasValue && seasonNumber.Value != 0;
 
-                    var season = i.Season;
-                    return season != null && string.Equals(season.PresentationUniqueKey, seasonPresentationKey, StringComparison.OrdinalIgnoreCase);
-                });
-            }
-            else
+            return episodes.Where(episode =>
             {
-                var seasonPresentationKey = parentSeason.PresentationUniqueKey;
-
-                return episodes.Where(episode =>
+                var currentSeasonNumber = supportSpecialsInSeason ? episode.AiredSeasonNumber : episode.ParentIndexNumber;
+                if (currentSeasonNumber.HasValue && seasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber.Value)
                 {
-                    var currentSeasonNumber = episode.AiredSeasonNumber;
+                    return true;
+                }
 
-                    if (currentSeasonNumber.HasValue && seasonNumber.HasValue && currentSeasonNumber.Value == seasonNumber.Value)
-                    {
-                        return true;
-                    }
+                if (!currentSeasonNumber.HasValue && !seasonNumber.HasValue && parentSeason.LocationType == LocationType.Virtual)
+                {
+                    return true;
+                }
 
+                if (!episode.ParentIndexNumber.HasValue)
+                {
                     var season = episode.Season;
                     return season != null && string.Equals(season.PresentationUniqueKey, seasonPresentationKey, StringComparison.OrdinalIgnoreCase);
-                });
-            }
+                }
+
+                return false;
+            });
         }
 
         protected override bool GetBlockUnratedValue(UserPolicy config)
@@ -508,5 +488,22 @@ namespace MediaBrowser.Controller.Entities.TV
 
             return hasChanges;
         }
+
+        public override List<ExternalUrl> GetRelatedUrls()
+        {
+            var list = base.GetRelatedUrls();
+
+            var imdbId = this.GetProviderId(MetadataProviders.Imdb);
+            if (!string.IsNullOrWhiteSpace(imdbId))
+            {
+                list.Add(new ExternalUrl
+                {
+                    Name = "Trakt",
+                    Url = string.Format("https://trakt.tv/shows/{0}", imdbId)
+                });
+            }
+
+            return list;
+        }
     }
 }

+ 1 - 14
MediaBrowser.Controller/Entities/IHasTags.cs → MediaBrowser.Controller/Entities/TagExtensions.cs

@@ -1,24 +1,11 @@
 using System;
-using System.Collections.Generic;
 using System.Linq;
 
 namespace MediaBrowser.Controller.Entities
 {
-    /// <summary>
-    /// Interface IHasTags
-    /// </summary>
-    public interface IHasTags
-    {
-        /// <summary>
-        /// Gets or sets the tags.
-        /// </summary>
-        /// <value>The tags.</value>
-        List<string> Tags { get; set; }
-    }
-
     public static class TagExtensions
     {
-        public static void AddTag(this IHasTags item, string name)
+        public static void AddTag(this BaseItem item, string name)
         {
             if (string.IsNullOrWhiteSpace(name))
             {

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

@@ -5,13 +5,14 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Runtime.Serialization;
 using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Model.Providers;
 
 namespace MediaBrowser.Controller.Entities
 {
     /// <summary>
     /// Class Trailer
     /// </summary>
-    public class Trailer : Video, IHasCriticRating, IHasProductionLocations, IHasBudget, IHasKeywords, IHasTaglines, IHasMetascore, IHasOriginalTitle, IHasLookupInfo<TrailerInfo>
+    public class Trailer : Video, IHasCriticRating, IHasProductionLocations, IHasBudget, IHasTaglines, IHasMetascore, IHasOriginalTitle, IHasLookupInfo<TrailerInfo>
     {
         public List<string> ProductionLocations { get; set; }
 
@@ -30,8 +31,6 @@ namespace MediaBrowser.Controller.Entities
 
         public List<MediaUrl> RemoteTrailers { get; set; }
 
-        public List<string> Keywords { get; set; }
-
         [IgnoreDataMember]
         public bool IsLocalTrailer
         {
@@ -110,5 +109,22 @@ namespace MediaBrowser.Controller.Entities
 
             return hasChanges;
         }
+
+        public override List<ExternalUrl> GetRelatedUrls()
+        {
+            var list = base.GetRelatedUrls();
+
+            var imdbId = this.GetProviderId(MetadataProviders.Imdb);
+            if (!string.IsNullOrWhiteSpace(imdbId))
+            {
+                list.Add(new ExternalUrl
+                {
+                    Name = "Trakt",
+                    Url = string.Format("https://trakt.tv/movies/{0}", imdbId)
+                });
+            }
+
+            return list;
+        }
     }
 }

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

@@ -38,6 +38,11 @@ namespace MediaBrowser.Controller.Entities
             return PostFilterAndSort(result.Where(filter), query);
         }
 
+        public override int GetChildCount(User user)
+        {
+            return GetChildren(user, true).Count();
+        }
+
         [IgnoreDataMember]
         protected override bool SupportsShortcutChildren
         {

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

@@ -45,6 +45,11 @@ namespace MediaBrowser.Controller.Entities
             return list;
         }
 
+        public override int GetChildCount(User user)
+        {
+            return GetChildren(user, true).Count();
+        }
+
         protected override Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
         {
             var parent = this as Folder;
@@ -58,7 +63,7 @@ namespace MediaBrowser.Controller.Entities
                 parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent;
             }
 
-            return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, CollectionManager, PlaylistManager)
+            return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, ConfigurationManager, PlaylistManager)
                 .GetUserItems(parent, this, ViewType, query);
         }
 

+ 46 - 75
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -18,6 +18,8 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Model.Configuration;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -30,10 +32,10 @@ namespace MediaBrowser.Controller.Entities
         private readonly ILogger _logger;
         private readonly IUserDataManager _userDataManager;
         private readonly ITVSeriesManager _tvSeriesManager;
-        private readonly ICollectionManager _collectionManager;
+        private readonly IServerConfigurationManager _config;
         private readonly IPlaylistManager _playlistManager;
 
-        public UserViewBuilder(IUserViewManager userViewManager, ILiveTvManager liveTvManager, IChannelManager channelManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, ICollectionManager collectionManager, IPlaylistManager playlistManager)
+        public UserViewBuilder(IUserViewManager userViewManager, ILiveTvManager liveTvManager, IChannelManager channelManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, IServerConfigurationManager config, IPlaylistManager playlistManager)
         {
             _userViewManager = userViewManager;
             _liveTvManager = liveTvManager;
@@ -42,7 +44,7 @@ namespace MediaBrowser.Controller.Entities
             _logger = logger;
             _userDataManager = userDataManager;
             _tvSeriesManager = tvSeriesManager;
-            _collectionManager = collectionManager;
+            _config = config;
             _playlistManager = playlistManager;
         }
 
@@ -159,7 +161,7 @@ namespace MediaBrowser.Controller.Entities
                     return await GetTvGenres(queryParent, user, query).ConfigureAwait(false);
 
                 case SpecialFolder.TvGenre:
-                    return await GetTvGenreItems(queryParent, displayParent, user, query).ConfigureAwait(false);
+                    return GetTvGenreItems(queryParent, displayParent, user, query);
 
                 case SpecialFolder.TvResume:
                     return GetTvResume(queryParent, user, query);
@@ -332,13 +334,14 @@ namespace MediaBrowser.Controller.Entities
 
         private QueryResult<BaseItem> GetMusicAlbumArtists(Folder parent, User user, InternalItemsQuery query)
         {
-            var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
+            var items = parent.QueryRecursive(new InternalItemsQuery(user)
             {
                 Recursive = true,
                 ParentId = parent.Id,
-                IncludeItemTypes = new[] { typeof(Audio.Audio).Name }
+                IncludeItemTypes = new[] { typeof(Audio.Audio).Name },
+                EnableTotalRecordCount = false
 
-            }).Cast<IHasAlbumArtist>();
+            }).Items.Cast<IHasAlbumArtist>();
 
             var artists = _libraryManager.GetAlbumArtists(items);
 
@@ -347,13 +350,14 @@ namespace MediaBrowser.Controller.Entities
 
         private QueryResult<BaseItem> GetMusicArtists(Folder parent, User user, InternalItemsQuery query)
         {
-            var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
+            var items = parent.QueryRecursive(new InternalItemsQuery(user)
             {
                 Recursive = true,
                 ParentId = parent.Id,
-                IncludeItemTypes = new[] { typeof(Audio.Audio).Name, typeof(MusicVideo).Name }
+                IncludeItemTypes = new[] { typeof(Audio.Audio).Name, typeof(MusicVideo).Name },
+                EnableTotalRecordCount = false
 
-            }).Cast<IHasArtist>();
+            }).Items.Cast<IHasArtist>();
 
             var artists = _libraryManager.GetArtists(items);
 
@@ -362,13 +366,14 @@ namespace MediaBrowser.Controller.Entities
 
         private QueryResult<BaseItem> GetFavoriteArtists(Folder parent, User user, InternalItemsQuery query)
         {
-            var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
+            var items = parent.QueryRecursive(new InternalItemsQuery(user)
             {
                 Recursive = true,
                 ParentId = parent.Id,
-                IncludeItemTypes = new[] { typeof(Audio.Audio).Name }
+                IncludeItemTypes = new[] { typeof(Audio.Audio).Name },
+                EnableTotalRecordCount = false
 
-            }).Cast<IHasAlbumArtist>();
+            }).Items.Cast<IHasAlbumArtist>();
 
             var artists = _libraryManager.GetAlbumArtists(items).Where(i => _userDataManager.GetUserData(user, i).IsFavorite);
 
@@ -545,7 +550,7 @@ namespace MediaBrowser.Controller.Entities
             query.Limit = GetSpecialItemsLimit();
             query.IncludeItemTypes = new[] { typeof(Movie).Name };
 
-            return _libraryManager.GetItemsResult(query);
+            return ConvertToResult(_libraryManager.GetItemList(query));
         }
 
         private QueryResult<BaseItem> GetMovieResume(Folder parent, User user, InternalItemsQuery query)
@@ -559,7 +564,17 @@ namespace MediaBrowser.Controller.Entities
             query.Limit = GetSpecialItemsLimit();
             query.IncludeItemTypes = new[] { typeof(Movie).Name };
 
-            return _libraryManager.GetItemsResult(query);
+            return ConvertToResult(_libraryManager.GetItemList(query));
+        }
+
+        private QueryResult<BaseItem> ConvertToResult(IEnumerable<BaseItem> items)
+        {
+            var arr = items.ToArray();
+            return new QueryResult<BaseItem>
+            {
+                Items = arr,
+                TotalRecordCount = arr.Length
+            };
         }
 
         private async Task<QueryResult<BaseItem>> GetMovieGenres(Folder parent, User user, InternalItemsQuery query)
@@ -660,8 +675,9 @@ namespace MediaBrowser.Controller.Entities
             query.SetUser(user);
             query.Limit = GetSpecialItemsLimit();
             query.IncludeItemTypes = new[] { typeof(Episode).Name };
+            query.ExcludeLocationTypes = new[] { LocationType.Virtual };
 
-            return _libraryManager.GetItemsResult(query);
+            return ConvertToResult(_libraryManager.GetItemList(query));
         }
 
         private QueryResult<BaseItem> GetTvNextUp(Folder parent, InternalItemsQuery query)
@@ -690,7 +706,7 @@ namespace MediaBrowser.Controller.Entities
             query.Limit = GetSpecialItemsLimit();
             query.IncludeItemTypes = new[] { typeof(Episode).Name };
 
-            return _libraryManager.GetItemsResult(query);
+            return ConvertToResult(_libraryManager.GetItemList(query));
         }
 
         private QueryResult<BaseItem> GetTvSeries(Folder parent, User user, InternalItemsQuery query)
@@ -737,7 +753,7 @@ namespace MediaBrowser.Controller.Entities
             return GetResult(genres, parent, query);
         }
 
-        private async Task<QueryResult<BaseItem>> GetTvGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
+        private QueryResult<BaseItem> GetTvGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
         {
             query.Recursive = true;
             query.ParentId = queryParent.Id;
@@ -766,7 +782,7 @@ namespace MediaBrowser.Controller.Entities
         {
             items = items.Where(i => Filter(i, query.User, query, _userDataManager, _libraryManager));
 
-            return PostFilterAndSort(items, queryParent, null, query, _libraryManager);
+            return PostFilterAndSort(items, queryParent, null, query, _libraryManager, _config);
         }
 
         public static bool FilterItem(BaseItem item, InternalItemsQuery query)
@@ -779,14 +795,15 @@ namespace MediaBrowser.Controller.Entities
             int? totalRecordLimit,
             InternalItemsQuery query)
         {
-            return PostFilterAndSort(items, queryParent, totalRecordLimit, query, _libraryManager);
+            return PostFilterAndSort(items, queryParent, totalRecordLimit, query, _libraryManager, _config);
         }
 
         public static QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items,
             BaseItem queryParent,
             int? totalRecordLimit,
             InternalItemsQuery query,
-            ILibraryManager libraryManager)
+            ILibraryManager libraryManager,
+            IServerConfigurationManager configurationManager)
         {
             var user = query.User;
 
@@ -795,7 +812,7 @@ namespace MediaBrowser.Controller.Entities
                 query.IsVirtualUnaired,
                 query.IsUnaired);
 
-            items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user);
+            items = CollapseBoxSetItemsIfNeeded(items, query, queryParent, user, configurationManager);
 
             // This must be the last filter
             if (!string.IsNullOrEmpty(query.AdjacentTo))
@@ -809,14 +826,15 @@ namespace MediaBrowser.Controller.Entities
         public static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(IEnumerable<BaseItem> items,
             InternalItemsQuery query,
             BaseItem queryParent,
-            User user)
+            User user,
+            IServerConfigurationManager configurationManager)
         {
             if (items == null)
             {
                 throw new ArgumentNullException("items");
             }
 
-            if (CollapseBoxSetItems(query, queryParent, user))
+            if (CollapseBoxSetItems(query, queryParent, user, configurationManager))
             {
                 items = BaseItem.CollectionManager.CollapseItemsWithinBoxSets(items, user);
             }
@@ -849,7 +867,8 @@ namespace MediaBrowser.Controller.Entities
 
         public static bool CollapseBoxSetItems(InternalItemsQuery query,
             BaseItem queryParent,
-            User user)
+            User user,
+            IServerConfigurationManager configurationManager)
         {
             // Could end up stuck in a loop like this
             if (queryParent is BoxSet)
@@ -861,7 +880,7 @@ namespace MediaBrowser.Controller.Entities
 
             if (!param.HasValue)
             {
-                if (user != null && !user.Configuration.GroupMoviesIntoBoxSets)
+                if (user != null && !configurationManager.Configuration.EnableGroupingIntoCollections)
                 {
                     return false;
                 }
@@ -992,11 +1011,6 @@ namespace MediaBrowser.Controller.Entities
                 return false;
             }
 
-            if (request.IsYearMismatched.HasValue)
-            {
-                return false;
-            }
-
             if (!string.IsNullOrWhiteSpace(request.Person))
             {
                 return false;
@@ -1415,16 +1429,6 @@ namespace MediaBrowser.Controller.Entities
                 }
             }
 
-            if (query.IsYearMismatched.HasValue)
-            {
-                var filterValue = query.IsYearMismatched.Value;
-
-                if (IsYearMismatched(item, libraryManager) != filterValue)
-                {
-                    return false;
-                }
-            }
-
             if (query.HasOfficialRating.HasValue)
             {
                 var filterValue = query.HasOfficialRating.Value;
@@ -1658,12 +1662,7 @@ namespace MediaBrowser.Controller.Entities
             var tags = query.Tags;
             if (tags.Length > 0)
             {
-                var hasTags = item as IHasTags;
-                if (hasTags == null)
-                {
-                    return false;
-                }
-                if (!tags.Any(v => hasTags.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))
+                if (!tags.Any(v => item.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))
                 {
                     return false;
                 }
@@ -1976,34 +1975,6 @@ namespace MediaBrowser.Controller.Entities
             return _userViewManager.GetUserSubView(parent.Id.ToString("N"), type, sortName, CancellationToken.None);
         }
 
-        public static bool IsYearMismatched(BaseItem item, ILibraryManager libraryManager)
-        {
-            if (item.ProductionYear.HasValue)
-            {
-                var path = item.Path;
-
-                if (!string.IsNullOrEmpty(path))
-                {
-                    var info = libraryManager.ParseName(Path.GetFileName(path));
-                    var yearInName = info.Year;
-
-                    // Go up a level if we didn't get a year
-                    if (!yearInName.HasValue)
-                    {
-                        info = libraryManager.ParseName(Path.GetFileName(Path.GetDirectoryName(path)));
-                        yearInName = info.Year;
-                    }
-
-                    if (yearInName.HasValue)
-                    {
-                        return yearInName.Value != item.ProductionYear.Value;
-                    }
-                }
-            }
-
-            return false;
-        }
-
         public static IEnumerable<BaseItem> FilterForAdjacency(IEnumerable<BaseItem> items, string adjacentToId)
         {
             var list = items.ToList();

+ 0 - 4
MediaBrowser.Controller/Entities/Video.cs

@@ -21,7 +21,6 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     public class Video : BaseItem,
         IHasAspectRatio,
-        IHasTags,
         ISupportsPlaceHolders,
         IHasMediaSources,
         IHasShortOverview,
@@ -59,10 +58,7 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
-        public long? Size { get; set; }
-        public string Container { get; set; }
         public int? TotalBitrate { get; set; }
-        public string ShortOverview { get; set; }
         public ExtraType? ExtraType { get; set; }
 
         /// <summary>

+ 4 - 3
MediaBrowser.Controller/IServerApplicationHost.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Model.System;
 using System;
 using System.Collections.Generic;
 using System.Net;
+using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller
 {
@@ -17,7 +18,7 @@ namespace MediaBrowser.Controller
         /// Gets the system info.
         /// </summary>
         /// <returns>SystemInfo.</returns>
-        SystemInfo GetSystemInfo();
+        Task<SystemInfo> GetSystemInfo();
 
         /// <summary>
         /// Gets a value indicating whether [supports automatic run at startup].
@@ -65,13 +66,13 @@ namespace MediaBrowser.Controller
         /// Gets the local ip address.
         /// </summary>
         /// <value>The local ip address.</value>
-        List<IPAddress> LocalIpAddresses { get; }
+        Task<List<IPAddress>> GetLocalIpAddresses();
 
         /// <summary>
         /// Gets the local API URL.
         /// </summary>
         /// <value>The local API URL.</value>
-        string LocalApiUrl { get; }
+        Task<string> GetLocalApiUrl();
 
         /// <summary>
         /// Gets the local API URL.

+ 8 - 7
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -11,6 +11,7 @@ using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
 using CommonIO;
+using MediaBrowser.Model.Dto;
 
 namespace MediaBrowser.Controller.Library
 {
@@ -150,13 +151,6 @@ namespace MediaBrowser.Controller.Library
         /// <returns>BaseItem.</returns>
         BaseItem GetItemById(Guid id);
 
-        /// <summary>
-        /// Gets the memory item by identifier.
-        /// </summary>
-        /// <param name="id">The identifier.</param>
-        /// <returns>BaseItem.</returns>
-        BaseItem GetMemoryItemById(Guid id);
-
         /// <summary>
         /// Gets the intros.
         /// </summary>
@@ -574,5 +568,12 @@ namespace MediaBrowser.Controller.Library
         void RemoveVirtualFolder(string name, bool refreshLibrary);
         void AddMediaPath(string virtualFolderName, string path);
         void RemoveMediaPath(string virtualFolderName, string path);
+
+        QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query);
+        QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query);
+        QueryResult<Tuple<BaseItem, ItemCounts>> GetGameGenres(InternalItemsQuery query);
+        QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query);
+        QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query);
+        QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query);
     }
 }

+ 3 - 1
MediaBrowser.Controller/Library/IUserDataManager.cs

@@ -40,7 +40,9 @@ namespace MediaBrowser.Controller.Library
         /// <param name="item">The item.</param>
         /// <param name="user">The user.</param>
         /// <returns>UserItemDataDto.</returns>
-        UserItemDataDto GetUserDataDto(IHasUserData item, User user);
+        Task<UserItemDataDto> GetUserDataDto(IHasUserData item, User user);
+
+        Task<UserItemDataDto> GetUserDataDto(IHasUserData item, BaseItemDto itemDto, User user);
 
         /// <summary>
         /// Get all user data for the given user

+ 1 - 0
MediaBrowser.Controller/LiveTv/IListingsProvider.cs

@@ -15,5 +15,6 @@ namespace MediaBrowser.Controller.LiveTv
         Task AddMetadata(ListingsProviderInfo info, List<ChannelInfo> channels, CancellationToken cancellationToken);
         Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings);
         Task<List<NameIdPair>> GetLineups(ListingsProviderInfo info, string country, string location);
+        Task<List<ChannelInfo>> GetChannels(ListingsProviderInfo info, CancellationToken cancellationToken);
     }
 }

+ 18 - 0
MediaBrowser.Controller/LiveTv/ILiveTvManager.cs

@@ -8,6 +8,7 @@ using MediaBrowser.Model.Querying;
 using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Events;
 
 namespace MediaBrowser.Controller.LiveTv
 {
@@ -344,6 +345,13 @@ namespace MediaBrowser.Controller.LiveTv
         /// <param name="validateListings">if set to <c>true</c> [validate listings].</param>
         /// <returns>Task.</returns>
         Task<ListingsProviderInfo> SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings);
+
+        void DeleteListingsProvider(string id);
+
+        Task<TunerChannelMapping> SetChannelMapping(string providerId, string tunerChannelNumber, string providerChannelNumber);
+
+        TunerChannelMapping GetTunerChannelMapping(ChannelInfo channel, List<NameValuePair> mappings, List<ChannelInfo> providerChannels);
+
         /// <summary>
         /// Gets the lineups.
         /// </summary>
@@ -385,5 +393,15 @@ namespace MediaBrowser.Controller.LiveTv
         List<NameValuePair> GetSatIniMappings();
 
         Task<List<ChannelInfo>> GetSatChannelScanResult(TunerHostInfo info, CancellationToken cancellationToken);
+
+        Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken);
+        Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken);
+
+        List<IListingsProvider> ListingProviders { get;}
+
+        event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
+        event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCancelled;
+        event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCreated;
+        event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCreated;
     }
 }

+ 19 - 0
MediaBrowser.Controller/LiveTv/ILiveTvService.cs

@@ -226,4 +226,23 @@ namespace MediaBrowser.Controller.LiveTv
         /// <returns>Task.</returns>
         Task ResetTuner(string id, CancellationToken cancellationToken);
     }
+
+    public interface ISupportsNewTimerIds
+    {
+        /// <summary>
+        /// Creates the timer asynchronous.
+        /// </summary>
+        /// <param name="info">The information.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task<string> CreateTimer(TimerInfo info, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Creates the series timer asynchronous.
+        /// </summary>
+        /// <param name="info">The information.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken);
+    }
 }

+ 21 - 0
MediaBrowser.Controller/LiveTv/LiveTvProgram.cs

@@ -7,6 +7,7 @@ using System;
 using System.Collections.Generic;
 using System.Runtime.Serialization;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
 
 namespace MediaBrowser.Controller.LiveTv
 {
@@ -235,5 +236,25 @@ namespace MediaBrowser.Controller.LiveTv
                 return false;
             }
         }
+
+        public override List<ExternalUrl> GetRelatedUrls()
+        {
+            var list = base.GetRelatedUrls();
+
+            var imdbId = this.GetProviderId(MetadataProviders.Imdb);
+            if (!string.IsNullOrWhiteSpace(imdbId))
+            {
+                if (IsMovie)
+                {
+                    list.Add(new ExternalUrl
+                    {
+                        Name = "Trakt",
+                        Url = string.Format("https://trakt.tv/movies/{0}", imdbId)
+                    });
+                }
+            }
+
+            return list;
+        }
     }
 }

+ 14 - 0
MediaBrowser.Controller/LiveTv/TimerEventInfo.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+    public class TimerEventInfo
+    {
+        public string Id { get; set; }
+        public string ProgramId { get; set; }
+    }
+}

+ 16 - 0
MediaBrowser.Controller/LiveTv/TunerChannelMapping.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.LiveTv
+{
+    public class TunerChannelMapping
+    {
+        public string Name { get; set; }
+        public string Number { get; set; }
+        public string ProviderChannelNumber { get; set; }
+        public string ProviderChannelName { get; set; }
+    }
+}

+ 4 - 10
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -142,7 +142,7 @@
     <Compile Include="Entities\IHasDisplayOrder.cs" />
     <Compile Include="Entities\IHasId.cs" />
     <Compile Include="Entities\IHasImages.cs" />
-    <Compile Include="Entities\IHasKeywords.cs" />
+    <Compile Include="Entities\KeywordExtensions.cs" />
     <Compile Include="Entities\IHasMediaSources.cs" />
     <Compile Include="Entities\IHasMetascore.cs" />
     <Compile Include="Entities\IHasOriginalTitle.cs" />
@@ -154,7 +154,6 @@
     <Compile Include="Entities\IHasSpecialFeatures.cs" />
     <Compile Include="Entities\IHasStartDate.cs" />
     <Compile Include="Entities\IHasTaglines.cs" />
-    <Compile Include="Entities\IHasTags.cs" />
     <Compile Include="Entities\IHasThemeMedia.cs" />
     <Compile Include="Entities\IHasTrailers.cs" />
     <Compile Include="Entities\IHasUserData.cs" />
@@ -177,6 +176,7 @@
     <Compile Include="Entities\PhotoAlbum.cs" />
     <Compile Include="Entities\Share.cs" />
     <Compile Include="Entities\SourceType.cs" />
+    <Compile Include="Entities\TagExtensions.cs" />
     <Compile Include="Entities\UserView.cs" />
     <Compile Include="Entities\UserViewBuilder.cs" />
     <Compile Include="FileOrganization\IFileOrganizationService.cs" />
@@ -218,7 +218,9 @@
     <Compile Include="LiveTv\ProgramInfo.cs" />
     <Compile Include="LiveTv\RecordingInfo.cs" />
     <Compile Include="LiveTv\SeriesTimerInfo.cs" />
+    <Compile Include="LiveTv\TimerEventInfo.cs" />
     <Compile Include="LiveTv\TimerInfo.cs" />
+    <Compile Include="LiveTv\TunerChannelMapping.cs" />
     <Compile Include="Localization\ILocalizationManager.cs" />
     <Compile Include="MediaEncoding\ChapterImageRefreshOptions.cs" />
     <Compile Include="MediaEncoding\EncodingJobOptions.cs" />
@@ -290,21 +292,17 @@
     <Compile Include="Providers\IImageFileSaver.cs" />
     <Compile Include="Providers\IImageProvider.cs" />
     <Compile Include="Providers\IImageSaver.cs" />
-    <Compile Include="Providers\IItemIdentityConverter.cs" />
-    <Compile Include="Providers\IItemIdentityProvider.cs" />
     <Compile Include="Providers\ILocalImageFileProvider.cs" />
     <Compile Include="Providers\ILocalMetadataProvider.cs" />
     <Compile Include="Providers\ImageRefreshMode.cs" />
     <Compile Include="Providers\ImageRefreshOptions.cs" />
     <Compile Include="Providers\IPreRefreshProvider.cs" />
-    <Compile Include="Providers\IProviderRepository.cs" />
     <Compile Include="Providers\IRemoteImageProvider.cs" />
     <Compile Include="Providers\ILocalImageProvider.cs" />
     <Compile Include="Providers\IMetadataProvider.cs" />
     <Compile Include="Providers\IMetadataService.cs" />
     <Compile Include="Providers\IRemoteMetadataProvider.cs" />
     <Compile Include="Providers\IRemoteSearchProvider.cs" />
-    <Compile Include="Providers\ISeriesOrderProvider.cs" />
     <Compile Include="Providers\ItemInfo.cs" />
     <Compile Include="Providers\LiveTvProgramLookupInfo.cs" />
     <Compile Include="Providers\LocalImageInfo.cs" />
@@ -330,12 +328,8 @@
     <Compile Include="Sorting\SortHelper.cs" />
     <Compile Include="Subtitles\ISubtitleManager.cs" />
     <Compile Include="Subtitles\ISubtitleProvider.cs" />
-    <Compile Include="Providers\ItemIdentifier.cs" />
-    <Compile Include="Providers\ItemIdentities.cs" />
     <Compile Include="Providers\ItemLookupInfo.cs" />
     <Compile Include="Providers\MetadataRefreshOptions.cs" />
-    <Compile Include="Providers\MetadataStatus.cs" />
-    <Compile Include="Providers\ISeriesOrderManager.cs" />
     <Compile Include="Session\ISessionManager.cs" />
     <Compile Include="Entities\AggregateFolder.cs" />
     <Compile Include="Entities\Audio\Audio.cs" />

+ 3 - 2
MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Dlna;
+using System.Linq;
+using MediaBrowser.Model.Dlna;
 
 namespace MediaBrowser.Controller.MediaEncoding
 {
@@ -74,7 +75,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             Level = info.VideoLevel;
             ItemId = info.ItemId;
             MediaSourceId = info.MediaSourceId;
-            AudioCodec = info.AudioCodec;
+            AudioCodec = info.TargetAudioCodec;
             MaxAudioChannels = info.MaxAudioChannels;
             AudioBitRate = info.AudioBitrate;
             AudioSampleRate = info.TargetAudioSampleRate;

+ 12 - 12
MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs

@@ -13,18 +13,14 @@ namespace MediaBrowser.Controller.MediaEncoding
     /// </summary>
     public interface IMediaEncoder : ITranscoderSupport
     {
+        string EncoderLocationType { get; }
+
         /// <summary>
         /// Gets the encoder path.
         /// </summary>
         /// <value>The encoder path.</value>
         string EncoderPath { get; }
 
-        /// <summary>
-        /// Gets the version.
-        /// </summary>
-        /// <value>The version.</value>
-        string Version { get; }
-
         /// <summary>
         /// Supportses the decoder.
         /// </summary>
@@ -66,12 +62,12 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// <param name="maxWidth">The maximum width.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        Task ExtractVideoImagesOnInterval(string[] inputFiles, 
-            MediaProtocol protocol, 
-            Video3DFormat? threedFormat, 
-            TimeSpan interval, 
-            string targetDirectory, 
-            string filenamePrefix, 
+        Task ExtractVideoImagesOnInterval(string[] inputFiles,
+            MediaProtocol protocol,
+            Video3DFormat? threedFormat,
+            TimeSpan interval,
+            string targetDirectory,
+            string filenamePrefix,
             int? maxWidth,
             CancellationToken cancellationToken);
 
@@ -134,5 +130,9 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// <param name="path">The path.</param>
         /// <returns>System.String.</returns>
         string EscapeSubtitleFilterPath(string path);
+
+        Task Init();
+
+        Task UpdateEncoderPath(string path, string pathType);
     }
 }

+ 1 - 7
MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs

@@ -10,13 +10,6 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// <summary>
         /// Gets the subtitles.
         /// </summary>
-        /// <param name="itemId">The item identifier.</param>
-        /// <param name="mediaSourceId">The media source identifier.</param>
-        /// <param name="subtitleStreamIndex">Index of the subtitle stream.</param>
-        /// <param name="outputFormat">The output format.</param>
-        /// <param name="startTimeTicks">The start time ticks.</param>
-        /// <param name="endTimeTicks">The end time ticks.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{Stream}.</returns>
         Task<Stream> GetSubtitles(string itemId,
             string mediaSourceId,
@@ -24,6 +17,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             string outputFormat,
             long startTimeTicks,
             long? endTimeTicks,
+            bool preserveOriginalTimestamps,
             CancellationToken cancellationToken);
 
         /// <summary>

+ 4 - 4
MediaBrowser.Controller/Net/IHttpResultFactory.cs

@@ -80,7 +80,7 @@ namespace MediaBrowser.Controller.Net
         /// <param name="responseHeaders">The response headers.</param>
         /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
         /// <returns>System.Object.</returns>
-        object GetStaticResult(IRequest requestContext, 
+        Task<object> GetStaticResult(IRequest requestContext, 
             Guid cacheKey, 
             DateTime? lastDateModified,
             TimeSpan? cacheDuration, 
@@ -94,7 +94,7 @@ namespace MediaBrowser.Controller.Net
         /// <param name="requestContext">The request context.</param>
         /// <param name="options">The options.</param>
         /// <returns>System.Object.</returns>
-        object GetStaticResult(IRequest requestContext, StaticResultOptions options);
+        Task<object> GetStaticResult(IRequest requestContext, StaticResultOptions options);
 
         /// <summary>
         /// Gets the static file result.
@@ -103,7 +103,7 @@ namespace MediaBrowser.Controller.Net
         /// <param name="path">The path.</param>
         /// <param name="fileShare">The file share.</param>
         /// <returns>System.Object.</returns>
-        object GetStaticFileResult(IRequest requestContext, string path, FileShare fileShare = FileShare.Read);
+        Task<object> GetStaticFileResult(IRequest requestContext, string path, FileShare fileShare = FileShare.Read);
 
         /// <summary>
         /// Gets the static file result.
@@ -111,7 +111,7 @@ namespace MediaBrowser.Controller.Net
         /// <param name="requestContext">The request context.</param>
         /// <param name="options">The options.</param>
         /// <returns>System.Object.</returns>
-        object GetStaticFileResult(IRequest requestContext, 
+        Task<object> GetStaticFileResult(IRequest requestContext, 
             StaticFileResultOptions options);
     }
 }

+ 11 - 3
MediaBrowser.Controller/Persistence/IItemRepository.cs

@@ -4,6 +4,7 @@ using System;
 using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Querying;
 
 namespace MediaBrowser.Controller.Persistence
@@ -81,7 +82,7 @@ namespace MediaBrowser.Controller.Persistence
         /// <param name="chapters">The chapters.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        Task SaveChapters(Guid id, IEnumerable<ChapterInfo> chapters, CancellationToken cancellationToken);
+        Task SaveChapters(Guid id, List<ChapterInfo> chapters, CancellationToken cancellationToken);
 
         /// <summary>
         /// Gets the media streams.
@@ -97,7 +98,7 @@ namespace MediaBrowser.Controller.Persistence
         /// <param name="streams">The streams.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        Task SaveMediaStreams(Guid id, IEnumerable<MediaStream> streams, CancellationToken cancellationToken);
+        Task SaveMediaStreams(Guid id, List<MediaStream> streams, CancellationToken cancellationToken);
 
         /// <summary>
         /// Gets the item ids.
@@ -153,7 +154,7 @@ namespace MediaBrowser.Controller.Persistence
         /// </summary>
         /// <param name="query">The query.</param>
         /// <returns>List&lt;BaseItem&gt;.</returns>
-        IEnumerable<BaseItem> GetItemList(InternalItemsQuery query);
+        List<BaseItem> GetItemList(InternalItemsQuery query);
 
         /// <summary>
         /// Updates the inherited values.
@@ -161,6 +162,13 @@ namespace MediaBrowser.Controller.Persistence
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         Task UpdateInheritedValues(CancellationToken cancellationToken);
+
+        QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query);
+        QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query);
+        QueryResult<Tuple<BaseItem, ItemCounts>> GetGameGenres(InternalItemsQuery query);
+        QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query);
+        QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query);
+        QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query);
     }
 }
 

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.