aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid <daullmer@gmail.com>2021-02-13 22:55:33 +0100
committerDavid <daullmer@gmail.com>2021-02-13 22:55:33 +0100
commitfc7377fb9bf2b227a8a2300e6909d7c74862c8ab (patch)
tree667966225e0aa2a4dce983f744f07581e5bfd37f
parent7f12b273197204c49862623b6de6b1a573af2cd8 (diff)
parent6616add8c8526456e343d7b576d22df0ed261124 (diff)
Merge remote-tracking branch 'jellyfin/master' into nfo-tests
# Conflicts: # tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs # tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
-rw-r--r--.ci/azure-pipelines-abi.yml2
-rw-r--r--.ci/azure-pipelines-api-client.yml2
-rw-r--r--.ci/azure-pipelines-main.yml2
-rw-r--r--.ci/azure-pipelines-test.yml2
-rw-r--r--.ci/azure-pipelines.yml2
-rw-r--r--Emby.Dlna/Profiles/SonyBravia2010Profile.cs4
-rw-r--r--Emby.Dlna/Profiles/SonyBravia2011Profile.cs4
-rw-r--r--Emby.Dlna/Profiles/SonyBravia2012Profile.cs4
-rw-r--r--Emby.Dlna/Profiles/SonyBravia2013Profile.cs4
-rw-r--r--Emby.Dlna/Profiles/SonyBravia2014Profile.cs4
-rw-r--r--Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml4
-rw-r--r--Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml4
-rw-r--r--Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml4
-rw-r--r--Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml4
-rw-r--r--Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml4
-rw-r--r--Emby.Naming/Common/NamingOptions.cs4
-rw-r--r--Emby.Naming/Emby.Naming.csproj2
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs4
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketConnection.cs52
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs2
-rw-r--r--Emby.Server.Implementations/Localization/Core/bg-BG.json22
-rw-r--r--Emby.Server.Implementations/Localization/Core/eo.json26
-rw-r--r--Emby.Server.Implementations/Localization/Core/fi.json148
-rw-r--r--Emby.Server.Implementations/Localization/Core/hi.json4
-rw-r--r--Emby.Server.Implementations/Plugins/PluginManager.cs4
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs7
-rw-r--r--Jellyfin.Api/Attributes/AcceptsFileAttribute.cs28
-rw-r--r--Jellyfin.Api/Attributes/AcceptsImageFileAttribute.cs18
-rw-r--r--Jellyfin.Api/Controllers/ArtistsController.cs18
-rw-r--r--Jellyfin.Api/Controllers/CollectionController.cs1
-rw-r--r--Jellyfin.Api/Controllers/DashboardController.cs6
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/FilterController.cs37
-rw-r--r--Jellyfin.Api/Controllers/GenresController.cs9
-rw-r--r--Jellyfin.Api/Controllers/HlsSegmentController.cs2
-rw-r--r--Jellyfin.Api/Controllers/ImageController.cs4
-rw-r--r--Jellyfin.Api/Controllers/InstantMixController.cs1
-rw-r--r--Jellyfin.Api/Controllers/ItemLookupController.cs2
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs27
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs1
-rw-r--r--Jellyfin.Api/Controllers/MusicGenresController.cs9
-rw-r--r--Jellyfin.Api/Controllers/PackageController.cs1
-rw-r--r--Jellyfin.Api/Controllers/PersonsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/PlaylistsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/PluginsController.cs6
-rw-r--r--Jellyfin.Api/Controllers/SearchController.cs9
-rw-r--r--Jellyfin.Api/Controllers/StudiosController.cs9
-rw-r--r--Jellyfin.Api/Controllers/SuggestionsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/SyncPlayController.cs1
-rw-r--r--Jellyfin.Api/Controllers/SystemController.cs1
-rw-r--r--Jellyfin.Api/Controllers/TimeSyncController.cs1
-rw-r--r--Jellyfin.Api/Controllers/TrailersController.cs4
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/UserLibraryController.cs5
-rw-r--r--Jellyfin.Api/Controllers/UserViewsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/VideoHlsController.cs1
-rw-r--r--Jellyfin.Api/Controllers/YearsController.cs15
-rw-r--r--Jellyfin.Api/Helpers/RequestHelpers.cs16
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj10
-rw-r--r--Jellyfin.Data/Enums/BaseItemKind.cs190
-rw-r--r--Jellyfin.Data/Jellyfin.Data.csproj6
-rw-r--r--Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj4
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs1
-rw-r--r--Jellyfin.Server/Filters/FileRequestFilter.cs43
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj4
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj2
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs5
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs708
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs8
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs32
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs11
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/AssParser.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs2
-rw-r--r--MediaBrowser.Model/Configuration/EncodingOptions.cs6
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs7
-rw-r--r--MediaBrowser.Model/Dto/BaseItemDto.cs3
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj2
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs14
-rw-r--r--SharedVersion.cs4
-rw-r--r--build.yaml2
-rw-r--r--debian/changelog6
-rw-r--r--debian/metapackage/jellyfin2
-rw-r--r--deployment/Dockerfile.debian.amd642
-rw-r--r--deployment/Dockerfile.debian.arm642
-rw-r--r--deployment/Dockerfile.debian.armhf2
-rw-r--r--deployment/Dockerfile.linux.amd642
-rw-r--r--deployment/Dockerfile.linux.amd64-musl2
-rw-r--r--deployment/Dockerfile.linux.arm642
-rw-r--r--deployment/Dockerfile.linux.armhf2
-rw-r--r--deployment/Dockerfile.macos2
-rw-r--r--deployment/Dockerfile.portable2
-rw-r--r--deployment/Dockerfile.ubuntu.amd642
-rw-r--r--deployment/Dockerfile.ubuntu.arm642
-rw-r--r--deployment/Dockerfile.ubuntu.armhf2
-rw-r--r--deployment/Dockerfile.windows.amd642
-rw-r--r--fedora/jellyfin.spec4
-rw-r--r--tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs34
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj2
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs69
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj11
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs12
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs45
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ForceKeepAlive.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/Partial.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ValidPartial.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/discover.json (renamed from tests/Jellyfin.Server.Implementations.Tests/LiveTv/discover.json)0
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/lineup.json (renamed from tests/Jellyfin.Server.Implementations.Tests/LiveTv/lineup.json)0
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj1
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs10
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs11
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs9
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs8
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs4
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo1
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Bone Orchard.nfo1
117 files changed, 1284 insertions, 611 deletions
diff --git a/.ci/azure-pipelines-abi.yml b/.ci/azure-pipelines-abi.yml
index 14df7e7c8..8d0737b66 100644
--- a/.ci/azure-pipelines-abi.yml
+++ b/.ci/azure-pipelines-abi.yml
@@ -7,7 +7,7 @@ parameters:
default: "ubuntu-latest"
- name: DotNetSdkVersion
type: string
- default: 5.0.100
+ default: 5.0.103
jobs:
- job: CompatibilityCheck
diff --git a/.ci/azure-pipelines-api-client.yml b/.ci/azure-pipelines-api-client.yml
index 177f78889..0e944e6f4 100644
--- a/.ci/azure-pipelines-api-client.yml
+++ b/.ci/azure-pipelines-api-client.yml
@@ -4,7 +4,7 @@
default: "ubuntu-latest"
- name: GeneratorVersion
type: string
- default: "5.0.0-beta2"
+ default: "5.0.1"
jobs:
- job: GenerateApiClients
diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml
index 95dd3ccac..4bc72f9eb 100644
--- a/.ci/azure-pipelines-main.yml
+++ b/.ci/azure-pipelines-main.yml
@@ -1,7 +1,7 @@
parameters:
LinuxImage: 'ubuntu-latest'
RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
- DotNetSdkVersion: 5.0.100
+ DotNetSdkVersion: 5.0.103
jobs:
- job: Build
diff --git a/.ci/azure-pipelines-test.yml b/.ci/azure-pipelines-test.yml
index 36152c82a..95e0d8c58 100644
--- a/.ci/azure-pipelines-test.yml
+++ b/.ci/azure-pipelines-test.yml
@@ -10,7 +10,7 @@ parameters:
default: "tests/**/*Tests.csproj"
- name: DotNetSdkVersion
type: string
- default: 5.0.100
+ default: 5.0.103
jobs:
- job: Test
diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml
index ec4c25435..6430503f9 100644
--- a/.ci/azure-pipelines.yml
+++ b/.ci/azure-pipelines.yml
@@ -6,7 +6,7 @@ variables:
- name: RestoreBuildProjects
value: 'Jellyfin.Server/Jellyfin.Server.csproj'
- name: DotNetSdkVersion
- value: 5.0.100
+ value: 5.0.103
pr:
autoCancel: true
diff --git a/Emby.Dlna/Profiles/SonyBravia2010Profile.cs b/Emby.Dlna/Profiles/SonyBravia2010Profile.cs
index 8ab4acd1b..9f0d82b8f 100644
--- a/Emby.Dlna/Profiles/SonyBravia2010Profile.cs
+++ b/Emby.Dlna/Profiles/SonyBravia2010Profile.cs
@@ -13,7 +13,7 @@ namespace Emby.Dlna.Profiles
Identification = new DeviceIdentification
{
- FriendlyName = @"KDL-\d{2}[EHLNPB]X\d[01]\d.*",
+ FriendlyName = @"KDL-[0-9]{2}[EHLNPB]X[0-9][01][0-9].*",
Manufacturer = "Sony",
Headers = new[]
@@ -21,7 +21,7 @@ namespace Emby.Dlna.Profiles
new HttpHeaderInfo
{
Name = "X-AV-Client-Info",
- Value = @".*KDL-\d{2}[EHLNPB]X\d[01]\d.*",
+ Value = @".*KDL-[0-9]{2}[EHLNPB]X[0-9][01][0-9].*",
Match = HeaderMatchType.Regex
}
}
diff --git a/Emby.Dlna/Profiles/SonyBravia2011Profile.cs b/Emby.Dlna/Profiles/SonyBravia2011Profile.cs
index 42d253394..dfb91817a 100644
--- a/Emby.Dlna/Profiles/SonyBravia2011Profile.cs
+++ b/Emby.Dlna/Profiles/SonyBravia2011Profile.cs
@@ -13,7 +13,7 @@ namespace Emby.Dlna.Profiles
Identification = new DeviceIdentification
{
- FriendlyName = @"KDL-\d{2}([A-Z]X\d2\d|CX400).*",
+ FriendlyName = @"KDL-[0-9]{2}([A-Z]X[0-9]2[0-9]|CX400).*",
Manufacturer = "Sony",
Headers = new[]
@@ -21,7 +21,7 @@ namespace Emby.Dlna.Profiles
new HttpHeaderInfo
{
Name = "X-AV-Client-Info",
- Value = @".*KDL-\d{2}([A-Z]X\d2\d|CX400).*",
+ Value = @".*KDL-[0-9]{2}([A-Z]X[0-9]2[0-9]|CX400).*",
Match = HeaderMatchType.Regex
}
}
diff --git a/Emby.Dlna/Profiles/SonyBravia2012Profile.cs b/Emby.Dlna/Profiles/SonyBravia2012Profile.cs
index 0598e8342..d59ee38d7 100644
--- a/Emby.Dlna/Profiles/SonyBravia2012Profile.cs
+++ b/Emby.Dlna/Profiles/SonyBravia2012Profile.cs
@@ -13,7 +13,7 @@ namespace Emby.Dlna.Profiles
Identification = new DeviceIdentification
{
- FriendlyName = @"KDL-\d{2}[A-Z]X\d5(\d|G).*",
+ FriendlyName = @"KDL-[0-9]{2}[A-Z]X[0-9]5([0-9]|G).*",
Manufacturer = "Sony",
Headers = new[]
@@ -21,7 +21,7 @@ namespace Emby.Dlna.Profiles
new HttpHeaderInfo
{
Name = "X-AV-Client-Info",
- Value = @".*KDL-\d{2}[A-Z]X\d5(\d|G).*",
+ Value = @".*KDL-[0-9]{2}[A-Z]X[0-9]5([0-9]|G).*",
Match = HeaderMatchType.Regex
}
}
diff --git a/Emby.Dlna/Profiles/SonyBravia2013Profile.cs b/Emby.Dlna/Profiles/SonyBravia2013Profile.cs
index 3d90a1e72..73b0fd67e 100644
--- a/Emby.Dlna/Profiles/SonyBravia2013Profile.cs
+++ b/Emby.Dlna/Profiles/SonyBravia2013Profile.cs
@@ -13,7 +13,7 @@ namespace Emby.Dlna.Profiles
Identification = new DeviceIdentification
{
- FriendlyName = @"KDL-\d{2}[WR][5689]\d{2}A.*",
+ FriendlyName = @"KDL-[0-9]{2}[WR][5689][0-9]{2}A.*",
Manufacturer = "Sony",
Headers = new[]
@@ -21,7 +21,7 @@ namespace Emby.Dlna.Profiles
new HttpHeaderInfo
{
Name = "X-AV-Client-Info",
- Value = @".*KDL-\d{2}[WR][5689]\d{2}A.*",
+ Value = @".*KDL-[0-9]{2}[WR][5689][0-9]{2}A.*",
Match = HeaderMatchType.Regex
}
}
diff --git a/Emby.Dlna/Profiles/SonyBravia2014Profile.cs b/Emby.Dlna/Profiles/SonyBravia2014Profile.cs
index 9188f73ef..db8ee5750 100644
--- a/Emby.Dlna/Profiles/SonyBravia2014Profile.cs
+++ b/Emby.Dlna/Profiles/SonyBravia2014Profile.cs
@@ -13,7 +13,7 @@ namespace Emby.Dlna.Profiles
Identification = new DeviceIdentification
{
- FriendlyName = @"(KDL-\d{2}W[5-9]\d{2}B|KDL-\d{2}R480|XBR-\d{2}X[89]\d{2}B|KD-\d{2}[SX][89]\d{3}B).*",
+ FriendlyName = @"(KDL-[0-9]{2}W[5-9][0-9]{2}B|KDL-[0-9]{2}R480|XBR-[0-9]{2}X[89][0-9]{2}B|KD-[0-9]{2}[SX][89][0-9]{3}B).*",
Manufacturer = "Sony",
Headers = new[]
@@ -21,7 +21,7 @@ namespace Emby.Dlna.Profiles
new HttpHeaderInfo
{
Name = "X-AV-Client-Info",
- Value = @".*(KDL-\d{2}W[5-9]\d{2}B|KDL-\d{2}R480|XBR-\d{2}X[89]\d{2}B|KD-\d{2}[SX][89]\d{3}B).*",
+ Value = @".*(KDL-[0-9]{2}W[5-9][0-9]{2}B|KDL-[0-9]{2}R480|XBR-[0-9]{2}X[89][0-9]{2}B|KD-[0-9]{2}[SX][89][0-9]{3}B).*",
Match = HeaderMatchType.Regex
}
}
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml
index f20e9fcb6..1461db311 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml
@@ -3,10 +3,10 @@
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Sony Bravia (2010)</Name>
<Identification>
- <FriendlyName>KDL-\d{2}[EHLNPB]X\d[01]\d.*</FriendlyName>
+ <FriendlyName>KDL-[0-9]{2}[EHLNPB]X[0-9][01][0-9].*</FriendlyName>
<Manufacturer>Sony</Manufacturer>
<Headers>
- <HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-\d{2}[EHLNPB]X\d[01]\d.*" match="Regex" />
+ <HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-[0-9]{2}[EHLNPB]X[0-9][01][0-9].*" match="Regex" />
</Headers>
</Identification>
<Manufacturer>Microsoft Corporation</Manufacturer>
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml
index e516ff512..7c5f2b181 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml
@@ -3,10 +3,10 @@
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Sony Bravia (2011)</Name>
<Identification>
- <FriendlyName>KDL-\d{2}([A-Z]X\d2\d|CX400).*</FriendlyName>
+ <FriendlyName>KDL-[0-9]{2}([A-Z]X[0-9]2[0-9]|CX400).*</FriendlyName>
<Manufacturer>Sony</Manufacturer>
<Headers>
- <HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-\d{2}([A-Z]X\d2\d|CX400).*" match="Regex" />
+ <HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-[0-9]{2}([A-Z]X[0-9]2[0-9]|CX400).*" match="Regex" />
</Headers>
</Identification>
<Manufacturer>Microsoft Corporation</Manufacturer>
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml
index 88bd1c2f5..842a8fba3 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml
@@ -3,10 +3,10 @@
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Sony Bravia (2012)</Name>
<Identification>
- <FriendlyName>KDL-\d{2}[A-Z]X\d5(\d|G).*</FriendlyName>
+ <FriendlyName>KDL-[0-9]{2}[A-Z]X[0-9]5([0-9]|G).*</FriendlyName>
<Manufacturer>Sony</Manufacturer>
<Headers>
- <HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-\d{2}[A-Z]X\d5(\d|G).*" match="Regex" />
+ <HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-[0-9]{2}[A-Z]X[0-9]5([0-9]|G).*" match="Regex" />
</Headers>
</Identification>
<Manufacturer>Microsoft Corporation</Manufacturer>
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml
index 3ca9893cd..f1135c3fe 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml
@@ -3,10 +3,10 @@
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Sony Bravia (2013)</Name>
<Identification>
- <FriendlyName>KDL-\d{2}[WR][5689]\d{2}A.*</FriendlyName>
+ <FriendlyName>KDL-[0-9]{2}[WR][5689][0-9]{2}A.*</FriendlyName>
<Manufacturer>Sony</Manufacturer>
<Headers>
- <HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-\d{2}[WR][5689]\d{2}A.*" match="Regex" />
+ <HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-[0-9]{2}[WR][5689][0-9]{2}A.*" match="Regex" />
</Headers>
</Identification>
<Manufacturer>Microsoft Corporation</Manufacturer>
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml
index 8804a75df..85c7868c6 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml
@@ -3,10 +3,10 @@
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Sony Bravia (2014)</Name>
<Identification>
- <FriendlyName>(KDL-\d{2}W[5-9]\d{2}B|KDL-\d{2}R480|XBR-\d{2}X[89]\d{2}B|KD-\d{2}[SX][89]\d{3}B).*</FriendlyName>
+ <FriendlyName>(KDL-[0-9]{2}W[5-9][0-9]{2}B|KDL-[0-9]{2}R480|XBR-[0-9]{2}X[89][0-9]{2}B|KD-[0-9]{2}[SX][89][0-9]{3}B).*</FriendlyName>
<Manufacturer>Sony</Manufacturer>
<Headers>
- <HttpHeaderInfo name="X-AV-Client-Info" value=".*(KDL-\d{2}W[5-9]\d{2}B|KDL-\d{2}R480|XBR-\d{2}X[89]\d{2}B|KD-\d{2}[SX][89]\d{3}B).*" match="Regex" />
+ <HttpHeaderInfo name="X-AV-Client-Info" value=".*(KDL-[0-9]{2}W[5-9][0-9]{2}B|KDL-[0-9]{2}R480|XBR-[0-9]{2}X[89][0-9]{2}B|KD-[0-9]{2}[SX][89][0-9]{3}B).*" match="Regex" />
</Headers>
</Identification>
<Manufacturer>Microsoft Corporation</Manufacturer>
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index ba4446ff1..22a3e8bb4 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -284,7 +284,7 @@ namespace Emby.Naming.Common
// Not a Kodi rule as well, but below rule also causes false positives for triple-digit episode names
// [bar] Foo - 1 [baz] special case of below expression to prevent false positives with digits in the series name
- new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[\s_]*-[\s_]*(?<epnumber>\d+).*$")
+ new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[\s_]*-[\s_]*(?<epnumber>[0-9]+).*$")
{
IsNamed = true
},
@@ -588,7 +588,7 @@ namespace Emby.Naming.Common
AudioBookNamesExpressions = new[]
{
// Detect year usually in brackets after name Batman (2020)
- @"^(?<name>.+?)\s*\(\s*(?<year>\d{4})\s*\)\s*$",
+ @"^(?<name>.+?)\s*\(\s*(?<year>[0-9]{4})\s*\)\s*$",
@"^\s*(?<name>[^ ].*?)\s*$"
};
diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj
index 24c15759d..b43203e9d 100644
--- a/Emby.Naming/Emby.Naming.csproj
+++ b/Emby.Naming/Emby.Naming.csproj
@@ -33,7 +33,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Naming</PackageId>
- <VersionPrefix>10.7.0</VersionPrefix>
+ <VersionPrefix>10.8.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index e3ab0d6ea..54b18a8c8 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -249,7 +249,7 @@ namespace Emby.Server.Implementations.Dto
var activeRecording = liveTvManager.GetActiveRecordingInfo(item.Path);
if (activeRecording != null)
{
- dto.Type = "Recording";
+ dto.Type = BaseItemKind.Recording;
dto.CanDownload = false;
dto.RunTimeTicks = null;
@@ -904,7 +904,7 @@ namespace Emby.Server.Implementations.Dto
}
}
- dto.Type = item.GetClientTypeName();
+ dto.Type = item.GetBaseItemKind();
if ((item.CommunityRating ?? 0) > 0)
{
dto.CommunityRating = item.CommunityRating;
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index fed2addf8..7e0c2c1da 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -5,6 +5,7 @@ using System.Buffers;
using System.IO.Pipelines;
using System.Net;
using System.Net.WebSockets;
+using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -138,7 +139,7 @@ namespace Emby.Server.Implementations.HttpServer
writer.Advance(bytesRead);
// Make the data available to the PipeReader
- FlushResult flushResult = await writer.FlushAsync().ConfigureAwait(false);
+ FlushResult flushResult = await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
if (flushResult.IsCompleted)
{
// The PipeReader stopped reading
@@ -181,32 +182,16 @@ namespace Emby.Server.Implementations.HttpServer
}
WebSocketMessage<object>? stub;
+ long bytesConsumed = 0;
try
{
-
- if (buffer.IsSingleSegment)
- {
- stub = JsonSerializer.Deserialize<WebSocketMessage<object>>(buffer.FirstSpan, _jsonOptions);
- }
- else
- {
- var buf = ArrayPool<byte>.Shared.Rent(Convert.ToInt32(buffer.Length));
- try
- {
- buffer.CopyTo(buf);
- stub = JsonSerializer.Deserialize<WebSocketMessage<object>>(buf, _jsonOptions);
- }
- finally
- {
- ArrayPool<byte>.Shared.Return(buf);
- }
- }
+ stub = DeserializeWebSocketMessage(buffer, out bytesConsumed);
}
catch (JsonException ex)
{
// Tell the PipeReader how much of the buffer we have consumed
reader.AdvanceTo(buffer.End);
- _logger.LogError(ex, "Error processing web socket message");
+ _logger.LogError(ex, "Error processing web socket message: {Data}", Encoding.UTF8.GetString(buffer));
return;
}
@@ -217,27 +202,34 @@ namespace Emby.Server.Implementations.HttpServer
}
// Tell the PipeReader how much of the buffer we have consumed
- reader.AdvanceTo(buffer.End);
+ reader.AdvanceTo(buffer.GetPosition(bytesConsumed));
_logger.LogDebug("WS {IP} received message: {@Message}", RemoteEndPoint, stub);
- var info = new WebSocketMessageInfo
- {
- MessageType = stub.MessageType,
- Data = stub.Data?.ToString(), // Data can be null
- Connection = this
- };
-
- if (info.MessageType == SessionMessageType.KeepAlive)
+ if (stub.MessageType == SessionMessageType.KeepAlive)
{
await SendKeepAliveResponse().ConfigureAwait(false);
}
else
{
- await OnReceive(info).ConfigureAwait(false);
+ await OnReceive(
+ new WebSocketMessageInfo
+ {
+ MessageType = stub.MessageType,
+ Data = stub.Data?.ToString(), // Data can be null
+ Connection = this
+ }).ConfigureAwait(false);
}
}
+ internal WebSocketMessage<object>? DeserializeWebSocketMessage(ReadOnlySequence<byte> bytes, out long bytesConsumed)
+ {
+ var jsonReader = new Utf8JsonReader(bytes);
+ var ret = JsonSerializer.Deserialize<WebSocketMessage<object>>(ref jsonReader, _jsonOptions);
+ bytesConsumed = jsonReader.BytesConsumed;
+ return ret;
+ }
+
private Task SendKeepAliveResponse()
{
LastKeepAliveDate = DateTime.UtcNow;
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
index e9e688fa6..60f82806f 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
@@ -79,11 +79,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return new MusicArtist();
}
- if (_config.Configuration.EnableSimpleArtistDetection)
- {
- return null;
- }
-
// Avoid mis-identifying top folders
if (args.Parent.IsRoot)
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index cdc8c6870..f09338330 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public LegacyHdHomerunChannelCommands(string url)
{
// parse url for channel and program
- var regExp = new Regex(@"\/ch(\d+)-?(\d*)");
+ var regExp = new Regex(@"\/ch([0-9]+)-?([0-9]*)");
var match = regExp.Match(url);
if (match.Success)
{
diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json
index 7ff30df71..9db3b50d9 100644
--- a/Emby.Server.Implementations/Localization/Core/bg-BG.json
+++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json
@@ -55,26 +55,26 @@
"NotificationOptionPluginInstalled": "Приставката е инсталирана",
"NotificationOptionPluginUninstalled": "Приставката е деинсталирана",
"NotificationOptionPluginUpdateInstalled": "Обновлението на приставката е инсталирано",
- "NotificationOptionServerRestartRequired": "Нужно е повторно пускане на сървъра",
+ "NotificationOptionServerRestartRequired": "Сървърът трябва да се рестартира",
"NotificationOptionTaskFailed": "Грешка в планирана задача",
- "NotificationOptionUserLockedOut": "Потребителя е заключен",
+ "NotificationOptionUserLockedOut": "Потребителят е заключен",
"NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна",
"NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно",
"Photos": "Снимки",
"Playlists": "Списъци",
"Plugin": "Приставка",
- "PluginInstalledWithName": "{0} е инсталирано",
- "PluginUninstalledWithName": "{0} е деинсталирано",
- "PluginUpdatedWithName": "{0} е обновено",
+ "PluginInstalledWithName": "{0} е инсталиранa",
+ "PluginUninstalledWithName": "{0} е деинсталиранa",
+ "PluginUpdatedWithName": "{0} е обновенa",
"ProviderValue": "Доставчик: {0}",
"ScheduledTaskFailedWithName": "{0} се провали",
"ScheduledTaskStartedWithName": "{0} започна",
- "ServerNameNeedsToBeRestarted": "{0} е нужно да се рестартира",
+ "ServerNameNeedsToBeRestarted": "{0} трябва да се рестартира",
"Shows": "Сериали",
"Songs": "Песни",
"StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.",
"SubtitleDownloadFailureForItem": "Неуспешно изтегляне на субтитри за {0}",
- "SubtitleDownloadFailureFromForItem": "Поднадписите за {1} от {0} не можаха да се изтеглят",
+ "SubtitleDownloadFailureFromForItem": "Субтитрите за {1} от {0} не можаха да бъдат изтеглени",
"Sync": "Синхронизиране",
"System": "Система",
"TvShows": "Телевизионни сериали",
@@ -92,12 +92,12 @@
"ValueHasBeenAddedToLibrary": "{0} беше добавен във Вашата библиотека",
"ValueSpecialEpisodeName": "Специални - {0}",
"VersionNumber": "Версия {0}",
- "TaskDownloadMissingSubtitlesDescription": "Търси Интернет за липсващи поднадписи, на база конфигурацията за мета-данни.",
- "TaskDownloadMissingSubtitles": "Изтегляне на липсващи поднадписи",
+ "TaskDownloadMissingSubtitlesDescription": "Търси Интернет за липсващи субтитри, на база конфигурацията за мета-данни.",
+ "TaskDownloadMissingSubtitles": "Изтегляне на липсващи субтитри",
"TaskRefreshChannelsDescription": "Обновява информацията за интернет канала.",
"TaskRefreshChannels": "Обновяване на Канали",
- "TaskCleanTranscodeDescription": "Изтрива прекодирани файлове по-стари от един ден.",
- "TaskCleanTranscode": "Изчиства директорията за прекодиране",
+ "TaskCleanTranscodeDescription": "Изтрива транскодирани файлове по-стари от един ден.",
+ "TaskCleanTranscode": "Изчиства директорията за транскодиране",
"TaskUpdatePluginsDescription": "Изтегля и инсталира актуализации за добавките, които са настроени за автоматична актуализация.",
"TaskUpdatePlugins": "Актуализира добавките",
"TaskRefreshPeopleDescription": "Актуализира мета-данните за артистите и режисьорите за Вашата медийна библиотека.",
diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json
new file mode 100644
index 000000000..3ff7eddae
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/eo.json
@@ -0,0 +1,26 @@
+{
+ "NotificationOptionInstallationFailed": "Instalada fiasko",
+ "NotificationOptionAudioPlaybackStopped": "Sono de ludado haltis",
+ "NotificationOptionAudioPlayback": "Ludado de sono startis",
+ "NameSeasonUnknown": "Sezono Nekonata",
+ "NameSeasonNumber": "Sezono {0}",
+ "NameInstallFailed": "{0} instalado fiaskis",
+ "Music": "Muziko",
+ "Movies": "Filmoj",
+ "ItemRemovedWithName": "{0} forigis el la biblioteko",
+ "ItemAddedWithName": "{0} aldonis al la biblioteko",
+ "HeaderLiveTV": "Viva Televido",
+ "HeaderContinueWatching": "Daŭrigi Spektado",
+ "HeaderAlbumArtists": "Artistoj de Albumo",
+ "Folders": "Dosierujoj",
+ "DeviceOnlineWithName": "{0} estas konektita",
+ "Default": "Defaŭlte",
+ "Collections": "Kolektoj",
+ "ChapterNameValue": "Ĉapitro {0}",
+ "Channels": "Kanaloj",
+ "Books": "Libroj",
+ "Artists": "Artistoj",
+ "Application": "Aplikaĵo",
+ "AppDeviceValues": "Aplikaĵo: {0}, Aparato: {1}",
+ "Albums": "Albumoj"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json
index b45bdcbad..fd6148e78 100644
--- a/Emby.Server.Implementations/Localization/Core/fi.json
+++ b/Emby.Server.Implementations/Localization/Core/fi.json
@@ -1,121 +1,121 @@
{
- "HeaderLiveTV": "Live-TV",
- "NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.",
+ "HeaderLiveTV": "Live TV",
+ "NewVersionIsAvailable": "Uusi versio Jellyfin-palvelimesta on ladattavissa.",
"NameSeasonUnknown": "Tuntematon kausi",
"NameSeasonNumber": "Kausi {0}",
"NameInstallFailed": "{0} asennus epäonnistui",
"MusicVideos": "Musiikkivideot",
"Music": "Musiikki",
"Movies": "Elokuvat",
- "MixedContent": "Sekoitettu sisältö",
+ "MixedContent": "Sekalainen sisältö",
"MessageServerConfigurationUpdated": "Palvelimen asetukset on päivitetty",
- "MessageNamedServerConfigurationUpdatedWithValue": "Palvelimen asetusryhmä {0} on päivitetty",
- "MessageApplicationUpdatedTo": "Jellyfin palvelin on päivitetty versioon {0}",
- "MessageApplicationUpdated": "Jellyfin palvelin on päivitetty",
- "Latest": "Uusimmat",
- "LabelRunningTimeValue": "Toiston kesto: {0}",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Palvelimen asetusten osio {0} on päivitetty",
+ "MessageApplicationUpdatedTo": "Jellyfin-palvelin on päivitetty versioon {0}",
+ "MessageApplicationUpdated": "Jellyfin-palvelin on päivitetty",
+ "Latest": "Viimeisimmät",
+ "LabelRunningTimeValue": "Kesto: {0}",
"LabelIpAddressValue": "IP-osoite: {0}",
"ItemRemovedWithName": "{0} poistettiin kirjastosta",
"ItemAddedWithName": "{0} lisättiin kirjastoon",
- "Inherit": "Periytyä",
+ "Inherit": "Peri",
"HomeVideos": "Kotivideot",
"HeaderRecordingGroups": "Tallennusryhmät",
"HeaderNextUp": "Seuraavaksi",
"HeaderFavoriteSongs": "Suosikkikappaleet",
"HeaderFavoriteShows": "Suosikkisarjat",
"HeaderFavoriteEpisodes": "Suosikkijaksot",
- "HeaderFavoriteArtists": "Suosikkiartistit",
+ "HeaderFavoriteArtists": "Suosikkiesittäjät",
"HeaderFavoriteAlbums": "Suosikkialbumit",
- "HeaderContinueWatching": "Jatka katsomista",
- "HeaderAlbumArtists": "Albumin artistit",
+ "HeaderContinueWatching": "Jatka katselua",
+ "HeaderAlbumArtists": "Albumin esittäjät",
"Genres": "Tyylilajit",
"Folders": "Kansiot",
"Favorites": "Suosikit",
- "FailedLoginAttemptWithUserName": "Kirjautuminen epäonnistui kohteesta {0}",
+ "FailedLoginAttemptWithUserName": "Epäonnistunut kirjautumisyritys lähteestä \"{0}\"",
"DeviceOnlineWithName": "{0} on yhdistetty",
- "DeviceOfflineWithName": "{0} yhteys on katkaistu",
+ "DeviceOfflineWithName": "{0} on katkaissut yhteyden",
"Collections": "Kokoelmat",
- "ChapterNameValue": "Jakso: {0}",
+ "ChapterNameValue": "Kappale {0}",
"Channels": "Kanavat",
- "CameraImageUploadedFrom": "Uusi kamerakuva on ladattu {0}",
+ "CameraImageUploadedFrom": "Uusi kameran kuva on sirretty lähteestä {0}",
"Books": "Kirjat",
- "AuthenticationSucceededWithUserName": "Käyttäjän {0} todennus onnistui",
- "Artists": "Artistit",
+ "AuthenticationSucceededWithUserName": "{0} on todennettu",
+ "Artists": "Esittäjät",
"Application": "Sovellus",
"AppDeviceValues": "Sovellus: {0}, Laite: {1}",
"Albums": "Albumit",
"User": "Käyttäjä",
"System": "Järjestelmä",
"ScheduledTaskFailedWithName": "{0} epäonnistui",
- "PluginUpdatedWithName": "{0} päivitetty",
- "PluginInstalledWithName": "{0} asennettu",
- "Photos": "Kuvat",
- "ScheduledTaskStartedWithName": "{0} aloitettu",
- "PluginUninstalledWithName": "{0} poistettu",
+ "PluginUpdatedWithName": "{0} päivitettiin",
+ "PluginInstalledWithName": "{0} asennettiin",
+ "Photos": "Valokuvat",
+ "ScheduledTaskStartedWithName": "\"{0}\" käynnistetty",
+ "PluginUninstalledWithName": "{0} poistettiin",
"Playlists": "Soittolistat",
"VersionNumber": "Versio {0}",
- "ValueSpecialEpisodeName": "Erikois - {0}",
- "ValueHasBeenAddedToLibrary": "{0} lisättiin mediakirjastoon",
- "UserStoppedPlayingItemWithValues": "{0} toistaminen valmistui {1} laitteella {2}",
- "UserStartedPlayingItemWithValues": "{0} toistaa {1} laitteella {2}",
- "UserPolicyUpdatedWithName": "Käyttöoikeudet päivitetty käyttäjälle {0}",
- "UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}",
- "UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}",
- "UserOfflineFromDevice": "{0} yhteys katkaistu kohteesta {1}",
- "UserLockedOutWithName": "Käyttäjä {0} lukittu",
- "UserDownloadingItemWithValues": "{0} lataa {1}",
- "UserDeletedWithName": "Käyttäjä {0} poistettu",
- "UserCreatedWithName": "Käyttäjä {0} luotu",
- "TvShows": "TV-ohjelmat",
- "Sync": "Synkronoi",
- "SubtitleDownloadFailureFromForItem": "Tekstitystä ei voitu ladata osoitteesta {0} kohteelle {1}",
- "StartupEmbyServerIsLoading": "Jellyfin palvelin latautuu. Yritä hetken kuluttua uudelleen.",
+ "ValueSpecialEpisodeName": "Erikoisjakso - {0}",
+ "ValueHasBeenAddedToLibrary": "\"{0}\" on lisätty mediakirjastoon",
+ "UserStoppedPlayingItemWithValues": "{0} lopetti kohteen \"{1}\" toiston sijainnissa \"{2}\"",
+ "UserStartedPlayingItemWithValues": "{0} toistaa kohdetta \"{1}\" sijainnissa \"{2}\"",
+ "UserPolicyUpdatedWithName": "Käyttäjän {0} käyttöoikeudet on päivitetty",
+ "UserPasswordChangedWithName": "Käyttäjän {0} salasana on vaihdettu",
+ "UserOnlineFromDevice": "{0} on yhdistänyt sijainnista \"{1}\"",
+ "UserOfflineFromDevice": "{0} on katkaissut yhteyden sijainnista \"{1}\"",
+ "UserLockedOutWithName": "Käyttäjä {0} on lukittu",
+ "UserDownloadingItemWithValues": "{0} lataa kohdetta \"{1}\"",
+ "UserDeletedWithName": "Käyttäjä {0} on poistettu",
+ "UserCreatedWithName": "Käyttäjä {0} on luotu",
+ "TvShows": "Sarjat",
+ "Sync": "Synkronointi",
+ "SubtitleDownloadFailureFromForItem": "Tekstityksen lataus lähteestä \"{0}\" kohteelle \"{1}\" epäonnistui",
+ "StartupEmbyServerIsLoading": "Jellyfin-palvelin latautuu. Yritä hetken kuluttua uudelleen.",
"Songs": "Kappaleet",
- "Shows": "Ohjelmat",
- "ServerNameNeedsToBeRestarted": "{0} on käynnistettävä uudelleen",
- "ProviderValue": "Tarjoaja: {0}",
- "Plugin": "Liitännäinen",
- "NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty",
- "NotificationOptionVideoPlayback": "Videota toistetaan",
- "NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos",
- "NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui",
- "NotificationOptionServerRestartRequired": "Palvelin on käynnistettävä uudelleen",
- "NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty",
- "NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
- "NotificationOptionPluginInstalled": "Liitännäinen asennettu",
- "NotificationOptionPluginError": "Ongelma liitännäisessä",
- "NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty",
+ "Shows": "Sarjat",
+ "ServerNameNeedsToBeRestarted": "\"{0}\" on käynnistettävä uudelleen",
+ "ProviderValue": "Lähde: {0}",
+ "Plugin": "Laajennus",
+ "NotificationOptionVideoPlaybackStopped": "Videon toisto lopetettu",
+ "NotificationOptionVideoPlayback": "Videon toisto aloitettu",
+ "NotificationOptionUserLockedOut": "Käyttäjä on lukittu",
+ "NotificationOptionTaskFailed": "Ajoitettu tehtävä epäonnistui",
+ "NotificationOptionServerRestartRequired": "Tarvitaan palvelimen uudelleenkäynnistys",
+ "NotificationOptionPluginUpdateInstalled": "Laajennus on päivitetty",
+ "NotificationOptionPluginUninstalled": "Laajennus on poistettu",
+ "NotificationOptionPluginInstalled": "Laajennus on asennettu",
+ "NotificationOptionPluginError": "Laajennuksen virhe",
+ "NotificationOptionNewLibraryContent": "Sisältöä on lisätty",
"NotificationOptionInstallationFailed": "Asennus epäonnistui",
- "NotificationOptionCameraImageUploaded": "Kameran kuva ladattu",
+ "NotificationOptionCameraImageUploaded": "Kameran kuva on tallennettu",
"NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu",
- "NotificationOptionAudioPlayback": "Toistetaan ääntä",
- "NotificationOptionApplicationUpdateInstalled": "Sovelluspäivitys asennettu",
- "NotificationOptionApplicationUpdateAvailable": "Ohjelmistopäivitys saatavilla",
+ "NotificationOptionAudioPlayback": "Äänen toisto aloitettu",
+ "NotificationOptionApplicationUpdateInstalled": "Sovelluspäivitys asennettiin",
+ "NotificationOptionApplicationUpdateAvailable": "Sovelluspäivitys on saatavilla",
"TasksMaintenanceCategory": "Ylläpito",
- "TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä videon metadatatietojen pohjalta.",
+ "TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä määritettyjen metatietoasetusten mukaisesti.",
"TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset",
"TaskRefreshChannelsDescription": "Päivittää internet-kanavien tiedot.",
"TaskRefreshChannels": "Päivitä kanavat",
- "TaskCleanTranscodeDescription": "Poistaa transkoodatut tiedostot jotka ovat yli päivän vanhoja.",
- "TaskCleanTranscode": "Puhdista transkoodaushakemisto",
- "TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset liitännäisille jotka on asetettu päivittymään automaattisesti.",
- "TaskUpdatePlugins": "Päivitä liitännäiset",
- "TaskRefreshPeopleDescription": "Päivittää näyttelijöiden ja ohjaajien mediatiedot kirjastossasi.",
+ "TaskCleanTranscodeDescription": "Poistaa päivää vanhemmat transkoodaustiedostot.",
+ "TaskCleanTranscode": "Puhdista transkoodauskansio",
+ "TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset laajennuksille, jotka on määritetty päivittymään automaattisesti.",
+ "TaskUpdatePlugins": "Päivitä laajennukset",
+ "TaskRefreshPeopleDescription": "Päivittää mediakirjaston näyttelijöiden ja ohjaajien metatiedot.",
"TaskRefreshPeople": "Päivitä henkilöt",
- "TaskCleanLogsDescription": "Poistaa lokitiedostot jotka ovat yli {0} päivää vanhoja.",
- "TaskCleanLogs": "Puhdista lokihakemisto",
- "TaskRefreshLibraryDescription": "Skannaa mediakirjastosi uudet tiedostot ja päivittää metatiedot.",
- "TaskRefreshLibrary": "Skannaa mediakirjasto",
- "TaskRefreshChapterImagesDescription": "Luo pienoiskuvat videoille joissa on jaksoja.",
- "TaskRefreshChapterImages": "Pura jakson kuvat",
- "TaskCleanCacheDescription": "Poistaa järjestelmälle tarpeettomat väliaikaistiedostot.",
- "TaskCleanCache": "Tyhjennä välimuisti-hakemisto",
- "TasksChannelsCategory": "Internet kanavat",
+ "TaskCleanLogsDescription": "Poistaa {0} päivää vanhemmat lokitiedostot.",
+ "TaskCleanLogs": "Siivoa lokikansio",
+ "TaskRefreshLibraryDescription": "Tarkastaa mediakirjastosi sisällön uusien tiedostojen varalta ja päivittää metatiedot.",
+ "TaskRefreshLibrary": "Päivitä mediakirjasto",
+ "TaskRefreshChapterImagesDescription": "Luo esikatselukuvat videoille, jotka sisältävät kappalejaon.",
+ "TaskRefreshChapterImages": "Pura kappalejaon kuvat",
+ "TaskCleanCacheDescription": "Poistaa tarpeettomiksi jääneet väliaikaistiedostot.",
+ "TaskCleanCache": "Tyhjennä välimuistikansio",
+ "TasksChannelsCategory": "Internet-kanavat",
"TasksApplicationCategory": "Sovellus",
"TasksLibraryCategory": "Kirjasto",
"Forced": "Pakotettu",
"Default": "Oletus",
- "TaskCleanActivityLogDescription": "Poistaa määritettyä vanhemmat tapahtumat aktiviteettilokista.",
- "TaskCleanActivityLog": "Tyhjennä aktiviteettiloki",
+ "TaskCleanActivityLogDescription": "Poistaa määritettyä ikää vanhemmat tapahtumat toimintahistoriasta.",
+ "TaskCleanActivityLog": "Tyhjennä toimintahistoria",
"Undefined": "Määrittelemätön"
}
diff --git a/Emby.Server.Implementations/Localization/Core/hi.json b/Emby.Server.Implementations/Localization/Core/hi.json
index b9e5f301d..ef3697b15 100644
--- a/Emby.Server.Implementations/Localization/Core/hi.json
+++ b/Emby.Server.Implementations/Localization/Core/hi.json
@@ -1,5 +1,5 @@
{
- "Albums": "संग्रह",
+ "Albums": "एल्बम",
"HeaderRecordingGroups": "रिकॉर्डिंग समूह",
"HeaderNextUp": "इसके बाद",
"HeaderLiveTV": "लाइव टीवी",
@@ -26,7 +26,7 @@
"AuthenticationSucceededWithUserName": "सफलता से प्रमाणीकृत",
"Artists": "कलाकारों",
"Application": "एप्लिकेशन",
- "AppDeviceValues": "एप: {0}, मशीन: {1}",
+ "AppDeviceValues": "एप: {0}, उपकरण: {1}",
"NotificationOptionPluginUninstalled": "प्लगइन अनइंस्टाल हो गया",
"NotificationOptionPluginInstalled": "प्लगइन इनस्टॉल हो गया",
"NotificationOptionPluginError": "प्लगइन फ़ैल हो गया",
diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs
index 8e5987026..c26ccfd88 100644
--- a/Emby.Server.Implementations/Plugins/PluginManager.cs
+++ b/Emby.Server.Implementations/Plugins/PluginManager.cs
@@ -348,7 +348,7 @@ namespace Emby.Server.Implementations.Plugins
try
{
var data = JsonSerializer.Serialize(manifest, _jsonOptions);
- File.WriteAllText(Path.Combine(path, "meta.json"), data, Encoding.UTF8);
+ File.WriteAllText(Path.Combine(path, "meta.json"), data);
return true;
}
#pragma warning disable CA1031 // Do not catch general exception types
@@ -519,7 +519,7 @@ namespace Emby.Server.Implementations.Plugins
return _plugins.Remove(plugin);
}
- private LocalPlugin LoadManifest(string dir)
+ internal LocalPlugin LoadManifest(string dir)
{
Version? version;
PluginManifest? manifest = null;
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 4e026a0e6..10e28c33a 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -1456,7 +1456,12 @@ namespace Emby.Server.Implementations.Session
throw new SecurityException("Unknown quick connect token");
}
- request.UserId = result.Items[0].UserId;
+ var info = result.Items[0];
+ request.UserId = info.UserId;
+
+ // There's no need to keep the quick connect token in the database, as AuthenticateNewSessionInternal() issues a long lived token.
+ _authRepo.Delete(info);
+
return AuthenticateNewSessionInternal(request, false);
}
diff --git a/Jellyfin.Api/Attributes/AcceptsFileAttribute.cs b/Jellyfin.Api/Attributes/AcceptsFileAttribute.cs
new file mode 100644
index 000000000..49b6689cd
--- /dev/null
+++ b/Jellyfin.Api/Attributes/AcceptsFileAttribute.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace Jellyfin.Api.Attributes
+{
+ /// <summary>
+ /// Internal produces image attribute.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Method)]
+ public class AcceptsFileAttribute : Attribute
+ {
+ private readonly string[] _contentTypes;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AcceptsFileAttribute"/> class.
+ /// </summary>
+ /// <param name="contentTypes">Content types this endpoint produces.</param>
+ public AcceptsFileAttribute(params string[] contentTypes)
+ {
+ _contentTypes = contentTypes;
+ }
+
+ /// <summary>
+ /// Gets the configured content types.
+ /// </summary>
+ /// <returns>the configured content types.</returns>
+ public string[] GetContentTypes() => _contentTypes;
+ }
+}
diff --git a/Jellyfin.Api/Attributes/AcceptsImageFileAttribute.cs b/Jellyfin.Api/Attributes/AcceptsImageFileAttribute.cs
new file mode 100644
index 000000000..001f27409
--- /dev/null
+++ b/Jellyfin.Api/Attributes/AcceptsImageFileAttribute.cs
@@ -0,0 +1,18 @@
+namespace Jellyfin.Api.Attributes
+{
+ /// <summary>
+ /// Produces file attribute of "image/*".
+ /// </summary>
+ public class AcceptsImageFileAttribute : AcceptsFileAttribute
+ {
+ private const string ContentType = "image/*";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AcceptsImageFileAttribute"/> class.
+ /// </summary>
+ public AcceptsImageFileAttribute()
+ : base(ContentType)
+ {
+ }
+ }
+}
diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs
index fed7ed3e5..4b2e5e7ea 100644
--- a/Jellyfin.Api/Controllers/ArtistsController.cs
+++ b/Jellyfin.Api/Controllers/ArtistsController.cs
@@ -3,8 +3,10 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -88,8 +90,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? searchTerm,
[FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
@@ -127,8 +129,8 @@ namespace Jellyfin.Api.Controllers
var query = new InternalItemsQuery(user)
{
- ExcludeItemTypes = excludeItemTypes,
- IncludeItemTypes = includeItemTypes,
+ ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
+ IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
MediaTypes = mediaTypes,
StartIndex = startIndex,
Limit = limit,
@@ -287,8 +289,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? searchTerm,
[FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
@@ -326,8 +328,8 @@ namespace Jellyfin.Api.Controllers
var query = new InternalItemsQuery(user)
{
- ExcludeItemTypes = excludeItemTypes,
- IncludeItemTypes = includeItemTypes,
+ ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
+ IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
MediaTypes = mediaTypes,
StartIndex = startIndex,
Limit = limit,
diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs
index 2a7b2b5c6..852d1e9cb 100644
--- a/Jellyfin.Api/Controllers/CollectionController.cs
+++ b/Jellyfin.Api/Controllers/CollectionController.cs
@@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Dto;
diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs
index ff7895373..b2baa9cea 100644
--- a/Jellyfin.Api/Controllers/DashboardController.cs
+++ b/Jellyfin.Api/Controllers/DashboardController.cs
@@ -7,17 +7,11 @@ using Jellyfin.Api.Attributes;
using Jellyfin.Api.Models;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Controller;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Extensions;
-using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Plugins;
using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
-using Microsoft.Net.Http.Headers;
namespace Jellyfin.Api.Controllers
{
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index 6e85737d2..e375645cf 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -15,7 +15,6 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs
index 9220b988f..223b2a2b6 100644
--- a/Jellyfin.Api/Controllers/FilterController.cs
+++ b/Jellyfin.Api/Controllers/FilterController.cs
@@ -1,13 +1,12 @@
using System;
using System.Linq;
using Jellyfin.Api.Constants;
+using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
+using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization;
@@ -51,7 +50,7 @@ namespace Jellyfin.Api.Controllers
public ActionResult<QueryFiltersLegacy> GetQueryFiltersLegacy(
[FromQuery] Guid? userId,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes)
{
var user = userId.HasValue && !userId.Equals(Guid.Empty)
@@ -60,10 +59,10 @@ namespace Jellyfin.Api.Controllers
BaseItem? item = null;
if (includeItemTypes.Length != 1
- || !(string.Equals(includeItemTypes[0], nameof(BoxSet), StringComparison.OrdinalIgnoreCase)
- || string.Equals(includeItemTypes[0], nameof(Playlist), StringComparison.OrdinalIgnoreCase)
- || string.Equals(includeItemTypes[0], nameof(Trailer), StringComparison.OrdinalIgnoreCase)
- || string.Equals(includeItemTypes[0], "Program", StringComparison.OrdinalIgnoreCase)))
+ || !(includeItemTypes[0] == BaseItemKind.BoxSet
+ || includeItemTypes[0] == BaseItemKind.Playlist
+ || includeItemTypes[0] == BaseItemKind.Trailer
+ || includeItemTypes[0] == BaseItemKind.Program))
{
item = _libraryManager.GetParentItem(parentId, user?.Id);
}
@@ -72,7 +71,7 @@ namespace Jellyfin.Api.Controllers
{
User = user,
MediaTypes = mediaTypes,
- IncludeItemTypes = includeItemTypes,
+ IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
Recursive = true,
EnableTotalRecordCount = false,
DtoOptions = new DtoOptions
@@ -137,7 +136,7 @@ namespace Jellyfin.Api.Controllers
public ActionResult<QueryFilters> GetQueryFilters(
[FromQuery] Guid? userId,
[FromQuery] Guid? parentId,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool? isAiring,
[FromQuery] bool? isMovie,
[FromQuery] bool? isSports,
@@ -152,10 +151,10 @@ namespace Jellyfin.Api.Controllers
BaseItem? parentItem = null;
if (includeItemTypes.Length == 1
- && (string.Equals(includeItemTypes[0], nameof(BoxSet), StringComparison.OrdinalIgnoreCase)
- || string.Equals(includeItemTypes[0], nameof(Playlist), StringComparison.OrdinalIgnoreCase)
- || string.Equals(includeItemTypes[0], nameof(Trailer), StringComparison.OrdinalIgnoreCase)
- || string.Equals(includeItemTypes[0], "Program", StringComparison.OrdinalIgnoreCase)))
+ && (includeItemTypes[0] == BaseItemKind.BoxSet
+ || includeItemTypes[0] == BaseItemKind.Playlist
+ || includeItemTypes[0] == BaseItemKind.Trailer
+ || includeItemTypes[0] == BaseItemKind.Program))
{
parentItem = null;
}
@@ -167,7 +166,7 @@ namespace Jellyfin.Api.Controllers
var filters = new QueryFilters();
var genreQuery = new InternalItemsQuery(user)
{
- IncludeItemTypes = includeItemTypes,
+ IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
DtoOptions = new DtoOptions
{
Fields = Array.Empty<ItemFields>(),
@@ -192,10 +191,10 @@ namespace Jellyfin.Api.Controllers
}
if (includeItemTypes.Length == 1
- && (string.Equals(includeItemTypes[0], nameof(MusicAlbum), StringComparison.OrdinalIgnoreCase)
- || string.Equals(includeItemTypes[0], nameof(MusicVideo), StringComparison.OrdinalIgnoreCase)
- || string.Equals(includeItemTypes[0], nameof(MusicArtist), StringComparison.OrdinalIgnoreCase)
- || string.Equals(includeItemTypes[0], nameof(Audio), StringComparison.OrdinalIgnoreCase)))
+ && (includeItemTypes[0] == BaseItemKind.MusicAlbum
+ || includeItemTypes[0] == BaseItemKind.MusicVideo
+ || includeItemTypes[0] == BaseItemKind.MusicArtist
+ || includeItemTypes[0] == BaseItemKind.Audio))
{
filters.Genres = _libraryManager.GetMusicGenres(genreQuery).Items.Select(i => new NameGuidPair
{
diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs
index b6755ed5e..7bcf4674c 100644
--- a/Jellyfin.Api/Controllers/GenresController.cs
+++ b/Jellyfin.Api/Controllers/GenresController.cs
@@ -6,6 +6,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -74,8 +75,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? searchTerm,
[FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool? isFavorite,
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
@@ -96,8 +97,8 @@ namespace Jellyfin.Api.Controllers
var query = new InternalItemsQuery(user)
{
- ExcludeItemTypes = excludeItemTypes,
- IncludeItemTypes = includeItemTypes,
+ ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
+ IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
StartIndex = startIndex,
Limit = limit,
IsFavorite = isFavorite,
diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs
index f51987732..25abe73ed 100644
--- a/Jellyfin.Api/Controllers/HlsSegmentController.cs
+++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs
@@ -2,13 +2,11 @@ using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.IO;
-using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.IO;
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index c606d327c..dc3634970 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/Jellyfin.Api/Controllers/ImageController.cs
@@ -87,6 +87,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Users/{userId}/Images/{imageType}")]
[Authorize(Policy = Policies.DefaultAuthorization)]
+ [AcceptsImageFile]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
@@ -133,6 +134,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Users/{userId}/Images/{imageType}/{index}")]
[Authorize(Policy = Policies.DefaultAuthorization)]
+ [AcceptsImageFile]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
@@ -312,6 +314,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
[HttpPost("Items/{itemId}/Images/{imageType}")]
[Authorize(Policy = Policies.RequiresElevation)]
+ [AcceptsImageFile]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
@@ -346,6 +349,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
[HttpPost("Items/{itemId}/Images/{imageType}/{imageIndex}")]
[Authorize(Policy = Policies.RequiresElevation)]
+ [AcceptsImageFile]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs
index 244625752..f061755c3 100644
--- a/Jellyfin.Api/Controllers/InstantMixController.cs
+++ b/Jellyfin.Api/Controllers/InstantMixController.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
-using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.ModelBinders;
diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs
index 6c38f77ce..dfc68ffce 100644
--- a/Jellyfin.Api/Controllers/ItemLookupController.cs
+++ b/Jellyfin.Api/Controllers/ItemLookupController.cs
@@ -2,8 +2,6 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
-using System.Linq;
-using System.Net.Mime;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 7d7747495..2c9760f6d 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -1,6 +1,5 @@
using System;
using System.ComponentModel.DataAnnotations;
-using System.Globalization;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
@@ -178,8 +177,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
@@ -233,8 +232,8 @@ namespace Jellyfin.Api.Controllers
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
if (includeItemTypes.Length == 1
- && (includeItemTypes[0].Equals("Playlist", StringComparison.OrdinalIgnoreCase)
- || includeItemTypes[0].Equals("BoxSet", StringComparison.OrdinalIgnoreCase)))
+ && (includeItemTypes[0] == BaseItemKind.Playlist
+ || includeItemTypes[0] == BaseItemKind.BoxSet))
{
parentId = null;
}
@@ -251,7 +250,7 @@ namespace Jellyfin.Api.Controllers
&& string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
{
recursive = true;
- includeItemTypes = new[] { "Playlist" };
+ includeItemTypes = new[] { BaseItemKind.Playlist };
}
var enabledChannels = user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels);
@@ -286,8 +285,8 @@ namespace Jellyfin.Api.Controllers
{
IsPlayed = isPlayed,
MediaTypes = mediaTypes,
- IncludeItemTypes = includeItemTypes,
- ExcludeItemTypes = excludeItemTypes,
+ IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
+ ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
Recursive = recursive ?? false,
OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder),
IsFavorite = isFavorite,
@@ -611,8 +610,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
@@ -773,8 +772,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true)
{
@@ -810,8 +809,8 @@ namespace Jellyfin.Api.Controllers
CollapseBoxSetItems = false,
EnableTotalRecordCount = enableTotalRecordCount,
AncestorIds = ancestorIds,
- IncludeItemTypes = includeItemTypes,
- ExcludeItemTypes = excludeItemTypes,
+ IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
+ ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
SearchTerm = searchTerm
});
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index 28d359ac3..db4aa9668 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -11,7 +11,6 @@ using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.LibraryDtos;
using Jellyfin.Data.Entities;
diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs
index 2608a9cd0..7f7058b5e 100644
--- a/Jellyfin.Api/Controllers/MusicGenresController.cs
+++ b/Jellyfin.Api/Controllers/MusicGenresController.cs
@@ -6,6 +6,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@@ -74,8 +75,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? searchTerm,
[FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool? isFavorite,
[FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
@@ -96,8 +97,8 @@ namespace Jellyfin.Api.Controllers
var query = new InternalItemsQuery(user)
{
- ExcludeItemTypes = excludeItemTypes,
- IncludeItemTypes = includeItemTypes,
+ ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
+ IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
StartIndex = startIndex,
Limit = limit,
IsFavorite = isFavorite,
diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs
index c589f54ac..5dd49ef2f 100644
--- a/Jellyfin.Api/Controllers/PackageController.cs
+++ b/Jellyfin.Api/Controllers/PackageController.cs
@@ -4,7 +4,6 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
-using MediaBrowser.Common.Json;
using MediaBrowser.Common.Updates;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Updates;
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
index 17e631197..70a94e27c 100644
--- a/Jellyfin.Api/Controllers/PersonsController.cs
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Dto;
diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs
index a55e4ad2f..1667d6ede 100644
--- a/Jellyfin.Api/Controllers/PlaylistsController.cs
+++ b/Jellyfin.Api/Controllers/PlaylistsController.cs
@@ -6,7 +6,6 @@ using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.PlaylistDtos;
using MediaBrowser.Controller.Dto;
diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs
index b73611c97..a5aa9bfca 100644
--- a/Jellyfin.Api/Controllers/PluginsController.cs
+++ b/Jellyfin.Api/Controllers/PluginsController.cs
@@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
-using System.Globalization;
using System.IO;
using System.Linq;
-using System.Net.Mime;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
@@ -300,9 +298,7 @@ namespace Jellyfin.Api.Controllers
}
var imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath ?? string.Empty);
- if (((ServerConfiguration)_config.CommonConfiguration).DisablePluginImages
- || plugin.Manifest.ImagePath == null
- || !System.IO.File.Exists(imagePath))
+ if (plugin.Manifest.ImagePath == null || !System.IO.File.Exists(imagePath))
{
return NotFound();
}
diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs
index 08255ff8f..6c22050a7 100644
--- a/Jellyfin.Api/Controllers/SearchController.cs
+++ b/Jellyfin.Api/Controllers/SearchController.cs
@@ -6,6 +6,7 @@ using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
+using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -83,8 +84,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? limit,
[FromQuery] Guid? userId,
[FromQuery, Required] string searchTerm,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
[FromQuery] Guid? parentId,
[FromQuery] bool? isMovie,
@@ -109,8 +110,8 @@ namespace Jellyfin.Api.Controllers
IncludeStudios = includeStudios,
StartIndex = startIndex,
UserId = userId ?? Guid.Empty,
- IncludeItemTypes = includeItemTypes,
- ExcludeItemTypes = excludeItemTypes,
+ IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
+ ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
MediaTypes = mediaTypes,
ParentId = parentId,
diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs
index bb54c59f6..da8f8b199 100644
--- a/Jellyfin.Api/Controllers/StudiosController.cs
+++ b/Jellyfin.Api/Controllers/StudiosController.cs
@@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -73,8 +74,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? searchTerm,
[FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool? isFavorite,
[FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit,
@@ -96,8 +97,8 @@ namespace Jellyfin.Api.Controllers
var query = new InternalItemsQuery(user)
{
- ExcludeItemTypes = excludeItemTypes,
- IncludeItemTypes = includeItemTypes,
+ ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
+ IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
StartIndex = startIndex,
Limit = limit,
IsFavorite = isFavorite,
diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs
index 9f1dec712..a55f13e66 100644
--- a/Jellyfin.Api/Controllers/SuggestionsController.cs
+++ b/Jellyfin.Api/Controllers/SuggestionsController.cs
@@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
diff --git a/Jellyfin.Api/Controllers/SyncPlayController.cs b/Jellyfin.Api/Controllers/SyncPlayController.cs
index 82cbe58df..f878f2329 100644
--- a/Jellyfin.Api/Controllers/SyncPlayController.cs
+++ b/Jellyfin.Api/Controllers/SyncPlayController.cs
@@ -1,4 +1,3 @@
-using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading;
diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs
index e67a27ae3..bbbe5fb8d 100644
--- a/Jellyfin.Api/Controllers/SystemController.cs
+++ b/Jellyfin.Api/Controllers/SystemController.cs
@@ -5,7 +5,6 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mime;
-using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
diff --git a/Jellyfin.Api/Controllers/TimeSyncController.cs b/Jellyfin.Api/Controllers/TimeSyncController.cs
index c730ac12b..7df51c7af 100644
--- a/Jellyfin.Api/Controllers/TimeSyncController.cs
+++ b/Jellyfin.Api/Controllers/TimeSyncController.cs
@@ -1,5 +1,4 @@
using System;
-using System.Globalization;
using MediaBrowser.Model.SyncPlay;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs
index 242b8f068..dd3836551 100644
--- a/Jellyfin.Api/Controllers/TrailersController.cs
+++ b/Jellyfin.Api/Controllers/TrailersController.cs
@@ -148,7 +148,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
@@ -194,7 +194,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true)
{
- var includeItemTypes = new[] { "Trailer" };
+ var includeItemTypes = new[] { BaseItemKind.Trailer };
return _itemsController
.GetItems(
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index 223f58859..e1c67f830 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
-using System.Globalization;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs
index 0e65591cc..1d70406ac 100644
--- a/Jellyfin.Api/Controllers/UserLibraryController.cs
+++ b/Jellyfin.Api/Controllers/UserLibraryController.cs
@@ -8,6 +8,7 @@ using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
+using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -269,7 +270,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] Guid userId,
[FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool? isPlayed,
[FromQuery] bool? enableImages,
[FromQuery] int? imageTypeLimit,
@@ -296,7 +297,7 @@ namespace Jellyfin.Api.Controllers
new LatestItemsQuery
{
GroupItems = groupItems,
- IncludeItemTypes = includeItemTypes,
+ IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
IsPlayed = isPlayed,
Limit = limit,
ParentId = parentId ?? Guid.Empty,
diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs
index e1483ce9d..7bc5ecdf1 100644
--- a/Jellyfin.Api/Controllers/UserViewsController.cs
+++ b/Jellyfin.Api/Controllers/UserViewsController.cs
@@ -4,7 +4,6 @@ using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.UserViewDtos;
using MediaBrowser.Controller.Dto;
diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs
index 7e743ee0c..ba51aa43e 100644
--- a/Jellyfin.Api/Controllers/VideoHlsController.cs
+++ b/Jellyfin.Api/Controllers/VideoHlsController.cs
@@ -12,7 +12,6 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs
index 7c27752f7..d6dc6650c 100644
--- a/Jellyfin.Api/Controllers/YearsController.cs
+++ b/Jellyfin.Api/Controllers/YearsController.cs
@@ -74,8 +74,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes,
- [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
+ [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
[FromQuery] bool? enableUserData,
@@ -101,8 +101,8 @@ namespace Jellyfin.Api.Controllers
var query = new InternalItemsQuery(user)
{
- ExcludeItemTypes = excludeItemTypes,
- IncludeItemTypes = includeItemTypes,
+ ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
+ IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
MediaTypes = mediaTypes,
DtoOptions = dtoOptions
};
@@ -193,16 +193,17 @@ namespace Jellyfin.Api.Controllers
return _dtoService.GetBaseItemDto(item, dtoOptions);
}
- private bool FilterItem(BaseItem f, IReadOnlyCollection<string> excludeItemTypes, IReadOnlyCollection<string> includeItemTypes, IReadOnlyCollection<string> mediaTypes)
+ private bool FilterItem(BaseItem f, IReadOnlyCollection<BaseItemKind> excludeItemTypes, IReadOnlyCollection<BaseItemKind> includeItemTypes, IReadOnlyCollection<string> mediaTypes)
{
+ var baseItemKind = f.GetBaseItemKind();
// Exclude item types
- if (excludeItemTypes.Count > 0 && excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
+ if (excludeItemTypes.Count > 0 && excludeItemTypes.Contains(baseItemKind))
{
return false;
}
// Include item types
- if (includeItemTypes.Count > 0 && !includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
+ if (includeItemTypes.Count > 0 && !includeItemTypes.Contains(baseItemKind))
{
return false;
}
diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs
index db0ccc657..94856e03e 100644
--- a/Jellyfin.Api/Helpers/RequestHelpers.cs
+++ b/Jellyfin.Api/Helpers/RequestHelpers.cs
@@ -129,5 +129,21 @@ namespace Jellyfin.Api.Helpers
TotalRecordCount = result.TotalRecordCount
};
}
+
+ internal static string[] GetItemTypeStrings(IReadOnlyList<BaseItemKind> itemKinds)
+ {
+ if (itemKinds.Count == 0)
+ {
+ return Array.Empty<string>();
+ }
+
+ var itemTypes = new string[itemKinds.Count];
+ for (var i = 0; i < itemKinds.Count; i++)
+ {
+ itemTypes[i] = itemKinds[i].ToString();
+ }
+
+ return itemTypes;
+ }
}
}
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index 8437369b2..ef105fdce 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -15,10 +15,10 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.2" />
+ <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.3" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
- <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="5.6.3" />
+ <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.0.2" />
</ItemGroup>
<ItemGroup>
@@ -38,4 +38,10 @@
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
+ <ItemGroup>
+ <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
+ <_Parameter1>Jellyfin.Api.Tests</_Parameter1>
+ </AssemblyAttribute>
+ </ItemGroup>
+
</Project>
diff --git a/Jellyfin.Data/Enums/BaseItemKind.cs b/Jellyfin.Data/Enums/BaseItemKind.cs
new file mode 100644
index 000000000..aac30279e
--- /dev/null
+++ b/Jellyfin.Data/Enums/BaseItemKind.cs
@@ -0,0 +1,190 @@
+namespace Jellyfin.Data.Enums
+{
+ /// <summary>
+ /// The base item kind.
+ /// </summary>
+ /// <remarks>
+ /// This enum is generated from all classes that inherit from <c>BaseItem</c>.
+ /// </remarks>
+ public enum BaseItemKind
+ {
+ /// <summary>
+ /// Item is aggregate folder.
+ /// </summary>
+ AggregateFolder,
+
+ /// <summary>
+ /// Item is audio.
+ /// </summary>
+ Audio,
+
+ /// <summary>
+ /// Item is audio book.
+ /// </summary>
+ AudioBook,
+
+ /// <summary>
+ /// Item is base plugin folder.
+ /// </summary>
+ BasePluginFolder,
+
+ /// <summary>
+ /// Item is book.
+ /// </summary>
+ Book,
+
+ /// <summary>
+ /// Item is box set.
+ /// </summary>
+ BoxSet,
+
+ /// <summary>
+ /// Item is channel.
+ /// </summary>
+ Channel,
+
+ /// <summary>
+ /// Item is channel folder item.
+ /// </summary>
+ ChannelFolderItem,
+
+ /// <summary>
+ /// Item is collection folder.
+ /// </summary>
+ CollectionFolder,
+
+ /// <summary>
+ /// Item is episode.
+ /// </summary>
+ Episode,
+
+ /// <summary>
+ /// Item is folder.
+ /// </summary>
+ Folder,
+
+ /// <summary>
+ /// Item is genre.
+ /// </summary>
+ Genre,
+
+ /// <summary>
+ /// Item is manual playlists folder.
+ /// </summary>
+ ManualPlaylistsFolder,
+
+ /// <summary>
+ /// Item is movie.
+ /// </summary>
+ Movie,
+
+ /// <summary>
+ /// Item is music album.
+ /// </summary>
+ MusicAlbum,
+
+ /// <summary>
+ /// Item is music artist.
+ /// </summary>
+ MusicArtist,
+
+ /// <summary>
+ /// Item is music genre.
+ /// </summary>
+ MusicGenre,
+
+ /// <summary>
+ /// Item is music video.
+ /// </summary>
+ MusicVideo,
+
+ /// <summary>
+ /// Item is person.
+ /// </summary>
+ Person,
+
+ /// <summary>
+ /// Item is photo.
+ /// </summary>
+ Photo,
+
+ /// <summary>
+ /// Item is photo album.
+ /// </summary>
+ PhotoAlbum,
+
+ /// <summary>
+ /// Item is playlist.
+ /// </summary>
+ Playlist,
+
+ /// <summary>
+ /// Item is program
+ /// </summary>
+ Program,
+
+ /// <summary>
+ /// Item is recording.
+ /// </summary>
+ /// <remarks>
+ /// Manually added.
+ /// </remarks>
+ Recording,
+
+ /// <summary>
+ /// Item is season.
+ /// </summary>
+ Season,
+
+ /// <summary>
+ /// Item is series.
+ /// </summary>
+ Series,
+
+ /// <summary>
+ /// Item is studio.
+ /// </summary>
+ Studio,
+
+ /// <summary>
+ /// Item is trailer.
+ /// </summary>
+ Trailer,
+
+ /// <summary>
+ /// Item is live tv channel.
+ /// </summary>
+ /// <remarks>
+ /// Type is overridden.
+ /// </remarks>
+ TvChannel,
+
+ /// <summary>
+ /// Item is live tv program.
+ /// </summary>
+ /// <remarks>
+ /// Type is overridden.
+ /// </remarks>
+ TvProgram,
+
+ /// <summary>
+ /// Item is user root folder.
+ /// </summary>
+ UserRootFolder,
+
+ /// <summary>
+ /// Item is user view.
+ /// </summary>
+ UserView,
+
+ /// <summary>
+ /// Item is video.
+ /// </summary>
+ Video,
+
+ /// <summary>
+ /// Item is year.
+ /// </summary>
+ Year
+ }
+} \ No newline at end of file
diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj
index b95879de4..a8ac45645 100644
--- a/Jellyfin.Data/Jellyfin.Data.csproj
+++ b/Jellyfin.Data/Jellyfin.Data.csproj
@@ -19,7 +19,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Data</PackageId>
- <VersionPrefix>10.7.0</VersionPrefix>
+ <VersionPrefix>10.8.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
@@ -41,8 +41,8 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.2" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.2" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.3" />
</ItemGroup>
<ItemGroup>
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index 05052e5c0..4f24da0ee 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -26,11 +26,11 @@
<ItemGroup>
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.2">
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.2">
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index da4cc267b..545937207 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -316,6 +316,7 @@ namespace Jellyfin.Server.Extensions
c.OperationFilter<SecurityRequirementsOperationFilter>();
c.OperationFilter<FileResponseFilter>();
+ c.OperationFilter<FileRequestFilter>();
c.OperationFilter<ParameterObsoleteFilter>();
c.DocumentFilter<WebsocketModelFilter>();
});
diff --git a/Jellyfin.Server/Filters/FileRequestFilter.cs b/Jellyfin.Server/Filters/FileRequestFilter.cs
new file mode 100644
index 000000000..69e10994f
--- /dev/null
+++ b/Jellyfin.Server/Filters/FileRequestFilter.cs
@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using Jellyfin.Api.Attributes;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace Jellyfin.Server.Filters
+{
+ /// <inheritdoc />
+ public class FileRequestFilter : IOperationFilter
+ {
+ /// <inheritdoc />
+ public void Apply(OpenApiOperation operation, OperationFilterContext context)
+ {
+ foreach (var attribute in context.ApiDescription.ActionDescriptor.EndpointMetadata)
+ {
+ if (attribute is AcceptsFileAttribute acceptsFileAttribute)
+ {
+ operation.RequestBody = GetRequestBody(acceptsFileAttribute.GetContentTypes());
+ break;
+ }
+ }
+ }
+
+ private static OpenApiRequestBody GetRequestBody(IEnumerable<string> contentTypes)
+ {
+ var body = new OpenApiRequestBody();
+ var mediaType = new OpenApiMediaType
+ {
+ Schema = new OpenApiSchema
+ {
+ Type = "string",
+ Format = "binary"
+ }
+ };
+ foreach (var contentType in contentTypes)
+ {
+ body.Content.Add(contentType, mediaType);
+ }
+
+ return body;
+ }
+ }
+}
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 3ebcc3279..bf4f80669 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -40,8 +40,8 @@
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
- <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.2" />
- <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.2" />
+ <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.3" />
+ <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.3" />
<PackageReference Include="prometheus-net" Version="4.1.1" />
<PackageReference Include="prometheus-net.AspNetCore" Version="4.1.1" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 320e60dc6..e469436a9 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Common</PackageId>
- <VersionPrefix>10.7.0</VersionPrefix>
+ <VersionPrefix>10.8.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index cbb02aabd..7598b29e6 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1998,6 +1998,11 @@ namespace MediaBrowser.Controller.Entities
return GetType().Name;
}
+ public BaseItemKind GetBaseItemKind()
+ {
+ return Enum.Parse<BaseItemKind>(GetClientTypeName());
+ }
+
/// <summary>
/// Gets the linked child.
/// </summary>
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 5f75df54e..6b1c096ac 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId>
- <VersionPrefix>10.7.0</VersionPrefix>
+ <VersionPrefix>10.8.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index efab87a38..07a9f5ba6 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -112,14 +112,51 @@ namespace MediaBrowser.Controller.MediaEncoding
return _mediaEncoder.SupportsHwaccel("vaapi");
}
+ private bool IsCudaSupported()
+ {
+ return _mediaEncoder.SupportsHwaccel("cuda")
+ && _mediaEncoder.SupportsFilter("scale_cuda", null)
+ && _mediaEncoder.SupportsFilter("yadif_cuda", null);
+ }
+
private bool IsTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
{
var videoStream = state.VideoStream;
return IsColorDepth10(state)
&& _mediaEncoder.SupportsHwaccel("opencl")
&& options.EnableTonemapping
- && !string.IsNullOrEmpty(videoStream.VideoRange)
- && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase);
+ && string.Equals(videoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase);
+ }
+
+ private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
+ {
+ var videoStream = state.VideoStream;
+ var codec = videoStream.Codec;
+ if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ // Limited to HEVC for now since the filter doesn't accept master data from VP9.
+ return IsColorDepth10(state)
+ && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
+ && _mediaEncoder.SupportsHwaccel("vaapi")
+ && options.EnableVppTonemapping
+ && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase);
+ }
+
+ // Hybrid VPP tonemapping for QSV with VAAPI
+ var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+ if (isLinux && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ // Limited to HEVC for now since the filter doesn't accept master data from VP9.
+ return IsColorDepth10(state)
+ && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
+ && _mediaEncoder.SupportsHwaccel("vaapi")
+ && _mediaEncoder.SupportsHwaccel("qsv")
+ && options.EnableVppTonemapping
+ && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase);
+ }
+
+ // Native VPP tonemapping may come to QSV in the future.
+ return false;
}
/// <summary>
@@ -458,11 +495,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
- var isNvdecHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
+ var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
+ var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase);
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
var isTonemappingSupported = IsTonemappingSupported(state, encodingOptions);
+ var isVppTonemappingSupported = IsVppTonemappingSupported(state, encodingOptions);
if (!IsCopyCodec(outputVideoCodec))
{
@@ -472,7 +511,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (isVaapiDecoder)
{
- if (isTonemappingSupported)
+ if (isTonemappingSupported && !isVppTonemappingSupported)
{
arg.Append("-init_hw_device vaapi=va:")
.Append(encodingOptions.VaapiDevice)
@@ -526,16 +565,35 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// While using SW decoder
- else
+ else if (isSwDecoder)
{
arg.Append("-init_hw_device qsv=hw -filter_hw_device hw ");
}
+
+ // Hybrid VPP tonemapping with VAAPI
+ else if (isVaapiDecoder && isVppTonemappingSupported)
+ {
+ arg.Append("-init_hw_device vaapi=va:")
+ .Append(encodingOptions.VaapiDevice)
+ .Append(' ')
+ .Append("-init_hw_device qsv@va ")
+ .Append("-hwaccel_output_format vaapi ");
+ }
}
}
if (state.IsVideoRequest
- && (string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecHevcDecoder || isSwDecoder)
- || (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && isD3d11vaDecoder || isSwDecoder))
+ && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
+ && isNvdecDecoder)
+ {
+ arg.Append("-hwaccel_output_format cuda ");
+ }
+
+ if (state.IsVideoRequest
+ && ((string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
+ && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder))
+ || (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)
+ && (isD3d11vaDecoder || isSwDecoder))))
{
if (isTonemappingSupported)
{
@@ -922,18 +980,23 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var videoStream = state.VideoStream;
var isColorDepth10 = IsColorDepth10(state);
+ var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty;
+ var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
- if (isColorDepth10
- && _mediaEncoder.SupportsHwaccel("opencl")
- && encodingOptions.EnableTonemapping
- && !string.IsNullOrEmpty(videoStream.VideoRange)
- && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase))
+ if (!isNvdecDecoder)
{
- param += " -pix_fmt nv12";
- }
- else
- {
- param += " -pix_fmt yuv420p";
+ if (isColorDepth10
+ && _mediaEncoder.SupportsHwaccel("opencl")
+ && encodingOptions.EnableTonemapping
+ && !string.IsNullOrEmpty(videoStream.VideoRange)
+ && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase))
+ {
+ param += " -pix_fmt nv12";
+ }
+ else
+ {
+ param += " -pix_fmt yuv420p";
+ }
}
}
@@ -1912,12 +1975,18 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
var isVaapiHevcEncoder = outputVideoCodec.IndexOf("hevc_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
+ var isQsvH264Encoder = outputVideoCodec.Contains("h264_qsv", StringComparison.OrdinalIgnoreCase);
+ var isQsvHevcEncoder = outputVideoCodec.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase);
+ var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
+ var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
var isTonemappingSupported = IsTonemappingSupported(state, options);
+ var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
+ var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
// Tonemapping and burn-in graphical subtitles requires overlay_vaapi.
// But it's still in ffmpeg mailing list. Disable it for now.
- if (isTonemappingSupported && isTonemappingSupportedOnVaapi)
+ if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported)
{
return GetOutputSizeParam(state, options, outputVideoCodec);
}
@@ -1940,11 +2009,15 @@ namespace MediaBrowser.Controller.MediaEncoding
height.Value);
}
- // For QSV, feed it into hardware encoder now
- if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
- || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)))
+ if (!string.IsNullOrEmpty(videoSizeParam)
+ && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported))
{
- videoSizeParam += ",hwupload=extra_hw_frames=64";
+ // For QSV, feed it into hardware encoder now
+ if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)))
+ {
+ videoSizeParam += ",hwupload=extra_hw_frames=64";
+ }
}
}
@@ -1971,7 +2044,9 @@ namespace MediaBrowser.Controller.MediaEncoding
[sub]: SW scaling subtitle to FixedOutputSize
[base][sub]: SW overlay
*/
- retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"";
+ retStr = !outputSizeParam.IsEmpty
+ ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""
+ : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"";
}
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
@@ -1984,7 +2059,9 @@ namespace MediaBrowser.Controller.MediaEncoding
[sub]: SW scaling subtitle to FixedOutputSize
[base][sub]: SW overlay
*/
- retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"";
+ retStr = !outputSizeParam.IsEmpty
+ ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""
+ : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"";
}
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|| string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
@@ -1995,13 +2072,23 @@ namespace MediaBrowser.Controller.MediaEncoding
with fixed frame size.
Currently only supports linux.
*/
- if (isLinux)
+ if (isTonemappingSupportedOnQsv && isVppTonemappingSupported)
+ {
+ retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload,format=nv12[base];[base][sub]overlay\"";
+ }
+ else if (isLinux)
{
retStr = !outputSizeParam.IsEmpty
? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\""
: " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"";
}
}
+ else if (isNvdecDecoder && isNvencEncoder)
+ {
+ retStr = !outputSizeParam.IsEmpty
+ ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=nv12|yuv420p,hwupload_cuda\""
+ : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=nv12|yuv420p,hwupload_cuda\"";
+ }
return string.Format(
CultureInfo.InvariantCulture,
@@ -2092,17 +2179,28 @@ namespace MediaBrowser.Controller.MediaEncoding
|| state.DeInterlace("h265", true)
|| state.DeInterlace("hevc", true);
+ var isVaapiDecoder = videoDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
+ var isVaapiH264Encoder = videoEncoder.Contains("h264_vaapi", StringComparison.OrdinalIgnoreCase);
+ var isVaapiHevcEncoder = videoEncoder.Contains("hevc_vaapi", StringComparison.OrdinalIgnoreCase);
+ var isQsvH264Encoder = videoEncoder.Contains("h264_qsv", StringComparison.OrdinalIgnoreCase);
+ var isQsvHevcEncoder = videoEncoder.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase);
var isTonemappingSupported = IsTonemappingSupported(state, options);
- var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && !qsv_or_vaapi;
+ var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
+ var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)&& isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
+ var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
+ var isP010PixFmtRequired = (isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported))
+ || (isTonemappingSupportedOnQsv && isVppTonemappingSupported);
- var outputPixFmt = string.Empty;
- if (isTonemappingSupported && isTonemappingSupportedOnVaapi)
+
+ var outputPixFmt = "format=nv12";
+ if (isP010PixFmtRequired)
{
- outputPixFmt = "format=p010:out_range=limited";
+ outputPixFmt = "format=p010";
}
- else
+
+ if (isTonemappingSupportedOnQsv && isVppTonemappingSupported)
{
- outputPixFmt = "format=nv12";
+ qsv_or_vaapi = false;
}
if (!videoWidth.HasValue
@@ -2122,7 +2220,9 @@ namespace MediaBrowser.Controller.MediaEncoding
":" + outputPixFmt,
(qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty));
}
- else
+
+ // Assert 10-bit is P010 so as we can avoid the extra scaler to get a bit more fps on high res HDR videos.
+ else if (!isP010PixFmtRequired)
{
filters.Add(
string.Format(
@@ -2133,6 +2233,49 @@ namespace MediaBrowser.Controller.MediaEncoding
(qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty));
}
}
+ else if ((videoDecoder ?? string.Empty).Contains("cuda", StringComparison.OrdinalIgnoreCase)
+ && width.HasValue
+ && height.HasValue)
+ {
+ var outputWidth = width.Value;
+ var outputHeight = height.Value;
+
+ var isTonemappingSupported = IsTonemappingSupported(state, options);
+ var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase);
+ var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilter("scale_cuda", "Output format (default \"same\")");
+
+ var outputPixFmt = string.Empty;
+ if (isCudaFormatConversionSupported)
+ {
+ outputPixFmt = "format=nv12";
+ if (isTonemappingSupported && isTonemappingSupportedOnNvenc)
+ {
+ outputPixFmt = "format=p010";
+ }
+ }
+
+ if (!videoWidth.HasValue
+ || outputWidth != videoWidth.Value
+ || !videoHeight.HasValue
+ || outputHeight != videoHeight.Value)
+ {
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "scale_cuda=w={0}:h={1}{2}",
+ outputWidth,
+ outputHeight,
+ isCudaFormatConversionSupported ? (":" + outputPixFmt) : string.Empty));
+ }
+ else if (isCudaFormatConversionSupported)
+ {
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "scale_cuda={0}",
+ outputPixFmt));
+ }
+ }
else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
&& width.HasValue
&& height.HasValue)
@@ -2367,17 +2510,22 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiHevcEncoder = outputVideoCodec.IndexOf("hevc_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1;
var isQsvHevcEncoder = outputVideoCodec.IndexOf("hevc_qsv", StringComparison.OrdinalIgnoreCase) != -1;
- var isNvdecH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
- var isNvdecHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
+ var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
+ var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
+ var isCuvidH264Decoder = videoDecoder.Contains("h264_cuvid", StringComparison.OrdinalIgnoreCase);
+ var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase);
var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1;
var isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1;
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
var isColorDepth10 = IsColorDepth10(state);
var isTonemappingSupported = IsTonemappingSupported(state, options);
- var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecHevcDecoder || isSwDecoder;
- var isTonemappingSupportedOnAmf = string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && isD3d11vaDecoder || isSwDecoder;
+ var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
+ var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder);
+ var isTonemappingSupportedOnAmf = string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && (isD3d11vaDecoder || isSwDecoder);
var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
+ var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
+ var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
@@ -2385,10 +2533,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.RealFrameRate ?? 60) <= 30;
var isScalingInAdvance = false;
+ var isCudaDeintInAdvance = false;
+ var isHwuploadCudaRequired = false;
var isDeinterlaceH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
var isDeinterlaceHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
- if (isTonemappingSupportedOnNvenc || isTonemappingSupportedOnAmf || isTonemappingSupportedOnVaapi)
+ // Add OpenCL tonemapping filter for NVENC/AMF/VAAPI.
+ if (isTonemappingSupportedOnNvenc || isTonemappingSupportedOnAmf || (isTonemappingSupportedOnVaapi && !isVppTonemappingSupported))
{
// Currently only with the use of NVENC decoder can we get a decent performance.
// Currently only the HEVC/H265 format is supported with NVDEC decoder.
@@ -2428,15 +2579,17 @@ namespace MediaBrowser.Controller.MediaEncoding
filters.Add("format=p010");
}
- if (isNvdecHevcDecoder || isSwDecoder || isD3d11vaDecoder)
+ if ((isDeinterlaceH264 || isDeinterlaceHevc) && isNvdecDecoder)
{
- // Upload the HDR10 or HLG data to the OpenCL device,
- // use tonemap_opencl filter for tone mapping,
- // and then download the SDR data to memory.
- filters.Add("hwupload");
+ isCudaDeintInAdvance = true;
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "yadif_cuda={0}:-1:0",
+ doubleRateDeinterlace ? "1" : "0"));
}
- if (isVaapiDecoder)
+ if (isVaapiDecoder || isNvdecDecoder)
{
isScalingInAdvance = true;
filters.AddRange(
@@ -2452,11 +2605,28 @@ namespace MediaBrowser.Controller.MediaEncoding
request.Height,
request.MaxWidth,
request.MaxHeight));
+ }
- // hwmap the HDR data to opencl device by cl-va p010 interop.
+ // hwmap the HDR data to opencl device by cl-va p010 interop.
+ if (isVaapiDecoder)
+ {
filters.Add("hwmap");
}
+ // convert cuda device data to p010 host data.
+ if (isNvdecDecoder)
+ {
+ filters.Add("hwdownload,format=p010");
+ }
+
+ if (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder || isD3d11vaDecoder)
+ {
+ // Upload the HDR10 or HLG data to the OpenCL device,
+ // use tonemap_opencl filter for tone mapping,
+ // and then download the SDR data to memory.
+ filters.Add("hwupload");
+ }
+
filters.Add(
string.Format(
CultureInfo.InvariantCulture,
@@ -2468,21 +2638,15 @@ namespace MediaBrowser.Controller.MediaEncoding
options.TonemappingParam,
options.TonemappingRange));
- if (isNvdecHevcDecoder || isSwDecoder || isD3d11vaDecoder)
+ if (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder || isD3d11vaDecoder)
{
filters.Add("hwdownload");
+ filters.Add("format=nv12");
}
- if (isSwDecoder || isD3d11vaDecoder)
+ if (isNvdecDecoder && isNvencEncoder)
{
- if (isLibX264Encoder
- || isLibX265Encoder
- || hasGraphicalSubs
- || (isNvdecHevcDecoder && isDeinterlaceHevc)
- || (!isNvdecHevcDecoder && isDeinterlaceH264 || isDeinterlaceHevc))
- {
- filters.Add("format=nv12");
- }
+ isHwuploadCudaRequired = true;
}
if (isVaapiDecoder)
@@ -2494,22 +2658,25 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// When the input may or may not be hardware VAAPI decodable.
- if ((isVaapiH264Encoder || isVaapiHevcEncoder) && !isTonemappingSupported && !isTonemappingSupportedOnVaapi)
+ if ((isVaapiH264Encoder || isVaapiHevcEncoder)
+ && !(isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported)))
{
filters.Add("format=nv12|vaapi");
filters.Add("hwupload");
}
// When burning in graphical subtitles using overlay_qsv, upload videostream to the same qsv context.
- else if (isLinux && hasGraphicalSubs && (isQsvH264Encoder || isQsvHevcEncoder))
+ else if (isLinux && hasGraphicalSubs && (isQsvH264Encoder || isQsvHevcEncoder)
+ && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported))
{
filters.Add("hwupload=extra_hw_frames=64");
}
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first.
- else if (IsVaapiSupported(state) && isVaapiDecoder && (isLibX264Encoder || isLibX265Encoder))
+ else if ((IsVaapiSupported(state) && isVaapiDecoder) && (isLibX264Encoder || isLibX265Encoder)
+ && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported))
{
- var codec = videoStream.Codec.ToLowerInvariant();
+ var codec = videoStream.Codec;
// Assert 10-bit hardware VAAPI decodable
if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
@@ -2534,9 +2701,10 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// Add hardware deinterlace filter before scaling filter.
- if (isDeinterlaceH264)
+ if (isDeinterlaceH264 || isDeinterlaceHevc)
{
- if (isVaapiH264Encoder)
+ if (isVaapiEncoder
+ || (isTonemappingSupportedOnQsv && isVppTonemappingSupported))
{
filters.Add(
string.Format(
@@ -2544,6 +2712,14 @@ namespace MediaBrowser.Controller.MediaEncoding
"deinterlace_vaapi=rate={0}",
doubleRateDeinterlace ? "field" : "frame"));
}
+ else if (isNvdecDecoder && !isCudaDeintInAdvance)
+ {
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "yadif_cuda={0}:-1:0",
+ doubleRateDeinterlace ? "1" : "0"));
+ }
}
// Add software deinterlace filter before scaling filter.
@@ -2552,7 +2728,8 @@ namespace MediaBrowser.Controller.MediaEncoding
&& !isVaapiHevcEncoder
&& !isQsvH264Encoder
&& !isQsvHevcEncoder
- && !isNvdecH264Decoder)
+ && !isNvdecDecoder
+ && !isCuvidH264Decoder)
{
if (string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase))
{
@@ -2590,14 +2767,76 @@ namespace MediaBrowser.Controller.MediaEncoding
request.MaxHeight));
}
+ // Add VPP tonemapping filter for VAAPI.
+ // Full hardware based video post processing, faster than OpenCL but lacks fine tuning options.
+ if ((isTonemappingSupportedOnVaapi || isTonemappingSupportedOnQsv)
+ && isVppTonemappingSupported)
+ {
+ filters.Add("tonemap_vaapi=format=nv12:transfer=bt709:matrix=bt709:primaries=bt709");
+ }
+
+ // Another case is when using Nvenc decoder.
+ if (isNvdecDecoder && !isTonemappingSupported)
+ {
+ var codec = videoStream.Codec;
+ var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilter("scale_cuda", "Output format (default \"same\")");
+
+ // Assert 10-bit hardware decodable
+ if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)))
+ {
+ if (isCudaFormatConversionSupported)
+ {
+ if (isLibX264Encoder || isLibX265Encoder || hasSubs)
+ {
+ if (isNvencEncoder)
+ {
+ isHwuploadCudaRequired = true;
+ }
+
+ filters.Add("hwdownload");
+ filters.Add("format=nv12");
+ }
+ }
+ else
+ {
+ // Download data from GPU to CPU as p010 format.
+ filters.Add("hwdownload");
+ filters.Add("format=p010");
+
+ // Cuda lacks of a pixel format converter.
+ if (isNvencEncoder)
+ {
+ isHwuploadCudaRequired = true;
+ filters.Add("format=yuv420p");
+ }
+ }
+ }
+
+ // Assert 8-bit hardware decodable
+ else if (!isColorDepth10 && (isLibX264Encoder || isLibX265Encoder || hasSubs))
+ {
+ if (isNvencEncoder)
+ {
+ isHwuploadCudaRequired = true;
+ }
+
+ filters.Add("hwdownload");
+ filters.Add("format=nv12");
+ }
+ }
+
// Add parameters to use VAAPI with burn-in text subtitles (GH issue #642)
- if (isVaapiH264Encoder || isVaapiHevcEncoder)
+ if (isVaapiH264Encoder
+ || isVaapiHevcEncoder
+ || (isTonemappingSupportedOnQsv && isVppTonemappingSupported))
{
if (hasTextSubs)
{
// Convert hw context from ocl to va.
// For tonemapping and text subs burn-in.
- if (isTonemappingSupported && isTonemappingSupportedOnVaapi)
+ if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported)
{
filters.Add("scale_vaapi");
}
@@ -2608,8 +2847,6 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- var output = string.Empty;
-
if (hasTextSubs)
{
var subParam = GetTextSubtitleParam(state);
@@ -2618,12 +2855,34 @@ namespace MediaBrowser.Controller.MediaEncoding
// Ensure proper filters are passed to ffmpeg in case of hardware acceleration via VA-API
// Reference: https://trac.ffmpeg.org/wiki/Hardware/VAAPI
- if (isVaapiH264Encoder)
+ if (isVaapiH264Encoder || isVaapiHevcEncoder)
{
filters.Add("hwmap");
}
+
+ if (isTonemappingSupportedOnQsv && isVppTonemappingSupported)
+ {
+ filters.Add("hwmap,format=vaapi");
+ }
+
+ if (isNvdecDecoder && isNvencEncoder)
+ {
+ isHwuploadCudaRequired = true;
+ }
+ }
+
+ // Interop the VAAPI data to QSV for hybrid tonemapping
+ if (isTonemappingSupportedOnQsv && isVppTonemappingSupported && !hasGraphicalSubs)
+ {
+ filters.Add("hwmap=derive_device=qsv,scale_qsv");
}
+ if (isHwuploadCudaRequired && !hasGraphicalSubs)
+ {
+ filters.Add("hwupload_cuda");
+ }
+
+ var output = string.Empty;
if (filters.Count > 0)
{
output += string.Format(
@@ -3096,63 +3355,32 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
+ // Hybrid VPP tonemapping with VAAPI
+ if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)
+ && IsVppTonemappingSupported(state, encodingOptions))
+ {
+ // Since tonemap_vaapi only support HEVC for now, no need to check the codec again.
+ return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10);
+ }
+
if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{
switch (videoStream.Codec.ToLowerInvariant())
{
case "avc":
case "h264":
- if (_mediaEncoder.SupportsDecoder("h264_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
- {
- // qsv decoder does not support 10-bit input
- if ((videoStream.BitDepth ?? 8) > 8)
- {
- encodingOptions.HardwareDecodingCodecs = Array.Empty<string>();
- return null;
- }
-
- return "-c:v h264_qsv";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "h264_qsv", "h264", isColorDepth10);
case "hevc":
case "h265":
- if (_mediaEncoder.SupportsDecoder("hevc_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase))
- {
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_qsv";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "hevc_qsv", "hevc", isColorDepth10);
case "mpeg2video":
- if (_mediaEncoder.SupportsDecoder("mpeg2_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg2_qsv";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "mpeg2_qsv", "mpeg2video", isColorDepth10);
case "vc1":
- if (_mediaEncoder.SupportsDecoder("vc1_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v vc1_qsv";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vc1_qsv", "vc1", isColorDepth10);
case "vp8":
- if (_mediaEncoder.SupportsDecoder("vp8_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v vp8_qsv";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vp8_qsv", "vp8", isColorDepth10);
case "vp9":
- if (_mediaEncoder.SupportsDecoder("vp9_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase))
- {
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_qsv";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vp9_qsv", "vp9", isColorDepth10);
}
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
@@ -3161,57 +3389,34 @@ namespace MediaBrowser.Controller.MediaEncoding
{
case "avc":
case "h264":
- if (_mediaEncoder.SupportsDecoder("h264_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v h264_cuvid";
- }
-
- break;
+ return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
+ ? GetHwaccelType(state, encodingOptions, "h264", isColorDepth10)
+ : GetHwDecoderName(encodingOptions, "h264_cuvid", "h264", isColorDepth10);
case "hevc":
case "h265":
- if (_mediaEncoder.SupportsDecoder("hevc_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase))
- {
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_cuvid";
- }
-
- break;
+ return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
+ ? GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10)
+ : GetHwDecoderName(encodingOptions, "hevc_cuvid", "hevc", isColorDepth10);
case "mpeg2video":
- if (_mediaEncoder.SupportsDecoder("mpeg2_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg2_cuvid";
- }
-
- break;
+ return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
+ ? GetHwaccelType(state, encodingOptions, "mpeg2video", isColorDepth10)
+ : GetHwDecoderName(encodingOptions, "mpeg2_cuvid", "mpeg2video", isColorDepth10);
case "vc1":
- if (_mediaEncoder.SupportsDecoder("vc1_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v vc1_cuvid";
- }
-
- break;
+ return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
+ ? GetHwaccelType(state, encodingOptions, "vc1", isColorDepth10)
+ : GetHwDecoderName(encodingOptions, "vc1_cuvid", "vc1", isColorDepth10);
case "mpeg4":
- if (_mediaEncoder.SupportsDecoder("mpeg4_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg4_cuvid";
- }
-
- break;
+ return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
+ ? GetHwaccelType(state, encodingOptions, "mpeg4", isColorDepth10)
+ : GetHwDecoderName(encodingOptions, "mpeg4_cuvid", "mpeg4", isColorDepth10);
case "vp8":
- if (_mediaEncoder.SupportsDecoder("vp8_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v vp8_cuvid";
- }
-
- break;
+ return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
+ ? GetHwaccelType(state, encodingOptions, "vp8", isColorDepth10)
+ : GetHwDecoderName(encodingOptions, "vp8_cuvid", "vp8", isColorDepth10);
case "vp9":
- if (_mediaEncoder.SupportsDecoder("vp9_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase))
- {
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_cuvid";
- }
-
- break;
+ return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
+ ? GetHwaccelType(state, encodingOptions, "vp9", isColorDepth10)
+ : GetHwDecoderName(encodingOptions, "vp9_cuvid", "vp9", isColorDepth10);
}
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "mediacodec", StringComparison.OrdinalIgnoreCase))
@@ -3220,50 +3425,18 @@ namespace MediaBrowser.Controller.MediaEncoding
{
case "avc":
case "h264":
- if (_mediaEncoder.SupportsDecoder("h264_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v h264_mediacodec";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "h264_mediacodec", "h264", isColorDepth10);
case "hevc":
case "h265":
- if (_mediaEncoder.SupportsDecoder("hevc_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase))
- {
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_mediacodec";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "hevc_mediacodec", "hevc", isColorDepth10);
case "mpeg2video":
- if (_mediaEncoder.SupportsDecoder("mpeg2_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg2_mediacodec";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "mpeg2_mediacodec", "mpeg2video", isColorDepth10);
case "mpeg4":
- if (_mediaEncoder.SupportsDecoder("mpeg4_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg4_mediacodec";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "mpeg4_mediacodec", "mpeg4", isColorDepth10);
case "vp8":
- if (_mediaEncoder.SupportsDecoder("vp8_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v vp8_mediacodec";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vp8_mediacodec", "vp8", isColorDepth10);
case "vp9":
- if (_mediaEncoder.SupportsDecoder("vp9_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase))
- {
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_mediacodec";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vp9_mediacodec", "vp9", isColorDepth10);
}
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase))
@@ -3272,33 +3445,13 @@ namespace MediaBrowser.Controller.MediaEncoding
{
case "avc":
case "h264":
- if (_mediaEncoder.SupportsDecoder("h264_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v h264_mmal";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "h264_mmal", "h264", isColorDepth10);
case "mpeg2video":
- if (_mediaEncoder.SupportsDecoder("mpeg2_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg2_mmal";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "mpeg2_mmal", "mpeg2video", isColorDepth10);
case "mpeg4":
- if (_mediaEncoder.SupportsDecoder("mpeg4_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg4_mmal";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "mpeg4_mmal", "mpeg4", isColorDepth10);
case "vc1":
- if (_mediaEncoder.SupportsDecoder("vc1_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v vc1_mmal";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vc1_mmal", "vc1", isColorDepth10);
}
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
@@ -3307,20 +3460,18 @@ namespace MediaBrowser.Controller.MediaEncoding
{
case "avc":
case "h264":
- return GetHwaccelType(state, encodingOptions, "h264");
+ return GetHwaccelType(state, encodingOptions, "h264", isColorDepth10);
case "hevc":
case "h265":
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : GetHwaccelType(state, encodingOptions, "hevc");
+ return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10);
case "mpeg2video":
- return GetHwaccelType(state, encodingOptions, "mpeg2video");
+ return GetHwaccelType(state, encodingOptions, "mpeg2video", isColorDepth10);
case "vc1":
- return GetHwaccelType(state, encodingOptions, "vc1");
+ return GetHwaccelType(state, encodingOptions, "vc1", isColorDepth10);
case "mpeg4":
- return GetHwaccelType(state, encodingOptions, "mpeg4");
+ return GetHwaccelType(state, encodingOptions, "mpeg4", isColorDepth10);
case "vp9":
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : GetHwaccelType(state, encodingOptions, "vp9");
+ return GetHwaccelType(state, encodingOptions, "vp9", isColorDepth10);
}
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
@@ -3329,20 +3480,18 @@ namespace MediaBrowser.Controller.MediaEncoding
{
case "avc":
case "h264":
- return GetHwaccelType(state, encodingOptions, "h264");
+ return GetHwaccelType(state, encodingOptions, "h264", isColorDepth10);
case "hevc":
case "h265":
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : GetHwaccelType(state, encodingOptions, "hevc");
+ return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10);
case "mpeg2video":
- return GetHwaccelType(state, encodingOptions, "mpeg2video");
+ return GetHwaccelType(state, encodingOptions, "mpeg2video", isColorDepth10);
case "vc1":
- return GetHwaccelType(state, encodingOptions, "vc1");
+ return GetHwaccelType(state, encodingOptions, "vc1", isColorDepth10);
case "vp8":
- return GetHwaccelType(state, encodingOptions, "vp8");
+ return GetHwaccelType(state, encodingOptions, "vp8", isColorDepth10);
case "vp9":
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : GetHwaccelType(state, encodingOptions, "vp9");
+ return GetHwaccelType(state, encodingOptions, "vp9", isColorDepth10);
}
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
@@ -3351,57 +3500,20 @@ namespace MediaBrowser.Controller.MediaEncoding
{
case "avc":
case "h264":
- if (_mediaEncoder.SupportsDecoder("h264_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v h264_opencl";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "h264_opencl", "h264", isColorDepth10);
case "hevc":
case "h265":
- if (_mediaEncoder.SupportsDecoder("hevc_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
- {
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_opencl";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "hevc_opencl", "hevc", isColorDepth10);
case "mpeg2video":
- if (_mediaEncoder.SupportsDecoder("mpeg2_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg2_opencl";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "mpeg2_opencl", "mpeg2video", isColorDepth10);
case "mpeg4":
- if (_mediaEncoder.SupportsDecoder("mpeg4_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v mpeg4_opencl";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "mpeg4_opencl", "mpeg4", isColorDepth10);
case "vc1":
- if (_mediaEncoder.SupportsDecoder("vc1_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v vc1_opencl";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vc1_opencl", "vc1", isColorDepth10);
case "vp8":
- if (_mediaEncoder.SupportsDecoder("vp8_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
- {
- return "-c:v vp8_opencl";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vp8_opencl", "vp8", isColorDepth10);
case "vp9":
- if (_mediaEncoder.SupportsDecoder("vp9_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase))
- {
- return (isColorDepth10 &&
- !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_opencl";
- }
-
- break;
+ return GetHwDecoderName(encodingOptions, "vp9_opencl", "vp9", isColorDepth10);
}
}
}
@@ -3425,14 +3537,42 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
+ /// Gets a hw decoder name
+ /// </summary>
+ public string GetHwDecoderName(EncodingOptions options, string decoder, string videoCodec, bool isColorDepth10)
+ {
+ var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoder) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase);
+ if (isColorDepth10 && isCodecAvailable)
+ {
+ if ((options.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Hevc)
+ || (options.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Vp9))
+ {
+ return null;
+ }
+ }
+
+ return isCodecAvailable ? ("-c:v " + decoder) : null;
+ }
+
+ /// <summary>
/// Gets a hwaccel type to use as a hardware decoder(dxva/vaapi) depending on the system
/// </summary>
- public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec)
+ public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, bool isColorDepth10)
{
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
var isWindows8orLater = Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1);
var isDxvaSupported = _mediaEncoder.SupportsHwaccel("dxva2") || _mediaEncoder.SupportsHwaccel("d3d11va");
+ var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase);
+
+ if (isColorDepth10 && isCodecAvailable)
+ {
+ if ((options.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Hevc)
+ || (options.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Vp9))
+ {
+ return null;
+ }
+ }
if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
{
@@ -3451,7 +3591,9 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
+ || (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)
+ && IsVppTonemappingSupported(state, options)))
{
if (IsVaapiSupported(state) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase))
{
@@ -3462,6 +3604,14 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ if (options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase))
+ {
+ return "-hwaccel cuda";
+ }
+ }
+
return null;
}
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index 34fe895cc..5cbb57990 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -51,6 +51,14 @@ namespace MediaBrowser.Controller.MediaEncoding
bool SupportsHwaccel(string hwaccel);
/// <summary>
+ /// Whether given filter is supported.
+ /// </summary>
+ /// <param name="filter">The filter.</param>
+ /// <param name="option">The option.</param>
+ /// <returns><c>true</c> if the filter is supported, <c>false</c> otherwise.</returns>
+ bool SupportsFilter(string filter, string option);
+
+ /// <summary>
/// Extracts the audio image.
/// </summary>
/// <param name="path">The path.</param>
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 92f16ab95..9e2417603 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -296,6 +296,38 @@ namespace MediaBrowser.MediaEncoding.Encoder
return found;
}
+ public bool CheckFilter(string filter, string option)
+ {
+ if (string.IsNullOrEmpty(filter))
+ {
+ return false;
+ }
+
+ string output = null;
+ try
+ {
+ output = GetProcessOutput(_encoderPath, "-h filter=" + filter);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error detecting the given filter");
+ }
+
+ if (output.Contains("Filter " + filter, StringComparison.Ordinal))
+ {
+ if (string.IsNullOrEmpty(option))
+ {
+ return true;
+ }
+
+ return output.Contains(option, StringComparison.Ordinal);
+ }
+
+ _logger.LogWarning("Filter: {Name} with option {Option} is not available", filter, option);
+
+ return false;
+ }
+
private IEnumerable<string> GetCodecs(Codec codec)
{
string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index fbd08a97c..c0b6cf28b 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -295,6 +295,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase);
}
+ public bool SupportsFilter(string filter, string option)
+ {
+ if (_ffmpegPath != null)
+ {
+ var validator = new EncoderValidator(_logger, _ffmpegPath);
+ return validator.CheckFilter(filter, option);
+ }
+
+ return false;
+ }
+
public bool CanEncodeToAudioCodec(string codec)
{
if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
index e0b7914fb..bb48bed27 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
@@ -57,7 +57,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
subEvent.Text = subEvent.Text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase);
- subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase);
+ subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w0-9]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase);
trackEvents.Add(subEvent);
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
index 4a87f87dc..ccef7eeea 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
@@ -79,7 +79,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
subEvent.Text = string.Join(ParserValues.NewLine, multiline);
subEvent.Text = subEvent.Text.Replace(@"\N", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase);
- subEvent.Text = Regex.Replace(subEvent.Text, @"\{(?:\\\d?[\w.-]+(?:\([^\)]*\)|&H?[0-9A-Fa-f]+&|))+\}", string.Empty, RegexOptions.IgnoreCase);
+ subEvent.Text = Regex.Replace(subEvent.Text, @"\{(?:\\[0-9]?[\w.-]+(?:\([^\)]*\)|&H?[0-9A-Fa-f]+&|))+\}", string.Empty, RegexOptions.IgnoreCase);
subEvent.Text = Regex.Replace(subEvent.Text, "<", "&lt;", RegexOptions.IgnoreCase);
subEvent.Text = Regex.Replace(subEvent.Text, ">", "&gt;", RegexOptions.IgnoreCase);
subEvent.Text = Regex.Replace(subEvent.Text, "&lt;(\\/?(font|b|u|i|s))((\\s+(\\w|\\w[\\w\\-]*\\w)(\\s*=\\s*(?:\\\".*?\\\"|'.*?'|[^'\\\">\\s]+))?)+\\s*|\\s*)(\\/?)&gt;", "<$1$3$7>", RegexOptions.IgnoreCase);
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index 38b333510..da467e133 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -39,6 +39,8 @@ namespace MediaBrowser.Model.Configuration
public bool EnableTonemapping { get; set; }
+ public bool EnableVppTonemapping { get; set; }
+
public string TonemappingAlgorithm { get; set; }
public string TonemappingRange { get; set; }
@@ -65,6 +67,8 @@ namespace MediaBrowser.Model.Configuration
public bool EnableDecodingColorDepth10Vp9 { get; set; }
+ public bool EnableEnhancedNvdecDecoder { get; set; }
+
public bool EnableHardwareEncoding { get; set; }
public bool AllowHevcEncoding { get; set; }
@@ -88,6 +92,7 @@ namespace MediaBrowser.Model.Configuration
// The left side of the dot is the platform number, and the right side is the device number on the platform.
OpenclDevice = "0.0";
EnableTonemapping = false;
+ EnableVppTonemapping = false;
TonemappingAlgorithm = "hable";
TonemappingRange = "auto";
TonemappingDesat = 0;
@@ -100,6 +105,7 @@ namespace MediaBrowser.Model.Configuration
DeinterlaceMethod = "yadif";
EnableDecodingColorDepth10Hevc = true;
EnableDecodingColorDepth10Vp9 = true;
+ EnableEnhancedNvdecDecoder = true;
EnableHardwareEncoding = true;
AllowHevcEncoding = true;
EnableSubtitleExtraction = true;
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 0f0ad0f9a..ba55a2ace 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -418,8 +418,6 @@ namespace MediaBrowser.Model.Configuration
public PathSubstitution[] PathSubstitutions { get; set; } = Array.Empty<PathSubstitution>();
- public bool EnableSimpleArtistDetection { get; set; } = false;
-
public string[] UninstalledPlugins { get; set; } = Array.Empty<string>();
/// <summary>
@@ -461,10 +459,5 @@ namespace MediaBrowser.Model.Configuration
/// Gets or sets a value indicating whether older plugins should automatically be deleted from the plugin folder.
/// </summary>
public bool RemoveOldPlugins { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether plugin image should be disabled.
- /// </summary>
- public bool DisablePluginImages { get; set; }
}
}
diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs
index 3f7aac9cd..2f9f9d3cd 100644
--- a/MediaBrowser.Model/Dto/BaseItemDto.cs
+++ b/MediaBrowser.Model/Dto/BaseItemDto.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using Jellyfin.Data.Enums;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Library;
@@ -276,7 +277,7 @@ namespace MediaBrowser.Model.Dto
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
- public string Type { get; set; }
+ public BaseItemKind Type { get; set; }
/// <summary>
/// Gets or sets the people.
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index e5166672f..c53428651 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Model</PackageId>
- <VersionPrefix>10.7.0</VersionPrefix>
+ <VersionPrefix>10.8.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index f889327e0..67700f5fb 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -721,20 +721,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
break;
}
- case "musicBrainzArtistID":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- break;
- }
-
- var id = reader.ReadElementContentAsString();
- item.SetProviderId(MetadataProvider.MusicBrainzArtist.ToString(), id);
-
- break;
- }
-
default:
string readerName = reader.Name;
if (_validProviderIds.TryGetValue(readerName, out string? providerIdValue))
diff --git a/SharedVersion.cs b/SharedVersion.cs
index d17074fc0..5e2f151a2 100644
--- a/SharedVersion.cs
+++ b/SharedVersion.cs
@@ -1,4 +1,4 @@
using System.Reflection;
-[assembly: AssemblyVersion("10.7.0")]
-[assembly: AssemblyFileVersion("10.7.0")]
+[assembly: AssemblyVersion("10.8.0")]
+[assembly: AssemblyFileVersion("10.8.0")]
diff --git a/build.yaml b/build.yaml
index 7c32d955c..18434ee00 100644
--- a/build.yaml
+++ b/build.yaml
@@ -1,7 +1,7 @@
---
# We just wrap `build` so this is really it
name: "jellyfin"
-version: "10.7.0"
+version: "10.8.0"
packages:
- debian.amd64
- debian.arm64
diff --git a/debian/changelog b/debian/changelog
index 184143fe9..430594cac 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+jellyfin-server (10.8.0-1) unstable; urgency=medium
+
+ * Forthcoming stable release
+
+ -- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 04 Dec 2020 21:55:12 -0500
+
jellyfin-server (10.7.0-1) unstable; urgency=medium
* Forthcoming stable release
diff --git a/debian/metapackage/jellyfin b/debian/metapackage/jellyfin
index 026fcb821..a9a0ae5b0 100644
--- a/debian/metapackage/jellyfin
+++ b/debian/metapackage/jellyfin
@@ -5,7 +5,7 @@ Homepage: https://jellyfin.org
Standards-Version: 3.9.2
Package: jellyfin
-Version: 10.7.0
+Version: 10.8.0
Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org>
Depends: jellyfin-server, jellyfin-web
Description: Provides the Jellyfin Free Software Media System
diff --git a/deployment/Dockerfile.debian.amd64 b/deployment/Dockerfile.debian.amd64
index f5cf232d6..428072613 100644
--- a/deployment/Dockerfile.debian.amd64
+++ b/deployment/Dockerfile.debian.amd64
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.debian.arm64 b/deployment/Dockerfile.debian.arm64
index d9414a610..b540efc09 100644
--- a/deployment/Dockerfile.debian.arm64
+++ b/deployment/Dockerfile.debian.arm64
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.debian.armhf b/deployment/Dockerfile.debian.armhf
index 7f2275aaa..426ce02fc 100644
--- a/deployment/Dockerfile.debian.armhf
+++ b/deployment/Dockerfile.debian.armhf
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.linux.amd64 b/deployment/Dockerfile.linux.amd64
index 54d75dcbe..3b91515f3 100644
--- a/deployment/Dockerfile.linux.amd64
+++ b/deployment/Dockerfile.linux.amd64
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.linux.amd64-musl b/deployment/Dockerfile.linux.amd64-musl
index e4c724219..2ca9072ba 100644
--- a/deployment/Dockerfile.linux.amd64-musl
+++ b/deployment/Dockerfile.linux.amd64-musl
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.linux.arm64 b/deployment/Dockerfile.linux.arm64
index 633802598..03efd306d 100644
--- a/deployment/Dockerfile.linux.arm64
+++ b/deployment/Dockerfile.linux.arm64
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.linux.armhf b/deployment/Dockerfile.linux.armhf
index ec0b015cc..585572204 100644
--- a/deployment/Dockerfile.linux.armhf
+++ b/deployment/Dockerfile.linux.armhf
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.macos b/deployment/Dockerfile.macos
index 25f15be18..b37afdcfb 100644
--- a/deployment/Dockerfile.macos
+++ b/deployment/Dockerfile.macos
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.portable b/deployment/Dockerfile.portable
index cd71ce9d4..686b20197 100644
--- a/deployment/Dockerfile.portable
+++ b/deployment/Dockerfile.portable
@@ -15,7 +15,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64
index ea539b360..3513bf8ec 100644
--- a/deployment/Dockerfile.ubuntu.amd64
+++ b/deployment/Dockerfile.ubuntu.amd64
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64
index f2f5368f7..5acdf0d17 100644
--- a/deployment/Dockerfile.ubuntu.arm64
+++ b/deployment/Dockerfile.ubuntu.arm64
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf
index ba597801b..42f757d05 100644
--- a/deployment/Dockerfile.ubuntu.armhf
+++ b/deployment/Dockerfile.ubuntu.armhf
@@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.windows.amd64 b/deployment/Dockerfile.windows.amd64
index c73126841..6ed1193fb 100644
--- a/deployment/Dockerfile.windows.amd64
+++ b/deployment/Dockerfile.windows.amd64
@@ -15,7 +15,7 @@ RUN apt-get update \
# Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
-RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec
index 71583c24e..928fe590f 100644
--- a/fedora/jellyfin.spec
+++ b/fedora/jellyfin.spec
@@ -7,7 +7,7 @@
%endif
Name: jellyfin
-Version: 10.7.0
+Version: 10.8.0
Release: 1%{?dist}
Summary: The Free Software Media System
License: GPLv3
@@ -137,6 +137,8 @@ fi
%systemd_postun_with_restart jellyfin.service
%changelog
+* Fri Dec 04 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
+- Forthcoming stable release
* Mon Jul 27 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
- Forthcoming stable release
* Mon Mar 23 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
diff --git a/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs b/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs
index 606041c7f..97e441b1d 100644
--- a/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs
+++ b/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs
@@ -6,11 +6,11 @@ using Xunit;
namespace Jellyfin.Api.Tests.Helpers
{
- public class RequestHelpersTests
+ public static class RequestHelpersTests
{
[Theory]
[MemberData(nameof(GetOrderBy_Success_TestData))]
- public void GetOrderBy_Success(IReadOnlyList<string> sortBy, IReadOnlyList<SortOrder> requestedSortOrder, (string, SortOrder)[] expected)
+ public static void GetOrderBy_Success(IReadOnlyList<string> sortBy, IReadOnlyList<SortOrder> requestedSortOrder, (string, SortOrder)[] expected)
{
Assert.Equal(expected, RequestHelpers.GetOrderBy(sortBy, requestedSortOrder));
}
@@ -55,5 +55,35 @@ namespace Jellyfin.Api.Tests.Helpers
}
};
}
+
+ [Fact]
+ public static void GetItemTypeStrings_Empty_Empty()
+ {
+ Assert.Empty(RequestHelpers.GetItemTypeStrings(Array.Empty<BaseItemKind>()));
+ }
+
+ [Fact]
+ public static void GetItemTypeStrings_Valid_Success()
+ {
+ BaseItemKind[] input =
+ {
+ BaseItemKind.AggregateFolder,
+ BaseItemKind.Audio,
+ BaseItemKind.BasePluginFolder,
+ BaseItemKind.CollectionFolder
+ };
+
+ string[] expected =
+ {
+ "AggregateFolder",
+ "Audio",
+ "BasePluginFolder",
+ "CollectionFolder"
+ };
+
+ var res = RequestHelpers.GetItemTypeStrings(input);
+
+ Assert.Equal(expected, res);
+ }
}
}
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index 3da728c63..eca3df79b 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -16,7 +16,7 @@
<PackageReference Include="AutoFixture" Version="4.15.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.15.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.2" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.3" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="xunit" Version="2.4.1" />
diff --git a/tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs b/tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs
new file mode 100644
index 000000000..1ce2096ea
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Buffers;
+using System.IO;
+using System.Text.Json;
+using Emby.Server.Implementations.HttpServer;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.HttpServer
+{
+ public class WebSocketConnectionTests
+ {
+ [Fact]
+ public void DeserializeWebSocketMessage_SingleSegment_Success()
+ {
+ var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!, null!);
+ var bytes = File.ReadAllBytes("Test Data/HttpServer/ForceKeepAlive.json");
+ con.DeserializeWebSocketMessage(new ReadOnlySequence<byte>(bytes), out var bytesConsumed);
+ Assert.Equal(109, bytesConsumed);
+ }
+
+ [Fact]
+ public void DeserializeWebSocketMessage_MultipleSegments_Success()
+ {
+ const int SplitPos = 64;
+ var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!, null!);
+ var bytes = File.ReadAllBytes("Test Data/HttpServer/ForceKeepAlive.json");
+ var seg1 = new BufferSegment(new Memory<byte>(bytes, 0, SplitPos));
+ var seg2 = seg1.Append(new Memory<byte>(bytes, SplitPos, bytes.Length - SplitPos));
+ con.DeserializeWebSocketMessage(new ReadOnlySequence<byte>(seg1, 0, seg2, seg2.Memory.Length - 1), out var bytesConsumed);
+ Assert.Equal(109, bytesConsumed);
+ }
+
+ [Fact]
+ public void DeserializeWebSocketMessage_ValidPartial_Success()
+ {
+ var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!, null!);
+ var bytes = File.ReadAllBytes("Test Data/HttpServer/ValidPartial.json");
+ con.DeserializeWebSocketMessage(new ReadOnlySequence<byte>(bytes), out var bytesConsumed);
+ Assert.Equal(109, bytesConsumed);
+ }
+
+ [Fact]
+ public void DeserializeWebSocketMessage_Partial_ThrowJsonException()
+ {
+ var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!, null!);
+ var bytes = File.ReadAllBytes("Test Data/HttpServer/Partial.json");
+ Assert.Throws<JsonException>(() => con.DeserializeWebSocketMessage(new ReadOnlySequence<byte>(bytes), out var bytesConsumed));
+ }
+
+ internal class BufferSegment : ReadOnlySequenceSegment<byte>
+ {
+ public BufferSegment(Memory<byte> memory)
+ {
+ Memory = memory;
+ }
+
+ public BufferSegment Append(Memory<byte> memory)
+ {
+ var segment = new BufferSegment(memory)
+ {
+ RunningIndex = RunningIndex + Memory.Length
+ };
+ Next = segment;
+ return segment;
+ }
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index 469fe01e2..174f29b09 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -14,6 +14,12 @@
</PropertyGroup>
<ItemGroup>
+ <None Include="Test Data\**\*.*">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ </ItemGroup>
+
+ <ItemGroup>
<PackageReference Include="AutoFixture" Version="4.15.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
@@ -35,11 +41,6 @@
<ProjectReference Include="..\..\Emby.Server.Implementations\Emby.Server.Implementations.csproj" />
</ItemGroup>
- <ItemGroup>
- <EmbeddedResource Include="LiveTv\discover.json" />
- <EmbeddedResource Include="LiveTv\lineup.json" />
- </ItemGroup>
-
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs
index 75939526d..8847239d9 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@@ -21,24 +22,15 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
public HdHomerunHostTests()
{
- const string BaseResourcePath = "Jellyfin.Server.Implementations.Tests.LiveTv.";
-
var messageHandler = new Mock<HttpMessageHandler>();
messageHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.Returns<HttpRequestMessage, CancellationToken>(
(m, _) =>
{
- var resource = BaseResourcePath + m.RequestUri?.Segments[^1];
- var stream = typeof(HdHomerunHostTests).Assembly.GetManifestResourceStream(resource);
- if (stream == null)
- {
- throw new NullReferenceException("Resource doesn't exist: " + resource);
- }
-
return Task.FromResult(new HttpResponseMessage()
{
- Content = new StreamContent(stream)
+ Content = new StreamContent(File.OpenRead("Test Data/LiveTv/" + m.RequestUri?.Segments[^1]))
});
});
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs
new file mode 100644
index 000000000..bc6a44741
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs
@@ -0,0 +1,45 @@
+using System;
+using System.IO;
+using Emby.Server.Implementations.Plugins;
+using MediaBrowser.Common.Plugins;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Plugins
+{
+ public class PluginManagerTests
+ {
+ private static readonly string _testPathRoot = Path.Combine(Path.GetTempPath(), "jellyfin-test-data");
+
+ [Fact]
+ public void SaveManifest_RoundTrip_Success()
+ {
+ var pluginManager = new PluginManager(new NullLogger<PluginManager>(), null!, null!, null!, new Version(1, 0));
+ var manifest = new PluginManifest()
+ {
+ Version = "1.0"
+ };
+
+ var tempPath = Path.Combine(_testPathRoot, "manifest-" + Path.GetRandomFileName());
+ Directory.CreateDirectory(tempPath);
+
+ Assert.True(pluginManager.SaveManifest(manifest, tempPath));
+
+ var res = pluginManager.LoadManifest(tempPath);
+
+ Assert.Equal(manifest.Category, res.Manifest.Category);
+ Assert.Equal(manifest.Changelog, res.Manifest.Changelog);
+ Assert.Equal(manifest.Description, res.Manifest.Description);
+ Assert.Equal(manifest.Id, res.Manifest.Id);
+ Assert.Equal(manifest.Name, res.Manifest.Name);
+ Assert.Equal(manifest.Overview, res.Manifest.Overview);
+ Assert.Equal(manifest.Owner, res.Manifest.Owner);
+ Assert.Equal(manifest.TargetAbi, res.Manifest.TargetAbi);
+ Assert.Equal(manifest.Timestamp, res.Manifest.Timestamp);
+ Assert.Equal(manifest.Version, res.Manifest.Version);
+ Assert.Equal(manifest.Status, res.Manifest.Status);
+ Assert.Equal(manifest.AutoUpdate, res.Manifest.AutoUpdate);
+ Assert.Equal(manifest.ImagePath, res.Manifest.ImagePath);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ForceKeepAlive.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ForceKeepAlive.json
new file mode 100644
index 000000000..0472a3cd0
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ForceKeepAlive.json
@@ -0,0 +1 @@
+{"MessageType":"ForceKeepAlive","MessageId":"00000000-0000-0000-0000-000000000000","ServerId":null,"Data":60}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/Partial.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/Partial.json
new file mode 100644
index 000000000..72f810725
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/Partial.json
@@ -0,0 +1 @@
+{"MessageType":"KeepAlive","MessageId":"d29ef449-6965-4000
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ValidPartial.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ValidPartial.json
new file mode 100644
index 000000000..62d9099c8
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ValidPartial.json
@@ -0,0 +1 @@
+{"MessageType":"ForceKeepAlive","MessageId":"00000000-0000-0000-0000-000000000000","ServerId":null,"Data":60}{"MessageType":"KeepAlive","MessageId":"d29ef449-6965-4000
diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/discover.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/discover.json
index 851f17bb2..851f17bb2 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/discover.json
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/discover.json
diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/lineup.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/lineup.json
index 4cb5ebc8e..4cb5ebc8e 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/lineup.json
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/lineup.json
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
index 2106a78a8..aed3e8ac5 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
@@ -31,6 +31,7 @@
<ItemGroup>
<ProjectReference Include="../../MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj" />
+ <ProjectReference Include="../../MediaBrowser.Providers/MediaBrowser.Providers.csproj" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
index b087e522e..d93b14153 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
@@ -7,6 +7,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Movies;
using MediaBrowser.XbmcMetadata.Parsers;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
@@ -23,8 +24,13 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
public EpisodeNfoProviderTests()
{
var providerManager = new Mock<IProviderManager>();
+
+ var imdbExternalId = new ImdbExternalId();
+ var externalIdInfo = new ExternalIdInfo(imdbExternalId.ProviderName, imdbExternalId.Key, imdbExternalId.Type, imdbExternalId.UrlFormatString);
+
providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny<IHasProviderIds>()))
- .Returns(Enumerable.Empty<ExternalIdInfo>());
+ .Returns(new[] { externalIdInfo });
+
var config = new Mock<IConfigurationManager>();
config.Setup(x => x.GetConfiguration(It.IsAny<string>()))
.Returns(new XbmcMetadataOptions());
@@ -60,6 +66,8 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
Assert.Equal(2, item.AirsAfterSeasonNumber);
Assert.Equal(3, item.AirsBeforeSeasonNumber);
Assert.Equal(1, item.AirsBeforeEpisodeNumber);
+ Assert.Equal("tt5017734", item.ProviderIds[MetadataProvider.Imdb.ToString()]);
+ Assert.Equal("1276153", item.ProviderIds[MetadataProvider.Tmdb.ToString()]);
// Credits
var writers = result.People.Where(x => x.Type == PersonType.Writer).ToArray();
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
index 7d31a3ec6..582ac27d8 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
@@ -8,6 +8,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Plugins.Tmdb.Movies;
using MediaBrowser.XbmcMetadata.Parsers;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
@@ -22,8 +23,13 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
public MovieNfoParserTests()
{
var providerManager = new Mock<IProviderManager>();
+
+ var tmdbExternalId = new TmdbMovieExternalId();
+ var externalIdInfo = new ExternalIdInfo(tmdbExternalId.ProviderName, tmdbExternalId.Key, tmdbExternalId.Type, tmdbExternalId.UrlFormatString);
+
providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny<IHasProviderIds>()))
- .Returns(Enumerable.Empty<ExternalIdInfo>());
+ .Returns(new[] { externalIdInfo });
+
var config = new Mock<IConfigurationManager>();
config.Setup(x => x.GetConfiguration(It.IsAny<string>()))
.Returns(new XbmcMetadataOptions());
@@ -43,7 +49,8 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
Assert.Equal("Justice League", item.OriginalTitle);
Assert.Equal("Justice for all.", item.Tagline);
- Assert.Equal("tt0974015", item.ProviderIds["imdb"]);
+ Assert.Equal("tt0974015", item.ProviderIds[MetadataProvider.Imdb.ToString()]);
+ Assert.Equal("141052", item.ProviderIds[MetadataProvider.Tmdb.ToString()]);
Assert.Equal(4, item.Genres.Length);
Assert.Contains("Action", item.Genres);
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
index bdffea560..1fe56cadd 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
@@ -9,6 +9,8 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Music;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
using MediaBrowser.XbmcMetadata.Parsers;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
@@ -23,8 +25,13 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
public MusicAlbumNfoProviderTests()
{
var providerManager = new Mock<IProviderManager>();
+
+ var musicBrainzArtist = new MusicBrainzArtistExternalId();
+ var externalIdInfo = new ExternalIdInfo(musicBrainzArtist.ProviderName, musicBrainzArtist.Key, musicBrainzArtist.Type, "MusicBrainzServer");
+
providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny<IHasProviderIds>()))
- .Returns(Enumerable.Empty<ExternalIdInfo>());
+ .Returns(new[] { externalIdInfo });
+
var config = new Mock<IConfigurationManager>();
config.Setup(x => x.GetConfiguration(It.IsAny<string>()))
.Returns(new XbmcMetadataOptions());
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
index 2a4d376c6..4869cf088 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
@@ -7,6 +7,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Music;
using MediaBrowser.XbmcMetadata.Parsers;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
@@ -21,8 +22,13 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
public MusicArtistNfoParserTests()
{
var providerManager = new Mock<IProviderManager>();
+
+ var musicBrainzArtist = new MusicBrainzArtistExternalId();
+ var externalIdInfo = new ExternalIdInfo(musicBrainzArtist.ProviderName, musicBrainzArtist.Key, musicBrainzArtist.Type, "MusicBrainzServer");
+
providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny<IHasProviderIds>()))
- .Returns(Enumerable.Empty<ExternalIdInfo>());
+ .Returns(new[] { externalIdInfo });
+
var config = new Mock<IConfigurationManager>();
config.Setup(x => x.GetConfiguration(It.IsAny<string>()))
.Returns(new XbmcMetadataOptions());
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
index 756a1d83a..fdcb151ea 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
@@ -43,8 +43,8 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
Assert.Equal("American Gods", item.OriginalTitle);
Assert.Equal(string.Empty, item.Tagline);
Assert.Equal(0, item.RunTimeTicks);
- Assert.Equal("46639", item.ProviderIds["tmdb"]);
- Assert.Equal("253573", item.ProviderIds["tvdb"]);
+ Assert.Equal("46639", item.ProviderIds[MetadataProvider.Tmdb.ToString()]);
+ Assert.Equal("253573", item.ProviderIds[MetadataProvider.Tvdb.ToString()]);
Assert.Equal("tt11111", item.ProviderIds[MetadataProvider.Imdb.ToString()]);
Assert.Equal(3, item.Genres.Length);
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo
index 76ea8c618..f7532ac51 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo
@@ -35,6 +35,7 @@
<plot>Fueled by his restored faith in humanity and inspired by Superman&apos;s selfless act, Bruce Wayne enlists the help of his newfound ally, Diana Prince, to face an even greater enemy. Together, Batman and Wonder Woman work quickly to find and recruit a team of meta-humans to stand against this newly awakened threat. But despite the formation of this unprecedented league of heroes-Batman, Wonder Woman, Aquaman, Cyborg and The Flash-it may already be too late to save the planet from an assault of catastrophic proportions.</plot>
<tagline>Justice for all.</tagline>
<runtime>120</runtime>
+ <tmdbId>141052</tmdbId>
<thumb aspect="set.poster" preview="https://assets.fanart.tv/preview/movies/468551/movieposter/justice-league-collection-5c24ea65591d3.jpg">https://assets.fanart.tv/fanart/movies/468551/movieposter/justice-league-collection-5c24ea65591d3.jpg</thumb>
<thumb aspect="set.poster" preview="https://assets.fanart.tv/preview/movies/468551/movieposter/justice-league-collection-5c24ea65591d3.jpg">https://assets.fanart.tv/fanart/movies/468551/movieposter/justice-league-collection-5c24ea65591d3.jpg</thumb>
<thumb aspect="set.clearlogo" preview="https://assets.fanart.tv/preview/movies/468551/hdmovielogo/justice-league-collection-5ba855ed4239a.png">https://assets.fanart.tv/fanart/movies/468551/hdmovielogo/justice-league-collection-5ba855ed4239a.png</thumb>
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Bone Orchard.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Bone Orchard.nfo
index c2cefe36c..cd275e4cf 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Bone Orchard.nfo
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Bone Orchard.nfo
@@ -28,6 +28,7 @@
<lastplayed></lastplayed>
<id>1276153</id>
<uniqueid type="tmdb" default="true">1276153</uniqueid>
+ <imdbId>tt5017734</imdbId>
<genre>Drama</genre>
<genre>Mystery</genre>
<genre>Sci-Fi &amp; Fantasy</genre>