# -*- coding: utf-8 -*-
from json import loads
from time import time
from traceback import format_exc

import cv2
import subprocess as sp

import numpy as np
from loguru import logger
from common import Constant
from exception.CustomerException import ServiceException
from enums.ExceptionEnum import ExceptionType


class Cv2Util:
    __slots__ = [
        'pullUrl',
        'pushUrl',
        'orFilePath',
        'aiFilePath',
        'p',
        'or_video_file',
        'ai_video_file',
        'fps',
        'width',
        'height',
        'wh',
        'h',
        'w',
        'all_frames',
        'bit_rate',
        'pull_p',
        'requestId',
        'p_push_retry_num',
        'isGpu',
        'read_w_h',
        'context',
        'p_push_time'
    ]

    def __init__(self, pullUrl=None, pushUrl=None, orFilePath=None, aiFilePath=None, requestId=None, context=None,
                 gpu_ids=None):
        self.pullUrl = pullUrl
        self.pushUrl = pushUrl
        self.orFilePath = orFilePath
        self.aiFilePath = aiFilePath
        self.p = None
        self.or_video_file = None
        self.ai_video_file = None
        self.fps = None
        self.width = None
        self.height = None
        self.wh = None
        self.h = None
        self.w = None
        self.all_frames = None
        self.bit_rate = None
        self.pull_p = None
        self.requestId = requestId
        self.p_push_time = 0
        self.p_push_retry_num = 0
        self.isGpu = False
        self.read_w_h = None
        self.context = context
        if gpu_ids is not None and len(gpu_ids) > 0:
            self.isGpu = True

    def getFrameConfig(self, fps, width, height):
        if self.fps is None or self.width != width or self.height != height:
            self.fps = fps
            self.width = width
            self.height = height
            if width > Constant.width:
                self.h = int(self.height // 2)
                self.w = int(self.width // 2)
            else:
                self.h = int(self.height)
                self.w = int(self.width)

    def clear_video_info(self):
        self.fps = None
        self.width = None
        self.height = None

    '''
        获取视频信息
    '''

    def get_video_info(self):
        try:
            if self.pullUrl is None or len(self.pullUrl) == 0:
                logger.error("拉流地址不能为空, requestId:{}", self.requestId)
                raise ServiceException(ExceptionType.PULL_STREAM_URL_EXCEPTION.value[0],
                                       ExceptionType.PULL_STREAM_URL_EXCEPTION.value[1])
            args = ['ffprobe', '-show_format', '-show_streams', '-of', 'json', self.pullUrl]
            p = sp.Popen(args, stdout=sp.PIPE, stderr=sp.PIPE)
            out, err = p.communicate(timeout=20)
            if p.returncode != 0:
                raise Exception("未获取视频信息！！！！！requestId:" + self.requestId)
            probe = loads(out.decode('utf-8'))
            if probe is None or probe.get("streams") is None:
                raise Exception("未获取视频信息！！！！！requestId:" + self.requestId)
            # 视频大小
            # format = probe['format']
            # size = int(format['size'])/1024/1024
            video_stream = next((stream for stream in probe['streams'] if stream.get('codec_type') == 'video'), None)
            if video_stream is None:
                raise Exception("未获取视频信息！！！！！requestId:" + self.requestId)
            width = video_stream.get('width')
            height = video_stream.get('height')
            nb_frames = video_stream.get('nb_frames')
            fps = video_stream.get('r_frame_rate')
            # duration = video_stream.get('duration')
            # bit_rate = video_stream.get('bit_rate')
            if width is not None and int(width) != 0 and height is not None and int(height) != 0:
                self.width = int(width)
                self.height = int(height)
                self.wh = self.width * self.height * 3
                if width > Constant.width:
                    self.h = int(self.height // 2)
                    self.w = int(self.width // 2)
                else:
                    self.h = int(self.height)
                    self.w = int(self.width)
                if nb_frames:
                    self.all_frames = int(nb_frames)
                up, down = str(fps).split('/')
                self.fps = int(eval(up) / eval(down))
                if self.fps > 30:
                    logger.info("获取视频FPS大于30帧, FPS:{}, requestId:{}", self.fps, self.requestId)
                    self.fps = 30
                if self.fps < 25:
                    logger.info("获取视频FPS小于25帧, FPS:{}, requestId:{}", self.fps, self.requestId)
                    self.fps = 25
                    # if duration:
                #    self.duration = float(video_stream['duration'])
                # self.bit_rate = int(bit_rate) / 1000
                logger.info("视频信息, width:{}|height:{}|fps:{}|all_frames:{}, requestId:{}",
                            self.width, self.height, self.fps, self.all_frames, self.requestId)
        except ServiceException as s:
            logger.error("获取视频信息异常: {}, requestId:{}", s.msg, self.requestId)
            self.clear_video_info()
            raise s
        except Exception:
            logger.error("获取视频信息异常：{}, requestId:{}", format_exc(), self.requestId)
            self.clear_video_info()

    '''
        录屏任务获取视频信息
    '''

    def get_recording_video_info(self):
        try:
            video_info = 'ffprobe -show_format -show_streams -of json %s' % self.pullUrl
            p = sp.Popen(video_info, stdout=sp.PIPE, stderr=sp.PIPE, shell=True)
            out, err = p.communicate(timeout=17)
            if p.returncode != 0:
                raise Exception("未获取视频信息！！！！！requestId:" + self.requestId)
            probe = loads(out.decode('utf-8'))
            if probe is None or probe.get("streams") is None:
                raise Exception("未获取视频信息！！！！！requestId:" + self.requestId)
            video_stream = next((stream for stream in probe['streams'] if stream.get('codec_type') == 'video'), None)
            if video_stream is None:
                raise Exception("未获取视频信息！！！！！requestId:" + self.requestId)
            width = video_stream.get('width')
            height = video_stream.get('height')
            nb_frames = video_stream.get('nb_frames')
            fps = video_stream.get('r_frame_rate')
            if width and int(width) > 0:
                self.width = int(width)
            if height and int(height) > 0:
                self.height = int(height)
            if self.width and self.height:
                self.wh = int(width * height * 3)
                self.read_w_h = ([self.height, self.width, 3])
            if nb_frames and int(nb_frames) > 0:
                self.all_frames = int(nb_frames)
            if fps:
                up, down = str(fps).split('/')
                self.fps = int(eval(up) / eval(down))
            logger.info("视频信息, width:{}|height:{}|fps:{}|all_frames:{}, requestId:{}", self.width,
                        self.height, self.fps, self.all_frames, self.requestId)
        except ServiceException as s:
            logger.error("获取视频信息异常: {}, requestId:{}", s.msg, self.requestId)
            self.clear_video_info()
            raise s
        except Exception:
            logger.error("获取视频信息异常：{}, requestId:{}", format_exc(), self.requestId)
            self.clear_video_info()

    def getRecordingFrameConfig(self, fps, width, height):
        self.fps = fps
        self.width = width
        self.height = height

    '''
        录屏拉取视频
    '''

    def recording_pull_p(self):
        try:
            # 如果视频信息不存在, 不初始化拉流
            if self.checkconfig():
                return
            # 如果已经初始化, 不再初始化
            if self.pull_p:
                return
            command = ['ffmpeg -re', '-y', '-an'
                       # '-hide_banner',
                       ]
            if self.pullUrl.startswith('rtsp://'):
                command.extend(['-rtsp_transport', 'tcp'])
            if self.isGpu:
                command.extend(['-hwaccel', 'cuda'])
            command.extend(['-i', self.pullUrl,
                            '-f', 'rawvideo',
                            '-pix_fmt', 'bgr24',
                            '-r', '25',
                            '-'])
            self.pull_p = sp.Popen(command, stdout=sp.PIPE)
        except ServiceException as s:
            logger.exception("构建拉流管道异常: {}, requestId:{}", s.msg, self.requestId)
            self.clear_video_info()
            if self.pull_p:
                logger.info("重试, 关闭拉流管道, requestId:{}", self.requestId)
                self.pull_p.stdout.close()
                self.pull_p.terminate()
                self.pull_p.wait()
                self.pull_p = None
            raise s
        except Exception as e:
            logger.error("构建拉流管道异常：{}, requestId:{}", e, self.requestId)
            self.clear_video_info()
            if self.pull_p:
                logger.info("重试, 关闭拉流管道, requestId:{}", self.requestId)
                self.pull_p.stdout.close()
                self.pull_p.terminate()
                self.pull_p.wait()
                self.pull_p = None

    def recording_read(self):
        result = None
        try:
            self.recording_pull_p()
            in_bytes = self.pull_p.stdout.read(self.wh)
            if in_bytes is not None and len(in_bytes) > 0:
                try:
                    result = np.frombuffer(in_bytes, np.uint8).reshape(self.read_w_h)
                except Exception:
                    logger.error("视频格式异常：{}, requestId:{}", format_exc(), self.requestId)
                    raise ServiceException(ExceptionType.VIDEO_RESOLUTION_EXCEPTION.value[0],
                                           ExceptionType.VIDEO_RESOLUTION_EXCEPTION.value[1])
        except ServiceException as s:
            if self.pull_p:
                logger.info("重试, 关闭拉流管道, requestId:{}", self.requestId)
                self.pull_p.stdout.close()
                self.pull_p.terminate()
                self.pull_p.wait()
                self.pull_p = None
            raise s
        except Exception:
            logger.error("读流异常：{}, requestId:{}", format_exc(), self.requestId)
            if self.pull_p:
                logger.info("重试, 关闭拉流管道, requestId:{}", self.requestId)
                self.pull_p.stdout.close()
                self.pull_p.terminate()
                self.pull_p.wait()
                self.pull_p = None
        return result

    '''
        拉取视频
    '''

    def build_pull_p(self):
        try:
            command = ['ffmpeg']
            if self.pullUrl.startswith("rtsp://"):
                command.extend(['-rtsp_transport', 'tcp'])
            command.extend(['-re',
                            '-y',
                            '-an',
                            # '-hwaccel', 'cuda', cuvid
                            '-c:v', 'h264_cuvid',
                            # '-resize', self.wah,
                            '-i', self.pullUrl,
                            '-f', 'rawvideo',
                            '-pix_fmt', 'bgr24',
                            '-r', '25',
                            '-'])
            self.pull_p = sp.Popen(command, stdout=sp.PIPE)
        except ServiceException as s:
            logger.exception("构建拉流管道异常: {}, requestId:{}", s.msg, self.requestId)
            raise s
        except Exception as e:
            logger.error("构建拉流管道异常：{}, requestId:{}", format_exc(), self.requestId)
            self.clear_video_info()
            if self.pull_p:
                logger.info("重试, 关闭拉流管道, requestId:{}", self.requestId)
                self.pull_p.stdout.close()
                self.pull_p.terminate()
                self.pull_p.wait()
                self.pull_p = None

    def checkconfig(self):
        if self.width is None or self.height is None or self.fps is None:
            return True
        return False

    def read(self):
        result = None
        try:
            if self.pull_p is None:
                self.build_pull_p()
            in_bytes = self.pull_p.stdout.read(self.wh)
            if in_bytes is not None and len(in_bytes) > 0:
                try:
                    result = (np.frombuffer(in_bytes, np.uint8).reshape([self.height, self.width, 3]))
                    # img = (np.frombuffer(in_bytes, np.uint8)).reshape((self.h, self.w))
                except Exception as ei:
                    logger.error("视频格式异常：{}, requestId:{}", format_exc(), self.requestId)
                    raise ServiceException(ExceptionType.VIDEO_RESOLUTION_EXCEPTION.value[0],
                                           ExceptionType.VIDEO_RESOLUTION_EXCEPTION.value[1])
                # result = cv2.cvtColor(img, cv2.COLOR_YUV2BGR_NV12)
                # result = cv2.cvtColor(result, cv2.COLOR_RGB2BGR)
                if self.width > Constant.width:
                    result = cv2.resize(result, (self.w, self.h), interpolation=cv2.INTER_LINEAR)
        except ServiceException as s:
            raise s
        except Exception as e:
            self.clear_video_info()
            if self.pull_p:
                logger.info("关闭拉流管道, requestId:{}", self.requestId)
                self.pull_p.stdout.close()
                self.pull_p.terminate()
                self.pull_p.wait()
                self.pull_p = None
            logger.error("读流异常：{}, requestId:{}", format_exc(), self.requestId)
        return result

    def close(self):
        self.clear_video_info()
        if self.pull_p:
            if self.pull_p.stdout:
                self.pull_p.stdout.close()
            self.pull_p.terminate()
            self.pull_p.wait()
            self.pull_p = None
            logger.info("关闭拉流管道完成, requestId:{}", self.requestId)
        if self.p:
            if self.p.stdin:
                self.p.stdin.close()
            self.p.terminate()
            self.p.wait()
            self.p = None
            # self.p.communicate()
            # self.p.kill()
            logger.info("关闭管道完成, requestId:{}", self.requestId)
        if self.or_video_file:
            self.or_video_file.release()
            self.or_video_file = None
            logger.info("关闭原视频写入流完成, requestId:{}", self.requestId)
        if self.ai_video_file:
            self.ai_video_file.release()
            self.ai_video_file = None
            logger.info("关闭AI视频写入流完成, requestId:{}", self.requestId)

    # 构建 cv2
    # def build_cv2(self):
    #     try:
    #         if self.cap is not None:
    #             logger.info("重试, 关闭cap, requestId:{}", self.requestId)
    #             self.cap.release()
    #         if self.p is not None:
    #             logger.info("重试, 关闭管道, requestId:{}", self.requestId)
    #             self.p.stdin.close()
    #             self.p.terminate()
    #             self.p.wait()
    #         if self.pullUrl is None:
    #             logger.error("拉流地址不能为空, requestId:{}", self.requestId)
    #             raise ServiceException(ExceptionType.PULL_STREAM_URL_EXCEPTION.value[0],
    #                                    ExceptionType.PULL_STREAM_URL_EXCEPTION.value[1])
    #         if self.pushUrl is None:
    #             logger.error("推流地址不能为空, requestId:{}", self.requestId)
    #             raise ServiceException(ExceptionType.PUSH_STREAM_URL_EXCEPTION.value[0],
    #                                    ExceptionType.PUSH_STREAM_URL_EXCEPTION.value[1])
    #         self.cap = cv2.VideoCapture(self.pullUrl)
    #         if self.fps is None or self.fps == 0:
    #             self.fps = int(self.cap.get(cv2.CAP_PROP_FPS))
    #         if self.width is None or self.width == 0:
    #             self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    #         if self.height is None or self.height == 0:
    #             self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    #         command = ['/usr/bin/ffmpeg',
    #                    # '-y',  # 不经过确认，输出时直接覆盖同名文件。
    #                    '-f', 'rawvideo',
    #                    '-vcodec', 'rawvideo',
    #                    '-pix_fmt', 'bgr24',  # 显示可用的像素格式
    #                    # '-s', "{}x{}".format(self.width * 2, self.height),
    #                    '-s', "{}x{}".format(int(self.width), int(self.height/2)),
    #                    # '-r', str(15),
    #                    '-i', '-',  # 指定输入文件
    #                    '-g', '25',
    #                    '-b:v', '3000k',
    #                    '-tune', 'zerolatency',  # 加速编码速度
    #                    '-c:v', 'libx264',  # 指定视频编码器
    #                    '-sc_threshold', '0',
    #                    '-pix_fmt', 'yuv420p',
    #                    '-an',
    #                    '-preset', 'ultrafast',  # 指定输出的视频质量，会影响文件的生成速度，有以下几个可用的值 ultrafast,
    #                    # superfast, veryfast, faster, fast, medium, slow, slower, veryslow。
    #                    '-f', 'flv',
    #                    self.pushUrl]
    #         # 管道配置
    #         logger.info("fps:{}|height:{}|width:{}|requestId:{}", self.fps, self.height, self.width, self.requestId)
    #         self.p = sp.Popen(command, stdin=sp.PIPE)
    #     except ServiceException as s:
    #         logger.exception("构建cv2异常: {}, requestId:{}", s, self.requestId)
    #         raise s
    #     except Exception as e:
    #         logger.exception("初始化cv2异常：{}, requestId:{}", e, self.requestId)

    # 构建 cv2
    def build_p(self):
        try:
            if self.pushUrl is None or len(self.pushUrl) == 0:
                logger.error("推流地址不能为空, requestId:{}", self.requestId)
                raise ServiceException(ExceptionType.PUSH_STREAM_URL_EXCEPTION.value[0],
                                       ExceptionType.PUSH_STREAM_URL_EXCEPTION.value[1])
            command = ['ffmpeg',
                       # '-loglevel', 'debug',
                       '-re',
                       '-y',
                       "-an",
                       '-f', 'rawvideo',
                       '-vcodec', 'rawvideo',
                       '-pix_fmt', 'bgr24',
                       '-thread_queue_size', '1024',
                       '-s', "{}x{}".format(self.w * 2, self.h),
                       '-i', '-',  # 指定输入文件
                       '-r', str(25),
                       '-g', str(25),
                       '-maxrate', '6000k',
                       # '-profile:v', 'high',
                       '-b:v', '5000k',
                       # '-crf', '18',
                       # '-rc:v', 'vbr',
                       # '-cq:v', '25',
                       # '-qmin', '25',
                       # '-qmax', '25',
                       '-c:v', 'h264_nvenc',  #
                       '-bufsize', '5000k',
                       # '-c:v', 'libx264',  # 指定视频编码器
                       # '-tune', 'zerolatency',  # 加速编码速度
                       # '-sc_threshold', '0',
                       '-pix_fmt', 'yuv420p',
                       # '-flvflags', 'no_duration_filesize',
                       # '-preset', 'fast',  # 指定输出的视频质量，会影响文件的生成速度，有以下几个可用的值 ultrafast,
                       '-preset', 'p6',  # 指定输出的视频质量，会影响文件的生成速度，有以下几个可用的值 ultrafast,
                       '-tune', 'll',
                       '-f', 'flv',
                       self.pushUrl]
            logger.info("fps:{}|height:{}|width:{}|requestId:{}", self.fps, self.height, self.width,
                        self.requestId)
            self.p = sp.Popen(command, stdin=sp.PIPE, shell=False)
        except ServiceException as s:
            if self.p:
                if self.p.stdin:
                    self.p.stdin.close()
                self.p.terminate()
                self.p.wait()
            logger.exception("构建p管道异常: {}, requestId:{}", s.msg, self.requestId)
            raise s
        except Exception as e:
            if self.p:
                if self.p.stdin:
                    self.p.stdin.close()
                self.p.terminate()
                self.p.wait()
            logger.error("初始化p管道异常：{}, requestId:{}", format_exc(), self.requestId)

    def push_stream(self, frame):
        current_retry_num = 0
        while True:
            try:
                if self.p is None:
                    self.build_p()
                self.p.stdin.write(frame.tostring())
                break
            except ServiceException as s:
                raise s
            except Exception as ex:
                if self.p_push_time == 0:
                    self.p_push_time = time.time()
                if time.time() - self.p_push_time < 2:
                    self.p_push_retry_num += 1
                    self.p_push_time = time.time()
                if time.time() - self.p_push_time > 60:
                    self.p_push_retry_num = 0
                    self.p_push_time = time.time()
                logger.error("推流管道异常:{}, requestId: {}", format_exc(), self.requestId)
                if self.p:
                    try:
                        if self.p.stdin:
                            self.p.stdin.close()
                        self.p.terminate()
                        self.p.wait()
                    except:
                        logger.error("推流管道异常:{}, requestId: {}", format_exc(), self.requestId)
                self.p = None
                current_retry_num += 1
                if self.p_push_retry_num > 100:
                    logger.error("推流进管道异常：{}, requestId: {}", format_exc(), self.requestId)
                    raise ServiceException(ExceptionType.PUSH_STREAMING_CHANNEL_IS_OCCUPIED.value[0],
                                           ExceptionType.PUSH_STREAMING_CHANNEL_IS_OCCUPIED.value[1])
                if current_retry_num > 3:
                    logger.error("推流进管道异常：{}, requestId: {}", format_exc(), self.requestId)
                    raise ServiceException(ExceptionType.PUSH_STREAM_EXCEPTION.value[0],
                                           ExceptionType.PUSH_STREAM_EXCEPTION.value[1])

    def build_or_write(self):
        try:
            if self.orFilePath is not None and self.or_video_file is None:
                self.or_video_file = cv2.VideoWriter(self.orFilePath, cv2.VideoWriter_fourcc(*'mp4v'), 25,
                                                     (self.w, self.h))
                # self.or_video_file.set(cv2.CAP_PROP_BITRATE, 5000)
                if self.or_video_file is None:
                    logger.error("or_video_file为空, requestId:{}", self.requestId)
                    raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0],
                                           ExceptionType.SERVICE_INNER_EXCEPTION.value[1])
        except ServiceException as s:
            if self.or_video_file:
                self.or_video_file.release()
                self.or_video_file = None
            logger.error("构建OR文件写对象异常: {}, requestId:{}", s.msg, self.requestId)
            raise s
        except Exception as e:
            if self.or_video_file:
                self.or_video_file.release()
                self.or_video_file = None
            logger.error("构建OR文件写对象异常: {}, requestId:{}", format_exc(), self.requestId)
            raise e
        except:
            if self.or_video_file:
                self.or_video_file.release()
                self.or_video_file = None
            logger.exception("构建OR文件写对象异常:{}, requestId:{}", format_exc(), self.requestId)
            raise Exception("构建OR文件写对象异常")

    def build_ai_write(self):
        try:
            if self.aiFilePath is not None and self.ai_video_file is None:
                self.ai_video_file = cv2.VideoWriter(self.aiFilePath, cv2.VideoWriter_fourcc(*'mp4v'), 25,
                                                     (self.w * 2, self.h))
                # self.ai_video_file.set(cv2.CAP_PROP_BITRATE, 5000)
                if self.ai_video_file is None:
                    logger.error("ai_video_file为空, requestId:{}", self.requestId)
                    raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0],
                                           ExceptionType.SERVICE_INNER_EXCEPTION.value[1])
        except ServiceException as s:
            if self.ai_video_file:
                self.ai_video_file.release()
                self.ai_video_file = None
            logger.error("构建AI文件写对象异常: {}, requestId:{}", s.msg, self.requestId)
            raise s
        except Exception as e:
            if self.ai_video_file:
                self.ai_video_file.release()
                self.ai_video_file = None
            logger.error("构建AI文件写对象异常: {}, requestId:{}", format_exc(), self.requestId)
            raise e
        except:
            if self.ai_video_file:
                self.ai_video_file.release()
                self.ai_video_file = None
            logger.error("构建AI文件写对象异常:{}, requestId:{}", format_exc(), self.requestId)
            raise Exception("构建AI文件写对象异常")

    def video_or_write(self, frame):
        ai_retry_num = 0
        while True:
            try:
                if self.or_video_file is None:
                    self.build_or_write()
                self.or_video_file.write(frame)
                break
            except ServiceException as s:
                raise s
            except Exception as ex:
                if ai_retry_num > 3:
                    logger.error("重新写入原视频视频到本地, 重试失败:{}, requestId: {}", format_exc(),
                                 self.requestId)
                    raise ex
            finally:
                ai_retry_num += 1

    def video_ai_write(self, frame):
        ai_retry_num = 0
        while True:
            try:
                if self.ai_video_file is None:
                    self.build_ai_write()
                self.ai_video_file.write(frame)
                break
            except ServiceException as s:
                raise s
            except Exception as ex:
                if ai_retry_num > 3:
                    logger.exception("重新写入分析后的视频到本地,重试失败:{}, requestId: {}", format_exc(),
                                     self.requestId)
                    raise ex
            finally:
                ai_retry_num += 1

    def video_merge(self, frame1, frame2):
        # frameLeft = cv2.resize(frame1, (int(self.width / 2), int(self.height / 2)), interpolation=cv2.INTER_LINEAR)
        # frameRight = cv2.resize(frame2, (int(self.width / 2), int(self.height / 2)), interpolation=cv2.INTER_LINEAR)
        # frame_merge = np.hstack((frameLeft, frameRight))
        frame_merge = np.hstack((frame1, frame2))
        return frame_merge

    def getP(self):
        if self.p is None:
            logger.error("获取管道为空, requestId:{}", self.requestId)
            raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0],
                                   ExceptionType.SERVICE_INNER_EXCEPTION.value[1])
        return self.p

    def getOrVideoFile(self):
        if self.or_video_file is None:
            logger.error("获取原视频写入对象为空, requestId:{}", self.requestId)
            raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0],
                                   ExceptionType.SERVICE_INNER_EXCEPTION.value[1])
        return self.or_video_file

    def getAiVideoFile(self):
        if self.ai_video_file is None:
            logger.error("获取AI视频写入对象为空, requestId:{}", self.requestId)
            raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0],
                                   ExceptionType.SERVICE_INNER_EXCEPTION.value[1])
        return self.ai_video_file


def check_video_stream(width, height):
    if width is None or height is None:
        return True
    return False


def build_video_info(pull_url, requestId):
    try:
        if pull_url is None or len(pull_url) == 0:
            logger.error("拉流地址不能为空, requestId:{}", requestId)
            raise ServiceException(ExceptionType.PULL_STREAM_URL_EXCEPTION.value[0],
                                   ExceptionType.PULL_STREAM_URL_EXCEPTION.value[1])
        pp = sp.Popen(['ffprobe', '-show_format', '-show_streams', '-of', 'json', pull_url], stdout=sp.PIPE,
                      stderr=sp.PIPE)
        out, err = pp.communicate(timeout=18)
        if pp.returncode != 0:
            logger.error("获取视频信息失败: {}, requestId:{}", err.decode('utf-8'), requestId)
            raise Exception("未获取视频信息！！！！")
        probe = loads(out.decode('utf-8'))
        video_stream = next((stream for stream in probe['streams'] if stream.get('codec_type') == 'video'), None)
        if video_stream is None:
            raise Exception("未获取视频信息！！！！")
        width_new, height_new = video_stream.get('width'), video_stream.get('height')
        nb_frames = video_stream.get('nb_frames', 0)
        duration = video_stream.get('duration')
        if duration is not None and float(duration) != float(0):
            nb_frames = int(float(duration) * 25)
        if width_new is not None and int(width_new) != 0 and height_new is not None and int(height_new) != 0:
            height_o = int(height_new)
            width, height = int(width_new), height_o * 3 // 2
            width_height_3 = width * height
            all_frames = int(nb_frames)
            w_2, h_2 = width, height_o
            if width > Constant.width:
                w_2, h_2 = width // 2, height_o // 2
            logger.info("视频信息, width:{}|height:{}|all_frames:{}, requestId:{}", width, height_o, all_frames,
                        requestId)
            return width, height, width_height_3, all_frames, w_2, h_2
        raise Exception("未获取视频信息！！！！")
    except ServiceException as s:
        logger.error("获取视频信息异常: {}, requestId:{}", s.msg, requestId)
        raise s
    except Exception:
        logger.error("获取视频信息异常：{}, requestId:{}", format_exc(), requestId)
        return None, None, None, 0, None, None


def build_video_info2(pull_url, requestId):
    try:
        pp = sp.Popen(['ffprobe', '-show_format', '-show_streams', '-of', 'json', pull_url], stdout=sp.PIPE,
                      stderr=sp.PIPE)
        out, err = pp.communicate(timeout=17)
        if pp.returncode != 0:
            logger.error("获取视频信息失败: {}, requestId:{}", err.decode('utf-8'), requestId)
            raise Exception("未获取视频信息！！！！")
        probe = loads(out.decode('utf-8'))
        video_stream = next((stream for stream in probe['streams'] if stream.get('codec_type') == 'video'), None)
        if video_stream is None:
            raise Exception("未获取视频信息！！！！")
        width_new, height_new = video_stream.get('width'), video_stream.get('height')
        nb_frames = video_stream.get('nb_frames', 0)
        # fps = video_stream.get('r_frame_rate')
        duration = video_stream.get('duration')
        if duration is not None and float(duration) != float(0):
            nb_frames = int(float(duration) * 25)
        # bit_rate = video_stream.get('bit_rate')
        if width_new is not None and int(width_new) != 0 and height_new is not None and int(height_new) != 0:
            width_o, height_o = int(width_new), int(height_new)
            # width_height_3 = width * height * 3
            width_height_3 = width_o * height_o * 3 // 2
            width, height, all_frames = width_o, height_o * 3 // 2, int(nb_frames)
            w, h = width_o, height_o
            if width > Constant.width:
                w, h = width_o // 2, height_o // 2
                # up, down = str(fps).split('/')
            # self.fps = int(eval(up) / eval(down))
            # if duration:
            #    self.duration = float(video_stream['duration'])
            # self.bit_rate = int(bit_rate) / 1000
            logger.info("视频信息, width:{}|height:{}|all_frames:{}, requestId:{}", width_o, height_o, all_frames,
                        requestId)
            return width, height, width_height_3, all_frames, w, h
        raise Exception("未获取视频信息！！！！")
    except ServiceException as s:
        logger.error("获取视频信息异常: {}, requestId:{}", s.msg, requestId)
        raise s
    except Exception:
        logger.error("获取视频信息异常：{}, requestId:{}", format_exc(), requestId)
        return None, None, None, 0, None, None


def start_pull_p(pull_url, requestId):
    try:
        command = ['ffmpeg']
        if pull_url.startswith("rtsp://"):
            command.extend(['-timeout', '20000000', '-rtsp_transport', 'tcp'])
        if pull_url.startswith("http") or pull_url.startswith("rtmp"):
            command.extend(['-rw_timeout', '20000000'])
        command.extend(['-re',
                        '-y',
                        '-an',
                        # '-hwaccel', 'cuda', cuvid
                        '-c:v', 'h264_cuvid',
                        # '-resize', self.wah,
                        '-i', pull_url,
                        '-f', 'rawvideo',
                        # '-pix_fmt', 'bgr24',
                        '-r', '25',
                        '-'])
        return sp.Popen(command, stdout=sp.PIPE)
    except ServiceException as s:
        logger.error("构建拉流管道异常: {}, requestId:{}", s.msg, requestId)
        raise s
    except Exception as e:
        logger.error("构建拉流管道异常：{}, requestId:{}", format_exc(), requestId)
        raise e


def clear_pull_p(pull_p, requestId):
    try:
        if pull_p and pull_p.poll() is None:
            logger.info("关闭拉流管道, requestId:{}", requestId)
            if pull_p.stdout:
                pull_p.stdout.close()
            pull_p.terminate()
            pull_p.wait(timeout=30)
            logger.info("拉流管道已关闭, requestId:{}", requestId)
    except Exception as e:
        logger.error("关闭拉流管道异常: {}, requestId:{}", format_exc(), requestId)
        if pull_p and pull_p.poll() is None:
            pull_p.kill()
            pull_p.wait(timeout=30)
        raise e


def pull_read_video_stream(pull_p, pull_url, width, height, width_height_3, w_2, h_2, requestId):
    result = None
    try:
        if pull_p is None:
            pull_p = start_pull_p(pull_url, requestId)
        in_bytes = pull_p.stdout.read(width_height_3)
        if in_bytes is not None and len(in_bytes) > 0:
            try:
                # result = (np.frombuffer(in_bytes, np.uint8).reshape([height * 3 // 2, width, 3]))
                result = (np.frombuffer(in_bytes, np.uint8)).reshape((height, width))
                result = cv2.cvtColor(result, cv2.COLOR_YUV2BGR_NV12)
                # result = cv2.cvtColor(result, cv2.COLOR_RGB2BGR)
                if width > Constant.width:
                    result = cv2.resize(result, (w_2, h_2), interpolation=cv2.INTER_LINEAR)
            except Exception:
                logger.error("视频格式异常：{}, requestId:{}", format_exc(), requestId)
                raise ServiceException(ExceptionType.VIDEO_RESOLUTION_EXCEPTION.value[0],
                                       ExceptionType.VIDEO_RESOLUTION_EXCEPTION.value[1])
    except ServiceException as s:
        clear_pull_p(pull_p, requestId)
        raise s
    except Exception:
        clear_pull_p(pull_p, requestId)
        pull_p, width, height = None, None, None
        logger.error("读流异常：{}, requestId:{}", format_exc(), requestId)
    return result, pull_p, width, height


def pull_read_video_stream2(pull_p, pull_url, width, height, width_height_3, w, h, requestId):
    result = None
    try:
        if pull_p is None:
            pull_p = start_pull_p(pull_url, requestId)
        in_bytes = pull_p.stdout.read(width_height_3)
        if in_bytes is not None and len(in_bytes) > 0:
            try:
                result = (np.frombuffer(in_bytes, np.uint8)).reshape((height, width))
                result = cv2.cvtColor(result, cv2.COLOR_YUV2BGR_NV12)
                if width > Constant.width:
                    result = cv2.resize(result, (w, h), interpolation=cv2.INTER_LINEAR)
            except Exception:
                logger.error("视频格式异常：{}, requestId:{}", format_exc(), requestId)
                raise ServiceException(ExceptionType.VIDEO_RESOLUTION_EXCEPTION.value[0],
                                       ExceptionType.VIDEO_RESOLUTION_EXCEPTION.value[1])
    except ServiceException as s:
        clear_pull_p(pull_p, requestId)
        raise s
    except Exception:
        clear_pull_p(pull_p, requestId)
        pull_p, width, height = None, None, None
        logger.error("读流异常：{}, requestId:{}", format_exc(), requestId)
    return result, pull_p, width, height


def video_conjuncing(frame1, frame2):
    # frameLeft = cv2.resize(frame1, (int(self.width / 2), int(self.height / 2)), interpolation=cv2.INTER_LINEAR)
    # frameRight = cv2.resize(frame2, (int(self.width / 2), int(self.height / 2)), interpolation=cv2.INTER_LINEAR)
    # frame_merge = np.hstack((frameLeft, frameRight))
    frame_merge = np.hstack((frame1, frame2))
    return frame_merge


