public Command ConvertToPng(string ffmpegPath, string inputFile, string outputFile)
    {
        Process process = new FFmpegProcessBuilder(ffmpegPath, false, _logger)
                          .WithThreads(1)
                          .WithQuiet()
                          .WithInput(inputFile)
                          .WithOutputFormat("apng", outputFile)
                          .Build();

        return(Cli.Wrap(process.StartInfo.FileName)
               .WithArguments(process.StartInfo.ArgumentList)
               .WithValidation(CommandResultValidation.None)
               .WithEnvironmentVariables(process.StartInfo.Environment.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))
               .WithStandardErrorPipe(PipeTarget.ToStream(Stream.Null)));
    }
    public Command WrapSegmenter(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host)
    {
        FFmpegPlaybackSettings playbackSettings = _playbackSettingsCalculator.ConcatSettings;

        Process process = new FFmpegProcessBuilder(ffmpegPath, saveReports, _logger)
                          .WithThreads(1)
                          .WithQuiet()
                          .WithFormatFlags(playbackSettings.FormatFlags)
                          .WithRealtimeOutput(true)
                          .WithInput($"http://localhost:{Settings.ListenPort}/iptv/channel/{channel.Number}.m3u8?mode=segmenter")
                          .WithMap("0")
                          .WithCopyCodec()
                          .WithMetadata(channel, None)
                          .WithFormat("mpegts")
                          .WithPipe()
                          .Build();

        return(Cli.Wrap(process.StartInfo.FileName)
               .WithArguments(process.StartInfo.ArgumentList)
               .WithValidation(CommandResultValidation.None)
               .WithEnvironmentVariables(process.StartInfo.Environment.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))
               .WithStandardErrorPipe(PipeTarget.ToStream(Stream.Null)));
    }
    public async Task <Command> ForError(
        string ffmpegPath,
        Channel channel,
        Option <TimeSpan> duration,
        string errorMessage,
        bool hlsRealtime,
        long ptsOffset)
    {
        FFmpegPlaybackSettings playbackSettings =
            _playbackSettingsCalculator.CalculateErrorSettings(channel.FFmpegProfile);

        IDisplaySize desiredResolution = channel.FFmpegProfile.Resolution;

        var fontSize = (int)Math.Round(channel.FFmpegProfile.Resolution.Height / 20.0);
        var margin   = (int)Math.Round(channel.FFmpegProfile.Resolution.Height * 0.05);

        string subtitleFile = await new SubtitleBuilder(_tempFilePool)
                              .WithResolution(desiredResolution)
                              .WithFontName("Roboto")
                              .WithFontSize(fontSize)
                              .WithAlignment(2)
                              .WithMarginV(margin)
                              .WithPrimaryColor("&HFFFFFF")
                              .WithFormattedContent(errorMessage.Replace(Environment.NewLine, "\\N"))
                              .BuildFile();

        var videoStream = new MediaStream {
            Index = 0
        };
        var audioStream = new MediaStream {
            Index = 0
        };

        string videoCodec = playbackSettings.VideoFormat switch
        {
            FFmpegProfileVideoFormat.Hevc => "libx265",
            FFmpegProfileVideoFormat.Mpeg2Video => "mpeg2video",
            _ => "libx264"
        };

        string audioCodec = playbackSettings.AudioFormat switch
        {
            FFmpegProfileAudioFormat.Ac3 => "ac3",
            _ => "aac"
        };

        FFmpegProcessBuilder builder = new FFmpegProcessBuilder(ffmpegPath, false, _logger)
                                       .WithThreads(1)
                                       .WithQuiet()
                                       .WithFormatFlags(playbackSettings.FormatFlags)
                                       .WithRealtimeOutput(playbackSettings.RealtimeOutput)
                                       .WithLoopedImage(Path.Combine(FileSystemLayout.ResourcesCacheFolder, "background.png"))
                                       .WithLibavfilter()
                                       .WithInput("anullsrc")
                                       .WithSubtitleFile(subtitleFile)
                                       .WithFilterComplex(
            videoStream,
            audioStream,
            Path.Combine(FileSystemLayout.ResourcesCacheFolder, "background.png"),
            "fake-audio-path",
            playbackSettings.VideoFormat)
                                       .WithPixfmt("yuv420p")
                                       .WithPlaybackArgs(playbackSettings, videoCodec, audioCodec)
                                       .WithMetadata(channel, None);

        await duration.IfSomeAsync(d => builder = builder.WithDuration(d));

        Process process = channel.StreamingMode switch
        {
            // HLS needs to segment and generate playlist
            StreamingMode.HttpLiveStreamingSegmenter =>
            builder.WithHls(
                channel.Number,
                None,
                ptsOffset,
                playbackSettings.VideoTrackTimeScale,
                playbackSettings.FrameRate)
            .Build(),
            _ => builder.WithFormat("mpegts")
            .WithPipe()
            .Build()
        };

        return(Cli.Wrap(process.StartInfo.FileName)
               .WithArguments(process.StartInfo.ArgumentList)
               .WithValidation(CommandResultValidation.None)
               .WithEnvironmentVariables(process.StartInfo.Environment.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))
               .WithStandardErrorPipe(PipeTarget.ToStream(Stream.Null)));
    }
    public async Task <Either <BaseError, string> > GenerateSongImage(
        string ffmpegPath,
        string ffprobePath,
        Option <string> subtitleFile,
        Channel channel,
        Option <ChannelWatermark> playoutItemWatermark,
        Option <ChannelWatermark> globalWatermark,
        MediaVersion videoVersion,
        string videoPath,
        bool boxBlur,
        Option <string> watermarkPath,
        WatermarkLocation watermarkLocation,
        int horizontalMarginPercent,
        int verticalMarginPercent,
        int watermarkWidthPercent,
        CancellationToken cancellationToken)
    {
        try
        {
            string outputFile = _tempFilePool.GetNextTempFile(TempFileCategory.SongBackground);

            MediaStream videoStream = await _ffmpegStreamSelector.SelectVideoStream(videoVersion);

            Option <ChannelWatermark> watermarkOverride =
                videoVersion is FallbackMediaVersion or CoverArtMediaVersion
                    ? new ChannelWatermark
            {
                Mode = ChannelWatermarkMode.Permanent,
                HorizontalMarginPercent = horizontalMarginPercent,
                VerticalMarginPercent   = verticalMarginPercent,
                Location     = watermarkLocation,
                Size         = WatermarkSize.Scaled,
                WidthPercent = watermarkWidthPercent,
                Opacity      = 100
            }
                    : None;

            Option <WatermarkOptions> watermarkOptions =
                await GetWatermarkOptions(
                    ffprobePath,
                    channel,
                    playoutItemWatermark,
                    globalWatermark,
                    videoVersion,
                    watermarkOverride,
                    watermarkPath);

            FFmpegPlaybackSettings playbackSettings =
                _playbackSettingsCalculator.CalculateErrorSettings(channel.FFmpegProfile);

            FFmpegPlaybackSettings scalePlaybackSettings = _playbackSettingsCalculator.CalculateSettings(
                StreamingMode.TransportStream,
                channel.FFmpegProfile,
                videoVersion,
                videoStream,
                None,
                DateTimeOffset.UnixEpoch,
                DateTimeOffset.UnixEpoch,
                TimeSpan.Zero,
                TimeSpan.Zero,
                false,
                Option <int> .None);

            FFmpegProcessBuilder builder = new FFmpegProcessBuilder(ffmpegPath, false, _logger)
                                           .WithThreads(1)
                                           .WithQuiet()
                                           .WithFormatFlags(playbackSettings.FormatFlags)
                                           .WithSongInput(videoPath, videoStream.Codec, videoStream.PixelFormat, boxBlur)
                                           .WithWatermark(watermarkOptions, None, channel.FFmpegProfile.Resolution)
                                           .WithSubtitleFile(subtitleFile);

            foreach (IDisplaySize scaledSize in scalePlaybackSettings.ScaledSize)
            {
                builder = builder.WithScaling(scaledSize);

                if (NeedToPad(channel.FFmpegProfile.Resolution, scaledSize))
                {
                    builder = builder.WithBlackBars(channel.FFmpegProfile.Resolution);
                }
            }

            using Process process = builder
                                    .WithFilterComplex(
                      videoStream,
                      None,
                      videoPath,
                      None,
                      playbackSettings.VideoFormat)
                                    .WithOutputFormat("apng", outputFile)
                                    .Build();

            _logger.LogInformation(
                "ffmpeg song arguments {FFmpegArguments}",
                string.Join(" ", process.StartInfo.ArgumentList));

            await Cli.Wrap(process.StartInfo.FileName)
            .WithArguments(process.StartInfo.ArgumentList)
            .WithValidation(CommandResultValidation.None)
            .ExecuteAsync(cancellationToken);

            return(outputFile);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Error generating song image");
            _client.Notify(ex);
            return(Left(BaseError.New(ex.Message)));
        }
    }