Browse Source

Merge pull request #15 from MediaBrowser/dev

Dev
hatharry 9 years ago
parent
commit
f21f9923de
100 changed files with 1873 additions and 2944 deletions
  1. 0 1
      Emby.Drawing/Emby.Drawing.csproj
  2. 3 14
      Emby.Drawing/ImageMagick/ImageMagickEncoder.cs
  3. 29 328
      Emby.Drawing/ImageMagick/StripCollageBuilder.cs
  4. 29 24
      Emby.Drawing/ImageProcessor.cs
  5. 12 5
      MediaBrowser.Api/ApiEntryPoint.cs
  6. 79 112
      MediaBrowser.Api/BaseApiService.cs
  7. 1 0
      MediaBrowser.Api/BrandingService.cs
  8. 21 1
      MediaBrowser.Api/ConfigurationService.cs
  9. 2 4
      MediaBrowser.Api/Dlna/DlnaService.cs
  10. 34 1
      MediaBrowser.Api/EnvironmentService.cs
  11. 3 2
      MediaBrowser.Api/FilterService.cs
  12. 40 15
      MediaBrowser.Api/GamesService.cs
  13. 11 8
      MediaBrowser.Api/Images/ImageService.cs
  14. 5 5
      MediaBrowser.Api/Images/RemoteImageService.cs
  15. 40 24
      MediaBrowser.Api/ItemLookupService.cs
  16. 19 31
      MediaBrowser.Api/ItemUpdateService.cs
  17. 0 2
      MediaBrowser.Api/Library/FileOrganizationService.cs
  18. 0 89
      MediaBrowser.Api/Library/LibraryHelpers.cs
  19. 7 4
      MediaBrowser.Api/Library/LibraryService.cs
  20. 4 111
      MediaBrowser.Api/Library/LibraryStructureService.cs
  21. 132 15
      MediaBrowser.Api/LiveTv/LiveTvService.cs
  22. 0 3
      MediaBrowser.Api/MediaBrowser.Api.csproj
  23. 116 147
      MediaBrowser.Api/Movies/MoviesService.cs
  24. 1 1
      MediaBrowser.Api/Movies/TrailersService.cs
  25. 7 6
      MediaBrowser.Api/Music/AlbumsService.cs
  26. 11 10
      MediaBrowser.Api/Music/InstantMixService.cs
  27. 7 5
      MediaBrowser.Api/PackageReviewService.cs
  28. 179 72
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  29. 0 224
      MediaBrowser.Api/Playback/Dash/ManifestBuilder.cs
  30. 0 547
      MediaBrowser.Api/Playback/Dash/MpegDashService.cs
  31. 12 41
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  32. 42 34
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  33. 5 23
      MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
  34. 7 14
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  35. 52 9
      MediaBrowser.Api/Playback/MediaInfoService.cs
  36. 3 2
      MediaBrowser.Api/Playback/Progressive/AudioService.cs
  37. 22 17
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  38. 18 121
      MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
  39. 4 6
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  40. 8 5
      MediaBrowser.Api/Playback/StreamRequest.cs
  41. 25 15
      MediaBrowser.Api/Playback/StreamState.cs
  42. 2 2
      MediaBrowser.Api/PlaylistService.cs
  43. 89 79
      MediaBrowser.Api/Reports/ReportsService.cs
  44. 17 19
      MediaBrowser.Api/SimilarItemsHelper.cs
  45. 19 9
      MediaBrowser.Api/StartupWizardService.cs
  46. 28 11
      MediaBrowser.Api/Subtitles/SubtitleService.cs
  47. 9 10
      MediaBrowser.Api/Sync/SyncService.cs
  48. 1 1
      MediaBrowser.Api/System/SystemInfoWebSocketListener.cs
  49. 8 8
      MediaBrowser.Api/System/SystemService.cs
  50. 80 39
      MediaBrowser.Api/TvShowsService.cs
  51. 27 13
      MediaBrowser.Api/UserLibrary/ArtistsService.cs
  52. 146 12
      MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
  53. 13 4
      MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
  54. 8 21
      MediaBrowser.Api/UserLibrary/GameGenresService.cs
  55. 17 44
      MediaBrowser.Api/UserLibrary/GenresService.cs
  56. 67 27
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  57. 11 10
      MediaBrowser.Api/UserLibrary/MusicGenresService.cs
  58. 3 3
      MediaBrowser.Api/UserLibrary/PlaystateService.cs
  59. 9 2
      MediaBrowser.Api/UserLibrary/StudiosService.cs
  60. 8 12
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  61. 7 2
      MediaBrowser.Api/UserService.cs
  62. 5 11
      MediaBrowser.Api/VideosService.cs
  63. 4 4
      MediaBrowser.Common.Implementations/BaseApplicationHost.cs
  64. 1 0
      MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs
  65. 41 11
      MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
  66. 6 4
      MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
  67. 22 23
      MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
  68. 2 2
      MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs
  69. 1 1
      MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
  70. 2 2
      MediaBrowser.Common.Implementations/Security/MbAdmin.cs
  71. 1 1
      MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs
  72. 6 2
      MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs
  73. 10 5
      MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs
  74. 6 3
      MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs
  75. 1 0
      MediaBrowser.Common.Implementations/Updates/InstallationManager.cs
  76. 3 3
      MediaBrowser.Common.Implementations/packages.config
  77. 3 0
      MediaBrowser.Common/Net/HttpRequestOptions.cs
  78. 8 2
      MediaBrowser.Common/ScheduledTasks/DailyTrigger.cs
  79. 2 1
      MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs
  80. 11 2
      MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs
  81. 2 1
      MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs
  82. 2 1
      MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs
  83. 2 1
      MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs
  84. 1 1
      MediaBrowser.Controller/Channels/Channel.cs
  85. 0 101
      MediaBrowser.Controller/Channels/ChannelAudioItem.cs
  86. 0 89
      MediaBrowser.Controller/Channels/ChannelFolderItem.cs
  87. 8 0
      MediaBrowser.Controller/Channels/ChannelItemInfo.cs
  88. 1 1
      MediaBrowser.Controller/Channels/ChannelMediaInfo.cs
  89. 0 126
      MediaBrowser.Controller/Channels/ChannelVideoItem.cs
  90. 0 11
      MediaBrowser.Controller/Channels/IChannelItem.cs
  91. 0 18
      MediaBrowser.Controller/Channels/IChannelMediaItem.cs
  92. 1 1
      MediaBrowser.Controller/Chapters/IChapterManager.cs
  93. 1 1
      MediaBrowser.Controller/Drawing/IImageProcessor.cs
  94. 0 5
      MediaBrowser.Controller/Drawing/ImageCollageOptions.cs
  95. 2 1
      MediaBrowser.Controller/Dto/IDtoService.cs
  96. 34 3
      MediaBrowser.Controller/Entities/AggregateFolder.cs
  97. 22 27
      MediaBrowser.Controller/Entities/Audio/Audio.cs
  98. 27 37
      MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
  99. 54 10
      MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
  100. 22 6
      MediaBrowser.Controller/Entities/Audio/MusicGenre.cs

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

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

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

@@ -111,7 +111,6 @@ namespace Emby.Drawing.ImageMagick
                 wand.CurrentImage.TrimImage(10);
                 wand.CurrentImage.TrimImage(10);
                 wand.SaveImage(outputPath);
                 wand.SaveImage(outputPath);
             }
             }
-            SaveDelay();
         }
         }
 
 
         public ImageSize GetImageSize(string path)
         public ImageSize GetImageSize(string path)
@@ -189,7 +188,6 @@ namespace Emby.Drawing.ImageMagick
                     }
                     }
                 }
                 }
             }
             }
-            SaveDelay();
         }
         }
 
 
         private void AddForegroundLayer(MagickWand wand, ImageProcessingOptions options)
         private void AddForegroundLayer(MagickWand wand, ImageProcessingOptions options)
@@ -284,25 +282,16 @@ namespace Emby.Drawing.ImageMagick
 
 
             if (ratio >= 1.4)
             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)
             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
             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
         public string Name

+ 29 - 328
Emby.Drawing/ImageMagick/StripCollageBuilder.cs

@@ -9,140 +9,35 @@ namespace Emby.Drawing.ImageMagick
     public class StripCollageBuilder
     public class StripCollageBuilder
     {
     {
         private readonly IApplicationPaths _appPaths;
         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;
             _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)
         private MagickWand BuildThumbCollageWand(List<string> paths, int width, int height)
         {
         {
             var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
             var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
@@ -295,9 +115,9 @@ namespace Emby.Drawing.ImageMagick
                 wand.OpenImage("gradient:#111111-#111111");
                 wand.OpenImage("gradient:#111111-#111111");
                 using (var draw = new DrawingWand())
                 using (var draw = new DrawingWand())
                 {
                 {
-                    var iSlice = Convert.ToInt32(width * .1166666667 * 2);
+                    var iSlice = Convert.ToInt32(width * 0.24125);
                     int iTrans = Convert.ToInt32(height * .25);
                     int iTrans = Convert.ToInt32(height * .25);
-                    int iHeight = Convert.ToInt32(height * .62);
+                    int iHeight = Convert.ToInt32(height * .70);
                     var horizontalImagePadding = Convert.ToInt32(width * 0.0125);
                     var horizontalImagePadding = Convert.ToInt32(width * 0.0125);
 
 
                     foreach (var element in wandImages.ImageList)
                     foreach (var element in wandImages.ImageList)
@@ -339,7 +159,7 @@ namespace Emby.Drawing.ImageMagick
 
 
                                         wandList.AddImage(mwr);
                                         wandList.AddImage(mwr);
                                         int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2;
                                         int ex = (int)(wand.CurrentImage.Width - mwg.CurrentImage.Width) / 2;
-                                        wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .085));
+                                        wand.CurrentImage.CompositeImage(wandList.AppendImages(true), CompositeOperator.AtopCompositeOp, ex, Convert.ToInt32(height * .045));
                                     }
                                     }
                                 }
                                 }
                             }
                             }
@@ -352,148 +172,29 @@ namespace Emby.Drawing.ImageMagick
         }
         }
 
 
         private MagickWand BuildSquareCollageWand(List<string> paths, int width, int height)
         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 * .3);
-                    int iTrans = Convert.ToInt32(height * .25);
-                    int iHeight = Convert.ToInt32(height * .63);
-                    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 * .07));
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-
-                return wand;
-            }
-        }
-
-        private MagickWand BuildSquareCollageWandWithText(List<string> paths, string label, int width, int height)
         {
         {
             var inputPaths = ImageHelpers.ProjectPaths(paths, 4);
             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;
         }
         }
     }
     }
 }
 }

+ 29 - 24
Emby.Drawing/ImageProcessor.cs

@@ -163,7 +163,7 @@ namespace Emby.Drawing
             return _imageEncoder.SupportedOutputFormats;
             return _imageEncoder.SupportedOutputFormats;
         }
         }
 
 
-        public async Task<Tuple<string, string>> ProcessImage(ImageProcessingOptions options)
+        public async Task<Tuple<string, string, DateTime>> ProcessImage(ImageProcessingOptions options)
         {
         {
             if (options == null)
             if (options == null)
             {
             {
@@ -178,14 +178,13 @@ namespace Emby.Drawing
             }
             }
 
 
             var originalImagePath = originalImage.Path;
             var originalImagePath = originalImage.Path;
+            var dateModified = originalImage.DateModified;
 
 
             if (!_imageEncoder.SupportsImageEncoding)
             if (!_imageEncoder.SupportsImageEncoding)
             {
             {
-                return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
+                return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
             }
             }
 
 
-            var dateModified = originalImage.DateModified;
-
             if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding)
             if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding)
             {
             {
                 var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false);
                 var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false);
@@ -211,7 +210,7 @@ namespace Emby.Drawing
             if (options.HasDefaultOptions(originalImagePath))
             if (options.HasDefaultOptions(originalImagePath))
             {
             {
                 // Just spit out the original file if all the options are default
                 // Just spit out the original file if all the options are default
-                return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
+                return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
             }
             }
 
 
             ImageSize? originalImageSize;
             ImageSize? originalImageSize;
@@ -221,7 +220,7 @@ namespace Emby.Drawing
                 if (options.HasDefaultOptions(originalImagePath, originalImageSize.Value))
                 if (options.HasDefaultOptions(originalImagePath, originalImageSize.Value))
                 {
                 {
                     // Just spit out the original file if all the options are default
                     // Just spit out the original file if all the options are default
-                    return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
+                    return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
                 }
                 }
             }
             }
             catch
             catch
@@ -235,10 +234,6 @@ namespace Emby.Drawing
             var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]);
             var outputFormat = GetOutputFormat(options.SupportedOutputFormats[0]);
             var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor, options.ForegroundLayer);
             var cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.BackgroundColor, options.ForegroundLayer);
 
 
-            var semaphore = GetLock(cacheFilePath);
-
-            await semaphore.WaitAsync().ConfigureAwait(false);
-
             var imageProcessingLockTaken = false;
             var imageProcessingLockTaken = false;
 
 
             try
             try
@@ -251,15 +246,20 @@ namespace Emby.Drawing
                     var newHeight = Convert.ToInt32(newSize.Height);
                     var newHeight = Convert.ToInt32(newSize.Height);
 
 
                     _fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
                     _fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
+                    var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(cacheFilePath));
+                    _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
 
 
                     await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
                     await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
 
 
                     imageProcessingLockTaken = true;
                     imageProcessingLockTaken = true;
 
 
-                    _imageEncoder.EncodeImage(originalImagePath, cacheFilePath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
+                    _imageEncoder.EncodeImage(originalImagePath, tmpPath, AutoOrient(options.Item), newWidth, newHeight, quality, options, outputFormat);
+                    CopyFile(tmpPath, cacheFilePath);
+
+                    return new Tuple<string, string, DateTime>(tmpPath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(tmpPath));
                 }
                 }
 
 
-                return new Tuple<string, string>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath));
+                return new Tuple<string, string, DateTime>(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath));
             }
             }
             catch (Exception ex)
             catch (Exception ex)
             {
             {
@@ -267,7 +267,7 @@ namespace Emby.Drawing
                 _logger.ErrorException("Error encoding image", ex);
                 _logger.ErrorException("Error encoding image", ex);
 
 
                 // Just spit out the original file if all the options are default
                 // Just spit out the original file if all the options are default
-                return new Tuple<string, string>(originalImagePath, MimeTypes.GetMimeType(originalImagePath));
+                return new Tuple<string, string, DateTime>(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
             }
             }
             finally
             finally
             {
             {
@@ -275,8 +275,18 @@ namespace Emby.Drawing
                 {
                 {
                     _imageProcessingSemaphore.Release();
                     _imageProcessingSemaphore.Release();
                 }
                 }
+            }
+        }
+
+        private void CopyFile(string src, string destination)
+        {
+            try
+            {
+                File.Copy(src, destination, true);
+            }
+            catch
+            {
 
 
-                semaphore.Release();
             }
             }
         }
         }
 
 
@@ -412,14 +422,9 @@ namespace Emby.Drawing
 
 
             var croppedImagePath = GetCachePath(CroppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath));
             var croppedImagePath = GetCachePath(CroppedWhitespaceImageCachePath, name, Path.GetExtension(originalImagePath));
 
 
-            var semaphore = GetLock(croppedImagePath);
-
-            await semaphore.WaitAsync().ConfigureAwait(false);
-
             // Check again in case of contention
             // Check again in case of contention
             if (_fileSystem.FileExists(croppedImagePath))
             if (_fileSystem.FileExists(croppedImagePath))
             {
             {
-                semaphore.Release();
                 return GetResult(croppedImagePath);
                 return GetResult(croppedImagePath);
             }
             }
 
 
@@ -428,11 +433,15 @@ namespace Emby.Drawing
             try
             try
             {
             {
                 _fileSystem.CreateDirectory(Path.GetDirectoryName(croppedImagePath));
                 _fileSystem.CreateDirectory(Path.GetDirectoryName(croppedImagePath));
+                var tmpPath = Path.ChangeExtension(Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")), Path.GetExtension(croppedImagePath));
+                _fileSystem.CreateDirectory(Path.GetDirectoryName(tmpPath));
 
 
                 await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
                 await _imageProcessingSemaphore.WaitAsync().ConfigureAwait(false);
                 imageProcessingLockTaken = true;
                 imageProcessingLockTaken = true;
 
 
-                _imageEncoder.CropWhiteSpace(originalImagePath, croppedImagePath);
+                _imageEncoder.CropWhiteSpace(originalImagePath, tmpPath);
+                CopyFile(tmpPath, croppedImagePath);
+                return GetResult(tmpPath);
             }
             }
             catch (NotImplementedException)
             catch (NotImplementedException)
             {
             {
@@ -452,11 +461,7 @@ namespace Emby.Drawing
                 {
                 {
                     _imageProcessingSemaphore.Release();
                     _imageProcessingSemaphore.Release();
                 }
                 }
-
-                semaphore.Release();
             }
             }
-
-            return GetResult(croppedImagePath);
         }
         }
 
 
         private Tuple<string, DateTime> GetResult(string path)
         private Tuple<string, DateTime> GetResult(string path)

+ 12 - 5
MediaBrowser.Api/ApiEntryPoint.cs

@@ -237,9 +237,12 @@ namespace MediaBrowser.Api
         {
         {
             lock (_activeTranscodingJobs)
             lock (_activeTranscodingJobs)
             {
             {
-                var job = _activeTranscodingJobs.First(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
+                var job = _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
 
 
-                _activeTranscodingJobs.Remove(job);
+                if (job != null)
+                {
+                    _activeTranscodingJobs.Remove(job);
+                }
             }
             }
 
 
             if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
             if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
@@ -349,7 +352,7 @@ namespace MediaBrowser.Api
 
 
             if (job.Type != TranscodingJobType.Progressive)
             if (job.Type != TranscodingJobType.Progressive)
             {
             {
-                timerDuration = 1800000;
+                timerDuration = 60000;
             }
             }
 
 
             job.PingTimeout = timerDuration;
             job.PingTimeout = timerDuration;
@@ -488,13 +491,17 @@ namespace MediaBrowser.Api
                 {
                 {
                     try
                     try
                     {
                     {
-                        Logger.Info("Killing ffmpeg process for {0}", job.Path);
+                        Logger.Info("Stopping ffmpeg process with q command for {0}", job.Path);
 
 
                         //process.Kill();
                         //process.Kill();
                         process.StandardInput.WriteLine("q");
                         process.StandardInput.WriteLine("q");
 
 
                         // Need to wait because killing is asynchronous
                         // Need to wait because killing is asynchronous
-                        process.WaitForExit(5000);
+                        if (!process.WaitForExit(5000))
+                        {
+                            Logger.Info("Killing ffmpeg process for {0}", job.Path);
+                            process.Kill();
+                        }
                     }
                     }
                     catch (Exception ex)
                     catch (Exception ex)
                     {
                     {

+ 79 - 112
MediaBrowser.Api/BaseApiService.cs

@@ -79,7 +79,7 @@ namespace MediaBrowser.Api
                 }
                 }
             }
             }
         }
         }
-        
+
         /// <summary>
         /// <summary>
         /// To the optimized serialized result using cache.
         /// To the optimized serialized result using cache.
         /// </summary>
         /// </summary>
@@ -115,12 +115,9 @@ namespace MediaBrowser.Api
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         protected object ToStaticFileResult(string path)
         protected object ToStaticFileResult(string path)
         {
         {
-            return ResultFactory.GetStaticFileResult(Request, path);
+            return ResultFactory.GetStaticFileResult(Request, path).Result;
         }
         }
 
 
-        private readonly char[] _dashReplaceChars = { '?', '/', '&' };
-        private const char SlugChar = '-';
-
         protected DtoOptions GetDtoOptions(object request)
         protected DtoOptions GetDtoOptions(object request)
         {
         {
             var options = new DtoOptions();
             var options = new DtoOptions();
@@ -154,152 +151,122 @@ namespace MediaBrowser.Api
 
 
         protected MusicArtist GetArtist(string name, ILibraryManager libraryManager)
         protected MusicArtist GetArtist(string name, ILibraryManager libraryManager)
         {
         {
-            return libraryManager.GetArtist(DeSlugArtistName(name, libraryManager));
-        }
-
-        protected Studio GetStudio(string name, ILibraryManager libraryManager)
-        {
-            return libraryManager.GetStudio(DeSlugStudioName(name, libraryManager));
-        }
-
-        protected Genre GetGenre(string name, ILibraryManager libraryManager)
-        {
-            return libraryManager.GetGenre(DeSlugGenreName(name, libraryManager));
-        }
+            if (name.IndexOf(BaseItem.SlugChar) != -1)
+            {
+                var result = libraryManager.GetItemList(new InternalItemsQuery
+                {
+                    SlugName = name,
+                    IncludeItemTypes = new[] { typeof(MusicArtist).Name }
 
 
-        protected MusicGenre GetMusicGenre(string name, ILibraryManager libraryManager)
-        {
-            return libraryManager.GetMusicGenre(DeSlugGenreName(name, libraryManager));
-        }
+                }).OfType<MusicArtist>().FirstOrDefault();
 
 
-        protected GameGenre GetGameGenre(string name, ILibraryManager libraryManager)
-        {
-            return libraryManager.GetGameGenre(DeSlugGameGenreName(name, libraryManager));
-        }
+                if (result != null)
+                {
+                    return result;
+                }
+            }
 
 
-        protected Person GetPerson(string name, ILibraryManager libraryManager)
-        {
-            return libraryManager.GetPerson(DeSlugPersonName(name, libraryManager));
+            return libraryManager.GetArtist(name);
         }
         }
 
 
-        /// <summary>
-        /// Deslugs an artist name by finding the correct entry in the library
-        /// </summary>
-        /// <param name="name"></param>
-        /// <param name="libraryManager"></param>
-        /// <returns></returns>
-        protected string DeSlugArtistName(string name, ILibraryManager libraryManager)
+        protected Studio GetStudio(string name, ILibraryManager libraryManager)
         {
         {
-            if (name.IndexOf(SlugChar) == -1)
-            {
-                return name;
-            }
-
-            var items = libraryManager.GetItemList(new InternalItemsQuery
+            if (name.IndexOf(BaseItem.SlugChar) != -1)
             {
             {
-                IncludeItemTypes = new[] { typeof(Audio).Name, typeof(MusicVideo).Name, typeof(MusicAlbum).Name }
-            });
-
-            return items
-                .OfType<IHasArtist>()
-                .SelectMany(i => i.AllArtists)
-                .DistinctNames()
-                .FirstOrDefault(i =>
+                var result = libraryManager.GetItemList(new InternalItemsQuery
                 {
                 {
-                    i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar));
+                    SlugName = name,
+                    IncludeItemTypes = new[] { typeof(Studio).Name }
 
 
-                    return string.Equals(i, name, StringComparison.OrdinalIgnoreCase);
+                }).OfType<Studio>().FirstOrDefault();
+
+                if (result != null)
+                {
+                    return result;
+                }
+            }
 
 
-                }) ?? name;
+            return libraryManager.GetStudio(name);
         }
         }
 
 
-        /// <summary>
-        /// Deslugs a genre name by finding the correct entry in the library
-        /// </summary>
-        protected string DeSlugGenreName(string name, ILibraryManager libraryManager)
+        protected Genre GetGenre(string name, ILibraryManager libraryManager)
         {
         {
-            if (name.IndexOf(SlugChar) == -1)
+            if (name.IndexOf(BaseItem.SlugChar) != -1)
             {
             {
-                return name;
-            }
-
-            return libraryManager.RootFolder.GetRecursiveChildren()
-                .SelectMany(i => i.Genres)
-                .DistinctNames()
-                .FirstOrDefault(i =>
+                var result = libraryManager.GetItemList(new InternalItemsQuery
                 {
                 {
-                    i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar));
+                    SlugName = name,
+                    IncludeItemTypes = new[] { typeof(Genre).Name }
 
 
-                    return string.Equals(i, name, StringComparison.OrdinalIgnoreCase);
+                }).OfType<Genre>().FirstOrDefault();
+
+                if (result != null)
+                {
+                    return result;
+                }
+            }
 
 
-                }) ?? name;
+            return libraryManager.GetGenre(name);
         }
         }
 
 
-        protected string DeSlugGameGenreName(string name, ILibraryManager libraryManager)
+        protected MusicGenre GetMusicGenre(string name, ILibraryManager libraryManager)
         {
         {
-            if (name.IndexOf(SlugChar) == -1)
+            if (name.IndexOf(BaseItem.SlugChar) != -1)
             {
             {
-                return name;
-            }
+                var result = libraryManager.GetItemList(new InternalItemsQuery
+                {
+                    SlugName = name,
+                    IncludeItemTypes = new[] { typeof(MusicGenre).Name }
 
 
-            var items = libraryManager.GetItemList(new InternalItemsQuery
-            {
-                IncludeItemTypes = new[] { typeof(Game).Name }
-            });
+                }).OfType<MusicGenre>().FirstOrDefault();
 
 
-            return items
-                .SelectMany(i => i.Genres)
-                .DistinctNames()
-                .FirstOrDefault(i =>
+                if (result != null)
                 {
                 {
-                    i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar));
-
-                    return string.Equals(i, name, StringComparison.OrdinalIgnoreCase);
+                    return result;
+                }
+            }
 
 
-                }) ?? name;
+            return libraryManager.GetMusicGenre(name);
         }
         }
 
 
-        /// <summary>
-        /// Deslugs a studio name by finding the correct entry in the library
-        /// </summary>
-        protected string DeSlugStudioName(string name, ILibraryManager libraryManager)
+        protected GameGenre GetGameGenre(string name, ILibraryManager libraryManager)
         {
         {
-            if (name.IndexOf(SlugChar) == -1)
+            if (name.IndexOf(BaseItem.SlugChar) != -1)
             {
             {
-                return name;
-            }
-
-            return libraryManager.RootFolder
-                .GetRecursiveChildren()
-                .SelectMany(i => i.Studios)
-                .DistinctNames()
-                .FirstOrDefault(i =>
+                var result = libraryManager.GetItemList(new InternalItemsQuery
                 {
                 {
-                    i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar));
+                    SlugName = name,
+                    IncludeItemTypes = new[] { typeof(GameGenre).Name }
 
 
-                    return string.Equals(i, name, StringComparison.OrdinalIgnoreCase);
+                }).OfType<GameGenre>().FirstOrDefault();
 
 
-                }) ?? name;
+                if (result != null)
+                {
+                    return result;
+                }
+            }
+
+            return libraryManager.GetGameGenre(name);
         }
         }
 
 
-        /// <summary>
-        /// Deslugs a person name by finding the correct entry in the library
-        /// </summary>
-        protected string DeSlugPersonName(string name, ILibraryManager libraryManager)
+        protected Person GetPerson(string name, ILibraryManager libraryManager)
         {
         {
-            if (name.IndexOf(SlugChar) == -1)
+            if (name.IndexOf(BaseItem.SlugChar) != -1)
             {
             {
-                return name;
-            }
-
-            return libraryManager.GetPeopleNames(new InternalPeopleQuery())
-                .FirstOrDefault(i =>
+                var result = libraryManager.GetItemList(new InternalItemsQuery
                 {
                 {
-                    i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar));
+                    SlugName = name,
+                    IncludeItemTypes = new[] { typeof(Person).Name }
 
 
-                    return string.Equals(i, name, StringComparison.OrdinalIgnoreCase);
+                }).OfType<Person>().FirstOrDefault();
+
+                if (result != null)
+                {
+                    return result;
+                }
+            }
 
 
-                }) ?? name;
+            return libraryManager.GetPerson(name);
         }
         }
 
 
         protected string GetPathValue(int index)
         protected string GetPathValue(int index)

+ 1 - 0
MediaBrowser.Api/BrandingService.cs

@@ -10,6 +10,7 @@ namespace MediaBrowser.Api
     }
     }
 
 
     [Route("/Branding/Css", "GET", Summary = "Gets custom css")]
     [Route("/Branding/Css", "GET", Summary = "Gets custom css")]
+    [Route("/Branding/Css.css", "GET", Summary = "Gets custom css")]
     public class GetBrandingCss
     public class GetBrandingCss
     {
     {
     }
     }

+ 21 - 1
MediaBrowser.Api/ConfigurationService.cs

@@ -9,7 +9,9 @@ using ServiceStack.Web;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
+using System.Threading.Tasks;
 using CommonIO;
 using CommonIO;
+using MediaBrowser.Controller.MediaEncoding;
 
 
 namespace MediaBrowser.Api
 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
     public class ConfigurationService : BaseApiService
     {
     {
         /// <summary>
         /// <summary>
@@ -86,14 +98,22 @@ namespace MediaBrowser.Api
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
         private readonly IProviderManager _providerManager;
         private readonly IProviderManager _providerManager;
         private readonly ILibraryManager _libraryManager;
         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;
             _jsonSerializer = jsonSerializer;
             _configurationManager = configurationManager;
             _configurationManager = configurationManager;
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;
             _providerManager = providerManager;
             _providerManager = providerManager;
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
+            _mediaEncoder = mediaEncoder;
+        }
+
+        public void Post(UpdateMediaEncoderPath request)
+        {
+            var task = _mediaEncoder.UpdateEncoderPath(request.Path, request.PathType);
+            Task.WaitAll(task);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 2 - 4
MediaBrowser.Api/Dlna/DlnaService.cs

@@ -31,11 +31,9 @@ namespace MediaBrowser.Api.Dlna
         public string Id { get; set; }
         public string Id { get; set; }
     }
     }
 
 
-    [Route("/Dlna/Profiles/{ProfileId}", "POST", Summary = "Updates a profile")]
+    [Route("/Dlna/Profiles/{Id}", "POST", Summary = "Updates a profile")]
     public class UpdateProfile : DeviceProfile, IReturnVoid
     public class UpdateProfile : DeviceProfile, IReturnVoid
     {
     {
-        [ApiMember(Name = "ProfileId", Description = "Profile Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public string ProfileId { get; set; }
     }
     }
 
 
     [Route("/Dlna/Profiles", "POST", Summary = "Creates a profile")]
     [Route("/Dlna/Profiles", "POST", Summary = "Creates a profile")]
@@ -89,4 +87,4 @@ namespace MediaBrowser.Api.Dlna
             _dlnaManager.CreateProfile(request);
             _dlnaManager.CreateProfile(request);
         }
         }
     }
     }
-}
+}

+ 34 - 1
MediaBrowser.Api/EnvironmentService.cs

@@ -90,6 +90,17 @@ namespace MediaBrowser.Api
         public string Path { get; set; }
         public string Path { get; set; }
     }
     }
 
 
+    public class DefaultDirectoryBrowserInfo
+    {
+        public string Path { get; set; }
+    }
+
+    [Route("/Environment/DefaultDirectoryBrowser", "GET", Summary = "Gets the parent path of a given path")]
+    public class GetDefaultDirectoryBrowser : IReturn<DefaultDirectoryBrowserInfo>
+    {
+        
+    }
+
     /// <summary>
     /// <summary>
     /// Class EnvironmentService
     /// Class EnvironmentService
     /// </summary>
     /// </summary>
@@ -108,7 +119,6 @@ namespace MediaBrowser.Api
         /// Initializes a new instance of the <see cref="EnvironmentService" /> class.
         /// Initializes a new instance of the <see cref="EnvironmentService" /> class.
         /// </summary>
         /// </summary>
         /// <param name="networkManager">The network manager.</param>
         /// <param name="networkManager">The network manager.</param>
-        /// <exception cref="System.ArgumentNullException">networkManager</exception>
         public EnvironmentService(INetworkManager networkManager, IFileSystem fileSystem)
         public EnvironmentService(INetworkManager networkManager, IFileSystem fileSystem)
         {
         {
             if (networkManager == null)
             if (networkManager == null)
@@ -120,6 +130,29 @@ namespace MediaBrowser.Api
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;
         }
         }
 
 
+        public object Get(GetDefaultDirectoryBrowser request)
+        {
+            var result = new DefaultDirectoryBrowserInfo();
+
+            if (Environment.OSVersion.Platform == PlatformID.Unix)
+            {
+                try
+                {
+                    var qnap = "/share/CACHEDEV1_DATA";
+                    if (Directory.Exists(qnap))
+                    {
+                        result.Path = qnap;
+                    }
+                }
+                catch
+                {
+
+                }
+            }
+
+            return ToOptimizedResult(result);
+        }
+
         /// <summary>
         /// <summary>
         /// Gets the specified request.
         /// Gets the specified request.
         /// </summary>
         /// </summary>

+ 3 - 2
MediaBrowser.Api/FilterService.cs

@@ -80,7 +80,7 @@ namespace MediaBrowser.Api
                 .OrderBy(i => i)
                 .OrderBy(i => i)
                 .ToArray();
                 .ToArray();
 
 
-            result.Tags = items.OfType<IHasTags>()
+            result.Tags = items
                 .SelectMany(i => i.Tags)
                 .SelectMany(i => i.Tags)
                 .Distinct(StringComparer.OrdinalIgnoreCase)
                 .Distinct(StringComparer.OrdinalIgnoreCase)
                 .OrderBy(i => i)
                 .OrderBy(i => i)
@@ -103,7 +103,8 @@ namespace MediaBrowser.Api
                 User = user,
                 User = user,
                 MediaTypes = request.GetMediaTypes(),
                 MediaTypes = request.GetMediaTypes(),
                 IncludeItemTypes = request.GetIncludeItemTypes(),
                 IncludeItemTypes = request.GetIncludeItemTypes(),
-                Recursive = true
+                Recursive = true,
+                EnableTotalRecordCount = false
             };
             };
 
 
             return query;
             return query;

+ 40 - 15
MediaBrowser.Api/GamesService.cs

@@ -10,6 +10,8 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Querying;
 
 
 namespace MediaBrowser.Api
 namespace MediaBrowser.Api
 {
 {
@@ -107,8 +109,7 @@ namespace MediaBrowser.Api
             {
             {
                 IncludeItemTypes = new[] { typeof(GameSystem).Name }
                 IncludeItemTypes = new[] { typeof(GameSystem).Name }
             };
             };
-            var parentIds = new string[] { } ;
-            var gameSystems = _libraryManager.GetItemList(query, parentIds)
+            var gameSystems = _libraryManager.GetItemList(query)
                 .Cast<GameSystem>()
                 .Cast<GameSystem>()
                 .ToList();
                 .ToList();
 
 
@@ -128,8 +129,7 @@ namespace MediaBrowser.Api
             {
             {
                 IncludeItemTypes = new[] { typeof(Game).Name }
                 IncludeItemTypes = new[] { typeof(Game).Name }
             };
             };
-            var parentIds = new string[] { };
-            var games = _libraryManager.GetItemList(query, parentIds)
+            var games = _libraryManager.GetItemList(query)
                 .Cast<Game>()
                 .Cast<Game>()
                 .ToList();
                 .ToList();
 
 
@@ -162,7 +162,10 @@ namespace MediaBrowser.Api
 
 
             var items = user == null ? 
             var items = user == null ? 
                 system.GetRecursiveChildren(i => i is Game) :
                 system.GetRecursiveChildren(i => i is Game) :
-                system.GetRecursiveChildren(user, i => i is Game);
+                system.GetRecursiveChildren(user, new InternalItemsQuery(user)
+                {
+                    IncludeItemTypes = new[] { typeof(Game).Name }
+                });
 
 
             var games = items.Cast<Game>().ToList();
             var games = items.Cast<Game>().ToList();
 
 
@@ -182,20 +185,42 @@ namespace MediaBrowser.Api
         /// </summary>
         /// </summary>
         /// <param name="request">The request.</param>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
         /// <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 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;
         }
         }
     }
     }
 }
 }

+ 11 - 8
MediaBrowser.Api/Images/ImageService.cs

@@ -273,7 +273,9 @@ namespace MediaBrowser.Api.Images
         {
         {
             var list = new List<ImageInfo>();
             var list = new List<ImageInfo>();
 
 
-            foreach (var image in item.ImageInfos.Where(i => !item.AllowsMultipleImages(i.Type)))
+            var itemImages = item.ImageInfos;
+
+            foreach (var image in itemImages.Where(i => !item.AllowsMultipleImages(i.Type)))
             {
             {
                 var info = GetImageInfo(item, image, null);
                 var info = GetImageInfo(item, image, null);
 
 
@@ -283,14 +285,14 @@ namespace MediaBrowser.Api.Images
                 }
                 }
             }
             }
 
 
-            foreach (var imageType in item.ImageInfos.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages))
+            foreach (var imageType in itemImages.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages))
             {
             {
                 var index = 0;
                 var index = 0;
 
 
                 // Prevent implicitly captured closure
                 // Prevent implicitly captured closure
                 var currentImageType = imageType;
                 var currentImageType = imageType;
 
 
-                foreach (var image in item.ImageInfos.Where(i => i.Type == currentImageType))
+                foreach (var image in itemImages.Where(i => i.Type == currentImageType))
                 {
                 {
                     var info = GetImageInfo(item, image, index);
                     var info = GetImageInfo(item, image, index);
 
 
@@ -514,7 +516,7 @@ namespace MediaBrowser.Api.Images
         /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
         /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         /// <exception cref="ResourceNotFoundException"></exception>
         /// <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)
             if (request.PercentPlayed.HasValue)
             {
             {
@@ -594,8 +596,7 @@ namespace MediaBrowser.Api.Images
                 supportedImageEnhancers,
                 supportedImageEnhancers,
                 cacheDuration,
                 cacheDuration,
                 responseHeaders,
                 responseHeaders,
-                isHeadRequest)
-                .Result;
+                isHeadRequest);
         }
         }
 
 
         private async Task<object> GetImageResult(IHasImages item,
         private async Task<object> GetImageResult(IHasImages item,
@@ -632,18 +633,20 @@ namespace MediaBrowser.Api.Images
 
 
             headers["Vary"] = "Accept";
             headers["Vary"] = "Accept";
 
 
-            return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
+            return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
             {
             {
                 CacheDuration = cacheDuration,
                 CacheDuration = cacheDuration,
                 ResponseHeaders = headers,
                 ResponseHeaders = headers,
                 ContentType = imageResult.Item2,
                 ContentType = imageResult.Item2,
+                DateLastModified = imageResult.Item3,
                 IsHeadRequest = isHeadRequest,
                 IsHeadRequest = isHeadRequest,
                 Path = imageResult.Item1,
                 Path = imageResult.Item1,
 
 
                 // Sometimes imagemagick keeps a hold on the file briefly even after it's done writing to it.
                 // 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
                 // I'd rather do this than add a delay after saving the file
                 FileShare = FileShare.ReadWrite
                 FileShare = FileShare.ReadWrite
-            });
+
+            }).ConfigureAwait(false);
         }
         }
 
 
         private List<ImageFormat> GetOutputFormats(ImageRequest request, ItemImageInfo image, bool cropwhitespace, List<IImageEnhancer> enhancers)
         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))
 				if (_fileSystem.FileExists(contentPath))
-                {
-                    return ToStaticFileResult(contentPath);
-                }
+				{
+				    return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false);
+				}
             }
             }
             catch (DirectoryNotFoundException)
             catch (DirectoryNotFoundException)
             {
             {
@@ -259,9 +259,9 @@ namespace MediaBrowser.Api.Images
                 contentPath = await reader.ReadToEndAsync().ConfigureAwait(false);
                 contentPath = await reader.ReadToEndAsync().ConfigureAwait(false);
             }
             }
 
 
-            return ToStaticFileResult(contentPath);
+            return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false);
         }
         }
-        
+
         /// <summary>
         /// <summary>
         /// Downloads the image.
         /// Downloads the image.
         /// </summary>
         /// </summary>

+ 40 - 24
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")]
     [Route("/Items/RemoteSearch/AdultVideo", "POST")]
     [Authenticated]
     [Authenticated]
     public class GetAdultVideoRemoteSearchResults : RemoteSearchQuery<ItemLookupInfo>, IReturn<List<RemoteSearchResult>>
     public class GetAdultVideoRemoteSearchResults : RemoteSearchQuery<ItemLookupInfo>, IReturn<List<RemoteSearchResult>>
@@ -132,60 +138,65 @@ namespace MediaBrowser.Api
             return ToOptimizedResult(infos);
             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);
             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);
             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);
             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);
             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);
             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);
             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);
             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)
         public void Post(ApplySearchCriteria request)
@@ -202,14 +213,19 @@ namespace MediaBrowser.Api
             //    }
             //    }
             //}
             //}
             Logger.Info("Setting provider id's to item {0}-{1}: {2}", item.Id, item.Name, _json.SerializeToString(request.ProviderIds));
             Logger.Info("Setting provider id's to item {0}-{1}: {2}", item.Id, item.Name, _json.SerializeToString(request.ProviderIds));
+
+            // Since the refresh process won't erase provider Ids, we need to set this explicitly now.
             item.ProviderIds = request.ProviderIds;
             item.ProviderIds = request.ProviderIds;
+            //item.ProductionYear = request.ProductionYear;
+            //item.Name = request.Name;
 
 
-			var task = _providerManager.RefreshFullItem(item, new MetadataRefreshOptions(_fileSystem)
+            var task = _providerManager.RefreshFullItem(item, new MetadataRefreshOptions(_fileSystem)
             {
             {
                 MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
                 MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
                 ImageRefreshMode = ImageRefreshMode.FullRefresh,
                 ImageRefreshMode = ImageRefreshMode.FullRefresh,
                 ReplaceAllMetadata = true,
                 ReplaceAllMetadata = true,
-                ReplaceAllImages = request.ReplaceAllImages
+                ReplaceAllImages = request.ReplaceAllImages,
+                SearchResult = request
 
 
             }, CancellationToken.None);
             }, CancellationToken.None);
             Task.WaitAll(task);
             Task.WaitAll(task);
@@ -234,9 +250,9 @@ namespace MediaBrowser.Api
                     contentPath = await reader.ReadToEndAsync().ConfigureAwait(false);
                     contentPath = await reader.ReadToEndAsync().ConfigureAwait(false);
                 }
                 }
 
 
-				if (_fileSystem.FileExists(contentPath))
+                if (_fileSystem.FileExists(contentPath))
                 {
                 {
-                    return ToStaticFileResult(contentPath);
+                    return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false);
                 }
                 }
             }
             }
             catch (DirectoryNotFoundException)
             catch (DirectoryNotFoundException)
@@ -256,7 +272,7 @@ namespace MediaBrowser.Api
                 contentPath = await reader.ReadToEndAsync().ConfigureAwait(false);
                 contentPath = await reader.ReadToEndAsync().ConfigureAwait(false);
             }
             }
 
 
-            return ToStaticFileResult(contentPath);
+            return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -275,7 +291,7 @@ namespace MediaBrowser.Api
 
 
             var fullCachePath = GetFullCachePath(urlHash + "." + ext);
             var fullCachePath = GetFullCachePath(urlHash + "." + ext);
 
 
-			_fileSystem.CreateDirectory(Path.GetDirectoryName(fullCachePath));
+            _fileSystem.CreateDirectory(Path.GetDirectoryName(fullCachePath));
             using (var stream = result.Content)
             using (var stream = result.Content)
             {
             {
                 using (var filestream = _fileSystem.GetFileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
                 using (var filestream = _fileSystem.GetFileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
@@ -284,7 +300,7 @@ namespace MediaBrowser.Api
                 }
                 }
             }
             }
 
 
-			_fileSystem.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
+            _fileSystem.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
             using (var writer = new StreamWriter(pointerCachePath))
             using (var writer = new StreamWriter(pointerCachePath))
             {
             {
                 await writer.WriteAsync(fullCachePath).ConfigureAwait(false);
                 await writer.WriteAsync(fullCachePath).ConfigureAwait(false);

+ 19 - 31
MediaBrowser.Api/ItemUpdateService.cs

@@ -70,26 +70,21 @@ namespace MediaBrowser.Api
                 Cultures = _localizationManager.GetCultures().ToList()
                 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();
                     }
                     }
                 }
                 }
             }
             }
@@ -247,6 +242,12 @@ namespace MediaBrowser.Api
                 hasBudget.Revenue = request.Revenue;
                 hasBudget.Revenue = request.Revenue;
             }
             }
 
 
+            var hasOriginalTitle = item as IHasOriginalTitle;
+            if (hasOriginalTitle != null)
+            {
+                hasOriginalTitle.OriginalTitle = hasOriginalTitle.OriginalTitle;
+            }
+
             var hasCriticRating = item as IHasCriticRating;
             var hasCriticRating = item as IHasCriticRating;
             if (hasCriticRating != null)
             if (hasCriticRating != null)
             {
             {
@@ -274,11 +275,7 @@ namespace MediaBrowser.Api
                 episode.AbsoluteEpisodeNumber = request.AbsoluteEpisodeNumber;
                 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;
             var hasTaglines = item as IHasTaglines;
             if (hasTaglines != null)
             if (hasTaglines != null)
@@ -292,11 +289,7 @@ namespace MediaBrowser.Api
                 hasShortOverview.ShortOverview = request.ShortOverview;
                 hasShortOverview.ShortOverview = request.ShortOverview;
             }
             }
 
 
-            var hasKeywords = item as IHasKeywords;
-            if (hasKeywords != null)
-            {
-                hasKeywords.Keywords = request.Keywords;
-            }
+            item.Keywords = request.Keywords;
 
 
             if (request.Studios != null)
             if (request.Studios != null)
             {
             {
@@ -421,11 +414,6 @@ namespace MediaBrowser.Api
                 series.Status = request.SeriesStatus;
                 series.Status = request.SeriesStatus;
                 series.AirDays = request.AirDays;
                 series.AirDays = request.AirDays;
                 series.AirTime = request.AirTime;
                 series.AirTime = request.AirTime;
-
-                if (request.DisplaySpecialsWithSeasons.HasValue)
-                {
-                    series.DisplaySpecialsWithSeasons = request.DisplaySpecialsWithSeasons.Value;
-                }
             }
             }
         }
         }
 
 

+ 0 - 2
MediaBrowser.Api/Library/FileOrganizationService.cs

@@ -119,8 +119,6 @@ namespace MediaBrowser.Api.Library
     {
     {
         private readonly IFileOrganizationService _iFileOrganizationService;
         private readonly IFileOrganizationService _iFileOrganizationService;
 
 
-        /// The _json serializer
-        /// </summary>
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IJsonSerializer _jsonSerializer;
 
 
         public FileOrganizationService(IFileOrganizationService iFileOrganizationService, IJsonSerializer jsonSerializer)
         public FileOrganizationService(IFileOrganizationService iFileOrganizationService, IJsonSerializer jsonSerializer)

+ 0 - 89
MediaBrowser.Api/Library/LibraryHelpers.cs

@@ -1,89 +0,0 @@
-using MediaBrowser.Controller;
-using System;
-using System.IO;
-using System.Linq;
-using CommonIO;
-
-namespace MediaBrowser.Api.Library
-{
-    /// <summary>
-    /// Class LibraryHelpers
-    /// </summary>
-    public static class LibraryHelpers
-    {
-        /// <summary>
-        /// The shortcut file extension
-        /// </summary>
-        private const string ShortcutFileExtension = ".mblink";
-        /// <summary>
-        /// The shortcut file search
-        /// </summary>
-        private const string ShortcutFileSearch = "*" + ShortcutFileExtension;
-
-        /// <summary>
-        /// Deletes a shortcut from within a virtual folder, within either the default view or a user view
-        /// </summary>
-        /// <param name="fileSystem">The file system.</param>
-        /// <param name="virtualFolderName">Name of the virtual folder.</param>
-        /// <param name="mediaPath">The media path.</param>
-        /// <param name="appPaths">The app paths.</param>
-        /// <exception cref="System.IO.DirectoryNotFoundException">The media folder does not exist</exception>
-        public static void RemoveMediaPath(IFileSystem fileSystem, string virtualFolderName, string mediaPath, IServerApplicationPaths appPaths)
-        {
-            if (string.IsNullOrWhiteSpace(mediaPath))
-            {
-                throw new ArgumentNullException("mediaPath");
-            }
-
-            var rootFolderPath = appPaths.DefaultUserViewsPath;
-            var path = Path.Combine(rootFolderPath, virtualFolderName);
-
-            if (!fileSystem.DirectoryExists(path))
-            {
-                throw new DirectoryNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
-            }
-            
-            var shortcut = Directory.EnumerateFiles(path, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase));
-
-            if (!string.IsNullOrEmpty(shortcut))
-            {
-                fileSystem.DeleteFile(shortcut);
-            }
-        }
-
-        /// <summary>
-        /// Adds an additional mediaPath to an existing virtual folder, within either the default view or a user view
-        /// </summary>
-        /// <param name="fileSystem">The file system.</param>
-        /// <param name="virtualFolderName">Name of the virtual folder.</param>
-        /// <param name="path">The path.</param>
-        /// <param name="appPaths">The app paths.</param>
-        public static void AddMediaPath(IFileSystem fileSystem, string virtualFolderName, string path, IServerApplicationPaths appPaths)
-        {
-            if (string.IsNullOrWhiteSpace(path))
-            {
-                throw new ArgumentNullException("path");
-            }
-
-			if (!fileSystem.DirectoryExists(path))
-            {
-                throw new DirectoryNotFoundException("The path does not exist.");
-            }
-
-            var rootFolderPath = appPaths.DefaultUserViewsPath;
-            var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
-
-            var shortcutFilename = fileSystem.GetFileNameWithoutExtension(path);
-
-            var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
-
-			while (fileSystem.FileExists(lnk))
-            {
-                shortcutFilename += "1";
-                lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
-            }
-
-            fileSystem.CreateShortcut(lnk, path);
-        }
-    }
-}

+ 7 - 4
MediaBrowser.Api/Library/LibraryService.cs

@@ -350,7 +350,8 @@ namespace MediaBrowser.Api.Library
                     Fields = request.Fields,
                     Fields = request.Fields,
                     Id = request.Id,
                     Id = request.Id,
                     Limit = request.Limit,
                     Limit = request.Limit,
-                    UserId = request.UserId
+                    UserId = request.UserId,
+                    ExcludeArtistIds = request.ExcludeArtistIds
                 });
                 });
             }
             }
             if (item is MusicArtist)
             if (item is MusicArtist)
@@ -493,7 +494,7 @@ namespace MediaBrowser.Api.Library
             }
             }
         }
         }
 
 
-        public object Get(GetDownload request)
+        public Task<object> Get(GetDownload request)
         {
         {
             var item = _libraryManager.GetItemById(request.Id);
             var item = _libraryManager.GetItemById(request.Id);
             var auth = _authContext.GetAuthorizationInfo(Request);
             var auth = _authContext.GetAuthorizationInfo(Request);
@@ -552,7 +553,7 @@ namespace MediaBrowser.Api.Library
             }
             }
         }
         }
 
 
-        public object Get(GetFile request)
+        public Task<object> Get(GetFile request)
         {
         {
             var item = _libraryManager.GetItemById(request.Id);
             var item = _libraryManager.GetItemById(request.Id);
             var locationType = item.LocationType;
             var locationType = item.LocationType;
@@ -565,7 +566,7 @@ namespace MediaBrowser.Api.Library
                 throw new ArgumentException("This command cannot be used for directories.");
                 throw new ArgumentException("This command cannot be used for directories.");
             }
             }
 
 
-            return ToStaticFileResult(item.Path);
+            return ResultFactory.GetStaticFileResult(Request, item.Path);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -839,6 +840,7 @@ namespace MediaBrowser.Api.Library
             var dtoOptions = GetDtoOptions(request);
             var dtoOptions = GetDtoOptions(request);
 
 
             var dtos = GetThemeSongIds(item).Select(_libraryManager.GetItemById)
             var dtos = GetThemeSongIds(item).Select(_libraryManager.GetItemById)
+                            .Where(i => i != null)
                             .OrderBy(i => i.SortName)
                             .OrderBy(i => i.SortName)
                             .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
                             .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
 
 
@@ -882,6 +884,7 @@ namespace MediaBrowser.Api.Library
             var dtoOptions = GetDtoOptions(request);
             var dtoOptions = GetDtoOptions(request);
 
 
             var dtos = GetThemeVideoIds(item).Select(_libraryManager.GetItemById)
             var dtos = GetThemeVideoIds(item).Select(_libraryManager.GetItemById)
+                            .Where(i => i != null)
                             .OrderBy(i => i.SortName)
                             .OrderBy(i => i.SortName)
                             .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
                             .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
 
 

+ 4 - 111
MediaBrowser.Api/Library/LibraryStructureService.cs

@@ -190,75 +190,7 @@ namespace MediaBrowser.Api.Library
         /// <param name="request">The request.</param>
         /// <param name="request">The request.</param>
         public void Post(AddVirtualFolder request)
         public void Post(AddVirtualFolder request)
         {
         {
-            if (string.IsNullOrWhiteSpace(request.Name))
-            {
-                throw new ArgumentNullException("request");
-            }
-
-            var name = _fileSystem.GetValidFilename(request.Name);
-
-            var rootFolderPath = _appPaths.DefaultUserViewsPath;
-
-            var virtualFolderPath = Path.Combine(rootFolderPath, name);
-            while (_fileSystem.DirectoryExists(virtualFolderPath))
-            {
-                name += "1";
-                virtualFolderPath = Path.Combine(rootFolderPath, name);
-            }
-
-            if (request.Paths != null)
-            {
-                var invalidpath = request.Paths.FirstOrDefault(i => !_fileSystem.DirectoryExists(i));
-                if (invalidpath != null)
-                {
-                    throw new ArgumentException("The specified path does not exist: " + invalidpath + ".");
-                }
-            }
-            
-            _libraryMonitor.Stop();
-
-            try
-            {
-				_fileSystem.CreateDirectory(virtualFolderPath);
-
-                if (!string.IsNullOrEmpty(request.CollectionType))
-                {
-                    var path = Path.Combine(virtualFolderPath, request.CollectionType + ".collection");
-
-                    using (File.Create(path))
-                    {
-
-                    }
-                }
-
-                if (request.Paths != null)
-                {
-                    foreach (var path in request.Paths)
-                    {
-                        LibraryHelpers.AddMediaPath(_fileSystem, name, path, _appPaths);
-                    }
-                }
-            }
-            finally
-            {
-                Task.Run(() =>
-                {
-                    // No need to start if scanning the library because it will handle it
-                    if (request.RefreshLibrary)
-                    {
-                        _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
-                    }
-                    else
-                    {
-                        // Need to add a delay here or directory watchers may still pick up the changes
-                        var task = Task.Delay(1000);
-                        // Have to block here to allow exceptions to bubble
-                        Task.WaitAll(task);
-                        
-                        _libraryMonitor.Start();
-                    }
-                });
-            }
+            _libraryManager.AddVirtualFolder(request.Name, request.CollectionType, request.Paths, request.RefreshLibrary);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -336,46 +268,7 @@ namespace MediaBrowser.Api.Library
         /// <param name="request">The request.</param>
         /// <param name="request">The request.</param>
         public void Delete(RemoveVirtualFolder request)
         public void Delete(RemoveVirtualFolder request)
         {
         {
-            if (string.IsNullOrWhiteSpace(request.Name))
-            {
-                throw new ArgumentNullException("request");
-            }
-
-            var rootFolderPath = _appPaths.DefaultUserViewsPath;
-
-            var path = Path.Combine(rootFolderPath, request.Name);
-
-			if (!_fileSystem.DirectoryExists(path))
-            {
-                throw new DirectoryNotFoundException("The media folder does not exist");
-            }
-
-            _libraryMonitor.Stop();
-
-            try
-            {
-                _fileSystem.DeleteDirectory(path, true);
-            }
-            finally
-            {
-                Task.Run(() =>
-                {
-                    // No need to start if scanning the library because it will handle it
-                    if (request.RefreshLibrary)
-                    {
-                        _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
-                    }
-                    else
-                    {
-                        // Need to add a delay here or directory watchers may still pick up the changes
-                        var task = Task.Delay(1000);
-                        // Have to block here to allow exceptions to bubble
-                        Task.WaitAll(task);
-
-                        _libraryMonitor.Start();
-                    }
-                });
-            }
+            _libraryManager.RemoveVirtualFolder(request.Name, request.RefreshLibrary);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -393,7 +286,7 @@ namespace MediaBrowser.Api.Library
 
 
             try
             try
             {
             {
-                LibraryHelpers.AddMediaPath(_fileSystem, request.Name, request.Path, _appPaths);
+                _libraryManager.AddMediaPath(request.Name, request.Path);
             }
             }
             finally
             finally
             {
             {
@@ -432,7 +325,7 @@ namespace MediaBrowser.Api.Library
 
 
             try
             try
             {
             {
-                LibraryHelpers.RemoveMediaPath(_fileSystem, request.Name, request.Path, _appPaths);
+                _libraryManager.RemoveMediaPath(request.Name, request.Path);
             }
             }
             finally
             finally
             {
             {

+ 132 - 15
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -146,6 +146,13 @@ namespace MediaBrowser.Api.LiveTv
         /// <value>The fields.</value>
         /// <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)]
         [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 string Fields { get; set; }
+
+        public bool EnableTotalRecordCount { get; set; }
+
+        public GetRecordings()
+        {
+            EnableTotalRecordCount = true;
+        }
     }
     }
 
 
     [Route("/LiveTv/Recordings/Groups", "GET", Summary = "Gets live tv recording groups")]
     [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")]
         [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 string SeriesTimerId { get; set; }
+
+        public bool? IsActive { get; set; }
     }
     }
 
 
     [Route("/LiveTv/Programs", "GET,POST", Summary = "Gets available live tv epgs..")]
     [Route("/LiveTv/Programs", "GET,POST", Summary = "Gets available live tv epgs..")]
@@ -254,6 +263,8 @@ namespace MediaBrowser.Api.LiveTv
         [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
         public bool? EnableImages { get; set; }
         public bool? EnableImages { get; set; }
 
 
+        public bool EnableTotalRecordCount { get; set; }
+
         [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
         public int? ImageTypeLimit { get; set; }
         public int? ImageTypeLimit { get; set; }
 
 
@@ -266,12 +277,24 @@ namespace MediaBrowser.Api.LiveTv
         /// <value>The fields.</value>
         /// <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)]
         [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 string Fields { get; set; }
+
+        public GetPrograms()
+        {
+            EnableTotalRecordCount = true;
+        }
     }
     }
 
 
     [Route("/LiveTv/Programs/Recommended", "GET", Summary = "Gets available live tv epgs..")]
     [Route("/LiveTv/Programs/Recommended", "GET", Summary = "Gets available live tv epgs..")]
     [Authenticated]
     [Authenticated]
     public class GetRecommendedPrograms : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
     public class GetRecommendedPrograms : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
     {
     {
+        public bool EnableTotalRecordCount { get; set; }
+
+        public GetRecommendedPrograms()
+        {
+            EnableTotalRecordCount = true;
+        }
+
         [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
         [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
         public string UserId { get; set; }
         public string UserId { get; set; }
 
 
@@ -425,6 +448,12 @@ namespace MediaBrowser.Api.LiveTv
         public string Id { get; set; }
         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")]
     [Route("/LiveTv/ListingProviders", "POST", Summary = "Adds a listing provider")]
     [Authenticated(AllowBeforeStartupWizard = true)]
     [Authenticated(AllowBeforeStartupWizard = true)]
     public class AddListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
     public class AddListingProvider : ListingsProviderInfo, IReturn<ListingsProviderInfo>
@@ -464,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")]
     [Route("/LiveTv/Registration", "GET")]
     [Authenticated]
     [Authenticated]
     public class GetLiveTvRegistrationInfo : IReturn<MBRegistrationRecord>
     public class GetLiveTvRegistrationInfo : IReturn<MBRegistrationRecord>
@@ -482,7 +537,14 @@ namespace MediaBrowser.Api.LiveTv
     [Authenticated(AllowBeforeStartupWizard = true)]
     [Authenticated(AllowBeforeStartupWizard = true)]
     public class GetSatIniMappings : IReturn<List<NameValuePair>>
     public class GetSatIniMappings : IReturn<List<NameValuePair>>
     {
     {
-        
+
+    }
+
+    [Route("/LiveTv/TunerHosts/Satip/ChannelScan", "GET", Summary = "Scans for available channels")]
+    [Authenticated(AllowBeforeStartupWizard = true)]
+    public class GetSatChannnelScanResult : TunerHostInfo
+    {
+
     }
     }
 
 
     public class LiveTvService : BaseApiService
     public class LiveTvService : BaseApiService
@@ -504,6 +566,18 @@ namespace MediaBrowser.Api.LiveTv
             _dtoService = dtoService;
             _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);
+
+            return ToOptimizedResult(result);
+        }
+
         public async Task<object> Get(GetLiveTvRegistrationInfo request)
         public async Task<object> Get(GetLiveTvRegistrationInfo request)
         {
         {
             var result = await _liveTvManager.GetRegistrationInfo(request.ChannelId, request.ProgramId, request.Feature).ConfigureAwait(false);
             var result = await _liveTvManager.GetRegistrationInfo(request.ChannelId, request.ProgramId, request.Feature).ConfigureAwait(false);
@@ -511,6 +585,46 @@ namespace MediaBrowser.Api.LiveTv
             return ToOptimizedResult(result);
             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)
         public object Get(GetSatIniMappings request)
         {
         {
             return ToOptimizedResult(_liveTvManager.GetSatIniMappings());
             return ToOptimizedResult(_liveTvManager.GetSatIniMappings());
@@ -522,9 +636,7 @@ namespace MediaBrowser.Api.LiveTv
 
 
             var response = await _httpClient.Get(new HttpRequestOptions
             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);
             }).ConfigureAwait(false);
 
 
@@ -554,11 +666,7 @@ namespace MediaBrowser.Api.LiveTv
 
 
         public void Delete(DeleteListingProvider request)
         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)
         public async Task<object> Post(AddTunerHost request)
@@ -581,6 +689,11 @@ namespace MediaBrowser.Api.LiveTv
             return _config.GetConfiguration<LiveTvOptions>("livetv");
             return _config.GetConfiguration<LiveTvOptions>("livetv");
         }
         }
 
 
+        private void UpdateConfiguration(LiveTvOptions options)
+        {
+            _config.SaveConfiguration("livetv", options);
+        }
+
         public async Task<object> Get(GetLineups request)
         public async Task<object> Get(GetLineups request)
         {
         {
             var info = await _liveTvManager.GetLineups(request.Type, request.Id, request.Country, request.Location).ConfigureAwait(false);
             var info = await _liveTvManager.GetLineups(request.Type, request.Id, request.Country, request.Location).ConfigureAwait(false);
@@ -613,14 +726,14 @@ namespace MediaBrowser.Api.LiveTv
 
 
             var user = string.IsNullOrEmpty(request.UserId) ? null : _userManager.GetUserById(request.UserId);
             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>
             var result = new QueryResult<BaseItemDto>
             {
             {
                 Items = returnArray,
                 Items = returnArray,
                 TotalRecordCount = channelResult.TotalRecordCount
                 TotalRecordCount = channelResult.TotalRecordCount
             };
             };
-            
+
             return ToOptimizedSerializedResultUsingCache(result);
             return ToOptimizedSerializedResultUsingCache(result);
         }
         }
 
 
@@ -648,7 +761,8 @@ namespace MediaBrowser.Api.LiveTv
             {
             {
                 ChannelIds = (request.ChannelIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray(),
                 ChannelIds = (request.ChannelIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray(),
                 UserId = request.UserId,
                 UserId = request.UserId,
-                HasAired = request.HasAired
+                HasAired = request.HasAired,
+                EnableTotalRecordCount = request.EnableTotalRecordCount
             };
             };
 
 
             if (!string.IsNullOrEmpty(request.MinStartDate))
             if (!string.IsNullOrEmpty(request.MinStartDate))
@@ -695,7 +809,8 @@ namespace MediaBrowser.Api.LiveTv
                 HasAired = request.HasAired,
                 HasAired = request.HasAired,
                 IsMovie = request.IsMovie,
                 IsMovie = request.IsMovie,
                 IsKids = request.IsKids,
                 IsKids = request.IsKids,
-                IsSports = request.IsSports
+                IsSports = request.IsSports,
+                EnableTotalRecordCount = request.EnableTotalRecordCount
             };
             };
 
 
             var result = await _liveTvManager.GetRecommendedPrograms(query, GetDtoOptions(request), CancellationToken.None).ConfigureAwait(false);
             var result = await _liveTvManager.GetRecommendedPrograms(query, GetDtoOptions(request), CancellationToken.None).ConfigureAwait(false);
@@ -722,7 +837,8 @@ namespace MediaBrowser.Api.LiveTv
                 Limit = request.Limit,
                 Limit = request.Limit,
                 Status = request.Status,
                 Status = request.Status,
                 SeriesTimerId = request.SeriesTimerId,
                 SeriesTimerId = request.SeriesTimerId,
-                IsInProgress = request.IsInProgress
+                IsInProgress = request.IsInProgress,
+                EnableTotalRecordCount = request.EnableTotalRecordCount
 
 
             }, options, CancellationToken.None).ConfigureAwait(false);
             }, options, CancellationToken.None).ConfigureAwait(false);
 
 
@@ -753,7 +869,8 @@ namespace MediaBrowser.Api.LiveTv
             var result = await _liveTvManager.GetTimers(new TimerQuery
             var result = await _liveTvManager.GetTimers(new TimerQuery
             {
             {
                 ChannelId = request.ChannelId,
                 ChannelId = request.ChannelId,
-                SeriesTimerId = request.SeriesTimerId
+                SeriesTimerId = request.SeriesTimerId,
+                IsActive = request.IsActive
 
 
             }, CancellationToken.None).ConfigureAwait(false);
             }, CancellationToken.None).ConfigureAwait(false);
 
 

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

@@ -80,8 +80,6 @@
     <Compile Include="FilterService.cs" />
     <Compile Include="FilterService.cs" />
     <Compile Include="IHasDtoOptions.cs" />
     <Compile Include="IHasDtoOptions.cs" />
     <Compile Include="Library\ChapterService.cs" />
     <Compile Include="Library\ChapterService.cs" />
-    <Compile Include="Playback\Dash\ManifestBuilder.cs" />
-    <Compile Include="Playback\Dash\MpegDashService.cs" />
     <Compile Include="Playback\MediaInfoService.cs" />
     <Compile Include="Playback\MediaInfoService.cs" />
     <Compile Include="Playback\TranscodingThrottler.cs" />
     <Compile Include="Playback\TranscodingThrottler.cs" />
     <Compile Include="PlaylistService.cs" />
     <Compile Include="PlaylistService.cs" />
@@ -131,7 +129,6 @@
     <Compile Include="ItemUpdateService.cs" />
     <Compile Include="ItemUpdateService.cs" />
     <Compile Include="Library\LibraryService.cs" />
     <Compile Include="Library\LibraryService.cs" />
     <Compile Include="Library\FileOrganizationService.cs" />
     <Compile Include="Library\FileOrganizationService.cs" />
-    <Compile Include="Library\LibraryHelpers.cs" />
     <Compile Include="Library\LibraryStructureService.cs" />
     <Compile Include="Library\LibraryStructureService.cs" />
     <Compile Include="LiveTv\LiveTvService.cs" />
     <Compile Include="LiveTv\LiveTvService.cs" />
     <Compile Include="LocalizationService.cs" />
     <Compile Include="LocalizationService.cs" />

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

@@ -14,6 +14,7 @@ using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using MediaBrowser.Controller.LiveTv;
 
 
 namespace MediaBrowser.Api.Movies
 namespace MediaBrowser.Api.Movies
 {
 {
@@ -112,16 +113,14 @@ namespace MediaBrowser.Api.Movies
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public async Task<object> Get(GetSimilarMovies request)
         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);
             return ToOptimizedSerializedResultUsingCache(result);
         }
         }
 
 
         public async Task<object> Get(GetSimilarTrailers request)
         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);
             return ToOptimizedSerializedResultUsingCache(result);
         }
         }
@@ -130,152 +129,93 @@ namespace MediaBrowser.Api.Movies
         {
         {
             var user = _userManager.GetUserById(request.UserId);
             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);
-            movies = _libraryManager.ReplaceVideosWithPrimaryVersions(movies);
-
-            var listEligibleForCategories = new List<BaseItem>();
-            var listEligibleForSuggestion = new List<BaseItem>();
-
-            var list = movies.ToList();
-
-            listEligibleForCategories.AddRange(list);
-            listEligibleForSuggestion.AddRange(list);
-
-            listEligibleForCategories = listEligibleForCategories
-                .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);
             var dtoOptions = GetDtoOptions(request);
 
 
             dtoOptions.Fields = request.GetItemFields().ToList();
             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);
             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 user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
 
 
             var item = string.IsNullOrEmpty(request.Id) ?
             var item = string.IsNullOrEmpty(request.Id) ?
                 (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
                 (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
                 _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
                 _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
-            
-            var query = new InternalItemsQuery(user)
-            {
-                IncludeItemTypes = new[] { typeof(Movie).Name }
-            };
 
 
-            if (user == null || user.Configuration.IncludeTrailersInSuggestions)
+            var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
             {
             {
-                var includeList = query.IncludeItemTypes.ToList();
-                includeList.Add(typeof(Trailer).Name);
-                query.IncludeItemTypes = includeList.ToArray();
-            }
-
-            var parentIds = new string[] { };
-            var list = _libraryManager.GetItemList(query, parentIds)
-                .Where(i =>
+                Limit = request.Limit,
+                IncludeItemTypes = new[]
                 {
                 {
-                    // Strip out secondary versions
-                    var v = i as Video;
-                    return v != null && !v.PrimaryVersionId.HasValue;
-                })
-                .DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N"))
-                .ToList();
-
-            if (item is Video)
-            {
-                var imdbId = item.GetProviderId(MetadataProviders.Imdb);
-
-                // Use imdb id to try to filter duplicates of the same item
-                if (!string.IsNullOrWhiteSpace(imdbId))
-                {
-                    list = list
-                        .Where(i => !string.Equals(imdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase))
-                        .ToList();
-                }
-            }
-
-            var items = SimilarItemsHelper.GetSimilaritems(item, _libraryManager, list, getSimilarityScore).ToList();
+                        typeof(Movie).Name,
+                        typeof(Trailer).Name,
+                        typeof(LiveTvProgram).Name
+                },
+                IsMovie = true,
+                SimilarTo = item,
+                EnableGroupByMetadataKey = true
 
 
-            IEnumerable<BaseItem> returnItems = items;
-
-            if (request.Limit.HasValue)
-            {
-                returnItems = returnItems.Take(request.Limit.Value);
-            }
+            }).ToList();
 
 
             var dtoOptions = GetDtoOptions(request);
             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;
             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 categories = new List<RecommendationDto>();
 
 
-            var recentlyPlayedMovies = allMoviesForCategories
-                .Select(i =>
-                {
-                    var userdata = _userDataRepository.GetUserData(user.Id, i.GetUserDataKey());
-                    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.Id, i.GetUserDataKey());
+                    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();
             var mostRecentMovies = recentlyPlayedMovies.Take(6).ToList();
             // Get recently played directors
             // Get recently played directors
@@ -288,11 +228,11 @@ namespace MediaBrowser.Api.Movies
                 .OrderBy(i => Guid.NewGuid())
                 .OrderBy(i => Guid.NewGuid())
                 .ToList();
                 .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>>
             var categoryTypes = new List<IEnumerator<RecommendationDto>>
             {
             {
@@ -335,44 +275,63 @@ namespace MediaBrowser.Api.Movies
             return categories.OrderBy(i => i.RecommendationType).ThenBy(i => Guid.NewGuid());
             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)
                 if (items.Count > 0)
                 {
                 {
                     yield return new RecommendationDto
                     yield return new RecommendationDto
                     {
                     {
-                        BaselineItemName = director,
-                        CategoryId = director.GetMD5().ToString("N"),
+                        BaselineItemName = name,
+                        CategoryId = name.GetMD5().ToString("N"),
                         RecommendationType = type,
                         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)
             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)
                 if (items.Count > 0)
                 {
                 {
@@ -381,20 +340,30 @@ namespace MediaBrowser.Api.Movies
                         BaselineItemName = name,
                         BaselineItemName = name,
                         CategoryId = name.GetMD5().ToString("N"),
                         CategoryId = name.GetMD5().ToString("N"),
                         RecommendationType = type,
                         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)
             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)
                 if (similar.Count > 0)
                 {
                 {
@@ -403,7 +372,7 @@ namespace MediaBrowser.Api.Movies
                         BaselineItemName = item.Name,
                         BaselineItemName = item.Name,
                         CategoryId = item.Id.ToString("N"),
                         CategoryId = item.Id.ToString("N"),
                         RecommendationType = type,
                         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";
             getItems.IncludeItemTypes = "Trailer";
 
 
-            return new ItemsService(_userManager, _libraryManager, _userDataRepository, _localizationManager, _dtoService, _collectionManager)
+            return new ItemsService(_userManager, _libraryManager, _localizationManager, _dtoService)
             {
             {
                 AuthorizationContext = AuthorizationContext,
                 AuthorizationContext = AuthorizationContext,
                 Logger = Logger,
                 Logger = Logger,

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

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

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

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

+ 7 - 5
MediaBrowser.Api/PackageReviewService.cs

@@ -112,7 +112,7 @@ namespace MediaBrowser.Api
             _appHost = appHost;
             _appHost = appHost;
         }
         }
 
 
-        public object Get(ReviewRequest request)
+        public async Task<object> Get(ReviewRequest request)
         {
         {
             var parms = "?id=" + request.Id;
             var parms = "?id=" + request.Id;
 
 
@@ -133,11 +133,13 @@ namespace MediaBrowser.Api
                 parms += "&title=true";
                 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)
         public void Post(CreateReviewRequest request)

+ 179 - 72
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -286,13 +286,25 @@ namespace MediaBrowser.Api.Playback
 
 
         protected string GetH264Encoder(StreamState state)
         protected string GetH264Encoder(StreamState state)
         {
         {
-            if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+            // Only use alternative encoders for video files.
+            // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
+            // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
+            if (state.VideoType == VideoType.VideoFile)
             {
             {
-                // It's currently failing on live tv
-                if (state.RunTimeTicks.HasValue)
+                if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
+                    string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     return "h264_qsv";
                     return "h264_qsv";
                 }
                 }
+
+                if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
+                {
+                    return "h264_nvenc";
+                }
+                if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "h264_omx", StringComparison.OrdinalIgnoreCase))
+                {
+                    return "h264_omx";
+                }
             }
             }
 
 
             return "libx264";
             return "libx264";
@@ -332,10 +344,10 @@ namespace MediaBrowser.Api.Playback
 
 
             }
             }
 
 
-            // h264 (libnvenc)
-            else if (string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase))
+            // h264 (h264_nvenc)
+            else if (string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
             {
             {
-                param = "-preset high-performance";
+                param = "-preset default";
             }
             }
 
 
             // webm
             // webm
@@ -397,15 +409,18 @@ namespace MediaBrowser.Api.Playback
 
 
             if (!string.IsNullOrEmpty(state.VideoRequest.Profile))
             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))
             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))
+                // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
+                if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
+                    string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     switch (state.VideoRequest.Level)
                     switch (state.VideoRequest.Level)
                     {
                     {
@@ -441,13 +456,20 @@ namespace MediaBrowser.Api.Playback
                             break;
                             break;
                     }
                     }
                 }
                 }
-                else
+                else if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     param += " -level " + state.VideoRequest.Level;
                     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, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+            {
+                param = "-pix_fmt yuv420p " + param;
+            }
+
+            return param;
         }
         }
 
 
         protected string GetAudioFilterParam(StreamState state, bool isHls)
         protected string GetAudioFilterParam(StreamState state, bool isHls)
@@ -460,7 +482,7 @@ namespace MediaBrowser.Api.Playback
             // Boost volume to 200% when downsampling from 6ch to 2ch
             // Boost volume to 200% when downsampling from 6ch to 2ch
             if (channels.HasValue && channels.Value <= 2)
             if (channels.HasValue && channels.Value <= 2)
             {
             {
-                if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
+                if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5 && !ApiEntryPoint.Instance.GetEncodingOptions().DownMixAudioBoost.Equals(1))
                 {
                 {
                     volParam = ",volume=" + ApiEntryPoint.Instance.GetEncodingOptions().DownMixAudioBoost.ToString(UsCulture);
                     volParam = ",volume=" + ApiEntryPoint.Instance.GetEncodingOptions().DownMixAudioBoost.ToString(UsCulture);
                 }
                 }
@@ -563,14 +585,6 @@ namespace MediaBrowser.Api.Playback
                 filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(ih\\,{0})", maxHeightParam));
                 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;
             var output = string.Empty;
 
 
             if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode)
             if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode)
@@ -614,7 +628,7 @@ namespace MediaBrowser.Api.Playback
 
 
                 if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
                 if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
                 {
                 {
-                    var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.MediaSource.Protocol, CancellationToken.None).Result;
+                    var charenc = SubtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).Result;
 
 
                     if (!string.IsNullOrEmpty(charenc))
                     if (!string.IsNullOrEmpty(charenc))
                     {
                     {
@@ -712,15 +726,16 @@ namespace MediaBrowser.Api.Playback
                 inputChannels = null;
                 inputChannels = null;
             }
             }
 
 
+            int? resultChannels = null;
             var codec = outputAudioCodec ?? string.Empty;
             var codec = outputAudioCodec ?? string.Empty;
 
 
             if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
             if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
             {
             {
                 // wmav2 currently only supports two channel output
                 // wmav2 currently only supports two channel output
-                return Math.Min(2, inputChannels ?? 2);
+                resultChannels = Math.Min(2, inputChannels ?? 2);
             }
             }
 
 
-            if (request.MaxAudioChannels.HasValue)
+            else if (request.MaxAudioChannels.HasValue)
             {
             {
                 var channelLimit = codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1
                 var channelLimit = codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1
                    ? 2
                    ? 2
@@ -732,10 +747,18 @@ namespace MediaBrowser.Api.Playback
                 }
                 }
 
 
                 // If we don't have any media info then limit it to 5 to prevent encoding errors due to asking for too many channels
                 // If we don't have any media info then limit it to 5 to prevent encoding errors due to asking for too many channels
-                return Math.Min(request.MaxAudioChannels.Value, channelLimit);
+                resultChannels = Math.Min(request.MaxAudioChannels.Value, channelLimit);
             }
             }
 
 
-            return request.AudioChannels;
+            if (resultChannels.HasValue && !string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+            {
+                if (request.TranscodingMaxAudioChannels.HasValue)
+                {
+                    resultChannels = Math.Min(request.TranscodingMaxAudioChannels.Value, resultChannels.Value);
+                }
+            }
+
+            return resultChannels ?? request.AudioChannels;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -821,9 +844,22 @@ namespace MediaBrowser.Api.Playback
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
         protected string GetVideoDecoder(StreamState state)
         protected string GetVideoDecoder(StreamState state)
         {
         {
-            if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+            {
+                return null;
+            }
+
+            // Only use alternative encoders for video files.
+            // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
+            // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
+            if (state.VideoType != VideoType.VideoFile)
+            {
+                return null;
+            }
+
+            if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
             {
             {
-                if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
+                if (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     switch (state.MediaSource.VideoStream.Codec.ToLower())
                     switch (state.MediaSource.VideoStream.Codec.ToLower())
                     {
                     {
@@ -831,6 +867,7 @@ namespace MediaBrowser.Api.Playback
                         case "h264":
                         case "h264":
                             if (MediaEncoder.SupportsDecoder("h264_qsv"))
                             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;
                             break;
@@ -947,14 +984,24 @@ namespace MediaBrowser.Api.Playback
 
 
             await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
             await AcquireResources(state, cancellationTokenSource).ConfigureAwait(false);
 
 
-            var transcodingId = Guid.NewGuid().ToString("N");
-            var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
-
-            if (ApiEntryPoint.Instance.GetEncodingOptions().EnableDebugLogging)
+            if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
             {
             {
-                commandLineArgs = "-loglevel debug " + commandLineArgs;
+                var auth = AuthorizationContext.GetAuthorizationInfo(Request);
+                if (!string.IsNullOrWhiteSpace(auth.UserId))
+                {
+                    var user = UserManager.GetUserById(auth.UserId);
+                    if (!user.Policy.EnableVideoPlaybackTranscoding)
+                    {
+                        ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
+
+                        throw new ArgumentException("User does not have access to video transcoding");
+                    }
+                }
             }
             }
 
 
+            var transcodingId = Guid.NewGuid().ToString("N");
+            var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
+
             var process = new Process
             var process = new Process
             {
             {
                 StartInfo = new ProcessStartInfo
                 StartInfo = new ProcessStartInfo
@@ -963,7 +1010,7 @@ namespace MediaBrowser.Api.Playback
                     UseShellExecute = false,
                     UseShellExecute = false,
 
 
                     // Must consume both stdout and stderr or deadlocks may occur
                     // Must consume both stdout and stderr or deadlocks may occur
-                    RedirectStandardOutput = true,
+                    //RedirectStandardOutput = true,
                     RedirectStandardError = true,
                     RedirectStandardError = true,
                     RedirectStandardInput = true,
                     RedirectStandardInput = true,
 
 
@@ -995,7 +1042,17 @@ namespace MediaBrowser.Api.Playback
             var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
             var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
             Logger.Info(commandLineLogMessage);
             Logger.Info(commandLineLogMessage);
 
 
-            var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, "transcode-" + Guid.NewGuid() + ".txt");
+            var logFilePrefix = "transcode";
+            if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
+            {
+                logFilePrefix = "directstream";
+            }
+            else if (state.VideoRequest != null && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+            {
+                logFilePrefix = "remux";
+            }
+
+            var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
             FileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath));
             FileSystem.CreateDirectory(Path.GetDirectoryName(logFilePath));
 
 
             // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
             // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
@@ -1020,13 +1077,13 @@ namespace MediaBrowser.Api.Playback
             }
             }
 
 
             // MUST read both stdout and stderr asynchronously or a deadlock may occurr
             // MUST read both stdout and stderr asynchronously or a deadlock may occurr
-            process.BeginOutputReadLine();
+            //process.BeginOutputReadLine();
 
 
             // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
             // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
-            StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream);
+            Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream));
 
 
             // Wait for the file to exist before proceeeding
             // Wait for the file to exist before proceeeding
-			while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
+            while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
             {
             {
                 await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
                 await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
             }
             }
@@ -1066,7 +1123,7 @@ namespace MediaBrowser.Api.Playback
             return true;
             return true;
         }
         }
 
 
-        private async void StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
+        private async Task StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
         {
         {
             try
             try
             {
             {
@@ -1172,7 +1229,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;
             var bitrate = request.VideoBitRate;
 
 
@@ -1197,6 +1254,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;
             return bitrate;
         }
         }
 
 
@@ -1452,10 +1521,7 @@ namespace MediaBrowser.Api.Playback
                 }
                 }
                 else if (i == 19)
                 else if (i == 19)
                 {
                 {
-                    if (videoRequest != null)
-                    {
-                        videoRequest.Cabac = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
-                    }
+                    // cabac no longer used
                 }
                 }
                 else if (i == 20)
                 else if (i == 20)
                 {
                 {
@@ -1481,6 +1547,13 @@ namespace MediaBrowser.Api.Playback
                     }
                     }
                 }
                 }
                 else if (i == 25)
                 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)
                     if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
                     {
                     {
@@ -1491,6 +1564,17 @@ namespace MediaBrowser.Api.Playback
                         }
                         }
                     }
                     }
                 }
                 }
+                else if (i == 27)
+                {
+                    request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
+                }
+                else if (i == 28)
+                {
+                    if (videoRequest != null)
+                    {
+                        videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
+                    }
+                }
             }
             }
         }
         }
 
 
@@ -1582,7 +1666,8 @@ namespace MediaBrowser.Api.Playback
             var state = new StreamState(MediaSourceManager, Logger)
             var state = new StreamState(MediaSourceManager, Logger)
             {
             {
                 Request = request,
                 Request = request,
-                RequestedUrl = url
+                RequestedUrl = url,
+                UserAgent = Request.UserAgent
             };
             };
 
 
             //if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
             //if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
@@ -1595,7 +1680,8 @@ namespace MediaBrowser.Api.Playback
             if (!string.IsNullOrWhiteSpace(request.AudioCodec))
             if (!string.IsNullOrWhiteSpace(request.AudioCodec))
             {
             {
                 state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
                 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);
             var item = LibraryManager.GetItemById(request.Id);
@@ -1649,14 +1735,14 @@ namespace MediaBrowser.Api.Playback
             if (videoRequest != null)
             if (videoRequest != null)
             {
             {
                 state.OutputVideoCodec = state.VideoRequest.VideoCodec;
                 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)
                 if (state.OutputVideoBitrate.HasValue)
                 {
                 {
                     var resolution = ResolutionNormalizer.Normalize(
                     var resolution = ResolutionNormalizer.Normalize(
-						state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
-						state.OutputVideoBitrate.Value,
-						state.VideoStream == null ? null : state.VideoStream.Codec,
+                        state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
+                        state.OutputVideoBitrate.Value,
+                        state.VideoStream == null ? null : state.VideoStream.Codec,
                         state.OutputVideoCodec,
                         state.OutputVideoCodec,
                         videoRequest.MaxWidth,
                         videoRequest.MaxWidth,
                         videoRequest.MaxHeight);
                         videoRequest.MaxHeight);
@@ -1680,12 +1766,25 @@ namespace MediaBrowser.Api.Playback
 
 
         private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest)
         private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest)
         {
         {
-            if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
+            if (state.VideoStream != null && CanStreamCopyVideo(state))
             {
             {
                 state.OutputVideoCodec = "copy";
                 state.OutputVideoCodec = "copy";
             }
             }
+            else
+            {
+                // If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not
+                var auth = AuthorizationContext.GetAuthorizationInfo(Request);
+                if (!string.IsNullOrWhiteSpace(auth.UserId))
+                {
+                    var user = UserManager.GetUserById(auth.UserId);
+                    if (!user.Policy.EnableVideoPlaybackTranscoding)
+                    {
+                        state.OutputVideoCodec = "copy";
+                    }
+                }
+            }
 
 
-            if (state.AudioStream != null && CanStreamCopyAudio(videoRequest, state.AudioStream, state.SupportedAudioCodecs))
+            if (state.AudioStream != null && CanStreamCopyAudio(state, state.SupportedAudioCodecs))
             {
             {
                 state.OutputAudioCodec = "copy";
                 state.OutputAudioCodec = "copy";
             }
             }
@@ -1773,8 +1872,11 @@ namespace MediaBrowser.Api.Playback
             state.MediaSource = mediaSource;
             state.MediaSource = mediaSource;
         }
         }
 
 
-        protected virtual bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
+        protected virtual bool CanStreamCopyVideo(StreamState state)
         {
         {
+            var request = state.VideoRequest;
+            var videoStream = state.VideoStream;
+
             if (videoStream.IsInterlaced)
             if (videoStream.IsInterlaced)
             {
             {
                 return false;
                 return false;
@@ -1784,7 +1886,7 @@ namespace MediaBrowser.Api.Playback
             {
             {
                 return false;
                 return false;
             }
             }
-            
+
             // Can't stream copy if we're burning in subtitles
             // Can't stream copy if we're burning in subtitles
             if (request.SubtitleStreamIndex.HasValue)
             if (request.SubtitleStreamIndex.HasValue)
             {
             {
@@ -1794,6 +1896,15 @@ namespace MediaBrowser.Api.Playback
                 }
                 }
             }
             }
 
 
+            if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+            {
+                if (videoStream.IsAVC.HasValue && !videoStream.IsAVC.Value)
+                {
+                    Logger.Debug("Cannot stream copy video. Stream is marked as not AVC");
+                    return false;
+                }
+            }
+
             // Source and target codecs must match
             // Source and target codecs must match
             if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase))
             if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase))
             {
             {
@@ -1805,10 +1916,10 @@ namespace MediaBrowser.Api.Playback
             {
             {
                 if (string.IsNullOrEmpty(videoStream.Profile))
                 if (string.IsNullOrEmpty(videoStream.Profile))
                 {
                 {
-                    return false;
+                    //return false;
                 }
                 }
 
 
-                if (!string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase))
+                if (!string.IsNullOrEmpty(videoStream.Profile) && !string.Equals(request.Profile, videoStream.Profile, StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     var currentScore = GetVideoProfileScore(videoStream.Profile);
                     var currentScore = GetVideoProfileScore(videoStream.Profile);
                     var requestedScore = GetVideoProfileScore(request.Profile);
                     var requestedScore = GetVideoProfileScore(request.Profile);
@@ -1884,24 +1995,16 @@ namespace MediaBrowser.Api.Playback
                 {
                 {
                     if (!videoStream.Level.HasValue)
                     if (!videoStream.Level.HasValue)
                     {
                     {
-                        return false;
+                        //return false;
                     }
                     }
 
 
-                    if (videoStream.Level.Value > requestLevel)
+                    if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel)
                     {
                     {
                         return false;
                         return false;
                     }
                     }
                 }
                 }
             }
             }
 
 
-            if (request.Cabac.HasValue && request.Cabac.Value)
-            {
-                if (videoStream.IsCabac.HasValue && !videoStream.IsCabac.Value)
-                {
-                    return false;
-                }
-            }
-
             return request.EnableAutoStreamCopy;
             return request.EnableAutoStreamCopy;
         }
         }
 
 
@@ -1921,8 +2024,11 @@ namespace MediaBrowser.Api.Playback
             return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase));
             return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase));
         }
         }
 
 
-        protected virtual bool CanStreamCopyAudio(VideoStreamRequest request, MediaStream audioStream, List<string> supportedAudioCodecs)
+        protected virtual bool CanStreamCopyAudio(StreamState state, List<string> supportedAudioCodecs)
         {
         {
+            var request = state.VideoRequest;
+            var audioStream = state.AudioStream;
+
             // Source and target codecs must match
             // Source and target codecs must match
             if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase))
             if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase))
             {
             {
@@ -2028,7 +2134,6 @@ namespace MediaBrowser.Api.Playback
                 state.TargetPacketLength,
                 state.TargetPacketLength,
                 state.TargetTimestamp,
                 state.TargetTimestamp,
                 state.IsTargetAnamorphic,
                 state.IsTargetAnamorphic,
-                state.IsTargetCabac,
                 state.TargetRefFrames,
                 state.TargetRefFrames,
                 state.TargetVideoStreamCount,
                 state.TargetVideoStreamCount,
                 state.TargetAudioStreamCount,
                 state.TargetAudioStreamCount,
@@ -2054,6 +2159,8 @@ namespace MediaBrowser.Api.Playback
                     if (state.VideoRequest != null)
                     if (state.VideoRequest != null)
                     {
                     {
                         state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
                         state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
+                        state.VideoRequest.ForceLiveStream = transcodingProfile.ForceLiveStream;
+                        state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
                     }
                     }
                 }
                 }
             }
             }
@@ -2131,7 +2238,6 @@ namespace MediaBrowser.Api.Playback
                     state.TargetPacketLength,
                     state.TargetPacketLength,
                     state.TranscodeSeekInfo,
                     state.TranscodeSeekInfo,
                     state.IsTargetAnamorphic,
                     state.IsTargetAnamorphic,
-                    state.IsTargetCabac,
                     state.TargetRefFrames,
                     state.TargetRefFrames,
                     state.TargetVideoStreamCount,
                     state.TargetVideoStreamCount,
                     state.TargetAudioStreamCount,
                     state.TargetAudioStreamCount,
@@ -2218,12 +2324,13 @@ namespace MediaBrowser.Api.Playback
 
 
             if (state.VideoRequest != null)
             if (state.VideoRequest != null)
             {
             {
+                // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking
                 if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase) && state.VideoRequest.CopyTimestamps)
                 if (string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase) && state.VideoRequest.CopyTimestamps)
                 {
                 {
-                    inputModifier += " -noaccurate_seek";
+                    //inputModifier += " -noaccurate_seek";
                 }
                 }
             }
             }
-            
+
             return inputModifier;
             return inputModifier;
         }
         }
 
 

+ 0 - 224
MediaBrowser.Api/Playback/Dash/ManifestBuilder.cs

