mirror of https://github.com/ErsatzTV/ErsatzTV.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
265 lines
11 KiB
265 lines
11 KiB
using ErsatzTV.FFmpeg.Environment; |
|
using ErsatzTV.FFmpeg.Format; |
|
|
|
namespace ErsatzTV.FFmpeg.Filter; |
|
|
|
public class ComplexFilter : IPipelineStep |
|
{ |
|
private readonly FrameState _currentState; |
|
private readonly FFmpegState _ffmpegState; |
|
private readonly Option<VideoInputFile> _maybeVideoInputFile; |
|
private readonly Option<AudioInputFile> _maybeAudioInputFile; |
|
private readonly Option<WatermarkInputFile> _maybeWatermarkInputFile; |
|
private readonly Option<SubtitleInputFile> _maybeSubtitleInputFile; |
|
private readonly FrameSize _resolution; |
|
|
|
public ComplexFilter( |
|
FrameState currentState, |
|
FFmpegState ffmpegState, |
|
Option<VideoInputFile> maybeVideoInputFile, |
|
Option<AudioInputFile> maybeAudioInputFile, |
|
Option<WatermarkInputFile> maybeWatermarkInputFile, |
|
Option<SubtitleInputFile> maybeSubtitleInputFile, |
|
FrameSize resolution) |
|
{ |
|
_currentState = currentState; |
|
_ffmpegState = ffmpegState; |
|
_maybeVideoInputFile = maybeVideoInputFile; |
|
_maybeAudioInputFile = maybeAudioInputFile; |
|
_maybeWatermarkInputFile = maybeWatermarkInputFile; |
|
_maybeSubtitleInputFile = maybeSubtitleInputFile; |
|
_resolution = resolution; |
|
} |
|
|
|
private IList<string> Arguments() |
|
{ |
|
var audioLabel = "0:a"; |
|
var videoLabel = "0:v"; |
|
string watermarkLabel; |
|
string subtitleLabel; |
|
|
|
var result = new List<string>(); |
|
|
|
string audioFilterComplex = string.Empty; |
|
string videoFilterComplex = string.Empty; |
|
string watermarkFilterComplex = string.Empty; |
|
string watermarkOverlayFilterComplex = string.Empty; |
|
string subtitleFilterComplex = string.Empty; |
|
string subtitleOverlayFilterComplex = string.Empty; |
|
|
|
var distinctPaths = new List<string>(); |
|
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 ((string path, _) in _maybeSubtitleInputFile) |
|
{ |
|
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 = AvailableWatermarkOverlayFilters.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 |
|
// videotoolbox seems to require a hwupload for hevc |
|
// also wait to upload videotoolbox if a subtitle overlay is coming |
|
string uploadFilter = string.Empty; |
|
if (_maybeSubtitleInputFile.Map(s => s.IsImageBased).IfNone(false) == false && |
|
(_ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Vaapi || |
|
_ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.VideoToolbox && |
|
_currentState.VideoFormat == VideoFormat.Hevc)) |
|
{ |
|
uploadFilter = new HardwareUploadFilter(_ffmpegState).Filter; |
|
} |
|
|
|
if (!string.IsNullOrWhiteSpace(uploadFilter)) |
|
{ |
|
uploadFilter = "," + uploadFilter; |
|
} |
|
|
|
watermarkOverlayFilterComplex = $"{tempVideoLabel}{watermarkLabel}{overlayFilter.Filter}{uploadFilter}[vf]"; |
|
|
|
// change the mapped label |
|
videoLabel = "[vf]"; |
|
} |
|
} |
|
} |
|
|
|
foreach (SubtitleInputFile subtitleInputFile in _maybeSubtitleInputFile.Filter(s => !s.Copy)) |
|
{ |
|
int inputIndex = distinctPaths.IndexOf(subtitleInputFile.Path); |
|
foreach ((int index, _, _) in subtitleInputFile.Streams) |
|
{ |
|
subtitleLabel = $"{inputIndex}:{index}"; |
|
if (subtitleInputFile.FilterSteps.Any(f => !string.IsNullOrWhiteSpace(f.Filter))) |
|
{ |
|
subtitleFilterComplex += $"[{inputIndex}:{index}]"; |
|
subtitleFilterComplex += string.Join( |
|
",", |
|
subtitleInputFile.FilterSteps.Select(f => f.Filter).Filter(s => !string.IsNullOrWhiteSpace(s))); |
|
subtitleLabel = "[st]"; |
|
subtitleFilterComplex += subtitleLabel; |
|
} |
|
else |
|
{ |
|
subtitleLabel = $"[{subtitleLabel}]"; |
|
} |
|
|
|
IPipelineFilterStep overlayFilter = |
|
AvailableSubtitleOverlayFilters.ForAcceleration(_ffmpegState.HardwareAccelerationMode); |
|
|
|
if (overlayFilter.Filter != string.Empty) |
|
{ |
|
string tempVideoLabel = string.IsNullOrWhiteSpace(videoFilterComplex) && |
|
string.IsNullOrWhiteSpace(watermarkFilterComplex) |
|
? $"[{videoLabel}]" |
|
: videoLabel; |
|
|
|
// vaapi uses software overlay and needs to upload |
|
// videotoolbox seems to require a hwupload for hevc |
|
string uploadFilter = string.Empty; |
|
if (_ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.Vaapi |
|
|| _ffmpegState.HardwareAccelerationMode == HardwareAccelerationMode.VideoToolbox && |
|
_currentState.VideoFormat == VideoFormat.Hevc) |
|
{ |
|
uploadFilter = new HardwareUploadFilter(_ffmpegState).Filter; |
|
} |
|
|
|
if (!string.IsNullOrWhiteSpace(uploadFilter)) |
|
{ |
|
uploadFilter = "," + uploadFilter; |
|
} |
|
|
|
subtitleOverlayFilterComplex = |
|
$"{tempVideoLabel}{subtitleLabel}{overlayFilter.Filter}{uploadFilter}[vst]"; |
|
|
|
// change the mapped label |
|
videoLabel = "[vst]"; |
|
} |
|
} |
|
} |
|
|
|
if (!string.IsNullOrWhiteSpace(audioFilterComplex) || !string.IsNullOrWhiteSpace(videoFilterComplex)) |
|
{ |
|
var filterComplex = string.Join( |
|
";", |
|
new[] |
|
{ |
|
audioFilterComplex, |
|
videoFilterComplex, |
|
watermarkFilterComplex, |
|
subtitleFilterComplex, |
|
watermarkOverlayFilterComplex, |
|
subtitleOverlayFilterComplex |
|
}.Where( |
|
s => !string.IsNullOrWhiteSpace(s))); |
|
|
|
result.AddRange(new[] { "-filter_complex", filterComplex }); |
|
} |
|
|
|
result.AddRange(new[] { "-map", audioLabel, "-map", videoLabel }); |
|
|
|
foreach (SubtitleInputFile subtitleInputFile in _maybeSubtitleInputFile.Filter(s => s.Copy)) |
|
{ |
|
int inputIndex = distinctPaths.IndexOf(subtitleInputFile.Path); |
|
foreach ((int index, _, _) in subtitleInputFile.Streams) |
|
{ |
|
subtitleLabel = $"{inputIndex}:{index}"; |
|
result.AddRange(new[] { "-map", subtitleLabel }); |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>(); |
|
public IList<string> GlobalOptions => Array.Empty<string>(); |
|
public IList<string> InputOptions(InputFile inputFile) => Array.Empty<string>(); |
|
public IList<string> FilterOptions => Arguments(); |
|
public IList<string> OutputOptions => Array.Empty<string>(); |
|
public FrameState NextState(FrameState currentState) => currentState; |
|
}
|
|
|