def run(self): if not TTS.support(self._provider) : self.log(F('Неизвестный провайдер: {}', self._provider), logger.CRIT) self._file_path = self.cfg.path['tts_error'] return self._unlock() msg = F('{} за {}{}: {}', *self._generating(), self._file_path) self.log(msg, logger.DEBUG if self._realtime else logger.INFO)
def rec_compile(self, model, username): if not self.cfg.detector.ALLOW_TRAINING: msg = F('{} не поддерживает тренировку моделей.', self.cfg.detector) self.own.say(msg) self.log(msg, logger.WARN) return self._send_notify('model_compile', True) samples = [] samples_miss = [] for num in range(1, self.cfg.detector.SAMPLES_COUNT + 1): sample_path = self.cfg.path_to_sample(model, num) if not os.path.isfile(sample_path): samples_miss.append('{}.wav'.format(num)) else: samples.append(sample_path) if len(samples) >= self.cfg.detector.SAMPLES_TRAINING: username = username if len(username) > 1 else None self._compile_model(model, samples, username) else: split, more = 4, '' if len(samples_miss) > 4: split, more = 3, F(' и еще {}', len(samples_miss) - 3) path = os.path.join(self.cfg.path['samples'], model) samples_miss = '{}{}'.format(', '.join(samples_miss[:split]), more) self.log( F('Ошибка компиляции - файлы {} не найдены в {}.', samples_miss, path), logger.ERROR) self.own.say(F('Ошибка компиляции - файлы не найдены.')) self._send_notify('model_compile', False)
def _generating(self): sha1 = hashlib.sha1(self._msg.encode()).hexdigest() ext = 'opus' if self.cfg.yandex_api(self._provider) in (2, 3) else 'mp3' find_part = ''.join(('_', sha1, '.')) rname = find_part + ext use_cache = self.cfg.gt('cache', 'tts_size', 50) > 0 msg_gen = '\'{}\' '.format(self._msg) if self._realtime: self.log('say {}'.format(msg_gen), logger.INFO) msg_gen = '' if use_cache and self._found_in_cache(find_part, ext): self._unlock() work_time = time.time() - self._start_time action = F('{}найдено в кэше', msg_gen) time_diff = '' else: if not use_cache and self._provider in ('rhvoice-rest', 'rhvoice'): ext = 'wav' self._buff_size *= 4 self._file_path = os.path.join(self.cfg.gt('cache', 'path'), self._provider + rname) if use_cache else \ '<{}><{}>'.format(sha1, ext) self._tts_gen(self._file_path if use_cache else None, ext, self._msg) self._unlock() work_time = time.time() - self._start_time action = F('{}сгенерированно {}', msg_gen, self._provider) reply = utils.pretty_time(self._work_time) diff = utils.pretty_time(work_time - self._work_time) time_diff = ' [reply:{}, diff:{}]'.format(reply, diff) return action, utils.pretty_time(work_time), time_diff
def models_load(self): def lower_warning(): if self.detector.FAKE_MODELS: return l_name_ = file.lower() if l_name_ != file: msg_ = 'Please, rename {} to {} in {} for stability!'.format( repr(file), repr(l_name_), self.path['models']) self.log(msg_, logger.WARN) models, paths, msg = [], [], None if self.detector.NO_MODELS: pass elif not (os.path.isdir(self.path['models']) or self.detector.FAKE_MODELS): msg = F('Директория с моделями не найдена {}', self.path['models']) else: allow = self.get_allow_models() for file in self.get_all_models(): full_path = file if self.detector.FAKE_MODELS else os.path.join( self.path['models'], file) if self.detector.FAKE_MODELS or os.path.isfile(full_path): lower_warning() if not allow or file in allow: paths.append(full_path) models.append(file) self.models = ModelsStorage(paths, self['models'], models, no_models=self.detector.NO_MODELS) msg = msg or F('Загружено {} моделей', len(self.models)) self.log(msg, logger.INFO) self.own.say_info(msg)
def _external_check(self): while self._queue.qsize() and self.work: try: (cmd, data, lvl, late) = self._queue.get_nowait() except queue.Empty: self.log(F('Пустая очередь? Impossible!'), logger.ERROR) continue if late: late = time.time() - late msg = F('Получено {}:{}, lvl={} опоздание {} секунд.', cmd, repr(data)[:300], lvl, int(late)) if late > self.MAX_LATE: self.log(F('{} Игнорирую.', msg), logger.WARN) continue else: self.log(msg, logger.DEBUG) if cmd == 'tts': self.own.say(data, lvl=lvl) elif cmd == 'ask' and data: self._detected_parse(True, *self.own.listen(data)) elif cmd == 'voice' and not data: self._detected_parse(False, *self.own.listen(voice=True)) elif cmd in self.DATA_CALL: self.DATA_CALL[cmd](data) elif cmd in self.ARGS_CALL: self.ARGS_CALL[cmd](*data) else: self.log( F('Не верный вызов, WTF? {}:{}, lvl={}', cmd, repr(data)[:300], lvl), logger.ERROR)
def majordomo(self, _, phrase): if not phrase: self.log(F('Вы ничего не сказали?'), logger.DEBUG) return if not self.own.has_subscribers('cmd'): if not self.cfg['smarthome']['ip']: self.log(F('IP сервера не задан.'), logger.CRIT) return Say( F('IP сервера не задан, исправьте это! Мой IP адрес: {}', self.cfg.gts('ip'))) else: msg = F('Невозможно доставить - маршрут не найден') self.log(msg, logger.CRIT) return Say(msg) # FIX: 'Скажи ' -> 'скажи ' if phrase.startswith(F('Скажи ')): phrase = phrase[0].lower() + phrase[1:] kwargs = {'qry': phrase} if self.model: kwargs['username'] = self.cfg.gt('persons', self.model) if self.rms: kwargs.update(zip(('rms_min', 'rms_max', 'rms_avg'), self.rms)) self.own.send_notify('cmd', **kwargs)
def lock(self, phrase, *_): if self.get_one_way is lock: if phrase == F('блокировка'): return Set(one_way=None), Say(F('Блокировка снята')) else: return Say(F('блокировка')) else: return Set(one_way=lock), Say(F('Блокировка включена'))
def debug(_, phrase, *__): if phrase == F('выход'): return Set(debug=False), Say( F('Внимание! Выход из режима разработчика')) elif phrase == F('режим разработчика'): msg = 'Внимание! Включён режим разработчика. Для возврата в обычный режим скажите \'выход\'' return Set(debug=True), Say(F(msg)) return Next
def _detected(self, model_name, phrase, msg, cb): self.own.voice_activated_callback() no_hello = self.cfg.gts('no_hello') hello = '' if phrase and self.own.sys_say_chance and not no_hello: hello = F('{} слушает', phrase) self.log(F('Голосовая активация по {}{}', model_name, msg), logger.INFO) cb(hello, *self.own.listen(hello, voice=no_hello), model_name)
def rec_play(self, model, sample): file = self.cfg.path_to_sample(model, sample) if os.path.isfile(file): self.own.say(file, is_file=True) else: self.own.say( F('Ошибка воспроизведения - файл {} не найден', '{}.wav'.format(sample))) self.log(F('Файл {} не найден.', file), logger.WARN)
def _up_dependency(self, name: str, updater): to_update = self.cfg.gt('update', name) packages = updater(to_update) if packages: msg = F('Зависимости {} {}обновлены: {}').format( name, '' if to_update else F('не '), packages) event = '{}_{}'.format(name, 'yes' if to_update else 'no') self._notify_update(event) self.log(msg, logger.DEBUG if to_update else logger.WARN)
def lang_init(self): lang = self.gts('lang') err = languages.set_lang(lang, self.gts('lang_check')) if err: self.log(F('Ошибка инициализации языка {}: {}', repr(lang), err), logger.ERROR) msg = F('Локализация {} загружена за {}', lang, utils.pretty_time(languages.set_lang.load_time)) self.log(msg, logger.INFO)
def _remove_candidates(self, files: list): for file in files: path = os.path.join(self.cfg.path['backups'], file) try: os.remove(path) except OSError as e: self.log(F('Ошибка удаления старого бэкапа {}: {}', file, e), logger.WARN) else: self.log(F('Удален старый бэкап {}', file))
def get_mic_index(self): device_index = self.cfg.gts('mic_index', -1) if device_index > self.max_mic_index: if self.max_mic_index >= 0: mics = F('Доступны {}, от 0 до {}.', self.max_mic_index + 1, self.max_mic_index) else: mics = F('Микрофоны не найдены') self.log(F('Не верный индекс микрофона {}. {}', device_index, mics), logger.WARN) return None return None if device_index < 0 else device_index
def __now_i_set_emo(self, key): if key == self.cfg.get('emotion', 'unset'): return Say(F('Я уже {}.', YANDEX_EMOTION[key])) self.cfg['yandex']['emotion'] = key self.cfg.config_save() return Say( F( 'Теперь я очень {} {}.', YANDEX_EMOTION[key], YANDEX_SPEAKER.get(self.cfg['yandex'].get('speaker', 'unset'), F('Ошибка'))))
def __now_i_set_speaker(self, key, prov: dict, speakers: dict, yandex=False): if key == prov.get('speaker', 'unset'): return Say(F('Я уже {}.', speakers[key])) prov['speaker'] = key self.cfg.config_save() return Say( F( 'Теперь меня зовут {}, а еще я {}.', speakers[key], YANDEX_EMOTION.get(self.cfg['yandex'].get('emotion', 'unset'), F('Ошибка')) if yandex else F('без характера')))
def load_dict(self, name: str, format_='json') -> dict or None: file_path = os.path.join(self.path['data'], name + DATA_FORMATS.get(format_, '.json')) if not os.path.isfile(file_path): self.log(F('Файл не найден (это нормально): {}', file_path)) return None try: return utils.dict_from_file(file_path) except RuntimeError as e: self.log(F('Ошибка загрузки {}: {}', file_path, str(e)), logger.ERROR) return None
def voice_record(self, hello: str or None, save_to: str, convert_rate=None, convert_width=None, limit=8): if self.max_mic_index == -2: self.log(F('Микрофоны не найдены'), logger.ERROR) return F('Микрофоны не найдены') self._lock.acquire() self._start_stt_event() try: return self._voice_record(hello, save_to, convert_rate, convert_width, limit) finally: self.own.clear_lvl() self._stop_stt_event() self._lock.release()
def _send_cmd(self, kwargs: dict): username = kwargs.pop('username', None) try: self.log( F('Запрос был успешен: {}', self._send('cmd', kwargs, username)), logger.DEBUG) except RuntimeError as e: self._skip.got_error() e = '[{}] {}'.format(self.own.srv_ip, e) self.log(F('Ошибка коммуникации с сервером: {}', e), logger.ERROR) self.own.say(F('Ошибка коммуникации с сервером: {}', '')) else: self._skip.clear()
def _check_file(self, filename: str) -> tuple: if self.work: return F('Демон еще работает'), None if not utils.is_valid_base_filename(filename): return F('Некорректное имя файла: {}', repr(filename)), None path = os.path.join(self.cfg.path['backups'], filename) if not os.path.isfile(path): return F('Файл не найден: {}', path), None try: self._get_zip_timestamp(path) except Exception as e: return F('Архив поврежден: {}: {}', filename, e), None return None, path
def _open_socket(self) -> bool: self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self._socket.settimeout(1) try: self._socket.bind(self._local) except OSError as e: say = F('Ошибка запуска сервера{}.', F(' - адрес уже используется') if e.errno == 98 else '') self.log(F('Ошибка запуска сервера на {}:{}: {}', *self._local, e), logger_.CRIT) self.own.say(say) return False self._socket.listen(1) return True
def _config_save(self): wtime = time.time() config = ConfigParserOnOff() for key, val in self.items(): if isinstance(val, dict): config[key] = val with open(self.path['settings'], 'w', encoding='utf8') as configfile: config.write(configfile) self.log( F('Конфигурация сохранена за {}', utils.pretty_time(time.time() - wtime)), logger.INFO) self.own.say_info(F('Конфигурация сохранена!'))
def _print_info(self): active, inactive, disable = [], [], [] for module in self.all.values(): if not module['enable']: disable.append(module['name']) elif module['mode'] in (NM, ANY): active.append(module['name']) else: inactive.append(module['name']) if disable: self._log(F('Отключенные модули: {}', ', '.join(disable))) if inactive: self._log(F('Неактивные модули: {}', ', '.join(inactive))) if active: self._log(F('Активные модули: {}', ', '.join(active)), logger.INFO)
def config_load(self): wtime = time.time() if not os.path.isfile(self.path['settings']): msg = 'Файл настроек не найден по пути {}. Для первого запуска это нормально' self.log(F(msg, self.path['settings']), logger.INFO) return True updater = ConfigUpdater(self, self.log) count = updater.from_ini(self.path['settings']) wtime = time.time() - wtime self.lang_init() self.log( F('Загружено {} опций за {}', count, utils.pretty_time(wtime)), logger.INFO) self.own.say_info(F('Конфигурация загружена!')) return updater.save_ini
def who_am_i(self, *_): def get_yandex_emo(): return YANDEX_EMOTION.get(self.cfg.gt('yandex', 'emotion', 'unset'), F('Ошибка')) speakers = __tts_selector(self.cfg) if speakers is None: return Say(F('Не поддерживается для {}', self.cfg.gts('providertts'))) speaker = self.cfg[self.cfg.gts('providertts')].get('speaker', 'unset') emotion = F( ' Я очень {}.', get_yandex_emo()) if self.cfg.gts('providertts') == 'yandex' else '' return Say( F('Меня зовут {}.{}', speakers.get(speaker, F('Ошибка')), emotion))
def _fallback(self, up, hash_=None, mode='fallback'): self.log(F('Выполняется откат обновления.'), logger.DEBUG) try: if hash_: up.set_old_hash(hash_) up.fallback() except RuntimeError as e: self._notify_update('{}_failed'.format(mode)) self.log(F('Во время отката обновления возникла ошибка: {}', e), logger.CRIT) return F('Откат невозможен.') else: self._notify_update('{}_ok'.format(mode)) self.log(F('Откат обновления выполнен успешно.'), logger.INFO) return F('Выполнен откат.')
def _set_music_volume(self, value, quiet=False): if value is not None: try: value_clean = clean_volume(value) except RuntimeError as e: msg = F('Недопустимое значение: {}', value) self.log('{}, {}'.format(msg, e), logger.WARN) if not quiet: self.own.say(msg) return self.own.music_real_volume = value_clean value = self.own.music_real_volume self.log(F('Громкость музыки {} процентов', value)) if not quiet: self.own.say(F('Громкость музыки {} процентов', value))
def stop_all_systems(self): self._cfg.config_save(final=True) self.join_thread(self._plugins) self._mm.stop() self.join_thread(self._discovery) self.join_thread(self._server) self.join_thread(self._terminal) self.join_thread(self._backup) self.join_thread(self._updater) self.join_thread(self._notifier) self.join_thread(self._duplex_pool) self._play.quiet() self._play.kill_popen() self._play.say_info(F('Голосовой терминал завершает свою работу.')) self._stt.stop() self._play.stop() self.join_thread(self._music) if self._restore_filename: self._backup.restore(self._restore_filename) self._restore_filename = '' self.join_thread(self._logger.remote_log) self._pub.stopping = True self._logger.join() self._pub.join() self._pub.report()
def _listen(self, hello: str, voice) -> tuple: lvl = 5 # Включаем монопольный режим commands = None if self.cfg.gts('alarmkwactivated'): self.own.play(self.cfg.path['ding'], lvl, blocking=2) else: self.own.set_lvl(lvl) self.own.kill_popen() self.log('audio devices: {}'.format(pyaudio.PyAudio().get_device_count() - 1), logger.DEBUG) hello = hello or self.sys_say.hello file_path = self.own.tts(hello) if not voice and hello else None if self.cfg.gts('blocking_listener'): audio, record_time, energy, rms = self._block_listen(hello, lvl, file_path) else: audio, record_time, energy, rms = self._non_block_listen(hello, lvl, file_path) self.log(F('Голос записан за {}', utils.pretty_time(record_time)), logger.INFO) # Выключаем монопольный режим self.own.clear_lvl() if self.cfg.gts('alarmstt'): self.own.play(self.cfg.path['dong']) if audio is not None: commands = self.voice_recognition(audio) if commands: self.log(utils.recognition_msg(commands, energy, rms), logger.INFO) return commands, rms
def _voice_record(self, hello: str or None, save_to: str, convert_rate, convert_width, limit): lvl = 5 # Включаем монопольный режим if hello is not None: self.own.say(self.own.tts(hello)(), lvl, True, is_file=True, blocking=120) else: self.own.set_lvl(lvl) r = sr.Recognizer(record_callback=self.own.record_callback) mic = sr.Microphone(device_index=self.get_mic_index()) vad = self.own.get_vad_detector(mic) self.own.play(self.cfg.path['ding'], lvl, blocking=3) with mic as source: record_time = time.time() try: adata = r.listen1(source=source, vad=vad, timeout=5, phrase_time_limit=limit) except sr.WaitTimeoutError as e: return str(e) if time.time() - record_time < 0.5: return F('Во время записи произошел сбой, это нужно исправить') try: os.makedirs(os.path.dirname(save_to), exist_ok=True) with open(save_to, "wb") as f: f.write(adata.get_wav_data(convert_rate, convert_width)) except IOError as err: return str(err) else: return None