Procházet zdrojové kódy

Merge remote-tracking branch 'upstream/master' into mad-stylez

crobibero před 4 roky
rodič
revize
becd3b1542

+ 1 - 1
Dockerfile

@@ -14,7 +14,7 @@ COPY . .
 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 # because of changes in docker and systemd we need to not build in parallel at the moment
 # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
 
 FROM debian:buster-slim
 

+ 1 - 1
Dockerfile.arm

@@ -21,7 +21,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 # Discard objs - may cause failures if exists
 RUN find . -type d -name obj | xargs -r rm -r
 # Build
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
 
 
 FROM multiarch/qemu-user-static:x86_64-arm as qemu

+ 1 - 1
Dockerfile.arm64

@@ -21,7 +21,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 # Discard objs - may cause failures if exists
 RUN find . -type d -name obj | xargs -r rm -r
 # Build
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
 
 FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
 FROM arm64v8/debian:buster-slim

+ 5 - 1
Jellyfin.Api/BaseJellyfinApiController.cs

@@ -1,4 +1,5 @@
 using System.Net.Mime;
+using MediaBrowser.Common.Json;
 using Microsoft.AspNetCore.Mvc;
 
 namespace Jellyfin.Api
@@ -8,7 +9,10 @@ namespace Jellyfin.Api
     /// </summary>
     [ApiController]
     [Route("[controller]")]
-    [Produces(MediaTypeNames.Application.Json)]
+    [Produces(
+        MediaTypeNames.Application.Json,
+        JsonDefaults.CamelCaseMediaType,
+        JsonDefaults.PascalCaseMediaType)]
     public class BaseJellyfinApiController : ControllerBase
     {
     }

+ 5 - 6
Jellyfin.Api/Controllers/DlnaServerController.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
+using System.Net.Mime;
 using System.Threading.Tasks;
 using Emby.Dlna;
 using Emby.Dlna.Main;
