Jelajahi Sumber

Merge branch 'master' of github.com:jellyfin/jellyfin into nfo-watched

David Ullmer 4 tahun lalu
induk
melakukan
ab5fb1b1f2
75 mengubah file dengan 810 tambahan dan 484 penghapusan
  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. 22 30
      Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
  19. 1 1
      Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
  20. 26 0
      Emby.Server.Implementations/Localization/Core/eo.json
  21. 74 74
      Emby.Server.Implementations/Localization/Core/fi.json
  22. 2 2
      Emby.Server.Implementations/Localization/Core/hi.json
  23. 2 2
      Emby.Server.Implementations/Plugins/PluginManager.cs
  24. 6 1
      Emby.Server.Implementations/Session/SessionManager.cs
  25. 2 2
      Jellyfin.Api/Jellyfin.Api.csproj
  26. 3 3
      Jellyfin.Data/Jellyfin.Data.csproj
  27. 2 2
      Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
  28. 2 2
      Jellyfin.Server/Jellyfin.Server.csproj
  29. 1 1
      MediaBrowser.Common/MediaBrowser.Common.csproj
  30. 1 1
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  31. 389 277
      MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
  32. 8 0
      MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
  33. 32 0
      MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
  34. 11 0
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  35. 1 1
      MediaBrowser.MediaEncoding/Subtitles/AssParser.cs
  36. 1 1
      MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs
  37. 6 0
      MediaBrowser.Model/Configuration/EncodingOptions.cs
  38. 1 1
      MediaBrowser.Model/MediaBrowser.Model.csproj
  39. 0 14
      MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
  40. 2 2
      SharedVersion.cs
  41. 1 1
      build.yaml
  42. 6 0
      debian/changelog
  43. 1 1
      debian/metapackage/jellyfin
  44. 1 1
      deployment/Dockerfile.debian.amd64
  45. 1 1
      deployment/Dockerfile.debian.arm64
  46. 1 1
      deployment/Dockerfile.debian.armhf
  47. 1 1
      deployment/Dockerfile.linux.amd64
  48. 1 1
      deployment/Dockerfile.linux.amd64-musl
  49. 1 1
      deployment/Dockerfile.linux.arm64
  50. 1 1
      deployment/Dockerfile.linux.armhf
  51. 1 1
      deployment/Dockerfile.macos
  52. 1 1
      deployment/Dockerfile.portable
  53. 1 1
      deployment/Dockerfile.ubuntu.amd64
  54. 1 1
      deployment/Dockerfile.ubuntu.arm64
  55. 1 1
      deployment/Dockerfile.ubuntu.armhf
  56. 1 1
      deployment/Dockerfile.windows.amd64
  57. 3 1
      fedora/jellyfin.spec
  58. 1 1
      tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
  59. 69 0
      tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs
  60. 6 5
      tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
  61. 2 10
      tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs
  62. 45 0
      tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs
  63. 1 0
      tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ForceKeepAlive.json
  64. 1 0
      tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/Partial.json
  65. 1 0
      tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ValidPartial.json
  66. 0 0
      tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/discover.json
  67. 0 0
      tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/lineup.json
  68. 1 0
      tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
  69. 9 1
      tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs
  70. 8 2
      tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
  71. 8 1
      tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
  72. 7 1
      tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
  73. 2 2
      tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs
  74. 1 0
      tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo
  75. 1 0
      tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Bone Orchard.nfo

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

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

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

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

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

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

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

+ 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 ditekan karena terlalu besar
+ 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;

+ 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

@@ -785,20 +785,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>

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

+ 69 - 0
tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs

@@ -0,0 +1,69 @@
+using System;
+using System.Buffers;
+using System.IO;
+using System.Text.Json;
+using Emby.Server.Implementations.HttpServer;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.HttpServer
+{
+    public class WebSocketConnectionTests
+    {
+        [Fact]
+        public void DeserializeWebSocketMessage_SingleSegment_Success()
+        {
+            var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!, null!);
+            var bytes = File.ReadAllBytes("Test Data/HttpServer/ForceKeepAlive.json");
+            con.DeserializeWebSocketMessage(new ReadOnlySequence<byte>(bytes), out var bytesConsumed);
+            Assert.Equal(109, bytesConsumed);
+        }
+
+        [Fact]
+        public void DeserializeWebSocketMessage_MultipleSegments_Success()
+        {
+            const int SplitPos = 64;
+            var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!, null!);
+            var bytes = File.ReadAllBytes("Test Data/HttpServer/ForceKeepAlive.json");
+            var seg1 = new BufferSegment(new Memory<byte>(bytes, 0, SplitPos));
+            var seg2 = seg1.Append(new Memory<byte>(bytes, SplitPos, bytes.Length - SplitPos));
+            con.DeserializeWebSocketMessage(new ReadOnlySequence<byte>(seg1, 0, seg2, seg2.Memory.Length - 1), out var bytesConsumed);
+            Assert.Equal(109, bytesConsumed);
+        }
+
+        [Fact]
+        public void DeserializeWebSocketMessage_ValidPartial_Success()
+        {
+            var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!, null!);
+            var bytes = File.ReadAllBytes("Test Data/HttpServer/ValidPartial.json");
+            con.DeserializeWebSocketMessage(new ReadOnlySequence<byte>(bytes), out var bytesConsumed);
+            Assert.Equal(109, bytesConsumed);
+        }
+
+        [Fact]
+        public void DeserializeWebSocketMessage_Partial_ThrowJsonException()
+        {
+            var con = new WebSocketConnection(new NullLogger<WebSocketConnection>(), null!, null!, null!);
+            var bytes = File.ReadAllBytes("Test Data/HttpServer/Partial.json");
+            Assert.Throws<JsonException>(() => con.DeserializeWebSocketMessage(new ReadOnlySequence<byte>(bytes), out var bytesConsumed));
+        }
+
+        internal class BufferSegment : ReadOnlySequenceSegment<byte>
+        {
+            public BufferSegment(Memory<byte> memory)
+            {
+                Memory = memory;
+            }
+
+            public BufferSegment Append(Memory<byte> memory)
+            {
+                var segment = new BufferSegment(memory)
+                {
+                    RunningIndex = RunningIndex + Memory.Length
+                };
+                Next = segment;
+                return segment;
+            }
+        }
+    }
+}

+ 6 - 5
tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj

@@ -13,6 +13,12 @@
     <RootNamespace>Jellyfin.Server.Implementations.Tests</RootNamespace>
   </PropertyGroup>
 
+  <ItemGroup>
+    <None Include="Test Data\**\*.*">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
+
   <ItemGroup>
     <PackageReference Include="AutoFixture" Version="4.15.0" />
     <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
@@ -35,11 +41,6 @@
     <ProjectReference Include="..\..\Emby.Server.Implementations\Emby.Server.Implementations.csproj" />
   </ItemGroup>
 
-  <ItemGroup>
-    <EmbeddedResource Include="LiveTv\discover.json" />
-    <EmbeddedResource Include="LiveTv\lineup.json" />
-  </ItemGroup>
-
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
     <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
   </PropertyGroup>

+ 2 - 10
tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs

@@ -1,4 +1,5 @@
 using System;
+using System.IO;
 using System.Net.Http;
 using System.Threading;
 using System.Threading.Tasks;
