StephRocks22's avatar

Is there any way that I can speed up the ffmpeg processing time

Hello, I am using ffmpeg to upload video files, I am noticing that its taking a long time to process the video. The video could just be 1.2mb or 5.3mb or 10mb and its still slow. Is there any way that I can speed up the process. I would greatly appreciate it if you could help me. My code is below.

$video=new Video;   
  $file = $request->file('file');   
 $fileName =uniqid().$file->getClientOriginalName();
 
 $request->file->move(public_path('/app'), $fileName);
            $name_file=uniqid().'video.mp4';
         $ffp=FFMpeg::fromDisk('local')
         ->open($fileName)
    ->addFilter(function ($filters) {
        $filters->resize(new \FFMpeg\Coordinate\Dimension(640, 480));
    })
    ->export()
    ->toDisk('s3')
    ->inFormat(new \FFMpeg\Format\Video\X264('libmp3lame'))
    ->save($name_file);
               $imageName = Storage::disk('s3')->url($name_file);


    $video->title=$imageName;
    $video->save();

0 likes
21 replies
hollyit's avatar

Are you sure it's actually FFMPEG and not just slow uploading to s3? The best thing to do would be to process the video file through the command line using straight FFMPEG and then doing the same video file through your app and see if there's a big time difference in processing. I've never used the PHP package for FFMPEG, but I handle a rather large site with tens of thousands of videos that are all processed through FFMPEG triggered from a Node JS server and even the more compressed mobile versions transcode fast. A 1024p video clip that's 10 minutes long is processed in less than 3 minutes, and that's on a low end VPS.

StephRocks22's avatar

@hollyit Thank you for responding. Yes it is because when I remove ffmpeg from my code I notice that the file is uploading real fast

usman's avatar

@stephrocks22 after saving the file on the disk, defer the resizing and apply filters logic using a queued job. You will need to configure the queues first, for instance you can configure the basic database driver using:

./artisan queue:table
./artisan migrate

The above commands will create the database table for queued jobs. Now you can issue the following command to create a job:

./artisan make:job VideoResize

The above command will create the VideoResize job class with the following definition:

class VideoResize implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        //
    }
}

You can edit the constructor to receive a file name and move the resizing and filtering logic inside the handle method.

Here is how you can dispatch this command from your controller:

dispatch(new App\Jobs\VideoResize($filename);

The above code will place the job on the database queue that you can work using the artisan queue:work command.

StephRocks22's avatar

@usman am I going to have to constanly write php artisan queue:work comand or is that just a one time thing? because my stuff is on the web?

Cronix's avatar

No, you just run it once. It keeps running in the background. If you upload any code changes that would affect what it's doing, you'd need to php artisan queue:restart so it will pick up your latest code changes. When you start the queue worker, it creates an instance of laravel in memory and no longer reads from the files, which is why it's so fast as it never has to bootstrap itself on every request, but it also means it only knows about the code that was present when you started the worker.

What you probably need to do is run supervisor, so it will restart the queue worker if it crashes for some reason.

To keep the queue:work process running permanently in the background, you should use a process monitor such as Supervisor to ensure that the queue worker does not stop running.

Remember, queue workers are long-lived processes and store the booted application state in memory. As a result, they will not notice changes in your code base after they have been started. So, during your deployment process, be sure to restart your queue workers.

https://laravel.com/docs/5.7/queues#running-the-queue-worker

StephRocks22's avatar

@cronix I just tried what @usman told me to do but i don't know its not working. I uploaded a video file and it does load the page fast and thats it,I don't get no message nothing. Honestly I am just now trying to learn the laravel queue and I honestly don't know what I'm doing. If you can guide me with this I will greatly appreciate it. This is what I tried

//This the Filecontroller
 public function fileUploadPost(Request $request)
    {
       dispatch(new App\Jobs\VideoResize($filename));

        return response()->json(['success'=>'Your file is now processing .']);
    }

  //this is the job VideoResize.php

public function __construct()
    {
        //
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        //
         $request->validate([
            'file' => 'required',
        ]);
 
       


  $video=new Video;   
  $file = $request->file('file');   
 $fileName =uniqid().$file->getClientOriginalName();
 
 $request->file->move(public_path('/app'), $fileName);
            $name_file=uniqid().'video.mp4';
         $ffp=FFMpeg::fromDisk('local')
         ->open($fileName)
    ->addFilter(function ($filters) {
        $filters->resize(new \FFMpeg\Coordinate\Dimension(640, 480));
    })
    ->export()
    ->toDisk('s3')
    ->inFormat(new \FFMpeg\Format\Video\X264('libmp3lame'))
    ->save($name_file);
               $imageName = Storage::disk('s3')->url($name_file);


    $video->title=$imageName;
    $video->save();

    }

Cronix's avatar

First thing that sticks out is where does $filename come from in your fileUploadPost() method?

dispatch(new App\Jobs\VideoResize($filename));
Cronix's avatar

$filename isn't defined in the method. You'd need to get the filename from the $request object, which is available to that method (it's the only thing that IS being passed to the method).

StephRocks22's avatar

@cronix I just defined the name what happens next? I tested it out and I got nothing.

 public function fileUploadPost(Request $request)
    {
 
       


  $file = $request->file('file');   
 $fileName =uniqid().$file->getClientOriginalName();
       dispatch(new App\Jobs\VideoResize($filename));


    }
Cronix's avatar

It wasn't meant to solve all of your issues (you have several here), it was the first step.

Now that you're passing the filename properly to the job, you need to set your job up correctly. It's not receiving the filename you're passing to it. You need to accept it in the constructor and assign it to a class property, which you can then access in the handle() method. Keep working on it! https://laravel.com/docs/5.7/queues#creating-jobs

shez1983's avatar

correct me if i am wrong but your last fileUpload controller code, you are passing filename, you need to STORE it first on your server/s3 wherever and then give the path or name to the JOB.

2ndly when the job runs, your app doesnt get a notification ie in your browser that the job is finished, if you want to do that you will need to use JS/sockets etc

StephRocks22's avatar

@cronix and @shez1983 I'm sorry guys I am like all over the place with this. How about this. I had did something before I messaged you guys about my issue. I did something where you upload the file then you will be redirected to a page where it says thank you... your video is being process and will be ready shortly so I'm not having users staring at a screen for however long and so when its ready they will see it on their profile page. But I wanted to go a little deeper and send them a message when its done. Let me show you what I did.

//this was the controller

  $request->validate([
            'file' => 'required',
        ]);
 
       


  $video=new Video;   
  $file = $request->file('file');   
 $fileName =uniqid().$file->getClientOriginalName();
 
 $request->file->move(public_path('/app'), $fileName);
            $name_file=uniqid().'video.mp4';
         $ffp=FFMpeg::fromDisk('local')
         ->open($fileName)
    ->addFilter(function ($filters) {
        $filters->resize(new \FFMpeg\Coordinate\Dimension(640, 480));
    })
    ->export()
    ->toDisk('s3')
    ->inFormat(new \FFMpeg\Format\Video\X264('libmp3lame'))
    ->save($name_file);
               $imageName = Storage::disk('s3')->url($name_file);


    $video->title=$imageName;
    $video->save();

//this was the blade.php

<div class="container">
    <div class="card">
      <div class="card-header">
      </div>
      <div class="card-body">
            <form method="POST" action="{{ route('fileUploadPost') }}" enctype="multipart/form-data">
                @csrf
                <div class="form-group">
                    <input name="file" id="poster" type="file" class="form-control"><br/>
                  

                    <div>

                    </div>


                    <!--<input type="submit"  value="Submit" class="btn btn-success">-->
                </div>
            </form>  

            
      </div>
    </div>
</div>
 
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.js"></script>
<script src="http://malsup.github.com/jquery.form.js"></script>
 
<script type="text/javascript">

   $('#poster').change(function() {
  $('form').submit();
});
    
 

 
    $('form').ajaxForm({
      
        uploadProgress: function(event, position, total, percentComplete) {
                    window.location.href = "success";

            
        }
    });
     
   
</script>

usman's avatar

@cronix $filename was just a hypothetical variable that was meant to be initialized when the store method would have been called on the received file.

@stephrocks22 there are a couple of ways to send the notification back to the client: you can use jquery polling or use the laravel echo as @shez1983 suggested.

StephRocks22's avatar

@cronix @usman @hollyit I have come to a conclusion that I need to calm down and relax about this whole thing. I am realizing that I am trying to rush through the process of getting this and not taking my time to understand. I am a total newbie when it comes to the Laravel queue, so I have decided to follow this tutorial and now I am getting the hang of it step by step. But I am having problems with this. When I try to process the job it keeps failing, then when I take a look at the laravel.log it tells me that " ffprobe failed to execute command" here is what I have for the job

 public function handle()
    {
        
 
        $converted_name = $this->getCleanFileName($this->video->path);
 
        // open the uploaded video from the right disk...
        FFMpeg::fromDisk($this->video->disk)
            ->open($this->video->path)
 
            // add the 'resize' filter...
            ->addFilter(function ($filters) {
                $filters->resize(new Dimension(960, 540));
            })
 
            // call the 'export' method...
            ->export()
 
            // tell the MediaExporter to which disk and in which format we want to export...
            ->toDisk('s3')
            ->inFormat(new \FFMpeg\Format\Video\X264('libmp3lame'))
 
            // call the 'save' method with a filename...
            ->save($converted_name);
            
       $imageName = Storage::disk('s3')->url($converted_name);
        // update the database so we know the convertion is done!
        $this->video->update([
            'converted_for_streaming_at' => Carbon::now(),
            'processed' => true,
            'stream_path' => $imageName
        ]);
    }

//this the controller

public function store(StoreVideoRequest $request){

        $path=str_random(16). '.' . $request->video->getClientOriginalExtension();
        $request->video->storeAs('app',$path);

        $video=Video::create([
          'disk'=>'public',
           'original_name'=>$request->video->getClientOriginalName(),
           'path'=>$path,
           'title'=>$request->title,

        ]);

        ConvertVideoForStreaming::dispatch($video);
 
usman's avatar

@stephrocks22 hi, I am not familiar with the specifics of the ffmpeg libs you are using. But looking at the above code I can conclude that ffprobe is not able to find the file. And this could be the issue due to the fact that you are saving the file relative to the app folder but trying to access it through the public disk that uses a different base folder.

If all your files are publicly linkable, I would suggest you to change the default filesystem disk to public from local in the .env file, or specify directly in config/filesystems.php file. Then I hope the issue will be resolved. It is the issue related to the path.

Also you have the code in place for generating random file name. The store method covers that for you. I would suggest you to go through the Requests, and Storage documentation that will make the things easier for you.

Usman.

Please or to participate in this conversation.