ownvoice-cli 0.1.1__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.
@@ -0,0 +1,22 @@
1
+ name: Publish PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: read
12
+ id-token: write
13
+ steps:
14
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
15
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
16
+ with:
17
+ python-version: "3.x"
18
+ - run: pip install build
19
+ - run: python -m build
20
+ - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
21
+ with:
22
+ packages-dir: dist
@@ -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
@@ -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.
@@ -0,0 +1,190 @@
1
+ Metadata-Version: 2.4
2
+ Name: ownvoice-cli
3
+ Version: 0.1.1
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
+ Project-URL: Author - Rudrendu Paul, https://github.com/RudrenduPaul
9
+ Project-URL: Author - Sourav Nandy, https://github.com/Sourav-nandy-ai
10
+ Author: Rudrendu Paul, Sourav Nandy
11
+ License: MIT
12
+ License-File: LICENSE
13
+ Keywords: fine-tuning,lora,peft,pocket-tts,text-to-speech,tts,voice-cloning
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: numba>=0.59
26
+ Requires-Dist: peft>=0.11.0
27
+ Requires-Dist: pocket-tts>=0.1.0
28
+ Requires-Dist: resemblyzer>=0.1.4
29
+ Requires-Dist: safetensors>=0.4.0
30
+ Requires-Dist: setuptools<81
31
+ Requires-Dist: soundfile>=0.12.0
32
+ Requires-Dist: torch>=2.5
33
+ Requires-Dist: torchaudio>=2.5
34
+ Requires-Dist: typer>=0.12.0
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest-mock>=3.14.0; extra == 'dev'
37
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # OwnVoice
41
+
42
+ [![PyPI](https://img.shields.io/pypi/v/ownvoice)](https://pypi.org/project/ownvoice/) [![npm](https://img.shields.io/npm/v/ownvoice)](https://www.npmjs.com/package/ownvoice)
43
+
44
+ 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.
45
+
46
+ ## Why this exists
47
+
48
+ 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.
49
+
50
+ 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.
51
+
52
+ ## What OwnVoice is not
53
+
54
+ 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.
55
+
56
+ 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.
57
+
58
+ ## Install
59
+
60
+ Requires Python 3.11 or newer.
61
+
62
+ ```bash
63
+ pip install ownvoice
64
+ ```
65
+
66
+ **npx / agent-native environments:** OwnVoice is a Python/PyTorch CLI, so the [npm package](https://www.npmjs.com/package/ownvoice) 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.
67
+
68
+ ```bash
69
+ npx ownvoice check
70
+ ```
71
+
72
+ **Not functional yet:** the npm wrapper is published and installable, but it delegates to `uvx ownvoice`, and OwnVoice itself is not yet published to PyPI (`pip install git+...` above is the only working install path today). `npx ownvoice` will fail with a clear "package not found" error from `uv` until the PyPI release ships.
73
+
74
+ **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.
75
+
76
+ ## Quickstart
77
+
78
+ ### 1. `ownvoice check`, the free Day-0 validation
79
+
80
+ 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.
81
+
82
+ ```
83
+ $ ownvoice check
84
+ [ownvoice check] PASS: PEFT LoRA injection succeeded against pocket-tts's flow_lm module (target_modules="all-linear").
85
+ ```
86
+
87
+ 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:
88
+
89
+ ```
90
+ $ ownvoice check
91
+ [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.
92
+
93
+ Module tree (for debugging / for the issue #30 blocker post):
94
+ <root>: FlowLMModel
95
+ input_linear: Linear
96
+ transformer: StreamingTransformer
97
+ transformer.layers.0.self_attn.in_proj: Linear
98
+ transformer.layers.0.self_attn.out_proj: Linear
99
+ ...
100
+ ```
101
+
102
+ ### 2. `ownvoice train`
103
+
104
+ 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:
105
+
106
+ ```
107
+ $ ownvoice train --voice-clips ./my-voice-clips
108
+ [ownvoice train] USABLE ADAPTER
109
+ Usable adapter (similarity 0.812 >= 0.75). Try it now:
110
+ ownvoice infer --adapter ownvoice-adapter/adapter.safetensors --text "This is my own voice, trained with OwnVoice."
111
+ ```
112
+
113
+ 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).
114
+
115
+ 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:
116
+
117
+ ```
118
+ $ ownvoice train --voice-clips ./my-voice-clips
119
+ [ownvoice train] BELOW THRESHOLD
120
+ 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:
121
+ ownvoice infer --adapter ownvoice-adapter/adapter.safetensors --text "This is my own voice, trained with OwnVoice."
122
+ ```
123
+
124
+ 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.
125
+
126
+ ### 3. `ownvoice infer`
127
+
128
+ ```
129
+ $ ownvoice infer --adapter ownvoice-adapter/adapter.safetensors --text "Hello, this is my own voice."
130
+ [ownvoice infer] Wrote ownvoice-output.wav
131
+ ```
132
+
133
+ 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:
134
+
135
+ ```
136
+ $ ownvoice check --json
137
+ {"success": true, "message": "PEFT LoRA injection succeeded against pocket-tts's flow_lm module (target_modules=\"all-linear\").", "module_tree": null}
138
+ ```
139
+
140
+ ## How it works
141
+
142
+ ```
143
+ voice clips (wav)
144
+ |
145
+ v
146
+ data.py --validate format/duration--> clean clip set
147
+ |
148
+ v
149
+ train.py --PEFT LoRA (target_modules="all-linear")--> adapter.safetensors + metadata.json
150
+ |
151
+ v
152
+ infer.py --generate test utterance--> synthesized audio
153
+ |
154
+ v
155
+ score.py --resample to 16kHz mono--> Resemblyzer cosine similarity
156
+ |
157
+ v
158
+ CLI report (>= 0.75 = usable adapter, below triggers a labeled next-step message)
159
+ ```
160
+
161
+ `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).
162
+
163
+ OwnVoice is intentionally single-model: it wraps pocket-tts only, with no abstraction layer for a second base model, since none is in scope.
164
+
165
+ ## Consent and misuse
166
+
167
+ 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.
168
+
169
+ ## Setup-time benchmark vs comparable tools
170
+
171
+ | Tool | Time to first working setup | Notable design choice | Source |
172
+ |---|---|---|---|
173
+ | [kokoro-tts](https://github.com/nazdridoy/kokoro-tts) | under 2 minutes | `pip install git+...`, instant CLI synthesis, no fine-tuning | kokoro-tts README |
174
+ | [Unsloth](https://unsloth.ai) | under 1 minute to start a run | one-command training start (`uv pip install`) | Unsloth docs |
175
+ | [pocket-tts](https://github.com/kyutai-labs/pocket-tts) | seconds | `--voice <wav>` zero-shot cloning, no training available | pocket-tts README |
176
+ | **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 |
177
+
178
+ 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.
179
+
180
+ ## Implementation status
181
+
182
+ 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.
183
+
184
+ ## Contributing
185
+
186
+ 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).
187
+
188
+ ## License
189
+
190
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,151 @@
1
+ # OwnVoice
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/ownvoice)](https://pypi.org/project/ownvoice/) [![npm](https://img.shields.io/npm/v/ownvoice)](https://www.npmjs.com/package/ownvoice)
4
+
5
+ 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.
6
+
7
+ ## Why this exists
8
+
9
+ 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.
10
+
11
+ 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.
12
+
13
+ ## What OwnVoice is not
14
+
15
+ 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.
16
+
17
+ 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.
18
+
19
+ ## Install
20
+
21
+ Requires Python 3.11 or newer.
22
+
23
+ ```bash
24
+ pip install ownvoice
25
+ ```
26
+
27
+ **npx / agent-native environments:** OwnVoice is a Python/PyTorch CLI, so the [npm package](https://www.npmjs.com/package/ownvoice) 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.
28
+
29
+ ```bash
30
+ npx ownvoice check
31
+ ```
32
+
33
+ **Not functional yet:** the npm wrapper is published and installable, but it delegates to `uvx ownvoice`, and OwnVoice itself is not yet published to PyPI (`pip install git+...` above is the only working install path today). `npx ownvoice` will fail with a clear "package not found" error from `uv` until the PyPI release ships.
34
+
35
+ **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.
36
+
37
+ ## Quickstart
38
+
39
+ ### 1. `ownvoice check`, the free Day-0 validation
40
+
41
+ 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.
42
+
43
+ ```
44
+ $ ownvoice check
45
+ [ownvoice check] PASS: PEFT LoRA injection succeeded against pocket-tts's flow_lm module (target_modules="all-linear").
46
+ ```
47
+
48
+ 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:
49
+
50
+ ```
51
+ $ ownvoice check
52
+ [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.
53
+
54
+ Module tree (for debugging / for the issue #30 blocker post):
55
+ <root>: FlowLMModel
56
+ input_linear: Linear
57
+ transformer: StreamingTransformer
58
+ transformer.layers.0.self_attn.in_proj: Linear
59
+ transformer.layers.0.self_attn.out_proj: Linear
60
+ ...
61
+ ```
62
+
63
+ ### 2. `ownvoice train`
64
+
65
+ 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:
66
+
67
+ ```
68
+ $ ownvoice train --voice-clips ./my-voice-clips
69
+ [ownvoice train] USABLE ADAPTER
70
+ Usable adapter (similarity 0.812 >= 0.75). Try it now:
71
+ ownvoice infer --adapter ownvoice-adapter/adapter.safetensors --text "This is my own voice, trained with OwnVoice."
72
+ ```
73
+
74
+ 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).
75
+
76
+ 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:
77
+
78
+ ```
79
+ $ ownvoice train --voice-clips ./my-voice-clips
80
+ [ownvoice train] BELOW THRESHOLD
81
+ 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:
82
+ ownvoice infer --adapter ownvoice-adapter/adapter.safetensors --text "This is my own voice, trained with OwnVoice."
83
+ ```
84
+
85
+ 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.
86
+
87
+ ### 3. `ownvoice infer`
88
+
89
+ ```
90
+ $ ownvoice infer --adapter ownvoice-adapter/adapter.safetensors --text "Hello, this is my own voice."
91
+ [ownvoice infer] Wrote ownvoice-output.wav
92
+ ```
93
+
94
+ 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:
95
+
96
+ ```
97
+ $ ownvoice check --json
98
+ {"success": true, "message": "PEFT LoRA injection succeeded against pocket-tts's flow_lm module (target_modules=\"all-linear\").", "module_tree": null}
99
+ ```
100
+
101
+ ## How it works
102
+
103
+ ```
104
+ voice clips (wav)
105
+ |
106
+ v
107
+ data.py --validate format/duration--> clean clip set
108
+ |
109
+ v
110
+ train.py --PEFT LoRA (target_modules="all-linear")--> adapter.safetensors + metadata.json
111
+ |
112
+ v
113
+ infer.py --generate test utterance--> synthesized audio
114
+ |
115
+ v
116
+ score.py --resample to 16kHz mono--> Resemblyzer cosine similarity
117
+ |
118
+ v
119
+ CLI report (>= 0.75 = usable adapter, below triggers a labeled next-step message)
120
+ ```
121
+
122
+ `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).
123
+
124
+ OwnVoice is intentionally single-model: it wraps pocket-tts only, with no abstraction layer for a second base model, since none is in scope.
125
+
126
+ ## Consent and misuse
127
+
128
+ 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.
129
+
130
+ ## Setup-time benchmark vs comparable tools
131
+
132
+ | Tool | Time to first working setup | Notable design choice | Source |
133
+ |---|---|---|---|
134
+ | [kokoro-tts](https://github.com/nazdridoy/kokoro-tts) | under 2 minutes | `pip install git+...`, instant CLI synthesis, no fine-tuning | kokoro-tts README |
135
+ | [Unsloth](https://unsloth.ai) | under 1 minute to start a run | one-command training start (`uv pip install`) | Unsloth docs |
136
+ | [pocket-tts](https://github.com/kyutai-labs/pocket-tts) | seconds | `--voice <wav>` zero-shot cloning, no training available | pocket-tts README |
137
+ | **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 |
138
+
139
+ 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.
140
+
141
+ ## Implementation status
142
+
143
+ 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.
144
+
145
+ ## Contributing
146
+
147
+ 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).
148
+
149
+ ## License
150
+
151
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { spawnSync } = require('node:child_process');
5
+ const path = require('node:path');
6
+
7
+ // Pinned to this npm package's own version, not left floating: an unqualified
8
+ // "uvx ownvoice"/"pipx run ownvoice" always fetches whatever is currently
9
+ // newest on PyPI. That decouples what a user actually installed (this npm
10
+ // package, a fixed point in time) from what runs on their machine (PyPI's
11
+ // latest, which changes underneath them and could be a compromised or
12
+ // unintended publish) -- a supply-chain determinism gap. Pinning to this
13
+ // package's own version keeps the two registries in lockstep: bump this
14
+ // package.json's version when PyPI's ownvoice ships a new release, and every
15
+ // subsequent invocation resolves to that exact pinned release, not whatever
16
+ // is newest at run time.
17
+ const PACKAGE_VERSION = require(path.join(__dirname, '..', 'package.json')).version;
18
+
19
+ function commandExists(cmd) {
20
+ const probe = process.platform === 'win32' ? 'where' : 'which';
21
+ const result = spawnSync(probe, [cmd], { stdio: 'ignore' });
22
+ return result.status === 0;
23
+ }
24
+
25
+ function run(cmd, args) {
26
+ const result = spawnSync(cmd, args, { stdio: 'inherit' });
27
+ if (result.error) {
28
+ return null;
29
+ }
30
+ return result.status;
31
+ }
32
+
33
+ const args = process.argv.slice(2);
34
+
35
+ // ownvoice is a Python/PyTorch package (LoRA fine-tuning on top of pocket-tts).
36
+ // This wrapper never bundles a platform binary -- there isn't one to bundle --
37
+ // it bootstraps into the real ownvoice Python CLI via whichever Python runner
38
+ // is already on PATH, preferring uv/uvx since that's the primary documented
39
+ // install path (`uvx ownvoice train ...`) and increasingly present by default
40
+ // in agent and CI sandboxes.
41
+ const runners = [
42
+ { cmd: 'uvx', build: (a) => [`ownvoice==${PACKAGE_VERSION}`, ...a] },
43
+ { cmd: 'pipx', build: (a) => ['run', `ownvoice==${PACKAGE_VERSION}`, ...a] },
44
+ ];
45
+
46
+ for (const runner of runners) {
47
+ if (commandExists(runner.cmd)) {
48
+ const status = run(runner.cmd, runner.build(args));
49
+ if (status !== null) {
50
+ process.exit(status);
51
+ }
52
+ }
53
+ }
54
+
55
+ console.error(
56
+ [
57
+ 'ownvoice: no Python runner found (checked uvx, pipx).',
58
+ '',
59
+ 'ownvoice is a Python/PyTorch CLI; this npm package is a thin wrapper',
60
+ 'that bootstraps it, not a standalone Node reimplementation.',
61
+ '',
62
+ 'Install one of the following, then re-run this command:',
63
+ ' - uv (recommended): https://docs.astral.sh/uv/getting-started/installation/',
64
+ ' - pipx: https://pipx.pypa.io/stable/installation/',
65
+ '',
66
+ 'Or install ownvoice directly with pip:',
67
+ ' pip install ownvoice',
68
+ ].join('\n')
69
+ );
70
+ process.exit(1);
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "ownvoice-cli",
3
+ "version": "0.1.1",
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__"]