@@ -1,224 +0,0 @@
-using System;
-using System.Globalization;
-using System.Security;
-using System.Text;
-
-namespace MediaBrowser.Api.Playback.Dash
-{
-    public class ManifestBuilder
-    {
-        protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
-        public string GetManifestText(StreamState state, string playlistUrl)
-        {
-            var builder = new StringBuilder();
-
-            var time = TimeSpan.FromTicks(state.RunTimeTicks.Value);
-
-            var duration = "PT" + time.Hours.ToString("00", UsCulture) + "H" + time.Minutes.ToString("00", UsCulture) + "M" + time.Seconds.ToString("00", UsCulture) + ".00S";
-
-            builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
-
-            builder.AppendFormat(
-                "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"urn:mpeg:dash:schema:mpd:2011\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xsi:schemaLocation=\"urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd\" profiles=\"urn:mpeg:dash:profile:isoff-live:2011\" type=\"static\" mediaPresentationDuration=\"{0}\" minBufferTime=\"PT5.0S\">",
-                duration);
-
-            builder.Append("<ProgramInformation>");
-            builder.Append("</ProgramInformation>");
-
-            builder.Append("<Period start=\"PT0S\">");
-            builder.Append(GetVideoAdaptationSet(state, playlistUrl));
-            builder.Append(GetAudioAdaptationSet(state, playlistUrl));
-            builder.Append("</Period>");
-
-            builder.Append("</MPD>");
-
-            return builder.ToString();
-        }
-
-        private string GetVideoAdaptationSet(StreamState state, string playlistUrl)
-        {
-            var builder = new StringBuilder();
-
-            builder.Append("<AdaptationSet id=\"video\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">");
-            builder.Append(GetVideoRepresentationOpenElement(state));
-
-            AppendSegmentList(state, builder, "0", playlistUrl);
-
-            builder.Append("</Representation>");
-            builder.Append("</AdaptationSet>");
-
-            return builder.ToString();
-        }
-
-        private string GetAudioAdaptationSet(StreamState state, string playlistUrl)
-        {
-            var builder = new StringBuilder();
-
-            builder.Append("<AdaptationSet id=\"audio\" segmentAlignment=\"true\" bitstreamSwitching=\"true\">");
-            builder.Append(GetAudioRepresentationOpenElement(state));
-
-            builder.Append("<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"6\" />");
-
-            AppendSegmentList(state, builder, "1", playlistUrl);
-
-            builder.Append("</Representation>");
-            builder.Append("</AdaptationSet>");
-
-            return builder.ToString();
-        }
-
-        private string GetVideoRepresentationOpenElement(StreamState state)
-        {
-            var codecs = GetVideoCodecDescriptor(state);
-
-            var mime = "video/mp4";
-
-            var xml = "<Representation id=\"0\" mimeType=\"" + mime + "\" codecs=\"" + codecs + "\"";
-
-            if (state.OutputWidth.HasValue)
-            {
-                xml += " width=\"" + state.OutputWidth.Value.ToString(UsCulture) + "\"";
-            }
-            if (state.OutputHeight.HasValue)
-            {
-                xml += " height=\"" + state.OutputHeight.Value.ToString(UsCulture) + "\"";
-            }
-            if (state.OutputVideoBitrate.HasValue)
-            {
-                xml += " bandwidth=\"" + state.OutputVideoBitrate.Value.ToString(UsCulture) + "\"";
-            }
-
-            xml += ">";
-
-            return xml;
-        }
-
-        private string GetAudioRepresentationOpenElement(StreamState state)
-        {
-            var codecs = GetAudioCodecDescriptor(state);
-
-            var mime = "audio/mp4";
-
-            var xml = "<Representation id=\"1\" mimeType=\"" + mime + "\" codecs=\"" + codecs + "\"";
-
-            if (state.OutputAudioSampleRate.HasValue)
-            {
-                xml += " audioSamplingRate=\"" + state.OutputAudioSampleRate.Value.ToString(UsCulture) + "\"";
-            }
-            if (state.OutputAudioBitrate.HasValue)
-            {
-                xml += " bandwidth=\"" + state.OutputAudioBitrate.Value.ToString(UsCulture) + "\"";
-            }
-
-            xml += ">";
-
-            return xml;
-        }
-
-        private string GetVideoCodecDescriptor(StreamState state)
-        {
-            // https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
-            // http://www.chipwreck.de/blog/2010/02/25/html-5-video-tag-and-attributes/
-
-            var level = state.TargetVideoLevel ?? 0;
-            var profile = state.TargetVideoProfile ?? string.Empty;
-
-            if (profile.IndexOf("high", StringComparison.OrdinalIgnoreCase) != -1)
-            {
-                if (level >= 4.1)
-                {
-                    return "avc1.640028";
-                }
-
-                if (level >= 4)
-                {
-                    return "avc1.640028";
-                }
-
-                return "avc1.64001f";
-            }
-
-            if (profile.IndexOf("main", StringComparison.OrdinalIgnoreCase) != -1)
-            {
-                if (level >= 4)
-                {
-                    return "avc1.4d0028";
-                }
-
-                if (level >= 3.1)
-                {
-                    return "avc1.4d001f";
-                }
-
-                return "avc1.4d001e";
-            }
-
-            if (level >= 3.1)
-            {
-                return "avc1.42001f";
-            }
-
-            return "avc1.42E01E";
-        }
-
-        private string GetAudioCodecDescriptor(StreamState state)
-        {
-            // https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html
-
-            if (string.Equals(state.OutputAudioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
-            {
-                return "mp4a.40.34";
-            }
-
-            // AAC 5ch
-            if (state.OutputAudioChannels.HasValue && state.OutputAudioChannels.Value >= 5)
-            {
-                return "mp4a.40.5";
-            }
-
-            // AAC 2ch
-            return "mp4a.40.2";
-        }
-
-        private void AppendSegmentList(StreamState state, StringBuilder builder, string type, string playlistUrl)
-        {
-            var extension = ".m4s";
-
-            var seconds = TimeSpan.FromTicks(state.RunTimeTicks ?? 0).TotalSeconds;
-
-            var queryStringIndex = playlistUrl.IndexOf('?');
-            var queryString = queryStringIndex == -1 ? string.Empty : playlistUrl.Substring(queryStringIndex);
-
-            var index = 0;
-            var duration = 1000000 * state.SegmentLength;
-            builder.AppendFormat("<SegmentList timescale=\"1000000\" duration=\"{0}\" startNumber=\"1\">", duration.ToString(CultureInfo.InvariantCulture));
-
-            while (seconds > 0)
-            {
-                var filename = index == 0
-                    ? "init"
-                    : (index - 1).ToString(UsCulture);
-
-                var segmentUrl = string.Format("dash/{3}/{0}{1}{2}",
-                    filename,
-                    extension,
-                    SecurityElement.Escape(queryString),
-                    type);
-
-                if (index == 0)
-                {
-                    builder.AppendFormat("<Initialization sourceURL=\"{0}\"/>", segmentUrl);
-                }
-                else
-                {
-                    builder.AppendFormat("<SegmentURL media=\"{0}\"/>", segmentUrl);
-                }
-
-                seconds -= state.SegmentLength;
-                index++;
-            }
-            builder.Append("</SegmentList>");
-        }
-    }
-}

+ 0 - 547
MediaBrowser.Api/Playback/Dash/MpegDashService.cs

@@ -1,547 +0,0 @@
-using MediaBrowser.Api.Playback.Hls;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Serialization;
-using ServiceStack;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using CommonIO;
-using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
-
-namespace MediaBrowser.Api.Playback.Dash
-{
-    /// <summary>
-    /// Options is needed for chromecast. Threw Head in there since it's related
-    /// </summary>
-    [Route("/Videos/{Id}/master.mpd", "GET", Summary = "Gets a video stream using Mpeg dash.")]
-    [Route("/Videos/{Id}/master.mpd", "HEAD", Summary = "Gets a video stream using Mpeg dash.")]
-    public class GetMasterManifest : VideoStreamRequest
-    {
-        public bool EnableAdaptiveBitrateStreaming { get; set; }
-
-        public GetMasterManifest()
-        {
-            EnableAdaptiveBitrateStreaming = true;
-        }
-    }
-
-    [Route("/Videos/{Id}/dash/{RepresentationId}/{SegmentId}.m4s", "GET")]
-    public class GetDashSegment : VideoStreamRequest
-    {
-        /// <summary>
-        /// Gets or sets the segment id.
-        /// </summary>
-        /// <value>The segment id.</value>
-        public string SegmentId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the representation identifier.
-        /// </summary>
-        /// <value>The representation identifier.</value>
-        public string RepresentationId { get; set; }
-    }
-
-    public class MpegDashService : BaseHlsService
-    {
-        public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer)
-        {
-            NetworkManager = networkManager;
-        }
-
-        protected INetworkManager NetworkManager { get; private set; }
-
-        public object Get(GetMasterManifest request)
-        {
-            var result = GetAsync(request, "GET").Result;
-
-            return result;
-        }
-
-        public object Head(GetMasterManifest request)
-        {
-            var result = GetAsync(request, "HEAD").Result;
-
-            return result;
-        }
-
-        protected override bool EnableOutputInSubFolder
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        private async Task<object> GetAsync(GetMasterManifest request, string method)
-        {
-            if (string.IsNullOrEmpty(request.MediaSourceId))
-            {
-                throw new ArgumentException("MediaSourceId is required");
-            }
-
-            var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
-
-            var playlistText = string.Empty;
-
-            if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase))
-            {
-                playlistText = new ManifestBuilder().GetManifestText(state, Request.RawUrl);
-            }
-
-            return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.mpd"), new Dictionary<string, string>());
-        }
-
-        public object Get(GetDashSegment request)
-        {
-            return GetDynamicSegment(request, request.SegmentId, request.RepresentationId).Result;
-        }
-
-        private async Task<object> GetDynamicSegment(VideoStreamRequest request, string segmentId, string representationId)
-        {
-            if ((request.StartTimeTicks ?? 0) > 0)
-            {
-                throw new ArgumentException("StartTimeTicks is not allowed.");
-            }
-
-            var cancellationTokenSource = new CancellationTokenSource();
-            var cancellationToken = cancellationTokenSource.Token;
-
-            var requestedIndex = string.Equals(segmentId, "init", StringComparison.OrdinalIgnoreCase) ?
-                -1 :
-                int.Parse(segmentId, NumberStyles.Integer, UsCulture);
-
-            var state = await GetState(request, cancellationToken).ConfigureAwait(false);
-
-            var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".mpd");
-
-            var segmentExtension = GetSegmentFileExtension(state);
-
-            var segmentPath = FindSegment(playlistPath, representationId, segmentExtension, requestedIndex);
-            var segmentLength = state.SegmentLength;
-
-            TranscodingJob job = null;
-
-            if (!string.IsNullOrWhiteSpace(segmentPath))
-            {
-                job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
-                return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
-            }
-
-            await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
-            try
-            {
-                segmentPath = FindSegment(playlistPath, representationId, segmentExtension, requestedIndex);
-                if (!string.IsNullOrWhiteSpace(segmentPath))
-                {
-                    job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
-                    return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false);
-                }
-                else
-                {
-                    if (string.Equals(representationId, "0", StringComparison.OrdinalIgnoreCase))
-                    {
-                        job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
-                        var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension);
-                        var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength;
-                        Logger.Debug("Current transcoding index is {0}. requestedIndex={1}. segmentGapRequiringTranscodingChange={2}", currentTranscodingIndex ?? -2, requestedIndex, segmentGapRequiringTranscodingChange);
-                        if (currentTranscodingIndex == null || requestedIndex < currentTranscodingIndex.Value || requestedIndex - currentTranscodingIndex.Value > segmentGapRequiringTranscodingChange)
-                        {
-                            // If the playlist doesn't already exist, startup ffmpeg
-                            try
-                            {
-                                ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false);
-
-                                if (currentTranscodingIndex.HasValue)
-                                {
-                                    DeleteLastTranscodedFiles(playlistPath, 0);
-                                }
-
-                                var positionTicks = GetPositionTicks(state, requestedIndex);
-                                request.StartTimeTicks = positionTicks;
-
-                                var startNumber = GetStartNumber(state);
-
-                                var workingDirectory = Path.Combine(Path.GetDirectoryName(playlistPath), (startNumber == -1 ? 0 : startNumber).ToString(CultureInfo.InvariantCulture));
-                                state.WaitForPath = Path.Combine(workingDirectory, Path.GetFileName(playlistPath));
-                                FileSystem.CreateDirectory(workingDirectory);
-                                job = await StartFfMpeg(state, playlistPath, cancellationTokenSource, workingDirectory).ConfigureAwait(false);
-                                await WaitForMinimumDashSegmentCount(Path.Combine(workingDirectory, Path.GetFileName(playlistPath)), 1, cancellationTokenSource.Token).ConfigureAwait(false);
-                            }
-                            catch
-                            {
-                                state.Dispose();
-                                throw;
-                            }
-                        }
-                    }
-                }
-            }
-            finally
-            {
-                ApiEntryPoint.Instance.TranscodingStartLock.Release();
-            }
-
-            while (string.IsNullOrWhiteSpace(segmentPath))
-            {
-                segmentPath = FindSegment(playlistPath, representationId, segmentExtension, requestedIndex);
-                await Task.Delay(50, cancellationToken).ConfigureAwait(false);
-            }
-
-            Logger.Info("returning {0}", segmentPath);
-            return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job ?? ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType), cancellationToken).ConfigureAwait(false);
-        }
-
-        private long GetPositionTicks(StreamState state, int requestedIndex)
-        {
-            if (requestedIndex <= 0)
-            {
-                return 0;
-            }
-
-            var startSeconds = requestedIndex * state.SegmentLength;
-            return TimeSpan.FromSeconds(startSeconds).Ticks;
-        }
-
-        protected  Task WaitForMinimumDashSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken)
-        {
-            return WaitForSegment(playlist, "stream0-" + segmentCount.ToString("00000", CultureInfo.InvariantCulture) + ".m4s", cancellationToken);
-        }
-
-        private async Task<object> GetSegmentResult(string playlistPath,
-            string segmentPath,
-            int segmentIndex,
-            int segmentLength,
-            TranscodingJob transcodingJob,
-            CancellationToken cancellationToken)
-        {
-            // If all transcoding has completed, just return immediately
-            if (transcodingJob != null && transcodingJob.HasExited)
-            {
-                return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
-            }
-
-            // Wait for the file to stop being written to, then stream it
-            var length = new FileInfo(segmentPath).Length;
-            var eofCount = 0;
-
-            while (eofCount < 10)
-            {
-                var info = new FileInfo(segmentPath);
-
-                if (!info.Exists)
-                {
-                    break;
-                }
-
-                var newLength = info.Length;
-
-                if (newLength == length)
-                {
-                    eofCount++;
-                }
-                else
-                {
-                    eofCount = 0;
-                }
-
-                length = newLength;
-                await Task.Delay(100, cancellationToken).ConfigureAwait(false);
-            }
-
-            return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob);
-        }
-
-        private object GetSegmentResult(string segmentPath, int index, int segmentLength, TranscodingJob transcodingJob)
-        {
-            var segmentEndingSeconds = (1 + index) * segmentLength;
-            var segmentEndingPositionTicks = TimeSpan.FromSeconds(segmentEndingSeconds).Ticks;
-
-            return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
-            {
-                Path = segmentPath,
-                FileShare = FileShare.ReadWrite,
-                OnComplete = () =>
-                {
-                    if (transcodingJob != null)
-                    {
-                        transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks);
-                    }
-
-                }
-            });
-        }
-
-        public int? GetCurrentTranscodingIndex(string playlist, string segmentExtension)
-        {
-            var job = ApiEntryPoint.Instance.GetTranscodingJob(playlist, TranscodingJobType);
-
-            if (job == null || job.HasExited)
-            {
-                return null;
-            }
-
-            var file = GetLastTranscodingFiles(playlist, segmentExtension, FileSystem, 1).FirstOrDefault();
-
-            if (file == null)
-            {
-                return null;
-            }
-
-            return GetIndex(file.FullName);
-        }
-
-        public int GetIndex(string segmentPath)
-        {
-            var indexString = Path.GetFileNameWithoutExtension(segmentPath).Split('-').LastOrDefault();
-
-            if (string.Equals(indexString, "init", StringComparison.OrdinalIgnoreCase))
-            {
-                return -1;
-            }
-            var startNumber = int.Parse(Path.GetFileNameWithoutExtension(Path.GetDirectoryName(segmentPath)), NumberStyles.Integer, UsCulture);
-
-            return startNumber + int.Parse(indexString, NumberStyles.Integer, UsCulture) - 1;
-        }
-
-        private void DeleteLastTranscodedFiles(string playlistPath, int retryCount)
-        {
-            if (retryCount >= 5)
-            {
-                return;
-            }
-        }
-
-        private static List<FileSystemMetadata> GetLastTranscodingFiles(string playlist, string segmentExtension, IFileSystem fileSystem, int count)
-        {
-            var folder = Path.GetDirectoryName(playlist);
-
-            try
-            {
-				return fileSystem.GetFiles(folder)
-                    .Where(i => string.Equals(i.Extension, segmentExtension, StringComparison.OrdinalIgnoreCase))
-                    .OrderByDescending(fileSystem.GetLastWriteTimeUtc)
-                    .Take(count)
-                    .ToList();
-            }
-            catch (DirectoryNotFoundException)
-            {
-                return new List<FileSystemMetadata>();
-            }
-        }
-
-        private string FindSegment(string playlist, string representationId, string segmentExtension, int requestedIndex)
-        {
-            var folder = Path.GetDirectoryName(playlist);
-
-            if (requestedIndex == -1)
-            {
-                var path = Path.Combine(folder, "0", "stream" + representationId + "-" + "init" + segmentExtension);
-				return FileSystem.FileExists(path) ? path : null;
-            }
-
-            try
-            {
-                foreach (var subfolder in FileSystem.GetDirectoryPaths(folder).ToList())
-                {
-                    var subfolderName = Path.GetFileNameWithoutExtension(subfolder);
-                    int startNumber;
-                    if (int.TryParse(subfolderName, NumberStyles.Any, UsCulture, out startNumber))
-                    {
-                        var segmentIndex = requestedIndex - startNumber + 1;
-                        var path = Path.Combine(folder, subfolderName, "stream" + representationId + "-" + segmentIndex.ToString("00000", CultureInfo.InvariantCulture) + segmentExtension);
-						if (FileSystem.FileExists(path))
-                        {
-                            return path;
-                        }
-                    }
-                }
-            }
-            catch (DirectoryNotFoundException)
-            {
-                
-            }
-
-            return null;
-        }
-
-        protected override string GetAudioArguments(StreamState state)
-        {
-            var codec = GetAudioEncoder(state);
-
-            if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
-            {
-                return "-codec:a:0 copy";
-            }
-
-            var args = "-codec:a:0 " + codec;
-
-            var channels = state.OutputAudioChannels;
-
-            if (channels.HasValue)
-            {
-                args += " -ac " + channels.Value;
-            }
-
-            var bitrate = state.OutputAudioBitrate;
-
-            if (bitrate.HasValue)
-            {
-                args += " -ab " + bitrate.Value.ToString(UsCulture);
-            }
-
-            args += " " + GetAudioFilterParam(state, true);
-
-            return args;
-        }
-
-        protected override string GetVideoArguments(StreamState state)
-        {
-            var codec = GetVideoEncoder(state);
-
-            var args = "-codec:v:0 " + codec;
-
-            if (state.EnableMpegtsM2TsMode)
-            {
-                args += " -mpegts_m2ts_mode 1";
-            }
-
-            // See if we can save come cpu cycles by avoiding encoding
-            if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
-            {
-                return state.VideoStream != null && IsH264(state.VideoStream) ?
-                    args + " -bsf:v h264_mp4toannexb" :
-                    args;
-            }
-
-            var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
-                state.SegmentLength.ToString(UsCulture));
-
-            var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
-
-            args += " " + GetVideoQualityParam(state, GetH264Encoder(state)) + keyFrameArg;
-
-            // Add resolution params, if specified
-            if (!hasGraphicalSubs)
-            {
-                args += GetOutputSizeParam(state, codec, false);
-            }
-
-            // This is for internal graphical subs
-            if (hasGraphicalSubs)
-            {
-                args += GetGraphicalSubtitleParam(state, codec);
-            }
-
-            return args;
-        }
-
-        protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
-        {
-            // test url http://192.168.1.2:8096/videos/233e8905d559a8f230db9bffd2ac9d6d/master.mpd?mediasourceid=233e8905d559a8f230db9bffd2ac9d6d&videocodec=h264&audiocodec=aac&maxwidth=1280&videobitrate=500000&audiobitrate=128000&profile=baseline&level=3
-            // Good info on i-frames http://blog.streamroot.io/encode-multi-bitrate-videos-mpeg-dash-mse-based-media-players/
-
-            var threads = GetNumberOfThreads(state, false);
-
-            var inputModifier = GetInputModifier(state);
-
-            var initSegmentName = "stream$RepresentationID$-init.m4s";
-            var segmentName = "stream$RepresentationID$-$Number%05d$.m4s";
-
-            var args = string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts {5} -f dash -init_seg_name \"{6}\" -media_seg_name \"{7}\" -use_template 0 -use_timeline 1 -min_seg_duration {8} -y \"{9}\"",
-                inputModifier,
-                GetInputArgument(state),
-                threads,
-                GetMapArgs(state),
-                GetVideoArguments(state),
-                GetAudioArguments(state),
-                initSegmentName,
-                segmentName,
-                (state.SegmentLength * 1000000).ToString(CultureInfo.InvariantCulture),
-                state.WaitForPath
-                ).Trim();
-
-            return args;
-        }
-
-        protected override int GetStartNumber(StreamState state)
-        {
-            return GetStartNumber(state.VideoRequest);
-        }
-
-        private int GetStartNumber(VideoStreamRequest request)
-        {
-            var segmentId = "0";
-
-            var segmentRequest = request as GetDashSegment;
-            if (segmentRequest != null)
-            {
-                segmentId = segmentRequest.SegmentId;
-            }
-
-            if (string.Equals(segmentId, "init", StringComparison.OrdinalIgnoreCase))
-            {
-                return -1;
-            }
-
-            return int.Parse(segmentId, NumberStyles.Integer, UsCulture);
-        }
-
-        /// <summary>
-        /// Gets the segment file extension.
-        /// </summary>
-        /// <param name="state">The state.</param>
-        /// <returns>System.String.</returns>
-        protected override string GetSegmentFileExtension(StreamState state)
-        {
-            return ".m4s";
-        }
-
-        protected override TranscodingJobType TranscodingJobType
-        {
-            get
-            {
-                return TranscodingJobType.Dash;
-            }
-        }
-
-        private async Task WaitForSegment(string playlist, string segment, CancellationToken cancellationToken)
-        {
-            var segmentFilename = Path.GetFileName(segment);
-
-            Logger.Debug("Waiting for {0} in {1}", segmentFilename, playlist);
-
-            while (true)
-            {
-                // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
-                using (var fileStream = GetPlaylistFileStream(playlist))
-                {
-                    using (var reader = new StreamReader(fileStream))
-                    {
-                        while (!reader.EndOfStream)
-                        {
-                            var line = await reader.ReadLineAsync().ConfigureAwait(false);
-
-                            if (line.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1)
-                            {
-                                Logger.Debug("Finished waiting for {0} in {1}", segmentFilename, playlist);
-                                return;
-                            }
-                        }
-                        await Task.Delay(100, cancellationToken).ConfigureAwait(false);
-                    }
-                }
-            }
-        }
-    }
-}

+ 12 - 41
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -63,9 +63,9 @@ namespace MediaBrowser.Api.Playback.Hls
         /// <param name="request">The request.</param>
         /// <param name="request">The request.</param>
         /// <param name="isLive">if set to <c>true</c> [is live].</param>
         /// <param name="isLive">if set to <c>true</c> [is live].</param>
         /// <returns>System.Object.</returns>
         /// <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>
         /// <summary>
@@ -83,11 +83,6 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
             var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false);
             var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false);
 
 
-            if (isLive)
-            {
-                state.Request.StartTimeTicks = null;
-            }
-
             TranscodingJob job = null;
             TranscodingJob job = null;
             var playlist = state.OutputFilePath;
             var playlist = state.OutputFilePath;
 
 
@@ -137,13 +132,6 @@ namespace MediaBrowser.Api.Playback.Hls
             var appendBaselineStream = false;
             var appendBaselineStream = false;
             var baselineStreamBitrate = 64000;
             var baselineStreamBitrate = 64000;
 
 
-            var hlsVideoRequest = state.VideoRequest as GetHlsVideoStreamLegacy;
-            if (hlsVideoRequest != null)
-            {
-                appendBaselineStream = hlsVideoRequest.AppendBaselineStream;
-                baselineStreamBitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? baselineStreamBitrate;
-            }
-
             var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate);
             var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate);
 
 
             job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
             job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
@@ -248,11 +236,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
         protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
         protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
         {
         {
-            var hlsVideoRequest = state.VideoRequest as GetHlsVideoStreamLegacy;
-
-            var itsOffsetMs = hlsVideoRequest == null
-                                       ? 0
-                                       : hlsVideoRequest.TimeStampOffsetMs;
+            var itsOffsetMs = 0;
 
 
             var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture));
             var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds.ToString(UsCulture));
 
 
@@ -286,26 +270,6 @@ namespace MediaBrowser.Api.Playback.Hls
                 outputPath
                 outputPath
                 ).Trim();
                 ).Trim();
 
 
-            if (hlsVideoRequest != null)
-            {
-                if (hlsVideoRequest.AppendBaselineStream)
-                {
-                    var lowBitratePath = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath) + "-low.m3u8");
-
-                    var bitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? 64000;
-
-                    var lowBitrateParams = string.Format(" -threads {0} -vn -codec:a:0 libmp3lame -ac 2 -ab {1} -hls_time {2} -start_number {3} -hls_list_size {4} -y \"{5}\"",
-                        threads,
-                        bitrate / 2,
-                        state.SegmentLength.ToString(UsCulture),
-                        startNumberParam,
-                        state.HlsListSize.ToString(UsCulture),
-                        lowBitratePath);
-
-                    args += " " + lowBitrateParams;
-                }
-            }
-
             return args;
             return args;
         }
         }
 
 
@@ -314,9 +278,16 @@ namespace MediaBrowser.Api.Playback.Hls
             return 0;
             return 0;
         }
         }
 
 
-        protected override bool CanStreamCopyAudio(VideoStreamRequest request, MediaStream audioStream, List<string> supportedAudioCodecs)
+        protected bool IsLiveStream(StreamState state)
         {
         {
-            return false;
+            var isLiveStream = (state.RunTimeTicks ?? 0) == 0;
+
+            if (state.VideoRequest.ForceLiveStream)
+            {
+                return true;
+            }
+
+            return isLiveStream;
         }
         }
     }
     }
 }
 }

+ 42 - 34
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -475,7 +475,7 @@ namespace MediaBrowser.Api.Playback.Hls
                         ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob);
                         ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob);
                     }
                     }
                 }
                 }
-            });
+            }).Result;
         }
         }
 
 
         private async Task<object> GetMasterPlaylistInternal(StreamRequest request, string method)
         private async Task<object> GetMasterPlaylistInternal(StreamRequest request, string method)
@@ -506,7 +506,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
             builder.AppendLine("#EXTM3U");
             builder.AppendLine("#EXTM3U");
 
 
-            var isLiveStream = (state.RunTimeTicks ?? 0) == 0;
+            var isLiveStream = IsLiveStream(state);
 
 
             var queryStringIndex = Request.RawUrl.IndexOf('?');
             var queryStringIndex = Request.RawUrl.IndexOf('?');
             var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
             var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex);
@@ -525,10 +525,16 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
             var subtitleGroup = subtitleStreams.Count > 0 &&
             var subtitleGroup = subtitleStreams.Count > 0 &&
                 request is GetMasterHlsVideoPlaylist &&
                 request is GetMasterHlsVideoPlaylist &&
-                ((GetMasterHlsVideoPlaylist)request).SubtitleMethod == SubtitleDeliveryMethod.Hls ?
+                (state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Hls || state.VideoRequest.EnableSubtitlesInManifest) ?
                 "subs" :
                 "subs" :
                 null;
                 null;
 
 
+            // If we're burning in subtitles then don't add additional subs to the manifest
+            if (state.SubtitleStream != null && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode)
+            {
+                subtitleGroup = null;
+            }
+
             if (!string.IsNullOrWhiteSpace(subtitleGroup))
             if (!string.IsNullOrWhiteSpace(subtitleGroup))
             {
             {
                 AddSubtitles(state, subtitleStreams, builder);
                 AddSubtitles(state, subtitleStreams, builder);
@@ -572,13 +578,11 @@ namespace MediaBrowser.Api.Playback.Hls
             {
             {
                 const string format = "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"{0}\",DEFAULT={1},FORCED={2},AUTOSELECT=YES,URI=\"{3}\",LANGUAGE=\"{4}\"";
                 const string format = "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"{0}\",DEFAULT={1},FORCED={2},AUTOSELECT=YES,URI=\"{3}\",LANGUAGE=\"{4}\"";
 
 
-                var name = stream.Language;
+                var name = stream.DisplayTitle;
 
 
                 var isDefault = selectedIndex.HasValue && selectedIndex.Value == stream.Index;
                 var isDefault = selectedIndex.HasValue && selectedIndex.Value == stream.Index;
                 var isForced = stream.IsForced;
                 var isForced = stream.IsForced;
 
 
-                if (string.IsNullOrWhiteSpace(name)) name = stream.Codec ?? "Unknown";
-
                 var url = string.Format("{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}&api_key={3}",
                 var url = string.Format("{0}/Subtitles/{1}/subtitles.m3u8?SegmentLength={2}&api_key={3}",
                     state.Request.MediaSourceId,
                     state.Request.MediaSourceId,
                     stream.Index.ToString(UsCulture),
                     stream.Index.ToString(UsCulture),
@@ -816,12 +820,12 @@ namespace MediaBrowser.Api.Playback.Hls
             // See if we can save come cpu cycles by avoiding encoding
             // See if we can save come cpu cycles by avoiding encoding
             if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
             if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
             {
             {
-                if (state.VideoStream != null && IsH264(state.VideoStream))
+                if (state.VideoStream != null && IsH264(state.VideoStream) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     args += " -bsf:v h264_mp4toannexb";
                     args += " -bsf:v h264_mp4toannexb";
                 }
                 }
 
 
-                args += " -flags -global_header -sc_threshold 0";
+                args += " -flags -global_header";
             }
             }
             else
             else
             {
             {
@@ -846,7 +850,12 @@ namespace MediaBrowser.Api.Playback.Hls
                     args += GetGraphicalSubtitleParam(state, codec);
                     args += GetGraphicalSubtitleParam(state, codec);
                 }
                 }
 
 
-                args += " -flags -global_header -sc_threshold 0";
+                args += " -flags -global_header";
+            }
+
+            if (EnableCopyTs(state) && args.IndexOf("-copyts", StringComparison.OrdinalIgnoreCase) == -1)
+            {
+                args += " -copyts";
             }
             }
 
 
             return args;
             return args;
@@ -854,7 +863,8 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
         private bool EnableCopyTs(StreamState state)
         private bool EnableCopyTs(StreamState state)
         {
         {
-            return state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode;
+            //return state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.VideoRequest.SubtitleMethod == SubtitleDeliveryMethod.Encode;
+            return true;
         }
         }
 
 
         protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
         protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
@@ -876,24 +886,28 @@ namespace MediaBrowser.Api.Playback.Hls
 
 
             var mapArgs = state.IsOutputVideo ? GetMapArgs(state) : string.Empty;
             var mapArgs = state.IsOutputVideo ? GetMapArgs(state) : string.Empty;
 
 
-            //var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state);
-
-            //return string.Format("{0} {11} {1}{10} -map_metadata -1 -threads {2} {3} {4} {5} -f segment -segment_time {6} -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
-            //    inputModifier,
-            //    GetInputArgument(state),
-            //    threads,
-            //    mapArgs,
-            //    GetVideoArguments(state),
-            //    GetAudioArguments(state),
-            //    state.SegmentLength.ToString(UsCulture),
-            //    startNumberParam,
-            //    outputPath,
-            //    outputTsArg,
-            //            slowSeekParam,
-            //            toTimeParam
-            //    ).Trim();
-
-            return string.Format("{0}{11} {1} -map_metadata -1 -threads {2} {3} {4}{5} {6} -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10}\"",
+            var enableGenericSegmenter = false;
+
+            if (enableGenericSegmenter)
+            {
+                var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state);
+
+                return string.Format("{0} {10} {1} -map_metadata -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
+                    inputModifier,
+                    GetInputArgument(state),
+                    threads,
+                    mapArgs,
+                    GetVideoArguments(state),
+                    GetAudioArguments(state),
+                    state.SegmentLength.ToString(UsCulture),
+                    startNumberParam,
+                    outputPath,
+                    outputTsArg,
+                            toTimeParam
+                    ).Trim();
+            }
+
+            return string.Format("{0}{11} {1} -map_metadata -1 -threads {2} {3} {4}{5} {6} -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10}\"",
                             inputModifier,
                             inputModifier,
                             GetInputArgument(state),
                             GetInputArgument(state),
                             threads,
                             threads,
@@ -928,11 +942,5 @@ namespace MediaBrowser.Api.Playback.Hls
         {
         {
             return isOutputVideo ? ".ts" : ".ts";
             return isOutputVideo ? ".ts" : ".ts";
         }
         }
-
-        protected override bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
-        {
-            return false;
-            //return base.CanStreamCopyVideo(request, videoStream);
-        }
     }
     }
 }
 }

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

@@ -5,6 +5,7 @@ using ServiceStack;
 using System;
 using System;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
+using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Api.Playback.Hls
 namespace MediaBrowser.Api.Playback.Hls
 {
 {
@@ -31,25 +32,6 @@ namespace MediaBrowser.Api.Playback.Hls
         public string SegmentId { get; set; }
         public string SegmentId { get; set; }
     }
     }
 
 
