Browse Source

Merge remote-tracking branch 'jellyfin/master' into nfo-tests

# Conflicts:
#	tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
#	tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
David 4 years ago
parent
commit
fc7377fb9b
100 changed files with 1081 additions and 587 deletions
  1. 1 1
      .ci/azure-pipelines-abi.yml
  2. 1 1
      .ci/azure-pipelines-api-client.yml
  3. 1 1
      .ci/azure-pipelines-main.yml
  4. 1 1
      .ci/azure-pipelines-test.yml
  5. 1 1
      .ci/azure-pipelines.yml
  6. 2 2
      Emby.Dlna/Profiles/SonyBravia2010Profile.cs
  7. 2 2
      Emby.Dlna/Profiles/SonyBravia2011Profile.cs
  8. 2 2
      Emby.Dlna/Profiles/SonyBravia2012Profile.cs
  9. 2 2
      Emby.Dlna/Profiles/SonyBravia2013Profile.cs
  10. 2 2
      Emby.Dlna/Profiles/SonyBravia2014Profile.cs
  11. 2 2
      Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml
  12. 2 2
      Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml
  13. 2 2
      Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml
  14. 2 2
      Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml
  15. 2 2
      Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml
  16. 2 2
      Emby.Naming/Common/NamingOptions.cs
  17. 1 1
      Emby.Naming/Emby.Naming.csproj
  18. 2 2
      Emby.Server.Implementations/Dto/DtoService.cs
  19. 22 30
      Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
  20. 0 5
      Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
  21. 1 1
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
  22. 11 11
      Emby.Server.Implementations/Localization/Core/bg-BG.json
  23. 26 0
      Emby.Server.Implementations/Localization/Core/eo.json
  24. 74 74
      Emby.Server.Implementations/Localization/Core/fi.json
  25. 2 2
      Emby.Server.Implementations/Localization/Core/hi.json
  26. 2 2
      Emby.Server.Implementations/Plugins/PluginManager.cs
  27. 6 1
      Emby.Server.Implementations/Session/SessionManager.cs
  28. 28 0
      Jellyfin.Api/Attributes/AcceptsFileAttribute.cs
  29. 18 0
      Jellyfin.Api/Attributes/AcceptsImageFileAttribute.cs
  30. 10 8
      Jellyfin.Api/Controllers/ArtistsController.cs
  31. 0 1
      Jellyfin.Api/Controllers/CollectionController.cs
  32. 0 6
      Jellyfin.Api/Controllers/DashboardController.cs
  33. 0 1
      Jellyfin.Api/Controllers/DynamicHlsController.cs
  34. 18 19
      Jellyfin.Api/Controllers/FilterController.cs
  35. 5 4
      Jellyfin.Api/Controllers/GenresController.cs
  36. 0 2
      Jellyfin.Api/Controllers/HlsSegmentController.cs
  37. 4 0
      Jellyfin.Api/Controllers/ImageController.cs
  38. 0 1
      Jellyfin.Api/Controllers/InstantMixController.cs
  39. 0 2
      Jellyfin.Api/Controllers/ItemLookupController.cs
  40. 13 14
      Jellyfin.Api/Controllers/ItemsController.cs
  41. 0 1
      Jellyfin.Api/Controllers/LibraryController.cs
  42. 5 4
      Jellyfin.Api/Controllers/MusicGenresController.cs
  43. 0 1
      Jellyfin.Api/Controllers/PackageController.cs
  44. 0 1
      Jellyfin.Api/Controllers/PersonsController.cs
  45. 0 1
      Jellyfin.Api/Controllers/PlaylistsController.cs
  46. 1 5
      Jellyfin.Api/Controllers/PluginsController.cs
  47. 5 4
      Jellyfin.Api/Controllers/SearchController.cs
  48. 5 4
      Jellyfin.Api/Controllers/StudiosController.cs
  49. 0 1
      Jellyfin.Api/Controllers/SuggestionsController.cs
  50. 0 1
      Jellyfin.Api/Controllers/SyncPlayController.cs
  51. 0 1
      Jellyfin.Api/Controllers/SystemController.cs
  52. 0 1
      Jellyfin.Api/Controllers/TimeSyncController.cs
  53. 2 2
      Jellyfin.Api/Controllers/TrailersController.cs
  54. 0 1
      Jellyfin.Api/Controllers/TvShowsController.cs
  55. 3 2
      Jellyfin.Api/Controllers/UserLibraryController.cs
  56. 0 1
      Jellyfin.Api/Controllers/UserViewsController.cs
  57. 0 1
      Jellyfin.Api/Controllers/VideoHlsController.cs
  58. 8 7
      Jellyfin.Api/Controllers/YearsController.cs
  59. 16 0
      Jellyfin.Api/Helpers/RequestHelpers.cs
  60. 8 2
      Jellyfin.Api/Jellyfin.Api.csproj
  61. 190 0
      Jellyfin.Data/Enums/BaseItemKind.cs
  62. 3 3
      Jellyfin.Data/Jellyfin.Data.csproj
  63. 2 2
      Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
  64. 1 0
      Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
  65. 43 0
      Jellyfin.Server/Filters/FileRequestFilter.cs
  66. 2 2
      Jellyfin.Server/Jellyfin.Server.csproj
  67. 1 1
      MediaBrowser.Common/MediaBrowser.Common.csproj
  68. 5 0
      MediaBrowser.Controller/Entities/BaseItem.cs
  69. 1 1
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  70. 389 277
      MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
  71. 8 0
      MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
  72. 32 0
      MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
  73. 11 0
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  74. 1 1
      MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
  75. 1 1
      MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
  76. 6 0
      MediaBrowser.Model/Configuration/EncodingOptions.cs
  77. 0 7
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  78. 2 1
      MediaBrowser.Model/Dto/BaseItemDto.cs
  79. 1 1
      MediaBrowser.Model/MediaBrowser.Model.csproj
  80. 0 14
      MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
  81. 2 2
      SharedVersion.cs
  82. 1 1
      build.yaml
  83. 6 0
      debian/changelog
  84. 1 1
      debian/metapackage/jellyfin
  85. 1 1
      deployment/Dockerfile.debian.amd64
  86. 1 1
      deployment/Dockerfile.debian.arm64
  87. 1 1
      deployment/Dockerfile.debian.armhf
  88. 1 1
      deployment/Dockerfile.linux.amd64
  89. 1 1
      deployment/Dockerfile.linux.amd64-musl
  90. 1 1
      deployment/Dockerfile.linux.arm64
  91. 1 1
      deployment/Dockerfile.linux.armhf
  92. 1 1
      deployment/Dockerfile.macos
  93. 1 1
      deployment/Dockerfile.portable
  94. 1 1
      deployment/Dockerfile.ubuntu.amd64
  95. 1 1
      deployment/Dockerfile.ubuntu.arm64
  96. 1 1
      deployment/Dockerfile.ubuntu.armhf
  97. 1 1
      deployment/Dockerfile.windows.amd64
  98. 3 1
      fedora/jellyfin.spec
  99. 32 2
      tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs
  100. 1 1
      tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj

+ 1 - 1
.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

+ 1 - 1
.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

+ 1 - 1
.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

+ 1 - 1
.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

+ 1 - 1
.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

+ 2 - 2
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
                     }
                 }

+ 2 - 2
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
                     }
                 }

+ 2 - 2
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
                     }
                 }

+ 2 - 2
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
                     }
                 }

+ 2 - 2
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
                     }
                 }

+ 2 - 2
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>

+ 2 - 2
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>

+ 2 - 2
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>

+ 2 - 2
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>

+ 2 - 2
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>

+ 2 - 2
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*$"
             };
 

+ 1 - 1
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>

+ 2 - 2
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;

+ 22 - 30
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;

+ 0 - 5
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)
             {

+ 1 - 1
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)
             {

+ 11 - 11
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": "Актуализира мета-данните за артистите и режисьорите за Вашата медийна библиотека.",

+ 26 - 0
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"
+}

+ 74 - 74
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"
 }

+ 2 - 2
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": "प्लगइन फ़ैल हो गया",

+ 2 - 2
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;

+ 6 - 1
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);
         }
 

+ 28 - 0
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;
+    }
+}

+ 18 - 0
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)
+        {
+        }
+    }
+}

+ 10 - 8
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,

+ 0 - 1
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;

+ 0 - 6
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
 {

+ 0 - 1
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;

+ 18 - 19
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
                 {

+ 5 - 4
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,

+ 0 - 2
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;

+ 4 - 0
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")]

+ 0 - 1
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;

+ 0 - 2
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;

+ 13 - 14
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
             });
 

+ 0 - 1
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;

+ 5 - 4
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,

+ 0 - 1
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;

+ 0 - 1
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;

+ 0 - 1
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;

+ 1 - 5
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();
             }

+ 5 - 4
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,
 

+ 5 - 4
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,

+ 0 - 1
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;

+ 0 - 1
Jellyfin.Api/Controllers/SyncPlayController.cs

@@ -1,4 +1,3 @@
-using System;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.Threading;

+ 0 - 1
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;

+ 0 - 1
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;

+ 2 - 2
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(

+ 0 - 1
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;

+ 3 - 2
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,

+ 0 - 1
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;

+ 0 - 1
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;

+ 8 - 7
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;
             }

+ 16 - 0
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;
+        }
     }
 }

+ 8 - 2
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>

+ 190 - 0
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
+    }
+}

+ 3 - 3
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>

+ 2 - 2
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>

+ 1 - 0
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>();
             });

+ 43 - 0
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;
+        }
+    }
+}

+ 2 - 2
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" />

+ 1 - 1
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>

+ 5 - 0
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>

+ 1 - 1
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>

File diff suppressed because it is too large
+ 389 - 277
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs


+ 8 - 0
MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs

@@ -50,6 +50,14 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
         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>

+ 32 - 0
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";

+ 11 - 0
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))

+ 1 - 1
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);
                 }

+ 1 - 1
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);

+ 6 - 0
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;

+ 0 - 7
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; }
     }
 }

+ 2 - 1
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.

+ 1 - 1
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>

+ 0 - 14
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))

+ 2 - 2
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")]

+ 1 - 1
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

+ 6 - 0
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

+ 1 - 1
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

+ 1 - 1
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

+ 1 - 1
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

+ 1 - 1
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

+ 1 - 1
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

+ 1 - 1
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

+ 1 - 1
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

+ 1 - 1
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

+ 1 - 1
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

+ 1 - 1
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

+ 1 - 1
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

+ 1 - 1
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

+ 1 - 1
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

+ 1 - 1
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

+ 3 - 1
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>

+ 32 - 2
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);
+        }
     }
 }

+ 1 - 1
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" />

Some files were not shown because too many files changed in this diff