ownvoice 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ownvoice-0.1.0/.gitignore +28 -0
- ownvoice-0.1.0/LICENSE +21 -0
- ownvoice-0.1.0/PKG-INFO +183 -0
- ownvoice-0.1.0/README.md +147 -0
- ownvoice-0.1.0/npm/bin/ownvoice.js +57 -0
- ownvoice-0.1.0/npm/package.json +35 -0
- ownvoice-0.1.0/ownvoice/__init__.py +10 -0
- ownvoice-0.1.0/ownvoice/cli.py +216 -0
- ownvoice-0.1.0/ownvoice/data.py +139 -0
- ownvoice-0.1.0/ownvoice/infer.py +227 -0
- ownvoice-0.1.0/ownvoice/score.py +90 -0
- ownvoice-0.1.0/ownvoice/train.py +464 -0
- ownvoice-0.1.0/pyproject.toml +58 -0
- ownvoice-0.1.0/tests/__init__.py +0 -0
- ownvoice-0.1.0/tests/conftest.py +36 -0
- ownvoice-0.1.0/tests/test_cli.py +267 -0
- ownvoice-0.1.0/tests/test_data.py +128 -0
- ownvoice-0.1.0/tests/test_infer.py +160 -0
- ownvoice-0.1.0/tests/test_score.py +102 -0
- ownvoice-0.1.0/tests/test_train.py +301 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
env/
|
|
13
|
+
|
|
14
|
+
# Test / tooling caches
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.ruff_cache/
|
|
18
|
+
.coverage
|
|
19
|
+
htmlcov/
|
|
20
|
+
|
|
21
|
+
# OwnVoice runtime output (adapters, generated audio) -- never commit real voice data or trained weights
|
|
22
|
+
ownvoice-adapter/
|
|
23
|
+
*.safetensors
|
|
24
|
+
ownvoice-output.wav
|
|
25
|
+
*.wav
|
|
26
|
+
|
|
27
|
+
# OS
|
|
28
|
+
.DS_Store
|
ownvoice-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rudrendu Paul
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ownvoice-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ownvoice
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Train a LoRA voice adapter for pocket-tts and own the resulting model, no API lock-in.
|
|
5
|
+
Project-URL: Homepage, https://github.com/RudrenduPaul/ownvoice
|
|
6
|
+
Project-URL: Repository, https://github.com/RudrenduPaul/ownvoice
|
|
7
|
+
Project-URL: Issues, https://github.com/RudrenduPaul/ownvoice/issues
|
|
8
|
+
Author: Rudrendu Paul
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: fine-tuning,lora,peft,pocket-tts,text-to-speech,tts,voice-cloning
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: numba>=0.59
|
|
24
|
+
Requires-Dist: peft>=0.11.0
|
|
25
|
+
Requires-Dist: pocket-tts>=0.1.0
|
|
26
|
+
Requires-Dist: resemblyzer>=0.1.4
|
|
27
|
+
Requires-Dist: safetensors>=0.4.0
|
|
28
|
+
Requires-Dist: soundfile>=0.12.0
|
|
29
|
+
Requires-Dist: torch>=2.5
|
|
30
|
+
Requires-Dist: torchaudio>=2.5
|
|
31
|
+
Requires-Dist: typer>=0.12.0
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest-mock>=3.14.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# OwnVoice
|
|
38
|
+
|
|
39
|
+
Train a LoRA voice adapter for [pocket-tts](https://github.com/kyutai-labs/pocket-tts) and keep the result: a file on your own disk, not an API subscription.
|
|
40
|
+
|
|
41
|
+
## Why this exists
|
|
42
|
+
|
|
43
|
+
pocket-tts is a genuinely good, MIT-licensed, CPU-capable local text-to-speech model from Kyutai. Its own maintainers have been clear that fine-tuning code isn't coming any time soon: on [issue #30](https://github.com/kyutai-labs/pocket-tts/issues/30), maintainer @vvolhejn wrote "We are not planning to release fine-tuning code for our TTS and STT models in the near future," and 18 people reacted to that thread asking for exactly this. OwnVoice is a small, standalone CLI that fills that specific gap: point it at a handful of your own voice recordings, and it trains a LoRA adapter you keep and run yourself.
|
|
44
|
+
|
|
45
|
+
It is not a hosted service, it has no billing, and it does not track usage. It is a training script, an inference script, and a scoring script, wired together behind three CLI commands.
|
|
46
|
+
|
|
47
|
+
## What OwnVoice is not
|
|
48
|
+
|
|
49
|
+
pocket-tts already ships zero-shot voice cloning out of the box: pass a `.wav` file to `--voice` (or call `get_state_for_audio_prompt()` from Python) and it clones that voice with no training step at all. If that is all you need, use pocket-tts directly, it is simpler and faster.
|
|
50
|
+
|
|
51
|
+
OwnVoice exists for a narrower case: baking a voice permanently into trained weights, so generation no longer depends on distributing or re-processing a reference audio clip at runtime, with (based on the training objective, not yet independently benchmarked at scale) more consistent output across many generations than a single-clip zero-shot embedding tends to produce. That is the specific gap the 18 reactors on issue #30 were describing, and it is the only thing OwnVoice adds on top of what pocket-tts already does well.
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
Requires Python 3.11 or newer.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install git+https://github.com/RudrenduPaul/ownvoice
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**npx / agent-native environments:** OwnVoice is a Python/PyTorch CLI, so the npm package below is a thin wrapper, not a Node reimplementation. It bootstraps into the real CLI via [`uv`](https://docs.astral.sh/uv/) or `pipx`, whichever is already on `PATH` -- useful for coding-agent sandboxes and CI runners that default to a Node toolchain. **Not yet published to the npm registry** (coming soon, tracked alongside the PyPI release):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx ownvoice check
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Torch and CUDA:** `ownvoice check` (see below) needs no GPU at all and runs on CPU, matching pocket-tts's own CPU-capable design. Training a real adapter is much faster on an NVIDIA GPU. If you have one, install the CUDA build of PyTorch first by following [pytorch.org/get-started/locally](https://pytorch.org/get-started/locally/), then install OwnVoice on top of it, so `pip` does not silently pull the CPU-only wheel instead. On Apple Silicon or a CPU-only machine, the default `pip install` of torch is fine: `ownvoice check` and `ownvoice infer` will run normally, `ownvoice train` will just take longer per epoch.
|
|
68
|
+
|
|
69
|
+
## Quickstart
|
|
70
|
+
|
|
71
|
+
### 1. `ownvoice check`, the free Day-0 validation
|
|
72
|
+
|
|
73
|
+
Before recording anything or renting a GPU, confirm that PEFT's LoRA injection actually works against pocket-tts's real model structure. This is entirely free: CPU only, no training, no GPU.
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
$ ownvoice check
|
|
77
|
+
[ownvoice check] PASS: PEFT LoRA injection succeeded against pocket-tts's flow_lm module (target_modules="all-linear").
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
If it fails, OwnVoice prints the model's real module tree instead of a raw stack trace, so you can see exactly what did not match and report it precisely:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
$ ownvoice check
|
|
84
|
+
[ownvoice check] FAIL: PEFT LoRA injection failed against pocket-tts's flow_lm module structure: <error detail>. Please post an honest blocker (this error plus the module tree above) as a comment on https://github.com/kyutai-labs/pocket-tts/issues/30 rather than working around it silently, that issue is exactly where this gap needs to be visible.
|
|
85
|
+
|
|
86
|
+
Module tree (for debugging / for the issue #30 blocker post):
|
|
87
|
+
<root>: FlowLMModel
|
|
88
|
+
input_linear: Linear
|
|
89
|
+
transformer: StreamingTransformer
|
|
90
|
+
transformer.layers.0.self_attn.in_proj: Linear
|
|
91
|
+
transformer.layers.0.self_attn.out_proj: Linear
|
|
92
|
+
...
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 2. `ownvoice train`
|
|
96
|
+
|
|
97
|
+
Record 5 to 10 minutes of clean audio of the voice you want to train (your own voice, with your own consent, see Consent and misuse below), split into a few `.wav` clips in one directory, then point OwnVoice at it:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
$ ownvoice train --voice-clips ./my-voice-clips
|
|
101
|
+
[ownvoice train] USABLE ADAPTER
|
|
102
|
+
Usable adapter (similarity 0.812 >= 0.75). Try it now:
|
|
103
|
+
ownvoice infer --adapter ownvoice-adapter/adapter.safetensors --text "This is my own voice, trained with OwnVoice."
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Only `--voice-clips` is required. Every other flag has a sensible default: `--out` (`./ownvoice-adapter/`), `--epochs` (10), `--lora-rank` (8), `--lora-alpha` (16), `--lora-dropout` (0.05), `--learning-rate` (1e-4), and `--eval-text` (the sentence synthesized to compute the similarity score).
|
|
107
|
+
|
|
108
|
+
A run that finishes but does not clear the similarity bar still exits `0`. It is a labeled result with a concrete next step, not a crash:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
$ ownvoice train --voice-clips ./my-voice-clips
|
|
112
|
+
[ownvoice train] BELOW THRESHOLD
|
|
113
|
+
Below threshold (similarity 0.612 < 0.75). The adapter was still saved, try more/cleaner voice clips, more epochs, or a higher --lora-rank, then re-run. You can still listen to it:
|
|
114
|
+
ownvoice infer --adapter ownvoice-adapter/adapter.safetensors --text "This is my own voice, trained with OwnVoice."
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Only a data-loading problem (no usable clips) or a caught PEFT-injection failure exits non-zero. Every successful run writes `adapter.safetensors` and `metadata.json` (training config, the similarity score, a timestamp) to the output directory: two files you keep, with no server round-trip needed to use them again.
|
|
118
|
+
|
|
119
|
+
### 3. `ownvoice infer`
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
$ ownvoice infer --adapter ownvoice-adapter/adapter.safetensors --text "Hello, this is my own voice."
|
|
123
|
+
[ownvoice infer] Wrote ownvoice-output.wav
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Every subcommand also supports `--json` for a structured, machine-parseable output mode, useful if a script or an agent is calling `ownvoice` programmatically instead of a person reading the terminal:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
$ ownvoice check --json
|
|
130
|
+
{"success": true, "message": "PEFT LoRA injection succeeded against pocket-tts's flow_lm module (target_modules=\"all-linear\").", "module_tree": null}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## How it works
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
voice clips (wav)
|
|
137
|
+
|
|
|
138
|
+
v
|
|
139
|
+
data.py --validate format/duration--> clean clip set
|
|
140
|
+
|
|
|
141
|
+
v
|
|
142
|
+
train.py --PEFT LoRA (target_modules="all-linear")--> adapter.safetensors + metadata.json
|
|
143
|
+
|
|
|
144
|
+
v
|
|
145
|
+
infer.py --generate test utterance--> synthesized audio
|
|
146
|
+
|
|
|
147
|
+
v
|
|
148
|
+
score.py --resample to 16kHz mono--> Resemblyzer cosine similarity
|
|
149
|
+
|
|
|
150
|
+
v
|
|
151
|
+
CLI report (>= 0.75 = usable adapter, below triggers a labeled next-step message)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`ownvoice/data.py` loads and validates the voice-clip directory. `ownvoice/train.py` loads pocket-tts's frozen base model, injects a LoRA adapter into its `flow_lm` transformer with PEFT (`target_modules="all-linear"`), runs the training loop, and saves the adapter plus a manifest. `ownvoice/infer.py` loads a saved adapter back onto the base model and generates speech. `ownvoice/score.py` resamples audio to 16kHz mono with `torchaudio.transforms.Resample` and scores speaker similarity with [Resemblyzer](https://github.com/resemble-ai/Resemblyzer).
|
|
155
|
+
|
|
156
|
+
OwnVoice is intentionally single-model: it wraps pocket-tts only, with no abstraction layer for a second base model, since none is in scope.
|
|
157
|
+
|
|
158
|
+
## Consent and misuse
|
|
159
|
+
|
|
160
|
+
This tool clones a voice from audio you have the right to use. Do not clone someone else's voice, or a public figure's voice, without their explicit consent. OwnVoice ships no bulk-generation or auto-scaling feature in this version, keeping the blast radius of any single misuse case small.
|
|
161
|
+
|
|
162
|
+
## Setup-time benchmark vs comparable tools
|
|
163
|
+
|
|
164
|
+
| Tool | Time to first working setup | Notable design choice | Source |
|
|
165
|
+
|---|---|---|---|
|
|
166
|
+
| [kokoro-tts](https://github.com/nazdridoy/kokoro-tts) | under 2 minutes | `pip install git+...`, instant CLI synthesis, no fine-tuning | kokoro-tts README |
|
|
167
|
+
| [Unsloth](https://unsloth.ai) | under 1 minute to start a run | one-command training start (`uv pip install`) | Unsloth docs |
|
|
168
|
+
| [pocket-tts](https://github.com/kyutai-labs/pocket-tts) | seconds | `--voice <wav>` zero-shot cloning, no training available | pocket-tts README |
|
|
169
|
+
| **OwnVoice** | under 2 minutes to a confirmed-working training environment | `ownvoice check`: free, instant, CPU-only PEFT-compatibility validation before spending anything on a GPU | this repo |
|
|
170
|
+
|
|
171
|
+
OwnVoice's own training run is real GPU time, honestly labeled and not hidden behind a fake progress bar, the same category norm Unsloth uses. What OwnVoice compresses to under two minutes is everything *before* that: confirming your environment actually works.
|
|
172
|
+
|
|
173
|
+
## Implementation status
|
|
174
|
+
|
|
175
|
+
This is a young v0.1. `ownvoice check`, the CLI argument parsing, voice-clip validation, the similarity scoring math, and the adapter/manifest save and load path are implemented and covered by the test suite (`pytest`). LoRA injection was verified structurally against pocket-tts's real source and then confirmed for real: `ownvoice check` was run against pocket-tts's actual downloaded weights, on CPU, and PEFT's `target_modules="all-linear"` injection genuinely succeeded. The full training and generation path has since been verified end to end for real too: a real 2-epoch LoRA training run against loaded pocket-tts weights produced a finite, non-NaN flow-matching loss, and the resulting adapter produced a real, non-silent generated `.wav` file via `ownvoice infer`. That validation surfaced two real gaps in the naive approach and fixed them: (1) pocket-tts's published, inference-only PyPI package does not actually expose a way to compute the training loss through `FlowLMModel.forward()` despite its own docstring claiming otherwise, so OwnVoice computes the flow-matching loss directly from `flow_lm`'s real submodules instead; (2) swapping `base_model.flow_lm` to the PEFT-wrapped model before calling `generate_audio()` breaks pocket-tts's internal KV-cache state lookup -- no swap is needed at all, since PEFT's LoRA injection already mutates `base_model.flow_lm` in place. One real, external limitation to know about: the publicly downloadable pocket-tts weights (`kyutai/pocket-tts-without-voice-cloning`) refuse a raw reference-clip path/URL outright; OwnVoice works around this by pre-loading and resampling the clip itself, but voice-cloning fidelity from that checkpoint is a known limitation of the base model, not an OwnVoice bug -- for kyutai's best-quality cloning weights, request gated access at [huggingface.co/kyutai/pocket-tts](https://huggingface.co/kyutai/pocket-tts). Run `ownvoice check` yourself and read the source before trusting any of it further, that is the right amount of skepticism for a v0.1.
|
|
176
|
+
|
|
177
|
+
## Contributing
|
|
178
|
+
|
|
179
|
+
Issues and PRs welcome, MIT licensed throughout. If you want to help close the actual gap this project targets, the most useful contribution is upstream: a lightweight LoRA-adapter training script contributed back to [kyutai-labs/pocket-tts](https://github.com/kyutai-labs/pocket-tts) itself, discussed on [issue #30](https://github.com/kyutai-labs/pocket-tts/issues/30).
|
|
180
|
+
|
|
181
|
+
## License
|
|
182
|
+
|
|
183
|
+
MIT. See [LICENSE](LICENSE).
|
ownvoice-0.1.0/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# OwnVoice
|
|
2
|
+
|
|
3
|
+
Train a LoRA voice adapter for [pocket-tts](https://github.com/kyutai-labs/pocket-tts) and keep the result: a file on your own disk, not an API subscription.
|
|
4
|
+
|
|
5
|
+
## Why this exists
|
|
6
|
+
|
|
7
|
+
pocket-tts is a genuinely good, MIT-licensed, CPU-capable local text-to-speech model from Kyutai. Its own maintainers have been clear that fine-tuning code isn't coming any time soon: on [issue #30](https://github.com/kyutai-labs/pocket-tts/issues/30), maintainer @vvolhejn wrote "We are not planning to release fine-tuning code for our TTS and STT models in the near future," and 18 people reacted to that thread asking for exactly this. OwnVoice is a small, standalone CLI that fills that specific gap: point it at a handful of your own voice recordings, and it trains a LoRA adapter you keep and run yourself.
|
|
8
|
+
|
|
9
|
+
It is not a hosted service, it has no billing, and it does not track usage. It is a training script, an inference script, and a scoring script, wired together behind three CLI commands.
|
|
10
|
+
|
|
11
|
+
## What OwnVoice is not
|
|
12
|
+
|
|
13
|
+
pocket-tts already ships zero-shot voice cloning out of the box: pass a `.wav` file to `--voice` (or call `get_state_for_audio_prompt()` from Python) and it clones that voice with no training step at all. If that is all you need, use pocket-tts directly, it is simpler and faster.
|
|
14
|
+
|
|
15
|
+
OwnVoice exists for a narrower case: baking a voice permanently into trained weights, so generation no longer depends on distributing or re-processing a reference audio clip at runtime, with (based on the training objective, not yet independently benchmarked at scale) more consistent output across many generations than a single-clip zero-shot embedding tends to produce. That is the specific gap the 18 reactors on issue #30 were describing, and it is the only thing OwnVoice adds on top of what pocket-tts already does well.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
Requires Python 3.11 or newer.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install git+https://github.com/RudrenduPaul/ownvoice
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**npx / agent-native environments:** OwnVoice is a Python/PyTorch CLI, so the npm package below is a thin wrapper, not a Node reimplementation. It bootstraps into the real CLI via [`uv`](https://docs.astral.sh/uv/) or `pipx`, whichever is already on `PATH` -- useful for coding-agent sandboxes and CI runners that default to a Node toolchain. **Not yet published to the npm registry** (coming soon, tracked alongside the PyPI release):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx ownvoice check
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Torch and CUDA:** `ownvoice check` (see below) needs no GPU at all and runs on CPU, matching pocket-tts's own CPU-capable design. Training a real adapter is much faster on an NVIDIA GPU. If you have one, install the CUDA build of PyTorch first by following [pytorch.org/get-started/locally](https://pytorch.org/get-started/locally/), then install OwnVoice on top of it, so `pip` does not silently pull the CPU-only wheel instead. On Apple Silicon or a CPU-only machine, the default `pip install` of torch is fine: `ownvoice check` and `ownvoice infer` will run normally, `ownvoice train` will just take longer per epoch.
|
|
32
|
+
|
|
33
|
+
## Quickstart
|
|
34
|
+
|
|
35
|
+
### 1. `ownvoice check`, the free Day-0 validation
|
|
36
|
+
|
|
37
|
+
Before recording anything or renting a GPU, confirm that PEFT's LoRA injection actually works against pocket-tts's real model structure. This is entirely free: CPU only, no training, no GPU.
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
$ ownvoice check
|
|
41
|
+
[ownvoice check] PASS: PEFT LoRA injection succeeded against pocket-tts's flow_lm module (target_modules="all-linear").
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
If it fails, OwnVoice prints the model's real module tree instead of a raw stack trace, so you can see exactly what did not match and report it precisely:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
$ ownvoice check
|
|
48
|
+
[ownvoice check] FAIL: PEFT LoRA injection failed against pocket-tts's flow_lm module structure: <error detail>. Please post an honest blocker (this error plus the module tree above) as a comment on https://github.com/kyutai-labs/pocket-tts/issues/30 rather than working around it silently, that issue is exactly where this gap needs to be visible.
|
|
49
|
+
|
|
50
|
+
Module tree (for debugging / for the issue #30 blocker post):
|
|
51
|
+
<root>: FlowLMModel
|
|
52
|
+
input_linear: Linear
|
|
53
|
+
transformer: StreamingTransformer
|
|
54
|
+
transformer.layers.0.self_attn.in_proj: Linear
|
|
55
|
+
transformer.layers.0.self_attn.out_proj: Linear
|
|
56
|
+
...
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. `ownvoice train`
|
|
60
|
+
|
|
61
|
+
Record 5 to 10 minutes of clean audio of the voice you want to train (your own voice, with your own consent, see Consent and misuse below), split into a few `.wav` clips in one directory, then point OwnVoice at it:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
$ ownvoice train --voice-clips ./my-voice-clips
|
|
65
|
+
[ownvoice train] USABLE ADAPTER
|
|
66
|
+
Usable adapter (similarity 0.812 >= 0.75). Try it now:
|
|
67
|
+
ownvoice infer --adapter ownvoice-adapter/adapter.safetensors --text "This is my own voice, trained with OwnVoice."
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Only `--voice-clips` is required. Every other flag has a sensible default: `--out` (`./ownvoice-adapter/`), `--epochs` (10), `--lora-rank` (8), `--lora-alpha` (16), `--lora-dropout` (0.05), `--learning-rate` (1e-4), and `--eval-text` (the sentence synthesized to compute the similarity score).
|
|
71
|
+
|
|
72
|
+
A run that finishes but does not clear the similarity bar still exits `0`. It is a labeled result with a concrete next step, not a crash:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
$ ownvoice train --voice-clips ./my-voice-clips
|
|
76
|
+
[ownvoice train] BELOW THRESHOLD
|
|
77
|
+
Below threshold (similarity 0.612 < 0.75). The adapter was still saved, try more/cleaner voice clips, more epochs, or a higher --lora-rank, then re-run. You can still listen to it:
|
|
78
|
+
ownvoice infer --adapter ownvoice-adapter/adapter.safetensors --text "This is my own voice, trained with OwnVoice."
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Only a data-loading problem (no usable clips) or a caught PEFT-injection failure exits non-zero. Every successful run writes `adapter.safetensors` and `metadata.json` (training config, the similarity score, a timestamp) to the output directory: two files you keep, with no server round-trip needed to use them again.
|
|
82
|
+
|
|
83
|
+
### 3. `ownvoice infer`
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
$ ownvoice infer --adapter ownvoice-adapter/adapter.safetensors --text "Hello, this is my own voice."
|
|
87
|
+
[ownvoice infer] Wrote ownvoice-output.wav
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Every subcommand also supports `--json` for a structured, machine-parseable output mode, useful if a script or an agent is calling `ownvoice` programmatically instead of a person reading the terminal:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
$ ownvoice check --json
|
|
94
|
+
{"success": true, "message": "PEFT LoRA injection succeeded against pocket-tts's flow_lm module (target_modules=\"all-linear\").", "module_tree": null}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## How it works
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
voice clips (wav)
|
|
101
|
+
|
|
|
102
|
+
v
|
|
103
|
+
data.py --validate format/duration--> clean clip set
|
|
104
|
+
|
|
|
105
|
+
v
|
|
106
|
+
train.py --PEFT LoRA (target_modules="all-linear")--> adapter.safetensors + metadata.json
|
|
107
|
+
|
|
|
108
|
+
v
|
|
109
|
+
infer.py --generate test utterance--> synthesized audio
|
|
110
|
+
|
|
|
111
|
+
v
|
|
112
|
+
score.py --resample to 16kHz mono--> Resemblyzer cosine similarity
|
|
113
|
+
|
|
|
114
|
+
v
|
|
115
|
+
CLI report (>= 0.75 = usable adapter, below triggers a labeled next-step message)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`ownvoice/data.py` loads and validates the voice-clip directory. `ownvoice/train.py` loads pocket-tts's frozen base model, injects a LoRA adapter into its `flow_lm` transformer with PEFT (`target_modules="all-linear"`), runs the training loop, and saves the adapter plus a manifest. `ownvoice/infer.py` loads a saved adapter back onto the base model and generates speech. `ownvoice/score.py` resamples audio to 16kHz mono with `torchaudio.transforms.Resample` and scores speaker similarity with [Resemblyzer](https://github.com/resemble-ai/Resemblyzer).
|
|
119
|
+
|
|
120
|
+
OwnVoice is intentionally single-model: it wraps pocket-tts only, with no abstraction layer for a second base model, since none is in scope.
|
|
121
|
+
|
|
122
|
+
## Consent and misuse
|
|
123
|
+
|
|
124
|
+
This tool clones a voice from audio you have the right to use. Do not clone someone else's voice, or a public figure's voice, without their explicit consent. OwnVoice ships no bulk-generation or auto-scaling feature in this version, keeping the blast radius of any single misuse case small.
|
|
125
|
+
|
|
126
|
+
## Setup-time benchmark vs comparable tools
|
|
127
|
+
|
|
128
|
+
| Tool | Time to first working setup | Notable design choice | Source |
|
|
129
|
+
|---|---|---|---|
|
|
130
|
+
| [kokoro-tts](https://github.com/nazdridoy/kokoro-tts) | under 2 minutes | `pip install git+...`, instant CLI synthesis, no fine-tuning | kokoro-tts README |
|
|
131
|
+
| [Unsloth](https://unsloth.ai) | under 1 minute to start a run | one-command training start (`uv pip install`) | Unsloth docs |
|
|
132
|
+
| [pocket-tts](https://github.com/kyutai-labs/pocket-tts) | seconds | `--voice <wav>` zero-shot cloning, no training available | pocket-tts README |
|
|
133
|
+
| **OwnVoice** | under 2 minutes to a confirmed-working training environment | `ownvoice check`: free, instant, CPU-only PEFT-compatibility validation before spending anything on a GPU | this repo |
|
|
134
|
+
|
|
135
|
+
OwnVoice's own training run is real GPU time, honestly labeled and not hidden behind a fake progress bar, the same category norm Unsloth uses. What OwnVoice compresses to under two minutes is everything *before* that: confirming your environment actually works.
|
|
136
|
+
|
|
137
|
+
## Implementation status
|
|
138
|
+
|
|
139
|
+
This is a young v0.1. `ownvoice check`, the CLI argument parsing, voice-clip validation, the similarity scoring math, and the adapter/manifest save and load path are implemented and covered by the test suite (`pytest`). LoRA injection was verified structurally against pocket-tts's real source and then confirmed for real: `ownvoice check` was run against pocket-tts's actual downloaded weights, on CPU, and PEFT's `target_modules="all-linear"` injection genuinely succeeded. The full training and generation path has since been verified end to end for real too: a real 2-epoch LoRA training run against loaded pocket-tts weights produced a finite, non-NaN flow-matching loss, and the resulting adapter produced a real, non-silent generated `.wav` file via `ownvoice infer`. That validation surfaced two real gaps in the naive approach and fixed them: (1) pocket-tts's published, inference-only PyPI package does not actually expose a way to compute the training loss through `FlowLMModel.forward()` despite its own docstring claiming otherwise, so OwnVoice computes the flow-matching loss directly from `flow_lm`'s real submodules instead; (2) swapping `base_model.flow_lm` to the PEFT-wrapped model before calling `generate_audio()` breaks pocket-tts's internal KV-cache state lookup -- no swap is needed at all, since PEFT's LoRA injection already mutates `base_model.flow_lm` in place. One real, external limitation to know about: the publicly downloadable pocket-tts weights (`kyutai/pocket-tts-without-voice-cloning`) refuse a raw reference-clip path/URL outright; OwnVoice works around this by pre-loading and resampling the clip itself, but voice-cloning fidelity from that checkpoint is a known limitation of the base model, not an OwnVoice bug -- for kyutai's best-quality cloning weights, request gated access at [huggingface.co/kyutai/pocket-tts](https://huggingface.co/kyutai/pocket-tts). Run `ownvoice check` yourself and read the source before trusting any of it further, that is the right amount of skepticism for a v0.1.
|
|
140
|
+
|
|
141
|
+
## Contributing
|
|
142
|
+
|
|
143
|
+
Issues and PRs welcome, MIT licensed throughout. If you want to help close the actual gap this project targets, the most useful contribution is upstream: a lightweight LoRA-adapter training script contributed back to [kyutai-labs/pocket-tts](https://github.com/kyutai-labs/pocket-tts) itself, discussed on [issue #30](https://github.com/kyutai-labs/pocket-tts/issues/30).
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require('node:child_process');
|
|
5
|
+
|
|
6
|
+
function commandExists(cmd) {
|
|
7
|
+
const probe = process.platform === 'win32' ? 'where' : 'which';
|
|
8
|
+
const result = spawnSync(probe, [cmd], { stdio: 'ignore' });
|
|
9
|
+
return result.status === 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function run(cmd, args) {
|
|
13
|
+
const result = spawnSync(cmd, args, { stdio: 'inherit' });
|
|
14
|
+
if (result.error) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return result.status;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const args = process.argv.slice(2);
|
|
21
|
+
|
|
22
|
+
// ownvoice is a Python/PyTorch package (LoRA fine-tuning on top of pocket-tts).
|
|
23
|
+
// This wrapper never bundles a platform binary -- there isn't one to bundle --
|
|
24
|
+
// it bootstraps into the real ownvoice Python CLI via whichever Python runner
|
|
25
|
+
// is already on PATH, preferring uv/uvx since that's the primary documented
|
|
26
|
+
// install path (`uvx ownvoice train ...`) and increasingly present by default
|
|
27
|
+
// in agent and CI sandboxes.
|
|
28
|
+
const runners = [
|
|
29
|
+
{ cmd: 'uvx', build: (a) => ['ownvoice', ...a] },
|
|
30
|
+
{ cmd: 'pipx', build: (a) => ['run', 'ownvoice', ...a] },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
for (const runner of runners) {
|
|
34
|
+
if (commandExists(runner.cmd)) {
|
|
35
|
+
const status = run(runner.cmd, runner.build(args));
|
|
36
|
+
if (status !== null) {
|
|
37
|
+
process.exit(status);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.error(
|
|
43
|
+
[
|
|
44
|
+
'ownvoice: no Python runner found (checked uvx, pipx).',
|
|
45
|
+
'',
|
|
46
|
+
'ownvoice is a Python/PyTorch CLI; this npm package is a thin wrapper',
|
|
47
|
+
'that bootstraps it, not a standalone Node reimplementation.',
|
|
48
|
+
'',
|
|
49
|
+
'Install one of the following, then re-run this command:',
|
|
50
|
+
' - uv (recommended): https://docs.astral.sh/uv/getting-started/installation/',
|
|
51
|
+
' - pipx: https://pipx.pypa.io/stable/installation/',
|
|
52
|
+
'',
|
|
53
|
+
'Or install ownvoice directly with pip:',
|
|
54
|
+
' pip install ownvoice',
|
|
55
|
+
].join('\n')
|
|
56
|
+
);
|
|
57
|
+
process.exit(1);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ownvoice",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Train a LoRA voice adapter for pocket-tts and own the resulting model, no API lock-in. npx-installable wrapper around the ownvoice Python CLI, for agent and Node-first environments.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"ownvoice": "bin/ownvoice.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [
|
|
12
|
+
"tts",
|
|
13
|
+
"text-to-speech",
|
|
14
|
+
"voice-cloning",
|
|
15
|
+
"lora",
|
|
16
|
+
"peft",
|
|
17
|
+
"pocket-tts",
|
|
18
|
+
"fine-tuning",
|
|
19
|
+
"cli",
|
|
20
|
+
"agent-native"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "Rudrendu Paul",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/RudrenduPaul/ownvoice.git"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/RudrenduPaul/ownvoice",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/RudrenduPaul/ownvoice/issues"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""OwnVoice: train a LoRA voice adapter for pocket-tts, own the resulting model.
|
|
2
|
+
|
|
3
|
+
OwnVoice wraps kyutai-labs/pocket-tts (MIT-licensed, CPU-capable local
|
|
4
|
+
text-to-speech) with a LoRA fine-tuning workflow. The output is an adapter
|
|
5
|
+
file you keep and run yourself, not an API subscription.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
|
|
10
|
+
__all__ = ["__version__"]
|