mirror of
https://github.com/rhasspy/piper-sample-generator.git
synced 2026-08-27 18:15:58 -04:00
Compare commits
20 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2971426a55 | ||
|
|
275077a1a3 | ||
|
|
1a8c49bd29 | ||
|
|
c9d824c0e2 | ||
|
|
ded9350eaf | ||
|
|
66ec23fa49 | ||
|
|
c0d2dd9ea4 | ||
|
|
942002d0ee | ||
|
|
395aef7dbc | ||
|
|
df5799c141 | ||
|
|
af7ac7aae2 | ||
|
|
5e67370ab3 | ||
|
|
4d7e4b390c | ||
|
|
4057c1a620 | ||
|
|
9c1019c932 | ||
|
|
213d4d561a | ||
|
|
172d7b5cae | ||
|
|
315e555f49 | ||
|
|
2dbff77c61 | ||
|
|
77d8c0d4b3 |
25 changed files with 2713 additions and 471 deletions
19
CHANGELOG.md
Normal file
19
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 3.2.0
|
||||||
|
|
||||||
|
- Refactor as `piper_sample_generator` package
|
||||||
|
|
||||||
|
## 3.1.0
|
||||||
|
|
||||||
|
- Support MPS acceleration on Apple Silicon
|
||||||
|
- Add `--phoneme-input` flag
|
||||||
|
|
||||||
|
## 3.0.0
|
||||||
|
|
||||||
|
- Move phonemization to piper 1.3.0 (piper-phonemize is deprecated)
|
||||||
|
- Move to PyTorch 2
|
||||||
|
- Add support for using Piper voices (`.onnx`) directly
|
||||||
|
- Allow multiple `--model` for Piper voices (`.onnx`)
|
||||||
|
- Remove silence trimming
|
||||||
|
- Remove `min-phoneme-count`
|
||||||
78
README.md
78
README.md
|
|
@ -1,35 +1,51 @@
|
||||||
# Piper Sample Generator
|
# Piper Sample Generator
|
||||||
|
|
||||||
Generates samples using [Piper](https://github.com/rhasspy/piper/) for training a wake word system like [openWakeWord](https://github.com/dscripka/openWakeWord).
|
Generate spoken audio samples using [Piper][piper] for training a wake word system like [openWakeWord][] or [microWakeWord][].
|
||||||
|
|
||||||
|
Supports normal [Piper voices][piper voices] or a special [generator][] that can mix speaker embeddings (English only).
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Create a virtual environment and install the requirements:
|
|
||||||
|
|
||||||
``` sh
|
``` sh
|
||||||
git clone https://github.com/rhasspy/piper-sample-generator.git
|
pip install piper-sample-generator
|
||||||
cd piper-sample-generator/
|
|
||||||
|
|
||||||
python3 -m venv .venv
|
|
||||||
source .venv/bin/activate
|
|
||||||
python3 -m pip install --upgrade pip
|
|
||||||
python3 -m pip install -r requirements.txt
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Download the LibriTTS-R generator (exported from [checkpoint](https://huggingface.co/datasets/rhasspy/piper-checkpoints/tree/main/en/en_US/libritts_r/medium)):
|
## Piper Voices
|
||||||
|
|
||||||
|
Download one or more [Piper voices][piper voices] (both the `.onnx` and `.onnx.json` files for each voice). [Audio samples][piper samples] are available.
|
||||||
|
|
||||||
|
As an example, we'll download the U.S. English "lessac" voice in medium quality:
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
mkdir -p voices
|
||||||
|
wget -O voices/en_US-lessac-medium.onnx 'https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/medium/en_US-lessac-medium.onnx?download=true'
|
||||||
|
wget -O voices/en_US-lessac-medium.onnx.json 'https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/lessac/medium/en_US-lessac-medium.onnx.json?download=true'
|
||||||
|
```
|
||||||
|
|
||||||
|
Generate a small set of samples with the CLI:
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
python3 -m piper_sample_generator 'okay piper.' --model voices/en_US-lessac-medium.onnx --max-samples 10 --output-dir okay_piper/
|
||||||
|
```
|
||||||
|
|
||||||
|
Check the `okay_piper/` directory for 10 WAV files (named `0.wav` to `9.wav`).
|
||||||
|
|
||||||
|
You can add multiple `--model <voice>` arguments to cycle between different voices when generating samples.
|
||||||
|
|
||||||
|
See `--help` for more options, including `--length-scales` (speaking speeds).
|
||||||
|
|
||||||
|
## Generator
|
||||||
|
|
||||||
|
Download the LibriTTS-R generator (exported from [checkpoint][]):
|
||||||
|
|
||||||
``` sh
|
``` sh
|
||||||
wget -O models/en-us-libritts-high.pt 'https://github.com/rhasspy/piper-sample-generator/releases/download/v2.0.0/en_US-libritts_r-medium.pt'
|
wget -O models/en-us-libritts-high.pt 'https://github.com/rhasspy/piper-sample-generator/releases/download/v2.0.0/en_US-libritts_r-medium.pt'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Run
|
|
||||||
|
|
||||||
Generate a small set of samples with the CLI:
|
Generate a small set of samples with the CLI:
|
||||||
|
|
||||||
``` sh
|
``` sh
|
||||||
python3 generate_samples.py 'okay, piper.' --max-samples 10 --output-dir okay_piper/
|
python3 -m piper_sample_generator 'okay piper.' --model models/en-us-libritts-high.pt --max-samples 10 --output-dir okay_piper/
|
||||||
```
|
```
|
||||||
|
|
||||||
Check the `okay_piper/` directory for 10 WAV files (named `0.wav` to `9.wav`).
|
Check the `okay_piper/` directory for 10 WAV files (named `0.wav` to `9.wav`).
|
||||||
|
|
@ -37,38 +53,38 @@ Check the `okay_piper/` directory for 10 WAV files (named `0.wav` to `9.wav`).
|
||||||
Generation can be much faster and more efficient if you have a GPU available and PyTorch is configured to use it. In this case, increase the batch size:
|
Generation can be much faster and more efficient if you have a GPU available and PyTorch is configured to use it. In this case, increase the batch size:
|
||||||
|
|
||||||
``` sh
|
``` sh
|
||||||
python3 generate_samples.py 'okay, piper.' --max-samples 100 --batch-size 10 --output-dir okay_piper/
|
python3 -m piper_sample_generator 'okay piper.' --model models/en-us-libritts-high.pt --max-samples 100 --batch-size 10 --output-dir okay_piper/
|
||||||
```
|
```
|
||||||
|
|
||||||
On an NVidia 2080 Ti with 11GB, a batch size of 100 was possible (generating approximately 100 samples per second).
|
On an NVidia 2080 Ti with 11GB, a batch size of 100 was possible (generating approximately 100 samples per second).
|
||||||
|
|
||||||
Setting `--max-speakers` to a value less than 904 (the number of speakers LibriTTS) is recommended. Because very few samples of later speakers were in the original dataset, using them can cause audio artifacts.
|
Setting `--max-speakers` to a value less than 904 (the number of speakers LibriTTS) is recommended. Because very few samples of later speakers were in the original dataset, using them can cause audio artifacts.
|
||||||
|
|
||||||
See `--help` for more options, including adjust the `--length-scales` (speaking speeds) and `--slerp-weights` (speaker blending) which are cycled per batch.
|
See `--help` for more options, including the `--length-scales` (speaking speeds) and `--slerp-weights` (speaker blending) which are cycled per batch.
|
||||||
|
|
||||||
Alternatively, you can import the generate function into another Python script:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from generate_samples import generate_samples # make sure to add this to your Python path as needed
|
|
||||||
|
|
||||||
generate_samples(text = ["okay, piper"], max_samples = 100, output_dir = output_dir, batch_size=10)
|
|
||||||
```
|
|
||||||
|
|
||||||
There are some additional arguments available when importing the function directly, see the docstring of `generate_sample` for more information.
|
|
||||||
|
|
||||||
### Augmentation
|
### Augmentation
|
||||||
|
|
||||||
Once you have samples generating, you can augment them using [audiomentation](https://iver56.github.io/audiomentations/):
|
Once you have samples generated, you can augment them using [audiomentation](https://iver56.github.io/audiomentations/):
|
||||||
|
|
||||||
``` sh
|
``` sh
|
||||||
python3 augment.py --sample-rate 16000 okay_piper/ okay_piper_augmented/
|
python3 -m piper_sample_generator.augment --sample-rate 22050 okay_piper/ okay_piper_augmented/
|
||||||
```
|
```
|
||||||
|
|
||||||
This will do several things to each sample:
|
This will do several things to each sample:
|
||||||
|
|
||||||
1. Randomly decrease the volume
|
1. Randomly decrease the volume
|
||||||
* The original samples are normalized, so different volume levels are needed
|
* The original samples are normalized, so different volume levels are needed
|
||||||
2. Randomly [apply an impulse response](https://iver56.github.io/audiomentations/waveform_transforms/apply_impulse_response/) using the files in `impulses/`
|
2. Randomly apply an [impulse response][] using the files in `piper_sample_generator/impulses/`
|
||||||
* Change the acoustics of the sample to sound like the speaker was in a room with echo or using a poor quality microphone
|
* Change the acoustics of the sample to sound like the speaker was in a room with echo or using a poor quality microphone
|
||||||
3. Resample to 16Khz for training (e.g., [openWakeWord](https://github.com/dscripka/openWakeWord))
|
3. Resample to 16Khz for training (e.g., [openWakeWord][])
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Links -->
|
||||||
|
[piper]: https://github.com/OHF-Voice/piper1-gpl/
|
||||||
|
[openWakeWord]: https://github.com/dscripka/openWakeWord
|
||||||
|
[microWakeWord]: https://github.com/kahrendt/microWakeWord/
|
||||||
|
[piper voices]: https://huggingface.co/rhasspy/piper-voices
|
||||||
|
[generator]: https://github.com/rhasspy/piper-sample-generator/releases/download/v2.0.0/en_US-libritts_r-medium.pt
|
||||||
|
[piper samples]: https://rhasspy.github.io/piper-samples/
|
||||||
|
[checkpoint]: https://huggingface.co/datasets/rhasspy/piper-checkpoints/tree/main/en/en_US/libritts_r/medium
|
||||||
|
[impulse response]: https://iver56.github.io/audiomentations/waveform_transforms/apply_impulse_response/
|
||||||
|
|
|
||||||
|
|
@ -1,407 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import argparse
|
|
||||||
import gc
|
|
||||||
import itertools as it
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import wave
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import List, Union
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import torch
|
|
||||||
import torchaudio
|
|
||||||
import webrtcvad
|
|
||||||
from piper_phonemize import phonemize_espeak
|
|
||||||
|
|
||||||
from piper_train.vits import commons
|
|
||||||
|
|
||||||
_DIR = Path(__file__).parent
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
|
|
||||||
# Main generation function
|
|
||||||
def generate_samples(
|
|
||||||
text: Union[List, str],
|
|
||||||
output_dir: str,
|
|
||||||
max_samples: int = None,
|
|
||||||
file_names: List[str] = [],
|
|
||||||
model: str = os.path.join(
|
|
||||||
Path(__file__).parent, "models", "en_US-libritts_r-medium.pt"
|
|
||||||
),
|
|
||||||
batch_size: int = 1,
|
|
||||||
slerp_weights: List[float] = [0.5],
|
|
||||||
length_scales: List[float] = [0.75, 1, 1.25],
|
|
||||||
noise_scales: List[float] = [0.667],
|
|
||||||
noise_scale_ws: List[float] = [0.8],
|
|
||||||
max_speakers: float = None,
|
|
||||||
verbose: bool = False,
|
|
||||||
auto_reduce_batch_size: bool = False,
|
|
||||||
**kwargs,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Generate synthetic speech clips, saving the clips to the specified output directory.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
text (List[str]): The text to convert into speech. Can be either a
|
|
||||||
a list of strings, or a path to a file with text on each line.
|
|
||||||
output_dir (str): The location to save the generated clips.
|
|
||||||
max_samples (int): The maximum number of samples to generate.
|
|
||||||
file_names (List[str]): The names to use when saving the files. Must be the same length
|
|
||||||
as the `text` argument, if a list.
|
|
||||||
model (str): The path to the STT model to use for generation.
|
|
||||||
batch_size (int): The batch size to use when generated the clips
|
|
||||||
slerp_weights (List[float]): The weights to use when mixing speakers via SLERP.
|
|
||||||
length_scales (List[float]): Controls the average duration/speed of the generated speech.
|
|
||||||
noise_scales (List[float]): A parameter for overall variability of the generated speech.
|
|
||||||
noise_scale_ws (List[float]): A parameter for the stochastic duration of words/phonemes.
|
|
||||||
max_speakers (int): The maximum speaker number to use, if the model is multi-speaker.
|
|
||||||
verbose (bool): Enable or disable more detailed logging messages (default: False).
|
|
||||||
auto_reduce_batch_size (bool): Automatically and temporarily reduce the batch size
|
|
||||||
if CUDA OOM errors are detected, and try to resume generation.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
None
|
|
||||||
"""
|
|
||||||
|
|
||||||
if max_samples is None:
|
|
||||||
max_samples = len(text)
|
|
||||||
|
|
||||||
_LOGGER.debug("Loading %s", model)
|
|
||||||
model_path = Path(model)
|
|
||||||
model = torch.load(model_path)
|
|
||||||
model.eval()
|
|
||||||
_LOGGER.info("Successfully loaded the model")
|
|
||||||
|
|
||||||
if torch.cuda.is_available():
|
|
||||||
model.cuda()
|
|
||||||
_LOGGER.debug("CUDA available, using GPU")
|
|
||||||
|
|
||||||
output_dir = Path(output_dir)
|
|
||||||
output_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
config_path = f"{model_path}.json"
|
|
||||||
with open(config_path, "r", encoding="utf-8") as config_file:
|
|
||||||
config = json.load(config_file)
|
|
||||||
|
|
||||||
voice = config["espeak"]["voice"]
|
|
||||||
sample_rate = config["audio"]["sample_rate"]
|
|
||||||
num_speakers = config["num_speakers"]
|
|
||||||
if max_speakers is not None:
|
|
||||||
num_speakers = min(num_speakers, max_speakers)
|
|
||||||
|
|
||||||
max_len = None
|
|
||||||
|
|
||||||
sample_idx = 0
|
|
||||||
is_done = False
|
|
||||||
settings_iter = it.cycle(
|
|
||||||
it.product(
|
|
||||||
slerp_weights,
|
|
||||||
length_scales,
|
|
||||||
noise_scales,
|
|
||||||
noise_scale_ws,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Define resampler to get to 16khz (https://pytorch.org/audio/stable/tutorials/audio_resampling_tutorial.html#kaiser-best)
|
|
||||||
sample_rate = 22050
|
|
||||||
resample_rate = 16000
|
|
||||||
resampler = torchaudio.transforms.Resample(
|
|
||||||
sample_rate,
|
|
||||||
resample_rate,
|
|
||||||
lowpass_filter_width=64,
|
|
||||||
rolloff=0.9475937167399596,
|
|
||||||
resampling_method="kaiser_window",
|
|
||||||
beta=14.769656459379492,
|
|
||||||
)
|
|
||||||
|
|
||||||
speakers_iter = it.cycle(it.product(range(num_speakers), range(num_speakers)))
|
|
||||||
speakers_batch = list(it.islice(speakers_iter, 0, batch_size))
|
|
||||||
if isinstance(text, str) and os.path.exists(text):
|
|
||||||
texts = it.cycle(
|
|
||||||
[
|
|
||||||
i.strip()
|
|
||||||
for i in open(text, "r", encoding="utf-8").readlines()
|
|
||||||
if len(i.strip()) > 0
|
|
||||||
]
|
|
||||||
)
|
|
||||||
elif isinstance(text, list):
|
|
||||||
texts = it.cycle(text)
|
|
||||||
else:
|
|
||||||
texts = it.cycle([text])
|
|
||||||
|
|
||||||
if file_names:
|
|
||||||
file_names = it.cycle(file_names)
|
|
||||||
|
|
||||||
batch_idx = 0
|
|
||||||
while speakers_batch:
|
|
||||||
if is_done:
|
|
||||||
break
|
|
||||||
|
|
||||||
batch_size = len(speakers_batch)
|
|
||||||
slerp_weight, length_scale, noise_scale, noise_scale_w = next(settings_iter)
|
|
||||||
|
|
||||||
with torch.no_grad():
|
|
||||||
speaker_1 = torch.LongTensor([s[0] for s in speakers_batch])
|
|
||||||
speaker_2 = torch.LongTensor([s[1] for s in speakers_batch])
|
|
||||||
|
|
||||||
phoneme_ids = [
|
|
||||||
get_phonemes(voice, config, next(texts), verbose)
|
|
||||||
for i in range(batch_size)
|
|
||||||
]
|
|
||||||
|
|
||||||
def right_pad_lists(lists):
|
|
||||||
max_length = max(len(lst) for lst in lists)
|
|
||||||
padded_lists = []
|
|
||||||
for lst in lists:
|
|
||||||
padded_l = lst + [1] * (
|
|
||||||
max_length - len(lst)
|
|
||||||
) # phoneme 1 (corresponding to '^' character seems to work best)
|
|
||||||
padded_lists.append(padded_l)
|
|
||||||
return padded_lists
|
|
||||||
|
|
||||||
phoneme_ids = right_pad_lists(phoneme_ids)
|
|
||||||
|
|
||||||
if auto_reduce_batch_size:
|
|
||||||
oom_error = True
|
|
||||||
counter = 1
|
|
||||||
while oom_error is True:
|
|
||||||
try:
|
|
||||||
audio = generate_audio(
|
|
||||||
model,
|
|
||||||
speaker_1[0 : batch_size // counter],
|
|
||||||
speaker_2[0 : batch_size // counter],
|
|
||||||
phoneme_ids[0 : batch_size // counter],
|
|
||||||
slerp_weight,
|
|
||||||
noise_scale,
|
|
||||||
noise_scale_w,
|
|
||||||
length_scale,
|
|
||||||
max_len,
|
|
||||||
)
|
|
||||||
oom_error = False
|
|
||||||
except torch.cuda.OutOfMemoryError:
|
|
||||||
torch.cuda.empty_cache()
|
|
||||||
gc.collect()
|
|
||||||
counter += 1 # reduce batch size to avoid OOM errors
|
|
||||||
else:
|
|
||||||
audio = generate_audio(
|
|
||||||
model,
|
|
||||||
speaker_1,
|
|
||||||
speaker_2,
|
|
||||||
phoneme_ids,
|
|
||||||
slerp_weight,
|
|
||||||
noise_scale,
|
|
||||||
noise_scale_w,
|
|
||||||
length_scale,
|
|
||||||
max_len,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Resample audio
|
|
||||||
audio = resampler(audio.cpu()).numpy()
|
|
||||||
|
|
||||||
audio_int16 = audio_float_to_int16(audio)
|
|
||||||
for audio_idx in range(audio_int16.shape[0]):
|
|
||||||
# Use webrtcvad to trip silence from the clips
|
|
||||||
audio_data = remove_silence(audio_int16[audio_idx].flatten())[None,]
|
|
||||||
|
|
||||||
if isinstance(file_names, it.cycle):
|
|
||||||
wav_path = output_dir / next(file_names)
|
|
||||||
else:
|
|
||||||
wav_path = output_dir / f"{sample_idx}.wav"
|
|
||||||
with wave.open(str(wav_path), "wb") as wav_file:
|
|
||||||
wav_file.setframerate(resample_rate)
|
|
||||||
wav_file.setsampwidth(2)
|
|
||||||
wav_file.setnchannels(1)
|
|
||||||
wav_file.writeframes(audio_data)
|
|
||||||
|
|
||||||
sample_idx += 1
|
|
||||||
if sample_idx >= max_samples:
|
|
||||||
is_done = True
|
|
||||||
break
|
|
||||||
|
|
||||||
# print(f"Batch {batch_idx +1}/{max_samples//batch_size} complete", " "*200, end='\r')
|
|
||||||
|
|
||||||
# Next batch
|
|
||||||
_LOGGER.debug(f"Batch {batch_idx +1}/{max_samples//batch_size} complete")
|
|
||||||
speakers_batch = list(it.islice(speakers_iter, 0, batch_size))
|
|
||||||
batch_idx += 1
|
|
||||||
|
|
||||||
_LOGGER.info("Done")
|
|
||||||
|
|
||||||
|
|
||||||
def remove_silence(x, frame_duration=0.030, sample_rate=16000, min_start=2000):
|
|
||||||
"""Uses webrtc voice activity detection to remove silence from the clips"""
|
|
||||||
vad = webrtcvad.Vad(0)
|
|
||||||
if x.dtype == np.float32 or x.dtype == np.float64:
|
|
||||||
x = (x * 32767).astype(np.int16)
|
|
||||||
x_new = x[0:min_start].tolist()
|
|
||||||
step_size = int(sample_rate * frame_duration)
|
|
||||||
for i in range(min_start, x.shape[0] - step_size, step_size):
|
|
||||||
vad_res = vad.is_speech(x[i : i + step_size].tobytes(), sample_rate)
|
|
||||||
if vad_res:
|
|
||||||
x_new.extend(x[i : i + step_size].tolist())
|
|
||||||
return np.array(x_new).astype(np.int16)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_audio(
|
|
||||||
model,
|
|
||||||
speaker_1,
|
|
||||||
speaker_2,
|
|
||||||
phoneme_ids,
|
|
||||||
slerp_weight,
|
|
||||||
noise_scale,
|
|
||||||
noise_scale_w,
|
|
||||||
length_scale,
|
|
||||||
max_len,
|
|
||||||
):
|
|
||||||
x = torch.LongTensor(phoneme_ids)
|
|
||||||
x_lengths = torch.LongTensor([len(i) for i in phoneme_ids])
|
|
||||||
|
|
||||||
if torch.cuda.is_available():
|
|
||||||
speaker_1 = speaker_1.cuda()
|
|
||||||
speaker_2 = speaker_2.cuda()
|
|
||||||
x = x.cuda()
|
|
||||||
x_lengths = x_lengths.cuda()
|
|
||||||
|
|
||||||
x, m_p_orig, logs_p_orig, x_mask = model.enc_p(x, x_lengths)
|
|
||||||
emb0 = model.emb_g(speaker_1)
|
|
||||||
emb1 = model.emb_g(speaker_2)
|
|
||||||
g = slerp(emb0, emb1, slerp_weight).unsqueeze(-1) # [b, h, 1]
|
|
||||||
|
|
||||||
if model.use_sdp:
|
|
||||||
logw = model.dp(x, x_mask, g=g, reverse=True, noise_scale=noise_scale_w)
|
|
||||||
else:
|
|
||||||
logw = model.dp(x, x_mask, g=g)
|
|
||||||
w = torch.exp(logw) * x_mask * length_scale
|
|
||||||
w_ceil = torch.ceil(w)
|
|
||||||
y_lengths = torch.clamp_min(torch.sum(w_ceil, [1, 2]), 1).long()
|
|
||||||
y_mask = torch.unsqueeze(
|
|
||||||
commons.sequence_mask(y_lengths, y_lengths.max()), 1
|
|
||||||
).type_as(x_mask)
|
|
||||||
attn_mask = torch.unsqueeze(x_mask, 2) * torch.unsqueeze(y_mask, -1)
|
|
||||||
attn = commons.generate_path(w_ceil, attn_mask)
|
|
||||||
|
|
||||||
m_p = torch.matmul(attn.squeeze(1), m_p_orig.transpose(1, 2)).transpose(
|
|
||||||
1, 2
|
|
||||||
) # [b, t', t], [b, t, d] -> [b, d, t']
|
|
||||||
logs_p = torch.matmul(attn.squeeze(1), logs_p_orig.transpose(1, 2)).transpose(
|
|
||||||
1, 2
|
|
||||||
) # [b, t', t], [b, t, d] -> [b, d, t']
|
|
||||||
|
|
||||||
z_p = m_p + torch.randn_like(m_p) * torch.exp(logs_p) * noise_scale
|
|
||||||
z = model.flow(z_p, y_mask, g=g, reverse=True)
|
|
||||||
o = model.dec((z * y_mask)[:, :, :max_len], g=g)
|
|
||||||
|
|
||||||
audio = o
|
|
||||||
return audio
|
|
||||||
|
|
||||||
|
|
||||||
def get_phonemes(voice, config, text, verbose):
|
|
||||||
# Combine all sentences
|
|
||||||
phonemes = [
|
|
||||||
p
|
|
||||||
for sentence_phonemes in phonemize_espeak(text, voice)
|
|
||||||
for p in sentence_phonemes
|
|
||||||
]
|
|
||||||
if verbose is True:
|
|
||||||
_LOGGER.debug("Phonemes: %s", phonemes)
|
|
||||||
|
|
||||||
id_map = config["phoneme_id_map"]
|
|
||||||
phoneme_ids = list(id_map["^"])
|
|
||||||
for phoneme in phonemes:
|
|
||||||
p_ids = id_map.get(phoneme)
|
|
||||||
if p_ids is not None:
|
|
||||||
phoneme_ids.extend(p_ids)
|
|
||||||
phoneme_ids.extend(id_map["_"])
|
|
||||||
|
|
||||||
phoneme_ids.extend(id_map["$"])
|
|
||||||
return phoneme_ids
|
|
||||||
|
|
||||||
|
|
||||||
def slerp(v1, v2, t, DOT_THR=0.9995, zdim=-1):
|
|
||||||
"""SLERP for pytorch tensors interpolating `v1` to `v2` with scale of `t`.
|
|
||||||
|
|
||||||
`DOT_THR` determines when the vectors are too close to parallel.
|
|
||||||
If they are too close, then a regular linear interpolation is used.
|
|
||||||
|
|
||||||
`zdim` is the feature dimension over which to compute norms and find angles.
|
|
||||||
For example: if a sequence of 5 vectors is input with shape [5, 768]
|
|
||||||
Then `zdim = 1` or `zdim = -1` computes SLERP along the feature dim of 768.
|
|
||||||
|
|
||||||
Theory Reference:
|
|
||||||
https://splines.readthedocs.io/en/latest/rotation/slerp.html
|
|
||||||
PyTorch reference:
|
|
||||||
https://discuss.pytorch.org/t/help-regarding-slerp-function-for-generative-model-sampling/32475/3
|
|
||||||
Numpy reference:
|
|
||||||
https://gist.github.com/dvschultz/3af50c40df002da3b751efab1daddf2c
|
|
||||||
"""
|
|
||||||
|
|
||||||
# take the dot product between normalized vectors
|
|
||||||
v1_norm = v1 / torch.norm(v1, dim=zdim, keepdim=True)
|
|
||||||
v2_norm = v2 / torch.norm(v2, dim=zdim, keepdim=True)
|
|
||||||
dot = (v1_norm * v2_norm).sum(zdim)
|
|
||||||
|
|
||||||
# if the vectors are too close, return a simple linear interpolation
|
|
||||||
if (torch.abs(dot) > DOT_THR).any():
|
|
||||||
res = (1 - t) * v1 + t * v2
|
|
||||||
|
|
||||||
# else apply SLERP
|
|
||||||
else:
|
|
||||||
# compute the angle terms we need
|
|
||||||
theta = torch.acos(dot)
|
|
||||||
theta_t = theta * t
|
|
||||||
sin_theta = torch.sin(theta)
|
|
||||||
sin_theta_t = torch.sin(theta_t)
|
|
||||||
|
|
||||||
# compute the sine scaling terms for the vectors
|
|
||||||
s1 = torch.sin(theta - theta_t) / sin_theta
|
|
||||||
s2 = sin_theta_t / sin_theta
|
|
||||||
|
|
||||||
# interpolate the vectors
|
|
||||||
res = (s1.unsqueeze(zdim) * v1) + (s2.unsqueeze(zdim) * v2)
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def audio_float_to_int16(
|
|
||||||
audio: np.ndarray, max_wav_value: float = 32767.0
|
|
||||||
) -> np.ndarray:
|
|
||||||
"""Normalize audio and convert to int16 range"""
|
|
||||||
audio_norm = audio * (max_wav_value / max(0.01, np.max(np.abs(audio))))
|
|
||||||
audio_norm = np.clip(audio_norm, -max_wav_value, max_wav_value)
|
|
||||||
audio_norm = audio_norm.astype("int16")
|
|
||||||
return audio_norm
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Get command line arguments
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument("text")
|
|
||||||
parser.add_argument("--max-samples", required=True, type=int)
|
|
||||||
parser.add_argument(
|
|
||||||
"--model", default=_DIR / "models" / "en_US-libritts_r-medium.pt"
|
|
||||||
)
|
|
||||||
parser.add_argument("--batch-size", type=int, default=1)
|
|
||||||
parser.add_argument("--slerp-weights", nargs="+", type=float, default=[0.5])
|
|
||||||
parser.add_argument(
|
|
||||||
"--length-scales", nargs="+", type=float, default=[1.0, 0.75, 1.25, 1.4]
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--noise-scales",
|
|
||||||
nargs="+",
|
|
||||||
type=float,
|
|
||||||
default=[0.667, 0.75, 0.85, 0.9, 1.0, 1.4],
|
|
||||||
)
|
|
||||||
parser.add_argument("--noise-scale-ws", nargs="+", type=float, default=[0.8])
|
|
||||||
parser.add_argument("--output-dir", default="output")
|
|
||||||
parser.add_argument(
|
|
||||||
"--max-speakers",
|
|
||||||
type=int,
|
|
||||||
help="Maximum number of speakers to use (default: all)",
|
|
||||||
)
|
|
||||||
args = parser.parse_args().__dict__
|
|
||||||
|
|
||||||
# Generate speech
|
|
||||||
generate_samples(**args)
|
|
||||||
740
models/de_DE-mls-medium.pt.json
Executable file
740
models/de_DE-mls-medium.pt.json
Executable file
|
|
@ -0,0 +1,740 @@
|
||||||
|
{
|
||||||
|
"dataset": "mls",
|
||||||
|
"audio": {
|
||||||
|
"sample_rate": 22050,
|
||||||
|
"quality": "medium"
|
||||||
|
},
|
||||||
|
"espeak": {
|
||||||
|
"voice": "de"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "de_DE"
|
||||||
|
},
|
||||||
|
"inference": {
|
||||||
|
"noise_scale": 0.333,
|
||||||
|
"length_scale": 1,
|
||||||
|
"noise_w": 0.333
|
||||||
|
},
|
||||||
|
"phoneme_type": "espeak",
|
||||||
|
"phoneme_map": {},
|
||||||
|
"phoneme_id_map": {
|
||||||
|
" ": [
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"!": [
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"\"": [
|
||||||
|
150
|
||||||
|
],
|
||||||
|
"#": [
|
||||||
|
149
|
||||||
|
],
|
||||||
|
"$": [
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"'": [
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"(": [
|
||||||
|
6
|
||||||
|
],
|
||||||
|
")": [
|
||||||
|
7
|
||||||
|
],
|
||||||
|
",": [
|
||||||
|
8
|
||||||
|
],
|
||||||
|
"-": [
|
||||||
|
9
|
||||||
|
],
|
||||||
|
".": [
|
||||||
|
10
|
||||||
|
],
|
||||||
|
"0": [
|
||||||
|
130
|
||||||
|
],
|
||||||
|
"1": [
|
||||||
|
131
|
||||||
|
],
|
||||||
|
"2": [
|
||||||
|
132
|
||||||
|
],
|
||||||
|
"3": [
|
||||||
|
133
|
||||||
|
],
|
||||||
|
"4": [
|
||||||
|
134
|
||||||
|
],
|
||||||
|
"5": [
|
||||||
|
135
|
||||||
|
],
|
||||||
|
"6": [
|
||||||
|
136
|
||||||
|
],
|
||||||
|
"7": [
|
||||||
|
137
|
||||||
|
],
|
||||||
|
"8": [
|
||||||
|
138
|
||||||
|
],
|
||||||
|
"9": [
|
||||||
|
139
|
||||||
|
],
|
||||||
|
":": [
|
||||||
|
11
|
||||||
|
],
|
||||||
|
";": [
|
||||||
|
12
|
||||||
|
],
|
||||||
|
"?": [
|
||||||
|
13
|
||||||
|
],
|
||||||
|
"X": [
|
||||||
|
156
|
||||||
|
],
|
||||||
|
"^": [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"_": [
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"a": [
|
||||||
|
14
|
||||||
|
],
|
||||||
|
"b": [
|
||||||
|
15
|
||||||
|
],
|
||||||
|
"c": [
|
||||||
|
16
|
||||||
|
],
|
||||||
|
"d": [
|
||||||
|
17
|
||||||
|
],
|
||||||
|
"e": [
|
||||||
|
18
|
||||||
|
],
|
||||||
|
"f": [
|
||||||
|
19
|
||||||
|
],
|
||||||
|
"g": [
|
||||||
|
154
|
||||||
|
],
|
||||||
|
"h": [
|
||||||
|
20
|
||||||
|
],
|
||||||
|
"i": [
|
||||||
|
21
|
||||||
|
],
|
||||||
|
"j": [
|
||||||
|
22
|
||||||
|
],
|
||||||
|
"k": [
|
||||||
|
23
|
||||||
|
],
|
||||||
|
"l": [
|
||||||
|
24
|
||||||
|
],
|
||||||
|
"m": [
|
||||||
|
25
|
||||||
|
],
|
||||||
|
"n": [
|
||||||
|
26
|
||||||
|
],
|
||||||
|
"o": [
|
||||||
|
27
|
||||||
|
],
|
||||||
|
"p": [
|
||||||
|
28
|
||||||
|
],
|
||||||
|
"q": [
|
||||||
|
29
|
||||||
|
],
|
||||||
|
"r": [
|
||||||
|
30
|
||||||
|
],
|
||||||
|
"s": [
|
||||||
|
31
|
||||||
|
],
|
||||||
|
"t": [
|
||||||
|
32
|
||||||
|
],
|
||||||
|
"u": [
|
||||||
|
33
|
||||||
|
],
|
||||||
|
"v": [
|
||||||
|
34
|
||||||
|
],
|
||||||
|
"w": [
|
||||||
|
35
|
||||||
|
],
|
||||||
|
"x": [
|
||||||
|
36
|
||||||
|
],
|
||||||
|
"y": [
|
||||||
|
37
|
||||||
|
],
|
||||||
|
"z": [
|
||||||
|
38
|
||||||
|
],
|
||||||
|
"æ": [
|
||||||
|
39
|
||||||
|
],
|
||||||
|
"ç": [
|
||||||
|
40
|
||||||
|
],
|
||||||
|
"ð": [
|
||||||
|
41
|
||||||
|
],
|
||||||
|
"ø": [
|
||||||
|
42
|
||||||
|
],
|
||||||
|
"ħ": [
|
||||||
|
43
|
||||||
|
],
|
||||||
|
"ŋ": [
|
||||||
|
44
|
||||||
|
],
|
||||||
|
"œ": [
|
||||||
|
45
|
||||||
|
],
|
||||||
|
"ǀ": [
|
||||||
|
46
|
||||||
|
],
|
||||||
|
"ǁ": [
|
||||||
|
47
|
||||||
|
],
|
||||||
|
"ǂ": [
|
||||||
|
48
|
||||||
|
],
|
||||||
|
"ǃ": [
|
||||||
|
49
|
||||||
|
],
|
||||||
|
"ɐ": [
|
||||||
|
50
|
||||||
|
],
|
||||||
|
"ɑ": [
|
||||||
|
51
|
||||||
|
],
|
||||||
|
"ɒ": [
|
||||||
|
52
|
||||||
|
],
|
||||||
|
"ɓ": [
|
||||||
|
53
|
||||||
|
],
|
||||||
|
"ɔ": [
|
||||||
|
54
|
||||||
|
],
|
||||||
|
"ɕ": [
|
||||||
|
55
|
||||||
|
],
|
||||||
|
"ɖ": [
|
||||||
|
56
|
||||||
|
],
|
||||||
|
"ɗ": [
|
||||||
|
57
|
||||||
|
],
|
||||||
|
"ɘ": [
|
||||||
|
58
|
||||||
|
],
|
||||||
|
"ə": [
|
||||||
|
59
|
||||||
|
],
|
||||||
|
"ɚ": [
|
||||||
|
60
|
||||||
|
],
|
||||||
|
"ɛ": [
|
||||||
|
61
|
||||||
|
],
|
||||||
|
"ɜ": [
|
||||||
|
62
|
||||||
|
],
|
||||||
|
"ɞ": [
|
||||||
|
63
|
||||||
|
],
|
||||||
|
"ɟ": [
|
||||||
|
64
|
||||||
|
],
|
||||||
|
"ɠ": [
|
||||||
|
65
|
||||||
|
],
|
||||||
|
"ɡ": [
|
||||||
|
66
|
||||||
|
],
|
||||||
|
"ɢ": [
|
||||||
|
67
|
||||||
|
],
|
||||||
|
"ɣ": [
|
||||||
|
68
|
||||||
|
],
|
||||||
|
"ɤ": [
|
||||||
|
69
|
||||||
|
],
|
||||||
|
"ɥ": [
|
||||||
|
70
|
||||||
|
],
|
||||||
|
"ɦ": [
|
||||||
|
71
|
||||||
|
],
|
||||||
|
"ɧ": [
|
||||||
|
72
|
||||||
|
],
|
||||||
|
"ɨ": [
|
||||||
|
73
|
||||||
|
],
|
||||||
|
"ɪ": [
|
||||||
|
74
|
||||||
|
],
|
||||||
|
"ɫ": [
|
||||||
|
75
|
||||||
|
],
|
||||||
|
"ɬ": [
|
||||||
|
76
|
||||||
|
],
|
||||||
|
"ɭ": [
|
||||||
|
77
|
||||||
|
],
|
||||||
|
"ɮ": [
|
||||||
|
78
|
||||||
|
],
|
||||||
|
"ɯ": [
|
||||||
|
79
|
||||||
|
],
|
||||||
|
"ɰ": [
|
||||||
|
80
|
||||||
|
],
|
||||||
|
"ɱ": [
|
||||||
|
81
|
||||||
|
],
|
||||||
|
"ɲ": [
|
||||||
|
82
|
||||||
|
],
|
||||||
|
"ɳ": [
|
||||||
|
83
|
||||||
|
],
|
||||||
|
"ɴ": [
|
||||||
|
84
|
||||||
|
],
|
||||||
|
"ɵ": [
|
||||||
|
85
|
||||||
|
],
|
||||||
|
"ɶ": [
|
||||||
|
86
|
||||||
|
],
|
||||||
|
"ɸ": [
|
||||||
|
87
|
||||||
|
],
|
||||||
|
"ɹ": [
|
||||||
|
88
|
||||||
|
],
|
||||||
|
"ɺ": [
|
||||||
|
89
|
||||||
|
],
|
||||||
|
"ɻ": [
|
||||||
|
90
|
||||||
|
],
|
||||||
|
"ɽ": [
|
||||||
|
91
|
||||||
|
],
|
||||||
|
"ɾ": [
|
||||||
|
92
|
||||||
|
],
|
||||||
|
"ʀ": [
|
||||||
|
93
|
||||||
|
],
|
||||||
|
"ʁ": [
|
||||||
|
94
|
||||||
|
],
|
||||||
|
"ʂ": [
|
||||||
|
95
|
||||||
|
],
|
||||||
|
"ʃ": [
|
||||||
|
96
|
||||||
|
],
|
||||||
|
"ʄ": [
|
||||||
|
97
|
||||||
|
],
|
||||||
|
"ʈ": [
|
||||||
|
98
|
||||||
|
],
|
||||||
|
"ʉ": [
|
||||||
|
99
|
||||||
|
],
|
||||||
|
"ʊ": [
|
||||||
|
100
|
||||||
|
],
|
||||||
|
"ʋ": [
|
||||||
|
101
|
||||||
|
],
|
||||||
|
"ʌ": [
|
||||||
|
102
|
||||||
|
],
|
||||||
|
"ʍ": [
|
||||||
|
103
|
||||||
|
],
|
||||||
|
"ʎ": [
|
||||||
|
104
|
||||||
|
],
|
||||||
|
"ʏ": [
|
||||||
|
105
|
||||||
|
],
|
||||||
|
"ʐ": [
|
||||||
|
106
|
||||||
|
],
|
||||||
|
"ʑ": [
|
||||||
|
107
|
||||||
|
],
|
||||||
|
"ʒ": [
|
||||||
|
108
|
||||||
|
],
|
||||||
|
"ʔ": [
|
||||||
|
109
|
||||||
|
],
|
||||||
|
"ʕ": [
|
||||||
|
110
|
||||||
|
],
|
||||||
|
"ʘ": [
|
||||||
|
111
|
||||||
|
],
|
||||||
|
"ʙ": [
|
||||||
|
112
|
||||||
|
],
|
||||||
|
"ʛ": [
|
||||||
|
113
|
||||||
|
],
|
||||||
|
"ʜ": [
|
||||||
|
114
|
||||||
|
],
|
||||||
|
"ʝ": [
|
||||||
|
115
|
||||||
|
],
|
||||||
|
"ʟ": [
|
||||||
|
116
|
||||||
|
],
|
||||||
|
"ʡ": [
|
||||||
|
117
|
||||||
|
],
|
||||||
|
"ʢ": [
|
||||||
|
118
|
||||||
|
],
|
||||||
|
"ʦ": [
|
||||||
|
155
|
||||||
|
],
|
||||||
|
"ʰ": [
|
||||||
|
145
|
||||||
|
],
|
||||||
|
"ʲ": [
|
||||||
|
119
|
||||||
|
],
|
||||||
|
"ˈ": [
|
||||||
|
120
|
||||||
|
],
|
||||||
|
"ˌ": [
|
||||||
|
121
|
||||||
|
],
|
||||||
|
"ː": [
|
||||||
|
122
|
||||||
|
],
|
||||||
|
"ˑ": [
|
||||||
|
123
|
||||||
|
],
|
||||||
|
"˞": [
|
||||||
|
124
|
||||||
|
],
|
||||||
|
"ˤ": [
|
||||||
|
146
|
||||||
|
],
|
||||||
|
"̃": [
|
||||||
|
141
|
||||||
|
],
|
||||||
|
"̊": [
|
||||||
|
158
|
||||||
|
],
|
||||||
|
"̝": [
|
||||||
|
157
|
||||||
|
],
|
||||||
|
"̧": [
|
||||||
|
140
|
||||||
|
],
|
||||||
|
"̩": [
|
||||||
|
144
|
||||||
|
],
|
||||||
|
"̪": [
|
||||||
|
142
|
||||||
|
],
|
||||||
|
"̯": [
|
||||||
|
143
|
||||||
|
],
|
||||||
|
"̺": [
|
||||||
|
152
|
||||||
|
],
|
||||||
|
"̻": [
|
||||||
|
153
|
||||||
|
],
|
||||||
|
"β": [
|
||||||
|
125
|
||||||
|
],
|
||||||
|
"ε": [
|
||||||
|
147
|
||||||
|
],
|
||||||
|
"θ": [
|
||||||
|
126
|
||||||
|
],
|
||||||
|
"χ": [
|
||||||
|
127
|
||||||
|
],
|
||||||
|
"ᵻ": [
|
||||||
|
128
|
||||||
|
],
|
||||||
|
"↑": [
|
||||||
|
151
|
||||||
|
],
|
||||||
|
"↓": [
|
||||||
|
148
|
||||||
|
],
|
||||||
|
"ⱱ": [
|
||||||
|
129
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"num_symbols": 256,
|
||||||
|
"num_speakers": 236,
|
||||||
|
"speaker_id_map": {
|
||||||
|
"2422": 0,
|
||||||
|
"4536": 1,
|
||||||
|
"2037": 2,
|
||||||
|
"9565": 3,
|
||||||
|
"10148": 4,
|
||||||
|
"6507": 5,
|
||||||
|
"5055": 6,
|
||||||
|
"3503": 7,
|
||||||
|
"252": 8,
|
||||||
|
"9132": 9,
|
||||||
|
"3990": 10,
|
||||||
|
"5753": 11,
|
||||||
|
"5424": 12,
|
||||||
|
"2602": 13,
|
||||||
|
"4174": 14,
|
||||||
|
"3885": 15,
|
||||||
|
"12415": 16,
|
||||||
|
"8470": 17,
|
||||||
|
"11927": 18,
|
||||||
|
"9639": 19,
|
||||||
|
"3494": 20,
|
||||||
|
"2946": 21,
|
||||||
|
"5283": 22,
|
||||||
|
"4533": 23,
|
||||||
|
"2497": 24,
|
||||||
|
"12275": 25,
|
||||||
|
"1649": 26,
|
||||||
|
"146": 27,
|
||||||
|
"8337": 28,
|
||||||
|
"4542": 29,
|
||||||
|
"589": 30,
|
||||||
|
"1998": 31,
|
||||||
|
"3797": 32,
|
||||||
|
"5244": 33,
|
||||||
|
"7328": 34,
|
||||||
|
"7998": 35,
|
||||||
|
"10179": 36,
|
||||||
|
"9610": 37,
|
||||||
|
"20": 38,
|
||||||
|
"253": 39,
|
||||||
|
"12899": 40,
|
||||||
|
"7194": 41,
|
||||||
|
"3759": 42,
|
||||||
|
"2677": 43,
|
||||||
|
"6719": 44,
|
||||||
|
"1897": 45,
|
||||||
|
"11990": 46,
|
||||||
|
"6880": 47,
|
||||||
|
"19": 48,
|
||||||
|
"9515": 49,
|
||||||
|
"327": 50,
|
||||||
|
"3244": 51,
|
||||||
|
"5324": 52,
|
||||||
|
"2234": 53,
|
||||||
|
"3124": 54,
|
||||||
|
"2043": 55,
|
||||||
|
"143": 56,
|
||||||
|
"8139": 57,
|
||||||
|
"9646": 58,
|
||||||
|
"8659": 59,
|
||||||
|
"9538": 60,
|
||||||
|
"989": 61,
|
||||||
|
"5405": 62,
|
||||||
|
"10087": 63,
|
||||||
|
"8294": 64,
|
||||||
|
"4396": 65,
|
||||||
|
"1474": 66,
|
||||||
|
"139": 67,
|
||||||
|
"136": 68,
|
||||||
|
"10791": 69,
|
||||||
|
"7242": 70,
|
||||||
|
"3631": 71,
|
||||||
|
"9908": 72,
|
||||||
|
"7906": 73,
|
||||||
|
"1171": 74,
|
||||||
|
"7479": 75,
|
||||||
|
"5632": 76,
|
||||||
|
"3731": 77,
|
||||||
|
"4650": 78,
|
||||||
|
"135": 79,
|
||||||
|
"145": 80,
|
||||||
|
"137": 81,
|
||||||
|
"1757": 82,
|
||||||
|
"91": 83,
|
||||||
|
"9514": 84,
|
||||||
|
"13494": 85,
|
||||||
|
"1946": 86,
|
||||||
|
"3277": 87,
|
||||||
|
"5595": 88,
|
||||||
|
"278": 89,
|
||||||
|
"7120": 90,
|
||||||
|
"7406": 91,
|
||||||
|
"11695": 92,
|
||||||
|
"1593": 93,
|
||||||
|
"3862": 94,
|
||||||
|
"138": 95,
|
||||||
|
"141": 96,
|
||||||
|
"9948": 97,
|
||||||
|
"1163": 98,
|
||||||
|
"1054": 99,
|
||||||
|
"1844": 100,
|
||||||
|
"4911": 101,
|
||||||
|
"7261": 102,
|
||||||
|
"8223": 103,
|
||||||
|
"7624": 104,
|
||||||
|
"144": 105,
|
||||||
|
"13871": 106,
|
||||||
|
"2974": 107,
|
||||||
|
"5934": 108,
|
||||||
|
"7002": 109,
|
||||||
|
"8769": 110,
|
||||||
|
"3363": 111,
|
||||||
|
"3040": 112,
|
||||||
|
"6067": 113,
|
||||||
|
"9494": 114,
|
||||||
|
"8743": 115,
|
||||||
|
"13255": 116,
|
||||||
|
"1660": 117,
|
||||||
|
"3588": 118,
|
||||||
|
"4748": 119,
|
||||||
|
"8450": 120,
|
||||||
|
"5295": 121,
|
||||||
|
"4705": 122,
|
||||||
|
"8125": 123,
|
||||||
|
"7272": 124,
|
||||||
|
"7320": 125,
|
||||||
|
"1874": 126,
|
||||||
|
"1262": 127,
|
||||||
|
"10870": 128,
|
||||||
|
"12379": 129,
|
||||||
|
"4463": 130,
|
||||||
|
"10349": 131,
|
||||||
|
"2252": 132,
|
||||||
|
"8325": 133,
|
||||||
|
"3052": 134,
|
||||||
|
"4001": 135,
|
||||||
|
"7456": 136,
|
||||||
|
"140": 137,
|
||||||
|
"4739": 138,
|
||||||
|
"11299": 139,
|
||||||
|
"4730": 140,
|
||||||
|
"6659": 141,
|
||||||
|
"2034": 142,
|
||||||
|
"2732": 143,
|
||||||
|
"2158": 144,
|
||||||
|
"3698": 145,
|
||||||
|
"5675": 146,
|
||||||
|
"6315": 147,
|
||||||
|
"10904": 148,
|
||||||
|
"7202": 149,
|
||||||
|
"11480": 150,
|
||||||
|
"10625": 151,
|
||||||
|
"11546": 152,
|
||||||
|
"4576": 153,
|
||||||
|
"4512": 154,
|
||||||
|
"8634": 155,
|
||||||
|
"13626": 156,
|
||||||
|
"1613": 157,
|
||||||
|
"287": 158,
|
||||||
|
"9207": 159,
|
||||||
|
"6982": 160,
|
||||||
|
"1724": 161,
|
||||||
|
"10191": 162,
|
||||||
|
"1091": 163,
|
||||||
|
"2909": 164,
|
||||||
|
"1965": 165,
|
||||||
|
"2506": 166,
|
||||||
|
"4414": 167,
|
||||||
|
"5764": 168,
|
||||||
|
"12776": 169,
|
||||||
|
"1033": 170,
|
||||||
|
"13726": 171,
|
||||||
|
"2314": 172,
|
||||||
|
"6826": 173,
|
||||||
|
"9706": 174,
|
||||||
|
"8427": 175,
|
||||||
|
"9168": 176,
|
||||||
|
"9287": 177,
|
||||||
|
"6905": 178,
|
||||||
|
"4153": 179,
|
||||||
|
"3330": 180,
|
||||||
|
"2859": 181,
|
||||||
|
"5406": 182,
|
||||||
|
"2840": 183,
|
||||||
|
"1920": 184,
|
||||||
|
"9241": 185,
|
||||||
|
"10163": 186,
|
||||||
|
"8305": 187,
|
||||||
|
"12461": 188,
|
||||||
|
"3276": 189,
|
||||||
|
"11413": 190,
|
||||||
|
"10536": 191,
|
||||||
|
"10614": 192,
|
||||||
|
"7579": 193,
|
||||||
|
"8675": 194,
|
||||||
|
"7483": 195,
|
||||||
|
"7270": 196,
|
||||||
|
"8704": 197,
|
||||||
|
"4468": 198,
|
||||||
|
"6611": 199,
|
||||||
|
"11497": 200,
|
||||||
|
"11772": 201,
|
||||||
|
"2792": 202,
|
||||||
|
"11481": 203,
|
||||||
|
"10162": 204,
|
||||||
|
"10819": 205,
|
||||||
|
"8732": 206,
|
||||||
|
"11328": 207,
|
||||||
|
"11920": 208,
|
||||||
|
"6646": 209,
|
||||||
|
"7486": 210,
|
||||||
|
"11870": 211,
|
||||||
|
"12417": 212,
|
||||||
|
"10364": 213,
|
||||||
|
"6117": 214,
|
||||||
|
"6448": 215,
|
||||||
|
"10433": 216,
|
||||||
|
"7515": 217,
|
||||||
|
"5823": 218,
|
||||||
|
"8567": 219,
|
||||||
|
"10947": 220,
|
||||||
|
"11869": 221,
|
||||||
|
"12335": 222,
|
||||||
|
"12500": 223,
|
||||||
|
"13755": 224,
|
||||||
|
"7006": 225,
|
||||||
|
"3685": 226,
|
||||||
|
"5819": 227,
|
||||||
|
"9353": 228,
|
||||||
|
"11355": 229,
|
||||||
|
"12174": 230,
|
||||||
|
"7150": 231,
|
||||||
|
"6952": 232,
|
||||||
|
"11987": 233,
|
||||||
|
"3995": 234,
|
||||||
|
"7449": 235
|
||||||
|
},
|
||||||
|
"piper_version": "1.0.0"
|
||||||
|
}
|
||||||
629
models/fr_FR-mls-medium.pt.json
Executable file
629
models/fr_FR-mls-medium.pt.json
Executable file
|
|
@ -0,0 +1,629 @@
|
||||||
|
{
|
||||||
|
"dataset": "mls",
|
||||||
|
"audio": {
|
||||||
|
"sample_rate": 22050,
|
||||||
|
"quality": "medium"
|
||||||
|
},
|
||||||
|
"espeak": {
|
||||||
|
"voice": "fr"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "fr_FR"
|
||||||
|
},
|
||||||
|
"inference": {
|
||||||
|
"noise_scale": 0.333,
|
||||||
|
"length_scale": 1,
|
||||||
|
"noise_w": 0.333
|
||||||
|
},
|
||||||
|
"phoneme_type": "espeak",
|
||||||
|
"phoneme_map": {},
|
||||||
|
"phoneme_id_map": {
|
||||||
|
" ": [
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"!": [
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"\"": [
|
||||||
|
150
|
||||||
|
],
|
||||||
|
"#": [
|
||||||
|
149
|
||||||
|
],
|
||||||
|
"$": [
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"'": [
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"(": [
|
||||||
|
6
|
||||||
|
],
|
||||||
|
")": [
|
||||||
|
7
|
||||||
|
],
|
||||||
|
",": [
|
||||||
|
8
|
||||||
|
],
|
||||||
|
"-": [
|
||||||
|
9
|
||||||
|
],
|
||||||
|
".": [
|
||||||
|
10
|
||||||
|
],
|
||||||
|
"0": [
|
||||||
|
130
|
||||||
|
],
|
||||||
|
"1": [
|
||||||
|
131
|
||||||
|
],
|
||||||
|
"2": [
|
||||||
|
132
|
||||||
|
],
|
||||||
|
"3": [
|
||||||
|
133
|
||||||
|
],
|
||||||
|
"4": [
|
||||||
|
134
|
||||||
|
],
|
||||||
|
"5": [
|
||||||
|
135
|
||||||
|
],
|
||||||
|
"6": [
|
||||||
|
136
|
||||||
|
],
|
||||||
|
"7": [
|
||||||
|
137
|
||||||
|
],
|
||||||
|
"8": [
|
||||||
|
138
|
||||||
|
],
|
||||||
|
"9": [
|
||||||
|
139
|
||||||
|
],
|
||||||
|
":": [
|
||||||
|
11
|
||||||
|
],
|
||||||
|
";": [
|
||||||
|
12
|
||||||
|
],
|
||||||
|
"?": [
|
||||||
|
13
|
||||||
|
],
|
||||||
|
"X": [
|
||||||
|
156
|
||||||
|
],
|
||||||
|
"^": [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"_": [
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"a": [
|
||||||
|
14
|
||||||
|
],
|
||||||
|
"b": [
|
||||||
|
15
|
||||||
|
],
|
||||||
|
"c": [
|
||||||
|
16
|
||||||
|
],
|
||||||
|
"d": [
|
||||||
|
17
|
||||||
|
],
|
||||||
|
"e": [
|
||||||
|
18
|
||||||
|
],
|
||||||
|
"f": [
|
||||||
|
19
|
||||||
|
],
|
||||||
|
"g": [
|
||||||
|
154
|
||||||
|
],
|
||||||
|
"h": [
|
||||||
|
20
|
||||||
|
],
|
||||||
|
"i": [
|
||||||
|
21
|
||||||
|
],
|
||||||
|
"j": [
|
||||||
|
22
|
||||||
|
],
|
||||||
|
"k": [
|
||||||
|
23
|
||||||
|
],
|
||||||
|
"l": [
|
||||||
|
24
|
||||||
|
],
|
||||||
|
"m": [
|
||||||
|
25
|
||||||
|
],
|
||||||
|
"n": [
|
||||||
|
26
|
||||||
|
],
|
||||||
|
"o": [
|
||||||
|
27
|
||||||
|
],
|
||||||
|
"p": [
|
||||||
|
28
|
||||||
|
],
|
||||||
|
"q": [
|
||||||
|
29
|
||||||
|
],
|
||||||
|
"r": [
|
||||||
|
30
|
||||||
|
],
|
||||||
|
"s": [
|
||||||
|
31
|
||||||
|
],
|
||||||
|
"t": [
|
||||||
|
32
|
||||||
|
],
|
||||||
|
"u": [
|
||||||
|
33
|
||||||
|
],
|
||||||
|
"v": [
|
||||||
|
34
|
||||||
|
],
|
||||||
|
"w": [
|
||||||
|
35
|
||||||
|
],
|
||||||
|
"x": [
|
||||||
|
36
|
||||||
|
],
|
||||||
|
"y": [
|
||||||
|
37
|
||||||
|
],
|
||||||
|
"z": [
|
||||||
|
38
|
||||||
|
],
|
||||||
|
"æ": [
|
||||||
|
39
|
||||||
|
],
|
||||||
|
"ç": [
|
||||||
|
40
|
||||||
|
],
|
||||||
|
"ð": [
|
||||||
|
41
|
||||||
|
],
|
||||||
|
"ø": [
|
||||||
|
42
|
||||||
|
],
|
||||||
|
"ħ": [
|
||||||
|
43
|
||||||
|
],
|
||||||
|
"ŋ": [
|
||||||
|
44
|
||||||
|
],
|
||||||
|
"œ": [
|
||||||
|
45
|
||||||
|
],
|
||||||
|
"ǀ": [
|
||||||
|
46
|
||||||
|
],
|
||||||
|
"ǁ": [
|
||||||
|
47
|
||||||
|
],
|
||||||
|
"ǂ": [
|
||||||
|
48
|
||||||
|
],
|
||||||
|
"ǃ": [
|
||||||
|
49
|
||||||
|
],
|
||||||
|
"ɐ": [
|
||||||
|
50
|
||||||
|
],
|
||||||
|
"ɑ": [
|
||||||
|
51
|
||||||
|
],
|
||||||
|
"ɒ": [
|
||||||
|
52
|
||||||
|
],
|
||||||
|
"ɓ": [
|
||||||
|
53
|
||||||
|
],
|
||||||
|
"ɔ": [
|
||||||
|
54
|
||||||
|
],
|
||||||
|
"ɕ": [
|
||||||
|
55
|
||||||
|
],
|
||||||
|
"ɖ": [
|
||||||
|
56
|
||||||
|
],
|
||||||
|
"ɗ": [
|
||||||
|
57
|
||||||
|
],
|
||||||
|
"ɘ": [
|
||||||
|
58
|
||||||
|
],
|
||||||
|
"ə": [
|
||||||
|
59
|
||||||
|
],
|
||||||
|
"ɚ": [
|
||||||
|
60
|
||||||
|
],
|
||||||
|
"ɛ": [
|
||||||
|
61
|
||||||
|
],
|
||||||
|
"ɜ": [
|
||||||
|
62
|
||||||
|
],
|
||||||
|
"ɞ": [
|
||||||
|
63
|
||||||
|
],
|
||||||
|
"ɟ": [
|
||||||
|
64
|
||||||
|
],
|
||||||
|
"ɠ": [
|
||||||
|
65
|
||||||
|
],
|
||||||
|
"ɡ": [
|
||||||
|
66
|
||||||
|
],
|
||||||
|
"ɢ": [
|
||||||
|
67
|
||||||
|
],
|
||||||
|
"ɣ": [
|
||||||
|
68
|
||||||
|
],
|
||||||
|
"ɤ": [
|
||||||
|
69
|
||||||
|
],
|
||||||
|
"ɥ": [
|
||||||
|
70
|
||||||
|
],
|
||||||
|
"ɦ": [
|
||||||
|
71
|
||||||
|
],
|
||||||
|
"ɧ": [
|
||||||
|
72
|
||||||
|
],
|
||||||
|
"ɨ": [
|
||||||
|
73
|
||||||
|
],
|
||||||
|
"ɪ": [
|
||||||
|
74
|
||||||
|
],
|
||||||
|
"ɫ": [
|
||||||
|
75
|
||||||
|
],
|
||||||
|
"ɬ": [
|
||||||
|
76
|
||||||
|
],
|
||||||
|
"ɭ": [
|
||||||
|
77
|
||||||
|
],
|
||||||
|
"ɮ": [
|
||||||
|
78
|
||||||
|
],
|
||||||
|
"ɯ": [
|
||||||
|
79
|
||||||
|
],
|
||||||
|
"ɰ": [
|
||||||
|
80
|
||||||
|
],
|
||||||
|
"ɱ": [
|
||||||
|
81
|
||||||
|
],
|
||||||
|
"ɲ": [
|
||||||
|
82
|
||||||
|
],
|
||||||
|
"ɳ": [
|
||||||
|
83
|
||||||
|
],
|
||||||
|
"ɴ": [
|
||||||
|
84
|
||||||
|
],
|
||||||
|
"ɵ": [
|
||||||
|
85
|
||||||
|
],
|
||||||
|
"ɶ": [
|
||||||
|
86
|
||||||
|
],
|
||||||
|
"ɸ": [
|
||||||
|
87
|
||||||
|
],
|
||||||
|
"ɹ": [
|
||||||
|
88
|
||||||
|
],
|
||||||
|
"ɺ": [
|
||||||
|
89
|
||||||
|
],
|
||||||
|
"ɻ": [
|
||||||
|
90
|
||||||
|
],
|
||||||
|
"ɽ": [
|
||||||
|
91
|
||||||
|
],
|
||||||
|
"ɾ": [
|
||||||
|
92
|
||||||
|
],
|
||||||
|
"ʀ": [
|
||||||
|
93
|
||||||
|
],
|
||||||
|
"ʁ": [
|
||||||
|
94
|
||||||
|
],
|
||||||
|
"ʂ": [
|
||||||
|
95
|
||||||
|
],
|
||||||
|
"ʃ": [
|
||||||
|
96
|
||||||
|
],
|
||||||
|
"ʄ": [
|
||||||
|
97
|
||||||
|
],
|
||||||
|
"ʈ": [
|
||||||
|
98
|
||||||
|
],
|
||||||
|
"ʉ": [
|
||||||
|
99
|
||||||
|
],
|
||||||
|
"ʊ": [
|
||||||
|
100
|
||||||
|
],
|
||||||
|
"ʋ": [
|
||||||
|
101
|
||||||
|
],
|
||||||
|
"ʌ": [
|
||||||
|
102
|
||||||
|
],
|
||||||
|
"ʍ": [
|
||||||
|
103
|
||||||
|
],
|
||||||
|
"ʎ": [
|
||||||
|
104
|
||||||
|
],
|
||||||
|
"ʏ": [
|
||||||
|
105
|
||||||
|
],
|
||||||
|
"ʐ": [
|
||||||
|
106
|
||||||
|
],
|
||||||
|
"ʑ": [
|
||||||
|
107
|
||||||
|
],
|
||||||
|
"ʒ": [
|
||||||
|
108
|
||||||
|
],
|
||||||
|
"ʔ": [
|
||||||
|
109
|
||||||
|
],
|
||||||
|
"ʕ": [
|
||||||
|
110
|
||||||
|
],
|
||||||
|
"ʘ": [
|
||||||
|
111
|
||||||
|
],
|
||||||
|
"ʙ": [
|
||||||
|
112
|
||||||
|
],
|
||||||
|
"ʛ": [
|
||||||
|
113
|
||||||
|
],
|
||||||
|
"ʜ": [
|
||||||
|
114
|
||||||
|
],
|
||||||
|
"ʝ": [
|
||||||
|
115
|
||||||
|
],
|
||||||
|
"ʟ": [
|
||||||
|
116
|
||||||
|
],
|
||||||
|
"ʡ": [
|
||||||
|
117
|
||||||
|
],
|
||||||
|
"ʢ": [
|
||||||
|
118
|
||||||
|
],
|
||||||
|
"ʦ": [
|
||||||
|
155
|
||||||
|
],
|
||||||
|
"ʰ": [
|
||||||
|
145
|
||||||
|
],
|
||||||
|
"ʲ": [
|
||||||
|
119
|
||||||
|
],
|
||||||
|
"ˈ": [
|
||||||
|
120
|
||||||
|
],
|
||||||
|
"ˌ": [
|
||||||
|
121
|
||||||
|
],
|
||||||
|
"ː": [
|
||||||
|
122
|
||||||
|
],
|
||||||
|
"ˑ": [
|
||||||
|
123
|
||||||
|
],
|
||||||
|
"˞": [
|
||||||
|
124
|
||||||
|
],
|
||||||
|
"ˤ": [
|
||||||
|
146
|
||||||
|
],
|
||||||
|
"̃": [
|
||||||
|
141
|
||||||
|
],
|
||||||
|
"̊": [
|
||||||
|
158
|
||||||
|
],
|
||||||
|
"̝": [
|
||||||
|
157
|
||||||
|
],
|
||||||
|
"̧": [
|
||||||
|
140
|
||||||
|
],
|
||||||
|
"̩": [
|
||||||
|
144
|
||||||
|
],
|
||||||
|
"̪": [
|
||||||
|
142
|
||||||
|
],
|
||||||
|
"̯": [
|
||||||
|
143
|
||||||
|
],
|
||||||
|
"̺": [
|
||||||
|
152
|
||||||
|
],
|
||||||
|
"̻": [
|
||||||
|
153
|
||||||
|
],
|
||||||
|
"β": [
|
||||||
|
125
|
||||||
|
],
|
||||||
|
"ε": [
|
||||||
|
147
|
||||||
|
],
|
||||||
|
"θ": [
|
||||||
|
126
|
||||||
|
],
|
||||||
|
"χ": [
|
||||||
|
127
|
||||||
|
],
|
||||||
|
"ᵻ": [
|
||||||
|
128
|
||||||
|
],
|
||||||
|
"↑": [
|
||||||
|
151
|
||||||
|
],
|
||||||
|
"↓": [
|
||||||
|
148
|
||||||
|
],
|
||||||
|
"ⱱ": [
|
||||||
|
129
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"num_symbols": 256,
|
||||||
|
"num_speakers": 125,
|
||||||
|
"speaker_id_map": {
|
||||||
|
"1840": 0,
|
||||||
|
"3698": 1,
|
||||||
|
"123": 2,
|
||||||
|
"1474": 3,
|
||||||
|
"12709": 4,
|
||||||
|
"7423": 5,
|
||||||
|
"9242": 6,
|
||||||
|
"8778": 7,
|
||||||
|
"3060": 8,
|
||||||
|
"4512": 9,
|
||||||
|
"6249": 10,
|
||||||
|
"12541": 11,
|
||||||
|
"13634": 12,
|
||||||
|
"10065": 13,
|
||||||
|
"6128": 14,
|
||||||
|
"5232": 15,
|
||||||
|
"5764": 16,
|
||||||
|
"12713": 17,
|
||||||
|
"12823": 18,
|
||||||
|
"6070": 19,
|
||||||
|
"12501": 20,
|
||||||
|
"9121": 21,
|
||||||
|
"1649": 22,
|
||||||
|
"2776": 23,
|
||||||
|
"11772": 24,
|
||||||
|
"5612": 25,
|
||||||
|
"11822": 26,
|
||||||
|
"1590": 27,
|
||||||
|
"5525": 28,
|
||||||
|
"10827": 29,
|
||||||
|
"1243": 30,
|
||||||
|
"13142": 31,
|
||||||
|
"62": 32,
|
||||||
|
"13177": 33,
|
||||||
|
"10620": 34,
|
||||||
|
"8102": 35,
|
||||||
|
"8582": 36,
|
||||||
|
"11875": 37,
|
||||||
|
"7239": 38,
|
||||||
|
"9854": 39,
|
||||||
|
"7377": 40,
|
||||||
|
"10082": 41,
|
||||||
|
"12512": 42,
|
||||||
|
"1329": 43,
|
||||||
|
"2506": 44,
|
||||||
|
"6856": 45,
|
||||||
|
"10058": 46,
|
||||||
|
"103": 47,
|
||||||
|
"14": 48,
|
||||||
|
"6381": 49,
|
||||||
|
"1664": 50,
|
||||||
|
"11954": 51,
|
||||||
|
"66": 52,
|
||||||
|
"1127": 53,
|
||||||
|
"3270": 54,
|
||||||
|
"13611": 55,
|
||||||
|
"13658": 56,
|
||||||
|
"12968": 57,
|
||||||
|
"1989": 58,
|
||||||
|
"12981": 59,
|
||||||
|
"7193": 60,
|
||||||
|
"6348": 61,
|
||||||
|
"7679": 62,
|
||||||
|
"2284": 63,
|
||||||
|
"3182": 64,
|
||||||
|
"3503": 65,
|
||||||
|
"2033": 66,
|
||||||
|
"2771": 67,
|
||||||
|
"7614": 68,
|
||||||
|
"125": 69,
|
||||||
|
"3204": 70,
|
||||||
|
"5595": 71,
|
||||||
|
"5553": 72,
|
||||||
|
"694": 73,
|
||||||
|
"1624": 74,
|
||||||
|
"1887": 75,
|
||||||
|
"2926": 76,
|
||||||
|
"7150": 77,
|
||||||
|
"3190": 78,
|
||||||
|
"3344": 79,
|
||||||
|
"4699": 80,
|
||||||
|
"1798": 81,
|
||||||
|
"1745": 82,
|
||||||
|
"5077": 83,
|
||||||
|
"753": 84,
|
||||||
|
"52": 85,
|
||||||
|
"4174": 86,
|
||||||
|
"4018": 87,
|
||||||
|
"12899": 88,
|
||||||
|
"1844": 89,
|
||||||
|
"4396": 90,
|
||||||
|
"1817": 91,
|
||||||
|
"2155": 92,
|
||||||
|
"2946": 93,
|
||||||
|
"4336": 94,
|
||||||
|
"4609": 95,
|
||||||
|
"1977": 96,
|
||||||
|
"10957": 97,
|
||||||
|
"204": 98,
|
||||||
|
"4650": 99,
|
||||||
|
"5295": 100,
|
||||||
|
"5968": 101,
|
||||||
|
"4744": 102,
|
||||||
|
"2825": 103,
|
||||||
|
"9804": 104,
|
||||||
|
"707": 105,
|
||||||
|
"30": 106,
|
||||||
|
"115": 107,
|
||||||
|
"5840": 108,
|
||||||
|
"2587": 109,
|
||||||
|
"2607": 110,
|
||||||
|
"2544": 111,
|
||||||
|
"28": 112,
|
||||||
|
"27": 113,
|
||||||
|
"177": 114,
|
||||||
|
"112": 115,
|
||||||
|
"94": 116,
|
||||||
|
"2596": 117,
|
||||||
|
"3595": 118,
|
||||||
|
"7032": 119,
|
||||||
|
"7848": 120,
|
||||||
|
"11247": 121,
|
||||||
|
"7439": 122,
|
||||||
|
"2904": 123,
|
||||||
|
"6362": 124
|
||||||
|
},
|
||||||
|
"piper_version": "1.0.0"
|
||||||
|
}
|
||||||
554
models/nl_NL-mls-medium.pt.json
Executable file
554
models/nl_NL-mls-medium.pt.json
Executable file
|
|
@ -0,0 +1,554 @@
|
||||||
|
{
|
||||||
|
"audio": {
|
||||||
|
"sample_rate": 22050
|
||||||
|
},
|
||||||
|
"espeak": {
|
||||||
|
"voice": "nl"
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"code": "nl_NL"
|
||||||
|
},
|
||||||
|
"inference": {
|
||||||
|
"noise_scale": 0.333,
|
||||||
|
"length_scale": 1,
|
||||||
|
"noise_w": 0.333
|
||||||
|
},
|
||||||
|
"phoneme_type": "espeak",
|
||||||
|
"phoneme_map": {},
|
||||||
|
"phoneme_id_map": {
|
||||||
|
" ": [
|
||||||
|
3
|
||||||
|
],
|
||||||
|
"!": [
|
||||||
|
4
|
||||||
|
],
|
||||||
|
"\"": [
|
||||||
|
150
|
||||||
|
],
|
||||||
|
"#": [
|
||||||
|
149
|
||||||
|
],
|
||||||
|
"$": [
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"'": [
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"(": [
|
||||||
|
6
|
||||||
|
],
|
||||||
|
")": [
|
||||||
|
7
|
||||||
|
],
|
||||||
|
",": [
|
||||||
|
8
|
||||||
|
],
|
||||||
|
"-": [
|
||||||
|
9
|
||||||
|
],
|
||||||
|
".": [
|
||||||
|
10
|
||||||
|
],
|
||||||
|
"0": [
|
||||||
|
130
|
||||||
|
],
|
||||||
|
"1": [
|
||||||
|
131
|
||||||
|
],
|
||||||
|
"2": [
|
||||||
|
132
|
||||||
|
],
|
||||||
|
"3": [
|
||||||
|
133
|
||||||
|
],
|
||||||
|
"4": [
|
||||||
|
134
|
||||||
|
],
|
||||||
|
"5": [
|
||||||
|
135
|
||||||
|
],
|
||||||
|
"6": [
|
||||||
|
136
|
||||||
|
],
|
||||||
|
"7": [
|
||||||
|
137
|
||||||
|
],
|
||||||
|
"8": [
|
||||||
|
138
|
||||||
|
],
|
||||||
|
"9": [
|
||||||
|
139
|
||||||
|
],
|
||||||
|
":": [
|
||||||
|
11
|
||||||
|
],
|
||||||
|
";": [
|
||||||
|
12
|
||||||
|
],
|
||||||
|
"?": [
|
||||||
|
13
|
||||||
|
],
|
||||||
|
"X": [
|
||||||
|
156
|
||||||
|
],
|
||||||
|
"^": [
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"_": [
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"a": [
|
||||||
|
14
|
||||||
|
],
|
||||||
|
"b": [
|
||||||
|
15
|
||||||
|
],
|
||||||
|
"c": [
|
||||||
|
16
|
||||||
|
],
|
||||||
|
"d": [
|
||||||
|
17
|
||||||
|
],
|
||||||
|
"e": [
|
||||||
|
18
|
||||||
|
],
|
||||||
|
"f": [
|
||||||
|
19
|
||||||
|
],
|
||||||
|
"g": [
|
||||||
|
154
|
||||||
|
],
|
||||||
|
"h": [
|
||||||
|
20
|
||||||
|
],
|
||||||
|
"i": [
|
||||||
|
21
|
||||||
|
],
|
||||||
|
"j": [
|
||||||
|
22
|
||||||
|
],
|
||||||
|
"k": [
|
||||||
|
23
|
||||||
|
],
|
||||||
|
"l": [
|
||||||
|
24
|
||||||
|
],
|
||||||
|
"m": [
|
||||||
|
25
|
||||||
|
],
|
||||||
|
"n": [
|
||||||
|
26
|
||||||
|
],
|
||||||
|
"o": [
|
||||||
|
27
|
||||||
|
],
|
||||||
|
"p": [
|
||||||
|
28
|
||||||
|
],
|
||||||
|
"q": [
|
||||||
|
29
|
||||||
|
],
|
||||||
|
"r": [
|
||||||
|
30
|
||||||
|
],
|
||||||
|
"s": [
|
||||||
|
31
|
||||||
|
],
|
||||||
|
"t": [
|
||||||
|
32
|
||||||
|
],
|
||||||
|
"u": [
|
||||||
|
33
|
||||||
|
],
|
||||||
|
"v": [
|
||||||
|
34
|
||||||
|
],
|
||||||
|
"w": [
|
||||||
|
35
|
||||||
|
],
|
||||||
|
"x": [
|
||||||
|
36
|
||||||
|
],
|
||||||
|
"y": [
|
||||||
|
37
|
||||||
|
],
|
||||||
|
"z": [
|
||||||
|
38
|
||||||
|
],
|
||||||
|
"æ": [
|
||||||
|
39
|
||||||
|
],
|
||||||
|
"ç": [
|
||||||
|
40
|
||||||
|
],
|
||||||
|
"ð": [
|
||||||
|
41
|
||||||
|
],
|
||||||
|
"ø": [
|
||||||
|
42
|
||||||
|
],
|
||||||
|
"ħ": [
|
||||||
|
43
|
||||||
|
],
|
||||||
|
"ŋ": [
|
||||||
|
44
|
||||||
|
],
|
||||||
|
"œ": [
|
||||||
|
45
|
||||||
|
],
|
||||||
|
"ǀ": [
|
||||||
|
46
|
||||||
|
],
|
||||||
|
"ǁ": [
|
||||||
|
47
|
||||||
|
],
|
||||||
|
"ǂ": [
|
||||||
|
48
|
||||||
|
],
|
||||||
|
"ǃ": [
|
||||||
|
49
|
||||||
|
],
|
||||||
|
"ɐ": [
|
||||||
|
50
|
||||||
|
],
|
||||||
|
"ɑ": [
|
||||||
|
51
|
||||||
|
],
|
||||||
|
"ɒ": [
|
||||||
|
52
|
||||||
|
],
|
||||||
|
"ɓ": [
|
||||||
|
53
|
||||||
|
],
|
||||||
|
"ɔ": [
|
||||||
|
54
|
||||||
|
],
|
||||||
|
"ɕ": [
|
||||||
|
55
|
||||||
|
],
|
||||||
|
"ɖ": [
|
||||||
|
56
|
||||||
|
],
|
||||||
|
"ɗ": [
|
||||||
|
57
|
||||||
|
],
|
||||||
|
"ɘ": [
|
||||||
|
58
|
||||||
|
],
|
||||||
|
"ə": [
|
||||||
|
59
|
||||||
|
],
|
||||||
|
"ɚ": [
|
||||||
|
60
|
||||||
|
],
|
||||||
|
"ɛ": [
|
||||||
|
61
|
||||||
|
],
|
||||||
|
"ɜ": [
|
||||||
|
62
|
||||||
|
],
|
||||||
|
"ɞ": [
|
||||||
|
63
|
||||||
|
],
|
||||||
|
"ɟ": [
|
||||||
|
64
|
||||||
|
],
|
||||||
|
"ɠ": [
|
||||||
|
65
|
||||||
|
],
|
||||||
|
"ɡ": [
|
||||||
|
66
|
||||||
|
],
|
||||||
|
"ɢ": [
|
||||||
|
67
|
||||||
|
],
|
||||||
|
"ɣ": [
|
||||||
|
68
|
||||||
|
],
|
||||||
|
"ɤ": [
|
||||||
|
69
|
||||||
|
],
|
||||||
|
"ɥ": [
|
||||||
|
70
|
||||||
|
],
|
||||||
|
"ɦ": [
|
||||||
|
71
|
||||||
|
],
|
||||||
|
"ɧ": [
|
||||||
|
72
|
||||||
|
],
|
||||||
|
"ɨ": [
|
||||||
|
73
|
||||||
|
],
|
||||||
|
"ɪ": [
|
||||||
|
74
|
||||||
|
],
|
||||||
|
"ɫ": [
|
||||||
|
75
|
||||||
|
],
|
||||||
|
"ɬ": [
|
||||||
|
76
|
||||||
|
],
|
||||||
|
"ɭ": [
|
||||||
|
77
|
||||||
|
],
|
||||||
|
"ɮ": [
|
||||||
|
78
|
||||||
|
],
|
||||||
|
"ɯ": [
|
||||||
|
79
|
||||||
|
],
|
||||||
|
"ɰ": [
|
||||||
|
80
|
||||||
|
],
|
||||||
|
"ɱ": [
|
||||||
|
81
|
||||||
|
],
|
||||||
|
"ɲ": [
|
||||||
|
82
|
||||||
|
],
|
||||||
|
"ɳ": [
|
||||||
|
83
|
||||||
|
],
|
||||||
|
"ɴ": [
|
||||||
|
84
|
||||||
|
],
|
||||||
|
"ɵ": [
|
||||||
|
85
|
||||||
|
],
|
||||||
|
"ɶ": [
|
||||||
|
86
|
||||||
|
],
|
||||||
|
"ɸ": [
|
||||||
|
87
|
||||||
|
],
|
||||||
|
"ɹ": [
|
||||||
|
88
|
||||||
|
],
|
||||||
|
"ɺ": [
|
||||||
|
89
|
||||||
|
],
|
||||||
|
"ɻ": [
|
||||||
|
90
|
||||||
|
],
|
||||||
|
"ɽ": [
|
||||||
|
91
|
||||||
|
],
|
||||||
|
"ɾ": [
|
||||||
|
92
|
||||||
|
],
|
||||||
|
"ʀ": [
|
||||||
|
93
|
||||||
|
],
|
||||||
|
"ʁ": [
|
||||||
|
94
|
||||||
|
],
|
||||||
|
"ʂ": [
|
||||||
|
95
|
||||||
|
],
|
||||||
|
"ʃ": [
|
||||||
|
96
|
||||||
|
],
|
||||||
|
"ʄ": [
|
||||||
|
97
|
||||||
|
],
|
||||||
|
"ʈ": [
|
||||||
|
98
|
||||||
|
],
|
||||||
|
"ʉ": [
|
||||||
|
99
|
||||||
|
],
|
||||||
|
"ʊ": [
|
||||||
|
100
|
||||||
|
],
|
||||||
|
"ʋ": [
|
||||||
|
101
|
||||||
|
],
|
||||||
|
"ʌ": [
|
||||||
|
102
|
||||||
|
],
|
||||||
|
"ʍ": [
|
||||||
|
103
|
||||||
|
],
|
||||||
|
"ʎ": [
|
||||||
|
104
|
||||||
|
],
|
||||||
|
"ʏ": [
|
||||||
|
105
|
||||||
|
],
|
||||||
|
"ʐ": [
|
||||||
|
106
|
||||||
|
],
|
||||||
|
"ʑ": [
|
||||||
|
107
|
||||||
|
],
|
||||||
|
"ʒ": [
|
||||||
|
108
|
||||||
|
],
|
||||||
|
"ʔ": [
|
||||||
|
109
|
||||||
|
],
|
||||||
|
"ʕ": [
|
||||||
|
110
|
||||||
|
],
|
||||||
|
"ʘ": [
|
||||||
|
111
|
||||||
|
],
|
||||||
|
"ʙ": [
|
||||||
|
112
|
||||||
|
],
|
||||||
|
"ʛ": [
|
||||||
|
113
|
||||||
|
],
|
||||||
|
"ʜ": [
|
||||||
|
114
|
||||||
|
],
|
||||||
|
"ʝ": [
|
||||||
|
115
|
||||||
|
],
|
||||||
|
"ʟ": [
|
||||||
|
116
|
||||||
|
],
|
||||||
|
"ʡ": [
|
||||||
|
117
|
||||||
|
],
|
||||||
|
"ʢ": [
|
||||||
|
118
|
||||||
|
],
|
||||||
|
"ʦ": [
|
||||||
|
155
|
||||||
|
],
|
||||||
|
"ʰ": [
|
||||||
|
145
|
||||||
|
],
|
||||||
|
"ʲ": [
|
||||||
|
119
|
||||||
|
],
|
||||||
|
"ˈ": [
|
||||||
|
120
|
||||||
|
],
|
||||||
|
"ˌ": [
|
||||||
|
121
|
||||||
|
],
|
||||||
|
"ː": [
|
||||||
|
122
|
||||||
|
],
|
||||||
|
"ˑ": [
|
||||||
|
123
|
||||||
|
],
|
||||||
|
"˞": [
|
||||||
|
124
|
||||||
|
],
|
||||||
|
"ˤ": [
|
||||||
|
146
|
||||||
|
],
|
||||||
|
"̃": [
|
||||||
|
141
|
||||||
|
],
|
||||||
|
"̊": [
|
||||||
|
158
|
||||||
|
],
|
||||||
|
"̝": [
|
||||||
|
157
|
||||||
|
],
|
||||||
|
"̧": [
|
||||||
|
140
|
||||||
|
],
|
||||||
|
"̩": [
|
||||||
|
144
|
||||||
|
],
|
||||||
|
"̪": [
|
||||||
|
142
|
||||||
|
],
|
||||||
|
"̯": [
|
||||||
|
143
|
||||||
|
],
|
||||||
|
"̺": [
|
||||||
|
152
|
||||||
|
],
|
||||||
|
"̻": [
|
||||||
|
153
|
||||||
|
],
|
||||||
|
"β": [
|
||||||
|
125
|
||||||
|
],
|
||||||
|
"ε": [
|
||||||
|
147
|
||||||
|
],
|
||||||
|
"θ": [
|
||||||
|
126
|
||||||
|
],
|
||||||
|
"χ": [
|
||||||
|
127
|
||||||
|
],
|
||||||
|
"ᵻ": [
|
||||||
|
128
|
||||||
|
],
|
||||||
|
"↑": [
|
||||||
|
151
|
||||||
|
],
|
||||||
|
"↓": [
|
||||||
|
148
|
||||||
|
],
|
||||||
|
"ⱱ": [
|
||||||
|
129
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"num_symbols": 256,
|
||||||
|
"num_speakers": 52,
|
||||||
|
"speaker_id_map": {
|
||||||
|
"2450": 0,
|
||||||
|
"1724": 1,
|
||||||
|
"1666": 2,
|
||||||
|
"5809": 3,
|
||||||
|
"496": 4,
|
||||||
|
"2506": 5,
|
||||||
|
"7432": 6,
|
||||||
|
"3619": 7,
|
||||||
|
"4429": 8,
|
||||||
|
"3798": 9,
|
||||||
|
"12500": 10,
|
||||||
|
"10587": 11,
|
||||||
|
"2951": 12,
|
||||||
|
"1775": 13,
|
||||||
|
"9861": 14,
|
||||||
|
"880": 15,
|
||||||
|
"3034": 16,
|
||||||
|
"2825": 17,
|
||||||
|
"5438": 18,
|
||||||
|
"3245": 19,
|
||||||
|
"4396": 20,
|
||||||
|
"11290": 21,
|
||||||
|
"11936": 22,
|
||||||
|
"6916": 23,
|
||||||
|
"10294": 24,
|
||||||
|
"10079": 25,
|
||||||
|
"7588": 26,
|
||||||
|
"7579": 27,
|
||||||
|
"123": 28,
|
||||||
|
"3024": 29,
|
||||||
|
"960": 30,
|
||||||
|
"10984": 31,
|
||||||
|
"2792": 32,
|
||||||
|
"7723": 33,
|
||||||
|
"4174": 34,
|
||||||
|
"2981": 35,
|
||||||
|
"5764": 36,
|
||||||
|
"6513": 37,
|
||||||
|
"7884": 38,
|
||||||
|
"6697": 39,
|
||||||
|
"12749": 40,
|
||||||
|
"11157": 41,
|
||||||
|
"2239": 42,
|
||||||
|
"10879": 43,
|
||||||
|
"1085": 44,
|
||||||
|
"8480": 45,
|
||||||
|
"8331": 46,
|
||||||
|
"6282": 47,
|
||||||
|
"10632": 48,
|
||||||
|
"2602": 49,
|
||||||
|
"5367": 50,
|
||||||
|
"11472": 51
|
||||||
|
},
|
||||||
|
"piper_version": "1.0.0"
|
||||||
|
}
|
||||||
1
piper_sample_generator/__init__.py
Normal file
1
piper_sample_generator/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
"""Piper sample generator."""
|
||||||
628
piper_sample_generator/__main__.py
Executable file
628
piper_sample_generator/__main__.py
Executable file
|
|
@ -0,0 +1,628 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import gc
|
||||||
|
import itertools as it
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import unicodedata
|
||||||
|
import wave
|
||||||
|
from collections.abc import Iterable
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Optional, Tuple, Union, cast
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
from piper import PiperVoice, SynthesisConfig
|
||||||
|
from piper.phonemize_espeak import EspeakPhonemizer
|
||||||
|
|
||||||
|
try:
|
||||||
|
from piper_train.vits import commons
|
||||||
|
except ImportError:
|
||||||
|
from piper_train.vits import commons
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
# Main generation function
|
||||||
|
def generate_samples(
|
||||||
|
text: Union[List[str], str],
|
||||||
|
output_dir: Union[str, Path],
|
||||||
|
model: Union[str, Path],
|
||||||
|
max_samples: Optional[int] = None,
|
||||||
|
file_names: Optional[Iterable[str]] = None,
|
||||||
|
batch_size: int = 1,
|
||||||
|
slerp_weights: Tuple[float, ...] = (0.5,),
|
||||||
|
length_scales: Tuple[float, ...] = (0.75, 1, 1.25),
|
||||||
|
noise_scales: Tuple[float, ...] = (0.667,),
|
||||||
|
noise_scale_ws: Tuple[float, ...] = (0.8,),
|
||||||
|
max_speakers: Optional[int] = None,
|
||||||
|
verbose: bool = False,
|
||||||
|
phoneme_input: bool = False,
|
||||||
|
**kwargs,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Generate synthetic speech clips, saving the clips to the specified output directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text (List[str]): The text to convert into speech. Can be either a
|
||||||
|
a list of strings, or a path to a file with text on each line.
|
||||||
|
output_dir (str): The location to save the generated clips.
|
||||||
|
model (str): The path to the TTS generator model (.pt).
|
||||||
|
max_samples (int): The maximum number of samples to generate.
|
||||||
|
file_names (List[str]): The names to use when saving the files. Must be the same length
|
||||||
|
as the `text` argument, if a list.
|
||||||
|
batch_size (int): The batch size to use when generated the clips
|
||||||
|
slerp_weights (List[float]): The weights to use when mixing speakers via SLERP.
|
||||||
|
length_scales (List[float]): Controls the average duration/speed of the generated speech.
|
||||||
|
noise_scales (List[float]): A parameter for overall variability of the generated speech.
|
||||||
|
noise_scale_ws (List[float]): A parameter for the stochastic duration of words/phonemes.
|
||||||
|
max_speakers (int): The maximum speaker number to use, if the model is multi-speaker.
|
||||||
|
verbose (bool): Enable or disable more detailed logging messages (default: False).
|
||||||
|
phoneme_input (bool): Set to indicate given input text is phoneme input.
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
|
||||||
|
if max_samples is None:
|
||||||
|
max_samples = len(text)
|
||||||
|
|
||||||
|
_LOGGER.debug("Loading %s", model)
|
||||||
|
model_path = Path(model)
|
||||||
|
|
||||||
|
torch_model = torch.load(model_path, weights_only=False)
|
||||||
|
torch_model.eval()
|
||||||
|
_LOGGER.info("Successfully loaded the model")
|
||||||
|
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
torch_model.cuda()
|
||||||
|
_LOGGER.debug("CUDA available, using GPU")
|
||||||
|
elif torch.backends.mps.is_available():
|
||||||
|
mps_device = torch.device("mps")
|
||||||
|
torch_model.to(mps_device)
|
||||||
|
_LOGGER.debug("MPS available, using GPU")
|
||||||
|
|
||||||
|
output_dir = Path(output_dir)
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
config_path = f"{model_path}.json"
|
||||||
|
with open(config_path, "r", encoding="utf-8") as config_file:
|
||||||
|
config = json.load(config_file)
|
||||||
|
|
||||||
|
voice = config["espeak"]["voice"]
|
||||||
|
sample_rate = config["audio"]["sample_rate"]
|
||||||
|
num_speakers = config["num_speakers"]
|
||||||
|
if max_speakers is not None:
|
||||||
|
num_speakers = min(num_speakers, max_speakers)
|
||||||
|
|
||||||
|
max_len = None
|
||||||
|
|
||||||
|
sample_idx = 0
|
||||||
|
is_done = False
|
||||||
|
settings_iter = it.cycle(
|
||||||
|
it.product(
|
||||||
|
slerp_weights,
|
||||||
|
length_scales,
|
||||||
|
noise_scales,
|
||||||
|
noise_scale_ws,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
speakers_iter = it.cycle(it.product(range(num_speakers), range(num_speakers)))
|
||||||
|
speakers_batch = list(it.islice(speakers_iter, 0, batch_size))
|
||||||
|
if isinstance(text, str) and os.path.isfile(text):
|
||||||
|
texts = it.cycle(
|
||||||
|
[
|
||||||
|
i.strip()
|
||||||
|
for i in open(text, "r", encoding="utf-8").readlines()
|
||||||
|
if len(i.strip()) > 0
|
||||||
|
]
|
||||||
|
)
|
||||||
|
elif isinstance(text, list):
|
||||||
|
texts = it.cycle(text)
|
||||||
|
else:
|
||||||
|
texts = it.cycle([text])
|
||||||
|
|
||||||
|
if file_names:
|
||||||
|
file_names = it.cycle(file_names)
|
||||||
|
|
||||||
|
batch_idx = 0
|
||||||
|
while speakers_batch:
|
||||||
|
if is_done:
|
||||||
|
break
|
||||||
|
|
||||||
|
batch_size = len(speakers_batch)
|
||||||
|
slerp_weight, length_scale, noise_scale, noise_scale_w = next(settings_iter)
|
||||||
|
|
||||||
|
with torch.no_grad():
|
||||||
|
speaker_1 = torch.LongTensor([s[0] for s in speakers_batch])
|
||||||
|
speaker_2 = torch.LongTensor([s[1] for s in speakers_batch])
|
||||||
|
|
||||||
|
phoneme_ids_by_batch = []
|
||||||
|
for i in range(batch_size):
|
||||||
|
phoneme_ids = get_phonemes(
|
||||||
|
voice, config, next(texts), verbose, phoneme_input
|
||||||
|
)
|
||||||
|
phoneme_ids_by_batch.append(phoneme_ids)
|
||||||
|
|
||||||
|
def right_pad_lists(lists):
|
||||||
|
max_length = max(len(lst) for lst in lists)
|
||||||
|
padded_lists = []
|
||||||
|
for lst in lists:
|
||||||
|
padded_l = lst + [1] * (
|
||||||
|
max_length - len(lst)
|
||||||
|
) # phoneme 1 (corresponding to '^' character seems to work best)
|
||||||
|
padded_lists.append(padded_l)
|
||||||
|
return padded_lists
|
||||||
|
|
||||||
|
phoneme_ids_by_batch = right_pad_lists(phoneme_ids_by_batch)
|
||||||
|
audio, phoneme_samples = generate_audio(
|
||||||
|
torch_model,
|
||||||
|
speaker_1,
|
||||||
|
speaker_2,
|
||||||
|
phoneme_ids_by_batch,
|
||||||
|
slerp_weight,
|
||||||
|
noise_scale,
|
||||||
|
noise_scale_w,
|
||||||
|
length_scale,
|
||||||
|
max_len,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Trim audio to actual length based on phoneme samples
|
||||||
|
for i in range(audio.shape[0]):
|
||||||
|
# Fill time after last speech with silence (zeros)
|
||||||
|
# It will be removed in the next stage with np.trim_zeros
|
||||||
|
last_sample_idx = int(phoneme_samples[i].flatten().sum().item())
|
||||||
|
audio[i, 0, last_sample_idx + 1 :] = 0
|
||||||
|
|
||||||
|
audio_numpy = audio.cpu().numpy()
|
||||||
|
|
||||||
|
if torch.backends.mps.is_available():
|
||||||
|
# There seems to be a memory leak if we don't empty the cache
|
||||||
|
# after each batch with mps
|
||||||
|
torch.mps.empty_cache()
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
|
audio_int16 = audio_float_to_int16(audio_numpy)
|
||||||
|
for audio_idx in range(audio_int16.shape[0]):
|
||||||
|
audio_data = np.trim_zeros(audio_int16[audio_idx].flatten())
|
||||||
|
|
||||||
|
if isinstance(file_names, it.cycle):
|
||||||
|
wav_path = output_dir / next(file_names)
|
||||||
|
else:
|
||||||
|
wav_path = output_dir / f"{sample_idx}.wav"
|
||||||
|
|
||||||
|
wav_file: wave.Wave_write = wave.open(str(wav_path), "wb")
|
||||||
|
with wav_file:
|
||||||
|
wav_file.setframerate(sample_rate)
|
||||||
|
wav_file.setsampwidth(2)
|
||||||
|
wav_file.setnchannels(1)
|
||||||
|
wav_file.writeframes(audio_data)
|
||||||
|
|
||||||
|
sample_idx += 1
|
||||||
|
if sample_idx >= max_samples:
|
||||||
|
is_done = True
|
||||||
|
break
|
||||||
|
|
||||||
|
# print(f"Batch {batch_idx +1}/{max_samples//batch_size} complete", " "*200, end='\r')
|
||||||
|
|
||||||
|
# Next batch
|
||||||
|
_LOGGER.debug("Batch %s/%s complete", batch_idx + 1, max_samples // batch_size)
|
||||||
|
speakers_batch = list(it.islice(speakers_iter, 0, batch_size))
|
||||||
|
batch_idx += 1
|
||||||
|
|
||||||
|
_LOGGER.info("Done")
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def generate_samples_onnx(
|
||||||
|
text: Union[List[str], str],
|
||||||
|
output_dir: Union[str, Path],
|
||||||
|
model: Union[str, Path, List[Union[str, Path]]],
|
||||||
|
max_samples: Optional[int] = None,
|
||||||
|
file_names: Optional[Iterable[str]] = None,
|
||||||
|
length_scales: Tuple[float, ...] = (0.75, 1, 1.25),
|
||||||
|
noise_scales: Tuple[float, ...] = (0.667,),
|
||||||
|
noise_scale_ws: Tuple[float, ...] = (0.8,),
|
||||||
|
max_speakers: Optional[int] = None,
|
||||||
|
phoneme_input: bool = False,
|
||||||
|
**kwargs,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Generate synthetic speech clips, saving the clips to the specified output directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text (List[str]): The text to convert into speech. Can be either a
|
||||||
|
a list of strings, or a path to a file with text on each line.
|
||||||
|
output_dir (str): The location to save the generated clips.
|
||||||
|
model (str): The path to the Piper TTS model (.onnx).
|
||||||
|
max_samples (int): The maximum number of samples to generate.
|
||||||
|
file_names (List[str]): The names to use when saving the files. Must be the same length
|
||||||
|
as the `text` argument, if a list.
|
||||||
|
length_scales (List[float]): Controls the average duration/speed of the generated speech.
|
||||||
|
noise_scales (List[float]): A parameter for overall variability of the generated speech.
|
||||||
|
noise_scale_ws (List[float]): A parameter for the stochastic duration of words/phonemes.
|
||||||
|
max_speakers (int): The maximum speaker number to use, if the model is multi-speaker.
|
||||||
|
phoneme_input (bool): Set to indicate given input text is phoneme input.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
|
||||||
|
if max_samples is None:
|
||||||
|
max_samples = len(text)
|
||||||
|
|
||||||
|
if not isinstance(model, list):
|
||||||
|
model = [model]
|
||||||
|
|
||||||
|
_LOGGER.debug("Loading %s", model)
|
||||||
|
voices = [PiperVoice.load(m, use_cuda=torch.cuda.is_available()) for m in model]
|
||||||
|
_LOGGER.info("Successfully loaded model(s)")
|
||||||
|
|
||||||
|
output_dir = Path(output_dir)
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
sample_idx = 0
|
||||||
|
settings_iter = it.cycle(
|
||||||
|
it.product(
|
||||||
|
voices,
|
||||||
|
length_scales,
|
||||||
|
noise_scales,
|
||||||
|
noise_scale_ws,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(text, str) and os.path.exists(text):
|
||||||
|
texts = it.cycle(
|
||||||
|
[
|
||||||
|
i.strip()
|
||||||
|
for i in open(text, "r", encoding="utf-8").readlines()
|
||||||
|
if len(i.strip()) > 0
|
||||||
|
]
|
||||||
|
)
|
||||||
|
elif isinstance(text, list):
|
||||||
|
texts = it.cycle(text)
|
||||||
|
else:
|
||||||
|
texts = it.cycle([text])
|
||||||
|
|
||||||
|
if file_names:
|
||||||
|
file_names = it.cycle(file_names)
|
||||||
|
|
||||||
|
for voice, length_scale, noise_scale, noise_w_scale in settings_iter:
|
||||||
|
num_speakers = voice.config.num_speakers
|
||||||
|
if max_speakers is not None:
|
||||||
|
num_speakers = min(num_speakers, max_speakers)
|
||||||
|
|
||||||
|
for speaker_id in range(num_speakers):
|
||||||
|
if isinstance(file_names, it.cycle):
|
||||||
|
wav_path = output_dir / next(file_names)
|
||||||
|
else:
|
||||||
|
wav_path = output_dir / f"{sample_idx}.wav"
|
||||||
|
|
||||||
|
text_input = next(texts)
|
||||||
|
|
||||||
|
if phoneme_input:
|
||||||
|
# For ONNX models with phoneme input, build phoneme IDs manually
|
||||||
|
phonemes = list(unicodedata.normalize("NFD", text_input))
|
||||||
|
|
||||||
|
# Build phoneme IDs similar to get_phonemes function
|
||||||
|
id_map = voice.config.phoneme_id_map
|
||||||
|
|
||||||
|
# Beginning of utterance
|
||||||
|
phoneme_ids = list(id_map.get("^", [1])) # Default to [1] if not found
|
||||||
|
phoneme_ids.extend(id_map.get("_", [0])) # Default to [0] if not found
|
||||||
|
|
||||||
|
# Add phonemes
|
||||||
|
for phoneme in phonemes:
|
||||||
|
p_ids = id_map.get(phoneme)
|
||||||
|
if p_ids is not None:
|
||||||
|
phoneme_ids.extend(p_ids)
|
||||||
|
phoneme_ids.extend(id_map.get("_", [0]))
|
||||||
|
else:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Phoneme '%s' not found in model's phoneme map", phoneme
|
||||||
|
)
|
||||||
|
|
||||||
|
# End of utterance
|
||||||
|
phoneme_ids.extend(id_map.get("$", [2])) # Default to [2] if not found
|
||||||
|
|
||||||
|
# Generate audio from phoneme IDs
|
||||||
|
syn_config = SynthesisConfig(
|
||||||
|
speaker_id=speaker_id,
|
||||||
|
length_scale=length_scale,
|
||||||
|
noise_scale=noise_scale,
|
||||||
|
noise_w_scale=noise_w_scale,
|
||||||
|
)
|
||||||
|
audio = voice.phoneme_ids_to_audio(phoneme_ids, syn_config)
|
||||||
|
|
||||||
|
# Convert to int16 and write to WAV
|
||||||
|
audio_int16 = audio_float_to_int16(audio[np.newaxis, :])
|
||||||
|
wav_file: wave.Wave_write = wave.open(str(wav_path), "wb")
|
||||||
|
with wav_file:
|
||||||
|
wav_file.setframerate(voice.config.sample_rate)
|
||||||
|
wav_file.setsampwidth(2)
|
||||||
|
wav_file.setnchannels(1)
|
||||||
|
wav_file.writeframes(audio_int16.flatten())
|
||||||
|
else:
|
||||||
|
with wave.open(str(wav_path), "wb") as wav_file:
|
||||||
|
voice.synthesize_wav(
|
||||||
|
text_input,
|
||||||
|
wav_file=wav_file,
|
||||||
|
syn_config=SynthesisConfig(
|
||||||
|
speaker_id=speaker_id,
|
||||||
|
length_scale=length_scale,
|
||||||
|
noise_scale=noise_scale,
|
||||||
|
noise_w_scale=noise_w_scale,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
sample_idx += 1
|
||||||
|
if sample_idx >= max_samples:
|
||||||
|
return
|
||||||
|
|
||||||
|
_LOGGER.info("Done")
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def generate_audio(
|
||||||
|
model,
|
||||||
|
speaker_1,
|
||||||
|
speaker_2,
|
||||||
|
phoneme_ids,
|
||||||
|
slerp_weight,
|
||||||
|
noise_scale,
|
||||||
|
noise_scale_w,
|
||||||
|
length_scale,
|
||||||
|
max_len,
|
||||||
|
) -> Tuple[torch.FloatTensor, torch.FloatTensor]:
|
||||||
|
x = torch.LongTensor(phoneme_ids)
|
||||||
|
x_lengths = torch.LongTensor([len(i) for i in phoneme_ids])
|
||||||
|
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
speaker_1 = speaker_1.cuda()
|
||||||
|
speaker_2 = speaker_2.cuda()
|
||||||
|
x = cast(torch.LongTensor, x.cuda())
|
||||||
|
x_lengths = cast(torch.LongTensor, x_lengths.cuda())
|
||||||
|
elif torch.backends.mps.is_available():
|
||||||
|
mps_device = torch.device("mps")
|
||||||
|
speaker_1 = speaker_1.to(mps_device)
|
||||||
|
speaker_2 = speaker_2.to(mps_device)
|
||||||
|
x = cast(torch.LongTensor, x.to(mps_device))
|
||||||
|
x_lengths = cast(torch.LongTensor, x_lengths.to(mps_device))
|
||||||
|
|
||||||
|
x, m_p_orig, logs_p_orig, x_mask = model.enc_p(x, x_lengths)
|
||||||
|
emb0 = model.emb_g(speaker_1)
|
||||||
|
emb1 = model.emb_g(speaker_2)
|
||||||
|
g = slerp(emb0, emb1, slerp_weight).unsqueeze(-1) # [b, h, 1]
|
||||||
|
|
||||||
|
if model.use_sdp:
|
||||||
|
logw = model.dp(x, x_mask, g=g, reverse=True, noise_scale=noise_scale_w)
|
||||||
|
else:
|
||||||
|
logw = model.dp(x, x_mask, g=g)
|
||||||
|
w = torch.exp(logw) * x_mask * length_scale
|
||||||
|
w_ceil = torch.ceil(w)
|
||||||
|
y_lengths = torch.clamp_min(torch.sum(w_ceil, [1, 2]), 1).long()
|
||||||
|
y_mask = torch.unsqueeze(
|
||||||
|
commons.sequence_mask(y_lengths, int(y_lengths.max().item())), 1
|
||||||
|
).type_as(x_mask)
|
||||||
|
attn_mask = torch.unsqueeze(x_mask, 2) * torch.unsqueeze(y_mask, -1)
|
||||||
|
attn = commons.generate_path(w_ceil, attn_mask)
|
||||||
|
|
||||||
|
m_p = torch.matmul(attn.squeeze(1), m_p_orig.transpose(1, 2)).transpose(
|
||||||
|
1, 2
|
||||||
|
) # [b, t', t], [b, t, d] -> [b, d, t']
|
||||||
|
logs_p = torch.matmul(attn.squeeze(1), logs_p_orig.transpose(1, 2)).transpose(
|
||||||
|
1, 2
|
||||||
|
) # [b, t', t], [b, t, d] -> [b, d, t']
|
||||||
|
|
||||||
|
z_p = m_p + torch.randn_like(m_p) * torch.exp(logs_p) * noise_scale
|
||||||
|
z = model.flow(z_p, y_mask, g=g, reverse=True)
|
||||||
|
o = model.dec((z * y_mask)[:, :, :max_len], g=g)
|
||||||
|
|
||||||
|
audio = cast(torch.FloatTensor, o)
|
||||||
|
phoneme_samples = cast(torch.FloatTensor, w_ceil * 256) # hop length
|
||||||
|
|
||||||
|
return audio, phoneme_samples
|
||||||
|
|
||||||
|
|
||||||
|
_PHONEMIZER = EspeakPhonemizer()
|
||||||
|
|
||||||
|
|
||||||
|
def get_phonemes(
|
||||||
|
voice: str,
|
||||||
|
config: Dict[str, Any],
|
||||||
|
text: str,
|
||||||
|
verbose: bool = False,
|
||||||
|
phoneme_input: bool = False,
|
||||||
|
) -> List[int]:
|
||||||
|
# Combine all sentences
|
||||||
|
if phoneme_input:
|
||||||
|
phonemes = list(unicodedata.normalize("NFD", text))
|
||||||
|
else:
|
||||||
|
phonemes = [
|
||||||
|
p
|
||||||
|
for sentence_phonemes in _PHONEMIZER.phonemize(voice, text)
|
||||||
|
for p in sentence_phonemes
|
||||||
|
]
|
||||||
|
if verbose is True:
|
||||||
|
_LOGGER.debug("Phonemes: %s", phonemes)
|
||||||
|
|
||||||
|
id_map = config["phoneme_id_map"]
|
||||||
|
|
||||||
|
# Beginning of utterance
|
||||||
|
phoneme_ids = list(id_map["^"])
|
||||||
|
phoneme_ids.extend(id_map["_"])
|
||||||
|
|
||||||
|
# Phoneme ids for just the text
|
||||||
|
text_phoneme_ids = []
|
||||||
|
|
||||||
|
for phoneme in phonemes:
|
||||||
|
p_ids = id_map.get(phoneme)
|
||||||
|
if p_ids is not None:
|
||||||
|
phoneme_ids.extend(p_ids)
|
||||||
|
text_phoneme_ids.extend(p_ids)
|
||||||
|
phoneme_ids.extend(id_map["_"])
|
||||||
|
text_phoneme_ids.extend(id_map["_"])
|
||||||
|
|
||||||
|
# End of utterance
|
||||||
|
phoneme_ids.extend(id_map["$"])
|
||||||
|
|
||||||
|
return phoneme_ids
|
||||||
|
|
||||||
|
|
||||||
|
def slerp(v1, v2, t: float, DOT_THR: float = 0.9995, zdim: int = -1):
|
||||||
|
"""SLERP for pytorch tensors interpolating `v1` to `v2` with scale of `t`.
|
||||||
|
|
||||||
|
`DOT_THR` determines when the vectors are too close to parallel.
|
||||||
|
If they are too close, then a regular linear interpolation is used.
|
||||||
|
|
||||||
|
`zdim` is the feature dimension over which to compute norms and find angles.
|
||||||
|
For example: if a sequence of 5 vectors is input with shape [5, 768]
|
||||||
|
Then `zdim = 1` or `zdim = -1` computes SLERP along the feature dim of 768.
|
||||||
|
|
||||||
|
Theory Reference:
|
||||||
|
https://splines.readthedocs.io/en/latest/rotation/slerp.html
|
||||||
|
PyTorch reference:
|
||||||
|
https://discuss.pytorch.org/t/help-regarding-slerp-function-for-generative-model-sampling/32475/3
|
||||||
|
Numpy reference:
|
||||||
|
https://gist.github.com/dvschultz/3af50c40df002da3b751efab1daddf2c
|
||||||
|
"""
|
||||||
|
|
||||||
|
# take the dot product between normalized vectors
|
||||||
|
v1_norm = v1 / torch.norm(v1, dim=zdim, keepdim=True)
|
||||||
|
v2_norm = v2 / torch.norm(v2, dim=zdim, keepdim=True)
|
||||||
|
dot = (v1_norm * v2_norm).sum(zdim)
|
||||||
|
|
||||||
|
# if the vectors are too close, return a simple linear interpolation
|
||||||
|
if (torch.abs(dot) > DOT_THR).any():
|
||||||
|
res = (1 - t) * v1 + t * v2
|
||||||
|
|
||||||
|
# else apply SLERP
|
||||||
|
else:
|
||||||
|
# compute the angle terms we need
|
||||||
|
theta = torch.acos(dot)
|
||||||
|
theta_t = theta * t
|
||||||
|
sin_theta = torch.sin(theta)
|
||||||
|
sin_theta_t = torch.sin(theta_t)
|
||||||
|
|
||||||
|
# compute the sine scaling terms for the vectors
|
||||||
|
s1 = torch.sin(theta - theta_t) / sin_theta
|
||||||
|
s2 = sin_theta_t / sin_theta
|
||||||
|
|
||||||
|
# interpolate the vectors
|
||||||
|
res = (s1.unsqueeze(zdim) * v1) + (s2.unsqueeze(zdim) * v2)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def audio_float_to_int16(
|
||||||
|
audio: np.ndarray, max_wav_value: float = 32767.0
|
||||||
|
) -> np.ndarray:
|
||||||
|
"""Normalize audio and convert to int16 range"""
|
||||||
|
audio_norm = audio * (max_wav_value / max(0.01, np.max(np.abs(audio))))
|
||||||
|
audio_norm = np.clip(audio_norm, -max_wav_value, max_wav_value)
|
||||||
|
audio_norm = audio_norm.astype("int16")
|
||||||
|
return audio_norm
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
"""Main entry point."""
|
||||||
|
|
||||||
|
# Get command line arguments
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("text")
|
||||||
|
parser.add_argument(
|
||||||
|
"--max-samples",
|
||||||
|
required=True,
|
||||||
|
type=int,
|
||||||
|
help="Maximum number of samples to generate",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--model",
|
||||||
|
required=True,
|
||||||
|
action="append",
|
||||||
|
help="Path to PyTorch generator (.pt) or Piper voice model (.onnx)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--batch-size", type=int, default=1, help="CUDA batch size (generator only)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--slerp-weights",
|
||||||
|
nargs="+",
|
||||||
|
type=float,
|
||||||
|
default=[0.5],
|
||||||
|
help="Speaker blending weights (generator only)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--length-scales",
|
||||||
|
nargs="+",
|
||||||
|
type=float,
|
||||||
|
default=[1.0, 0.75, 1.25, 1.4],
|
||||||
|
help="Audio length scales (< 1 is faster, > 1 is slower)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--noise-scales",
|
||||||
|
nargs="+",
|
||||||
|
type=float,
|
||||||
|
default=[0.667, 0.75, 0.85, 0.9, 1.0, 1.4],
|
||||||
|
help="Noise amounts added to audio (most voices use 0.667)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--noise-scale-ws",
|
||||||
|
nargs="+",
|
||||||
|
type=float,
|
||||||
|
default=[0.8],
|
||||||
|
help="Phoneme width variation (most voices use 0.8)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--output-dir",
|
||||||
|
default="output",
|
||||||
|
help="Directory to output WAV files (default: ./output)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--max-speakers",
|
||||||
|
type=int,
|
||||||
|
help="Maximum number of speakers to use (default: no limit)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--phoneme-input", action="store_true", help="Treat input text as phoneme input"
|
||||||
|
)
|
||||||
|
parser.add_argument("--verbose", action="store_true")
|
||||||
|
args = parser.parse_args().__dict__
|
||||||
|
|
||||||
|
# Generate speech
|
||||||
|
model_paths = [Path(m) for m in args["model"]]
|
||||||
|
assert model_paths
|
||||||
|
|
||||||
|
if any(mp for mp in model_paths[1:] if mp.suffix != model_paths[0].suffix):
|
||||||
|
_LOGGER.error("All models must have the same suffix (.pt or .onnx)")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if model_paths[0].suffix == ".onnx":
|
||||||
|
# Use Piper voice (.onnx)
|
||||||
|
generate_samples_onnx(**args)
|
||||||
|
elif model_paths[0].suffix == ".pt":
|
||||||
|
# Use PyTorch generator (.pt)
|
||||||
|
if len(model_paths) > 1:
|
||||||
|
_LOGGER.error("Only one generator (.pt) is supported")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
args["model"] = args["model"][0]
|
||||||
|
generate_samples(**args)
|
||||||
|
else:
|
||||||
|
_LOGGER.error("Models must have .pt or .onnx suffix")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import argparse
|
import argparse
|
||||||
import audioop
|
import audioop
|
||||||
import sys
|
|
||||||
import wave
|
import wave
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from audiomentations import Compose, ApplyImpulseResponse, Gain
|
from audiomentations import ApplyImpulseResponse, Compose, Gain
|
||||||
|
|
||||||
_DIR = Path(__file__).parent
|
_DIR = Path(__file__).parent
|
||||||
|
|
||||||
|
|
@ -15,14 +14,14 @@ def main() -> None:
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("input_dir")
|
parser.add_argument("input_dir")
|
||||||
parser.add_argument("output_dir")
|
parser.add_argument("output_dir")
|
||||||
parser.add_argument("--sample-rate", type=int)
|
parser.add_argument("--sample-rate", type=int, required=True)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
impulses = list((_DIR / "impulses").glob("*.wav"))
|
impulses = list((_DIR / "impulses").glob("*.wav"))
|
||||||
|
|
||||||
augment = Compose(
|
augment = Compose(
|
||||||
transforms=[
|
transforms=[
|
||||||
Gain(min_gain_in_db=-12, max_gain_in_db=0),
|
Gain(min_gain_db=-12, max_gain_db=0),
|
||||||
ApplyImpulseResponse(impulses),
|
ApplyImpulseResponse(impulses),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
@ -35,9 +34,10 @@ def main() -> None:
|
||||||
output_wav = output_dir / (input_wav.relative_to(input_dir))
|
output_wav = output_dir / (input_wav.relative_to(input_dir))
|
||||||
output_wav.parent.mkdir(parents=True, exist_ok=True)
|
output_wav.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
with wave.open(str(input_wav), "rb") as input_wav_file, wave.open(
|
with (
|
||||||
str(output_wav), "wb"
|
wave.open(str(input_wav), "rb") as input_wav_file,
|
||||||
) as output_wav_file:
|
wave.open(str(output_wav), "wb") as output_wav_file,
|
||||||
|
):
|
||||||
assert input_wav_file.getsampwidth() == 2
|
assert input_wav_file.getsampwidth() == 2
|
||||||
assert input_wav_file.getnchannels() == 1
|
assert input_wav_file.getnchannels() == 1
|
||||||
|
|
||||||
5
pylintrc
5
pylintrc
|
|
@ -1,3 +1,6 @@
|
||||||
|
[MASTER]
|
||||||
|
ignored-modules=torch
|
||||||
|
|
||||||
[MESSAGES CONTROL]
|
[MESSAGES CONTROL]
|
||||||
disable=
|
disable=
|
||||||
format,
|
format,
|
||||||
|
|
@ -31,7 +34,7 @@ disable=
|
||||||
missing-class-docstring,
|
missing-class-docstring,
|
||||||
missing-function-docstring,
|
missing-function-docstring,
|
||||||
import-error,
|
import-error,
|
||||||
consider-using-with
|
relative-beyond-top-level
|
||||||
|
|
||||||
[FORMAT]
|
[FORMAT]
|
||||||
expected-line-ending-format=LF
|
expected-line-ending-format=LF
|
||||||
|
|
|
||||||
45
pyproject.toml
Normal file
45
pyproject.toml
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=62.3"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "piper-sample-generator"
|
||||||
|
version = "3.2.0"
|
||||||
|
license = {text = "MIT"}
|
||||||
|
description = "Generate TTS audio samples for training wake word systems"
|
||||||
|
readme = "README.md"
|
||||||
|
authors = [
|
||||||
|
{name = "The Home Assistant Authors", email = "hello@home-assistant.io"}
|
||||||
|
]
|
||||||
|
keywords = ["piper", "sample", "tts", "wakeword"]
|
||||||
|
requires-python = ">=3.9.0"
|
||||||
|
dependencies = [
|
||||||
|
"audiomentations==0.33.0",
|
||||||
|
"piper-tts==1.3.0",
|
||||||
|
"numpy>=2,<3",
|
||||||
|
"torch>=2,<3",
|
||||||
|
"torchaudio",
|
||||||
|
"webrtcvad",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = [
|
||||||
|
"black==22.12.0",
|
||||||
|
"flake8==6.0.0",
|
||||||
|
"isort==5.11.3",
|
||||||
|
"mypy==0.991",
|
||||||
|
"pylint==2.15.9",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
"Source Code" = "http://github.com/rhasspy/piper-sample-generator"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
platforms = ["any"]
|
||||||
|
zip-safe = true
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
include = ["piper_sample_generator*"]
|
||||||
|
|
||||||
|
[tool.setuptools.package-data]
|
||||||
|
piper_sample_generator = ["impulses/*.wav"]
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
audiomentations==0.33.0
|
|
||||||
piper-phonemize==1.1.0
|
|
||||||
numpy<2
|
|
||||||
torch
|
|
||||||
torchaudio
|
|
||||||
webrtcvad
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
black==22.12.0
|
|
||||||
flake8==6.0.0
|
|
||||||
isort==5.11.3
|
|
||||||
mypy==0.991
|
|
||||||
pylint==2.15.9
|
|
||||||
|
|
@ -6,8 +6,13 @@ from pathlib import Path
|
||||||
_DIR = Path(__file__).parent
|
_DIR = Path(__file__).parent
|
||||||
_PROGRAM_DIR = _DIR.parent
|
_PROGRAM_DIR = _DIR.parent
|
||||||
_VENV_DIR = _PROGRAM_DIR / ".venv"
|
_VENV_DIR = _PROGRAM_DIR / ".venv"
|
||||||
_SCRIPT = _PROGRAM_DIR / "generate_samples.py"
|
_MODULE_DIR = _PROGRAM_DIR / "piper_sample_generator"
|
||||||
|
|
||||||
context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
|
if _VENV_DIR.exists():
|
||||||
subprocess.check_call([context.env_exe, "-m", "black", str(_SCRIPT)])
|
context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
|
||||||
subprocess.check_call([context.env_exe, "-m", "isort", str(_SCRIPT)])
|
python_exe = context.env_exe
|
||||||
|
else:
|
||||||
|
python_exe = "python3"
|
||||||
|
|
||||||
|
subprocess.check_call([python_exe, "-m", "black", str(_MODULE_DIR)])
|
||||||
|
subprocess.check_call([python_exe, "-m", "isort", str(_MODULE_DIR)])
|
||||||
|
|
|
||||||
19
script/lint
19
script/lint
|
|
@ -6,11 +6,16 @@ from pathlib import Path
|
||||||
_DIR = Path(__file__).parent
|
_DIR = Path(__file__).parent
|
||||||
_PROGRAM_DIR = _DIR.parent
|
_PROGRAM_DIR = _DIR.parent
|
||||||
_VENV_DIR = _PROGRAM_DIR / ".venv"
|
_VENV_DIR = _PROGRAM_DIR / ".venv"
|
||||||
_SCRIPT = _PROGRAM_DIR / "generate_samples.py"
|
_MODULE_DIR = _PROGRAM_DIR / "piper_sample_generator"
|
||||||
|
|
||||||
context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
|
if _VENV_DIR.exists():
|
||||||
subprocess.check_call([context.env_exe, "-m", "black", str(_SCRIPT), "--check"])
|
context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
|
||||||
subprocess.check_call([context.env_exe, "-m", "isort", str(_SCRIPT), "--check"])
|
python_exe = context.env_exe
|
||||||
subprocess.check_call([context.env_exe, "-m", "flake8", str(_SCRIPT)])
|
else:
|
||||||
subprocess.check_call([context.env_exe, "-m", "pylint", str(_SCRIPT)])
|
python_exe = "python3"
|
||||||
subprocess.check_call([context.env_exe, "-m", "mypy", str(_SCRIPT)])
|
|
||||||
|
subprocess.check_call([python_exe, "-m", "black", str(_MODULE_DIR), "--check"])
|
||||||
|
subprocess.check_call([python_exe, "-m", "isort", str(_MODULE_DIR), "--check"])
|
||||||
|
subprocess.check_call([python_exe, "-m", "flake8", str(_MODULE_DIR)])
|
||||||
|
subprocess.check_call([python_exe, "-m", "pylint", str(_MODULE_DIR)])
|
||||||
|
subprocess.check_call([python_exe, "-m", "mypy", str(_MODULE_DIR)])
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,10 @@ _DIR = Path(__file__).parent
|
||||||
_PROGRAM_DIR = _DIR.parent
|
_PROGRAM_DIR = _DIR.parent
|
||||||
_VENV_DIR = _PROGRAM_DIR / ".venv"
|
_VENV_DIR = _PROGRAM_DIR / ".venv"
|
||||||
|
|
||||||
context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
|
if _VENV_DIR.exists():
|
||||||
subprocess.check_call([context.env_exe, "generate_samples.py"] + sys.argv[1:])
|
context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
|
||||||
|
python_exe = context.env_exe
|
||||||
|
else:
|
||||||
|
python_exe = "python3"
|
||||||
|
|
||||||
|
subprocess.check_call([python_exe, "-m", "piper_sample_generator"] + sys.argv[1:])
|
||||||
|
|
|
||||||
12
script/setup
12
script/setup
|
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
import subprocess
|
import subprocess
|
||||||
import venv
|
import venv
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
@ -7,6 +8,9 @@ _DIR = Path(__file__).parent
|
||||||
_PROGRAM_DIR = _DIR.parent
|
_PROGRAM_DIR = _DIR.parent
|
||||||
_VENV_DIR = _PROGRAM_DIR / ".venv"
|
_VENV_DIR = _PROGRAM_DIR / ".venv"
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--dev", action="store_true", help="Install dev requirements")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Create virtual environment
|
# Create virtual environment
|
||||||
builder = venv.EnvBuilder(with_pip=True)
|
builder = venv.EnvBuilder(with_pip=True)
|
||||||
|
|
@ -19,4 +23,10 @@ subprocess.check_call(pip + ["install", "--upgrade", "pip"])
|
||||||
subprocess.check_call(pip + ["install", "--upgrade", "setuptools", "wheel"])
|
subprocess.check_call(pip + ["install", "--upgrade", "setuptools", "wheel"])
|
||||||
|
|
||||||
# Install requirements
|
# Install requirements
|
||||||
subprocess.check_call(pip + ["install", "-r", str(_PROGRAM_DIR / "requirements.txt")])
|
subprocess.check_call(pip + ["install", "-e", str(_PROGRAM_DIR)])
|
||||||
|
|
||||||
|
if args.dev:
|
||||||
|
# Install dev requirements
|
||||||
|
subprocess.check_call(
|
||||||
|
pip + ["install", "-e", f"{_PROGRAM_DIR}[dev]"]
|
||||||
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue