commit
f21f9923de
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -17,134 +17,29 @@ namespace Emby.Drawing.ImageMagick
|
||||||
_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 = BuildPosterCollageWandWithText(paths, text, width, height))
|
|
||||||
{
|
|
||||||
wand.SaveImage(outputPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
using (var wand = BuildPosterCollageWand(paths, width, height))
|
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 = BuildSquareCollageWandWithText(paths, text, width, height))
|
|
||||||
{
|
|
||||||
wand.SaveImage(outputPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
using (var wand = BuildSquareCollageWand(paths, width, height))
|
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 = BuildThumbCollageWandWithText(paths, text, width, height))
|
|
||||||
{
|
|
||||||
wand.SaveImage(outputPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
using (var wand = BuildThumbCollageWand(paths, width, height))
|
using (var wand = BuildThumbCollageWand(paths, width, height))
|
||||||
{
|
{
|
||||||
wand.SaveImage(outputPath);
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private MagickWand BuildPosterCollageWand(List<string> paths, int width, int height)
|
private MagickWand BuildPosterCollageWand(List<string> paths, int width, int height)
|
||||||
{
|
{
|
||||||
|
@ -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);
|
for (var y = 0; y < 2; y++)
|
||||||
wand.OpenImage("gradient:#111111-#111111");
|
|
||||||
using (var draw = new DrawingWand())
|
|
||||||
{
|
{
|
||||||
using (var fcolor = new PixelWand(ColorName.White))
|
using (var temp = new MagickWand(inputPaths[imageIndex]))
|
||||||
{
|
{
|
||||||
draw.FillColor = fcolor;
|
temp.CurrentImage.ScaleImage(cellWidth, cellHeight);
|
||||||
draw.Font = MontserratLightFont;
|
// draw this image into the strip at the next position
|
||||||
draw.FontSize = 60;
|
var xPos = x*cellWidth;
|
||||||
draw.FontWeight = FontWeightType.LightStyle;
|
var yPos = y*cellHeight;
|
||||||
draw.TextAntialias = true;
|
outputWand.CurrentImage.CompositeImage(temp, CompositeOperator.OverCompositeOp, xPos, yPos);
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
imageIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return wand;
|
return outputWand;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string MontserratLightFont
|
|
||||||
{
|
|
||||||
get { return PlayedIndicatorDrawer.ExtractFont("MontserratLight.otf", _appPaths, _fileSystem); }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -237,10 +237,13 @@ 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));
|
||||||
|
|
||||||
|
if (job != null)
|
||||||
|
{
|
||||||
_activeTranscodingJobs.Remove(job);
|
_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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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));
|
if (name.IndexOf(BaseItem.SlugChar) != -1)
|
||||||
|
{
|
||||||
|
var result = libraryManager.GetItemList(new InternalItemsQuery
|
||||||
|
{
|
||||||
|
SlugName = name,
|
||||||
|
IncludeItemTypes = new[] { typeof(MusicArtist).Name }
|
||||||
|
|
||||||
|
}).OfType<MusicArtist>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return libraryManager.GetArtist(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Studio GetStudio(string name, ILibraryManager libraryManager)
|
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<Studio>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return libraryManager.GetStudio(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Genre GetGenre(string name, ILibraryManager 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(Genre).Name }
|
||||||
|
|
||||||
|
}).OfType<Genre>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return libraryManager.GetGenre(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected MusicGenre GetMusicGenre(string name, ILibraryManager libraryManager)
|
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<MusicGenre>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return libraryManager.GetMusicGenre(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected GameGenre GetGameGenre(string name, ILibraryManager libraryManager)
|
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<GameGenre>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return libraryManager.GetGameGenre(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Person GetPerson(string name, ILibraryManager libraryManager)
|
protected Person GetPerson(string name, ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
return libraryManager.GetPerson(DeSlugPersonName(name, libraryManager));
|
if (name.IndexOf(BaseItem.SlugChar) != -1)
|
||||||
|
{
|
||||||
|
var result = libraryManager.GetItemList(new InternalItemsQuery
|
||||||
|
{
|
||||||
|
SlugName = name,
|
||||||
|
IncludeItemTypes = new[] { typeof(Person).Name }
|
||||||
|
|
||||||
|
}).OfType<Person>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return libraryManager.GetPerson(name);
|
||||||
/// 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)
|
|
||||||
{
|
|
||||||
if (name.IndexOf(SlugChar) == -1)
|
|
||||||
{
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
var items = libraryManager.GetItemList(new InternalItemsQuery
|
|
||||||
{
|
|
||||||
IncludeItemTypes = new[] { typeof(Audio).Name, typeof(MusicVideo).Name, typeof(MusicAlbum).Name }
|
|
||||||
});
|
|
||||||
|
|
||||||
return items
|
|
||||||
.OfType<IHasArtist>()
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deslugs a genre name by finding the correct entry in the library
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deslugs a studio name by finding the correct entry in the library
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Deslugs a person name by finding the correct entry in the library
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string GetPathValue(int index)
|
protected string GetPathValue(int index)
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
var gameSystems = _libraryManager.GetItemList(query, parentIds)
|
|
||||||
.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)
|
||||||
var games = _libraryManager.GetItemList(query, parentIds)
|
|
||||||
.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 dtoOptions = GetDtoOptions(request);
|
var result = await GetSimilarItemsResult(request).ConfigureAwait(false);
|
||||||
|
|
||||||
var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
|
|
||||||
_itemRepo,
|
|
||||||
_libraryManager,
|
|
||||||
_userDataRepository,
|
|
||||||
_dtoService,
|
|
||||||
Logger,
|
|
||||||
request, new[] { typeof(Game) },
|
|
||||||
SimilarItemsHelper.GetSimiliarityScore);
|
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
|
||||||
|
{
|
||||||
|
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
|
||||||
|
|
||||||
|
var item = string.IsNullOrEmpty(request.Id) ?
|
||||||
|
(!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
|
||||||
|
_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
|
||||||
|
|
||||||
|
var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||||
|
{
|
||||||
|
Limit = request.Limit,
|
||||||
|
IncludeItemTypes = new[]
|
||||||
|
{
|
||||||
|
typeof(Game).Name
|
||||||
|
},
|
||||||
|
SimilarTo = item
|
||||||
|
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
var dtoOptions = GetDtoOptions(request);
|
||||||
|
|
||||||
|
var result = new QueryResult<BaseItemDto>
|
||||||
|
{
|
||||||
|
Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(),
|
||||||
|
|
||||||
|
TotalRecordCount = itemsResult.Count
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
isHeadRequest);
|
||||||
.Result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
@ -239,7 +239,7 @@ 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,7 +259,7 @@ 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>
|
||||||
|
|
|
@ -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);
|
||||||
|
@ -236,7 +252,7 @@ namespace MediaBrowser.Api
|
||||||
|
|
||||||
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>
|
||||||
|
|
|
@ -70,11 +70,7 @@ namespace MediaBrowser.Api
|
||||||
Cultures = _localizationManager.GetCultures().ToList()
|
Cultures = _localizationManager.GetCultures().ToList()
|
||||||
};
|
};
|
||||||
|
|
||||||
var locationType = item.LocationType;
|
if (!item.IsVirtualItem && !(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName))
|
||||||
if (locationType == LocationType.FileSystem ||
|
|
||||||
locationType == LocationType.Offline)
|
|
||||||
{
|
|
||||||
if (!(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName))
|
|
||||||
{
|
{
|
||||||
var inheritedContentType = _libraryManager.GetInheritedContentType(item);
|
var inheritedContentType = _libraryManager.GetInheritedContentType(item);
|
||||||
var configuredContentType = _libraryManager.GetConfiguredContentType(item);
|
var configuredContentType = _libraryManager.GetConfiguredContentType(item);
|
||||||
|
@ -92,7 +88,6 @@ namespace MediaBrowser.Api
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return ToOptimizedResult(info);
|
return ToOptimizedResult(info);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
item.Tags = request.Tags;
|
||||||
if (hasTags != null)
|
|
||||||
{
|
|
||||||
hasTags.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;
|
item.Keywords = request.Keywords;
|
||||||
if (hasKeywords != null)
|
|
||||||
{
|
|
||||||
hasKeywords.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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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));
|
||||||
|
|
||||||
|
|
|
@ -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))
|
_libraryManager.AddVirtualFolder(request.Name, request.CollectionType, request.Paths, request.RefreshLibrary);
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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))
|
_libraryManager.RemoveVirtualFolder(request.Name, request.RefreshLibrary);
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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>
|
||||||
|
@ -485,6 +540,13 @@ namespace MediaBrowser.Api.LiveTv
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[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
|
||||||
{
|
{
|
||||||
private readonly ILiveTvManager _liveTvManager;
|
private readonly ILiveTvManager _liveTvManager;
|
||||||
|
@ -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",
|
Url = "https://json.schedulesdirect.org/20141201/available/countries"
|
||||||
CacheLength = TimeSpan.FromDays(1),
|
|
||||||
CacheMode = CacheMode.Unconditional
|
|
||||||
|
|
||||||
}).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();
|
_liveTvManager.DeleteListingsProvider(request.Id);
|
||||||
|
|
||||||
config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(request.Id, i.Id, StringComparison.OrdinalIgnoreCase)).ToList();
|
|
||||||
|
|
||||||
_config.SaveConfiguration("livetv", config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,7 +726,7 @@ 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>
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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(
|
var result = await GetSimilarItemsResult(request).ConfigureAwait(false);
|
||||||
request, SimilarItemsHelper.GetSimiliarityScore).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(
|
var result = await GetSimilarItemsResult(request).ConfigureAwait(false);
|
||||||
request, SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
|
@ -130,50 +129,16 @@ 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;
|
||||||
|
|
||||||
|
@ -181,101 +146,76 @@ namespace MediaBrowser.Api.Movies
|
||||||
(!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)
|
var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { typeof(Movie).Name }
|
Limit = request.Limit,
|
||||||
};
|
IncludeItemTypes = new[]
|
||||||
|
|
||||||
if (user == null || user.Configuration.IncludeTrailersInSuggestions)
|
|
||||||
{
|
{
|
||||||
var includeList = query.IncludeItemTypes.ToList();
|
typeof(Movie).Name,
|
||||||
includeList.Add(typeof(Trailer).Name);
|
typeof(Trailer).Name,
|
||||||
query.IncludeItemTypes = includeList.ToArray();
|
typeof(LiveTvProgram).Name
|
||||||
}
|
},
|
||||||
|
IsMovie = true,
|
||||||
|
SimilarTo = item,
|
||||||
|
EnableGroupByMetadataKey = true
|
||||||
|
|
||||||
var parentIds = new string[] { };
|
}).ToList();
|
||||||
var list = _libraryManager.GetItemList(query, parentIds)
|
|
||||||
.Where(i =>
|
|
||||||
{
|
|
||||||
// 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();
|
|
||||||
|
|
||||||
IEnumerable<BaseItem> returnItems = items;
|
|
||||||
|
|
||||||
if (request.Limit.HasValue)
|
|
||||||
{
|
|
||||||
returnItems = returnItems.Take(request.Limit.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
var parentIdGuid = string.IsNullOrWhiteSpace(parentId) ? (Guid?)null : new Guid(parentId);
|
||||||
.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 excludeFromLiked = recentlyPlayedMovies.Take(10);
|
var query = new InternalItemsQuery(user)
|
||||||
var likedMovies = allMovies
|
|
||||||
.Select(i =>
|
|
||||||
{
|
{
|
||||||
var score = 0;
|
IncludeItemTypes = new[]
|
||||||
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)
|
var recentlyPlayedMovies = _libraryManager.GetItemList(query).ToList();
|
||||||
{
|
|
||||||
score = 2;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
score = userData.Likes.HasValue ? userData.Likes.Value ? 1 : -1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Tuple<BaseItem, int>(i, score);
|
var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||||
})
|
{
|
||||||
.OrderByDescending(i => i.Item2)
|
IncludeItemTypes = new[]
|
||||||
.ThenBy(i => Guid.NewGuid())
|
{
|
||||||
.Where(i => i.Item2 > 0)
|
typeof(Movie).Name,
|
||||||
.Select(i => i.Item1)
|
typeof(Trailer).Name,
|
||||||
.Where(i => !excludeFromLiked.Contains(i));
|
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 similarToRecentlyPlayed = GetSimilarTo(user, recentlyPlayedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator();
|
||||||
var similarToLiked = GetSimilarTo(user, allMovies, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedItem).GetEnumerator();
|
var similarToLiked = GetSimilarTo(user, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedItem).GetEnumerator();
|
||||||
|
|
||||||
var hasDirectorFromRecentlyPlayed = GetWithDirector(user, allMovies, recentDirectors, itemLimit, dtoOptions, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator();
|
var hasDirectorFromRecentlyPlayed = GetWithDirector(user, recentDirectors, itemLimit, dtoOptions, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator();
|
||||||
var hasActorFromRecentlyPlayed = GetWithActor(user, allMovies, recentActors, itemLimit, dtoOptions, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator();
|
var hasActorFromRecentlyPlayed = GetWithActor(user, recentActors, itemLimit, dtoOptions, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator();
|
||||||
|
|
||||||
var categoryTypes = new List<IEnumerator<RecommendationDto>>
|
var categoryTypes = new List<IEnumerator<RecommendationDto>>
|
||||||
{
|
{
|
||||||
|
@ -335,42 +275,26 @@ 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)
|
|
||||||
{
|
|
||||||
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<RecommendationDto> GetWithActor(User user, List<BaseItem> allMovies, 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
|
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"))
|
||||||
|
|
||||||
var items = allMovies
|
|
||||||
.Where(i => itemsWithActor.Contains(i.Id))
|
|
||||||
.Take(itemLimit)
|
.Take(itemLimit)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
@ -381,20 +305,65 @@ 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> GetWithActor(User user, IEnumerable<string> 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<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
|
var similar = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||||
.GetSimilaritems(item, _libraryManager, allMovies, SimilarItemsHelper.GetSimiliarityScore)
|
{
|
||||||
.Take(itemLimit)
|
Limit = itemLimit,
|
||||||
.ToList();
|
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()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,12 +133,14 @@ namespace MediaBrowser.Api
|
||||||
parms += "&title=true";
|
parms += "&title=true";
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = _httpClient.Get(MbAdminUrl + "/service/packageReview/retrieve" + parms, CancellationToken.None).Result;
|
using (var result = await _httpClient.Get(MbAdminUrl + "/service/packageReview/retrieve" + parms, CancellationToken.None)
|
||||||
|
.ConfigureAwait(false))
|
||||||
|
{
|
||||||
var reviews = _serializer.DeserializeFromStream<List<PackageReviewInfo>>(result);
|
var reviews = _serializer.DeserializeFromStream<List<PackageReviewInfo>>(result);
|
||||||
|
|
||||||
return ToOptimizedResult(reviews);
|
return ToOptimizedResult(reviews);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Post(CreateReviewRequest request)
|
public void Post(CreateReviewRequest request)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 (string.Equals(ApiEntryPoint.Instance.GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) ||
|
||||||
if (state.RunTimeTicks.HasValue)
|
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)
|
// h264 (h264_nvenc)
|
||||||
else if (string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase))
|
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))
|
||||||
{
|
{
|
||||||
|
if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
// not supported by h264_omx
|
||||||
param += " -profile:v " + state.VideoRequest.Profile;
|
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 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) ||
|
||||||
// h264_qsv and libnvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
|
string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
|
||||||
if (String.Equals(h264Encoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || String.Equals(h264Encoder, "libnvenc", 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);
|
||||||
|
|
||||||
|
if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
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 transcodingId = Guid.NewGuid().ToString("N");
|
||||||
var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
|
var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
|
||||||
|
|
||||||
if (ApiEntryPoint.Instance.GetEncodingOptions().EnableDebugLogging)
|
|
||||||
{
|
|
||||||
commandLineArgs = "-loglevel debug " + commandLineArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,10 +1077,10 @@ 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)
|
||||||
|
@ -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)
|
// cabac no longer used
|
||||||
{
|
|
||||||
videoRequest.Cabac = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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,7 +1735,7 @@ 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)
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
|
@ -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,9 +2324,10 @@ 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";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 = 0;
|
||||||
|
|
||||||
var itsOffsetMs = hlsVideoRequest == null
|
|
||||||
? 0
|
|
||||||
: hlsVideoRequest.TimeStampOffsetMs;
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
var enableGenericSegmenter = false;
|
||||||
|
|
||||||
//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}\"",
|
if (enableGenericSegmenter)
|
||||||
// inputModifier,
|
{
|
||||||
// GetInputArgument(state),
|
var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(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}\"",
|
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,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);
|
||||||
|
|
||||||
|
|
|
@ -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,9 +86,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 (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
|
if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return state.VideoStream != null && IsH264(state.VideoStream) ?
|
// if h264_mp4toannexb is ever added, do not use it for live tv
|
||||||
args + " -bsf:v h264_mp4toannexb" :
|
if (state.VideoStream != null && IsH264(state.VideoStream) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
|
||||||
args;
|
{
|
||||||
|
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})\"",
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 bytesRead = await CopyToAsyncInternal(fs, outputStream, BufferSize, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var fsPosition = fs.Position;
|
//var position = fs.Position;
|
||||||
|
//_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
|
||||||
var bytesRead = fsPosition - position;
|
|
||||||
|
|
||||||
//Logger.Debug("Streamed {0} bytes from file {1}", bytesRead, 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];
|
byte[] buffer = new byte[bufferSize];
|
||||||
int count;
|
int bytesRead;
|
||||||
while ((count = source.Read(array, 0, array.Length)) != 0)
|
int totalBytesRead = 0;
|
||||||
|
|
||||||
|
while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
|
||||||
{
|
{
|
||||||
//if (_job != null)
|
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
||||||
//{
|
|
||||||
// var didPause = false;
|
|
||||||
// var totalPauseTime = 0;
|
|
||||||
|
|
||||||
// if (_job.IsUserPaused)
|
_bytesWritten += bytesRead;
|
||||||
// {
|
totalBytesRead += bytesRead;
|
||||||
// _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);
|
|
||||||
|
|
||||||
_bytesWritten += count;
|
|
||||||
|
|
||||||
if (_job != null)
|
if (_job != null)
|
||||||
{
|
{
|
||||||
_job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten);
|
_job.BytesDownloaded = Math.Max(_job.BytesDownloaded ?? _bytesWritten, _bytesWritten);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return totalBytesRead;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) &&
|
if (state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
|
||||||
(string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) || isOutputMkv))
|
|
||||||
{
|
{
|
||||||
args += " -bsf:v h264_mp4toannexb";
|
args += " -bsf:v h264_mp4toannexb";
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,8 @@ 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>
|
||||||
|
@ -190,8 +192,9 @@ 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 ForceLiveStream { get; set; }
|
||||||
public bool? Cabac { get; set; }
|
|
||||||
|
public bool EnableSubtitlesInManifest { get; set; }
|
||||||
|
|
||||||
public VideoStreamRequest()
|
public VideoStreamRequest()
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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<object> </returns>
|
/// <returns> A Task<object> </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 ResultFactory.GetResult(returnResult, contentType, headers);
|
||||||
return ro;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#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,70 +355,86 @@ 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>
|
private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request, User user)
|
||||||
/// <param name="request"> The request. </param>
|
|
||||||
/// <returns> The query result. </returns>
|
|
||||||
private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request)
|
|
||||||
{
|
{
|
||||||
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default list type = children
|
||||||
|
|
||||||
|
var folder = item as Folder;
|
||||||
|
if (folder == null)
|
||||||
|
{
|
||||||
|
folder = user == null ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(request.Ids))
|
||||||
|
{
|
||||||
|
request.Recursive = true;
|
||||||
|
var query = GetItemsQuery(request, user);
|
||||||
|
var result = await folder.GetItems(query).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(request.SortBy))
|
||||||
|
{
|
||||||
|
var ids = query.ItemIds.ToList();
|
||||||
|
|
||||||
|
// Try to preserve order
|
||||||
|
result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
if (request.Recursive)
|
if (request.Recursive)
|
||||||
{
|
{
|
||||||
var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
|
return await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
|
return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
items = ((Folder)item).GetChildren(user, true);
|
IEnumerable<BaseItem> items = folder.GetChildren(user, true);
|
||||||
|
|
||||||
|
var itemsArray = items.ToArray();
|
||||||
|
|
||||||
|
return new QueryResult<BaseItem>
|
||||||
|
{
|
||||||
|
Items = itemsArray,
|
||||||
|
TotalRecordCount = itemsArray.Length
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return new QueryResult<BaseItem> { Items = items.ToArray() };
|
#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? minDate = string.IsNullOrWhiteSpace(request.MinDate) ?
|
||||||
(DateTime?)null :
|
(DateTime?)null :
|
||||||
|
@ -438,18 +451,15 @@ namespace MediaBrowser.Api.Reports
|
||||||
var result = builder.GetResult(queryResult, request);
|
var result = builder.GetResult(queryResult, request);
|
||||||
result.TotalRecordCount = queryResult.TotalRecordCount;
|
result.TotalRecordCount = queryResult.TotalRecordCount;
|
||||||
return result;
|
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);
|
||||||
|
|
|
@ -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;
|
return item.Tags;
|
||||||
if (hasTags != null)
|
|
||||||
{
|
|
||||||
return hasTags.Tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new List<string>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<string> GetKeywords(BaseItem item)
|
private static IEnumerable<string> GetKeywords(BaseItem item)
|
||||||
{
|
{
|
||||||
var hasTags = item as IHasKeywords;
|
return item.Keywords;
|
||||||
if (hasTags != null)
|
|
||||||
{
|
|
||||||
return hasTags.Keywords;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new List<string>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -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;
|
SetWizardFinishValues(_config.Configuration);
|
||||||
_config.Configuration.EnableCustomPathSubFolders = true;
|
|
||||||
_config.Configuration.EnableDateLastRefresh = true;
|
|
||||||
_config.Configuration.EnableStandaloneMusicKeys = true;
|
|
||||||
_config.Configuration.EnableCaseSensitiveItemIds = true;
|
|
||||||
_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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
return ResultFactory.GetResult(stream, MimeTypes.GetMimeType("file." + request.Format));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Stream> GetSubtitles(GetSubtitle request)
|
|
||||||
{
|
{
|
||||||
return await _subtitleEncoder.GetSubtitles(request.Id,
|
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(text, MimeTypes.GetMimeType("file." + request.Format));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task<Stream> GetSubtitles(GetSubtitle request)
|
||||||
|
{
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
await _syncManager.ReportSyncJobItemTransferBeginning(request.Id).ConfigureAwait(false);
|
||||||
Task.WaitAll(task);
|
|
||||||
|
|
||||||
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)
|
var dtos = (await _dtoService.GetBaseItemDtos(items, dtoOptions, authenticatedUser).ConfigureAwait(false));
|
||||||
.ToList();
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ 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));
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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 dtoOptions = GetDtoOptions(request);
|
var result = await GetSimilarItemsResult(request).ConfigureAwait(false);
|
||||||
|
|
||||||
var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager,
|
|
||||||
_itemRepo,
|
|
||||||
_libraryManager,
|
|
||||||
_userDataManager,
|
|
||||||
_dtoService,
|
|
||||||
Logger,
|
|
||||||
request, new[] { typeof(Series) },
|
|
||||||
SimilarItemsHelper.GetSimiliarityScore);
|
|
||||||
|
|
||||||
return ToOptimizedSerializedResultUsingCache(result);
|
return ToOptimizedSerializedResultUsingCache(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetUpcomingEpisodes request)
|
private async Task<QueryResult<BaseItemDto>> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request)
|
||||||
|
{
|
||||||
|
var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
|
||||||
|
|
||||||
|
var item = string.IsNullOrEmpty(request.Id) ?
|
||||||
|
(!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
|
||||||
|
_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
|
||||||
|
|
||||||
|
var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||||
|
{
|
||||||
|
Limit = request.Limit,
|
||||||
|
IncludeItemTypes = new[]
|
||||||
|
{
|
||||||
|
typeof(Series).Name
|
||||||
|
},
|
||||||
|
SimilarTo = item
|
||||||
|
|
||||||
|
}).ToList();
|
||||||
|
|
||||||
|
var dtoOptions = GetDtoOptions(request);
|
||||||
|
|
||||||
|
var result = new QueryResult<BaseItemDto>
|
||||||
|
{
|
||||||
|
Items = (await _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user).ConfigureAwait(false)).ToArray(),
|
||||||
|
|
||||||
|
TotalRecordCount = itemsResult.Count
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
var returnList = returnItems.ToList();
|
||||||
.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
|
||||||
|
|
|
@ -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)
|
throw new NotImplementedException();
|
||||||
{
|
|
||||||
return LibraryManager.GetAlbumArtists(items
|
|
||||||
.Where(i => !i.IsFolder)
|
|
||||||
.OfType<IHasAlbumArtist>());
|
|
||||||
}
|
|
||||||
|
|
||||||
return LibraryManager.GetArtists(items
|
|
||||||
.Where(i => !i.IsFolder)
|
|
||||||
.OfType<IHasArtist>());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 (!tags.Any(v => i.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))
|
||||||
if (hasTags == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!tags.Any(v => hasTags.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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
throw new NotImplementedException();
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,11 +93,28 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override QueryResult<Tuple<BaseItem, ItemCounts>> GetItems(GetItemsByName request, InternalItemsQuery query)
|
||||||
|
{
|
||||||
|
var viewType = GetParentItemViewType(request);
|
||||||
|
|
||||||
|
if (string.Equals(viewType, CollectionType.Music) || string.Equals(viewType, CollectionType.MusicVideos))
|
||||||
|
{
|
||||||
|
return LibraryManager.GetMusicGenres(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(viewType, CollectionType.Games))
|
||||||
|
{
|
||||||
|
return LibraryManager.GetGameGenres(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return LibraryManager.GetGenres(query);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all items.
|
/// Gets all items.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -105,52 +123,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)
|
||||||
{
|
{
|
||||||
var viewType = GetParentItemViewType(request);
|
throw new NotImplementedException();
|
||||||
|
|
||||||
if (string.Equals(viewType, CollectionType.Music) || string.Equals(viewType, CollectionType.MusicVideos))
|
|
||||||
{
|
|
||||||
return items
|
|
||||||
.SelectMany(i => i.Genres)
|
|
||||||
.DistinctNames()
|
|
||||||
.Select(name => LibraryManager.GetMusicGenre(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, ILocalizationManager localization, IDtoService dtoService)
|
||||||
public ItemsService(IUserManager userManager, ILibraryManager libraryManager, IUserDataManager userDataRepository, ILocalizationManager localization, IDtoService dtoService, ICollectionManager collectionManager)
|
|
||||||
{
|
{
|
||||||
|
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);
|
||||||
|
@ -85,14 +101,31 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
{
|
{
|
||||||
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 GetItemsToSerialize(request, user).ConfigureAwait(false);
|
var result = await GetQueryResult(request, user).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("GetItemsToSerialize returned null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.Items == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("GetItemsToSerialize result.Items returned null");
|
||||||
|
}
|
||||||
|
|
||||||
var dtoOptions = GetDtoOptions(request);
|
var 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 await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
|
return await folder.GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 await folder.GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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))
|
||||||
|
@ -308,15 +342,15 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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))
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
throw new NotImplementedException();
|
||||||
.SelectMany(i => i.Genres)
|
|
||||||
.DistinctNames()
|
|
||||||
.Select(name => LibraryManager.GetMusicGenre(name));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)))
|
var videosWithVersions = items.Where(i => i.MediaSourceCount > 1)
|
||||||
{
|
|
||||||
throw new ArgumentException("Only videos can be grouped together.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var videos = items.Cast<Video>().ToList();
|
|
||||||
|
|
||||||
var videosWithVersions = videos.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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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)
|
||||||
|
{
|
||||||
|
if (options.PreferIpv4)
|
||||||
{
|
{
|
||||||
AddIpv4Option(httpWebRequest, options);
|
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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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">
|
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\NLog.4.2.3\lib\net45\NLog.dll</HintPath>
|
<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">
|
<Reference Include="SimpleInjector, Version=3.2.0.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\SimpleInjector.3.1.2\lib\net45\SimpleInjector.dll</HintPath>
|
<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" />
|
||||||
|
|
|
@ -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>
|
||||||
|
@ -121,18 +122,16 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||||
public TaskResult LastExecutionResult
|
public TaskResult LastExecutionResult
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
|
||||||
if (_lastExecutionResult == null)
|
|
||||||
{
|
{
|
||||||
var path = GetHistoryFilePath();
|
var path = GetHistoryFilePath();
|
||||||
|
|
||||||
lock (_lastExecutionResultSyncLock)
|
lock (_lastExecutionResultSyncLock)
|
||||||
{
|
{
|
||||||
if (_lastExecutionResult == null)
|
if (_lastExecutionResult == null && !_readFromFile)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return JsonSerializer.DeserializeFromFile<TaskResult>(path);
|
_lastExecutionResult = JsonSerializer.DeserializeFromFile<TaskResult>(path);
|
||||||
}
|
}
|
||||||
catch (DirectoryNotFoundException)
|
catch (DirectoryNotFoundException)
|
||||||
{
|
{
|
||||||
|
@ -146,7 +145,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
|
||||||
{
|
{
|
||||||
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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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/";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public JsonSerializer(IFileSystem fileSystem)
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,11 +13,13 @@ 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)
|
public XmlSerializer(IFileSystem fileSystem, ILogger logger)
|
||||||
{
|
{
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to cache these
|
// Need to cache these
|
||||||
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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" />
|
<package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
|
||||||
</packages>
|
</packages>
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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.
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<BaseItemDto>.</returns>
|
/// <returns>IEnumerable<BaseItemDto>.</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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_requiresRefresh = _requiresRefresh || !args.PhysicalLocations.SequenceEqual(PhysicalLocations);
|
||||||
|
if (setPhysicalLocations)
|
||||||
|
{
|
||||||
PhysicalLocationsList = args.PhysicalLocations.ToList();
|
PhysicalLocationsList = args.PhysicalLocations.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
public override List<string> GetUserDataKeys()
|
||||||
/// Gets the user data key.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
protected override string CreateUserDataKey()
|
|
||||||
{
|
{
|
||||||
|
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;
|
||||||
|
@ -178,25 +173,25 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||||
songKey = albumArtist + "-" + songKey;
|
songKey = albumArtist + "-" + songKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
return songKey;
|
list.Insert(0, songKey);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
var parent = AlbumEntity;
|
var parent = AlbumEntity;
|
||||||
|
|
||||||
if (parent != null)
|
if (parent != null && IndexNumber.HasValue)
|
||||||
{
|
{
|
||||||
var parentKey = parent.GetUserDataKey();
|
list.InsertRange(0, parent.GetUserDataKeys().Select(i =>
|
||||||
|
|
||||||
if (IndexNumber.HasValue)
|
|
||||||
{
|
{
|
||||||
var songKey = (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("0000 - ") : "")
|
var songKey = (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("0000 - ") : "")
|
||||||
+ IndexNumber.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()
|
||||||
|
|
|
@ -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>
|
public override List<string> GetUserDataKeys()
|
||||||
/// Gets the user data key.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
protected override string CreateUserDataKey()
|
|
||||||
{
|
{
|
||||||
var id = this.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
|
var list = base.GetUserDataKeys();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(id))
|
|
||||||
{
|
|
||||||
return "MusicAlbum-MusicBrainzReleaseGroup-" + id;
|
|
||||||
}
|
|
||||||
|
|
||||||
id = this.GetProviderId(MetadataProviders.MusicBrainzAlbum);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(id))
|
|
||||||
{
|
|
||||||
return "MusicAlbum-Musicbrainz-" + id;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ConfigurationManager.Configuration.EnableStandaloneMusicKeys)
|
if (ConfigurationManager.Configuration.EnableStandaloneMusicKeys)
|
||||||
{
|
{
|
||||||
var albumArtist = AlbumArtist;
|
var albumArtist = AlbumArtist;
|
||||||
if (!string.IsNullOrWhiteSpace(albumArtist))
|
if (!string.IsNullOrWhiteSpace(albumArtist))
|
||||||
{
|
{
|
||||||
return albumArtist + "-" + Name;
|
list.Insert(0, albumArtist + "-" + Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.CreateUserDataKey();
|
var id = this.GetProviderId(MetadataProviders.MusicBrainzAlbum);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(id))
|
||||||
|
{
|
||||||
|
list.Insert(0, "MusicAlbum-Musicbrainz-" + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
id = this.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(id))
|
||||||
|
{
|
||||||
|
list.Insert(0, "MusicAlbum-MusicBrainzReleaseGroup-" + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 totalItems = items.Count;
|
||||||
|
|
||||||
var others = items.Except(songs).ToList();
|
|
||||||
|
|
||||||
var totalItems = songs.Count + others.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
public override List<string> GetUserDataKeys()
|
||||||
/// Gets the user data key.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
protected override string CreateUserDataKey()
|
|
||||||
{
|
{
|
||||||
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);
|
||||||
|
|
|
@ -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>
|
public override List<string> GetUserDataKeys()
|
||||||
/// Gets the user data key.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
protected override string CreateUserDataKey()
|
|
||||||
{
|
{
|
||||||
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 have changed in this diff Show More
Loading…
Reference in New Issue
Block a user