Заставим Python слушать — часть 1.

Здравствуйте, друзья-человеки. В этой серии статей мы собираемся раскрыть таинственный мир систем распознавания речи и использовать сервисы Deepgram в этом контексте. Многих может заинтересовать эта тема на том основании, что многие голосовые помощники, такие как Amazon’s Alexa, Google’s Assistant, Apple’s Siri, соревнуются за право быстро стать доминирующей умной колонкой, используя различные типы глубоких нейронных сетей (сети с прямой и обратной связью). Глубокие нейронные сети были представлены в 2006 году [0] самим крестным отцом: Джеффри Хинтон [1]

👉 Оглавление (TOC).

  • Что такое речь?
  • Что такое распознавание речи?
  • История распознавания речи
  • Что такое Deepgram?
  • Уникальные особенности Deepgram
  • Распознавание речи с живого микрофона
    • Установите deepgram-sdk, pyaudio
    • Устройства ввода и вывода
      • Аудиозапись и волновые файлы
      • Эксперименты
      • Собираем все вместе
        • Функциональное программирование
        • Объектно-ориентированный подход
  • Deepgram python sdk
  • Соединение Pyaudio и Deepgram
  • Обработка исключений
  • Обертывание
  • Ссылка

Что такое речь?

🔝 Перейти к TOC.

Человеческий голос — это физическое явление, которое мы не можем увидеть. Форма задней стенки глотки и ее вибрация используются для создания звука речи [2]. Когда микрофон улавливает звуки, он преобразует их в электрический сигнал, который может быть передан по проводному или беспроводному соединению в программное обеспечение на компьютере, динамики или устройство распознавания голоса. Мозг инициирует речь, приводя в действие мышцы рта для производства звука [3]. Например, когда человек произносит слово «Привет», он артикулирует его губами и языком, в то время как его голосовые связки вибрируют и воздух проходит между ними.

Что такое распознавание речи?

🔝 Go To TOC.

Распознавание речи — это процесс преобразования произнесенных слов в текст. В некоторых случаях он может использоваться в сочетании с другими технологиями для обеспечения компьютерного ввода или замены клавиатуры и мыши. Эта технология существует с 1950-х годов, но за последние годы она значительно усовершенствовалась. Распознавание речи использует методы DSP (цифровой обработки сигналов) для обработки и анализа аудиосигналов [4].

Распознавание речи часто используется как отдельное приложение или часть большого программного пакета, включающего другие функции, такие как диктовка. Оно позволяет пользователю управлять компьютером или другим устройством с помощью речи. Она также известна под различными терминами, такими как распознавание голоса, преобразование голоса в текст, преобразование речи в текст или распознавание речи,

Кратко ознакомившись с распознаванием речи, теперь давайте взглянем на захватывающую историю распознавания речи, которая, как ни удивительно, насчитывает около 72 лет, начиная с 1950-х годов, как упоминалось выше.

История распознавания речи

🔝 Go To TOC.

В первом десятилетии пятидесятого века ученые компании Bell System создали машину Audrey (Automatic Digit Recognizer), которая состоит из трех основных компонентов:

  • Микрофон, улавливающий человеческую речь.
  • Аппаратное обеспечение, запрограммированное на выполнение транскрипции.
  • Дисплей, на котором отображается число, произносимое в микрофон (правая сторона изображения).

Как следует из названия, эта машина может распознавать цифры (0-9).

В 1962 году IBM выпустила первое устройство под названием Shoebox [7] для распознавания устных слов; оно может распознавать десять цифр и шесть арифметических слов (например, плюс, минус и т.д.). Например, если кто-то скажет через микрофон 2 плюс 2, Shoebox запустит функцию сложения для вычисления и отображения результата.

В те времена эти технологии работали путем преобразования голосовых сигналов в электрические импульсы, а затем каждое слово разбивалось на небольшие фонетические единицы. Например, слово «привет» делилось на «привет», «хе л о» или что-то в этом роде.

В 1970-х годах Министерство обороны США оказало финансовую поддержку исследованиям. DARPA (Defense Advanced Research Projects Agency, то самое агентство, которое якобы было разоблачено за содействие биологическим экспериментам, связанным с s@rs-c0v2 [8]. Черт, чувак. Все эти теории заговора все время были правдой.) финансировала один из самых значительных проектов по распознаванию речи. В результате было распознано более тысячи слов.

В 1982 году синтезатор SAM [9] стал первой коммерческой программой синтеза речи, озвучивающей компьютер Commodore 64 1982 года.

Значительная веха была достигнута в конце 1980-х годов, когда были представлены модели, основанные на статистике (например, Hidden Markov Model.), которые могут распознавать около пяти тысяч слов.

Она работает путем присвоения каждой букве узла с вероятностью предсказания следующей буквы в слове, которое представляет собой край. Как видно из приведенного ниже примера, слово «картофель» может произноситься по-разному, например, «p oh t ah t oh», «p ah t ay t oh» и др.

