using ErsatzTV.FFmpeg.Format; using ErsatzTV.FFmpeg.State; using Microsoft.Extensions.Logging; namespace ErsatzTV.FFmpeg.Filter; public class OverlayWatermarkFilter : BaseFilter { private readonly ILogger _logger; private readonly IPixelFormat _outputPixelFormat; private readonly FrameSize _resolution; private readonly FrameSize _squarePixelFrameSize; private readonly WatermarkState _watermarkState; public OverlayWatermarkFilter( WatermarkState watermarkState, FrameSize resolution, FrameSize squarePixelFrameSize, IPixelFormat outputPixelFormat, ILogger logger) { _watermarkState = watermarkState; _resolution = resolution; _squarePixelFrameSize = squarePixelFrameSize; _outputPixelFormat = outputPixelFormat; _logger = logger; } public override string Filter => $"overlay={Position}:format={(_outputPixelFormat.BitDepth == 10 ? '1' : '0')}"; protected string Position { get { (double horizontalMargin, double verticalMargin) = _watermarkState.PlaceWithinSourceContent ? SourceContentMargins() : NormalMargins(); return _watermarkState.Location switch { // TODO: can these be pre-calculated (and used with accelerated overlay filters) WatermarkLocation.BottomLeft => $"x={horizontalMargin}:y=H-h-{verticalMargin}", WatermarkLocation.TopLeft => $"x={horizontalMargin}:y={verticalMargin}", WatermarkLocation.TopRight => $"x=W-w-{horizontalMargin}:y={verticalMargin}", WatermarkLocation.TopMiddle => $"x=(W-w)/2:y={verticalMargin}", WatermarkLocation.RightMiddle => $"x=W-w-{horizontalMargin}:y=(H-h)/2", WatermarkLocation.BottomMiddle => $"x=(W-w)/2:y=H-h-{verticalMargin}", WatermarkLocation.LeftMiddle => $"x={horizontalMargin}:y=(H-h)/2", _ => $"x=W-w-{horizontalMargin}:y=H-h-{verticalMargin}" }; } } public override FrameState NextState(FrameState currentState) => currentState with { FrameDataLocation = FrameDataLocation.Software }; private WatermarkMargins NormalMargins() { double horizontalMargin = Math.Round(_watermarkState.HorizontalMarginPercent / 100.0 * _resolution.Width); double verticalMargin = Math.Round(_watermarkState.VerticalMarginPercent / 100.0 * _resolution.Height); return new WatermarkMargins(horizontalMargin, verticalMargin); } private WatermarkMargins SourceContentMargins() { int horizontalPadding = _resolution.Width - _squarePixelFrameSize.Width; int verticalPadding = _resolution.Height - _squarePixelFrameSize.Height; _logger.LogDebug("Resolution: {Width}x{Height}", _resolution.Width, _resolution.Height); _logger.LogDebug("Square Pix: {Width}x{Height}", _squarePixelFrameSize.Width, _squarePixelFrameSize.Height); double horizontalMargin = Math.Round( _watermarkState.HorizontalMarginPercent / 100.0 * _squarePixelFrameSize.Width + horizontalPadding / 2.0); double verticalMargin = Math.Round( _watermarkState.VerticalMarginPercent / 100.0 * _squarePixelFrameSize.Height + verticalPadding / 2.0); return new WatermarkMargins(horizontalMargin, verticalMargin); } private sealed record WatermarkMargins(double HorizontalMargin, double VerticalMargin); }