PHP and FFMPEG - Performing intelligent video conversion
Whenever I have limited experience with something or just need to do some research, I go straight to StackOverflow.com - a computer programming question and answer site. Out of the 24 questions I've asked on the site, only one has gone unanswered to date, and I've gleaned countless other answers from questions others have already asked. In case you haven't already guessed, I really like the site.
Not long ago, I needed to convert uploaded video files for a project in PHP. FFMPEG was a clear and obvious choice for the job, but I had never used it before, so I turned to my favorite resource. Sure enough, I got enough information in the answers to build a fully working solution. I posted that solution as an answer to my own question in case anyone else could use it.
Unfortunately, StackOverflow seems to be having trouble displaying my answer, so I've reposted it here for anyone who needs it.
The original question
I'm converting videos uploaded to a php script from various formats (.avi, .mpg, .wmv, .mov, etc.) to a single .flv format. The conversion is working great but what I'm having trouble with is the resolution of the videos.
This is the command I'm currently running (with PHP vars):
1 | ffmpeg -i $original -ab 96k -b 700k -ar 44100 -s 640x480 -acodec mp3 $converted |
Both $original and $converted contain the full paths to those files. My problem is that this always converts to 640x480 (like I'm telling it to) even when the source is smaller. Obviously, this is a waste of disk space and bandwidth when the video is downloaded. Also, this doesn't account for input videos being in any aspect ratio other than 4:3, resulting in a "squished" conversion if I upload a 16:9 video.
There are 3 things I need to do:
- Determine the aspect ratio of the original video.
- If not 4:3, pad top and bottom with black bars.
- Convert to 640x480 if either dimension of the original is larger or a 4:3 aspect ratio relating to the width/height of the original (whichever is closer to 640x480).
I've run ffmpeg -i on a few videos, but I don't see a consistent format or location to find the original's resolution from. Once I'm able to figure that out, I know I can "do the math" to figure out the right size and specify padding to fix the aspect ratio with -padttop, -padbottom, etc.
The final result
Getting FFMPEG's output
First I had to get the output from ffmpeg -i, which was a challenge in itself. Thanks to hegemon's answer on my other question, I was finally able to get it working with 2>&1 at the end of my command. And thanks to Evert's answer to this question, I was able to parse the output with preg_match to find the original file's height and width.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | function get_vid_dim($file) { $command = '/usr/bin/ffmpeg -i ' . escapeshellarg($file) . ' 2>&1'; $dimensions = array(); exec($command,$output,$status); if (!preg_match('/Stream #(?:[0-9\.]+)(?:.*)\: Video: (?P<videocodec>.*) (?P<width>[0-9]*)x(?P<height>[0-9]*)/',implode("\n",$output),$matches)) { preg_match('/Could not find codec parameters \(Video: (?P<videocodec>.*) (?P<width>[0-9]*)x(?P<height>[0-9]*)\)/',implode("\n",$output),$matches); } if(!empty($matches['width']) && !empty($matches['height'])) { $dimensions['width'] = $matches['width']; $dimensions['height'] = $matches['height']; } return $dimensions; } |
Determining the best dimensions
I wrote this function to determine the best dimensions to use for conversion. It takes the original's dimensions, target dimensions, and whether or not to force conversion to the target aspect ratio (determined from its width/height). The target dimensions will always be the largest result this function can return.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | /*********************************************************************************** get_dimensions() Takes in a set of video dimensions - original and target - and returns the optimal conversion dimensions. It will always return the smaller of the original or target dimensions. For example: original dimensions of 320x240 and target dimensions of 640x480. The result will be 320x240 because converting to 640x480 would be a waste of disk space, processing, and bandwidth (assuming these videos are to be downloaded). @param $original_width: The actual width of the original video file which is to be converted. @param $original_height: The actual height of the original video file which is to be converted. @param $target_width: The width of the video file which we will be converting to. @param $target_height: The height of the video file which we will be converting to. @param $force_aspect: Boolean value of whether or not to force conversion to the target's aspect ratio using padding (so the video isn't stretched). If false, the conversion dimensions will retain the aspect ratio of the original. Optional parameter. Defaults to true. @return: An array containing the size and padding information to be used for conversion. Format: Array ( [width] => int [height] => int [padtop] => int // top padding (if applicable) [padbottom] => int // bottom padding (if applicable) [padleft] => int // left padding (if applicable) [padright] => int // right padding (if applicable) ) ***********************************************************************************/ function get_dimensions($original_width,$original_height,$target_width,$target_height,$force_aspect = true) { // Array to be returned by this function $target = array(); // Target aspect ratio (width / height) $aspect = $target_width / $target_height; // Target reciprocal aspect ratio (height / width) $raspect = $target_height / $target_width; if($original_width/$original_height !== $aspect) { // Aspect ratio is different if($original_width/$original_height > $aspect) { // Width is the greater of the two dimensions relative to the target dimensions if($original_width < $target_width) { // Original video is smaller. Scale down dimensions for conversion $target_width = $original_width; $target_height = round($raspect * $target_width); } // Calculate height from width $original_height = round($original_height / $original_width * $target_width); $original_width = $target_width; if($force_aspect) { // Pad top and bottom $dif = round(($target_height - $original_height) / 2); $target['padtop'] = $dif; $target['padbottom'] = $dif; } } else { // Height is the greater of the two dimensions relative to the target dimensions if($original_height < $target_height) { // Original video is smaller. Scale down dimensions for conversion $target_height = $original_height; $target_width = round($aspect * $target_height); } //Calculate width from height $original_width = round($original_width / $original_height * $target_height); $original_height = $target_height; if($force_aspect) { // Pad left and right $dif = round(($target_width - $original_width) / 2); $target['padleft'] = $dif; $target['padright'] = $dif; } } } else { // The aspect ratio is the same if($original_width !== $target_width) { if($original_width < $target_width) { // The original video is smaller. Use its resolution for conversion $target_width = $original_width; $target_height = $original_height; } else { // The original video is larger, Use the target dimensions for conversion $original_width = $target_width; $original_height = $target_height; } } } if($force_aspect) { // Use the target_ vars because they contain dimensions relative to the target aspect ratio $target['width'] = $target_width; $target['height'] = $target_height; } else { // Use the original_ vars because they contain dimensions relative to the original's aspect ratio $target['width'] = $original_width; $target['height'] = $original_height; } return $target; } |
Usage
Here are a few examples of what you will get from get_dimensions() to make things more clear:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | get_dimensions(480,360,640,480,true); -returns: Array([width] => 480, [height] => 360) get_dimensions(480,182,640,480,true); -returns: Array([padtop] => 89, [padbottom] => 89, [width] => 480, [height] => 360) get_dimensions(480,182,640,480,false); -returns: Array([width] => 480, [height] => 182) get_dimensions(640,480,480,182,true); -returns: Array([padleft] => 119, [padright] => 119, [width] => 480, [height] => 182) get_dimensions(720,480,640,480,true); -returns: Array([padtop] => 27, [padbottom] => 27, [width] => 640, [height] => 480) get_dimensions(720,480,640,480,false); -returns: Array([width] => 640, [height] => 427) |
The Finished Product
Now, to put it all together:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | $src = '/var/videos/originals/original.mpg'; $original = get_vid_dim($src); if(!empty($original['width']) && !empty($original['height'])) { $target = get_dimensions($original['width'],$original['height'],640,480,true); $command = '/usr/bin/ffmpeg -i ' . $src . ' -ab 96k -b 700k -ar 44100 -s ' . $target['width'] . 'x' . $target['height']; $command .= (!empty($target['padtop']) ? ' -padtop ' . $target['padtop'] : ''); $command .= (!empty($target['padbottom']) ? ' -padbottom ' . $target['padbottom'] : ''); $command .= (!empty($target['padleft']) ? ' -padleft ' . $target['padleft'] : ''); $command .= (!empty($target['padright']) ? ' -padright ' . $target['padright'] : ''); $command .= ' -acodec mp3 /var/videos/converted/target.flv 2>&1'; exec($command,$output,$status); if($status == 0) { // Success echo 'Woohoo!'; } else { // Error. $output has the details echo '<pre>',join("\n",$output),'</pre>'; } } |
I hope this is helpful to someone.
Possibly Related posts:

