wyoming-microsoft-stt 1.3.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: wyoming-microsoft-stt
3
+ Version: 1.3.3
4
+ Summary: Add your description here
5
+ Home-page: https://github.com/hugobloem/wyoming-microsoft-stt
6
+ Author: Hugo Bloem
7
+ Author-email:
8
+ Requires-Python: >=3.13
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: azure-cognitiveservices-speech>=1.45.0
11
+ Requires-Dist: pydantic>=2.11.7
12
+ Requires-Dist: wyoming>=1.7.2
13
+ Dynamic: author
14
+ Dynamic: home-page
15
+
16
+ # Wyoming Microsoft STT
17
+ Wyoming protocol server for Microsoft Azure speech-to-text.
18
+
19
+ This Python package provides a Wyoming integration for Microsoft Azure speech-to-text and can be directly used with [Home Assistant](https://www.home-assistant.io/) voice and [Rhasspy](https://github.com/rhasspy/rhasspy3).
20
+
21
+ ## Azure Speech Service
22
+ This program uses [Microsoft Azure Speech Service](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/). You can sign up to a free Azure account which comes with free tier of 5 audio hours per month, this should be enough for running a voice assistant as each command is relatively short. Once this amount is exceeded Azure could charge you for each second used (Current pricing is $0.36 per audio hour). I am not responsible for any incurred charges and recommend you set up a spending limit to reduce your exposure. However, for normal usage the free tier could suffice and the resource should not switch to a paid service automatically.
23
+
24
+ If you have not set up a speech resource, you can follow the instructions below. (you only need to do this once and works both for [Speech-to-Text](https://github.com/hugobloem/wyoming-microsoft-stt) and [Text-to-Speech](https://github.com/hugobloem/wyoming-microsoft-tts))
25
+
26
+ 1. Sign in or create an account on [portal.azure.com](https://portal.azure.com).
27
+ 2. Create a subscription by searching for `subscription` in the search bar. [Consult Microsoft Learn for more information](https://learn.microsoft.com/en-gb/azure/cost-management-billing/manage/create-subscription#create-a-subscription-in-the-azure-portal).
28
+ 3. Create a speech resource by searching for `speech service`.
29
+ 4. Select the subscription you created, pick or create a resource group, select a region, pick an identifiable name, and select the pricing tier (you probably want Free F0)
30
+ 5. Once created, copy one of the keys from the speech service page. You will need this to run this program.
31
+
32
+ ## Usage
33
+ Depending on the installation method parameters are parsed differently. However, the same options are used for each of the installation methods and can be found in the table below. Your service region and subscription key can be found on the speech service resource page (step 5 the Azure Speech service instructions).
34
+
35
+ For the bare-metal Python install the program is run as follows:
36
+ ```python
37
+ python -m wyoming-microsoft-stt --<key> <value>
38
+ ```
39
+
40
+ | Key | Optional | Description |
41
+ |---|---|---|
42
+ | `service-region` | No | Azure service region e.g., `uksouth` |
43
+ | `subscription-key` | No | Azure subscription key |
44
+ | `language` | Yes | Default language to set for transcription, default: `en-GB`. For auto-detection provide multiple languages. |
45
+ | `uri` | No | Uri where the server will be broadcasted e.g., `tcp://0.0.0.0:10300` |
46
+ | `download-dir` | Yes | Directory to download models into (default: ) |
47
+ | `update-languages` | Yes | Download latest languages.json during startup |
48
+ | `debug` | Yes | Log debug messages |
49
+
50
+ ## Multi-language support
51
+ This add-on can also auto-detect the spoken language from a list of pre-defined languages (max. 10). To do this in Home Assistant provide the languages separated by semi-colons like so:
52
+ <img width="689" alt="Screenshot 2025-05-04 at 11 59 55" src="https://github.com/user-attachments/assets/b3c54fe5-ebf3-404a-a8e8-b0d27efaf76d" />
53
+
54
+ > [!NOTE]
55
+ > Setting multiple languages will override the options set by Home Assistant's Voice configuration! It will prompt you to select a language but the option is ignored when speech is processed.
56
+
57
+
58
+ ## Installation
59
+ Depending on your use case there are different installation options.
60
+
61
+ - **Using pip**
62
+ Clone the repository and install the package using pip. Please note the platform requirements as noted [here](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/quickstarts/setup-platform?tabs=linux%2Cubuntu%2Cdotnetcli%2Cdotnet%2Cjre%2Cmaven%2Cnodejs%2Cmac%2Cpypi&pivots=programming-language-python#platform-requirements).
63
+ ```sh
64
+ pip install .
65
+ ```
66
+
67
+ - **Home Assistant Add-On**
68
+ Add the following repository as an add-on repository to your Home Assistant, or click the button below.
69
+ [https://github.com/hugobloem/homeassistant-addons](https://github.com/hugobloem/homeassistant-addons)
70
+
71
+ [![Open your Home Assistant instance and show the add add-on repository dialog with a specific repository URL pre-filled.](https://my.home-assistant.io/badges/supervisor_add_addon_repository.svg)](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Fhugobloem%2Fhomeassistant-addons)
72
+
73
+ - **Docker container**
74
+ To run as a Docker container use the following command:
75
+ ```bash
76
+ docker run ghcr.io/hugobloem/wyoming-microsoft-stt-noha:latest --<key> <value>
77
+ ```
78
+ For the relevant keys please look at [the table below](#usage)
79
+
80
+ - **docker compose**
81
+
82
+ Below is a sample for a docker compose file. The azure region + subscription key can be set in environment variables. Everything else needs to be passed via command line arguments.
83
+
84
+ ```yaml
85
+ wyoming-proxy-azure-stt:
86
+ image: ghcr.io/hugobloem/wyoming-microsoft-stt-noha
87
+ container_name: wyoming-azure-stt
88
+ ports:
89
+ - "10300:10300"
90
+ environment:
91
+ AZURE_SERVICE_REGION: swedencentral
92
+ AZURE_SUBSCRIPTION_KEY: XXX
93
+ command: --language=en-GB,nl-NL --uri=tcp://0.0.0.0:10300
94
+ ```
@@ -0,0 +1,79 @@
1
+ # Wyoming Microsoft STT
2
+ Wyoming protocol server for Microsoft Azure speech-to-text.
3
+
4
+ This Python package provides a Wyoming integration for Microsoft Azure speech-to-text and can be directly used with [Home Assistant](https://www.home-assistant.io/) voice and [Rhasspy](https://github.com/rhasspy/rhasspy3).
5
+
6
+ ## Azure Speech Service
7
+ This program uses [Microsoft Azure Speech Service](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/). You can sign up to a free Azure account which comes with free tier of 5 audio hours per month, this should be enough for running a voice assistant as each command is relatively short. Once this amount is exceeded Azure could charge you for each second used (Current pricing is $0.36 per audio hour). I am not responsible for any incurred charges and recommend you set up a spending limit to reduce your exposure. However, for normal usage the free tier could suffice and the resource should not switch to a paid service automatically.
8
+
9
+ If you have not set up a speech resource, you can follow the instructions below. (you only need to do this once and works both for [Speech-to-Text](https://github.com/hugobloem/wyoming-microsoft-stt) and [Text-to-Speech](https://github.com/hugobloem/wyoming-microsoft-tts))
10
+
11
+ 1. Sign in or create an account on [portal.azure.com](https://portal.azure.com).
12
+ 2. Create a subscription by searching for `subscription` in the search bar. [Consult Microsoft Learn for more information](https://learn.microsoft.com/en-gb/azure/cost-management-billing/manage/create-subscription#create-a-subscription-in-the-azure-portal).
13
+ 3. Create a speech resource by searching for `speech service`.
14
+ 4. Select the subscription you created, pick or create a resource group, select a region, pick an identifiable name, and select the pricing tier (you probably want Free F0)
15
+ 5. Once created, copy one of the keys from the speech service page. You will need this to run this program.
16
+
17
+ ## Usage
18
+ Depending on the installation method parameters are parsed differently. However, the same options are used for each of the installation methods and can be found in the table below. Your service region and subscription key can be found on the speech service resource page (step 5 the Azure Speech service instructions).
19
+
20
+ For the bare-metal Python install the program is run as follows:
21
+ ```python
22
+ python -m wyoming-microsoft-stt --<key> <value>
23
+ ```
24
+
25
+ | Key | Optional | Description |
26
+ |---|---|---|
27
+ | `service-region` | No | Azure service region e.g., `uksouth` |
28
+ | `subscription-key` | No | Azure subscription key |
29
+ | `language` | Yes | Default language to set for transcription, default: `en-GB`. For auto-detection provide multiple languages. |
30
+ | `uri` | No | Uri where the server will be broadcasted e.g., `tcp://0.0.0.0:10300` |
31
+ | `download-dir` | Yes | Directory to download models into (default: ) |
32
+ | `update-languages` | Yes | Download latest languages.json during startup |
33
+ | `debug` | Yes | Log debug messages |
34
+
35
+ ## Multi-language support
36
+ This add-on can also auto-detect the spoken language from a list of pre-defined languages (max. 10). To do this in Home Assistant provide the languages separated by semi-colons like so:
37
+ <img width="689" alt="Screenshot 2025-05-04 at 11 59 55" src="https://github.com/user-attachments/assets/b3c54fe5-ebf3-404a-a8e8-b0d27efaf76d" />
38
+
39
+ > [!NOTE]
40
+ > Setting multiple languages will override the options set by Home Assistant's Voice configuration! It will prompt you to select a language but the option is ignored when speech is processed.
41
+
42
+
43
+ ## Installation
44
+ Depending on your use case there are different installation options.
45
+
46
+ - **Using pip**
47
+ Clone the repository and install the package using pip. Please note the platform requirements as noted [here](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/quickstarts/setup-platform?tabs=linux%2Cubuntu%2Cdotnetcli%2Cdotnet%2Cjre%2Cmaven%2Cnodejs%2Cmac%2Cpypi&pivots=programming-language-python#platform-requirements).
48
+ ```sh
49
+ pip install .
50
+ ```
51
+
52
+ - **Home Assistant Add-On**
53
+ Add the following repository as an add-on repository to your Home Assistant, or click the button below.
54
+ [https://github.com/hugobloem/homeassistant-addons](https://github.com/hugobloem/homeassistant-addons)
55
+
56
+ [![Open your Home Assistant instance and show the add add-on repository dialog with a specific repository URL pre-filled.](https://my.home-assistant.io/badges/supervisor_add_addon_repository.svg)](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Fhugobloem%2Fhomeassistant-addons)
57
+
58
+ - **Docker container**
59
+ To run as a Docker container use the following command:
60
+ ```bash
61
+ docker run ghcr.io/hugobloem/wyoming-microsoft-stt-noha:latest --<key> <value>
62
+ ```
63
+ For the relevant keys please look at [the table below](#usage)
64
+
65
+ - **docker compose**
66
+
67
+ Below is a sample for a docker compose file. The azure region + subscription key can be set in environment variables. Everything else needs to be passed via command line arguments.
68
+
69
+ ```yaml
70
+ wyoming-proxy-azure-stt:
71
+ image: ghcr.io/hugobloem/wyoming-microsoft-stt-noha
72
+ container_name: wyoming-azure-stt
73
+ ports:
74
+ - "10300:10300"
75
+ environment:
76
+ AZURE_SERVICE_REGION: swedencentral
77
+ AZURE_SUBSCRIPTION_KEY: XXX
78
+ command: --language=en-GB,nl-NL --uri=tcp://0.0.0.0:10300
79
+ ```
@@ -0,0 +1,64 @@
1
+ [project]
2
+ name = "wyoming-microsoft-stt"
3
+ version = "1.3.3"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = [
8
+ "azure-cognitiveservices-speech>=1.45.0",
9
+ "pydantic>=2.11.7",
10
+ "wyoming>=1.7.2",
11
+ ]
12
+
13
+ [dependency-groups]
14
+ dev = [
15
+ "pytest>=8.4.1",
16
+ "pytest-asyncio>=1.1.0",
17
+ "ruff>=0.12.10",
18
+ ]
19
+
20
+ [tool.ruff]
21
+ lint.select = [
22
+ "B007", # Loop control variable {name} not used within loop body
23
+ "B014", # Exception handler with duplicate exception
24
+ "C", # complexity
25
+ "D", # docstrings
26
+ "E", # pycodestyle
27
+ "F", # pyflakes/autoflake
28
+ "ICN001", # import concentions; {name} should be imported as {asname}
29
+ "PGH004", # Use specific rule codes when using noqa
30
+ "PLC0414", # Useless import alias. Import alias does not rename original package.
31
+ "SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass
32
+ "SIM117", # Merge with-statements that use the same scope
33
+ "SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys()
34
+ "SIM201", # Use {left} != {right} instead of not {left} == {right}
35
+ "SIM212", # Use {a} if {a} else {b} instead of {b} if not {a} else {a}
36
+ "SIM300", # Yoda conditions. Use 'age == 42' instead of '42 == age'.
37
+ "SIM401", # Use get from dict with default instead of an if block
38
+ "T20", # flake8-print
39
+ "TRY004", # Prefer TypeError exception for invalid type
40
+ "RUF006", # Store a reference to the return value of asyncio.create_task
41
+ "UP", # pyupgrade
42
+ "W", # pycodestyle
43
+ ]
44
+
45
+ lint.ignore = [
46
+ "D202", # No blank lines allowed after function docstring
47
+ "D203", # 1 blank line required before class docstring
48
+ "D213", # Multi-line docstring summary should start at the second line
49
+ "D404", # First word of the docstring should not be This
50
+ "D406", # Section name should end with a newline
51
+ "D407", # Section name underlining
52
+ "D411", # Missing blank line before section
53
+ "E501", # line too long
54
+ "E731", # do not assign a lambda expression, use a def
55
+ ]
56
+
57
+ [lint.flake8-pytest-style]
58
+ fixture-parentheses = false
59
+
60
+ [lint.pyupgrade]
61
+ keep-runtime-typing = true
62
+
63
+ [lint.mccabe]
64
+ max-complexity = 25
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,44 @@
1
+ from pathlib import Path # noqa: D100
2
+
3
+ import setuptools
4
+ from setuptools import setup
5
+
6
+ this_dir = Path(__file__).parent
7
+ module_dir = this_dir / "wyoming_microsoft_stt"
8
+
9
+ requirements = []
10
+ requirements_path = this_dir / "requirements.txt"
11
+ if requirements_path.is_file():
12
+ with open(requirements_path, encoding="utf-8") as requirements_file:
13
+ requirements = requirements_file.read().splitlines()
14
+
15
+ data_files = [module_dir / "languages.json"]
16
+
17
+ # -----------------------------------------------------------------------------
18
+
19
+ setup(
20
+ name="wyoming_microsoft_stt",
21
+ version="1.3.3",
22
+ description="Wyoming Server for Microsoft STT",
23
+ url="https://github.com/hugobloem/wyoming-microsoft-stt",
24
+ author="Hugo Bloem",
25
+ author_email="",
26
+ license="MIT",
27
+ packages=setuptools.find_packages(),
28
+ package_data={
29
+ "wyoming_microsoft_stt": [str(p.relative_to(module_dir)) for p in data_files]
30
+ },
31
+ install_requires=requirements,
32
+ classifiers=[
33
+ "Development Status :: 3 - Alpha",
34
+ "Intended Audience :: Developers",
35
+ "Topic :: Text Processing :: Linguistic",
36
+ "License :: OSI Approved :: MIT License",
37
+ "Programming Language :: Python :: 3.7",
38
+ "Programming Language :: Python :: 3.8",
39
+ "Programming Language :: Python :: 3.9",
40
+ "Programming Language :: Python :: 3.10",
41
+ "Programming Language :: Python :: 3.11",
42
+ ],
43
+ keywords="rhasspy wyoming microsft stt",
44
+ )
@@ -0,0 +1 @@
1
+ """Tests."""
@@ -0,0 +1,15 @@
1
+ """Fixtures for tests."""
2
+
3
+ from wyoming_microsoft_stt import SpeechConfig
4
+ import pytest
5
+ import os
6
+
7
+
8
+ @pytest.fixture
9
+ def microsoft_stt_args():
10
+ """Return MicrosoftSTT instance."""
11
+ args = SpeechConfig(
12
+ subscription_key=os.environ.get("SPEECH_KEY"),
13
+ service_region=os.environ.get("SPEECH_REGION"),
14
+ )
15
+ return args
@@ -0,0 +1,19 @@
1
+ """Tests for the MicrosoftTTS class."""
2
+
3
+ from wyoming_microsoft_stt.microsoft_stt import MicrosoftSTT
4
+
5
+
6
+ def test_initialize(microsoft_stt_args):
7
+ """Test initialization."""
8
+ microsoft_stt = MicrosoftSTT(microsoft_stt_args)
9
+ assert microsoft_stt.speech_config is not None
10
+
11
+
12
+ def test_set_profanity(microsoft_stt_args):
13
+ """Test set_profanity."""
14
+ microsoft_stt = MicrosoftSTT(microsoft_stt_args)
15
+ assert microsoft_stt.speech_config is not None
16
+
17
+ profanity = "masked"
18
+ microsoft_stt.set_profanity(profanity)
19
+ # There is currently no way to check the set profanity level
@@ -0,0 +1,109 @@
1
+ """Tests for the Microsoft STT service."""
2
+
3
+ import asyncio
4
+ import re
5
+ import sys
6
+ import os
7
+ import wave
8
+ from asyncio.subprocess import PIPE
9
+ from pathlib import Path
10
+
11
+
12
+ import pytest
13
+ from wyoming.asr import Transcript
14
+ from wyoming.audio import AudioStart, AudioStop, wav_to_chunks
15
+ from wyoming.event import async_read_event, async_write_event
16
+ from wyoming.info import Describe, Info
17
+
18
+ import logging
19
+
20
+ _LOGGER = logging.getLogger(__name__)
21
+
22
+ _DIR = Path(__file__).parent
23
+ _PROGRAM_DIR = _DIR.parent
24
+ _LOCAL_DIR = _PROGRAM_DIR / "local"
25
+ _SAMPLES_PER_CHUNK = 1024
26
+
27
+ # Need to give time for the model to download
28
+ _START_TIMEOUT = 60
29
+ _TRANSCRIBE_TIMEOUT = 60
30
+
31
+
32
+ @pytest.mark.asyncio
33
+ async def test_multilanguage() -> None:
34
+ """Test the transcription."""
35
+ proc = await asyncio.create_subprocess_exec(
36
+ sys.executable,
37
+ "-m",
38
+ "wyoming_microsoft_stt",
39
+ "--uri",
40
+ "stdio://",
41
+ "--language",
42
+ "en-GB",
43
+ "nl-NL",
44
+ "--service-region",
45
+ os.environ.get("SPEECH_REGION"),
46
+ "--subscription-key",
47
+ os.environ.get("SPEECH_KEY"),
48
+ "--debug",
49
+ stdin=PIPE,
50
+ stdout=PIPE,
51
+ )
52
+ assert proc.stdin is not None
53
+ assert proc.stdout is not None
54
+
55
+ # Check info
56
+ await async_write_event(Describe().event(), proc.stdin)
57
+ while True:
58
+ event = await asyncio.wait_for(
59
+ async_read_event(proc.stdout), timeout=_START_TIMEOUT
60
+ )
61
+ assert event is not None
62
+
63
+ if not Info.is_type(event.type):
64
+ continue
65
+
66
+ info = Info.from_event(event)
67
+ assert len(info.asr) == 1, "Expected one asr service"
68
+ asr = info.asr[0]
69
+ assert len(asr.models) > 0, "Expected at least one model"
70
+ break
71
+
72
+ # Test known WAV
73
+ with wave.open(str(_DIR / "zet_het_licht_aan.wav"), "rb") as example_wav:
74
+ await async_write_event(
75
+ AudioStart(
76
+ rate=example_wav.getframerate(),
77
+ width=example_wav.getsampwidth(),
78
+ channels=example_wav.getnchannels(),
79
+ ).event(),
80
+ proc.stdin,
81
+ )
82
+ for chunk in wav_to_chunks(example_wav, _SAMPLES_PER_CHUNK):
83
+ await async_write_event(chunk.event(), proc.stdin)
84
+ _LOGGER.info("Sent bytes of audio data to the server")
85
+
86
+ await async_write_event(AudioStop().event(), proc.stdin)
87
+ _LOGGER.info("Sent audio stop event to the server")
88
+
89
+ while True:
90
+ event = await asyncio.wait_for(
91
+ async_read_event(proc.stdout), timeout=_TRANSCRIBE_TIMEOUT
92
+ )
93
+ assert event is not None
94
+
95
+ if not Transcript.is_type(event.type):
96
+ continue
97
+
98
+ transcript = Transcript.from_event(event)
99
+ _LOGGER.info(f"Received transcript: {transcript.text}")
100
+ text = transcript.text.lower().strip()
101
+ text = re.sub(r"[^a-z ]", "", text)
102
+ assert text == "zet het licht aan"
103
+ break
104
+
105
+ # Need to close stdin for graceful termination
106
+ proc.stdin.close()
107
+ _, stderr = await proc.communicate()
108
+
109
+ assert proc.returncode == 0, stderr.decode()
@@ -0,0 +1,114 @@
1
+ """Tests for the Microsoft STT service."""
2
+
3
+ import asyncio
4
+ import re
5
+ import sys
6
+ import os
7
+ import wave
8
+ from asyncio.subprocess import PIPE
9
+ from pathlib import Path
10
+
11
+
12
+ import pytest
13
+ from wyoming.asr import Transcript
14
+ from wyoming.audio import AudioStart, AudioStop, wav_to_chunks
15
+ from wyoming.event import async_read_event, async_write_event
16
+ from wyoming.info import Describe, Info
17
+
18
+ import logging
19
+
20
+ _LOGGER = logging.getLogger(__name__)
21
+
22
+ _DIR = Path(__file__).parent
23
+ _PROGRAM_DIR = _DIR.parent
24
+ _LOCAL_DIR = _PROGRAM_DIR / "local"
25
+ _SAMPLES_PER_CHUNK = 1024
26
+
27
+ # Need to give time for the model to download
28
+ _START_TIMEOUT = 60
29
+ _TRANSCRIBE_TIMEOUT = 60
30
+
31
+
32
+ @pytest.mark.asyncio
33
+ async def test_transcribe() -> None:
34
+ """Test the transcription."""
35
+ proc = await asyncio.create_subprocess_exec(
36
+ sys.executable,
37
+ "-m",
38
+ "wyoming_microsoft_stt",
39
+ "--uri",
40
+ "stdio://",
41
+ "--language",
42
+ "en-GB",
43
+ "--service-region",
44
+ os.environ.get("SPEECH_REGION"),
45
+ "--subscription-key",
46
+ os.environ.get("SPEECH_KEY"),
47
+ "--debug",
48
+ stdin=PIPE,
49
+ stdout=PIPE,
50
+ )
51
+ assert proc.stdin is not None
52
+ assert proc.stdout is not None
53
+
54
+ # Check info
55
+ await async_write_event(Describe().event(), proc.stdin)
56
+ while True:
57
+ event = await asyncio.wait_for(
58
+ async_read_event(proc.stdout), timeout=_START_TIMEOUT
59
+ )
60
+ assert event is not None
61
+
62
+ if not Info.is_type(event.type):
63
+ continue
64
+
65
+ info = Info.from_event(event)
66
+ assert len(info.asr) == 1, "Expected one asr service"
67
+ asr = info.asr[0]
68
+ assert len(asr.models) > 0, "Expected at least one model"
69
+ break
70
+
71
+ # Test known WAV
72
+ with wave.open(str(_DIR / "long_text.wav"), "rb") as example_wav:
73
+ await async_write_event(
74
+ AudioStart(
75
+ rate=example_wav.getframerate(),
76
+ width=example_wav.getsampwidth(),
77
+ channels=example_wav.getnchannels(),
78
+ ).event(),
79
+ proc.stdin,
80
+ )
81
+ for chunk in wav_to_chunks(example_wav, _SAMPLES_PER_CHUNK):
82
+ await async_write_event(chunk.event(), proc.stdin)
83
+ _LOGGER.info("Sent bytes of audio data to the server")
84
+
85
+ await async_write_event(AudioStop().event(), proc.stdin)
86
+ _LOGGER.info("Sent audio stop event to the server")
87
+
88
+ while True:
89
+ event = await asyncio.wait_for(
90
+ async_read_event(proc.stdout), timeout=_TRANSCRIBE_TIMEOUT
91
+ )
92
+ assert event is not None
93
+
94
+ if not Transcript.is_type(event.type):
95
+ continue
96
+
97
+ transcript = Transcript.from_event(event)
98
+ text = transcript.text.lower().strip()
99
+ text = re.sub(r"[^a-z ]", "", text)
100
+ _LOGGER.info(f"Received transcript: {text}")
101
+
102
+ original_text = "The Netherlands, informally Holland, is a country in Northwestern Europe with overseas territories in the Caribbean. It is the largest of the four constituent countries of the Kingdom of the Netherlands. The Netherlands consists of 12 provinces. It borders Germany to the east and Belgium to the south, with the North Sea coastline to the north and west. It shares maritime borders with the United Kingdom, Germany, and Belgium."
103
+ # Remove punctuation and convert to lowercase
104
+ original_text = original_text.lower()
105
+ original_text = re.sub(r"[^a-z ]", "", original_text)
106
+
107
+ assert text == original_text
108
+ break
109
+
110
+ # Need to close stdin for graceful termination
111
+ proc.stdin.close()
112
+ _, stderr = await proc.communicate()
113
+
114
+ assert proc.returncode == 0, stderr.decode()