class PreciseEngine(WakeWordEnginePlugin): program_url = ( 'https://raw.githubusercontent.com/MycroftAI/' 'precise-data/dist/{arch}/precise-engine.tar.gz' ) model_url = ( 'https://raw.githubusercontent.com/MycroftAI/' 'precise-data/models/{model_name}.tar.gz' ) def __init__(self, rt, on_activation: Callable): super().__init__(rt, on_activation) exe_file = which('precise-engine') precise_folder = join(self.rt.paths.user_config, 'precise') if not exe_file: exe_file = join(precise_folder, 'precise-engine', 'precise-engine') download_extract_tar( self.program_url.format(arch=platform.machine()), precise_folder, check_md5=False, subdir='precise-engine', on_update=lambda: self.rt.interfaces.faceplate.text('Updating listener...'), on_complete=lambda: self.rt.interfaces.faceplate.reset() ) log.debug('Using precise executable: ' + exe_file) model_folder = join(precise_folder, 'models', self.wake_word) model_file = join(model_folder, self.wake_word + '.pb') model_url = self.model_url.format(model_name=self.wake_word) download_extract_tar(model_url, model_folder, check_md5=True) from precise_runner import PreciseRunner, PreciseEngine engine = PreciseEngine(exe_file, model_file, chunk_size=1024) self.runner = PreciseRunner(engine, on_activation=on_activation) def startup(self): self.runner.start() def shutdown(self): self.runner.stop() def continue_listening(self): self.runner.play() def pause_listening(self): self.runner.pause()
class HotwordDetector(Thread): """ Precise decoder to detect whether a keyword specified by `decoder_model` exists in a microphone input stream. :param decoder_model: decoder model file path, a string or a list of strings :param sensitivity: decoder sensitivity, a float of a list of floats. The bigger the value, the more senstive the decoder. If an empty list is provided, then the default sensitivity in the model will be used. """ def __init__(self, keyword=None, sensitivity=None, detected_callback=None ): super(HotwordDetector, self).__init__() sl = SettingLoader() self.settings = sl.settings self.paused_loop = False self.detected_callback = detected_callback self.sensitivity = sensitivity trigger_level = 3 self.keyword = keyword self.found_keyword = False if not os.path.exists(RESOURCE_FILE): if self.downloadPreciseEngine(): Utils.print_info("[Precise] Download complete") else: raise PreciseEngineNotFound("Error downloading precise engine, check your internet connection or try again later.") engine = PreciseEngine(RESOURCE_FILE, self.keyword) self.stream = ReadWriteStream() self.runner = PreciseRunner(engine, sensitivity=float(self.sensitivity), trigger_level=trigger_level, on_activation=self.activation ) self.runner.start() self.pause() # To avoid that precise starts detecting without beeing ready, we pause it right after start if self.settings.machine.startswith("arm"): # Because importing tensorflow takes up to 10 seconds, we sleep a while Utils.print_info("Starting precise trigger") time.sleep(10) def run(self): logger.debug("detecting...") while True: if not self.paused_loop: data = self.stream.read() if len(data) > 0: self.stream.write(data) if self.found_keyword: self.pause() # We start pausing it here, to avoid double activations message = "[Precise] Keyword detected" Utils.print_info(message) logger.debug(message) self.detected_callback() time.sleep(0.01) logger.debug("finished") def activation(self): self.found_keyword = True def pause(self): self.runner.pause() self.paused_loop = True def unpause(self): self.runner.play() self.paused_loop = False self.found_keyword = False def downloadPreciseEngine(self): import json import requests import tarfile Utils.print_info("[Preicse] Precise engine not present, starting download now") url = "https://api.github.com/repos/MycroftAI/mycroft-precise/releases/latest" response = requests.get(url) if response.status_code == 200: download_url = None arch = self.settings.machine for asset in response.json()["assets"]: if arch in asset.get("name"): if asset.get("name").startswith("precise-engine") and asset.get("name").endswith(".tar.gz"): download_name = asset.get("name") download_url = asset.get('browser_download_url') filepath = os.path.join(TOP_DIR, download_name) if download_url: Utils.print_info("[Precise] Downloading %s this can take a moment" % download_name) file = requests.get(download_url) if file.status_code == 200: with open(filepath, 'wb') as f: f.write(file.content) with tarfile.open(filepath) as tar: tar.extractall(path=TOP_DIR) os.remove(filepath) return True return False