Move to package

This commit is contained in:
Michael Hansen 2026-03-12 15:16:51 -05:00
parent c9d824c0e2
commit 1a8c49bd29
20 changed files with 47 additions and 63 deletions

View file

@ -1,5 +1,9 @@
# Changelog
## 3.2.0
- Refactor as `piper_sample_generator` package
## 3.1.0
- Support MPS acceleration on Apple Silicon

View file

@ -33,7 +33,7 @@ wget -O voices/en_US-lessac-medium.onnx.json 'https://huggingface.co/rhasspy/pip
Generate a small set of samples with the CLI:
``` sh
python3 generate_samples.py 'okay piper.' --model voices/en_US-lessac-medium.onnx --max-samples 10 --output-dir okay_piper/
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`).
@ -53,7 +53,7 @@ wget -O models/en-us-libritts-high.pt 'https://github.com/rhasspy/piper-sample-g
Generate a small set of samples with the CLI:
``` sh
python3 generate_samples.py 'okay piper.' --model models/en-us-libritts-high.pt --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`).
@ -61,7 +61,7 @@ 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 generate_samples.py 'okay piper.' --model models/en-us-libritts-high.pt --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).
@ -75,14 +75,14 @@ See `--help` for more options, including the `--length-scales` (speaking speeds)
Once you have samples generated, you can augment them using [audiomentation](https://iver56.github.io/audiomentations/):
``` sh
python3 augment.py --sample-rate 22050 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:
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 `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
3. Resample to 16Khz for training (e.g., [openWakeWord][])

View file

@ -0,0 +1 @@
"""Piper sample generator."""

View file

@ -16,6 +16,9 @@ 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__)
@ -176,7 +179,8 @@ def generate_samples(
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
# 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()

View file

@ -1,12 +1,11 @@
#!/usr/bin/env python3
import argparse
import audioop
import sys
import wave
from pathlib import Path
import numpy as np
from audiomentations import Compose, ApplyImpulseResponse, Gain
from audiomentations import ApplyImpulseResponse, Compose, Gain
_DIR = Path(__file__).parent
@ -35,9 +34,10 @@ 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

View file

@ -1,3 +1,6 @@
[MASTER]
ignored-modules=torch
[MESSAGES CONTROL]
disable=
format,
@ -31,14 +34,7 @@ disable=
missing-class-docstring,
missing-function-docstring,
import-error,
consider-using-with
relative-beyond-top-level
[FORMAT]
expected-line-ending-format=LF
[TYPECHECK]
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=numpy.*,torch.*

View file

@ -4,41 +4,31 @@ build-backend = "setuptools.build_meta"
[project]
name = "piper-sample-generator"
version = "3.1.0"
license = {text = "Apache-2.0"}
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"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Topic :: Text Processing :: Linguistic",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
requires-python = ">=3.9.0"
dependencies = [
"piper-tts>=1.3.0,<2",
"audiomentations==0.33.0",
"piper-tts==1.3.0",
"numpy>=2,<3",
"torch>=2,<3",
"torchaudio",
"audiomentations",
"numpy",
"webrtcvad",
]
[project.optional-dependencies]
dev = [
"black==24.8.0",
"flake8==7.2.0",
"mypy==1.14.0",
"pylint==3.2.7",
"pytest==8.3.5",
"black==22.12.0",
"flake8==6.0.0",
"isort==5.11.3",
"mypy==0.991",
"pylint==2.15.9",
]
[project.urls]
@ -49,4 +39,4 @@ platforms = ["any"]
zip-safe = true
[tool.setuptools.packages.find]
include = []
include = ["piper_sample_generator*"]

View file

@ -1,6 +0,0 @@
audiomentations==0.33.0
piper-phonemize==1.1.0
numpy<2
torch<2
torchaudio
webrtcvad

View file

@ -1,5 +0,0 @@
black==22.12.0
flake8==6.0.0
isort==5.11.3
mypy==0.991
pylint==2.15.9

View file

@ -6,7 +6,7 @@ from pathlib import Path
_DIR = Path(__file__).parent
_PROGRAM_DIR = _DIR.parent
_VENV_DIR = _PROGRAM_DIR / ".venv"
_SCRIPT = _PROGRAM_DIR / "generate_samples.py"
_MODULE_DIR = _PROGRAM_DIR / "piper_sample_generator"
if _VENV_DIR.exists():
context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
@ -14,5 +14,5 @@ if _VENV_DIR.exists():
else:
python_exe = "python3"
subprocess.check_call([python_exe, "-m", "black", str(_SCRIPT)])
subprocess.check_call([python_exe, "-m", "isort", str(_SCRIPT)])
subprocess.check_call([python_exe, "-m", "black", str(_MODULE_DIR)])
subprocess.check_call([python_exe, "-m", "isort", str(_MODULE_DIR)])

View file

@ -6,7 +6,7 @@ from pathlib import Path
_DIR = Path(__file__).parent
_PROGRAM_DIR = _DIR.parent
_VENV_DIR = _PROGRAM_DIR / ".venv"
_SCRIPT = _PROGRAM_DIR / "generate_samples.py"
_MODULE_DIR = _PROGRAM_DIR / "piper_sample_generator"
if _VENV_DIR.exists():
context = venv.EnvBuilder().ensure_directories(_VENV_DIR)
@ -14,8 +14,8 @@ if _VENV_DIR.exists():
else:
python_exe = "python3"
subprocess.check_call([python_exe, "-m", "black", str(_SCRIPT), "--check"])
subprocess.check_call([python_exe, "-m", "isort", str(_SCRIPT), "--check"])
subprocess.check_call([python_exe, "-m", "flake8", str(_SCRIPT)])
subprocess.check_call([python_exe, "-m", "pylint", str(_SCRIPT)])
subprocess.check_call([python_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)])

View file

@ -14,4 +14,4 @@ if _VENV_DIR.exists():
else:
python_exe = "python3"
subprocess.check_call([python_exe, "generate_samples.py"] + sys.argv[1:])
subprocess.check_call([python_exe, "-m", "piper_sample_generator"] + sys.argv[1:])