Compare commits

...

38 Commits

Author SHA1 Message Date
Joshua M. Boniface 69f30bc52c Merge pull request #5782 from cvium/fix-release-10.7.2
Fix 10.7.2 nullable
2021-04-11 16:31:26 -04:00
cvium 3b605b6280 fix 10.7.2 nullable 2021-04-11 22:19:59 +02:00
Joshua M. Boniface e8a359f97b Bump version 10.7.2 2021-04-11 14:19:21 -04:00
Bond-009 dbfaafc08a Merge pull request #5596 from BaronGreenback/DLNA_Hardening
Implemented DLNA exception handling

(cherry picked from commit 55102973d6)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:17:47 -04:00
Bond-009 de6747f6c5 Merge pull request #5385 from Bond-009/dlna2
Use XDocument.LoadAsync instead of XDocument.Parse

(cherry picked from commit a1cdc2c63f)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:17:46 -04:00
Bond-009 100fe40b0a Merge pull request #5769 from cvium/workstation-gc
Enable Workstation GC mode

(cherry picked from commit f0625bb023)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Joshua M. Boniface 2197d20783 Merge pull request #5764 from cvium/fix-folders-perms
Do not check permissions for Folders collectiontype

(cherry picked from commit 770c123d12)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Claus Vium f4f9ab777f Merge pull request #5750 from iwalton3/fix-audio-selection-uns
Fix setting audio stream in PlaybackInfo for jellyfin-web.

(cherry picked from commit bf510ca04e)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Joshua M. Boniface 9ca7d62709 Merge pull request #5748 from cvium/playlist-audio-type
Set mediatype to Audio for playlists in a music library

(cherry picked from commit cd0daa7985)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Bond-009 7aad16b6ec Merge pull request #5747 from cvium/more-convertimage-fixes
Catch IOException and include stack trace when saving images

(cherry picked from commit 240e67d485)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Bond-009 f77673438e Merge pull request #5746 from cvium/dangling-symlinks
(cherry picked from commit 62117a6c12)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Bond-009 bc2eb9fa79 Merge pull request #5736 from jellyfin/catch-httprequestex-librarymanager
fetching images should not kill the scanner

(cherry picked from commit 6577f1deb2)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Claus Vium df69ce55f7 Merge pull request #5734 from jellyfin/fix-isplayed-itemvalues
move IsPlayed to outerquery

(cherry picked from commit d532e95410)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Claus Vium 93cca4d50e Merge pull request #5725 from BrianCArnold/Fix2845_PlaylistDeletion
(cherry picked from commit 9d0467addf)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Bond-009 3c64bcffe3 Merge pull request #5717 from cvium/nullable-custompref-value
(cherry picked from commit 47bbe4c146)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Claus Vium 6ece01d425 Merge pull request #5712 from BaronGreenback/5700
(cherry picked from commit 1a92d94e92)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Bond-009 53f333bd64 Merge pull request #5702 from cvium/ws-auth
add simple auth handling to websocketmanager

(cherry picked from commit 3412120c61)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Bond-009 9e459090ed Merge pull request #5693 from Maxr1998/probe-result-tweaks
(cherry picked from commit 7978f30ff7)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Claus Vium 95a4fc0f18 Merge pull request #5688 from crobibero/api-docs-sever-discovery
Add SessionDiscoveryInfo to generated api-docs

(cherry picked from commit f718735b4e)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Bond-009 62bf3db885 Merge pull request #5672 from jellyfin/skip-bad-images
ensure only valid images are saved in ItemImageProvider

(cherry picked from commit 38913a42b4)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Bond-009 42d702c091 Merge pull request #5671 from jellyfin/tmdbmovieprovider-originaltitle
set original title in tmdbmovieprovider

(cherry picked from commit 7c51d0a50e)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Bond-009 d07fe14814 Merge pull request #5661 from ferferga/openapi-product-version
Return Major.Minor.Build instead of Major.Minor.Build.Revision for OpenAPI

(cherry picked from commit cb111eb767)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Bond-009 970eaf8dfb Merge pull request #5634 from cvium/directoryservice-case-sensitive
make directoryservice cache case sensitive

(cherry picked from commit 1de031a7c3)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Claus Vium bc27c2b7da Merge pull request #5631 from BrianCArnold/FixMessageCommand
(cherry picked from commit a1718e392b)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Claus Vium 37b969304a Merge pull request #5629 from lmaonator/fix-cast-stream-selection
(cherry picked from commit 90d9530aed)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Claus Vium c6b5c4dda5 Merge pull request #5624 from crobibero/subtitle-format
(cherry picked from commit 9144d11a9d)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Claus Vium 51f5da8015 Merge pull request #5621 from cvium/enable-range-processing-download
enable range processing for download endpoints

(cherry picked from commit 411570e6d4)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Claus Vium 6e89ca9a34 Merge pull request #5620 from MrTimscampi/iso-ignore
(cherry picked from commit a76d997a86)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Claus Vium de1896828f Merge pull request #5613 from accek/accek-samsung-dlna-fix
(cherry picked from commit e64f9f2f66)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Bond-009 7d1d159b8a Merge pull request #5556 from oddstr13/image-fill-resize
(cherry picked from commit 790f7430aa)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Bond-009 c3c98331d9 Merge pull request #5495 from BaronGreenback/RemoteAccessFix
(cherry picked from commit a890a85092)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Joshua M. Boniface e78fa8c3ef Merge pull request #5416 from BaronGreenback/SubnetOverlappFix
(cherry picked from commit 19e7ebb279)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Bond-009 d63fb437c6 Merge pull request #5258 from Smith00101010/next-up-specials-fix
(cherry picked from commit 9d548c62ba)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2021-04-11 14:13:44 -04:00
Claus Vium 25c6388e23 Merge pull request #5600 from cvium/fix-hls-defaults-10.7
Fix hls defaults for 10.7
2021-03-28 00:15:57 +01:00
Claus Vium 8f16e10fc6 Apply suggestions from code review 2021-03-25 08:44:34 +01:00
cvium 1f07586d1c forgot streaminfo 2021-03-22 23:11:25 +01:00
cvium 5e0f480e48 fix build and isdirectstream 2021-03-22 23:08:09 +01:00
cvium 210d10400a change HLS endpoint defaults to false 2021-03-22 23:05:05 +01:00
74 changed files with 1508 additions and 411 deletions
+1
View File
@@ -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)
+33 -8
View File
@@ -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>();
+1 -5
View File
@@ -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");
+5
View File
@@ -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;
+15 -8
View File
@@ -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(
+2 -4
View File
@@ -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)
{ {
+96 -17
View File
@@ -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 />
+1 -1
View File
@@ -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;
+12 -12
View File
@@ -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.");
} }
+100 -8
View File
@@ -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,
+10 -4
View File
@@ -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>
+19 -13
View File
@@ -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);
+47 -19
View File
@@ -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,
+6 -6
View File
@@ -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 };
+4
View File
@@ -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;
} }
} }
} }
+1 -1
View File
@@ -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>
+53 -19
View File
@@ -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);
@@ -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>();
}); });
} }
@@ -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);
} }
} }
} }
+1
View File
@@ -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);
} }
} }
+7 -4
View File
@@ -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.
+6 -60
View File
@@ -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)
+1 -3
View File
@@ -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>
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+6
View File
@@ -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
+5
View File
@@ -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
# #
+1 -1
View File
@@ -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
+4
View File
@@ -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
+3 -1
View File
@@ -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>
-55
View File
@@ -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);
}
} }
} }