diff --git a/CHANGELOG.md b/CHANGELOG.md index ded67e853..acd9b69d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added +- Add channel config overlay system for Next engine + - The channel config schema can be found at https://github.com/ErsatzTV/next/blob/main/schema/channel_config.json + - Config overlays should be created in the config subfolder `next/channel-config-overlays` + - A config file named `default.json` will apply to all channels using the Next engine + - A config file named `{channel_number}.json` (e.g. `1.json`) will apply to the channel with that number + - Channel overlays will override values from the default overlay which will override values from the FFmpeg Profile + ### Fixed - Fix HLS Direct playback when JWT auth is also used diff --git a/ErsatzTV.Application/Streaming/Commands/StartFFmpegNextSessionHandler.cs b/ErsatzTV.Application/Streaming/Commands/StartFFmpegNextSessionHandler.cs index dba572f53..e009b9846 100644 --- a/ErsatzTV.Application/Streaming/Commands/StartFFmpegNextSessionHandler.cs +++ b/ErsatzTV.Application/Streaming/Commands/StartFFmpegNextSessionHandler.cs @@ -324,6 +324,16 @@ public class StartFFmpegNextSessionHandler( }; } + string tonemapAlgorithm = ffmpegProfile.TonemapAlgorithm switch + { + FFmpegProfileTonemapAlgorithm.Clip => "clip", + FFmpegProfileTonemapAlgorithm.Gamma => "gamma", + FFmpegProfileTonemapAlgorithm.Reinhard => "reinhard", + FFmpegProfileTonemapAlgorithm.Mobius => "mobius", + FFmpegProfileTonemapAlgorithm.Hable => "hable", + _ => "linear" + }; + var videoNormalization = new Video { Format = ffmpegProfile.VideoFormat switch @@ -356,8 +366,22 @@ public class StartFFmpegNextSessionHandler( ScalingBehavior.Crop => ScalingMode.Crop, _ => ScalingMode.ScaleAndPad }, - // TODO: NEXT: more tonemap algorithms - TonemapAlgorithm = "linear", + Deinterlace = ffmpegProfile.DeinterlaceVideo, + Filters = new Filters + { + Tonemap = new TonemapClass + { + Tonemap = tonemapAlgorithm + }, + TonemapOpencl = new TonemapOpenclClass + { + Tonemap = tonemapAlgorithm + }, + Libplacebo = new LibplaceboClass + { + Tonemapping = tonemapAlgorithm + } + }, VaapiDevice = ffmpegProfile.VaapiDevice, VaapiDriver = ffmpegProfile.VaapiDriver switch { diff --git a/ErsatzTV.Application/Streaming/NextSessionWorker.cs b/ErsatzTV.Application/Streaming/NextSessionWorker.cs index 3dd0bc309..a13a8103b 100644 --- a/ErsatzTV.Application/Streaming/NextSessionWorker.cs +++ b/ErsatzTV.Application/Streaming/NextSessionWorker.cs @@ -99,9 +99,26 @@ public class NextSessionWorker( _workingDirectory = fileSystem.Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber); _heartbeatFileName = fileSystem.Path.Combine(_workingDirectory, ".heartbeat"); + List arguments = ["run", "--output-folder", _workingDirectory, "--number", channelNumber, "-"]; + + string defaultOverlayFile = fileSystem.Path.Combine( + FileSystemLayout.NextChannelConfigOverlaysFolder, + "default.json"); + if (fileSystem.File.Exists(defaultOverlayFile)) + { + arguments.Add(defaultOverlayFile); + } + + string channelOverlayFile = fileSystem.Path.Combine( + FileSystemLayout.NextChannelConfigOverlaysFolder, + $"{channelNumber}.json"); + if (fileSystem.File.Exists(channelOverlayFile)) + { + arguments.Add(channelOverlayFile); + } + CommandResult commandResult = await Cli.Wrap(channelBinary) - .WithArguments( - ["run", "--output-folder", _workingDirectory, "--number", channelNumber, "-"]) + .WithArguments(arguments) .WithStandardInputPipe(PipeSource.FromString(channelConfig.ToJson())) .WithStandardOutputPipe(PipeTarget.ToDelegate(l => logger.LogDebug("{Line}", l))) .WithStandardErrorPipe(PipeTarget.ToDelegate(l => logger.LogDebug("{Line}", l))) diff --git a/ErsatzTV.Core/FileSystemLayout.cs b/ErsatzTV.Core/FileSystemLayout.cs index 9cdc7ed41..064f7cfbd 100644 --- a/ErsatzTV.Core/FileSystemLayout.cs +++ b/ErsatzTV.Core/FileSystemLayout.cs @@ -67,6 +67,7 @@ public static class FileSystemLayout public static readonly string DefaultMpegTsScriptFolder; + public static readonly string NextChannelConfigOverlaysFolder; public static readonly string NextPlayoutsFolder; public static readonly string MacOsOldAppDataFolder = Path.Combine( @@ -195,6 +196,7 @@ public static class FileSystemLayout MpegTsScriptsFolder = Path.Combine(ScriptsFolder, "mpegts"); DefaultMpegTsScriptFolder = Path.Combine(MpegTsScriptsFolder, "default"); + NextChannelConfigOverlaysFolder = Path.Combine(AppDataFolder, "next", "channel-config-overlays"); NextPlayoutsFolder = Path.Combine(AppDataFolder, "next", "playouts"); } } diff --git a/ErsatzTV.Core/Next/Config/ChannelConfig.cs b/ErsatzTV.Core/Next/Config/ChannelConfig.cs index e9665eedd..175044d05 100644 --- a/ErsatzTV.Core/Next/Config/ChannelConfig.cs +++ b/ErsatzTV.Core/Next/Config/ChannelConfig.cs @@ -113,6 +113,9 @@ namespace ErsatzTV.Core.Next.Config [JsonProperty("deinterlace", NullValueHandling = NullValueHandling.Ignore)] public bool? Deinterlace { get; set; } + [JsonProperty("filters", NullValueHandling = NullValueHandling.Ignore)] + public Filters Filters { get; set; } + [JsonProperty("format")] public VideoFormat? Format { get; set; } @@ -122,9 +125,6 @@ namespace ErsatzTV.Core.Next.Config [JsonProperty("scaling_mode", NullValueHandling = NullValueHandling.Ignore)] public ScalingMode? ScalingMode { get; set; } - [JsonProperty("tonemap_algorithm")] - public string TonemapAlgorithm { get; set; } - [JsonProperty("vaapi_device")] public string VaapiDevice { get; set; } @@ -135,6 +135,99 @@ namespace ErsatzTV.Core.Next.Config public long? Width { get; set; } } + public partial class Filters + { + [JsonProperty("bwdif")] + public BwdifClass Bwdif { get; set; } + + [JsonProperty("bwdif_cuda")] + public BwdifCudaClass BwdifCuda { get; set; } + + [JsonProperty("deinterlace_qsv")] + public DeinterlaceQsvClass DeinterlaceQsv { get; set; } + + [JsonProperty("deinterlace_vaapi")] + public DeinterlaceVaapiClass DeinterlaceVaapi { get; set; } + + [JsonProperty("libplacebo")] + public LibplaceboClass Libplacebo { get; set; } + + [JsonProperty("tonemap")] + public TonemapClass Tonemap { get; set; } + + [JsonProperty("tonemap_opencl")] + public TonemapOpenclClass TonemapOpencl { get; set; } + + [JsonProperty("w3fdif")] + public W3FdifClass W3Fdif { get; set; } + + [JsonProperty("yadif")] + public YadifClass Yadif { get; set; } + + [JsonProperty("yadif_cuda")] + public YadifCudaClass YadifCuda { get; set; } + } + + public partial class BwdifClass + { + [JsonProperty("mode")] + public string Mode { get; set; } + } + + public partial class BwdifCudaClass + { + [JsonProperty("mode")] + public string Mode { get; set; } + } + + public partial class DeinterlaceQsvClass + { + [JsonProperty("mode")] + public string Mode { get; set; } + } + + public partial class DeinterlaceVaapiClass + { + [JsonProperty("mode")] + public string Mode { get; set; } + } + + public partial class LibplaceboClass + { + [JsonProperty("tonemapping")] + public string Tonemapping { get; set; } + } + + public partial class TonemapClass + { + [JsonProperty("tonemap")] + public string Tonemap { get; set; } + } + + public partial class TonemapOpenclClass + { + [JsonProperty("tonemap")] + public string Tonemap { get; set; } + } + + public partial class W3FdifClass + { + [JsonProperty("mode")] + public string Mode { get; set; } + } + + public partial class YadifClass + { + [JsonProperty("mode")] + public string Mode { get; set; } + } + + public partial class YadifCudaClass + { + [JsonProperty("mode")] + public string Mode { get; set; } + } + public partial class Playout { [JsonProperty("folder")] @@ -173,6 +266,7 @@ namespace ErsatzTV.Core.Next.Config { public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings { + NullValueHandling = NullValueHandling.Ignore, MetadataPropertyHandling = MetadataPropertyHandling.Ignore, DateParseHandling = DateParseHandling.None, Converters = diff --git a/ErsatzTV/Startup.cs b/ErsatzTV/Startup.cs index 711c1e22a..4ace6cb47 100644 --- a/ErsatzTV/Startup.cs +++ b/ErsatzTV/Startup.cs @@ -363,6 +363,7 @@ public class Startup FileSystemLayout.AudioStreamSelectorScriptsFolder, FileSystemLayout.MpegTsScriptsFolder, FileSystemLayout.DefaultMpegTsScriptFolder, + FileSystemLayout.NextChannelConfigOverlaysFolder, FileSystemLayout.NextPlayoutsFolder, ];