"""
Component that will help set the openalpr for video streams.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/openalpr/
"""
from base64 import b64encode
import logging
import os
from time import time

import requests
import voluptuous as vol

from homeassistant.config import load_yaml_config_file
from homeassistant.const import (
    CONF_API_KEY, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, ATTR_ENTITY_ID,
    EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN)
from homeassistant.components.ffmpeg import (
    get_binary, run_test, CONF_INPUT, CONF_EXTRA_ARGUMENTS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent

DOMAIN = 'openalpr'
DEPENDENCIES = ['ffmpeg']
REQUIREMENTS = [
    'https://github.com/pvizeli/cloudapi/releases/download/1.0.2/'
    'python-1.0.2.zip#openalpr_api==1.0.2',
    'ha-alpr==0.3']

_LOGGER = logging.getLogger(__name__)

SERVICE_SCAN = 'scan'
SERVICE_RESTART = 'restart'

EVENT_FOUND = 'openalpr.found'

ATTR_PLATE = 'plate'


ENGINE_LOCAL = 'local'
ENGINE_CLOUD = 'cloud'

RENDER_IMAGE = 'image'
RENDER_FFMPEG = 'ffmpeg'

OPENALPR_REGIONS = [
    'us',
    'eu',
    'au',
    'auwide',
    'gb',
    'kr',
    'mx',
    'sg',
]

CONF_RENDER = 'render'
CONF_ENGINE = 'engine'
CONF_REGION = 'region'
CONF_INTERVAL = 'interval'
CONF_ENTITIES = 'entities'
CONF_CONFIDENCE = 'confidence'
CONF_ALPR_BINARY = 'alpr_binary'

DEFAULT_NAME = 'OpenAlpr'
DEFAULT_ENGINE = ENGINE_LOCAL
DEFAULT_RENDER = RENDER_FFMPEG
DEFAULT_BINARY = 'alpr'
DEFAULT_INTERVAL = 10
DEFAULT_CONFIDENCE = 80.0

DEVICE_SCHEMA = vol.Schema({
    vol.Required(CONF_INPUT): cv.string,
    vol.Optional(CONF_INTERVAL, default=DEFAULT_INTERVAL): cv.positive_int,
    vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
    vol.Optional(CONF_RENDER, default=DEFAULT_RENDER):
        vol.In([RENDER_IMAGE, RENDER_FFMPEG]),
    vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
    vol.Optional(CONF_USERNAME): cv.string,
    vol.Optional(CONF_PASSWORD): cv.string,
})

CONFIG_SCHEMA = vol.Schema({
    DOMAIN: vol.Schema({
        vol.Required(CONF_ENGINE): vol.In([ENGINE_LOCAL, ENGINE_CLOUD]),
        vol.Required(CONF_REGION): vol.In(OPENALPR_REGIONS),
        vol.Optional(CONF_CONFIDENCE, default=DEFAULT_CONFIDENCE):
            vol.Coerce(float),
        vol.Optional(CONF_API_KEY): cv.string,
        vol.Optional(CONF_ALPR_BINARY, default=DEFAULT_BINARY): cv.string,
        vol.Required(CONF_ENTITIES):
            vol.All(cv.ensure_list, [DEVICE_SCHEMA]),
    })
}, extra=vol.ALLOW_EXTRA)


SERVICE_RESTART_SCHEMA = vol.Schema({
    vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})

SERVICE_SCAN_SCHEMA = vol.Schema({
    vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})


def scan(hass, entity_id=None):
    """Scan a image immediately."""
    data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
    hass.services.call(DOMAIN, SERVICE_SCAN, data)


def restart(hass, entity_id=None):
    """Restart a ffmpeg process."""
    data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
    hass.services.call(DOMAIN, SERVICE_RESTART, data)


def setup(hass, config):
    """Setup the OpenAlpr component."""
    engine = config[DOMAIN].get(CONF_ENGINE)
    region = config[DOMAIN].get(CONF_REGION)
    confidence = config[DOMAIN].get(CONF_CONFIDENCE)
    api_key = config[DOMAIN].get(CONF_API_KEY)
    binary = config[DOMAIN].get(CONF_ALPR_BINARY)
    use_render_fffmpeg = False

    _LOGGER.warning("This platform is replaced by 'image_processing' and will "
                    "be removed in a future version!")

    component = EntityComponent(_LOGGER, DOMAIN, hass)
    openalpr_device = []

    for device in config[DOMAIN].get(CONF_ENTITIES):
        input_source = device.get(CONF_INPUT)
        render = device.get(CONF_RENDER)

        ##
        # create api
        if engine == ENGINE_LOCAL:
            alpr_api = OpenalprApiLocal(
                confidence=confidence,
                region=region,
                binary=binary,
            )
        else:
            alpr_api = OpenalprApiCloud(
                confidence=confidence,
                region=region,
                api_key=api_key,
            )

        ##
        # Create Alpr device / render engine
        if render == RENDER_FFMPEG:
            use_render_fffmpeg = True
            if not run_test(hass, input_source):
                _LOGGER.error("'%s' is not valid ffmpeg input", input_source)
                continue

            alpr_dev = OpenalprDeviceFFmpeg(
                name=device.get(CONF_NAME),
                interval=device.get(CONF_INTERVAL),
                api=alpr_api,
                input_source=input_source,
                extra_arguments=device.get(CONF_EXTRA_ARGUMENTS),
            )
        else:
            alpr_dev = OpenalprDeviceImage(
                name=device.get(CONF_NAME),
                interval=device.get(CONF_INTERVAL),
                api=alpr_api,
                input_source=input_source,
                username=device.get(CONF_USERNAME),
                password=device.get(CONF_PASSWORD),
            )

        # register shutdown event
        openalpr_device.append(alpr_dev)
        hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, alpr_dev.shutdown)

    component.add_entities(openalpr_device)

    descriptions = load_yaml_config_file(
        os.path.join(os.path.dirname(__file__), 'services.yaml'))

    def _handle_service_scan(service):
        """Handle service for immediately scan."""
        device_list = component.extract_from_service(service)

        for device in device_list:
            device.scan()

    hass.services.register(DOMAIN, SERVICE_SCAN,
                           _handle_service_scan,
                           descriptions[DOMAIN][SERVICE_SCAN],
                           schema=SERVICE_SCAN_SCHEMA)

    # Add restart service only if a device use ffmpeg as render
    if not use_render_fffmpeg:
        return True

    def _handle_service_restart(service):
        """Handle service for restart ffmpeg process."""
        device_list = component.extract_from_service(service)

        for device in device_list:
            device.restart()

    hass.services.register(DOMAIN, SERVICE_RESTART,
                           _handle_service_restart,
                           descriptions[DOMAIN][SERVICE_RESTART],
                           schema=SERVICE_RESTART_SCHEMA)

    return True


class OpenalprDevice(Entity):
    """Represent a openalpr device object for processing stream/images."""

    def __init__(self, name, interval, api):
        """Init image processing."""
        self._name = name
        self._interval = interval
        self._api = api
        self._last = {}

    @property
    def state(self):
        """Return the state of the entity."""
        confidence = 0
        plate = STATE_UNKNOWN

        # search high plate
        for i_pl, i_co in self._last.items():
            if i_co > confidence:
                confidence = i_co
                plate = i_pl
        return plate

    def shutdown(self, event):
        """Close stream."""
        if hasattr(self._api, "shutdown"):
            self._api.shutdown(event)

    def restart(self):
        """Restart stream."""
        raise NotImplementedError()

    def _process_image(self, image):
        """Callback for processing image."""
        self._api.process_image(image, self._process_event)

    def _process_event(self, plates):
        """Send event with new plates."""
        state_change = False
        plates_set = set(plates)
        last_set = set(self._last)
        new_plates = plates_set - last_set

        # send events
        for i_plate in new_plates:
            self.hass.bus.fire(EVENT_FOUND, {
                ATTR_PLATE: i_plate,
                ATTR_ENTITY_ID: self.entity_id
            })

        # update entity store
        if last_set <= plates_set:
            state_change = True
        self._last = plates

        # update HA state
        if state_change:
            self.update_ha_state()

    @property
    def device_state_attributes(self):
        """Return device specific state attributes."""
        return {'plates': self._last}

    def scan(self):
        """Immediately scan a image."""
        raise NotImplementedError()

    @property
    def name(self):
        """Return the name of the entity."""
        return self._name


class OpenalprDeviceFFmpeg(OpenalprDevice):
    """Represent a openalpr device object for processing stream/images."""

    def __init__(self, name, interval, api, input_source,
                 extra_arguments=None):
        """Init image processing."""
        from haffmpeg import ImageStream, ImageSingle

        super().__init__(name, interval, api)
        self._input_source = input_source
        self._extra_arguments = extra_arguments

        if self._interval > 0:
            self._ffmpeg = ImageStream(get_binary(), self._process_image)
        else:
            self._ffmpeg = ImageSingle(get_binary())

        self._start_ffmpeg()

    def shutdown(self, event):
        """Close ffmpeg stream."""
        if self._interval > 0:
            self._ffmpeg.close()

    def restart(self):
        """Restart ffmpeg stream."""
        if self._interval > 0:
            self._ffmpeg.close()
            self._start_ffmpeg()

    def scan(self):
        """Immediately scan a image."""
        from haffmpeg import IMAGE_PNG

        # process single image
        if self._interval == 0:
            image = self._ffmpeg.get_image(
                self._input_source,
                output_format=IMAGE_PNG,
                extra_cmd=self._extra_arguments
            )
            return self._process_image(image)

        # stream
        self._ffmpeg.push_image()

    def _start_ffmpeg(self):
        """Start a ffmpeg image stream."""
        from haffmpeg import IMAGE_PNG
        if self._interval == 0:
            return

        self._ffmpeg.open_stream(
            input_source=self._input_source,
            interval=self._interval,
            output_format=IMAGE_PNG,
            extra_cmd=self._extra_arguments,
        )

    @property
    def should_poll(self):
        """Return True if render is be 'image' or False if 'ffmpeg'."""
        return False

    @property
    def available(self):
        """Return True if entity is available."""
        return self._interval == 0 or self._ffmpeg.is_running


class OpenalprDeviceImage(OpenalprDevice):
    """Represent a openalpr device object for processing stream/images."""

    def __init__(self, name, interval, api, input_source,
                 username=None, password=<PASSWORD>):
        """Init image processing."""
        super().__init__(name, interval, api)

        self._next = time()
        self._username = username
        self._password = password
        self._url = input_source

    def restart(self):
        """Fake restart with scan a picture."""
        self.scan()

    def scan(self):
        """Immediately scan a image."""
        # send request
        if self._username is not None and self._password is not None:
            req = requests.get(
                self._url, auth=(self._username, self._password), timeout=15)
        else:
            req = requests.get(self._url, timeout=15)

        # process image
        image = req.content
        self._process_image(image)

    @property
    def should_poll(self):
        """Return True if render is be 'image' or False if 'ffmpeg'."""
        return self._interval > 0

    def update(self):
        """Retrieve latest state."""
        if self._next > time():
            return
        self.scan()
        self._next = time() + self._interval


class OpenalprApi(object):
    """OpenAlpr api class."""

    def __init__(self, region, confidence):
        """Init basic api processing."""
        self._region = region
        self._confidence = confidence

    def process_image(self, image, event_callback):
        """Callback for processing image."""
        raise NotImplementedError()


class OpenalprApiCloud(OpenalprApi):
    """Use the cloud openalpr api to parse licences plate."""

    def __init__(self, region, confidence, api_key):
        """Init cloud api processing."""
        import openalpr_api

        super().__init__(region=region, confidence=confidence)
        self._api = openalpr_api.DefaultApi()
        self._api_key = api_key

    def process_image(self, image, event_callback):
        """Callback for processing image."""
        result = self._api.recognize_post(
            self._api_key,
            'plate',
            image="",
            image_bytes=str(b64encode(image), 'utf-8'),
            country=self._region
        )

        # process result
        f_plates = {}
        # pylint: disable=no-member
        for object_plate in result.plate.results:
            plate = object_plate.plate
            confidence = object_plate.confidence
            if confidence >= self._confidence:
                f_plates[plate] = confidence
        event_callback(f_plates)


class OpenalprApiLocal(OpenalprApi):
    """Use local openalpr library to parse licences plate."""

    def __init__(self, region, confidence, binary):
        """Init local api processing."""
        # pylint: disable=import-error
        from haalpr import HAAlpr

        super().__init__(region=region, confidence=confidence)
        self._api = HAAlpr(binary=binary, country=region)

    def process_image(self, image, event_callback):
        """Callback for processing image."""
        result = self._api.recognize_byte(image)

        # process result
        f_plates = {}
        for found in result:
            for plate, confidence in found.items():
                if confidence >= self._confidence:
                    f_plates[plate] = confidence
        event_callback(f_plates)

"""
Component that will help set the openalpr for video streams.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/openalpr/
"""
from base64 import b64encode
import logging
import os
from time import time

import requests
import voluptuous as vol

from homeassistant.config import load_yaml_config_file
from homeassistant.const import (
    CONF_API_KEY, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, ATTR_ENTITY_ID,
    EVENT_HOMEASSISTANT_STOP, STATE_UNKNOWN)
from homeassistant.components.ffmpeg import (
    get_binary, run_test, CONF_INPUT, CONF_EXTRA_ARGUMENTS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent

DOMAIN = 'openalpr'
DEPENDENCIES = ['ffmpeg']
REQUIREMENTS = [
    'https://github.com/pvizeli/cloudapi/releases/download/1.0.2/'
    'python-1.0.2.zip#openalpr_api==1.0.2',
    'ha-alpr==0.3']

_LOGGER = logging.getLogger(__name__)

SERVICE_SCAN = 'scan'
SERVICE_RESTART = 'restart'

EVENT_FOUND = 'openalpr.found'

ATTR_PLATE = 'plate'


ENGINE_LOCAL = 'local'
ENGINE_CLOUD = 'cloud'

RENDER_IMAGE = 'image'
RENDER_FFMPEG = 'ffmpeg'

OPENALPR_REGIONS = [
    'us',
    'eu',
    'au',
    'auwide',
    'gb',
    'kr',
    'mx',
    'sg',
]

CONF_RENDER = 'render'
CONF_ENGINE = 'engine'
CONF_REGION = 'region'
CONF_INTERVAL = 'interval'
CONF_ENTITIES = 'entities'
CONF_CONFIDENCE = 'confidence'
CONF_ALPR_BINARY = 'alpr_binary'

DEFAULT_NAME = 'OpenAlpr'
DEFAULT_ENGINE = ENGINE_LOCAL
DEFAULT_RENDER = RENDER_FFMPEG
DEFAULT_BINARY = 'alpr'
DEFAULT_INTERVAL = 10
DEFAULT_CONFIDENCE = 80.0

DEVICE_SCHEMA = vol.Schema({
    vol.Required(CONF_INPUT): cv.string,
    vol.Optional(CONF_INTERVAL, default=DEFAULT_INTERVAL): cv.positive_int,
    vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
    vol.Optional(CONF_RENDER, default=DEFAULT_RENDER):
        vol.In([RENDER_IMAGE, RENDER_FFMPEG]),
    vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
    vol.Optional(CONF_USERNAME): cv.string,
    vol.Optional(CONF_PASSWORD): cv.string,
})

CONFIG_SCHEMA = vol.Schema({
    DOMAIN: vol.Schema({
        vol.Required(CONF_ENGINE): vol.In([ENGINE_LOCAL, ENGINE_CLOUD]),
        vol.Required(CONF_REGION): vol.In(OPENALPR_REGIONS),
        vol.Optional(CONF_CONFIDENCE, default=DEFAULT_CONFIDENCE):
            vol.Coerce(float),
        vol.Optional(CONF_API_KEY): cv.string,
        vol.Optional(CONF_ALPR_BINARY, default=DEFAULT_BINARY): cv.string,
        vol.Required(CONF_ENTITIES):
            vol.All(cv.ensure_list, [DEVICE_SCHEMA]),
    })
}, extra=vol.ALLOW_EXTRA)


SERVICE_RESTART_SCHEMA = vol.Schema({
    vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})

SERVICE_SCAN_SCHEMA = vol.Schema({
    vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})


def scan(hass, entity_id=None):
    """Scan a image immediately."""
    data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
    hass.services.call(DOMAIN, SERVICE_SCAN, data)


def restart(hass, entity_id=None):
    """Restart a ffmpeg process."""
    data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
    hass.services.call(DOMAIN, SERVICE_RESTART, data)


def setup(hass, config):
    """Setup the OpenAlpr component."""
    engine = config[DOMAIN].get(CONF_ENGINE)
    region = config[DOMAIN].get(CONF_REGION)
    confidence = config[DOMAIN].get(CONF_CONFIDENCE)
    api_key = config[DOMAIN].get(CONF_API_KEY)
    binary = config[DOMAIN].get(CONF_ALPR_BINARY)
    use_render_fffmpeg = False

    _LOGGER.warning("This platform is replaced by 'image_processing' and will "
                    "be removed in a future version!")

    component = EntityComponent(_LOGGER, DOMAIN, hass)
    openalpr_device = []

    for device in config[DOMAIN].get(CONF_ENTITIES):
        input_source = device.get(CONF_INPUT)
        render = device.get(CONF_RENDER)

        ##
        # create api
        if engine == ENGINE_LOCAL:
            alpr_api = OpenalprApiLocal(
                confidence=confidence,
                region=region,
                binary=binary,
            )
        else:
            alpr_api = OpenalprApiCloud(
                confidence=confidence,
                region=region,
                api_key=api_key,
            )

        ##
        # Create Alpr device / render engine
        if render == RENDER_FFMPEG:
            use_render_fffmpeg = True
            if not run_test(hass, input_source):
                _LOGGER.error("'%s' is not valid ffmpeg input", input_source)
                continue

            alpr_dev = OpenalprDeviceFFmpeg(
                name=device.get(CONF_NAME),
                interval=device.get(CONF_INTERVAL),
                api=alpr_api,
                input_source=input_source,
                extra_arguments=device.get(CONF_EXTRA_ARGUMENTS),
            )
        else:
            alpr_dev = OpenalprDeviceImage(
                name=device.get(CONF_NAME),
                interval=device.get(CONF_INTERVAL),
                api=alpr_api,
                input_source=input_source,
                username=device.get(CONF_USERNAME),
                password=device.get(CONF_PASSWORD),
            )

        # register shutdown event
        openalpr_device.append(alpr_dev)
        hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, alpr_dev.shutdown)

    component.add_entities(openalpr_device)

    descriptions = load_yaml_config_file(
        os.path.join(os.path.dirname(__file__), 'services.yaml'))

    def _handle_service_scan(service):
        """Handle service for immediately scan."""
        device_list = component.extract_from_service(service)

        for device in device_list:
            device.scan()

    hass.services.register(DOMAIN, SERVICE_SCAN,
                           _handle_service_scan,
                           descriptions[DOMAIN][SERVICE_SCAN],
                           schema=SERVICE_SCAN_SCHEMA)

    # Add restart service only if a device use ffmpeg as render
    if not use_render_fffmpeg:
        return True

    def _handle_service_restart(service):
        """Handle service for restart ffmpeg process."""
        device_list = component.extract_from_service(service)

        for device in device_list:
            device.restart()

    hass.services.register(DOMAIN, SERVICE_RESTART,
                           _handle_service_restart,
                           descriptions[DOMAIN][SERVICE_RESTART],
                           schema=SERVICE_RESTART_SCHEMA)

    return True


class OpenalprDevice(Entity):
    """Represent a openalpr device object for processing stream/images."""

    def __init__(self, name, interval, api):
        """Init image processing."""
        self._name = name
        self._interval = interval
        self._api = api
        self._last = {}

    @property
    def state(self):
        """Return the state of the entity."""
        confidence = 0
        plate = STATE_UNKNOWN

        # search high plate
        for i_pl, i_co in self._last.items():
            if i_co > confidence:
                confidence = i_co
                plate = i_pl
        return plate

    def shutdown(self, event):
        """Close stream."""
        if hasattr(self._api, "shutdown"):
            self._api.shutdown(event)

    def restart(self):
        """Restart stream."""
        raise NotImplementedError()

    def _process_image(self, image):
        """Callback for processing image."""
        self._api.process_image(image, self._process_event)

    def _process_event(self, plates):
        """Send event with new plates."""
        state_change = False
        plates_set = set(plates)
        last_set = set(self._last)
        new_plates = plates_set - last_set

        # send events
        for i_plate in new_plates:
            self.hass.bus.fire(EVENT_FOUND, {
                ATTR_PLATE: i_plate,
                ATTR_ENTITY_ID: self.entity_id
            })

        # update entity store
        if last_set <= plates_set:
            state_change = True
        self._last = plates

        # update HA state
        if state_change:
            self.update_ha_state()

    @property
    def device_state_attributes(self):
        """Return device specific state attributes."""
        return {'plates': self._last}

    def scan(self):
        """Immediately scan a image."""
        raise NotImplementedError()

    @property
    def name(self):
        """Return the name of the entity."""
        return self._name


class OpenalprDeviceFFmpeg(OpenalprDevice):
    """Represent a openalpr device object for processing stream/images."""

    def __init__(self, name, interval, api, input_source,
                 extra_arguments=None):
        """Init image processing."""
        from haffmpeg import ImageStream, ImageSingle

        super().__init__(name, interval, api)
        self._input_source = input_source
        self._extra_arguments = extra_arguments

        if self._interval > 0:
            self._ffmpeg = ImageStream(get_binary(), self._process_image)
        else:
            self._ffmpeg = ImageSingle(get_binary())

        self._start_ffmpeg()

    def shutdown(self, event):
        """Close ffmpeg stream."""
        if self._interval > 0:
            self._ffmpeg.close()

    def restart(self):
        """Restart ffmpeg stream."""
        if self._interval > 0:
            self._ffmpeg.close()
            self._start_ffmpeg()

    def scan(self):
        """Immediately scan a image."""
        from haffmpeg import IMAGE_PNG

        # process single image
        if self._interval == 0:
            image = self._ffmpeg.get_image(
                self._input_source,
                output_format=IMAGE_PNG,
                extra_cmd=self._extra_arguments
            )
            return self._process_image(image)

        # stream
        self._ffmpeg.push_image()

    def _start_ffmpeg(self):
        """Start a ffmpeg image stream."""
        from haffmpeg import IMAGE_PNG
        if self._interval == 0:
            return

        self._ffmpeg.open_stream(
            input_source=self._input_source,
            interval=self._interval,
            output_format=IMAGE_PNG,
            extra_cmd=self._extra_arguments,
        )

    @property
    def should_poll(self):
        """Return True if render is be 'image' or False if 'ffmpeg'."""
        return False

    @property
    def available(self):
        """Return True if entity is available."""
        return self._interval == 0 or self._ffmpeg.is_running


class OpenalprDeviceImage(OpenalprDevice):
    """Represent a openalpr device object for processing stream/images."""

    def __init__(self, name, interval, api, input_source,
                 username=None, password=<PASSWORD>):
        """Init image processing."""
        super().__init__(name, interval, api)

        self._next = time()
        self._username = username
        self._password = password
        self._url = input_source

    def restart(self):
        """Fake restart with scan a picture."""
        self.scan()

    def scan(self):
        """Immediately scan a image."""
        # send request
        if self._username is not None and self._password is not None:
            req = requests.get(
                self._url, auth=(self._username, self._password), timeout=15)
        else:
            req = requests.get(self._url, timeout=15)

        # process image
        image = req.content
        self._process_image(image)

    @property
    def should_poll(self):
        """Return True if render is be 'image' or False if 'ffmpeg'."""
        return self._interval > 0

    def update(self):
        """Retrieve latest state."""
        if self._next > time():
            return
        self.scan()
        self._next = time() + self._interval


class OpenalprApi(object):
    """OpenAlpr api class."""

    def __init__(self, region, confidence):
        """Init basic api processing."""
        self._region = region
        self._confidence = confidence

    def process_image(self, image, event_callback):
        """Callback for processing image."""
        raise NotImplementedError()


class OpenalprApiCloud(OpenalprApi):
    """Use the cloud openalpr api to parse licences plate."""

    def __init__(self, region, confidence, api_key):
        """Init cloud api processing."""
        import openalpr_api

        super().__init__(region=region, confidence=confidence)
        self._api = openalpr_api.DefaultApi()
        self._api_key = api_key

    def process_image(self, image, event_callback):
        """Callback for processing image."""
        result = self._api.recognize_post(
            self._api_key,
            'plate',
            image="",
            image_bytes=str(b64encode(image), 'utf-8'),
            country=self._region
        )

        # process result
        f_plates = {}
        # pylint: disable=no-member
        for object_plate in result.plate.results:
            plate = object_plate.plate
            confidence = object_plate.confidence
            if confidence >= self._confidence:
                f_plates[plate] = confidence
        event_callback(f_plates)


class OpenalprApiLocal(OpenalprApi):
    """Use local openalpr library to parse licences plate."""

    def __init__(self, region, confidence, binary):
        """Init local api processing."""
        # pylint: disable=import-error
        from haalpr import HAAlpr

        super().__init__(region=region, confidence=confidence)
        self._api = HAAlpr(binary=binary, country=region)

    def process_image(self, image, event_callback):
        """Callback for processing image."""
        result = self._api.recognize_byte(image)

        # process result
        f_plates = {}
        for found in result:
            for plate, confidence in found.items():
                if confidence >= self._confidence:
                    f_plates[plate] = confidence
        event_callback(f_plates)

Component that will help set the openalpr for video streams.  For more details about this component, please refer to the documentation at https://home-assistant.io/components/openalpr/ #openalpr_api==1.0.2', Scan a image immediately. Restart a ffmpeg process. Setup the OpenAlpr component. ## # create api ## # Create Alpr device / render engine # register shutdown event Handle service for immediately scan. # Add restart service only if a device use ffmpeg as render Handle service for restart ffmpeg process. Represent a openalpr device object for processing stream/images. Init image processing. Return the state of the entity. # search high plate Close stream. Restart stream. Callback for processing image. Send event with new plates. # send events # update entity store # update HA state Return device specific state attributes. Immediately scan a image. Return the name of the entity. Represent a openalpr device object for processing stream/images. Init image processing. Close ffmpeg stream. Restart ffmpeg stream. Immediately scan a image. # process single image # stream Start a ffmpeg image stream. Return True if render is be 'image' or False if 'ffmpeg'. Return True if entity is available. Represent a openalpr device object for processing stream/images. Init image processing. Fake restart with scan a picture. Immediately scan a image. # send request # process image Return True if render is be 'image' or False if 'ffmpeg'. Retrieve latest state. OpenAlpr api class. Init basic api processing. Callback for processing image. Use the cloud openalpr api to parse licences plate. Init cloud api processing. Callback for processing image. # process result # pylint: disable=no-member Use local openalpr library to parse licences plate. Init local api processing. # pylint: disable=import-error Callback for processing image. # process result