Esempio n. 1
0
        protected override FFmpegJob Map(TranscodeJob job, FFmpegConfig config)
        {
            var result = base.Map(job, config);

            result.Format   = GetFormatName(job.Format);
            result.Metadata = job.Metadata;

            // This is a workaround for subtitle overlays with MKV reporting an incorrect duration
            if ((job.Format == ContainerFormat.Mkv) && (job.HardSubtitles?.SourceStreamIndex != null))
            {
                result.Duration = job.SourceInfo.Duration;
            }

            var trueHdStreams =
                from o in job.Streams
                where o.GetType() == typeof(OutputStream)
                join s in job.SourceInfo.Streams.OfType <AudioStreamInfo>() on o.SourceStreamIndex equals s.Index
                where s.Format == AudioFormat.DolbyTrueHd
                select o;

            if (trueHdStreams.Any())
            {
                result.MaxMuxingQueueSize = 1024;
            }

            return(result);
        }
Esempio n. 2
0
        protected virtual IList <MappedStream> MapStreams(FFmpegConfig config, TranscodeJob job)
        {
            IList <MappedStream>          result = new List <MappedStream>();
            IDictionary <int, StreamInfo> sourceStreamsByIndex = job.SourceInfo.Streams.ToDictionary(s => s.Index);

            for (int i = 0; i < job.Streams.Count; i++)
            {
                OutputStream outputStream = job.Streams[i];
                StreamInfo   sourceStream;

                if (!sourceStreamsByIndex.TryGetValue(outputStream.SourceStreamIndex, out sourceStream))
                {
                    throw new ArgumentException(
                              $"{nameof(job.SourceInfo)} does not contain a stream with index {outputStream.SourceStreamIndex}.",
                              nameof(job.SourceInfo));
                }

                MappedStream mappedStream = MapStream(config, sourceStream, outputStream);

                if (mappedStream != null)
                {
                    result.Add(mappedStream);
                }
            }

            return(result);
        }
Esempio n. 3
0
 protected virtual void AddTonemapFilters(IList <IFilter> filters, FFmpegConfig config)
 {
     filters.Add(new Filter("zscale")
     {
         Options = new Option[]
         {
             new Option("t", "linear"),
             new Option("npl", "100")
         }
     });
     filters.Add(GetFormatFilter("gbrpf32le"));
     filters.Add(new Filter("zscale")
     {
         Options = new Option[]
         {
             new Option("p", "bt709")
         }
     });
     filters.Add(GetTonemapFilter(config));
     filters.Add(new Filter("zscale")
     {
         Options = new Option[]
         {
             new Option("t", "bt709"),
             new Option("m", "bt709"),
             new Option("r", "tv")
         }
     });
     filters.Add(GetFormatFilter("yuv420p"));
 }
Esempio n. 4
0
        public object Get()
        {
            // RODAR antes: apt-get install ffmpeg

            FFmpegConfig.SetDirectories("ffmpeg", "ffprobe", "/tmp/");

            var location = "input";

            var bytes = System.IO.File.ReadAllBytes(location);

            System.IO.File.WriteAllBytes("/tmp/input.mp4", bytes);

            var input = FFmpegProcess.GetInfo(location);

            return(new { FFmpegResult = input, Locations = Directory.GetFiles("/tmp/") });

            //if (input.Streams.Any(p => p.codec_type.Equals("video")))
            //{
            //    var destinationthumb = Path.ChangeExtension(file, "jpg");
            //    FFmpegProcess.GetThumbNail(file, destinationthumb);
            //}
            //else if (input.Streams.Any(p => p.codec_type.Equals("audio")))
            //{
            //    Console.WriteLine($"Duration: {input.Format.durationTs}");
            //}
        }
        public void Setup()
        {
            _fileSystem       = Substitute.For <IFileSystem>();
            _fileService      = Substitute.For <IFile>();
            _directoryService = Substitute.For <IDirectory>();
            _serializer       = Substitute.For <ISerializer <string> >();
            _userDirectory    = Guid.NewGuid().ToString();
            _userFileName     = Guid.NewGuid().ToString();
            _defaultFileName  = Guid.NewGuid().ToString();
            _configManager    = new FFmpegConfigManager(_fileSystem, _serializer, _defaultFileName, _userFileName);

            _userConfig    = new FFmpegConfig();
            _defaultConfig = new FFmpegConfig();

            var userText    = Guid.NewGuid().ToString();
            var defaultText = Guid.NewGuid().ToString();

            _fileSystem.File.Returns(_fileService);
            _fileSystem.Directory.Returns(_directoryService);
            _fileService.Exists(Arg.Any <string>()).Returns(true);
            _fileService.ReadAllText(_userFileName).Returns(userText);
            _fileService.ReadAllText(_defaultFileName).Returns(defaultText);
            _directoryService.Exists(Arg.Any <string>()).Returns(true);
            _serializer.Deserialize <FFmpegConfig>(userText).Returns(_userConfig);
            _serializer.Deserialize <FFmpegConfig>(defaultText).Returns(_defaultConfig);
        }
