From 5f2cd11199875fb6c4698b89d235703cda86021f Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Fri, 4 Dec 2020 21:55:32 -0500 Subject: [PATCH 01/27] Bump version to 10.8.0 for next release --- Emby.Naming/Emby.Naming.csproj | 2 +- Jellyfin.Data/Jellyfin.Data.csproj | 2 +- MediaBrowser.Common/MediaBrowser.Common.csproj | 2 +- MediaBrowser.Controller/MediaBrowser.Controller.csproj | 2 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 2 +- SharedVersion.cs | 4 ++-- build.yaml | 2 +- debian/changelog | 6 ++++++ debian/metapackage/jellyfin | 2 +- fedora/jellyfin.spec | 4 +++- 10 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj index 24c15759d4..b43203e9dd 100644 --- a/Emby.Naming/Emby.Naming.csproj +++ b/Emby.Naming/Emby.Naming.csproj @@ -33,7 +33,7 @@ Jellyfin Contributors Jellyfin.Naming - 10.7.0 + 10.8.0 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index 89d6f4d9b7..4496069ab1 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -19,7 +19,7 @@ Jellyfin Contributors Jellyfin.Data - 10.7.0 + 10.8.0 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index be5e7f5b45..3222466c10 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Common - 10.7.0 + 10.8.0 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 5f75df54eb..6b1c096acb 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Controller - 10.7.0 + 10.8.0 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index b86187f9be..c24b9b613b 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -8,7 +8,7 @@ Jellyfin Contributors Jellyfin.Model - 10.7.0 + 10.8.0 https://github.com/jellyfin/jellyfin GPL-3.0-only diff --git a/SharedVersion.cs b/SharedVersion.cs index d17074fc0d..5e2f151a22 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; -[assembly: AssemblyVersion("10.7.0")] -[assembly: AssemblyFileVersion("10.7.0")] +[assembly: AssemblyVersion("10.8.0")] +[assembly: AssemblyFileVersion("10.8.0")] diff --git a/build.yaml b/build.yaml index 7c32d955c1..18434ee003 100644 --- a/build.yaml +++ b/build.yaml @@ -1,7 +1,7 @@ --- # We just wrap `build` so this is really it name: "jellyfin" -version: "10.7.0" +version: "10.8.0" packages: - debian.amd64 - debian.arm64 diff --git a/debian/changelog b/debian/changelog index 184143fe9a..430594cac2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +jellyfin-server (10.8.0-1) unstable; urgency=medium + + * Forthcoming stable release + + -- Jellyfin Packaging Team Fri, 04 Dec 2020 21:55:12 -0500 + jellyfin-server (10.7.0-1) unstable; urgency=medium * Forthcoming stable release diff --git a/debian/metapackage/jellyfin b/debian/metapackage/jellyfin index 026fcb8217..a9a0ae5b01 100644 --- a/debian/metapackage/jellyfin +++ b/debian/metapackage/jellyfin @@ -5,7 +5,7 @@ Homepage: https://jellyfin.org Standards-Version: 3.9.2 Package: jellyfin -Version: 10.7.0 +Version: 10.8.0 Maintainer: Jellyfin Packaging Team Depends: jellyfin-server, jellyfin-web Description: Provides the Jellyfin Free Software Media System diff --git a/fedora/jellyfin.spec b/fedora/jellyfin.spec index 197126ee56..ebecb701be 100644 --- a/fedora/jellyfin.spec +++ b/fedora/jellyfin.spec @@ -7,7 +7,7 @@ %endif Name: jellyfin -Version: 10.7.0 +Version: 10.8.0 Release: 1%{?dist} Summary: The Free Software Media System License: GPLv3 @@ -137,6 +137,8 @@ fi %systemd_postun_with_restart jellyfin.service %changelog +* Fri Dec 04 2020 Jellyfin Packaging Team +- Forthcoming stable release * Mon Jul 27 2020 Jellyfin Packaging Team - Forthcoming stable release * Mon Mar 23 2020 Jellyfin Packaging Team From 8dd83327b5b9b77912b3b69ec00dc31898a86bc7 Mon Sep 17 00:00:00 2001 From: Matt Montgomery <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Fri, 1 Jan 2021 17:26:31 -0600 Subject: [PATCH 02/27] Remove quick connect tokens after usage --- Emby.Server.Implementations/Session/SessionManager.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 885f65c64e..92cbb08129 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -1456,7 +1456,12 @@ namespace Emby.Server.Implementations.Session throw new SecurityException("Unknown quick connect token"); } - request.UserId = result.Items[0].UserId; + var info = result.Items[0]; + request.UserId = info.UserId; + + // There's no need to keep the quick connect token in the database, as AuthenticateNewSessionInternal() issues a long lived token. + _authRepo.Delete(info); + return AuthenticateNewSessionInternal(request, false); } From 9a5ceb34d169431fba8cf90dca79e0a448e88bde Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Wed, 13 Jan 2021 01:11:12 +0100 Subject: [PATCH 03/27] Improve WebSocket Deserialization --- .../HttpServer/WebSocketConnection.cs | 52 ++++++-------- .../HttpServer/WebSocketConnectionTests.cs | 69 +++++++++++++++++++ ...llyfin.Server.Implementations.Tests.csproj | 11 +-- .../LiveTv/HdHomerunHostTests.cs | 12 +--- .../Test Data/HttpServer/ForceKeepAlive.json | 1 + .../Test Data/HttpServer/Partial.json | 1 + .../Test Data/HttpServer/ValidPartial.json | 1 + .../{ => Test Data}/LiveTv/discover.json | 0 .../{ => Test Data}/LiveTv/lineup.json | 0 9 files changed, 102 insertions(+), 45 deletions(-) create mode 100644 tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ForceKeepAlive.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/Partial.json create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ValidPartial.json rename tests/Jellyfin.Server.Implementations.Tests/{ => Test Data}/LiveTv/discover.json (100%) rename tests/Jellyfin.Server.Implementations.Tests/{ => Test Data}/LiveTv/lineup.json (100%) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index fed2addf80..7e0c2c1da2 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.IO.Pipelines; using System.Net; using System.Net.WebSockets; +using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -138,7 +139,7 @@ namespace Emby.Server.Implementations.HttpServer writer.Advance(bytesRead); // Make the data available to the PipeReader - FlushResult flushResult = await writer.FlushAsync().ConfigureAwait(false); + FlushResult flushResult = await writer.FlushAsync(cancellationToken).ConfigureAwait(false); if (flushResult.IsCompleted) { // The PipeReader stopped reading @@ -181,32 +182,16 @@ namespace Emby.Server.Implementations.HttpServer } WebSocketMessage? stub; + long bytesConsumed = 0; try { - - if (buffer.IsSingleSegment) - { - stub = JsonSerializer.Deserialize>(buffer.FirstSpan, _jsonOptions); - } - else - { - var buf = ArrayPool.Shared.Rent(Convert.ToInt32(buffer.Length)); - try - { - buffer.CopyTo(buf); - stub = JsonSerializer.Deserialize>(buf, _jsonOptions); - } - finally - { - ArrayPool.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? DeserializeWebSocketMessage(ReadOnlySequence bytes, out long bytesConsumed) + { + var jsonReader = new Utf8JsonReader(bytes); + var ret = JsonSerializer.Deserialize>(ref jsonReader, _jsonOptions); + bytesConsumed = jsonReader.BytesConsumed; + return ret; + } + private Task SendKeepAliveResponse() { LastKeepAliveDate = DateTime.UtcNow; diff --git a/tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs b/tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs new file mode 100644 index 0000000000..1ce2096ea6 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/HttpServer/WebSocketConnectionTests.cs @@ -0,0 +1,69 @@ +using System; +using System.Buffers; +using System.IO; +using System.Text.Json; +using Emby.Server.Implementations.HttpServer; +using Microsoft.Extensions.Logging.Abstractions; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.HttpServer +{ + public class WebSocketConnectionTests + { + [Fact] + public void DeserializeWebSocketMessage_SingleSegment_Success() + { + var con = new WebSocketConnection(new NullLogger(), null!, null!, null!); + var bytes = File.ReadAllBytes("Test Data/HttpServer/ForceKeepAlive.json"); + con.DeserializeWebSocketMessage(new ReadOnlySequence(bytes), out var bytesConsumed); + Assert.Equal(109, bytesConsumed); + } + + [Fact] + public void DeserializeWebSocketMessage_MultipleSegments_Success() + { + const int SplitPos = 64; + var con = new WebSocketConnection(new NullLogger(), null!, null!, null!); + var bytes = File.ReadAllBytes("Test Data/HttpServer/ForceKeepAlive.json"); + var seg1 = new BufferSegment(new Memory(bytes, 0, SplitPos)); + var seg2 = seg1.Append(new Memory(bytes, SplitPos, bytes.Length - SplitPos)); + con.DeserializeWebSocketMessage(new ReadOnlySequence(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(), null!, null!, null!); + var bytes = File.ReadAllBytes("Test Data/HttpServer/ValidPartial.json"); + con.DeserializeWebSocketMessage(new ReadOnlySequence(bytes), out var bytesConsumed); + Assert.Equal(109, bytesConsumed); + } + + [Fact] + public void DeserializeWebSocketMessage_Partial_ThrowJsonException() + { + var con = new WebSocketConnection(new NullLogger(), null!, null!, null!); + var bytes = File.ReadAllBytes("Test Data/HttpServer/Partial.json"); + Assert.Throws(() => con.DeserializeWebSocketMessage(new ReadOnlySequence(bytes), out var bytesConsumed)); + } + + internal class BufferSegment : ReadOnlySequenceSegment + { + public BufferSegment(Memory memory) + { + Memory = memory; + } + + public BufferSegment Append(Memory memory) + { + var segment = new BufferSegment(memory) + { + RunningIndex = RunningIndex + Memory.Length + }; + Next = segment; + return segment; + } + } + } +} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj index 5c41705142..7894570391 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj +++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj @@ -13,6 +13,12 @@ Jellyfin.Server.Implementations.Tests + + + PreserveNewest + + + @@ -35,11 +41,6 @@ - - - - - ../jellyfin-tests.ruleset diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs index 75939526dc..8847239d90 100644 --- a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs +++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/HdHomerunHostTests.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -21,24 +22,15 @@ namespace Jellyfin.Server.Implementations.Tests.LiveTv public HdHomerunHostTests() { - const string BaseResourcePath = "Jellyfin.Server.Implementations.Tests.LiveTv."; - var messageHandler = new Mock(); messageHandler.Protected() .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) .Returns( (m, _) => { - var resource = BaseResourcePath + m.RequestUri?.Segments[^1]; - var stream = typeof(HdHomerunHostTests).Assembly.GetManifestResourceStream(resource); - if (stream == null) - { - throw new NullReferenceException("Resource doesn't exist: " + resource); - } - return Task.FromResult(new HttpResponseMessage() { - Content = new StreamContent(stream) + Content = new StreamContent(File.OpenRead("Test Data/LiveTv/" + m.RequestUri?.Segments[^1])) }); }); diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ForceKeepAlive.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ForceKeepAlive.json new file mode 100644 index 0000000000..0472a3cd0c --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ForceKeepAlive.json @@ -0,0 +1 @@ +{"MessageType":"ForceKeepAlive","MessageId":"00000000-0000-0000-0000-000000000000","ServerId":null,"Data":60} diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/Partial.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/Partial.json new file mode 100644 index 0000000000..72f810725a --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/Partial.json @@ -0,0 +1 @@ +{"MessageType":"KeepAlive","MessageId":"d29ef449-6965-4000 diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ValidPartial.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ValidPartial.json new file mode 100644 index 0000000000..62d9099c8b --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/HttpServer/ValidPartial.json @@ -0,0 +1 @@ +{"MessageType":"ForceKeepAlive","MessageId":"00000000-0000-0000-0000-000000000000","ServerId":null,"Data":60}{"MessageType":"KeepAlive","MessageId":"d29ef449-6965-4000 diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/discover.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/discover.json similarity index 100% rename from tests/Jellyfin.Server.Implementations.Tests/LiveTv/discover.json rename to tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/discover.json diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/lineup.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/lineup.json similarity index 100% rename from tests/Jellyfin.Server.Implementations.Tests/LiveTv/lineup.json rename to tests/Jellyfin.Server.Implementations.Tests/Test Data/LiveTv/lineup.json From 326fa8ce384d4e0c433007e02f3f68b8d5538e55 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Mon, 25 Jan 2021 03:40:34 +0800 Subject: [PATCH 04/27] add an enhanced nvdec decoder --- .../MediaEncoding/EncodingHelper.cs | 514 +++++++++--------- .../Configuration/EncodingOptions.cs | 3 + 2 files changed, 262 insertions(+), 255 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index efab87a38e..3b70ed9ddc 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -112,6 +112,11 @@ namespace MediaBrowser.Controller.MediaEncoding return _mediaEncoder.SupportsHwaccel("vaapi"); } + private bool IsCudaSupported(EncodingJobInfo state) + { + return _mediaEncoder.SupportsHwaccel("cuda"); + } + private bool IsTonemappingSupported(EncodingJobInfo state, EncodingOptions options) { var videoStream = state.VideoStream; @@ -458,7 +463,8 @@ namespace MediaBrowser.Controller.MediaEncoding var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; - var isNvdecHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1; + var isNvdecDecoder = videoDecoder.IndexOf("cuda", StringComparison.OrdinalIgnoreCase) != -1; + var isCuvidHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1; var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); @@ -534,8 +540,17 @@ namespace MediaBrowser.Controller.MediaEncoding } if (state.IsVideoRequest - && (string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecHevcDecoder || isSwDecoder) - || (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && isD3d11vaDecoder || isSwDecoder)) + && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + { + if (isNvdecDecoder) + { + arg.Append("-hwaccel_output_format cuda "); + } + } + + if (state.IsVideoRequest + && (string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder)) + || (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && (isD3d11vaDecoder || isSwDecoder))) { if (isTonemappingSupported) { @@ -922,18 +937,23 @@ namespace MediaBrowser.Controller.MediaEncoding { var videoStream = state.VideoStream; var isColorDepth10 = IsColorDepth10(state); + var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty; + var isNvdecDecoder = videoDecoder.IndexOf("cuda", StringComparison.OrdinalIgnoreCase) != -1; - if (isColorDepth10 - && _mediaEncoder.SupportsHwaccel("opencl") - && encodingOptions.EnableTonemapping - && !string.IsNullOrEmpty(videoStream.VideoRange) - && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) + if (!isNvdecDecoder) { - param += " -pix_fmt nv12"; - } - else - { - param += " -pix_fmt yuv420p"; + if (isColorDepth10 + && _mediaEncoder.SupportsHwaccel("opencl") + && encodingOptions.EnableTonemapping + && !string.IsNullOrEmpty(videoStream.VideoRange) + && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase)) + { + param += " -pix_fmt nv12"; + } + else + { + param += " -pix_fmt yuv420p"; + } } } @@ -1912,6 +1932,8 @@ namespace MediaBrowser.Controller.MediaEncoding var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiHevcEncoder = outputVideoCodec.IndexOf("hevc_vaapi", StringComparison.OrdinalIgnoreCase) != -1; + var isNvdecDecoder = videoDecoder.IndexOf("cuda", StringComparison.OrdinalIgnoreCase) != -1; + var isNvencEncoder = outputVideoCodec.IndexOf("nvenc", StringComparison.OrdinalIgnoreCase) != -1; var isTonemappingSupported = IsTonemappingSupported(state, options); var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); @@ -1940,11 +1962,14 @@ namespace MediaBrowser.Controller.MediaEncoding height.Value); } - // For QSV, feed it into hardware encoder now - if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) - || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))) + if (!string.IsNullOrEmpty(videoSizeParam)) { - videoSizeParam += ",hwupload=extra_hw_frames=64"; + // For QSV, feed it into hardware encoder now + if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))) + { + videoSizeParam += ",hwupload=extra_hw_frames=64"; + } } } @@ -2002,6 +2027,12 @@ namespace MediaBrowser.Controller.MediaEncoding : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\""; } } + else if (isNvdecDecoder && isNvencEncoder) + { + retStr = !outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=yuv420p|nv12,hwupload_cuda\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=yuv420p|nv12,hwupload_cuda\""; + } return string.Format( CultureInfo.InvariantCulture, @@ -2133,6 +2164,26 @@ namespace MediaBrowser.Controller.MediaEncoding (qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty)); } } + else if ((videoDecoder ?? string.Empty).IndexOf("cuda", StringComparison.OrdinalIgnoreCase) != -1 + && width.HasValue + && height.HasValue) + { + var outputWidth = width.Value; + var outputHeight = height.Value; + + if (!videoWidth.HasValue + || outputWidth != videoWidth.Value + || !videoHeight.HasValue + || outputHeight != videoHeight.Value) + { + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale_cuda=w={0}:h={1}", + outputWidth, + outputHeight)); + } + } else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1 && width.HasValue && height.HasValue) @@ -2367,17 +2418,20 @@ namespace MediaBrowser.Controller.MediaEncoding var isVaapiHevcEncoder = outputVideoCodec.IndexOf("hevc_vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1; var isQsvHevcEncoder = outputVideoCodec.IndexOf("hevc_qsv", StringComparison.OrdinalIgnoreCase) != -1; - var isNvdecH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1; - var isNvdecHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1; + var isNvdecDecoder = videoDecoder.IndexOf("cuda", StringComparison.OrdinalIgnoreCase) != -1; + var isNvencEncoder = outputVideoCodec.IndexOf("nvenc", StringComparison.OrdinalIgnoreCase) != -1; + var isCuvidH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1; + var isCuvidHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1; var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1; var isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1; var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isColorDepth10 = IsColorDepth10(state); var isTonemappingSupported = IsTonemappingSupported(state, options); - var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecHevcDecoder || isSwDecoder; - var isTonemappingSupportedOnAmf = string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && isD3d11vaDecoder || isSwDecoder; + var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder); + var isTonemappingSupportedOnAmf = string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && (isD3d11vaDecoder || isSwDecoder); var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); + var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; @@ -2385,6 +2439,8 @@ namespace MediaBrowser.Controller.MediaEncoding var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.RealFrameRate ?? 60) <= 30; var isScalingInAdvance = false; + var isCudaDeintInAdvance = false; + var isHwuploadCudaRequired = false; var isDeinterlaceH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); var isDeinterlaceHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); @@ -2428,15 +2484,17 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("format=p010"); } - if (isNvdecHevcDecoder || isSwDecoder || isD3d11vaDecoder) + if ((isDeinterlaceH264 || isDeinterlaceHevc) && isNvdecDecoder) { - // Upload the HDR10 or HLG data to the OpenCL device, - // use tonemap_opencl filter for tone mapping, - // and then download the SDR data to memory. - filters.Add("hwupload"); + isCudaDeintInAdvance = true; + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "yadif={0}:-1:0", + doubleRateDeinterlace ? "1" : "0")); } - if (isVaapiDecoder) + if (isVaapiDecoder || isNvdecDecoder) { isScalingInAdvance = true; filters.AddRange( @@ -2452,11 +2510,28 @@ namespace MediaBrowser.Controller.MediaEncoding request.Height, request.MaxWidth, request.MaxHeight)); + } - // hwmap the HDR data to opencl device by cl-va p010 interop. + // hwmap the HDR data to opencl device by cl-va p010 interop. + if (isVaapiDecoder) + { filters.Add("hwmap"); } + // convert cuda device data to p010 host data. + if (isNvdecDecoder) + { + filters.Add("hwdownload,format=p010"); + } + + if (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder || isD3d11vaDecoder) + { + // Upload the HDR10 or HLG data to the OpenCL device, + // use tonemap_opencl filter for tone mapping, + // and then download the SDR data to memory. + filters.Add("hwupload"); + } + filters.Add( string.Format( CultureInfo.InvariantCulture, @@ -2468,21 +2543,15 @@ namespace MediaBrowser.Controller.MediaEncoding options.TonemappingParam, options.TonemappingRange)); - if (isNvdecHevcDecoder || isSwDecoder || isD3d11vaDecoder) + if (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder || isD3d11vaDecoder) { filters.Add("hwdownload"); + filters.Add("format=nv12"); } - if (isSwDecoder || isD3d11vaDecoder) + if (isNvdecDecoder && isNvencEncoder) { - if (isLibX264Encoder - || isLibX265Encoder - || hasGraphicalSubs - || (isNvdecHevcDecoder && isDeinterlaceHevc) - || (!isNvdecHevcDecoder && isDeinterlaceH264 || isDeinterlaceHevc)) - { - filters.Add("format=nv12"); - } + isHwuploadCudaRequired = true; } if (isVaapiDecoder) @@ -2507,7 +2576,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first. - else if (IsVaapiSupported(state) && isVaapiDecoder && (isLibX264Encoder || isLibX265Encoder)) + else if ((IsVaapiSupported(state) && isVaapiDecoder) && (isLibX264Encoder || isLibX265Encoder)) { var codec = videoStream.Codec.ToLowerInvariant(); @@ -2534,9 +2603,9 @@ namespace MediaBrowser.Controller.MediaEncoding } // Add hardware deinterlace filter before scaling filter. - if (isDeinterlaceH264) + if (isDeinterlaceH264 || isDeinterlaceHevc) { - if (isVaapiH264Encoder) + if (isVaapiEncoder) { filters.Add( string.Format( @@ -2544,6 +2613,14 @@ namespace MediaBrowser.Controller.MediaEncoding "deinterlace_vaapi=rate={0}", doubleRateDeinterlace ? "field" : "frame")); } + else if (isNvdecDecoder && !isCudaDeintInAdvance) + { + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "yadif_cuda={0}:-1:0", + doubleRateDeinterlace ? "1" : "0")); + } } // Add software deinterlace filter before scaling filter. @@ -2552,7 +2629,8 @@ namespace MediaBrowser.Controller.MediaEncoding && !isVaapiHevcEncoder && !isQsvH264Encoder && !isQsvHevcEncoder - && !isNvdecH264Decoder) + && !isNvdecDecoder + && !isCuvidH264Decoder) { if (string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase)) { @@ -2590,6 +2668,41 @@ namespace MediaBrowser.Controller.MediaEncoding request.MaxHeight)); } + // Another case is using Nvenc decoder. + if (isNvdecDecoder && !isTonemappingSupported) + { + var codec = videoStream.Codec.ToLowerInvariant(); + + // Assert 10-bit hardware decodable + if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))) + { + // Download data from GPU to CPU as p010le format. + filters.Add("hwdownload"); + filters.Add("format=p010"); + + // cuda lacks of a pixel format converter. + if (isNvencEncoder) + { + isHwuploadCudaRequired = true; + filters.Add("format=yuv420p"); + } + } + + // Assert 8-bit hardware decodable + else if (!isColorDepth10 && (isLibX264Encoder || isLibX265Encoder || hasSubs)) + { + if (isNvencEncoder) + { + isHwuploadCudaRequired = true; + } + + filters.Add("hwdownload"); + filters.Add("format=nv12"); + } + } + // Add parameters to use VAAPI with burn-in text subtitles (GH issue #642) if (isVaapiH264Encoder || isVaapiHevcEncoder) { @@ -2618,10 +2731,20 @@ namespace MediaBrowser.Controller.MediaEncoding // Ensure proper filters are passed to ffmpeg in case of hardware acceleration via VA-API // Reference: https://trac.ffmpeg.org/wiki/Hardware/VAAPI - if (isVaapiH264Encoder) + if (isVaapiH264Encoder || isVaapiHevcEncoder) { filters.Add("hwmap"); } + + if (isNvdecDecoder && isNvencEncoder) + { + isHwuploadCudaRequired = true; + } + } + + if (isHwuploadCudaRequired && !hasGraphicalSubs) + { + filters.Add("hwupload_cuda"); } if (filters.Count > 0) @@ -3102,57 +3225,18 @@ namespace MediaBrowser.Controller.MediaEncoding { case "avc": case "h264": - if (_mediaEncoder.SupportsDecoder("h264_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase)) - { - // qsv decoder does not support 10-bit input - if ((videoStream.BitDepth ?? 8) > 8) - { - encodingOptions.HardwareDecodingCodecs = Array.Empty(); - return null; - } - - return "-c:v h264_qsv"; - } - - break; + return GetHwDecoderName(encodingOptions, "h264_qsv", "h264", isColorDepth10); case "hevc": case "h265": - if (_mediaEncoder.SupportsDecoder("hevc_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase)) - { - return (isColorDepth10 && - !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_qsv"; - } - - break; + return GetHwDecoderName(encodingOptions, "hevc_qsv", "hevc", isColorDepth10); case "mpeg2video": - if (_mediaEncoder.SupportsDecoder("mpeg2_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v mpeg2_qsv"; - } - - break; + return GetHwDecoderName(encodingOptions, "mpeg2_qsv", "mpeg2video", isColorDepth10); case "vc1": - if (_mediaEncoder.SupportsDecoder("vc1_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v vc1_qsv"; - } - - break; + return GetHwDecoderName(encodingOptions, "vc1_qsv", "vc1", isColorDepth10); case "vp8": - if (_mediaEncoder.SupportsDecoder("vp8_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v vp8_qsv"; - } - - break; + return GetHwDecoderName(encodingOptions, "vp8_qsv", "vp8", isColorDepth10); case "vp9": - if (_mediaEncoder.SupportsDecoder("vp9_qsv") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase)) - { - return (isColorDepth10 && - !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_qsv"; - } - - break; + return GetHwDecoderName(encodingOptions, "vp9_qsv", "vp9", isColorDepth10); } } else if (string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) @@ -3161,57 +3245,34 @@ namespace MediaBrowser.Controller.MediaEncoding { case "avc": case "h264": - if (_mediaEncoder.SupportsDecoder("h264_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v h264_cuvid"; - } - - break; + return encodingOptions.EnableEnhancedNvdecDecoder + ? GetHwaccelType(state, encodingOptions, "h264", isColorDepth10) + : GetHwDecoderName(encodingOptions, "h264_cuvid", "h264", isColorDepth10); case "hevc": case "h265": - if (_mediaEncoder.SupportsDecoder("hevc_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase)) - { - return (isColorDepth10 && - !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_cuvid"; - } - - break; + return encodingOptions.EnableEnhancedNvdecDecoder + ? GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10) + : GetHwDecoderName(encodingOptions, "hevc_cuvid", "hevc", isColorDepth10); case "mpeg2video": - if (_mediaEncoder.SupportsDecoder("mpeg2_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v mpeg2_cuvid"; - } - - break; + return encodingOptions.EnableEnhancedNvdecDecoder + ? GetHwaccelType(state, encodingOptions, "mpeg2video", isColorDepth10) + : GetHwDecoderName(encodingOptions, "mpeg2_cuvid", "mpeg2video", isColorDepth10); case "vc1": - if (_mediaEncoder.SupportsDecoder("vc1_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v vc1_cuvid"; - } - - break; + return encodingOptions.EnableEnhancedNvdecDecoder + ? GetHwaccelType(state, encodingOptions, "vc1", isColorDepth10) + : GetHwDecoderName(encodingOptions, "vc1_cuvid", "vc1", isColorDepth10); case "mpeg4": - if (_mediaEncoder.SupportsDecoder("mpeg4_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v mpeg4_cuvid"; - } - - break; + return encodingOptions.EnableEnhancedNvdecDecoder + ? GetHwaccelType(state, encodingOptions, "mpeg4", isColorDepth10) + : GetHwDecoderName(encodingOptions, "mpeg4_cuvid", "mpeg4", isColorDepth10); case "vp8": - if (_mediaEncoder.SupportsDecoder("vp8_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v vp8_cuvid"; - } - - break; + return encodingOptions.EnableEnhancedNvdecDecoder + ? GetHwaccelType(state, encodingOptions, "vp8", isColorDepth10) + : GetHwDecoderName(encodingOptions, "vp8_cuvid", "vp8", isColorDepth10); case "vp9": - if (_mediaEncoder.SupportsDecoder("vp9_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase)) - { - return (isColorDepth10 && - !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_cuvid"; - } - - break; + return encodingOptions.EnableEnhancedNvdecDecoder + ? GetHwaccelType(state, encodingOptions, "vp9", isColorDepth10) + : GetHwDecoderName(encodingOptions, "vp9_cuvid", "vp9", isColorDepth10); } } else if (string.Equals(encodingOptions.HardwareAccelerationType, "mediacodec", StringComparison.OrdinalIgnoreCase)) @@ -3220,50 +3281,18 @@ namespace MediaBrowser.Controller.MediaEncoding { case "avc": case "h264": - if (_mediaEncoder.SupportsDecoder("h264_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v h264_mediacodec"; - } - - break; + return GetHwDecoderName(encodingOptions, "h264_mediacodec", "h264", isColorDepth10); case "hevc": case "h265": - if (_mediaEncoder.SupportsDecoder("hevc_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase)) - { - return (isColorDepth10 && - !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_mediacodec"; - } - - break; + return GetHwDecoderName(encodingOptions, "hevc_mediacodec", "hevc", isColorDepth10); case "mpeg2video": - if (_mediaEncoder.SupportsDecoder("mpeg2_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v mpeg2_mediacodec"; - } - - break; + return GetHwDecoderName(encodingOptions, "mpeg2_mediacodec", "mpeg2video", isColorDepth10); case "mpeg4": - if (_mediaEncoder.SupportsDecoder("mpeg4_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v mpeg4_mediacodec"; - } - - break; + return GetHwDecoderName(encodingOptions, "mpeg4_mediacodec", "mpeg4", isColorDepth10); case "vp8": - if (_mediaEncoder.SupportsDecoder("vp8_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp8", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v vp8_mediacodec"; - } - - break; + return GetHwDecoderName(encodingOptions, "vp8_mediacodec", "vp8", isColorDepth10); case "vp9": - if (_mediaEncoder.SupportsDecoder("vp9_mediacodec") && encodingOptions.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase)) - { - return (isColorDepth10 && - !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_mediacodec"; - } - - break; + return GetHwDecoderName(encodingOptions, "vp9_mediacodec", "vp9", isColorDepth10); } } else if (string.Equals(encodingOptions.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase)) @@ -3272,33 +3301,13 @@ namespace MediaBrowser.Controller.MediaEncoding { case "avc": case "h264": - if (_mediaEncoder.SupportsDecoder("h264_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v h264_mmal"; - } - - break; + return GetHwDecoderName(encodingOptions, "h264_mmal", "h264", isColorDepth10); case "mpeg2video": - if (_mediaEncoder.SupportsDecoder("mpeg2_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v mpeg2_mmal"; - } - - break; + return GetHwDecoderName(encodingOptions, "mpeg2_mmal", "mpeg2video", isColorDepth10); case "mpeg4": - if (_mediaEncoder.SupportsDecoder("mpeg4_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v mpeg4_mmal"; - } - - break; + return GetHwDecoderName(encodingOptions, "mpeg4_mmal", "mpeg4", isColorDepth10); case "vc1": - if (_mediaEncoder.SupportsDecoder("vc1_mmal") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v vc1_mmal"; - } - - break; + return GetHwDecoderName(encodingOptions, "vc1_mmal", "vc1", isColorDepth10); } } else if (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) @@ -3307,20 +3316,18 @@ namespace MediaBrowser.Controller.MediaEncoding { case "avc": case "h264": - return GetHwaccelType(state, encodingOptions, "h264"); + return GetHwaccelType(state, encodingOptions, "h264", isColorDepth10); case "hevc": case "h265": - return (isColorDepth10 && - !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : GetHwaccelType(state, encodingOptions, "hevc"); + return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10); case "mpeg2video": - return GetHwaccelType(state, encodingOptions, "mpeg2video"); + return GetHwaccelType(state, encodingOptions, "mpeg2video", isColorDepth10); case "vc1": - return GetHwaccelType(state, encodingOptions, "vc1"); + return GetHwaccelType(state, encodingOptions, "vc1", isColorDepth10); case "mpeg4": - return GetHwaccelType(state, encodingOptions, "mpeg4"); + return GetHwaccelType(state, encodingOptions, "mpeg4", isColorDepth10); case "vp9": - return (isColorDepth10 && - !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : GetHwaccelType(state, encodingOptions, "vp9"); + return GetHwaccelType(state, encodingOptions, "vp9", isColorDepth10); } } else if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) @@ -3329,20 +3336,18 @@ namespace MediaBrowser.Controller.MediaEncoding { case "avc": case "h264": - return GetHwaccelType(state, encodingOptions, "h264"); + return GetHwaccelType(state, encodingOptions, "h264", isColorDepth10); case "hevc": case "h265": - return (isColorDepth10 && - !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : GetHwaccelType(state, encodingOptions, "hevc"); + return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10); case "mpeg2video": - return GetHwaccelType(state, encodingOptions, "mpeg2video"); + return GetHwaccelType(state, encodingOptions, "mpeg2video", isColorDepth10); case "vc1": - return GetHwaccelType(state, encodingOptions, "vc1"); + return GetHwaccelType(state, encodingOptions, "vc1", isColorDepth10); case "vp8": - return GetHwaccelType(state, encodingOptions, "vp8"); + return GetHwaccelType(state, encodingOptions, "vp8", isColorDepth10); case "vp9": - return (isColorDepth10 && - !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : GetHwaccelType(state, encodingOptions, "vp9"); + return GetHwaccelType(state, encodingOptions, "vp9", isColorDepth10); } } else if (string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) @@ -3351,57 +3356,20 @@ namespace MediaBrowser.Controller.MediaEncoding { case "avc": case "h264": - if (_mediaEncoder.SupportsDecoder("h264_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v h264_opencl"; - } - - break; + return GetHwDecoderName(encodingOptions, "h264_opencl", "h264", isColorDepth10); case "hevc": case "h265": - if (_mediaEncoder.SupportsDecoder("hevc_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase)) - { - return (isColorDepth10 && - !encodingOptions.EnableDecodingColorDepth10Hevc) ? null : "-c:v hevc_opencl"; - } - - break; + return GetHwDecoderName(encodingOptions, "hevc_opencl", "hevc", isColorDepth10); case "mpeg2video": - if (_mediaEncoder.SupportsDecoder("mpeg2_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg2video", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v mpeg2_opencl"; - } - - break; + return GetHwDecoderName(encodingOptions, "mpeg2_opencl", "mpeg2video", isColorDepth10); case "mpeg4": - if (_mediaEncoder.SupportsDecoder("mpeg4_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("mpeg4", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v mpeg4_opencl"; - } - - break; + return GetHwDecoderName(encodingOptions, "mpeg4_opencl", "mpeg4", isColorDepth10); case "vc1": - if (_mediaEncoder.SupportsDecoder("vc1_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v vc1_opencl"; - } - - break; + return GetHwDecoderName(encodingOptions, "vc1_opencl", "vc1", isColorDepth10); case "vp8": - if (_mediaEncoder.SupportsDecoder("vp8_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) - { - return "-c:v vp8_opencl"; - } - - break; + return GetHwDecoderName(encodingOptions, "vp8_opencl", "vp8", isColorDepth10); case "vp9": - if (_mediaEncoder.SupportsDecoder("vp9_opencl") && encodingOptions.HardwareDecodingCodecs.Contains("vc1", StringComparer.OrdinalIgnoreCase)) - { - return (isColorDepth10 && - !encodingOptions.EnableDecodingColorDepth10Vp9) ? null : "-c:v vp9_opencl"; - } - - break; + return GetHwDecoderName(encodingOptions, "vp9_opencl", "vp9", isColorDepth10); } } } @@ -3424,15 +3392,43 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } + /// + /// Gets a hw decoder name + /// + public string GetHwDecoderName(EncodingOptions options, string decoder, string videoCodec, bool isColorDepth10) + { + var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoder) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase); + if (isColorDepth10 && isCodecAvailable) + { + if ((options.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Hevc) + || (options.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Vp9)) + { + return null; + } + } + + return isCodecAvailable ? ("-c:v " + decoder) : null; + } + /// /// Gets a hwaccel type to use as a hardware decoder(dxva/vaapi) depending on the system /// - public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec) + public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, bool isColorDepth10) { var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isWindows8orLater = Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1); var isDxvaSupported = _mediaEncoder.SupportsHwaccel("dxva2") || _mediaEncoder.SupportsHwaccel("d3d11va"); + var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase); + + if (isColorDepth10 && isCodecAvailable) + { + if ((options.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Hevc) + || (options.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Vp9)) + { + return null; + } + } if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)) { @@ -3462,6 +3458,14 @@ namespace MediaBrowser.Controller.MediaEncoding } } + if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + { + if (IsCudaSupported(state) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) + { + return "-hwaccel cuda"; + } + } + return null; } diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 38b3335107..5cd8744eda 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -65,6 +65,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; } @@ -100,6 +102,7 @@ namespace MediaBrowser.Model.Configuration DeinterlaceMethod = "yadif"; EnableDecodingColorDepth10Hevc = true; EnableDecodingColorDepth10Vp9 = true; + EnableEnhancedNvdecDecoder = true; EnableHardwareEncoding = true; AllowHevcEncoding = true; EnableSubtitleExtraction = true; From 8c640a1492ad3722778636222a6c29d0cce8cdc9 Mon Sep 17 00:00:00 2001 From: Troy <47042611+M0ssTee@users.noreply.github.com> Date: Mon, 1 Feb 2021 02:46:30 +0000 Subject: [PATCH 05/27] Replaced /d with [0-9], see issue #2923 --- Emby.Dlna/Profiles/SonyBravia2010Profile.cs | 4 ++-- Emby.Dlna/Profiles/SonyBravia2011Profile.cs | 4 ++-- Emby.Dlna/Profiles/SonyBravia2012Profile.cs | 4 ++-- Emby.Dlna/Profiles/SonyBravia2013Profile.cs | 4 ++-- Emby.Dlna/Profiles/SonyBravia2014Profile.cs | 4 ++-- Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml | 4 ++-- Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml | 4 ++-- Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml | 4 ++-- Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml | 4 ++-- Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml | 4 ++-- Emby.Naming/Common/NamingOptions.cs | 2 +- .../LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs | 2 +- MediaBrowser.MediaEncoding/Subtitles/AssParser.cs | 2 +- MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs | 2 +- 14 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Emby.Dlna/Profiles/SonyBravia2010Profile.cs b/Emby.Dlna/Profiles/SonyBravia2010Profile.cs index 8ab4acd1be..9f0d82b8f8 100644 --- a/Emby.Dlna/Profiles/SonyBravia2010Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2010Profile.cs @@ -13,7 +13,7 @@ namespace Emby.Dlna.Profiles Identification = new DeviceIdentification { - FriendlyName = @"KDL-\d{2}[EHLNPB]X\d[01]\d.*", + FriendlyName = @"KDL-[0-9]{2}[EHLNPB]X[0-9][01][0-9].*", Manufacturer = "Sony", Headers = new[] @@ -21,7 +21,7 @@ namespace Emby.Dlna.Profiles new HttpHeaderInfo { Name = "X-AV-Client-Info", - Value = @".*KDL-\d{2}[EHLNPB]X\d[01]\d.*", + Value = @".*KDL-[0-9]{2}[EHLNPB]X[0-9][01][0-9].*", Match = HeaderMatchType.Regex } } diff --git a/Emby.Dlna/Profiles/SonyBravia2011Profile.cs b/Emby.Dlna/Profiles/SonyBravia2011Profile.cs index 42d253394c..dfb91817ac 100644 --- a/Emby.Dlna/Profiles/SonyBravia2011Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2011Profile.cs @@ -13,7 +13,7 @@ namespace Emby.Dlna.Profiles Identification = new DeviceIdentification { - FriendlyName = @"KDL-\d{2}([A-Z]X\d2\d|CX400).*", + FriendlyName = @"KDL-[0-9]{2}([A-Z]X[0-9]2[0-9]|CX400).*", Manufacturer = "Sony", Headers = new[] @@ -21,7 +21,7 @@ namespace Emby.Dlna.Profiles new HttpHeaderInfo { Name = "X-AV-Client-Info", - Value = @".*KDL-\d{2}([A-Z]X\d2\d|CX400).*", + Value = @".*KDL-[0-9]{2}([A-Z]X[0-9]2[0-9]|CX400).*", Match = HeaderMatchType.Regex } } diff --git a/Emby.Dlna/Profiles/SonyBravia2012Profile.cs b/Emby.Dlna/Profiles/SonyBravia2012Profile.cs index 0598e83422..d59ee38d74 100644 --- a/Emby.Dlna/Profiles/SonyBravia2012Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2012Profile.cs @@ -13,7 +13,7 @@ namespace Emby.Dlna.Profiles Identification = new DeviceIdentification { - FriendlyName = @"KDL-\d{2}[A-Z]X\d5(\d|G).*", + FriendlyName = @"KDL-[0-9]{2}[A-Z]X[0-9]5([0-9]|G).*", Manufacturer = "Sony", Headers = new[] @@ -21,7 +21,7 @@ namespace Emby.Dlna.Profiles new HttpHeaderInfo { Name = "X-AV-Client-Info", - Value = @".*KDL-\d{2}[A-Z]X\d5(\d|G).*", + Value = @".*KDL-[0-9]{2}[A-Z]X[0-9]5([0-9]|G).*", Match = HeaderMatchType.Regex } } diff --git a/Emby.Dlna/Profiles/SonyBravia2013Profile.cs b/Emby.Dlna/Profiles/SonyBravia2013Profile.cs index 3d90a1e72d..73b0fd67eb 100644 --- a/Emby.Dlna/Profiles/SonyBravia2013Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2013Profile.cs @@ -13,7 +13,7 @@ namespace Emby.Dlna.Profiles Identification = new DeviceIdentification { - FriendlyName = @"KDL-\d{2}[WR][5689]\d{2}A.*", + FriendlyName = @"KDL-[0-9]{2}[WR][5689][0-9]{2}A.*", Manufacturer = "Sony", Headers = new[] @@ -21,7 +21,7 @@ namespace Emby.Dlna.Profiles new HttpHeaderInfo { Name = "X-AV-Client-Info", - Value = @".*KDL-\d{2}[WR][5689]\d{2}A.*", + Value = @".*KDL-[0-9]{2}[WR][5689][0-9]{2}A.*", Match = HeaderMatchType.Regex } } diff --git a/Emby.Dlna/Profiles/SonyBravia2014Profile.cs b/Emby.Dlna/Profiles/SonyBravia2014Profile.cs index 9188f73ef1..db8ee5750f 100644 --- a/Emby.Dlna/Profiles/SonyBravia2014Profile.cs +++ b/Emby.Dlna/Profiles/SonyBravia2014Profile.cs @@ -13,7 +13,7 @@ namespace Emby.Dlna.Profiles Identification = new DeviceIdentification { - FriendlyName = @"(KDL-\d{2}W[5-9]\d{2}B|KDL-\d{2}R480|XBR-\d{2}X[89]\d{2}B|KD-\d{2}[SX][89]\d{3}B).*", + FriendlyName = @"(KDL-[0-9]{2}W[5-9][0-9]{2}B|KDL-[0-9]{2}R480|XBR-[0-9]{2}X[89][0-9]{2}B|KD-[0-9]{2}[SX][89][0-9]{3}B).*", Manufacturer = "Sony", Headers = new[] @@ -21,7 +21,7 @@ namespace Emby.Dlna.Profiles new HttpHeaderInfo { Name = "X-AV-Client-Info", - Value = @".*(KDL-\d{2}W[5-9]\d{2}B|KDL-\d{2}R480|XBR-\d{2}X[89]\d{2}B|KD-\d{2}[SX][89]\d{3}B).*", + Value = @".*(KDL-[0-9]{2}W[5-9][0-9]{2}B|KDL-[0-9]{2}R480|XBR-[0-9]{2}X[89][0-9]{2}B|KD-[0-9]{2}[SX][89][0-9]{3}B).*", Match = HeaderMatchType.Regex } } diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml index f20e9fcb6f..1461db3117 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml +++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2010).xml @@ -3,10 +3,10 @@ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> Sony Bravia (2010) - KDL-\d{2}[EHLNPB]X\d[01]\d.* + KDL-[0-9]{2}[EHLNPB]X[0-9][01][0-9].* Sony - + Microsoft Corporation diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml index e516ff512d..7c5f2b1817 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml +++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml @@ -3,10 +3,10 @@ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> Sony Bravia (2011) - KDL-\d{2}([A-Z]X\d2\d|CX400).* + KDL-[0-9]{2}([A-Z]X[0-9]2[0-9]|CX400).* Sony - + Microsoft Corporation diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml index 88bd1c2f53..842a8fba33 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml +++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml @@ -3,10 +3,10 @@ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> Sony Bravia (2012) - KDL-\d{2}[A-Z]X\d5(\d|G).* + KDL-[0-9]{2}[A-Z]X[0-9]5([0-9]|G).* Sony - + Microsoft Corporation diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml index 3ca9893cdc..f1135c3fe3 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml +++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml @@ -3,10 +3,10 @@ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> Sony Bravia (2013) - KDL-\d{2}[WR][5689]\d{2}A.* + KDL-[0-9]{2}[WR][5689][0-9]{2}A.* Sony - + Microsoft Corporation diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml index 8804a75dfa..85c7868c66 100644 --- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml +++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml @@ -3,10 +3,10 @@ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> Sony Bravia (2014) - (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).* + (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).* Sony - + Microsoft Corporation diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 035d1b2280..365f1a9268 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -587,7 +587,7 @@ namespace Emby.Naming.Common AudioBookNamesExpressions = new[] { // Detect year usually in brackets after name Batman (2020) - @"^(?.+?)\s*\(\s*(?\d{4})\s*\)\s*$", + @"^(?.+?)\s*\(\s*(?[0-9]{4})\s*\)\s*$", @"^\s*(?[^ ].*?)\s*$" }; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index cdc8c6870a..615eff2c4e 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public LegacyHdHomerunChannelCommands(string url) { // parse url for channel and program - var regExp = new Regex(@"\/ch(\d+)-?(\d*)"); + var regExp = new Regex(@"\/ch([0-9]+)-?([[0-9]*)"); var match = regExp.Match(url); if (match.Success) { diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs index e0b7914fbe..3b46fdaf79 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles subEvent.Text = subEvent.Text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase); - subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w\d]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase); + subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w[0-9]]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase); trackEvents.Add(subEvent); } diff --git a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs index 4a87f87dc1..ccef7eeeae 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SrtParser.cs @@ -79,7 +79,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles subEvent.Text = string.Join(ParserValues.NewLine, multiline); subEvent.Text = subEvent.Text.Replace(@"\N", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase); - subEvent.Text = Regex.Replace(subEvent.Text, @"\{(?:\\\d?[\w.-]+(?:\([^\)]*\)|&H?[0-9A-Fa-f]+&|))+\}", string.Empty, RegexOptions.IgnoreCase); + subEvent.Text = Regex.Replace(subEvent.Text, @"\{(?:\\[0-9]?[\w.-]+(?:\([^\)]*\)|&H?[0-9A-Fa-f]+&|))+\}", string.Empty, RegexOptions.IgnoreCase); subEvent.Text = Regex.Replace(subEvent.Text, "<", "<", RegexOptions.IgnoreCase); subEvent.Text = Regex.Replace(subEvent.Text, ">", ">", RegexOptions.IgnoreCase); subEvent.Text = Regex.Replace(subEvent.Text, "<(\\/?(font|b|u|i|s))((\\s+(\\w|\\w[\\w\\-]*\\w)(\\s*=\\s*(?:\\\".*?\\\"|'.*?'|[^'\\\">\\s]+))?)+\\s*|\\s*)(\\/?)>", "<$1$3$7>", RegexOptions.IgnoreCase); From 8f88d0d2cbcfb8ef0a79fbfa4173aabda9395366 Mon Sep 17 00:00:00 2001 From: M0ssTee <47042611+M0ssTee@users.noreply.github.com> Date: Wed, 3 Feb 2021 00:57:04 -0500 Subject: [PATCH 06/27] Update Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs Co-authored-by: Cody Robibero --- .../LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs index 615eff2c4e..f09338330f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs @@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun public LegacyHdHomerunChannelCommands(string url) { // parse url for channel and program - var regExp = new Regex(@"\/ch([0-9]+)-?([[0-9]*)"); + var regExp = new Regex(@"\/ch([0-9]+)-?([0-9]*)"); var match = regExp.Match(url); if (match.Success) { From 256bb3ee989082ed10bfb0cb3f02481bf3292a43 Mon Sep 17 00:00:00 2001 From: M0ssTee <47042611+M0ssTee@users.noreply.github.com> Date: Thu, 4 Feb 2021 16:14:57 -0500 Subject: [PATCH 07/27] Update MediaBrowser.MediaEncoding/Subtitles/AssParser.cs Co-authored-by: Cody Robibero --- MediaBrowser.MediaEncoding/Subtitles/AssParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs index 3b46fdaf79..bb48bed270 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/AssParser.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles subEvent.Text = subEvent.Text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase); - subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w[0-9]]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase); + subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w0-9]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase); trackEvents.Add(subEvent); } From 09b9fa3ce12e8837605c00f7b8f963e649c7ecc7 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Tue, 26 Jan 2021 03:45:56 +0800 Subject: [PATCH 08/27] add vpp tonemapping for vaapi --- .../MediaEncoding/EncodingHelper.cs | 92 +++++++++++++------ .../Configuration/EncodingOptions.cs | 3 + 2 files changed, 69 insertions(+), 26 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 3b70ed9ddc..1ba02e276e 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -127,6 +127,25 @@ namespace MediaBrowser.Controller.MediaEncoding && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase); } + private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options) + { + var videoStream = state.VideoStream; + var codec = videoStream.Codec; + if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + { + // Limited to HEVC for now since the filter doesn't accept master data from VP9. + return IsColorDepth10(state) + && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) + && _mediaEncoder.SupportsHwaccel("vaapi") + && options.EnableVppTonemapping + && !string.IsNullOrEmpty(videoStream.ColorTransfer) + && videoStream.ColorTransfer.Equals("smpte2084", StringComparison.OrdinalIgnoreCase); + } + + // Vpp tonemapping may come to QSV in the future. + return false; + } + /// /// Gets the name of the output video codec. /// @@ -469,6 +488,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); var isTonemappingSupported = IsTonemappingSupported(state, encodingOptions); + var isVppTonemappingSupported = IsVppTonemappingSupported(state, encodingOptions); if (!IsCopyCodec(outputVideoCodec)) { @@ -478,7 +498,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (isVaapiDecoder) { - if (isTonemappingSupported) + if (isTonemappingSupported && !isVppTonemappingSupported) { arg.Append("-init_hw_device vaapi=va:") .Append(encodingOptions.VaapiDevice) @@ -938,7 +958,7 @@ namespace MediaBrowser.Controller.MediaEncoding var videoStream = state.VideoStream; var isColorDepth10 = IsColorDepth10(state); var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty; - var isNvdecDecoder = videoDecoder.IndexOf("cuda", StringComparison.OrdinalIgnoreCase) != -1; + var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); if (!isNvdecDecoder) { @@ -1932,14 +1952,15 @@ namespace MediaBrowser.Controller.MediaEncoding var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiHevcEncoder = outputVideoCodec.IndexOf("hevc_vaapi", StringComparison.OrdinalIgnoreCase) != -1; - var isNvdecDecoder = videoDecoder.IndexOf("cuda", StringComparison.OrdinalIgnoreCase) != -1; - var isNvencEncoder = outputVideoCodec.IndexOf("nvenc", StringComparison.OrdinalIgnoreCase) != -1; + var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); + var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase); var isTonemappingSupported = IsTonemappingSupported(state, options); + var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); // Tonemapping and burn-in graphical subtitles requires overlay_vaapi. // But it's still in ffmpeg mailing list. Disable it for now. - if (isTonemappingSupported && isTonemappingSupportedOnVaapi) + if (isTonemappingSupported && isTonemappingSupportedOnVaapi && !isVppTonemappingSupported) { return GetOutputSizeParam(state, options, outputVideoCodec); } @@ -2123,17 +2144,24 @@ namespace MediaBrowser.Controller.MediaEncoding || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); + var isVaapiDecoder = videoDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); + var isVaapiH264Encoder = videoEncoder.Contains("h264_vaapi", StringComparison.OrdinalIgnoreCase); + var isVaapiHevcEncoder = videoEncoder.Contains("hevc_vaapi", StringComparison.OrdinalIgnoreCase); var isTonemappingSupported = IsTonemappingSupported(state, options); - var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && !qsv_or_vaapi; + var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); + var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); - var outputPixFmt = string.Empty; - if (isTonemappingSupported && isTonemappingSupportedOnVaapi) + var outputPixFmt = "format=nv12"; + if (isTonemappingSupportedOnVaapi) { - outputPixFmt = "format=p010:out_range=limited"; - } - else - { - outputPixFmt = "format=nv12"; + if (isVppTonemappingSupported) + { + outputPixFmt = "format=p010"; + } + else if (isTonemappingSupported) + { + outputPixFmt = "format=p010:out_range=limited"; + } } if (!videoWidth.HasValue @@ -2164,7 +2192,7 @@ namespace MediaBrowser.Controller.MediaEncoding (qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty)); } } - else if ((videoDecoder ?? string.Empty).IndexOf("cuda", StringComparison.OrdinalIgnoreCase) != -1 + else if ((videoDecoder ?? string.Empty).Contains("cuda", StringComparison.OrdinalIgnoreCase) && width.HasValue && height.HasValue) { @@ -2418,15 +2446,16 @@ namespace MediaBrowser.Controller.MediaEncoding var isVaapiHevcEncoder = outputVideoCodec.IndexOf("hevc_vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1; var isQsvHevcEncoder = outputVideoCodec.IndexOf("hevc_qsv", StringComparison.OrdinalIgnoreCase) != -1; - var isNvdecDecoder = videoDecoder.IndexOf("cuda", StringComparison.OrdinalIgnoreCase) != -1; - var isNvencEncoder = outputVideoCodec.IndexOf("nvenc", StringComparison.OrdinalIgnoreCase) != -1; - var isCuvidH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1; - var isCuvidHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1; + var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); + var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase); + var isCuvidH264Decoder = videoDecoder.Contains("h264_cuvid", StringComparison.OrdinalIgnoreCase); + var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase); var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1; var isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1; var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isColorDepth10 = IsColorDepth10(state); var isTonemappingSupported = IsTonemappingSupported(state, options); + var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder); var isTonemappingSupportedOnAmf = string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && (isD3d11vaDecoder || isSwDecoder); var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); @@ -2444,7 +2473,8 @@ namespace MediaBrowser.Controller.MediaEncoding var isDeinterlaceH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); var isDeinterlaceHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); - if (isTonemappingSupportedOnNvenc || isTonemappingSupportedOnAmf || isTonemappingSupportedOnVaapi) + // Add OpenCL tonemapping filter for NVENC/AMF/VAAPI. + if (isTonemappingSupportedOnNvenc || isTonemappingSupportedOnAmf || (isTonemappingSupportedOnVaapi && !isVppTonemappingSupported)) { // Currently only with the use of NVENC decoder can we get a decent performance. // Currently only the HEVC/H265 format is supported with NVDEC decoder. @@ -2490,7 +2520,7 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add( string.Format( CultureInfo.InvariantCulture, - "yadif={0}:-1:0", + "yadif_cuda={0}:-1:0", doubleRateDeinterlace ? "1" : "0")); } @@ -2563,7 +2593,10 @@ namespace MediaBrowser.Controller.MediaEncoding } // When the input may or may not be hardware VAAPI decodable. - if ((isVaapiH264Encoder || isVaapiHevcEncoder) && !isTonemappingSupported && !isTonemappingSupportedOnVaapi) + if ((isVaapiH264Encoder || isVaapiHevcEncoder) + && !isTonemappingSupported + && !isVppTonemappingSupported + && !isTonemappingSupportedOnVaapi) { filters.Add("format=nv12|vaapi"); filters.Add("hwupload"); @@ -2668,21 +2701,28 @@ namespace MediaBrowser.Controller.MediaEncoding request.MaxHeight)); } - // Another case is using Nvenc decoder. + // Add Vpp tonemapping filter for VAAPI. + // Full hardware based video post processing, faster than OpenCL but lacks fine tuning options. + if (isTonemappingSupportedOnVaapi && isVppTonemappingSupported) + { + filters.Add("tonemap_vaapi=format=nv12:transfer=bt709:matrix=bt709:primaries=bt709"); + } + + // Another case is when using Nvenc decoder. if (isNvdecDecoder && !isTonemappingSupported) { - var codec = videoStream.Codec.ToLowerInvariant(); + var codec = videoStream.Codec; // Assert 10-bit hardware decodable if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))) { - // Download data from GPU to CPU as p010le format. + // Download data from GPU to CPU as p010 format. filters.Add("hwdownload"); filters.Add("format=p010"); - // cuda lacks of a pixel format converter. + // Cuda lacks of a pixel format converter. if (isNvencEncoder) { isHwuploadCudaRequired = true; @@ -2710,7 +2750,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // Convert hw context from ocl to va. // For tonemapping and text subs burn-in. - if (isTonemappingSupported && isTonemappingSupportedOnVaapi) + if (isTonemappingSupported && isTonemappingSupportedOnVaapi && !isVppTonemappingSupported) { filters.Add("scale_vaapi"); } diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 5cd8744eda..da467e133f 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -39,6 +39,8 @@ namespace MediaBrowser.Model.Configuration public bool EnableTonemapping { get; set; } + public bool EnableVppTonemapping { get; set; } + public string TonemappingAlgorithm { get; set; } public string TonemappingRange { get; set; } @@ -90,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; From b0e0e19468eeadcff0c3a16b47607ce2620227af Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Wed, 27 Jan 2021 03:20:53 +0800 Subject: [PATCH 09/27] add cuda format converter --- .../MediaEncoding/EncodingHelper.cs | 131 +++++++++++------- .../MediaEncoding/IMediaEncoder.cs | 8 ++ .../Encoder/EncoderValidator.cs | 32 +++++ .../Encoder/MediaEncoder.cs | 11 ++ 4 files changed, 133 insertions(+), 49 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 1ba02e276e..1f60f3bcc4 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -112,9 +112,11 @@ namespace MediaBrowser.Controller.MediaEncoding return _mediaEncoder.SupportsHwaccel("vaapi"); } - private bool IsCudaSupported(EncodingJobInfo state) + private bool IsCudaSupported() { - return _mediaEncoder.SupportsHwaccel("cuda"); + return _mediaEncoder.SupportsHwaccel("cuda") + && _mediaEncoder.SupportsFilter("scale_cuda", null) + && _mediaEncoder.SupportsFilter("yadif_cuda", null); } private bool IsTonemappingSupported(EncodingJobInfo state, EncodingOptions options) @@ -123,8 +125,7 @@ namespace MediaBrowser.Controller.MediaEncoding return IsColorDepth10(state) && _mediaEncoder.SupportsHwaccel("opencl") && options.EnableTonemapping - && !string.IsNullOrEmpty(videoStream.VideoRange) - && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase); + && string.Equals(videoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase); } private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options) @@ -138,8 +139,7 @@ namespace MediaBrowser.Controller.MediaEncoding && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) && _mediaEncoder.SupportsHwaccel("vaapi") && options.EnableVppTonemapping - && !string.IsNullOrEmpty(videoStream.ColorTransfer) - && videoStream.ColorTransfer.Equals("smpte2084", StringComparison.OrdinalIgnoreCase); + && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase); } // Vpp tonemapping may come to QSV in the future. @@ -482,8 +482,8 @@ namespace MediaBrowser.Controller.MediaEncoding var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; - var isNvdecDecoder = videoDecoder.IndexOf("cuda", StringComparison.OrdinalIgnoreCase) != -1; - var isCuvidHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1; + var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); + var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase); var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); var isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); @@ -560,17 +560,17 @@ namespace MediaBrowser.Controller.MediaEncoding } if (state.IsVideoRequest - && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) + && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) + && isNvdecDecoder) { - if (isNvdecDecoder) - { - arg.Append("-hwaccel_output_format cuda "); - } + arg.Append("-hwaccel_output_format cuda "); } if (state.IsVideoRequest - && (string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder)) - || (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && (isD3d11vaDecoder || isSwDecoder))) + && ((string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) + && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder)) + || (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) + && (isD3d11vaDecoder || isSwDecoder)))) { if (isTonemappingSupported) { @@ -2051,8 +2051,8 @@ namespace MediaBrowser.Controller.MediaEncoding else if (isNvdecDecoder && isNvencEncoder) { retStr = !outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=yuv420p|nv12,hwupload_cuda\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=yuv420p|nv12,hwupload_cuda\""; + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=nv12|yuv420p,hwupload_cuda\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=nv12|yuv420p,hwupload_cuda\""; } return string.Format( @@ -2152,16 +2152,9 @@ namespace MediaBrowser.Controller.MediaEncoding var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); var outputPixFmt = "format=nv12"; - if (isTonemappingSupportedOnVaapi) + if (isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported)) { - if (isVppTonemappingSupported) - { - outputPixFmt = "format=p010"; - } - else if (isTonemappingSupported) - { - outputPixFmt = "format=p010:out_range=limited"; - } + outputPixFmt = "format=p010"; } if (!videoWidth.HasValue @@ -2181,7 +2174,9 @@ namespace MediaBrowser.Controller.MediaEncoding ":" + outputPixFmt, (qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty)); } - else + + // Assert 10-bit is P010 so as we can avoid the extra scaler to get a bit more fps on high res HDR videos. + else if (!(isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported))) { filters.Add( string.Format( @@ -2199,6 +2194,20 @@ namespace MediaBrowser.Controller.MediaEncoding var outputWidth = width.Value; var outputHeight = height.Value; + var isTonemappingSupported = IsTonemappingSupported(state, options); + var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase); + var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilter("scale_cuda", "Output format (default \"same\")"); + + var outputPixFmt = string.Empty; + if (isCudaFormatConversionSupported) + { + outputPixFmt = "format=nv12"; + if (isTonemappingSupported && isTonemappingSupportedOnNvenc) + { + outputPixFmt = "format=p010"; + } + } + if (!videoWidth.HasValue || outputWidth != videoWidth.Value || !videoHeight.HasValue @@ -2207,9 +2216,18 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add( string.Format( CultureInfo.InvariantCulture, - "scale_cuda=w={0}:h={1}", + "scale_cuda=w={0}:h={1}{2}", outputWidth, - outputHeight)); + outputHeight, + isCudaFormatConversionSupported ? (":" + outputPixFmt) : string.Empty)); + } + else if (isCudaFormatConversionSupported) + { + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale_cuda={0}", + outputPixFmt)); } } else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1 @@ -2594,9 +2612,7 @@ namespace MediaBrowser.Controller.MediaEncoding // When the input may or may not be hardware VAAPI decodable. if ((isVaapiH264Encoder || isVaapiHevcEncoder) - && !isTonemappingSupported - && !isVppTonemappingSupported - && !isTonemappingSupportedOnVaapi) + && !(isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported))) { filters.Add("format=nv12|vaapi"); filters.Add("hwupload"); @@ -2611,7 +2627,7 @@ namespace MediaBrowser.Controller.MediaEncoding // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first. else if ((IsVaapiSupported(state) && isVaapiDecoder) && (isLibX264Encoder || isLibX265Encoder)) { - var codec = videoStream.Codec.ToLowerInvariant(); + var codec = videoStream.Codec; // Assert 10-bit hardware VAAPI decodable if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) @@ -2712,21 +2728,38 @@ namespace MediaBrowser.Controller.MediaEncoding if (isNvdecDecoder && !isTonemappingSupported) { var codec = videoStream.Codec; + var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilter("scale_cuda", "Output format (default \"same\")"); // Assert 10-bit hardware decodable if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))) { - // Download data from GPU to CPU as p010 format. - filters.Add("hwdownload"); - filters.Add("format=p010"); - - // Cuda lacks of a pixel format converter. - if (isNvencEncoder) + if (isCudaFormatConversionSupported) { - isHwuploadCudaRequired = true; - filters.Add("format=yuv420p"); + if (isLibX264Encoder || isLibX265Encoder || hasSubs) + { + if (isNvencEncoder) + { + isHwuploadCudaRequired = true; + } + + filters.Add("hwdownload"); + filters.Add("format=nv12"); + } + } + else + { + // Download data from GPU to CPU as p010 format. + filters.Add("hwdownload"); + filters.Add("format=p010"); + + // Cuda lacks of a pixel format converter. + if (isNvencEncoder) + { + isHwuploadCudaRequired = true; + filters.Add("format=yuv420p"); + } } } @@ -3285,32 +3318,32 @@ namespace MediaBrowser.Controller.MediaEncoding { case "avc": case "h264": - return encodingOptions.EnableEnhancedNvdecDecoder + return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported() ? GetHwaccelType(state, encodingOptions, "h264", isColorDepth10) : GetHwDecoderName(encodingOptions, "h264_cuvid", "h264", isColorDepth10); case "hevc": case "h265": - return encodingOptions.EnableEnhancedNvdecDecoder + return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported() ? GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10) : GetHwDecoderName(encodingOptions, "hevc_cuvid", "hevc", isColorDepth10); case "mpeg2video": - return encodingOptions.EnableEnhancedNvdecDecoder + return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported() ? GetHwaccelType(state, encodingOptions, "mpeg2video", isColorDepth10) : GetHwDecoderName(encodingOptions, "mpeg2_cuvid", "mpeg2video", isColorDepth10); case "vc1": - return encodingOptions.EnableEnhancedNvdecDecoder + return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported() ? GetHwaccelType(state, encodingOptions, "vc1", isColorDepth10) : GetHwDecoderName(encodingOptions, "vc1_cuvid", "vc1", isColorDepth10); case "mpeg4": - return encodingOptions.EnableEnhancedNvdecDecoder + return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported() ? GetHwaccelType(state, encodingOptions, "mpeg4", isColorDepth10) : GetHwDecoderName(encodingOptions, "mpeg4_cuvid", "mpeg4", isColorDepth10); case "vp8": - return encodingOptions.EnableEnhancedNvdecDecoder + return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported() ? GetHwaccelType(state, encodingOptions, "vp8", isColorDepth10) : GetHwDecoderName(encodingOptions, "vp8_cuvid", "vp8", isColorDepth10); case "vp9": - return encodingOptions.EnableEnhancedNvdecDecoder + return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported() ? GetHwaccelType(state, encodingOptions, "vp9", isColorDepth10) : GetHwDecoderName(encodingOptions, "vp9_cuvid", "vp9", isColorDepth10); } @@ -3500,7 +3533,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)) { - if (IsCudaSupported(state) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) + if (options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) { return "-hwaccel cuda"; } diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 34fe895cc4..0bfa7d3c2b 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -50,6 +50,14 @@ namespace MediaBrowser.Controller.MediaEncoding /// true if XXXX, false otherwise. bool SupportsHwaccel(string hwaccel); + /// + /// Whether given filter is supported. + /// + /// The filter. + /// The option. + /// true if XXXX, false otherwise. + bool SupportsFilter(string filter, string option); + /// /// Extracts the audio image. /// diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 92f16ab95c..9e24176033 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -296,6 +296,38 @@ namespace MediaBrowser.MediaEncoding.Encoder return found; } + public bool CheckFilter(string filter, string option) + { + if (string.IsNullOrEmpty(filter)) + { + return false; + } + + string output = null; + try + { + output = GetProcessOutput(_encoderPath, "-h filter=" + filter); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error detecting the given filter"); + } + + if (output.Contains("Filter " + filter, StringComparison.Ordinal)) + { + if (string.IsNullOrEmpty(option)) + { + return true; + } + + return output.Contains(option, StringComparison.Ordinal); + } + + _logger.LogWarning("Filter: {Name} with option {Option} is not available", filter, option); + + return false; + } + private IEnumerable GetCodecs(Codec codec) { string codecstr = codec == Codec.Encoder ? "encoders" : "decoders"; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index fbd08a97c7..c0b6cf28bb 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -295,6 +295,17 @@ namespace MediaBrowser.MediaEncoding.Encoder return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase); } + public bool SupportsFilter(string filter, string option) + { + if (_ffmpegPath != null) + { + var validator = new EncoderValidator(_logger, _ffmpegPath); + return validator.CheckFilter(filter, option); + } + + return false; + } + public bool CanEncodeToAudioCodec(string codec) { if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase)) From 305206816163515907f9a20f26d1c7a06f9132d1 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Fri, 29 Jan 2021 17:53:42 +0800 Subject: [PATCH 10/27] hybird vpp tonemapping for QSV on Linux --- .../MediaEncoding/EncodingHelper.cs | 113 ++++++++++++++---- .../MediaEncoding/IMediaEncoder.cs | 2 +- 2 files changed, 94 insertions(+), 21 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 1f60f3bcc4..cbba1a6345 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -142,7 +142,20 @@ namespace MediaBrowser.Controller.MediaEncoding && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase); } - // Vpp tonemapping may come to QSV in the future. + // Hybrid VPP tonemapping for QSV with VAAPI + var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + if (isLinux && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + { + // Limited to HEVC for now since the filter doesn't accept master data from VP9. + return IsColorDepth10(state) + && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) + && _mediaEncoder.SupportsHwaccel("vaapi") + && _mediaEncoder.SupportsHwaccel("qsv") + && options.EnableVppTonemapping + && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase); + } + + // Native VPP tonemapping may come to QSV in the future. return false; } @@ -552,10 +565,20 @@ namespace MediaBrowser.Controller.MediaEncoding } // While using SW decoder - else + else if (isSwDecoder) { arg.Append("-init_hw_device qsv=hw -filter_hw_device hw "); } + + // Hybrid VPP tonemapping with VAAPI + else if (isVaapiDecoder && isVppTonemappingSupported) + { + arg.Append("-init_hw_device vaapi=va:") + .Append(encodingOptions.VaapiDevice) + .Append(' ') + .Append("-init_hw_device qsv@va ") + .Append("-hwaccel_output_format vaapi "); + } } } @@ -1952,15 +1975,18 @@ namespace MediaBrowser.Controller.MediaEncoding var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiHevcEncoder = outputVideoCodec.IndexOf("hevc_vaapi", StringComparison.OrdinalIgnoreCase) != -1; + var isQsvH264Encoder = outputVideoCodec.Contains("h264_qsv", StringComparison.OrdinalIgnoreCase); + var isQsvHevcEncoder = outputVideoCodec.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase); var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase); var isTonemappingSupported = IsTonemappingSupported(state, options); var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); + var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder); // Tonemapping and burn-in graphical subtitles requires overlay_vaapi. // But it's still in ffmpeg mailing list. Disable it for now. - if (isTonemappingSupported && isTonemappingSupportedOnVaapi && !isVppTonemappingSupported) + if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported) { return GetOutputSizeParam(state, options, outputVideoCodec); } @@ -1983,7 +2009,8 @@ namespace MediaBrowser.Controller.MediaEncoding height.Value); } - if (!string.IsNullOrEmpty(videoSizeParam)) + if (!string.IsNullOrEmpty(videoSizeParam) + && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported)) { // For QSV, feed it into hardware encoder now if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) @@ -2017,7 +2044,9 @@ namespace MediaBrowser.Controller.MediaEncoding [sub]: SW scaling subtitle to FixedOutputSize [base][sub]: SW overlay */ - retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""; + retStr = !outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""; } // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first @@ -2030,7 +2059,9 @@ namespace MediaBrowser.Controller.MediaEncoding [sub]: SW scaling subtitle to FixedOutputSize [base][sub]: SW overlay */ - retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""; + retStr = !outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\""; } else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) @@ -2041,7 +2072,11 @@ namespace MediaBrowser.Controller.MediaEncoding with fixed frame size. Currently only supports linux. */ - if (isLinux) + if (isTonemappingSupportedOnQsv && isVppTonemappingSupported) + { + retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload,format=nv12[base];[base][sub]overlay\""; + } + else if (isLinux) { retStr = !outputSizeParam.IsEmpty ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"" @@ -2147,16 +2182,27 @@ namespace MediaBrowser.Controller.MediaEncoding var isVaapiDecoder = videoDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); var isVaapiH264Encoder = videoEncoder.Contains("h264_vaapi", StringComparison.OrdinalIgnoreCase); var isVaapiHevcEncoder = videoEncoder.Contains("hevc_vaapi", StringComparison.OrdinalIgnoreCase); + var isQsvH264Encoder = videoEncoder.Contains("h264_qsv", StringComparison.OrdinalIgnoreCase); + var isQsvHevcEncoder = videoEncoder.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase); var isTonemappingSupported = IsTonemappingSupported(state, options); var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); - var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); + var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)&& isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); + var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder); + var isP010PixFmtRequired = (isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported)) + || (isTonemappingSupportedOnQsv && isVppTonemappingSupported); + var outputPixFmt = "format=nv12"; - if (isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported)) + if (isP010PixFmtRequired) { outputPixFmt = "format=p010"; } + if (isTonemappingSupportedOnQsv && isVppTonemappingSupported) + { + qsv_or_vaapi = false; + } + if (!videoWidth.HasValue || outputWidth != videoWidth.Value || !videoHeight.HasValue @@ -2176,7 +2222,7 @@ namespace MediaBrowser.Controller.MediaEncoding } // Assert 10-bit is P010 so as we can avoid the extra scaler to get a bit more fps on high res HDR videos. - else if (!(isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported))) + else if (!isP010PixFmtRequired) { filters.Add( string.Format( @@ -2477,6 +2523,7 @@ namespace MediaBrowser.Controller.MediaEncoding var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder); var isTonemappingSupportedOnAmf = string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && (isD3d11vaDecoder || isSwDecoder); var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); + var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder); var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; @@ -2619,13 +2666,15 @@ namespace MediaBrowser.Controller.MediaEncoding } // When burning in graphical subtitles using overlay_qsv, upload videostream to the same qsv context. - else if (isLinux && hasGraphicalSubs && (isQsvH264Encoder || isQsvHevcEncoder)) + else if (isLinux && hasGraphicalSubs && (isQsvH264Encoder || isQsvHevcEncoder) + && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported)) { filters.Add("hwupload=extra_hw_frames=64"); } // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first. - else if ((IsVaapiSupported(state) && isVaapiDecoder) && (isLibX264Encoder || isLibX265Encoder)) + else if ((IsVaapiSupported(state) && isVaapiDecoder) && (isLibX264Encoder || isLibX265Encoder) + && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported)) { var codec = videoStream.Codec; @@ -2654,7 +2703,8 @@ namespace MediaBrowser.Controller.MediaEncoding // Add hardware deinterlace filter before scaling filter. if (isDeinterlaceH264 || isDeinterlaceHevc) { - if (isVaapiEncoder) + if (isVaapiEncoder + || (isTonemappingSupportedOnQsv && isVppTonemappingSupported)) { filters.Add( string.Format( @@ -2717,9 +2767,10 @@ namespace MediaBrowser.Controller.MediaEncoding request.MaxHeight)); } - // Add Vpp tonemapping filter for VAAPI. + // Add VPP tonemapping filter for VAAPI. // Full hardware based video post processing, faster than OpenCL but lacks fine tuning options. - if (isTonemappingSupportedOnVaapi && isVppTonemappingSupported) + if ((isTonemappingSupportedOnVaapi || isTonemappingSupportedOnQsv) + && isVppTonemappingSupported) { filters.Add("tonemap_vaapi=format=nv12:transfer=bt709:matrix=bt709:primaries=bt709"); } @@ -2777,13 +2828,15 @@ namespace MediaBrowser.Controller.MediaEncoding } // Add parameters to use VAAPI with burn-in text subtitles (GH issue #642) - if (isVaapiH264Encoder || isVaapiHevcEncoder) + if (isVaapiH264Encoder + || isVaapiHevcEncoder + || (isTonemappingSupportedOnQsv && isVppTonemappingSupported)) { if (hasTextSubs) { // Convert hw context from ocl to va. // For tonemapping and text subs burn-in. - if (isTonemappingSupported && isTonemappingSupportedOnVaapi && !isVppTonemappingSupported) + if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported) { filters.Add("scale_vaapi"); } @@ -2794,8 +2847,6 @@ namespace MediaBrowser.Controller.MediaEncoding } } - var output = string.Empty; - if (hasTextSubs) { var subParam = GetTextSubtitleParam(state); @@ -2809,17 +2860,29 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("hwmap"); } + if (isTonemappingSupportedOnQsv && isVppTonemappingSupported) + { + filters.Add("hwmap,format=vaapi"); + } + if (isNvdecDecoder && isNvencEncoder) { isHwuploadCudaRequired = true; } } + // Interop the VAAPI data to QSV for hybrid tonemapping + if (isTonemappingSupportedOnQsv && isVppTonemappingSupported && !hasGraphicalSubs) + { + filters.Add("hwmap=derive_device=qsv,scale_qsv"); + } + if (isHwuploadCudaRequired && !hasGraphicalSubs) { filters.Add("hwupload_cuda"); } + var output = string.Empty; if (filters.Count > 0) { output += string.Format( @@ -3292,6 +3355,14 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } + // Hybrid VPP tonemapping with VAAPI + if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) + && IsVppTonemappingSupported(state, encodingOptions)) + { + // Since tonemap_vaapi only support HEVC for now, no need to check the codec again. + return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10); + } + if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { switch (videoStream.Codec.ToLowerInvariant()) @@ -3520,7 +3591,9 @@ namespace MediaBrowser.Controller.MediaEncoding } } - if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) + || (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) + && IsVppTonemappingSupported(state, options))) { if (IsVaapiSupported(state) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 0bfa7d3c2b..5cbb579902 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -55,7 +55,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// The filter. /// The option. - /// true if XXXX, false otherwise. + /// true if the filter is supported, false otherwise. bool SupportsFilter(string filter, string option); /// From 49b08798e628a4dee9af6e220d709af36b12fd97 Mon Sep 17 00:00:00 2001 From: Oskari Lavinto Date: Sun, 7 Feb 2021 08:22:51 +0000 Subject: [PATCH 11/27] Translated using Weblate (Finnish) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fi/ --- .../Localization/Core/fi.json | 148 +++++++++--------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json index b45bdcbad7..fd6148e789 100644 --- a/Emby.Server.Implementations/Localization/Core/fi.json +++ b/Emby.Server.Implementations/Localization/Core/fi.json @@ -1,121 +1,121 @@ { - "HeaderLiveTV": "Live-TV", - "NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.", + "HeaderLiveTV": "Live TV", + "NewVersionIsAvailable": "Uusi versio Jellyfin-palvelimesta on ladattavissa.", "NameSeasonUnknown": "Tuntematon kausi", "NameSeasonNumber": "Kausi {0}", "NameInstallFailed": "{0} asennus epäonnistui", "MusicVideos": "Musiikkivideot", "Music": "Musiikki", "Movies": "Elokuvat", - "MixedContent": "Sekoitettu sisältö", + "MixedContent": "Sekalainen sisältö", "MessageServerConfigurationUpdated": "Palvelimen asetukset on päivitetty", - "MessageNamedServerConfigurationUpdatedWithValue": "Palvelimen asetusryhmä {0} on päivitetty", - "MessageApplicationUpdatedTo": "Jellyfin palvelin on päivitetty versioon {0}", - "MessageApplicationUpdated": "Jellyfin palvelin on päivitetty", - "Latest": "Uusimmat", - "LabelRunningTimeValue": "Toiston kesto: {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Palvelimen asetusten osio {0} on päivitetty", + "MessageApplicationUpdatedTo": "Jellyfin-palvelin on päivitetty versioon {0}", + "MessageApplicationUpdated": "Jellyfin-palvelin on päivitetty", + "Latest": "Viimeisimmät", + "LabelRunningTimeValue": "Kesto: {0}", "LabelIpAddressValue": "IP-osoite: {0}", "ItemRemovedWithName": "{0} poistettiin kirjastosta", "ItemAddedWithName": "{0} lisättiin kirjastoon", - "Inherit": "Periytyä", + "Inherit": "Peri", "HomeVideos": "Kotivideot", "HeaderRecordingGroups": "Tallennusryhmät", "HeaderNextUp": "Seuraavaksi", "HeaderFavoriteSongs": "Suosikkikappaleet", "HeaderFavoriteShows": "Suosikkisarjat", "HeaderFavoriteEpisodes": "Suosikkijaksot", - "HeaderFavoriteArtists": "Suosikkiartistit", + "HeaderFavoriteArtists": "Suosikkiesittäjät", "HeaderFavoriteAlbums": "Suosikkialbumit", - "HeaderContinueWatching": "Jatka katsomista", - "HeaderAlbumArtists": "Albumin artistit", + "HeaderContinueWatching": "Jatka katselua", + "HeaderAlbumArtists": "Albumin esittäjät", "Genres": "Tyylilajit", "Folders": "Kansiot", "Favorites": "Suosikit", - "FailedLoginAttemptWithUserName": "Kirjautuminen epäonnistui kohteesta {0}", + "FailedLoginAttemptWithUserName": "Epäonnistunut kirjautumisyritys lähteestä \"{0}\"", "DeviceOnlineWithName": "{0} on yhdistetty", - "DeviceOfflineWithName": "{0} yhteys on katkaistu", + "DeviceOfflineWithName": "{0} on katkaissut yhteyden", "Collections": "Kokoelmat", - "ChapterNameValue": "Jakso: {0}", + "ChapterNameValue": "Kappale {0}", "Channels": "Kanavat", - "CameraImageUploadedFrom": "Uusi kamerakuva on ladattu {0}", + "CameraImageUploadedFrom": "Uusi kameran kuva on sirretty lähteestä {0}", "Books": "Kirjat", - "AuthenticationSucceededWithUserName": "Käyttäjän {0} todennus onnistui", - "Artists": "Artistit", + "AuthenticationSucceededWithUserName": "{0} on todennettu", + "Artists": "Esittäjät", "Application": "Sovellus", "AppDeviceValues": "Sovellus: {0}, Laite: {1}", "Albums": "Albumit", "User": "Käyttäjä", "System": "Järjestelmä", "ScheduledTaskFailedWithName": "{0} epäonnistui", - "PluginUpdatedWithName": "{0} päivitetty", - "PluginInstalledWithName": "{0} asennettu", - "Photos": "Kuvat", - "ScheduledTaskStartedWithName": "{0} aloitettu", - "PluginUninstalledWithName": "{0} poistettu", + "PluginUpdatedWithName": "{0} päivitettiin", + "PluginInstalledWithName": "{0} asennettiin", + "Photos": "Valokuvat", + "ScheduledTaskStartedWithName": "\"{0}\" käynnistetty", + "PluginUninstalledWithName": "{0} poistettiin", "Playlists": "Soittolistat", "VersionNumber": "Versio {0}", - "ValueSpecialEpisodeName": "Erikois - {0}", - "ValueHasBeenAddedToLibrary": "{0} lisättiin mediakirjastoon", - "UserStoppedPlayingItemWithValues": "{0} toistaminen valmistui {1} laitteella {2}", - "UserStartedPlayingItemWithValues": "{0} toistaa {1} laitteella {2}", - "UserPolicyUpdatedWithName": "Käyttöoikeudet päivitetty käyttäjälle {0}", - "UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}", - "UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}", - "UserOfflineFromDevice": "{0} yhteys katkaistu kohteesta {1}", - "UserLockedOutWithName": "Käyttäjä {0} lukittu", - "UserDownloadingItemWithValues": "{0} lataa {1}", - "UserDeletedWithName": "Käyttäjä {0} poistettu", - "UserCreatedWithName": "Käyttäjä {0} luotu", - "TvShows": "TV-ohjelmat", - "Sync": "Synkronoi", - "SubtitleDownloadFailureFromForItem": "Tekstitystä ei voitu ladata osoitteesta {0} kohteelle {1}", - "StartupEmbyServerIsLoading": "Jellyfin palvelin latautuu. Yritä hetken kuluttua uudelleen.", + "ValueSpecialEpisodeName": "Erikoisjakso - {0}", + "ValueHasBeenAddedToLibrary": "\"{0}\" on lisätty mediakirjastoon", + "UserStoppedPlayingItemWithValues": "{0} lopetti kohteen \"{1}\" toiston sijainnissa \"{2}\"", + "UserStartedPlayingItemWithValues": "{0} toistaa kohdetta \"{1}\" sijainnissa \"{2}\"", + "UserPolicyUpdatedWithName": "Käyttäjän {0} käyttöoikeudet on päivitetty", + "UserPasswordChangedWithName": "Käyttäjän {0} salasana on vaihdettu", + "UserOnlineFromDevice": "{0} on yhdistänyt sijainnista \"{1}\"", + "UserOfflineFromDevice": "{0} on katkaissut yhteyden sijainnista \"{1}\"", + "UserLockedOutWithName": "Käyttäjä {0} on lukittu", + "UserDownloadingItemWithValues": "{0} lataa kohdetta \"{1}\"", + "UserDeletedWithName": "Käyttäjä {0} on poistettu", + "UserCreatedWithName": "Käyttäjä {0} on luotu", + "TvShows": "Sarjat", + "Sync": "Synkronointi", + "SubtitleDownloadFailureFromForItem": "Tekstityksen lataus lähteestä \"{0}\" kohteelle \"{1}\" epäonnistui", + "StartupEmbyServerIsLoading": "Jellyfin-palvelin latautuu. Yritä hetken kuluttua uudelleen.", "Songs": "Kappaleet", - "Shows": "Ohjelmat", - "ServerNameNeedsToBeRestarted": "{0} on käynnistettävä uudelleen", - "ProviderValue": "Tarjoaja: {0}", - "Plugin": "Liitännäinen", - "NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty", - "NotificationOptionVideoPlayback": "Videota toistetaan", - "NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos", - "NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui", - "NotificationOptionServerRestartRequired": "Palvelin on käynnistettävä uudelleen", - "NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty", - "NotificationOptionPluginUninstalled": "Liitännäinen poistettu", - "NotificationOptionPluginInstalled": "Liitännäinen asennettu", - "NotificationOptionPluginError": "Ongelma liitännäisessä", - "NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty", + "Shows": "Sarjat", + "ServerNameNeedsToBeRestarted": "\"{0}\" on käynnistettävä uudelleen", + "ProviderValue": "Lähde: {0}", + "Plugin": "Laajennus", + "NotificationOptionVideoPlaybackStopped": "Videon toisto lopetettu", + "NotificationOptionVideoPlayback": "Videon toisto aloitettu", + "NotificationOptionUserLockedOut": "Käyttäjä on lukittu", + "NotificationOptionTaskFailed": "Ajoitettu tehtävä epäonnistui", + "NotificationOptionServerRestartRequired": "Tarvitaan palvelimen uudelleenkäynnistys", + "NotificationOptionPluginUpdateInstalled": "Laajennus on päivitetty", + "NotificationOptionPluginUninstalled": "Laajennus on poistettu", + "NotificationOptionPluginInstalled": "Laajennus on asennettu", + "NotificationOptionPluginError": "Laajennuksen virhe", + "NotificationOptionNewLibraryContent": "Sisältöä on lisätty", "NotificationOptionInstallationFailed": "Asennus epäonnistui", - "NotificationOptionCameraImageUploaded": "Kameran kuva ladattu", + "NotificationOptionCameraImageUploaded": "Kameran kuva on tallennettu", "NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu", - "NotificationOptionAudioPlayback": "Toistetaan ääntä", - "NotificationOptionApplicationUpdateInstalled": "Sovelluspäivitys asennettu", - "NotificationOptionApplicationUpdateAvailable": "Ohjelmistopäivitys saatavilla", + "NotificationOptionAudioPlayback": "Äänen toisto aloitettu", + "NotificationOptionApplicationUpdateInstalled": "Sovelluspäivitys asennettiin", + "NotificationOptionApplicationUpdateAvailable": "Sovelluspäivitys on saatavilla", "TasksMaintenanceCategory": "Ylläpito", - "TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä videon metadatatietojen pohjalta.", + "TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä määritettyjen metatietoasetusten mukaisesti.", "TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset", "TaskRefreshChannelsDescription": "Päivittää internet-kanavien tiedot.", "TaskRefreshChannels": "Päivitä kanavat", - "TaskCleanTranscodeDescription": "Poistaa transkoodatut tiedostot jotka ovat yli päivän vanhoja.", - "TaskCleanTranscode": "Puhdista transkoodaushakemisto", - "TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset liitännäisille jotka on asetettu päivittymään automaattisesti.", - "TaskUpdatePlugins": "Päivitä liitännäiset", - "TaskRefreshPeopleDescription": "Päivittää näyttelijöiden ja ohjaajien mediatiedot kirjastossasi.", + "TaskCleanTranscodeDescription": "Poistaa päivää vanhemmat transkoodaustiedostot.", + "TaskCleanTranscode": "Puhdista transkoodauskansio", + "TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset laajennuksille, jotka on määritetty päivittymään automaattisesti.", + "TaskUpdatePlugins": "Päivitä laajennukset", + "TaskRefreshPeopleDescription": "Päivittää mediakirjaston näyttelijöiden ja ohjaajien metatiedot.", "TaskRefreshPeople": "Päivitä henkilöt", - "TaskCleanLogsDescription": "Poistaa lokitiedostot jotka ovat yli {0} päivää vanhoja.", - "TaskCleanLogs": "Puhdista lokihakemisto", - "TaskRefreshLibraryDescription": "Skannaa mediakirjastosi uudet tiedostot ja päivittää metatiedot.", - "TaskRefreshLibrary": "Skannaa mediakirjasto", - "TaskRefreshChapterImagesDescription": "Luo pienoiskuvat videoille joissa on jaksoja.", - "TaskRefreshChapterImages": "Pura jakson kuvat", - "TaskCleanCacheDescription": "Poistaa järjestelmälle tarpeettomat väliaikaistiedostot.", - "TaskCleanCache": "Tyhjennä välimuisti-hakemisto", - "TasksChannelsCategory": "Internet kanavat", + "TaskCleanLogsDescription": "Poistaa {0} päivää vanhemmat lokitiedostot.", + "TaskCleanLogs": "Siivoa lokikansio", + "TaskRefreshLibraryDescription": "Tarkastaa mediakirjastosi sisällön uusien tiedostojen varalta ja päivittää metatiedot.", + "TaskRefreshLibrary": "Päivitä mediakirjasto", + "TaskRefreshChapterImagesDescription": "Luo esikatselukuvat videoille, jotka sisältävät kappalejaon.", + "TaskRefreshChapterImages": "Pura kappalejaon kuvat", + "TaskCleanCacheDescription": "Poistaa tarpeettomiksi jääneet väliaikaistiedostot.", + "TaskCleanCache": "Tyhjennä välimuistikansio", + "TasksChannelsCategory": "Internet-kanavat", "TasksApplicationCategory": "Sovellus", "TasksLibraryCategory": "Kirjasto", "Forced": "Pakotettu", "Default": "Oletus", - "TaskCleanActivityLogDescription": "Poistaa määritettyä vanhemmat tapahtumat aktiviteettilokista.", - "TaskCleanActivityLog": "Tyhjennä aktiviteettiloki", + "TaskCleanActivityLogDescription": "Poistaa määritettyä ikää vanhemmat tapahtumat toimintahistoriasta.", + "TaskCleanActivityLog": "Tyhjennä toimintahistoria", "Undefined": "Määrittelemätön" } From e8fd4531ed9e41be7996ee5e67a5f4c98cc75c39 Mon Sep 17 00:00:00 2001 From: Jakub Fabijan Date: Sun, 7 Feb 2021 19:57:33 +0000 Subject: [PATCH 12/27] Added translation using Weblate (Esperanto) --- Emby.Server.Implementations/Localization/Core/eo.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 Emby.Server.Implementations/Localization/Core/eo.json diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/Emby.Server.Implementations/Localization/Core/eo.json @@ -0,0 +1 @@ +{} From 6c2eb5fc7e872a29b4a0951849681ae0764dbb8e Mon Sep 17 00:00:00 2001 From: Jakub Fabijan Date: Sun, 7 Feb 2021 20:20:17 +0000 Subject: [PATCH 13/27] Translated using Weblate (Esperanto) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eo/ --- .../Localization/Core/eo.json | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json index 0967ef424b..3ff7eddae3 100644 --- a/Emby.Server.Implementations/Localization/Core/eo.json +++ b/Emby.Server.Implementations/Localization/Core/eo.json @@ -1 +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" +} From 22e86671052841eb3ce949074fba465f48c9c236 Mon Sep 17 00:00:00 2001 From: Nyanmisaka Date: Mon, 8 Feb 2021 16:41:38 +0800 Subject: [PATCH 14/27] Apply suggestions from code review Co-authored-by: Claus Vium --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index cbba1a6345..07a9f5ba61 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2234,8 +2234,8 @@ namespace MediaBrowser.Controller.MediaEncoding } } else if ((videoDecoder ?? string.Empty).Contains("cuda", StringComparison.OrdinalIgnoreCase) - && width.HasValue - && height.HasValue) + && width.HasValue + && height.HasValue) { var outputWidth = width.Value; var outputHeight = height.Value; @@ -2667,14 +2667,14 @@ namespace MediaBrowser.Controller.MediaEncoding // When burning in graphical subtitles using overlay_qsv, upload videostream to the same qsv context. else if (isLinux && hasGraphicalSubs && (isQsvH264Encoder || isQsvHevcEncoder) - && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported)) + && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported)) { filters.Add("hwupload=extra_hw_frames=64"); } // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first. else if ((IsVaapiSupported(state) && isVaapiDecoder) && (isLibX264Encoder || isLibX265Encoder) - && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported)) + && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported)) { var codec = videoStream.Codec; From df121c1516e4fa531904529397736e89ca41366c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Feb 2021 12:00:45 +0000 Subject: [PATCH 15/27] Bump Swashbuckle.AspNetCore.ReDoc from 5.6.3 to 6.0.2 Bumps [Swashbuckle.AspNetCore.ReDoc](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) from 5.6.3 to 6.0.2. - [Release notes](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/releases) - [Commits](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/compare/v5.6.3...v6.0.2) Signed-off-by: dependabot[bot] --- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 8437369b2f..3dd4776c12 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -18,7 +18,7 @@ - + From 311b2f50122237d96fc93fc48c3658f4970de5a2 Mon Sep 17 00:00:00 2001 From: cvium Date: Mon, 8 Feb 2021 15:38:06 +0100 Subject: [PATCH 16/27] Exclude BOM when writing meta.json plugin manifest --- Emby.Server.Implementations/Plugins/PluginManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 8e5987026b..2e2b3f332f 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -348,7 +348,7 @@ namespace Emby.Server.Implementations.Plugins try { var data = JsonSerializer.Serialize(manifest, _jsonOptions); - File.WriteAllText(Path.Combine(path, "meta.json"), data, Encoding.UTF8); + File.WriteAllText(Path.Combine(path, "meta.json"), data); return true; } #pragma warning disable CA1031 // Do not catch general exception types From 88f37833df41bdb4865b90bfb16319b5bd6d2695 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 8 Feb 2021 16:33:37 +0100 Subject: [PATCH 17/27] Remove last usage of \d --- Emby.Naming/Common/NamingOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs index 413976d9d7..22a3e8bb49 100644 --- a/Emby.Naming/Common/NamingOptions.cs +++ b/Emby.Naming/Common/NamingOptions.cs @@ -284,7 +284,7 @@ namespace Emby.Naming.Common // Not a Kodi rule as well, but below rule also causes false positives for triple-digit episode names // [bar] Foo - 1 [baz] special case of below expression to prevent false positives with digits in the series name - new EpisodeExpression(@".*?(\[.*?\])+.*?(?[\w\s]+?)[\s_]*-[\s_]*(?\d+).*$") + new EpisodeExpression(@".*?(\[.*?\])+.*?(?[\w\s]+?)[\s_]*-[\s_]*(?[0-9]+).*$") { IsNamed = true }, From 13c1c2815f82be2bd316f73fe377b1ff3587e24f Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 8 Feb 2021 17:10:20 +0100 Subject: [PATCH 18/27] Add regression test for PluginManager.SaveManifest --- .../Plugins/PluginManager.cs | 2 +- .../Plugins/PluginManagerTests.cs | 45 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs index 2e2b3f332f..c26ccfd886 100644 --- a/Emby.Server.Implementations/Plugins/PluginManager.cs +++ b/Emby.Server.Implementations/Plugins/PluginManager.cs @@ -519,7 +519,7 @@ namespace Emby.Server.Implementations.Plugins return _plugins.Remove(plugin); } - private LocalPlugin LoadManifest(string dir) + internal LocalPlugin LoadManifest(string dir) { Version? version; PluginManifest? manifest = null; diff --git a/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs new file mode 100644 index 0000000000..bc6a447410 --- /dev/null +++ b/tests/Jellyfin.Server.Implementations.Tests/Plugins/PluginManagerTests.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; +using Emby.Server.Implementations.Plugins; +using MediaBrowser.Common.Plugins; +using Microsoft.Extensions.Logging.Abstractions; +using Xunit; + +namespace Jellyfin.Server.Implementations.Tests.Plugins +{ + public class PluginManagerTests + { + private static readonly string _testPathRoot = Path.Combine(Path.GetTempPath(), "jellyfin-test-data"); + + [Fact] + public void SaveManifest_RoundTrip_Success() + { + var pluginManager = new PluginManager(new NullLogger(), 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); + } + } +} From 086254821ffbcaf6321f7e4fada419e6ee3174c6 Mon Sep 17 00:00:00 2001 From: David Ullmer Date: Tue, 9 Feb 2021 11:19:19 +0100 Subject: [PATCH 19/27] Add test for generic nfo provider id parsing --- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 14 -------------- .../Jellyfin.XbmcMetadata.Tests.csproj | 1 + .../Parsers/EpisodeNfoProviderTests.cs | 10 +++++++++- .../Parsers/MovieNfoParserTests.cs | 11 +++++++++-- .../Parsers/MusicAlbumNfoProviderTests.cs | 9 ++++++++- .../Parsers/MusicArtistNfoParserTests.cs | 8 +++++++- .../Parsers/SeriesNfoParserTests.cs | 4 ++-- .../Test Data/Justice League.nfo | 1 + .../Test Data/The Bone Orchard.nfo | 1 + 9 files changed, 38 insertions(+), 21 deletions(-) diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index f889327e0e..67700f5fbe 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -721,20 +721,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers break; } - case "musicBrainzArtistID": - { - if (reader.IsEmptyElement) - { - reader.Read(); - break; - } - - var id = reader.ReadElementContentAsString(); - item.SetProviderId(MetadataProvider.MusicBrainzArtist.ToString(), id); - - break; - } - default: string readerName = reader.Name; if (_validProviderIds.TryGetValue(readerName, out string? providerIdValue)) diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj index 2106a78a84..aed3e8ac5d 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj +++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj @@ -31,6 +31,7 @@ + diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs index 67b4b969a3..d0cd8b2878 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/EpisodeNfoProviderTests.cs @@ -7,6 +7,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Movies; using MediaBrowser.XbmcMetadata.Parsers; using Microsoft.Extensions.Logging.Abstractions; using Moq; @@ -23,8 +24,13 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers public EpisodeNfoProviderTests() { var providerManager = new Mock(); + + var imdbExternalId = new ImdbExternalId(); + var externalIdInfo = new ExternalIdInfo(imdbExternalId.ProviderName, imdbExternalId.Key, imdbExternalId.Type, imdbExternalId.UrlFormatString); + providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny())) - .Returns(Enumerable.Empty()); + .Returns(new[] { externalIdInfo }); + var config = new Mock(); config.Setup(x => x.GetConfiguration(It.IsAny())) .Returns(new XbmcMetadataOptions()); @@ -56,6 +62,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(); diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs index 765464eced..2f7ee65d51 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs @@ -7,6 +7,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; @@ -21,8 +22,13 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers public MovieNfoParserTests() { var providerManager = new Mock(); + + var tmdbExternalId = new TmdbMovieExternalId(); + var externalIdInfo = new ExternalIdInfo(tmdbExternalId.ProviderName, tmdbExternalId.Key, tmdbExternalId.Type, tmdbExternalId.UrlFormatString); + providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny())) - .Returns(Enumerable.Empty()); + .Returns(new[] { externalIdInfo }); + var config = new Mock(); config.Setup(x => x.GetConfiguration(It.IsAny())) .Returns(new XbmcMetadataOptions()); @@ -42,7 +48,8 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers Assert.Equal("Justice League", item.OriginalTitle); Assert.Equal("Justice for all.", item.Tagline); - Assert.Equal("tt0974015", item.ProviderIds["imdb"]); + Assert.Equal("tt0974015", item.ProviderIds[MetadataProvider.Imdb.ToString()]); + Assert.Equal("141052", item.ProviderIds[MetadataProvider.Tmdb.ToString()]); Assert.Equal(4, item.Genres.Length); Assert.Contains("Action", item.Genres); diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs index bdffea560c..1fe56caddb 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs @@ -9,6 +9,8 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Music; +using MediaBrowser.Providers.Plugins.MusicBrainz; using MediaBrowser.XbmcMetadata.Parsers; using Microsoft.Extensions.Logging.Abstractions; using Moq; @@ -23,8 +25,13 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers public MusicAlbumNfoProviderTests() { var providerManager = new Mock(); + + var musicBrainzArtist = new MusicBrainzArtistExternalId(); + var externalIdInfo = new ExternalIdInfo(musicBrainzArtist.ProviderName, musicBrainzArtist.Key, musicBrainzArtist.Type, "MusicBrainzServer"); + providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny())) - .Returns(Enumerable.Empty()); + .Returns(new[] { externalIdInfo }); + var config = new Mock(); config.Setup(x => x.GetConfiguration(It.IsAny())) .Returns(new XbmcMetadataOptions()); diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs index 2a4d376c6b..4869cf088a 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs @@ -7,6 +7,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; +using MediaBrowser.Providers.Music; using MediaBrowser.XbmcMetadata.Parsers; using Microsoft.Extensions.Logging.Abstractions; using Moq; @@ -21,8 +22,13 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers public MusicArtistNfoParserTests() { var providerManager = new Mock(); + + var musicBrainzArtist = new MusicBrainzArtistExternalId(); + var externalIdInfo = new ExternalIdInfo(musicBrainzArtist.ProviderName, musicBrainzArtist.Key, musicBrainzArtist.Type, "MusicBrainzServer"); + providerManager.Setup(x => x.GetExternalIdInfos(It.IsAny())) - .Returns(Enumerable.Empty()); + .Returns(new[] { externalIdInfo }); + var config = new Mock(); config.Setup(x => x.GetConfiguration(It.IsAny())) .Returns(new XbmcMetadataOptions()); diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs index 3bbfb66e3e..9e535182e2 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs +++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/SeriesNfoParserTests.cs @@ -43,8 +43,8 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers Assert.Equal("American Gods", item.OriginalTitle); Assert.Equal(string.Empty, item.Tagline); Assert.Equal(0, item.RunTimeTicks); - Assert.Equal("46639", item.ProviderIds["tmdb"]); - Assert.Equal("253573", item.ProviderIds["tvdb"]); + Assert.Equal("46639", item.ProviderIds[MetadataProvider.Tmdb.ToString()]); + Assert.Equal("253573", item.ProviderIds[MetadataProvider.Tvdb.ToString()]); Assert.Equal(3, item.Genres.Length); Assert.Contains("Drama", item.Genres); diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo index 6e6da25d30..18b44d9448 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo +++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/Justice League.nfo @@ -30,6 +30,7 @@ Fueled by his restored faith in humanity and inspired by Superman's selfless act, Bruce Wayne enlists the help of his newfound ally, Diana Prince, to face an even greater enemy. Together, Batman and Wonder Woman work quickly to find and recruit a team of meta-humans to stand against this newly awakened threat. But despite the formation of this unprecedented league of heroes-Batman, Wonder Woman, Aquaman, Cyborg and The Flash-it may already be too late to save the planet from an assault of catastrophic proportions. Justice for all. 120 + 141052 https://assets.fanart.tv/fanart/movies/468551/movieposter/justice-league-collection-5c24ea65591d3.jpg https://assets.fanart.tv/fanart/movies/468551/movieposter/justice-league-collection-5c24ea65591d3.jpg https://assets.fanart.tv/fanart/movies/468551/hdmovielogo/justice-league-collection-5ba855ed4239a.png diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Bone Orchard.nfo b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Bone Orchard.nfo index e77c02a349..14feffcbaf 100644 --- a/tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Bone Orchard.nfo +++ b/tests/Jellyfin.XbmcMetadata.Tests/Test Data/The Bone Orchard.nfo @@ -24,6 +24,7 @@ 1276153 1276153 + tt5017734 Drama Mystery Sci-Fi & Fantasy From 407c35f087d7998ff072b68f837bdefb15a7304b Mon Sep 17 00:00:00 2001 From: crobibero Date: Tue, 9 Feb 2021 19:02:02 -0700 Subject: [PATCH 20/27] Update to dotnet 5.0.3 --- .ci/azure-pipelines-abi.yml | 2 +- .ci/azure-pipelines-api-client.yml | 2 +- .ci/azure-pipelines-main.yml | 2 +- .ci/azure-pipelines-test.yml | 2 +- .ci/azure-pipelines.yml | 2 +- Jellyfin.Api/Jellyfin.Api.csproj | 2 +- Jellyfin.Data/Jellyfin.Data.csproj | 4 ++-- .../Jellyfin.Server.Implementations.csproj | 4 ++-- Jellyfin.Server/Jellyfin.Server.csproj | 4 ++-- deployment/Dockerfile.debian.amd64 | 2 +- deployment/Dockerfile.debian.arm64 | 2 +- deployment/Dockerfile.debian.armhf | 2 +- deployment/Dockerfile.linux.amd64 | 2 +- deployment/Dockerfile.linux.amd64-musl | 2 +- deployment/Dockerfile.linux.arm64 | 2 +- deployment/Dockerfile.linux.armhf | 2 +- deployment/Dockerfile.macos | 2 +- deployment/Dockerfile.portable | 2 +- deployment/Dockerfile.ubuntu.amd64 | 2 +- deployment/Dockerfile.ubuntu.arm64 | 2 +- deployment/Dockerfile.ubuntu.armhf | 2 +- deployment/Dockerfile.windows.amd64 | 2 +- tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj | 2 +- 23 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.ci/azure-pipelines-abi.yml b/.ci/azure-pipelines-abi.yml index 14df7e7c8d..8d0737b66c 100644 --- a/.ci/azure-pipelines-abi.yml +++ b/.ci/azure-pipelines-abi.yml @@ -7,7 +7,7 @@ parameters: default: "ubuntu-latest" - name: DotNetSdkVersion type: string - default: 5.0.100 + default: 5.0.103 jobs: - job: CompatibilityCheck diff --git a/.ci/azure-pipelines-api-client.yml b/.ci/azure-pipelines-api-client.yml index 177f78889c..0e944e6f47 100644 --- a/.ci/azure-pipelines-api-client.yml +++ b/.ci/azure-pipelines-api-client.yml @@ -4,7 +4,7 @@ default: "ubuntu-latest" - name: GeneratorVersion type: string - default: "5.0.0-beta2" + default: "5.0.1" jobs: - job: GenerateApiClients diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml index 95dd3ccac2..4bc72f9eb0 100644 --- a/.ci/azure-pipelines-main.yml +++ b/.ci/azure-pipelines-main.yml @@ -1,7 +1,7 @@ parameters: LinuxImage: 'ubuntu-latest' RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj' - DotNetSdkVersion: 5.0.100 + DotNetSdkVersion: 5.0.103 jobs: - job: Build diff --git a/.ci/azure-pipelines-test.yml b/.ci/azure-pipelines-test.yml index 36152c82a4..95e0d8c58d 100644 --- a/.ci/azure-pipelines-test.yml +++ b/.ci/azure-pipelines-test.yml @@ -10,7 +10,7 @@ parameters: default: "tests/**/*Tests.csproj" - name: DotNetSdkVersion type: string - default: 5.0.100 + default: 5.0.103 jobs: - job: Test diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index ec4c254358..6430503f9a 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -6,7 +6,7 @@ variables: - name: RestoreBuildProjects value: 'Jellyfin.Server/Jellyfin.Server.csproj' - name: DotNetSdkVersion - value: 5.0.100 + value: 5.0.103 pr: autoCancel: true diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 3dd4776c12..c2f4ab522a 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -15,7 +15,7 @@ - + diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj index faea50297d..a8ac45645f 100644 --- a/Jellyfin.Data/Jellyfin.Data.csproj +++ b/Jellyfin.Data/Jellyfin.Data.csproj @@ -41,8 +41,8 @@ - - + + diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj index 05052e5c06..4f24da0ee4 100644 --- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj +++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj @@ -26,11 +26,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 3ebcc3279d..bf4f806693 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -40,8 +40,8 @@ - - + + diff --git a/deployment/Dockerfile.debian.amd64 b/deployment/Dockerfile.debian.amd64 index f5cf232d65..4280726135 100644 --- a/deployment/Dockerfile.debian.amd64 +++ b/deployment/Dockerfile.debian.amd64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.debian.arm64 b/deployment/Dockerfile.debian.arm64 index d9414a6104..b540efc09a 100644 --- a/deployment/Dockerfile.debian.arm64 +++ b/deployment/Dockerfile.debian.arm64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.debian.armhf b/deployment/Dockerfile.debian.armhf index 7f2275aaa3..426ce02fcd 100644 --- a/deployment/Dockerfile.debian.armhf +++ b/deployment/Dockerfile.debian.armhf @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.amd64 b/deployment/Dockerfile.linux.amd64 index 54d75dcbe9..3b91515f36 100644 --- a/deployment/Dockerfile.linux.amd64 +++ b/deployment/Dockerfile.linux.amd64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.amd64-musl b/deployment/Dockerfile.linux.amd64-musl index e4c7242191..2ca9072bae 100644 --- a/deployment/Dockerfile.linux.amd64-musl +++ b/deployment/Dockerfile.linux.amd64-musl @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.arm64 b/deployment/Dockerfile.linux.arm64 index 6338025981..03efd306d7 100644 --- a/deployment/Dockerfile.linux.arm64 +++ b/deployment/Dockerfile.linux.arm64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.linux.armhf b/deployment/Dockerfile.linux.armhf index ec0b015cc8..585572204b 100644 --- a/deployment/Dockerfile.linux.armhf +++ b/deployment/Dockerfile.linux.armhf @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.macos b/deployment/Dockerfile.macos index 25f15be183..b37afdcfbc 100644 --- a/deployment/Dockerfile.macos +++ b/deployment/Dockerfile.macos @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.portable b/deployment/Dockerfile.portable index cd71ce9d49..686b20197c 100644 --- a/deployment/Dockerfile.portable +++ b/deployment/Dockerfile.portable @@ -15,7 +15,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64 index ea539b3609..3513bf8ecb 100644 --- a/deployment/Dockerfile.ubuntu.amd64 +++ b/deployment/Dockerfile.ubuntu.amd64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64 index f2f5368f7a..5acdf0d178 100644 --- a/deployment/Dockerfile.ubuntu.arm64 +++ b/deployment/Dockerfile.ubuntu.arm64 @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf index ba597801b5..42f757d058 100644 --- a/deployment/Dockerfile.ubuntu.armhf +++ b/deployment/Dockerfile.ubuntu.armhf @@ -16,7 +16,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/deployment/Dockerfile.windows.amd64 b/deployment/Dockerfile.windows.amd64 index c731268416..6ed1193fb0 100644 --- a/deployment/Dockerfile.windows.amd64 +++ b/deployment/Dockerfile.windows.amd64 @@ -15,7 +15,7 @@ RUN apt-get update \ # Install dotnet repository # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current -RUN wget https://download.visualstudio.microsoft.com/download/pr/7f736160-9f34-4595-8d72-13630c437aef/b9c4513afb0f8872eb95793c70ac52f6/dotnet-sdk-5.0.102-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ +RUN wget https://download.visualstudio.microsoft.com/download/pr/a2052604-de46-4cd4-8256-9bc222537d32/a798771950904eaf91c0c37c58f516e1/dotnet-sdk-5.0.103-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ && mkdir -p dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj index 3da728c633..eca3df79b3 100644 --- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj +++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj @@ -16,7 +16,7 @@ - + From d5f0b046bb21dea0c120aa27f68fabc5947a8f1d Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 10 Feb 2021 16:12:52 -0700 Subject: [PATCH 21/27] Add image file accept to openapi --- .../Attributes/AcceptsFileAttribute.cs | 28 ++++++++++++ .../Attributes/AcceptsImageFileAttribute.cs | 18 ++++++++ Jellyfin.Api/Controllers/ImageController.cs | 4 ++ .../ApiServiceCollectionExtensions.cs | 1 + Jellyfin.Server/Filters/FileRequestFilter.cs | 43 +++++++++++++++++++ 5 files changed, 94 insertions(+) create mode 100644 Jellyfin.Api/Attributes/AcceptsFileAttribute.cs create mode 100644 Jellyfin.Api/Attributes/AcceptsImageFileAttribute.cs create mode 100644 Jellyfin.Server/Filters/FileRequestFilter.cs diff --git a/Jellyfin.Api/Attributes/AcceptsFileAttribute.cs b/Jellyfin.Api/Attributes/AcceptsFileAttribute.cs new file mode 100644 index 0000000000..49b6689cde --- /dev/null +++ b/Jellyfin.Api/Attributes/AcceptsFileAttribute.cs @@ -0,0 +1,28 @@ +using System; + +namespace Jellyfin.Api.Attributes +{ + /// + /// Internal produces image attribute. + /// + [AttributeUsage(AttributeTargets.Method)] + public class AcceptsFileAttribute : Attribute + { + private readonly string[] _contentTypes; + + /// + /// Initializes a new instance of the class. + /// + /// Content types this endpoint produces. + public AcceptsFileAttribute(params string[] contentTypes) + { + _contentTypes = contentTypes; + } + + /// + /// Gets the configured content types. + /// + /// the configured content types. + public string[] GetContentTypes() => _contentTypes; + } +} diff --git a/Jellyfin.Api/Attributes/AcceptsImageFileAttribute.cs b/Jellyfin.Api/Attributes/AcceptsImageFileAttribute.cs new file mode 100644 index 0000000000..001f27409e --- /dev/null +++ b/Jellyfin.Api/Attributes/AcceptsImageFileAttribute.cs @@ -0,0 +1,18 @@ +namespace Jellyfin.Api.Attributes +{ + /// + /// Produces file attribute of "image/*". + /// + public class AcceptsImageFileAttribute : AcceptsFileAttribute + { + private const string ContentType = "image/*"; + + /// + /// Initializes a new instance of the class. + /// + public AcceptsImageFileAttribute() + : base(ContentType) + { + } + } +} diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index c606d327c5..dc36349704 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -87,6 +87,7 @@ namespace Jellyfin.Api.Controllers /// A . [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 /// A . [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 /// A on success, or a if item not found. [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 /// A on success, or a if item not found. [HttpPost("Items/{itemId}/Images/{imageType}/{imageIndex}")] [Authorize(Policy = Policies.RequiresElevation)] + [AcceptsImageFile] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index da4cc267b3..545937207c 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -316,6 +316,7 @@ namespace Jellyfin.Server.Extensions c.OperationFilter(); c.OperationFilter(); + c.OperationFilter(); c.OperationFilter(); c.DocumentFilter(); }); diff --git a/Jellyfin.Server/Filters/FileRequestFilter.cs b/Jellyfin.Server/Filters/FileRequestFilter.cs new file mode 100644 index 0000000000..69e10994f4 --- /dev/null +++ b/Jellyfin.Server/Filters/FileRequestFilter.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using Jellyfin.Api.Attributes; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Jellyfin.Server.Filters +{ + /// + public class FileRequestFilter : IOperationFilter + { + /// + 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 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; + } + } +} From 223b42aed3395f7d01ea513bf352cdf4fd3e7002 Mon Sep 17 00:00:00 2001 From: crobibero Date: Wed, 10 Feb 2021 17:09:23 -0700 Subject: [PATCH 22/27] Create BaseItemKind enum --- Emby.Server.Implementations/Dto/DtoService.cs | 4 +- Jellyfin.Api/Controllers/ArtistsController.cs | 18 +- .../Controllers/CollectionController.cs | 1 - .../Controllers/DashboardController.cs | 6 - .../Controllers/DynamicHlsController.cs | 1 - Jellyfin.Api/Controllers/FilterController.cs | 37 ++-- Jellyfin.Api/Controllers/GenresController.cs | 9 +- .../Controllers/HlsSegmentController.cs | 2 - .../Controllers/InstantMixController.cs | 1 - .../Controllers/ItemLookupController.cs | 2 - Jellyfin.Api/Controllers/ItemsController.cs | 27 ++- Jellyfin.Api/Controllers/LibraryController.cs | 1 - .../Controllers/MusicGenresController.cs | 9 +- Jellyfin.Api/Controllers/PackageController.cs | 1 - Jellyfin.Api/Controllers/PersonsController.cs | 1 - .../Controllers/PlaylistsController.cs | 1 - Jellyfin.Api/Controllers/PluginsController.cs | 2 - Jellyfin.Api/Controllers/SearchController.cs | 9 +- Jellyfin.Api/Controllers/StudiosController.cs | 9 +- .../Controllers/SuggestionsController.cs | 1 - .../Controllers/SyncPlayController.cs | 1 - Jellyfin.Api/Controllers/SystemController.cs | 1 - .../Controllers/TimeSyncController.cs | 1 - .../Controllers/TrailersController.cs | 4 +- Jellyfin.Api/Controllers/TvShowsController.cs | 1 - .../Controllers/UserLibraryController.cs | 5 +- .../Controllers/UserViewsController.cs | 1 - .../Controllers/VideoHlsController.cs | 1 - Jellyfin.Api/Controllers/YearsController.cs | 15 +- Jellyfin.Api/Helpers/RequestHelpers.cs | 16 ++ Jellyfin.Data/Enums/BaseItemKind.cs | 190 ++++++++++++++++++ MediaBrowser.Controller/Entities/BaseItem.cs | 5 + MediaBrowser.Model/Dto/BaseItemDto.cs | 3 +- 33 files changed, 289 insertions(+), 97 deletions(-) create mode 100644 Jellyfin.Data/Enums/BaseItemKind.cs diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index e3ab0d6eab..54b18a8c80 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -249,7 +249,7 @@ namespace Emby.Server.Implementations.Dto var activeRecording = liveTvManager.GetActiveRecordingInfo(item.Path); if (activeRecording != null) { - dto.Type = "Recording"; + dto.Type = BaseItemKind.Recording; dto.CanDownload = false; dto.RunTimeTicks = null; @@ -904,7 +904,7 @@ namespace Emby.Server.Implementations.Dto } } - dto.Type = item.GetClientTypeName(); + dto.Type = item.GetBaseItemKind(); if ((item.CommunityRating ?? 0) > 0) { dto.CommunityRating = item.CommunityRating; diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs index fed7ed3e5a..4b2e5e7ea6 100644 --- a/Jellyfin.Api/Controllers/ArtistsController.cs +++ b/Jellyfin.Api/Controllers/ArtistsController.cs @@ -3,8 +3,10 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -88,8 +90,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, @@ -127,8 +129,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = excludeItemTypes, - IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), MediaTypes = mediaTypes, StartIndex = startIndex, Limit = limit, @@ -287,8 +289,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, @@ -326,8 +328,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = excludeItemTypes, - IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), MediaTypes = mediaTypes, StartIndex = startIndex, Limit = limit, diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs index 2a7b2b5c62..852d1e9cbd 100644 --- a/Jellyfin.Api/Controllers/CollectionController.cs +++ b/Jellyfin.Api/Controllers/CollectionController.cs @@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; -using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Dto; diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs index ff7895373b..b2baa9cea0 100644 --- a/Jellyfin.Api/Controllers/DashboardController.cs +++ b/Jellyfin.Api/Controllers/DashboardController.cs @@ -7,17 +7,11 @@ using Jellyfin.Api.Attributes; using Jellyfin.Api.Models; using MediaBrowser.Common.Plugins; using MediaBrowser.Controller; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Extensions; -using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Net; using MediaBrowser.Model.Plugins; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; namespace Jellyfin.Api.Controllers { diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 6e85737d26..e375645cf0 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -15,7 +15,6 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs index 9220b988fa..223b2a2b66 100644 --- a/Jellyfin.Api/Controllers/FilterController.cs +++ b/Jellyfin.Api/Controllers/FilterController.cs @@ -1,13 +1,12 @@ using System; using System.Linq; using Jellyfin.Api.Constants; +using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; @@ -51,7 +50,7 @@ namespace Jellyfin.Api.Controllers public ActionResult 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 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(), @@ -192,10 +191,10 @@ namespace Jellyfin.Api.Controllers } if (includeItemTypes.Length == 1 - && (string.Equals(includeItemTypes[0], nameof(MusicAlbum), StringComparison.OrdinalIgnoreCase) - || string.Equals(includeItemTypes[0], nameof(MusicVideo), StringComparison.OrdinalIgnoreCase) - || string.Equals(includeItemTypes[0], nameof(MusicArtist), StringComparison.OrdinalIgnoreCase) - || string.Equals(includeItemTypes[0], nameof(Audio), StringComparison.OrdinalIgnoreCase))) + && (includeItemTypes[0] == BaseItemKind.MusicAlbum + || includeItemTypes[0] == BaseItemKind.MusicVideo + || includeItemTypes[0] == BaseItemKind.MusicArtist + || includeItemTypes[0] == BaseItemKind.Audio)) { filters.Genres = _libraryManager.GetMusicGenres(genreQuery).Items.Select(i => new NameGuidPair { diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs index b6755ed5e5..7bcf4674c6 100644 --- a/Jellyfin.Api/Controllers/GenresController.cs +++ b/Jellyfin.Api/Controllers/GenresController.cs @@ -6,6 +6,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -74,8 +75,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isFavorite, [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, @@ -96,8 +97,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = excludeItemTypes, - IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), StartIndex = startIndex, Limit = limit, IsFavorite = isFavorite, diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs index f519877326..25abe73ed0 100644 --- a/Jellyfin.Api/Controllers/HlsSegmentController.cs +++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs @@ -2,13 +2,11 @@ using System; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.IO; diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs index 2446257528..f061755c30 100644 --- a/Jellyfin.Api/Controllers/InstantMixController.cs +++ b/Jellyfin.Api/Controllers/InstantMixController.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.ModelBinders; diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs index 6c38f77ce1..dfc68ffcec 100644 --- a/Jellyfin.Api/Controllers/ItemLookupController.cs +++ b/Jellyfin.Api/Controllers/ItemLookupController.cs @@ -2,8 +2,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; -using System.Linq; -using System.Net.Mime; using System.Text.Json; using System.Threading; using System.Threading.Tasks; diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs index 7d77474958..2c9760f6d0 100644 --- a/Jellyfin.Api/Controllers/ItemsController.cs +++ b/Jellyfin.Api/Controllers/ItemsController.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel.DataAnnotations; -using System.Globalization; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; @@ -178,8 +177,8 @@ namespace Jellyfin.Api.Controllers [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, @@ -233,8 +232,8 @@ namespace Jellyfin.Api.Controllers .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); if (includeItemTypes.Length == 1 - && (includeItemTypes[0].Equals("Playlist", StringComparison.OrdinalIgnoreCase) - || includeItemTypes[0].Equals("BoxSet", StringComparison.OrdinalIgnoreCase))) + && (includeItemTypes[0] == BaseItemKind.Playlist + || includeItemTypes[0] == BaseItemKind.BoxSet)) { parentId = null; } @@ -251,7 +250,7 @@ namespace Jellyfin.Api.Controllers && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)) { recursive = true; - includeItemTypes = new[] { "Playlist" }; + includeItemTypes = new[] { BaseItemKind.Playlist }; } var enabledChannels = user!.GetPreferenceValues(PreferenceKind.EnabledChannels); @@ -286,8 +285,8 @@ namespace Jellyfin.Api.Controllers { IsPlayed = isPlayed, MediaTypes = mediaTypes, - IncludeItemTypes = includeItemTypes, - ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), Recursive = recursive ?? false, OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder), IsFavorite = isFavorite, @@ -611,8 +610,8 @@ namespace Jellyfin.Api.Controllers [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, @@ -773,8 +772,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool? enableImages = true) { @@ -810,8 +809,8 @@ namespace Jellyfin.Api.Controllers CollapseBoxSetItems = false, EnableTotalRecordCount = enableTotalRecordCount, AncestorIds = ancestorIds, - IncludeItemTypes = includeItemTypes, - ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), SearchTerm = searchTerm }); diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs index 28d359ac3a..db4aa96681 100644 --- a/Jellyfin.Api/Controllers/LibraryController.cs +++ b/Jellyfin.Api/Controllers/LibraryController.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; -using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.LibraryDtos; using Jellyfin.Data.Entities; diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs index 2608a9cd09..7f7058b5ef 100644 --- a/Jellyfin.Api/Controllers/MusicGenresController.cs +++ b/Jellyfin.Api/Controllers/MusicGenresController.cs @@ -6,6 +6,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -74,8 +75,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isFavorite, [FromQuery] int? imageTypeLimit, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, @@ -96,8 +97,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = excludeItemTypes, - IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), StartIndex = startIndex, Limit = limit, IsFavorite = isFavorite, diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs index c589f54ac4..5dd49ef2fd 100644 --- a/Jellyfin.Api/Controllers/PackageController.cs +++ b/Jellyfin.Api/Controllers/PackageController.cs @@ -4,7 +4,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; using Jellyfin.Api.Constants; -using MediaBrowser.Common.Json; using MediaBrowser.Common.Updates; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Updates; diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs index 17e631197c..70a94e27cb 100644 --- a/Jellyfin.Api/Controllers/PersonsController.cs +++ b/Jellyfin.Api/Controllers/PersonsController.cs @@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; -using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Dto; diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index a55e4ad2ff..1667d6edee 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; -using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.PlaylistDtos; using MediaBrowser.Controller.Dto; diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index b73611c979..b2e8bee91e 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Globalization; using System.IO; using System.Linq; -using System.Net.Mime; using System.Text.Json; using System.Threading.Tasks; using Jellyfin.Api.Attributes; diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs index 08255ff8f6..6c22050a79 100644 --- a/Jellyfin.Api/Controllers/SearchController.cs +++ b/Jellyfin.Api/Controllers/SearchController.cs @@ -6,6 +6,7 @@ using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -83,8 +84,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] int? limit, [FromQuery] Guid? userId, [FromQuery, Required] string searchTerm, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, [FromQuery] Guid? parentId, [FromQuery] bool? isMovie, @@ -109,8 +110,8 @@ namespace Jellyfin.Api.Controllers IncludeStudios = includeStudios, StartIndex = startIndex, UserId = userId ?? Guid.Empty, - IncludeItemTypes = includeItemTypes, - ExcludeItemTypes = excludeItemTypes, + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), MediaTypes = mediaTypes, ParentId = parentId, diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs index bb54c59f69..da8f8b1995 100644 --- a/Jellyfin.Api/Controllers/StudiosController.cs +++ b/Jellyfin.Api/Controllers/StudiosController.cs @@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; @@ -73,8 +74,8 @@ namespace Jellyfin.Api.Controllers [FromQuery] string? searchTerm, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isFavorite, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, @@ -96,8 +97,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = excludeItemTypes, - IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), StartIndex = startIndex, Limit = limit, IsFavorite = isFavorite, diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs index 9f1dec712a..a55f13e66e 100644 --- a/Jellyfin.Api/Controllers/SuggestionsController.cs +++ b/Jellyfin.Api/Controllers/SuggestionsController.cs @@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; -using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; diff --git a/Jellyfin.Api/Controllers/SyncPlayController.cs b/Jellyfin.Api/Controllers/SyncPlayController.cs index 82cbe58df2..f878f2329c 100644 --- a/Jellyfin.Api/Controllers/SyncPlayController.cs +++ b/Jellyfin.Api/Controllers/SyncPlayController.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Threading; diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs index e67a27ae30..bbbe5fb8da 100644 --- a/Jellyfin.Api/Controllers/SystemController.cs +++ b/Jellyfin.Api/Controllers/SystemController.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Net; using System.Net.Mime; -using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; diff --git a/Jellyfin.Api/Controllers/TimeSyncController.cs b/Jellyfin.Api/Controllers/TimeSyncController.cs index c730ac12b3..7df51c7afb 100644 --- a/Jellyfin.Api/Controllers/TimeSyncController.cs +++ b/Jellyfin.Api/Controllers/TimeSyncController.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using MediaBrowser.Model.SyncPlay; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs index 242b8f068e..dd3836551b 100644 --- a/Jellyfin.Api/Controllers/TrailersController.cs +++ b/Jellyfin.Api/Controllers/TrailersController.cs @@ -148,7 +148,7 @@ namespace Jellyfin.Api.Controllers [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery] bool? isFavorite, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, @@ -194,7 +194,7 @@ namespace Jellyfin.Api.Controllers [FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool? enableImages = true) { - var includeItemTypes = new[] { "Trailer" }; + var includeItemTypes = new[] { BaseItemKind.Trailer }; return _itemsController .GetItems( diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs index 223f588593..e1c67f8307 100644 --- a/Jellyfin.Api/Controllers/TvShowsController.cs +++ b/Jellyfin.Api/Controllers/TvShowsController.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Globalization; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index 0e65591cce..1d70406acc 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -8,6 +8,7 @@ using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; +using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; @@ -269,7 +270,7 @@ namespace Jellyfin.Api.Controllers [FromRoute, Required] Guid userId, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery] bool? isPlayed, [FromQuery] bool? enableImages, [FromQuery] int? imageTypeLimit, @@ -296,7 +297,7 @@ namespace Jellyfin.Api.Controllers new LatestItemsQuery { GroupItems = groupItems, - IncludeItemTypes = includeItemTypes, + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), IsPlayed = isPlayed, Limit = limit, ParentId = parentId ?? Guid.Empty, diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs index e1483ce9d5..7bc5ecdf1d 100644 --- a/Jellyfin.Api/Controllers/UserViewsController.cs +++ b/Jellyfin.Api/Controllers/UserViewsController.cs @@ -4,7 +4,6 @@ using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; using Jellyfin.Api.Extensions; -using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.UserViewDtos; using MediaBrowser.Controller.Dto; diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs index 7e743ee0c7..ba51aa43e2 100644 --- a/Jellyfin.Api/Controllers/VideoHlsController.cs +++ b/Jellyfin.Api/Controllers/VideoHlsController.cs @@ -12,7 +12,6 @@ using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs index 7c27752f7f..d6dc6650cf 100644 --- a/Jellyfin.Api/Controllers/YearsController.cs +++ b/Jellyfin.Api/Controllers/YearsController.cs @@ -74,8 +74,8 @@ namespace Jellyfin.Api.Controllers [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, - [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy, [FromQuery] bool? enableUserData, @@ -101,8 +101,8 @@ namespace Jellyfin.Api.Controllers var query = new InternalItemsQuery(user) { - ExcludeItemTypes = excludeItemTypes, - IncludeItemTypes = includeItemTypes, + ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes), + IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes), MediaTypes = mediaTypes, DtoOptions = dtoOptions }; @@ -193,16 +193,17 @@ namespace Jellyfin.Api.Controllers return _dtoService.GetBaseItemDto(item, dtoOptions); } - private bool FilterItem(BaseItem f, IReadOnlyCollection excludeItemTypes, IReadOnlyCollection includeItemTypes, IReadOnlyCollection mediaTypes) + private bool FilterItem(BaseItem f, IReadOnlyCollection excludeItemTypes, IReadOnlyCollection includeItemTypes, IReadOnlyCollection mediaTypes) { + var baseItemKind = f.GetBaseItemKind(); // Exclude item types - if (excludeItemTypes.Count > 0 && excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase)) + if (excludeItemTypes.Count > 0 && excludeItemTypes.Contains(baseItemKind)) { return false; } // Include item types - if (includeItemTypes.Count > 0 && !includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase)) + if (includeItemTypes.Count > 0 && !includeItemTypes.Contains(baseItemKind)) { return false; } diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs index db0ccc6577..94856e03ed 100644 --- a/Jellyfin.Api/Helpers/RequestHelpers.cs +++ b/Jellyfin.Api/Helpers/RequestHelpers.cs @@ -129,5 +129,21 @@ namespace Jellyfin.Api.Helpers TotalRecordCount = result.TotalRecordCount }; } + + internal static string[] GetItemTypeStrings(IReadOnlyList itemKinds) + { + if (itemKinds.Count == 0) + { + return Array.Empty(); + } + + var itemTypes = new string[itemKinds.Count]; + for (var i = 0; i < itemKinds.Count; i++) + { + itemTypes[i] = itemKinds[i].ToString(); + } + + return itemTypes; + } } } diff --git a/Jellyfin.Data/Enums/BaseItemKind.cs b/Jellyfin.Data/Enums/BaseItemKind.cs new file mode 100644 index 0000000000..aac30279ed --- /dev/null +++ b/Jellyfin.Data/Enums/BaseItemKind.cs @@ -0,0 +1,190 @@ +namespace Jellyfin.Data.Enums +{ + /// + /// The base item kind. + /// + /// + /// This enum is generated from all classes that inherit from BaseItem. + /// + public enum BaseItemKind + { + /// + /// Item is aggregate folder. + /// + AggregateFolder, + + /// + /// Item is audio. + /// + Audio, + + /// + /// Item is audio book. + /// + AudioBook, + + /// + /// Item is base plugin folder. + /// + BasePluginFolder, + + /// + /// Item is book. + /// + Book, + + /// + /// Item is box set. + /// + BoxSet, + + /// + /// Item is channel. + /// + Channel, + + /// + /// Item is channel folder item. + /// + ChannelFolderItem, + + /// + /// Item is collection folder. + /// + CollectionFolder, + + /// + /// Item is episode. + /// + Episode, + + /// + /// Item is folder. + /// + Folder, + + /// + /// Item is genre. + /// + Genre, + + /// + /// Item is manual playlists folder. + /// + ManualPlaylistsFolder, + + /// + /// Item is movie. + /// + Movie, + + /// + /// Item is music album. + /// + MusicAlbum, + + /// + /// Item is music artist. + /// + MusicArtist, + + /// + /// Item is music genre. + /// + MusicGenre, + + /// + /// Item is music video. + /// + MusicVideo, + + /// + /// Item is person. + /// + Person, + + /// + /// Item is photo. + /// + Photo, + + /// + /// Item is photo album. + /// + PhotoAlbum, + + /// + /// Item is playlist. + /// + Playlist, + + /// + /// Item is program + /// + Program, + + /// + /// Item is recording. + /// + /// + /// Manually added. + /// + Recording, + + /// + /// Item is season. + /// + Season, + + /// + /// Item is series. + /// + Series, + + /// + /// Item is studio. + /// + Studio, + + /// + /// Item is trailer. + /// + Trailer, + + /// + /// Item is live tv channel. + /// + /// + /// Type is overridden. + /// + TvChannel, + + /// + /// Item is live tv program. + /// + /// + /// Type is overridden. + /// + TvProgram, + + /// + /// Item is user root folder. + /// + UserRootFolder, + + /// + /// Item is user view. + /// + UserView, + + /// + /// Item is video. + /// + Video, + + /// + /// Item is year. + /// + Year + } +} \ No newline at end of file diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index cbb02aabd7..7598b29e6e 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1998,6 +1998,11 @@ namespace MediaBrowser.Controller.Entities return GetType().Name; } + public BaseItemKind GetBaseItemKind() + { + return Enum.Parse(GetClientTypeName()); + } + /// /// Gets the linked child. /// diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 3f7aac9cd8..2f9f9d3cdb 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Library; @@ -276,7 +277,7 @@ namespace MediaBrowser.Model.Dto /// Gets or sets the type. /// /// The type. - public string Type { get; set; } + public BaseItemKind Type { get; set; } /// /// Gets or sets the people. From d490c1c2bcb2852c9159e8578bc7a60e086e4202 Mon Sep 17 00:00:00 2001 From: Manjot Singh Date: Thu, 11 Feb 2021 06:16:43 +0000 Subject: [PATCH 23/27] Translated using Weblate (Hindi) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hi/ --- Emby.Server.Implementations/Localization/Core/hi.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/hi.json b/Emby.Server.Implementations/Localization/Core/hi.json index b9e5f301d9..ef3697b15f 100644 --- a/Emby.Server.Implementations/Localization/Core/hi.json +++ b/Emby.Server.Implementations/Localization/Core/hi.json @@ -1,5 +1,5 @@ { - "Albums": "संग्रह", + "Albums": "एल्बम", "HeaderRecordingGroups": "रिकॉर्डिंग समूह", "HeaderNextUp": "इसके बाद", "HeaderLiveTV": "लाइव टीवी", @@ -26,7 +26,7 @@ "AuthenticationSucceededWithUserName": "सफलता से प्रमाणीकृत", "Artists": "कलाकारों", "Application": "एप्लिकेशन", - "AppDeviceValues": "एप: {0}, मशीन: {1}", + "AppDeviceValues": "एप: {0}, उपकरण: {1}", "NotificationOptionPluginUninstalled": "प्लगइन अनइंस्टाल हो गया", "NotificationOptionPluginInstalled": "प्लगइन इनस्टॉल हो गया", "NotificationOptionPluginError": "प्लगइन फ़ैल हो गया", From 97935d2cd2b5b75e6ca515c99f5821045a06a019 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Thu, 11 Feb 2021 17:46:27 +0100 Subject: [PATCH 24/27] Add tests for RequestHelpers.GetItemTypeStrings --- Jellyfin.Api/Jellyfin.Api.csproj | 6 ++++ .../Helpers/RequestHelpersTests.cs | 34 +++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index c2f4ab522a..ef105fdced 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -38,4 +38,10 @@ ../jellyfin.ruleset + + + <_Parameter1>Jellyfin.Api.Tests + + + diff --git a/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs b/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs index 606041c7f4..97e441b1dc 100644 --- a/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs +++ b/tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs @@ -6,11 +6,11 @@ using Xunit; namespace Jellyfin.Api.Tests.Helpers { - public class RequestHelpersTests + public static class RequestHelpersTests { [Theory] [MemberData(nameof(GetOrderBy_Success_TestData))] - public void GetOrderBy_Success(IReadOnlyList sortBy, IReadOnlyList requestedSortOrder, (string, SortOrder)[] expected) + public static void GetOrderBy_Success(IReadOnlyList sortBy, IReadOnlyList 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())); + } + + [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); + } } } From 9fcdbd4c4b79560cb22f6f27182b4c36606dae79 Mon Sep 17 00:00:00 2001 From: dkanada Date: Fri, 12 Feb 2021 21:58:37 +0900 Subject: [PATCH 25/27] remove deprecated settings from server config --- .../Library/Resolvers/Audio/MusicArtistResolver.cs | 5 ----- Jellyfin.Api/Controllers/PluginsController.cs | 4 +--- MediaBrowser.Model/Configuration/ServerConfiguration.cs | 7 ------- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs index e9e688fa67..60f82806fb 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs @@ -79,11 +79,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio return new MusicArtist(); } - if (_config.Configuration.EnableSimpleArtistDetection) - { - return null; - } - // Avoid mis-identifying top folders if (args.Parent.IsRoot) { diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs index b2e8bee91e..a5aa9bfcae 100644 --- a/Jellyfin.Api/Controllers/PluginsController.cs +++ b/Jellyfin.Api/Controllers/PluginsController.cs @@ -298,9 +298,7 @@ namespace Jellyfin.Api.Controllers } var imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath ?? string.Empty); - if (((ServerConfiguration)_config.CommonConfiguration).DisablePluginImages - || plugin.Manifest.ImagePath == null - || !System.IO.File.Exists(imagePath)) + if (plugin.Manifest.ImagePath == null || !System.IO.File.Exists(imagePath)) { return NotFound(); } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 0f0ad0f9aa..ba55a2ace6 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -418,8 +418,6 @@ namespace MediaBrowser.Model.Configuration public PathSubstitution[] PathSubstitutions { get; set; } = Array.Empty(); - public bool EnableSimpleArtistDetection { get; set; } = false; - public string[] UninstalledPlugins { get; set; } = Array.Empty(); /// @@ -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. /// public bool RemoveOldPlugins { get; set; } - - /// - /// Gets or sets a value indicating whether plugin image should be disabled. - /// - public bool DisablePluginImages { get; set; } } } From 0a8587295e85dca553dcb8167b98cc89d4443fa1 Mon Sep 17 00:00:00 2001 From: Dar Donkov Date: Fri, 12 Feb 2021 17:44:59 +0000 Subject: [PATCH 26/27] Translated using Weblate (Bulgarian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/bg/ --- .../Localization/Core/bg-BG.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json index 7ff30df710..bbc8570894 100644 --- a/Emby.Server.Implementations/Localization/Core/bg-BG.json +++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json @@ -55,26 +55,26 @@ "NotificationOptionPluginInstalled": "Приставката е инсталирана", "NotificationOptionPluginUninstalled": "Приставката е деинсталирана", "NotificationOptionPluginUpdateInstalled": "Обновлението на приставката е инсталирано", - "NotificationOptionServerRestartRequired": "Нужно е повторно пускане на сървъра", + "NotificationOptionServerRestartRequired": "Сървърът трябва да се рестартира", "NotificationOptionTaskFailed": "Грешка в планирана задача", - "NotificationOptionUserLockedOut": "Потребителя е заключен", + "NotificationOptionUserLockedOut": "Потребителят е заключен", "NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна", "NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно", "Photos": "Снимки", "Playlists": "Списъци", "Plugin": "Приставка", - "PluginInstalledWithName": "{0} е инсталирано", - "PluginUninstalledWithName": "{0} е деинсталирано", - "PluginUpdatedWithName": "{0} е обновено", + "PluginInstalledWithName": "{0} е инсталиранa", + "PluginUninstalledWithName": "{0} е деинсталиранa", + "PluginUpdatedWithName": "{0} е обновенa", "ProviderValue": "Доставчик: {0}", "ScheduledTaskFailedWithName": "{0} се провали", "ScheduledTaskStartedWithName": "{0} започна", - "ServerNameNeedsToBeRestarted": "{0} е нужно да се рестартира", + "ServerNameNeedsToBeRestarted": "{0} трябва да се рестартира", "Shows": "Сериали", "Songs": "Песни", "StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.", "SubtitleDownloadFailureForItem": "Неуспешно изтегляне на субтитри за {0}", - "SubtitleDownloadFailureFromForItem": "Поднадписите за {1} от {0} не можаха да се изтеглят", + "SubtitleDownloadFailureFromForItem": "Субтитрите за {1} от {0} не можаха да бъдат изтеглени", "Sync": "Синхронизиране", "System": "Система", "TvShows": "Телевизионни сериали", From cd612edbf063f1378890e693193400e7a0c02a17 Mon Sep 17 00:00:00 2001 From: Dar Donkov Date: Fri, 12 Feb 2021 18:01:22 +0000 Subject: [PATCH 27/27] Translated using Weblate (Bulgarian) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/bg/ --- Emby.Server.Implementations/Localization/Core/bg-BG.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/bg-BG.json b/Emby.Server.Implementations/Localization/Core/bg-BG.json index bbc8570894..9db3b50d91 100644 --- a/Emby.Server.Implementations/Localization/Core/bg-BG.json +++ b/Emby.Server.Implementations/Localization/Core/bg-BG.json @@ -92,12 +92,12 @@ "ValueHasBeenAddedToLibrary": "{0} беше добавен във Вашата библиотека", "ValueSpecialEpisodeName": "Специални - {0}", "VersionNumber": "Версия {0}", - "TaskDownloadMissingSubtitlesDescription": "Търси Интернет за липсващи поднадписи, на база конфигурацията за мета-данни.", - "TaskDownloadMissingSubtitles": "Изтегляне на липсващи поднадписи", + "TaskDownloadMissingSubtitlesDescription": "Търси Интернет за липсващи субтитри, на база конфигурацията за мета-данни.", + "TaskDownloadMissingSubtitles": "Изтегляне на липсващи субтитри", "TaskRefreshChannelsDescription": "Обновява информацията за интернет канала.", "TaskRefreshChannels": "Обновяване на Канали", - "TaskCleanTranscodeDescription": "Изтрива прекодирани файлове по-стари от един ден.", - "TaskCleanTranscode": "Изчиства директорията за прекодиране", + "TaskCleanTranscodeDescription": "Изтрива транскодирани файлове по-стари от един ден.", + "TaskCleanTranscode": "Изчиства директорията за транскодиране", "TaskUpdatePluginsDescription": "Изтегля и инсталира актуализации за добавките, които са настроени за автоматична актуализация.", "TaskUpdatePlugins": "Актуализира добавките", "TaskRefreshPeopleDescription": "Актуализира мета-данните за артистите и режисьорите за Вашата медийна библиотека.",