PHP and FFMPEG - Performing intelligent video conversion

前端 未结 3 1792
北海茫月
北海茫月 2020-12-24 00:14

I have an oddly difficult task to perform. I thought it would be easy, but all my efforts have been fruitless.

I\'m converting videos uploaded to a php script from

相关标签:
3条回答
  • 2020-12-24 00:28

    This works for me:

    $data = 'ffmpeg output';
    $matches = array();
    
    if (!preg_match('/Stream #(?:[0-9\.]+)(?:.*)\: Video: (?P<videocodec>.*) (?P<width>[0-9]*)x(?P<height>[0-9]*)/',$data,$matches)
       preg_match('/Could not find codec parameters \(Video: (?P<videocodec>.*) (?P<width>[0-9]*)x(?P<height>[0-9]*)\)/',$data,$matches)
    

    This might not always work, but it works most of the times, which was good enough in my case :)

    0 讨论(0)
  • 2020-12-24 00:28

    Ok. I have a fully-functional solution. This is for anyone who finds this question wanting to do the same thing. My code may not be very elegant, but it gets the job done.


    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.

    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.

    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:

    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:

    $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>';
        }
    }
    
    0 讨论(0)
  • 2020-12-24 00:31

    I'm not familiar with PHP, but I wrote a utility to work with ffmpeg in C# several months ago. I used regular expressions to do this. There are some regular expressions which may help you from there:

    // this is for version detection
    "FFmpeg version (?<version>(\w|\d|\.|-)+)"
    // this is for duration parsing
    "Duration: (?<hours>\d{1,3}):(?<minutes>\d{2}):(?<seconds>\d{2})(.(?<fractions>\d{1,3}))?"
    
    // these are connected:
    // 0) this is base for getting stream info
    "Stream #(?<number>\d+?\.\d+?)(\((?<language>\w+)\))?: (?<type>.+): (?<data>.+)"
    // 1) if the type is audio:
    "(?<codec>\w+), (?<frequency>[\d]+) (?<frequencyUnit>[MK]?Hz), (?<chanel>\w+), (?<format>\w+)(, (?<bitrate>\d+) (?<bitrateUnit>[\w/]+))?"
    // 2) if the type is video:
    "(?<codec>\w+), (?<format>\w+), (?<width>\d+)x(?<height>\d+), (?<bitrate>\d+(\.\d+)?) (?<bitrateUnit>[\w\(\)]+)"
    

    So getting width and height you can calculate an aspect ratio.

    Note: I know that in some cases there expressions may fail.

    0 讨论(0)
提交回复
热议问题