#!/usr/bin/env python

import logging
logger = logging.getLogger(__name__)

class BaseCodec(object):
    """
    Base audio/video codec class.
    """

    encoder_options = {}
    codec_name = None
    ffmpeg_codec_name = None

    def parse_options(self, opt):
        if 'codec' not in opt or opt['codec'] != self.codec_name:
            raise ValueError('invalid codec name')
        return None

    def _codec_specific_parse_options(self, safe):
        return safe

    def _codec_specific_produce_ffmpeg_list(self, safe):
        return []

    def safe_options(self, opts):
        safe = {}

        # Only copy options that are expected and of correct type
        # (and do typecasting on them)
        for k, v in opts.items():
            if k in self.encoder_options:
                typ = self.encoder_options[k]
                try:
                    safe[k] = typ(v)
                except:
                    pass

        return safe

class AudioCodec(BaseCodec):
    """
    Base audio codec class handles general audio options. Possible
    parameters are:
      * codec (string) - audio codec name
      * channels (integer) - number of audio channels
      * bitrate (integer) - stream bitrate
      * samplerate (integer) - sample rate (frequency)

    Supported audio codecs are: null (no audio), copy (copy from
    original), vorbis, aac, mp3, mp2
    """

    encoder_options = {
        'codec': str,
        'channels': int,
        'bitrate': int,
        'samplerate': int,
        'filters': str
    }

    def _extend_af(self, optlist, value):

        if not value:
            return optlist

        if optlist.count('-af'):
            current_af = optlist[optlist.index('-af') + 1]
            new_af = "{0},{1}".format(current_af, value)  # append filters to current
            optlist[optlist.index('-af') + 1] = new_af
        else:
            optlist.extend(['-af', value])
        return optlist

    def parse_options(self, opt):
        super(AudioCodec, self).parse_options(opt)

        safe = self.safe_options(opt)

        if 'channels' in safe:
            c = safe['channels']
            if c < 1 or c > 12:
                del safe['channels']

        if 'bitrate' in safe:
            br = safe['bitrate']
            if br < 8 or br > 512:
                del safe['bitrate']

        if 'samplerate' in safe:
            f = safe['samplerate']
            if f < 1000 or f > 50000:
                del safe['samplerate']

        safe = self._codec_specific_parse_options(safe)

        optlist = ['-c:a', self.ffmpeg_codec_name]
        if 'channels' in safe:
            optlist.extend(['-ac', str(safe['channels'])])
        if 'bitrate' in safe:
            optlist.extend(['-ab', str(safe['bitrate']) + 'k'])
        if 'samplerate' in safe:
            optlist.extend(['-ar', str(safe['samplerate'])])
        if 'volume' in safe:
            optlist.extend(['-af', 'volume={0:.1f}dB'.format(safe['volume'])])
        if 'filters' in safe:
            optlist = self._extend_af(optlist, safe['filters'])

        optlist.extend(self._codec_specific_produce_ffmpeg_list(safe))
        return optlist

class SubtitleCodec(BaseCodec):
    """
    Base subtitle codec class handles general subtitle options. Possible
    parameters are:
      * codec (string) - subtitle codec name (mov_text, subrib, ssa only supported currently)
      * language (string) - language of subtitle stream (3 char code)
      * forced (int) - force subtitles (1 true, 0 false)
      * default (int) - default subtitles (1 true, 0 false)

    Supported subtitle codecs are: null (no subtitle), mov_text
    """

    encoder_options = {
        'codec': str,
        'language': str,
        'forced': int,
        'default': int
    }

    def parse_options(self, opt):
        super(SubtitleCodec, self).parse_options(opt)
        safe = self.safe_options(opt)

        if 'forced' in safe:
            f = safe['forced']
            if f < 0 or f > 1:
                del safe['forced']

        if 'default' in safe:
            d = safe['default']
            if d < 0 or d > 1:
                del safe['default']

        if 'language' in safe:
            l = safe['language']
            if len(l) > 3:
                del safe['language']

        safe = self._codec_specific_parse_options(safe)

        optlist = ['-c:s', self.ffmpeg_codec_name]

        optlist.extend(self._codec_specific_produce_ffmpeg_list(safe))
        return optlist

class DecoderCodec(BaseCodec):
    """
    """
    encoder_options = {
        'codec': str,
    }

    def parse_options(self, opt):
        super(DecoderCodec, self).parse_options(opt)
        safe = self.safe_options(opt)
        safe = self._codec_specific_parse_options(safe)

        optlist = ['-decoder', self.ffmpeg_codec_name]

        optlist.extend(self._codec_specific_produce_ffmpeg_list(safe))
        return optlist

class VideoCodec(BaseCodec):
    """
    Base video codec class handles general video options. Possible
    parameters are:
      * codec (string) - video codec name
      * bitrate (float) - stream bitrate
      * fps (float) - frames per second
      * max_width (integer) - video width
      * max_height (integer) - video height
      * filters (string) - filters (flip, rotate, etc)
      * sizing_policy (string) - aspect preserval mode; one of:
            ...
      * src_width (int) - source width
      * src_height (int) - source height
      * src_rotate (90) -

    Aspect preserval mode is only used if both source
    and both destination sizes are specified. If source
    dimensions are not specified, aspect settings are ignored.

    If source dimensions are specified, and only one
    of the destination dimensions is specified, the other one
    is calculated to preserve the aspect ratio.

    Supported video codecs are: null (no video), copy (copy directly
    from the source), Theora, H.264/AVC, DivX, VP8, H.263, Flv,
    MPEG-1, MPEG-2.
    """

    encoder_options = {
        'codec': str,
        'bitrate': float,
        'fps': float,
        'pix_fmt': str,
        'max_width': int,
        'max_height': int,
        'sizing_policy': str,
        'src_width': int,
        'src_height': int,
        'src_rotate': int,
        'crop': str,
        'filters': str,
        'autorotate': bool,
        'bufsize': int,
    }

    def _autorotate(self, src_rotate):
        filters = {
            90: "transpose=1",
            180: "transpose=2,transpose=2",
            270: "transpose=2"
        }
        return filters.get(src_rotate)

    def _extend_vf(self, optlist, value):

        if not value:
            return optlist

        if optlist.count('-vf'):
            current_vf = optlist[optlist.index('-vf') + 1]
            new_vf = "{0},{1}".format(current_vf, value)  # append filters to current
            optlist[optlist.index('-vf') + 1] = new_vf
        else:
            optlist.extend(['-vf', value])
        return optlist

    def _div_by_2(self, d):
        return float(d) + 1 if float(d) % 2 else float(d)

    def _aspect_corrections(self, sw, sh, max_width, max_height, sizing_policy):
        if not max_width or not max_height or not sw or not sh:
            return sw, sh, None

        if sizing_policy not in ['Fit', 'Fill', 'Stretch', 'Keep', 'ShrinkToFit', 'ShrinkToFill']:
            print ("invalid option "+sizing_policy)
            return sw, sh, None

        """
        Fit: FFMPEG scales the output video so it matches the value
        that you specified in either Max Width or Max Height without exceeding the other value."
        """
        if sizing_policy == 'Fit':
            if float(sh / sw) == float(max_height):
                return max_width, max_height, None
            elif float(sh / sw) < float(max_height):  # scaling height
                factor = float(float(max_height) / float(sh))
                return int(sw * factor), max_height, None
            else:
                factor = float(float(max_width) / float(sw))
                return max_width, int(sh * factor), None

        """
        Fill: FFMPEG scales the output video so it matches the value that you specified
        in either Max Width or Max Height and matches or exceeds the other value. Elastic Transcoder
        centers the output video and then crops it in the dimension (if any) that exceeds the maximum value.
        """
        if sizing_policy == 'Fill':
            if float(sh / sw) == float(max_height):
                return max_width, max_height, None
            elif float(sh / sw) < float(max_height):  # scaling width
                factor = float(float(max_width) / float(sw))
                h0 = int(sh * factor)
                dh = (h0 - max_height) / 2
                return max_width, h0, 'crop={0}:{1}:{2}:0'.format(max_width, max_height, dh)
            else:
                factor = float(float(max_height) / float(sh))
                w0 = int(sw * factor)
                dw = (w0 - max_width) / 2
                return w0, max_height, 'crop={0}:{1}:{2}:0'.format(max_width, max_height, dw)

        """
        Stretch: FFMPEG stretches the output video to match the values that you specified for Max
        Width and Max Height. If the relative proportions of the input video and the output video are different,
        the output video will be distorted.
        """
        if sizing_policy == 'Stretch':
            return max_width, max_height, None

        """
        Keep: FFMPEG does not scale the output video. If either dimension of the input video exceeds
        the values that you specified for Max Width and Max Height, FFMPEG crops the output video.
        """
        if sizing_policy == 'Keep':
            return sw, sh, None

        """
        ShrinkToFit: FFMPEG scales the output video down so that its dimensions match the values that
        you specified for at least one of Max Width and Max Height without exceeding either value. If you specify
        this option, Elastic Transcoder does not scale the video up.
        """
        if sizing_policy == 'ShrinkToFit':
            if sh > max_height or sw > max_width:
                if float(sh / sw) == float(max_height):
                    return max_width, max_height, None
                elif float(sh / sw) < float(max_height):  # target is taller
                    factor = float(float(max_height) / float(sh))
                    return int(sw * factor), max_height, None
                else:
                    factor = float(float(max_width) / float(sw))
                    return max_width, int(sh * factor), None
            else:
                return sw, sh, None

        """
        ShrinkToFill: FFMPEG scales the output video down so that its dimensions match the values that
        you specified for at least one of Max Width and Max Height without dropping below either value. If you specify
        this option, FFMPEG does not scale the video up.
        """
        if sizing_policy == 'ShrinkToFill':
            if sh < max_height or sw < max_width:
                if float(sh / sw) == float(max_height):
                    return max_width, max_height, None
                elif float(sh / sw) < float(max_height):  # scaling width
                    factor = float(float(max_width) / float(sw))
                    h0 = int(sh * factor)
                    dh = (h0 - max_height) / 2
                    return max_width, h0, 'crop=%d:%d:%d:0' % (max_width, max_height, dh)
                else:
                    factor = float(float(max_height) / float(sh))
                    w0 = int(sw * factor)
                    dw = (w0 - max_width) / 2
                    return w0, max_height, 'crop={0}:{1}:{2}:0'.format(max_width, max_height, dw)
            else:
                return int(sw*factor), max_height, None

        assert False, sizing_policy

    def parse_options(self, opt):
        super(VideoCodec, self).parse_options(opt)

        safe = self.safe_options(opt)

        if 'fps' in safe:
            f = safe['fps']
            if f < 1 or f > 120:
                del safe['fps']

        if 'bitrate' in safe:
            br = safe['bitrate']
            if br < 0.1 or br > 200:
                del safe['bitrate']

        if 'bufsize' in safe:
            bufsize = safe['bufsize']
            if bufsize < 1 or bufsize > 10000:
                del safe['bufsize']

        w = h = None

        if 'max_width' in safe:
            w = safe['max_width']
            if w < 16 or w > 4000:
                w = None
            elif w % 2:
                w += 1

        if 'max_height' in safe:
            h = safe['max_height']
            if h < 16 or h > 3000:
                h = None
            elif h % 2:
                h += 1

        crop_width, crop_height = None, None

        if 'crop' in safe:
            try:
                crop_width, crop_height, _, _ = safe['crop'].split(':')
                crop_width, crop_height = int(crop_width), int(crop_height)
            except:
                del safe['crop']
            else:
                if crop_width < 16 or crop_width > 4100:
                    crop_width = None
                if crop_height < 16 or crop_height > 3000:
                    crop_height = None

        if crop_width and crop_height:
            sw = crop_width
            sh = crop_height
        else:
            sw = safe.get('src_width', None)
            sh = safe.get('src_height', None)

        sizing_policy = 'Keep'
        if 'sizing_policy' in safe:
            if safe['sizing_policy'] in ['Fit', 'Fill', 'Stretch', 'Keep', 'ShrinkToFit', 'ShrinkToFill']:
                sizing_policy = safe['sizing_policy']

        w, h, filters = self._aspect_corrections(sw, sh, w, h, sizing_policy)
        w = self._div_by_2(w)
        h = self._div_by_2(h)

        safe['max_width'] = w
        safe['max_height'] = h
        safe['aspect_filters'] = filters

        # swap height and width if vertical rotate
        if safe.get('autorotate') and 'src_rotate' in safe:
            if safe['src_rotate'] in [90, 270]:
                old_w = w
                old_h = h
                safe['max_width'] = w = old_h
                safe['max_height'] = h = old_w

        if w and h:
            safe['aspect'] = '{0}:{1}'.format(w, h)

        safe = self._codec_specific_parse_options(safe)

        #w = safe['max_width']
        #h = safe['max_height']
        filters = safe['aspect_filters']

        # Use the most common pixel format by default. If the selected pixel format can not be selected,
        # ffmpeg select the best pixel format supported by the encoder.
        pix_fmt = safe.get('pix_fmt', 'yuv420p')

        optlist = ['-c:v', self.ffmpeg_codec_name, '-pix_fmt', pix_fmt]

        if 'fps' in safe:
            optlist.extend(['-r', str(round(safe['fps'], 2))])
        if 'bitrate' in safe:
            optlist.extend(['-vb', str(safe['bitrate']) + 'M'])
        if 'bufsize' in safe:
            optlist.extend(['-bufsize', str(safe['bufsize']) + 'k'])

        if 'vaapi' not in self.ffmpeg_codec_name:
            if w and h:
                optlist.extend(['-s', '{0}x{1}'.format(int(w), int(h))])
                if 'aspect' in safe:
                    optlist.extend(['-aspect', '{0}:{1}'.format(int(w), int(h))])

        if safe.get('crop'):
            optlist = self._extend_vf(optlist, 'crop={0}'.format(safe['crop']))

        if filters:
            optlist = self._extend_vf(filters)

        if safe.get('autorotate', False) and 'src_rotate' in safe:
            rotate_filter = self._autorotate(safe['src_rotate'])
            optlist = self._extend_vf(optlist, rotate_filter)

        if 'filters' in safe:
            optlist = self._extend_vf(optlist, safe['filters'])

        optlist.extend(self._codec_specific_produce_ffmpeg_list(safe))
        return optlist

class AudioNullCodec(BaseCodec):
    """
    Null audio codec (no audio).
    """
    codec_name = None

    def parse_options(self, opt):
        return ['-an']

class VideoNullCodec(BaseCodec):
    """
    Null video codec (no video).
    """

    codec_name = None

    def parse_options(self, opt):
        return ['-vn']

class SubtitleNullCodec(BaseCodec):
    """
    Null video codec (no video).
    """

    codec_name = None

    def parse_options(self, opt):
        return ['-sn']

class DecoderNullCodec(BaseCodec):
    """
    Null video codec (no video).
    """

    codec_name = None

    def parse_options(self, opt):
        return []

class AudioCopyCodec(BaseCodec):
    """
    Copy audio stream directly from the source.
    """
    codec_name = 'copy'

    def parse_options(self, opt):
        return ['-c:a', 'copy']

class VideoCopyCodec(BaseCodec):
    """
    Copy video stream directly from the source.
    """
    codec_name = 'copy'

    def parse_options(self, opt):
        return ['-c:v', 'copy']

class SubtitleCopyCodec(BaseCodec):
    """
    Copy subtitle stream directly from the source.
    """
    codec_name = 'copy'

    def parse_options(self, opt):
        return ['-c:s', 'copy']

# Audio Codecs
class VorbisCodec(AudioCodec):
    """
    Vorbis audio codec.
    @see http://ffmpeg.org/trac/ffmpeg/wiki/TheoraVorbisEncodingGuide
    """
    codec_name = 'vorbis'
    ffmpeg_codec_name = 'libvorbis'
    encoder_options = AudioCodec.encoder_options.copy()
    encoder_options.update({
        'quality': int,  # audio quality. Range is 0-10(highest quality)
        # 3-6 is a good range to try. Default is 3
    })

    def _codec_specific_produce_ffmpeg_list(self, safe):
        optlist = []
        if 'quality' in safe:
            optlist.extend(['-qscale:a', safe['quality']])
        return optlist

class AacCodec(AudioCodec):
    """
    AAC audio codec.
    """
    codec_name = 'aac'
    ffmpeg_codec_name = 'aac'
    aac_experimental_enable = ['-strict', 'experimental']

    def _codec_specific_produce_ffmpeg_list(self, safe):
        return self.aac_experimental_enable

class FdkAacCodec(AudioCodec):
    """
    AAC audio codec.
    """
    codec_name = 'libfdk_aac'
    ffmpeg_codec_name = 'libfdk_aac'

class Ac3Codec(AudioCodec):
    """
    AC3 audio codec.
    """
    codec_name = 'ac3'
    ffmpeg_codec_name = 'ac3'

class FlacCodec(AudioCodec):
    """
    FLAC audio codec.
    """
    codec_name = 'flac'
    ffmpeg_codec_name = 'flac'

class DtsCodec(AudioCodec):
    """
    DTS audio codec.
    """
    codec_name = 'dts'
    ffmpeg_codec_name = 'dts'

class Mp3Codec(AudioCodec):
    """
    MP3 (MPEG layer 3) audio codec.
    """
    codec_name = 'mp3'
    ffmpeg_codec_name = 'libmp3lame'

class Mp2Codec(AudioCodec):
    """
    MP2 (MPEG layer 2) audio codec.
    """
    codec_name = 'mp2'
    ffmpeg_codec_name = 'mp2'

# Video Codecs
class TheoraCodec(VideoCodec):
    """
    Theora video codec.
    @see http://ffmpeg.org/trac/ffmpeg/wiki/TheoraVorbisEncodingGuide
    """
    codec_name = 'theora'
    ffmpeg_codec_name = 'libtheora'
    encoder_options = VideoCodec.encoder_options.copy()
    encoder_options.update({
        'quality': int,  # audio quality. Range is 0-10(highest quality)
        # 5-7 is a good range to try (default is 200k bitrate)
    })

    def _codec_specific_produce_ffmpeg_list(self, safe):
        optlist = []
        if 'quality' in safe:
            optlist.extend(['-qscale:v', safe['quality']])
        return optlist

class H264VaapiCodec(VideoCodec):
    """
    H264/AVC Video codec by Mesa
    """
    codec_name = 'h264_vaapi'
    ffmpeg_codec_name = 'h264_vaapi'
    encoder_options = VideoCodec.encoder_options.copy()
    encoder_options.update({
        'qp': int,
        'quality': int,
        'low_power': int,
        'coder': int,
        'hwaccel_device': str,
    })

    def _codec_specific_produce_ffmpeg_list(self, safe):
        optlist = []
        if 'qp' in safe:
            if 0 <= safe['qp'] <= 52:
                optlist.extend(['-qp', str(safe['qp'])])
            else:
                logger.error('Constant QP ({}) invalid. Reverting to default (20) ...'.format(safe['qp']))
                optlist.extend(['-qp', '20'])
        if 'quality' in safe:
            if 0 <= safe['quality'] <= 8:
                optlist.extend(['-quality', str(safe['quality'])])
            else:
                logger.error('Encode quality invalid ({}). Reverting to default (0)...'.format(safe['quality']))
                optlist.extend(['-quality', '0'])
        if 'low_power' in safe:
            if safe['low_power'] in [0, 1]:
                optlist.extend(['-low_power', str(safe['low_power'])])
            else:
                logger.error('Invalid low_power ({})'.format(safe['low_power']))
        if 'coder' in safe:
            if safe['coder'] in [0, 1]:
                optlist.extend(['-coder', str(safe['coder'])])
            else:
                logger.error('Invalid coder ({})'.format(safe['coder']))
        return optlist

class HevcVaapiCodec(VideoCodec):
    """
    H264/AVC Video codec by Mesa
    """
    codec_name = 'hevc_vaapi'
    ffmpeg_codec_name = 'hevc_vaapi'
    encoder_options = VideoCodec.encoder_options.copy()
    encoder_options.update({
        'qp': int,
        'vprofile': int,
    })

    def _codec_specific_produce_ffmpeg_list(self, safe):
        optlist = []
        if 'qp' in safe:
            if 0 <= safe['qp'] <= 52:
                optlist.extend(['-qp', str(safe['qp'])])
            else:
                logger.error('Constant QP ({}) invalid. Reverting to default (25) ...'.format(safe['qp']))
                optlist.extend(['-qp', '25'])

        if 'vprofile' in safe:
            if safe['vprofile'] in [2]:
                optlist.extend(['-profile:v', str(safe['vprofile'])])
            else:
                logger.error('Profile ({}) invalid.'.format(safe['vprofile']))
        return optlist

class H264NvencCodec(VideoCodec):
    """
    H264/AVC Video codec by Nvidia.
    """
    codec_name = 'h264_nvenc'
    ffmpeg_codec_name = 'h264_nvenc'
    encoder_options = VideoCodec.encoder_options.copy()
    encoder_options.update({
        'preset': str,
        'profile': str,
        'level': str,
        'rc': str,
        'rc-lookahead': int,
        'surfaces': int,
        'cbr': bool,
        '2pass': bool,
        'gpu': int,
        'delay': int,
        'no-scenecut': bool,
        'forced-idr': bool,
        'b_adapt': bool,
        'spatial-aq': bool,
        'temporal-aq': bool,
        'zerolatency': bool,
        'nonref_p': bool,
        'strict_gop': bool,
        'aq-strength': bool,
        'cq': float,
        'qmin': float,
        'qmax': float
    })

    def _codec_specific_produce_ffmpeg_list(self, safe):
        optlist = []
        if 'preset' in safe:
            if safe['preset'] in ['default', 'slow', 'medium', 'fast', 'hp', 'hq', 'bd', 'll', 'llhq', 'llhp', 'lossless', 'losslesshp']:
                optlist.extend(['-preset', safe['preset']])
            else:
                logger.error(safe['preset']+' is not a valid preset for nvenc_h264 encoder ...')
                optlist.extend(['-preset', 'medium'])
        if 'profile' in safe:
            if safe['profile'] in ['baseline', 'main', 'high', 'high444p']:
                optlist.extend(['-profile:v', safe['profile']])
            else:
                logger.error(safe['profile']+' is not a valid profile for nvenc_h264 encoder ...')
                optlist.extend(['-profile:v', 'main'])
        if 'level' in safe:
            if safe['level'] in ['auto', '1', '1.0', '1b', '1.0b', '1.1', '1.2', '1.3', '2', '2.0', '2.1', '2.2', '3', '3.0', '3.1', '3.2', '4', '4.0', '4.1', '4.2', '5', '5.0', '5.1']:
                optlist.extend(['-level', safe['level']])
            else:
                logger.error(safe['level']+' is not a valid level for nvenc_h264 encoder ...')
                optlist.extend(['-level', 'auto'])
        if 'rc' in safe:
            if safe['rc'] in ['constqp', 'vbr', 'cbr', 'vbr_minqp', 'll_2pass_quality', 'll_2pass_size', 'vbr_2pass']:
                optlist.extend(['-rc', safe['rc']])
            else:
                logger.error(safe['rc']+' is not a valid rc for nvenc_h264 encoder ...')
                optlist.extend(['-rc', '-1'])
        if 'rc-lookahead' in safe:
            if safe['rc-lookahead'] >= -1:
                optlist.extend(['-rc-lookahead', str(safe['rc-lookahead'])])
            else:
                logger.error(str(safe['rc-lookahead'])+' is not a valid rc-lookahead for nvenc_h264 encoder ...')
                optlist.extend(['-rc-lookahead', '-1'])
        if 'surfaces' in safe:
            if safe['surfaces'] >= 0:
                optlist.extend(['-surfaces', str(safe['surfaces'])])
            else:
                logger.error(str(safe['surfaces'])+' is not a valid surfaces for nvenc_h264 encoder ...')
                optlist.extend(['-surfaces', '32'])
        if 'cbr' in safe:
            if type(safe['cbr']) == bool:
                optlist.extend(['-cbr', str(int(safe['cbr']))])
            else:
                logger.error(str(int(safe['cbr']))+' is not a valid cbr for nvenc_h264 encoder ...')
                optlist.extend(['-cbr', '0'])
        if '2pass' in safe:
            if type(safe['2pass']) == bool:
                optlist.extend(['-2pass', str(int(safe['2pass']))])
            else:
                logger.error(str(int(safe['2pass']))+' is not a valid 2pass for nvenc_h264 encoder ...')
                optlist.extend(['-2pass', 'auto'])
        if 'gpu' in safe:
            if safe['gpu'] >= -2:
                optlist.extend(['-gpu', str(safe['gpu'])])
            else:
                logger.error(str(safe['gpu'])+' is not a valid gpu for nvenc_h264 encoder ...')
                optlist.extend(['-gpu', 'any'])
        if 'delay' in safe:
            if safe['delay'] >= 0:
                optlist.extend(['-delay', str(safe['delay'])])
            else:
                logger.error(str(safe['delay'])+' is not a valid delay for nvenc_h264 encoder ...')
        if 'no-scenecut' in safe:
            if type(safe['no-scenecut']) == bool:
                optlist.extend(['-no-scenecut', str(int(safe['no-scenecut']))])
            else:
                logger.error(str(int(safe['no-scenecut']))+' is not a valid no-scenecut for nvenc_h264 encoder ...')
                optlist.extend(['-no-scenecut', '0'])
        if 'forced-idr' in safe:
            if type(safe['forced-idr']) == bool:
                optlist.extend(['-forced-idr', str(int(safe['forced-idr']))])
            else:
                logger.error(str(int(safe['forced-idr']))+' is not a valid forced-idr for nvenc_h264 encoder ...')
                optlist.extend(['-forced-idr', 'auto'])
        if 'b_adapt' in safe:
            if type(safe['b_adapt']) == bool:
                optlist.extend(['-b_adapt', str(int(safe['b_adapt']))])
            else:
                logger.error(str(int(safe['b_adapt']))+' is not a valid b_adapt for nvenc_h264 encoder ...')
                optlist.extend(['-b_adapt', '1'])
        if 'spatial-aq' in safe:
            if type(safe['spatial-aq']) == bool:
                optlist.extend(['-spatial-aq', str(int(safe['spatial-aq']))])
            else:
                logger.error(str(int(safe['spatial-aq']))+' is not a valid spatial-aq for nvenc_h264 encoder ...')
                optlist.extend(['-spatial-aq', '0'])
        if 'temporal-aq' in safe:
            if type(safe['temporal-aq']) == bool:
                optlist.extend(['-temporal-aq', str(int(safe['temporal-aq']))])
            else:
                logger.error(str(int(safe['temporal-aq']))+' is not a valid temporal-aq for nvenc_h264 encoder ...')
                optlist.extend(['-temporal-aq', '0'])
        if 'zerolatency' in safe:
            if type(safe['zerolatency']) == bool:
                optlist.extend(['-zerolatency', str(int(safe['zerolatency']))])
            else:
                logger.error(str(int(safe['zerolatency']))+' is not a valid zerolatency for nvenc_h264 encoder ...')
                optlist.extend(['-zerolatency', '0'])
        if 'nonref_p' in safe:
            if type(safe['nonref_p']) == bool:
                optlist.extend(['-nonref_p', str(int(safe['nonref_p']))])
            else:
                logger.error(str(int(safe['nonref_p']))+' is not a valid nonref_p for nvenc_h264 encoder ...')
                optlist.extend(['-nonref_p', '0'])
        if 'strict_gop' in safe:
            if type(safe['strict_gop']) == bool:
                optlist.extend(['-strict_gop', str(int(safe['strict_gop']))])
            else:
                logger.error(str(int(safe['strict_gop']))+' is not a valid strict_gop for nvenc_h264 encoder ...')
                optlist.extend(['-strict_gop', '0'])
        if 'aq-strength' in safe:
            if 1 <= safe['aq-strength'] <= 15:
                optlist.extend(['-aq-strength', str(safe['aq-strength'])])
            else:
                logger.error(str(safe['aq-strength'])+' is not a valid aq-strength for nvenc_h264 encoder ...')
                optlist.extend(['-aq-strength', '8'])
        if 'cq' in safe:
            if 0 <= safe['cq'] <= 51:
                optlist.extend(['-cq', str(safe['cq'])])
            else:
                logger.error(str(safe['cq'])+' is not a valid cq for hevc_nvenc encoder ...')
                optlist.extend(['-cq', '0'])
        if 'qmin' in safe:
            if 0 <= safe['qmin'] <= 51:
                optlist.extend(['-qmin', str(safe['qmin'])])
            else:
                logger.error(str(safe['qmin'])+' is not a valid qmin for hevc_nvenc encoder ...')
                optlist.extend(['-qmin', '0'])
        if 'qmax' in safe:
            if 0 <= safe['qmax'] <= 51:
                optlist.extend(['-qmax', str(safe['qmax'])])
            else:
                logger.error(str(safe['qmax'])+' is not a valid qmax for hevc_nvenc encoder ...')
                optlist.extend(['-qmax', '51'])
        return optlist

class HEVCNvencCodec(VideoCodec):
    """
    HEVC/AVC Video codec by Nvidia.
    """
    codec_name = 'hevc_nvenc'
    ffmpeg_codec_name = 'hevc_nvenc'
    encoder_options = VideoCodec.encoder_options.copy()
    encoder_options.update({
        'preset': str,
        'profile': str,
        'level': str,
        'tier': str,
        'rc': str,
        'rc-lookahead': int,
        'surfaces': int,
        'cbr': bool,
        '2pass': bool,
        'gpu': int,
        'delay': int,
        'no-scenecut': bool,
        'forced-idr': bool,
        'spatial_aq': bool,
        'temporal_aq': bool,
        'zerolatency': bool,
        'nonref_p': bool,
        'strict_gop': bool,
        'aq-strength': int,
        'cq': float,
        'aud': bool,
        'bluray-compat': bool,
        'init_qpP': float,
        'init_qpB': float,
        'init_qpI': float,
        'qp': float,
    })

    def _codec_specific_produce_ffmpeg_list(self, safe):
        optlist = []

        if 'preset' in safe:
            if safe['preset'] in ['default', 'slow', 'medium', 'fast', 'hp', 'hq', 'bd', 'll', 'llhq', 'llhp', 'lossless', 'losslesshp']:
                optlist.extend(['-preset', safe['preset']])
            else:
                logger.error(safe['preset']+' is not a valid preset for hevc_nvenc encoder ...')
                optlist.extend(['-preset', 'medium'])
        if 'profile' in safe:
            if safe['profile'] in ['main', 'main10', 'rext']:
                optlist.extend(['-profile:v', safe['profile']])
            else:
                logger.error(safe['profile']+' is not a valid profile for hevc_nvenc encoder ...')
                optlist.extend(['-profile:v', 'main'])
        if 'level' in safe:
            if safe['level'] in ['auto', '1', '1.0', '2', '2.0', '2.1', '3', '3.0', '3.1', '4', '4.0', '4.1', '5', '5.0', '5.1', '5.2', '6', '6.0', '6.1', '6.2']:
                optlist.extend(['-level', safe['level']])
            else:
                logger.error(safe['level']+' is not a valid level for hevc_nvenc encoder ...')
                optlist.extend(['-level', 'auto'])
        if 'tier' in safe:
            if safe['tier'] in ['main', 'high']:
                optlist.extend(['-tier', safe['tier']])
            else:
                logger.error(safe['tier']+' is not a valid tier for hevc_nvenc encoder ...')
                optlist.exntend(['-tier', 'main'])
        if 'rc' in safe:
            if safe['rc'] in ['constqp', 'vbr', 'cbr', 'vbr_minqp', 'll_2pass_quality', 'll_2pass_size', 'vbr_2pass']:
                optlist.extend(['-rc', safe['rc']])
            else:
                logger.error(safe['rc']+' is not a valid rc for hevc_nvenc encoder ...')
                optlist.extend(['-rc', '-1'])
        if 'rc-lookahead' in safe:
            if 0 < safe['rc-lookahead'] < 33:
                optlist.extend(['-rc-lookahead', str(safe['rc-lookahead'])])
            else:
                logger.error(safe['rc-lookahead']+' is not a valid rc-lookahead for hevc_nvenc encoder ...')
                optlist.extend(['-rc-lookahead', '-1'])
        if 'surfaces' in safe:
            if safe['surfaces'] >= 0:
                optlist.extend(['-surfaces', str(safe['surfaces'])])
            else:
                logger.error(safe['surfaces'+' is not a valid surfaces for hevc_nvenc encoder ...'])
                optlist.extend(['-surfaces', '32'])
        if 'cbr' in safe:
            if safe['cbr'] in [False, True]:
                optlist.extend(['-cbr', str(int(safe['cbr']))])
            else:
                logger.error(str(int(safe['cbr']))+' is not a valid cbr for hevc_nvenc encoder ...')
                optlist.extend(['-cbr', '0'])
        if '2pass' in safe:
            if safe['2pass'] in [False, True]:
                optlist.extend(['-2pass', str(int(safe['2pass']))])
            else:
                logger.error(str(int(safe['2pass']))+' is not a valid 2pass for hevc_nvenc encoder ...')
                optlist.extend(['-2pass', 'auto'])
        if 'gpu' in safe:
            if type(safe['gpu']) == int:
                optlist.extend(['-gpu', str(safe['gpu'])])
            else:
                logger.error(str('gpu')+' is not a valid gpu for hevc_nvenc encoder ...')
                optlist.extend(['-gpu', 'any'])
        if 'delay' in safe:
            if safe['delay'] >= 0:
                optlist.extend(['-delay', str(safe['delay'])])
            else:
                logger.error(str(safe['delay'])+' is not a valid delay for hevc_nvenc encoder ...')
        if 'no-scenecut' in safe:
            if safe['no-scenecut'] in [False, True]:
                optlist.extend(['-no-scenecut', str(int(safe['no-scenecut']))])
            else:
                logger.error(str(int(safe['no-scenecut']))+' is not a valid no-scenecut for hevc_nvenc encoder ...')
                optlist.extend(['-no-scenecut', '0'])
        if 'forced-idr' in safe:
            if safe['forced-idr'] in [False, True]:
                optlist.extend(['-forced-idr', str(int(safe['forced-idr']))])
            else:
                logger.error(str(int(safe['forced-idr']))+' is not a valid forced-idr for hevc_nvenc encoder ...')
                optlist.extend(['-forced-idr', 'auto'])
        if 'spatial_aq' in safe:
            if safe['spatial_aq'] in [False, True]:
                optlist.extend(['-spatial_aq', str(int(safe['spatial_aq']))])
            else:
                logger.error(str(int(safe['spatial_aq']))+' is not a valid spatial_aq for hevc_nvenc encoder ...')
                optlist.extend(['-spatial_aq', '0'])
        if 'temporal_aq' in safe:
            if safe['temporal_aq'] in [False, True]:
                optlist.extend(['-temporal_aq', str(int(safe['temporal_aq']))])
            else:
                logger.error(str(int(safe['temporal_aq']))+' is not a valid temporal_aq for hevc_nvenc encoder ...')
                optlist.extend(['-temporal_aq', '0'])
        if 'zerolatency' in safe:
            if safe['zerolatency'] in [False, True]:
                optlist.extend(['-zerolatency', str(int(safe['zerolatency']))])
            else:
                logger.error(str(int(safe['zerolatency']))+' is not a valid zerolatency for hevc_nvenc encoder ...')
                optlist.extend(['-zerolatency', '0'])
        if 'nonref_p' in safe:
            if safe['nonref_p'] in [False, True]:
                optlist.extend(['-nonref_p', str(int(safe['nonref_p']))])
            else:
                logger.error(str(int(safe['nonref_p']))+' is not a valid nonref_p for hevc_nvenc encoder ...')
                optlist.extend(['-nonref_p', '0'])
        if 'strict_gop' in safe:
            if safe['strict_gop'] in [False, True]:
                optlist.extend(['-strict_gop', str(int(safe['strict_gop']))])
            else:
                logger.error(str(int(safe['strict_gop']))+' is not a valid strict_gop for hevc_nvenc encoder ...')
                optlist.extend(['-strict_gop', '0'])
        if 'aq-strength' in safe:
            if 0 < safe['aq-strength'] < 16:
                optlist.extend(['-aq-strength', str(safe['aq-strength'])])
            else:
                logger.error(str(safe['aq-strength'])+' is not a valid aq-strength for hevc_nvenc encoder ...')
                optlist.extend(['-aq-strength', '8'])
        if 'cq' in safe:
            if 0 <= safe['cq'] <= 51:
                optlist.extend(['-cq', str(safe['cq'])])
            else:
                logger.error(str(safe['cq'])+' is not a valid cq for hevc_nvenc encoder ...')
                optlist.extend(['-cq', '0'])
        if 'aud' in safe:
            if safe['aud'] in [False, True]:
                optlist.extend(['-aud', str(int(safe['aud']))])
            else:
                logger.error(str(safe['aud'])+' is not a valid aud for hevc_nvenc encoder ...')
                optlist.extend(['-aud', '0'])
        if 'bluray-compat' in safe:
            if safe['bluray-compat'] in [False, True]:
                optlist.extend(['-bluray-compat'], str(int(safe['bluray-compat'])))
            else:
                logger.error(str(safe['bluray-compat'])+' is not a valid bluray-compat for hevc_nvenc encoder ...')
                optlist.extend(['-bluray-compat'], '0')
        if 'init_qpP' in safe:
            if -1 <= safe['init_qpP'] <= 51:
                optlist.extend(['-init_qpP', str(safe['init_qpP'])])
            else:
                logger.error(str(safe['init_qpP'])+' is not a valid init_qpP for hevc_nvenc encoder ...')
                optlist.extend(['-init_qpP', '-1'])
        if 'init_qpB' in safe:
            if -1 <= safe['init_qpP'] <= 51:
                optlist.extend(['-init_qpB', str(safe['init_qpB'])])
            else:
                logger.error(str(safe['init_qpB'])+' is not a valid init_qpB for hevc_nvenc encoder ...')
                optlist.extend(['-init_qpB', '-1'])
        if 'init_qpI' in safe:
            if -1 <= safe['init_qpI'] <= 51:
                optlist.extend(['-init_qpI', str(safe['init_qpI'])])
            else:
                logger.error(str(safe['init_qpI'])+' is not a valid init_qpI for hevc_nvenc encoder ...')
                optlist.extend(['-init_qpI', '-1'])
        if 'qp' in safe:
            if -1 <= safe['qp'] <= 51:
                optlist.extend(['-qp', str(safe['qp'])])
            else:
                logger.error(str(safe['qp'])+' is not a valid qp for hevc_nvenc encoder ...')
                optlist.extend(['-qp', '-1'])
        return optlist

class HEVCCodec(VideoCodec):
    """
    HEVC/AVC video codec.
    """
    codec_name = 'hevc'
    ffmpeg_codec_name = 'libx265'
    encoder_options = VideoCodec.encoder_options.copy()
    encoder_options.update({
        'preset': str,
        'tune': str,
        'ssim': bool,
        'psnr': bool,
        'profile': str,
        'level-idc': str,
        'high-tier': bool,
        'ref': int,
        'allow-non-conformance': bool,
        'uhd-bd': bool,
        'rd': int,
        'ctu': int,
        'min-cu-size': int,
        'limit-refs': int,
        'limit-modes': bool,
        'rect': bool,
        'amp': bool,
        'early-skip': bool,
        'rskip': bool,
        'fast-intra': bool,
        'b-intra': bool,
        'cu-lossless': bool,
        'tskip-fast': bool,
        'rd-refine': bool,
        'analysis-mode': int,
        'analysis-file': str,
        'rdoq-level': int,
        'tu-intra-depth': int,
        'tu-inter-depth': int,
        'limit-tu': int,
        'nr-intra': int,
        'nr-inter': int,
        'tskip': bool,
        'rdpenalty': int,
        'max-tu-size': int,
        'dynamic-rd': int,
        'ssim-rd': bool,
        'max-merge': int,
        'me': int,
        'subme': int,
        'merange': int,
        'temporal-mvp': bool,
        'weightp': bool,
        'weightb': bool,
        'analyze-src-pics': bool,
        'strong-intra-smoothing': bool,
        'constrained-intra': bool,
        'psy-rd': float,
        'psy-rdoq': float,
        'open-gop': bool,
        'keyint': int,
        'min-keyint': int,
        'scenecut': bool,
        'scenecut-bias': float,
        'intra-refresh': bool,
        'rc-lookahead': int,
        'lookahead-slices': int,
        'lookahead-threads': int,
        'b-adapt': int,
        'bframes': int,
        'bframe-bias': int,
        'b-pyramid': bool,
        'bitrate': int,
        'crf': float,
        'crf-max': float,
        'crf-min': float,
        'vbv-bufsize': int,
        'vbv-maxrate': int,
        'vbv-init': float,
        'qp': int,
        'lossless': bool,
        'aq-mode': int,
        'aq-strength': float,
        'aq-motion': bool,
        'qg-size': int,
        'cutree': bool,
        'pass': int,
        'stats': str,
        'slow-firstpass': bool,
        'multi-pass-opt-analysis': bool,
        'multi-pass-opt-distortion': bool,
        'strict-cbr': bool,
        'cbqpoffs': int,
        'crqpoffs': int,
        'qcomp': float,
        'qpstep': int,
        'qpmin': int,
        'qpmax': int,
        'rc-grain': bool,
        'qblur': float,
        'cplxblur': float,
        'signhide': bool,
        'qpfile': str,
        'scaling-list': str,
        'lambda-file': str,
        'sao': bool,
        'sao-non-deblock': bool,
        'sar': int,
        'display-window': str,
        'overscan': str,
        'videoformat': int,
        'range': str,
        'colorprim': int,
        'transfer': int,
        'colormatrix': int,
        'chromaloc': int,
        'hdr': bool,
        'hdr-opt': bool,
        'min-luma': int,
        'max-luma': int,
        'annexb': bool,
        'repeat-headers': bool,
        'aud': bool,
        'hrd': bool,
        'info': bool,
        'hash': int,
        'temporal-layers': bool,
        'log2-max-poc-lsb': int,
        'vui-timing-info': bool,
        'vui-hrd-info': bool,
        'opt-qp-pps': bool,
        'opt-ref-list-length-pps': bool,
        'multi-pass-opt-rps': bool,
        'opt-cu-delta-qp': bool
    })

    def check_condition(self, name, value, default, condition, x265_params):
        if condition:
            x265_params.append(name+'='+str(value))
        else:
            if type(default) != bool:
                x265_params.append(name+'='+str(default))
            logger.error(str(value)+' is not a valid '+name+' for libx265 encoder ...')

    def _codec_specific_produce_ffmpeg_list(self, safe):
        optlist = []
        x265_params = []
        if 'preset' in safe:
            if safe['preset'] in ['ultrafast', 'superfast', 'veryfast', 'faster', 'fast', 'medium', 'slow', 'slower', 'veryslow', 'placebo']:
                optlist.extend(['preset', safe['preset']])
            else:
                logger.error(safe['preset']+' is not a valid preset for libx265 encoder ...')
                optlist.extend(['preset', 'medium'])
        if 'tune' in safe:
            if safe['tune'] in ['psnr', 'ssim', 'grain', 'fastdecode', 'zerolatency']:
                optlist.extend(['tune', safe['tune']])
            else:
                logger.error(safe['tune']+' is not a valid tune for libx265 encoder ...')
                optlist.extend(['tune', 'grain'])
        if 'ssim' in safe: x265_params.append('ssim' if safe['ssim'] else 'no-ssim')
        if 'psnr' in safe: x265_params.append('psnr' if safe['psnr'] else 'no-psnr')
        if 'profile' in safe: self.check_condition('profile', safe['profile'], False, safe['profile'] in [ 'main', 'main-intra', 'mainstillpicture', 'main444-8', 'main444-intra', 'main444-stillpicture', 'main10', 'main10-intra', 'main422-10', 'main422-10-intra', 'main444-10', 'main444-10-intra', 'main12', 'main12-intra', 'main422-12', 'main422-12-intra', 'main444-12', 'main444-12-intra'], x265_params)
        if 'level-idc' in safe: self.check_condition('level-idc', safe['level-idc'], 0, safe['level-idc'] in ['1', '2', '2.1', '3', '3.1', '4', '4.1', '5', '5.1', '5.2', '6', '6.1', '6.2', '8.5'], x265_params)
        if 'high-tier' in safe: x265_params.append('high-tier' if safe['high-tier'] else 'no-high-tier')
        if 'ref' in safe: self.check_condition('ref', safe['ref'], 3, 1 <= safe['ref'] <= 16, x265_params)
        if 'allow-non-conformance' in safe: x265_params.append('allow-non-conformance' if safe['allow-non-conformance'] else 'no-allow-non-conformance')
        if 'uhd-bd' in safe: x265_params.append('uhd-bd' if safe['uhd-bd'] else 'no-uhd-bd')
        if 'rd' in safe: self.check_condition('rd', safe['rd'], 3, 1 <= safe['rd'] <= 6, x265_params)
        if 'ctu' in safe: self.check_condition('ctu', safe['ctu'], 64, safe['ctu'] in [64, 32, 16], x265_params)
        if 'min-cu-size' in safe: self.check_condition('min-cu-size', safe['min-cu-size'], 8, safe['min-cu-size'] in [8, 16, 32], x265_params)
        if 'limit-refs' in safe: self.check_condition('limit-refs', safe['limit-refs'], 3, 0 <= safe['limit-refs'] <= 3, x265_params)
        if 'limit-modes' in safe: x265_params.append('limit-modes' if safe['limit-modes'] else 'no-limit-modes')
        if 'rect' in safe: x265_params.append('rect' if safe['rect'] else 'no-rect')
        if 'amp' in safe: x265_params.append('amp' if safe['amp'] else 'no-amp')
        if 'early-skip' in safe: x265_params.append('early-skip' if safe['early-skip'] else 'no-early-skip')
        if 'rskip' in safe: x265_params.append('rskip' if safe['rskip'] else 'no-rskip')
        if 'fast-intra' in safe: x265_params.append('fast-intra' if safe['fast-intra'] else 'no-fast-intra')
        if 'b-intra' in safe: x265_params.append('b-intra' if safe['b-intra'] else 'no-b-intra')
        if 'cu-lossless' in safe: x265_params.append('cu-lossless' if safe['cu-lossless'] else 'no-cu-lossless')
        if 'tskip-fast' in safe: x265_params.append('tskip-fast' if safe['tskip-fast'] else 'no-tskip-fast')
        if 'rd-refine' in safe: x265_params.append('rd-refine' if safe['rd-refine'] else 'no-rd-refine')
        if 'analysis-mode' in safe: self.check_condition('analysis-mode', safe['analysis-mode'], False, 0 <= safe['analysis-mode'] <= 2, x265_params)
        if 'analysis-file' in safe: self.check_condition('analysis-file', safe['analysis-file'], False, True, x265_params)
        if 'rdoq-level' in safe:
            if safe['rdoq-level'] == -1: x265_params.append('no-rdoq-level')
            else: self.check_condition('rdoq-level', safe['rdoq-level'], False, 0 <= safe['rdoq-level'] <= 2, x265_params)
        if 'tu-intra-depth' in safe: self.check_condition('tu-intra-depth', safe['tu-intra-depth'], 1, 1 <= safe['tu-intra-depth'] <= 4, x265_params)
        if 'tu-inter-depth' in safe: self.check_condition('tu-inter-depth', safe['tu-inter-depth'], 1, 1 <= safe['tu-inter-depth'] <= 4, x265_params)
        if 'limit-tu' in safe: self.check_condition('limit-tu', safe['limit-tu'], 0, 0 <= safe['limit-tu'] <= 4, x265_params)
        if 'nr-intra' in safe: self.check_condition('nr-intra', safe['nr-intra'], 0, 0 <= safe['nr-intra'] <= 2000, x265_params)
        if 'nr-inter' in safe: self.check_condition('nr-inter', safe['nr-inter'], 0, 0 <= safe['nr-inter'] <= 2000, x265_params)
        if 'rdpenalty' in safe: self.check_condition('rdpenalty', safe['rdpenalty'], 0, 0 <= safe['rdpenalty'] <= 2, x265_params)
        if 'tskip' in safe: x265_params.append('tskip' if safe['tskip'] else 'no-tskip')
        if 'max-tu-size' in safe: self.check_condition('max-tu-size', safe['max-tu-size'], 32, safe['max-tu-size'] in [4,8,16,32], x265_params)
        if 'dynamic-rd' in safe: self.check_condition('dynamic-rd', safe['dynamic-rd'], 0, 0 <= safe['dynamic-rd'] <= 4, x265_params)
        if 'ssim-rd' in safe: x265_params.append('ssim-rd' if safe['ssim-rd'] else 'no-ssim-rd')
        if 'max-merge' in safe: self.check_condition('max-merge', safe['max-merge'], 2, 1 <= safe['max-merge'] <= 5, x265_params)
        if 'me' in safe: self.check_condition('me', safe['me'], 1, 0 <= safe['me'] <= 5, x265_params)
        if 'subme' in safe: self.check_condition('subme', safe['subme'], 2, 0 <= safe['subme'] <= 7, x265_params)
        if 'merange' in safe: self.check_condition('merange', safe['merange'], 57, 0 <= safe['merange'] <= 32768, x265_params)
        if 'temporal-mvp' in safe: x265_params.append('temporal-mvp' if safe['temporal-mvp'] else 'no-temporal-mvp')
        if 'weightp' in safe: x265_params.append('weightp' if safe['weightp'] else 'no-weightp')
        if 'weightb' in safe: x265_params.append('weightb' if safe['weightb'] else 'no-weightb')
        if 'analyze-src-pics' in safe: x265_params.append('analyze-src-pics' if safe['analyze-src-pics'] else 'no-analyze-src-pics')
        if 'strong-intra-smoothing' in safe: x265_params.append('strong-intra-smoothing' if safe['strong-intra-smoothing'] else 'no-strong-intra-smoothing')
        if 'constrained-intra' in safe: x265_params.append('constrained-intra' if safe['constrained-intra'] else 'no-constrained-intra')
        if 'psy-rd' in safe: self.check_condition('psy-rd', safe['psy-rd'], 2.0, 0.0 <= safe['psy-rd'] <= 5.0, x265_params)
        if 'psy-rdoq' in safe: self.check_condition('psy-rdoq', safe['psy-rdoq'], 0.0, 0.0 <= safe['psy-rdoq'] <= 50.0, x265_params)
        if 'open-gop' in safe: x265_params.append('open-gop' if safe['open-gop'] else 'no-open-gop')
        if 'keyint' in safe: self.check_condition('keyint', safe['keyint'], 250, True, x265_params)
        if 'min-keyint' in safe: self.check_condition('min-keyint', safe['min-keyint'], 0, 0 <= safe['min-keyint'], x265_params)
        if 'scenecut' in safe: x265_params.append('scenecut' if safe['scenecut'] else 'no-scenecut')
        if 'scenecut-bias' in safe: self.check_condition('scenecut-bias', safe['scenecut-bias'], 5.0, 0.0 <= safe['scenecut-bias'] <= 100.0, x265_params)
        if 'intra-refresh' in safe:
            if safe['intra-refresh']: x265_params.append('intra-refresh')
        if 'rc-lookahead' in safe: self.check_condition('rc-lookahead', safe['rc-lookahead'], 20, 0 < safe['rc-lookahead'] <= 250, x265_params)
        if 'lookahead-slices' in safe: self.check_condition('lookahead-slices', safe['lookahead-slices'], False, 0 <= safe['lookahead-slices'] <= 16, x265_params)
        if 'lookahead-threads' in safe: self.check_condition('lookahead-threads', safe['lookahead-threads'], 0, True, x265_params)
        if 'b-adapt' in safe: self.check_condition('b-adapt', safe['b-adapt'], 2, 0 <= safe['b-adapt'] <= 2, x265_params)
        if 'bframes' in safe: self.check_condition('bframes', safe['bframes'], 4, 0 <= safe['bframes'] <= 16, x265_params)
        if 'bframe-bias' in safe: self.check_condition('bframe-bias', safe['bframe-bias'], 0, -90 <= safe['bframe-bias'] <= 100, x265_params)
        if 'b-pyramid' in safe: x265_params.append('b-pyramid' if safe['b-pyramid'] else 'no-b-pyramid')
        if 'bitrate' in safe: self.check_condition('bitrate', safe['bitrate'], 0, 0 <= safe['bitrate'], x265_params)
        if 'crf' in safe: self.check_condition('crf', safe['crf'], 28.0, 0.0 <= safe['crf'] <= 51.0, x265_params)
        if 'crf-max' in safe: self.check_condition('crf-max', safe['crf-max'], False, 0 <= safe['crf-max'] <= 51.0, x265_params)
        if 'crf-min' in safe: self.check_condition('crf-min', safe['crf-min'], False, 0 <= safe['crf-min'] <= 51.0, x265_params)
        if 'vbv-bufsize' in safe: self.check_condition('vbv-bufsize', safe['vbv-bufsize'], 0, 0 <= safe['vbv-bufsize'], x265_params)
        if 'vbv-maxrate' in safe: self.check_condition('vbv-maxrate', safe['vbv-maxrate'], 0, 0 <= safe['vbv-maxrate'], x265_params)
        if 'vbv-init' in safe: self.check_condition('vbv-init', safe['vbv-init'], 0.9, 0.0 <= safe['vbv-init'] <= 1.0, x265_params)
        if 'qp' in safe: self.check_condition('qp', safe['qp'], False, 0 <= safe['qp'] <= 51, x265_params)
        if 'lossless' in safe: x265_params.append('lossless' if safe['lossless'] else 'no-lossless')
        if 'aq-mode' in safe: self.check_condition('aq-mode', safe['aq-mode'], 1, 0 <= safe['aq-mode'] <= 3, x265_params)
        if 'aq-strength' in safe: self.check_condition('aq-strength', safe['aq-strength'], 1.0, 0.0 <= safe['aq-strength'] <= 3.0, x265_params)
        if 'aq-motion' in safe: x265_params.append('aq-motion' if safe['aq-motion'] else 'no-aq-motion')
        if 'qg-size' in safe: self.check_condition('qg-size', safe['qg-size'], False, safe['qg-size'] in [8, 16, 32, 64], x265_params)
        if 'cutree' in safe: x265_params.append('cutree' if safe['cutree'] else 'no-cutree')
        if 'pass' in safe: self.check_condition('pass', safe['pass'], False, 0 <= safe['pass'] <= 3, x265_params)
        if 'stats' in safe: self.check_condition('stats', safe['stats'], False, True, x265_params)
        if 'slow-firstpass' in safe: x265_params.append('slow-firstpass' if safe['slow-firstpass'] else 'no-slow-firstpass')
        if 'multi-pass-opt-analysis' in safe: x265_params.append('multi-pass-opt-analysis' if safe['multi-pass-opt-analysis'] else 'no-multi-pass-opt-analysis')
        if 'multi-pass-opt-distortion' in safe: x265_params.append('multi-pass-opt-distortion' if safe['multi-pass-opt-distortion'] else 'no-multi-pass-opt-distortion')
        if 'strict-cbr' in safe: x265_params.append('strict-cbr' if safe['strict-cbr'] else 'no-strict-cbr')
        if 'cbqpoffs' in safe: self.check_condition('cbqpoffs', safe['cbqpoffs'], 0, -12 <= safe['cbqpoffs'] <= 12, x265_params)
        if 'crqpoffs' in safe: self.check_condition('crqpoffs', safe['crqpoffs'], 0, -12 <= safe['crqpoffs'] <= 12, x265_params)
        if 'qcomp' in safe: self.check_condition('qcomp', safe['qcomp'], 0.6, 0 <= safe['qcomp'] <= 1, x265_params)
        if 'qpstep' in safe: self.check_condition('qpstep', safe['qpstep'], 4, 0 <= safe['qpstep'], x265_params)
        if 'qpmin' in safe: self.check_condition('qpmin', safe['qpmin'], 0, 0 <= safe['qpmin'], x265_params)
        if 'qpmax' in safe: self.check_condition('qpmax', safe['qpmax'], 69, 0 <= safe['qpmax'], x265_params)
        if 'rc-grain' in safe: x265_params.append('rc-grain' if safe['rc-grain'] else 'no-rc-grain')
        if 'qblur' in safe: self.check_condition('qblur', safe['qblur'], 0.5, 0 <= safe['qblur'], x265_params)
        if 'cplxblur' in safe: self.check_condition('cplxblur', safe['cplxblur'], 20, 0 <= safe['cplxblur'], x265_params)
        if 'signhide' in safe: x265_params.append('signhide' if safe['signhide'] else 'no-signhide')
        if 'qpfile' in safe: self.check_condition('qpfile', safe['qpfile'], False, True, x265_params)
        if 'scaling-list' in safe: self.check_condition('scaling-list', safe['scaling-list'], False, True, x265_params)
        if 'lambda-file' in safe: self.check_condition('lambda-file', safe['lambda-file'], False, True, x265_params)
        if 'sao' in safe: x265_params.append('sao' if safe['sao'] else 'no-sao')
        if 'sao-non-deblock' in safe: x265_params.append('sao-non-deblock' if safe['sao-non-deblock'] else 'no-sao-non-deblock')
        if 'sar' in safe: self.check_condition('sar', safe['sar'], False, 1 <= safe['sar'] <= 16, x265_params)
        if 'display-window' in safe: self.check_condition('display-window', safe['display-window'], False, safe['display-window'] in ['left', 'top', 'right', 'bottom'], x265_params)
        if 'overscan' in safe: self.check_condition('overscan', safe['overscan'], False, safe['overscan'] in ['show', 'crop'], x265_params)
        if 'videoformat' in safe: self.check_condition('videoformat', safe['videoformat'], False, 0 <= safe['videoformat'] <= 5, x265_params)
        if 'range' in safe: self.check_condition('range', safe['range'], False, safe['range'] in ['full', 'limited'], x265_params)
        if 'colorprim' in safe: self.check_condition('colorprim', safe['colorprim'], False, 1 <= safe['colorprim'] <= 9, x265_params)
        if 'transfer' in safe: self.check_condition('transfer', safe['transfer'], False, 1 <= safe['transfer'] <= 18, x265_params)
        if 'colormatrix' in safe: self.check_condition('colormatrix', safe['colormatrix'], False, 0 <= safe['colormatrix'] <= 10, x265_params)
        if 'chromaloc' in safe: self.check_condition('chromaloc', safe['chromaloc'], False, 0 <= safe['chromaloc'] <= 5, x265_params)
        if 'hdr' in safe: x265_params.append('hdr' if safe['hdr'] else 'no-hdr')
        if 'hdr-opt' in safe: x265_params.append('hdr-opt' if safe['hdr-opt'] else 'no-hdr-opt')
        if 'min-luma' in safe: self.check_condition('min-luma', safe['min-luma'], False, 0 <= safe['min-luma'], x265_params)
        if 'max-luma' in safe: self.check_condition('max-luma', safe['max-luma'], False, 0 <= safe['max-luma'], x265_params)
        if 'annexb' in safe: x265_params.append('annexb' if safe['annexb'] else 'no-annexb')
        if 'repeat-headers' in safe: x265_params.append('repeat-headers' if safe['repeat-headers'] else 'no-repeat-headers')
        if 'aud' in safe: x265_params.append('aud' if safe['aud'] else 'no-aud')
        if 'hrd' in safe: x265_params.append('hrd' if safe['hrd'] else 'no-hrd')
        if 'info' in safe: x265_params.append('info' if safe['info'] else 'no-info')
        if 'hash' in safe: self.check_condition('hash', safe['hash'], False, 1 <= safe['hash'] <= 3, x265_params)
        if 'temporal-layers' in safe: x265_params('temporal-layers' if safe['temporal-layers'] else 'no-temporal-layers')
        if 'log2-max-poc-lsb' in safe: self.check_condition('log2-max-poc-lsb', safe['log2-max-poc-lsb'], 8, 0 <= safe['log2-max-poc-lsb'], x265_params)
        if 'vui-timing-info' in safe: x265_params.append('vui-timing-info' if safe['vui-timing-info'] else 'no-vui-timing-info')
        if 'vui-hrd-info' in safe: x265_params.append('vui-hrd-info' if safe['vui-hrd-info'] else 'no-vui-hrd-info')
        if 'opt-qp-pps' in safe: x265_params.append('opt-qp-pps' if safe['opt-qp-pps'] else 'no-opt-qp-pps')
        if 'opt-ref-list-length-pps' in safe: x265_params.append('opt-ref-list-length-pps' if safe['opt-ref-list-length-pps'] else 'no-opt-ref-list-length-pps')
        if 'multi-pass-opt-rps' in safe: x265_params.append('multi-pass-opt-rps' if safe['multi-pass-opt-rps'] else 'no-multi-pass-opt-rps')
        if 'opt-cu-delta-qp' in safe: x265_params.append('opt-cu-delta-qp' if safe['opt-cu-delta-qp'] else 'no-opt-cu-delta-qp')

        if len(x265_params) > 0:
            optlist.extend(['-x265-params', ':'.join(map(str,x265_params))])

        return optlist
