Всем привет, я программист-любитель, изучающий язык python уже многие годы.

Не буду долго тянуть с приветствием и сразу перейду к делу. Этот гайд является по сути пошаговой инструкцией для создания базового кода, который можно будет потом расширять и дополнять. Голосовой ассистент - вещь весьма удобная, особенно если эту вещь можно настроить под свои нужды. Спасибо языку python, имеющему бесчисленное множество библиотек и фреймворков, позволяющих писать почти что угодно под почти любую платформу.

Для нашего же случая подойдут библиотеки silero (для tts), vosk (для stt) и sounddevice (для воспроизведения аудио)
сразу установим их: pip install silero vosk sounddevice

Преобразование текста в речь

Библиотека silero может преобразовывать текст в речь на множестве языков, нас же интересует русский язык, поэтому создадим небольшой класс на основе официальной документации:

import sounddevice as sd
import torch
import time

# константы голосов, поддерживаемых в silero
SPEAKER_AIDAR   = "aidar"
SPEAKER_BAYA    = "baya"
SPEAKER_KSENIYA = "kseniya"
SPEAKER_XENIA   = "xenia"
SPEAKER_RANDOM  = "random"

# константы девайсов для работы torch
DEVICE_CPU    = "cpu"
DEVICE_CUDA   = "cuda" 
DEVICE_VULKAN = "vulkan"
DEVICE_OPENGL = "opengl"
DEVICE_OPENCL = "opencl"

class TTS:
    def __init__(
            self, speaker: str = SPEAKER_BAYA, 
            device: str        = DEVICE_CPU, 
            samplerate: int    = 48_000
        ):
        
        # подгружаем модель 
        self.__MODEL__, _ = torch.hub.load(
            repo_or_dir="snakers4/silero-models",
            model="silero_tts",
            language="ru",
            speaker="ru_v3"
        )
        self.__MODEL__.to(torch.device(device))

        self.__SPEAKER__ = speaker
        self.__SAMPLERATE__ = samplerate
    
    def text2speech(self, text: str):
        # генерируем аудио из текста
        audio = self.__MODEL__.apply_tts(
            text=text,               
            speaker=self.__SPEAKER__,
            sample_rate=self.__SAMPLERATE__, 
            put_accent=True,
            put_yo=True
        )

        # проигрываем то что получилось
        sd.play(audio, samplerate=self.__SAMPLERATE__)
        time.sleep((len(audio)/self.__SAMPLERATE__))
        sd.stop()

При инициализации подгружается нейронная модель, если её нет на компьютере, то она будет скачана, иначе она будет взята из кэша загрузок.
Сама же нейронная модель работает на torch, поэтому можно будет запускать её не на процессоре а на видеокарте, для лучшей скорости работы. Правда мой ноутбук с RTX 3050 Ti mobile хоть и позволяет произвести запуск на видеокарте, всё равно на процессоре модель работает быстрее, поэтому поэкспериментируйте, чтобы найти наилучший способ.

Распознавание речи

Для распознавания речи будем использовать библиотеку vosk, сначала перейдём на официальный сайт с моделями распознавания и скачаем необходимую модель распознавания речи, обратите внимание что моделей две и та модель, которая больше весит, будет соответственно лучше распознавать речь, но и при этом будет медленнее подгружаться.
Создайте папку в вашей рабочей области и распакуйте скачанный архив в эту папку.

Далее создадим ещё один небольшой класс на основе документации:

import sounddevice as sd
import vosk
import sys
import queue
import json

class STT:
    def __init__(self, modelpath: str = "model", samplerate: int = 16000):
        self.__REC__ = vosk.KaldiRecognizer(vosk.Model(modelpath), samplerate)
        self.__Q__ = queue.Queue()
        self.__SAMPLERATE__ = samplerate

    
    def q_callback(self, indata, _, __, status):
        if status:
            print(status, file=sys.stderr)
        self.__Q__.put(bytes(indata))

    def listen(self, executor: callable):
        with sd.RawInputStream(
                samplerate=self.__SAMPLERATE__, 
                blocksize=8000, 
                device=1, 
                dtype='int16',
                channels=1, 
                callback=self.q_callback
            ):
            while True:
                data = self.__Q__.get()
                if self.__REC__.AcceptWaveform(data):
                    executor(json.loads(self.__REC__.Result())["text"])

При инициализации параметр modelpath это путь к вашей скачанной модели, а параметр executor в функции listen это функция, которая будет что-то делать с распознанным текстом.

Главный файл

На основе двух написанных классов создадим файл main.py со следующим кодом:

from fuzzywuzzy import fuzz

from tts import TTS
from stt import STT

commandsList = []

def equ(text, needed):
    return fuzz.ratio(text, needed) >= 70

def execute(text: str):
    print(f"> {text}")
    
    if equ(text, "расскажи анекдот"):
        text = "какой то анекдот!"
        tts.text2speech(text)
        print(f"- {text}")
    
    elif equ(text, "что ты умеешь"):
        text = "я умею всё, чему ты мен+я науч+ил!"
        tts.text2speech(text)
        print(f"- {text}")
    
    elif equ(text, "выключи"):
        text = "надеюсь, я не стану про+ектом, кот+орый ты забр+осишь!"
        tts.text2speech(text)
        print(f"- {text}")
        raise SystemExit

tts = TTS()
stt = STT(modelpath="model_small")

print("listen...")
stt.listen(execute)

В коде выше используется библиотека fuzzywuzzy, позволяющая нечётко сравнивать строки, данная библиотека необязательна, но лучше с ней чем без неё.

Также стоит отметить, что н+екоторые фр+азы для озв+учки я п+ишу вот так, плюсы в тексте позволяют указать ударение в слове

Мини-заключение

На этом костяк нашего проекта завершён, осталось лишь добавить необходимый функционал и всё готово!

Так же хотелось бы добавить, что я не считаю себя идеальным супер программистом, поэтому я жду жёсткой критики, ведь именно работа над ошибками позволяет программисту быть программистом.

Комментарии (10)


  1. CyberexTech
    05.12.2024 10:50

    Что-то подобное делал ранее, только в качестве самостоятельного устройства. Для синтеза речи лучше использовать модель v4_ru.pt, она быстрее.


    1. PlayingPlate6667 Автор
      05.12.2024 10:50

      Возможно, но модель v4 у меня почему-то выдаёт какой-то электронный голос.


  1. Jury_78
    05.12.2024 10:50

    При инициализации подгружается нейронная модель

    Если воспользоваться RHVoice то модель не нужна.


    1. Advisory
      05.12.2024 10:50

      RHVoice

      Документация предлагает прослушать примеры голосов на странице https://data2data.ru/tts/ , однако страница недоступна. Не знаете где еще можно ознакомиться с примерами?


      1. Jury_78
        05.12.2024 10:50

        У меня доступна:

        p.s. Еще тут есть >>>


        1. Advisory
          05.12.2024 10:50

          Теперь и у меня доступна. А до этого в разных браузерах происходил редирект на разные сайты.

          Очень не удобно прослушивание сделано — нужно скачивать mp3-файлы. Но по сути, один и тот же голос, в котором меняют скорость и тембр. В остальном никаких отличий. Не помню как называется, но, кажется в 2003 году в Говорилке его использовали.


  1. vkrasikov
    05.12.2024 10:50

    Молодец!

    В коде выше используется библиотека fuzzywuzzy, позволяющая нечётко сравнивать строки, данная библиотека необязательна, но лучше с ней чем без неё.

    Можешь ещё попробовать поработать с языковыми моделями, это щас модно :) Например, сравнивать строки, задавая вопрос гигачату:

    from gigachat import GigaChat
    
    GIGACHAT_TOKEN = "MTQ....................................1NA=="
    
    s1 = "Что ты умеешь?"
    s2 = "Расскажи, что ты можешь делать?"
    
    with GigaChat(credentials=GIGACHAT_TOKEN, verify_ssl_certs=False) as giga:
        response = giga.chat(
            "На сколько процентов похожи две следующие строки? Сравни по смыслу:\n"
            f"1) {s1}\n"
            f"2) {s2}\n"
            "В ответе напиши только одно число.")
        print(response.choices[0].message.content)


    1. PlayingPlate6667 Автор
      05.12.2024 10:50

      Спасибо за совет!
      решение интересное, но сам ассистент ориентирован на оффлайн работу, поэтому я использовал именно то, что использовал.


  1. diverdm
    05.12.2024 10:50

    п+ишу

    ??


  1. BosonBeard
    05.12.2024 10:50

    Спасибо за статью!