Example #1
0
        public async Task <bool> 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));
            }

            var job = new FFmpegJob()
            {
                HideBanner    = true,
                StartTime     = TimeSpan.FromMilliseconds(mediaInfo.Duration.TotalMilliseconds / 2),
                InputFileName = mediaInfo.FileName,
                FrameCount    = FRAME_COUNT,
                Filters       = new IFilter[]
                {
                    new Filter("idet")
                }
            };
            var             arguments  = _argumentGenerator.GenerateArguments(job);
            FrameStatistics statistics = null;

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

                //The interlace detection data is written to standard error.
                if (!string.IsNullOrWhiteSpace(processResult.ErrorData))
                {
                    statistics = Parse(processResult.ErrorData);
                }
                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);
            }

            return(statistics != null &&
                   (statistics.TffCount + statistics.BffCount) > (statistics.ProgressiveCount + statistics.UndeterminedCount));
        }
        public void GenerateArgumentsCallsReflector()
        {
            var job = new FFmpegJob()
            {
                InputFileName = "test"
            };

            _generator.GenerateArguments(job);

            _reflector.Received().Reflect(job);
        }
        public void GenerateArgumentsUsesPlaceholderForMissingOutput()
        {
            var job = new FFmpegJob()
            {
                InputFileName = "test"
            };
            string actual = _generator.GenerateArguments(job);

            Assert.IsNotNull(actual);
            Assert.IsTrue(actual.EndsWith($"-f null -", StringComparison.InvariantCulture),
                          "The result did not end with the output placeholder.");
        }
        public async Task DetectGeneratesArguments()
        {
            FFmpegJob job = null;

            _argumentGenerator.When(x => x.GenerateArguments(Arg.Any <FFmpegJob>()))
            .Do(x => job = x[0] as FFmpegJob);

            await _detector.Detect(_mediaInfo);

            Assert.IsNotNull(job);
            Assert.AreEqual(TimeSpan.FromHours(1), job.StartTime);
            Assert.AreEqual(_mediaInfo.FileName, job.InputFileName);
            Assert.AreEqual(100, job.FrameCount);
            Assert.AreEqual(1, job.Filters?.Count);
            Assert.AreEqual("idet", (job.Filters[0] as Filter).Name);
        }
        public void GenerateArgumentsUsesArgumentConverter()
        {
            var job = new FFmpegJob()
            {
                InputFileName = "/Users/fred/Movies/movie.mkv"
            };

            _reflector.Reflect(job).Returns(new ArgumentProperty[]
            {
                new ArgumentProperty()
                {
                    Value     = job.InputFileName,
                    Converter = _mockConverter
                }
            });

            string actual = _generator.GenerateArguments(job);

            Assert.IsNotNull(actual);
            Assert.IsTrue(actual.Contains(job.InputFileName), "The result did not contain the converted value.");
        }
        public void GenerateArgumentsUsesArgumentName()
        {
            var job = new FFmpegJob()
            {
                InputFileName = "test"
            };

            _reflector.Reflect(job).Returns(new ArgumentProperty[]
            {
                new ArgumentProperty()
                {
                    ArgumentName = "-f",
                    Converter    = _mockConverter
                }
            });

            string actual = _generator.GenerateArguments(job);

            Assert.IsNotNull(actual);
            Assert.IsTrue(actual.Contains("-f"), "The result did not contain the argument name.");
        }
        public string GenerateArguments(FFmpegJob job)
        {
            if (job == null)
            {
                throw new ArgumentNullException(nameof(job));
            }

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

            IEnumerable <ArgumentProperty> properties = _reflector.Reflect(job);

            var builder = new StringBuilder();

            foreach (var property in properties)
            {
                if (builder.Length > 0)
                {
                    builder.Append(" ");
                }

                string argument = property?.Converter?.Convert(property?.ArgumentName, property?.Value);

                if (!string.IsNullOrWhiteSpace(argument))
                {
                    builder.Append(argument);
                }
            }

            if (string.IsNullOrWhiteSpace(job.OutputFileName) && string.IsNullOrWhiteSpace(job.Format))
            {
                // Some jobs like crop or interlace detection won't write to a file,
                // but FFmpeg still requires the output argument
                builder.Append(" -f null -");
            }

            return(builder?.ToString());
        }
Example #8
0
        public void Setup()
        {
            _ffmpegFileName    = "usr/sbin/ffmpeg";
            _process           = Substitute.For <IProcess>();
            _argumentGenerator = Substitute.For <IFFmpegArgumentGenerator>();
            _configManager     = Substitute.For <IConfigManager <FFmpegConfig> >();
            _transcoder        = new MediaTranscoder(_ffmpegFileName,
                                                     () => _process,
                                                     _configManager,
                                                     _argumentGenerator);

            _argumentGenerator.When(x => x.GenerateArguments(Arg.Any <FFmpegJob>()))
            .Do(x => _ffmpegJob = x[0] as FFmpegJob);

            _videoSource = new VideoStreamInfo()
            {
                Index = 0
            };
            _videoOutput = new VideoOutputStream()
            {
                SourceStreamIndex = 0
            };
            _transcodeJob = new TranscodeJob()
            {
                SourceInfo = new MediaInfo()
                {
                    FileName = "source",
                    Streams  = new List <StreamInfo>()
                    {
                        _videoSource
                    }
                },
                OutputFileName = "destination",
                Streams        = new List <OutputStream>()
                {
                    _videoOutput
                }
            };
            _ffmpegJob = null;
        }
        public void GenerateArgumentsDelimitsArguments()
        {
            var job = new FFmpegJob()
            {
                InputFileName  = "/Users/fred/Movies/source.mkv",
                OutputFileName = "/Users/fred/Movies/destination.m4v",
                Format         = "mp4"
            };

            _reflector.Reflect(job).Returns(new ArgumentProperty[]
            {
                new ArgumentProperty()
                {
                    ArgumentName = "-i",
                    Value        = job.InputFileName,
                    Converter    = _mockConverter
                },
                new ArgumentProperty()
                {
                    ArgumentName = "-f",
                    Value        = job.Format,
                    Converter    = _mockConverter
                },
                new ArgumentProperty()
                {
                    ArgumentName = "-o",
                    Value        = job.OutputFileName,
                    Converter    = _mockConverter
                }
            });

            string expected = $"-i {job.InputFileName} -f {job.Format} -o {job.OutputFileName}";
            string actual   = _generator.GenerateArguments(job);

            Assert.AreEqual(expected, actual);
        }
        public void GenerateArgumentsThrowsForEmptyInputFileName()
        {
            var job = new FFmpegJob();

            _generator.GenerateArguments(job);
        }
Example #11
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);
        }
Example #12
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);
        }