def build_push_p(push_url, width, height, requestId):
    push_p = None
    try:
        if push_url is None or len(push_url) == 0:
            logger.error("推流地址不能为空, requestId:{}", requestId)
            raise ServiceException(ExceptionType.PUSH_STREAM_URL_EXCEPTION.value[0],
                                   ExceptionType.PUSH_STREAM_URL_EXCEPTION.value[1])
        command = ['ffmpeg',
                   # '-loglevel', 'debug',
                   # '-re',
                   '-y',
                   "-an",
                   '-f', 'rawvideo',
                   '-vcodec', 'rawvideo',
                   '-pix_fmt', 'bgr24',
                   '-thread_queue_size', '1024',
                   '-s', "{}x{}".format(width, height),
                   '-i', '-',  # 指定输入文件
                   '-r', str(25),
                   '-g', str(25),
                   '-maxrate', '6000k',
                   # '-profile:v', 'high',
                   '-b:v', '4000k',
                   # '-crf', '18',
                   # '-rc:v', 'vbr',
                   # '-cq:v', '25',
                   # '-qmin', '25',
                   # '-qmax', '25',
                   '-c:v', 'h264_nvenc',  #
                   '-bufsize', '4000k',
                   # '-c:v', 'libx264',  # 指定视频编码器
                   # '-tune', 'zerolatency',  # 加速编码速度
                   # '-sc_threshold', '0',
                   # '-rc', 'cbr_ld_hq',
                   # '-zerolatency', '1',
                   '-pix_fmt', 'yuv420p',
                   # '-flvflags', 'no_duration_filesize',
                   # '-preset', 'fast',  # 指定输出的视频质量，会影响文件的生成速度，有以下几个可用的值 ultrafast,
                   '-preset', 'p6',  # 指定输出的视频质量，会影响文件的生成速度，有以下几个可用的值 ultrafast,
                   '-tune', 'll',
                   '-f', 'flv',
                   push_url]
        logger.info("height:{}|width:{}|requestId:{}", height, width, requestId)
        push_p = sp.Popen(command, stdin=sp.PIPE, shell=False)
        return push_p
    except ServiceException as s:
        if push_p:
            if push_p.stdin:
                push_p.stdin.close()
            push_p.terminate()
            push_p.wait()
        logger.error("构建p管道异常: {}, requestId:{}", s.msg, requestId)
        raise s
    except Exception as e:
        if push_p:
            if push_p.stdin:
                push_p.stdin.close()
            push_p.terminate()
            push_p.wait()
        logger.error("初始化p管道异常：{}, requestId:{}", format_exc(), requestId)
        raise e


def push_video_stream(frame, push_p, push_url, p_push_status, requestId):
    """
    :param frame: 当前视频帧
    :param push_p: 推流管道
    :param push_url: 推流地址
    :param p_push_status: 控制异常控制
    :param requestId: 请求id
    :return: 推流管道
    """
    st = time()
    try:
        if push_p is None:
            height, width = frame.shape[0:2]
            push_p = build_push_p(push_url, width, height, requestId)
        push_p.stdin.write(frame.tostring())
        return push_p
    except ServiceException as s:
        clear_push_p(push_p, requestId)
        raise s
    except Exception:
        et = time() - st
        logger.error("推流异常使用时间:{}, requestId: {}", et, requestId)
        if et > 20:
            logger.error("推流进管道异常：{}, requestId: {}", format_exc(), requestId)
            raise ServiceException(ExceptionType.PUSH_STREAMING_CHANNEL_IS_OCCUPIED.value[0],
                                   ExceptionType.PUSH_STREAMING_CHANNEL_IS_OCCUPIED.value[1])
        if p_push_status[0] == 0:
            p_push_status[0] = time()
            p_push_status[1] += 1
        elif time() - p_push_status[0] <= 60:
            p_push_status[1] += 1
            p_push_status[0] = time()
        elif time() - p_push_status[0] > 60:
            p_push_status[1] = 1
            p_push_status[0] = time()
        logger.error("推流管道异常:{}, requestId: {}", format_exc(), requestId)
        clear_push_p(push_p, requestId)
        if p_push_status[1] > 5:
            logger.error("推流进管道异常：{}, requestId: {}", format_exc(), requestId)
            raise ServiceException(ExceptionType.PUSH_STREAMING_CHANNEL_IS_OCCUPIED.value[0],
                                   ExceptionType.PUSH_STREAMING_CHANNEL_IS_OCCUPIED.value[1])
        return None


