laravel5.5集成FFmpeg,redis队列异步视频转码

1、laravel PHP-FFmpeg 扩展 下载地址:https://github.com/PHP-FFMpeg/PHP-FFMpeg

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
  6 {
  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));
 18 
 19         if ($obj && property_exists($obj, "format")) {
 20             $type           = $obj->format->format_name;
 21             $result["Type"] = $type;
 22 
 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             }
 29 
 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         }
 41 
 42         return $result;
 43     }
 44 
 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);
 55 
 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     }
 58 
 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;
 68 
 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);
100 
101         return true;
102     }
103 
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     }
129 
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     }
147 
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         }
158         
159         $size = filesize($file);
160         // 1024*1024 = 1MB
161         if (1048576 > $size) {
162             return  md5_file($file);
163         }
164 
165         $file_path = '';
166         $file_path_temp = '';
167         
168         fseek($fp, 0); // 1 - 2012
169         $file_path .= fread($fp, 2012);
170 
171         fseek($fp, $size / 2 - 1999); // size/2 - 1999
172         $file_path .= fread($fp, 1999);
173 
174         fseek($fp, -2010, SEEK_END); // -2010
175         $file_path .= fread($fp, 2010);
176         
177         fclose($fp);
178         
179         //自己定义你的临时文件路径
180         $file_path_temp = sprintf("%s/%s_%s", public_path(), time(), basename($file));
181 
182         if(! $fp_temp = fopen($file_path_temp, "wb")){
183             return false;
184         }
185         //写入截取的文件片段
186         fwrite($fp_temp, $file_path);
187         fclose($fp_temp);
188         
189         $md5 = md5_file($file_path_temp);
190         unlink($file_path_temp);
191 
192         return $md5;
193     }
194 }