Home > Programming > PHP and FFMPEG - Performing intelligent video conversion

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:

  1. Determine the aspect ratio of the original video.
  2. If not 4:3, pad top and bottom with black bars.
  3. 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.

address
  • http://thecorbettfamily.org Steven

    Andrew, there is absolutely no way I can thank you enough for posting this. I'm totally amazed- this is just awesome.

    Thank you, thank you, thank you for taking the time to share this.

    Big thumbs up and blessings!!

    • http://andrewensley.com/ Andrew

      Haha... you're welcome. Good luck and let me know if you have any questions.

      • http://pulse.yahoo.com/_Y4U5CO2JTX76UE4IQUDTOAOHJ4 ivn

         Hi Andrew. The code seems great I have just tried it but can not get a convert video output. I have made some modification for example on video locations and ffmpeg location.

        This is my code: Please help. Can not get converted video. thanks
        function get_vid_dim($file)
        {
            $command = 'ffmpeg -i ' . escapeshellarg($file) . ' 2>&1';
            $dimensions = array();
            exec($command,$output,$status);
            if (!preg_match('/Stream #(?:[0-9.]+)(?:.*): Video: (?P.*) (?P[0-9]*)x(?P[0-9]*)/',implode("n",$output),$matches))
            {
                preg_match('/Could not find codec parameters (Video: (?P.*) (?P[0-9]*)x(?P[0-9]*))/',implode("n",$output),$matches);
            }
            if(!empty($matches['width']) && !empty($matches['height']))
            {
                $dimensions['width'] = $matches['width'];
                $dimensions['height'] = $matches['height'];
            }
            return $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 &1';

            exec($command,$output,$status);

            if($status == 0)
            {
                // Success
                echo 'Woohoo!';
            }
            else
            {
                // Error.  $output has the details
                echo '',join("n",$output),'';
            }
        }

        • Paj

          the padleft, padright etc command are deprecated, does anyone have an updated script?

  • zewolf

    Thank you so much, i'm looking for this for a while, I test it tonight !

  • http://weapi.co.cc morteza

    Thank you for your helpful article and code. but to make it shorter, you can do some optimization. like here:

    1
    2
    3
    4
    5
    6
    7
    8
    function get_dimensions($original_width,$original_height,$target_width,$target_height,$force_aspect)
    {
        //remove these and at the function argument list, add: $force_aspect=true
        if(!isset($force_aspect))
        {
            $force_aspect = true;
        }
    //rest of your code
  • http://andrewensley.com/ Andrew

    @ morteza

    Thanks for the tip. Not only is my original code longer, it's also incorrect. With my code, leaving out the $force_aspect parameter triggers a PHP Warning, which is very bad.

    I found this a few days after implementing in production, but forgot to change the code here. I've changed it now.

  • Jonas

    T-H-A-N-K Y-O-U! Seriously. Thanks!

  • http://andrewensley.com/ Andrew

    @ Jonas

    You're welcome!

  • Nath

    3 Years later and still useful! Thanks! :D

  • Leichenzug

    Awesome, many thanks for this, i searched for something like that to fix my page ^^

  • JM

    Excellent, this saved at least 2 days of my life ^^

    However, the ffmpeg version I use doesn't output exactly the same info as yours I guess (I have some strange value between "Video:" and the resolution: "yuv420p") so I had to change the regexp to this :

    '/Stream #(?:[0-9.]+)(?:.*): Video: (?P.*), (?:[a-z0-9.]+), (?P[0-9]*)x(?P[0-9]*)/'

    cheers and thanks again

information
7ads6x98y
guidelines