@@ -17,8 +18,6 @@ namespace Jellyfin.Api.Controllers
     [Route("Dlna")]
     public class DlnaServerController : BaseJellyfinApiController
     {
-        private const string XMLContentType = "text/xml; charset=UTF-8";
-
         private readonly IDlnaManager _dlnaManager;
         private readonly IContentDirectory _contentDirectory;
         private readonly IConnectionManager _connectionManager;
@@ -44,7 +43,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the description xml.</returns>
         [HttpGet("{serverId}/description")]
         [HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")]
-        [Produces(XMLContentType)]
+        [Produces(MediaTypeNames.Text.Xml)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult GetDescriptionXml([FromRoute] string serverId)
         {
@@ -63,7 +62,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("{serverId}/ContentDirectory")]
         [HttpGet("{serverId}/ContentDirectory/ContentDirectory", Name = "GetContentDirectory_2")]
         [HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_3")]
-        [Produces(XMLContentType)]
+        [Produces(MediaTypeNames.Text.Xml)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
         public ActionResult GetContentDirectory([FromRoute] string serverId)
@@ -79,7 +78,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("{serverId}/MediaReceiverRegistrar")]
         [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")]
         [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_3")]
-        [Produces(XMLContentType)]
+        [Produces(MediaTypeNames.Text.Xml)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
         public ActionResult GetMediaReceiverRegistrar([FromRoute] string serverId)
@@ -95,7 +94,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("{serverId}/ConnectionManager")]
         [HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")]
         [HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_3")]
-        [Produces(XMLContentType)]
+        [Produces(MediaTypeNames.Text.Xml)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
         public ActionResult GetConnectionManager([FromRoute] string serverId)

+ 0 - 56
Jellyfin.Api/MvcRoutePrefix.cs

@@ -1,56 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.ApplicationModels;
-
-namespace Jellyfin.Api
-{
-    /// <summary>
-    /// Route prefixing for ASP.NET MVC.
-    /// </summary>
-    public static class MvcRoutePrefix
-    {
-        /// <summary>
-        /// Adds route prefixes to the MVC conventions.
-        /// </summary>
-        /// <param name="opts">The MVC options.</param>
-        /// <param name="prefixes">The list of prefixes.</param>
-        public static void UseGeneralRoutePrefix(this MvcOptions opts, params string[] prefixes)
-        {
-            opts.Conventions.Insert(0, new RoutePrefixConvention(prefixes));
-        }
-
-        private class RoutePrefixConvention : IApplicationModelConvention
-        {
-            private readonly AttributeRouteModel[] _routePrefixes;
-
-            public RoutePrefixConvention(IEnumerable<string> prefixes)
-            {
-                _routePrefixes = prefixes.Select(p => new AttributeRouteModel(new RouteAttribute(p))).ToArray();
-            }
-
-            public void Apply(ApplicationModel application)
-            {
-                foreach (var controller in application.Controllers)
-                {
-                    if (controller.Selectors == null)
-                    {
-                        continue;
-                    }
-
-                    var newSelectors = new List<SelectorModel>();
-                    foreach (var selector in controller.Selectors)
-                    {
-                        newSelectors.AddRange(_routePrefixes.Select(routePrefix => new SelectorModel(selector)
-                        {
-                            AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(routePrefix, selector.AttributeRouteModel)
-                        }));
-                    }
-
-                    controller.Selectors.Clear();
-                    newSelectors.ForEach(selector => controller.Selectors.Add(selector));
-                }
-            }
-        }
-    }
-}

+ 3 - 2
Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs

@@ -3,7 +3,6 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Reflection;
-using Jellyfin.Api;
 using Jellyfin.Api.Auth;
 using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
 using Jellyfin.Api.Auth.DownloadPolicy;
@@ -18,7 +17,6 @@ using Jellyfin.Api.Constants;
 using Jellyfin.Api.Controllers;
 using Jellyfin.Server.Formatters;
 using Jellyfin.Server.Models;
-using MediaBrowser.Common;
 using MediaBrowser.Common.Json;
 using MediaBrowser.Model.Entities;
 using Microsoft.AspNetCore.Authentication;
@@ -150,6 +148,9 @@ namespace Jellyfin.Server.Extensions
                 })
                 .AddMvc(opts =>
                 {
+                    // Allow requester to change between camelCase and PascalCase
+                    opts.RespectBrowserAcceptHeader = true;
+
                     opts.OutputFormatters.Insert(0, new CamelCaseJsonProfileFormatter());
                     opts.OutputFormatters.Insert(0, new PascalCaseJsonProfileFormatter());
 

+ 1 - 1
Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs

@@ -15,7 +15,7 @@ namespace Jellyfin.Server.Formatters
         public CamelCaseJsonProfileFormatter() : base(JsonDefaults.GetCamelCaseOptions())
         {
             SupportedMediaTypes.Clear();
-            SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"CamelCase\""));
+            SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.CamelCaseMediaType));
         }
     }
 }

+ 3 - 2
Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs

@@ -1,3 +1,4 @@
+using System.Net.Mime;
 using MediaBrowser.Common.Json;
 using Microsoft.AspNetCore.Mvc.Formatters;
 using Microsoft.Net.Http.Headers;
@@ -16,8 +17,8 @@ namespace Jellyfin.Server.Formatters
         {
             SupportedMediaTypes.Clear();
             // Add application/json for default formatter
-            SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
-            SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"PascalCase\""));
+            SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json));
+            SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(JsonDefaults.PascalCaseMediaType));
         }
     }
 }

+ 2 - 1
Jellyfin.Server/Formatters/XmlOutputFormatter.cs

@@ -16,8 +16,9 @@ namespace Jellyfin.Server.Formatters
         /// </summary>
         public XmlOutputFormatter()
         {
+            SupportedMediaTypes.Clear();
             SupportedMediaTypes.Add(MediaTypeNames.Text.Xml);
-            SupportedMediaTypes.Add("text/xml;charset=UTF-8");
+
             SupportedEncodings.Add(Encoding.UTF8);
             SupportedEncodings.Add(Encoding.Unicode);
         }

+ 0 - 36
Jellyfin.Server/HealthChecks/JellyfinDbHealthCheck.cs

@@ -1,36 +0,0 @@
-using System.Threading;
-using System.Threading.Tasks;
-using Jellyfin.Server.Implementations;
-using Microsoft.Extensions.Diagnostics.HealthChecks;
-
-namespace Jellyfin.Server.HealthChecks
-{
-    /// <summary>
-    /// Checks connectivity to the database.
-    /// </summary>
-    public class JellyfinDbHealthCheck : IHealthCheck
-    {
-        private readonly JellyfinDbProvider _dbProvider;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="JellyfinDbHealthCheck"/> class.
-        /// </summary>
-        /// <param name="dbProvider">The jellyfin db provider.</param>
-        public JellyfinDbHealthCheck(JellyfinDbProvider dbProvider)
-        {
-            _dbProvider = dbProvider;
-        }
-
-        /// <inheritdoc />
-        public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
-        {
-            await using var jellyfinDb = _dbProvider.CreateContext();
-            if (await jellyfinDb.Database.CanConnectAsync(cancellationToken).ConfigureAwait(false))
-            {
-                return HealthCheckResult.Healthy("Database connection successful.");
-            }
-
-            return HealthCheckResult.Unhealthy("Unable to connect to the database.");
-        }
-    }
-}

+ 1 - 0
Jellyfin.Server/Jellyfin.Server.csproj

@@ -44,6 +44,7 @@
     <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.7" />
     <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.7" />
     <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.7" />
+    <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.7" />
     <PackageReference Include="prometheus-net" Version="3.6.0" />
     <PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" />
     <PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />

+ 2 - 2
Jellyfin.Server/Startup.cs

@@ -3,7 +3,7 @@ using System.ComponentModel;
 using System.Net.Http.Headers;
 using Jellyfin.Api.TypeConverters;
 using Jellyfin.Server.Extensions;
-using Jellyfin.Server.HealthChecks;
+using Jellyfin.Server.Implementations;
 using Jellyfin.Server.Middleware;
 using Jellyfin.Server.Models;
 using MediaBrowser.Common.Net;
@@ -80,7 +80,7 @@ namespace Jellyfin.Server
                 .ConfigurePrimaryHttpMessageHandler(x => new DefaultHttpClientHandler());
 
             services.AddHealthChecks()
-                .AddCheck<JellyfinDbHealthCheck>("JellyfinDb");
+                .AddDbContextCheck<JellyfinDb>();
         }
 
         /// <summary>

+ 10 - 0
MediaBrowser.Common/Json/JsonDefaults.cs

@@ -9,6 +9,16 @@ namespace MediaBrowser.Common.Json
     /// </summary>
     public static class JsonDefaults
     {
+        /// <summary>
+        /// Pascal case json profile media type.
+        /// </summary>
+        public const string PascalCaseMediaType = "application/json; profile=\"PascalCase\"";
+
+        /// <summary>
+        /// Camel case json profile media type.
+        /// </summary>
+        public const string CamelCaseMediaType = "application/json; profile=\"CamelCase\"";
+
         /// <summary>
         /// Gets the default <see cref="JsonSerializerOptions" /> options.
         /// </summary>

+ 51 - 8
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -60,8 +60,6 @@ namespace MediaBrowser.Controller.Entities
 
         protected BaseItem()
         {
-            ThemeSongIds = Array.Empty<Guid>();
-            ThemeVideoIds = Array.Empty<Guid>();
             Tags = Array.Empty<string>();
             Genres = Array.Empty<string>();
             Studios = Array.Empty<string>();
@@ -100,12 +98,52 @@ namespace MediaBrowser.Controller.Entities
         };
 
         [JsonIgnore]
-        public Guid[] ThemeSongIds { get; set; }
+        public Guid[] ThemeSongIds
+        {
+            get
+            {
+                if (_themeSongIds == null)
+                {
+                    _themeSongIds = GetExtras()
+                        .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeSong)
+                        .Select(song => song.Id)
+                        .ToArray();
+                }
+
+                return _themeSongIds;
+            }
+
+            private set
+            {
+                _themeSongIds = value;
+            }
+        }
+
         [JsonIgnore]
-        public Guid[] ThemeVideoIds { get; set; }
+        public Guid[] ThemeVideoIds
+        {
+            get
+            {
+                if (_themeVideoIds == null)
+                {
+                    _themeVideoIds = GetExtras()
+                        .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeVideo)
+                        .Select(song => song.Id)
+                        .ToArray();
+                }
+
+                return _themeVideoIds;
+            }
+
+            private set
+            {
+                _themeVideoIds = value;
+            }
+        }
 
         [JsonIgnore]
         public string PreferredMetadataCountryCode { get; set; }
+
         [JsonIgnore]
         public string PreferredMetadataLanguage { get; set; }
 
@@ -635,6 +673,9 @@ namespace MediaBrowser.Controller.Entities
         }
 
         private string _sortName;
+        private Guid[] _themeSongIds;
+        private Guid[] _themeVideoIds;
+
         /// <summary>
         /// Gets the name of the sort.
         /// </summary>
@@ -1582,7 +1623,8 @@ namespace MediaBrowser.Controller.Entities
 
             await Task.WhenAll(tasks).ConfigureAwait(false);
 
-            item.ThemeVideoIds = newThemeVideoIds;
+            // They are expected to be sorted by SortName
+            item.ThemeVideoIds = newThemeVideos.OrderBy(i => i.SortName).Select(i => i.Id).ToArray();
 
             return themeVideosChanged;
         }
@@ -1619,7 +1661,8 @@ namespace MediaBrowser.Controller.Entities
 
             await Task.WhenAll(tasks).ConfigureAwait(false);
 
-            item.ThemeSongIds = newThemeSongIds;
+            // They are expected to be sorted by SortName
+            item.ThemeSongIds = newThemeSongs.OrderBy(i => i.SortName).Select(i => i.Id).ToArray();
 
             return themeSongsChanged;
         }
@@ -2910,12 +2953,12 @@ namespace MediaBrowser.Controller.Entities
 
         public IEnumerable<BaseItem> GetThemeSongs()
         {
-            return ThemeVideoIds.Select(LibraryManager.GetItemById).Where(i => i.ExtraType.Equals(Model.Entities.ExtraType.ThemeSong)).OrderBy(i => i.SortName);
+            return ThemeSongIds.Select(LibraryManager.GetItemById);
         }
 
         public IEnumerable<BaseItem> GetThemeVideos()
         {
-            return ThemeVideoIds.Select(LibraryManager.GetItemById).Where(i => i.ExtraType.Equals(Model.Entities.ExtraType.ThemeVideo)).OrderBy(i => i.SortName);
+            return ThemeVideoIds.Select(LibraryManager.GetItemById);
         }
 
         /// <summary>

+ 123 - 56
MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs

@@ -456,6 +456,7 @@ 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 isNvencHevcDecoder = 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);
@@ -515,6 +516,24 @@ namespace MediaBrowser.Controller.MediaEncoding
                     }
                 }
 
