Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 69f30bc52c | |||
| 3b605b6280 | |||
| e8a359f97b | |||
| dbfaafc08a | |||
| de6747f6c5 | |||
| 100fe40b0a | |||
| 2197d20783 | |||
| f4f9ab777f | |||
| 9ca7d62709 | |||
| 7aad16b6ec | |||
| f77673438e | |||
| bc2eb9fa79 | |||
| df69ce55f7 | |||
| 93cca4d50e | |||
| 3c64bcffe3 | |||
| 6ece01d425 | |||
| 53f333bd64 | |||
| 9e459090ed | |||
| 95a4fc0f18 | |||
| 62bf3db885 | |||
| 42d702c091 | |||
| d07fe14814 | |||
| 970eaf8dfb | |||
| bc27c2b7da | |||
| 37b969304a | |||
| c6b5c4dda5 | |||
| 51f5da8015 | |||
| 6e89ca9a34 | |||
| de1896828f | |||
| 7d1d159b8a | |||
| c3c98331d9 | |||
| e78fa8c3ef | |||
| d63fb437c6 | |||
| 25c6388e23 | |||
| 8f16e10fc6 | |||
| 1f07586d1c | |||
| 5e0f480e48 | |||
| 210d10400a |
@@ -104,6 +104,7 @@
|
|||||||
- [shemanaev](https://github.com/shemanaev)
|
- [shemanaev](https://github.com/shemanaev)
|
||||||
- [skaro13](https://github.com/skaro13)
|
- [skaro13](https://github.com/skaro13)
|
||||||
- [sl1288](https://github.com/sl1288)
|
- [sl1288](https://github.com/sl1288)
|
||||||
|
- [Smith00101010](https://github.com/Smith00101010)
|
||||||
- [sorinyo2004](https://github.com/sorinyo2004)
|
- [sorinyo2004](https://github.com/sorinyo2004)
|
||||||
- [sparky8251](https://github.com/sparky8251)
|
- [sparky8251](https://github.com/sparky8251)
|
||||||
- [spookbits](https://github.com/spookbits)
|
- [spookbits](https://github.com/spookbits)
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
|
var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -253,7 +253,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
|
var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -278,7 +278,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
|
var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -305,7 +305,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
_logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header);
|
_logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header);
|
||||||
|
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
|
var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -378,6 +378,10 @@ namespace Emby.Dlna.PlayTo
|
|||||||
public async Task SetPlay(CancellationToken cancellationToken)
|
public async Task SetPlay(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
if (avCommands == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await SetPlay(avCommands, cancellationToken).ConfigureAwait(false);
|
await SetPlay(avCommands, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -388,7 +392,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
|
var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -406,7 +410,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
|
var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -528,7 +532,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
|
var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -578,7 +582,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
|
var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -665,6 +669,10 @@ namespace Emby.Dlna.PlayTo
|
|||||||
}
|
}
|
||||||
|
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
if (rendererCommands == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||||
Properties.BaseUrl,
|
Properties.BaseUrl,
|
||||||
@@ -733,6 +741,11 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (rendererCommands == null)
|
||||||
|
{
|
||||||
|
return (false, null);
|
||||||
|
}
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
var result = await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(
|
||||||
Properties.BaseUrl,
|
Properties.BaseUrl,
|
||||||
service,
|
service,
|
||||||
@@ -914,6 +927,10 @@ namespace Emby.Dlna.PlayTo
|
|||||||
var httpClient = new SsdpHttpClient(_httpClientFactory);
|
var httpClient = new SsdpHttpClient(_httpClientFactory);
|
||||||
|
|
||||||
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (document == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
AvCommands = TransportCommands.Create(document);
|
AvCommands = TransportCommands.Create(document);
|
||||||
return AvCommands;
|
return AvCommands;
|
||||||
@@ -942,6 +959,10 @@ namespace Emby.Dlna.PlayTo
|
|||||||
var httpClient = new SsdpHttpClient(_httpClientFactory);
|
var httpClient = new SsdpHttpClient(_httpClientFactory);
|
||||||
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
|
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
|
||||||
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (document == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
RendererCommands = TransportCommands.Create(document);
|
RendererCommands = TransportCommands.Create(document);
|
||||||
return RendererCommands;
|
return RendererCommands;
|
||||||
@@ -973,6 +994,10 @@ namespace Emby.Dlna.PlayTo
|
|||||||
var ssdpHttpClient = new SsdpHttpClient(httpClientFactory);
|
var ssdpHttpClient = new SsdpHttpClient(httpClientFactory);
|
||||||
|
|
||||||
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
|
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
|
||||||
|
if (document == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var friendlyNames = new List<string>();
|
var friendlyNames = new List<string>();
|
||||||
|
|
||||||
|
|||||||
@@ -943,11 +943,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
request.DeviceId = values.GetValueOrDefault("DeviceId");
|
request.DeviceId = values.GetValueOrDefault("DeviceId");
|
||||||
request.MediaSourceId = values.GetValueOrDefault("MediaSourceId");
|
request.MediaSourceId = values.GetValueOrDefault("MediaSourceId");
|
||||||
request.LiveStreamId = values.GetValueOrDefault("LiveStreamId");
|
request.LiveStreamId = values.GetValueOrDefault("LiveStreamId");
|
||||||
|
request.IsDirectStream = string.Equals("true", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase);
|
||||||
// Be careful, IsDirectStream==true by default (Static != false or not in query).
|
|
||||||
// See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? true.
|
|
||||||
request.IsDirectStream = !string.Equals("false", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex");
|
request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex");
|
||||||
request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex");
|
request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex");
|
||||||
request.StartPositionTicks = GetLongValue(values, "StartPositionTicks");
|
request.StartPositionTicks = GetLongValue(values, "StartPositionTicks");
|
||||||
|
|||||||
@@ -178,6 +178,11 @@ namespace Emby.Dlna.PlayTo
|
|||||||
if (controller == null)
|
if (controller == null)
|
||||||
{
|
{
|
||||||
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false);
|
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (device == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Ignoring device as xml response is invalid.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
string deviceName = device.Properties.Name;
|
string deviceName = device.Properties.Name;
|
||||||
|
|
||||||
|
|||||||
@@ -45,10 +45,10 @@ namespace Emby.Dlna.PlayTo
|
|||||||
cancellationToken)
|
cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
return await XDocument.LoadAsync(
|
||||||
return XDocument.Parse(
|
stream,
|
||||||
await reader.ReadToEndAsync().ConfigureAwait(false),
|
LoadOptions.PreserveWhitespace,
|
||||||
LoadOptions.PreserveWhitespace);
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
|
private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
|
||||||
@@ -94,10 +94,17 @@ namespace Emby.Dlna.PlayTo
|
|||||||
options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
|
options.Headers.TryAddWithoutValidation("FriendlyName.DLNA.ORG", FriendlyName);
|
||||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).SendAsync(options, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
try
|
||||||
return XDocument.Parse(
|
{
|
||||||
await reader.ReadToEndAsync().ConfigureAwait(false),
|
return await XDocument.LoadAsync(
|
||||||
LoadOptions.PreserveWhitespace);
|
stream,
|
||||||
|
LoadOptions.PreserveWhitespace,
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<HttpResponseMessage> PostSoapDataAsync(
|
private async Task<HttpResponseMessage> PostSoapDataAsync(
|
||||||
|
|||||||
@@ -13,12 +13,10 @@ namespace Emby.Dlna.PlayTo
|
|||||||
public class TransportCommands
|
public class TransportCommands
|
||||||
{
|
{
|
||||||
private const string CommandBase = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:{0} xmlns:m=\"{1}\">" + "{2}" + "</m:{0}>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>";
|
private const string CommandBase = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:{0} xmlns:m=\"{1}\">" + "{2}" + "</m:{0}>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>";
|
||||||
private List<StateVariable> _stateVariables = new List<StateVariable>();
|
|
||||||
private List<ServiceAction> _serviceActions = new List<ServiceAction>();
|
|
||||||
|
|
||||||
public List<StateVariable> StateVariables => _stateVariables;
|
public List<StateVariable> StateVariables { get; } = new List<StateVariable>();
|
||||||
|
|
||||||
public List<ServiceAction> ServiceActions => _serviceActions;
|
public List<ServiceAction> ServiceActions { get; } = new List<ServiceAction>();
|
||||||
|
|
||||||
public static TransportCommands Create(XDocument document)
|
public static TransportCommands Create(XDocument document)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
@@ -171,11 +172,26 @@ namespace Emby.Drawing
|
|||||||
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageDimensions newSize = ImageHelper.GetNewImageSize(options, null);
|
|
||||||
int quality = options.Quality;
|
int quality = options.Quality;
|
||||||
|
|
||||||
ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency);
|
ImageFormat outputFormat = GetOutputFormat(options.SupportedOutputFormats, requiresTransparency);
|
||||||
string cacheFilePath = GetCacheFilePath(originalImagePath, newSize, quality, dateModified, outputFormat, options.AddPlayedIndicator, options.PercentPlayed, options.UnplayedCount, options.Blur, options.BackgroundColor, options.ForegroundLayer);
|
string cacheFilePath = GetCacheFilePath(
|
||||||
|
originalImagePath,
|
||||||
|
options.Width,
|
||||||
|
options.Height,
|
||||||
|
options.MaxWidth,
|
||||||
|
options.MaxHeight,
|
||||||
|
options.FillWidth,
|
||||||
|
options.FillHeight,
|
||||||
|
quality,
|
||||||
|
dateModified,
|
||||||
|
outputFormat,
|
||||||
|
options.AddPlayedIndicator,
|
||||||
|
options.PercentPlayed,
|
||||||
|
options.UnplayedCount,
|
||||||
|
options.Blur,
|
||||||
|
options.BackgroundColor,
|
||||||
|
options.ForegroundLayer);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -246,48 +262,111 @@ namespace Emby.Drawing
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cache file path based on a set of parameters.
|
/// Gets the cache file path based on a set of parameters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string GetCacheFilePath(string originalPath, ImageDimensions outputSize, int quality, DateTime dateModified, ImageFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, int? blur, string backgroundColor, string foregroundLayer)
|
private string GetCacheFilePath(
|
||||||
|
string originalPath,
|
||||||
|
int? width,
|
||||||
|
int? height,
|
||||||
|
int? maxWidth,
|
||||||
|
int? maxHeight,
|
||||||
|
int? fillWidth,
|
||||||
|
int? fillHeight,
|
||||||
|
int quality,
|
||||||
|
DateTime dateModified,
|
||||||
|
ImageFormat format,
|
||||||
|
bool addPlayedIndicator,
|
||||||
|
double percentPlayed,
|
||||||
|
int? unwatchedCount,
|
||||||
|
int? blur,
|
||||||
|
string backgroundColor,
|
||||||
|
string foregroundLayer)
|
||||||
{
|
{
|
||||||
var filename = originalPath
|
var filename = new StringBuilder(256);
|
||||||
+ "width=" + outputSize.Width
|
filename.Append(originalPath);
|
||||||
+ "height=" + outputSize.Height
|
|
||||||
+ "quality=" + quality
|
filename.Append(",quality=");
|
||||||
+ "datemodified=" + dateModified.Ticks
|
filename.Append(quality);
|
||||||
+ "f=" + format;
|
|
||||||
|
filename.Append(",datemodified=");
|
||||||
|
filename.Append(dateModified.Ticks);
|
||||||
|
|
||||||
|
filename.Append(",f=");
|
||||||
|
filename.Append(format);
|
||||||
|
|
||||||
|
if (width.HasValue)
|
||||||
|
{
|
||||||
|
filename.Append(",width=");
|
||||||
|
filename.Append(width.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (height.HasValue)
|
||||||
|
{
|
||||||
|
filename.Append(",height=");
|
||||||
|
filename.Append(height.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxWidth.HasValue)
|
||||||
|
{
|
||||||
|
filename.Append(",maxwidth=");
|
||||||
|
filename.Append(maxWidth.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxHeight.HasValue)
|
||||||
|
{
|
||||||
|
filename.Append(",maxheight=");
|
||||||
|
filename.Append(maxHeight.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fillWidth.HasValue)
|
||||||
|
{
|
||||||
|
filename.Append(",fillwidth=");
|
||||||
|
filename.Append(fillWidth.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fillHeight.HasValue)
|
||||||
|
{
|
||||||
|
filename.Append(",fillheight=");
|
||||||
|
filename.Append(fillHeight.Value);
|
||||||
|
}
|
||||||
|
|
||||||
if (addPlayedIndicator)
|
if (addPlayedIndicator)
|
||||||
{
|
{
|
||||||
filename += "pl=true";
|
filename.Append(",pl=true");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (percentPlayed > 0)
|
if (percentPlayed > 0)
|
||||||
{
|
{
|
||||||
filename += "p=" + percentPlayed;
|
filename.Append(",p=");
|
||||||
|
filename.Append(percentPlayed);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unwatchedCount.HasValue)
|
if (unwatchedCount.HasValue)
|
||||||
{
|
{
|
||||||
filename += "p=" + unwatchedCount.Value;
|
filename.Append(",p=");
|
||||||
|
filename.Append(unwatchedCount.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blur.HasValue)
|
if (blur.HasValue)
|
||||||
{
|
{
|
||||||
filename += "blur=" + blur.Value;
|
filename.Append(",blur=");
|
||||||
|
filename.Append(blur.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(backgroundColor))
|
if (!string.IsNullOrEmpty(backgroundColor))
|
||||||
{
|
{
|
||||||
filename += "b=" + backgroundColor;
|
filename.Append(",b=");
|
||||||
|
filename.Append(backgroundColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(foregroundLayer))
|
if (!string.IsNullOrEmpty(foregroundLayer))
|
||||||
{
|
{
|
||||||
filename += "fl=" + foregroundLayer;
|
filename.Append(",fl=");
|
||||||
|
filename.Append(foregroundLayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
filename += "v=" + Version;
|
filename.Append(",v=");
|
||||||
|
filename.Append(Version);
|
||||||
|
|
||||||
return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLowerInvariant());
|
return GetCachePath(ResizedImageCachePath, filename.ToString(), "." + format.ToString().ToLowerInvariant());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Naming</PackageId>
|
<PackageId>Jellyfin.Naming</PackageId>
|
||||||
<VersionPrefix>10.7.1</VersionPrefix>
|
<VersionPrefix>10.7.2</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -5415,7 +5415,6 @@ AND Type = @InternalPersonType)");
|
|||||||
ItemIds = query.ItemIds,
|
ItemIds = query.ItemIds,
|
||||||
TopParentIds = query.TopParentIds,
|
TopParentIds = query.TopParentIds,
|
||||||
ParentId = query.ParentId,
|
ParentId = query.ParentId,
|
||||||
IsPlayed = query.IsPlayed,
|
|
||||||
IsAiring = query.IsAiring,
|
IsAiring = query.IsAiring,
|
||||||
IsMovie = query.IsMovie,
|
IsMovie = query.IsMovie,
|
||||||
IsSports = query.IsSports,
|
IsSports = query.IsSports,
|
||||||
@@ -5441,6 +5440,7 @@ AND Type = @InternalPersonType)");
|
|||||||
|
|
||||||
var outerQuery = new InternalItemsQuery(query.User)
|
var outerQuery = new InternalItemsQuery(query.User)
|
||||||
{
|
{
|
||||||
|
IsPlayed = query.IsPlayed,
|
||||||
IsFavorite = query.IsFavorite,
|
IsFavorite = query.IsFavorite,
|
||||||
IsFavoriteOrLiked = query.IsFavoriteOrLiked,
|
IsFavoriteOrLiked = query.IsFavoriteOrLiked,
|
||||||
IsLiked = query.IsLiked,
|
IsLiked = query.IsLiked,
|
||||||
|
|||||||
@@ -14,15 +14,18 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
public class WebSocketManager : IWebSocketManager
|
public class WebSocketManager : IWebSocketManager
|
||||||
{
|
{
|
||||||
private readonly IWebSocketListener[] _webSocketListeners;
|
private readonly IWebSocketListener[] _webSocketListeners;
|
||||||
|
private readonly IAuthService _authService;
|
||||||
private readonly ILogger<WebSocketManager> _logger;
|
private readonly ILogger<WebSocketManager> _logger;
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
|
|
||||||
public WebSocketManager(
|
public WebSocketManager(
|
||||||
|
IAuthService authService,
|
||||||
IEnumerable<IWebSocketListener> webSocketListeners,
|
IEnumerable<IWebSocketListener> webSocketListeners,
|
||||||
ILogger<WebSocketManager> logger,
|
ILogger<WebSocketManager> logger,
|
||||||
ILoggerFactory loggerFactory)
|
ILoggerFactory loggerFactory)
|
||||||
{
|
{
|
||||||
_webSocketListeners = webSocketListeners.ToArray();
|
_webSocketListeners = webSocketListeners.ToArray();
|
||||||
|
_authService = authService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
}
|
}
|
||||||
@@ -30,6 +33,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task WebSocketRequestHandler(HttpContext context)
|
public async Task WebSocketRequestHandler(HttpContext context)
|
||||||
{
|
{
|
||||||
|
_ = _authService.Authenticate(context.Request);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress);
|
_logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress);
|
||||||
|
|||||||
@@ -249,9 +249,18 @@ namespace Emby.Server.Implementations.IO
|
|||||||
// Issue #2354 get the size of files behind symbolic links
|
// Issue #2354 get the size of files behind symbolic links
|
||||||
if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
||||||
{
|
{
|
||||||
using (Stream thisFileStream = File.OpenRead(fileInfo.FullName))
|
try
|
||||||
{
|
{
|
||||||
result.Length = thisFileStream.Length;
|
using (Stream thisFileStream = File.OpenRead(fileInfo.FullName))
|
||||||
|
{
|
||||||
|
result.Length = thisFileStream.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException ex)
|
||||||
|
{
|
||||||
|
// Dangling symlinks cannot be detected before opening the file unfortunately...
|
||||||
|
Logger.LogError(ex, "Reading the file size of the symlink at {Path} failed. Marking the file as not existing.", fileInfo.FullName);
|
||||||
|
result.Exists = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1914,12 +1914,17 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
catch (ArgumentException)
|
catch (ArgumentException)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Cannot get image index for {0}", img.Path);
|
_logger.LogWarning("Cannot get image index for {ImagePath}", img.Path);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException)
|
catch (Exception ex) when (ex is InvalidOperationException || ex is IOException)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Cannot fetch image from {0}", img.Path);
|
_logger.LogWarning(ex, "Cannot fetch image from {ImagePath}", img.Path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Cannot fetch image from {ImagePath}. Http status code: {HttpStatus}", img.Path, ex.StatusCode);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1932,7 +1937,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Cannot get image dimensions for {0}", image.Path);
|
_logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path);
|
||||||
image.Width = 0;
|
image.Width = 0;
|
||||||
image.Height = 0;
|
image.Height = 0;
|
||||||
continue;
|
continue;
|
||||||
@@ -1944,7 +1949,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Cannot compute blurhash for {0}", image.Path);
|
_logger.LogError(ex, "Cannot compute blurhash for {ImagePath}", image.Path);
|
||||||
image.BlurHash = string.Empty;
|
image.BlurHash = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1954,7 +1959,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Cannot update DateModified for {0}", image.Path);
|
_logger.LogError(ex, "Cannot update DateModified for {ImagePath}", image.Path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
|||||||
{
|
{
|
||||||
Path = args.Path,
|
Path = args.Path,
|
||||||
Name = Path.GetFileNameWithoutExtension(args.Path),
|
Name = Path.GetFileNameWithoutExtension(args.Path),
|
||||||
IsInMixedFolder = true
|
IsInMixedFolder = true,
|
||||||
|
PlaylistMediaType = MediaType.Audio
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,11 +82,6 @@ namespace Emby.Server.Implementations.MediaEncoder
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (video.VideoType == VideoType.Dvd)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (video.IsShortcut)
|
if (video.IsShortcut)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
@@ -368,7 +369,7 @@ namespace Emby.Server.Implementations.Plugins
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path)
|
public async Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path, PluginStatus status)
|
||||||
{
|
{
|
||||||
if (packageInfo == null)
|
if (packageInfo == null)
|
||||||
{
|
{
|
||||||
@@ -411,9 +412,9 @@ namespace Emby.Server.Implementations.Plugins
|
|||||||
Overview = packageInfo.Overview,
|
Overview = packageInfo.Overview,
|
||||||
Owner = packageInfo.Owner,
|
Owner = packageInfo.Owner,
|
||||||
TargetAbi = versionInfo.TargetAbi ?? string.Empty,
|
TargetAbi = versionInfo.TargetAbi ?? string.Empty,
|
||||||
Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp),
|
Timestamp = string.IsNullOrEmpty(versionInfo.Timestamp) ? DateTime.MinValue : DateTime.Parse(versionInfo.Timestamp, CultureInfo.InvariantCulture),
|
||||||
Version = versionInfo.Version,
|
Version = versionInfo.Version,
|
||||||
Status = PluginStatus.Active,
|
Status = status == PluginStatus.Disabled ? PluginStatus.Disabled : PluginStatus.Active, // Keep disabled state.
|
||||||
AutoUpdate = true,
|
AutoUpdate = true,
|
||||||
ImagePath = imagePath
|
ImagePath = imagePath
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -131,11 +131,11 @@ namespace Emby.Server.Implementations.Sorting
|
|||||||
return GetSpecialCompareValue(x).CompareTo(GetSpecialCompareValue(y));
|
return GetSpecialCompareValue(x).CompareTo(GetSpecialCompareValue(y));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetSpecialCompareValue(Episode item)
|
private static long GetSpecialCompareValue(Episode item)
|
||||||
{
|
{
|
||||||
// First sort by season number
|
// First sort by season number
|
||||||
// Since there are three sort orders, pad with 9 digits (3 for each, figure 1000 episode buffer should be enough)
|
// Since there are three sort orders, pad with 9 digits (3 for each, figure 1000 episode buffer should be enough)
|
||||||
var val = (item.AirsAfterSeasonNumber ?? item.AirsBeforeSeasonNumber ?? 0) * 1000000000;
|
var val = (item.AirsAfterSeasonNumber ?? item.AirsBeforeSeasonNumber ?? 0) * 1000000000L;
|
||||||
|
|
||||||
// Second sort order is if it airs after the season
|
// Second sort order is if it airs after the season
|
||||||
if (item.AirsAfterSeasonNumber.HasValue)
|
if (item.AirsAfterSeasonNumber.HasValue)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
@@ -11,7 +10,6 @@ using MediaBrowser.Controller.Dto;
|
|||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.TV;
|
using MediaBrowser.Controller.TV;
|
||||||
using MediaBrowser.Model.Entities;
|
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||||
using Series = MediaBrowser.Controller.Entities.TV.Series;
|
using Series = MediaBrowser.Controller.Entities.TV.Series;
|
||||||
@@ -23,12 +21,14 @@ namespace Emby.Server.Implementations.TV
|
|||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IUserDataManager _userDataManager;
|
private readonly IUserDataManager _userDataManager;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private readonly IServerConfigurationManager _configurationManager;
|
||||||
|
|
||||||
public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager)
|
public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_userDataManager = userDataManager;
|
_userDataManager = userDataManager;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
_configurationManager = configurationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<BaseItem> GetNextUp(NextUpQuery request, DtoOptions dtoOptions)
|
public QueryResult<BaseItem> GetNextUp(NextUpQuery request, DtoOptions dtoOptions)
|
||||||
@@ -200,13 +200,10 @@ namespace Emby.Server.Implementations.TV
|
|||||||
ParentIndexNumberNotEquals = 0,
|
ParentIndexNumberNotEquals = 0,
|
||||||
DtoOptions = new DtoOptions
|
DtoOptions = new DtoOptions
|
||||||
{
|
{
|
||||||
Fields = new ItemFields[]
|
Fields = new[] { ItemFields.SortName },
|
||||||
{
|
|
||||||
ItemFields.SortName
|
|
||||||
},
|
|
||||||
EnableImages = false
|
EnableImages = false
|
||||||
}
|
}
|
||||||
}).FirstOrDefault();
|
}).Cast<Episode>().FirstOrDefault();
|
||||||
|
|
||||||
Func<Episode> getEpisode = () =>
|
Func<Episode> getEpisode = () =>
|
||||||
{
|
{
|
||||||
@@ -224,6 +221,43 @@ namespace Emby.Server.Implementations.TV
|
|||||||
DtoOptions = dtoOptions
|
DtoOptions = dtoOptions
|
||||||
}).Cast<Episode>().FirstOrDefault();
|
}).Cast<Episode>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (_configurationManager.Configuration.DisplaySpecialsWithinSeasons)
|
||||||
|
{
|
||||||
|
var consideredEpisodes = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||||
|
{
|
||||||
|
AncestorWithPresentationUniqueKey = null,
|
||||||
|
SeriesPresentationUniqueKey = seriesKey,
|
||||||
|
ParentIndexNumber = 0,
|
||||||
|
IncludeItemTypes = new[] { nameof(Episode) },
|
||||||
|
IsPlayed = false,
|
||||||
|
IsVirtualItem = false,
|
||||||
|
DtoOptions = dtoOptions
|
||||||
|
})
|
||||||
|
.Cast<Episode>()
|
||||||
|
.Where(episode => episode.AirsBeforeSeasonNumber != null || episode.AirsAfterSeasonNumber != null)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (lastWatchedEpisode != null)
|
||||||
|
{
|
||||||
|
// Last watched episode is added, because there could be specials that aired before the last watched episode
|
||||||
|
consideredEpisodes.Add(lastWatchedEpisode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextEpisode != null)
|
||||||
|
{
|
||||||
|
consideredEpisodes.Add(nextEpisode);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sortedConsideredEpisodes = _libraryManager.Sort(consideredEpisodes, user, new[] { (ItemSortBy.AiredEpisodeOrder, SortOrder.Ascending) })
|
||||||
|
.Cast<Episode>();
|
||||||
|
if (lastWatchedEpisode != null)
|
||||||
|
{
|
||||||
|
sortedConsideredEpisodes = sortedConsideredEpisodes.SkipWhile(episode => episode.Id != lastWatchedEpisode.Id).Skip(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextEpisode = sortedConsideredEpisodes.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
if (nextEpisode != null)
|
if (nextEpisode != null)
|
||||||
{
|
{
|
||||||
var userData = _userDataManager.GetUserData(user, nextEpisode);
|
var userData = _userDataManager.GetUserData(user, nextEpisode);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ using MediaBrowser.Controller.Configuration;
|
|||||||
using MediaBrowser.Controller.Events;
|
using MediaBrowser.Controller.Events;
|
||||||
using MediaBrowser.Controller.Events.Updates;
|
using MediaBrowser.Controller.Events.Updates;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Plugins;
|
||||||
using MediaBrowser.Model.Updates;
|
using MediaBrowser.Model.Updates;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -194,7 +195,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
var plugin = _pluginManager.GetPlugin(packageGuid, version.VersionNumber);
|
var plugin = _pluginManager.GetPlugin(packageGuid, version.VersionNumber);
|
||||||
if (plugin != null)
|
if (plugin != null)
|
||||||
{
|
{
|
||||||
await _pluginManager.GenerateManifest(package, version.VersionNumber, plugin.Path);
|
await _pluginManager.GenerateManifest(package, version.VersionNumber, plugin.Path, plugin.Manifest.Status).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove versions with a target ABI greater then the current application version.
|
// Remove versions with a target ABI greater then the current application version.
|
||||||
@@ -500,7 +501,8 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
var plugins = _pluginManager.Plugins;
|
var plugins = _pluginManager.Plugins;
|
||||||
foreach (var plugin in plugins)
|
foreach (var plugin in plugins)
|
||||||
{
|
{
|
||||||
if (plugin.Manifest?.AutoUpdate == false)
|
// Don't auto update when plugin marked not to, or when it's disabled.
|
||||||
|
if (plugin.Manifest?.AutoUpdate == false || plugin.Manifest?.Status == PluginStatus.Disabled)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -515,7 +517,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PerformPackageInstallation(InstallationInfo package, CancellationToken cancellationToken)
|
private async Task PerformPackageInstallation(InstallationInfo package, PluginStatus status, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(package.SourceUrl);
|
var extension = Path.GetExtension(package.SourceUrl);
|
||||||
if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase))
|
||||||
@@ -567,7 +569,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
|
|
||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
_zipClient.ExtractAllFromZip(stream, targetDir, true);
|
_zipClient.ExtractAllFromZip(stream, targetDir, true);
|
||||||
await _pluginManager.GenerateManifest(package.PackageInfo, package.Version, targetDir);
|
await _pluginManager.GenerateManifest(package.PackageInfo, package.Version, targetDir, status).ConfigureAwait(false);
|
||||||
_pluginManager.ImportPluginFrom(targetDir);
|
_pluginManager.ImportPluginFrom(targetDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,7 +578,7 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
LocalPlugin? plugin = _pluginManager.Plugins.FirstOrDefault(p => p.Id.Equals(package.Id) && p.Version.Equals(package.Version))
|
LocalPlugin? plugin = _pluginManager.Plugins.FirstOrDefault(p => p.Id.Equals(package.Id) && p.Version.Equals(package.Version))
|
||||||
?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version));
|
?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version));
|
||||||
|
|
||||||
await PerformPackageInstallation(package, cancellationToken).ConfigureAwait(false);
|
await PerformPackageInstallation(package, plugin?.Manifest.Status ?? PluginStatus.Active, cancellationToken).ConfigureAwait(false);
|
||||||
_logger.LogInformation(plugin == null ? "New plugin installed: {PluginName} {PluginVersion}" : "Plugin updated: {PluginName} {PluginVersion}", package.Name, package.Version);
|
_logger.LogInformation(plugin == null ? "New plugin installed: {PluginName} {PluginVersion}" : "Plugin updated: {PluginName} {PluginVersion}", package.Name, package.Version);
|
||||||
|
|
||||||
return plugin != null;
|
return plugin != null;
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Container = container,
|
Container = container,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -168,7 +168,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -177,13 +177,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
@@ -309,7 +309,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Container = container,
|
Container = container,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -333,7 +333,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -342,13 +342,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
var streamingRequest = new HlsVideoRequestDto
|
var streamingRequest = new HlsVideoRequestDto
|
||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -250,7 +250,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -259,13 +259,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
@@ -393,7 +393,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
var streamingRequest = new HlsAudioRequestDto
|
var streamingRequest = new HlsAudioRequestDto
|
||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -417,7 +417,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -426,13 +426,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
@@ -556,7 +556,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
var streamingRequest = new VideoRequestDto
|
var streamingRequest = new VideoRequestDto
|
||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -580,7 +580,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -589,13 +589,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
@@ -721,7 +721,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
var streamingRequest = new StreamingRequestDto
|
var streamingRequest = new StreamingRequestDto
|
||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -745,7 +745,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -754,13 +754,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
@@ -891,7 +891,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Container = container,
|
Container = container,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -915,7 +915,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -924,13 +924,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
@@ -1063,7 +1063,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Container = container,
|
Container = container,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -1087,7 +1087,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -1096,13 +1096,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
var transcodePath = _serverConfigurationManager.GetTranscodePath();
|
var transcodePath = _serverConfigurationManager.GetTranscodePath();
|
||||||
file = Path.GetFullPath(Path.Combine(transcodePath, file));
|
file = Path.GetFullPath(Path.Combine(transcodePath, file));
|
||||||
var fileDir = Path.GetDirectoryName(file);
|
var fileDir = Path.GetDirectoryName(file);
|
||||||
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath))
|
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid segment.");
|
return BadRequest("Invalid segment.");
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
var transcodePath = _serverConfigurationManager.GetTranscodePath();
|
var transcodePath = _serverConfigurationManager.GetTranscodePath();
|
||||||
file = Path.GetFullPath(Path.Combine(transcodePath, file));
|
file = Path.GetFullPath(Path.Combine(transcodePath, file));
|
||||||
var fileDir = Path.GetDirectoryName(file);
|
var fileDir = Path.GetDirectoryName(file);
|
||||||
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath) || Path.GetExtension(file) != ".m3u8")
|
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodePath, StringComparison.Ordinal) || Path.GetExtension(file) != ".m3u8")
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid segment.");
|
return BadRequest("Invalid segment.");
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
file = Path.GetFullPath(Path.Combine(transcodeFolderPath, file));
|
file = Path.GetFullPath(Path.Combine(transcodeFolderPath, file));
|
||||||
var fileDir = Path.GetDirectoryName(file);
|
var fileDir = Path.GetDirectoryName(file);
|
||||||
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodeFolderPath))
|
if (string.IsNullOrEmpty(fileDir) || !fileDir.StartsWith(transcodeFolderPath, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid segment.");
|
return BadRequest("Invalid segment.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!path.StartsWith(_applicationPaths.GeneralPath))
|
if (!path.StartsWith(_applicationPaths.GeneralPath, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid image path.");
|
return BadRequest("Invalid image path.");
|
||||||
}
|
}
|
||||||
@@ -177,7 +177,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
|
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
|
||||||
{
|
{
|
||||||
if (!path.StartsWith(basePath))
|
if (!path.StartsWith(basePath, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid image path.");
|
return BadRequest("Invalid image path.");
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
|
if (!string.IsNullOrEmpty(path) && System.IO.File.Exists(path))
|
||||||
{
|
{
|
||||||
if (!path.StartsWith(basePath))
|
if (!path.StartsWith(basePath, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid image path.");
|
return BadRequest("Invalid image path.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -480,6 +480,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
|
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param>
|
/// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param>
|
||||||
@@ -509,6 +511,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] string? tag,
|
[FromQuery] string? tag,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] ImageFormat? format,
|
[FromQuery] ImageFormat? format,
|
||||||
@@ -539,6 +543,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -560,6 +566,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
|
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param>
|
/// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param>
|
||||||
@@ -589,6 +597,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] string? tag,
|
[FromQuery] string? tag,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] ImageFormat? format,
|
[FromQuery] ImageFormat? format,
|
||||||
@@ -618,6 +628,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -638,6 +650,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
|
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param>
|
/// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param>
|
||||||
@@ -667,6 +681,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromRoute, Required] string tag,
|
[FromRoute, Required] string tag,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromRoute, Required] ImageFormat format,
|
[FromRoute, Required] ImageFormat format,
|
||||||
@@ -697,6 +713,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -721,6 +739,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -750,6 +770,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -776,6 +798,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -800,6 +824,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -829,6 +855,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -855,6 +883,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -880,6 +910,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -909,6 +941,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -934,6 +968,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -958,6 +994,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -987,6 +1025,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -1013,6 +1053,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -1038,6 +1080,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -1067,6 +1111,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -1092,6 +1138,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -1116,6 +1164,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -1145,6 +1195,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -1171,6 +1223,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -1196,6 +1250,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -1225,6 +1281,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -1250,6 +1308,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -1274,6 +1334,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -1303,6 +1365,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -1329,6 +1393,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -1354,6 +1420,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -1383,6 +1451,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -1408,6 +1478,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -1432,6 +1504,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -1461,6 +1535,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -1504,6 +1580,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -1530,6 +1608,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="width">The fixed image width to return.</param>
|
/// <param name="width">The fixed image width to return.</param>
|
||||||
/// <param name="height">The fixed image height to return.</param>
|
/// <param name="height">The fixed image height to return.</param>
|
||||||
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
|
||||||
|
/// <param name="fillWidth">Width of box to fill.</param>
|
||||||
|
/// <param name="fillHeight">Height of box to fill.</param>
|
||||||
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
|
||||||
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
|
||||||
/// <param name="blur">Optional. Blur image.</param>
|
/// <param name="blur">Optional. Blur image.</param>
|
||||||
@@ -1559,6 +1639,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] int? width,
|
[FromQuery] int? width,
|
||||||
[FromQuery] int? height,
|
[FromQuery] int? height,
|
||||||
[FromQuery] int? quality,
|
[FromQuery] int? quality,
|
||||||
|
[FromQuery] int? fillWidth,
|
||||||
|
[FromQuery] int? fillHeight,
|
||||||
[FromQuery] bool? cropWhitespace,
|
[FromQuery] bool? cropWhitespace,
|
||||||
[FromQuery] bool? addPlayedIndicator,
|
[FromQuery] bool? addPlayedIndicator,
|
||||||
[FromQuery] int? blur,
|
[FromQuery] int? blur,
|
||||||
@@ -1601,6 +1683,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
quality,
|
quality,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
cropWhitespace,
|
cropWhitespace,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
blur,
|
blur,
|
||||||
@@ -1685,6 +1769,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
int? width,
|
int? width,
|
||||||
int? height,
|
int? height,
|
||||||
int? quality,
|
int? quality,
|
||||||
|
int? fillWidth,
|
||||||
|
int? fillHeight,
|
||||||
bool? cropWhitespace,
|
bool? cropWhitespace,
|
||||||
bool? addPlayedIndicator,
|
bool? addPlayedIndicator,
|
||||||
int? blur,
|
int? blur,
|
||||||
@@ -1748,11 +1834,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
item,
|
item,
|
||||||
itemId,
|
itemId,
|
||||||
imageIndex,
|
imageIndex,
|
||||||
height,
|
|
||||||
maxHeight,
|
|
||||||
maxWidth,
|
|
||||||
quality,
|
|
||||||
width,
|
width,
|
||||||
|
height,
|
||||||
|
maxWidth,
|
||||||
|
maxHeight,
|
||||||
|
fillWidth,
|
||||||
|
fillHeight,
|
||||||
|
quality,
|
||||||
addPlayedIndicator,
|
addPlayedIndicator,
|
||||||
percentPlayed,
|
percentPlayed,
|
||||||
unplayedCount,
|
unplayedCount,
|
||||||
@@ -1847,11 +1935,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
BaseItem? item,
|
BaseItem? item,
|
||||||
Guid itemId,
|
Guid itemId,
|
||||||
int? index,
|
int? index,
|
||||||
int? height,
|
|
||||||
int? maxHeight,
|
|
||||||
int? maxWidth,
|
|
||||||
int? quality,
|
|
||||||
int? width,
|
int? width,
|
||||||
|
int? height,
|
||||||
|
int? maxWidth,
|
||||||
|
int? maxHeight,
|
||||||
|
int? fillWidth,
|
||||||
|
int? fillHeight,
|
||||||
|
int? quality,
|
||||||
bool? addPlayedIndicator,
|
bool? addPlayedIndicator,
|
||||||
double? percentPlayed,
|
double? percentPlayed,
|
||||||
int? unplayedCount,
|
int? unplayedCount,
|
||||||
@@ -1880,6 +1970,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
ItemId = itemId,
|
ItemId = itemId,
|
||||||
MaxHeight = maxHeight,
|
MaxHeight = maxHeight,
|
||||||
MaxWidth = maxWidth,
|
MaxWidth = maxWidth,
|
||||||
|
FillHeight = fillHeight,
|
||||||
|
FillWidth = fillWidth,
|
||||||
Quality = quality ?? 100,
|
Quality = quality ?? 100,
|
||||||
Width = width,
|
Width = width,
|
||||||
AddPlayedIndicator = addPlayedIndicator ?? false,
|
AddPlayedIndicator = addPlayedIndicator ?? false,
|
||||||
|
|||||||
@@ -247,8 +247,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
folder = _libraryManager.GetUserRootFolder();
|
folder = _libraryManager.GetUserRootFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (folder is IHasCollectionType hasCollectionType
|
string? collectionType = null;
|
||||||
&& string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
|
if (folder is IHasCollectionType hasCollectionType)
|
||||||
|
{
|
||||||
|
collectionType = hasCollectionType.CollectionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(collectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
recursive = true;
|
recursive = true;
|
||||||
includeItemTypes = new[] { "Playlist" };
|
includeItemTypes = new[] { "Playlist" };
|
||||||
@@ -271,10 +276,11 @@ namespace Jellyfin.Api.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(item is UserRootFolder)
|
if (item is not UserRootFolder
|
||||||
&& !isInEnabledFolder
|
&& !isInEnabledFolder
|
||||||
&& !user.HasPermission(PermissionKind.EnableAllFolders)
|
&& !user.HasPermission(PermissionKind.EnableAllFolders)
|
||||||
&& !user.HasPermission(PermissionKind.EnableAllChannels))
|
&& !user.HasPermission(PermissionKind.EnableAllChannels)
|
||||||
|
&& !string.Equals(collectionType, CollectionType.Folders, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Username, item.Name);
|
_logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Username, item.Name);
|
||||||
return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}.");
|
return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}.");
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path));
|
return PhysicalFile(item.Path, MimeTypes.GetMimeType(item.Path), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -667,7 +667,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO determine non-ASCII validity.
|
// TODO determine non-ASCII validity.
|
||||||
return PhysicalFile(path, MimeTypes.GetMimeType(path), filename);
|
return PhysicalFile(path, MimeTypes.GetMimeType(path), filename, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -153,6 +153,10 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="playCommand">The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.</param>
|
/// <param name="playCommand">The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.</param>
|
||||||
/// <param name="itemIds">The ids of the items to play, comma delimited.</param>
|
/// <param name="itemIds">The ids of the items to play, comma delimited.</param>
|
||||||
/// <param name="startPositionTicks">The starting position of the first item.</param>
|
/// <param name="startPositionTicks">The starting position of the first item.</param>
|
||||||
|
/// <param name="mediaSourceId">Optional. The media source id.</param>
|
||||||
|
/// <param name="audioStreamIndex">Optional. The index of the audio stream to play.</param>
|
||||||
|
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to play.</param>
|
||||||
|
/// <param name="startIndex">Optional. The start index.</param>
|
||||||
/// <response code="204">Instruction sent to session.</response>
|
/// <response code="204">Instruction sent to session.</response>
|
||||||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||||
[HttpPost("Sessions/{sessionId}/Playing")]
|
[HttpPost("Sessions/{sessionId}/Playing")]
|
||||||
@@ -162,13 +166,21 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromRoute, Required] string sessionId,
|
[FromRoute, Required] string sessionId,
|
||||||
[FromQuery, Required] PlayCommand playCommand,
|
[FromQuery, Required] PlayCommand playCommand,
|
||||||
[FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] itemIds,
|
[FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] itemIds,
|
||||||
[FromQuery] long? startPositionTicks)
|
[FromQuery] long? startPositionTicks,
|
||||||
|
[FromQuery] string? mediaSourceId,
|
||||||
|
[FromQuery] int? audioStreamIndex,
|
||||||
|
[FromQuery] int? subtitleStreamIndex,
|
||||||
|
[FromQuery] int? startIndex)
|
||||||
{
|
{
|
||||||
var playRequest = new PlayRequest
|
var playRequest = new PlayRequest
|
||||||
{
|
{
|
||||||
ItemIds = itemIds,
|
ItemIds = itemIds,
|
||||||
StartPositionTicks = startPositionTicks,
|
StartPositionTicks = startPositionTicks,
|
||||||
PlayCommand = playCommand
|
PlayCommand = playCommand,
|
||||||
|
MediaSourceId = mediaSourceId,
|
||||||
|
AudioStreamIndex = audioStreamIndex,
|
||||||
|
SubtitleStreamIndex = subtitleStreamIndex,
|
||||||
|
StartIndex = startIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
_sessionManager.SendPlayCommand(
|
_sessionManager.SendPlayCommand(
|
||||||
@@ -301,9 +313,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// Issues a command to a client to display a message to the user.
|
/// Issues a command to a client to display a message to the user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sessionId">The session id.</param>
|
/// <param name="sessionId">The session id.</param>
|
||||||
/// <param name="text">The message test.</param>
|
/// <param name="command">The <see cref="MessageCommand" /> object containing Header, Message Text, and TimeoutMs.</param>
|
||||||
/// <param name="header">The message header.</param>
|
|
||||||
/// <param name="timeoutMs">The message timeout. If omitted the user will have to confirm viewing the message.</param>
|
|
||||||
/// <response code="204">Message sent.</response>
|
/// <response code="204">Message sent.</response>
|
||||||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||||
[HttpPost("Sessions/{sessionId}/Message")]
|
[HttpPost("Sessions/{sessionId}/Message")]
|
||||||
@@ -311,16 +321,12 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
public ActionResult SendMessageCommand(
|
public ActionResult SendMessageCommand(
|
||||||
[FromRoute, Required] string sessionId,
|
[FromRoute, Required] string sessionId,
|
||||||
[FromQuery, Required] string text,
|
[FromBody, Required] MessageCommand command)
|
||||||
[FromQuery] string? header,
|
|
||||||
[FromQuery] long? timeoutMs)
|
|
||||||
{
|
{
|
||||||
var command = new MessageCommand
|
if (string.IsNullOrWhiteSpace(command.Header))
|
||||||
{
|
{
|
||||||
Header = string.IsNullOrEmpty(header) ? "Message from Server" : header,
|
command.Header = "Message from Server";
|
||||||
TimeoutMs = timeoutMs,
|
}
|
||||||
Text = text
|
|
||||||
};
|
|
||||||
|
|
||||||
_sessionManager.SendMessageCommand(RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, sessionId, command, CancellationToken.None);
|
_sessionManager.SendMessageCommand(RequestHelpers.GetSession(_sessionManager, _authContext, Request).Id, sessionId, command, CancellationToken.None);
|
||||||
|
|
||||||
|
|||||||
@@ -182,6 +182,10 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets subtitles in a specified format.
|
/// Gets subtitles in a specified format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="routeItemId">The (route) item id.</param>
|
||||||
|
/// <param name="routeMediaSourceId">The (route) media source id.</param>
|
||||||
|
/// <param name="routeIndex">The (route) subtitle stream index.</param>
|
||||||
|
/// <param name="routeFormat">The (route) format of the returned subtitle.</param>
|
||||||
/// <param name="itemId">The item id.</param>
|
/// <param name="itemId">The item id.</param>
|
||||||
/// <param name="mediaSourceId">The media source id.</param>
|
/// <param name="mediaSourceId">The media source id.</param>
|
||||||
/// <param name="index">The subtitle stream index.</param>
|
/// <param name="index">The subtitle stream index.</param>
|
||||||
@@ -189,22 +193,32 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <param name="endPositionTicks">Optional. The end position of the subtitle in ticks.</param>
|
/// <param name="endPositionTicks">Optional. The end position of the subtitle in ticks.</param>
|
||||||
/// <param name="copyTimestamps">Optional. Whether to copy the timestamps.</param>
|
/// <param name="copyTimestamps">Optional. Whether to copy the timestamps.</param>
|
||||||
/// <param name="addVttTimeMap">Optional. Whether to add a VTT time map.</param>
|
/// <param name="addVttTimeMap">Optional. Whether to add a VTT time map.</param>
|
||||||
/// <param name="startPositionTicks">Optional. The start position of the subtitle in ticks.</param>
|
/// <param name="startPositionTicks">The start position of the subtitle in ticks.</param>
|
||||||
/// <response code="200">File returned.</response>
|
/// <response code="200">File returned.</response>
|
||||||
/// <returns>A <see cref="FileContentResult"/> with the subtitle file.</returns>
|
/// <returns>A <see cref="FileContentResult"/> with the subtitle file.</returns>
|
||||||
[HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/Stream.{format}")]
|
[HttpGet("Videos/{routeItemId}/routeMediaSourceId/Subtitles/{routeIndex}/Stream.{routeFormat}")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesFile("text/*")]
|
[ProducesFile("text/*")]
|
||||||
public async Task<ActionResult> GetSubtitle(
|
public async Task<ActionResult> GetSubtitle(
|
||||||
[FromRoute, Required] Guid itemId,
|
[FromRoute, Required] Guid routeItemId,
|
||||||
[FromRoute, Required] string mediaSourceId,
|
[FromRoute, Required] string routeMediaSourceId,
|
||||||
[FromRoute, Required] int index,
|
[FromRoute, Required] int routeIndex,
|
||||||
[FromRoute, Required] string format,
|
[FromRoute, Required] string routeFormat,
|
||||||
|
[FromQuery, ParameterObsolete] Guid? itemId,
|
||||||
|
[FromQuery, ParameterObsolete] string? mediaSourceId,
|
||||||
|
[FromQuery, ParameterObsolete] int? index,
|
||||||
|
[FromQuery, ParameterObsolete] string? format,
|
||||||
[FromQuery] long? endPositionTicks,
|
[FromQuery] long? endPositionTicks,
|
||||||
[FromQuery] bool copyTimestamps = false,
|
[FromQuery] bool copyTimestamps = false,
|
||||||
[FromQuery] bool addVttTimeMap = false,
|
[FromQuery] bool addVttTimeMap = false,
|
||||||
[FromQuery] long startPositionTicks = 0)
|
[FromQuery] long startPositionTicks = 0)
|
||||||
{
|
{
|
||||||
|
// Set parameters to route value if not provided via query.
|
||||||
|
itemId ??= routeItemId;
|
||||||
|
mediaSourceId ??= routeMediaSourceId;
|
||||||
|
index ??= routeIndex;
|
||||||
|
format ??= routeFormat;
|
||||||
|
|
||||||
if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
format = "json";
|
format = "json";
|
||||||
@@ -212,9 +226,9 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(format))
|
if (string.IsNullOrEmpty(format))
|
||||||
{
|
{
|
||||||
var item = (Video)_libraryManager.GetItemById(itemId);
|
var item = (Video)_libraryManager.GetItemById(itemId.Value);
|
||||||
|
|
||||||
var idString = itemId.ToString("N", CultureInfo.InvariantCulture);
|
var idString = itemId.Value.ToString("N", CultureInfo.InvariantCulture);
|
||||||
var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false)
|
var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false)
|
||||||
.First(i => string.Equals(i.Id, mediaSourceId ?? idString, StringComparison.Ordinal));
|
.First(i => string.Equals(i.Id, mediaSourceId ?? idString, StringComparison.Ordinal));
|
||||||
|
|
||||||
@@ -226,7 +240,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
if (string.Equals(format, "vtt", StringComparison.OrdinalIgnoreCase) && addVttTimeMap)
|
if (string.Equals(format, "vtt", StringComparison.OrdinalIgnoreCase) && addVttTimeMap)
|
||||||
{
|
{
|
||||||
await using Stream stream = await EncodeSubtitles(itemId, mediaSourceId, index, format, startPositionTicks, endPositionTicks, copyTimestamps).ConfigureAwait(false);
|
await using Stream stream = await EncodeSubtitles(itemId.Value, mediaSourceId, index.Value, format, startPositionTicks, endPositionTicks, copyTimestamps).ConfigureAwait(false);
|
||||||
using var reader = new StreamReader(stream);
|
using var reader = new StreamReader(stream);
|
||||||
|
|
||||||
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||||
@@ -238,9 +252,9 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|
|
||||||
return File(
|
return File(
|
||||||
await EncodeSubtitles(
|
await EncodeSubtitles(
|
||||||
itemId,
|
itemId.Value,
|
||||||
mediaSourceId,
|
mediaSourceId,
|
||||||
index,
|
index.Value,
|
||||||
format,
|
format,
|
||||||
startPositionTicks,
|
startPositionTicks,
|
||||||
endPositionTicks,
|
endPositionTicks,
|
||||||
@@ -251,30 +265,44 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets subtitles in a specified format.
|
/// Gets subtitles in a specified format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="routeItemId">The (route) item id.</param>
|
||||||
|
/// <param name="routeMediaSourceId">The (route) media source id.</param>
|
||||||
|
/// <param name="routeIndex">The (route) subtitle stream index.</param>
|
||||||
|
/// <param name="routeStartPositionTicks">The (route) start position of the subtitle in ticks.</param>
|
||||||
|
/// <param name="routeFormat">The (route) format of the returned subtitle.</param>
|
||||||
/// <param name="itemId">The item id.</param>
|
/// <param name="itemId">The item id.</param>
|
||||||
/// <param name="mediaSourceId">The media source id.</param>
|
/// <param name="mediaSourceId">The media source id.</param>
|
||||||
/// <param name="index">The subtitle stream index.</param>
|
/// <param name="index">The subtitle stream index.</param>
|
||||||
/// <param name="startPositionTicks">Optional. The start position of the subtitle in ticks.</param>
|
/// <param name="startPositionTicks">The start position of the subtitle in ticks.</param>
|
||||||
/// <param name="format">The format of the returned subtitle.</param>
|
/// <param name="format">The format of the returned subtitle.</param>
|
||||||
/// <param name="endPositionTicks">Optional. The end position of the subtitle in ticks.</param>
|
/// <param name="endPositionTicks">Optional. The end position of the subtitle in ticks.</param>
|
||||||
/// <param name="copyTimestamps">Optional. Whether to copy the timestamps.</param>
|
/// <param name="copyTimestamps">Optional. Whether to copy the timestamps.</param>
|
||||||
/// <param name="addVttTimeMap">Optional. Whether to add a VTT time map.</param>
|
/// <param name="addVttTimeMap">Optional. Whether to add a VTT time map.</param>
|
||||||
/// <response code="200">File returned.</response>
|
/// <response code="200">File returned.</response>
|
||||||
/// <returns>A <see cref="FileContentResult"/> with the subtitle file.</returns>
|
/// <returns>A <see cref="FileContentResult"/> with the subtitle file.</returns>
|
||||||
[HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/{startPositionTicks}/Stream.{format}")]
|
[HttpGet("Videos/{routeItemId}/{routeMediaSourceId}/Subtitles/{routeIndex}/{routeStartPositionTicks}/Stream.{routeFormat}")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesFile("text/*")]
|
[ProducesFile("text/*")]
|
||||||
public Task<ActionResult> GetSubtitleWithTicks(
|
public Task<ActionResult> GetSubtitleWithTicks(
|
||||||
[FromRoute, Required] Guid itemId,
|
[FromRoute, Required] Guid routeItemId,
|
||||||
[FromRoute, Required] string mediaSourceId,
|
[FromRoute, Required] string routeMediaSourceId,
|
||||||
[FromRoute, Required] int index,
|
[FromRoute, Required] int routeIndex,
|
||||||
[FromRoute, Required] long startPositionTicks,
|
[FromRoute, Required] long routeStartPositionTicks,
|
||||||
[FromRoute, Required] string format,
|
[FromRoute, Required] string routeFormat,
|
||||||
|
[FromQuery, ParameterObsolete] Guid? itemId,
|
||||||
|
[FromQuery, ParameterObsolete] string? mediaSourceId,
|
||||||
|
[FromQuery, ParameterObsolete] int? index,
|
||||||
|
[FromQuery, ParameterObsolete] long? startPositionTicks,
|
||||||
|
[FromQuery, ParameterObsolete] string? format,
|
||||||
[FromQuery] long? endPositionTicks,
|
[FromQuery] long? endPositionTicks,
|
||||||
[FromQuery] bool copyTimestamps = false,
|
[FromQuery] bool copyTimestamps = false,
|
||||||
[FromQuery] bool addVttTimeMap = false)
|
[FromQuery] bool addVttTimeMap = false)
|
||||||
{
|
{
|
||||||
return GetSubtitle(
|
return GetSubtitle(
|
||||||
|
routeItemId,
|
||||||
|
routeMediaSourceId,
|
||||||
|
routeIndex,
|
||||||
|
routeFormat,
|
||||||
itemId,
|
itemId,
|
||||||
mediaSourceId,
|
mediaSourceId,
|
||||||
index,
|
index,
|
||||||
@@ -282,7 +310,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
endPositionTicks,
|
endPositionTicks,
|
||||||
copyTimestamps,
|
copyTimestamps,
|
||||||
addVttTimeMap,
|
addVttTimeMap,
|
||||||
startPositionTicks);
|
startPositionTicks ?? routeStartPositionTicks);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -219,11 +219,11 @@ namespace Jellyfin.Api.Controllers
|
|||||||
AudioBitRate = audioBitRate ?? maxStreamingBitrate,
|
AudioBitRate = audioBitRate ?? maxStreamingBitrate,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
SubtitleMethod = SubtitleDeliveryMethod.Hls,
|
SubtitleMethod = SubtitleDeliveryMethod.Hls,
|
||||||
RequireAvc = true,
|
RequireAvc = false,
|
||||||
DeInterlace = true,
|
DeInterlace = false,
|
||||||
RequireNonAnamorphic = true,
|
RequireNonAnamorphic = false,
|
||||||
EnableMpegtsM2TsMode = true,
|
EnableMpegtsM2TsMode = false,
|
||||||
TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
|
TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(',', mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
|
||||||
Context = EncodingContext.Static,
|
Context = EncodingContext.Static,
|
||||||
StreamOptions = new Dictionary<string, string>(),
|
StreamOptions = new Dictionary<string, string>(),
|
||||||
EnableAdaptiveBitrateStreaming = true
|
EnableAdaptiveBitrateStreaming = true
|
||||||
@@ -251,7 +251,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
AudioBitRate = isStatic ? (int?)null : (audioBitRate ?? maxStreamingBitrate),
|
AudioBitRate = isStatic ? (int?)null : (audioBitRate ?? maxStreamingBitrate),
|
||||||
MaxAudioBitDepth = maxAudioBitDepth,
|
MaxAudioBitDepth = maxAudioBitDepth,
|
||||||
AudioChannels = maxAudioChannels,
|
AudioChannels = maxAudioChannels,
|
||||||
CopyTimestamps = true,
|
CopyTimestamps = false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
SubtitleMethod = SubtitleDeliveryMethod.Embed,
|
SubtitleMethod = SubtitleDeliveryMethod.Embed,
|
||||||
TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
|
TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Container = container,
|
Container = container,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -248,7 +248,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -257,13 +257,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
|
|||||||
@@ -386,7 +386,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
{
|
{
|
||||||
Id = itemId,
|
Id = itemId,
|
||||||
Container = container,
|
Container = container,
|
||||||
Static = @static ?? true,
|
Static = @static ?? false,
|
||||||
Params = @params,
|
Params = @params,
|
||||||
Tag = tag,
|
Tag = tag,
|
||||||
DeviceProfileId = deviceProfileId,
|
DeviceProfileId = deviceProfileId,
|
||||||
@@ -410,7 +410,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Level = level,
|
Level = level,
|
||||||
Framerate = framerate,
|
Framerate = framerate,
|
||||||
MaxFramerate = maxFramerate,
|
MaxFramerate = maxFramerate,
|
||||||
CopyTimestamps = copyTimestamps ?? true,
|
CopyTimestamps = copyTimestamps ?? false,
|
||||||
StartTimeTicks = startTimeTicks,
|
StartTimeTicks = startTimeTicks,
|
||||||
Width = width,
|
Width = width,
|
||||||
Height = height,
|
Height = height,
|
||||||
@@ -419,13 +419,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
|
||||||
MaxRefFrames = maxRefFrames,
|
MaxRefFrames = maxRefFrames,
|
||||||
MaxVideoBitDepth = maxVideoBitDepth,
|
MaxVideoBitDepth = maxVideoBitDepth,
|
||||||
RequireAvc = requireAvc ?? true,
|
RequireAvc = requireAvc ?? false,
|
||||||
DeInterlace = deInterlace ?? true,
|
DeInterlace = deInterlace ?? false,
|
||||||
RequireNonAnamorphic = requireNonAnamorphic ?? true,
|
RequireNonAnamorphic = requireNonAnamorphic ?? false,
|
||||||
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
|
||||||
CpuCoreLimit = cpuCoreLimit,
|
CpuCoreLimit = cpuCoreLimit,
|
||||||
LiveStreamId = liveStreamId,
|
LiveStreamId = liveStreamId,
|
||||||
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? true,
|
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
|
||||||
VideoCodec = videoCodec,
|
VideoCodec = videoCodec,
|
||||||
SubtitleCodec = subtitleCodec,
|
SubtitleCodec = subtitleCodec,
|
||||||
TranscodeReasons = transcodeReasons,
|
TranscodeReasons = transcodeReasons,
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ namespace Jellyfin.Api.Helpers
|
|||||||
|
|
||||||
if (isHeadRequest)
|
if (isHeadRequest)
|
||||||
{
|
{
|
||||||
return new FileContentResult(Array.Empty<byte>(), contentType);
|
httpContext.Response.Headers[HeaderNames.ContentType] = contentType;
|
||||||
|
return new OkResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new FileStreamResult(await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), contentType);
|
return new FileStreamResult(await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false), contentType);
|
||||||
@@ -68,10 +69,10 @@ namespace Jellyfin.Api.Helpers
|
|||||||
{
|
{
|
||||||
httpContext.Response.ContentType = contentType;
|
httpContext.Response.ContentType = contentType;
|
||||||
|
|
||||||
// if the request is a head request, return a NoContent result with the same headers as it would with a GET request
|
// if the request is a head request, return an OkResult (200) with the same headers as it would with a GET request
|
||||||
if (isHeadRequest)
|
if (isHeadRequest)
|
||||||
{
|
{
|
||||||
return new NoContentResult();
|
return new OkResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new PhysicalFileResult(path, contentType) { EnableRangeProcessing = true };
|
return new PhysicalFileResult(path, contentType) { EnableRangeProcessing = true };
|
||||||
|
|||||||
@@ -282,6 +282,7 @@ namespace Jellyfin.Api.Helpers
|
|||||||
if (streamInfo != null)
|
if (streamInfo != null)
|
||||||
{
|
{
|
||||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
||||||
|
mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,6 +327,7 @@ namespace Jellyfin.Api.Helpers
|
|||||||
if (streamInfo != null)
|
if (streamInfo != null)
|
||||||
{
|
{
|
||||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
||||||
|
mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,6 +355,7 @@ namespace Jellyfin.Api.Helpers
|
|||||||
|
|
||||||
// Do this after the above so that StartPositionTicks is set
|
// Do this after the above so that StartPositionTicks is set
|
||||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
||||||
|
mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -390,6 +393,7 @@ namespace Jellyfin.Api.Helpers
|
|||||||
|
|
||||||
// Do this after the above so that StartPositionTicks is set
|
// Do this after the above so that StartPositionTicks is set
|
||||||
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
SetDeviceSpecificSubtitleInfo(streamInfo, mediaSource, auth.Token);
|
||||||
|
mediaSource.DefaultAudioStreamIndex = streamInfo.AudioStreamIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Data</PackageId>
|
<PackageId>Jellyfin.Data</PackageId>
|
||||||
<VersionPrefix>10.7.1</VersionPrefix>
|
<VersionPrefix>10.7.2</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
{
|
{
|
||||||
foreach (var item in source)
|
foreach (var item in source)
|
||||||
{
|
{
|
||||||
result.AddItem(item);
|
result.AddItem(item, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +274,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
if (_bindExclusions.Count > 0)
|
if (_bindExclusions.Count > 0)
|
||||||
{
|
{
|
||||||
// Return all the interfaces except the ones specifically excluded.
|
// Return all the interfaces except the ones specifically excluded.
|
||||||
return _interfaceAddresses.Exclude(_bindExclusions);
|
return _interfaceAddresses.Exclude(_bindExclusions, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (individualInterfaces)
|
if (individualInterfaces)
|
||||||
@@ -310,7 +310,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove any excluded bind interfaces.
|
// Remove any excluded bind interfaces.
|
||||||
return _bindAddresses.Exclude(_bindExclusions);
|
return _bindAddresses.Exclude(_bindExclusions, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -397,15 +397,26 @@ namespace Jellyfin.Networking.Manager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the first LAN interface address that isn't a loopback.
|
// Get the first LAN interface address that isn't a loopback.
|
||||||
var interfaces = CreateCollection(_interfaceAddresses
|
var interfaces = CreateCollection(
|
||||||
.Exclude(_bindExclusions)
|
_interfaceAddresses
|
||||||
.Where(IsInLocalNetwork)
|
.Exclude(_bindExclusions, false)
|
||||||
.OrderBy(p => p.Tag));
|
.Where(IsInLocalNetwork)
|
||||||
|
.OrderBy(p => p.Tag));
|
||||||
|
|
||||||
if (interfaces.Count > 0)
|
if (interfaces.Count > 0)
|
||||||
{
|
{
|
||||||
if (haveSource)
|
if (haveSource)
|
||||||
{
|
{
|
||||||
|
foreach (var intf in interfaces)
|
||||||
|
{
|
||||||
|
if (intf.Address.Equals(source.Address))
|
||||||
|
{
|
||||||
|
result = FormatIP6String(intf.Address);
|
||||||
|
_logger.LogDebug("{Source}: GetBindInterface: Has found matching interface. {Result}", source, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Does the request originate in one of the interface subnets?
|
// Does the request originate in one of the interface subnets?
|
||||||
// (For systems with multiple internal network cards, and multiple subnets)
|
// (For systems with multiple internal network cards, and multiple subnets)
|
||||||
foreach (var intf in interfaces)
|
foreach (var intf in interfaces)
|
||||||
@@ -532,10 +543,10 @@ namespace Jellyfin.Networking.Manager
|
|||||||
{
|
{
|
||||||
if (filter == null)
|
if (filter == null)
|
||||||
{
|
{
|
||||||
return _lanSubnets.Exclude(_excludedSubnets).AsNetworks();
|
return _lanSubnets.Exclude(_excludedSubnets, true).AsNetworks();
|
||||||
}
|
}
|
||||||
|
|
||||||
return _lanSubnets.Exclude(filter);
|
return _lanSubnets.Exclude(filter, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -566,7 +577,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
&& ((IsIP4Enabled && iface.Address.AddressFamily == AddressFamily.InterNetwork)
|
&& ((IsIP4Enabled && iface.Address.AddressFamily == AddressFamily.InterNetwork)
|
||||||
|| (IsIP6Enabled && iface.Address.AddressFamily == AddressFamily.InterNetworkV6)))
|
|| (IsIP6Enabled && iface.Address.AddressFamily == AddressFamily.InterNetworkV6)))
|
||||||
{
|
{
|
||||||
result.AddItem(iface);
|
result.AddItem(iface, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -576,6 +587,29 @@ namespace Jellyfin.Networking.Manager
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool HasRemoteAccess(IPAddress remoteIp)
|
||||||
|
{
|
||||||
|
var config = _configurationManager.GetNetworkConfiguration();
|
||||||
|
if (config.EnableRemoteAccess)
|
||||||
|
{
|
||||||
|
// Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely.
|
||||||
|
// If left blank, all remote addresses will be allowed.
|
||||||
|
if (RemoteAddressFilter.Count > 0 && !IsInLocalNetwork(remoteIp))
|
||||||
|
{
|
||||||
|
// remoteAddressFilter is a whitelist or blacklist.
|
||||||
|
return RemoteAddressFilter.ContainsAddress(remoteIp) == !config.IsRemoteIPFilterBlacklist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!IsInLocalNetwork(remoteIp))
|
||||||
|
{
|
||||||
|
// Remote not enabled. So everyone should be LAN.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reloads all settings and re-initialises the instance.
|
/// Reloads all settings and re-initialises the instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -610,8 +644,8 @@ namespace Jellyfin.Networking.Manager
|
|||||||
var address = IPNetAddress.Parse(parts[0]);
|
var address = IPNetAddress.Parse(parts[0]);
|
||||||
var index = int.Parse(parts[1], CultureInfo.InvariantCulture);
|
var index = int.Parse(parts[1], CultureInfo.InvariantCulture);
|
||||||
address.Tag = index;
|
address.Tag = index;
|
||||||
_interfaceAddresses.AddItem(address);
|
_interfaceAddresses.AddItem(address, false);
|
||||||
_interfaceNames.Add(parts[2], Math.Abs(index));
|
_interfaceNames[parts[2]] = Math.Abs(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1028,7 +1062,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
|
|
||||||
_logger.LogInformation("Defined LAN addresses : {0}", _lanSubnets.AsString());
|
_logger.LogInformation("Defined LAN addresses : {0}", _lanSubnets.AsString());
|
||||||
_logger.LogInformation("Defined LAN exclusions : {0}", _excludedSubnets.AsString());
|
_logger.LogInformation("Defined LAN exclusions : {0}", _excludedSubnets.AsString());
|
||||||
_logger.LogInformation("Using LAN addresses: {0}", _lanSubnets.Exclude(_excludedSubnets).AsNetworks().AsString());
|
_logger.LogInformation("Using LAN addresses: {0}", _lanSubnets.Exclude(_excludedSubnets, true).AsNetworks().AsString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1082,7 +1116,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
nw.Tag *= -1;
|
nw.Tag *= -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
_interfaceAddresses.AddItem(nw);
|
_interfaceAddresses.AddItem(nw, false);
|
||||||
|
|
||||||
// Store interface name so we can use the name in Collections.
|
// Store interface name so we can use the name in Collections.
|
||||||
_interfaceNames[adapter.Description.ToLower(CultureInfo.InvariantCulture)] = tag;
|
_interfaceNames[adapter.Description.ToLower(CultureInfo.InvariantCulture)] = tag;
|
||||||
@@ -1103,7 +1137,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
nw.Tag *= -1;
|
nw.Tag *= -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
_interfaceAddresses.AddItem(nw);
|
_interfaceAddresses.AddItem(nw, false);
|
||||||
|
|
||||||
// Store interface name so we can use the name in Collections.
|
// Store interface name so we can use the name in Collections.
|
||||||
_interfaceNames[adapter.Description.ToLower(CultureInfo.InvariantCulture)] = tag;
|
_interfaceNames[adapter.Description.ToLower(CultureInfo.InvariantCulture)] = tag;
|
||||||
@@ -1137,10 +1171,10 @@ namespace Jellyfin.Networking.Manager
|
|||||||
{
|
{
|
||||||
_logger.LogWarning("No interfaces information available. Using loopback.");
|
_logger.LogWarning("No interfaces information available. Using loopback.");
|
||||||
// Last ditch attempt - use loopback address.
|
// Last ditch attempt - use loopback address.
|
||||||
_interfaceAddresses.AddItem(IPNetAddress.IP4Loopback);
|
_interfaceAddresses.AddItem(IPNetAddress.IP4Loopback, false);
|
||||||
if (IsIP6Enabled)
|
if (IsIP6Enabled)
|
||||||
{
|
{
|
||||||
_interfaceAddresses.AddItem(IPNetAddress.IP6Loopback);
|
_interfaceAddresses.AddItem(IPNetAddress.IP6Loopback, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1217,7 +1251,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
private bool MatchesBindInterface(IPObject source, bool isInExternalSubnet, out string result)
|
private bool MatchesBindInterface(IPObject source, bool isInExternalSubnet, out string result)
|
||||||
{
|
{
|
||||||
result = string.Empty;
|
result = string.Empty;
|
||||||
var addresses = _bindAddresses.Exclude(_bindExclusions);
|
var addresses = _bindAddresses.Exclude(_bindExclusions, false);
|
||||||
|
|
||||||
int count = addresses.Count;
|
int count = addresses.Count;
|
||||||
if (count == 1 && (_bindAddresses[0].Equals(IPAddress.Any) || _bindAddresses[0].Equals(IPAddress.IPv6Any)))
|
if (count == 1 && (_bindAddresses[0].Equals(IPAddress.Any) || _bindAddresses[0].Equals(IPAddress.IPv6Any)))
|
||||||
@@ -1302,7 +1336,7 @@ namespace Jellyfin.Networking.Manager
|
|||||||
result = string.Empty;
|
result = string.Empty;
|
||||||
// Get the first WAN interface address that isn't a loopback.
|
// Get the first WAN interface address that isn't a loopback.
|
||||||
var extResult = _interfaceAddresses
|
var extResult = _interfaceAddresses
|
||||||
.Exclude(_bindExclusions)
|
.Exclude(_bindExclusions, false)
|
||||||
.Where(p => !IsInLocalNetwork(p))
|
.Where(p => !IsInLocalNetwork(p))
|
||||||
.OrderBy(p => p.Tag);
|
.OrderBy(p => p.Tag);
|
||||||
|
|
||||||
|
|||||||
Generated
+520
@@ -0,0 +1,520 @@
|
|||||||
|
#pragma warning disable CS1591
|
||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Jellyfin.Server.Implementations;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Implementations.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(JellyfinDb))]
|
||||||
|
[Migration("20210407110544_NullableCustomPrefValue")]
|
||||||
|
partial class NullableCustomPrefValue
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasDefaultSchema("jellyfin")
|
||||||
|
.HasAnnotation("ProductVersion", "5.0.3");
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("DayOfWeek")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("EndHour")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("StartHour")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AccessSchedules");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateCreated")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ItemId")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("LogSeverity")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Overview")
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ShortOverview")
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Type")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("ActivityLogs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Client")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("ItemId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId", "ItemId", "Client", "Key")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("CustomItemDisplayPreferences");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ChromecastVersion")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Client")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DashboardTheme")
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableNextVideoInfoOverlay")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("IndexBy")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid>("ItemId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("ScrollDirection")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("ShowBackdrop")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("ShowSidebar")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SkipBackwardLength")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SkipForwardLength")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("TvHome")
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.HasIndex("UserId", "ItemId", "Client")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("DisplayPreferences");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("DisplayPreferencesId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Order")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DisplayPreferencesId");
|
||||||
|
|
||||||
|
b.ToTable("HomeSection");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastModified")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ImageInfos");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Client")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("IndexBy")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid>("ItemId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("RememberIndexing")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("RememberSorting")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SortBy")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("ViewType")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("ItemDisplayPreferences");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Kind")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid?>("Permission_Permissions_Guid")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("Value")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Permission_Permissions_Guid");
|
||||||
|
|
||||||
|
b.ToTable("Permissions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Kind")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid?>("Preference_Preferences_Guid")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(65535)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Preference_Preferences_Guid");
|
||||||
|
|
||||||
|
b.ToTable("Preferences");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("AudioLanguagePreference")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("AuthenticationProviderId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("DisplayCollectionsView")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("DisplayMissingEpisodes")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("EasyPassword")
|
||||||
|
.HasMaxLength(65535)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableAutoLogin")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableLocalPassword")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableNextEpisodeAutoPlay")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableUserPreferenceAccess")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("HidePlayedInLatest")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("InternalId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("InvalidLoginAttemptCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastActivityDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastLoginDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("LoginAttemptsBeforeLockout")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("MaxActiveSessions")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("MaxParentalAgeRating")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("MustUpdatePassword")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.HasMaxLength(65535)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordResetProviderId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("PlayDefaultAudioTrack")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("RememberAudioSelections")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("RememberSubtitleSelections")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("RemoteClientBitrateLimit")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SubtitleLanguagePreference")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("SubtitleMode")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SyncPlayAccess")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("AccessSchedules")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("DisplayPreferences")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null)
|
||||||
|
.WithMany("HomeSections")
|
||||||
|
.HasForeignKey("DisplayPreferencesId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithOne("ProfileImage")
|
||||||
|
.HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("ItemDisplayPreferences")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("Permissions")
|
||||||
|
.HasForeignKey("Permission_Permissions_Guid");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("Preferences")
|
||||||
|
.HasForeignKey("Preference_Preferences_Guid");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("HomeSections");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("AccessSchedules");
|
||||||
|
|
||||||
|
b.Navigation("DisplayPreferences");
|
||||||
|
|
||||||
|
b.Navigation("ItemDisplayPreferences");
|
||||||
|
|
||||||
|
b.Navigation("Permissions");
|
||||||
|
|
||||||
|
b.Navigation("Preferences");
|
||||||
|
|
||||||
|
b.Navigation("ProfileImage");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma warning disable CS1591
|
||||||
|
// <auto-generated />
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Implementations.Migrations
|
||||||
|
{
|
||||||
|
public partial class NullableCustomPrefValue : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "Value",
|
||||||
|
schema: "jellyfin",
|
||||||
|
table: "CustomItemDisplayPreferences",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "TEXT");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "Value",
|
||||||
|
schema: "jellyfin",
|
||||||
|
table: "CustomItemDisplayPreferences",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "",
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "TEXT",
|
||||||
|
oldNullable: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasDefaultSchema("jellyfin")
|
.HasDefaultSchema("jellyfin")
|
||||||
.HasAnnotation("ProductVersion", "5.0.0");
|
.HasAnnotation("ProductVersion", "5.0.3");
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||||
{
|
{
|
||||||
@@ -110,7 +110,6 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("Value")
|
b.Property<string>("Value")
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
@@ -448,8 +447,8 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Jellyfin.Data.Entities.User", null)
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
.WithOne("DisplayPreferences")
|
.WithMany("DisplayPreferences")
|
||||||
.HasForeignKey("Jellyfin.Data.Entities.DisplayPreferences", "UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
});
|
});
|
||||||
@@ -502,8 +501,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
{
|
{
|
||||||
b.Navigation("AccessSchedules");
|
b.Navigation("AccessSchedules");
|
||||||
|
|
||||||
b.Navigation("DisplayPreferences")
|
b.Navigation("DisplayPreferences");
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("ItemDisplayPreferences");
|
b.Navigation("ItemDisplayPreferences");
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IDictionary<string, string> ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client)
|
public Dictionary<string, string> ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client)
|
||||||
{
|
{
|
||||||
return _dbContext.CustomItemDisplayPreferences
|
return _dbContext.CustomItemDisplayPreferences
|
||||||
.AsQueryable()
|
.AsQueryable()
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ namespace Jellyfin.Server.Extensions
|
|||||||
{
|
{
|
||||||
return serviceCollection.AddSwaggerGen(c =>
|
return serviceCollection.AddSwaggerGen(c =>
|
||||||
{
|
{
|
||||||
var version = typeof(ApplicationHost).Assembly.GetName().Version?.ToString() ?? "0.0.0.1";
|
var version = typeof(ApplicationHost).Assembly.GetName().Version?.ToString(3) ?? "0.0.1";
|
||||||
c.SwaggerDoc("api-docs", new OpenApiInfo
|
c.SwaggerDoc("api-docs", new OpenApiInfo
|
||||||
{
|
{
|
||||||
Title = "Jellyfin API",
|
Title = "Jellyfin API",
|
||||||
@@ -319,7 +319,7 @@ namespace Jellyfin.Server.Extensions
|
|||||||
c.OperationFilter<FileResponseFilter>();
|
c.OperationFilter<FileResponseFilter>();
|
||||||
c.OperationFilter<FileRequestFilter>();
|
c.OperationFilter<FileRequestFilter>();
|
||||||
c.OperationFilter<ParameterObsoleteFilter>();
|
c.OperationFilter<ParameterObsoleteFilter>();
|
||||||
c.DocumentFilter<WebsocketModelFilter>();
|
c.DocumentFilter<AdditionalModelFilter>();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -1,5 +1,6 @@
|
|||||||
using MediaBrowser.Common.Plugins;
|
using MediaBrowser.Common.Plugins;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
|
using MediaBrowser.Model.ApiClient;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using MediaBrowser.Model.SyncPlay;
|
using MediaBrowser.Model.SyncPlay;
|
||||||
@@ -9,9 +10,9 @@ using Swashbuckle.AspNetCore.SwaggerGen;
|
|||||||
namespace Jellyfin.Server.Filters
|
namespace Jellyfin.Server.Filters
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add models used in websocket messaging.
|
/// Add models not directly used by the API, but used for discovery and websockets.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WebsocketModelFilter : IDocumentFilter
|
public class AdditionalModelFilter : IDocumentFilter
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||||
@@ -27,6 +28,7 @@ namespace Jellyfin.Server.Filters
|
|||||||
context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<object>), context.SchemaRepository);
|
context.SchemaGenerator.GenerateSchema(typeof(GroupUpdate<object>), context.SchemaRepository);
|
||||||
|
|
||||||
context.SchemaGenerator.GenerateSchema(typeof(SessionMessageType), context.SchemaRepository);
|
context.SchemaGenerator.GenerateSchema(typeof(SessionMessageType), context.SchemaRepository);
|
||||||
|
context.SchemaGenerator.GenerateSchema(typeof(ServerDiscoveryInfo), context.SchemaRepository);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
<AssemblyName>jellyfin</AssemblyName>
|
<AssemblyName>jellyfin</AssemblyName>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<ServerGarbageCollection>false</ServerGarbageCollection>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
|||||||
@@ -29,9 +29,8 @@ namespace Jellyfin.Server.Middleware
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="httpContext">The current HTTP context.</param>
|
/// <param name="httpContext">The current HTTP context.</param>
|
||||||
/// <param name="networkManager">The network manager.</param>
|
/// <param name="networkManager">The network manager.</param>
|
||||||
/// <param name="serverConfigurationManager">The server configuration manager.</param>
|
|
||||||
/// <returns>The async task.</returns>
|
/// <returns>The async task.</returns>
|
||||||
public async Task Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
|
public async Task Invoke(HttpContext httpContext, INetworkManager networkManager)
|
||||||
{
|
{
|
||||||
if (httpContext.IsLocal())
|
if (httpContext.IsLocal())
|
||||||
{
|
{
|
||||||
@@ -42,32 +41,8 @@ namespace Jellyfin.Server.Middleware
|
|||||||
|
|
||||||
var remoteIp = httpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback;
|
var remoteIp = httpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback;
|
||||||
|
|
||||||
if (serverConfigurationManager.GetNetworkConfiguration().EnableRemoteAccess)
|
if (!networkManager.HasRemoteAccess(remoteIp))
|
||||||
{
|
{
|
||||||
// Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely.
|
|
||||||
// If left blank, all remote addresses will be allowed.
|
|
||||||
var remoteAddressFilter = networkManager.RemoteAddressFilter;
|
|
||||||
|
|
||||||
if (remoteAddressFilter.Count > 0 && !networkManager.IsInLocalNetwork(remoteIp))
|
|
||||||
{
|
|
||||||
// remoteAddressFilter is a whitelist or blacklist.
|
|
||||||
bool isListed = remoteAddressFilter.ContainsAddress(remoteIp);
|
|
||||||
if (!serverConfigurationManager.GetNetworkConfiguration().IsRemoteIPFilterBlacklist)
|
|
||||||
{
|
|
||||||
// Black list, so flip over.
|
|
||||||
isListed = !isListed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isListed)
|
|
||||||
{
|
|
||||||
// If your name isn't on the list, you arn't coming in.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!networkManager.IsInLocalNetwork(remoteIp))
|
|
||||||
{
|
|
||||||
// Remote not enabled. So everyone should be LAN.
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Common</PackageId>
|
<PackageId>Jellyfin.Common</PackageId>
|
||||||
<VersionPrefix>10.7.1</VersionPrefix>
|
<VersionPrefix>10.7.2</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -229,5 +229,12 @@ namespace MediaBrowser.Common.Net
|
|||||||
/// <param name="filter">Optional filter for the list.</param>
|
/// <param name="filter">Optional filter for the list.</param>
|
||||||
/// <returns>Returns a filtered list of LAN addresses.</returns>
|
/// <returns>Returns a filtered list of LAN addresses.</returns>
|
||||||
Collection<IPObject> GetFilteredLANSubnets(Collection<IPObject>? filter = null);
|
Collection<IPObject> GetFilteredLANSubnets(Collection<IPObject>? filter = null);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks to see if <paramref name="remoteIp"/> has access.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="remoteIp">IP Address of client.</param>
|
||||||
|
/// <returns><b>True</b> if has access, otherwise <b>false</b>.</returns>
|
||||||
|
bool HasRemoteAccess(IPAddress remoteIp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,9 +32,11 @@ namespace MediaBrowser.Common.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
|
/// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
|
||||||
/// <param name="item">Item to add.</param>
|
/// <param name="item">Item to add.</param>
|
||||||
public static void AddItem(this Collection<IPObject> source, IPObject item)
|
/// <param name="itemsAreNetworks">If <c>true</c> the values are treated as subnets.
|
||||||
|
/// If <b>false</b> items are addresses.</param>
|
||||||
|
public static void AddItem(this Collection<IPObject> source, IPObject item, bool itemsAreNetworks = true)
|
||||||
{
|
{
|
||||||
if (!source.ContainsAddress(item))
|
if (!source.ContainsAddress(item) || !itemsAreNetworks)
|
||||||
{
|
{
|
||||||
source.Add(item);
|
source.Add(item);
|
||||||
}
|
}
|
||||||
@@ -195,8 +197,9 @@ namespace MediaBrowser.Common.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
|
/// <param name="source">The <see cref="Collection{IPObject}"/>.</param>
|
||||||
/// <param name="excludeList">Items to exclude.</param>
|
/// <param name="excludeList">Items to exclude.</param>
|
||||||
|
/// <param name="isNetwork">Collection is a network collection.</param>
|
||||||
/// <returns>A new collection, with the items excluded.</returns>
|
/// <returns>A new collection, with the items excluded.</returns>
|
||||||
public static Collection<IPObject> Exclude(this Collection<IPObject> source, Collection<IPObject> excludeList)
|
public static Collection<IPObject> Exclude(this Collection<IPObject> source, Collection<IPObject> excludeList, bool isNetwork)
|
||||||
{
|
{
|
||||||
if (source.Count == 0 || excludeList == null)
|
if (source.Count == 0 || excludeList == null)
|
||||||
{
|
{
|
||||||
@@ -221,7 +224,7 @@ namespace MediaBrowser.Common.Net
|
|||||||
|
|
||||||
if (!found)
|
if (!found)
|
||||||
{
|
{
|
||||||
results.AddItem(outer);
|
results.AddItem(outer, isNetwork);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Model.Plugins;
|
||||||
using MediaBrowser.Model.Updates;
|
using MediaBrowser.Model.Updates;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
@@ -51,8 +52,9 @@ namespace MediaBrowser.Common.Plugins
|
|||||||
/// <param name="packageInfo">The <see cref="PackageInfo"/> used to generate a manifest.</param>
|
/// <param name="packageInfo">The <see cref="PackageInfo"/> used to generate a manifest.</param>
|
||||||
/// <param name="version">Version to be installed.</param>
|
/// <param name="version">Version to be installed.</param>
|
||||||
/// <param name="path">The path where to save the manifest.</param>
|
/// <param name="path">The path where to save the manifest.</param>
|
||||||
|
/// <param name="status">Initial status of the plugin.</param>
|
||||||
/// <returns>True if successful.</returns>
|
/// <returns>True if successful.</returns>
|
||||||
Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path);
|
Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path, PluginStatus status);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Imports plugin details from a folder.
|
/// Imports plugin details from a folder.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
@@ -9,67 +10,12 @@ namespace MediaBrowser.Controller.Drawing
|
|||||||
{
|
{
|
||||||
public static class ImageHelper
|
public static class ImageHelper
|
||||||
{
|
{
|
||||||
public static ImageDimensions GetNewImageSize(ImageProcessingOptions options, ImageDimensions? originalImageSize)
|
public static ImageDimensions GetNewImageSize(ImageProcessingOptions options, ImageDimensions originalImageSize)
|
||||||
{
|
{
|
||||||
if (originalImageSize.HasValue)
|
// Determine the output size based on incoming parameters
|
||||||
{
|
var newSize = DrawingUtils.Resize(originalImageSize, options.Width ?? 0, options.Height ?? 0, options.MaxWidth ?? 0, options.MaxHeight ?? 0);
|
||||||
// Determine the output size based on incoming parameters
|
newSize = DrawingUtils.ResizeFill(newSize, options.FillWidth, options.FillHeight);
|
||||||
var newSize = DrawingUtils.Resize(originalImageSize.Value, options.Width ?? 0, options.Height ?? 0, options.MaxWidth ?? 0, options.MaxHeight ?? 0);
|
return newSize;
|
||||||
|
|
||||||
return newSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetSizeEstimate(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ImageDimensions GetSizeEstimate(ImageProcessingOptions options)
|
|
||||||
{
|
|
||||||
if (options.Width.HasValue && options.Height.HasValue)
|
|
||||||
{
|
|
||||||
return new ImageDimensions(options.Width.Value, options.Height.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
double aspect = GetEstimatedAspectRatio(options.Image.Type, options.Item);
|
|
||||||
|
|
||||||
int? width = options.Width ?? options.MaxWidth;
|
|
||||||
|
|
||||||
if (width.HasValue)
|
|
||||||
{
|
|
||||||
int heightValue = Convert.ToInt32((double)width.Value / aspect);
|
|
||||||
return new ImageDimensions(width.Value, heightValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
var height = options.Height ?? options.MaxHeight ?? 200;
|
|
||||||
int widthValue = Convert.ToInt32(aspect * height);
|
|
||||||
return new ImageDimensions(widthValue, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static double GetEstimatedAspectRatio(ImageType type, BaseItem item)
|
|
||||||
{
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case ImageType.Art:
|
|
||||||
case ImageType.Backdrop:
|
|
||||||
case ImageType.Chapter:
|
|
||||||
case ImageType.Screenshot:
|
|
||||||
case ImageType.Thumb:
|
|
||||||
return 1.78;
|
|
||||||
case ImageType.Banner:
|
|
||||||
return 5.4;
|
|
||||||
case ImageType.Box:
|
|
||||||
case ImageType.BoxRear:
|
|
||||||
case ImageType.Disc:
|
|
||||||
case ImageType.Menu:
|
|
||||||
case ImageType.Profile:
|
|
||||||
return 1;
|
|
||||||
case ImageType.Logo:
|
|
||||||
return 2.58;
|
|
||||||
case ImageType.Primary:
|
|
||||||
double defaultPrimaryImageAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
|
|
||||||
return defaultPrimaryImageAspectRatio > 0 ? defaultPrimaryImageAspectRatio : 2.0 / 3;
|
|
||||||
default:
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ namespace MediaBrowser.Controller.Drawing
|
|||||||
|
|
||||||
public int? MaxHeight { get; set; }
|
public int? MaxHeight { get; set; }
|
||||||
|
|
||||||
|
public int? FillWidth { get; set; }
|
||||||
|
|
||||||
|
public int? FillHeight { get; set; }
|
||||||
|
|
||||||
public int Quality { get; set; }
|
public int Quality { get; set; }
|
||||||
|
|
||||||
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; set; }
|
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; set; }
|
||||||
@@ -95,6 +99,11 @@ namespace MediaBrowser.Controller.Drawing
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sizeValue.Width > FillWidth || sizeValue.Height > FillHeight)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ namespace MediaBrowser.Controller
|
|||||||
/// <param name="itemId">The item id.</param>
|
/// <param name="itemId">The item id.</param>
|
||||||
/// <param name="client">The client string.</param>
|
/// <param name="client">The client string.</param>
|
||||||
/// <returns>The dictionary of custom item display preferences.</returns>
|
/// <returns>The dictionary of custom item display preferences.</returns>
|
||||||
IDictionary<string, string> ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client);
|
Dictionary<string, string> ListCustomItemDisplayPreferences(Guid userId, Guid itemId, string client);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the custom item display preference for the user and client.
|
/// Sets the custom item display preference for the user and client.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Controller</PackageId>
|
<PackageId>Jellyfin.Controller</PackageId>
|
||||||
<VersionPrefix>10.7.1</VersionPrefix>
|
<VersionPrefix>10.7.2</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -313,6 +313,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ISO files don't have an ffmpeg format
|
||||||
|
if (string.Equals(container, "iso", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@@ -43,7 +44,8 @@ namespace MediaBrowser.Controller.Playlists
|
|||||||
|
|
||||||
public static bool IsPlaylistFile(string path)
|
public static bool IsPlaylistFile(string path)
|
||||||
{
|
{
|
||||||
return System.IO.Path.HasExtension(path);
|
// The path will sometimes be a directory and "Path.HasExtension" returns true if the name contains a '.' (dot).
|
||||||
|
return System.IO.Path.HasExtension(path) && !Directory.Exists(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ namespace MediaBrowser.Controller.Providers
|
|||||||
{
|
{
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, FileSystemMetadata[]> _cache = new ConcurrentDictionary<string, FileSystemMetadata[]>(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, FileSystemMetadata[]> _cache = new (StringComparer.Ordinal);
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache = new ConcurrentDictionary<string, FileSystemMetadata>(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache = new (StringComparer.Ordinal);
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, List<string>> _filePathCache = new ConcurrentDictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, List<string>> _filePathCache = new (StringComparer.Ordinal);
|
||||||
|
|
||||||
public DirectoryService(IFileSystem fileSystem)
|
public DirectoryService(IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -370,7 +370,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource)
|
public string GetInputArgument(string inputFile, MediaSourceInfo mediaSource)
|
||||||
{
|
{
|
||||||
var prefix = "file";
|
var prefix = "file";
|
||||||
if (mediaSource.VideoType == VideoType.BluRay || mediaSource.VideoType == VideoType.Iso)
|
if (mediaSource.VideoType == VideoType.BluRay)
|
||||||
{
|
{
|
||||||
prefix = "bluray";
|
prefix = "bluray";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace MediaBrowser.MediaEncoding.Probing
|
namespace MediaBrowser.MediaEncoding.Probing
|
||||||
{
|
{
|
||||||
@@ -85,12 +86,14 @@ namespace MediaBrowser.MediaEncoding.Probing
|
|||||||
{
|
{
|
||||||
var val = GetDictionaryValue(tags, key);
|
var val = GetDictionaryValue(tags, key);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(val))
|
if (string.IsNullOrEmpty(val))
|
||||||
{
|
{
|
||||||
if (DateTime.TryParse(val, out var i))
|
return null;
|
||||||
{
|
}
|
||||||
return i.ToUniversalTime();
|
|
||||||
}
|
if (DateTime.TryParse(val, DateTimeFormatInfo.CurrentInfo, DateTimeStyles.AssumeUniversal, out var i))
|
||||||
|
{
|
||||||
|
return i.ToUniversalTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ namespace MediaBrowser.MediaEncoding.Probing
|
|||||||
info.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
|
info.PremiereDate = FFProbeHelpers.GetDictionaryDateTime(tags, "retaildate") ??
|
||||||
FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
|
FFProbeHelpers.GetDictionaryDateTime(tags, "retail date") ??
|
||||||
FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
|
FFProbeHelpers.GetDictionaryDateTime(tags, "retail_date") ??
|
||||||
|
FFProbeHelpers.GetDictionaryDateTime(tags, "date_released") ??
|
||||||
FFProbeHelpers.GetDictionaryDateTime(tags, "date");
|
FFProbeHelpers.GetDictionaryDateTime(tags, "date");
|
||||||
|
|
||||||
if (isAudio)
|
if (isAudio)
|
||||||
|
|||||||
@@ -185,10 +185,8 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Be careful, IsDirectStream==true by default (Static != false or not in query).
|
|
||||||
// See initialization of StreamingRequestDto in AudioController.GetAudioStream() method : Static = @static ?? true.
|
|
||||||
if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) &&
|
if (string.Equals(pair.Name, "Static", StringComparison.OrdinalIgnoreCase) &&
|
||||||
string.Equals(pair.Value, "true", StringComparison.OrdinalIgnoreCase))
|
string.Equals(pair.Value, "false", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,52 @@ namespace MediaBrowser.Model.Drawing
|
|||||||
return new ImageDimensions(newWidth, newHeight);
|
return new ImageDimensions(newWidth, newHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scale down to fill box.
|
||||||
|
/// Returns original size if both width and height are null or zero.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size">The original size object.</param>
|
||||||
|
/// <param name="fillWidth">A new fixed width, if desired.</param>
|
||||||
|
/// <param name="fillHeight">A new fixed height, if desired.</param>
|
||||||
|
/// <returns>A new size object or size.</returns>
|
||||||
|
public static ImageDimensions ResizeFill(
|
||||||
|
ImageDimensions size,
|
||||||
|
int? fillWidth,
|
||||||
|
int? fillHeight)
|
||||||
|
{
|
||||||
|
// Return original size if input is invalid.
|
||||||
|
if ((fillWidth == null || fillWidth == 0)
|
||||||
|
&& (fillHeight == null || fillHeight == 0))
|
||||||
|
{
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fillWidth == null || fillWidth == 0)
|
||||||
|
{
|
||||||
|
fillWidth = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fillHeight == null || fillHeight == 0)
|
||||||
|
{
|
||||||
|
fillHeight = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
double widthRatio = size.Width / (double)fillWidth;
|
||||||
|
double heightRatio = size.Height / (double)fillHeight;
|
||||||
|
double scaleRatio = Math.Min(widthRatio, heightRatio);
|
||||||
|
|
||||||
|
// Clamp to current size.
|
||||||
|
if (scaleRatio < 1)
|
||||||
|
{
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newWidth = Convert.ToInt32(Math.Ceiling(size.Width / scaleRatio));
|
||||||
|
int newHeight = Convert.ToInt32(Math.Ceiling(size.Height / scaleRatio));
|
||||||
|
|
||||||
|
return new ImageDimensions(newWidth, newHeight);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the new width.
|
/// Gets the new width.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Model</PackageId>
|
<PackageId>Jellyfin.Model</PackageId>
|
||||||
<VersionPrefix>10.7.1</VersionPrefix>
|
<VersionPrefix>10.7.2</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace MediaBrowser.Model.Session
|
namespace MediaBrowser.Model.Session
|
||||||
{
|
{
|
||||||
public class MessageCommand
|
public class MessageCommand
|
||||||
{
|
{
|
||||||
public string Header { get; set; }
|
public string Header { get; set; }
|
||||||
|
|
||||||
|
[Required(AllowEmptyStrings = false)]
|
||||||
public string Text { get; set; }
|
public string Text { get; set; }
|
||||||
|
|
||||||
public long? TimeoutMs { get; set; }
|
public long? TimeoutMs { get; set; }
|
||||||
|
|||||||
@@ -469,6 +469,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
|
using var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
await _providerManager.SaveImage(
|
await _providerManager.SaveImage(
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
|||||||
var movie = new Movie
|
var movie = new Movie
|
||||||
{
|
{
|
||||||
Name = movieResult.Title ?? movieResult.OriginalTitle,
|
Name = movieResult.Title ?? movieResult.OriginalTitle,
|
||||||
|
OriginalTitle = movieResult.OriginalTitle,
|
||||||
Overview = movieResult.Overview?.Replace("\n\n", "\n", StringComparison.InvariantCulture),
|
Overview = movieResult.Overview?.Replace("\n\n", "\n", StringComparison.InvariantCulture),
|
||||||
Tagline = movieResult.Tagline,
|
Tagline = movieResult.Tagline,
|
||||||
ProductionLocations = movieResult.ProductionCountries.Select(pc => pc.Name).ToArray()
|
ProductionLocations = movieResult.ProductionCountries.Select(pc => pc.Name).ToArray()
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
[assembly: AssemblyVersion("10.7.1")]
|
[assembly: AssemblyVersion("10.7.2")]
|
||||||
[assembly: AssemblyFileVersion("10.7.1")]
|
[assembly: AssemblyFileVersion("10.7.2")]
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
# We just wrap `build` so this is really it
|
# We just wrap `build` so this is really it
|
||||||
name: "jellyfin"
|
name: "jellyfin"
|
||||||
version: "10.7.1"
|
version: "10.7.2"
|
||||||
packages:
|
packages:
|
||||||
- debian.amd64
|
- debian.amd64
|
||||||
- debian.arm64
|
- debian.arm64
|
||||||
|
|||||||
Vendored
+6
@@ -1,3 +1,9 @@
|
|||||||
|
jellyfin-server (10.7.2-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.2
|
||||||
|
|
||||||
|
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 11 Apr 2021 14:18:22 -0400
|
||||||
|
|
||||||
jellyfin-server (10.7.1-1) unstable; urgency=medium
|
jellyfin-server (10.7.1-1) unstable; urgency=medium
|
||||||
|
|
||||||
* New upstream version 10.7.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.1
|
* New upstream version 10.7.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.1
|
||||||
|
|||||||
Vendored
+5
@@ -33,6 +33,11 @@ JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg"
|
|||||||
# [OPTIONAL] run Jellyfin without the web app
|
# [OPTIONAL] run Jellyfin without the web app
|
||||||
#JELLYFIN_NOWEBAPP_OPT="--nowebclient"
|
#JELLYFIN_NOWEBAPP_OPT="--nowebclient"
|
||||||
|
|
||||||
|
# [OPTIONAL] run Jellyfin with ASP.NET Server Garbage Collection (uses more RAM and less CPU than Workstation GC)
|
||||||
|
# 0 = Workstation
|
||||||
|
# 1 = Server
|
||||||
|
#COMPlus_gcServer=1
|
||||||
|
|
||||||
#
|
#
|
||||||
# SysV init/Upstart options
|
# SysV init/Upstart options
|
||||||
#
|
#
|
||||||
|
|||||||
Vendored
+1
-1
@@ -5,7 +5,7 @@ Homepage: https://jellyfin.org
|
|||||||
Standards-Version: 3.9.2
|
Standards-Version: 3.9.2
|
||||||
|
|
||||||
Package: jellyfin
|
Package: jellyfin
|
||||||
Version: 10.7.1
|
Version: 10.7.2
|
||||||
Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org>
|
Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
Depends: jellyfin-server, jellyfin-web
|
Depends: jellyfin-server, jellyfin-web
|
||||||
Description: Provides the Jellyfin Free Software Media System
|
Description: Provides the Jellyfin Free Software Media System
|
||||||
|
|||||||
@@ -35,3 +35,7 @@ JELLYFIN_RESTART_OPT="--restartpath=/usr/libexec/jellyfin/restart.sh"
|
|||||||
# [OPTIONAL] run Jellyfin without the web app
|
# [OPTIONAL] run Jellyfin without the web app
|
||||||
#JELLYFIN_NOWEBAPP_OPT="--noautorunwebapp"
|
#JELLYFIN_NOWEBAPP_OPT="--noautorunwebapp"
|
||||||
|
|
||||||
|
# [OPTIONAL] run Jellyfin with ASP.NET Server Garbage Collection (uses more RAM and less CPU than Workstation GC)
|
||||||
|
# 0 = Workstation
|
||||||
|
# 1 = Server
|
||||||
|
#COMPlus_gcServer=1
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
%endif
|
%endif
|
||||||
|
|
||||||
Name: jellyfin
|
Name: jellyfin
|
||||||
Version: 10.7.1
|
Version: 10.7.2
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: The Free Software Media System
|
Summary: The Free Software Media System
|
||||||
License: GPLv3
|
License: GPLv3
|
||||||
@@ -137,6 +137,8 @@ fi
|
|||||||
%systemd_postun_with_restart jellyfin.service
|
%systemd_postun_with_restart jellyfin.service
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Sun Apr 11 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
- New upstream version 10.7.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.2
|
||||||
* Sun Mar 21 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
* Sun Mar 21 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
- New upstream version 10.7.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.1
|
- New upstream version 10.7.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.7.1
|
||||||
* Mon Mar 08 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
* Mon Mar 08 2021 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Reflection;
|
|
||||||
using Emby.Server.Implementations;
|
|
||||||
using Jellyfin.Server;
|
|
||||||
using MediaBrowser.Controller;
|
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Jellyfin.Api.Tests
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Implementation of the abstract <see cref="ApplicationHost" /> class.
|
|
||||||
/// </summary>
|
|
||||||
public class TestAppHost : CoreAppHost
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="TestAppHost" /> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="applicationPaths">The <see cref="ServerApplicationPaths" /> to be used by the <see cref="CoreAppHost" />.</param>
|
|
||||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory" /> to be used by the <see cref="CoreAppHost" />.</param>
|
|
||||||
/// <param name="options">The <see cref="StartupOptions" /> to be used by the <see cref="CoreAppHost" />.</param>
|
|
||||||
/// <param name="startup">The <see cref="IConfiguration" /> to be used by the <see cref="CoreAppHost" />.</param>
|
|
||||||
/// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
|
|
||||||
/// <param name="collection">The <see cref="IServiceCollection"/> to be used by the <see cref="CoreAppHost"/>.</param>
|
|
||||||
public TestAppHost(
|
|
||||||
IServerApplicationPaths applicationPaths,
|
|
||||||
ILoggerFactory loggerFactory,
|
|
||||||
IStartupOptions options,
|
|
||||||
IConfiguration startup,
|
|
||||||
IFileSystem fileSystem,
|
|
||||||
IServiceCollection collection)
|
|
||||||
: base(
|
|
||||||
applicationPaths,
|
|
||||||
loggerFactory,
|
|
||||||
options,
|
|
||||||
startup,
|
|
||||||
fileSystem,
|
|
||||||
collection)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
|
|
||||||
{
|
|
||||||
foreach (var a in base.GetAssembliesWithPartsInternal())
|
|
||||||
{
|
|
||||||
yield return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return typeof(TestPlugin).Assembly;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using MediaBrowser.Controller.Providers;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
using Moq;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Jellyfin.Controller.Tests
|
||||||
|
{
|
||||||
|
public class DirectoryServiceTests
|
||||||
|
{
|
||||||
|
private const string LowerCasePath = "/music/someartist";
|
||||||
|
private const string UpperCasePath = "/music/SOMEARTIST";
|
||||||
|
|
||||||
|
private static readonly FileSystemMetadata[] _lowerCaseFileSystemMetadata =
|
||||||
|
{
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
FullName = LowerCasePath + "/Artwork",
|
||||||
|
IsDirectory = true
|
||||||
|
},
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
FullName = LowerCasePath + "/Some Other Folder",
|
||||||
|
IsDirectory = true
|
||||||
|
},
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
FullName = LowerCasePath + "/Song 2.mp3",
|
||||||
|
IsDirectory = false
|
||||||
|
},
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
FullName = LowerCasePath + "/Song 3.mp3",
|
||||||
|
IsDirectory = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly FileSystemMetadata[] _upperCaseFileSystemMetadata =
|
||||||
|
{
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
FullName = UpperCasePath + "/Lyrics",
|
||||||
|
IsDirectory = true
|
||||||
|
},
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
FullName = UpperCasePath + "/Song 1.mp3",
|
||||||
|
IsDirectory = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetFileSystemEntries_GivenPathsWithDifferentCasing_CachesAll()
|
||||||
|
{
|
||||||
|
var fileSystemMock = new Mock<IFileSystem>();
|
||||||
|
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(_upperCaseFileSystemMetadata);
|
||||||
|
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(_lowerCaseFileSystemMetadata);
|
||||||
|
var directoryService = new DirectoryService(fileSystemMock.Object);
|
||||||
|
|
||||||
|
var upperCaseResult = directoryService.GetFileSystemEntries(UpperCasePath);
|
||||||
|
var lowerCaseResult = directoryService.GetFileSystemEntries(LowerCasePath);
|
||||||
|
|
||||||
|
Assert.Equal(_upperCaseFileSystemMetadata, upperCaseResult);
|
||||||
|
Assert.Equal(_lowerCaseFileSystemMetadata, lowerCaseResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetFiles_GivenPathsWithDifferentCasing_ReturnsCorrectFiles()
|
||||||
|
{
|
||||||
|
var fileSystemMock = new Mock<IFileSystem>();
|
||||||
|
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(_upperCaseFileSystemMetadata);
|
||||||
|
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(_lowerCaseFileSystemMetadata);
|
||||||
|
var directoryService = new DirectoryService(fileSystemMock.Object);
|
||||||
|
|
||||||
|
var upperCaseResult = directoryService.GetFiles(UpperCasePath);
|
||||||
|
var lowerCaseResult = directoryService.GetFiles(LowerCasePath);
|
||||||
|
|
||||||
|
Assert.Equal(_upperCaseFileSystemMetadata.Where(f => !f.IsDirectory), upperCaseResult);
|
||||||
|
Assert.Equal(_lowerCaseFileSystemMetadata.Where(f => !f.IsDirectory), lowerCaseResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetFile_GivenFilePathsWithDifferentCasing_ReturnsCorrectFile()
|
||||||
|
{
|
||||||
|
const string lowerCasePath = "/music/someartist/song 1.mp3";
|
||||||
|
var lowerCaseFileSystemMetadata = new FileSystemMetadata
|
||||||
|
{
|
||||||
|
FullName = lowerCasePath,
|
||||||
|
Exists = true
|
||||||
|
};
|
||||||
|
const string upperCasePath = "/music/SOMEARTIST/SONG 1.mp3";
|
||||||
|
var upperCaseFileSystemMetadata = new FileSystemMetadata
|
||||||
|
{
|
||||||
|
FullName = upperCasePath,
|
||||||
|
Exists = false
|
||||||
|
};
|
||||||
|
var fileSystemMock = new Mock<IFileSystem>();
|
||||||
|
fileSystemMock.Setup(f => f.GetFileInfo(It.Is<string>(x => x == upperCasePath))).Returns(upperCaseFileSystemMetadata);
|
||||||
|
fileSystemMock.Setup(f => f.GetFileInfo(It.Is<string>(x => x == lowerCasePath))).Returns(lowerCaseFileSystemMetadata);
|
||||||
|
var directoryService = new DirectoryService(fileSystemMock.Object);
|
||||||
|
|
||||||
|
var lowerCaseResult = directoryService.GetFile(lowerCasePath);
|
||||||
|
var upperCaseResult = directoryService.GetFile(upperCasePath);
|
||||||
|
|
||||||
|
Assert.Equal(lowerCaseFileSystemMetadata, lowerCaseResult);
|
||||||
|
Assert.Null(upperCaseResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetFile_GivenCachedPath_ReturnsCachedFile()
|
||||||
|
{
|
||||||
|
const string path = "/music/someartist/song 1.mp3";
|
||||||
|
var cachedFileSystemMetadata = new FileSystemMetadata
|
||||||
|
{
|
||||||
|
FullName = path,
|
||||||
|
Exists = true
|
||||||
|
};
|
||||||
|
var newFileSystemMetadata = new FileSystemMetadata
|
||||||
|
{
|
||||||
|
FullName = "/music/SOMEARTIST/song 1.mp3",
|
||||||
|
Exists = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var fileSystemMock = new Mock<IFileSystem>();
|
||||||
|
fileSystemMock.Setup(f => f.GetFileInfo(It.Is<string>(x => x == path))).Returns(cachedFileSystemMetadata);
|
||||||
|
var directoryService = new DirectoryService(fileSystemMock.Object);
|
||||||
|
|
||||||
|
var result = directoryService.GetFile(path);
|
||||||
|
fileSystemMock.Setup(f => f.GetFileInfo(It.Is<string>(x => x == path))).Returns(newFileSystemMetadata);
|
||||||
|
var secondResult = directoryService.GetFile(path);
|
||||||
|
|
||||||
|
Assert.Equal(cachedFileSystemMetadata, result);
|
||||||
|
Assert.Equal(cachedFileSystemMetadata, secondResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetFilePaths_GivenCachedFilePathWithoutClear_ReturnsOnlyCachedPaths()
|
||||||
|
{
|
||||||
|
const string path = "/music/someartist";
|
||||||
|
|
||||||
|
var cachedPaths = new[]
|
||||||
|
{
|
||||||
|
"/music/someartist/song 1.mp3",
|
||||||
|
"/music/someartist/song 2.mp3",
|
||||||
|
"/music/someartist/song 3.mp3",
|
||||||
|
"/music/someartist/song 4.mp3",
|
||||||
|
};
|
||||||
|
var newPaths = new[]
|
||||||
|
{
|
||||||
|
"/music/someartist/song 5.mp3",
|
||||||
|
"/music/someartist/song 6.mp3",
|
||||||
|
"/music/someartist/song 7.mp3",
|
||||||
|
"/music/someartist/song 8.mp3",
|
||||||
|
};
|
||||||
|
|
||||||
|
var fileSystemMock = new Mock<IFileSystem>();
|
||||||
|
fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths);
|
||||||
|
var directoryService = new DirectoryService(fileSystemMock.Object);
|
||||||
|
|
||||||
|
var result = directoryService.GetFilePaths(path);
|
||||||
|
fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths);
|
||||||
|
var secondResult = directoryService.GetFilePaths(path);
|
||||||
|
|
||||||
|
Assert.Equal(cachedPaths, result);
|
||||||
|
Assert.Equal(cachedPaths, secondResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetFilePaths_GivenCachedFilePathWithClear_ReturnsNewPaths()
|
||||||
|
{
|
||||||
|
const string path = "/music/someartist";
|
||||||
|
|
||||||
|
var cachedPaths = new[]
|
||||||
|
{
|
||||||
|
"/music/someartist/song 1.mp3",
|
||||||
|
"/music/someartist/song 2.mp3",
|
||||||
|
"/music/someartist/song 3.mp3",
|
||||||
|
"/music/someartist/song 4.mp3",
|
||||||
|
};
|
||||||
|
var newPaths = new[]
|
||||||
|
{
|
||||||
|
"/music/someartist/song 5.mp3",
|
||||||
|
"/music/someartist/song 6.mp3",
|
||||||
|
"/music/someartist/song 7.mp3",
|
||||||
|
"/music/someartist/song 8.mp3",
|
||||||
|
};
|
||||||
|
|
||||||
|
var fileSystemMock = new Mock<IFileSystem>();
|
||||||
|
fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths);
|
||||||
|
var directoryService = new DirectoryService(fileSystemMock.Object);
|
||||||
|
|
||||||
|
var result = directoryService.GetFilePaths(path);
|
||||||
|
fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths);
|
||||||
|
var secondResult = directoryService.GetFilePaths(path, true);
|
||||||
|
|
||||||
|
Assert.Equal(cachedPaths, result);
|
||||||
|
Assert.Equal(newPaths, secondResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
||||||
|
<PackageReference Include="Moq" Version="4.16.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
|
||||||
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
<PackageReference Include="coverlet.collector" Version="1.3.0" />
|
||||||
|
|||||||
@@ -412,7 +412,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||||||
@"/movies/John Wick - Kapitel 3 (2019) [imdbid=tt6146586]/John Wick - Kapitel 3 (2019) [imdbid=tt6146586] - Version 2.mkv"
|
@"/movies/John Wick - Kapitel 3 (2019) [imdbid=tt6146586]/John Wick - Kapitel 3 (2019) [imdbid=tt6146586] - Version 2.mkv"
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _videoListResolver.Resolve(files.Select(i => new FileSystemMetadata
|
var result = GetResolver().Resolve(files.Select(i => new FileSystemMetadata
|
||||||
{
|
{
|
||||||
IsDirectory = false,
|
IsDirectory = false,
|
||||||
FullName = i
|
FullName = i
|
||||||
@@ -432,7 +432,7 @@ namespace Jellyfin.Naming.Tests.Video
|
|||||||
@"/movies/John Wick - Chapter 3 (2019)/John Wick - Chapter 3 (2019) [Version 2.mkv"
|
@"/movies/John Wick - Chapter 3 (2019)/John Wick - Chapter 3 (2019) [Version 2.mkv"
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = _videoListResolver.Resolve(files.Select(i => new FileSystemMetadata
|
var result = GetResolver().Resolve(files.Select(i => new FileSystemMetadata
|
||||||
{
|
{
|
||||||
IsDirectory = false,
|
IsDirectory = false,
|
||||||
FullName = i
|
FullName = i
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ namespace Jellyfin.Networking.Tests
|
|||||||
[InlineData("192.168.1.208/24,-16,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[]")]
|
[InlineData("192.168.1.208/24,-16,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[]")]
|
||||||
// vEthernet1 and vEthernet212 should be excluded.
|
// vEthernet1 and vEthernet212 should be excluded.
|
||||||
[InlineData("192.168.1.200/24,-20,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.200/24", "[200.200.200.200/24]")]
|
[InlineData("192.168.1.200/24,-20,vEthernet1|192.168.2.208/24,-16,vEthernet212|200.200.200.200/24,11,eth11", "192.168.1.0/24;200.200.200.200/24", "[200.200.200.200/24]")]
|
||||||
|
// Overlapping interface,
|
||||||
|
[InlineData("192.168.1.110/24,-20,br0|192.168.1.10/24,-16,br0|200.200.200.200/24,11,eth11", "192.168.1.0/24", "[192.168.1.110/24,192.168.1.10/24]")]
|
||||||
public void IgnoreVirtualInterfaces(string interfaces, string lan, string value)
|
public void IgnoreVirtualInterfaces(string interfaces, string lan, string value)
|
||||||
{
|
{
|
||||||
var conf = new NetworkConfiguration()
|
var conf = new NetworkConfiguration()
|
||||||
@@ -83,7 +85,7 @@ namespace Jellyfin.Networking.Tests
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks IP address formats.
|
/// Checks IP address formats.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address"></param>
|
/// <param name="address">IP Address.</param>
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("127.0.0.1")]
|
[InlineData("127.0.0.1")]
|
||||||
[InlineData("127.0.0.1:123")]
|
[InlineData("127.0.0.1:123")]
|
||||||
@@ -107,7 +109,7 @@ namespace Jellyfin.Networking.Tests
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks IP address formats.
|
/// Checks IP address formats.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="address"></param>
|
/// <param name="address">IP Address.</param>
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("127.0.0.1")]
|
[InlineData("127.0.0.1")]
|
||||||
[InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")]
|
[InlineData("fd23:184f:2029:0:3139:7386:67d7:d517")]
|
||||||
@@ -199,29 +201,29 @@ namespace Jellyfin.Networking.Tests
|
|||||||
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
|
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
|
||||||
|
|
||||||
// Test included.
|
// Test included.
|
||||||
Collection<IPObject> nc = nm.CreateIPCollection(settings.Split(","), false);
|
Collection<IPObject> nc = nm.CreateIPCollection(settings.Split(','), false);
|
||||||
Assert.Equal(nc.AsString(), result1);
|
Assert.Equal(nc.AsString(), result1);
|
||||||
|
|
||||||
// Test excluded.
|
// Test excluded.
|
||||||
nc = nm.CreateIPCollection(settings.Split(","), true);
|
nc = nm.CreateIPCollection(settings.Split(','), true);
|
||||||
Assert.Equal(nc.AsString(), result3);
|
Assert.Equal(nc.AsString(), result3);
|
||||||
|
|
||||||
conf.EnableIPV6 = false;
|
conf.EnableIPV6 = false;
|
||||||
nm.UpdateSettings(conf);
|
nm.UpdateSettings(conf);
|
||||||
|
|
||||||
// Test IP4 included.
|
// Test IP4 included.
|
||||||
nc = nm.CreateIPCollection(settings.Split(","), false);
|
nc = nm.CreateIPCollection(settings.Split(','), false);
|
||||||
Assert.Equal(nc.AsString(), result2);
|
Assert.Equal(nc.AsString(), result2);
|
||||||
|
|
||||||
// Test IP4 excluded.
|
// Test IP4 excluded.
|
||||||
nc = nm.CreateIPCollection(settings.Split(","), true);
|
nc = nm.CreateIPCollection(settings.Split(','), true);
|
||||||
Assert.Equal(nc.AsString(), result4);
|
Assert.Equal(nc.AsString(), result4);
|
||||||
|
|
||||||
conf.EnableIPV6 = true;
|
conf.EnableIPV6 = true;
|
||||||
nm.UpdateSettings(conf);
|
nm.UpdateSettings(conf);
|
||||||
|
|
||||||
// Test network addresses of collection.
|
// Test network addresses of collection.
|
||||||
nc = nm.CreateIPCollection(settings.Split(","), false);
|
nc = nm.CreateIPCollection(settings.Split(','), false);
|
||||||
nc = nc.AsNetworks();
|
nc = nc.AsNetworks();
|
||||||
Assert.Equal(nc.AsString(), result5);
|
Assert.Equal(nc.AsString(), result5);
|
||||||
}
|
}
|
||||||
@@ -261,8 +263,8 @@ namespace Jellyfin.Networking.Tests
|
|||||||
|
|
||||||
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
|
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
|
||||||
|
|
||||||
Collection<IPObject> nc1 = nm.CreateIPCollection(settings.Split(","), false);
|
Collection<IPObject> nc1 = nm.CreateIPCollection(settings.Split(','), false);
|
||||||
Collection<IPObject> nc2 = nm.CreateIPCollection(compare.Split(","), false);
|
Collection<IPObject> nc2 = nm.CreateIPCollection(compare.Split(','), false);
|
||||||
|
|
||||||
Assert.Equal(nc1.Union(nc2).AsString(), result);
|
Assert.Equal(nc1.Union(nc2).AsString(), result);
|
||||||
}
|
}
|
||||||
@@ -371,10 +373,10 @@ namespace Jellyfin.Networking.Tests
|
|||||||
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
|
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
|
||||||
|
|
||||||
// Test included, IP6.
|
// Test included, IP6.
|
||||||
Collection<IPObject> ncSource = nm.CreateIPCollection(source.Split(","));
|
Collection<IPObject> ncSource = nm.CreateIPCollection(source.Split(','));
|
||||||
Collection<IPObject> ncDest = nm.CreateIPCollection(dest.Split(","));
|
Collection<IPObject> ncDest = nm.CreateIPCollection(dest.Split(','));
|
||||||
Collection<IPObject> ncResult = ncSource.Union(ncDest);
|
Collection<IPObject> ncResult = ncSource.Union(ncDest);
|
||||||
Collection<IPObject> resultCollection = nm.CreateIPCollection(result.Split(","));
|
Collection<IPObject> resultCollection = nm.CreateIPCollection(result.Split(','));
|
||||||
Assert.True(ncResult.Compare(resultCollection));
|
Assert.True(ncResult.Compare(resultCollection));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -515,5 +517,45 @@ namespace Jellyfin.Networking.Tests
|
|||||||
|
|
||||||
Assert.Equal(intf, result);
|
Assert.Equal(intf, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("185.10.10.10,200.200.200.200", "79.2.3.4", true)]
|
||||||
|
[InlineData("185.10.10.10", "185.10.10.10", false)]
|
||||||
|
[InlineData("", "100.100.100.100", false)]
|
||||||
|
|
||||||
|
public void HasRemoteAccess_GivenWhitelist_AllowsOnlyIpsInWhitelist(string addresses, string remoteIp, bool denied)
|
||||||
|
{
|
||||||
|
// Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely.
|
||||||
|
// If left blank, all remote addresses will be allowed.
|
||||||
|
var conf = new NetworkConfiguration()
|
||||||
|
{
|
||||||
|
EnableIPV4 = true,
|
||||||
|
RemoteIPFilter = addresses.Split(','),
|
||||||
|
IsRemoteIPFilterBlacklist = false
|
||||||
|
};
|
||||||
|
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
|
||||||
|
|
||||||
|
Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIp)), denied);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("185.10.10.10", "79.2.3.4", false)]
|
||||||
|
[InlineData("185.10.10.10", "185.10.10.10", true)]
|
||||||
|
[InlineData("", "100.100.100.100", false)]
|
||||||
|
public void HasRemoteAccess_GivenBlacklist_BlacklistTheIps(string addresses, string remoteIp, bool denied)
|
||||||
|
{
|
||||||
|
// Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely.
|
||||||
|
// If left blank, all remote addresses will be allowed.
|
||||||
|
var conf = new NetworkConfiguration()
|
||||||
|
{
|
||||||
|
EnableIPV4 = true,
|
||||||
|
RemoteIPFilter = addresses.Split(','),
|
||||||
|
IsRemoteIPFilterBlacklist = true
|
||||||
|
};
|
||||||
|
|
||||||
|
using var nm = new NetworkManager(GetMockConfig(conf), new NullLogger<NetworkManager>());
|
||||||
|
|
||||||
|
Assert.NotEqual(nm.HasRemoteAccess(IPAddress.Parse(remoteIp)), denied);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user