yakyak 0.5.4__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.
- yakyak-0.5.4/LICENSE.md +21 -0
- yakyak-0.5.4/PKG-INFO +129 -0
- yakyak-0.5.4/README.md +104 -0
- yakyak-0.5.4/setup.cfg +4 -0
- yakyak-0.5.4/setup.py +50 -0
- yakyak-0.5.4/yakyak/__init__.py +6 -0
- yakyak-0.5.4/yakyak/__main__.py +87 -0
- yakyak-0.5.4/yakyak/yakyak.py +176 -0
- yakyak-0.5.4/yakyak.egg-info/PKG-INFO +129 -0
- yakyak-0.5.4/yakyak.egg-info/SOURCES.txt +12 -0
- yakyak-0.5.4/yakyak.egg-info/dependency_links.txt +1 -0
- yakyak-0.5.4/yakyak.egg-info/entry_points.txt +2 -0
- yakyak-0.5.4/yakyak.egg-info/requires.txt +2 -0
- yakyak-0.5.4/yakyak.egg-info/top_level.txt +1 -0
yakyak-0.5.4/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 MakerMattDesign
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
yakyak-0.5.4/PKG-INFO
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: yakyak
|
|
3
|
+
Version: 0.5.4
|
|
4
|
+
Summary: Utility for generating synthetic voice with Wyoming-Piper.
|
|
5
|
+
Home-page: https://github.com/b202i/yakyak
|
|
6
|
+
Author: MakerMattDesign
|
|
7
|
+
Author-email: matt@makermattdesign.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: utility synthetic-voice wyoming-piper tts
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Requires-Python: >=3.7
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE.md
|
|
23
|
+
Requires-Dist: pydub==0.25.1
|
|
24
|
+
Requires-Dist: wyoming==1.5.3
|
|
25
|
+
|
|
26
|
+
# YakYak
|
|
27
|
+
YakYak is a utility for the generation of synthetic voice through use of Wyoming-Piper.
|
|
28
|
+
It can be used from the command line or called from python. It opens a TCP socket to
|
|
29
|
+
Wyoming-Piper running in Docker anywhere on your local area network. It scales to run
|
|
30
|
+
efficiently on large multi-core computers or small single board computers.
|
|
31
|
+
|
|
32
|
+
## Install YakYak, Docker Compose & FFMPEG
|
|
33
|
+
To install YakYak, python virtual environment is recommended.
|
|
34
|
+
|
|
35
|
+
Step 1, create a python virtual environment and activate it
|
|
36
|
+
> cd some_directory
|
|
37
|
+
> python3 -m venv .venv
|
|
38
|
+
> source .venv/bin/activate
|
|
39
|
+
|
|
40
|
+
Step 2, install the yakyak package
|
|
41
|
+
> pip install yakyak
|
|
42
|
+
|
|
43
|
+
### Setup wyoming-piper in docker, on your local area network.
|
|
44
|
+
The default docker_compose.yml is distributed in github.
|
|
45
|
+
> docker compose up -d
|
|
46
|
+
|
|
47
|
+
### Install ffmpeg for mac
|
|
48
|
+
> brew install ffmpeg
|
|
49
|
+
|
|
50
|
+
### Install ffmpeg for Ubuntu
|
|
51
|
+
> sudo apt install ffmpeg
|
|
52
|
+
|
|
53
|
+
## Test YakYak, Wyoming-Piper and FFMPEG installation
|
|
54
|
+
It will take a little longer the first time running YakYak,
|
|
55
|
+
the Wyoming-Piper app needs time to download voice files.
|
|
56
|
+
> piper -h localhost -t mp3
|
|
57
|
+
|
|
58
|
+
Observe successful test results
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
INFO:root:Server localhost:10200 is online
|
|
62
|
+
INFO:root:Success, test: mp3
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## How to use YakYak from the command line
|
|
66
|
+
As with many Linux applications, YakYak supports standard in, and standard out. It also supports file input with the -i command and -o for file output. For a complete set of commands type yakyak --help.
|
|
67
|
+
> yakyak --help
|
|
68
|
+
|
|
69
|
+
Create an mp3 file with "Hello world"
|
|
70
|
+
> echo Hello world | yakyak -f mp3 -o hello_world.mp3
|
|
71
|
+
|
|
72
|
+
If you are on Linux and have aplay installed, you can do this:
|
|
73
|
+
> echo Hello world | yakyak | aplay
|
|
74
|
+
This assumes that Docker is running on the same machine.
|
|
75
|
+
|
|
76
|
+
If Docker is running on a different machine on your network, you can do this:
|
|
77
|
+
> echo Hello world | yakyak --host a_different_machine.local | aplay
|
|
78
|
+
|
|
79
|
+
## How to use YakYak from Python
|
|
80
|
+
Create the file `test_yakyak.py` with the following content:
|
|
81
|
+
```python
|
|
82
|
+
from yakyak import is_server_online, piper_tts_server
|
|
83
|
+
|
|
84
|
+
print(f"{is_server_online(
|
|
85
|
+
'localhost',
|
|
86
|
+
10200,
|
|
87
|
+
)=}")
|
|
88
|
+
|
|
89
|
+
print(f"{piper_tts_server(
|
|
90
|
+
'localhost',
|
|
91
|
+
10200,
|
|
92
|
+
'Hello World',
|
|
93
|
+
'hello_world.mp3',
|
|
94
|
+
'mp3',
|
|
95
|
+
'en_US-amy-medium'
|
|
96
|
+
)=}")
|
|
97
|
+
```
|
|
98
|
+
> python3 test_yakyak.py
|
|
99
|
+
Observe that the server is online and a file hello_world.mp3 is created. Play the mp3 and you will hear "Hello world".
|
|
100
|
+
|
|
101
|
+
## Python run.py Test
|
|
102
|
+
|
|
103
|
+
From the command line type `python3 run.py` will produce the following when successful:
|
|
104
|
+
> python3 run.py
|
|
105
|
+
```text
|
|
106
|
+
run.py
|
|
107
|
+
check_ffmpeg_version()='ffmpeg version 6.0 Copyright (c) 2000-2023 the FFmpeg developers'
|
|
108
|
+
is_server_online(
|
|
109
|
+
'localhost',
|
|
110
|
+
10200,
|
|
111
|
+
)=True
|
|
112
|
+
INFO:root:Server localhost:10200 is online
|
|
113
|
+
run_test(
|
|
114
|
+
'localhost',
|
|
115
|
+
10200,
|
|
116
|
+
'mp3',
|
|
117
|
+
)=(True, 'Success, test: mp3')
|
|
118
|
+
INFO:root:Success, test: mp3
|
|
119
|
+
await piper_tts_server(
|
|
120
|
+
'localhost',
|
|
121
|
+
10200,
|
|
122
|
+
'Hello World',
|
|
123
|
+
'run_test.mp3',
|
|
124
|
+
'mp3',
|
|
125
|
+
'en_US-amy-medium'
|
|
126
|
+
)=None
|
|
127
|
+
|
|
128
|
+
Process finished with exit code 0
|
|
129
|
+
```
|
yakyak-0.5.4/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# YakYak
|
|
2
|
+
YakYak is a utility for the generation of synthetic voice through use of Wyoming-Piper.
|
|
3
|
+
It can be used from the command line or called from python. It opens a TCP socket to
|
|
4
|
+
Wyoming-Piper running in Docker anywhere on your local area network. It scales to run
|
|
5
|
+
efficiently on large multi-core computers or small single board computers.
|
|
6
|
+
|
|
7
|
+
## Install YakYak, Docker Compose & FFMPEG
|
|
8
|
+
To install YakYak, python virtual environment is recommended.
|
|
9
|
+
|
|
10
|
+
Step 1, create a python virtual environment and activate it
|
|
11
|
+
> cd some_directory
|
|
12
|
+
> python3 -m venv .venv
|
|
13
|
+
> source .venv/bin/activate
|
|
14
|
+
|
|
15
|
+
Step 2, install the yakyak package
|
|
16
|
+
> pip install yakyak
|
|
17
|
+
|
|
18
|
+
### Setup wyoming-piper in docker, on your local area network.
|
|
19
|
+
The default docker_compose.yml is distributed in github.
|
|
20
|
+
> docker compose up -d
|
|
21
|
+
|
|
22
|
+
### Install ffmpeg for mac
|
|
23
|
+
> brew install ffmpeg
|
|
24
|
+
|
|
25
|
+
### Install ffmpeg for Ubuntu
|
|
26
|
+
> sudo apt install ffmpeg
|
|
27
|
+
|
|
28
|
+
## Test YakYak, Wyoming-Piper and FFMPEG installation
|
|
29
|
+
It will take a little longer the first time running YakYak,
|
|
30
|
+
the Wyoming-Piper app needs time to download voice files.
|
|
31
|
+
> piper -h localhost -t mp3
|
|
32
|
+
|
|
33
|
+
Observe successful test results
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
INFO:root:Server localhost:10200 is online
|
|
37
|
+
INFO:root:Success, test: mp3
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## How to use YakYak from the command line
|
|
41
|
+
As with many Linux applications, YakYak supports standard in, and standard out. It also supports file input with the -i command and -o for file output. For a complete set of commands type yakyak --help.
|
|
42
|
+
> yakyak --help
|
|
43
|
+
|
|
44
|
+
Create an mp3 file with "Hello world"
|
|
45
|
+
> echo Hello world | yakyak -f mp3 -o hello_world.mp3
|
|
46
|
+
|
|
47
|
+
If you are on Linux and have aplay installed, you can do this:
|
|
48
|
+
> echo Hello world | yakyak | aplay
|
|
49
|
+
This assumes that Docker is running on the same machine.
|
|
50
|
+
|
|
51
|
+
If Docker is running on a different machine on your network, you can do this:
|
|
52
|
+
> echo Hello world | yakyak --host a_different_machine.local | aplay
|
|
53
|
+
|
|
54
|
+
## How to use YakYak from Python
|
|
55
|
+
Create the file `test_yakyak.py` with the following content:
|
|
56
|
+
```python
|
|
57
|
+
from yakyak import is_server_online, piper_tts_server
|
|
58
|
+
|
|
59
|
+
print(f"{is_server_online(
|
|
60
|
+
'localhost',
|
|
61
|
+
10200,
|
|
62
|
+
)=}")
|
|
63
|
+
|
|
64
|
+
print(f"{piper_tts_server(
|
|
65
|
+
'localhost',
|
|
66
|
+
10200,
|
|
67
|
+
'Hello World',
|
|
68
|
+
'hello_world.mp3',
|
|
69
|
+
'mp3',
|
|
70
|
+
'en_US-amy-medium'
|
|
71
|
+
)=}")
|
|
72
|
+
```
|
|
73
|
+
> python3 test_yakyak.py
|
|
74
|
+
Observe that the server is online and a file hello_world.mp3 is created. Play the mp3 and you will hear "Hello world".
|
|
75
|
+
|
|
76
|
+
## Python run.py Test
|
|
77
|
+
|
|
78
|
+
From the command line type `python3 run.py` will produce the following when successful:
|
|
79
|
+
> python3 run.py
|
|
80
|
+
```text
|
|
81
|
+
run.py
|
|
82
|
+
check_ffmpeg_version()='ffmpeg version 6.0 Copyright (c) 2000-2023 the FFmpeg developers'
|
|
83
|
+
is_server_online(
|
|
84
|
+
'localhost',
|
|
85
|
+
10200,
|
|
86
|
+
)=True
|
|
87
|
+
INFO:root:Server localhost:10200 is online
|
|
88
|
+
run_test(
|
|
89
|
+
'localhost',
|
|
90
|
+
10200,
|
|
91
|
+
'mp3',
|
|
92
|
+
)=(True, 'Success, test: mp3')
|
|
93
|
+
INFO:root:Success, test: mp3
|
|
94
|
+
await piper_tts_server(
|
|
95
|
+
'localhost',
|
|
96
|
+
10200,
|
|
97
|
+
'Hello World',
|
|
98
|
+
'run_test.mp3',
|
|
99
|
+
'mp3',
|
|
100
|
+
'en_US-amy-medium'
|
|
101
|
+
)=None
|
|
102
|
+
|
|
103
|
+
Process finished with exit code 0
|
|
104
|
+
```
|
yakyak-0.5.4/setup.cfg
ADDED
yakyak-0.5.4/setup.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from setuptools import find_packages, setup
|
|
4
|
+
|
|
5
|
+
with open("./README.md", "r") as f:
|
|
6
|
+
long_description = f.read()
|
|
7
|
+
|
|
8
|
+
this_dir = Path(__file__).parent
|
|
9
|
+
|
|
10
|
+
requirements = []
|
|
11
|
+
requirements_path = this_dir / "requirements.txt"
|
|
12
|
+
if requirements_path.is_file():
|
|
13
|
+
with open(requirements_path, "r", encoding="utf-8") as requirements_file:
|
|
14
|
+
requirements = requirements_file.read().splitlines()
|
|
15
|
+
|
|
16
|
+
module_name = "yakyak"
|
|
17
|
+
module_dir = this_dir / module_name
|
|
18
|
+
|
|
19
|
+
setup(
|
|
20
|
+
name=module_name,
|
|
21
|
+
version='0.5.4',
|
|
22
|
+
description="Utility for generating synthetic voice with Wyoming-Piper.",
|
|
23
|
+
packages=find_packages(),
|
|
24
|
+
install_requires=requirements,
|
|
25
|
+
long_description=long_description,
|
|
26
|
+
long_description_content_type="text/markdown",
|
|
27
|
+
url="https://github.com/b202i/yakyak",
|
|
28
|
+
author="MakerMattDesign",
|
|
29
|
+
author_email="matt@makermattdesign.com",
|
|
30
|
+
license="MIT",
|
|
31
|
+
python_requires=">=3.7",
|
|
32
|
+
classifiers=[
|
|
33
|
+
"Development Status :: 3 - Alpha",
|
|
34
|
+
"Topic :: Text Processing :: Linguistic",
|
|
35
|
+
"Intended Audience :: Developers",
|
|
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
|
+
"Operating System :: OS Independent",
|
|
43
|
+
],
|
|
44
|
+
keywords="utility synthetic-voice wyoming-piper tts",
|
|
45
|
+
entry_points={
|
|
46
|
+
'console_scripts': [
|
|
47
|
+
'yakyak = yakyak.__main__:main',
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import asyncio
|
|
5
|
+
import logging
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from yakyak import (
|
|
9
|
+
check_ffmpeg_version,
|
|
10
|
+
is_server_online,
|
|
11
|
+
piper_tts_server,
|
|
12
|
+
run_test,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
DEFAULT_VOICE = "en_US-amy-medium"
|
|
16
|
+
FFMPEG_NOT_FOUND = "ffmpeg is not installed or not found in PATH"
|
|
17
|
+
|
|
18
|
+
logging.basicConfig(level=logging.INFO)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_stdin():
|
|
22
|
+
return ''.join(sys.stdin) # This will include line breaks
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_input_file(file_path):
|
|
26
|
+
with open(file_path, 'r') as file:
|
|
27
|
+
file_contents = file.read()
|
|
28
|
+
|
|
29
|
+
return file_contents
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main():
|
|
33
|
+
parser = argparse.ArgumentParser(description='YakYak client for Piper TTS Server')
|
|
34
|
+
|
|
35
|
+
parser.add_argument('--debug', action='store_true',
|
|
36
|
+
help='Print debug messages to console')
|
|
37
|
+
parser.add_argument('--host', type=str, default='localhost',
|
|
38
|
+
help='Hostname or IP address')
|
|
39
|
+
parser.add_argument('-p', '--port', type=int, default=10200,
|
|
40
|
+
help='Server port (default: 10200)')
|
|
41
|
+
parser.add_argument('-f', '--audio-format', type=str, choices=['mp3', 'wav'], default='mp3',
|
|
42
|
+
help='Audio output format')
|
|
43
|
+
parser.add_argument('-i', '--input-file', type=str, default='stdin',
|
|
44
|
+
help='Path to input text file (default: stdin)')
|
|
45
|
+
parser.add_argument('-o', '--output-file', type=str, default='stdout',
|
|
46
|
+
help='Path to output audio file, WAV or MP3 (default: stdout)')
|
|
47
|
+
parser.add_argument('-v', '--voice', type=str, default=DEFAULT_VOICE,
|
|
48
|
+
help='Onnx voice model file')
|
|
49
|
+
parser.add_argument('--output-raw', '--output_raw', action='store_true',
|
|
50
|
+
help='Stream raw audio to stdout')
|
|
51
|
+
# parser.add_argument('-s', '--speaker', type=int, default=0,
|
|
52
|
+
# help='Id of speaker (default: 0)')
|
|
53
|
+
parser.add_argument('-t', '--test', type=str, choices=['mp3', 'wav'],
|
|
54
|
+
help='Output format to end test')
|
|
55
|
+
|
|
56
|
+
args = parser.parse_args()
|
|
57
|
+
if args.debug:
|
|
58
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
59
|
+
else:
|
|
60
|
+
logging.basicConfig(level=logging.INFO)
|
|
61
|
+
|
|
62
|
+
if args.test:
|
|
63
|
+
if args.test == 'mp3':
|
|
64
|
+
version_info = check_ffmpeg_version()
|
|
65
|
+
if version_info == FFMPEG_NOT_FOUND:
|
|
66
|
+
logging.error(FFMPEG_NOT_FOUND)
|
|
67
|
+
return FFMPEG_NOT_FOUND
|
|
68
|
+
|
|
69
|
+
run_test(args.host, args.port, args.test)
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
if args.input_file == 'stdin':
|
|
73
|
+
tts_message = get_stdin()
|
|
74
|
+
else:
|
|
75
|
+
tts_message = get_input_file(args.input_file)
|
|
76
|
+
|
|
77
|
+
if is_server_online(args.host, args.port):
|
|
78
|
+
logging.debug(f"Server {args.host}:{args.port} is online")
|
|
79
|
+
asyncio.run(
|
|
80
|
+
piper_tts_server(args.host, args.port, tts_message, args.output_file, args.audio_format, args.voice)
|
|
81
|
+
)
|
|
82
|
+
else:
|
|
83
|
+
logging.info(f"Server {args.host}:{args.port} is offline")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == '__main__':
|
|
87
|
+
main()
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import io
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import socket
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import tempfile
|
|
13
|
+
import wave
|
|
14
|
+
|
|
15
|
+
from pydub import AudioSegment
|
|
16
|
+
from wyoming.audio import AudioChunk, AudioStop
|
|
17
|
+
from wyoming.client import AsyncTcpClient
|
|
18
|
+
from wyoming.tts import Synthesize, SynthesizeVoice
|
|
19
|
+
|
|
20
|
+
DEFAULT_VOICE = "en_US-amy-medium"
|
|
21
|
+
FFMPEG_NOT_FOUND = "ffmpeg is not installed or not found in PATH"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class WyomingTtsClient:
|
|
25
|
+
def __init__(self, host: str, port: int) -> None:
|
|
26
|
+
self.host = host
|
|
27
|
+
self.port = port
|
|
28
|
+
|
|
29
|
+
async def get_tts_audio(self, message: str, voice_name=None, voice_speaker=None):
|
|
30
|
+
"""Load TTS from TCP socket."""
|
|
31
|
+
try:
|
|
32
|
+
"""Create a context for the tts client with a timeout in case things go bad."""
|
|
33
|
+
async with AsyncTcpClient(self.host, self.port) as client:
|
|
34
|
+
voice: SynthesizeVoice | None = None
|
|
35
|
+
if voice_name is not None:
|
|
36
|
+
voice = SynthesizeVoice(name=voice_name, speaker=voice_speaker)
|
|
37
|
+
|
|
38
|
+
synthesize = Synthesize(text=message, voice=voice)
|
|
39
|
+
await client.write_event(synthesize.event())
|
|
40
|
+
|
|
41
|
+
with io.BytesIO() as wav_io:
|
|
42
|
+
wav_writer: wave.Wave_write | None = None
|
|
43
|
+
while True:
|
|
44
|
+
event = await client.read_event()
|
|
45
|
+
if event is None:
|
|
46
|
+
logging.debug("Connection lost")
|
|
47
|
+
return None, None
|
|
48
|
+
|
|
49
|
+
if AudioStop.is_type(event.type):
|
|
50
|
+
break
|
|
51
|
+
|
|
52
|
+
if AudioChunk.is_type(event.type):
|
|
53
|
+
chunk = AudioChunk.from_event(event)
|
|
54
|
+
if wav_writer is None:
|
|
55
|
+
wav_writer = wave.open(wav_io, "wb")
|
|
56
|
+
wav_writer.setframerate(chunk.rate)
|
|
57
|
+
wav_writer.setsampwidth(chunk.width)
|
|
58
|
+
wav_writer.setnchannels(chunk.channels)
|
|
59
|
+
|
|
60
|
+
wav_writer.writeframes(chunk.audio)
|
|
61
|
+
|
|
62
|
+
if wav_writer is not None:
|
|
63
|
+
wav_writer.close()
|
|
64
|
+
|
|
65
|
+
data = wav_io.getvalue()
|
|
66
|
+
|
|
67
|
+
except (OSError, IOError) as e:
|
|
68
|
+
logging.error(f"TTS Error: {e}")
|
|
69
|
+
return None, None
|
|
70
|
+
|
|
71
|
+
return "wav", data
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
async def create(cls, host: str, port: int) -> WyomingTtsClient | None:
|
|
75
|
+
|
|
76
|
+
return cls(host, port)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def piper_tts_server(
|
|
80
|
+
host: str, port: int,
|
|
81
|
+
tts_text: str,
|
|
82
|
+
output_file: str = "output.mp3",
|
|
83
|
+
audio_format: str = "mp3",
|
|
84
|
+
voice: str = DEFAULT_VOICE
|
|
85
|
+
):
|
|
86
|
+
service = await WyomingTtsClient.create(host, port)
|
|
87
|
+
logging.debug(f"tts len: {len(tts_text)}, message: {tts_text}")
|
|
88
|
+
|
|
89
|
+
_audio_format, audio_data = await service.get_tts_audio(tts_text, voice)
|
|
90
|
+
|
|
91
|
+
"""Output can be to mp3 or wav and to stdout or a file"""
|
|
92
|
+
try:
|
|
93
|
+
if audio_data:
|
|
94
|
+
# Convert WAV to MP3 if needed
|
|
95
|
+
if audio_format == 'mp3':
|
|
96
|
+
audio = AudioSegment.from_wav(io.BytesIO(audio_data))
|
|
97
|
+
audio_data = io.BytesIO()
|
|
98
|
+
audio.export(audio_data, format='mp3')
|
|
99
|
+
audio_data = audio_data.getvalue() # Wav data replaced with mp3 data
|
|
100
|
+
|
|
101
|
+
# Output to stdout or file
|
|
102
|
+
if output_file == 'stdout':
|
|
103
|
+
sys.stdout.buffer.write(audio_data) # Use buffer for binary data
|
|
104
|
+
sys.stdout.flush()
|
|
105
|
+
else:
|
|
106
|
+
with open(output_file, "wb") as f:
|
|
107
|
+
f.write(audio_data)
|
|
108
|
+
except PermissionError as e:
|
|
109
|
+
logging.error(f"Error {e}")
|
|
110
|
+
except IOError as e:
|
|
111
|
+
logging.error(f"Error {e}")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def is_server_online(host: str, port: int) -> bool:
|
|
115
|
+
try:
|
|
116
|
+
# Create a TCP socket
|
|
117
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
118
|
+
sock.settimeout(1) # Timeout in seconds
|
|
119
|
+
result = sock.connect_ex((host, port))
|
|
120
|
+
return result == 0 # If the result is 0, the connection was successful
|
|
121
|
+
except socket.error as e: # Handle exceptions, such as network issues
|
|
122
|
+
logging.error(f"Socket error: {e}")
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def run_test(host: str, port: int, audio_format: str):
|
|
127
|
+
if audio_format not in ['mp3', 'wav']:
|
|
128
|
+
raise ValueError("audio_format must be 'mp3' or 'wav'")
|
|
129
|
+
|
|
130
|
+
logging.debug(f"Starting test {audio_format}, host: {host}:{port}")
|
|
131
|
+
|
|
132
|
+
if is_server_online(host, port):
|
|
133
|
+
logging.debug(f"Server {host}:{port} is online")
|
|
134
|
+
|
|
135
|
+
with tempfile.NamedTemporaryFile(delete=True) as temp_file:
|
|
136
|
+
test_file_path = temp_file.name
|
|
137
|
+
|
|
138
|
+
asyncio.run(
|
|
139
|
+
piper_tts_server(
|
|
140
|
+
host,
|
|
141
|
+
port,
|
|
142
|
+
"Hello world",
|
|
143
|
+
test_file_path,
|
|
144
|
+
audio_format,
|
|
145
|
+
DEFAULT_VOICE)
|
|
146
|
+
)
|
|
147
|
+
file_exists = os.path.exists(test_file_path)
|
|
148
|
+
file_len = os.path.getsize(test_file_path)
|
|
149
|
+
if file_exists and file_len > 0:
|
|
150
|
+
os.remove(test_file_path)
|
|
151
|
+
msg = "Success, test: " + audio_format
|
|
152
|
+
logging.info(msg)
|
|
153
|
+
return True, msg
|
|
154
|
+
else:
|
|
155
|
+
if os.path.exists(test_file_path):
|
|
156
|
+
os.remove(test_file_path)
|
|
157
|
+
msg = "Fail, test: " + audio_format
|
|
158
|
+
logging.info(msg)
|
|
159
|
+
return False, msg
|
|
160
|
+
|
|
161
|
+
else:
|
|
162
|
+
msg = f"Server {host}:{port} is offline"
|
|
163
|
+
logging.info(msg)
|
|
164
|
+
return False, msg
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def check_ffmpeg_version() -> str:
|
|
168
|
+
try:
|
|
169
|
+
result = subprocess.run(["ffmpeg", "-version"], capture_output=True, text=True, check=True)
|
|
170
|
+
first_line = result.stdout.split('\n', 1)[0]
|
|
171
|
+
if "ffmpeg version" in first_line:
|
|
172
|
+
return first_line
|
|
173
|
+
else:
|
|
174
|
+
return "ffmpeg is installed but version info is unclear"
|
|
175
|
+
except subprocess.CalledProcessError:
|
|
176
|
+
return FFMPEG_NOT_FOUND
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: yakyak
|
|
3
|
+
Version: 0.5.4
|
|
4
|
+
Summary: Utility for generating synthetic voice with Wyoming-Piper.
|
|
5
|
+
Home-page: https://github.com/b202i/yakyak
|
|
6
|
+
Author: MakerMattDesign
|
|
7
|
+
Author-email: matt@makermattdesign.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: utility synthetic-voice wyoming-piper tts
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Requires-Python: >=3.7
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE.md
|
|
23
|
+
Requires-Dist: pydub==0.25.1
|
|
24
|
+
Requires-Dist: wyoming==1.5.3
|
|
25
|
+
|
|
26
|
+
# YakYak
|
|
27
|
+
YakYak is a utility for the generation of synthetic voice through use of Wyoming-Piper.
|
|
28
|
+
It can be used from the command line or called from python. It opens a TCP socket to
|
|
29
|
+
Wyoming-Piper running in Docker anywhere on your local area network. It scales to run
|
|
30
|
+
efficiently on large multi-core computers or small single board computers.
|
|
31
|
+
|
|
32
|
+
## Install YakYak, Docker Compose & FFMPEG
|
|
33
|
+
To install YakYak, python virtual environment is recommended.
|
|
34
|
+
|
|
35
|
+
Step 1, create a python virtual environment and activate it
|
|
36
|
+
> cd some_directory
|
|
37
|
+
> python3 -m venv .venv
|
|
38
|
+
> source .venv/bin/activate
|
|
39
|
+
|
|
40
|
+
Step 2, install the yakyak package
|
|
41
|
+
> pip install yakyak
|
|
42
|
+
|
|
43
|
+
### Setup wyoming-piper in docker, on your local area network.
|
|
44
|
+
The default docker_compose.yml is distributed in github.
|
|
45
|
+
> docker compose up -d
|
|
46
|
+
|
|
47
|
+
### Install ffmpeg for mac
|
|
48
|
+
> brew install ffmpeg
|
|
49
|
+
|
|
50
|
+
### Install ffmpeg for Ubuntu
|
|
51
|
+
> sudo apt install ffmpeg
|
|
52
|
+
|
|
53
|
+
## Test YakYak, Wyoming-Piper and FFMPEG installation
|
|
54
|
+
It will take a little longer the first time running YakYak,
|
|
55
|
+
the Wyoming-Piper app needs time to download voice files.
|
|
56
|
+
> piper -h localhost -t mp3
|
|
57
|
+
|
|
58
|
+
Observe successful test results
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
INFO:root:Server localhost:10200 is online
|
|
62
|
+
INFO:root:Success, test: mp3
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## How to use YakYak from the command line
|
|
66
|
+
As with many Linux applications, YakYak supports standard in, and standard out. It also supports file input with the -i command and -o for file output. For a complete set of commands type yakyak --help.
|
|
67
|
+
> yakyak --help
|
|
68
|
+
|
|
69
|
+
Create an mp3 file with "Hello world"
|
|
70
|
+
> echo Hello world | yakyak -f mp3 -o hello_world.mp3
|
|
71
|
+
|
|
72
|
+
If you are on Linux and have aplay installed, you can do this:
|
|
73
|
+
> echo Hello world | yakyak | aplay
|
|
74
|
+
This assumes that Docker is running on the same machine.
|
|
75
|
+
|
|
76
|
+
If Docker is running on a different machine on your network, you can do this:
|
|
77
|
+
> echo Hello world | yakyak --host a_different_machine.local | aplay
|
|
78
|
+
|
|
79
|
+
## How to use YakYak from Python
|
|
80
|
+
Create the file `test_yakyak.py` with the following content:
|
|
81
|
+
```python
|
|
82
|
+
from yakyak import is_server_online, piper_tts_server
|
|
83
|
+
|
|
84
|
+
print(f"{is_server_online(
|
|
85
|
+
'localhost',
|
|
86
|
+
10200,
|
|
87
|
+
)=}")
|
|
88
|
+
|
|
89
|
+
print(f"{piper_tts_server(
|
|
90
|
+
'localhost',
|
|
91
|
+
10200,
|
|
92
|
+
'Hello World',
|
|
93
|
+
'hello_world.mp3',
|
|
94
|
+
'mp3',
|
|
95
|
+
'en_US-amy-medium'
|
|
96
|
+
)=}")
|
|
97
|
+
```
|
|
98
|
+
> python3 test_yakyak.py
|
|
99
|
+
Observe that the server is online and a file hello_world.mp3 is created. Play the mp3 and you will hear "Hello world".
|
|
100
|
+
|
|
101
|
+
## Python run.py Test
|
|
102
|
+
|
|
103
|
+
From the command line type `python3 run.py` will produce the following when successful:
|
|
104
|
+
> python3 run.py
|
|
105
|
+
```text
|
|
106
|
+
run.py
|
|
107
|
+
check_ffmpeg_version()='ffmpeg version 6.0 Copyright (c) 2000-2023 the FFmpeg developers'
|
|
108
|
+
is_server_online(
|
|
109
|
+
'localhost',
|
|
110
|
+
10200,
|
|
111
|
+
)=True
|
|
112
|
+
INFO:root:Server localhost:10200 is online
|
|
113
|
+
run_test(
|
|
114
|
+
'localhost',
|
|
115
|
+
10200,
|
|
116
|
+
'mp3',
|
|
117
|
+
)=(True, 'Success, test: mp3')
|
|
118
|
+
INFO:root:Success, test: mp3
|
|
119
|
+
await piper_tts_server(
|
|
120
|
+
'localhost',
|
|
121
|
+
10200,
|
|
122
|
+
'Hello World',
|
|
123
|
+
'run_test.mp3',
|
|
124
|
+
'mp3',
|
|
125
|
+
'en_US-amy-medium'
|
|
126
|
+
)=None
|
|
127
|
+
|
|
128
|
+
Process finished with exit code 0
|
|
129
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE.md
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
yakyak/__init__.py
|
|
5
|
+
yakyak/__main__.py
|
|
6
|
+
yakyak/yakyak.py
|
|
7
|
+
yakyak.egg-info/PKG-INFO
|
|
8
|
+
yakyak.egg-info/SOURCES.txt
|
|
9
|
+
yakyak.egg-info/dependency_links.txt
|
|
10
|
+
yakyak.egg-info/entry_points.txt
|
|
11
|
+
yakyak.egg-info/requires.txt
|
|
12
|
+
yakyak.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
yakyak
|