laravel5.5集成FFmpeg,redis队列异步视频转码
2、安装方法:
$ composer require php-ffmpeg/php-ffmpeg
3、使用:
安装
redis
:
$ composer require predis/predis
配置
.env
文件:
QUEUE_DRIVER=redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=
REDIS_PORT=6379
更改
config/queue.php
:
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => '{default}',
'retry_after' => 36000,//这里设置10个小时转码消耗的最大时间
//注意:
//参数项 --timeout 的值应该是中小于配置项retry_after的值
//这是为了确保队列进程总在任务重试以前关闭。
//如果 --timeout 比retry_after 大,则你的任务可能被执行两次。
创建任务:
$ php artisan make:job MakeCoding
编辑
App/Jobs/MakeCoding.php
:
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use App\Models\Element;//你的素材模型 自己定义
class MakeCoding implements ShouldQueue
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
* The number of seconds the job can run before timing out.
* @var int
public $timeout = 30000; //最大超时时间
public $element_id; //素材id
public $relapath; //素材路径
public $chakge_path; //更改后的路径
public $tries = 3;//最大失败次数
* Create a new job instance.
* @return void
public function __construct($relapath, $element_id)
$this->relapath = $relapath;
$this->element_id = $element_id;
* Execute the job.
* @return void
public function handle()
$path = public_path() . DIRECTORY_SEPARATOR . $this->relapath;//文件保存在public下
$save_path = explode('.', $path)[0] . '.mp4';
$this->chakge_path = explode('.', $this->relapath)[0] . '.mp4';
$ffmpeg_path = Config::get("phpFFmpeg.ffmpeg"); //ffmpeg运行的路径
$ffprobe_path = Config::get("phpFFmpeg.ffprobe"); //ffprobe运行路径
$ffmpeg = \FFMpeg\FFMpeg::create(array(
'ffmpeg.binaries' => $ffmpeg_path,
'ffprobe.binaries' => $ffprobe_path,
'timeout' => 30000, // The timeout for the underlying process
'ffmpeg.threads' => 12, // The number of threads that FFMpeg should use
$default = ini_get('max_execution_time');//取得php最大超时时间,相应的修改apache或nginx的配置文件中的超时时间,和文件上传大小限制
set_time_limit(-1);//设置成永不超时
$video = $ffmpeg->open($path);
$format = new \FFMpeg\Format\Video\X264('libmp3lame', 'libx264');
$format->on('progress', function ($video, $format, $percentage) {
echo $percentage . '% ';//转码进度
if ($percentage >= 99) {//有的文件转码到99%会认为完成了
//1、删除源文件
@unlink($path);
//2、修改数据库文件路径信息转码完成,自己定义
@Element::where('id', $this->element_id)->update(['path' => $this->chakge_path, 'trans_status' => '1']);
$video->save($format, $save_path);
set_time_limit($default);//恢复php的最大超时时间
添加配置文件 config/phpFFmpeg.php:
<?php
//windows
return [
'ffmpeg' =>'C:/xampp/php/ffmpeg/bin/ffmpeg.exe',
'ffprobe' =>'C:/xampp/php/ffmpeg/bin/ffprobe.exe',
/*linux
return [
'ffmpeg' =>'/usr/local/ffmpeg/bin/ffmpeg',
'ffprobe' =>'/usr/local/ffmpeg/bin/ffprobe',
];*/
FFmpeg执行文件 linux版下载地址:http://pan.baidu.com/s/1c2GUOgC 提取码:n61c
在你的项目目录鼠标 Shift+右键 在此打开命令行 运行队列进程:
$ php artisan queue:work
编辑你的上传文件的 Controller:
<?php
use App\Jobs\MakeCoding;
//这里写伪代码
//1、上传文件
//2、保存到数据库
//3、如果是上传视频,并且不是mp4格式的,那么进行转码
//4、发送任务 参数:素材的真实保存路径和素材id
if ($type == 'video' && 'mp4' != $ext) {
dispatch(new MakeCoding($realpath, $element_id));
更多使用教程,看github文档说明。
下面是整理的一个 IOHelper :
1 <?php
2 /**
3 * IO公共操作类
4 */
5 class IOHelper
7 /**
8 * 获取音频或视频信息
9 * @param [string] $path [文件路径]
10 * @return [array] result [文件信息]
11 */
12 public static function getMediaInfo($path)
13 {
14 $result = array();
15 $cmd = sprintf('/home/samba/ffmpeg/bin/ffprobe -v quiet -print_format json -show_format -show_streams "%s" 2>&1', $path);//你的FFMpeg执行文件地址自己定义
16 exec($cmd, $arr);
17 $obj = json_decode(implode($arr));
19 if ($obj && property_exists($obj, "format")) {
20 $type = $obj->format->format_name;
21 $result["Type"] = $type;
23 if (false === stripos($type, "_pipe")) {
24 $result["Duration"] = (int) $obj->format->duration; //持续时间
25 $result["DisplayDuration"] = self::formatTime($obj->format->duration); //格式化后的时间
26 $result["byte_size"] = (int) $obj->format->size; //字节大小
27 $result["format_size"] = self::formatSize($obj->format->size); //格式化后的大小
28 }
30 if ("mp3" != $type) {
31 foreach ($obj->streams as $stream) {
32 //视频流和音频流
33
if ($stream->codec_type == "video") {
34 //找到视频流
35 $result["Resolution"] = sprintf("%sx%s", @$stream->width, @$stream->height); //分辨率
36 break;
37 }
38 }
39 }
40 }
42 return $result;
43 }
45 /**
46 * 返回格式化后的时间格式
47 */
48 private static function formatTime($sec)
49 {
50 $sec = $sec % (24 * 3600);
51 $hours = floor($sec / 3600);
52 $remainSeconds = $sec % 3600;
53 $minutes = floor($remainSeconds / 60);
54 $seconds = intval($sec - $hours * 3600 - $minutes * 60);
56 return sprintf("%s:%s:%s", str_pad($hours, 2, "0", STR_PAD_LEFT), str_pad($minutes, 2, "0", STR_PAD_LEFT), str_pad($seconds, 2, "0", STR_PAD_LEFT));
57 }
59 /**
60 * 返回格式化后的文件尺寸
61 */
62 public static function formatSize($bytes, $decimals = 2)
63 {
64 $size = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
65 $factor = floor((strlen($bytes) - 1) / 3);
66 $byte = $bytes / pow(1024, $factor);
67 $formatSize = floor($byte * 100) / 100;
69 return sprintf("%.{$decimals}f", $formatSize) . @$size[$factor];
70 }
71 /**
72 * *
73 * @return boolean [是否是windows系统]
74 */
75 public static function isWin()
76 {
77 if (substr(PHP_OS, 0, 3) === 'WIN') {
78 return true;
79 }
80 return false;
81 }
82 /**
83 * 判断目录是否为空
84 * @param [string] $dir [目录路径]
85 * @return [bool] [true/false]
86 */
87 public static function isEmptyDir($dir)
88 {
89 if (!is_dir($dir)) {
90 return "$dir is not a directory";
91 }
92 $handle = opendir($dir);
93 while (($file = readdir($handle)) !== false) {
94 if ($file != "." && $file != "..") {
95 closedir($handle);
96 return false;
97 }
98 }
99 closedir($handle);
101 return true;
102 }
104 /**
105 * 删除一个目录
106 * @param [string] $dir [目录路径]
107 * @return [type] [description]
108 */
109 public static function removeDir($dir)
110 {
111 if (! is_dir($dir))
112 {
113 return "$dir is not a directory";
114 }
115 $handle = opendir($dir);
116 while (($file = readdir($handle)) !== false)
117 {
118 if ($file != "." && $file != "..")
119 {
120 is_dir("$dir/$file") ? removeDir("$dir/$file") : unlink("$dir/$file");
121 }
122 }
123 if (readdir($handle) == false)
124 {
125 closedir($handle);
126 rmdir($dir);
127 }
128 }
130 /**
131 * 生成一个文件
132 * @param [type] $path [description]
133 * @param [type] $data [description]
134 * @return [type] [description]
135 */
136 public static function writeFile ($path, $data)
137 {
138 //$dir = str_replace('/'.basename($path),'',$path);
139 //@mkdir($dir,0777,true);
140 $fp = fopen($path, 'w');
141 if(fwrite($fp, $data)){
142 return true;
143 }
144 fclose($fp);
145 return false;
146 }
148 /**
149 * 自定义获取文件md5
150 * @param [string] $file [文件路径]
151 * @return [string] [md5值]
152 */
153 public static function md5($file)
154 {
155 if(! $fp = fopen($file, 'r')){
156 return false;
157 }
159 $size = filesize($file);
160 // 1024*1024 = 1MB
161 if (1048576 > $size) {
162 return md5_file($file);
163 }
165 $file_path = '';
166 $file_path_temp = '';
168 fseek($fp, 0); // 1 - 2012
169 $file_path .= fread($fp, 2012);
171 fseek($fp, $size / 2 - 1999); // size/2 - 1999
172 $file_path .= fread($fp, 1999);
174 fseek($fp, -2010, SEEK_END); // -2010
175 $file_path .= fread($fp, 2010);
177 fclose($fp);
179 //自己定义你的临时文件路径
180 $file_path_temp = sprintf("%s/%s_%s", public_path(), time(), basename($file));
182 if(! $fp_temp = fopen($file_path_temp, "wb")){
183 return false;
184 }
185 //写入截取的文件片段
186 fwrite($fp_temp, $file_path);
187 fclose($fp_temp);
189 $md5 = md5_file($file_path_temp);
190 unlink($file_path_temp);
192 return $md5;
193 }
194 }