+                if (state.IsVideoRequest
+                    && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
+                {
+                    var isColorDepth10 = IsColorDepth10(state);
+
+                    if (isNvencHevcDecoder && isColorDepth10
+                        && _mediaEncoder.SupportsHwaccel("opencl")
+                        && encodingOptions.EnableTonemapping
+                        && !string.IsNullOrEmpty(state.VideoStream.VideoRange)
+                        && state.VideoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase))
+                    {
+                        arg.Append("-init_hw_device opencl=ocl:")
+                            .Append(encodingOptions.OpenclDevice)
+                            .Append(' ')
+                            .Append("-filter_hw_device ocl ");
+                    }
+                }
+
                 if (state.IsVideoRequest
                     && string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
                 {
@@ -1003,11 +1022,33 @@ namespace MediaBrowser.Controller.MediaEncoding
             if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)
                 && !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
                 && !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
+                && !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
                 && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
             {
                 param = "-pix_fmt yuv420p " + param;
             }
 
+            if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+            {
+                var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty;
+                var videoStream = state.VideoStream;
+                var isColorDepth10 = IsColorDepth10(state);
+
+                if (videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1
+                    && isColorDepth10
+                    && _mediaEncoder.SupportsHwaccel("opencl")
+                    && encodingOptions.EnableTonemapping
+                    && !string.IsNullOrEmpty(videoStream.VideoRange)
+                    && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase))
+                {
+                    param = "-pix_fmt nv12 " + param;
+                }
+                else
+                {
+                    param = "-pix_fmt yuv420p " + param;
+                }
+            }
+
             if (string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
             {
                 param = "-pix_fmt nv21 " + param;
@@ -1611,64 +1652,45 @@ namespace MediaBrowser.Controller.MediaEncoding
             var outputSizeParam = ReadOnlySpan<char>.Empty;
             var request = state.BaseRequest;
 
-            // Add resolution params, if specified
-            if (request.Width.HasValue
-                || request.Height.HasValue
-                || request.MaxHeight.HasValue
-                || request.MaxWidth.HasValue)
+            outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"');
+
+            // All possible beginning of video filters
+            // Don't break the order
+            string[] beginOfOutputSizeParam = new[]
             {
-                outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"');
+                // for tonemap_opencl
+                "hwupload,tonemap_opencl",
 
                 // hwupload=extra_hw_frames=64,vpp_qsv (for overlay_qsv on linux)
-                var index = outputSizeParam.IndexOf("hwupload=extra_hw_frames", StringComparison.OrdinalIgnoreCase);
+                "hwupload=extra_hw_frames",
+
+                // vpp_qsv
+                "vpp",
+
+                // hwdownload,format=p010le (hardware decode + software encode for vaapi)
+                "hwdownload",
+
+                // format=nv12|vaapi,hwupload,scale_vaapi
+                "format",
+
+                // bwdif,scale=expr
+                "bwdif",
+
+                // yadif,scale=expr
+                "yadif",
+
+                // scale=expr
+                "scale"
+            };
+
+            var index = -1;
+            foreach (var param in beginOfOutputSizeParam)
+            {
+                index = outputSizeParam.IndexOf(param, StringComparison.OrdinalIgnoreCase);
                 if (index != -1)
                 {
                     outputSizeParam = outputSizeParam.Slice(index);
-                }
-                else
-                {
-                    // vpp_qsv
-                    index = outputSizeParam.IndexOf("vpp", StringComparison.OrdinalIgnoreCase);
-                    if (index != -1)
-                    {
-                        outputSizeParam = outputSizeParam.Slice(index);
-                    }
-                    else
-                    {
-                        // hwdownload,format=p010le (hardware decode + software encode for vaapi)
-                        index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase);
-                        if (index != -1)
-                        {
-                            outputSizeParam = outputSizeParam.Slice(index);
-                        }
-                        else
-                        {
-                            // format=nv12|vaapi,hwupload,scale_vaapi
-                            index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase);
-                            if (index != -1)
-                            {
-                                outputSizeParam = outputSizeParam.Slice(index);
-                            }
-                            else
-                            {
-                                // yadif,scale=expr
-                                index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase);
-                                if (index != -1)
-                                {
-                                    outputSizeParam = outputSizeParam.Slice(index);
-                                }
-                                else
-                                {
-                                    // scale=expr
-                                    index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase);
-                                    if (index != -1)
-                                    {
-                                        outputSizeParam = outputSizeParam.Slice(index);
-                                    }
-                                }
-                            }
-                        }
-                    }
+                    break;
                 }
             }
 
@@ -1747,9 +1769,9 @@ namespace MediaBrowser.Controller.MediaEncoding
                 */
                 if (isLinux)
                 {
-                    retStr = !outputSizeParam.IsEmpty ?
-                        " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"" :
-                        " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"";
+                    retStr = !outputSizeParam.IsEmpty
+                        ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\""
+                        : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"";
                 }
             }
 
@@ -2084,8 +2106,10 @@ namespace MediaBrowser.Controller.MediaEncoding
             var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
             var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1;
             var isNvdecH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
+            var isNvdecHevcDecoder = videoDecoder.IndexOf("hevc_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
             var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1;
             var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+            var isColorDepth10 = IsColorDepth10(state);
 
             var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
             var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
@@ -2093,6 +2117,50 @@ namespace MediaBrowser.Controller.MediaEncoding
             // If double rate deinterlacing is enabled and the input framerate is 30fps or below, otherwise the output framerate will be too high for many devices
             var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.RealFrameRate ?? 60) <= 30;
 
+            // Currently only with the use of NVENC decoder can we get a decent performance.
+            // Currently only the HEVC/H265 format is supported.
+            // NVIDIA Pascal and Turing or higher are recommended.
+            if (isNvdecHevcDecoder && isColorDepth10
+                && _mediaEncoder.SupportsHwaccel("opencl")
+                && options.EnableTonemapping
+                && !string.IsNullOrEmpty(videoStream.VideoRange)
+                && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase))
+            {
+                var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}";
+
+                if (options.TonemappingParam != 0)
+                {
+                    parameters += ":param={4}";
+                }
+
+                if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase))
+                {
+                    parameters += ":range={5}";
+                }
+
+                // 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,
+                        parameters,
+                        options.TonemappingAlgorithm,
+                        options.TonemappingDesat,
+                        options.TonemappingThreshold,
+                        options.TonemappingPeak,
+                        options.TonemappingParam,
+                        options.TonemappingRange));
+                filters.Add("hwdownload");
+
+                if (hasGraphicalSubs || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true)
+                    || string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
+                {
+                    filters.Add("format=nv12");
+                }
+            }
+
             // When the input may or may not be hardware VAAPI decodable
             if (isVaapiH264Encoder)
             {
@@ -2110,7 +2178,6 @@ namespace MediaBrowser.Controller.MediaEncoding
             else if (IsVaapiSupported(state) && isVaapiDecoder && isLibX264Encoder)
             {
                 var codec = videoStream.Codec.ToLowerInvariant();
-                var isColorDepth10 = IsColorDepth10(state);
 
                 // Assert 10-bit hardware VAAPI decodable
                 if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)

+ 14 - 0
MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs

@@ -282,6 +282,20 @@ namespace MediaBrowser.MediaEncoding.Probing
         [JsonPropertyName("disposition")]
         public IReadOnlyDictionary<string, int> Disposition { get; set; }
 
+        /// <summary>
+        /// Gets or sets the color range.
+        /// </summary>
+        /// <value>The color range.</value>
+        [JsonPropertyName("color_range")]
+        public string ColorRange { get; set; }
+
+        /// <summary>
+        /// Gets or sets the color space.
+        /// </summary>
+        /// <value>The color space.</value>
+        [JsonPropertyName("color_space")]
+        public string ColorSpace { get; set; }
+
         /// <summary>
         /// Gets or sets the color transfer.
         /// </summary>

+ 10 - 0
MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs

@@ -714,6 +714,16 @@ namespace MediaBrowser.MediaEncoding.Probing
                     stream.RefFrames = streamInfo.Refs;
                 }
 
