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=<horizontal>:<vertical>:<left>:<right>:<colour>"
【参考方案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 - 执行智能视频转换的主要内容,如果未能解决你的问题,请参考以下文章
将任何类型的视频转换为MP4 php而不用ffmpeg [关闭]
FFmpeg的使用——PHP转换视频截取视频以及JW Player播放器控制