using ErsatzTV.FFmpeg.Environment; using LanguageExt; namespace ErsatzTV.FFmpeg.Filter; public class ComplexFilter : IPipelineStep { private readonly FrameState _currentState; private readonly FFmpegState _ffmpegState; private readonly Option _maybeVideoInputFile; private readonly Option _maybeAudioInputFile; private readonly Option _maybeWatermarkInputFile; private readonly FrameSize _resolution; public ComplexFilter( FrameState currentState, FFmpegState ffmpegState, Option maybeVideoInputFile, Option maybeAudioInputFile, Option maybeWatermarkInputFile, FrameSize resolution) { _currentState = currentState; _ffmpegState = ffmpegState; _maybeVideoInputFile = maybeVideoInputFile; _maybeAudioInputFile = maybeAudioInputFile; _maybeWatermarkInputFile = maybeWatermarkInputFile; _resolution = resolution; } private IList Arguments() { var audioLabel = "0:a"; var videoLabel = "0:v"; string watermarkLabel; var result = new List(); string audioFilterComplex = string.Empty; string videoFilterComplex = string.Empty; string watermarkFilterComplex = string.Empty; string overlayFilterComplex = string.Empty; var distinctPaths = new List(); foreach ((string path, _) in _maybeVideoInputFile) { if (!distinctPaths.Contains(path)) { distinctPaths.Add(path); } } foreach ((string path, _) in _maybeAudioInputFile) { if (!distinctPaths.Contains(path)) { distinctPaths.Add(path); } } foreach ((string path, _) in _maybeWatermarkInputFile) { if (!distinctPaths.Contains(path)) { distinctPaths.Add(path); } } foreach (VideoInputFile videoInputFile in _maybeVideoInputFile) { int inputIndex = distinctPaths.IndexOf(videoInputFile.Path); foreach ((int index, _, _) in videoInputFile.Streams) { videoLabel = $"{inputIndex}:{index}"; if (videoInputFile.FilterSteps.Any(f => !string.IsNullOrWhiteSpace(f.Filter))) { videoFilterComplex += $"[{inputIndex}:{index}]"; videoFilterComplex += string.Join( ",", videoInputFile.FilterSteps.Select(f => f.Filter).Filter(s => !string.IsNullOrWhiteSpace(s))); videoLabel = "[v]"; videoFilterComplex += videoLabel; } } } foreach (AudioInputFile audioInputFile in _maybeAudioInputFile) { int inputIndex = distinctPaths.IndexOf(audioInputFile.Path); foreach ((int index, _, _) in audioInputFile.Streams) { audioLabel = $"{inputIndex}:{index}"; if (audioInputFile.FilterSteps.Any(f => !string.IsNullOrWhiteSpace(f.Filter))) { audioFilterComplex += $"[{inputIndex}:{index}]"; audioFilterComplex += string.Join( ",", audioInputFile.FilterSteps.Select(f => f.Filter).Filter(s => !string.IsNullOrWhiteSpace(s))); audioLabel = "[a]"; audioFilterComplex += audioLabel; } } } foreach (WatermarkInputFile watermarkInputFile in _maybeWatermarkInputFile) { int inputIndex = distinctPaths.IndexOf(watermarkInputFile.Path); foreach ((int index, _, _) in watermarkInputFile.Streams) { watermarkLabel = $"{inputIndex}:{index}"; if (watermarkInputFile.FilterSteps.Any(f => !string.IsNullOrWhiteSpace(f.Filter))) { watermarkFilterComplex += $"[{inputIndex}:{index}]"; watermarkFilterComplex += string.Join( ",", watermarkInputFile.FilterSteps.Select(f => f.Filter).Filter(s => !string.IsNullOrWhiteSpace(s))); watermarkLabel = "[wm]"; watermarkFilterComplex += watermarkLabel; } IPipelineFilterStep overlayFilter = AvailableOverlayFilters.ForAcceleration( _ffmpegState.HardwareAccelerationMode, _currentState, watermarkInputFile.DesiredState, _resolution); if (overlayFilter.Filter != string.Empty) { string tempVideoLabel = string.IsNullOrWhiteSpace(videoFilterComplex) ? $"[{videoLabel}]" : videoLabel; // vaapi uses software overlay and needs to upload string uploadFilter = string.Empty; if (_ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Vaapi) { uploadFilter = new HardwareUploadFilter(_ffmpegState).Filter; } if (!string.IsNullOrWhiteSpace(uploadFilter)) { uploadFilter = "," + uploadFilter; } overlayFilterComplex = $"{tempVideoLabel}{watermarkLabel}{overlayFilter.Filter}{uploadFilter}[vf]"; // change the mapped label videoLabel = "[vf]"; } } } if (!string.IsNullOrWhiteSpace(audioFilterComplex) || !string.IsNullOrWhiteSpace(videoFilterComplex)) { var filterComplex = string.Join( ";", new[] { audioFilterComplex, videoFilterComplex, watermarkFilterComplex, overlayFilterComplex }.Where( s => !string.IsNullOrWhiteSpace(s))); result.AddRange(new[] { "-filter_complex", filterComplex }); } result.AddRange(new[] { "-map", audioLabel, "-map", videoLabel }); return result; } public IList EnvironmentVariables => Array.Empty(); public IList GlobalOptions => Array.Empty(); public IList InputOptions(InputFile inputFile) => Array.Empty(); public IList FilterOptions => Arguments(); public IList OutputOptions => Array.Empty(); public FrameState NextState(FrameState currentState) => currentState; }