-    /// <summary>
-    /// Class GetHlsVideoStream
-    /// </summary>
-    [Route("/Videos/{Id}/stream.m3u8", "GET")]
-    [Api(Description = "Gets a video stream using HTTP live streaming.")]
-    public class GetHlsVideoStreamLegacy : VideoStreamRequest
-    {
-        // TODO: Deprecate with new iOS app
-
-        [ApiMember(Name = "BaselineStreamAudioBitRate", Description = "Optional. Specify the audio bitrate for the baseline stream.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? BaselineStreamAudioBitRate { get; set; }
-
-        [ApiMember(Name = "AppendBaselineStream", Description = "Optional. Whether or not to include a baseline audio-only stream in the master playlist.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool AppendBaselineStream { get; set; }
-
-        [ApiMember(Name = "TimeStampOffsetMs", Description = "Optional. Alter the timestamps in the playlist by a given amount, in ms. Default is 1000.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int TimeStampOffsetMs { get; set; }
-    }
-
     /// <summary>
     /// <summary>
     /// Class GetHlsVideoSegment
     /// Class GetHlsVideoSegment
     /// </summary>
     /// </summary>
@@ -108,7 +90,7 @@ namespace MediaBrowser.Api.Playback.Hls
             _config = config;
             _config = config;
         }
         }
 
 
-        public object Get(GetHlsPlaylistLegacy request)
+        public Task<object> Get(GetHlsPlaylistLegacy request)
         {
         {
             var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
             var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
             file = Path.Combine(_appPaths.TranscodingTempPath, file);
             file = Path.Combine(_appPaths.TranscodingTempPath, file);
@@ -126,7 +108,7 @@ namespace MediaBrowser.Api.Playback.Hls
         /// </summary>
         /// </summary>
         /// <param name="request">The request.</param>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
-        public object Get(GetHlsVideoSegmentLegacy request)
+        public Task<object> Get(GetHlsVideoSegmentLegacy request)
         {
         {
             var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
             var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
             file = Path.Combine(_config.ApplicationPaths.TranscodingTempPath, file);
             file = Path.Combine(_config.ApplicationPaths.TranscodingTempPath, file);
@@ -150,10 +132,10 @@ namespace MediaBrowser.Api.Playback.Hls
             var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
             var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
             file = Path.Combine(_appPaths.TranscodingTempPath, file);
             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);
             var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
 
 

+ 7 - 14
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -27,16 +27,6 @@ namespace MediaBrowser.Api.Playback.Hls
         {
         {
         }
         }
 
 
-        /// <summary>
-        /// Gets the specified request.
-        /// </summary>
-        /// <param name="request">The request.</param>
-        /// <returns>System.Object.</returns>
-        public object Get(GetHlsVideoStreamLegacy request)
-        {
-            return ProcessRequest(request, false);
-        }
-
         public object Get(GetLiveHlsStream request)
         public object Get(GetLiveHlsStream request)
         {
         {
             return ProcessRequest(request, true);
             return ProcessRequest(request, true);
@@ -96,11 +86,14 @@ namespace MediaBrowser.Api.Playback.Hls
             // See if we can save come cpu cycles by avoiding encoding
             // See if we can save come cpu cycles by avoiding encoding
             if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
             if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
             {
             {
-                return state.VideoStream != null && IsH264(state.VideoStream) ?
-                    args + " -bsf:v h264_mp4toannexb" :
-                    args;
+                // if h264_mp4toannexb is ever added, do not use it for live tv
+                if (state.VideoStream != null && IsH264(state.VideoStream) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
+                {
+                    args += " -bsf:v h264_mp4toannexb";
+                }
+                return args;
             }
             }
-            
+
             var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
             var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"",
                 state.SegmentLength.ToString(UsCulture));
                 state.SegmentLength.ToString(UsCulture));
 
 

+ 52 - 9
MediaBrowser.Api/Playback/MediaInfoService.cs

@@ -15,6 +15,8 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.MediaEncoding;
 
 
 namespace MediaBrowser.Api.Playback
 namespace MediaBrowser.Api.Playback
 {
 {
@@ -66,14 +68,18 @@ namespace MediaBrowser.Api.Playback
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly INetworkManager _networkManager;
         private readonly INetworkManager _networkManager;
+        private readonly IMediaEncoder _mediaEncoder;
+        private readonly IUserManager _userManager;
 
 
-        public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager, IServerConfigurationManager config, INetworkManager networkManager)
+        public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager, IServerConfigurationManager config, INetworkManager networkManager, IMediaEncoder mediaEncoder, IUserManager userManager)
         {
         {
             _mediaSourceManager = mediaSourceManager;
             _mediaSourceManager = mediaSourceManager;
             _deviceManager = deviceManager;
             _deviceManager = deviceManager;
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
             _config = config;
             _config = config;
             _networkManager = networkManager;
             _networkManager = networkManager;
+            _mediaEncoder = mediaEncoder;
+            _userManager = userManager;
         }
         }
 
 
         public object Get(GetBitrateTestBytes request)
         public object Get(GetBitrateTestBytes request)
@@ -116,7 +122,7 @@ namespace MediaBrowser.Api.Playback
 
 
                 SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
                 SetDeviceSpecificData(item, result.MediaSource, profile, authInfo, request.MaxStreamingBitrate,
                     request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex,
                     request.StartTimeTicks ?? 0, result.MediaSource.Id, request.AudioStreamIndex,
-                    request.SubtitleStreamIndex, request.PlaySessionId);
+                    request.SubtitleStreamIndex, request.PlaySessionId, request.UserId);
             }
             }
             else
             else
             {
             {
@@ -156,7 +162,7 @@ namespace MediaBrowser.Api.Playback
             {
             {
                 var mediaSourceId = request.MediaSourceId;
                 var mediaSourceId = request.MediaSourceId;
 
 
-                SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex);
+                SetDeviceSpecificData(request.Id, info, profile, authInfo, request.MaxStreamingBitrate ?? profile.MaxStreamingBitrate, request.StartTimeTicks ?? 0, mediaSourceId, request.AudioStreamIndex, request.SubtitleStreamIndex, request.UserId);
             }
             }
 
 
             return ToOptimizedResult(info);
             return ToOptimizedResult(info);
@@ -218,16 +224,17 @@ namespace MediaBrowser.Api.Playback
             long startTimeTicks,
             long startTimeTicks,
             string mediaSourceId,
             string mediaSourceId,
             int? audioStreamIndex,
             int? audioStreamIndex,
-            int? subtitleStreamIndex)
+            int? subtitleStreamIndex,
+            string userId)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
 
 
             foreach (var mediaSource in result.MediaSources)
             foreach (var mediaSource in result.MediaSources)
             {
             {
-                SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, result.PlaySessionId);
+                SetDeviceSpecificData(item, mediaSource, profile, auth, maxBitrate, startTimeTicks, mediaSourceId, audioStreamIndex, subtitleStreamIndex, result.PlaySessionId, userId);
             }
             }
 
 
-            SortMediaSources(result);
+            SortMediaSources(result, maxBitrate);
         }
         }
 
 
         private void SetDeviceSpecificData(BaseItem item,
         private void SetDeviceSpecificData(BaseItem item,
@@ -239,9 +246,10 @@ namespace MediaBrowser.Api.Playback
             string mediaSourceId,
             string mediaSourceId,
             int? audioStreamIndex,
             int? audioStreamIndex,
             int? subtitleStreamIndex,
             int? subtitleStreamIndex,
-            string playSessionId)
+            string playSessionId,
+            string userId)
         {
         {
-            var streamBuilder = new StreamBuilder(Logger);
+            var streamBuilder = new StreamBuilder(_mediaEncoder, Logger);
 
 
             var options = new VideoOptions
             var options = new VideoOptions
             {
             {
@@ -259,6 +267,8 @@ namespace MediaBrowser.Api.Playback
                 options.SubtitleStreamIndex = subtitleStreamIndex;
                 options.SubtitleStreamIndex = subtitleStreamIndex;
             }
             }
 
 
+            var user = _userManager.GetUserById(userId);
+
             if (mediaSource.SupportsDirectPlay)
             if (mediaSource.SupportsDirectPlay)
             {
             {
                 var supportsDirectStream = mediaSource.SupportsDirectStream;
                 var supportsDirectStream = mediaSource.SupportsDirectStream;
@@ -267,6 +277,14 @@ namespace MediaBrowser.Api.Playback
                 mediaSource.SupportsDirectStream = true;
                 mediaSource.SupportsDirectStream = true;
                 options.MaxBitrate = maxBitrate;
                 options.MaxBitrate = maxBitrate;
 
 
+                if (item is Audio)
+                {
+                    if (!user.Policy.EnableAudioPlaybackTranscoding)
+                    {
+                        options.ForceDirectPlay = true;
+                    }
+                }
+
                 // The MediaSource supports direct stream, now test to see if the client supports it
                 // The MediaSource supports direct stream, now test to see if the client supports it
                 var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
                 var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
                     streamBuilder.BuildAudioItem(options) :
                     streamBuilder.BuildAudioItem(options) :
@@ -290,6 +308,14 @@ namespace MediaBrowser.Api.Playback
             {
             {
                 options.MaxBitrate = GetMaxBitrate(maxBitrate);
                 options.MaxBitrate = GetMaxBitrate(maxBitrate);
 
 
+                if (item is Audio)
+                {
+                    if (!user.Policy.EnableAudioPlaybackTranscoding)
+                    {
+                        options.ForceDirectStream = true;
+                    }
+                }
+
                 // The MediaSource supports direct stream, now test to see if the client supports it
                 // The MediaSource supports direct stream, now test to see if the client supports it
                 var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
                 var streamInfo = string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase) ?
                     streamBuilder.BuildAudioItem(options) :
                     streamBuilder.BuildAudioItem(options) :
@@ -375,7 +401,7 @@ namespace MediaBrowser.Api.Playback
             }
             }
         }
         }
 
 
-        private void SortMediaSources(PlaybackInfoResponse result)
+        private void SortMediaSources(PlaybackInfoResponse result, int? maxBitrate)
         {
         {
             var originalList = result.MediaSources.ToList();
             var originalList = result.MediaSources.ToList();
 
 
@@ -409,6 +435,23 @@ namespace MediaBrowser.Api.Playback
                         return 1;
                         return 1;
                 }
                 }
 
 
+            }).ThenBy(i =>
+            {
+                if (maxBitrate.HasValue)
+                {
+                    if (i.Bitrate.HasValue)
+                    {
+                        if (i.Bitrate.Value <= maxBitrate.Value)
+                        {
+                            return 0;
+                        }
+
+                        return 2;
+                    }
+                }
+
+                return 1;
+
             }).ThenBy(originalList.IndexOf)
             }).ThenBy(originalList.IndexOf)
             .ToList();
             .ToList();
         }
         }

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

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

+ 22 - 17
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -13,6 +13,7 @@ using ServiceStack.Web;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
+using System.IO;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using CommonIO;
 using CommonIO;
@@ -113,11 +114,11 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// <param name="request">The request.</param>
         /// <param name="request">The request.</param>
         /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
         /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        protected object ProcessRequest(StreamRequest request, bool isHeadRequest)
+        protected async Task<object> ProcessRequest(StreamRequest request, bool isHeadRequest)
         {
         {
             var cancellationTokenSource = new CancellationTokenSource();
             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>();
             var responseHeaders = new Dictionary<string, string>();
 
 
@@ -128,7 +129,8 @@ namespace MediaBrowser.Api.Playback.Progressive
 
 
                 using (state)
                 using (state)
                 {
                 {
-                    return GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).Result;
+                    return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource)
+                                .ConfigureAwait(false);
                 }
                 }
             }
             }
 
 
@@ -138,7 +140,7 @@ namespace MediaBrowser.Api.Playback.Progressive
             }
             }
 
 
             var outputPath = state.OutputFilePath;
             var outputPath = state.OutputFilePath;
-			var outputPathExists = FileSystem.FileExists(outputPath);
+            var outputPathExists = FileSystem.FileExists(outputPath);
 
 
             var isTranscodeCached = outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive);
             var isTranscodeCached = outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive);
 
 
@@ -151,13 +153,13 @@ namespace MediaBrowser.Api.Playback.Progressive
 
 
                 using (state)
                 using (state)
                 {
                 {
-                    return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
+                    return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
                     {
                     {
                         ResponseHeaders = responseHeaders,
                         ResponseHeaders = responseHeaders,
                         ContentType = contentType,
                         ContentType = contentType,
                         IsHeadRequest = isHeadRequest,
                         IsHeadRequest = isHeadRequest,
                         Path = state.MediaPath
                         Path = state.MediaPath
-                    });
+                    }).ConfigureAwait(false);
                 }
                 }
             }
             }
 
 
@@ -168,13 +170,13 @@ namespace MediaBrowser.Api.Playback.Progressive
 
 
                 try
                 try
                 {
                 {
-                    return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
+                    return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
                     {
                     {
                         ResponseHeaders = responseHeaders,
                         ResponseHeaders = responseHeaders,
                         ContentType = contentType,
                         ContentType = contentType,
                         IsHeadRequest = isHeadRequest,
                         IsHeadRequest = isHeadRequest,
                         Path = outputPath
                         Path = outputPath
-                    });
+                    }).ConfigureAwait(false);
                 }
                 }
                 finally
                 finally
                 {
                 {
@@ -185,7 +187,8 @@ namespace MediaBrowser.Api.Playback.Progressive
             // Need to start ffmpeg
             // Need to start ffmpeg
             try
             try
             {
             {
-                return GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).Result;
+                return await GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource)
+                            .ConfigureAwait(false);
             }
             }
             catch
             catch
             {
             {
@@ -229,7 +232,7 @@ namespace MediaBrowser.Api.Playback.Progressive
 
 
             if (trySupportSeek)
             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];
                     var val = response.Headers[name];
                     if (!string.IsNullOrWhiteSpace(val))
                     if (!string.IsNullOrWhiteSpace(val))
@@ -242,12 +245,12 @@ namespace MediaBrowser.Api.Playback.Progressive
             {
             {
                 responseHeaders["Accept-Ranges"] = "none";
                 responseHeaders["Accept-Ranges"] = "none";
             }
             }
-            
+
             if (response.ContentLength.HasValue)
             if (response.ContentLength.HasValue)
             {
             {
                 responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture);
                 responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture);
             }
             }
-            
+
             if (isHeadRequest)
             if (isHeadRequest)
             {
             {
                 using (response)
                 using (response)
@@ -324,7 +327,7 @@ namespace MediaBrowser.Api.Playback.Progressive
             {
             {
                 TranscodingJob job;
                 TranscodingJob job;
 
 
-				if (!FileSystem.FileExists(outputPath))
+                if (!FileSystem.FileExists(outputPath))
                 {
                 {
                     job = await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false);
                     job = await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false);
                 }
                 }
@@ -334,17 +337,19 @@ namespace MediaBrowser.Api.Playback.Progressive
                     state.Dispose();
                     state.Dispose();
                 }
                 }
 
 
-                var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem, job);
+                var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
 
 
-                result.Options["Content-Type"] = contentType;
+                outputHeaders["Content-Type"] = contentType;
 
 
                 // Add the response headers to the result object
                 // Add the response headers to the result object
                 foreach (var item in responseHeaders)
                 foreach (var item in responseHeaders)
                 {
                 {
-                    result.Options[item.Key] = item.Value;
+                    outputHeaders[item.Key] = item.Value;
                 }
                 }
 
 
-                return result;
+                Func<Stream,Task> streamWriter = stream => new ProgressiveFileCopier(FileSystem, job, Logger).StreamFile(outputPath, stream, CancellationToken.None);
+
+                return ResultFactory.GetAsyncStreamWriter(streamWriter, outputHeaders);
             }
             }
             finally
             finally
             {
             {

+ 18 - 121
MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs

@@ -3,90 +3,12 @@ using ServiceStack.Web;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
+using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using CommonIO;
 using CommonIO;
 
 
 namespace MediaBrowser.Api.Playback.Progressive
 namespace MediaBrowser.Api.Playback.Progressive
 {
 {
-    public class ProgressiveStreamWriter : IStreamWriter, IHasOptions
-    {
-        private string Path { get; set; }
-        private ILogger Logger { get; set; }
-        private readonly IFileSystem _fileSystem;
-        private readonly TranscodingJob _job;
-
-        /// <summary>
-        /// The _options
-        /// </summary>
-        private readonly IDictionary<string, string> _options = new Dictionary<string, string>();
-        /// <summary>
-        /// Gets the options.
-        /// </summary>
-        /// <value>The options.</value>
-        public IDictionary<string, string> Options
-        {
-            get { return _options; }
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="ProgressiveStreamWriter" /> class.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <param name="logger">The logger.</param>
-        /// <param name="fileSystem">The file system.</param>
-        public ProgressiveStreamWriter(string path, ILogger logger, IFileSystem fileSystem, TranscodingJob job)
-        {
-            Path = path;
-            Logger = logger;
-            _fileSystem = fileSystem;
-            _job = job;
-        }
-
-        /// <summary>
-        /// Writes to.
-        /// </summary>
-        /// <param name="responseStream">The response stream.</param>
-        public void WriteTo(Stream responseStream)
-        {
-            WriteToInternal(responseStream);
-        }
-
-        /// <summary>
-        /// Writes to async.
-        /// </summary>
-        /// <param name="responseStream">The response stream.</param>
-        /// <returns>Task.</returns>
-        private void WriteToInternal(Stream responseStream)
-        {
-            try
-            {
-                var task = new ProgressiveFileCopier(_fileSystem, _job, Logger).StreamFile(Path, responseStream);
-
-                Task.WaitAll(task);
-            }
-            catch (IOException)
-            {
-                // These error are always the same so don't dump the whole stack trace
-                Logger.Error("Error streaming media. The client has most likely disconnected or transcoding has failed.");
-
-                throw;
-            }
-            catch (Exception ex)
-            {
-                Logger.ErrorException("Error streaming media. The client has most likely disconnected or transcoding has failed.", ex);
-
-                throw;
-            }
-            finally
-            {
-                if (_job != null)
-                {
-                    ApiEntryPoint.Instance.OnTranscodeEndRequest(_job);
-                }
-            }
-        }
-    }
-
     public class ProgressiveFileCopier
     public class ProgressiveFileCopier
     {
     {
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
@@ -105,22 +27,18 @@ namespace MediaBrowser.Api.Playback.Progressive
             _logger = logger;
             _logger = logger;
         }
         }
 
 
-        public async Task StreamFile(string path, Stream outputStream)
+        public async Task StreamFile(string path, Stream outputStream, CancellationToken cancellationToken)
         {
         {
             var eofCount = 0;
             var eofCount = 0;
-            long position = 0;
 
 
-            using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, false))
+            using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
             {
             {
                 while (eofCount < 15)
                 while (eofCount < 15)
                 {
                 {
-                    CopyToInternal(fs, outputStream, BufferSize);
-
-                    var fsPosition = fs.Position;
-
-                    var bytesRead = fsPosition - position;
+                    var bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, cancellationToken).ConfigureAwait(false);
 
 
-                    //Logger.Debug("Streamed {0} bytes from file {1}", bytesRead, path);
+                    //var position = fs.Position;
+                    //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
 
 
                     if (bytesRead == 0)
                     if (bytesRead == 0)
                     {
                     {
@@ -128,57 +46,36 @@ namespace MediaBrowser.Api.Playback.Progressive
                         {
                         {
                             eofCount++;
                             eofCount++;
                         }
                         }
-                        await Task.Delay(100).ConfigureAwait(false);
+                        await Task.Delay(100, cancellationToken).ConfigureAwait(false);
                     }
                     }
                     else
                     else
                     {
                     {
                         eofCount = 0;
                         eofCount = 0;
                     }
                     }
-
-                    position = fsPosition;
                 }
                 }
             }
             }
         }
         }
 
 
-        private void CopyToInternal(Stream source, Stream destination, int bufferSize)
+        private async Task<int> CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, CancellationToken cancellationToken)
         {
         {
-            var array = new byte[bufferSize];
-            int count;
-            while ((count = source.Read(array, 0, array.Length)) != 0)
-            {
-                //if (_job != null)
-                //{
-                //    var didPause = false;
-                //    var totalPauseTime = 0;
+            byte[] buffer = new byte[bufferSize];
+            int bytesRead;
+            int totalBytesRead = 0;
 
 
-                //    if (_job.IsUserPaused)
-                //    {
-                //        _logger.Debug("Pausing writing to network stream while user has paused playback.");
-
-                //        while (_job.IsUserPaused && totalPauseTime < 30000)
-                //        {
-                //            didPause = true;
-                //            var pauseTime = 500;
-                //            totalPauseTime += pauseTime;
-                //            await Task.Delay(pauseTime).ConfigureAwait(false);
-                //        }
-                //    }
-
-                //    if (didPause)
-                //    {
-                //        _logger.Debug("Resuming writing to network stream due to user unpausing playback.");
-                //    }
-                //}
-
-                destination.Write(array, 0, count);
+            while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
+            {
+                await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
 
 
-                _bytesWritten += count;
+                _bytesWritten += bytesRead;
+                totalBytesRead += bytesRead;
 
 
                 if (_job != null)
                 if (_job != null)
                 {
                 {
                     _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten);
                     _job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten);
                 }
                 }
             }
             }
+
+            return totalBytesRead;
         }
         }
     }
     }
 }
 }

+ 4 - 6
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -10,6 +10,7 @@ using MediaBrowser.Model.Serialization;
 using ServiceStack;
 using ServiceStack;
 using System;
 using System;
 using System.IO;
 using System.IO;
+using System.Threading.Tasks;
 using CommonIO;
 using CommonIO;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dlna;
 
 
@@ -76,7 +77,7 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// </summary>
         /// </summary>
         /// <param name="request">The request.</param>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
-        public object Get(GetVideoStream request)
+        public Task<object> Get(GetVideoStream request)
         {
         {
             return ProcessRequest(request, false);
             return ProcessRequest(request, false);
         }
         }
@@ -86,7 +87,7 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// </summary>
         /// </summary>
         /// <param name="request">The request.</param>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
-        public object Head(GetVideoStream request)
+        public Task<object> Head(GetVideoStream request)
         {
         {
             return ProcessRequest(request, true);
             return ProcessRequest(request, true);
         }
         }
@@ -137,12 +138,9 @@ namespace MediaBrowser.Api.Playback.Progressive
                 args += " -mpegts_m2ts_mode 1";
                 args += " -mpegts_m2ts_mode 1";
             }
             }
 
 
-            var isOutputMkv = string.Equals(state.OutputContainer, "mkv", StringComparison.OrdinalIgnoreCase);
-
             if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
             if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase))
             {
             {
-                if (state.VideoStream != null && IsH264(state.VideoStream) &&
-                    (string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) || isOutputMkv))
+                if (state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     args += " -bsf:v h264_mp4toannexb";
                     args += " -bsf:v h264_mp4toannexb";
                 }
                 }

+ 8 - 5
MediaBrowser.Api/Playback/StreamRequest.cs

@@ -51,7 +51,9 @@ namespace MediaBrowser.Api.Playback
 
 
         [ApiMember(Name = "MaxAudioChannels", Description = "Optional. Specify a maximum number of audio channels to encode to, e.g. 2", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "MaxAudioChannels", Description = "Optional. Specify a maximum number of audio channels to encode to, e.g. 2", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
         public int? MaxAudioChannels { get; set; }
         public int? MaxAudioChannels { get; set; }
-        
+
+        public int? TranscodingMaxAudioChannels { get; set; }
+
         /// <summary>
         /// <summary>
         /// Gets or sets the audio sample rate.
         /// Gets or sets the audio sample rate.
         /// </summary>
         /// </summary>
@@ -189,10 +191,11 @@ namespace MediaBrowser.Api.Playback
 
 
         [ApiMember(Name = "CopyTimestamps", Description = "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "CopyTimestamps", Description = "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         public bool CopyTimestamps { get; set; }
         public bool CopyTimestamps { get; set; }
-        
-        [ApiMember(Name = "Cabac", Description = "Enable if cabac encoding is required", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
-        public bool? Cabac { get; set; }
-        
+
+        public bool ForceLiveStream { get; set; }
+
+        public bool EnableSubtitlesInManifest { get; set; }
+
         public VideoStreamRequest()
         public VideoStreamRequest()
         {
         {
             EnableAutoStreamCopy = true;
             EnableAutoStreamCopy = true;

+ 25 - 15
MediaBrowser.Api/Playback/StreamState.cs

@@ -69,7 +69,29 @@ namespace MediaBrowser.Api.Playback
 
 
         public List<string> PlayableStreamFileNames { get; set; }
         public List<string> PlayableStreamFileNames { get; set; }
 
 
-        public int SegmentLength = 3;
+        public int SegmentLength
+        {
+            get
+            {
+                if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+                {
+                    var userAgent = UserAgent ?? string.Empty;
+                    if (userAgent.IndexOf("AppleTV", StringComparison.OrdinalIgnoreCase) != -1)
+                    {
+                        return 10;
+                    }
+                    if (userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1)
+                    {
+                        return 10;
+                    }
+
+                    return 6;
+                }
+
+                return 3;
+            }
+        }
+
         public int HlsListSize
         public int HlsListSize
         {
         {
             get
             get
@@ -84,9 +106,10 @@ namespace MediaBrowser.Api.Playback
         public long? InputFileSize { get; set; }
         public long? InputFileSize { get; set; }
 
 
         public string OutputAudioSync = "1";
         public string OutputAudioSync = "1";
-        public string OutputVideoSync = "vfr";
+        public string OutputVideoSync = "-1";
 
 
         public List<string> SupportedAudioCodecs { get; set; }
         public List<string> SupportedAudioCodecs { get; set; }
+        public string UserAgent { get; set; }
 
 
         public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger)
         public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger)
         {
         {
@@ -480,18 +503,5 @@ namespace MediaBrowser.Api.Playback
                 return false;
                 return false;
             }
             }
         }
         }
-
-        public bool? IsTargetCabac
-        {
-            get
-            {
-                if (Request.Static)
-                {
-                    return VideoStream == null ? null : VideoStream.IsCabac;
-                }
-
-                return true;
-            }
-        }
     }
     }
 }
 }

+ 2 - 2
MediaBrowser.Api/PlaylistService.cs

@@ -157,7 +157,7 @@ namespace MediaBrowser.Api
             Task.WaitAll(task);
             Task.WaitAll(task);
         }
         }
 
 
-        public object Get(GetPlaylistItems request)
+        public async Task<object> Get(GetPlaylistItems request)
         {
         {
             var playlist = (Playlist)_libraryManager.GetItemById(request.Id);
             var playlist = (Playlist)_libraryManager.GetItemById(request.Id);
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
@@ -178,7 +178,7 @@ namespace MediaBrowser.Api
 
 
             var dtoOptions = GetDtoOptions(request);
             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();
                    .ToArray();
 
 
             var index = 0;
             var index = 0;

+ 89 - 79
MediaBrowser.Api/Reports/ReportsService.cs

@@ -42,7 +42,7 @@ namespace MediaBrowser.Api.Reports
 
 
         /// <summary> Manager for library. </summary>
         /// <summary> Manager for library. </summary>
         private readonly ILibraryManager _libraryManager;   ///< Manager for library
         private readonly ILibraryManager _libraryManager;   ///< Manager for library
-        /// <summary> The localization. </summary>
+                                                            /// <summary> The localization. </summary>
 
 
         private readonly ILocalizationManager _localization;    ///< The localization
         private readonly ILocalizationManager _localization;    ///< The localization
 
 
@@ -58,10 +58,10 @@ namespace MediaBrowser.Api.Reports
         /// <summary> Gets the given request. </summary>
         /// <summary> Gets the given request. </summary>
         /// <param name="request"> The request. </param>
         /// <param name="request"> The request. </param>
         /// <returns> A Task&lt;object&gt; </returns>
         /// <returns> A Task&lt;object&gt; </returns>
-        public async Task<object> Get(GetActivityLogs request)
+        public object Get(GetActivityLogs request)
         {
         {
             request.DisplayType = "Screen";
             request.DisplayType = "Screen";
-            ReportResult result = await GetReportActivities(request).ConfigureAwait(false);
+            ReportResult result = GetReportActivities(request);
             return ToOptimizedResult(result);
             return ToOptimizedResult(result);
         }
         }
 
 
@@ -104,7 +104,8 @@ namespace MediaBrowser.Api.Reports
                 return null;
                 return null;
 
 
             request.DisplayType = "Screen";
             request.DisplayType = "Screen";
-            var reportResult = await GetReportResult(request);
+            var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+            var reportResult = await GetReportResult(request, user);
 
 
             return ToOptimizedResult(reportResult);
             return ToOptimizedResult(reportResult);
         }
         }
@@ -117,7 +118,8 @@ namespace MediaBrowser.Api.Reports
             if (string.IsNullOrEmpty(request.IncludeItemTypes))
             if (string.IsNullOrEmpty(request.IncludeItemTypes))
                 return null;
                 return null;
             request.DisplayType = "Screen";
             request.DisplayType = "Screen";
-            var reportResult = await GetReportStatistic(request);
+            var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+            var reportResult = await GetReportStatistic(request, user);
 
 
             return ToOptimizedResult(reportResult);
             return ToOptimizedResult(reportResult);
         }
         }
@@ -150,6 +152,7 @@ namespace MediaBrowser.Api.Reports
             headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename);
             headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename);
             headers["Content-Encoding"] = "UTF-8";
             headers["Content-Encoding"] = "UTF-8";
 
 
+            var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
             ReportResult result = null;
             ReportResult result = null;
             switch (reportViewType)
             switch (reportViewType)
             {
             {
@@ -157,12 +160,12 @@ namespace MediaBrowser.Api.Reports
                 case ReportViewType.ReportData:
                 case ReportViewType.ReportData:
                     ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
                     ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
                     ReportBuilder dataBuilder = new ReportBuilder(_libraryManager);
                     ReportBuilder dataBuilder = new ReportBuilder(_libraryManager);
-                    QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+                    QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
                     result = dataBuilder.GetResult(queryResult.Items, request);
                     result = dataBuilder.GetResult(queryResult.Items, request);
                     result.TotalRecordCount = queryResult.TotalRecordCount;
                     result.TotalRecordCount = queryResult.TotalRecordCount;
                     break;
                     break;
                 case ReportViewType.ReportActivities:
                 case ReportViewType.ReportActivities:
-                    result = await GetReportActivities(request).ConfigureAwait(false);
+                    result = GetReportActivities(request);
                     break;
                     break;
             }
             }
 
 
@@ -177,23 +180,15 @@ namespace MediaBrowser.Api.Reports
                     break;
                     break;
             }
             }
 
 
-            object ro = ResultFactory.GetResult(returnResult, contentType, headers);
-            return ro;
+            return ResultFactory.GetResult(returnResult, contentType, headers);
         }
         }
 
 
         #endregion
         #endregion
 
 
-        #region [Private Methods]
-
-        /// <summary> Gets items query. </summary>
-        /// <param name="request"> The request. </param>
-        /// <param name="user"> The user. </param>
-        /// <returns> The items query. </returns>
         private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user)
         private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user)
         {
         {
-            var query = new InternalItemsQuery
+            var query = new InternalItemsQuery(user)
             {
             {
-                User = user,
                 IsPlayed = request.IsPlayed,
                 IsPlayed = request.IsPlayed,
                 MediaTypes = request.GetMediaTypes(),
                 MediaTypes = request.GetMediaTypes(),
                 IncludeItemTypes = request.GetIncludeItemTypes(),
                 IncludeItemTypes = request.GetIncludeItemTypes(),
@@ -213,7 +208,6 @@ namespace MediaBrowser.Api.Reports
                 NameStartsWith = request.NameStartsWith,
                 NameStartsWith = request.NameStartsWith,
                 NameStartsWithOrGreater = request.NameStartsWithOrGreater,
                 NameStartsWithOrGreater = request.NameStartsWithOrGreater,
                 HasImdbId = request.HasImdbId,
                 HasImdbId = request.HasImdbId,
-                IsYearMismatched = request.IsYearMismatched,
                 IsPlaceHolder = request.IsPlaceHolder,
                 IsPlaceHolder = request.IsPlaceHolder,
                 IsLocked = request.IsLocked,
                 IsLocked = request.IsLocked,
                 IsInBoxSet = request.IsInBoxSet,
                 IsInBoxSet = request.IsInBoxSet,
@@ -232,6 +226,7 @@ namespace MediaBrowser.Api.Reports
                 Tags = request.GetTags(),
                 Tags = request.GetTags(),
                 OfficialRatings = request.GetOfficialRatings(),
                 OfficialRatings = request.GetOfficialRatings(),
                 Genres = request.GetGenres(),
                 Genres = request.GetGenres(),
+                GenreIds = request.GetGenreIds(),
                 Studios = request.GetStudios(),
                 Studios = request.GetStudios(),
                 StudioIds = request.GetStudioIds(),
                 StudioIds = request.GetStudioIds(),
                 Person = request.Person,
                 Person = request.Person,
@@ -246,9 +241,11 @@ namespace MediaBrowser.Api.Reports
                 MaxPlayers = request.MaxPlayers,
                 MaxPlayers = request.MaxPlayers,
                 MinCommunityRating = request.MinCommunityRating,
                 MinCommunityRating = request.MinCommunityRating,
                 MinCriticRating = request.MinCriticRating,
                 MinCriticRating = request.MinCriticRating,
+                ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId),
                 ParentIndexNumber = request.ParentIndexNumber,
                 ParentIndexNumber = request.ParentIndexNumber,
                 AiredDuringSeason = request.AiredDuringSeason,
                 AiredDuringSeason = request.AiredDuringSeason,
-                AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater
+                AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
+                EnableTotalRecordCount = request.EnableTotalRecordCount
             };
             };
 
 
             if (!string.IsNullOrWhiteSpace(request.Ids))
             if (!string.IsNullOrWhiteSpace(request.Ids))
@@ -326,15 +323,15 @@ namespace MediaBrowser.Api.Reports
             }
             }
 
 
             // Min official rating
             // Min official rating
-            if (!string.IsNullOrEmpty(request.MinOfficialRating))
+            if (!string.IsNullOrWhiteSpace(request.MinOfficialRating))
             {
             {
                 query.MinParentalRating = _localization.GetRatingLevel(request.MinOfficialRating);
                 query.MinParentalRating = _localization.GetRatingLevel(request.MinOfficialRating);
             }
             }
 
 
             // Max official rating
             // Max official rating
