speechjenerator 0.1.0__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.
- speechjenerator-0.1.0/PKG-INFO +20 -0
- speechjenerator-0.1.0/README.md +2 -0
- speechjenerator-0.1.0/pyproject.toml +31 -0
- speechjenerator-0.1.0/src/speechjenerator/__init__.py +5 -0
- speechjenerator-0.1.0/src/speechjenerator/artifacts/__init__.py +0 -0
- speechjenerator-0.1.0/src/speechjenerator/artifacts/gtts_artifact.py +72 -0
- speechjenerator-0.1.0/src/speechjenerator/artifacts/registry.py +52 -0
- speechjenerator-0.1.0/src/speechjenerator/artifacts/wav_artifact.py +76 -0
- speechjenerator-0.1.0/src/speechjenerator/core/.gitkeep +0 -0
- speechjenerator-0.1.0/src/speechjenerator/core/base_class_record.py +121 -0
- speechjenerator-0.1.0/src/speechjenerator/core/base_speech_generator.py +114 -0
- speechjenerator-0.1.0/src/speechjenerator/examples/.gitkeep +0 -0
- speechjenerator-0.1.0/src/speechjenerator/examples/gtts_example.py +17 -0
- speechjenerator-0.1.0/src/speechjenerator/examples/kokoro_example.py +26 -0
- speechjenerator-0.1.0/src/speechjenerator/local/__init__.py +6 -0
- speechjenerator-0.1.0/src/speechjenerator/local/kokoro.py +88 -0
- speechjenerator-0.1.0/src/speechjenerator/registry.py +54 -0
- speechjenerator-0.1.0/src/speechjenerator/remote/__init__.py +6 -0
- speechjenerator-0.1.0/src/speechjenerator/remote/gtts.py +80 -0
- speechjenerator-0.1.0/src/speechjenerator/tools/.gitkeep +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: speechjenerator
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: It does TTS.
|
|
5
|
+
Author: Warren Davies
|
|
6
|
+
Requires-Python: >=3.10,<3.13
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
11
|
+
Requires-Dist: basejenerator (>=0.2.0,<0.3.0)
|
|
12
|
+
Requires-Dist: gtts (>=2.5.4,<3.0.0)
|
|
13
|
+
Requires-Dist: kokoro (>=0.9.4,<0.10.0)
|
|
14
|
+
Requires-Dist: sounddevice (>=0.5.5,<0.6.0)
|
|
15
|
+
Requires-Dist: soundfile (>=0.13.1,<0.14.0)
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# model-registry-template
|
|
19
|
+
A template for projects that require the ability to select from multiple models.
|
|
20
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "speechjenerator"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "It does TTS."
|
|
5
|
+
authors = [{ name = "Warren Davies"}]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
homepage = "https://github.com/WarrenDavies/speechjenerator"
|
|
8
|
+
repository = "https://github.com/WarrenDavies/speechjenerator"
|
|
9
|
+
packages = [
|
|
10
|
+
{ include = "speechjenerator", from = "src" }
|
|
11
|
+
]
|
|
12
|
+
requires-python = ">=3.10,<3.13"
|
|
13
|
+
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[tool.poetry.dependencies]
|
|
22
|
+
python = ">=3.10,<3.13"
|
|
23
|
+
basejenerator = "^0.2.0"
|
|
24
|
+
gtts = "^2.5.4"
|
|
25
|
+
sounddevice = "^0.5.5"
|
|
26
|
+
soundfile = "^0.13.1"
|
|
27
|
+
kokoro = "^0.9.4"
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["poetry-core>=1.0.0"]
|
|
31
|
+
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
import sounddevice as sd
|
|
6
|
+
import soundfile as sf
|
|
7
|
+
|
|
8
|
+
from basejenerator.artifacts.base_artifact import BaseArtifact
|
|
9
|
+
|
|
10
|
+
class GTTSArtifact(BaseArtifact):
|
|
11
|
+
"""
|
|
12
|
+
Holds a gTTS "model" object. This is more like a promise to create an audio file.
|
|
13
|
+
Due to how gTTS works, the Google Translate API is not called until `.save()`
|
|
14
|
+
is called.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
data (str): The gTTS "model" object.
|
|
18
|
+
item_extras (dict): any additional metadata related to this artifact
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, data, item_extras):
|
|
22
|
+
self.data = data
|
|
23
|
+
self.item_extras = item_extras
|
|
24
|
+
self.extension = ".mp3"
|
|
25
|
+
self.system = platform.system()
|
|
26
|
+
self.linux_mp3_players = [
|
|
27
|
+
["mpg123", "--quiet"],
|
|
28
|
+
["mpv", "--no-terminal"],
|
|
29
|
+
]
|
|
30
|
+
self.player_funcs = {
|
|
31
|
+
"Windows": self._play_on_windows,
|
|
32
|
+
"Darwin": self._play_on_mac,
|
|
33
|
+
"Linux": self._play_on_linux
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
def save(self, path):
|
|
37
|
+
"""
|
|
38
|
+
Called Google Translate API, gets the audio as WAV, and saves to disk.
|
|
39
|
+
"""
|
|
40
|
+
self.data.save(path)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _play_on_windows(self, path):
|
|
44
|
+
os.startfile(path)
|
|
45
|
+
|
|
46
|
+
def _play_on_mac(self, path):
|
|
47
|
+
subprocess.run(['afplay', path])
|
|
48
|
+
|
|
49
|
+
def _play_on_linux(self, path):
|
|
50
|
+
for player_command in self.linux_mp3_players:
|
|
51
|
+
player_command.append(path)
|
|
52
|
+
try:
|
|
53
|
+
subprocess.run(player_command, check=True)
|
|
54
|
+
break
|
|
55
|
+
except FileNotFoundError:
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
def play(self, path=None):
|
|
59
|
+
"""
|
|
60
|
+
Calls Google Translate API, gets the audio as mp3, saves to disk,
|
|
61
|
+
and plays the audio on the system in which speechjenerator is running
|
|
62
|
+
using .
|
|
63
|
+
"""
|
|
64
|
+
if not path:
|
|
65
|
+
path = self.data["config"]["path"]
|
|
66
|
+
self.save(path)
|
|
67
|
+
|
|
68
|
+
mp3_player = self.player_funcs[self.system]
|
|
69
|
+
try:
|
|
70
|
+
mp3_player(path)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
print(f"Error playing audio: {e}")
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Implements a simple decorator-based model registry for dynamically managing
|
|
3
|
+
different artifact classes.
|
|
4
|
+
|
|
5
|
+
This system allows concrete subclasses to register themselves
|
|
6
|
+
using a specific string key, enabling the application to instantiate the
|
|
7
|
+
correct class based on a configuration setting without explicit imports.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
ARTIFACT_REGISTRY = {}
|
|
11
|
+
|
|
12
|
+
def register_model(name):
|
|
13
|
+
"""
|
|
14
|
+
A decorator factory used to register a subclass.
|
|
15
|
+
|
|
16
|
+
The decorated class is stored in the global ARTIFACT_REGISTRY dictionary
|
|
17
|
+
under the provided `name`.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
name (str): The string key used to reference the model class
|
|
21
|
+
in the configuration.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Callable: A decorator function that takes a class and registers it.
|
|
25
|
+
"""
|
|
26
|
+
def decorator(cls):
|
|
27
|
+
ARTIFACT_REGISTRY[name] = cls
|
|
28
|
+
return cls
|
|
29
|
+
return decorator
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_class(artifact_name):
|
|
33
|
+
"""
|
|
34
|
+
Retrieves and instantiates the correct class based on the
|
|
35
|
+
configuration dictionary.
|
|
36
|
+
|
|
37
|
+
It looks up the class in ARTIFACT_REGISTRY using `artifact_name`.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
artifact_name: Must correspond to a registered artifact name.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
An instantiated object of the registered class.
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
KeyError: If the value of `artifact_name` is not found in the
|
|
47
|
+
ARTIFACT_REGISTRY.
|
|
48
|
+
"""
|
|
49
|
+
Class_ = ARTIFACT_REGISTRY[artifact_name]
|
|
50
|
+
artifact = Class_()
|
|
51
|
+
|
|
52
|
+
return model_object
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import subprocess
|
|
4
|
+
import datetime
|
|
5
|
+
import soundfile as sf
|
|
6
|
+
|
|
7
|
+
from basejenerator.artifacts.base_artifact import BaseArtifact
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class WavArtifact(BaseArtifact):
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
data (str): The gTTS "model" object.
|
|
15
|
+
item_extras (dict): any additional metadata related to this artifact
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, data, item_extras):
|
|
19
|
+
self.data = data
|
|
20
|
+
self.item_extras = item_extras
|
|
21
|
+
self.extension = ".wav"
|
|
22
|
+
self.system = platform.system()
|
|
23
|
+
self.linux_players = [
|
|
24
|
+
["mpv", "--no-terminal"],
|
|
25
|
+
["mpg123", "--quiet"],
|
|
26
|
+
|
|
27
|
+
]
|
|
28
|
+
self.player_funcs = {
|
|
29
|
+
"Windows": self._play_on_windows,
|
|
30
|
+
"Darwin": self._play_on_mac,
|
|
31
|
+
"Linux": self._play_on_linux
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def save(self, path):
|
|
36
|
+
"""
|
|
37
|
+
"""
|
|
38
|
+
if "chunk_index" in self.item_extras:
|
|
39
|
+
extension = f"_{str(self.item_extras['chunk_index'])}{self.extension}"
|
|
40
|
+
else:
|
|
41
|
+
extension = self.extension
|
|
42
|
+
|
|
43
|
+
self.path = f'{path}{extension}'
|
|
44
|
+
sf.write(self.path, self.data.data, 24000)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _play_on_windows(self, path):
|
|
48
|
+
os.startfile(path)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _play_on_mac(self, path):
|
|
52
|
+
subprocess.run(['afplay', path])
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _play_on_linux(self, path):
|
|
56
|
+
for player_command in self.linux_players:
|
|
57
|
+
player_command.append(path)
|
|
58
|
+
try:
|
|
59
|
+
subprocess.run(player_command, check=True)
|
|
60
|
+
break
|
|
61
|
+
except FileNotFoundError:
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def play(self, path=None):
|
|
66
|
+
"""
|
|
67
|
+
"""
|
|
68
|
+
if not path:
|
|
69
|
+
path = self.data["config"]["path"]
|
|
70
|
+
self.save(path)
|
|
71
|
+
|
|
72
|
+
player = self.player_funcs[self.system]
|
|
73
|
+
try:
|
|
74
|
+
player(self.path)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
print(f"Error playing audio: {e}")
|
|
File without changes
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from dataclasses import dataclass, fields, field
|
|
2
|
+
import csv
|
|
3
|
+
import os
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class BaseClassRecord():
|
|
8
|
+
"""
|
|
9
|
+
A data container for tracking and saving inference metadata.
|
|
10
|
+
|
|
11
|
+
This class serves as a structured record for a single inference event.
|
|
12
|
+
It includes functionality to serialize its own data to a CSV file, handling
|
|
13
|
+
formatting (e.g., float precision) and file management (creation vs. appending).
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
gen_data_file_path (str): Path to the CSV file where records are saved.
|
|
17
|
+
filename (str): Name of the generated file.
|
|
18
|
+
timestamp (str): Timestamp of generation.
|
|
19
|
+
model (str): Name of the model used.
|
|
20
|
+
device (str): Compute device used (e.g., 'cuda', 'cpu').
|
|
21
|
+
dtype (str): Data type used (e.g., 'float16').
|
|
22
|
+
total_generation_time (float): Total time taken
|
|
23
|
+
rating (float): Placeholder for user rating (defaults to -1).
|
|
24
|
+
fields_to_not_save (list[str]): Internal list of field names to exclude from the CSV.
|
|
25
|
+
"""
|
|
26
|
+
gen_data_file_path: str = ""
|
|
27
|
+
filename: str = ""
|
|
28
|
+
timestamp: str = ""
|
|
29
|
+
model: str = ""
|
|
30
|
+
device: str = ""
|
|
31
|
+
dtype: str = ""
|
|
32
|
+
total_generation_time: float = 0
|
|
33
|
+
rating: float = -1
|
|
34
|
+
fields_to_not_save: list[str] = field(default_factory=list)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def __post_init__(self):
|
|
38
|
+
"""
|
|
39
|
+
Post-initialization hook to define excluded fields.
|
|
40
|
+
|
|
41
|
+
Populates `fields_to_not_save` with technical fields that should not
|
|
42
|
+
appear in the final CSV report (e.g., file paths or the exclusion list itself).
|
|
43
|
+
"""
|
|
44
|
+
self.fields_to_not_save += ["gen_data_file_path", "fields_to_not_save", "path_to_csv"]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def create_data_row(self):
|
|
48
|
+
"""
|
|
49
|
+
Constructs a list of values for the CSV row based on current attributes.
|
|
50
|
+
|
|
51
|
+
Iterates through the dataclass fields, filtering out those in `fields_to_not_save`.
|
|
52
|
+
Floats are formatted to 2 decimal places.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
list: A list of values corresponding to the CSV columns.
|
|
56
|
+
"""
|
|
57
|
+
row = []
|
|
58
|
+
for field in fields(self):
|
|
59
|
+
if field.name in self.fields_to_not_save:
|
|
60
|
+
continue
|
|
61
|
+
field_value = getattr(self, field.name)
|
|
62
|
+
|
|
63
|
+
if isinstance(field_value, float):
|
|
64
|
+
row.append(f"{field_value:.2f}")
|
|
65
|
+
else:
|
|
66
|
+
row.append(field_value)
|
|
67
|
+
return row
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def append_data_to_csv(self):
|
|
71
|
+
"""
|
|
72
|
+
Appends the current record to the CSV file.
|
|
73
|
+
|
|
74
|
+
Opens the file at `gen_data_file_path` in append mode and writes
|
|
75
|
+
the data row generated by `create_data_row()`.
|
|
76
|
+
"""
|
|
77
|
+
with open(self.gen_data_file_path, "a", newline="") as f:
|
|
78
|
+
writer = csv.writer(f)
|
|
79
|
+
writer.writerow(self.create_data_row())
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def create_header_row(self):
|
|
83
|
+
"""
|
|
84
|
+
Generates the CSV header row based on field names.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
list[str]: A list of column names, excluding those in `fields_to_not_save`.
|
|
88
|
+
"""
|
|
89
|
+
headers = []
|
|
90
|
+
for header in fields(self):
|
|
91
|
+
if header.name in self.fields_to_not_save:
|
|
92
|
+
continue
|
|
93
|
+
headers.append(header.name)
|
|
94
|
+
return headers
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def create_new_stats_file(self):
|
|
98
|
+
"""
|
|
99
|
+
Creates a new CSV file and writes the header row.
|
|
100
|
+
|
|
101
|
+
Uses 'x' mode to open the file, which ensures a new file is created
|
|
102
|
+
and raises an error if the file already exists (though `save_data()` guards against this).
|
|
103
|
+
"""
|
|
104
|
+
with open(self.gen_data_file_path, "x", newline="") as f:
|
|
105
|
+
writer = csv.writer(f)
|
|
106
|
+
writer.writerow(self.create_header_row())
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def save_data(self):
|
|
110
|
+
"""
|
|
111
|
+
Saves the record of the generation stats to disk.
|
|
112
|
+
|
|
113
|
+
Checks if the target CSV file exists:
|
|
114
|
+
- If no: Creates the file and writes headers.
|
|
115
|
+
- If yes: Appends the current data row.
|
|
116
|
+
"""
|
|
117
|
+
if not os.path.exists(self.gen_data_file_path):
|
|
118
|
+
self.create_new_stats_file()
|
|
119
|
+
|
|
120
|
+
self.append_data_to_csv()
|
|
121
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
import datetime
|
|
3
|
+
import time
|
|
4
|
+
import os
|
|
5
|
+
import random
|
|
6
|
+
import torch
|
|
7
|
+
import gc
|
|
8
|
+
|
|
9
|
+
from basejenerator.base_generator import BaseGenerator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseSpeechGenerator(BaseGenerator):
|
|
13
|
+
"""
|
|
14
|
+
Abstract base class for TTS.
|
|
15
|
+
|
|
16
|
+
This class handles...
|
|
17
|
+
|
|
18
|
+
Subclasses must implement create_pipeline() and run_pipeline()
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
config (dict): Configuration dictionary containing model parameters, paths, and settings.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, config):
|
|
25
|
+
"""
|
|
26
|
+
Initializes the object with a config.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
config (dict): A dictionary containing configuration parameters.
|
|
30
|
+
Expected keys include:
|
|
31
|
+
-
|
|
32
|
+
"""
|
|
33
|
+
self.config = config
|
|
34
|
+
self.device = self.set_device()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def set_device(self):
|
|
38
|
+
"""
|
|
39
|
+
Sets the computation device based on CUDA availability.
|
|
40
|
+
|
|
41
|
+
Sets `self.device` to 'cuda' if available, otherwise defaults to 'cpu'.
|
|
42
|
+
"""
|
|
43
|
+
if "device" in self.config:
|
|
44
|
+
return self.config["device"]
|
|
45
|
+
|
|
46
|
+
if torch.cuda.is_available():
|
|
47
|
+
return "cuda"
|
|
48
|
+
else:
|
|
49
|
+
return "cpu"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def load(self):
|
|
54
|
+
"""
|
|
55
|
+
Abstract method to initialize the model pipeline.
|
|
56
|
+
|
|
57
|
+
Subclasses must implement this to load the specific model and tokenizer/pipeline object, assigning it to self.pipe (e.g., a Hugging Face Pipeline object).
|
|
58
|
+
"""
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def prepare(self):
|
|
64
|
+
"""
|
|
65
|
+
Reset lifecycle without tearing down the model - e.g., clear cache, etc.
|
|
66
|
+
"""
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def warmup(self):
|
|
72
|
+
"""
|
|
73
|
+
Initial inference run to create cache etc., mainly for benchmarking.
|
|
74
|
+
"""
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def generate(self):
|
|
79
|
+
"""
|
|
80
|
+
The public API that runs inference.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
GeneratorOutput
|
|
84
|
+
"""
|
|
85
|
+
return self.generate_impl()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def generate_impl(self):
|
|
90
|
+
"""
|
|
91
|
+
The public API that runs inference.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
GeneratorOutput
|
|
95
|
+
"""
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def teardown(self):
|
|
100
|
+
"""
|
|
101
|
+
Deletes the pipeline, empties the torch cache, and forces Python's garbage collector to run. Clears the slate to create
|
|
102
|
+
another pipeline.
|
|
103
|
+
"""
|
|
104
|
+
if self.model is None:
|
|
105
|
+
print("No pipeline found. You cannot teardown that which was not created.")
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
del self.model
|
|
109
|
+
self.model = None
|
|
110
|
+
|
|
111
|
+
if torch.cuda.is_available():
|
|
112
|
+
torch.cuda.empty_cache()
|
|
113
|
+
|
|
114
|
+
gc.collect()
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from speechjenerator import registry
|
|
2
|
+
|
|
3
|
+
output_path = "./outputs/test.wav"
|
|
4
|
+
config = {
|
|
5
|
+
"model": "gtts",
|
|
6
|
+
|
|
7
|
+
"text": "Hazlo o no lo hagas. Intentar no existe.",
|
|
8
|
+
"lang": "es"
|
|
9
|
+
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
speech_generator = registry.get_model_class(config)
|
|
13
|
+
speech_generator.load()
|
|
14
|
+
print(speech_generator.config)
|
|
15
|
+
output = speech_generator.generate()
|
|
16
|
+
speech = output.batch[0]
|
|
17
|
+
speech.play(output_path)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from speechjenerator import registry
|
|
2
|
+
import datetime
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
output_path = "./outputs/"
|
|
6
|
+
config = {
|
|
7
|
+
"model": "kokoro",
|
|
8
|
+
"text": """Do, or do not. There is no try.""",
|
|
9
|
+
"lang": "a",
|
|
10
|
+
"voice": "af_heart",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
filename_stem = datetime.datetime.now().strftime(format="%Y-%m-%d_%H-%M-%s")
|
|
15
|
+
speech_generator = registry.get_model_class(config)
|
|
16
|
+
print("loading model...")
|
|
17
|
+
speech_generator.load()
|
|
18
|
+
print("warmup run...")
|
|
19
|
+
speech_generator.warmup()
|
|
20
|
+
print("running inference...")
|
|
21
|
+
output = speech_generator.generate()
|
|
22
|
+
for chunk in output.batch:
|
|
23
|
+
chunk.play(output_path + filename_stem)
|
|
24
|
+
print("tearing down...")
|
|
25
|
+
speech_generator.teardown()
|
|
26
|
+
print("done!")
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import datetime
|
|
3
|
+
|
|
4
|
+
from kokoro import KPipeline
|
|
5
|
+
|
|
6
|
+
from basejenerator.generator_output import GeneratorOutput
|
|
7
|
+
|
|
8
|
+
from speechjenerator.artifacts.wav_artifact import WavArtifact
|
|
9
|
+
from speechjenerator.registry import register_model
|
|
10
|
+
from speechjenerator.core.base_speech_generator import BaseSpeechGenerator
|
|
11
|
+
|
|
12
|
+
@register_model("kokoro")
|
|
13
|
+
class Kokoro(BaseSpeechGenerator):
|
|
14
|
+
"""
|
|
15
|
+
Concrete implementation of BaseClass for Kokoro.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, config):
|
|
20
|
+
"""
|
|
21
|
+
Initializes the generator.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
config (dict): Configuration dictionary. Must include standard BaseClass
|
|
25
|
+
keys plus model-specific keys.
|
|
26
|
+
"""
|
|
27
|
+
super().__init__(config)
|
|
28
|
+
self.model = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def load(self):
|
|
32
|
+
"""
|
|
33
|
+
Loads Kokoro pipeline.
|
|
34
|
+
"""
|
|
35
|
+
pipeline = KPipeline(
|
|
36
|
+
lang_code=self.config["lang"],
|
|
37
|
+
device=self.device,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
self.model = pipeline
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def prepare(self):
|
|
44
|
+
"""
|
|
45
|
+
"""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def warmup(self):
|
|
50
|
+
self.generate_impl({
|
|
51
|
+
"text": "Do or do not. There is no try."
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def generate_impl(self, config = None):
|
|
56
|
+
"""
|
|
57
|
+
"""
|
|
58
|
+
inf_config = self.config.copy()
|
|
59
|
+
if config:
|
|
60
|
+
inf_config.update(config)
|
|
61
|
+
|
|
62
|
+
artifacts = []
|
|
63
|
+
for i, (gs, ps, audio) in enumerate(
|
|
64
|
+
self.model(
|
|
65
|
+
text=inf_config["text"],
|
|
66
|
+
voice=inf_config["voice"]
|
|
67
|
+
)
|
|
68
|
+
):
|
|
69
|
+
artifacts.append(
|
|
70
|
+
WavArtifact(
|
|
71
|
+
data=audio,
|
|
72
|
+
item_extras={
|
|
73
|
+
"text": gs,
|
|
74
|
+
"phonemes": ps,
|
|
75
|
+
"chunk_index": i
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return GeneratorOutput(artifacts)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_params_schema(self):
|
|
84
|
+
class ParamsSchema(BaseModel):
|
|
85
|
+
text: str = ""
|
|
86
|
+
lang: str = ""
|
|
87
|
+
|
|
88
|
+
return ParamsSchema
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Implements a simple decorator-based model registry for dynamically managing
|
|
3
|
+
different model classes.
|
|
4
|
+
|
|
5
|
+
This system allows concrete subclasses to register themselves
|
|
6
|
+
using a specific string key, enabling the application to instantiate the
|
|
7
|
+
correct class based on a configuration setting without explicit imports.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
MODEL_REGISTRY = {}
|
|
11
|
+
|
|
12
|
+
def register_model(name):
|
|
13
|
+
"""
|
|
14
|
+
A decorator factory used to register a subclass.
|
|
15
|
+
|
|
16
|
+
The decorated class is stored in the global MODEL_REGISTRY dictionary
|
|
17
|
+
under the provided `name`.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
name (str): The string key used to reference the model class
|
|
21
|
+
in the configuration.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Callable: A decorator function that takes a class and registers it.
|
|
25
|
+
"""
|
|
26
|
+
def decorator(cls):
|
|
27
|
+
MODEL_REGISTRY[name] = cls
|
|
28
|
+
return cls
|
|
29
|
+
return decorator
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_model_class(config):
|
|
33
|
+
"""
|
|
34
|
+
Retrieves and instantiates the correct class based on the
|
|
35
|
+
configuration dictionary.
|
|
36
|
+
|
|
37
|
+
It looks up the class in MODEL_REGISTRY using the key found in
|
|
38
|
+
`config['model']`.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
config (dict): The configuration dictionary, which must contain a
|
|
42
|
+
'model' key corresponding to a registered model name.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
BaseClass: An instantiated object of the registered generator class.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
KeyError: If the value of `config['model']` is not found in the
|
|
49
|
+
MODEL_REGISTRY.
|
|
50
|
+
"""
|
|
51
|
+
ModelClass = MODEL_REGISTRY[config["model"]]
|
|
52
|
+
model_object = ModelClass(config)
|
|
53
|
+
|
|
54
|
+
return model_object
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import datetime
|
|
3
|
+
|
|
4
|
+
from gtts import gTTS
|
|
5
|
+
|
|
6
|
+
from basejenerator.generator_output import GeneratorOutput
|
|
7
|
+
|
|
8
|
+
from speechjenerator.artifacts.gtts_artifact import GTTSArtifact
|
|
9
|
+
from speechjenerator.registry import register_model
|
|
10
|
+
from speechjenerator.core.base_speech_generator import BaseSpeechGenerator
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@register_model("gtts")
|
|
14
|
+
class GTTS(BaseSpeechGenerator):
|
|
15
|
+
"""
|
|
16
|
+
Concrete implementation of BaseClass for gTTS.
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, config):
|
|
21
|
+
"""
|
|
22
|
+
Initializes the gTTS generator.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
config (dict): Configuration dictionary. Must include standard BaseClass
|
|
26
|
+
keys plus model-specific keys.
|
|
27
|
+
"""
|
|
28
|
+
super().__init__(config)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def load(self):
|
|
32
|
+
"""
|
|
33
|
+
No-op - gTTS uses a remote API (Google Translate).
|
|
34
|
+
"""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def prepare(self):
|
|
39
|
+
"""
|
|
40
|
+
No-op - gTTS uses a remote API (Google Translate).
|
|
41
|
+
"""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def warmup(self):
|
|
46
|
+
"""
|
|
47
|
+
No-op - gTTS uses a remote API (Google Translate).
|
|
48
|
+
"""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def generate_impl(self, text=None, lang=None):
|
|
53
|
+
"""
|
|
54
|
+
"""
|
|
55
|
+
if not text:
|
|
56
|
+
text = self.config["text"]
|
|
57
|
+
|
|
58
|
+
if not lang:
|
|
59
|
+
lang = self.config["lang"]
|
|
60
|
+
|
|
61
|
+
gtts_obj = gTTS(text=text, lang=lang)
|
|
62
|
+
|
|
63
|
+
artifacts = self._quick_wrap([gtts_obj], [{}], GTTSArtifact)
|
|
64
|
+
|
|
65
|
+
return GeneratorOutput(artifacts)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def teardown(self):
|
|
69
|
+
"""
|
|
70
|
+
No-op - gTTS uses a remote API (Google Translate).
|
|
71
|
+
"""
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_params_schema(self):
|
|
76
|
+
class ParamsSchema(BaseModel):
|
|
77
|
+
text: str = ""
|
|
78
|
+
lang: str = ""
|
|
79
|
+
|
|
80
|
+
return ParamsSchema
|
|
File without changes
|