php实现ffmpeg处理视频的实践

本文主要介绍了php实现ffmpeg处理视频的实践,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。

最近有一个项目需要使用ffmpeg处理视频,这里我写了一个demo,方便我们来实现视频操作

ffmpeg操作demo

  1. <?php
  2. namespace common\helpers;
  3. use common\models\Config;
  4. use common\models\VideoApiLog;
  5. use Yii;
  6. use yii\helpers\ArrayHelper;
  7. use common\helpers\Universal;
  8. use yii\helpers\FileHelper;
  9. use yii\httpclient\Client;
  10. use yii\web\ServerErrorHttpException;
  11. /**
  12. * ffmpeg视频处理
  13. *
  14. * @author wangjian
  15. * @since 0.1
  16. */
  17. class FfmpegVideo
  18. {
  19. public $ffmpeg = 'ffmpeg';
  20. public function __construct($ffmpeg = null)
  21. {
  22. if ($ffmpeg) {
  23. $this->ffmpeg = $ffmpeg;
  24. }
  25. }
  26. /**
  27. * 添加视频文字滚动
  28. * @param $source string 视频
  29. * @param $saveFile string 保存文件
  30. * @param $text string 水印文字
  31. * @param array $options 水印样式
  32. * @param int $step 每秒步长
  33. * @param int $star 出现时间
  34. */
  35. public function titleMod($source, $saveFile, $text, $options = [], $step = 20, $star = 0)
  36. {
  37. $command = $this->ffmpeg .' -y -i '. $source .' -async 1 -metadata:s:v:0 start_time=0 -vf ';
  38. $fonts = Yii::getAlias('@webroot') . "/fonts/simsun.ttc";
  39. $fonts = str_replace('\\', '/', $fonts);
  40. $fonts = str_replace(':', '\\:', $fonts);
  41. $command .= '"drawtext=fontfile=\''. $fonts .'\': text=\''. $text .'\'';
  42. foreach ($options as $key => $value) {
  43. $command .= ':' . $key . '=' . $value;
  44. }
  45. $command .= ':x=\'if(gte(t,'. $star .'),((t-'. $star .') * '. $step .'),NAN)\'';
  46. $command .= '" ';
  47. $command .= $saveFile;
  48. exec($command, $output, $result_code);
  49. if ($result_code == 0) {
  50. return true;
  51. }
  52. return false;
  53. }
  54. /**
  55. * 图片水印
  56. * @param $source string 视频
  57. * @param $saveFile string 保存文件
  58. * @param $waterImage string 水印图片
  59. * @param $left integer 水印水平位置
  60. * @param $top integer 水印垂直位置
  61. * @param null $star 水印开始时间
  62. * @param null $duration 水印时长
  63. */
  64. public function imageWater($source, $saveFile, $waterImage, $left, $top, $star = null, $duration = null)
  65. {
  66. $waterImage = str_replace('\\', '/', $waterImage);
  67. $waterImage = str_replace(':', '\\:', $waterImage);
  68. $command = $this->ffmpeg . ' -y -i '. $source .' -vf "movie=\''. $waterImage .'\'[watermark];';
  69. $command .= '[in][watermark] overlay='. $left .':'. $top;
  70. if ($star) {
  71. $end = ($duration) ? $star + $duration : $star;
  72. $command .= ':enable=\'between(t,'. $star .','. $end .')\'';
  73. }
  74. $command .= '[out] " ' . $saveFile;
  75. exec($command, $output, $result_code);
  76. if ($result_code == 0) {
  77. return true;
  78. }
  79. return false;
  80. }
  81. /**
  82. * 给视频添加文字水印
  83. * @param $source string 视频
  84. * @param $saveFile string 保存文件
  85. * @param $text string 水印文字
  86. * @param array $options 水印样式
  87. * @param null $star 水印开始时间
  88. * @param null $duration 水印时长
  89. */
  90. public function titleWater($source, $saveFile, $text, $options = [], $star = null, $duration = null)
  91. {
  92. $command = $this->ffmpeg .' -y -i '. $source .' -vf ';
  93. $fonts = Yii::getAlias('@webroot') . "/fonts/STZHONGS.TTF";
  94. $fonts = str_replace('\\', '/', $fonts);
  95. $fonts = str_replace(':', '\\:', $fonts);
  96. $command .= '"drawtext=fontfile=\''. $fonts .'\': text=\''. $text .'\'';
  97. foreach ($options as $key => $value) {
  98. $command .= ':' . $key . '=' . $value;
  99. }
  100. if ($star) {
  101. $end = ($duration) ? $star + $duration : $star;
  102. $command .= ':enable=\'between(t,'. $star .','. $end .')\'';
  103. }
  104. $command .= '" ';
  105. $command .= $saveFile;
  106. exec($command, $output, $result_code);
  107. if ($result_code == 0) {
  108. return true;
  109. }
  110. return false;
  111. }
  112. /**
  113. * 将音频合并到视频中
  114. * @param $videoFile string 视频文件
  115. * @param $audioFile string 音频文件
  116. * @param $saveFile string 保存文件
  117. * @param $delay integer 声音插入延时秒数
  118. */
  119. public function mergeVideoAudio($videoFile, $audioFile, $saveFile, $delay = null)
  120. {
  121. $delayTime = 0;
  122. if ($delay) {
  123. $delayTime = $delay * 1000;
  124. }
  125. $command = $this->ffmpeg . ' -y -i '. $audioFile .' -i '. $videoFile .' -c:v copy -c:a aac -strict experimental -filter_complex "[0]adelay='. $delayTime .'|'. $delayTime .'[del1],[1][del1]amix" ' . $saveFile;
  126. exec($command, $output, $result_code);
  127. if ($result_code == 0) {
  128. return true;
  129. }
  130. return false;
  131. }
  132. /**
  133. * 静音
  134. */
  135. public function audioMute($source, $saveFile)
  136. {
  137. $command = $this->ffmpeg . ' -y -i '. $source .' -filter:a "volume=0" ' . $saveFile;
  138. exec($command, $output, $result_code);
  139. if ($result_code == 0) {
  140. return true;
  141. }
  142. return false;
  143. }
  144. /**
  145. * 提取视频的音频
  146. * @param $source string 需要提取声音的视频
  147. * @param $saveFile string 提取声音后保存的音频
  148. * @return bool
  149. */
  150. public function collectAudio($source, $saveFile)
  151. {
  152. $command = $this->ffmpeg . ' -y -i '. $source .' -vn -acodec copy ' . $saveFile;
  153. exec($command, $output, $result_code);
  154. if ($result_code == 0) {
  155. return true;
  156. }
  157. return false;
  158. }
  159. /**
  160. * 去除视频声音
  161. * @param $source string 需要去除声音的视频
  162. * @param $saveFile string 去除声音后保存的视频
  163. */
  164. public function removeAudio($source, $saveFile)
  165. {
  166. $command = $this->ffmpeg . ' -y -i '. $source .' -an ' . $saveFile;
  167. exec($command, $output, $result_code);
  168. if ($result_code == 0) {
  169. return true;
  170. }
  171. return false;
  172. }
  173. /**
  174. * 视频拼接
  175. * @param $sources array 需要拼接的视频/音频
  176. * @param $saveFile string 拼接后的视频/音频
  177. */
  178. public function spliceVideo($sources, $saveFile)
  179. {
  180. $commands = [];
  181. $temporaryFile = [];
  182. $basePath = sys_get_temp_dir();
  183. $index = 0;
  184. foreach ($sources as $i => $source) {
  185. $file = $basePath . '/' . $i . '.ts';
  186. $commands[$index] = $this->ffmpeg . ' -y -i '. $source .' -vcodec copy -acodec copy -vbsf h264_mp4toannexb ' . $file;
  187. $temporaryFile[] = $file;
  188. $index++;
  189. }
  190. $commands[$index] = $this->ffmpeg . ' -y -i "concat:'. implode('|', $temporaryFile) .'" -acodec copy -vcodec copy -absf aac_adtstoasc ' . $saveFile;
  191. foreach ($commands as $command) {
  192. exec($command, $output, $result_code);
  193. }
  194. foreach ($temporaryFile as $file) {
  195. @unlink($file);
  196. }
  197. return true;
  198. }
  199. /**
  200. * 视频剪切
  201. * @param $source string 需要剪切视频/音频
  202. * @param $saveFile string 剪切后保存视频/音频
  203. * @param $star string 剪切开始时间
  204. * @param null $duration string 剪切时长
  205. */
  206. public function clipVideo($source, $saveFile, $star, $duration = null)
  207. {
  208. $command = $this->ffmpeg . ' -y -ss '. $star;
  209. if ($duration) {
  210. $command .= ' -t '. $duration;
  211. }
  212. $command .= ' -i '. $source .' -acodec copy ' . $saveFile;
  213. exec($command, $output, $result_code);
  214. if ($result_code == 0) {
  215. return true;
  216. }
  217. return false;
  218. }
  219. const ROTATE_90 = 'transpose=1';
  220. const ROTATE_180 = 'hflip,vflip';
  221. const ROTATE_270 = 'transpose=2';
  222. /**
  223. * 视频旋转
  224. * @param $source string 需要旋转的视频
  225. * @param $saveFile string 旋转后视频
  226. * @param $rotate string 旋转角度
  227. */
  228. public function transposeVideo($source, $saveFile, $rotate)
  229. {
  230. $command = $this->ffmpeg . ' -y -i ' . $source . ' -vf ""transpose=1"" ' . $saveFile;
  231. exec($command, $output, $result_code);
  232. if ($result_code == 0) {
  233. return true;
  234. }
  235. return false;
  236. }
  237. /**
  238. * 视频转码
  239. * @param $source string 需要转码的视频/音频
  240. * @param $saveFile string 转码后的视频/音频
  241. */
  242. public function acodecVideo($source, $saveFile)
  243. {
  244. $command = $this->ffmpeg . ' -y -i '. $source .' -acodec copy -vcodec copy -f mp4 ' . $saveFile;
  245. exec($command, $output, $result_code);
  246. if ($result_code == 0) {
  247. return true;
  248. }
  249. return false;
  250. }
  251. /**
  252. * 视频拼接
  253. * @param $sources array 需要拼接的视频/音频
  254. * @param $saveFile string 拼接后的视频/音频
  255. */
  256. public function concatVideo($sources, $saveFile)
  257. {
  258. $file = $this->createTemporaryFile();
  259. $fileStream = @fopen($file, 'w');
  260. if($fileStream === false) {
  261. throw new ServerErrorHttpException('Cannot open the temporary file.');
  262. }
  263. $count_videos = 0;
  264. if(is_array($sources) && (count($sources) > 0)) {
  265. foreach ($sources as $videoPath) {
  266. $line = "";
  267. if($count_videos != 0)
  268. $line .= "\n";
  269. $line .= "file '". str_replace('\\','/',$videoPath) ."'";
  270. fwrite($fileStream, $line);
  271. $count_videos++;
  272. }
  273. }
  274. else {
  275. throw new ServerErrorHttpException('The list of videos is not a valid array.');
  276. }
  277. $command = $this->ffmpeg .' -y -f concat -safe 0 -i '. $file . ' -c copy ' . $saveFile;
  278. exec($command, $output, $result_code);
  279. fclose($fileStream);
  280. @unlink($file);//删除文件
  281. if ($result_code == 0) {
  282. return true;
  283. }
  284. return false;
  285. }
  286. /**
  287. * 创建一个临时文件
  288. */
  289. public function createTemporaryFile()
  290. {
  291. $basePath = sys_get_temp_dir();
  292. if (false === $file = @tempnam($basePath, null)) {
  293. throw new ServerErrorHttpException('Unable to generate a temporary filename');
  294. }
  295. return $file;
  296. }
  297. /**
  298. * 获取视频信息
  299. * @param $source string 需要获取时长的资源
  300. */
  301. public function getAttributes($source)
  302. {
  303. ob_start();
  304. $command = $this->ffmpeg . ' -i "'. $source .'" 2>&1';
  305. passthru($command);
  306. $getContent = ob_get_contents();
  307. ob_end_clean();
  308. $duration = 0;
  309. $widht = 0;
  310. $height = 0;
  311. if (preg_match("/Duration: (.*?), start: (.*?), bitrate: (\d*) kb\/s/", $getContent, $match)) {
  312. $matchs = explode(':', $match[1]);
  313. $duration = $matchs[0] * 3600 + $matchs[1] * 60 + $matchs[2]; //转换播放时间为秒数
  314. }
  315. if (preg_match("/Video: (.*?), (.*?), (.*?)[,\s]/", $getContent, $match)) {
  316. $matchs = explode('x', $match[3]);
  317. $widht = $matchs[0];
  318. $height = $matchs[1];
  319. }
  320. return [
  321. 'duration' => intval($duration),
  322. 'widht' => intval($widht),
  323. 'height' => intval($height),
  324. ];
  325. }
  326. }

使用简单示例

这里注意如果无法执行ffmpeg,实例化时需要传入ffmpeg的安装地址,例如linux下ffmpeg安装地址为/usr/local/ffmepg,那么实例化时需要传入/usr/local/ffmpeg/bin/ffmpeg

1:给视频添加文字

  1. $ffmpeg = new FfmpegVideo();
  2. $ffmpeg ->titleWater(
  3. 'XXX',//原视频
  4. 'XXX',//处理后保存视频
  5. 'XXX',//文字
  6. [
  7. 'x' => 30,//水平距离
  8. 'y' => 30,//垂直距离
  9. 'fontsize' => 20,//文字大小
  10. 'fontcolor' => 'red',//文字颜色
  11. 'shadowy' => 2,//文字阴影
  12. ],
  13. 200,//每秒移动步长
  14. 2//文字出现时间(秒)
  15. );

2:将视频设为静音

  1. $ffmpeg = new FfmpegVideo();
  2. $ffmpeg->audioMute(
  3. 'XXX',//原视频
  4. 'XXX',//处理后保存视频
  5. );

3:视频裁剪

  1. $ffmpeg = new FfmpegVideo();
  2. $ffmpeg->clipVideo(
  3. 'XXX',//原视频
  4. 'XXX',//处理后保存视频
  5. 0,//裁剪开始时间
  6. 10//裁剪时长
  7. );

4:视频拼接

  1. $ffmpeg = new FfmpegVideo();
  2. $ffmpeg->concatVideo(
  3. ['XXX', 'XXX'],//需要拼接的视频
  4. 'XXX',//处理后保存视频
  5. );

5:将音频合并到视频中

  1. $ffmpeg = new FfmpegVideo();
  2. $ffmpeg->mergeVideoAudio(
  3. 'XXX',//视频
  4. 'XXX',//音频
  5. 'XXX',//处理后保存视频
  6. 0//音频插入视频延时时间(秒)
  7. );

6:获取视频信息(长,宽,时长)

  1. $ffmpeg = new FfmpegVideo();
  2. $ffmpeg->getAttributes(
  3. 'XXX',//视频
  4. );