-            if (!string.IsNullOrEmpty(request.MaxOfficialRating))
+            if (!string.IsNullOrWhiteSpace(request.MaxOfficialRating))
             {
             {
-                query.MaxParentalRating = _localization.GetRatingLevel(request.MinOfficialRating);
+                query.MaxParentalRating = _localization.GetRatingLevel(request.MaxOfficialRating);
             }
             }
 
 
             // Artists
             // Artists
@@ -358,98 +355,111 @@ namespace MediaBrowser.Api.Reports
                 query.AlbumNames = request.Albums.Split('|');
                 query.AlbumNames = request.Albums.Split('|');
             }
             }
 
 
-            if (request.HasQueryLimit == false)
-            {
-                query.StartIndex = null;
-                query.Limit = null;
-            }
-
             return query;
             return query;
         }
         }
 
 
-        /// <summary> Gets query result. </summary>
-        /// <param name="request"> The request. </param>
-        /// <returns> The query result. </returns>
-        private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request)
+        private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request, User user)
         {
         {
-            // Placeholder in case needed later
+            // all report queries currently need this because it's not being specified
             request.Recursive = true;
             request.Recursive = true;
-            var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
-            request.Fields = "MediaSources,DateCreated,Settings,Studios,SyncInfo,ItemCounts";
-
-            var parentItem = string.IsNullOrEmpty(request.ParentId) ?
-                (user == null ? _libraryManager.RootFolder : user.RootFolder) :
-                _libraryManager.GetItemById(request.ParentId);
 
 
             var item = string.IsNullOrEmpty(request.ParentId) ?
             var item = string.IsNullOrEmpty(request.ParentId) ?
                 user == null ? _libraryManager.RootFolder : user.RootFolder :
                 user == null ? _libraryManager.RootFolder : user.RootFolder :
-                parentItem;
+                _libraryManager.GetItemById(request.ParentId);
 
 
-            IEnumerable<BaseItem> items;
+            if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase))
+            {
+                //item = user == null ? _libraryManager.RootFolder : user.RootFolder;
+            }
+            else if (string.Equals(request.IncludeItemTypes, "BoxSet", StringComparison.OrdinalIgnoreCase))
+            {
+                item = user == null ? _libraryManager.RootFolder : user.RootFolder;
+            }
 
 
-            if (request.Recursive)
+            // Default list type = children
+
+            var folder = item as Folder;
+            if (folder == null)
             {
             {
-                var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
-                return result;
+                folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder();
             }
             }
-            else
+
+            if (!string.IsNullOrEmpty(request.Ids))
             {
             {
-                if (user == null)
+                request.Recursive = true;
+                var query = GetItemsQuery(request, user);
+                var result = await folder.GetItems(query).ConfigureAwait(false);
+
+                if (string.IsNullOrWhiteSpace(request.SortBy))
                 {
                 {
-                    var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
-                    return result;
+                    var ids = query.ItemIds.ToList();
+
+                    // Try to preserve order
+                    result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
                 }
                 }
 
 
-                var userRoot = item as UserRootFolder;
+                return result;
+            }
 
 
-                if (userRoot == null)
-                {
-                    var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+            if (request.Recursive)
+            {
+                return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+            }
 
 
-                    return result;
-                }
+            if (user == null)
+            {
+                return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
+            }
+
+            var userRoot = item as UserRootFolder;
 
 
-                items = ((Folder)item).GetChildren(user, true);
+            if (userRoot == null)
+            {
+                return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
             }
             }
 
 
-            return new QueryResult<BaseItem> { Items = items.ToArray() };
+            IEnumerable<BaseItem> items = folder.GetChildren(user, true);
 
 
+            var itemsArray = items.ToArray();
+
+            return new QueryResult<BaseItem>
+            {
+                Items = itemsArray,
+                TotalRecordCount = itemsArray.Length
+            };
         }
         }
 
 
+        #region [Private Methods]
+
         /// <summary> Gets report activities. </summary>
         /// <summary> Gets report activities. </summary>
         /// <param name="request"> The request. </param>
         /// <param name="request"> The request. </param>
         /// <returns> The report activities. </returns>
         /// <returns> The report activities. </returns>
-        private Task<ReportResult> GetReportActivities(IReportsDownload request)
+        private ReportResult GetReportActivities(IReportsDownload request)
         {
         {
-            return Task<ReportResult>.Run(() =>
-            {
-                DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
-                (DateTime?)null :
-                DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
-
-                QueryResult<ActivityLogEntry> queryResult;
-                 if (request.HasQueryLimit)   
-                   queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
-                 else
-                     queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null);
-                //var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
-
-                ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager);
-                var result = builder.GetResult(queryResult, request);
-                result.TotalRecordCount = queryResult.TotalRecordCount;
-                return result;
+            DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
+            (DateTime?)null :
+            DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
 
 
-            });
+            QueryResult<ActivityLogEntry> queryResult;
+            if (request.HasQueryLimit)
+                queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
+            else
+                queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null);
+            //var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit);
 
 
+            ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager);
+            var result = builder.GetResult(queryResult, request);
+            result.TotalRecordCount = queryResult.TotalRecordCount;
+            return result;
         }
         }
 
 
         /// <summary> Gets report result. </summary>
         /// <summary> Gets report result. </summary>
         /// <param name="request"> The request. </param>
         /// <param name="request"> The request. </param>
         /// <returns> The report result. </returns>
         /// <returns> The report result. </returns>
-        private async Task<ReportResult> GetReportResult(GetItemReport request)
+        private async Task<ReportResult> GetReportResult(GetItemReport request, User user)
         {
         {
             ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
             ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
-            QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+            QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
             ReportResult reportResult = reportBuilder.GetResult(queryResult.Items, request);
             ReportResult reportResult = reportBuilder.GetResult(queryResult.Items, request);
             reportResult.TotalRecordCount = queryResult.TotalRecordCount;
             reportResult.TotalRecordCount = queryResult.TotalRecordCount;
 
 
@@ -459,10 +469,10 @@ namespace MediaBrowser.Api.Reports
         /// <summary> Gets report statistic. </summary>
         /// <summary> Gets report statistic. </summary>
         /// <param name="request"> The request. </param>
         /// <param name="request"> The request. </param>
         /// <returns> The report statistic. </returns>
         /// <returns> The report statistic. </returns>
-        private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request)
+        private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request, User user)
         {
         {
             ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
             ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
-            QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+            QueryResult<BaseItem> queryResult = await GetQueryResult(request, user).ConfigureAwait(false);
 
 
             ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager);
             ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager);
             ReportStatResult reportResult = reportBuilder.GetResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5);
             ReportStatResult reportResult = reportBuilder.GetResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5);

+ 17 - 19
MediaBrowser.Api/SimilarItemsHelper.cs

@@ -9,6 +9,8 @@ using ServiceStack;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Dto;
 
 
 namespace MediaBrowser.Api
 namespace MediaBrowser.Api
 {
 {
@@ -23,6 +25,8 @@ namespace MediaBrowser.Api
         /// <value>The id.</value>
         /// <value>The id.</value>
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
         [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
         public string Id { get; set; }
         public string Id { get; set; }
+
+        public string ExcludeArtistIds { get; set; }
     }
     }
 
 
     public class BaseGetSimilarItems : IReturn<ItemsResult>, IHasItemFields
     public class BaseGetSimilarItems : IReturn<ItemsResult>, IHasItemFields
@@ -54,7 +58,7 @@ namespace MediaBrowser.Api
     /// </summary>
     /// </summary>
     public static class SimilarItemsHelper
     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;
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? userManager.GetUserById(request.UserId) : null;
 
 
@@ -68,6 +72,12 @@ namespace MediaBrowser.Api
                 Recursive = true
                 Recursive = true
             };
             };
 
 
+            // ExcludeArtistIds
+            if (!string.IsNullOrEmpty(request.ExcludeArtistIds))
+            {
+                query.ExcludeArtistIds = request.ExcludeArtistIds.Split('|');
+            }
+
             var inputItems = libraryManager.GetItemList(query);
             var inputItems = libraryManager.GetItemList(query);
 
 
             var items = GetSimilaritems(item, libraryManager, inputItems, getSimilarityScore)
             var items = GetSimilaritems(item, libraryManager, inputItems, getSimilarityScore)
@@ -80,14 +90,14 @@ namespace MediaBrowser.Api
                 returnItems = returnItems.Take(request.Limit.Value);
                 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
                 TotalRecordCount = items.Count
             };
             };
-
-            return result;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -116,24 +126,12 @@ namespace MediaBrowser.Api
 
 
         private static IEnumerable<string> GetTags(BaseItem item)
         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)
         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>
         /// <summary>

+ 19 - 9
MediaBrowser.Api/StartupWizardService.cs

@@ -11,6 +11,7 @@ using ServiceStack;
 using System;
 using System;
 using System.Linq;
 using System.Linq;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using MediaBrowser.Controller.MediaEncoding;
 
 
 namespace MediaBrowser.Api
 namespace MediaBrowser.Api
 {
 {
@@ -52,34 +53,33 @@ namespace MediaBrowser.Api
         private readonly IUserManager _userManager;
         private readonly IUserManager _userManager;
         private readonly IConnectManager _connectManager;
         private readonly IConnectManager _connectManager;
         private readonly ILiveTvManager _liveTvManager;
         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;
             _config = config;
             _appHost = appHost;
             _appHost = appHost;
             _userManager = userManager;
             _userManager = userManager;
             _connectManager = connectManager;
             _connectManager = connectManager;
             _liveTvManager = liveTvManager;
             _liveTvManager = liveTvManager;
+            _mediaEncoder = mediaEncoder;
         }
         }
 
 
         public void Post(ReportStartupWizardComplete request)
         public void Post(ReportStartupWizardComplete request)
         {
         {
             _config.Configuration.IsStartupWizardCompleted = true;
             _config.Configuration.IsStartupWizardCompleted = true;
-            _config.Configuration.EnableLocalizedGuids = true;
-            _config.Configuration.EnableCustomPathSubFolders = true;
-            _config.Configuration.EnableDateLastRefresh = true;
-            _config.Configuration.EnableStandaloneMusicKeys = true;
-            _config.Configuration.EnableCaseSensitiveItemIds = true;
+            SetWizardFinishValues(_config.Configuration);
             _config.SaveConfiguration();
             _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
             return new StartupInfo
             {
             {
-                SupportsRunningAsService = info.SupportsRunningAsService
+                SupportsRunningAsService = info.SupportsRunningAsService,
+                HasMediaEncoder = !string.IsNullOrWhiteSpace(_mediaEncoder.EncoderPath)
             };
             };
         }
         }
 
 
@@ -111,6 +111,15 @@ namespace MediaBrowser.Api
             return result;
             return result;
         }
         }
 
 
+        private void SetWizardFinishValues(ServerConfiguration config)
+        {
+            config.EnableLocalizedGuids = true;
+            config.EnableStandaloneMusicKeys = true;
+            config.EnableCaseSensitiveItemIds = true;
+            //config.EnableFolderView = true;
+            config.SchemaVersion = 108;
+        }
+
         public void Post(UpdateStartupConfiguration request)
         public void Post(UpdateStartupConfiguration request)
         {
         {
             _config.Configuration.UICulture = request.UICulture;
             _config.Configuration.UICulture = request.UICulture;
@@ -225,6 +234,7 @@ namespace MediaBrowser.Api
     public class StartupInfo
     public class StartupInfo
     {
     {
         public bool SupportsRunningAsService { get; set; }
         public bool SupportsRunningAsService { get; set; }
+        public bool HasMediaEncoder { get; set; }
     }
     }
 
 
     public class StartupUser
     public class StartupUser

+ 28 - 11
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")]
         [ApiMember(Name = "EndPositionTicks", Description = "EndPositionTicks", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public long? EndPositionTicks { get; set; }
         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.")]
     [Route("/Videos/{Id}/{MediaSourceId}/Subtitles/{Index}/subtitles.m3u8", "GET", Summary = "Gets an HLS subtitle playlist.")]
@@ -159,6 +163,7 @@ namespace MediaBrowser.Api.Subtitles
             builder.AppendLine("#EXT-X-TARGETDURATION:" + request.SegmentLength.ToString(CultureInfo.InvariantCulture));
             builder.AppendLine("#EXT-X-TARGETDURATION:" + request.SegmentLength.ToString(CultureInfo.InvariantCulture));
             builder.AppendLine("#EXT-X-VERSION:3");
             builder.AppendLine("#EXT-X-VERSION:3");
             builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
             builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
+            builder.AppendLine("#EXT-X-PLAYLIST-TYPE:VOD");
 
 
             long positionTicks = 0;
             long positionTicks = 0;
             var segmentLengthTicks = TimeSpan.FromSeconds(request.SegmentLength).Ticks;
             var segmentLengthTicks = TimeSpan.FromSeconds(request.SegmentLength).Ticks;
@@ -170,11 +175,11 @@ namespace MediaBrowser.Api.Subtitles
                 var remaining = runtime - positionTicks;
                 var remaining = runtime - positionTicks;
                 var lengthTicks = Math.Min(remaining, segmentLengthTicks);
                 var lengthTicks = Math.Min(remaining, segmentLengthTicks);
 
 
-                builder.AppendLine("#EXTINF:" + TimeSpan.FromTicks(lengthTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture));
+                builder.AppendLine("#EXTINF:" + TimeSpan.FromTicks(lengthTicks).TotalSeconds.ToString(CultureInfo.InvariantCulture) + ",");
 
 
                 var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks);
                 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),
                     positionTicks.ToString(CultureInfo.InvariantCulture),
                     endPositionTicks.ToString(CultureInfo.InvariantCulture),
                     endPositionTicks.ToString(CultureInfo.InvariantCulture),
                     accessToken);
                     accessToken);
@@ -189,7 +194,7 @@ namespace MediaBrowser.Api.Subtitles
             return ResultFactory.GetResult(builder.ToString(), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
             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))
             if (string.Equals(request.Format, "js", StringComparison.OrdinalIgnoreCase))
             {
             {
@@ -205,23 +210,35 @@ namespace MediaBrowser.Api.Subtitles
                 var subtitleStream = mediaSource.MediaStreams
                 var subtitleStream = mediaSource.MediaStreams
                     .First(i => i.Type == MediaStreamType.Subtitle && i.Index == request.Index);
                     .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.MediaSourceId,
                 request.Index,
                 request.Index,
                 request.Format,
                 request.Format,
                 request.StartPositionTicks,
                 request.StartPositionTicks,
                 request.EndPositionTicks,
                 request.EndPositionTicks,
-                CancellationToken.None).ConfigureAwait(false);
+                request.CopyTimestamps,
+                CancellationToken.None);
         }
         }
 
 
         public object Get(SearchRemoteSubtitles request)
         public object Get(SearchRemoteSubtitles request)
@@ -247,9 +264,9 @@ namespace MediaBrowser.Api.Subtitles
             return ToOptimizedResult(result);
             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));
             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);
             Task.WaitAll(task);
         }
         }
 
 
-        public object Get(GetSyncJobItemFile request)
+        public async Task<object> Get(GetSyncJobItemFile request)
         {
         {
             var jobItem = _syncManager.GetJobItem(request.Id);
             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.");
                 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,
                 Path = jobItem.OutputPath,
                 OnError = () =>
                 OnError = () =>
@@ -252,10 +251,11 @@ namespace MediaBrowser.Api.Sync
                     var failedTask = _syncManager.ReportSyncJobItemTransferFailed(request.Id);
                     var failedTask = _syncManager.ReportSyncJobItemTransferFailed(request.Id);
                     Task.WaitAll(failedTask);
                     Task.WaitAll(failedTask);
                 }
                 }
-            });
+
+            }).ConfigureAwait(false);
         }
         }
 
 
-        public object Get(GetSyncDialogOptions request)
+        public async Task<object> Get(GetSyncDialogOptions request)
         {
         {
             var result = new SyncDialogOptions();
             var result = new SyncDialogOptions();
 
 
@@ -298,8 +298,7 @@ namespace MediaBrowser.Api.Sync
                     .Select(_libraryManager.GetItemById)
                     .Select(_libraryManager.GetItemById)
                     .Where(i => i != null);
                     .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);
                 result.Options = SyncHelper.GetSyncOptions(dtos);
             }
             }
@@ -343,7 +342,7 @@ namespace MediaBrowser.Api.Sync
             Task.WaitAll(task);
             Task.WaitAll(task);
         }
         }
 
 
-        public object Get(GetSyncJobItemAdditionalFile request)
+        public Task<object> Get(GetSyncJobItemAdditionalFile request)
         {
         {
             var jobItem = _syncManager.GetJobItem(request.Id);
             var jobItem = _syncManager.GetJobItem(request.Id);
 
 
@@ -359,7 +358,7 @@ namespace MediaBrowser.Api.Sync
                 throw new ArgumentException("Sync job additional file not found.");
                 throw new ArgumentException("Sync job additional file not found.");
             }
             }
 
 
-            return ToStaticFileResult(file.Path);
+            return ResultFactory.GetStaticFileResult(Request, file.Path);
         }
         }
 
 
         public void Post(EnableSyncJobItem request)
         public void Post(EnableSyncJobItem request)

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

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

+ 80 - 39
MediaBrowser.Api/TvShowsService.cs

@@ -12,6 +12,8 @@ using ServiceStack;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Dto;
 
 
 namespace MediaBrowser.Api
 namespace MediaBrowser.Api
 {
 {
@@ -123,7 +125,7 @@ namespace MediaBrowser.Api
     }
     }
 
 
     [Route("/Shows/{Id}/Episodes", "GET", Summary = "Gets episodes for a tv season")]
     [Route("/Shows/{Id}/Episodes", "GET", Summary = "Gets episodes for a tv season")]