Esempio n. 6
0
 protected virtual MappedVideoStream MapVideoStream(FFmpegConfig config,
                                                    VideoStreamInfo sourceStream,
                                                    VideoOutputStream outputStream)
 {
     return(new MappedVideoStream()
     {
         Input = GetStreamInput(sourceStream),
     });
 }
Esempio n. 7
0
 protected virtual MappedStream MapSubtitleStream(FFmpegConfig config,
                                                  SubtitleStreamInfo sourceStream,
                                                  SubtitleOutputStream outputStream)
 {
     return(new MappedStream(StreamType.Subtitle)
     {
         Input = GetStreamInput(sourceStream),
         Codec = new Codec(GetSubtitleCodecName(config, outputStream.Format))
     });
 }
Esempio n. 8
0
        protected virtual Codec GetVideoCodec(FFmpegConfig config,
                                              VideoStreamInfo sourceStream,
                                              VideoOutputStream outputStream)
        {
            VideoFormat format    = outputStream.Format;
            VideoCodec  codec     = config?.Video?.Codecs.GetValueOrDefault(format);
            string      codecName = GetVideoCodecName(format);
            X26xCodec   result    = format == VideoFormat.Hevc ? new X265Codec(codecName) : new X26xCodec(codecName);

            result.Preset = codec?.Preset;
            result.Crf    = outputStream.Quality;

            if (outputStream.DynamicRange == DynamicRange.High)
            {
                if (outputStream.Format != VideoFormat.Hevc)
                {
                    throw new NotSupportedException($"HDR is not supported with the video format {outputStream.Format}.");
                }

                var options = new List <Option>()
                {
                    new Option("colorprim", "bt2020"),
                    new Option("colormatrix", "bt2020nc"),
                    new Option("transfer", "smpte2084")
                };

                if (outputStream.CopyHdrMetadata)
                {
                    if (sourceStream.MasterDisplayProperties != null)
                    {
                        var properties = sourceStream.MasterDisplayProperties;
                        var value      = string.Format("\"G{0}B{1}R{2}WP{3}L({4},{5})\"",
                                                       properties.Green,
                                                       properties.Blue,
                                                       properties.Red,
                                                       properties.WhitePoint,
                                                       properties.Luminance.Max,
                                                       properties.Luminance.Min);

                        options.Add(new Option("master-display", value));
                    }

                    if (sourceStream.LightLevelProperties != null)
                    {
                        var properties = sourceStream.LightLevelProperties;

                        options.Add(new Option("max-cll", $"\"{properties.MaxCll},{properties.MaxFall}\""));
                    }
                }

                ((X265Codec)result).Options = options;
            }

            return(result);
        }
Esempio n. 9
0
        protected override MappedVideoStream MapVideoStream(FFmpegConfig config,
                                                            VideoStreamInfo sourceStream,
                                                            VideoOutputStream outputStream)
        {
            var result = base.MapVideoStream(config, sourceStream, outputStream);

            result.Codec = GetVideoCodec(config, sourceStream, outputStream);
            result.Tag   = outputStream.Tag;

            return(result);
        }
Esempio n. 10
0
        string GetSubtitleCodecName(FFmpegConfig config, SubtitleFormat format)
        {
            string result = config?.Subtitles?.Codecs?.GetValueOrDefault(format)?.Name;

            if (result == null)
            {
                throw new NotSupportedException($"The subtitle format {format} is not supported.");
            }

            return(result);
        }
Esempio n. 11
0
        string GetAudioCodecName(FFmpegConfig config, AudioFormat format)
        {
            string result = config?.Audio?.Codecs?.GetValueOrDefault(format)?.Name;

            if (result == null)
            {
                throw new NotSupportedException($"The audio format {format} is not supported.");
            }

            return(result);
        }
Esempio n. 12
0
        protected override MappedStream MapStream(FFmpegConfig config,
                                                  StreamInfo sourceStream,
                                                  OutputStream outputStream)
        {
            var result = base.MapStream(config, sourceStream, outputStream);

            if (result == null)
            {
                switch (outputStream)
                {
                case AudioOutputStream audioOutput:
                    if (sourceStream is AudioStreamInfo audioInput)
                    {
                        result = MapAudioStream(config, audioInput, audioOutput);
                    }
                    else
                    {
                        throw GetStreamMismatchException(nameof(sourceStream), nameof(outputStream));
                    }
                    break;

                case SubtitleOutputStream subtitleOutput:
                    if (sourceStream is SubtitleStreamInfo subtitleInput)
                    {
                        result = MapSubtitleStream(config, subtitleInput, subtitleOutput);
                    }
                    else
                    {
                        throw GetStreamMismatchException(nameof(sourceStream), nameof(outputStream));
                    }
                    break;

                default:
                    result = MapPassthruStream(sourceStream, outputStream);
                    break;
                }
            }

            if (result != null)
            {
                result.Metadata = outputStream.Metadata;

                if (outputStream.IsDefault.HasValue)
                {
                    result.Disposition = outputStream.IsDefault.Value ? "default" : "0";
                }
            }

            return(result);
        }
Esempio n. 13
0
        protected virtual MappedStream MapStream(FFmpegConfig config,
                                                 StreamInfo sourceStream,
                                                 OutputStream outputStream)
        {
            if (sourceStream is VideoStreamInfo videoSource)
            {
                if (outputStream is VideoOutputStream videoOutput)
                {
                    return(MapVideoStream(config, videoSource, videoOutput));
                }

                throw GetStreamMismatchException(nameof(sourceStream), nameof(outputStream));
            }

            return(null);
        }
        public void MapAddsDenoiseFilter()
        {
            var config = new FFmpegConfig()
            {
                Video = new VideoConfig()
                {
                    DenoiseOptions = "hqdn3d"
                }
            };

            _videoOutput.Denoise = true;

            var ffmpegJob = _jobRunner.CallMap(_transcodeJob, config);

            Assert.AreEqual(1, ffmpegJob.Filters?.Count);

            var filter = ffmpegJob.Filters[0] as CustomFilter;

            Assert.IsNotNull(filter);
            Assert.AreEqual(config.Video.DenoiseOptions, filter.Data);
        }
Esempio n. 15
0
        protected virtual MappedAudioStream MapAudioStream(FFmpegConfig config,
                                                           AudioStreamInfo sourceStream,
                                                           AudioOutputStream outputStream)
        {
            var result = new MappedAudioStream()
            {
                Input = GetStreamInput(sourceStream),
                Codec = new Codec(GetAudioCodecName(config, outputStream.Format))
            };

            if (outputStream.Mixdown.HasValue)
            {
                result.ChannelCount = AudioUtility.GetChannelCount(outputStream.Mixdown.Value);
            }

            if (outputStream.Quality.HasValue)
            {
                result.Bitrate = $"{outputStream.Quality:0}k";
            }

            return(result);
        }
Esempio n. 16
0
        public async Task <CropParameters> Detect(MediaInfo mediaInfo)
        {
            if (mediaInfo == null)
            {
                throw new ArgumentNullException(nameof(mediaInfo));
            }
            if (string.IsNullOrWhiteSpace(mediaInfo.FileName))
            {
                throw new ArgumentException($"{nameof(mediaInfo)}.FileName must not be empty or whitespace.", nameof(mediaInfo));
            }
            if (mediaInfo.Duration <= TimeSpan.Zero)
            {
                throw new ArgumentException($"{nameof(mediaInfo)}.Duration is invalid.", nameof(mediaInfo));
            }

            CropParameters       result    = null;
            IEnumerable <double> positions = GetSeekSeconds(mediaInfo.Duration);
            FFmpegConfig         config    = _configManager.Config;
            string options = string.Empty;

            if (!string.IsNullOrWhiteSpace(config?.Video?.CropDetectOptions))
            {
                options = "=" + config.Video.CropDetectOptions;
            }

            var lockTarget = new object();
            int?minX = null, minY = null, maxWidth = null, maxHeight = null;

            var tasks = positions.Select(async seconds =>
            {
                var job = new FFmpegJob()
                {
                    HideBanner    = true,
                    StartTime     = TimeSpan.FromSeconds(seconds),
                    InputFileName = mediaInfo.FileName,
                    FrameCount    = 2,
                    Filters       = new IFilter[]
                    {
                        new CustomFilter($"cropdetect{options}")
                    }
                };
                var arguments = _argumentGenerator.GenerateArguments(job);

                try
                {
                    var processResult = await _processRunner.Run(_ffmpegFileName, arguments, _timeout);

                    //The crop detection data is written to standard error.
                    if (!string.IsNullOrWhiteSpace(processResult.ErrorData))
                    {
                        var crop = Parse(processResult.ErrorData);

                        if (crop != null)
                        {
                            lock (lockTarget)
                            {
                                minX      = minX.HasValue ? Math.Min(crop.Start.X, minX.Value) : crop.Start.X;
                                minY      = minY.HasValue ? Math.Min(crop.Start.Y, minY.Value) : crop.Start.Y;
                                maxWidth  = maxWidth.HasValue ? Math.Max(crop.Size.Width, maxWidth.Value) : crop.Size.Width;
                                maxHeight = maxHeight.HasValue ? Math.Max(crop.Size.Height, maxHeight.Value) : crop.Size.Height;
                            }
                        }
                    }
                    else
                    {
                        Trace.WriteLine("No ffmpeg data on stderr to parse.");
                    }
                }
                catch (ArgumentException ex)
                {
                    Trace.WriteLine(ex.Message);
                    Debug.WriteLine(ex.StackTrace);
                }
                catch (InvalidOperationException ex)
                {
                    Trace.WriteLine(ex.Message);
                    Debug.WriteLine(ex.StackTrace);
                }
            });

            await Task.WhenAll(tasks);

            if (minX.HasValue && minY.HasValue && maxWidth.HasValue && maxHeight.HasValue)
            {
                result = new CropParameters()
                {
                    Start = new Coordinate <int>(minX.Value, minY.Value),
                    Size  = new Dimensions(maxWidth.Value, maxHeight.Value)
                };
            }

            return(result);
        }
            protected override FFmpegJob Map(TranscodeJob job, FFmpegConfig config)
            {
                ConfigPassed = config;

                return(JobToMap ?? base.Map(job, config));
            }
Esempio n. 18
0
        public async Task TestDetect()
        {
            const string ARGS = "generated arguments";

            var processRunner      = Substitute.For <IProcessRunner>();
            var timeout            = TimeSpan.FromMilliseconds(10);
            var ffmpegFileName     = "/usr/sbin/ffmpeg";
            var config             = new FFmpegConfig();
            var configManager      = Substitute.For <IConfigManager <FFmpegConfig> >();
            var argumentGenerator  = Substitute.For <IFFmpegArgumentGenerator>();
            var detector           = new CropDetector(ffmpegFileName, processRunner, configManager, argumentGenerator, timeout);
            IList <FFmpegJob> jobs = new List <FFmpegJob>();

            configManager.Config = config;
            argumentGenerator.GenerateArguments(Arg.Any <FFmpegJob>()).Returns(ARGS);
            argumentGenerator.When(x => x.GenerateArguments(Arg.Any <FFmpegJob>()))
            .Do(x => jobs.Add(x[0] as FFmpegJob));

            #region Test Exceptions

            await Assert.ThrowsExceptionAsync <ArgumentNullException>(async() => await detector.Detect(null));

            await Assert.ThrowsExceptionAsync <ArgumentException>(async() => await detector.Detect(new MediaInfo()
            {
                FileName = "test"
            }));

            #endregion

            #region Test video that has bars and exceeds max seek time

            var mediaInfo = new MediaInfo()
            {
                FileName = "/Users/fred/Documents/video.mkv",
                Duration = TimeSpan.FromMinutes(104)
            };
            var outputs = new string[]
            {
                "[Parsed_cropdetect_0 @ 0x7fce49600000] x1:0 x2:3839 y1:277 y2:1882 w:3840 h:1600 x:0 y:280 pts:102 t:0.102000 crop=3840:1600:0:280",
                "[Parsed_cropdetect_0 @ 0x7f8bf045da80] x1:859 x2:3839 y1:277 y2:1882 w:2976 h:1600 x:862 y:280 pts:120 t:0.120000 crop=2976:1600:862:280",
                "[Parsed_cropdetect_0 @ 0x7f9437704a00] x1:0 x2:3821 y1:277 y2:1774 w:3808 h:1488 x:8 y:282 pts:97 t:0.097000 crop=3808:1488:8:282",
                "[Parsed_cropdetect_0 @ 0x7f9032448880] x1:0 x2:3423 y1:277 y2:1882 w:3424 h:1600 x:0 y:280 pts:115 t:0.115000 crop=3424:1600:0:280",
                "[Parsed_cropdetect_0 @ 0x7ff79975be00] x1:1055 x2:3839 y1:277 y2:1882 w:2784 h:1600 x:1056 y:280 pts:91 t:0.091000 crop=2784:1600:1056:280"
            };
            int i = 0;

            processRunner.Run(ffmpegFileName, ARGS, timeout)
            .Returns(new ProcessResult()
            {
                ErrorData = outputs[i++]
            });

            CropParameters parameters = await detector.Detect(mediaInfo);

            Assert.AreEqual(5, jobs.Count);

            for (i = 0; i < jobs.Count; i++)
            {
                var job = jobs[i];

                Assert.IsNotNull(job);
                Assert.IsTrue(job.HideBanner);
                Assert.AreEqual(2, job.FrameCount);
                Assert.AreEqual(mediaInfo.FileName, job.InputFileName);
                Assert.AreEqual(TimeSpan.FromMinutes(i + 1), job.StartTime);
                Assert.AreEqual("cropdetect", (job.Filters.FirstOrDefault() as CustomFilter)?.Data);
            }

            Assert.IsNotNull(parameters);
            Assert.AreEqual(new Coordinate <int>(0, 280), parameters.Start);
            Assert.AreEqual(new Dimensions(3840, 1600), parameters.Size);

            #endregion

            #region Test video that does not have bars

            mediaInfo = new MediaInfo()
            {
                FileName = "/Users/fred/Documents/video2.mkv",
                Duration = TimeSpan.FromMinutes(1)
            };
            var output = @"
                Stream #0:0(eng): Video: hevc (Main 10), yuv420p10le(tv, bt2020nc/bt2020/smpte2084), 3840x2160 [SAR 1:1 DAR 16:9], 23.98 fps, 23.98 tbr, 1k tbn, 23.98 tbc
                Metadata:
                  BPS-eng         : 42940118
                  DURATION-eng    : 00:01:00
                  NUMBER_OF_FRAMES-eng: 75098
                  NUMBER_OF_BYTES-eng: 16812194126
                  SOURCE_ID-eng   : 001011
                  _STATISTICS_WRITING_DATE_UTC-eng: 2019-06-06 00:00:24
                  _STATISTICS_TAGS-eng: BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES SOURCE_ID
                [Parsed_cropdetect_0 @ 0x7ff6cce00a80] x1:0 x2:3839 y1:0 y2:2159 w:3840 h:2160 x:0 y:0 pts:91 t:0.091000 crop=3840:2160:0:0
                frame=    2 fps=0.0 q=-0.0 Lsize=N/A time=00:00:00.63 bitrate=N/A speed=1.87x";

            processRunner.Run(ffmpegFileName, ARGS, timeout)
            .Returns(new ProcessResult()
            {
                ErrorData = output
            });
            jobs.Clear();

            parameters = await detector.Detect(mediaInfo);

            Assert.AreEqual(5, jobs.Count);

            for (i = 0; i < jobs.Count; i++)
            {
                var job = jobs[i];

                Assert.AreEqual(TimeSpan.FromSeconds((i + 1) * 6), job?.StartTime);
            }

            Assert.IsNotNull(parameters);
            Assert.AreEqual(new Coordinate <int>(0, 0), parameters.Start);
            Assert.AreEqual(new Dimensions(3840, 2160), parameters.Size);

            #endregion

            #region Test that config is used

            config.Video = new VideoConfig()
            {
                CropDetectOptions = "24:16:0"
            };

            processRunner.Run(ffmpegFileName, ARGS, timeout)
            .Returns(new ProcessResult()
            {
                ErrorData = output
            });
            jobs.Clear();
            parameters = await detector.Detect(mediaInfo);

            foreach (var job in jobs)
            {
                Assert.AreEqual($"cropdetect={config.Video.CropDetectOptions}",
                                (job?.Filters?.FirstOrDefault() as CustomFilter)?.Data);
            }

            #endregion
        }
Esempio n. 19
0
        protected virtual FFmpegJob Map(TranscodeJob job, FFmpegConfig config)
        {
            if (job == null)
            {
                throw new ArgumentNullException(nameof(job));
            }

            if (job.SourceInfo == null)
            {
                throw new ArgumentException($"{nameof(job)}.{nameof(job.SourceInfo)} is null.", nameof(job));
            }

            if (string.IsNullOrWhiteSpace(job.SourceInfo.FileName))
            {
                throw new ArgumentException(
                          $"{nameof(job)}.{nameof(job.SourceInfo)}.{nameof(job.SourceInfo.FileName)} is null or empty.",
                          nameof(job));
            }

            if (job.SourceInfo.Streams?.Any() != true)
            {
                throw new ArgumentException(
                          $"{nameof(job)}.{nameof(job.SourceInfo)}.{nameof(job.SourceInfo.Streams)} is null or empty.",
                          nameof(job));
            }

            if (string.IsNullOrWhiteSpace(job.OutputFileName))
            {
                throw new ArgumentException($"{nameof(job)}.{nameof(job.OutputFileName)} is null or empty.", nameof(job));
            }

            if (job.Streams?.Any() != true)
            {
                throw new ArgumentException($"{nameof(job)}.{nameof(job.Streams)} is null or empty.", nameof(job));
            }

            var videoSource = job.SourceInfo.Streams.OfType <VideoStreamInfo>().FirstOrDefault();

            if (videoSource == null)
            {
                throw new NotSupportedException($"{nameof(job)}.{nameof(job.SourceInfo)} must contain a video stream.");
            }

            var result = new FFmpegJob()
            {
                HideBanner     = true,
                Overwrite      = true,
                InputFileName  = job.SourceInfo.FileName,
                OutputFileName = job.OutputFileName
            };

            SubtitleInfo subtitleInfo = null;

            if (job.HardSubtitles != null)
            {
                int i = 0;

                subtitleInfo = job.SourceInfo.Streams.OfType <SubtitleStreamInfo>()
                               .Select(s => new SubtitleInfo()
                {
                    AbsoluteIndex = s.Index,
                    RelativeIndex = i++,
                    SubtitleType  = s.SubtitleType,
                    FileName      = job.SourceInfo.FileName
                })
                               .FirstOrDefault(s =>
                                               s.AbsoluteIndex == job.HardSubtitles.SourceStreamIndex);

                if (subtitleInfo == null)
                {
                    throw new ArgumentException(
                              $"{nameof(job)}.{nameof(job.HardSubtitles)} contains an invalid index.", nameof(job));
                }

                if (job.HardSubtitles.ForcedOnly)
                {
                    result.ForcedSubtitlesOnly = true;
                }

                result.CanvasSize = videoSource.Dimensions;
            }

            result.Streams = MapStreams(config, job);

            var videoOutput = job.Streams.OfType <VideoOutputStream>()
                              .FirstOrDefault(s => s.SourceStreamIndex == videoSource.Index);

            if (videoOutput != null)
            {
                result.Filters = GetVideoFilters(config, videoSource, videoOutput, subtitleInfo);
            }

            return(result);
        }
Esempio n. 20
0
 protected virtual IFilter GetTonemapFilter(FFmpegConfig config)
 {
     return(new CustomFilter($"tonemap={config.Video.TonemapOptions}"));
 }
Esempio n. 21
0
 protected virtual IFilter GetDenoiseFilter(FFmpegConfig config)
 {
     return(new CustomFilter(config.Video.DenoiseOptions));
 }
Esempio n. 22
0
        protected virtual IList <IFilter> GetVideoFilters(FFmpegConfig config,
                                                          VideoStreamInfo sourceStream,
                                                          VideoOutputStream outputStream,
                                                          SubtitleInfo subtitleInfo)
        {
            var result = new List <IFilter>();

            if (subtitleInfo != null)
            {
                if (subtitleInfo.SubtitleType == SubtitleType.Graphic)
                {
                    const string SUB_LABEL = "sub";
                    const string REF_LABEL = "ref";

                    result.Add(GetScale2RefFilter(sourceStream, subtitleInfo.AbsoluteIndex, SUB_LABEL, REF_LABEL));
                    result.Add(GetOverlayFilter(sourceStream, REF_LABEL, SUB_LABEL));
                }
                else
                {
                    result.Add(GetSubtitlesFilter(subtitleInfo));
                }
            }

            bool setSampleAspectRatio = false;

            if ((outputStream.CropParameters != null) &&
                ((outputStream.CropParameters.Size.Width < sourceStream.Dimensions.Width) ||
                 (outputStream.CropParameters.Size.Height < sourceStream.Dimensions.Height)))
            {
                result.Add(GetCropFilter(outputStream.CropParameters));

                setSampleAspectRatio = true;
            }

            if (outputStream.ScaledDimensions.HasValue)
            {
                result.Add(GetScaleFilter(outputStream.ScaledDimensions.Value));

                setSampleAspectRatio = true;
            }

            if (setSampleAspectRatio)
            {
                result.Add(GetSampleAspectRatioFilter(1, 1));
            }

            if (outputStream.Deinterlace)
            {
                result.Add(GetDeinterlaceFilter(config));
            }

            if (outputStream.Denoise)
            {
                result.Add(GetDenoiseFilter(config));
            }

            if (outputStream.Tonemap)
            {
                AddTonemapFilters(result, config);
            }

            return(result);
        }
        public void MapAddsTonemapFilters()
        {
            var config = new FFmpegConfig()
            {
                Video = new VideoConfig()
                {
                    TonemapOptions = "hable"
                }
            };

            _videoOutput.Tonemap = true;

            var ffmpegJob = _jobRunner.CallMap(_transcodeJob, config);

            Assert.AreEqual(6, ffmpegJob.Filters?.Count);

            var filter = ffmpegJob.Filters[0] as Filter;

            Assert.IsNotNull(filter);
            Assert.AreEqual("zscale", filter.Name);
            Assert.AreEqual(2, filter.Options?.Count);

            var option = filter.Options[0];

            Assert.IsNotNull(option);
            Assert.AreEqual("t", option.Name);
            Assert.AreEqual("linear", option.Value);

            option = filter.Options[1];

            Assert.IsNotNull(option);
            Assert.AreEqual("npl", option.Name);
            Assert.AreEqual("100", option.Value);

            filter = ffmpegJob.Filters[1] as Filter;

            Assert.IsNotNull(filter);
            Assert.AreEqual("format", filter.Name);
            Assert.AreEqual(1, filter.Options?.Count);

            option = filter.Options[0];

            Assert.IsNotNull(option);
            Assert.IsNull(option.Name);
            Assert.AreEqual("gbrpf32le", option.Value);

            filter = ffmpegJob.Filters[2] as Filter;

            Assert.IsNotNull(filter);
            Assert.AreEqual("zscale", filter.Name);
            Assert.AreEqual(1, filter.Options?.Count);

            option = filter.Options[0];

            Assert.IsNotNull(option);
            Assert.AreEqual("p", option.Name);
            Assert.AreEqual("bt709", option.Value);

            var customfilter = ffmpegJob.Filters[3] as CustomFilter;

            Assert.IsNotNull(filter);
            Assert.AreEqual($"tonemap=hable", customfilter.Data);

            filter = ffmpegJob.Filters[4] as Filter;

            Assert.IsNotNull(filter);
            Assert.AreEqual("zscale", filter.Name);
            Assert.AreEqual(3, filter.Options?.Count);

            option = filter.Options[0];

            Assert.IsNotNull(option);
            Assert.AreEqual("t", option.Name);
            Assert.AreEqual("bt709", option.Value);

            option = filter.Options[1];

            Assert.IsNotNull(option);
            Assert.AreEqual("m", option.Name);
            Assert.AreEqual("bt709", option.Value);

            option = filter.Options[2];

            Assert.IsNotNull(option);
            Assert.AreEqual("r", option.Name);
            Assert.AreEqual("tv", option.Value);

            filter = ffmpegJob.Filters[5] as Filter;

            Assert.IsNotNull(filter);
            Assert.AreEqual("format", filter.Name);
            Assert.AreEqual(1, filter.Options?.Count);

            option = filter.Options[0];

            Assert.IsNotNull(option);
            Assert.IsNull(option.Name);
            Assert.AreEqual("yuv420p", option.Value);
        }
 public FFmpegJob CallMap(TranscodeJob job, FFmpegConfig config)
 {
     return(Map(job, config));
 }