123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- <?php
- // +----------------------------------------------------------------------
- // | [ WE CAN DO IT MORE SIMPLE ]
- // +----------------------------------------------------------------------
- // | Copyright (c) 2018-2020 rights reserved.
- // +----------------------------------------------------------------------
- // |
- // +----------------------------------------------------------------------
- // | Date: 2021/2/20 下午12:45
- // +----------------------------------------------------------------------
- declare (strict_types=1);
- namespace library\lib;
- use library\utils\Qiniu;
- use library\utils\RedisCli;
- use library\utils\Task;
- use think\facade\Cache;
- use think\facade\Db;
- class upMp4
- {
- /**
- * 配置文件
- * @var type
- */
- private $config;
- /**
- * 其他属性
- * @var type
- */
- private $param;
- /**
- * 唯一标识符
- * @var type
- */
- private $gid;
- /**
- * redis缓存
- * @var type
- */
- private $redis;
- /**
- * 文件格式
- * @var type
- */
- private $file;
- private $error;
- private $qiniu;
- private $result;
- /**
- * 构造函数
- * @param type $file
- */
- public function __construct($config = [])
- {
- $option = [
- 'fileredis' => 'file',
- 'checkTmpRoot' => '', //缓存切片文件
- 'mp4Root' => '', //MP4目录
- ];
- $this->config = empty($config) ? $option : $config;
- if (!empty($_FILES['file'])) {
- $r = (new RedisCli);
- $r->connect();
- $this->redis = $r->getRedis();
- $this->qiniu = new Qiniu;
- $this->file = $_FILES['file'];
- }
- }
- /**
- * 设置gid
- * @param type $gid
- */
- public function setGid($gid)
- {
- $this->gid = $gid;
- }
- /**
- * 设置属性
- * @param type $name 文件名称
- * @param type $size 文件大小
- * @param type $chunk 文件当前chunk
- * @param type $chunks 文件全部chunks
- */
- public function setParam($param)
- {
- $this->param = $param;
- }
- /**
- * 返回错误信息
- * @return type
- */
- public function getError()
- {
- return $this->error;
- }
- /**
- * 获取结果
- */
- public function getResult()
- {
- return $this->result;
- }
- /**
- * 执行数据[分包上传]
- */
- public function render($imgType = 'jpg')
- {
- $chunk = $this->param['chunk'] ?? 0;
- $chunks = $this->param['chunks'] ?? 0;
- //存入token
- $checkRoot = $this->config['checkTmpRoot'] . date('Ymd') . '/';
- if (!is_dir($checkRoot)) {
- mkdir($checkRoot, 0777);
- }
- $name = empty($this->param['name']) ? $this->file['name'] : $this->param['name'];
- $size = empty($this->param['size']) ? 0 : $this->param['size'];
- $token = md5($name . $this->gid . $size) . '.mp4_token';
- $file_name = md5(microtime() . $this->file['tmp_name']) . '_' . $chunk . ".tmp";
- move_uploaded_file($this->file['tmp_name'], $checkRoot . $file_name);
- //保存缓存
- $save['chunk'] = $chunk;
- $save['chunks'] = $chunks <= 0 ? 1 : $chunks;
- $save['file'] = $checkRoot . $file_name;
- $save['size'] = $size;
- $save['name'] = $name;
- $save['status'] = 1;
- //压着缓存里面
- $count = $this->redis->lpush($token, serialize($save));
- if ($count == 1) {
- $this->redis->expire($token, 5 * 60);
- }
- //获取所以文件缓存
- $len = $this->redis->llen($token);
- if ($chunks > $len) {
- $this->result = $save;
- return true;
- }
- $path = $this->config['mp4Root'] . date('Ymd') . '/';
- if (!is_dir($path)) {
- mkdir($path, 0777);
- }
- $fileData = [];
- for ($i = 0; $i < $len; $i++) {
- $fileData[] = unserialize($this->redis->lpop($token));
- }
- //删除缓存
- $this->redis->del($token);
- //chunk
- $chunkAr = array_column($fileData, 'chunk');
- array_multisort($chunkAr, SORT_ASC, $fileData);
- //校验文件完整性
- for ($i = 0; $i < $chunks; $i++) {
- if (!in_array($i, $chunkAr)) {
- $this->error = $i . '文件校验失败,请重新上传视频';
- return false;
- }
- }
- //重组文件
- $file_ext = explode('.', $fileData[0]['name']);
- $file = md5($token) . "." . end($file_ext);
- foreach ($fileData as $v) {
- if (file_exists($v['file'])) {
- file_put_contents($path . $file, file_get_contents($v['file']), FILE_APPEND);
- @unlink($v['file']);
- }
- }
- $rootMp4 = $path . $file;
- //检验是否获取状态
- $mp4Info = $this->video_info($rootMp4, 'ffmpeg');
- //格式错误
- if (empty($mp4Info['vcodec'])) {
- $this->error = '上传格式错误,请检查是否视频文件';
- return false;
- }
- $tmpImg = $path . md5($file_name) . '.' . $imgType;
- //获取图片位置
- if ($imgType == 'gif') {
- $command = "ffmpeg -y -ss 00:00:01 -t 2 -i " . $rootMp4 . " -vf fps=5,scale=400:-1 -gifflags +transdiff -y " . $tmpImg;
- }
- if ($imgType == 'jpg') {
- $command = "ffmpeg -ss 00:00:02 -i " . $rootMp4 . " -f image2 " . $tmpImg;
- }
- exec($command);
- if (!is_file($tmpImg)) {
- $this->error = '生成文件失败,请检查视频是否正常';
- return false;
- }
- //上传第一张图七牛
- $result = $this->qiniu->updateFile('img', '1.' . $imgType, $tmpImg);
- $img = $result['url'];
- //视频
- $qUrl = $this->qiniu->getYuFile('mp4', 'mp4');
- $url = $this->qiniu->config['endpoint'] . $qUrl;
- @unlink($tmpImg);
- //生成视频信息
- //判断视频不大于1920【缩小】
- $save = [];
- $mp4Width = $this->video_width($rootMp4);
- $save['width'] = $mp4Width['width'];
- $save['height'] = $mp4Width['height'];
- $save['size'] = $mp4Info['size'];
- $save['type'] = 'mp4';
- $save['url'] = $url;
- $save['file'] = $rootMp4;
- $save['time'] = time();
- $save['play_time'] = (int)$mp4Info['play_time'];
- $id = Db::name('resource')->insertGetId($save);
- $this->result = ['url' => $url,'resourceId'=>$id, 'play_time' => $save['play_time'], 'img' => $img, 'width' => $save['width'], 'height' => $save['height']];
- //任务执行器
- return true;
- }
- /**
- * 视频信息
- * @param type $file
- * @param type $ffmpeg
- * @return type
- */
- public function video_info($file, $ffmpeg)
- {
- ob_start();
- passthru(sprintf($ffmpeg . ' -i "%s" 2>&1', $file));
- $info = ob_get_contents();
- ob_end_clean();
- // 通过使用输出缓冲,获取到ffmpeg所有输出的内容。
- $ret = [];
- // Duration: 01:24:12.73, start: 0.000000, bitrate: 456 kb/s
- if (preg_match("/Duration: (.*?), start: (.*?), bitrate: (\d*) kb\/s/", $info, $match)) {
- $ret['duration'] = $match[1]; // 提取出播放时间
- $da = explode(':', $match[1]);
- $ret['seconds'] = $da[0] * 3600 + $da[1] * 60 + $da[2]; // 转换为秒
- $ret['start'] = $match[2]; // 开始时间
- $ret['bitrate'] = $match[3]; // bitrate 码率 单位 kb
- }
- // Stream #0.1: Video: rv40, yuv420p, 512x384, 355 kb/s, 12.05 fps, 12 tbr, 1k tbn, 12 tbc
- if (preg_match("/Video: (.*?), (.*?), (.*?)[,\s]/", $info, $match)) {
- $ret['vcodec'] = $match[1]; // 编码格式
- $ret['vformat'] = $match[2]; // 视频格式
- $ret['resolution'] = $match[3]; // 分辨率
- $a = explode('x', $match[3]);
- //$ret['width'] = $a[0];
- //$ret['height'] = $a[1];
- }
- // Stream #0.0: Audio: cook, 44100 Hz, stereo, s16, 96 kb/s
- if (preg_match("/Audio: (\w*), (\d*) Hz/", $info, $match)) {
- $ret['acodec'] = $match[1]; // 音频编码
- $ret['asamplerate'] = $match[2]; // 音频采样频率
- }
- if (isset($ret['seconds']) && isset($ret['start'])) {
- $ret['play_time'] = $ret['seconds'] + $ret['start']; // 实际播放时间
- }
- $ret['size'] = filesize($file); // 文件大小
- return $ret;
- }
- /**
- * 获取宽度高度
- * @param type $file
- * @return string
- */
- public function video_width($file)
- {
- ob_start();
- passthru("ffprobe -select_streams v -show_entries format=duration,size,bit_rate,filename -show_streams -v quiet -of csv=\"p=0\" -of json -i " . $file);
- $info = ob_get_contents();
- ob_end_clean();
- $infoAr = json_decode($info,true);
- if(empty($infoAr['streams'])) {
- return ['width'=>0,'height'=>0,'qt'=>''];
- }
- $streams = $infoAr['streams'][0];
- $ret = [];
- if(!empty($streams['tags'])
- && !empty($streams['tags']['rotate'])
- && in_array(abs($streams['tags']['rotate']),[90,270]) ) {
- $ret['width'] = $streams['height'];
- $ret['height'] = $streams['width'];
- } else {
- $ret['width'] = empty($streams['width']) ? 0 : $streams['width'];
- $ret['height'] =empty($streams['height']) ? 0 : $streams['height'];
- }
- $ret['qt'] = '';
- if (!empty($ret['width']) && !empty($ret['height']) && is_numeric($ret['width']) && is_numeric($ret['height'])) {
- $w = 640;
- if ($ret['width'] >= $ret['height']) {
- $lt = intval($ret['height']) / intval($ret['width']);
- $hqt = intval($w * $lt);
- $hqt = intval($hqt / 10) * 10;
- $ret['qt'] = '640x' . $hqt;
- }
- if ($ret['width'] < $ret['height']) {
- $lt = intval($ret['width']) / intval($ret['height']);
- $hqt = intval($w / $lt);
- $hqt = intval($hqt / 10) * 10;
- $ret['qt'] = '640x' . $hqt;
- }
- }
- return $ret;
- }
- }
|