diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj index dc410c321e..aa7dc5a153 100644 --- a/Emby.Drawing/Emby.Drawing.csproj +++ b/Emby.Drawing/Emby.Drawing.csproj @@ -80,7 +80,6 @@ - diff --git a/Emby.Drawing/ImageMagick/ImageMagickEncoder.cs b/Emby.Drawing/ImageMagick/ImageMagickEncoder.cs index cb60d1123e..3dbe7239dd 100644 --- a/Emby.Drawing/ImageMagick/ImageMagickEncoder.cs +++ b/Emby.Drawing/ImageMagick/ImageMagickEncoder.cs @@ -111,7 +111,6 @@ namespace Emby.Drawing.ImageMagick wand.CurrentImage.TrimImage(10); wand.SaveImage(outputPath); } - SaveDelay(); } public ImageSize GetImageSize(string path) @@ -189,7 +188,6 @@ namespace Emby.Drawing.ImageMagick } } } - SaveDelay(); } private void AddForegroundLayer(MagickWand wand, ImageProcessingOptions options) @@ -284,25 +282,16 @@ namespace Emby.Drawing.ImageMagick if (ratio >= 1.4) { - new StripCollageBuilder(_appPaths, _fileSystem).BuildThumbCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height, options.Text); + new StripCollageBuilder(_appPaths, _fileSystem).BuildThumbCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height); } else if (ratio >= .9) { - new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height, options.Text); + new StripCollageBuilder(_appPaths, _fileSystem).BuildSquareCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height); } else { - new StripCollageBuilder(_appPaths, _fileSystem).BuildPosterCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height, options.Text); + new StripCollageBuilder(_appPaths, _fileSystem).BuildPosterCollage(options.InputPaths.ToList(), options.OutputPath, options.Width, options.Height); } - - SaveDelay(); - } - - private void SaveDelay() - { - // For some reason the images are not always getting released right away - //var task = Task.Delay(300); - //Task.WaitAll(task); } public string Name diff --git a/Emby.Drawing/ImageMagick/StripCollageBuilder.cs b/Emby.Drawing/ImageMagick/StripCollageBuilder.cs index 8c50fb5eb9..7bc144c11d 100644 --- a/Emby.Drawing/ImageMagick/StripCollageBuilder.cs +++ b/Emby.Drawing/ImageMagick/StripCollageBuilder.cs @@ -9,140 +9,35 @@ namespace Emby.Drawing.ImageMagick public class StripCollageBuilder { private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; + private readonly IFileSystem _fileSystem; - public StripCollageBuilder(IApplicationPaths appPaths, IFileSystem fileSystem) + public StripCollageBuilder(IApplicationPaths appPaths, IFileSystem fileSystem) { _appPaths = appPaths; - _fileSystem = fileSystem; + _fileSystem = fileSystem; } - public void BuildPosterCollage(List paths, string outputPath, int width, int height, string text) + public void BuildPosterCollage(List 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 paths, string outputPath, int width, int height, string text) + public void BuildSquareCollage(List 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 paths, string outputPath, int width, int height, string text) + public void BuildThumbCollage(List 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 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 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 paths, int width, int height) { var inputPaths = ImageHelpers.ProjectPaths(paths, 4); @@ -295,9 +115,9 @@ namespace Emby.Drawing.ImageMagick wand.OpenImage("gradient:#111111-#111111"); 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 iHeight = Convert.ToInt32(height * .62); + int iHeight = Convert.ToInt32(height * .70); var horizontalImagePadding = Convert.ToInt32(width * 0.0125); foreach (var element in wandImages.ImageList) @@ -339,7 +159,7 @@ namespace Emby.Drawing.ImageMagick 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 * .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 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 paths, string label, int width, int height) { var inputPaths = ImageHelpers.ProjectPaths(paths, 4); - using (var wandImages = new MagickWand(inputPaths.ToArray())) + var outputWand = new MagickWand(width, height, new PixelWand("none", 1)); + var imageIndex = 0; + var cellWidth = width/2; + var cellHeight = height/2; + for (var x = 0; x < 2; x++) { - var wand = new MagickWand(width, height); - wand.OpenImage("gradient:#111111-#111111"); - using (var draw = new DrawingWand()) + for (var y = 0; y < 2; y++) { - using (var fcolor = new PixelWand(ColorName.White)) + using (var temp = new MagickWand(inputPaths[imageIndex])) { - 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()) - { - 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; } } } diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index dc93cb7304..80ebbb7190 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -163,7 +163,7 @@ namespace Emby.Drawing return _imageEncoder.SupportedOutputFormats; } - public async Task> ProcessImage(ImageProcessingOptions options) + public async Task> ProcessImage(ImageProcessingOptions options) { if (options == null) { @@ -178,14 +178,13 @@ namespace Emby.Drawing } var originalImagePath = originalImage.Path; + var dateModified = originalImage.DateModified; if (!_imageEncoder.SupportsImageEncoding) { - return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath)); + return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } - var dateModified = originalImage.DateModified; - if (options.CropWhiteSpace && _imageEncoder.SupportsImageEncoding) { var tuple = await GetWhitespaceCroppedImage(originalImagePath, dateModified).ConfigureAwait(false); @@ -211,7 +210,7 @@ namespace Emby.Drawing if (options.HasDefaultOptions(originalImagePath)) { // Just spit out the original file if all the options are default - return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath)); + return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } ImageSize? originalImageSize; @@ -221,7 +220,7 @@ namespace Emby.Drawing if (options.HasDefaultOptions(originalImagePath, originalImageSize.Value)) { // Just spit out the original file if all the options are default - return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath)); + return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } } catch @@ -235,10 +234,6 @@ namespace Emby.Drawing 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 semaphore = GetLock(cacheFilePath); - - await semaphore.WaitAsync().ConfigureAwait(false); - var imageProcessingLockTaken = false; try @@ -251,15 +246,20 @@ namespace Emby.Drawing var newHeight = Convert.ToInt32(newSize.Height); _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); 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(tmpPath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(tmpPath)); } - return new Tuple(cacheFilePath, GetMimeType(outputFormat, cacheFilePath)); + return new Tuple(cacheFilePath, GetMimeType(outputFormat, cacheFilePath), _fileSystem.GetLastWriteTimeUtc(cacheFilePath)); } catch (Exception ex) { @@ -267,7 +267,7 @@ namespace Emby.Drawing _logger.ErrorException("Error encoding image", ex); // Just spit out the original file if all the options are default - return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath)); + return new Tuple(originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } finally { @@ -275,8 +275,18 @@ namespace Emby.Drawing { _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 semaphore = GetLock(croppedImagePath); - - await semaphore.WaitAsync().ConfigureAwait(false); - // Check again in case of contention if (_fileSystem.FileExists(croppedImagePath)) { - semaphore.Release(); return GetResult(croppedImagePath); } @@ -428,11 +433,15 @@ namespace Emby.Drawing try { _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); imageProcessingLockTaken = true; - _imageEncoder.CropWhiteSpace(originalImagePath, croppedImagePath); + _imageEncoder.CropWhiteSpace(originalImagePath, tmpPath); + CopyFile(tmpPath, croppedImagePath); + return GetResult(tmpPath); } catch (NotImplementedException) { @@ -452,11 +461,7 @@ namespace Emby.Drawing { _imageProcessingSemaphore.Release(); } - - semaphore.Release(); } - - return GetResult(croppedImagePath); } private Tuple GetResult(string path) diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 8233717abf..dc811812ae 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -237,9 +237,12 @@ namespace MediaBrowser.Api { 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)) @@ -349,7 +352,7 @@ namespace MediaBrowser.Api if (job.Type != TranscodingJobType.Progressive) { - timerDuration = 1800000; + timerDuration = 60000; } job.PingTimeout = timerDuration; @@ -488,13 +491,17 @@ namespace MediaBrowser.Api { 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.StandardInput.WriteLine("q"); // 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) { diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 30750b3741..44a367be09 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -79,7 +79,7 @@ namespace MediaBrowser.Api } } } - + /// /// To the optimized serialized result using cache. /// @@ -115,12 +115,9 @@ namespace MediaBrowser.Api /// System.Object. 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) { var options = new DtoOptions(); @@ -154,152 +151,122 @@ namespace MediaBrowser.Api protected MusicArtist GetArtist(string name, ILibraryManager libraryManager) { - return libraryManager.GetArtist(DeSlugArtistName(name, libraryManager)); + if (name.IndexOf(BaseItem.SlugChar) != -1) + { + var result = libraryManager.GetItemList(new InternalItemsQuery + { + SlugName = name, + IncludeItemTypes = new[] { typeof(MusicArtist).Name } + + }).OfType().FirstOrDefault(); + + if (result != null) + { + return result; + } + } + + return libraryManager.GetArtist(name); } protected Studio GetStudio(string name, ILibraryManager libraryManager) { - return libraryManager.GetStudio(DeSlugStudioName(name, libraryManager)); + if (name.IndexOf(BaseItem.SlugChar) != -1) + { + var result = libraryManager.GetItemList(new InternalItemsQuery + { + SlugName = name, + IncludeItemTypes = new[] { typeof(Studio).Name } + + }).OfType().FirstOrDefault(); + + if (result != null) + { + return result; + } + } + + return libraryManager.GetStudio(name); } 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(Genre).Name } + + }).OfType().FirstOrDefault(); + + if (result != null) + { + return result; + } + } + + return libraryManager.GetGenre(name); } protected MusicGenre GetMusicGenre(string name, ILibraryManager libraryManager) { - return libraryManager.GetMusicGenre(DeSlugGenreName(name, libraryManager)); + if (name.IndexOf(BaseItem.SlugChar) != -1) + { + var result = libraryManager.GetItemList(new InternalItemsQuery + { + SlugName = name, + IncludeItemTypes = new[] { typeof(MusicGenre).Name } + + }).OfType().FirstOrDefault(); + + if (result != null) + { + return result; + } + } + + return libraryManager.GetMusicGenre(name); } protected GameGenre GetGameGenre(string name, ILibraryManager libraryManager) { - return libraryManager.GetGameGenre(DeSlugGameGenreName(name, libraryManager)); + if (name.IndexOf(BaseItem.SlugChar) != -1) + { + var result = libraryManager.GetItemList(new InternalItemsQuery + { + SlugName = name, + IncludeItemTypes = new[] { typeof(GameGenre).Name } + + }).OfType().FirstOrDefault(); + + if (result != null) + { + return result; + } + } + + return libraryManager.GetGameGenre(name); } protected Person GetPerson(string name, ILibraryManager libraryManager) { - return libraryManager.GetPerson(DeSlugPersonName(name, libraryManager)); - } - - /// - /// Deslugs an artist name by finding the correct entry in the library - /// - /// - /// - /// - protected string DeSlugArtistName(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(Person).Name } + + }).OfType().FirstOrDefault(); + + if (result != null) + { + return result; + } } - var items = libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Audio).Name, typeof(MusicVideo).Name, typeof(MusicAlbum).Name } - }); - - return items - .OfType() - .SelectMany(i => i.AllArtists) - .DistinctNames() - .FirstOrDefault(i => - { - i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); - - return string.Equals(i, name, StringComparison.OrdinalIgnoreCase); - - }) ?? name; - } - - /// - /// Deslugs a genre name by finding the correct entry in the library - /// - protected string DeSlugGenreName(string name, ILibraryManager libraryManager) - { - if (name.IndexOf(SlugChar) == -1) - { - return name; - } - - return libraryManager.RootFolder.GetRecursiveChildren() - .SelectMany(i => i.Genres) - .DistinctNames() - .FirstOrDefault(i => - { - i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); - - return string.Equals(i, name, StringComparison.OrdinalIgnoreCase); - - }) ?? name; - } - - protected string DeSlugGameGenreName(string name, ILibraryManager libraryManager) - { - if (name.IndexOf(SlugChar) == -1) - { - return name; - } - - var items = libraryManager.GetItemList(new InternalItemsQuery - { - IncludeItemTypes = new[] { typeof(Game).Name } - }); - - return items - .SelectMany(i => i.Genres) - .DistinctNames() - .FirstOrDefault(i => - { - i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); - - return string.Equals(i, name, StringComparison.OrdinalIgnoreCase); - - }) ?? name; - } - - /// - /// Deslugs a studio name by finding the correct entry in the library - /// - protected string DeSlugStudioName(string name, ILibraryManager libraryManager) - { - if (name.IndexOf(SlugChar) == -1) - { - return name; - } - - return libraryManager.RootFolder - .GetRecursiveChildren() - .SelectMany(i => i.Studios) - .DistinctNames() - .FirstOrDefault(i => - { - i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); - - return string.Equals(i, name, StringComparison.OrdinalIgnoreCase); - - }) ?? name; - } - - /// - /// Deslugs a person name by finding the correct entry in the library - /// - protected string DeSlugPersonName(string name, ILibraryManager libraryManager) - { - if (name.IndexOf(SlugChar) == -1) - { - return name; - } - - return libraryManager.GetPeopleNames(new InternalPeopleQuery()) - .FirstOrDefault(i => - { - i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); - - return string.Equals(i, name, StringComparison.OrdinalIgnoreCase); - - }) ?? name; + return libraryManager.GetPerson(name); } protected string GetPathValue(int index) diff --git a/MediaBrowser.Api/BrandingService.cs b/MediaBrowser.Api/BrandingService.cs index c900e4d069..e991565ad2 100644 --- a/MediaBrowser.Api/BrandingService.cs +++ b/MediaBrowser.Api/BrandingService.cs @@ -10,6 +10,7 @@ namespace MediaBrowser.Api } [Route("/Branding/Css", "GET", Summary = "Gets custom css")] + [Route("/Branding/Css.css", "GET", Summary = "Gets custom css")] public class GetBrandingCss { } diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index 446415fbb5..2e5c252d63 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -9,7 +9,9 @@ using ServiceStack.Web; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using CommonIO; +using MediaBrowser.Controller.MediaEncoding; namespace MediaBrowser.Api { @@ -71,6 +73,16 @@ namespace MediaBrowser.Api } + [Route("/System/MediaEncoder/Path", "POST", Summary = "Updates the path to the media encoder")] + [Authenticated(Roles = "Admin", AllowBeforeStartupWizard = true)] + public class UpdateMediaEncoderPath : IReturnVoid + { + [ApiMember(Name = "Path", Description = "Path", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Path { get; set; } + [ApiMember(Name = "PathType", Description = "PathType", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string PathType { get; set; } + } + public class ConfigurationService : BaseApiService { /// @@ -86,14 +98,22 @@ namespace MediaBrowser.Api private readonly IFileSystem _fileSystem; private readonly IProviderManager _providerManager; private readonly ILibraryManager _libraryManager; + private readonly IMediaEncoder _mediaEncoder; - public ConfigurationService(IJsonSerializer jsonSerializer, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IProviderManager providerManager, ILibraryManager libraryManager) + public ConfigurationService(IJsonSerializer jsonSerializer, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IProviderManager providerManager, ILibraryManager libraryManager, IMediaEncoder mediaEncoder) { _jsonSerializer = jsonSerializer; _configurationManager = configurationManager; _fileSystem = fileSystem; _providerManager = providerManager; _libraryManager = libraryManager; + _mediaEncoder = mediaEncoder; + } + + public void Post(UpdateMediaEncoderPath request) + { + var task = _mediaEncoder.UpdateEncoderPath(request.Path, request.PathType); + Task.WaitAll(task); } /// diff --git a/MediaBrowser.Api/Dlna/DlnaService.cs b/MediaBrowser.Api/Dlna/DlnaService.cs index b6c4f5dfba..dd4b59b920 100644 --- a/MediaBrowser.Api/Dlna/DlnaService.cs +++ b/MediaBrowser.Api/Dlna/DlnaService.cs @@ -31,11 +31,9 @@ namespace MediaBrowser.Api.Dlna 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 { - [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")] @@ -89,4 +87,4 @@ namespace MediaBrowser.Api.Dlna _dlnaManager.CreateProfile(request); } } -} +} \ No newline at end of file diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index 4e88e946f4..b354cb26c2 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -90,6 +90,17 @@ namespace MediaBrowser.Api 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 + { + + } + /// /// Class EnvironmentService /// @@ -108,7 +119,6 @@ namespace MediaBrowser.Api /// Initializes a new instance of the class. /// /// The network manager. - /// networkManager public EnvironmentService(INetworkManager networkManager, IFileSystem fileSystem) { if (networkManager == null) @@ -120,6 +130,29 @@ namespace MediaBrowser.Api _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); + } + /// /// Gets the specified request. /// diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs index 6d1c5d868d..b3b75359aa 100644 --- a/MediaBrowser.Api/FilterService.cs +++ b/MediaBrowser.Api/FilterService.cs @@ -80,7 +80,7 @@ namespace MediaBrowser.Api .OrderBy(i => i) .ToArray(); - result.Tags = items.OfType() + result.Tags = items .SelectMany(i => i.Tags) .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(i => i) @@ -103,7 +103,8 @@ namespace MediaBrowser.Api User = user, MediaTypes = request.GetMediaTypes(), IncludeItemTypes = request.GetIncludeItemTypes(), - Recursive = true + Recursive = true, + EnableTotalRecordCount = false }; return query; diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs index 040872fcc8..0953b95e62 100644 --- a/MediaBrowser.Api/GamesService.cs +++ b/MediaBrowser.Api/GamesService.cs @@ -10,6 +10,8 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Threading.Tasks; +using MediaBrowser.Model.Querying; namespace MediaBrowser.Api { @@ -107,8 +109,7 @@ namespace MediaBrowser.Api { IncludeItemTypes = new[] { typeof(GameSystem).Name } }; - var parentIds = new string[] { } ; - var gameSystems = _libraryManager.GetItemList(query, parentIds) + var gameSystems = _libraryManager.GetItemList(query) .Cast() .ToList(); @@ -128,8 +129,7 @@ namespace MediaBrowser.Api { IncludeItemTypes = new[] { typeof(Game).Name } }; - var parentIds = new string[] { }; - var games = _libraryManager.GetItemList(query, parentIds) + var games = _libraryManager.GetItemList(query) .Cast() .ToList(); @@ -162,7 +162,10 @@ namespace MediaBrowser.Api var items = user == null ? 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().ToList(); @@ -182,20 +185,42 @@ namespace MediaBrowser.Api /// /// The request. /// System.Object. - public object Get(GetSimilarGames request) + public async Task Get(GetSimilarGames request) { - var dtoOptions = GetDtoOptions(request); - - var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager, - _itemRepo, - _libraryManager, - _userDataRepository, - _dtoService, - Logger, - request, new[] { typeof(Game) }, - SimilarItemsHelper.GetSimiliarityScore); + var result = await GetSimilarItemsResult(request).ConfigureAwait(false); return ToOptimizedSerializedResultUsingCache(result); } + + private async Task> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request) + { + var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; + + var item = string.IsNullOrEmpty(request.Id) ? + (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder : + _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); + + var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user) + { + Limit = request.Limit, + IncludeItemTypes = new[] + { + typeof(Game).Name + }, + SimilarTo = item + + }).ToList(); + + var dtoOptions = GetDtoOptions(request); + + var result = new QueryResult + { + Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(), + + TotalRecordCount = itemsResult.Count + }; + + return result; + } } } diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index e5fe5bd684..5866ad15bc 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -273,7 +273,9 @@ namespace MediaBrowser.Api.Images { var list = new List(); - 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); @@ -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; // Prevent implicitly captured closure 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); @@ -514,7 +516,7 @@ namespace MediaBrowser.Api.Images /// if set to true [is head request]. /// System.Object. /// - public object GetImage(ImageRequest request, IHasImages item, bool isHeadRequest) + public Task GetImage(ImageRequest request, IHasImages item, bool isHeadRequest) { if (request.PercentPlayed.HasValue) { @@ -594,8 +596,7 @@ namespace MediaBrowser.Api.Images supportedImageEnhancers, cacheDuration, responseHeaders, - isHeadRequest) - .Result; + isHeadRequest); } private async Task GetImageResult(IHasImages item, @@ -632,18 +633,20 @@ namespace MediaBrowser.Api.Images headers["Vary"] = "Accept"; - return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions + return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { CacheDuration = cacheDuration, ResponseHeaders = headers, ContentType = imageResult.Item2, + DateLastModified = imageResult.Item3, IsHeadRequest = isHeadRequest, Path = imageResult.Item1, // Sometimes imagemagick keeps a hold on the file briefly even after it's done writing to it. // I'd rather do this than add a delay after saving the file FileShare = FileShare.ReadWrite - }); + + }).ConfigureAwait(false); } private List GetOutputFormats(ImageRequest request, ItemImageInfo image, bool cropwhitespace, List enhancers) diff --git a/MediaBrowser.Api/Images/RemoteImageService.cs b/MediaBrowser.Api/Images/RemoteImageService.cs index 02d1cdbe2d..b21e544951 100644 --- a/MediaBrowser.Api/Images/RemoteImageService.cs +++ b/MediaBrowser.Api/Images/RemoteImageService.cs @@ -238,9 +238,9 @@ namespace MediaBrowser.Api.Images } if (_fileSystem.FileExists(contentPath)) - { - return ToStaticFileResult(contentPath); - } + { + return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false); + } } catch (DirectoryNotFoundException) { @@ -259,9 +259,9 @@ namespace MediaBrowser.Api.Images contentPath = await reader.ReadToEndAsync().ConfigureAwait(false); } - return ToStaticFileResult(contentPath); + return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false); } - + /// /// Downloads the image. /// diff --git a/MediaBrowser.Api/ItemLookupService.cs b/MediaBrowser.Api/ItemLookupService.cs index 8be37e2100..357ff43949 100644 --- a/MediaBrowser.Api/ItemLookupService.cs +++ b/MediaBrowser.Api/ItemLookupService.cs @@ -38,6 +38,12 @@ namespace MediaBrowser.Api { } + [Route("/Items/RemoteSearch/Trailer", "POST")] + [Authenticated] + public class GetTrailerRemoteSearchResults : RemoteSearchQuery, IReturn> + { + } + [Route("/Items/RemoteSearch/AdultVideo", "POST")] [Authenticated] public class GetAdultVideoRemoteSearchResults : RemoteSearchQuery, IReturn> @@ -132,60 +138,65 @@ namespace MediaBrowser.Api return ToOptimizedResult(infos); } - public object Post(GetMovieRemoteSearchResults request) + public async Task Post(GetTrailerRemoteSearchResults request) { - var result = _providerManager.GetRemoteSearchResults(request, CancellationToken.None).Result; + var result = await _providerManager.GetRemoteSearchResults(request, CancellationToken.None).ConfigureAwait(false); return ToOptimizedResult(result); } - public object Post(GetSeriesRemoteSearchResults request) + public async Task Post(GetMovieRemoteSearchResults request) { - var result = _providerManager.GetRemoteSearchResults(request, CancellationToken.None).Result; + var result = await _providerManager.GetRemoteSearchResults(request, CancellationToken.None).ConfigureAwait(false); return ToOptimizedResult(result); } - public object Post(GetGameRemoteSearchResults request) + public async Task Post(GetSeriesRemoteSearchResults request) { - var result = _providerManager.GetRemoteSearchResults(request, CancellationToken.None).Result; + var result = await _providerManager.GetRemoteSearchResults(request, CancellationToken.None).ConfigureAwait(false); return ToOptimizedResult(result); } - public object Post(GetBoxSetRemoteSearchResults request) + public async Task Post(GetGameRemoteSearchResults request) { - var result = _providerManager.GetRemoteSearchResults(request, CancellationToken.None).Result; + var result = await _providerManager.GetRemoteSearchResults(request, CancellationToken.None).ConfigureAwait(false); return ToOptimizedResult(result); } - public object Post(GetPersonRemoteSearchResults request) + public async Task Post(GetBoxSetRemoteSearchResults request) { - var result = _providerManager.GetRemoteSearchResults(request, CancellationToken.None).Result; + var result = await _providerManager.GetRemoteSearchResults(request, CancellationToken.None).ConfigureAwait(false); return ToOptimizedResult(result); } - public object Post(GetMusicAlbumRemoteSearchResults request) + public async Task Post(GetPersonRemoteSearchResults request) { - var result = _providerManager.GetRemoteSearchResults(request, CancellationToken.None).Result; + var result = await _providerManager.GetRemoteSearchResults(request, CancellationToken.None).ConfigureAwait(false); return ToOptimizedResult(result); } - public object Post(GetMusicArtistRemoteSearchResults request) + public async Task Post(GetMusicAlbumRemoteSearchResults request) { - var result = _providerManager.GetRemoteSearchResults(request, CancellationToken.None).Result; + var result = await _providerManager.GetRemoteSearchResults(request, CancellationToken.None).ConfigureAwait(false); return ToOptimizedResult(result); } - public object Get(GetRemoteSearchImage request) + public async Task Post(GetMusicArtistRemoteSearchResults request) { - var result = GetRemoteImage(request).Result; + var result = await _providerManager.GetRemoteSearchResults(request, CancellationToken.None).ConfigureAwait(false); - return result; + return ToOptimizedResult(result); + } + + public Task Get(GetRemoteSearchImage request) + { + return GetRemoteImage(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)); - item.ProviderIds = request.ProviderIds; - var task = _providerManager.RefreshFullItem(item, new MetadataRefreshOptions(_fileSystem) + // Since the refresh process won't erase provider Ids, we need to set this explicitly now. + item.ProviderIds = request.ProviderIds; + //item.ProductionYear = request.ProductionYear; + //item.Name = request.Name; + + var task = _providerManager.RefreshFullItem(item, new MetadataRefreshOptions(_fileSystem) { MetadataRefreshMode = MetadataRefreshMode.FullRefresh, ImageRefreshMode = ImageRefreshMode.FullRefresh, ReplaceAllMetadata = true, - ReplaceAllImages = request.ReplaceAllImages + ReplaceAllImages = request.ReplaceAllImages, + SearchResult = request }, CancellationToken.None); Task.WaitAll(task); @@ -234,9 +250,9 @@ namespace MediaBrowser.Api 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) @@ -256,7 +272,7 @@ namespace MediaBrowser.Api contentPath = await reader.ReadToEndAsync().ConfigureAwait(false); } - return ToStaticFileResult(contentPath); + return await ResultFactory.GetStaticFileResult(Request, contentPath).ConfigureAwait(false); } /// @@ -275,7 +291,7 @@ namespace MediaBrowser.Api var fullCachePath = GetFullCachePath(urlHash + "." + ext); - _fileSystem.CreateDirectory(Path.GetDirectoryName(fullCachePath)); + _fileSystem.CreateDirectory(Path.GetDirectoryName(fullCachePath)); using (var stream = result.Content) { 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)) { await writer.WriteAsync(fullCachePath).ConfigureAwait(false); diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index f378747749..b944a39b6a 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -70,26 +70,21 @@ namespace MediaBrowser.Api Cultures = _localizationManager.GetCultures().ToList() }; - var locationType = item.LocationType; - if (locationType == LocationType.FileSystem || - locationType == LocationType.Offline) + if (!item.IsVirtualItem && !(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName)) { - if (!(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName)) + var inheritedContentType = _libraryManager.GetInheritedContentType(item); + var configuredContentType = _libraryManager.GetConfiguredContentType(item); + + if (string.IsNullOrWhiteSpace(inheritedContentType) || string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || !string.IsNullOrWhiteSpace(configuredContentType)) { - var inheritedContentType = _libraryManager.GetInheritedContentType(item); - var configuredContentType = _libraryManager.GetConfiguredContentType(item); + info.ContentTypeOptions = GetContentTypeOptions(true); + info.ContentType = configuredContentType; - if (string.IsNullOrWhiteSpace(inheritedContentType) || string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || !string.IsNullOrWhiteSpace(configuredContentType)) + if (string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) { - info.ContentTypeOptions = GetContentTypeOptions(true); - info.ContentType = configuredContentType; - - if (string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) - { - info.ContentTypeOptions = info.ContentTypeOptions - .Where(i => string.IsNullOrWhiteSpace(i.Value) || string.Equals(i.Value, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) - .ToList(); - } + info.ContentTypeOptions = info.ContentTypeOptions + .Where(i => string.IsNullOrWhiteSpace(i.Value) || string.Equals(i.Value, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) + .ToList(); } } } @@ -247,6 +242,12 @@ namespace MediaBrowser.Api hasBudget.Revenue = request.Revenue; } + var hasOriginalTitle = item as IHasOriginalTitle; + if (hasOriginalTitle != null) + { + hasOriginalTitle.OriginalTitle = hasOriginalTitle.OriginalTitle; + } + var hasCriticRating = item as IHasCriticRating; if (hasCriticRating != null) { @@ -274,11 +275,7 @@ namespace MediaBrowser.Api episode.AbsoluteEpisodeNumber = request.AbsoluteEpisodeNumber; } - var hasTags = item as IHasTags; - if (hasTags != null) - { - hasTags.Tags = request.Tags; - } + item.Tags = request.Tags; var hasTaglines = item as IHasTaglines; if (hasTaglines != null) @@ -292,11 +289,7 @@ namespace MediaBrowser.Api hasShortOverview.ShortOverview = request.ShortOverview; } - var hasKeywords = item as IHasKeywords; - if (hasKeywords != null) - { - hasKeywords.Keywords = request.Keywords; - } + item.Keywords = request.Keywords; if (request.Studios != null) { @@ -421,11 +414,6 @@ namespace MediaBrowser.Api series.Status = request.SeriesStatus; series.AirDays = request.AirDays; series.AirTime = request.AirTime; - - if (request.DisplaySpecialsWithSeasons.HasValue) - { - series.DisplaySpecialsWithSeasons = request.DisplaySpecialsWithSeasons.Value; - } } } diff --git a/MediaBrowser.Api/Library/FileOrganizationService.cs b/MediaBrowser.Api/Library/FileOrganizationService.cs index 849e9cf0db..0ed08a8607 100644 --- a/MediaBrowser.Api/Library/FileOrganizationService.cs +++ b/MediaBrowser.Api/Library/FileOrganizationService.cs @@ -119,8 +119,6 @@ namespace MediaBrowser.Api.Library { private readonly IFileOrganizationService _iFileOrganizationService; - /// The _json serializer - /// private readonly IJsonSerializer _jsonSerializer; public FileOrganizationService(IFileOrganizationService iFileOrganizationService, IJsonSerializer jsonSerializer) diff --git a/MediaBrowser.Api/Library/LibraryHelpers.cs b/MediaBrowser.Api/Library/LibraryHelpers.cs deleted file mode 100644 index 46ec4f2701..0000000000 --- a/MediaBrowser.Api/Library/LibraryHelpers.cs +++ /dev/null @@ -1,89 +0,0 @@ -using MediaBrowser.Controller; -using System; -using System.IO; -using System.Linq; -using CommonIO; - -namespace MediaBrowser.Api.Library -{ - /// - /// Class LibraryHelpers - /// - public static class LibraryHelpers - { - /// - /// The shortcut file extension - /// - private const string ShortcutFileExtension = ".mblink"; - /// - /// The shortcut file search - /// - private const string ShortcutFileSearch = "*" + ShortcutFileExtension; - - /// - /// Deletes a shortcut from within a virtual folder, within either the default view or a user view - /// - /// The file system. - /// Name of the virtual folder. - /// The media path. - /// The app paths. - /// The media folder does not exist - 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); - } - } - - /// - /// Adds an additional mediaPath to an existing virtual folder, within either the default view or a user view - /// - /// The file system. - /// Name of the virtual folder. - /// The path. - /// The app paths. - 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); - } - } -} diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index f9b3def975..14a771db03 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -350,7 +350,8 @@ namespace MediaBrowser.Api.Library Fields = request.Fields, Id = request.Id, Limit = request.Limit, - UserId = request.UserId + UserId = request.UserId, + ExcludeArtistIds = request.ExcludeArtistIds }); } if (item is MusicArtist) @@ -493,7 +494,7 @@ namespace MediaBrowser.Api.Library } } - public object Get(GetDownload request) + public Task Get(GetDownload request) { var item = _libraryManager.GetItemById(request.Id); var auth = _authContext.GetAuthorizationInfo(Request); @@ -552,7 +553,7 @@ namespace MediaBrowser.Api.Library } } - public object Get(GetFile request) + public Task Get(GetFile request) { var item = _libraryManager.GetItemById(request.Id); var locationType = item.LocationType; @@ -565,7 +566,7 @@ namespace MediaBrowser.Api.Library throw new ArgumentException("This command cannot be used for directories."); } - return ToStaticFileResult(item.Path); + return ResultFactory.GetStaticFileResult(Request, item.Path); } /// @@ -839,6 +840,7 @@ namespace MediaBrowser.Api.Library var dtoOptions = GetDtoOptions(request); var dtos = GetThemeSongIds(item).Select(_libraryManager.GetItemById) + .Where(i => i != null) .OrderBy(i => i.SortName) .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); @@ -882,6 +884,7 @@ namespace MediaBrowser.Api.Library var dtoOptions = GetDtoOptions(request); var dtos = GetThemeVideoIds(item).Select(_libraryManager.GetItemById) + .Where(i => i != null) .OrderBy(i => i.SortName) .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)); diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index 244dcf09f3..3cf0d5d937 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -190,75 +190,7 @@ namespace MediaBrowser.Api.Library /// The 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(), 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); } /// @@ -336,46 +268,7 @@ namespace MediaBrowser.Api.Library /// The 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(), 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); } /// @@ -393,7 +286,7 @@ namespace MediaBrowser.Api.Library try { - LibraryHelpers.AddMediaPath(_fileSystem, request.Name, request.Path, _appPaths); + _libraryManager.AddMediaPath(request.Name, request.Path); } finally { @@ -432,7 +325,7 @@ namespace MediaBrowser.Api.Library try { - LibraryHelpers.RemoveMediaPath(_fileSystem, request.Name, request.Path, _appPaths); + _libraryManager.RemoveMediaPath(request.Name, request.Path); } finally { diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 5b7bc78a8b..c687758b72 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -146,6 +146,13 @@ namespace MediaBrowser.Api.LiveTv /// The fields. [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] public string Fields { get; set; } + + public bool EnableTotalRecordCount { get; set; } + + public GetRecordings() + { + EnableTotalRecordCount = true; + } } [Route("/LiveTv/Recordings/Groups", "GET", Summary = "Gets live tv recording groups")] @@ -200,6 +207,8 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "SeriesTimerId", Description = "Optional filter by timers belonging to a series timer", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string SeriesTimerId { get; set; } + + public bool? IsActive { get; set; } } [Route("/LiveTv/Programs", "GET,POST", Summary = "Gets available live tv epgs..")] @@ -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")] 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")] public int? ImageTypeLimit { get; set; } @@ -266,12 +277,24 @@ namespace MediaBrowser.Api.LiveTv /// The fields. [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 GetPrograms() + { + EnableTotalRecordCount = true; + } } [Route("/LiveTv/Programs/Recommended", "GET", Summary = "Gets available live tv epgs..")] [Authenticated] public class GetRecommendedPrograms : IReturn>, 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")] public string UserId { get; set; } @@ -425,6 +448,12 @@ namespace MediaBrowser.Api.LiveTv public string Id { get; set; } } + [Route("/LiveTv/ListingProviders/Default", "GET")] + [Authenticated(AllowBeforeStartupWizard = true)] + public class GetDefaultListingProvider : ListingsProviderInfo, IReturn + { + } + [Route("/LiveTv/ListingProviders", "POST", Summary = "Adds a listing provider")] [Authenticated(AllowBeforeStartupWizard = true)] public class AddListingProvider : ListingsProviderInfo, IReturn @@ -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 TunerChannels { get; set; } + public List ProviderChannels { get; set; } + public List Mappings { get; set; } + public string ProviderName { get; set; } + } + [Route("/LiveTv/Registration", "GET")] [Authenticated] public class GetLiveTvRegistrationInfo : IReturn @@ -482,7 +537,14 @@ namespace MediaBrowser.Api.LiveTv [Authenticated(AllowBeforeStartupWizard = true)] public class GetSatIniMappings : IReturn> { - + + } + + [Route("/LiveTv/TunerHosts/Satip/ChannelScan", "GET", Summary = "Scans for available channels")] + [Authenticated(AllowBeforeStartupWizard = true)] + public class GetSatChannnelScanResult : TunerHostInfo + { + } public class LiveTvService : BaseApiService @@ -504,6 +566,18 @@ namespace MediaBrowser.Api.LiveTv _dtoService = dtoService; } + public object Get(GetDefaultListingProvider request) + { + return ToOptimizedResult(new ListingsProviderInfo()); + } + + public async Task Get(GetSatChannnelScanResult request) + { + var result = await _liveTvManager.GetSatChannelScanResult(request, CancellationToken.None).ConfigureAwait(false); + + return ToOptimizedResult(result); + } + public async Task Get(GetLiveTvRegistrationInfo request) { var result = await _liveTvManager.GetRegistrationInfo(request.ChannelId, request.ProgramId, request.Feature).ConfigureAwait(false); @@ -511,6 +585,46 @@ namespace MediaBrowser.Api.LiveTv return ToOptimizedResult(result); } + public async Task Post(SetChannelMapping request) + { + return await _liveTvManager.SetChannelMapping(request.ProviderId, request.TunerChannelNumber, request.ProviderChannelNumber).ConfigureAwait(false); + } + + public async Task Get(GetChannelMappingOptions request) + { + var config = GetConfiguration(); + + var listingsProviderInfo = config.ListingProviders.First(i => string.Equals(request.ProviderId, i.Id, StringComparison.OrdinalIgnoreCase)); + + var listingsProviderName = _liveTvManager.ListingProviders.First(i => string.Equals(i.Type, listingsProviderInfo.Type, StringComparison.OrdinalIgnoreCase)).Name; + + var tunerChannels = await _liveTvManager.GetChannelsForListingsProvider(request.ProviderId, CancellationToken.None) + .ConfigureAwait(false); + + var providerChannels = await _liveTvManager.GetChannelsFromListingsProviderData(request.ProviderId, CancellationToken.None) + .ConfigureAwait(false); + + var mappings = listingsProviderInfo.ChannelMappings.ToList(); + + var result = new ChannelMappingOptions + { + TunerChannels = tunerChannels.Select(i => _liveTvManager.GetTunerChannelMapping(i, mappings, providerChannels)).ToList(), + + ProviderChannels = providerChannels.Select(i => new NameIdPair + { + Name = i.Name, + Id = i.Number + + }).ToList(), + + Mappings = mappings, + + ProviderName = listingsProviderName + }; + + return ToOptimizedResult(result); + } + public object Get(GetSatIniMappings request) { return ToOptimizedResult(_liveTvManager.GetSatIniMappings()); @@ -522,9 +636,7 @@ namespace MediaBrowser.Api.LiveTv var response = await _httpClient.Get(new HttpRequestOptions { - Url = "https://json.schedulesdirect.org/20141201/available/countries", - CacheLength = TimeSpan.FromDays(1), - CacheMode = CacheMode.Unconditional + Url = "https://json.schedulesdirect.org/20141201/available/countries" }).ConfigureAwait(false); @@ -554,11 +666,7 @@ namespace MediaBrowser.Api.LiveTv public void Delete(DeleteListingProvider request) { - var config = GetConfiguration(); - - config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(request.Id, i.Id, StringComparison.OrdinalIgnoreCase)).ToList(); - - _config.SaveConfiguration("livetv", config); + _liveTvManager.DeleteListingsProvider(request.Id); } public async Task Post(AddTunerHost request) @@ -581,6 +689,11 @@ namespace MediaBrowser.Api.LiveTv return _config.GetConfiguration("livetv"); } + private void UpdateConfiguration(LiveTvOptions options) + { + _config.SaveConfiguration("livetv", options); + } + public async Task Get(GetLineups request) { 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 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 { Items = returnArray, TotalRecordCount = channelResult.TotalRecordCount }; - + return ToOptimizedSerializedResultUsingCache(result); } @@ -648,7 +761,8 @@ namespace MediaBrowser.Api.LiveTv { ChannelIds = (request.ChannelIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToArray(), UserId = request.UserId, - HasAired = request.HasAired + HasAired = request.HasAired, + EnableTotalRecordCount = request.EnableTotalRecordCount }; if (!string.IsNullOrEmpty(request.MinStartDate)) @@ -695,7 +809,8 @@ namespace MediaBrowser.Api.LiveTv HasAired = request.HasAired, IsMovie = request.IsMovie, IsKids = request.IsKids, - IsSports = request.IsSports + IsSports = request.IsSports, + EnableTotalRecordCount = request.EnableTotalRecordCount }; var result = await _liveTvManager.GetRecommendedPrograms(query, GetDtoOptions(request), CancellationToken.None).ConfigureAwait(false); @@ -722,7 +837,8 @@ namespace MediaBrowser.Api.LiveTv Limit = request.Limit, Status = request.Status, SeriesTimerId = request.SeriesTimerId, - IsInProgress = request.IsInProgress + IsInProgress = request.IsInProgress, + EnableTotalRecordCount = request.EnableTotalRecordCount }, options, CancellationToken.None).ConfigureAwait(false); @@ -753,7 +869,8 @@ namespace MediaBrowser.Api.LiveTv var result = await _liveTvManager.GetTimers(new TimerQuery { ChannelId = request.ChannelId, - SeriesTimerId = request.SeriesTimerId + SeriesTimerId = request.SeriesTimerId, + IsActive = request.IsActive }, CancellationToken.None).ConfigureAwait(false); diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index cdc0cd6aea..77949d1793 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -80,8 +80,6 @@ - - @@ -131,7 +129,6 @@ - diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index e06b2c7f8e..755713c845 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -14,6 +14,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using MediaBrowser.Controller.LiveTv; namespace MediaBrowser.Api.Movies { @@ -112,16 +113,14 @@ namespace MediaBrowser.Api.Movies /// System.Object. public async Task Get(GetSimilarMovies request) { - var result = await GetSimilarItemsResult( - request, SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false); + var result = await GetSimilarItemsResult(request).ConfigureAwait(false); return ToOptimizedSerializedResultUsingCache(result); } public async Task Get(GetSimilarTrailers request) { - var result = await GetSimilarItemsResult( - request, SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false); + var result = await GetSimilarItemsResult(request).ConfigureAwait(false); return ToOptimizedSerializedResultUsingCache(result); } @@ -130,152 +129,93 @@ namespace MediaBrowser.Api.Movies { var user = _userManager.GetUserById(request.UserId); - var query = new InternalItemsQuery(user) - { - IncludeItemTypes = new[] { typeof(Movie).Name } - }; - - if (user.Configuration.IncludeTrailersInSuggestions) - { - var includeList = query.IncludeItemTypes.ToList(); - includeList.Add(typeof(Trailer).Name); - query.IncludeItemTypes = includeList.ToArray(); - } - - var parentIds = string.IsNullOrWhiteSpace(request.ParentId) ? new string[] { } : new[] { request.ParentId }; - var movies = _libraryManager.GetItemList(query, parentIds); - movies = _libraryManager.ReplaceVideosWithPrimaryVersions(movies); - - var listEligibleForCategories = new List(); - var listEligibleForSuggestion = new List(); - - 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); dtoOptions.Fields = request.GetItemFields().ToList(); - var result = GetRecommendationCategories(user, listEligibleForCategories, listEligibleForSuggestion, request.CategoryLimit, request.ItemLimit, dtoOptions); + var result = GetRecommendationCategories(user, request.ParentId, request.CategoryLimit, request.ItemLimit, dtoOptions); return ToOptimizedResult(result); } - private async Task GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, Func, List, BaseItem, int> getSimilarityScore) + private async Task> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request) { var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; var item = string.IsNullOrEmpty(request.Id) ? (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder : _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); - - var query = new InternalItemsQuery(user) - { - IncludeItemTypes = new[] { typeof(Movie).Name } - }; - if (user == null || user.Configuration.IncludeTrailersInSuggestions) + var 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(); + typeof(Movie).Name, + typeof(Trailer).Name, + typeof(LiveTvProgram).Name + }, + IsMovie = true, + SimilarTo = item, + EnableGroupByMetadataKey = true - 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(); - - IEnumerable returnItems = items; - - if (request.Limit.HasValue) - { - returnItems = returnItems.Take(request.Limit.Value); - } + }).ToList(); var dtoOptions = GetDtoOptions(request); - var result = new ItemsResult + var result = new QueryResult { - Items = _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user).ToArray(), + Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(), - TotalRecordCount = items.Count + TotalRecordCount = itemsResult.Count }; return result; } - private IEnumerable GetRecommendationCategories(User user, List allMoviesForCategories, List allMovies, int categoryLimit, int itemLimit, DtoOptions dtoOptions) + private IEnumerable GetRecommendationCategories(User user, string parentId, int categoryLimit, int itemLimit, DtoOptions dtoOptions) { var categories = new List(); - var recentlyPlayedMovies = allMoviesForCategories - .Select(i => + var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? (Guid?)null : new Guid(parentId); + + var query = new InternalItemsQuery(user) + { + IncludeItemTypes = new[] { - var userdata = _userDataRepository.GetUserData(user.Id, i.GetUserDataKey()); - return new Tuple(i, userdata.Played, userdata.LastPlayedDate ?? DateTime.MinValue); - }) - .Where(i => i.Item2) - .OrderByDescending(i => i.Item3) - .Select(i => i.Item1) - .ToList(); + 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 + }; - var excludeFromLiked = recentlyPlayedMovies.Take(10); - var likedMovies = allMovies - .Select(i => + var recentlyPlayedMovies = _libraryManager.GetItemList(query).ToList(); + + var likedMovies = _libraryManager.GetItemList(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.Random }, + SortOrder = SortOrder.Descending, + Limit = 10, + IsFavoriteOrLiked = true, + ExcludeItemIds = recentlyPlayedMovies.Select(i => i.Id.ToString("N")).ToArray(), + EnableGroupByMetadataKey = true, + ParentId = parentIdGuid, + Recursive = true - if (userData.IsFavorite) - { - score = 2; - } - else - { - score = userData.Likes.HasValue ? userData.Likes.Value ? 1 : -1 : 0; - } - - return new Tuple(i, score); - }) - .OrderByDescending(i => i.Item2) - .ThenBy(i => Guid.NewGuid()) - .Where(i => i.Item2 > 0) - .Select(i => i.Item1) - .Where(i => !excludeFromLiked.Contains(i)); + }).ToList(); var mostRecentMovies = recentlyPlayedMovies.Take(6).ToList(); // Get recently played directors @@ -288,11 +228,11 @@ namespace MediaBrowser.Api.Movies .OrderBy(i => Guid.NewGuid()) .ToList(); - var similarToRecentlyPlayed = GetSimilarTo(user, allMovies, recentlyPlayedMovies.Take(7).OrderBy(i => Guid.NewGuid()), itemLimit, dtoOptions, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator(); - var similarToLiked = GetSimilarTo(user, allMovies, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedItem).GetEnumerator(); + var similarToRecentlyPlayed = GetSimilarTo(user, recentlyPlayedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator(); + var similarToLiked = GetSimilarTo(user, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedItem).GetEnumerator(); - var hasDirectorFromRecentlyPlayed = GetWithDirector(user, allMovies, recentDirectors, itemLimit, dtoOptions, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator(); - var hasActorFromRecentlyPlayed = GetWithActor(user, allMovies, recentActors, itemLimit, dtoOptions, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator(); + var hasDirectorFromRecentlyPlayed = GetWithDirector(user, recentDirectors, itemLimit, dtoOptions, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator(); + var hasActorFromRecentlyPlayed = GetWithActor(user, recentActors, itemLimit, dtoOptions, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator(); var categoryTypes = new List> { @@ -335,44 +275,28 @@ namespace MediaBrowser.Api.Movies return categories.OrderBy(i => i.RecommendationType).ThenBy(i => Guid.NewGuid()); } - private IEnumerable GetWithDirector(User user, List allMovies, IEnumerable directors, int itemLimit, DtoOptions dtoOptions, RecommendationType type) - { - var userId = user.Id; - - foreach (var director in directors) - { - 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(); - - if (items.Count > 0) - { - yield return new RecommendationDto - { - BaselineItemName = director, - CategoryId = director.GetMD5().ToString("N"), - RecommendationType = type, - Items = _dtoService.GetBaseItemDtos(items, dtoOptions, user).ToArray() - }; - } - } - } - - private IEnumerable GetWithActor(User user, List allMovies, IEnumerable names, int itemLimit, DtoOptions dtoOptions, RecommendationType type) + private IEnumerable GetWithDirector(User user, IEnumerable names, int itemLimit, DtoOptions dtoOptions, RecommendationType type) { foreach (var name in names) { - var itemsWithActor = _libraryManager.GetItemIds(new InternalItemsQuery(user) + var items = _libraryManager.GetItemList(new InternalItemsQuery(user) { - Person = name + 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 - }); - - var items = allMovies - .Where(i => itemsWithActor.Contains(i.Id)) - .Take(itemLimit) - .ToList(); + }).DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N")) + .Take(itemLimit) + .ToList(); if (items.Count > 0) { @@ -381,20 +305,65 @@ namespace MediaBrowser.Api.Movies BaselineItemName = name, CategoryId = name.GetMD5().ToString("N"), RecommendationType = type, - Items = _dtoService.GetBaseItemDtos(items, dtoOptions, user).ToArray() + Items = _dtoService.GetBaseItemDtos(items, dtoOptions, user).Result.ToArray() }; } } } - private IEnumerable GetSimilarTo(User user, List allMovies, IEnumerable baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type) + private IEnumerable GetWithActor(User user, IEnumerable names, int itemLimit, DtoOptions dtoOptions, RecommendationType type) + { + foreach (var name in names) + { + 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, + IncludeItemTypes = new[] + { + typeof(Movie).Name, + typeof(Trailer).Name, + typeof(LiveTvProgram).Name + }, + IsMovie = true, + EnableGroupByMetadataKey = true + + }).DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString("N")) + .Take(itemLimit) + .ToList(); + + if (items.Count > 0) + { + yield return new RecommendationDto + { + BaselineItemName = name, + CategoryId = name.GetMD5().ToString("N"), + RecommendationType = type, + Items = _dtoService.GetBaseItemDtos(items, dtoOptions, user).Result.ToArray() + }; + } + } + } + + private IEnumerable GetSimilarTo(User user, List baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type) { foreach (var item in baselineItems) { - var similar = SimilarItemsHelper - .GetSimilaritems(item, _libraryManager, allMovies, SimilarItemsHelper.GetSimiliarityScore) - .Take(itemLimit) - .ToList(); + var similar = _libraryManager.GetItemList(new InternalItemsQuery(user) + { + Limit = itemLimit, + IncludeItemTypes = new[] + { + typeof(Movie).Name, + typeof(Trailer).Name, + typeof(LiveTvProgram).Name + }, + IsMovie = true, + SimilarTo = item, + EnableGroupByMetadataKey = true + + }).ToList(); if (similar.Count > 0) { @@ -403,7 +372,7 @@ namespace MediaBrowser.Api.Movies BaselineItemName = item.Name, CategoryId = item.Id.ToString("N"), RecommendationType = type, - Items = _dtoService.GetBaseItemDtos(similar, dtoOptions, user).ToArray() + Items = _dtoService.GetBaseItemDtos(similar, dtoOptions, user).Result.ToArray() }; } } diff --git a/MediaBrowser.Api/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs index d74dd5b6ac..c70620cf9f 100644 --- a/MediaBrowser.Api/Movies/TrailersService.cs +++ b/MediaBrowser.Api/Movies/TrailersService.cs @@ -58,7 +58,7 @@ namespace MediaBrowser.Api.Movies getItems.IncludeItemTypes = "Trailer"; - return new ItemsService(_userManager, _libraryManager, _userDataRepository, _localizationManager, _dtoService, _collectionManager) + return new ItemsService(_userManager, _libraryManager, _localizationManager, _dtoService) { AuthorizationContext = AuthorizationContext, Logger = Logger, diff --git a/MediaBrowser.Api/Music/AlbumsService.cs b/MediaBrowser.Api/Music/AlbumsService.cs index e774c30776..2d7e909bf6 100644 --- a/MediaBrowser.Api/Music/AlbumsService.cs +++ b/MediaBrowser.Api/Music/AlbumsService.cs @@ -8,6 +8,7 @@ using ServiceStack; using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace MediaBrowser.Api.Music { @@ -49,18 +50,18 @@ namespace MediaBrowser.Api.Music _dtoService = dtoService; } - public object Get(GetSimilarArtists request) + public async Task Get(GetSimilarArtists request) { var dtoOptions = GetDtoOptions(request); - var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager, + var result = await SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager, _itemRepo, _libraryManager, _userDataRepository, _dtoService, Logger, request, new[] { typeof(MusicArtist) }, - SimilarItemsHelper.GetSimiliarityScore); + SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false); return ToOptimizedSerializedResultUsingCache(result); } @@ -70,18 +71,18 @@ namespace MediaBrowser.Api.Music /// /// The request. /// System.Object. - public object Get(GetSimilarAlbums request) + public async Task Get(GetSimilarAlbums request) { var dtoOptions = GetDtoOptions(request); - var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager, + var result = await SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager, _itemRepo, _libraryManager, _userDataRepository, _dtoService, Logger, request, new[] { typeof(MusicAlbum) }, - GetAlbumSimilarityScore); + GetAlbumSimilarityScore).ConfigureAwait(false); return ToOptimizedSerializedResultUsingCache(result); } diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs index d2a4aa60cc..19265408bf 100644 --- a/MediaBrowser.Api/Music/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -8,6 +8,7 @@ using MediaBrowser.Model.Querying; using ServiceStack; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; namespace MediaBrowser.Api.Music { @@ -76,7 +77,7 @@ namespace MediaBrowser.Api.Music _libraryManager = libraryManager; } - public object Get(GetInstantMixFromItem request) + public Task Get(GetInstantMixFromItem request) { var item = _libraryManager.GetItemById(request.Id); @@ -87,7 +88,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request); } - public object Get(GetInstantMixFromArtistId request) + public Task Get(GetInstantMixFromArtistId request) { var item = _libraryManager.GetItemById(request.Id); @@ -98,7 +99,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request); } - public object Get(GetInstantMixFromMusicGenreId request) + public Task Get(GetInstantMixFromMusicGenreId request) { var item = _libraryManager.GetItemById(request.Id); @@ -109,7 +110,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request); } - public object Get(GetInstantMixFromSong request) + public Task Get(GetInstantMixFromSong request) { var item = _libraryManager.GetItemById(request.Id); @@ -120,7 +121,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request); } - public object Get(GetInstantMixFromAlbum request) + public Task Get(GetInstantMixFromAlbum request) { var album = _libraryManager.GetItemById(request.Id); @@ -131,7 +132,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request); } - public object Get(GetInstantMixFromPlaylist request) + public Task Get(GetInstantMixFromPlaylist request) { var playlist = (Playlist)_libraryManager.GetItemById(request.Id); @@ -142,7 +143,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request); } - public object Get(GetInstantMixFromMusicGenre request) + public Task Get(GetInstantMixFromMusicGenre request) { var user = _userManager.GetUserById(request.UserId); @@ -151,7 +152,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request); } - public object Get(GetInstantMixFromArtist request) + public Task Get(GetInstantMixFromArtist request) { var user = _userManager.GetUserById(request.UserId); var artist = _libraryManager.GetArtist(request.Name); @@ -161,7 +162,7 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request); } - private object GetResult(IEnumerable