class H264Codec(VideoCodec):
    """
    H.264/AVC video codec.
    @see http://ffmpeg.org/trac/ffmpeg/wiki/x264EncodingGuide
    """
    codec_name = 'h264'
    ffmpeg_codec_name = 'libx264'
    encoder_options = VideoCodec.encoder_options.copy()
    encoder_options.update({
        'preset': str,  # common presets are ultrafast, superfast, veryfast,
        # faster, fast, medium(default), slow, slower, veryslow
        'quality': int,  # constant rate factor, range:0(lossless)-51(worst)
        # default:23, recommended: 18-28
        # http://mewiki.project357.com/wiki/X264_Settings#profile
        'profile': str,  # default: not-set, for valid values see above link
        'tune': str,  # default: not-set, for valid values see above link
        'level': str,  # The H.264 level that you want to use for the output video
        'max_reference_frames': int,  # reference frames
        'max_rate': str,
        'max_frames_between_keyframes': int,
        'qmin': int,
        'qcomp': float,
        'keyint_min': int,
        'subq': int,
        'b-pyramid': int,
        'trellis': int,
    })

    def _codec_specific_produce_ffmpeg_list(self, safe):
        optlist = []
        if 'preset' in safe:
            optlist.extend(['-preset', safe['preset']])
        if 'quality' in safe:
            optlist.extend(['-crf', str(safe['quality'])])
        if 'profile' in safe:
            optlist.extend(['-profile:v', safe['profile']])
        if 'tune' in safe:
            optlist.extend(['-tune', safe['tune']])
        if 'level' in safe:
            optlist.extend(['-level', safe['level']])
        if 'max_reference_frames' in safe:
            optlist.extend(['-refs', str(safe['max_reference_frames'])])
        if 'max_rate' in safe:
            optlist.extend(['-maxrate', str(safe['max_rate'])])
        if 'max_frames_between_keyframes' in safe:
            optlist.extend(['-g', str(safe['max_frames_between_keyframes'])])
        if 'qmin' in safe:
            optlist.extend(['-qmin', str(safe['qmin'])])
        if 'qcomp' in safe:
            optlist.extend(['-qcomp', str(safe['qcomp'])])
        if 'keyint_min' in safe:
            optlist.extend(['-keyint_min', str(safe['keyint_min'])])
        if 'subq' in safe:
            optlist.extend(['-subq', str(safe['subq'])])
        if 'b-pyramid' in safe:
            optlist.extend(['-b-pyramid', str(safe['b-pyramid'])])
        if 'trellis' in safe:
            optlist.extend(['-trellis', str(safe['trellis'])])

        return optlist

class DivxCodec(VideoCodec):
    """
    DivX video codec.
    """
    codec_name = 'divx'
    ffmpeg_codec_name = 'mpeg4'

class Vp8Codec(VideoCodec):
    """
    Google VP8 video codec.
    """
    codec_name = 'vp8'
    ffmpeg_codec_name = 'libvpx'

class H263Codec(VideoCodec):
    """
    H.263 video codec.
    """
    codec_name = 'h263'
    ffmpeg_codec_name = 'h263'

class FlvCodec(VideoCodec):
    """
    Flash Video codec.
    """
    codec_name = 'flv'
    ffmpeg_codec_name = 'flv'

class Ffv1Codec(VideoCodec):
    """
    FFV1 Video codec.
    """
    codec_name = 'ffv1'
    ffmpeg_codec_name = 'ffv1'
    encoder_options = VideoCodec.encoder_options.copy()
    encoder_options.update({
        'level': int,  # 1, 3. Select which FFV1 version to use.
        'coder': int,  # 0=Golomb-Rice, 1=Range Coder,
                       # 2=Range Coder(with custom state transition table)
        'context': int,  # 0=small, 1=large
        'g': int,  # GOP size, >= 1. For archival use, GOP-size should be "1".
        'slices': int,  # 4, 6, 9, 12, 16, 24, 30.
                        # Each frame is split into this number of slices.
                        # This affects multithreading performance, as well as filesize:
                        # Increasing the number of slices might speed up
                        # performance, but also increases the filesize.
        'slicecrc': int,  # Error correction/detection, 0=off, 1=on.
                          # Enabling this option adds CRC information to each slice.
                          # This makes it possible for a decoder to detect
                          # errors in the bitstream, rather than blindly
                          # decoding a broken slice.
    })

    def _codec_specific_produce_ffmpeg_list(self, safe):
        optlist = []
        if 'level' in safe:
            optlist.extend(['-level', str(safe['level'])])
        if 'coder' in safe:
            optlist.extend(['-coder', str(safe['coder'])])
        if 'context' in safe:
            optlist.extend(['-context', str(safe['context'])])
        if 'g' in safe:
            optlist.extend(['-g', str(safe['g'])])
        if 'slices' in safe:
            optlist.extend(['-slices', str(safe['slices'])])
        if 'slicecrc' in safe:
            optlist.extend(['-slicecrc', str(safe['slicecrc'])])

        return optlist

class MpegCodec(VideoCodec):
    """
    Base MPEG video codec.
    """
    # Workaround for a bug in ffmpeg in which aspect ratio
    # is not correctly preserved, so we have to set it
    # again in vf; take care to put it *before* crop/pad, so
    # it uses the same adjusted dimensions as the codec itself
    # (pad/crop will adjust it further if neccessary)
    def _codec_specific_parse_options(self, safe):
        w = safe['max_width']
        h = safe['max_height']

        if w and h:
            filters = safe['aspect_filters']
            tmp = 'aspect=%d:%d' % (w, h)

            if filters is None:
                safe['aspect_filters'] = tmp
            else:
                safe['aspect_filters'] = tmp + ',' + filters

        return safe

class Mpeg1Codec(MpegCodec):
    """
    MPEG-1 video codec.
    """
    codec_name = 'mpeg1'
    ffmpeg_codec_name = 'mpeg1video'

class Mpeg2Codec(MpegCodec):
    """
    MPEG-2 video codec.
    """
    codec_name = 'mpeg2'
    ffmpeg_codec_name = 'mpeg2video'

# Subtitle Codecs
class MOVTextCodec(SubtitleCodec):
    """
    mov_text subtitle codec.
    """
    codec_name = 'mov_text'
    ffmpeg_codec_name = 'mov_text'

class SSA(SubtitleCodec):
    """
    SSA (SubStation Alpha) subtitle.
    """
    codec_name = 'ass'
    ffmpeg_codec_name = 'ass'

class SubRip(SubtitleCodec):
    """
    SubRip subtitle.
    """
    codec_name = 'subrip'
    ffmpeg_codec_name = 'subrip'

class DVBSub(SubtitleCodec):
    """
    DVB subtitles.
    """
    codec_name = 'dvbsub'
    ffmpeg_codec_name = 'dvbsub'

class DVDSub(SubtitleCodec):
    """
    DVD subtitles.
    """
    codec_name = 'dvdsub'
    ffmpeg_codec_name = 'dvdsub'

class H263CuvidCodec(DecoderCodec):
    """
    H263 cuda decoder
    """
    codec_name = 'h263_cuvid'
    ffmpeg_codec_name = 'h263_cuvid'

class H264CuvidCodec(DecoderCodec):
    """
    H264 cuda decoder
    """
    codec_name = 'h264_cuvid'
    ffmpeg_codec_name = 'h264_cuvid'

class HEVCCuvidCodec(DecoderCodec):
    """
    HEVC cuda decoder
    """
    codec_name = 'hevc_cuvid'
    ffmpeg_codec_name = 'hevc_cuvid'

class MJPEGCuvidCodec(DecoderCodec):
    """
    MJPEG cuda decoder
    """
    codec_name = 'mjpeg_cuvid'
    ffmpeg_codec_name = 'mjpeg_cuvid'

class Mpeg1CuvidCodec(DecoderCodec):
    """
    MPEG1 cuda decoder
    """
    codec_name = 'mpeg1_cuvid'
    ffmpeg_codec_name = 'mpeg1_cuvid'

class Mpeg2CuvidCodec(DecoderCodec):
    """
    MPEG2 cuda decoder
    """
    codec_name = 'mpeg2_cuvid'
    ffmpeg_codec_name = 'mpeg2_cuvid'

class Mpeg4CuvidCodec(DecoderCodec):
    """
    MPEG4 cuda decoder
    """
    codec_name = 'mpeg4_cuvid'
    ffmpeg_codec_name = 'mpeg4_cuvid'

class Vc1CuvidCodec(DecoderCodec):
    """
    VC1 cuda decoder
    """
    codec_name = 'vc1_cuvid'
    ffmpeg_codec_name = 'vc1_cuvid'

class Vp8CuvidCodec(DecoderCodec):
    """
    VP8 cuda decoder
    """
    codec_name = 'vp8_cuvid'
    ffmpeg_codec_name = 'vp8_cuvid'

class Vp9CuvidCodec(DecoderCodec):
    """
    VP9 cuda decoder
    """
    codec_name = 'vp9_cuvid'
    ffmpeg_codec_name = 'vp9_cuvid'

audio_codec_list = [
    AudioNullCodec, AudioCopyCodec, VorbisCodec, AacCodec, Mp3Codec, Mp2Codec,
    FdkAacCodec, Ac3Codec, DtsCodec, FlacCodec
]

decoder_codec_list = [
    DecoderNullCodec, H263CuvidCodec, H264CuvidCodec, HEVCCuvidCodec, MJPEGCuvidCodec,
    Mpeg1CuvidCodec, Mpeg2CuvidCodec, Mpeg4CuvidCodec, Vc1CuvidCodec,
    Vp8CuvidCodec, Vp9CuvidCodec
]

video_codec_list = [
    VideoNullCodec, VideoCopyCodec, TheoraCodec, H264Codec,
    DivxCodec, Vp8Codec, H263Codec, FlvCodec, Ffv1Codec, Mpeg1Codec,
    Mpeg2Codec, HEVCNvencCodec, H264NvencCodec, HEVCCodec, H264VaapiCodec,
    HevcVaapiCodec
]

subtitle_codec_list = [
    SubtitleNullCodec, SubtitleCopyCodec, MOVTextCodec, SSA, SubRip, DVDSub,
    DVBSub
]


import os
import termcolor
from typing import List, Callable, Set

filter_any = lambda x: True
filter_xml = lambda x: x.endswith('.xml')

# For a given list of files and or directories,
# recursively find all the files that adhere to an optional filename filter,
# merge the results while eliminating duplicates.
def get_files(paths: List[str], filter: Callable[[str], bool] = filter_any) -> List[str]:
    already_have: Set[str] = set()
    src_files = []

    def add_file(path):
        if path not in already_have:
            already_have.add(path)
            src_files.append(path)

    for p in paths:
        if os.path.isdir(p):
            # recursively scan directories
            for r, dirs, files in os.walk(p):
                files.sort()
                for f in files:
                    if filter(f):
                        add_file(os.path.join(r,f))
        elif os.path.isfile(p):
            add_file(p)
        else:
            print(termcolor.colored("%s: not a file or a directory, skipped." % p, 'yellow'))

    return src_files

# Drop file extension
def dropext(file):
    return os.path.splitext(file)[0]

# Ensure path of directories exists
def make_dirs(file):
    os.makedirs(os.path.dirname(file), exist_ok=True)


from os import utime
from game_classes import BowlerClass
import cached_data as cacheData
import constants as constant
import time

def main_menu(message):
    '''
    In this Function we load the first menu for the user.
    Menu Be Like Register - Login - Exit
    Based on User input, We will go to next stage.
    '''
    print(constant.String_Space+constant.WelcomeMessage+constant.String_Space)
    print(message)
    userInput = cacheData.util.validate_user_input(constant.MainMenu, 1, 3)

    if userInput == 1:
        if cacheData.util.get_player_details(cacheData.util.player_data):
            main_menu(constant.RegisterOption)
    elif userInput == 2:
        start_menu()
    elif userInput == 3:
        play_again()
    else:
        print(constant.Exit_Game_Err)

# function to ask play again or not

def play_again():
    '''
    In this function , We check the user input to start the game or leave the game.
    '''
    print(constant.String_Space+constant.ExitMessage+constant.String_Space)

    # convert the player's input to lower_case
    answer = input(">").lower()

    if "y" in answer:
        main_menu(constant.SelectOption)
    # if player typed "yes" or "y" start the game from the beginning
    else:
        exit()
    # if user types anything besides "yes" or "y", exit() the program

def start_menu():
    '''
    In this Function we verify the user details before start the game.
    If user is already register to the game then game will start.
    Else if user is not verified to game then user need to register to game.
    '''
    game_id = cacheData.util.check_game_id(constant.String_Space+constant.Game_Menu_Game_Id+constant.Exit_Command+constant.String_Space,
                                           constant.Game_id_Err_Message)
    if game_id == 0:
        play_again()

    print(constant.String_Space+constant.StartGameMenuWelcomeMessage.format(
        cacheData.util.player_data[game_id].name))
    print(constant.SelectOption)
    user_input = cacheData.util.validate_user_input(
        constant.StartGameMenu+constant.String_Space, 1, 4)
    sessionObj = cacheData.util.create_new_session(game_id)
    if user_input == 1:
        select_team(sessionObj)
    elif user_input == 2:
        resume_game(sessionObj)
    elif user_input == 3:
        play_again()
    elif user_input == 4:
        score_check = cacheData.util.check_last_score(game_id)
        print("\n"+score_check+"\n")
        play_again()
    else:
        print(constant.Exit_Game_Err)

def select_team(sessionObj):
    '''
    In this Function, We create the dynamic team menu for the user selection.
    Based on user input team is selected.
    '''
    print(constant.String_Space+constant.SelectTeam)
    print(constant.SelectOption)

    user_input = cacheData.util.validate_user_input(
        cacheData.util.generate_team_menu()+constant.Exit_Command_2.format(len(cacheData.util.teamDic)+1)+constant.String_Space, 1, len(cacheData.util.teamDic)+1)
    if user_input == len(cacheData.util.teamDic)+1:
        play_again()

    sessionObj.team_code = cacheData.util.generate_team(user_input)
    user_input = cacheData.util.game_rules(constant.RULES.format(constant.MAX_OVER) +
                                           constant.Game_Rules+constant.String_Space, constant.Game_Rule_Err)
    if 'y' in user_input:
        start_over(sessionObj)
    if 'e' in user_input:
        play_again()

def resume_game(session_obj):
    '''
    In this Function we check the user old session and start the previous game
    If user dont have previous game then we need to start the new game.
    '''
    last_session = cacheData.util.check_session(session_obj.game_id)
    if last_session == "":
        print(constant.String_Space +
              constant.No_Unfinished_Game+constant.String_Space)
        select_team(session_obj)
    else:
        user_input = cacheData.util.game_rules(constant.RULES.format(constant.MAX_OVER) +
                                               constant.Game_Rules+constant.String_Space, constant.Game_Rule_Err)
        if 'y' in user_input:
            start_over(last_session)
        if 'e' in user_input:
            play_again()

def start_over(session_obj):
    '''
    This Function control the Over , Handle the Out , Change the Over
    Control the Player Trophy.
    '''
    last_bowl_obj = BowlerClass("", "", "")
    while True:
        bowlerObj = cacheData.util.find_bowler_id(session_obj, last_bowl_obj)
        last_bowl_obj = bowlerObj
        session_obj.bowlerId = bowlerObj.bowlerId
        bowler_type = bowlerObj.bowler_type
        session_obj = play_overs(session_obj, bowlerObj, bowler_type)
        print_trophy_menu = ""
        if session_obj.status == constant.OUT:
            print(constant.Out_Menu.format(constant.Neg_One, session_obj.runs))
            print_trophy_menu = check_trophy(session_obj)

            print(constant.String_Space+constant.Last_Over_Message.format(
                session_obj.runs, print_trophy_menu+"\n")+constant.String_Space)
            session_obj.status = constant.FINISHED
            cacheData.util.write_session(session_obj)
            play_again()
            return

        if session_obj.status == constant.QUIT:
            session_obj.status = constant.UNFINISHED
            cacheData.util.write_session(session_obj)
            play_again()
            return

        session_obj.over = session_obj.over+1
        session_obj.ball = 0

        if session_obj.over >= int(constant.MAX_OVER):
            print_trophy_menu = check_trophy(session_obj)
            print(constant.String_Space+constant.Last_Over_Message.format(
                session_obj.runs, print_trophy_menu+"\n")+constant.String_Space)
            session_obj.status = constant.FINISHED
            cacheData.util.write_session(session_obj)
            return

        print(constant.String_Space +
              constant.New_Over_Start.format(session_obj.over)+constant.String_Space)

        time.sleep(int(constant.Delay_Time))

def check_trophy(session_obj):
    '''
    Check for the player trophy.
    Based on the player run scored, trophy awarded
    '''
    print_trophy_menu = ""
    if session_obj.runs <= 10:
        session_obj.trophy = "Bronze"
        print_trophy_menu = constant.Bronze+" " + constant.Trophy
    elif session_obj.runs <= 25 and session_obj.runs > 10:
        session_obj.trophy = "Silver"
        print_trophy_menu = constant.Silver + " " + constant.Trophy
    elif session_obj.runs <= 50 and session_obj.runs > 25:
        session_obj.trophy = "Gold"
        print_trophy_menu = constant.Gold + " " + constant.Trophy
    elif session_obj.runs > 50:
        session_obj.trophy = "Diamond"
        print_trophy_menu = constant.Diamond + " " + constant.Trophy
    return print_trophy_menu

def play_overs(session_obj, bowlerObj, bowler_type):
    '''
    In this Function we create the menu for the user with ball , bowler name , shot into menu
    Based on the user input what shot you have to play and score achive.
    '''
    bowl = []
    while True:
        bowl = cacheData.util.find_random_ball(bowler_type, bowl)
        shortObj = bowl[1]
        output = constant.Bowler_Menu.format(bowlerObj.bowlerName, bowl[0])

        final_score_menu = constant.final_score.format(
            session_obj.runs, session_obj.over+1, session_obj.ball+1)

        short_menu = ""
        count = 1
        for shorts in shortObj.shot_list:
            short_menu = f"{short_menu}{count}.{shorts} \n"
            count = count+1

        user_input = cacheData.util.validate_user_input(constant.String_Space +
                                                        output+short_menu+constant.Exit_Command_2.format(len(shortObj.shot_list)+1)+constant.String_Space+final_score_menu+constant.String_Space, 1, len(shortObj.shot_list)+1)

        if user_input == len(shortObj.shot_list)+1:
            session_obj.status = constant.QUIT
            return session_obj

        score = shortObj.score_list[user_input-1]
        if int(score) == -1:
            session_obj.status = constant.OUT
            return session_obj

        print_shot_message(score)
        session_obj.runs = session_obj.runs+int(score)
        session_obj.ball = session_obj.ball+1
        if session_obj.ball == int(constant.MAX_BALL):
            return session_obj

def print_shot_message(score):
    '''
    This Function create the Score and Image for the Console Output.
    '''
    if int(score) == 1:
        print(constant.Shot_Message+" "+constant.Bat+" "+constant.One)
    elif int(score) == 2:
        print(constant.Shot_Message+" "+constant.Bat+" "+constant.Second)
    elif int(score) == 3:
        print(constant.Shot_Message+" "+constant.Bat+" "+constant.Third)
    elif int(score) == 4:
        print(constant.Shot_Message+" "+constant.Bat+" "+constant.Four)
    elif int(score) == 5:
        print(constant.Shot_Message+" "+constant.Bat+" "+constant.Five)
    elif int(score) == 6:
        print(constant.Shot_Message+" "+constant.Bat+" "+constant.Six)


from django.conf.urls import url

from apps.play.views import *

urlpatterns = [
    url(r'^$', view_plays, name='index'),
    url(r'^stop$', stop_play, name='stop'), #Only POST
    url(r'^(?P<play_id_char>\w+)/$', show_play, name='show'),
    url(r'^(?P<play_id_char>\w+)/reply/(?P<question_id>\d+)/$', reply_play, name='reply'),
    url(r'^(?P<play_id_char>\w+)/dashboard/$', play_result, name='result'),
    url(r'^(?P<play_id_char>\w+)/download-excel$', download_excel, name='excel'),
]

#(?P<question_id>\d+)/


import torch
import torch.nn as nn
import math
from utilities.models.bottleneck import Bottleneck
from utilities.models.samePad2d import SamePad2d

class ResNet(nn.Module):
    def __init__(self, architecture, stage5=False):
        super(ResNet, self).__init__()
        assert architecture in ['resnet50', 'resnet101']
        self.inplanes = 64
        self.layers = [3, 4, {'resnet50': 6, 'resnet101': 23}[architecture], 3]
        self.block = Bottleneck
        self.stage5 = stage5

        self.C1 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
            nn.BatchNorm2d(64, eps=0.001, momentum=0.01),
            nn.ReLU(inplace=True),
            SamePad2d(kernel_size=3, stride=2)
        )
        # self.layers = [3,4,6,3] or [3,4,23,3]
        self.C2 = self.make_layers(self.block, 64, self.layers[0])
        self.C3 = self.make_layers(self.block, 128, self.layers[1], stride=2)
        self.C4 = self.make_layers(self.block, 256, self.layers[2], stride=2)
        if self.stage5:
            self.C5 = self.make_layers(self.block, 512, self.layers[3], stride=2)
        else:
            self.C5 = None

    def forward(self, x):
        x = self.C1(x)
        x = self.C2(x)
        x = self.C3(x)
        x = self.C4(x)
        x = self.C5(x)
        return x

    def stages(self):
        return [self.C1, self.C2, self.C3, self.C4, self.C5]

    def make_layers(self, block, planes, blocks, stride=1):
        downsample = None
        if stride!= 1 or self.inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                nn.Conv2d(self.inplanes, planes * block.expansion,
                          kernel_size=1, stride=stride),
                nn.BatchNorm2d(planes * block.expansion, eps=0.001, momentum=0.01)
            )
        layers = []
        # 该部分是将每个convx_x(即blocks)的第一个residual子结构保存在layers列表中
        layers.append(block(self.inplanes, planes, stride, downsample))
        self.inplanes = planes * block.expansion
        # 该部分是将每个convx_x的剩下residual子结构保存在layers列表中，这样就完成了一个blocks的构造
        for i in range(1, blocks):
            layers.append(block(self.inplanes, planes))
        return nn.Sequential(*layers)


from textwrap   import dedent
from tempfile   import NamedTemporaryFile
from contextlib import contextmanager

import autoswatch

from flask import Flask
from nose.tools import (
    assert_equals,
    assert_true,
    assert_false
)

class TestApp:
    """
    The unit tests are for the basic structure of the app,
    its configuration (and environments), and that it starts up
    and runs when the container is created.
    """

    def test_create_app_returns_app(self):
        """
        Assert that create_app returns an app.
        """
        app = autoswatch.create_app()
        assert_true(isinstance(app, Flask))

    def test_default_config_debug(self):
        """
        Assert that create_app, by default, runs in debug mode.
        """
        app = autoswatch.create_app({})
        assert_true(app.debug)

    def test_env_dev_config(self):
        """
        Assert when ENV is set to development it loads that config.
        """
        config = autoswatch.env_config('development')
        app    = autoswatch.create_app(config)
        assert_true(app.debug)

    def test_env_test_config(self):
        """
        Assert when ENV is set to test or test-guard it loads that config.
        """
        config = autoswatch.env_config('test')
        app    = autoswatch.create_app(config)

        assert_false(app.debug)
        assert_true(app.testing)

    def test_env_test_guard_config(self):
        """
        Assert when ENV is set to test or test-guard it loads that config.
        """
        config = autoswatch.env_config('test-guard')
        app    = autoswatch.create_app(config)

        assert_false(app.debug)
        assert_true(app.testing)

    def test_env_prod_config(self):
        """
        Assert when ENV is set to production it loads that config.
        """
        config = autoswatch.env_config('production')
        app    = autoswatch.create_app(config)

        assert_false(app.debug)
        assert_false(app.config['JSONIFY_PRETTYPRINT_REGULAR'])

    @contextmanager
    def config_file(self, contents):
        with NamedTemporaryFile() as tmpfile:
            tmpfile.write(bytes(dedent(contents).encode('utf-8')))
            tmpfile.seek(0)
            yield dict(path=tmpfile.name, pointer=tmpfile)

    def test_instance_config_loads(self):
        """
        Assert that given an instance file it is loaded into config.
        """
        contents = """
          SECRET_KEY="stillnottoosecret"
        """
        with self.config_file(contents) as config_file:
            app = autoswatch.create_app({}, config_file['path'])
            assert_equals(app.config['SECRET_KEY'], 'stillnottoosecret')

    def test_instance_config_overrides(self):
        """
        Assert that an instance file overrides default and ENV configs.
        """
        contents = """
          SECRET_KEY="stillnottoosecret"
        """
        with self.config_file(contents) as config_file:
            env_config = {
                'SECRET_KEY': 'somewhatsecret'
            }
            app = autoswatch.create_app(env_config, config_file['path'])
            assert_equals(app.config['SECRET_KEY'], 'stillnottoosecret')


class Level:
    def __init__(self, floor_number, slots_per_lane, no_of_lanes):
        self.floor_number = floor_number
        self.no_of_slots = slots_per_lane * no_of_lanes
        self.slots = list()

    def add_slot(self, slot):
        if len(self.slots) + 1 <= self.no_of_slots:
            print('Slot added in a level')
            self.slots.append(slot)
        else:
            print('Level is full. No slot can be added further')

    def park_vehicle(self, vehicle):
        for slot in self.slots:
            if slot.park_vehicle(vehicle):
                # print('Vehicle is parked in a slot in a level')
                # self.available_slots.remove(slot)
                # self.parked_slots.add(slot)
                return True
        # print('Level is Full or Unable to park vehicle in a slot in a level')
        return False

    def exit_vehicle(self, vehicle):
        for slot in self.slots:
            if slot.get_vehicle() is not None and \
                    slot.get_vehicle().get_plate_number() == vehicle.get_plate_number():
                slot.exit_vehicle()
                # self.parked_slots.remove(slot)
                # self.available_slots.add(slot)
                # print('Vehicle is removed from the slot in a given level')
                return True
        # print('Vehicle is not parked in the slot in a given level')
        return False

    def company_parked_vehicles(self, company_name):
        vehicle_list = list()
        for slot in self.slots:
            if slot.get_vehicle() is not None and \
                    slot.get_vehicle().get_company_name() == company_name:
                vehicle_list.append(slot.get_vehicle())
        #return len(vehicle_list)
        return vehicle_list


"""Accuracy:  43.709481451010035
[49.28781684]
Enter no. of hours: 100
y=4.587899 * 100.000000 + 12.584628
Risk Score:  471.3744889615699
"""
#import Packages
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

#reading data sets
dataset=pd.read_csv("data.csv")
x=dataset.iloc[:,:-1].values
y=dataset.iloc[:,1].values

from sklearn.linear_model import LinearRegression
regressor=LinearRegression()
regressor.fit(x,y)
print("Accuracy: ", regressor.score(x,y)*100)

y_pred=regressor.predict([[8]])
print(y_pred)

hours=int(input("Enter no. of hours: "))
eq=regressor.coef_*hours+regressor.intercept_
print("y=%f * %f + %f" %(regressor.coef_, hours,regressor.intercept_))
print("Risk Score: ", eq[0])

plt.plot(x,y,'o')
plt.plot(x,regressor.predict(x));
plt.show();


from quiz_functions import read_files

# Testing the read file to see if they match
def test_read_files__with_25_questions():
    # Fetches correct name of file contain the questions for first test
    questions_file = "questions.txt"
    # Fetches correct name of file contain the answers for first test
    answers_file = "answers.txt"
    # specified filed are calling the function read_files to read questions and answers
    actual_questions, actual_answers = read_files(questions_file, answers_file)
    # Asserting if that the 25 is equal to the amount of questions and answers
    assert len(actual_questions) == 25
    assert len(actual_answers) == 25

def test_read_files_with_18_questions():
    # Fetches correct name of file contain the questions for second test
    questions_file = "questions2.txt"
    # Fetches correct name of file contain the answers for second test
    answers_file = "answers2.txt"
    # specified filed are calling the function read_files to read questions and answers
    actual_questions, actual_answers = read_files(questions_file, answers_file)
    # Asserting if that the 25 is equal to the amount of questions and answers
    assert len(actual_questions) == 18
    assert len(actual_answers) == 18

# Failed test

# test_quiz_functions.py .F                                                                                [100%]

# FAILURES
# def test_read_files_with_18_questions

#    def test_read_files_with_18_questions():
#        questions_file = "question2.txt"
#        answers_file = "answers2.txt"

# >       actual_questions, actual_answers = read_files(questions_file, answers_file)

#test_quiz_functions.py:18:
#_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

# questions_file = 'question2.txt', answers_file = 'answers2.txt'

#    def read_files(questions_file, answers_file):
#        # opens question file and reads questions
#>           with open(questions_file, "r") as f:
#E           FileNotFoundError: [Errno 2] No such file or directory: 'question2.txt'

#quiz_functions.py:11: FileNotFoundError
# short test summary info
#FAILED test_quiz_functions.py::test_read_files_with_18_questions - FileNotFoundError: [Errno 2] No such file or directory: 'question2.txt'

# Passed test for my Read_files feature

# ============================================================================================= test session starts ==============================================================================================
# platform linux -- Python 3.10.6, pytest-7.3.1, pluggy-1.0.0
# rootdir: /mnt/c/Users/ninja/Desktop/BrandonPowell_T1A3
# collected 2 items

#test_quiz_functions.py ..                                                                                [100%]

# ============================================================================================== 2 passed in 0.29s


import typer

from back.session.async_redis import async_redis, list_pairs
from lib.typer import ZetsuBouTyper

_help = """
Manipulate the Redis.
"""
app = ZetsuBouTyper(name="redis", help=_help)

@app.command()
async def ping():
    """
    Ping the redis server.
    """
    await async_redis.ping()

@app.command()
async def list(key: str = typer.Option(default="*", help="Redis key.")):
    """
    List all keys.
    """
    async for key in async_redis.scan_iter(key):
        print(key)

@app.command(name="list-pairs")
async def _list_pairs(key: str = typer.Option(default="*", help="Redis key.")):
    """
    List all key-value pairs.
    """
    async for key, value in list_pairs(key=key):
        print(key, value)

@app.command()
async def set(
    key: str = typer.Argument(..., help="Key."),
    value: str = typer.Argument(..., help="Value."),
):
    """
    Set a key-value pair.
    """
    await async_redis.set(key, value)

@app.command()
async def get(key: str = typer.Argument(..., help="Key.")):
    """
    Get the value of the key.
    """
    value = await async_redis.get(key)
    print(value)

@app.command()
async def flushall():
    """
    Delete all key-value pairs.
    """
    await async_redis.flushall()


import re

import h5py
import numpy as np

from .data_loader import DataLoader

class ISO17DataLoader(DataLoader):
    def __init__(self, *args, **kwargs):
        self.use_energies = kwargs.pop("use_energies", True)
        self._force_mu = None
        self._force_sigma = None
        super().__init__(*args, **kwargs)

    @property
    def mu(self):
        atomic_means = np.array(
            [
                0.0,  # Dummy points
                -13.61312172,  # Hydrogens
                -1029.86312267,  # Carbons
                -2042.61123593,  # Oxygens
            ]
        ).reshape((-1, 1))
        if self._force_mu is None:
            self.load_data()
        return atomic_means, self._force_mu

    @property
    def sigma(self):
        if self._force_mu is None:
            self.load_data()
        return np.ones_like(self.mu[0]), self._force_sigma

    def load_data(self, *args, **kwargs):
        """
        The ISO17 has 640982 structures split across 5 different datasets. The ISO17 is composed of
        129 isomers of C7O2H10 each with 5000 structures (frames with 1 femtosecond resolution)
        along various MD trajectories. Each structure has an associated molecular energy,
        and set of atomic forces.

        :param kwargs: possible kwargs:
            return_stats: bool. Defaults to False. exit early by returning mu, sigma
            return_maxz: bool. Defaults to False. exit early by returning max_z
            dataset_type: str. Defaults to 'reference'. Indicator for which dataset to select for
            train/val split test_type: str. Defaults to 'test_other'. Indictator for which datset
            to select for testing
        :return: dict. The structure of the returned data is as such:
            {
                dataset: (x, y)
            }
            Possible values for dataset:
                'reference' - 80% of steps of 80% of MD trajectories, (404000 examples).
                'reference_eq' - equilibrium conformations of those molecules, (101 examples).
                'test_within' - remaining 20% unseen steps of reference trajectories,
                    (101000 examples).
                'test_other' - remaining 20% unseen MD trajectories, (130000 examples).
                'test_eq' - equilibrium conformations of test trajectories, (5881 examples).

        """
        if (
            self.data is not None
            and "dataset_type" not in kwargs
            and "test_type" not in kwargs
        ):
            return self.data
        dataset_name = kwargs.get("dataset_type", "reference")
        test_name = kwargs.get("test_type", "test_other")
        data = {
            dataset_name: [],  # Populated to [positions, atomic_nums, energies, forces]
            test_name: [],
        }

        # Load from hdf5 file
        with h5py.File(self.path, "r") as file:
            for name, l in data.items():
                positions = self.pad_along_axis(
                    np.array(file["{}/positions".format(name)]), self.num_points
                )
                atomic_nums = self.pad_along_axis(
                    np.tile(
                        np.expand_dims(file["{}/atomic_numbers".format(name)], axis=0),
                        (len(positions), 1),
                    ),
                    self.num_points,
                )

                energies = np.array(file["{}/energies".format(name)])
                forces = (
                    self.pad_along_axis(
                        np.array(file["{}/forces".format(name)]), self.num_points
                    )
                    * self.KCAL_PER_EV
                )

                if self.use_energies:
                    l.extend([atomic_nums, positions, energies, forces])
                else:
                    l.extend([atomic_nums, positions, forces])

        # Remapping
        if self.map_points:
            [self.remap_points(d[0]) for d in data.values()]
        self._max_z = np.max(data[dataset_name][0]) + 1
        if kwargs.get("return_maxz", False):
            return

        # Get Force mu/sigma
        self._force_mu = np.mean(data[dataset_name][-1])
        self._force_sigma = np.std(data[dataset_name][-1])

        # Split data
        self.splitting = re.search(r"\d{1,2}:\d{1,2}", self.splitting).group(0)
        self.data = self.three_way_split(
            data=[data[dataset_name][:2], data[dataset_name][2:]],
            length=len(data[dataset_name][0]),
        )
        self.splitting = None
        self.data.extend(
            self.three_way_split(
                data=[data[test_name][:2], data[test_name][2:]],
                length=len(data[dataset_name][0]),
            )
        )
        return self.data