<?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;
    }

}