def clear_push_p(push_p, requestId):
    if push_p:
        try:
            if push_p.stdin:
                push_p.stdin.close()
            push_p.terminate()
            push_p.wait()
        except Exception:
            logger.error("推流管道异常:{}, requestId: {}", format_exc(), requestId)


def close_or_write_stream(or_video_file, requestId):
    try:
        if or_video_file:
            or_video_file.release()
    except Exception:
        logger.info("关闭原视频写流管道异常:{}, requestId:{}", format_exc(), requestId)


def close_ai_write_stream(ai_video_file, requestId):
    try:
        if ai_video_file:
            ai_video_file.release()
    except Exception:
        logger.info("关闭AI视频写流管道异常:{}, requestId:{}", format_exc(), requestId)


def close_all_p(push_p, or_video_file, ai_video_file, requestId):
    logger.info("开始停止推流、写流管道！requestId:{}", requestId)
    clear_push_p(push_p, requestId)
    close_or_write_stream(or_video_file, requestId)
    close_ai_write_stream(ai_video_file, requestId)
    logger.info("停止推流、写流管道完成！requestId:{}", requestId)


def build_or_video(orFilePath, width, height, requestId):
    or_video_file = None
    try:
        or_video_file = cv2.VideoWriter(orFilePath, cv2.VideoWriter_fourcc(*'mp4v'), 25, (width, height))
        if or_video_file is None:
            logger.error("or_video_file为空, requestId:{}", requestId)
            raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0],
                                   ExceptionType.SERVICE_INNER_EXCEPTION.value[1])
        return or_video_file
    except ServiceException as s:
        if or_video_file:
            or_video_file.release()
        logger.error("构建OR文件写对象异常: {}, requestId:{}", s.msg, requestId)
        raise s
    except Exception as e:
        if or_video_file:
            or_video_file.release()
        logger.error("构建OR文件写对象异常: {}, requestId:{}", format_exc(), requestId)
        raise e


def write_or_video(frame, orFilePath, or_video_file, or_write_status, requestId):
    """
    :param frame: 当时视频帧
    :param orFilePath: 原视频名称
    :param or_video_file: 原视频本地写流对象
    :param or_write_status: 原视频写流状态检查 [0, 0] 第一个参数时间，第二个参数重试的次数
    :param requestId: 请求id
    :return: 原视频本地写流对象
    :desc 如果写流失败了, 不重试, 丢弃本次视频帧
    """
    try:
        if or_video_file is None:
            height, width = frame.shape[0], frame.shape[1]
            or_video_file = build_or_video(orFilePath, width, height, requestId)
        or_video_file.write(frame)
        return or_video_file
    except ServiceException as s:
        if or_video_file:
            or_video_file.release()
        raise s
    except Exception as ex:
        # 当第一次写视频帧到本地失败, 更新or_write_status的时间和重试次数
        if or_write_status[0] == 0:
            or_write_status[0] = time()
            or_write_status[1] += 1
        # 1分钟内失败重试次数更新, 1分钟中容忍小于5次的失败次数
        elif time() - or_write_status[0] <= 60:
            or_write_status[1] += 1
            or_write_status[0] = time()
        # 大于1分钟初始化检查数组
        elif time() - or_write_status[0] > 60:
            or_write_status[1] = 1
            or_write_status[0] = time()
        if or_write_status[1] > 5:
            if or_video_file:
                or_video_file.release()
            logger.error("重新写入原视频视频到本地, 重试失败:{}, requestId: {}", format_exc(), requestId)
            raise ex
        return or_video_file


def build_ai_video(aiFilePath, width, height, requestId):
    ai_video_file = None
    try:
        ai_video_file = cv2.VideoWriter(aiFilePath, cv2.VideoWriter_fourcc(*'mp4v'), 25, (width, height))
        if ai_video_file is None:
            logger.error("ai_video_file为空, requestId:{}", requestId)
            raise ServiceException(ExceptionType.SERVICE_INNER_EXCEPTION.value[0],
                                   ExceptionType.SERVICE_INNER_EXCEPTION.value[1])
        return ai_video_file
    except ServiceException as s:
        if ai_video_file:
            ai_video_file.release()
        logger.error("构建AI文件写对象异常: {}, requestId:{}", s.msg, requestId)
        raise s
    except Exception as e:
        if ai_video_file:
            ai_video_file.release()
        logger.error("构建AI文件写对象异常: {}, requestId:{}", format_exc(), requestId)
        raise e


def write_ai_video(frame, aiFilePath, ai_video_file, ai_write_status, requestId):
    try:
        if ai_video_file is None:
            height, width = frame.shape[0], frame.shape[1]
            ai_video_file = build_ai_video(aiFilePath, width, height, requestId)
        ai_video_file.write(frame)
        return ai_video_file
    except ServiceException as s:
        if ai_video_file:
            ai_video_file.release()
        raise s
    except Exception as ex:
        if ai_write_status[0] == 0:
            ai_write_status[0] = time()
            ai_write_status[1] += 1
        # 大于1分钟初始化检查数组
        elif time() - ai_write_status[0] > 60:
            ai_write_status[1] = 1
            ai_write_status[0] = time()
        # 1分钟内失败重试次数更新, 1分钟中容忍小于5次的失败次数
        elif time() - ai_write_status[0] <= 60:
            ai_write_status[1] += 1
            ai_write_status[0] = time()
        if ai_write_status[1] > 5:
            if ai_video_file:
                ai_video_file.release()
            logger.error("重新写入分析后的视频到本地,重试失败:{}, requestId: {}", format_exc(), requestId)
            raise ex
        return ai_video_file
