upMp4.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | [ WE CAN DO IT MORE SIMPLE ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2018-2020 rights reserved.
  6. // +----------------------------------------------------------------------
  7. // |
  8. // +----------------------------------------------------------------------
  9. // | Date: 2021/2/20 下午12:45
  10. // +----------------------------------------------------------------------
  11. declare (strict_types=1);
  12. namespace library\lib;
  13. use library\utils\Qiniu;
  14. use library\utils\RedisCli;
  15. use library\utils\Task;
  16. use think\facade\Cache;
  17. use think\facade\Db;
  18. class upMp4
  19. {
  20. /**
  21. * 配置文件
  22. * @var type
  23. */
  24. private $config;
  25. /**
  26. * 其他属性
  27. * @var type
  28. */
  29. private $param;
  30. /**
  31. * 唯一标识符
  32. * @var type
  33. */
  34. private $gid;
  35. /**
  36. * redis缓存
  37. * @var type
  38. */
  39. private $redis;
  40. /**
  41. * 文件格式
  42. * @var type
  43. */
  44. private $file;
  45. private $error;
  46. private $qiniu;
  47. private $result;
  48. /**
  49. * 构造函数
  50. * @param type $file
  51. */
  52. public function __construct($config = [])
  53. {
  54. $option = [
  55. 'fileredis' => 'file',
  56. 'checkTmpRoot' => '', //缓存切片文件
  57. 'mp4Root' => '', //MP4目录
  58. ];
  59. $this->config = empty($config) ? $option : $config;
  60. if (!empty($_FILES['file'])) {
  61. $r = (new RedisCli);
  62. $r->connect();
  63. $this->redis = $r->getRedis();
  64. $this->qiniu = new Qiniu;
  65. $this->file = $_FILES['file'];
  66. }
  67. }
  68. /**
  69. * 设置gid
  70. * @param type $gid
  71. */
  72. public function setGid($gid)
  73. {
  74. $this->gid = $gid;
  75. }
  76. /**
  77. * 设置属性
  78. * @param type $name 文件名称
  79. * @param type $size 文件大小
  80. * @param type $chunk 文件当前chunk
  81. * @param type $chunks 文件全部chunks
  82. */
  83. public function setParam($param)
  84. {
  85. $this->param = $param;
  86. }
  87. /**
  88. * 返回错误信息
  89. * @return type
  90. */
  91. public function getError()
  92. {
  93. return $this->error;
  94. }
  95. /**
  96. * 获取结果
  97. */
  98. public function getResult()
  99. {
  100. return $this->result;
  101. }
  102. /**
  103. * 执行数据[分包上传]
  104. */
  105. public function render($imgType = 'jpg')
  106. {
  107. $chunk = $this->param['chunk'] ?? 0;
  108. $chunks = $this->param['chunks'] ?? 0;
  109. //存入token
  110. $checkRoot = $this->config['checkTmpRoot'] . date('Ymd') . '/';
  111. if (!is_dir($checkRoot)) {
  112. mkdir($checkRoot, 0777);
  113. }
  114. $name = empty($this->param['name']) ? $this->file['name'] : $this->param['name'];
  115. $size = empty($this->param['size']) ? 0 : $this->param['size'];
  116. $token = md5($name . $this->gid . $size) . '.mp4_token';
  117. $file_name = md5(microtime() . $this->file['tmp_name']) . '_' . $chunk . ".tmp";
  118. move_uploaded_file($this->file['tmp_name'], $checkRoot . $file_name);
  119. //保存缓存
  120. $save['chunk'] = $chunk;
  121. $save['chunks'] = $chunks <= 0 ? 1 : $chunks;
  122. $save['file'] = $checkRoot . $file_name;
  123. $save['size'] = $size;
  124. $save['name'] = $name;
  125. $save['status'] = 1;
  126. //压着缓存里面
  127. $count = $this->redis->lpush($token, serialize($save));
  128. if ($count == 1) {
  129. $this->redis->expire($token, 5 * 60);
  130. }
  131. //获取所以文件缓存
  132. $len = $this->redis->llen($token);
  133. if ($chunks > $len) {
  134. $this->result = $save;
  135. return true;
  136. }
  137. $path = $this->config['mp4Root'] . date('Ymd') . '/';
  138. if (!is_dir($path)) {
  139. mkdir($path, 0777);
  140. }
  141. $fileData = [];
  142. for ($i = 0; $i < $len; $i++) {
  143. $fileData[] = unserialize($this->redis->lpop($token));
  144. }
  145. //删除缓存
  146. $this->redis->del($token);
  147. //chunk
  148. $chunkAr = array_column($fileData, 'chunk');
  149. array_multisort($chunkAr, SORT_ASC, $fileData);
  150. //校验文件完整性
  151. for ($i = 0; $i < $chunks; $i++) {
  152. if (!in_array($i, $chunkAr)) {
  153. $this->error = $i . '文件校验失败,请重新上传视频';
  154. return false;
  155. }
  156. }
  157. //重组文件
  158. $file_ext = explode('.', $fileData[0]['name']);
  159. $file = md5($token) . "." . end($file_ext);
  160. foreach ($fileData as $v) {
  161. if (file_exists($v['file'])) {
  162. file_put_contents($path . $file, file_get_contents($v['file']), FILE_APPEND);
  163. @unlink($v['file']);
  164. }
  165. }
  166. $rootMp4 = $path . $file;
  167. //检验是否获取状态
  168. $mp4Info = $this->video_info($rootMp4, 'ffmpeg');
  169. //格式错误
  170. if (empty($mp4Info['vcodec'])) {
  171. $this->error = '上传格式错误,请检查是否视频文件';
  172. return false;
  173. }
  174. $tmpImg = $path . md5($file_name) . '.' . $imgType;
  175. //获取图片位置
  176. if ($imgType == 'gif') {
  177. $command = "ffmpeg -y -ss 00:00:01 -t 2 -i " . $rootMp4 . " -vf fps=5,scale=400:-1 -gifflags +transdiff -y " . $tmpImg;
  178. }
  179. if ($imgType == 'jpg') {
  180. $command = "ffmpeg -ss 00:00:02 -i " . $rootMp4 . " -f image2 " . $tmpImg;
  181. }
  182. exec($command);
  183. if (!is_file($tmpImg)) {
  184. $this->error = '生成文件失败,请检查视频是否正常';
  185. return false;
  186. }
  187. //上传第一张图七牛
  188. $result = $this->qiniu->updateFile('img', '1.' . $imgType, $tmpImg);
  189. $img = $result['url'];
  190. //视频
  191. $qUrl = $this->qiniu->getYuFile('mp4', 'mp4');
  192. $url = $this->qiniu->config['endpoint'] . $qUrl;
  193. @unlink($tmpImg);
  194. //生成视频信息
  195. //判断视频不大于1920【缩小】
  196. $save = [];
  197. $mp4Width = $this->video_width($rootMp4);
  198. $save['width'] = $mp4Width['width'];
  199. $save['height'] = $mp4Width['height'];
  200. $save['size'] = $mp4Info['size'];
  201. $save['type'] = 'mp4';
  202. $save['url'] = $url;
  203. $save['file'] = $rootMp4;
  204. $save['time'] = time();
  205. $save['play_time'] = (int)$mp4Info['play_time'];
  206. $id = Db::name('resource')->insertGetId($save);
  207. $this->result = ['url' => $url,'resourceId'=>$id, 'play_time' => $save['play_time'], 'img' => $img, 'width' => $save['width'], 'height' => $save['height']];
  208. //任务执行器
  209. return true;
  210. }
  211. /**
  212. * 视频信息
  213. * @param type $file
  214. * @param type $ffmpeg
  215. * @return type
  216. */
  217. public function video_info($file, $ffmpeg)
  218. {
  219. ob_start();
  220. passthru(sprintf($ffmpeg . ' -i "%s" 2>&1', $file));
  221. $info = ob_get_contents();
  222. ob_end_clean();
  223. // 通过使用输出缓冲,获取到ffmpeg所有输出的内容。
  224. $ret = [];
  225. // Duration: 01:24:12.73, start: 0.000000, bitrate: 456 kb/s
  226. if (preg_match("/Duration: (.*?), start: (.*?), bitrate: (\d*) kb\/s/", $info, $match)) {
  227. $ret['duration'] = $match[1]; // 提取出播放时间
  228. $da = explode(':', $match[1]);
  229. $ret['seconds'] = $da[0] * 3600 + $da[1] * 60 + $da[2]; // 转换为秒
  230. $ret['start'] = $match[2]; // 开始时间
  231. $ret['bitrate'] = $match[3]; // bitrate 码率 单位 kb
  232. }
  233. // Stream #0.1: Video: rv40, yuv420p, 512x384, 355 kb/s, 12.05 fps, 12 tbr, 1k tbn, 12 tbc
  234. if (preg_match("/Video: (.*?), (.*?), (.*?)[,\s]/", $info, $match)) {
  235. $ret['vcodec'] = $match[1]; // 编码格式
  236. $ret['vformat'] = $match[2]; // 视频格式
  237. $ret['resolution'] = $match[3]; // 分辨率
  238. $a = explode('x', $match[3]);
  239. //$ret['width'] = $a[0];
  240. //$ret['height'] = $a[1];
  241. }
  242. // Stream #0.0: Audio: cook, 44100 Hz, stereo, s16, 96 kb/s
  243. if (preg_match("/Audio: (\w*), (\d*) Hz/", $info, $match)) {
  244. $ret['acodec'] = $match[1]; // 音频编码
  245. $ret['asamplerate'] = $match[2]; // 音频采样频率
  246. }
  247. if (isset($ret['seconds']) && isset($ret['start'])) {
  248. $ret['play_time'] = $ret['seconds'] + $ret['start']; // 实际播放时间
  249. }
  250. $ret['size'] = filesize($file); // 文件大小
  251. return $ret;
  252. }
  253. /**
  254. * 获取宽度高度
  255. * @param type $file
  256. * @return string
  257. */
  258. public function video_width($file)
  259. {
  260. ob_start();
  261. 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);
  262. $info = ob_get_contents();
  263. ob_end_clean();
  264. $infoAr = json_decode($info,true);
  265. if(empty($infoAr['streams'])) {
  266. return ['width'=>0,'height'=>0,'qt'=>''];
  267. }
  268. $streams = $infoAr['streams'][0];
  269. $ret = [];
  270. if(!empty($streams['tags'])
  271. && !empty($streams['tags']['rotate'])
  272. && in_array(abs($streams['tags']['rotate']),[90,270]) ) {
  273. $ret['width'] = $streams['height'];
  274. $ret['height'] = $streams['width'];
  275. } else {
  276. $ret['width'] = empty($streams['width']) ? 0 : $streams['width'];
  277. $ret['height'] =empty($streams['height']) ? 0 : $streams['height'];
  278. }
  279. $ret['qt'] = '';
  280. if (!empty($ret['width']) && !empty($ret['height']) && is_numeric($ret['width']) && is_numeric($ret['height'])) {
  281. $w = 640;
  282. if ($ret['width'] >= $ret['height']) {
  283. $lt = intval($ret['height']) / intval($ret['width']);
  284. $hqt = intval($w * $lt);
  285. $hqt = intval($hqt / 10) * 10;
  286. $ret['qt'] = '640x' . $hqt;
  287. }
  288. if ($ret['width'] < $ret['height']) {
  289. $lt = intval($ret['width']) / intval($ret['height']);
  290. $hqt = intval($w / $lt);
  291. $hqt = intval($hqt / 10) * 10;
  292. $ret['qt'] = '640x' . $hqt;
  293. }
  294. }
  295. return $ret;
  296. }
  297. }