@@ -21,24 +22,15 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv
 
         public HdHomerunHostTests()
         {
-            const string BaseResourcePath = "Jellyfin.Server.Implementations.Tests.LiveTv.";
-
             var messageHandler = new Mock<HttpMessageHandler>();
             messageHandler.Protected()
                 .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
                 .Returns<HttpRequestMessage, CancellationToken>(
                     (m, _) =>
                     {
-                        var resource = BaseResourcePath + m.RequestUri?.Segments[^1];
-                        var stream = typeof(HdHomerunHostTests).Assembly.GetManifestResourceStream(resource);
-                        if (stream == null)
-                        {
-                            throw new NullReferenceException("Resource doesn't exist: " + resource);
-                        }
-
                         return Task.FromResult(new HttpResponseMessage()
                         {
-                            Content = new StreamContent(stream)
+                            Content = new StreamContent(File.OpenRead("Test Data/LiveTv/" + m.RequestUri?.Segments[^1]))
                         });
                     });
 

+ 45 - 0
tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs

@@ -0,0 +1,45 @@
+using System;
+using System.IO;
+using Emby.Server.Implementations.Plugins;
+using MediaBrowser.Common.Plugins;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.Plugins
+{
+    public class PluginManagerTests
+    {
+        private static readonly string _testPathRoot = Path.Combine(Path.GetTempPath(), "jellyfin-test-data");
+
+        [Fact]
+        public void SaveManifest_RoundTrip_Success()
+        {
+            var pluginManager = new PluginManager(new NullLogger<PluginManager>(), null!, null!, null!, new Version(1, 0));
+            var manifest = new PluginManifest()
+            {
+                Version = "1.0"
+            };
+
+            var tempPath = Path.Combine(_testPathRoot, "manifest-" + Path.GetRandomFileName());
+            Directory.CreateDirectory(tempPath);
+
+            Assert.True(pluginManager.SaveManifest(manifest, tempPath));
+
+            var res = pluginManager.LoadManifest(tempPath);
+
+            Assert.Equal(manifest.Category, res.Manifest.Category);
+            Assert.Equal(manifest.Changelog, res.Manifest.Changelog);
+            Assert.Equal(manifest.Description, res.Manifest.Description);
+            Assert.Equal(manifest.Id, res.Manifest.Id);
+            Assert.Equal(manifest.Name, res.Manifest.Name);
+            Assert.Equal(manifest.Overview, res.Manifest.Overview);
+            Assert.Equal(manifest.Owner, res.Manifest.Owner);
+            Assert.Equal(manifest.TargetAbi, res.Manifest.TargetAbi);
+            Assert.Equal(manifest.Timestamp, res.Manifest.Timestamp);
+            Assert.Equal(manifest.Version, res.Manifest.Version);
+            Assert.Equal(manifest.Status, res.Manifest.Status);
+            Assert.Equal(manifest.AutoUpdate, res.Manifest.AutoUpdate);
+            Assert.Equal(manifest.ImagePath, res.Manifest.ImagePath);
+        }
+    }
+}

+ 1 - 0
tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ForceKeepAlive.json

@@ -0,0 +1 @@
+{"MessageType":"ForceKeepAlive","MessageId":"00000000-0000-0000-0000-000000000000","ServerId":null,"Data":60}

+ 1 - 0
tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/Partial.json

@@ -0,0 +1 @@
+{"MessageType":"KeepAlive","MessageId":"d29ef449-6965-4000

+ 1 - 0
tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ValidPartial.json

@@ -0,0 +1 @@
+{"MessageType":"ForceKeepAlive","MessageId":"00000000-0000-0000-0000-000000000000","ServerId":null,"Data":60}{"MessageType":"KeepAlive","MessageId":"d29ef449-6965-4000

+ 0 - 0
tests/Jellyfin.Server.Implementations.Tests/LiveTv/discover.json → tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/discover.json


+ 0 - 0
tests/Jellyfin.Server.Implementations.Tests/LiveTv/lineup.json → tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/lineup.json


+ 1 - 0
tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj

@@ -31,6 +31,7 @@
 
   <ItemGroup>
     <ProjectReference Include="../../MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj" />
+    <ProjectReference Include="../../MediaBrowser.Providers/MediaBrowser.Providers.csproj" />
   </ItemGroup>
 
   <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

+ 9 - 1
tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs

@@ -8,6 +8,7 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Movies;
 using MediaBrowser.XbmcMetadata.Parsers;
 using Microsoft.Extensions.Logging.Abstractions;
 using Moq;
@@ -24,8 +25,13 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
         public EpisodeNfoProviderTests()
         {
             var providerManager = new Mock<IProviderManager>();
+
+            var imdbExternalId = new ImdbExternalId();
+            var externalIdInfo = new ExternalIdInfo(imdbExternalId.ProviderName, imdbExternalId.Key, imdbExternalId.Type, imdbExternalId.UrlFormatString);
+
             providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny<IHasProviderIds>()))
-                .Returns(Enumerable.Empty<ExternalIdInfo>());
+                .Returns(new[] { externalIdInfo });
+
             var config = new Mock<IConfigurationManager>();
             config.Setup(x => x.GetConfiguration(It.IsAny<string>()))
                 .Returns(new XbmcMetadataOptions());
@@ -60,6 +66,8 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
             Assert.Equal(2017, item.ProductionYear);
             Assert.Single(item.Studios);
             Assert.Contains("Starz", item.Studios);
+            Assert.Equal("tt5017734", item.ProviderIds[MetadataProvider.Imdb.ToString()]);
+            Assert.Equal("1276153", item.ProviderIds[MetadataProvider.Tmdb.ToString()]);
 
             // Credits
             var writers = result.People.Where(x => x.Type == PersonType.Writer).ToArray();

+ 8 - 2
tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs

@@ -9,6 +9,7 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Plugins.Tmdb.Movies;
 using MediaBrowser.XbmcMetadata.Parsers;
 using Microsoft.Extensions.Logging.Abstractions;
 using Moq;
@@ -27,8 +28,12 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
             _testUser = new User("Test User", "Auth provider", "Reset provider");
 
             var providerManager = new Mock<IProviderManager>();
+
+            var tmdbExternalId = new TmdbMovieExternalId();
+            var externalIdInfo = new ExternalIdInfo(tmdbExternalId.ProviderName, tmdbExternalId.Key, tmdbExternalId.Type, tmdbExternalId.UrlFormatString);
+
             providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny<IHasProviderIds>()))
