【问题标题】:PHP and FFMPEG - Performing intelligent video conversionPHP 和 FFMPEG - 执行智能视频转换
【发布时间】:2010-11-09 13:37:58
【问题描述】:

我有一项异常艰巨的任务要执行。我以为这很容易,但我所有的努力都没有结果。

我正在将上传到 php 脚本的视频从各种格式(.avi、.mpg、.wmv、.mov 等)转换为单一的 .flv 格式。转换效果很好,但我遇到的问题是视频的分辨率。

这是我当前正在运行的命令(使用 PHP 变量):

ffmpeg -i $original -ab 96k -b 700k -ar 44100 -s 640x480 -acodec mp3 $converted

$original 和 $converted 都包含这些文件的完整路径。我的问题是,即使源较小,它也总是转换为 640x480(就像我告诉它的那样)。显然,这是在下载视频时对磁盘空间和带宽的浪费。此外,这不考虑输入视频的宽高比不是 4:3,如果我上传 16:9 的视频,则会导致“压缩”转换。

我需要做三件事:

  1. 确定原始视频的宽高比。
  2. 如果不是 4:3,则用黑条填充顶部和底部。
  3. 如果原件的尺寸较大或与原件的宽度/高度相关的宽高比为 4:3(以更接近 640x480 为准),请转换为 640x480。

我在一些视频上运行了ffmpeg -i,但我没有看到一致的格式或位置来找到原始分辨率。一旦我能够弄清楚这一点,我就知​​道我可以“做数学”来计算出正确的大小并指定填充以使用 -padttop、-padbottom 等来修复纵横比。

【问题讨论】:

    标签: php ffmpeg video-processing


    【解决方案1】:

    好的。我有一个功能齐全的解决方案。这适用于任何发现这个问题想要做同样事情的人。我的代码可能不是很优雅,但它可以完成工作。


    获取 FFMPEG 的输出

    首先我必须从 ffmpeg -i 获取输出,这本身就是一个挑战。感谢 hegemon 在my other question 上的回答,我终于能够在我的命令结束时让它与2>&1 一起工作。并且感谢 Evert 对这个问题的回答,我能够使用 preg_match 解析输出以找到原始文件的高度和宽度。

    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;
    }
    

    确定最佳尺寸

    我编写了这个函数来确定用于转换的最佳尺寸。它采用原件的尺寸、目标尺寸以及是否强制转换为目标纵横比(由其宽度/高度确定)。目标维度将始终是此函数可以返回的最大结果。

    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;
    }
    

    用法

    以下是您将从get_dimensions() 获得的一些示例,以使事情更清楚:

    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)
    

    成品

    现在,把它们放在一起:

    $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>';
        }
    }
    

    【讨论】:

    • SO 似乎由于某种原因无法呈现此答案。如果有人感兴趣,我已经将其发布在我的网站上。 andrewensley.com/2009/10/…
    • +1,我正在做同样的事情,谢谢你发布这个。 =)
    • 您可能希望更新您的答案以反映 ffmpeg api 中的更改。 -padtop 等已被删除以支持填充过滤器,即 -vf "pad=&lt;horizontal&gt;:&lt;vertical&gt;:&lt;left&gt;:&lt;right&gt;:&lt;colour&gt;"
    【解决方案2】:

    我不熟悉 PHP,但几个月前我写了一个实用程序来使用 C# 中的 ffmpeg。我使用正则表达式来做到这一点。有一些正则表达式可以帮助你:

    // 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\(\)]+)"
    

    因此获得宽度和高度,您可以计算纵横比。

    注意:我知道在某些情况下,表达式可能会失败。

    【讨论】:

    • 谢谢。一旦我能够实际捕获 ffmpeg 的输出,我也会尝试一下。 :-)
    • 如果你愿意,其实我可以发布我自己用 c# 编写的代码,但我猜它无济于事。
    • 是的。我需要 PHP 中的结果。我刚刚弄清楚了 ffmpeg 的输出,而 Evert 的解决方案对我有用。还是要谢谢你的帮助。它可能对一些正在寻找如何在 C# 中执行此操作的 Google 人员有用 =p
    【解决方案3】:

    这对我有用:

    $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)
    

    这可能并不总是有效,但它在大多数情况下都有效,这对我来说已经足够好了 :)

    【讨论】:

    • 这适用于我迄今为止测试过的每个视频。一旦我有一个强大的解决方案,我会发布我的代码作为答案,以防任何徘徊在此页面上的人需要它。谢谢。
    • 如果您有兴趣,我终于将所有部分放在一起并发布了我的答案以供参考。 stackoverflow.com/questions/1106955/…
    猜你喜欢
    • 2010-12-26
    • 2011-09-28
    • 1970-01-01
    • 1970-01-01
    • 2011-08-24
    • 1970-01-01
    • 2015-10-07
    • 1970-01-01
    相关资源
    最近更新 更多