diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 916341d..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,19 +0,0 @@ -# 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` diff --git a/README.md b/README.md index 944d0cf..3558195 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,35 @@ # Piper Sample Generator -Generate spoken audio samples using [Piper][piper] for training a wake word system like [openWakeWord][] or [microWakeWord][]. +Generates samples using [Piper](https://github.com/rhasspy/piper/) for training a wake word system like [openWakeWord](https://github.com/dscripka/openWakeWord). -Supports normal [Piper voices][piper voices] or a special [generator][] that can mix speaker embeddings (English only). ## Install -``` sh -pip install piper-sample-generator -``` - -## 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: +Create a virtual environment and install the requirements: ``` 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' +git clone https://github.com/rhasspy/piper-sample-generator.git +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 ``` -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 ` 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][]): +Download the LibriTTS-R generator (exported from [checkpoint](https://huggingface.co/datasets/rhasspy/piper-checkpoints/tree/main/en/en_US/libritts_r/medium)): ``` 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' ``` + +## Run + Generate a small set of samples with the CLI: ``` sh -python3 -m piper_sample_generator 'okay piper.' --model models/en-us-libritts-high.pt --max-samples 10 --output-dir okay_piper/ +python3 generate_samples.py 'okay, piper.' --max-samples 10 --output-dir okay_piper/ ``` Check the `okay_piper/` directory for 10 WAV files (named `0.wav` to `9.wav`). @@ -53,38 +37,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: ``` sh -python3 -m piper_sample_generator 'okay piper.' --model models/en-us-libritts-high.pt --max-samples 100 --batch-size 10 --output-dir okay_piper/ +python3 generate_samples.py 'okay, piper.' --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). 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 the `--length-scales` (speaking speeds) and `--slerp-weights` (speaker blending) which are cycled per batch. +See `--help` for more options, including adjust 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 -Once you have samples generated, you can augment them using [audiomentation](https://iver56.github.io/audiomentations/): +Once you have samples generating, you can augment them using [audiomentation](https://iver56.github.io/audiomentations/): ``` sh -python3 -m piper_sample_generator.augment --sample-rate 22050 okay_piper/ okay_piper_augmented/ +python3 augment.py --sample-rate 16000 okay_piper/ okay_piper_augmented/ ``` This will do several things to each sample: 1. Randomly decrease the volume * The original samples are normalized, so different volume levels are needed -2. Randomly apply an [impulse response][] using the files in `piper_sample_generator/impulses/` +2. Randomly [apply an impulse response](https://iver56.github.io/audiomentations/waveform_transforms/apply_impulse_response/) using the files in `impulses/` * 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][]) +3. Resample to 16Khz for training (e.g., [openWakeWord](https://github.com/dscripka/openWakeWord)) - - -[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/ diff --git a/piper_sample_generator/augment.py b/augment.py similarity index 87% rename from piper_sample_generator/augment.py rename to augment.py index 47a226d..834d223 100644 --- a/piper_sample_generator/augment.py +++ b/augment.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 import argparse import audioop +import sys import wave from pathlib import Path import numpy as np -from audiomentations import ApplyImpulseResponse, Compose, Gain +from audiomentations import Compose, ApplyImpulseResponse, Gain _DIR = Path(__file__).parent @@ -14,14 +15,14 @@ def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("input_dir") parser.add_argument("output_dir") - parser.add_argument("--sample-rate", type=int, required=True) + parser.add_argument("--sample-rate", type=int) args = parser.parse_args() impulses = list((_DIR / "impulses").glob("*.wav")) augment = Compose( transforms=[ - Gain(min_gain_db=-12, max_gain_db=0), + Gain(min_gain_in_db=-12, max_gain_in_db=0), ApplyImpulseResponse(impulses), ] ) @@ -34,10 +35,9 @@ def main() -> None: output_wav = output_dir / (input_wav.relative_to(input_dir)) output_wav.parent.mkdir(parents=True, exist_ok=True) - with ( - wave.open(str(input_wav), "rb") as input_wav_file, - wave.open(str(output_wav), "wb") as output_wav_file, - ): + with wave.open(str(input_wav), "rb") as input_wav_file, wave.open( + str(output_wav), "wb" + ) as output_wav_file: assert input_wav_file.getsampwidth() == 2 assert input_wav_file.getnchannels() == 1 diff --git a/generate_samples.py b/generate_samples.py new file mode 100755 index 0000000..349ebde --- /dev/null +++ b/generate_samples.py @@ -0,0 +1,407 @@ +#!/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) diff --git a/piper_sample_generator/impulses/Accoustic2_Impulse.wav b/impulses/Accoustic2_Impulse.wav similarity index 100% rename from piper_sample_generator/impulses/Accoustic2_Impulse.wav rename to impulses/Accoustic2_Impulse.wav diff --git a/piper_sample_generator/impulses/Blatty Plate.wav b/impulses/Blatty Plate.wav similarity index 100% rename from piper_sample_generator/impulses/Blatty Plate.wav rename to impulses/Blatty Plate.wav diff --git a/piper_sample_generator/impulses/Concrete Room.wav b/impulses/Concrete Room.wav similarity index 100% rename from piper_sample_generator/impulses/Concrete Room.wav rename to impulses/Concrete Room.wav diff --git a/piper_sample_generator/impulses/Derlon Sanctuary.wav b/impulses/Derlon Sanctuary.wav similarity index 100% rename from piper_sample_generator/impulses/Derlon Sanctuary.wav rename to impulses/Derlon Sanctuary.wav diff --git a/piper_sample_generator/impulses/Fat Bass.wav b/impulses/Fat Bass.wav similarity index 100% rename from piper_sample_generator/impulses/Fat Bass.wav rename to impulses/Fat Bass.wav diff --git a/piper_sample_generator/impulses/Reverse Gate.wav b/impulses/Reverse Gate.wav similarity index 100% rename from piper_sample_generator/impulses/Reverse Gate.wav rename to impulses/Reverse Gate.wav diff --git a/piper_sample_generator/impulses/Symphonic.wav b/impulses/Symphonic.wav similarity index 100% rename from piper_sample_generator/impulses/Symphonic.wav rename to impulses/Symphonic.wav diff --git a/piper_sample_generator/impulses/ir_bathroom1.wav b/impulses/ir_bathroom1.wav similarity index 100% rename from piper_sample_generator/impulses/ir_bathroom1.wav rename to impulses/ir_bathroom1.wav diff --git a/models/de_DE-mls-medium.pt.json b/models/de_DE-mls-medium.pt.json deleted file mode 100755 index 0f50a27..0000000 --- a/models/de_DE-mls-medium.pt.json +++ /dev/null @@ -1,740 +0,0 @@ -{ - "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" -} diff --git a/models/fr_FR-mls-medium.pt.json b/models/fr_FR-mls-medium.pt.json deleted file mode 100755 index ab1bb41..0000000 --- a/models/fr_FR-mls-medium.pt.json +++ /dev/null @@ -1,629 +0,0 @@ -{ - "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" -} diff --git a/models/nl_NL-mls-medium.pt.json b/models/nl_NL-mls-medium.pt.json deleted file mode 100755 index 5673d00..0000000 --- a/models/nl_NL-mls-medium.pt.json +++ /dev/null @@ -1,554 +0,0 @@ -{ - "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" -} diff --git a/piper_sample_generator/__init__.py b/piper_sample_generator/__init__.py deleted file mode 100644 index 0142775..0000000 --- a/piper_sample_generator/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Piper sample generator.""" diff --git a/piper_sample_generator/__main__.py b/piper_sample_generator/__main__.py deleted file mode 100755 index 3074fb5..0000000 --- a/piper_sample_generator/__main__.py +++ /dev/null @@ -1,628 +0,0 @@ -#!/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() diff --git a/pylintrc b/pylintrc index 60fdb1d..22a70d0 100644 --- a/pylintrc +++ b/pylintrc @@ -1,6 +1,3 @@ -[MASTER] -ignored-modules=torch - [MESSAGES CONTROL] disable= format, @@ -34,7 +31,7 @@ disable= missing-class-docstring, missing-function-docstring, import-error, - relative-beyond-top-level + consider-using-with [FORMAT] expected-line-ending-format=LF diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 76cf7d2..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,45 +0,0 @@ -[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"] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c6ac75a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +audiomentations==0.33.0 +piper-phonemize==1.1.0 +numpy<2 +torch +torchaudio +webrtcvad diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 0000000..77190e6 --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,5 @@ +black==22.12.0 +flake8==6.0.0 +isort==5.11.3 +mypy==0.991 +pylint==2.15.9 diff --git a/script/format b/script/format index b8b283b..19fa1f7 100755 --- a/script/format +++ b/script/format @@ -6,13 +6,8 @@ from pathlib import Path _DIR = Path(__file__).parent _PROGRAM_DIR = _DIR.parent _VENV_DIR = _PROGRAM_DIR / ".venv" -_MODULE_DIR = _PROGRAM_DIR / "piper_sample_generator" +_SCRIPT = _PROGRAM_DIR / "generate_samples.py" -if _VENV_DIR.exists(): - context = venv.EnvBuilder().ensure_directories(_VENV_DIR) - 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)]) +context = venv.EnvBuilder().ensure_directories(_VENV_DIR) +subprocess.check_call([context.env_exe, "-m", "black", str(_SCRIPT)]) +subprocess.check_call([context.env_exe, "-m", "isort", str(_SCRIPT)]) diff --git a/script/lint b/script/lint index 34222f0..d56932d 100755 --- a/script/lint +++ b/script/lint @@ -6,16 +6,11 @@ from pathlib import Path _DIR = Path(__file__).parent _PROGRAM_DIR = _DIR.parent _VENV_DIR = _PROGRAM_DIR / ".venv" -_MODULE_DIR = _PROGRAM_DIR / "piper_sample_generator" +_SCRIPT = _PROGRAM_DIR / "generate_samples.py" -if _VENV_DIR.exists(): - context = venv.EnvBuilder().ensure_directories(_VENV_DIR) - python_exe = context.env_exe -else: - python_exe = "python3" - -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)]) +context = venv.EnvBuilder().ensure_directories(_VENV_DIR) +subprocess.check_call([context.env_exe, "-m", "black", str(_SCRIPT), "--check"]) +subprocess.check_call([context.env_exe, "-m", "isort", str(_SCRIPT), "--check"]) +subprocess.check_call([context.env_exe, "-m", "flake8", str(_SCRIPT)]) +subprocess.check_call([context.env_exe, "-m", "pylint", str(_SCRIPT)]) +subprocess.check_call([context.env_exe, "-m", "mypy", str(_SCRIPT)]) diff --git a/script/run b/script/run index ae921c9..5fa23ff 100755 --- a/script/run +++ b/script/run @@ -8,10 +8,5 @@ _DIR = Path(__file__).parent _PROGRAM_DIR = _DIR.parent _VENV_DIR = _PROGRAM_DIR / ".venv" -if _VENV_DIR.exists(): - 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:]) +context = venv.EnvBuilder().ensure_directories(_VENV_DIR) +subprocess.check_call([context.env_exe, "generate_samples.py"] + sys.argv[1:]) diff --git a/script/setup b/script/setup index 0ff2e95..92ec185 100755 --- a/script/setup +++ b/script/setup @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import argparse import subprocess import venv from pathlib import Path @@ -8,9 +7,6 @@ _DIR = Path(__file__).parent _PROGRAM_DIR = _DIR.parent _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 builder = venv.EnvBuilder(with_pip=True) @@ -23,10 +19,4 @@ subprocess.check_call(pip + ["install", "--upgrade", "pip"]) subprocess.check_call(pip + ["install", "--upgrade", "setuptools", "wheel"]) # Install requirements -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]"] - ) +subprocess.check_call(pip + ["install", "-r", str(_PROGRAM_DIR / "requirements.txt")])