Wednesday, 24 October 2018

How to add filters (transition) between two videos using php-ffmpeg?

How to add filters (transition) between two videos using php-ffmpeg?

Question: What are the different filters available?
  1. alphaextract: Extract the alpha component from the input as a grayscale video.
  2. alphamerge: Add or replace the alpha component of the primary input with the grayscale value of a second input
    movie=in_alpha.mkv [alpha]; [in][alpha] alphamerge [out]
  3. amplify: Amplify differences between current pixel and pixels of adjacent frames in same pixel location.
  4. atadenoise: Adaptive Temporal Averaging Denoiser to the video input.
  5. avgblur: Apply average blur filter.
  6. bbox: Compute the bounding box for the non-black pixels in the input frame luminance plane.
  7. bitplanenoise: Show and measure bit plane noise.
  8. blackdetect: Detect video intervals that are (almost) completely black.
    blackdetect=d=2:pix_th=0.00



Question: Give few examples?
Apply transition from bottom layer to top layer in first 10 seconds
blend=all_expr='A*(if(gte(T,10),1,T/10))+B*(1-(if(gte(T,10),1,T/10)))'


Apply uncover left effect:
blend=all_expr='if(gte(N*SW+X,W),A,B)'


Apply uncover down effect:
blend=all_expr='if(gte(Y-N*SH,0),A,B)'


Display differences between the current and the previous frame:
tblend=all_mode=grainextract





Question: How to add video filter (transition) on videos with PHP-FFMPEG?
  1. You must have installed PHP-FFMPEG
  2. php composer.phar require php-ffmpeg/php-ffmpeg
    If not, you can check https://www.web-technology-experts-notes.in/2017/10/how-to-install-ffmpeg-in-wamp-server-in-windows7.html

  • Open File Path: \vendor\php-ffmpeg\php-ffmpeg\src\FFMpeg\Media\Concat.php
  • Add following function in Concat.php
     public function saveFromDifferentCodecsTransition(FormatInterface $format, $outputPathfile,$transition=array())
        {
            /**
             * @see https://ffmpeg.org/ffmpeg-formats.html#concat
             * @see https://trac.ffmpeg.org/wiki/Concatenate
             */
    
            // Check the validity of the parameter
            if(!is_array($this->sources) || (count($this->sources) == 0)) {
                throw new InvalidArgumentException('The list of videos is not a valid array.');
            }
    
            // Create the commands variable
            $commands = array();
    
            // Prepare the parameters
            $nbSources = 0;
            $files = array();
    
            // For each source, check if this is a legit file
            // and prepare the parameters
            foreach ($this->sources as $videoPath) {
                $files[] = '-i';
                $files[] = $videoPath;
                $nbSources++;
            }
    
            $commands = array_merge($commands, $files);
    
            // Set the parameters of the request
            $commands[] = '-filter_complex';
    
            $complex_filter = '';
            //$transition =array('transition_effect'=>'fade_out','transition_length'=>1.2);
            
            //No transistion
            if(empty($transition) || @$transition['transition_effect']=='none' || $nbSources==1){
                for($i=0; $i<$nbSources; $i++) {
                    $complex_filter .= '['.$i.':v:0] ['.$i.':a:0] ';
                }   
                
            }else{ //Transition with Fade in/Fade out
                if($transition['transition_effect']=='fade_in'){
                    for($i=0; $i<$nbSources; $i++) {
                        if($i == 0){
                            $complex_filter .='['.$i.':v]setpts=PTS-STARTPTS[v'.$i.'];';            
                        }else{
    
            
                            $complex_filter .='['.$i.':v]fade=type=in:duration='.$transition['transition_length'].',setpts=PTS-STARTPTS[v'.$i.'];';            
                        }
                    }
                }else if($transition['transition_effect']=='fade_out'){
                    for($i=0; $i<$nbSources; $i++) {
                        if($i == ($nbSources-1)){
                            $complex_filter .='['.$i.':v]setpts=PTS-STARTPTS[v'.$i.'];';            
                        }else{
                            $startTime=0;
                            /* Get duration  of video*/
                               try{
                                    $sourceFile = $this->sources[$i];
                                    $ffprobe    = \FFMpeg\FFProbe::create();
                                    $duration=
                                            $ffprobe->streams($sourceFile)
                                            ->videos()                   
                                            ->first()                  
                                            ->get('duration');  
                                    $startTime=$duration-$transition['transition_length'];
                               }catch(Exception $e){
                                   $transition['transition_length']=0;
                               }
                            /* Get duration  of video*/
                            $complex_filter .='['.$i.':v]fade=type=out:start_time='.$startTime.',setpts=PTS-STARTPTS[v'.$i.'];';            
                        }
                    }
                }
                    for($i=0; $i<$nbSources; $i++) {
                        $complex_filter .= '[v'.$i.']['.$i.':a]';
                    }             
                
            }
            
            $complex_filter .= 'concat=n='.$nbSources.':v=1:a=1 [v] [a]';
    
            $commands[] = $complex_filter;
            $commands[] = '-map';
            $commands[] = '[v]';
            $commands[] = '-map';
            $commands[] = '[a]';
    
            // Prepare the filters
            $filters = clone $this->filters;
            $filters->add(new SimpleFilter($format->getExtraParams(), 10));
    
            if ($this->driver->getConfiguration()->has('ffmpeg.threads')) {
                $filters->add(new SimpleFilter(array('-threads', $this->driver->getConfiguration()->get('ffmpeg.threads'))));
            }
            if ($format instanceof VideoInterface) {
                if (null !== $format->getVideoCodec()) {
                    $filters->add(new SimpleFilter(array('-vcodec', $format->getVideoCodec())));
                }
            }
            if ($format instanceof AudioInterface) {
                if (null !== $format->getAudioCodec()) {
                    $filters->add(new SimpleFilter(array('-acodec', $format->getAudioCodec())));
                }
            }
    
            // Add the filters
            foreach ($this->filters as $filter) {
                $commands = array_merge($commands, $filter->apply($this));
            }
    
            if ($format instanceof AudioInterface) {
                if (null !== $format->getAudioKiloBitrate()) {
                    $commands[] = '-b:a';
                    $commands[] = $format->getAudioKiloBitrate() . 'k';
                }
                if (null !== $format->getAudioChannels()) {
                    $commands[] = '-ac';
                    $commands[] = $format->getAudioChannels();
                }
            }
    
            // If the user passed some additional parameters
            if ($format instanceof VideoInterface) {
                if (null !== $format->getAdditionalParameters()) {
                    foreach ($format->getAdditionalParameters() as $additionalParameter) {
                        $commands[] = $additionalParameter;
                    }
                }
            }
    
            // Set the output file in the command
            $commands[] = $outputPathfile;
    
            $failure = null;
    
            try {
                $this->driver->command($commands);
            } catch (ExecutionFailureException $e) {
                throw new RuntimeException('Encoding failed', $e->getCode(), $e);
            }
    
            return $this;
        }



  • Question: How to call above function (used for scale in/scale out) with number of seconds?
    require_once 'ffmpeglib/vendor/autoload.php';
    $ffmpeg = FFMpeg\FFMpeg::create();
    $format = new FFMpeg\Format\Video\X264();
    $format->setAudioCodec("aac");  
    $newFileName='output_video.mp4';
    $captionStaticFilePath=$_SERVER['DOCUMENT_ROOT'].'/myvideos/';
    $videoFiles=array($captionStaticFilePath.'myvideo.mp4',$captionStaticFilePath.'myvideo1.mp4',$captionStaticFilePath.'myvideo2.mp4');
    
    /////////////// Debug ////////////////////////////
    $ffmpeg->getFFMpegDriver()->listen(new \Alchemy\BinaryDriver\Listeners\DebugListener());
    $ffmpeg->getFFMpegDriver()->on('debug', function ($message) {
       echo $message."
    ";
    });
    /////////////// Debug ////////////////////////////    
    $transition=array(
        'transition_effect'=>'fade_in', //fade_in,fade_out, black
        'transition_length'=>4 //number of seconds
    ); 
    try{
    $video = $ffmpeg->open($videoFiles[0])                        
        ->concat($videoFiles)
        ->saveFromDifferentCodecsTransition(new FFMpeg\Format\Video\X264,$captionStaticFilePath.$newFileName,$transition); 
    }catch(Exception $e){
    echo $e->getMessage();
     }