Недостатком этих алгоритмов является то, что они распознают только дискретную речь, поэтому вы не можете говорить естественно; вам нужно делать паузы между словами, что вызывает сожаление.

В 1990-х годах первый коммерческий продукт стал доступен широким массам, когда компания Dragon выпустила свой продукт под названием Dragon Dictate, способный распознавать около 60 тыс. слов.

В 2000-х годах компания Google выпустила приложение голосового поиска для iPhone [12]. Приложение обрабатывает голосовые запросы на основе облачного центра данных Google, сопоставляя их с большим пулом записей человеческой речи и обучаясь на основе запросов, собранных от пользователей (230 миллиардов слов), обученных нейронными сетями, которые появились в 2006 году, как упоминалось в начале статьи.

Думаю, на сегодня достаточно истории, которая, предположительно, будет продолжена в будущих статьях о распознавании речи. Теперь давайте перейдем к следующему разделу, посвященному изучению сервисов транскрипции Deepgram.

Что такое Deepgram?

🔝 Go To TOC.

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

Очевидно, что Deepgram имеет множество вариантов использования. Например, его можно использовать как сервис транскрипции встреч и телефонных звонков, как сервис преобразования речи в текст для видео или как автоматический транскрипт для аудиофайлов. Подробную информацию можно найти на сайте компании [13].

Уникальные возможности Deepgram

🔝 Go To TOC.

Было показано [15], что Deepgram обеспечивает значительно более высокие показатели точности (90%+), чем другие системы перевода. Кроме того, он обеспечивает гораздо более высокую скорость транскрибирования, чем другие системы (3 секунды на транскрибирование часовых записей), и более низкую стоимость (0,78$/час), что делает его привлекательным вариантом для предприятий, которым необходимо регулярно транскрибировать большие объемы контента.

На момент написания статьи этот сервис поддерживает большинство языков с большим разнообразием акцентов и диалектов, он может распознавать и транскрибировать аудио на 16 языках [16].

Самое приятное в Deepgram то, что он предлагает бесплатную пробную версию, которой может воспользоваться любой желающий. Более того, Deepgram предоставляет SDK с открытым исходным кодом и бесплатные инструменты распознавания речи, которые могут быть интегрированы в любое приложение или систему.

С помощью Deepgram нам не придется изобретать колесо и строить модель машинного обучения снизу вверх (это был бы фантастический проект, над которым мы могли бы поработать в будущем). Вместо этого мы будем использовать Python SDK, который позволяет нам взаимодействовать с различными конечными точками API Deepgram, использующими современную модель машинного обучения для транскрипции речи.

По сути, услуги транскрипции Deepgram просты в использовании, точны и быстры. Они помогут вам сэкономить время, деньги и ресурсы, обеспечивая при этом высокое качество контента.

Теперь перейдем к техническим аспектам.

Распознавание речи с живого микрофона

🔝 Go To TOC.

В этом разделе мы узнаем, как преобразовать речь в реальном времени в человекочитаемый текст. Для этого мы будем использовать deepgram-SDK вместе с пакетом PyAudio.

Установите deepgram-sdk, pyaudio

🔝 Go To TOC.

В Python есть удобный встроенный модуль wave, но он не поддерживает запись, только обработку аудиофайлов на лету. Для записи аудиоданных мы можем воспользоваться сторонним пакетом PyAudio. Официальный сайт является хорошей отправной точкой в том, как установить и использовать эту библиотеку на различных платформах.

Однако PyAudio зависит от другой библиотеки под названием portaudio, которая не входит в стандартные зависимости Linux. Чтобы установить ее на вашу машину, вам нужно выполнить следующую команду в терминале:

$ sudo apt-get install portaudio19-dev
Войти в полноэкранный режим Выйти из полноэкранного режима

Если приведенная выше команда выполнена успешно, вы можете загрузить и установить pyaudio на свою систему. Однако, поскольку ранее мы использовали поэлементно вместо pip для управления зависимостями, мы можем выполнить следующую команду для импорта PyAudio в наш проект:

$ poetry add pyaudio
Войти в полноэкранный режим Выйти из полноэкранного режима

Если установка прошла успешно, вы можете посмотреть версию portaudio, выполнив команду:

$ python3 -c 'import pyaudio as p; print(p.get_portaudio_version())'
1246720
Enter fullscreen mode Выйти из полноэкранного режима

Чтобы установить deepgram на свою машину, вы можете следовать их GitHub-репо. Аналогично, чтобы импортировать deepgram в наш проект с поэзией, просто выполните:

$ poetry add deepgram-sdk
Войти в полноэкранный режим Выйти из полноэкранного режима

Если установка прошла успешно, вы можете посмотреть версию deepgram, выполнив:

$ python3 -c 'import deepgram; print(deepgram._version.__version__)'
0.2.5
Enter fullscreen mode Выйти из полноэкранного режима

Теперь пришло время поиграть с этими модулями. Для этого убедитесь, что ваш микрофон включен по умолчанию и не отключен.

Устройства ввода и вывода

🔝 Go To TOC.

Теперь давайте откроем REPL и проверим все на практике.

Мы начнем с импорта модуля pyaudio и последующего инстанцирования класса PyAudio.

>>> import pyaudio
>>> py_audio = pyaudio.PyAudio()
Вход в полноэкранный режим Выход из полноэкранного режима

Если вы работаете в linux, вы можете столкнуться со следующими предупреждениями:

ALSA lib pcm_dmix.c:1089:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2642:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2642:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2642:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm_route.c:869:(find_matching_chmap) Found no matching channel map
ALSA lib pcm_oss.c:377:(_snd_pcm_oss_open) Unknown field port
ALSA lib pcm_oss.c:377:(_snd_pcm_oss_open) Unknown field port
ALSA lib pcm_usb_stream.c:486:(_snd_pcm_usb_stream_open) Invalid type for card
ALSA lib pcm_usb_stream.c:486:(_snd_pcm_usb_stream_open) Invalid type for card
ALSA lib pcm_dmix.c:1089:(snd_pcm_dmix_open) unable to open slave
Вход в полноэкранный режим Выход из полноэкранного режима

Давайте пока проигнорируем эти предупреждения.

py_audio имеет множество ценных атрибутов, которые можно использовать для получения информации о ваших устройствах ввода и вывода.

>>> for attr in dir(py_audio):
...   if not attr.startswith("_"):
...     print(attr)
... 
close
get_default_host_api_info
get_default_input_device_info
get_default_output_device_info
get_device_count
get_device_info_by_host_api_device_index
get_device_info_by_index
get_format_from_width
get_host_api_count
get_host_api_info_by_index
get_host_api_info_by_type
get_sample_size
is_format_supported
open
terminate
Вход в полноэкранный режим Выйти из полноэкранного режима

Например, чтобы получить информацию об устройстве ввода по умолчанию, вы можете вызвать следующий метод:

>>> py_audio.get_default_input_device_info()
{
    'index': 9,
    'structVersion': 2,
    'name': 'default',
    'hostApi': 0,
    'maxInputChannels': 32,
    'maxOutputChannels': 32,
    'defaultLowInputLatency': 0.008684807256235827,
    'defaultLowOutputLatency': 0.008684807256235827,
    'defaultHighInputLatency': 0.034807256235827665,
    'defaultHighOutputLatency': 0.034807256235827665,
    'defaultSampleRate': 44100.0
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Помните о значении ключа defaultSampleRate. Мы будем использовать ее при записи звука с микрофона.

Аналогично, чтобы получить информацию об устройстве ввода по умолчанию, вы можете вызвать следующий метод:

>>> py_audio.get_default_output_device_info()
{
    'index': 9,
    'structVersion': 2,
    'name': 'default',
    'hostApi': 0,
    'maxInputChannels': 32,
    'maxOutputChannels': 32,
    'defaultLowInputLatency': 0.008684807256235827,
    'defaultLowOutputLatency': 0.008684807256235827,
    'defaultHighInputLatency': 0.034807256235827665,
    'defaultHighOutputLatency': 0.034807256235827665,
    'defaultSampleRate': 44100.0
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Если вы хотите получить подробную информацию о каждом устройстве ввода/вывода на вашей машине, вы можете выполнить следующий код:

>>> for index in range(py_audio.get_device_count()):
...   device_info = py_audio.get_device_info_by_index(index)
...   for key, value in device_info.items():
...     print(key, value, sep=": ")
Войти в полноэкранный режим Выйти из полноэкранного режима

Аудиозапись и волновые файлы

🔝 Go To TOC.

Эксперименты

Для записи аудиоданных с микрофона необходимо вызвать метод open:

>>> from rich import inspect
>>> inspect(py_audio.open)
╭─ <bound method PyAudio.open of <pyaudio.PyAudio object at 0x7f6c8bed5180>> ─╮
│ def PyAudio.open(*args, **kwargs):                                          │
│                                                                             │
│ Open a new stream. See constructor for                                      │
│ :py:func:`Stream.__init__` for parameter details.                           │
│                                                                             │
│ 27 attribute(s) not shown. Run inspect(inspect) for options.                │
╰─────────────────────────────────────────────────────────────────────────────╯
Войти в полноэкранный режим Выйти из полноэкранного режима

Мы собираемся использовать rich для правильного отображения сообщений. Теперь создадим объект stream для записи:

>>> # open stream object as input & output
>>> audio_stream = py_audio.open(
    rate=44100,             # frames per second, 
    channels=1,             # mono, change to 2 if you want stereo
    format=pyaudio.paInt16, # sample format, 8 bytes. see inspect
    input=True,             # input device flag
    output=False,            # output device flag, if True, you can play back the audio. 
    frames_per_buffer=1024  # 1024 samples per frame
)
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь вы можете посмотреть на доступные атрибуты для этого объекта потока.

>>> for attr in dir(audio_stream):
...   if not attr.startswith("_"):
...     print(attr)
... 
close
get_cpu_load
get_input_latency
get_output_latency
get_read_available
get_time
get_write_available
is_active
is_stopped
read
start_stream
stop_stream
write
Войти в полноэкранный режим Выйти из полноэкранного режима

Функции read и write являются наиболее полезными функциями для этого учебника. Мы можем вызвать функцию read для записи образцов звука в терминах кадров.

>>> inspect(audio_stream.read)
╭─ <bound method Stream.read of <pyaudio.Stream object at 0x7f8310a41180>> ─╮
│ def Stream.read(num_frames, exception_on_overflow=True):                  │
│                                                                           │
│ Read samples from the stream.  Do not call when using                     │
│ *non-blocking* mode.                                                      │
│                                                                           │
│ 27 attribute(s) not shown. Run inspect(inspect) for options.              │
╰───────────────────────────────────────────────────────────────────────────╯
Вход в полноэкранный режим Выйти из полноэкранного режима

Очевидно, что метод read принимает количество кадров вместо длительности. Поэтому нам нужно преобразовать длительность — заданный период времени для записи данных — в число кадров. Для этого нужно найти, сколько кадров содержится в данной duration. Для этого используется следующая формула:

num_frames = int(rate / samples_per_frame * duration)
Вход в полноэкранный режим Выход из полноэкранного режима

Мы можем убедиться в правильности приведенной выше формулы с помощью анализа размерности:

единица измерения для:

  • скорость: сэмплы/секунду
  • сэмплы_на_кадр: сэмплы/кадр
  • длительность: секунда

Значение в левой части уравнения num_frames должно иметь единицу измерения в кадрах, что и имеет место в нашей формуле, если вы выполните вычисления. Теперь мы можем перебрать все кадры и считать 1024 выборки за кадр. Функция int была использована для округления результата до ближайшего целого числа.

>>> frames = []
>>> for _ in range(int(44100 / 1024 * 3)):
...   data = audio_stream.read(1024)
...   frames.append(data)
... 
>>> len(frames)
129
Вход в полноэкранный режим Выход из полноэкранного режима

Каждый добавляемый кадр представляет собой поток байтов:

>>> type(frames[0])
<class 'bytes'>
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь давайте сохраним этот объект в wav-файл, чтобы убедиться, что это действительно запись длительностью 3 секунды. Для этого импортируем встроенный модуль wave:

>>> import wave
Вход в полноэкранный режим Выход из полноэкранного режима

Посмотрим, какие атрибуты доступны для этого объекта:

>>> for attr in dir(wave):
...   if not attr.startswith("_"):
...     print(attr)
... 
Chunk
Error
WAVE_FORMAT_PCM
Wave_read
Wave_write
audioop
builtins
namedtuple
open
struct
sys
Войти в полноэкранный режим Выход из полноэкранного режима

Как вы уже догадались, мы собираемся использовать функцию open для открытия файла в режиме записи.

>>> wave_file = wave.open("sound.wav", "wb")
Войти в полноэкранный режим Выход из полноэкранного режима

Аналогично, давайте посмотрим все атрибуты этого объекта:

>>> for attr in dir(wave_file):
...   if not attr.startswith("_"):
...     print(attr)
... 
close
getcompname
getcomptype
getframerate
getmark
getmarkers
getnchannels
getnframes
getparams
getsampwidth
initfp
setcomptype
setframerate
setmark
setnchannels
setnframes
setparams
setsampwidth
tell
writeframes
writeframesraw
Войти в полноэкранный режим Выйти из полноэкранного режима

Так как мы собираемся писать в файл, то мы должны использовать либо writeframes, либо writefranmesraw. Обратитесь к официальной документации. Вы поймете, что функция writeframes имеет больше логики, чем writeframesraw, потому что она проверяет наличие нескольких фреймов записи в файле. Поэтому в данном учебнике мы будем использовать эту функцию.

Но сначала нам нужно задать некоторые параметры для объекта wave_file:

>>> wave_file.setnchannels(2)
>>> wave_file.setsampwidth(py_audio.get_sample_size(pyaudio.paInt16))
>>> wave_file.setframerate(44100)
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь все готово, можно записывать поток данных в файл:

>>> wave_file.writeframes(b"".join(frames))
>>> wave_file.close()
Войти в полноэкранный режим Выход из полноэкранного режима

Поэкспериментировав с модулями wave и pyaudio, давайте соберем все вместе.

Собираем все вместе

🔝 Go To TOC.

Есть два подхода к компоновке предыдущего кода: функциональное программирование или объектно-ориентированное программирование.

Функциональное программирование
🔝 Перейти к ТОС.
import wave
from typing import List, Optional, TypeVar, Union, IO

import pyaudio #  type: ignore

WaveWrite = TypeVar("WaveWrite", bound=wave.Wave_write)


def init_recording(
    file_name: Union[str, IO[bytes]] = "sound.wav", mode: Optional[str] = "wb"
) -> WaveWrite:
    wave_file = wave.open(file_name, mode)
    wave_file.setnchannels(2)
    wave_file.setsampwidth(2)
    wave_file.setframerate(44100)
    return wave_file


def record(wave_file: WaveWrite, duration: Optional[int] = 3) -> None:
    py_audio = pyaudio.PyAudio()
    audio_stream = py_audio.open(
        rate=44100,  # frames per second,
        channels=2,  # stereo, change to 1 if you want mono
        format=8,  # sample format, 8 bytes. see inspect
        input=True,  # input device flag
        frames_per_buffer=1024,  # 1024 samples per frame
    )
    frames = []
    for _ in range(int(44100 / 1024 * 3)):
        data = audio_stream.read(1024)
        frames.append(data)
    wave_file.writeframes(b"".join(frames))
    audio_stream.close()


if __name__ == "__main__":
    wave_file = init_recording() #  type: ignore
    record(wave_file)
    wave_file.close()
Вход в полноэкранный режим Выйти из полноэкранного режима
Объектно-ориентированный подход
🔝 Перейти к ТОС.

Как описано в документах ниже, я предположил, что каждое поле класса AudioRecorder по умолчанию является приватным и доступно только через геттеры и сеттеры. В python использование геттеров и сеттеров не является обязательным, но мне нравится использовать этот подход, поскольку я раньше писал на статически типизированных языках, в основном на c# и Java.

Обратите внимание на использование магического метода __attrs_post_init__, который устанавливает атрибут wave_file в момент инстанцирования после вызова __init__. Я также использовал подсказку типов, как вы можете заметить. В python вы не обязаны делать все это, но все же это возможно. __init__ генерируется автоматически с помощью модуля atts (обратите внимание, что каждый атрибут имеет метод define).

Этот фрагмент кода был адаптирован из модуля audio_record проекта deepwordle.

import os
import wave
from os import PathLike
from typing import IO, List, Optional, TypeVar, Union

import pyaudio  # type: ignore
from attrs import define, field

WaveWrite = TypeVar("WaveWrite", bound=wave.Wave_write)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))


@define
class AudioRecorder:
    """
    A brief encapsulation of an audio recorder object attributes and methods.
    All fields are assumed to be private by default, and only accessible through
    getters/setters, but someone still could hack his/her way around it!
    Attrs:
        frames_per_buffer: An integer indicating the number of frames per buffer;
            1024 frames/buffer by default.
        audio_format: An integer that represents the number of bits per sample
            stored as 16-bit signed int.
        channels: An integer indicating how many channels a microphone has.
        rate: An integer indicating how many samples per second: frequency.
        py_audio: pyaudio instance.
        data_stream: stream object to get data from microphone.
        wave_file: wave class instance.
        mode: file object mode.
        file_name: file name to store audio data in it.
    """

    _frames_per_buffer: int = field(init=True, default=1024)
    _audio_format: int = field(init=True, default=pyaudio.paInt16)
    _channels: int = field(init=True, default=1)
    _rate: int = field(init=True, default=44100)
    _py_audio: pyaudio.PyAudio = field(init=False, default=pyaudio.PyAudio())
    _data_stream: IO[bytes] = field(init=False, default=None)
    _wave_file: wave.Wave_write = field(init=False, default=None)
    _mode: str = field(init=True, default="wb")
    _file_name: Union[str, PathLike[str]] = field(init=True, default="sound.wav")

    @property
    def frames_per_buffer(self) -> int:
        """
        A getter method that returns the value of the `frames_per_buffer` attribute.
        :param self: Instance of the class.
        :return: An integer that represents the value of the `frames_per_buffer` attribute.
        """
        if not hasattr(self, "_frames_per_buffer"):
            raise AttributeError(
                f"Your {self.__class__.__name__!r} instance has no attribute named frames_per_buffer."
            )
        return self._frames_per_buffer

    @frames_per_buffer.setter
    def frames_per_buffer(self, value: int) -> None:
        """
        A setter method that changes the value of the `frames_per_buffer` attribute.
        :param value: An integer that represents the value of the `frames_per_buffer` attribute.
        :return: NoReturn.
        """
        setattr(self, "_frames_per_buffer", value)

    @property
    def audio_format(self) -> int:
        """
        A getter method that returns the value of the `audio_format` attribute.
        :param self: Instance of the class.
        :return: A string that represents the value of the `audio_format` attribute.
        """
        if not hasattr(self, "_audio_format"):
            raise AttributeError(
                f"Your {self.__class__.__name__!r} instance has no attribute named audio_format."
            )
        return self._audio_format

    @audio_format.setter
    def audio_format(self, value: int) -> None:
        """
        A setter method that changes the value of the `audio_format` attribute.
        :param value: An integer that represents the value of the `audio_format` attribute.
        :return: NoReturn.
        """
        setattr(self, "_frames_per_buffer", value)

    @property
    def channels(self) -> int:
        """
        A getter method that returns the value of the `channels` attribute.
        :param self: Instance of the class.
        :return: An integer that represents the value of the `channels` attribute.
        """
        if not hasattr(self, "_channels"):
            raise AttributeError(
                f"Your {self.__class__.__name__!r} instance has no attribute named channels."
            )
        return self._channels

    @channels.setter
    def channels(self, value: int) -> None:
        """
        A setter method that changes the value of the `channels` attribute.
        :param value: An integer that represents the value of the `channels` attribute.
        :return: NoReturn.
        """
        setattr(self, "_channels", value)

    @property
    def rate(self) -> int:
        """
        A getter method that returns the value of the `rate`attribute.
        :param self: Instance of the class.
        :return: A string that represents the value of the `rate` attribute.
        """
        if not hasattr(self, "_rate"):
            raise AttributeError(
                f"Your {self.__class__.__name__!r} instance has no attribute named rate."
            )
        return self._rate

    @rate.setter
    def rate(self, value: int) -> None:
        """
        A setter method that changes the value of the `rate` attribute.
        :param value: An integer that represents the value of the `rate` attribute.
        :return: NoReturn.
        """
        setattr(self, "_rate", value)

    @property
    def py_audio(self) -> pyaudio.PyAudio:
        """
        A getter method that returns the value of the `py_audio`attribute.
        :param self: Instance of the class.
        :return: A PyAudio object that represents the value of the `py_audio` attribute.
        """
        if not hasattr(self, "_py_audio"):
            raise AttributeError(
                f"Your {self.__class__.__name__!r} instance has no attribute named py_audio."
            )
        return self._py_audio

    @py_audio.setter
    def py_audio(self, value: int) -> None:
        """
        A setter method that changes the value of the `py_audio` attribute.
        :param value: A PyAudio object that represents the value of the `py_audio` attribute.
        :return: NoReturn.
        """
        setattr(self, "_py_audio", value)

    @property
    def data_stream(self) -> IO[bytes]:
        """
        A getter method that returns the value of the `data_stream`attribute.
        :param self: Instance of the class.
        :return: A string that represents the value of the `data_stream` attribute.
        """
        if not hasattr(self, "_data_stream"):
            raise AttributeError(
                f"Your {self.__class__.__name__!r} instance has no attribute named data_stream."
            )
        return self._data_stream

    @data_stream.setter
    def data_stream(self, value: IO[bytes]) -> None:
        """
        A setter method that changes the value of the `data_stream` attribute.
        :param value: A string that represents the value of the `data_stream` attribute.
        :return: NoReturn.
        """
        setattr(self, "_data_stream", value)

    @property
    def wave_file(self) -> wave.Wave_write:
        """
        A getter method that returns the value of the `wave_file`attribute.
        :param self: Instance of the class.
        :return: A string that represents the value of the `wave_file` attribute.
        """
        if not hasattr(self, "_wave_file"):
            raise AttributeError(
                f"Your {self.__class__.__name__!r} instance has no attribute named wave_file."
            )
        return self._wave_file

    @wave_file.setter
    def wave_file(self, value: wave.Wave_write) -> None:
        """
        A setter method that changes the value of the `wave_file` attribute.
        :param value: A string that represents the value of the `wave_file` attribute.
        :return: NoReturn.
        """
        setattr(self, "_wave_file", value)

    @property
    def file_name(self) -> Union[str, PathLike[str]]:
        """
        A getter method that returns the value of the `file_name`attribute.
        :param self: Instance of the class.
        :return: A string that represents the value of the `file_name` attribute.
        """
        if not hasattr(self, "_mode"):
            raise AttributeError(
                f"Your {self.__class__.__name__!r} instance has no attribute named file_name."
            )
        return self._file_name

    @file_name.setter
    def file_name(self, value: Union[str, PathLike[str]]) -> None:
        """
        A setter method that changes the value of the `file_name` attribute.
        :param value: A string that represents the value of the `file_name` attribute.
        :return: NoReturn.
        """
        setattr(self, "_file_name", value)

    @property
    def mode(self) -> str:
        """
        A getter method that returns the value of the `mode`attribute.
        :param self: Instance of the class.
        :return: A string that represents the value of the `mode` attribute.
        """
        if not hasattr(self, "_mode"):
            raise AttributeError(
                f"Your {self.__class__.__name__!r} instance has no attribute named mode."
            )
        return self._mode

    @mode.setter
    def mode(self, value: str) -> None:
        """
        A setter method that changes the value of the `mode` attribute.
        :param value: A string that represents the value of the `mode` attribute.
        :return: NoReturn.
        """
        setattr(self, "_mode", value)

    def __repr__(self) -> str:
        attrs: dict = {
            "frames_per_buffer": self.frames_per_buffer,
            "audio_format": self.audio_format,
            "channels": self.channels,
            "rate": self.rate,
            "py_audio": repr(self.py_audio),
            "data_stream": self.data_stream,
            "wave_file": repr(self.wave_file),
            "mode": self.mode,
            "file_name": self.file_name,
        }
        return f"{self.__class__.__name__}({attrs})"

    def __attrs_post_init__(self) -> None:
        wave_file = wave.open(os.path.join(BASE_DIR, self.file_name), self.mode)
        wave_file.setnchannels(self.channels)
        wave_file.setsampwidth(self.py_audio.get_sample_size(self.audio_format))
        wave_file.setframerate(self.rate)
        self.wave_file = wave_file
        del wave_file

    def record(self, duration: int = 3) -> None:
        self.data_stream = self.py_audio.open(
            format=self.audio_format,
            channels=self.channels,
            rate=self.rate,
            input=True,
            output=True,
            frames_per_buffer=self.frames_per_buffer,
        )
        frames: List[bytes] = []
        num_frames: int = int(self.rate / self.frames_per_buffer * duration)
        for _ in range(num_frames):
            data = self.data_stream.read(self.frames_per_buffer)
            frames.append(data)
        self.wave_file.writeframes(b"".join(frames))

    def stop_recording(self) -> None:
        if self.data_stream:
            self.data_stream.close()
            self.py_audio.terminate()
            self.wave_file.close()


if __name__ == "__main__":
    rec = AudioRecorder()
    print(rec)
    rec.record()
    rec.stop_recording()

Вход в полноэкранный режим Выход из полноэкранного режима

Deepgram python sdk.

🔝 Go To TOC.

Давайте вернемся в наш REPL и начнем играть с deepgram SDK.

Мы начнем с импорта модуля deepgram, а затем создадим экземпляр Deepgram.

>>> from deepgram import Deepgram
>>> for attr in dir(Deepgram):
...   if not attr.startswith("_"):
...     print(attr)
... 
keys
projects
transcription
usage
Вход в полноэкранный режим Выход из полноэкранного режима

Как видите, в классе Deepgram есть четыре основных атрибута. Используя deepgram, вы можете расшифровывать предварительно записанные аудиозаписи или прямые аудиопотоки, как на радио bbc. Вы можете следовать файлу Readme, чтобы получить информацию о настройке учетной записи deepgram и начать работу. Получив секретный ключ, вы сможете взаимодействовать с API для выполнения транскрипции. Получив ключ API, необходимо сохранить его в переменной окружения, чтобы успешно запустить следующий код:

$ export DEEPGRAM_API_KEY="XXXXXXXXX"
Войти в полноэкранный режим Выход из полноэкранного режима
from deepgram import Deepgram # type:  ignore
import asyncio
import os
from os import PathLike
from typing import Union, IO

async def transcribe(file_name: Union[Union[str, bytes, PathLike[str], PathLike[bytes]], int]):
    with open(file_name, "rb") as audio:
        source = {"buffer": audio, "mimetype": "audio/wav"}
        response = await deepgram.transcription.prerecorded(source)
        return response["results"]["channels"][0]["alternatives"][0]["words"]

if __name__ == "__main__":
    try:
        deepgram = Deepgram(os.environ.get("DEEPGRAM_API_KEY"))
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        words = loop.run_until_complete(transcribe("sound.wav"))
        string_words = " ".join(word_dict.get("word") for word_dict in words if "word" in word_dict)
        print(f"You said: {string_words}!")
        loop.close()

    except AttributeError:
        print("Please provide a valid `DEEPGRAM_API_KEY`.")
Войти в полноэкранный режим Выйти из полноэкранного режима

Если аудиофайл содержит только слова «hello» и «world», то приведенный выше скрипт выдаст следующее:

You said: hello world!
Войти в полноэкранный режим Выйти из полноэкранного режима

Подключение Pyaudio и Deepgram

🔝 Go To TOC.
import wave
from typing import List, Optional, TypeVar, Union, IO
import pyaudio #  type: ignore
from deepgram import Deepgram # type:  ignore
import asyncio
import os
from os import PathLike


WaveWrite = TypeVar("WaveWrite", bound=wave.Wave_write)


def init_recording(
    file_name: Union[str, IO[bytes]] = "sound.wav", mode: Optional[str] = "wb"
) -> WaveWrite:
    wave_file = wave.open(file_name, mode)
    wave_file.setnchannels(2)
    wave_file.setsampwidth(2)
    wave_file.setframerate(44100)
    return wave_file

def record(wave_file: WaveWrite, duration: Optional[int] = 3) -> None:
    py_audio = pyaudio.PyAudio()
    audio_stream = py_audio.open(
        rate=44100,  # frames per second,
        channels=2,  # stereo, change to 1 if you want mono
        format=8,  # sample format, 8 bytes. see inspect
        input=True,  # input device flag
        frames_per_buffer=1024,  # 1024 samples per frame
    )
    frames = []
    for _ in range(int(44100 / 1024 * 3)):
        data = audio_stream.read(1024)
        frames.append(data)
    wave_file.writeframes(b"".join(frames))
    audio_stream.close()

async def transcribe(file_name: Union[Union[str, bytes, PathLike[str], PathLike[bytes]], int]):
    with open(file_name, "rb") as audio:
        source = {"buffer": audio, "mimetype": "audio/wav"}
        response = await deepgram.transcription.prerecorded(source)
        return response["results"]["channels"][0]["alternatives"][0]["words"]

if __name__ == "__main__":
    # start recording
    print("Python is listening...")
    wave_file = init_recording() #  type: ignore
    record(wave_file)
    wave_file.close()
    # start transcribing
    deepgram = Deepgram(os.environ.get("DEEPGRAM_API_KEY"))
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    words = loop.run_until_complete(transcribe("sound.wav"))
    string_words = " ".join(word_dict.get("word") for word_dict in words if "word" in word_dict)
    print(f"You said: {string_words}!")
    loop.close()
Войти в полноэкранный режим Выход из полноэкранного режима

Обработка исключений

🔝 Go To TOC.

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

    try:
        # start recording
        print("Python is listening...")
        wave_file = init_recording() #  type: ignore
        record(wave_file)
        wave_file.close()
        # start transcribing
        deepgram = Deepgram(os.environ.get("DEEPGRAM_API_KEY"))
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        words = loop.run_until_complete(transcribe("sound.wav"))
        string_words = " ".join(word_dict.get("word") for word_dict in words if "word" in word_dict)
        print(f"You said: {string_words}!")
        loop.close()
    except Exception:
        print("Unauthorized user. Please provide a valid `DEEPGRAM_API_KEY` value")
Вход в полноэкранный режим Выход из полноэкранного режима

Мы можем построить цикл для бесконечной записи речи до тех пор, пока не будет выполнено условие.

    try:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        while True:
            wave_file = init_recording() #  type: ignore
            print("Python is listening...")
            record(wave_file)
            wave_file.close()
            # start transcribing
            deepgram = Deepgram(os.environ.get("DEEPGRAM_API_KEY"))
            words = loop.run_until_complete(transcribe("sound.wav"))
            string_words = " ".join(word_dict.get("word") for word_dict in words if "word" in word_dict)
            print(f"You said: {string_words}!")
            if string_words == "stop":
              print('Goodbye!')
              break
        loop.close()
    except Exception:
        print("Unauthorized user. Please provide a valid `DEEPGRAM_API_KEY` value")
Вход в полноэкранный режим Выход из полноэкранного режима

Операции ввода-вывода ограничивают производительность этой программы. Мы улучшим эту версию в следующих статьях, связанных с распознаванием речи.

Подведение итогов

🔝 Перейти к TOC.

В этой статье мы рассмотрели историю распознавания речи, узнали, как использовать deepgram python SDK для распознавания речи и pyaudio для записи звука. С помощью этих библиотек можно сделать гораздо больше, что выходит за рамки данной статьи. Помните, что мы можем улучшить наш проект, чтобы напрямую передавать аудиозаписи с микрофона без записи в волновой файл с помощью веб-сокетов, что является работой будущих статей. На основе этого мы также можем построить поисковую систему с голосовым управлением. Я хочу предложить поиграть с модулем webbrowser, чтобы найти еще больше интересных идей реализации. Мы будем работать над подобными проектами на протяжении следующих статей этого цикла.

Как всегда, эта статья — подарок для вас, и вы можете поделиться ею с кем угодно или использовать ее любым способом, который будет полезен для вашего личного и профессионального развития. Заранее благодарю вас за поддержку!

Счастливого кодинга, друзья; увидимся в следующей статье.

Ссылка

🔝 Go To TOC.

[0] Википедия. Глубокое обучение.

[1] Википедия. Джеффри Хинтон.

[2] William F. Katz, 2016. What Produces Speech: Анатомия вашей речи, Phonetics For Dummies.

[3] Jacquelyn Cafasso, 2019. Какая часть мозга управляет речью?, healthline.

[4] Steven W. Smith, in Digital Signal Processing: A Practical Guide for Engineers and Scientists, 2003.

[5] Сэм Лоусон, 2018, Bell-Laboratories-invented-Audrey, ClickZ.

[6] Pioneering Speech Recognition, IBM.

[7] IBM Cloud Education, 2020, Что такое распознавание речи.

[8] Project Veritas, 2022, Военные документы об усилении функции противоречат показаниям Фаучи под присягой, Youtube.

[9] Себастьян Макке, Программный автоматический рот — крошечный синтезатор речи, Github.

[10] Dimitrakakis, Christos & Bengio, Samy. (2011). Ансамбли на уровне фонем и предложений для распознавания речи EURASIP J. Audio, Speech and Music Processing. 2011. 10.1155/2011/426792.

[11] Эд Грабиановски, Как работает распознавание речи.

[12] Новости Google, 2008, Новая версия мобильного приложения Google для iPhone, теперь с голосовым поиском.

[13] Deepgram, Различные среды требуют различных моделей распознавания речи.

[14] Deepgram, Высокая точность для лучшего анализа речи.

[15] Deepgram, ПОЧЕМУ DEEPGRAM: Корпоративная аудиосистема сложна, а ASR не обязательно должна быть сложной.

[16] Deepgram, Каждый клиент. Услышанный и понятый).

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *