Browse Source

feat: add channel config overlays for next engine (#2891)

pull/2892/head
Jason Dove 1 week ago committed by GitHub
parent
commit
6429659711
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 8
      CHANGELOG.md
  2. 28
      ErsatzTV.Application/Streaming/Commands/StartFFmpegNextSessionHandler.cs
  3. 21
      ErsatzTV.Application/Streaming/NextSessionWorker.cs
  4. 2
      ErsatzTV.Core/FileSystemLayout.cs
  5. 100
      ErsatzTV.Core/Next/Config/ChannelConfig.cs
  6. 1
      ErsatzTV/Startup.cs

8
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/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [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 ### Fixed
- Fix HLS Direct playback when JWT auth is also used - Fix HLS Direct playback when JWT auth is also used

28
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 var videoNormalization = new Video
{ {
Format = ffmpegProfile.VideoFormat switch Format = ffmpegProfile.VideoFormat switch
@ -356,8 +366,22 @@ public class StartFFmpegNextSessionHandler(
ScalingBehavior.Crop => ScalingMode.Crop, ScalingBehavior.Crop => ScalingMode.Crop,
_ => ScalingMode.ScaleAndPad _ => ScalingMode.ScaleAndPad
}, },
// TODO: NEXT: more tonemap algorithms Deinterlace = ffmpegProfile.DeinterlaceVideo,
TonemapAlgorithm = "linear", Filters = new Filters
{
Tonemap = new TonemapClass
{
Tonemap = tonemapAlgorithm
},
TonemapOpencl = new TonemapOpenclClass
{
Tonemap = tonemapAlgorithm
},
Libplacebo = new LibplaceboClass
{
Tonemapping = tonemapAlgorithm
}
},
VaapiDevice = ffmpegProfile.VaapiDevice, VaapiDevice = ffmpegProfile.VaapiDevice,
VaapiDriver = ffmpegProfile.VaapiDriver switch VaapiDriver = ffmpegProfile.VaapiDriver switch
{ {

21
ErsatzTV.Application/Streaming/NextSessionWorker.cs

@ -99,9 +99,26 @@ public class NextSessionWorker(
_workingDirectory = fileSystem.Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber); _workingDirectory = fileSystem.Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber);
_heartbeatFileName = fileSystem.Path.Combine(_workingDirectory, ".heartbeat"); _heartbeatFileName = fileSystem.Path.Combine(_workingDirectory, ".heartbeat");
List<string> 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) CommandResult commandResult = await Cli.Wrap(channelBinary)
.WithArguments( .WithArguments(arguments)
["run", "--output-folder", _workingDirectory, "--number", channelNumber, "-"])
.WithStandardInputPipe(PipeSource.FromString(channelConfig.ToJson())) .WithStandardInputPipe(PipeSource.FromString(channelConfig.ToJson()))
.WithStandardOutputPipe(PipeTarget.ToDelegate(l => logger.LogDebug("{Line}", l))) .WithStandardOutputPipe(PipeTarget.ToDelegate(l => logger.LogDebug("{Line}", l)))
.WithStandardErrorPipe(PipeTarget.ToDelegate(l => logger.LogDebug("{Line}", l))) .WithStandardErrorPipe(PipeTarget.ToDelegate(l => logger.LogDebug("{Line}", l)))

2
ErsatzTV.Core/FileSystemLayout.cs

@ -67,6 +67,7 @@ public static class FileSystemLayout
public static readonly string DefaultMpegTsScriptFolder; public static readonly string DefaultMpegTsScriptFolder;
public static readonly string NextChannelConfigOverlaysFolder;
public static readonly string NextPlayoutsFolder; public static readonly string NextPlayoutsFolder;
public static readonly string MacOsOldAppDataFolder = Path.Combine( public static readonly string MacOsOldAppDataFolder = Path.Combine(
@ -195,6 +196,7 @@ public static class FileSystemLayout
MpegTsScriptsFolder = Path.Combine(ScriptsFolder, "mpegts"); MpegTsScriptsFolder = Path.Combine(ScriptsFolder, "mpegts");
DefaultMpegTsScriptFolder = Path.Combine(MpegTsScriptsFolder, "default"); DefaultMpegTsScriptFolder = Path.Combine(MpegTsScriptsFolder, "default");
NextChannelConfigOverlaysFolder = Path.Combine(AppDataFolder, "next", "channel-config-overlays");
NextPlayoutsFolder = Path.Combine(AppDataFolder, "next", "playouts"); NextPlayoutsFolder = Path.Combine(AppDataFolder, "next", "playouts");
} }
} }

100
ErsatzTV.Core/Next/Config/ChannelConfig.cs

@ -113,6 +113,9 @@ namespace ErsatzTV.Core.Next.Config
[JsonProperty("deinterlace", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("deinterlace", NullValueHandling = NullValueHandling.Ignore)]
public bool? Deinterlace { get; set; } public bool? Deinterlace { get; set; }
[JsonProperty("filters", NullValueHandling = NullValueHandling.Ignore)]
public Filters Filters { get; set; }
[JsonProperty("format")] [JsonProperty("format")]
public VideoFormat? Format { get; set; } public VideoFormat? Format { get; set; }
@ -122,9 +125,6 @@ namespace ErsatzTV.Core.Next.Config
[JsonProperty("scaling_mode", NullValueHandling = NullValueHandling.Ignore)] [JsonProperty("scaling_mode", NullValueHandling = NullValueHandling.Ignore)]
public ScalingMode? ScalingMode { get; set; } public ScalingMode? ScalingMode { get; set; }
[JsonProperty("tonemap_algorithm")]
public string TonemapAlgorithm { get; set; }
[JsonProperty("vaapi_device")] [JsonProperty("vaapi_device")]
public string VaapiDevice { get; set; } public string VaapiDevice { get; set; }
@ -135,6 +135,99 @@ namespace ErsatzTV.Core.Next.Config
public long? Width { get; set; } 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 public partial class Playout
{ {
[JsonProperty("folder")] [JsonProperty("folder")]
@ -173,6 +266,7 @@ namespace ErsatzTV.Core.Next.Config
{ {
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{ {
NullValueHandling = NullValueHandling.Ignore,
MetadataPropertyHandling = MetadataPropertyHandling.Ignore, MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
DateParseHandling = DateParseHandling.None, DateParseHandling = DateParseHandling.None,
Converters = Converters =

1
ErsatzTV/Startup.cs

@ -363,6 +363,7 @@ public class Startup
FileSystemLayout.AudioStreamSelectorScriptsFolder, FileSystemLayout.AudioStreamSelectorScriptsFolder,
FileSystemLayout.MpegTsScriptsFolder, FileSystemLayout.MpegTsScriptsFolder,
FileSystemLayout.DefaultMpegTsScriptFolder, FileSystemLayout.DefaultMpegTsScriptFolder,
FileSystemLayout.NextChannelConfigOverlaysFolder,
FileSystemLayout.NextPlayoutsFolder, FileSystemLayout.NextPlayoutsFolder,
]; ];

Loading…
Cancel
Save