PHP 和 FFMPEG - 执行智能视频转换

Posted

技术标签:

【中文标题】PHP 和 FFMPEG - 执行智能视频转换【英文标题】:PHP and FFMPEG - Performing intelligent video conversion 【发布时间】: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 的视频,则会导致“压缩”转换。

我需要做三件事:

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

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

【问题讨论】:

【参考方案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>\d1,3):(?<minutes>\d2):(?<seconds>\d2)(.(?<fractions>\d1,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)

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

【讨论】:

这适用于我迄今为止测试过的每个视频。一旦我有一个强大的解决方案,我会发布我的代码作为答案,以防任何徘徊在此页面上的人需要它。谢谢。 如果您有兴趣,我终于将所有部分放在一起并发布了我的答案以供参考。 ***.com/questions/1106955/…

以上是关于PHP 和 FFMPEG - 执行智能视频转换的主要内容,如果未能解决你的问题,请参考以下文章

php exec执行视频图片转换

FFmpeg + php 视屏转换

将任何类型的视频转换为MP4 php而不用ffmpeg [关闭]

FFmpeg的使用——PHP转换视频截取视频以及JW Player播放器控制

如何在没有 ffmpeg 的情况下在 php 中创建缩略图并将视频转换为 mp4? [关闭]

当我将视频转换为 mp4 时,PHP FFMPEG 不起作用 [关闭]