+                if (!string.IsNullOrEmpty(streamInfo.ColorRange))
+                {
+                    stream.ColorRange = streamInfo.ColorRange;
+                }
+
+                if (!string.IsNullOrEmpty(streamInfo.ColorSpace))
+                {
+                    stream.ColorSpace = streamInfo.ColorSpace;
+                }
+
                 if (!string.IsNullOrEmpty(streamInfo.ColorTransfer))
                 {
                     stream.ColorTransfer = streamInfo.ColorTransfer;

+ 28 - 1
MediaBrowser.Model/Configuration/EncodingOptions.cs

@@ -31,6 +31,22 @@ namespace MediaBrowser.Model.Configuration
 
         public string VaapiDevice { get; set; }
 
+        public string OpenclDevice { get; set; }
+
+        public bool EnableTonemapping { get; set; }
+
+        public string TonemappingAlgorithm { get; set; }
+
+        public string TonemappingRange { get; set; }
+
+        public double TonemappingDesat { get; set; }
+
+        public double TonemappingThreshold { get; set; }
+
+        public double TonemappingPeak { get; set; }
+
+        public double TonemappingParam { get; set; }
+
         public int H264Crf { get; set; }
 
         public int H265Crf { get; set; }
@@ -58,8 +74,19 @@ namespace MediaBrowser.Model.Configuration
             EnableThrottling = false;
             ThrottleDelaySeconds = 180;
             EncodingThreadCount = -1;
-            // This is a DRM device that is almost guaranteed to be there on every intel platform, plus it's the default one in ffmpeg if you don't specify anything
+            // This is a DRM device that is almost guaranteed to be there on every intel platform,
+            // plus it's the default one in ffmpeg if you don't specify anything
             VaapiDevice = "/dev/dri/renderD128";
+            // This is the OpenCL device that is used for tonemapping.
+            // 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;
+            TonemappingAlgorithm = "reinhard";
+            TonemappingRange = "auto";
+            TonemappingDesat = 0;
+            TonemappingThreshold = 0.8;
+            TonemappingPeak = 0;
+            TonemappingParam = 0;
             H264Crf = 23;
             H265Crf = 28;
             DeinterlaceDoubleRate = false;

+ 12 - 6
MediaBrowser.Model/Entities/MediaStream.cs

@@ -35,6 +35,18 @@ namespace MediaBrowser.Model.Entities
         /// <value>The language.</value>
         public string Language { get; set; }
 
+        /// <summary>
+        /// Gets or sets the color range.
+        /// </summary>
+        /// <value>The color range.</value>
+        public string ColorRange { get; set; }
+
+        /// <summary>
+        /// Gets or sets the color space.
+        /// </summary>
+        /// <value>The color space.</value>
+        public string ColorSpace { get; set; }
+
         /// <summary>
         /// Gets or sets the color transfer.
         /// </summary>
@@ -47,12 +59,6 @@ namespace MediaBrowser.Model.Entities
         /// <value>The color primaries.</value>
         public string ColorPrimaries { get; set; }
 
-        /// <summary>
-        /// Gets or sets the color space.
-        /// </summary>
-        /// <value>The color space.</value>
-        public string ColorSpace { get; set; }
-
         /// <summary>
         /// Gets or sets the comment.
         /// </summary>

+ 1 - 1
debian/rules

@@ -40,7 +40,7 @@ override_dh_clistrip:
 
 override_dh_auto_build:
 	dotnet publish --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
-		"-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" Jellyfin.Server
+		"-p:DebugSymbols=false;DebugType=none" Jellyfin.Server
 
 override_dh_auto_clean:
 	dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true

+ 1 - 1
deployment/Dockerfile.docker.amd64

@@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 
 # because of changes in docker and systemd we need to not build in parallel at the moment
 # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"

+ 1 - 1
deployment/Dockerfile.docker.arm64

@@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 
 # because of changes in docker and systemd we need to not build in parallel at the moment
 # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"

+ 1 - 1
deployment/Dockerfile.docker.armhf

@@ -12,4 +12,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
 
 # because of changes in docker and systemd we need to not build in parallel at the moment
 # see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
-RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none"
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"

+ 1 - 1
deployment/build.linux.amd64

@@ -16,7 +16,7 @@ else
 fi
 
 # Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
 tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version}
 rm -rf dist/jellyfin-server_${version}
 

+ 1 - 1
deployment/build.macos

@@ -16,7 +16,7 @@ else
 fi
 
 # Build archives
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
 tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_${version}
 rm -rf dist/jellyfin-server_${version}
 

+ 1 - 1
deployment/build.portable

@@ -16,7 +16,7 @@ else
 fi
 
 # Build archives
-dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
 tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version}
 rm -rf dist/jellyfin-server_${version}
 

+ 1 - 1
deployment/build.windows.amd64

@@ -23,7 +23,7 @@ fi
 output_dir="dist/jellyfin-server_${version}"
 
 # Build binary
-dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none;UseAppHost=true"
+dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
 
 # Prepare addins
 addin_build_dir="$( mktemp -d )"

+ 1 - 1
fedora/jellyfin.spec

@@ -54,7 +54,7 @@ The Jellyfin media server backend.
 export DOTNET_CLI_TELEMETRY_OPTOUT=1
 export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
 dotnet publish --configuration Release --output='%{buildroot}%{_libdir}/jellyfin' --self-contained --runtime %{dotnet_runtime} \
-    "-p:GenerateDocumentationFile=true;DebugSymbols=false;DebugType=none" Jellyfin.Server
+    "-p:DebugSymbols=false;DebugType=none" Jellyfin.Server
 %{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
 %{__install} -D -m 0644 %{SOURCE15} %{buildroot}%{_sysconfdir}/systemd/system/jellyfin.service.d/override.conf
 %{__install} -D -m 0644 Jellyfin.Server/Resources/Configuration/logging.json %{buildroot}%{_sysconfdir}/jellyfin/logging.json