-                .Returns(Enumerable.Empty<ExternalIdInfo>());
+                .Returns(new[] { externalIdInfo });
 
             var nfoConfig = new XbmcMetadataOptions()
             {
@@ -63,7 +68,8 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
 
             Assert.Equal("Justice League", item.OriginalTitle);
             Assert.Equal("Justice for all.", item.Tagline);
-            Assert.Equal("tt0974015", item.ProviderIds["imdb"]);
+            Assert.Equal("tt0974015", item.ProviderIds[MetadataProvider.Imdb.ToString()]);
+            Assert.Equal("141052", item.ProviderIds[MetadataProvider.Tmdb.ToString()]);
 
             Assert.Equal(4, item.Genres.Length);
             Assert.Contains("Action", item.Genres);

+ 8 - 1
tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs

@@ -10,6 +10,8 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Music;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
 using MediaBrowser.XbmcMetadata.Parsers;
 using Microsoft.Extensions.Logging.Abstractions;
 using Moq;
@@ -24,8 +26,13 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
         public MusicAlbumNfoProviderTests()
         {
             var providerManager = new Mock<IProviderManager>();
+
+            var musicBrainzArtist = new MusicBrainzArtistExternalId();
+            var externalIdInfo = new ExternalIdInfo(musicBrainzArtist.ProviderName, musicBrainzArtist.Key, musicBrainzArtist.Type, "MusicBrainzServer");
+
             providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny<IHasProviderIds>()))
-                .Returns(Enumerable.Empty<ExternalIdInfo>());
+                .Returns(new[] { externalIdInfo });
+
             var config = new Mock<IConfigurationManager>();
             config.Setup(x => x.GetConfiguration(It.IsAny<string>()))
                 .Returns(new XbmcMetadataOptions());

+ 7 - 1
tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs

@@ -8,6 +8,7 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Music;
 using MediaBrowser.XbmcMetadata.Parsers;
 using Microsoft.Extensions.Logging.Abstractions;
 using Moq;
@@ -22,8 +23,13 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
         public MusicArtistNfoParserTests()
         {
             var providerManager = new Mock<IProviderManager>();
+
+            var musicBrainzArtist = new MusicBrainzArtistExternalId();
+            var externalIdInfo = new ExternalIdInfo(musicBrainzArtist.ProviderName, musicBrainzArtist.Key, musicBrainzArtist.Type, "MusicBrainzServer");
+
             providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny<IHasProviderIds>()))
-                .Returns(Enumerable.Empty<ExternalIdInfo>());
+                .Returns(new[] { externalIdInfo });
+
             var config = new Mock<IConfigurationManager>();
             config.Setup(x => x.GetConfiguration(It.IsAny<string>()))
                 .Returns(new XbmcMetadataOptions());

+ 2 - 2
tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs

@@ -47,8 +47,8 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
             Assert.Equal("American Gods", item.OriginalTitle);
             Assert.Equal(string.Empty, item.Tagline);
             Assert.Equal(0, item.RunTimeTicks);
-            Assert.Equal("46639", item.ProviderIds["tmdb"]);
-            Assert.Equal("253573", item.ProviderIds["tvdb"]);
+            Assert.Equal("46639", item.ProviderIds[MetadataProvider.Tmdb.ToString()]);
+            Assert.Equal("253573", item.ProviderIds[MetadataProvider.Tvdb.ToString()]);
 
             Assert.Equal(3, item.Genres.Length);
             Assert.Contains("Drama", item.Genres);

+ 1 - 0
tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo

@@ -33,6 +33,7 @@
     <playcount>2</playcount>
     <watched>true</watched>
     <lastplayed>2021-02-11 07:47:23</lastplayed>
+    <tmdbId>141052</tmdbId>
     <thumb aspect="set.poster" preview="https://assets.fanart.tv/preview/movies/468551/movieposter/justice-league-collection-5c24ea65591d3.jpg">https://assets.fanart.tv/fanart/movies/468551/movieposter/justice-league-collection-5c24ea65591d3.jpg</thumb>
     <thumb aspect="set.poster" preview="https://assets.fanart.tv/preview/movies/468551/movieposter/justice-league-collection-5c24ea65591d3.jpg">https://assets.fanart.tv/fanart/movies/468551/movieposter/justice-league-collection-5c24ea65591d3.jpg</thumb>
     <thumb aspect="set.clearlogo" preview="https://assets.fanart.tv/preview/movies/468551/hdmovielogo/justice-league-collection-5ba855ed4239a.png">https://assets.fanart.tv/fanart/movies/468551/hdmovielogo/justice-league-collection-5ba855ed4239a.png</thumb>

+ 1 - 0
tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Bone Orchard.nfo

@@ -24,6 +24,7 @@
     <lastplayed></lastplayed>
     <id>1276153</id>
     <uniqueid type="tmdb" default="true">1276153</uniqueid>
+    <imdbId>tt5017734</imdbId>
     <genre>Drama</genre>
     <genre>Mystery</genre>
     <genre>Sci-Fi &amp; Fantasy</genre>

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini