private string RunScan(MediaProperties ma, FilterChain fc, int startSeconds = 0, int scanDuration = 0)
        {
            var videoStream = ma.Streams.FirstOrDefault(m => m.StreamType == StreamType.Video);

            if (scanDuration == 0)
            {
                scanDuration = Convert.ToInt32(ma.MediaDuration);
            }

            var scanCmd =
                $" -ss {startSeconds} -y -i {ma.MediaFile}  -pix_fmt yuv420p -t {scanDuration} {fc.GetVideoFilters()} -an -f null NUL";

            return(this.ExecuteFFMpeg_LogProgress(scanCmd, scanDuration).StdErr);
        }
        /// <summary>
        /// Scan media, looking for combing artifacts.
        /// Used for guiding de-interlacing and inverse-telecine filter selection.
        /// </summary>
        /// <param name="ma"> MediaAttributes.</param>
        /// <param name="startSeconds"></param>
        /// <param name="scanDuration"></param>
        public void DetectCombing(MediaProperties ma, int startSeconds = 0, int scanDuration = 0)
        {
            //Settings for detecting combing and telecine patterns.
            var filterIdet       = "idet";
            var filterFieldMatch = "fieldmatch=order=auto:combmatch=full:cthresh=12";

            var filterChain = new FilterChain();

            filterChain.AddFilter(filterIdet);
            filterChain.AddFilter(filterFieldMatch);

            this.utils.LogProxy($"Starting combing/telecine detection...", Utilities.LogLevel.Info);
            var scanData = this.RunScan(ma, filterChain, startSeconds, scanDuration);

            this.ParseCombScanData(ma, scanData);
        }
        /// <summary>
        /// Manages filters used to process video.
        /// Scaling
        /// De-interlacing
        /// Cropping
        /// Matching to master (for stitching scenarios, IE: dub cards)
        /// Burning in sub-titles.
        /// </summary>
        /// <param name="outputVideo">Defines the video stream being generated</param>
        /// <param name="videoEncodeJob">The overall job for this encode run</param>
        protected FilterChain ConfigureFilters(VideoOutputDefinition outputVideo, VideoEncodeJob videoEncodeJob)
        {
            //Video stream
            MediaProperties videoProperties = videoEncodeJob.InputMedia[0];
            MediaStream     inputVideo      = videoProperties.Streams[videoProperties.FirstVideoIndex];
            FilterChain     filters         = new FilterChain();

            //Derived from desired width, source aspect and cropping options.
            var TargetOutputHeight = 0;

            if (!String.IsNullOrEmpty(videoProperties.FFCropFilter) && videoEncodeJob.AutoCrop)
            {
                //must crop before any scaling happens...
                filters.AddFilter(videoProperties.FFCropFilter);

                if (inputVideo.VideoPixelAspect != 1)
                {
                    inputVideo.SquareVideoWidth  = Convert.ToInt32(videoProperties.cropValue.XExtent * inputVideo.VideoPixelAspect);
                    inputVideo.SquareVideoHeight = videoProperties.cropValue.YExtent;
                }
                else
                {
                    inputVideo.SquareVideoWidth  = videoProperties.cropValue.XExtent;
                    inputVideo.SquareVideoHeight = videoProperties.cropValue.YExtent;
                }
            }
            else
            {
                if (inputVideo.VideoPixelAspect != 1)
                {
                    inputVideo.SquareVideoWidth = Convert.ToInt32(inputVideo.VideoWidth * inputVideo.VideoPixelAspect);
                }
            }

            //De-interlace filter behavior.
            //Must happen before any scaling operations.
            //Only invoke on HD and SD at non-film frame rates
            if (videoProperties.HasCombing && inputVideo.SquareVideoWidth <= 1920 && inputVideo.VideoFrameRate > 24)
            {
                if (videoEncodeJob.DeintOverride != VideoEncodeJob.DeinterlaceOverride.None)
                {
                    //The job manager may desire to override behavior.
                    //For example, in the event where the main feature is cleanly telecined film
                    //but the credits are combed video. HBO LatAm does this.
                    filters.AddFilter(this.DeinterlaceOverrideMap[videoEncodeJob.DeintOverride]);
                }
                else if (videoProperties.IsMixedFilmVideo)
                {
                    //Typical: 30i animation with some telecine segments.
                    //Preserve original frame rate. Inverse telecine film.
                    //De-comb video. There will be judder in the film segments.
                    filters.AddFilter(FFBase.DeintFilter_VideoBias);
                }
                else if (videoProperties.IsPureFilm)
                {
                    //Old telecined content. Rare.
                    filters.AddFilter(FFBase.DeintFilter_PureTelecine);
                }
                else if (videoProperties.IsPureVideo)
                {
                    //Talk shows. Next day TV. Old sitcoms.
                    //This filter setting is slow, but yields good results.
                    filters.AddFilter(FFBase.DeintFilter_PureVideoSD);
                }
            }

            if (videoEncodeJob.MatchWidth != 0 && videoEncodeJob.MatchHeight != 0)
            {
                //Job indicates matching to master source.
                filters.AddFilter(this.GetScaleMatchFilter(inputVideo.VideoPixelAspect == 1, videoEncodeJob.MatchWidth, videoEncodeJob.MatchHeight));
                TargetOutputHeight = outputVideo.Width * videoEncodeJob.MatchHeight / videoEncodeJob.MatchWidth;
                TargetOutputHeight = (TargetOutputHeight % 2 == 0) ? TargetOutputHeight : ++TargetOutputHeight;
                filters.AddFilter($"scale={outputVideo.Width}:{TargetOutputHeight}");
            }
            else if (outputVideo.Width != inputVideo.SquareVideoWidth || inputVideo.VideoPixelAspect != 1)
            {
                //Input is either non-square or does not match desired output
                TargetOutputHeight = outputVideo.Width * inputVideo.SquareVideoHeight / inputVideo.SquareVideoWidth;
                TargetOutputHeight = (TargetOutputHeight % 2 == 0) ? TargetOutputHeight : ++TargetOutputHeight;
                filters.AddFilter($"scale={outputVideo.Width}:{TargetOutputHeight}");
            }

            //Set source pixel aspect
            filters.AddFilter("setsar=1/1");

            //Sub titles go last to ensure some degree of readability on low res streams
            if (!String.IsNullOrEmpty(videoEncodeJob.BurnSubs))
            {
                //Need to escape characters so the ASS filter will be happy.
                videoEncodeJob.BurnSubs = videoEncodeJob.BurnSubs.Replace(@"\", @"\\");
                videoEncodeJob.BurnSubs = videoEncodeJob.BurnSubs.Replace(@":", @"\:");

                filters.AddFilter($"ass='{videoEncodeJob.BurnSubs}'");
            }

            return(filters);
        }
Exemple #4
0
        /// <summary>
        /// Scan media, finding black bars and selecting the most common result.
        /// Used for automated cropping
        /// </summary>
        /// <param name="ma"> MediaAttributes.</param>
        /// <param name="startSeconds"></param>
        /// <param name="scanDuration"></param>
        public void DetectLetterbox(MediaProperties ma, int startSeconds = 0, int scanDuration = 0)
        {
            var filterCropdetect = "cropdetect=0.1:2:0";
            var filterChain      = new FilterChain();
            var videoStream      = ma.Streams.FirstOrDefault(m => m.StreamType == StreamType.Video);

            filterChain.AddFilter(filterCropdetect);

            this.utils.LogProxy($"Starting letterbox detection...", Utilities.LogLevel.Info);
            var scanData = this.RunScan(ma, filterChain, startSeconds, scanDuration);

            ma.cropValue = this.GetCommonCropValue(scanData);

            int bottomCrop = videoStream.VideoHeight - ma.cropValue.YExtent - ma.cropValue.YOffset;

            //If the bars at the top differ in size from those at the bottom
            //then re-scan because this is generally the result of noise at the top
            //of the video frame that breaks bar detection.
            if (Math.Abs(bottomCrop - ma.cropValue.YOffset) > 16)
            {
                filterChain.DeleteAll();

                //Different masking values are used for suspected PAL and NSTC content.
                //The crop filter syntax is counter-intuitive. It describes the visible region of media
                //IE: crop=width:height:x-offset:y-offset <- this is the size of the output. Not the mask.
                if (videoStream.VideoHeight < 650 && videoStream.VideoHeight > 525)
                {
                    filterChain.AddFilter($"crop={videoStream.VideoWidth.ToString()}:{videoStream.VideoHeight - 32}:0:{32}");
                }
                else if (videoStream.VideoHeight <= 525)
                {
                    filterChain.AddFilter($"crop={videoStream.VideoWidth.ToString()}:{videoStream.VideoHeight - 16}:0:{16}");
                }

                filterChain.AddFilter(filterCropdetect);

                this.utils.LogProxy($"Starting second letterbox detection scan due to detected noise...",
                                    Utilities.LogLevel.Info);
                scanData = this.RunScan(ma, filterChain, startSeconds, scanDuration);

                ma.cropValue = this.GetCommonCropValue(scanData);

                if (videoStream.VideoHeight < 650 && videoStream.VideoHeight > 525)
                {
                    ma.cropValue.YOffset += 32;
                }
                else if (videoStream.VideoHeight <= 525)
                {
                    ma.cropValue.YOffset += 16;
                }
            }

            //Only crop if we have a minimum of letterbox coverage
            if (Math.Abs(ma.cropValue.YExtent - videoStream.VideoHeight) > 16)
            {
                ma.HasLetterbox = true;
                //Crop to even values...
                if (ma.cropValue.YExtent % 2 != 0)
                {
                    ma.cropValue.YExtent--;
                }

                //Save pre-built crop filter for later use if desired.
                ma.FFCropFilter = $"crop={videoStream.VideoWidth.ToString()}:{ma.cropValue.YExtent.ToString()}:0:{ma.cropValue.YOffset.ToString()}";
            }
        }