-    public class GetEpisodes : IReturn<ItemsResult>, IHasItemFields
+    public class GetEpisodes : IReturn<ItemsResult>, IHasItemFields, IHasDtoOptions
     {
     {
         /// <summary>
         /// <summary>
         /// Gets or sets the user id.
         /// Gets or sets the user id.
@@ -173,10 +175,19 @@ namespace MediaBrowser.Api
         /// <value>The limit.</value>
         /// <value>The limit.</value>
         [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
         public int? Limit { get; set; }
         public int? Limit { get; set; }
+
+        [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
+        public bool? EnableImages { get; set; }
+
+        [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? ImageTypeLimit { get; set; }
+
+        [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string EnableImageTypes { get; set; }
     }
     }
 
 
     [Route("/Shows/{Id}/Seasons", "GET", Summary = "Gets seasons for a tv series")]
     [Route("/Shows/{Id}/Seasons", "GET", Summary = "Gets seasons for a tv series")]
-    public class GetSeasons : IReturn<ItemsResult>, IHasItemFields
+    public class GetSeasons : IReturn<ItemsResult>, IHasItemFields, IHasDtoOptions
     {
     {
         /// <summary>
         /// <summary>
         /// Gets or sets the user id.
         /// Gets or sets the user id.
@@ -206,6 +217,15 @@ namespace MediaBrowser.Api
 
 
         [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string AdjacentTo { get; set; }
         public string AdjacentTo { get; set; }
+
+        [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
+        public bool? EnableImages { get; set; }
+
+        [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+        public int? ImageTypeLimit { get; set; }
+
+        [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string EnableImageTypes { get; set; }
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -253,29 +273,51 @@ namespace MediaBrowser.Api
         /// </summary>
         /// </summary>
         /// <param name="request">The request.</param>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
         /// <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 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 user = _userManager.GetUserById(request.UserId);
 
 
-            var minPremiereDate = DateTime.Now.Date.ToUniversalTime();
+            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)
             var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
             {
             {
@@ -284,13 +326,15 @@ namespace MediaBrowser.Api
                 SortOrder = SortOrder.Ascending,
                 SortOrder = SortOrder.Ascending,
                 MinPremiereDate = minPremiereDate,
                 MinPremiereDate = minPremiereDate,
                 StartIndex = request.StartIndex,
                 StartIndex = request.StartIndex,
-                Limit = request.Limit
+                Limit = request.Limit,
+                ParentId = parentIdGuid,
+                Recursive = true
 
 
-            }, parentIds).ToList();
+            }).ToList();
 
 
             var options = GetDtoOptions(request);
             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
             var result = new ItemsResult
             {
             {
@@ -306,7 +350,7 @@ namespace MediaBrowser.Api
         /// </summary>
         /// </summary>
         /// <param name="request">The request.</param>
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
-        public object Get(GetNextUpEpisodes request)
+        public async Task<object> Get(GetNextUpEpisodes request)
         {
         {
             var result = _tvSeriesManager.GetNextUp(new NextUpQuery
             var result = _tvSeriesManager.GetNextUp(new NextUpQuery
             {
             {
@@ -321,7 +365,7 @@ namespace MediaBrowser.Api
 
 
             var options = GetDtoOptions(request);
             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
             return ToOptimizedSerializedResultUsingCache(new ItemsResult
             {
             {
@@ -354,7 +398,7 @@ namespace MediaBrowser.Api
             return items;
             return items;
         }
         }
 
 
-        public object Get(GetSeasons request)
+        public async Task<object> Get(GetSeasons request)
         {
         {
             var user = _userManager.GetUserById(request.UserId);
             var user = _userManager.GetUserById(request.UserId);
 
 
@@ -385,7 +429,7 @@ namespace MediaBrowser.Api
 
 
             var dtoOptions = GetDtoOptions(request);
             var dtoOptions = GetDtoOptions(request);
 
 
-            var returnItems = _dtoService.GetBaseItemDtos(seasons, dtoOptions, user)
+            var returnItems = (await _dtoService.GetBaseItemDtos(seasons, dtoOptions, user).ConfigureAwait(false))
                 .ToArray();
                 .ToArray();
 
 
             return new ItemsResult
             return new ItemsResult
@@ -397,21 +441,10 @@ namespace MediaBrowser.Api
 
 
         private IEnumerable<Season> FilterVirtualSeasons(GetSeasons request, IEnumerable<Season> items)
         private IEnumerable<Season> FilterVirtualSeasons(GetSeasons request, IEnumerable<Season> items)
         {
         {
-            if (request.IsMissing.HasValue && request.IsVirtualUnaired.HasValue)
-            {
-                var isMissing = request.IsMissing.Value;
-                var isVirtualUnaired = request.IsVirtualUnaired.Value;
-
-                if (!isMissing && !isVirtualUnaired)
-                {
-                    return items.Where(i => !i.IsMissingOrVirtualUnaired);
-                }
-            }
-
             if (request.IsMissing.HasValue)
             if (request.IsMissing.HasValue)
             {
             {
                 var val = request.IsMissing.Value;
                 var val = request.IsMissing.Value;
-                items = items.Where(i => i.IsMissingSeason == val);
+                items = items.Where(i => (i.IsMissingSeason) == val);
             }
             }
 
 
             if (request.IsVirtualUnaired.HasValue)
             if (request.IsVirtualUnaired.HasValue)
@@ -423,7 +456,7 @@ namespace MediaBrowser.Api
             return items;
             return items;
         }
         }
 
 
-        public object Get(GetEpisodes request)
+        public async Task<object> Get(GetEpisodes request)
         {
         {
             var user = _userManager.GetUserById(request.UserId);
             var user = _userManager.GetUserById(request.UserId);
 
 
@@ -449,7 +482,16 @@ namespace MediaBrowser.Api
                     throw new ResourceNotFoundException("No series exists with Id " + request.Id);
                     throw new ResourceNotFoundException("No series exists with Id " + request.Id);
                 }
                 }
 
 
-                episodes = series.GetEpisodes(user, request.Season.Value);
+                var season = series.GetSeasons(user).FirstOrDefault(i => i.IndexNumber == request.Season.Value);
+
+                if (season == null)
+                {
+                    episodes = new List<Episode>();
+                }
+                else
+                {
+                    episodes = series.GetEpisodes(user, season);
+                }
             }
             }
             else
             else
             {
             {
@@ -490,14 +532,13 @@ namespace MediaBrowser.Api
                 returnItems = UserViewBuilder.FilterForAdjacency(returnItems, request.AdjacentTo);
                 returnItems = UserViewBuilder.FilterForAdjacency(returnItems, request.AdjacentTo);
             }
             }
 
 
-            var returnList = _libraryManager.ReplaceVideosWithPrimaryVersions(returnItems)
-                .ToList();
+            var returnList = returnItems.ToList();
 
 
             var pagedItems = ApplyPaging(returnList, request.StartIndex, request.Limit);
             var pagedItems = ApplyPaging(returnList, request.StartIndex, request.Limit);
 
 
             var dtoOptions = GetDtoOptions(request);
             var dtoOptions = GetDtoOptions(request);
 
 
-            var dtos = _dtoService.GetBaseItemDtos(pagedItems, dtoOptions, user)
+            var dtos = (await _dtoService.GetBaseItemDtos(pagedItems, dtoOptions, user).ConfigureAwait(false))
                 .ToArray();
                 .ToArray();
 
 
             return new ItemsResult
             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;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
@@ -8,6 +9,8 @@ using MediaBrowser.Model.Dto;
 using ServiceStack;
 using ServiceStack;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
 
 
 namespace MediaBrowser.Api.UserLibrary
 namespace MediaBrowser.Api.UserLibrary
 {
 {
@@ -100,7 +103,12 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public object Get(GetArtists request)
         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);
             return ToOptimizedResult(result);
         }
         }
@@ -112,11 +120,26 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public object Get(GetAlbumArtists request)
         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);
             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>
         /// <summary>
         /// Gets all items.
         /// Gets all items.
         /// </summary>
         /// </summary>
@@ -125,16 +148,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
         /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
         protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
         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();
         }
         }
     }
     }
 }
 }

+ 146 - 12
MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs

@@ -8,6 +8,7 @@ using ServiceStack;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using MediaBrowser.Model.Dto;
 
 
 namespace MediaBrowser.Api.UserLibrary
 namespace MediaBrowser.Api.UserLibrary
 {
 {
@@ -83,6 +84,137 @@ namespace MediaBrowser.Api.UserLibrary
             return null;
             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>
         /// <summary>
         /// Gets the specified request.
         /// Gets the specified request.
         /// </summary>
         /// </summary>
@@ -121,6 +253,13 @@ namespace MediaBrowser.Api.UserLibrary
             var includeItemTypes = request.GetIncludeItemTypes();
             var includeItemTypes = request.GetIncludeItemTypes();
             var mediaTypes = request.GetMediaTypes();
             var mediaTypes = request.GetMediaTypes();
 
 
+            var query = new InternalItemsQuery(user)
+            {
+                ExcludeItemTypes = excludeItemTypes,
+                IncludeItemTypes = includeItemTypes,
+                MediaTypes = mediaTypes
+            };
+
             Func<BaseItem, bool> filter = i => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes);
             Func<BaseItem, bool> filter = i => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes);
 
 
             if (parentItem.IsFolder)
             if (parentItem.IsFolder)
@@ -130,7 +269,7 @@ namespace MediaBrowser.Api.UserLibrary
                 if (!string.IsNullOrWhiteSpace(request.UserId))
                 if (!string.IsNullOrWhiteSpace(request.UserId))
                 {
                 {
                     items = request.Recursive ?
                     items = request.Recursive ?
-                        folder.GetRecursiveChildren(user, filter) :
+                        folder.GetRecursiveChildren(user, query) :
                         folder.GetChildren(user, true).Where(filter);
                         folder.GetChildren(user, true).Where(filter);
                 }
                 }
                 else
                 else
@@ -274,7 +413,7 @@ namespace MediaBrowser.Api.UserLibrary
             {
             {
                 items = items.Where(i =>
                 items = items.Where(i =>
                     {
                     {
-                        var userdata = UserDataRepository.GetUserData(user.Id, i.GetUserDataKey());
+                        var userdata = UserDataRepository.GetUserData(user, i);
 
 
                         return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value;
                         return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value;
                     });
                     });
@@ -284,7 +423,7 @@ namespace MediaBrowser.Api.UserLibrary
             {
             {
                 items = items.Where(i =>
                 items = items.Where(i =>
                 {
                 {
-                    var userdata = UserDataRepository.GetUserData(user.Id, i.GetUserDataKey());
+                    var userdata = UserDataRepository.GetUserData(user, i);
 
 
                     return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value;
                     return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value;
                 });
                 });
@@ -294,7 +433,7 @@ namespace MediaBrowser.Api.UserLibrary
             {
             {
                 items = items.Where(i =>
                 items = items.Where(i =>
                 {
                 {
-                    var userdata = UserDataRepository.GetUserData(user.Id, i.GetUserDataKey());
+                    var userdata = UserDataRepository.GetUserData(user, i);
 
 
                     var likes = userdata.Likes ?? false;
                     var likes = userdata.Likes ?? false;
                     var favorite = userdata.IsFavorite;
                     var favorite = userdata.IsFavorite;
@@ -307,7 +446,7 @@ namespace MediaBrowser.Api.UserLibrary
             {
             {
                 items = items.Where(i =>
                 items = items.Where(i =>
                 {
                 {
-                    var userdata = UserDataRepository.GetUserData(user.Id, i.GetUserDataKey());
+                    var userdata = UserDataRepository.GetUserData(user, i);
 
 
                     return userdata != null && userdata.IsFavorite;
                     return userdata != null && userdata.IsFavorite;
                 });
                 });
@@ -326,12 +465,7 @@ namespace MediaBrowser.Api.UserLibrary
                 var tags = request.GetTags();
                 var tags = request.GetTags();
                 if (tags.Length > 0)
                 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;
                         return false;
                     }
                     }
@@ -372,7 +506,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="includeItemTypes">The include item types.</param>
         /// <param name="includeItemTypes">The include item types.</param>
         /// <param name="mediaTypes">The media types.</param>
         /// <param name="mediaTypes">The media types.</param>
         /// <returns>IEnumerable{BaseItem}.</returns>
         /// <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
             // Exclude item types
             if (excludeItemTypes.Length > 0)
             if (excludeItemTypes.Length > 0)

+ 13 - 4
MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs

@@ -12,6 +12,7 @@ namespace MediaBrowser.Api.UserLibrary
         protected BaseItemsRequest()
         protected BaseItemsRequest()
         {
         {
             EnableImages = true;
             EnableImages = true;
+            EnableTotalRecordCount = true;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -99,12 +100,13 @@ 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")]
         [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; }
         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")]
         [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; }
         public bool? IsInBoxSet { get; set; }
-        
+
+        public string ExcludeItemIds { get; set; }
+
+        public bool EnableTotalRecordCount { get; set; }
+
         /// <summary>
         /// <summary>
         /// Skips over a given number of items within the results. Use for paging.
         /// Skips over a given number of items within the results. Use for paging.
         /// </summary>
         /// </summary>
@@ -264,6 +266,8 @@ namespace MediaBrowser.Api.UserLibrary
         [ApiMember(Name = "Artists", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         [ApiMember(Name = "Artists", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Artists { get; set; }
         public string Artists { get; set; }
 
 
+        public string ExcludeArtistIds { get; set; }
+
         [ApiMember(Name = "ArtistIds", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         [ApiMember(Name = "ArtistIds", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string ArtistIds { get; set; }
         public string ArtistIds { get; set; }
 
 
@@ -367,6 +371,11 @@ namespace MediaBrowser.Api.UserLibrary
             return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
             return (IncludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
         }
         }
 
 
+        public string[] GetExcludeItemIds()
+        {
+            return (ExcludeItemIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+        }
+
         public string[] GetExcludeItemTypes()
         public string[] GetExcludeItemTypes()
         {
         {
             return (ExcludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
             return (ExcludeItemTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

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

@@ -9,16 +9,13 @@ using ServiceStack;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using MediaBrowser.Model.Querying;
 
 
 namespace MediaBrowser.Api.UserLibrary
 namespace MediaBrowser.Api.UserLibrary
 {
 {
     [Route("/GameGenres", "GET", Summary = "Gets all Game genres from a given item, folder, or the entire library")]
     [Route("/GameGenres", "GET", Summary = "Gets all Game genres from a given item, folder, or the entire library")]
     public class GetGameGenres : GetItemsByName
     public class GetGameGenres : GetItemsByName
     {
     {
-        public GetGameGenres()
-        {
-            MediaTypes = MediaType.Game;
-        }
     }
     }
 
 
     [Route("/GameGenres/{Name}", "GET", Summary = "Gets a Game genre, by name")]
     [Route("/GameGenres/{Name}", "GET", Summary = "Gets a Game genre, by name")]
@@ -87,11 +84,16 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public object Get(GetGameGenres request)
         public object Get(GetGameGenres request)
         {
         {
-            var result = GetResult(request);
+            var result = GetResultSlim(request);
 
 
             return ToOptimizedSerializedResultUsingCache(result);
             return ToOptimizedSerializedResultUsingCache(result);
         }
         }
 
 
+        protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+        {
+            return LibraryManager.GetGameGenres(query);
+        }
+
         /// <summary>
         /// <summary>
         /// Gets all items.
         /// Gets all items.
         /// </summary>
         /// </summary>
@@ -100,22 +102,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
         /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
         protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
         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;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using MediaBrowser.Model.Querying;
 
 
 namespace MediaBrowser.Api.UserLibrary
 namespace MediaBrowser.Api.UserLibrary
 {
 {
@@ -92,65 +93,37 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public object Get(GetGenres request)
         public object Get(GetGenres request)
         {
         {
-            var result = GetResult(request);
+            var result = GetResultSlim(request);
 
 
             return ToOptimizedSerializedResultUsingCache(result);
             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);
             var viewType = GetParentItemViewType(request);
 
 
             if (string.Equals(viewType, CollectionType.Music) || string.Equals(viewType, CollectionType.MusicVideos))
             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))
             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();
         }
         }
     }
     }
 }
 }

+ 67 - 27
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -34,7 +34,6 @@ namespace MediaBrowser.Api.UserLibrary
         /// The _user manager
         /// The _user manager
         /// </summary>
         /// </summary>
         private readonly IUserManager _userManager;
         private readonly IUserManager _userManager;
-        private readonly IUserDataManager _userDataRepository;
 
 
         /// <summary>
         /// <summary>
         /// The _library manager
         /// The _library manager
@@ -43,25 +42,37 @@ namespace MediaBrowser.Api.UserLibrary
         private readonly ILocalizationManager _localization;
         private readonly ILocalizationManager _localization;
 
 
         private readonly IDtoService _dtoService;
         private readonly IDtoService _dtoService;
-        private readonly ICollectionManager _collectionManager;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="ItemsService" /> class.
         /// Initializes a new instance of the <see cref="ItemsService" /> class.
         /// </summary>
         /// </summary>
         /// <param name="userManager">The user manager.</param>
         /// <param name="userManager">The user manager.</param>
         /// <param name="libraryManager">The library 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="localization">The localization.</param>
         /// <param name="dtoService">The dto service.</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;
             _userManager = userManager;
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
-            _userDataRepository = userDataRepository;
             _localization = localization;
             _localization = localization;
             _dtoService = dtoService;
             _dtoService = dtoService;
-            _collectionManager = collectionManager;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -71,6 +82,11 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public async Task<object> Get(GetItems request)
         public async Task<object> Get(GetItems request)
         {
         {
+            if (request == null)
+            {
+                throw new ArgumentNullException("request");
+            }
+
             var result = await GetItems(request).ConfigureAwait(false);
             var result = await GetItems(request).ConfigureAwait(false);
 
 
             return ToOptimizedSerializedResultUsingCache(result);
             return ToOptimizedSerializedResultUsingCache(result);
@@ -84,15 +100,32 @@ namespace MediaBrowser.Api.UserLibrary
         private async Task<ItemsResult> GetItems(GetItems request)
         private async Task<ItemsResult> GetItems(GetItems request)
         {
         {
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
+        
+            var result = await GetQueryResult(request, user).ConfigureAwait(false);
+
+            if (result == null)
+            {
+                throw new InvalidOperationException("GetItemsToSerialize returned null");
+            }
 
 
-            var result = await GetItemsToSerialize(request, user).ConfigureAwait(false);
+            if (result.Items == null)
+            {
+                throw new InvalidOperationException("GetItemsToSerialize result.Items returned null");
+            }
 
 
             var dtoOptions = GetDtoOptions(request);
             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
             return new ItemsResult
             {
             {
                 TotalRecordCount = result.TotalRecordCount,
                 TotalRecordCount = result.TotalRecordCount,
-                Items = _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user).ToArray()
+                Items = dtoList.ToArray()
             };
             };
         }
         }
 
 
@@ -102,7 +135,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <param name="request">The request.</param>
         /// <param name="request">The request.</param>
         /// <param name="user">The user.</param>
         /// <param name="user">The user.</param>
         /// <returns>IEnumerable{BaseItem}.</returns>
         /// <returns>IEnumerable{BaseItem}.</returns>
-        private async Task<QueryResult<BaseItem>> GetItemsToSerialize(GetItems request, User user)
+        private async Task<QueryResult<BaseItem>> GetQueryResult(GetItems request, User user)
         {
         {
             var item = string.IsNullOrEmpty(request.ParentId) ?
             var item = string.IsNullOrEmpty(request.ParentId) ?
                 user == null ? _libraryManager.RootFolder : user.RootFolder :
                 user == null ? _libraryManager.RootFolder : user.RootFolder :
@@ -119,11 +152,17 @@ namespace MediaBrowser.Api.UserLibrary
 
 
             // Default list type = children
             // Default list type = children
 
 
+            var folder = item as Folder;
+            if (folder == null)
+            {
+                folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder();
+            }
+
             if (!string.IsNullOrEmpty(request.Ids))
             if (!string.IsNullOrEmpty(request.Ids))
             {
             {
                 request.Recursive = true;
                 request.Recursive = true;
                 var query = GetItemsQuery(request, user);
                 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))
                 if (string.IsNullOrWhiteSpace(request.SortBy))
                 {
                 {
@@ -138,28 +177,22 @@ namespace MediaBrowser.Api.UserLibrary
 
 
             if (request.Recursive)
             if (request.Recursive)
             {
             {
-                var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
-
-                return result;
+                return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
             }
             }
 
 
             if (user == null)
             if (user == null)
             {
             {
-                var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
-
-                return result;
+                return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
             }
             }
 
 
             var userRoot = item as UserRootFolder;
             var userRoot = item as UserRootFolder;
 
 
             if (userRoot == null)
             if (userRoot == null)
             {
             {
-                var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
-
-                return result;
+                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();
             var itemsArray = items.ToArray();
 
 
@@ -193,7 +226,6 @@ namespace MediaBrowser.Api.UserLibrary
                 NameStartsWith = request.NameStartsWith,
                 NameStartsWith = request.NameStartsWith,
                 NameStartsWithOrGreater = request.NameStartsWithOrGreater,
                 NameStartsWithOrGreater = request.NameStartsWithOrGreater,
                 HasImdbId = request.HasImdbId,
                 HasImdbId = request.HasImdbId,
-                IsYearMismatched = request.IsYearMismatched,
                 IsPlaceHolder = request.IsPlaceHolder,
                 IsPlaceHolder = request.IsPlaceHolder,
                 IsLocked = request.IsLocked,
                 IsLocked = request.IsLocked,
                 IsInBoxSet = request.IsInBoxSet,
                 IsInBoxSet = request.IsInBoxSet,
@@ -230,7 +262,9 @@ namespace MediaBrowser.Api.UserLibrary
                 ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId),
                 ParentId = string.IsNullOrWhiteSpace(request.ParentId) ? (Guid?)null : new Guid(request.ParentId),
                 ParentIndexNumber = request.ParentIndexNumber,
                 ParentIndexNumber = request.ParentIndexNumber,
                 AiredDuringSeason = request.AiredDuringSeason,
                 AiredDuringSeason = request.AiredDuringSeason,
-                AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater
+                AlbumArtistStartsWithOrGreater = request.AlbumArtistStartsWithOrGreater,
+                EnableTotalRecordCount = request.EnableTotalRecordCount,
+                ExcludeItemIds = request.GetExcludeItemIds()
             };
             };
 
 
             if (!string.IsNullOrWhiteSpace(request.Ids))
             if (!string.IsNullOrWhiteSpace(request.Ids))
@@ -306,17 +340,17 @@ namespace MediaBrowser.Api.UserLibrary
             {
             {
                 query.LocationTypes = request.LocationTypes.Split(',').Select(d => (LocationType)Enum.Parse(typeof(LocationType), d, true)).ToArray();
                 query.LocationTypes = request.LocationTypes.Split(',').Select(d => (LocationType)Enum.Parse(typeof(LocationType), d, true)).ToArray();
             }
             }
-            
+
             // Min official rating
             // Min official rating
-            if (!string.IsNullOrEmpty(request.MinOfficialRating))
+            if (!string.IsNullOrWhiteSpace(request.MinOfficialRating))
             {
             {
                 query.MinParentalRating = _localization.GetRatingLevel(request.MinOfficialRating);
                 query.MinParentalRating = _localization.GetRatingLevel(request.MinOfficialRating);
             }
             }
 
 
             // Max official rating
             // Max official rating
-            if (!string.IsNullOrEmpty(request.MaxOfficialRating))
+            if (!string.IsNullOrWhiteSpace(request.MaxOfficialRating))
             {
             {
-                query.MaxParentalRating = _localization.GetRatingLevel(request.MinOfficialRating);
+                query.MaxParentalRating = _localization.GetRatingLevel(request.MaxOfficialRating);
             }
             }
 
 
             // Artists
             // Artists
@@ -334,6 +368,12 @@ namespace MediaBrowser.Api.UserLibrary
                 query.ArtistNames = request.Artists.Split('|');
                 query.ArtistNames = request.Artists.Split('|');
             }
             }
 
 
+            // ExcludeArtistIds
+            if (!string.IsNullOrEmpty(request.ExcludeArtistIds))
+            {
+                query.ExcludeArtistIds = request.ExcludeArtistIds.Split('|');
+            }
+
             // Albums
             // Albums
             if (!string.IsNullOrEmpty(request.Albums))
             if (!string.IsNullOrEmpty(request.Albums))
             {
             {

+ 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;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
@@ -8,16 +9,14 @@ using MediaBrowser.Model.Dto;
 using ServiceStack;
 using ServiceStack;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
 
 
 namespace MediaBrowser.Api.UserLibrary
 namespace MediaBrowser.Api.UserLibrary
 {
 {
     [Route("/MusicGenres", "GET", Summary = "Gets all music genres from a given item, folder, or the entire library")]
     [Route("/MusicGenres", "GET", Summary = "Gets all music genres from a given item, folder, or the entire library")]
     public class GetMusicGenres : GetItemsByName
     public class GetMusicGenres : GetItemsByName
     {
     {
-        public GetMusicGenres()
-        {
-            IncludeItemTypes = typeof(Audio).Name;
-        }
     }
     }
 
 
     [Route("/MusicGenres/{Name}", "GET", Summary = "Gets a music genre, by name")]
     [Route("/MusicGenres/{Name}", "GET", Summary = "Gets a music genre, by name")]
@@ -86,11 +85,16 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public object Get(GetMusicGenres request)
         public object Get(GetMusicGenres request)
         {
         {
-            var result = GetResult(request);
+            var result = GetResultSlim(request);
 
 
             return ToOptimizedSerializedResultUsingCache(result);
             return ToOptimizedSerializedResultUsingCache(result);
         }
         }
 
 
+        protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+        {
+            return LibraryManager.GetMusicGenres(query);
+        }
+
         /// <summary>
         /// <summary>
         /// Gets all items.
         /// Gets all items.
         /// </summary>
         /// </summary>
@@ -99,10 +103,7 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
         /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns>
         protected override IEnumerable<BaseItem> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items)
         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.
         /// Posts the specified request.
         /// </summary>
         /// </summary>
         /// <param name="request">The request.</param>
         /// <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);
             return ToOptimizedResult(result);
         }
         }
@@ -429,7 +429,7 @@ namespace MediaBrowser.Api.UserLibrary
                 await item.MarkUnplayed(user).ConfigureAwait(false);
                 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.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Net;
@@ -7,6 +8,7 @@ using MediaBrowser.Model.Dto;
 using ServiceStack;
 using ServiceStack;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using MediaBrowser.Model.Querying;
 
 
 namespace MediaBrowser.Api.UserLibrary
 namespace MediaBrowser.Api.UserLibrary
 {
 {
@@ -90,11 +92,16 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public object Get(GetStudios request)
         public object Get(GetStudios request)
         {
         {
-            var result = GetResult(request);
+            var result = GetResultSlim(request);
 
 
             return ToOptimizedSerializedResultUsingCache(result);
             return ToOptimizedSerializedResultUsingCache(result);
         }
         }
 
 
+        protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
+        {
+            return LibraryManager.GetStudios(query);
+        }
+
         /// <summary>
         /// <summary>
         /// Gets all items.
         /// Gets all items.
         /// </summary>
         /// </summary>

+ 8 - 12
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -488,9 +488,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// Posts the specified request.
         /// Posts the specified request.
         /// </summary>
         /// </summary>
         /// <param name="request">The request.</param>
         /// <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);
             return ToOptimizedResult(dto);
         }
         }
@@ -519,17 +519,15 @@ namespace MediaBrowser.Api.UserLibrary
 
 
             var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : _libraryManager.GetItemById(itemId);
             var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : _libraryManager.GetItemById(itemId);
 
 
-            var key = item.GetUserDataKey();
-
             // Get the user data for this item
             // Get the user data for this item
-            var data = _userDataRepository.GetUserData(user.Id, key);
+            var data = _userDataRepository.GetUserData(user, item);
 
 
             // Set favorite status
             // Set favorite status
             data.IsFavorite = isFavorite;
             data.IsFavorite = isFavorite;
 
 
             await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false);
             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>
         /// <summary>
@@ -547,9 +545,9 @@ namespace MediaBrowser.Api.UserLibrary
         /// Posts the specified request.
         /// Posts the specified request.
         /// </summary>
         /// </summary>
         /// <param name="request">The request.</param>
         /// <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);
             return ToOptimizedResult(dto);
         }
         }
@@ -567,16 +565,14 @@ namespace MediaBrowser.Api.UserLibrary
 
 
             var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : _libraryManager.GetItemById(itemId);
             var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : _libraryManager.GetItemById(itemId);
 
 
-            var key = item.GetUserDataKey();
-
             // Get the user data for this item
             // Get the user data for this item
-            var data = _userDataRepository.GetUserData(user.Id, key);
+            var data = _userDataRepository.GetUserData(user, item);
 
 
             data.Likes = likes;
             data.Likes = likes;
 
 
             await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.UpdateUserRating, CancellationToken.None).ConfigureAwait(false);
             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");
                 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);
             await _userManager.DeleteUser(user).ConfigureAwait(false);
         }
         }
@@ -465,6 +465,10 @@ namespace MediaBrowser.Api
                 }
                 }
 
 
                 await _userManager.ChangePassword(user, request.NewPassword).ConfigureAwait(false);
                 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.");
                     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);
             await _userManager.UpdateUserPolicy(request.Id, request).ConfigureAwait(false);

+ 5 - 11
MediaBrowser.Api/VideosService.cs

@@ -130,6 +130,7 @@ namespace MediaBrowser.Api
             var items = request.Ids.Split(',')
             var items = request.Ids.Split(',')
                 .Select(i => new Guid(i))
                 .Select(i => new Guid(i))
                 .Select(i => _libraryManager.GetItemById(i))
                 .Select(i => _libraryManager.GetItemById(i))
+                .OfType<Video>()
                 .ToList();
                 .ToList();
 
 
             if (items.Count < 2)
             if (items.Count < 2)
@@ -137,14 +138,7 @@ namespace MediaBrowser.Api
                 throw new ArgumentException("Please supply at least two videos to merge.");
                 throw new ArgumentException("Please supply at least two videos to merge.");
             }
             }
 
 
-            if (items.Any(i => !(i is Video)))
-            {
-                throw new ArgumentException("Only videos can be grouped together.");
-            }
-
-            var videos = items.Cast<Video>().ToList();
-
-            var videosWithVersions = videos.Where(i => i.MediaSourceCount > 1)
+            var videosWithVersions = items.Where(i => i.MediaSourceCount > 1)
                 .ToList();
                 .ToList();
 
 
             if (videosWithVersions.Count > 1)
             if (videosWithVersions.Count > 1)
@@ -156,7 +150,7 @@ namespace MediaBrowser.Api
 
 
             if (primaryVersion == null)
             if (primaryVersion == null)
             {
             {
-                primaryVersion = videos.OrderBy(i =>
+                primaryVersion = items.OrderBy(i =>
                 {
                 {
                     if (i.Video3DFormat.HasValue)
                     if (i.Video3DFormat.HasValue)
                     {
                     {
@@ -179,9 +173,9 @@ namespace MediaBrowser.Api
                     }).First();
                     }).First();
             }
             }
 
 
-            foreach (var item in videos.Where(i => i.Id != primaryVersion.Id))
+            foreach (var item in items.Where(i => i.Id != primaryVersion.Id))
             {
             {
-                item.PrimaryVersionId = primaryVersion.Id;
+                item.PrimaryVersionId = primaryVersion.Id.ToString("N");
 
 
                 await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
                 await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
 

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

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

+ 1 - 0
MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs

@@ -123,6 +123,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
         /// </summary>
         /// </summary>
         public void SaveConfiguration()
         public void SaveConfiguration()
         {
         {
+            Logger.Info("Saving system configuration");
             var path = CommonApplicationPaths.SystemConfigurationFilePath;
             var path = CommonApplicationPaths.SystemConfigurationFilePath;
 
 
             Directory.CreateDirectory(Path.GetDirectoryName(path));
             Directory.CreateDirectory(Path.GetDirectoryName(path));

+ 41 - 11
MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs

@@ -128,11 +128,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
 
         private void AddIpv4Option(HttpWebRequest request, HttpRequestOptions options)
         private void AddIpv4Option(HttpWebRequest request, HttpRequestOptions options)
         {
         {
-            if (!options.PreferIpv4)
-            {
-                return;
-            }
-
             request.ServicePoint.BindIPEndPointDelegate = (servicePount, remoteEndPoint, retryCount) =>
             request.ServicePoint.BindIPEndPointDelegate = (servicePount, remoteEndPoint, retryCount) =>
             {
             {
                 if (remoteEndPoint.AddressFamily == AddressFamily.InterNetwork)
                 if (remoteEndPoint.AddressFamily == AddressFamily.InterNetwork)
@@ -143,18 +138,33 @@ 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 url = options.Url;
+
+            var uriAddress = new Uri(url);
+            var userInfo = uriAddress.UserInfo;
+            if (!string.IsNullOrWhiteSpace(userInfo))
+            {
+                _logger.Info("Found userInfo in url: {0} ... url: {1}", userInfo, url);
+                url = url.Replace(userInfo + "@", string.Empty);
+            }
+
+            var request = CreateWebRequest(url);
             var httpWebRequest = request as HttpWebRequest;
             var httpWebRequest = request as HttpWebRequest;
 
 
             if (httpWebRequest != null)
             if (httpWebRequest != null)
             {
             {
-                AddIpv4Option(httpWebRequest, options);
+                if (options.PreferIpv4)
+                {
+                    AddIpv4Option(httpWebRequest, options);
+                }
 
 
                 AddRequestHeaders(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);
             request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
@@ -183,9 +193,27 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
                 }
                 }
             }
             }
 
 
+            if (!string.IsNullOrWhiteSpace(userInfo))
+            {
+                var parts = userInfo.Split(':');
+                if (parts.Length == 2)
+                {
+                    request.Credentials = GetCredential(url, parts[0], parts[1]);
+                    request.PreAuthenticate = true;
+                }
+            }
+
             return request;
             return request;
         }
         }
 
 
+        private CredentialCache GetCredential(string url, string username, string password)
+        {
+            //ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
+            CredentialCache credentialCache = new CredentialCache();
+            credentialCache.Add(new Uri(url), "Basic", new NetworkCredential(username, password));
+            return credentialCache;
+        }
+
         private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options)
         private void AddRequestHeaders(HttpWebRequest request, HttpRequestOptions options)
         {
         {
             foreach (var header in options.RequestHeaders.ToList())
             foreach (var header in options.RequestHeaders.ToList())
@@ -296,6 +324,8 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
 
         private async Task<HttpResponseInfo> GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url)
         private async Task<HttpResponseInfo> GetCachedResponse(string responseCachePath, TimeSpan cacheLength, string url)
         {
         {
+            _logger.Info("Checking for cache file {0}", responseCachePath);
+
             try
             try
             {
             {
                 if (_fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
                 if (_fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
@@ -366,7 +396,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
                 };
                 };
             }
             }
 
 
-            var httpWebRequest = GetRequest(options, httpMethod, options.EnableHttpCompression);
+            var httpWebRequest = GetRequest(options, httpMethod);
 
 
             if (options.RequestContentBytes != null ||
             if (options.RequestContentBytes != null ||
                 !string.IsNullOrEmpty(options.RequestContent) ||
                 !string.IsNullOrEmpty(options.RequestContent) ||
@@ -556,7 +586,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
 
             options.CancellationToken.ThrowIfCancellationRequested();
             options.CancellationToken.ThrowIfCancellationRequested();
 
 
-            var httpWebRequest = GetRequest(options, "GET", options.EnableHttpCompression);
+            var httpWebRequest = GetRequest(options, "GET");
 
 
             if (options.ResourcePool != null)
             if (options.ResourcePool != null)
             {
             {

+ 6 - 4
MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj

@@ -54,8 +54,9 @@
     <Reference Include="MoreLinq">
     <Reference Include="MoreLinq">
       <HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
       <HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
     </Reference>
     </Reference>
-    <Reference Include="NLog">
-      <HintPath>..\packages\NLog.4.2.3\lib\net45\NLog.dll</HintPath>
+    <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
+      <HintPath>..\packages\NLog.4.3.5\lib\net45\NLog.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     </Reference>
     <Reference Include="Patterns.Logging">
     <Reference Include="Patterns.Logging">
       <HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
       <HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
@@ -64,8 +65,9 @@
       <SpecificVersion>False</SpecificVersion>
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\ThirdParty\SharpCompress\SharpCompress.dll</HintPath>
       <HintPath>..\ThirdParty\SharpCompress\SharpCompress.dll</HintPath>
     </Reference>
     </Reference>
-    <Reference Include="SimpleInjector">
-      <HintPath>..\packages\SimpleInjector.3.1.2\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>
     <Reference Include="System" />
     <Reference Include="System" />
     <Reference Include="System.Configuration" />
     <Reference Include="System.Configuration" />

+ 22 - 23
MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs

@@ -106,6 +106,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
             InitTriggerEvents();
             InitTriggerEvents();
         }
         }
 
 
+        private bool _readFromFile = false;
         /// <summary>
         /// <summary>
         /// The _last execution result
         /// The _last execution result
         /// </summary>
         /// </summary>
@@ -122,31 +123,29 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
         {
         {
             get
             get
             {
             {
-                if (_lastExecutionResult == null)
-                {
-                    var path = GetHistoryFilePath();
+                var path = GetHistoryFilePath();
 
 
-                    lock (_lastExecutionResultSyncLock)
+                lock (_lastExecutionResultSyncLock)
+                {
+                    if (_lastExecutionResult == null && !_readFromFile)
                     {
                     {
-                        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);
                         }
                         }
+                        _readFromFile = true;
                     }
                     }
                 }
                 }
 
 
@@ -311,7 +310,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
 
 
                 trigger.Triggered -= trigger_Triggered;
                 trigger.Triggered -= trigger_Triggered;
                 trigger.Triggered += trigger_Triggered;
                 trigger.Triggered += trigger_Triggered;
-                trigger.Start(LastExecutionResult, isApplicationStartup);
+                trigger.Start(LastExecutionResult, Logger, Name, isApplicationStartup);
             }
             }
         }
         }
 
 
@@ -339,7 +338,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
 
 
             await Task.Delay(1000).ConfigureAwait(false);
             await Task.Delay(1000).ConfigureAwait(false);
 
 
-            trigger.Start(LastExecutionResult, false);
+            trigger.Start(LastExecutionResult, Logger, Name, false);
         }
         }
 
 
         private Task _currentTask;
         private Task _currentTask;

+ 2 - 2
MediaBrowser.Common.Implementations/ScheduledTasks/TaskManager.cs

@@ -88,8 +88,6 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;
 
 
             ScheduledTasks = new IScheduledTaskWorker[] { };
             ScheduledTasks = new IScheduledTaskWorker[] { };
-
-            BindToSystemEvent();
         }
         }
 
 
         private void BindToSystemEvent()
         private void BindToSystemEvent()
@@ -259,6 +257,8 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
             myTasks.AddRange(list.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger, _fileSystem)));
             myTasks.AddRange(list.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger, _fileSystem)));
 
 
             ScheduledTasks = myTasks.ToArray();
             ScheduledTasks = myTasks.ToArray();
+
+            BindToSystemEvent();
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 1 - 1
MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs

@@ -71,7 +71,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
 
 
             progress.Report(90);
             progress.Report(90);
 
 
-            minDateModified = DateTime.UtcNow.AddDays(-2);
+            minDateModified = DateTime.UtcNow.AddDays(-1);
 
 
             try
             try
             {
             {

+ 2 - 2
MediaBrowser.Common.Implementations/Security/MbAdmin.cs

@@ -3,11 +3,11 @@ namespace MediaBrowser.Common.Implementations.Security
 {
 {
     public class MbAdmin
     public class MbAdmin
     {
     {
-        public const string HttpUrl = "http://www.mb3admin.com/admin/";
+        public const string HttpUrl = "https://www.mb3admin.com/admin/";
         
         
         /// <summary>
         /// <summary>
         /// Leaving as http for now until we get it squared away
         /// Leaving as http for now until we get it squared away
         /// </summary>
         /// </summary>
-        public const string HttpsUrl = "http://www.mb3admin.com/admin/";
+        public const string HttpsUrl = "https://www.mb3admin.com/admin/";
     }
     }
 }
 }

+ 1 - 1
MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs

@@ -21,7 +21,7 @@ namespace MediaBrowser.Common.Implementations.Security
     public class PluginSecurityManager : ISecurityManager
     public class PluginSecurityManager : ISecurityManager
     {
     {
         private const string MBValidateUrl = MbAdmin.HttpsUrl + "service/registration/validate";
         private const string MBValidateUrl = MbAdmin.HttpsUrl + "service/registration/validate";
-        private const string AppstoreRegUrl = /*MbAdmin.HttpsUrl*/ "http://mb3admin.com/admin/service/appstore/register";
+        private const string AppstoreRegUrl = /*MbAdmin.HttpsUrl*/ "https://mb3admin.com/admin/service/appstore/register";
 
 
         /// <summary>
         /// <summary>
         /// The _is MB supporter
         /// The _is MB supporter

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

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

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

@@ -4,6 +4,7 @@ using System.Collections.Concurrent;
 using System.IO;
 using System.IO;
 using System.Xml;
 using System.Xml;
 using CommonIO;
 using CommonIO;
+using MediaBrowser.Model.Logging;
 
 
 namespace MediaBrowser.Common.Implementations.Serialization
 namespace MediaBrowser.Common.Implementations.Serialization
 {
 {
@@ -12,12 +13,14 @@ namespace MediaBrowser.Common.Implementations.Serialization
     /// </summary>
     /// </summary>
     public class XmlSerializer : IXmlSerializer
     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
         // Need to cache these
         // http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
         // http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
@@ -77,6 +80,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
         /// <param name="file">The file.</param>
         /// <param name="file">The file.</param>
         public void SerializeToFile(object obj, string file)
         public void SerializeToFile(object obj, string file)
         {
         {
+            _logger.Debug("Serializing to file {0}", file);
             using (var stream = new FileStream(file, FileMode.Create))
             using (var stream = new FileStream(file, FileMode.Create))
             {
             {
                 SerializeToStream(obj, stream);
                 SerializeToStream(obj, stream);
@@ -91,6 +95,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public object DeserializeFromFile(Type type, string file)
         public object DeserializeFromFile(Type type, string file)
         {
         {
+            _logger.Debug("Deserializing file {0}", file);
             using (var stream = _fileSystem.OpenRead(file))
             using (var stream = _fileSystem.OpenRead(file))
             {
             {
                 return DeserializeFromStream(type, stream);
                 return DeserializeFromStream(type, stream);

+ 6 - 3
MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs

@@ -54,7 +54,9 @@ namespace MediaBrowser.Common.Implementations.Updates
         {
         {
             if (updateLevel == PackageVersionClass.Release)
             if (updateLevel == PackageVersionClass.Release)
             {
             {
-                obj = obj.Where(i => !i.prerelease).ToArray();
+                // Technically all we need to do is check that it's not pre-release
+                // But let's addititional checks for -beta and -dev to handle builds that might be temporarily tagged incorrectly.
+                obj = obj.Where(i => !i.prerelease && !i.name.EndsWith("-beta", StringComparison.OrdinalIgnoreCase) && !i.name.EndsWith("-dev", StringComparison.OrdinalIgnoreCase)).ToArray();
             }
             }
             else if (updateLevel == PackageVersionClass.Beta)
             else if (updateLevel == PackageVersionClass.Beta)
             {
             {
@@ -70,7 +72,7 @@ namespace MediaBrowser.Common.Implementations.Updates
                 .Where(i => i != null)
                 .Where(i => i != null)
                 .OrderByDescending(i => Version.Parse(i.AvailableVersion))
                 .OrderByDescending(i => Version.Parse(i.AvailableVersion))
                 .FirstOrDefault();
                 .FirstOrDefault();
-            
+
             return availableUpdate ?? new CheckForUpdateResult
             return availableUpdate ?? new CheckForUpdateResult
             {
             {
                 IsUpdateAvailable = false
                 IsUpdateAvailable = false
@@ -111,7 +113,8 @@ namespace MediaBrowser.Common.Implementations.Updates
                     targetFilename = targetFilename,
                     targetFilename = targetFilename,
                     versionStr = version.ToString(),
                     versionStr = version.ToString(),
                     requiredVersionStr = "1.0.0",
                     requiredVersionStr = "1.0.0",
-                    description = obj.body
+                    description = obj.body,
+                    infoUrl = obj.html_url
                 }
                 }
             };
             };
         }
         }

+ 1 - 0
MediaBrowser.Common.Implementations/Updates/InstallationManager.cs

@@ -193,6 +193,7 @@ namespace MediaBrowser.Common.Implementations.Updates
         /// <returns>Task{List{PackageInfo}}.</returns>
         /// <returns>Task{List{PackageInfo}}.</returns>
         public async Task<IEnumerable<PackageInfo>> GetAvailablePackagesWithoutRegistrationInfo(CancellationToken cancellationToken)
         public async Task<IEnumerable<PackageInfo>> GetAvailablePackagesWithoutRegistrationInfo(CancellationToken cancellationToken)
         {
         {
+            _logger.Info("Opening {0}", PackageCachePath);
             try
             try
             {
             {
                 using (var stream = _fileSystem.OpenRead(PackageCachePath))
                 using (var stream = _fileSystem.OpenRead(PackageCachePath))

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

@@ -2,7 +2,7 @@
 <packages>
 <packages>
   <package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
   <package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
   <package id="morelinq" version="1.4.0" targetFramework="net45" />
   <package id="morelinq" version="1.4.0" targetFramework="net45" />
-  <package id="NLog" version="4.2.3" targetFramework="net45" />
+  <package id="NLog" version="4.3.5" targetFramework="net45" />
   <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
   <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
-  <package id="SimpleInjector" version="3.1.2" targetFramework="net45" />
-</packages>
+  <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;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using System.Net;
 using System.Threading;
 using System.Threading;
 
 
 namespace MediaBrowser.Common.Net
 namespace MediaBrowser.Common.Net
@@ -16,6 +17,8 @@ namespace MediaBrowser.Common.Net
         /// <value>The URL.</value>
         /// <value>The URL.</value>
         public string Url { get; set; }
         public string Url { get; set; }
 
 
+        public DecompressionMethods? DecompressionMethod { get; set; }
+
         /// <summary>
         /// <summary>
         /// Gets or sets the accept header.
         /// Gets or sets the accept header.
         /// </summary>
         /// </summary>

+ 8 - 2
MediaBrowser.Common/ScheduledTasks/DailyTrigger.cs

@@ -1,7 +1,9 @@
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Tasks;
 using MediaBrowser.Model.Tasks;
 using System;
 using System;
+using System.Globalization;
 using System.Threading;
 using System.Threading;
+using MediaBrowser.Model.Logging;
 
 
 namespace MediaBrowser.Common.ScheduledTasks
 namespace MediaBrowser.Common.ScheduledTasks
 {
 {
@@ -35,7 +37,7 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// </summary>
         /// </summary>
         /// <param name="lastResult">The last result.</param>
         /// <param name="lastResult">The last result.</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
-        public void Start(TaskResult lastResult, bool isApplicationStartup)
+        public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
         {
         {
             DisposeTimer();
             DisposeTimer();
 
 
@@ -44,7 +46,11 @@ namespace MediaBrowser.Common.ScheduledTasks
             var triggerDate = now.TimeOfDay > TimeOfDay ? now.Date.AddDays(1) : now.Date;
             var triggerDate = now.TimeOfDay > TimeOfDay ? now.Date.AddDays(1) : now.Date;
             triggerDate = triggerDate.Add(TimeOfDay);
             triggerDate = triggerDate.Add(TimeOfDay);
 
 
-            Timer = new Timer(state => OnTriggered(), null, triggerDate - now, TimeSpan.FromMilliseconds(-1));
+            var dueTime = triggerDate - now;
+
+            logger.Info("Daily trigger for {0} set to fire at {1}, which is {2} minutes from now.", taskName, triggerDate.ToString(), dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture));
+
+            Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 2 - 1
MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Tasks;
 using MediaBrowser.Model.Tasks;
 using System;
 using System;
+using MediaBrowser.Model.Logging;
 
 
 namespace MediaBrowser.Common.ScheduledTasks
 namespace MediaBrowser.Common.ScheduledTasks
 {
 {
@@ -19,7 +20,7 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// </summary>
         /// </summary>
         /// <param name="lastResult">The last result.</param>
         /// <param name="lastResult">The last result.</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
-        void Start(TaskResult lastResult, bool isApplicationStartup);
+        void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup);
 
 
         /// <summary>
         /// <summary>
         /// Stops waiting for the trigger action
         /// Stops waiting for the trigger action

+ 11 - 2
MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Model.Tasks;
 using System;
 using System;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
+using MediaBrowser.Model.Logging;
 
 
 namespace MediaBrowser.Common.ScheduledTasks
 namespace MediaBrowser.Common.ScheduledTasks
 {
 {
@@ -38,7 +39,7 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// </summary>
         /// </summary>
         /// <param name="lastResult">The last result.</param>
         /// <param name="lastResult">The last result.</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
-        public void Start(TaskResult lastResult, bool isApplicationStartup)
+        public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
         {
         {
             DisposeTimer();
             DisposeTimer();
 
 
@@ -59,7 +60,15 @@ namespace MediaBrowser.Common.ScheduledTasks
                 triggerDate = DateTime.UtcNow.AddMinutes(1);
                 triggerDate = DateTime.UtcNow.AddMinutes(1);
             }
             }
 
 
-            Timer = new Timer(state => OnTriggered(), null, triggerDate - DateTime.UtcNow, TimeSpan.FromMilliseconds(-1));
+            var dueTime = triggerDate - DateTime.UtcNow;
+            var maxDueTime = TimeSpan.FromDays(7);
+
+            if (dueTime > maxDueTime)
+            {
+                dueTime = maxDueTime;
+            }
+
+            Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 2 - 1
MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Model.Tasks;
 using MediaBrowser.Model.Tasks;
 using System;
 using System;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Logging;
 
 
 namespace MediaBrowser.Common.ScheduledTasks
 namespace MediaBrowser.Common.ScheduledTasks
 {
 {
@@ -30,7 +31,7 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// </summary>
         /// </summary>
         /// <param name="lastResult">The last result.</param>
         /// <param name="lastResult">The last result.</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
-        public async void Start(TaskResult lastResult, bool isApplicationStartup)
+        public async void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
         {
         {
             if (isApplicationStartup)
             if (isApplicationStartup)
             {
             {

+ 2 - 1
MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Model.Tasks;
 using Microsoft.Win32;
 using Microsoft.Win32;
 using System;
 using System;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Logging;
 
 
 namespace MediaBrowser.Common.ScheduledTasks
 namespace MediaBrowser.Common.ScheduledTasks
 {
 {
@@ -30,7 +31,7 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// </summary>
         /// </summary>
         /// <param name="lastResult">The last result.</param>
         /// <param name="lastResult">The last result.</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
-        public void Start(TaskResult lastResult, bool isApplicationStartup)
+        public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
         {
         {
             switch (SystemEvent)
             switch (SystemEvent)
             {
             {

+ 2 - 1
MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs

@@ -1,6 +1,7 @@
 using System;
 using System;
 using System.Threading;
 using System.Threading;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Events;
+using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Tasks;
 using MediaBrowser.Model.Tasks;
 
 
 namespace MediaBrowser.Common.ScheduledTasks
 namespace MediaBrowser.Common.ScheduledTasks
@@ -41,7 +42,7 @@ namespace MediaBrowser.Common.ScheduledTasks
         /// </summary>
         /// </summary>
         /// <param name="lastResult">The last result.</param>
         /// <param name="lastResult">The last result.</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
         /// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
-        public void Start(TaskResult lastResult, bool isApplicationStartup)
+        public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
         {
         {
             DisposeTimer();
             DisposeTimer();
 
 

+ 1 - 1
MediaBrowser.Controller/Channels/Channel.cs

@@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Channels
             set { }
             set { }
         }
         }
 
 
-        public override async Task<QueryResult<BaseItem>> GetItems(InternalItemsQuery query)
+        protected override async Task<QueryResult<BaseItem>> GetItemsInternal(InternalItemsQuery query)
         {
         {
             try
             try
             {
             {

+ 0 - 101
MediaBrowser.Controller/Channels/ChannelAudioItem.cs

@@ -1,101 +0,0 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Model.Channels;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.Serialization;
-using System.Threading;
-
-namespace MediaBrowser.Controller.Channels
-{
-    public class ChannelAudioItem : Audio
-    {
-        public ChannelMediaContentType ContentType { get; set; }
-
-        public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
-
-        public override UnratedItem GetBlockUnratedType()
-        {
-            return UnratedItem.ChannelContent;
-        }
-
-        protected override string CreateUserDataKey()
-        {
-            return ExternalId;
-        }
-
-        [IgnoreDataMember]
-        public override bool SupportsLocalMetadata
-        {
-            get
-            {
-                return false;
-            }
-        }
-
-        public override bool IsSaveLocalMetadataEnabled()
-        {
-            return false;
-        }
-
-        public ChannelAudioItem()
-        {
-            ChannelMediaSources = new List<ChannelMediaInfo>();
-        }
-
-        [IgnoreDataMember]
-        public override LocationType LocationType
-        {
-            get
-            {
-                if (string.IsNullOrEmpty(Path))
-                {
-                    return LocationType.Remote;
-                }
-                
-                return base.LocationType;
-            }
-        }
-
-        protected override string GetInternalMetadataPath(string basePath)
-        {
-            return System.IO.Path.Combine(basePath, "channels", ChannelId, Id.ToString("N"));
-        }
-
-        public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
-        {
-            var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None)
-                       .Result.ToList();
-
-            if (sources.Count > 0)
-            {
-                return sources;
-            }
-
-            var list = base.GetMediaSources(enablePathSubstitution).ToList();
-
-            foreach (var mediaSource in list)
-            {
-                if (string.IsNullOrWhiteSpace(mediaSource.Path))
-                {
-                    mediaSource.Type = MediaSourceType.Placeholder;
-                }
-            }
-
-            return list;
-        }
-
-        public override bool CanDelete()
-        {
-            return false;
-        }
-
-        public override bool IsVisibleStandalone(User user)
-        {
-            return IsVisibleStandaloneInternal(user, false) && ChannelVideoItem.IsChannelVisible(this, user);
-        }
-    }
-}

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

@@ -1,89 +0,0 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Channels;
-using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Users;
-using System;
-using System.Runtime.Serialization;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Configuration;
-
-namespace MediaBrowser.Controller.Channels
-{
-    public class ChannelFolderItem : Folder
-    {
-        public ChannelFolderType ChannelFolderType { get; set; }
-
-        protected override bool GetBlockUnratedValue(UserPolicy config)
-        {
-            // Don't block. 
-            return false;
-        }
-
-        public override UnratedItem GetBlockUnratedType()
-        {
-            return UnratedItem.ChannelContent;
-        }
-
-        [IgnoreDataMember]
-        public override bool SupportsLocalMetadata
-        {
-            get
-            {
-                return false;
-            }
-        }
-
-        public override bool IsSaveLocalMetadataEnabled()
-        {
-            return false;
-        }
-
-        protected override string CreateUserDataKey()
-        {
-            return ExternalId;
-        }
-
-        public override async Task<QueryResult<BaseItem>> GetItems(InternalItemsQuery query)
-        {
-            try
-            {
-                // Don't blow up here because it could cause parent screens with other content to fail
-                return await ChannelManager.GetChannelItemsInternal(new ChannelItemQuery
-                {
-                    ChannelId = ChannelId,
-                    FolderId = Id.ToString("N"),
-                    Limit = query.Limit,
-                    StartIndex = query.StartIndex,
-                    UserId = query.User.Id.ToString("N"),
-                    SortBy = query.SortBy,
-                    SortOrder = query.SortOrder
-
-                }, new Progress<double>(), CancellationToken.None);
-            }
-            catch
-            {
-                // Already logged at lower levels
-                return new QueryResult<BaseItem>
-                {
-
-                };
-            }
-        }
-
-        protected override string GetInternalMetadataPath(string basePath)
-        {
-            return System.IO.Path.Combine(basePath, "channels", ChannelId, Id.ToString("N"));
-        }
-
-        public override bool CanDelete()
-        {
-            return false;
-        }
-
-        public override bool IsVisibleStandalone(User user)
-        {
-            return IsVisibleStandaloneInternal(user, false) && ChannelVideoItem.IsChannelVisible(this, user);
-        }
-    }
-}

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

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

+ 1 - 1
MediaBrowser.Controller/Channels/ChannelMediaInfo.cs

@@ -65,7 +65,7 @@ namespace MediaBrowser.Controller.Channels
                 Name = id,
                 Name = id,
                 Id = id,
                 Id = id,
                 ReadAtNativeFramerate = ReadAtNativeFramerate,
                 ReadAtNativeFramerate = ReadAtNativeFramerate,
-                SupportsDirectStream = Protocol == MediaProtocol.File || Protocol == MediaProtocol.Http,
+                SupportsDirectStream = Protocol == MediaProtocol.File,
                 SupportsDirectPlay = SupportsDirectPlay
                 SupportsDirectPlay = SupportsDirectPlay
             };
             };
 
 

+ 0 - 126
MediaBrowser.Controller/Channels/ChannelVideoItem.cs

@@ -1,126 +0,0 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Channels;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Runtime.Serialization;
-using System.Threading;
-
-namespace MediaBrowser.Controller.Channels
-{
-    public class ChannelVideoItem : Video
-    {
-        public ChannelMediaContentType ContentType { get; set; }
-
-        public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
-
-        protected override string CreateUserDataKey()
-        {
-            if (ContentType == ChannelMediaContentType.MovieExtra)
-            {
-                var key = this.GetProviderId(MetadataProviders.Imdb) ?? this.GetProviderId(MetadataProviders.Tmdb);
-
-                if (!string.IsNullOrWhiteSpace(key))
-                {
-                    key = key + "-" + ExtraType.ToString().ToLower();
-
-                    // Make sure different trailers have their own data.
-                    if (RunTimeTicks.HasValue)
-                    {
-                        key += "-" + RunTimeTicks.Value.ToString(CultureInfo.InvariantCulture);
-                    }
-
-                    return key;
-                }
-            }
-
-            return ExternalId;
-        }
-
-        public override UnratedItem GetBlockUnratedType()
-        {
-            return UnratedItem.ChannelContent;
-        }
-
-        [IgnoreDataMember]
-        public override bool SupportsLocalMetadata
-        {
-            get
-            {
-                return false;
-            }
-        }
-
-        public override bool IsSaveLocalMetadataEnabled()
-        {
-            return false;
-        }
-
-        public ChannelVideoItem()
-        {
-            ChannelMediaSources = new List<ChannelMediaInfo>();
-        }
-
-        [IgnoreDataMember]
-        public override LocationType LocationType
-        {
-            get
-            {
-                if (string.IsNullOrEmpty(Path))
-                {
-                    return LocationType.Remote;
-                }
-
-                return base.LocationType;
-            }
-        }
-
-        public override IEnumerable<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
-        {
-            var sources = ChannelManager.GetStaticMediaSources(this, false, CancellationToken.None)
-                       .Result.ToList();
-
-            if (sources.Count > 0)
-            {
-                return sources;
-            }
-
-            var list = base.GetMediaSources(enablePathSubstitution).ToList();
-
-            foreach (var mediaSource in list)
-            {
-                if (string.IsNullOrWhiteSpace(mediaSource.Path))
-                {
-                    mediaSource.Type = MediaSourceType.Placeholder;
-                }
-            }
-
-            return list;
-        }
-
-        protected override string GetInternalMetadataPath(string basePath)
-        {
-            return System.IO.Path.Combine(basePath, "channels", ChannelId, Id.ToString("N"));
-        }
-
-        public override bool CanDelete()
-        {
-            return false;
-        }
-
-        public override bool IsVisibleStandalone(User user)
-        {
-            return IsVisibleStandaloneInternal(user, false) && IsChannelVisible(this, user);
-        }
-
-        internal static bool IsChannelVisible(BaseItem item, User user)
-        {
-            var channel = ChannelManager.GetChannel(item.ChannelId);
-
-            return channel.IsVisible(user);
-        }
-    }
-}

+ 0 - 11
MediaBrowser.Controller/Channels/IChannelItem.cs

@@ -1,11 +0,0 @@
-using MediaBrowser.Controller.Entities;
-
-namespace MediaBrowser.Controller.Channels
-{
-    public interface IChannelItem : IHasImages, IHasTags
-    {
-        string ChannelId { get; set; }
-
-        string ExternalId { get; set; }
-    }
-}

+ 0 - 18
MediaBrowser.Controller/Channels/IChannelMediaItem.cs

@@ -1,18 +0,0 @@
-using MediaBrowser.Model.Channels;
-using MediaBrowser.Model.Entities;
-using System.Collections.Generic;
-
-namespace MediaBrowser.Controller.Channels
-{
-    public interface IChannelMediaItem : IChannelItem
-    {
-        long? RunTimeTicks { get; set; }
-        string MediaType { get; }
-
-        ChannelMediaContentType ContentType { get; set; }
-
-        ExtraType? ExtraType { get; set; }
-
-        List<ChannelMediaInfo> ChannelMediaSources { get; set; }
-    }
-}

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

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

+ 1 - 1
MediaBrowser.Controller/Drawing/IImageProcessor.cs

@@ -84,7 +84,7 @@ namespace MediaBrowser.Controller.Drawing
         /// </summary>
         /// </summary>
         /// <param name="options">The options.</param>
         /// <param name="options">The options.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        Task<Tuple<string, string>> ProcessImage(ImageProcessingOptions options);
+        Task<Tuple<string, string, DateTime>> ProcessImage(ImageProcessingOptions options);
 
 
         /// <summary>
         /// <summary>
         /// Gets the enhanced image.
         /// Gets the enhanced image.

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

@@ -23,10 +23,5 @@ namespace MediaBrowser.Controller.Drawing
         /// </summary>
         /// </summary>
         /// <value>The height.</value>
         /// <value>The height.</value>
         public int Height { get; set; }
         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.Dto;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Controller.Dto
 namespace MediaBrowser.Controller.Dto
 {
 {
@@ -68,7 +69,7 @@ namespace MediaBrowser.Controller.Dto
         /// <param name="user">The user.</param>
         /// <param name="user">The user.</param>
         /// <param name="owner">The owner.</param>
         /// <param name="owner">The owner.</param>
         /// <returns>IEnumerable&lt;BaseItemDto&gt;.</returns>
         /// <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);
             BaseItem owner = null);
         
         
         /// <summary>
         /// <summary>

+ 34 - 3
MediaBrowser.Controller/Entities/AggregateFolder.cs

@@ -64,10 +64,37 @@ namespace MediaBrowser.Controller.Entities
 
 
         protected override IEnumerable<FileSystemMetadata> GetFileSystemChildren(IDirectoryService directoryService)
         protected override IEnumerable<FileSystemMetadata> GetFileSystemChildren(IDirectoryService directoryService)
         {
         {
-            return CreateResolveArgs(directoryService).FileSystemChildren;
+            return CreateResolveArgs(directoryService, true).FileSystemChildren;
         }
         }
 
 
-        private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService)
+        private bool _requiresRefresh;
+        public override bool RequiresRefresh()
+        {
+            var changed = base.RequiresRefresh() || _requiresRefresh;
+
+            if (!changed)
+            {
+                var locations = PhysicalLocations.ToList();
+
+                var newLocations = CreateResolveArgs(new DirectoryService(BaseItem.FileSystem), false).PhysicalLocations.ToList();
+
+                if (!locations.SequenceEqual(newLocations))
+                {
+                    changed = true;
+                }
+            }
+
+            return changed;
+        }
+
+        public override bool BeforeMetadataRefresh()
+        {
+            var changed = base.BeforeMetadataRefresh() || _requiresRefresh;
+            _requiresRefresh = false;
+            return changed;
+        }
+
+        private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations)
         {
         {
             var path = ContainingFolderPath;
             var path = ContainingFolderPath;
 
 
@@ -100,7 +127,11 @@ namespace MediaBrowser.Controller.Entities
                 args.FileSystemDictionary = fileSystemDictionary;
                 args.FileSystemDictionary = fileSystemDictionary;
             }
             }
 
 
-            PhysicalLocationsList = args.PhysicalLocations.ToList();
+            _requiresRefresh = _requiresRefresh || !args.PhysicalLocations.SequenceEqual(PhysicalLocations);
+            if (setPhysicalLocations)
+            {
+                PhysicalLocationsList = args.PhysicalLocations.ToList();
+            }
 
 
             return args;
             return args;
         }
         }

+ 22 - 27
MediaBrowser.Controller/Entities/Audio/Audio.cs

@@ -20,15 +20,12 @@ namespace MediaBrowser.Controller.Entities.Audio
         IHasArtist,
         IHasArtist,
         IHasMusicGenres,
         IHasMusicGenres,
         IHasLookupInfo<SongInfo>,
         IHasLookupInfo<SongInfo>,
-        IHasTags,
         IHasMediaSources,
         IHasMediaSources,
         IThemeMedia,
         IThemeMedia,
         IArchivable
         IArchivable
     {
     {
         public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
         public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
-        
-        public long? Size { get; set; }
-        public string Container { get; set; }
+
         public int? TotalBitrate { get; set; }
         public int? TotalBitrate { get; set; }
         public ExtraType? ExtraType { get; set; }
         public ExtraType? ExtraType { get; set; }
 
 
@@ -40,12 +37,6 @@ namespace MediaBrowser.Controller.Entities.Audio
 
 
         public List<string> AlbumArtists { get; set; }
         public List<string> AlbumArtists { get; set; }
 
 
-        /// <summary>
-        /// Gets or sets the album.
-        /// </summary>
-        /// <value>The album.</value>
-        public string Album { get; set; }
-
         [IgnoreDataMember]
         [IgnoreDataMember]
         public bool IsThemeMedia
         public bool IsThemeMedia
         {
         {
@@ -55,6 +46,12 @@ namespace MediaBrowser.Controller.Entities.Audio
             }
             }
         }
         }
 
 
+        [IgnoreDataMember]
+        public override bool EnableForceSaveOnDateModifiedChange
+        {
+            get { return true; }
+        }
+
         public Audio()
         public Audio()
         {
         {
             Artists = new List<string>();
             Artists = new List<string>();
@@ -150,12 +147,10 @@ namespace MediaBrowser.Controller.Entities.Audio
                     + (IndexNumber != null ? IndexNumber.Value.ToString("0000 - ") : "") + Name;
                     + (IndexNumber != null ? IndexNumber.Value.ToString("0000 - ") : "") + Name;
         }
         }
 
 
-        /// <summary>
-        /// Gets the user data key.
-        /// </summary>
-        /// <returns>System.String.</returns>
-        protected override string CreateUserDataKey()
+        public override List<string> GetUserDataKeys()
         {
         {
+            var list = base.GetUserDataKeys();
+
             if (ConfigurationManager.Configuration.EnableStandaloneMusicKeys)
             if (ConfigurationManager.Configuration.EnableStandaloneMusicKeys)
             {
             {
                 var songKey = IndexNumber.HasValue ? IndexNumber.Value.ToString("0000") : string.Empty;
                 var songKey = IndexNumber.HasValue ? IndexNumber.Value.ToString("0000") : string.Empty;
@@ -165,7 +160,7 @@ namespace MediaBrowser.Controller.Entities.Audio
                 {
                 {
                     songKey = ParentIndexNumber.Value.ToString("0000") + "-" + songKey;
                     songKey = ParentIndexNumber.Value.ToString("0000") + "-" + songKey;
                 }
                 }
-                songKey+= Name;
+                songKey += Name;
 
 
                 if (!string.IsNullOrWhiteSpace(Album))
                 if (!string.IsNullOrWhiteSpace(Album))
                 {
                 {
@@ -178,25 +173,25 @@ namespace MediaBrowser.Controller.Entities.Audio
                     songKey = albumArtist + "-" + songKey;
                     songKey = albumArtist + "-" + songKey;
                 }
                 }
 
 
-                return songKey;
+                list.Insert(0, songKey);
             }
             }
-
-            var parent = AlbumEntity;
-
-            if (parent != null)
+            else
             {
             {
-                var parentKey = parent.GetUserDataKey();
+                var parent = AlbumEntity;
 
 
-                if (IndexNumber.HasValue)
+                if (parent != null && IndexNumber.HasValue)
                 {
                 {
-                    var songKey = (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("0000 - ") : "")
-                                  + IndexNumber.Value.ToString("0000 - ");
+                    list.InsertRange(0, parent.GetUserDataKeys().Select(i =>
+                    {
+                        var songKey = (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("0000 - ") : "")
+                                      + IndexNumber.Value.ToString("0000 - ");
 
 
-                    return parentKey + songKey;
+                        return i + songKey;
+                    }));
                 }
                 }
             }
             }
 
 
-            return base.CreateUserDataKey();
+            return list;
         }
         }
 
 
         public override UnratedItem GetBlockUnratedType()
         public override UnratedItem GetBlockUnratedType()

+ 27 - 37
MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs

@@ -48,6 +48,15 @@ namespace MediaBrowser.Controller.Entities.Audio
             }
             }
         }
         }
 
 
+        [IgnoreDataMember]
+        public override bool SupportsCumulativeRunTimeTicks
+        {
+            get
+            {
+                return true;
+            }
+        }
+
         [IgnoreDataMember]
         [IgnoreDataMember]
         public List<string> AllArtists
         public List<string> AllArtists
         {
         {
@@ -96,36 +105,34 @@ namespace MediaBrowser.Controller.Entities.Audio
 
 
         public List<string> Artists { get; set; }
         public List<string> Artists { get; set; }
 
 
-        /// <summary>
-        /// Gets the user data key.
-        /// </summary>
-        /// <returns>System.String.</returns>
-        protected override string CreateUserDataKey()
+        public override List<string> GetUserDataKeys()
         {
         {
-            var id = this.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
+            var list = base.GetUserDataKeys();
 
 
-            if (!string.IsNullOrWhiteSpace(id))
+            if (ConfigurationManager.Configuration.EnableStandaloneMusicKeys)
             {
             {
-                return "MusicAlbum-MusicBrainzReleaseGroup-" + id;
+                var albumArtist = AlbumArtist;
+                if (!string.IsNullOrWhiteSpace(albumArtist))
+                {
+                    list.Insert(0, albumArtist + "-" + Name);
+                }
             }
             }
 
 
-            id = this.GetProviderId(MetadataProviders.MusicBrainzAlbum);
+            var id = this.GetProviderId(MetadataProviders.MusicBrainzAlbum);
 
 
             if (!string.IsNullOrWhiteSpace(id))
             if (!string.IsNullOrWhiteSpace(id))
             {
             {
-                return "MusicAlbum-Musicbrainz-" + id;
+                list.Insert(0, "MusicAlbum-Musicbrainz-" + id);
             }
             }
 
 
-            if (ConfigurationManager.Configuration.EnableStandaloneMusicKeys)
+            id = this.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
+
+            if (!string.IsNullOrWhiteSpace(id))
             {
             {
-                var albumArtist = AlbumArtist;
-                if (!string.IsNullOrWhiteSpace(albumArtist))
-                {
-                    return albumArtist + "-" + Name;
-                }
+                list.Insert(0, "MusicAlbum-MusicBrainzReleaseGroup-" + id);
             }
             }
 
 
-            return base.CreateUserDataKey();
+            return list;
         }
         }
 
 
         protected override bool GetBlockUnratedValue(UserPolicy config)
         protected override bool GetBlockUnratedValue(UserPolicy config)
@@ -172,17 +179,13 @@ namespace MediaBrowser.Controller.Entities.Audio
         {
         {
             var items = GetRecursiveChildren().ToList();
             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 numComplete = 0;
 
 
             var childUpdateType = ItemUpdateType.None;
             var childUpdateType = ItemUpdateType.None;
 
 
             // Refresh songs
             // Refresh songs
-            foreach (var item in songs)
+            foreach (var item in items)
             {
             {
                 cancellationToken.ThrowIfCancellationRequested();
                 cancellationToken.ThrowIfCancellationRequested();
 
 
@@ -192,7 +195,7 @@ namespace MediaBrowser.Controller.Entities.Audio
                 numComplete++;
                 numComplete++;
                 double percent = numComplete;
                 double percent = numComplete;
                 percent /= totalItems;
                 percent /= totalItems;
-                progress.Report(percent * 100);
+                progress.Report(percent * 95);
             }
             }
 
 
             var parentRefreshOptions = refreshOptions;
             var parentRefreshOptions = refreshOptions;
@@ -205,19 +208,6 @@ namespace MediaBrowser.Controller.Entities.Audio
             // Refresh current item
             // Refresh current item
             await RefreshMetadata(parentRefreshOptions, cancellationToken).ConfigureAwait(false);
             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);
             progress.Report(100);
         }
         }
     }
     }

+ 54 - 10
MediaBrowser.Controller/Entities/Audio/MusicArtist.cs

@@ -9,6 +9,7 @@ using System.Linq;
 using System.Runtime.Serialization;
 using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
 
 
 namespace MediaBrowser.Controller.Entities.Audio
 namespace MediaBrowser.Controller.Entities.Audio
 {
 {
@@ -17,7 +18,12 @@ namespace MediaBrowser.Controller.Entities.Audio
     /// </summary>
     /// </summary>
     public class MusicArtist : Folder, IMetadataContainer, IItemByName, IHasMusicGenres, IHasDualAccess, IHasProductionLocations, IHasLookupInfo<ArtistInfo>
     public class MusicArtist : Folder, IMetadataContainer, IItemByName, IHasMusicGenres, IHasDualAccess, IHasProductionLocations, IHasLookupInfo<ArtistInfo>
     {
     {
-        public bool IsAccessedByName { get; set; }
+        [IgnoreDataMember]
+        public bool IsAccessedByName
+        {
+            get { return ParentId == Guid.Empty; }
+        }
+
         public List<string> ProductionLocations { get; set; }
         public List<string> ProductionLocations { get; set; }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
@@ -29,6 +35,15 @@ namespace MediaBrowser.Controller.Entities.Audio
             }
             }
         }
         }
 
 
+        [IgnoreDataMember]
+        public override bool SupportsCumulativeRunTimeTicks
+        {
+            get
+            {
+                return true;
+            }
+        }
+
         [IgnoreDataMember]
         [IgnoreDataMember]
         public override bool SupportsAddingToPlaylist
         public override bool SupportsAddingToPlaylist
         {
         {
@@ -40,6 +55,18 @@ namespace MediaBrowser.Controller.Entities.Audio
             return !IsAccessedByName;
             return !IsAccessedByName;
         }
         }
 
 
+        public IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query)
+        {
+            if (query.IncludeItemTypes.Length == 0)
+            {
+                query.IncludeItemTypes = new[] { typeof(Audio).Name, typeof(MusicVideo).Name, typeof(MusicAlbum).Name };
+                query.ArtistNames = new[] { Name };
+            }
+
+            return LibraryManager.GetItemList(query);
+        }
+
+        [IgnoreDataMember]
         protected override IEnumerable<BaseItem> ActualChildren
         protected override IEnumerable<BaseItem> ActualChildren
         {
         {
             get
             get
@@ -53,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()
         public override bool IsSaveLocalMetadataEnabled()
         {
         {
             if (IsAccessedByName)
             if (IsAccessedByName)
@@ -80,13 +116,12 @@ namespace MediaBrowser.Controller.Entities.Audio
             ProductionLocations = new List<string>();
             ProductionLocations = new List<string>();
         }
         }
 
 
-        /// <summary>
-        /// Gets the user data key.
-        /// </summary>
-        /// <returns>System.String.</returns>
-        protected override string CreateUserDataKey()
+        public override List<string> GetUserDataKeys()
         {
         {
-            return GetUserDataKey(this);
+            var list = base.GetUserDataKeys();
+
+            list.InsertRange(0, GetUserDataKeys(this));
+            return list;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -121,18 +156,27 @@ namespace MediaBrowser.Controller.Entities.Audio
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
-        private static string GetUserDataKey(MusicArtist item)
+        private static List<string> GetUserDataKeys(MusicArtist item)
         {
         {
+            var list = new List<string>();
             var id = item.GetProviderId(MetadataProviders.MusicBrainzArtist);
             var id = item.GetProviderId(MetadataProviders.MusicBrainzArtist);
 
 
             if (!string.IsNullOrEmpty(id))
             if (!string.IsNullOrEmpty(id))
             {
             {
-                return "Artist-Musicbrainz-" + id;
+                list.Add("Artist-Musicbrainz-" + id);
             }
             }
 
 
-            return "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)
         protected override bool GetBlockUnratedValue(UserPolicy config)
         {
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Music);
             return config.BlockUnratedItems.Contains(UnratedItem.Music);

+ 22 - 6
MediaBrowser.Controller/Entities/Audio/MusicGenre.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Runtime.Serialization;
 using System.Runtime.Serialization;
+using MediaBrowser.Common.Extensions;
 
 
 namespace MediaBrowser.Controller.Entities.Audio
 namespace MediaBrowser.Controller.Entities.Audio
 {
 {
@@ -10,13 +11,20 @@ namespace MediaBrowser.Controller.Entities.Audio
     /// </summary>
     /// </summary>
     public class MusicGenre : BaseItem, IItemByName
     public class MusicGenre : BaseItem, IItemByName
     {
     {
-        /// <summary>
-        /// Gets the user data key.
-        /// </summary>
-        /// <returns>System.String.</returns>
-        protected override string CreateUserDataKey()
+        public override List<string> GetUserDataKeys()
         {
         {
-            return "MusicGenre-" + Name;
+            var list = base.GetUserDataKeys();
+
+            list.Insert(0, GetType().Name + "-" + (Name ?? string.Empty).RemoveDiacritics());
+            return list;
+        }
+
+        public override string PresentationUniqueKey
+        {
+            get
+            {
+                return GetUserDataKeys()[0];
+            }
         }
         }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
@@ -80,5 +88,13 @@ namespace MediaBrowser.Controller.Entities.Audio
                 return false;
                 return false;
             }
             }
         }
         }
+
+        public IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query)
+        {
+            query.Genres = new[] { Name };
+            query.IncludeItemTypes = new[] { typeof(MusicVideo).Name, typeof(Audio).Name, typeof(MusicAlbum).Name, typeof(MusicArtist).Name };
+
+            return LibraryManager.GetItemList(query);
+        }
     }
     }
 }
 }

Some files were not shown because too many files changed in this diff