voice-cloner-studio 2.0.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mubashir
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,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: voice-cloner-studio
3
+ Version: 2.0.0
4
+ Summary: Natural voice cloning in 23 languages (incl. Hindi) with a browser GUI, CLI and Python API - powered by Chatterbox Multilingual.
5
+ Author: Mubashir
6
+ License-Expression: MIT
7
+ Keywords: voice cloning,tts,text-to-speech,chatterbox,hindi,gradio
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
14
+ Classifier: Environment :: Web Environment
15
+ Requires-Python: <3.14,>=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ License-File: src/voice_cloner_studio/assets/LICENSE-resemblyzer.txt
19
+ Requires-Dist: chatterbox-tts>=0.1.7
20
+ Requires-Dist: soundfile>=0.12
21
+ Requires-Dist: numpy
22
+ Requires-Dist: librosa
23
+ Requires-Dist: pyloudnorm
24
+ Requires-Dist: gradio
25
+ Provides-Extra: denoise
26
+ Requires-Dist: noisereduce>=3.0; extra == "denoise"
27
+ Provides-Extra: test
28
+ Requires-Dist: pytest>=8; extra == "test"
29
+ Requires-Dist: gradio_client; extra == "test"
30
+ Dynamic: license-file
31
+
32
+ # 🎙️ Voice Cloner Studio
33
+
34
+ Natural voice cloning in **23 languages, including Hindi**. You get a browser GUI, a one-line command and a simple Python API.
35
+ It is powered by [Chatterbox Multilingual](https://github.com/resemble-ai/chatterbox) from Resemble AI, an open model (MIT licence) whose naturalness is in the same league as commercial services.
36
+
37
+ ```bash
38
+ pip install voice-cloner-studio
39
+ voice-cloner-studio # opens the GUI at http://127.0.0.1:7860
40
+ ```
41
+
42
+ ## Python, in 4 lines
43
+
44
+ ```python
45
+ from voice_cloner_studio import VoiceCloner
46
+
47
+ vc = VoiceCloner()
48
+ vc.clone("my_voice", "my_voice.wav") # once - saved for next time
49
+ vc.speak("Hello everyone! Welcome to my channel.", voice="my_voice") # -> dict with "path", "similarity"
50
+ vc.speak("नमस्ते दोस्तों!", voice="my_voice", language="Hindi", style="Expressive / energetic")
51
+ ```
52
+
53
+ ## Command line
54
+
55
+ ```bash
56
+ voice-clone --ref my_voice.wav --voice me --text "Hello there!" --out hello.wav
57
+ voice-clone --voice me --language Hindi --text "नमस्ते, आप कैसे हैं?"
58
+ voice-clone --voice me --text-file story.txt --style "Calm narrator" --takes 3
59
+ ```
60
+
61
+ ## Features
62
+
63
+ - **Clone** a voice from a clip or a microphone recording. The app cleans the clip, normalizes loudness and shows a quality report.
64
+ - **Choose a style:** Natural, Expressive, Calm narrator, Fast speaker, or Accent fix for speaking another language. You can also fine-tune emotion, pace and variation.
65
+ - **Best of N takes:** the app keeps the take that sounds most like the voice. A built-in speaker-verification model scores how close each result is.
66
+ - **Built-in default voice:** you can generate speech without cloning anything.
67
+ - **Long texts** are split into sentences automatically. A blank line adds a paragraph pause.
68
+ - **Voice library:** clone once and reuse forever.
69
+ - **Clear errors:** the GUI shows the real reason, and full details go to `error.log`.
70
+
71
+ Voices and outputs are saved in `VoiceClonerStudio` in your home folder. Change this with `--data-dir`.
72
+ The model (~2 GB) downloads once, on first use.
73
+
74
+ ## Best results
75
+
76
+ | | Do this |
77
+ |---|---|
78
+ | Reference | **One clean 10-20 s clip.** The model listens to the first ~10 s |
79
+ | Recording | Quiet room, no music or echo, normal volume |
80
+ | Delivery | Speak in the tone you want, because the clone copies it |
81
+ | Other language | Use the *Other-language accent fix* style |
82
+ | Bad take | Set Takes to 3, or change the seed |
83
+
84
+ An NVIDIA GPU makes generation real-time. On a laptop CPU, expect about 3-10 s of waiting per second of speech.
85
+
86
+ ## Responsible use
87
+
88
+ Clone only your own voice or voices you have permission to use. Chatterbox adds an inaudible watermark ([Perth](https://github.com/resemble-ai/perth)) to generated audio, so it can be identified as AI-made.
89
+
90
+ ## Licence
91
+
92
+ The code is under the MIT licence. Chatterbox is MIT, and the bundled Resemblyzer speaker encoder is Apache-2.0.
@@ -0,0 +1,61 @@
1
+ # 🎙️ Voice Cloner Studio
2
+
3
+ Natural voice cloning in **23 languages, including Hindi**. You get a browser GUI, a one-line command and a simple Python API.
4
+ It is powered by [Chatterbox Multilingual](https://github.com/resemble-ai/chatterbox) from Resemble AI, an open model (MIT licence) whose naturalness is in the same league as commercial services.
5
+
6
+ ```bash
7
+ pip install voice-cloner-studio
8
+ voice-cloner-studio # opens the GUI at http://127.0.0.1:7860
9
+ ```
10
+
11
+ ## Python, in 4 lines
12
+
13
+ ```python
14
+ from voice_cloner_studio import VoiceCloner
15
+
16
+ vc = VoiceCloner()
17
+ vc.clone("my_voice", "my_voice.wav") # once - saved for next time
18
+ vc.speak("Hello everyone! Welcome to my channel.", voice="my_voice") # -> dict with "path", "similarity"
19
+ vc.speak("नमस्ते दोस्तों!", voice="my_voice", language="Hindi", style="Expressive / energetic")
20
+ ```
21
+
22
+ ## Command line
23
+
24
+ ```bash
25
+ voice-clone --ref my_voice.wav --voice me --text "Hello there!" --out hello.wav
26
+ voice-clone --voice me --language Hindi --text "नमस्ते, आप कैसे हैं?"
27
+ voice-clone --voice me --text-file story.txt --style "Calm narrator" --takes 3
28
+ ```
29
+
30
+ ## Features
31
+
32
+ - **Clone** a voice from a clip or a microphone recording. The app cleans the clip, normalizes loudness and shows a quality report.
33
+ - **Choose a style:** Natural, Expressive, Calm narrator, Fast speaker, or Accent fix for speaking another language. You can also fine-tune emotion, pace and variation.
34
+ - **Best of N takes:** the app keeps the take that sounds most like the voice. A built-in speaker-verification model scores how close each result is.
35
+ - **Built-in default voice:** you can generate speech without cloning anything.
36
+ - **Long texts** are split into sentences automatically. A blank line adds a paragraph pause.
37
+ - **Voice library:** clone once and reuse forever.
38
+ - **Clear errors:** the GUI shows the real reason, and full details go to `error.log`.
39
+
40
+ Voices and outputs are saved in `VoiceClonerStudio` in your home folder. Change this with `--data-dir`.
41
+ The model (~2 GB) downloads once, on first use.
42
+
43
+ ## Best results
44
+
45
+ | | Do this |
46
+ |---|---|
47
+ | Reference | **One clean 10-20 s clip.** The model listens to the first ~10 s |
48
+ | Recording | Quiet room, no music or echo, normal volume |
49
+ | Delivery | Speak in the tone you want, because the clone copies it |
50
+ | Other language | Use the *Other-language accent fix* style |
51
+ | Bad take | Set Takes to 3, or change the seed |
52
+
53
+ An NVIDIA GPU makes generation real-time. On a laptop CPU, expect about 3-10 s of waiting per second of speech.
54
+
55
+ ## Responsible use
56
+
57
+ Clone only your own voice or voices you have permission to use. Chatterbox adds an inaudible watermark ([Perth](https://github.com/resemble-ai/perth)) to generated audio, so it can be identified as AI-made.
58
+
59
+ ## Licence
60
+
61
+ The code is under the MIT licence. Chatterbox is MIT, and the bundled Resemblyzer speaker encoder is Apache-2.0.
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "voice-cloner-studio"
7
+ version = "2.0.0"
8
+ description = "Natural voice cloning in 23 languages (incl. Hindi) with a browser GUI, CLI and Python API - powered by Chatterbox Multilingual."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10,<3.14"
11
+ license = "MIT"
12
+ license-files = ["LICENSE", "src/voice_cloner_studio/assets/LICENSE-resemblyzer.txt"]
13
+ authors = [{ name = "Mubashir" }]
14
+ keywords = ["voice cloning", "tts", "text-to-speech", "chatterbox", "hindi", "gradio"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Operating System :: OS Independent",
21
+ "Topic :: Multimedia :: Sound/Audio :: Speech",
22
+ "Environment :: Web Environment",
23
+ ]
24
+ dependencies = [
25
+ "chatterbox-tts>=0.1.7",
26
+ "soundfile>=0.12",
27
+ "numpy",
28
+ "librosa",
29
+ "pyloudnorm",
30
+ "gradio",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ denoise = ["noisereduce>=3.0"]
35
+ test = ["pytest>=8", "gradio_client"]
36
+
37
+ [project.scripts]
38
+ voice-cloner-studio = "voice_cloner_studio.app:main"
39
+ voice-clone = "voice_cloner_studio.cli:main"
40
+
41
+ [tool.setuptools.packages.find]
42
+ where = ["src"]
43
+
44
+ [tool.setuptools.package-data]
45
+ voice_cloner_studio = ["assets/*"]
46
+
47
+ [tool.pytest.ini_options]
48
+ testpaths = ["tests"]
49
+ filterwarnings = ["ignore::DeprecationWarning", "ignore::FutureWarning", "ignore::UserWarning"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,10 @@
1
+ """Voice Cloner Studio - natural voice cloning (Chatterbox Multilingual) with a browser GUI.
2
+
3
+ from voice_cloner_studio import VoiceCloner
4
+ vc = VoiceCloner()
5
+ vc.clone("me", "my_voice.wav")
6
+ print(vc.speak("Hello world!", voice="me")["path"])
7
+ """
8
+ __version__ = "2.0.0"
9
+
10
+ from .engine import BUILTIN, LANGUAGES, STYLES, VoiceCloner # noqa: E402,F401
@@ -0,0 +1,4 @@
1
+ from voice_cloner_studio.app import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,296 @@
1
+ """
2
+ Voice Cloner Studio - browser GUI.
3
+
4
+ Run: voice-cloner-studio (or: python -m voice_cloner_studio)
5
+ """
6
+ import argparse
7
+ import datetime as dt
8
+ import functools
9
+ import os
10
+ import sys
11
+ import traceback
12
+ from pathlib import Path
13
+
14
+ import gradio as gr
15
+ import numpy as np
16
+
17
+ from . import __version__
18
+ from .engine import BUILTIN, LANGUAGES, STYLES, VoiceCloner, default_data_dir, split_text
19
+
20
+ SIM_LEVELS = [(0.85, "🟢 Excellent - very hard to tell apart"),
21
+ (0.75, "🟢 Good - in the same range as two real recordings of this person"),
22
+ (0.65, "🟡 Fair - similar, but differences are noticeable"),
23
+ (0.00, "🔴 Weak - use a cleaner / longer reference clip")]
24
+
25
+ TIPS = """
26
+ ### For the most natural, attractive clone
27
+ 1. **One clean clip of 10-20 s** beats many clips - the model listens to the first ~10 s.
28
+ 2. Quiet room, no music/echo, mic ~15 cm away, normal speaking volume.
29
+ 3. **Speak the way you want the output to sound** - warm reference → warm clone.
30
+ 4. Pick a **Style**: *Expressive* for ads/YouTube, *Calm narrator* for audiobooks.
31
+ 5. Speaking another language than the reference? Use *Other-language accent fix*.
32
+ 6. Set **Takes = 3** - the app keeps the take that sounds most like you.
33
+ 7. Hindi: type in Devanagari (नमस्ते). Urdu: type it in Devanagari or Roman script.
34
+ """
35
+
36
+ CSS = "#title {text-align:center} #title h1 {font-size:2.2rem;margin-bottom:0}"
37
+
38
+ engine: VoiceCloner = None
39
+
40
+
41
+ def sim_label(score):
42
+ return next(label for th, label in SIM_LEVELS if score >= th)
43
+
44
+
45
+ def friendly_errors(fn):
46
+ """Show the real reason in the GUI (not a bare "Error") and keep the traceback in error.log."""
47
+ @functools.wraps(fn)
48
+ def wrapper(*args, **kwargs):
49
+ try:
50
+ return fn(*args, **kwargs)
51
+ except gr.Error:
52
+ raise
53
+ except Exception as e:
54
+ log = engine.data_dir / "error.log"
55
+ with open(log, "a", encoding="utf-8") as f:
56
+ f.write(f"\n===== {dt.datetime.now():%Y-%m-%d %H:%M:%S} {fn.__name__} (v{__version__})\n"
57
+ f"{traceback.format_exc()}")
58
+ traceback.print_exc()
59
+ msg = f"{type(e).__name__}: {e}"
60
+ if any(k in msg for k in ("ProxyError", "ConnectionError", "ConnectError", "Max retries",
61
+ "getaddrinfo", "403", "timed out", "LocalEntryNotFound")):
62
+ msg = ("Couldn't download the voice model (~2 GB). Check your internet / firewall and that "
63
+ "huggingface.co is reachable, then try again. Details: " + msg)
64
+ raise gr.Error(f"{msg[:600]} (full details in {log})")
65
+ return wrapper
66
+
67
+
68
+ def voice_choices():
69
+ return [BUILTIN] + engine.list_voices()
70
+
71
+
72
+ def library_rows():
73
+ rows = []
74
+ for v in engine.list_voices():
75
+ m = engine.voice_info(v)
76
+ rows.append([v, m.get("created", ""), m.get("clips", ""), m.get("seconds", ""), m.get("notes", "")])
77
+ return rows
78
+
79
+
80
+ @friendly_errors
81
+ def ui_clone(name, files, mic, denoise, notes, consent, progress=gr.Progress()):
82
+ if not consent:
83
+ raise gr.Error("Please confirm you have the speaker's consent before cloning a voice.")
84
+ sources = [f if isinstance(f, str) else f.name for f in (files or [])]
85
+ if mic:
86
+ sources.append(mic)
87
+ if not engine.safe_name(name):
88
+ raise gr.Error("Give the voice a name.")
89
+ if not sources:
90
+ raise gr.Error("Upload or record at least one reference clip.")
91
+ progress(0.2, desc="Loading model (first time downloads ~2 GB)")
92
+ _ = engine.model
93
+ progress(0.6, desc="Learning the voice")
94
+ name, rows, total = engine.clone(name, sources, denoise=denoise, notes=notes)
95
+
96
+ lines = [f"### ✅ Voice **{name}** created", "", "| Clip | Speech | Level | Clipping |", "|---|---|---|---|"]
97
+ lines += [f"| {n} | {d:.1f}s | {lv:.1f} dBFS | {c:.2f}% |" for n, d, lv, c in rows]
98
+ lines.append(f"\n**Total clean speech: {total:.1f}s**")
99
+ if total < 5:
100
+ lines.append("\n⚠️ Under 5 s - add a longer clip (10-20 s is ideal).")
101
+ elif total > 25:
102
+ lines.append("\nℹ️ Only the first ~15 s are used - put your best clip first.")
103
+ else:
104
+ lines.append("\n🟢 Good length.")
105
+ if any(r[3] > 0.5 for r in rows):
106
+ lines.append("\n⚠️ Some audio is clipping/distorted - re-record a bit quieter.")
107
+ choices = voice_choices()
108
+ return ("\n".join(lines), gr.update(choices=choices, value=name),
109
+ gr.update(choices=engine.list_voices(), value=name), library_rows())
110
+
111
+
112
+ @friendly_errors
113
+ def ui_speak(voice, text, language, style, exaggeration, cfg_weight, temperature, seed, takes,
114
+ progress=gr.Progress()):
115
+ if not (text or "").strip():
116
+ raise gr.Error("Type some text to speak.")
117
+ progress(0.02, desc="Loading model (first time downloads ~2 GB)")
118
+ r = engine.speak(text, voice=voice, language=language, exaggeration=exaggeration, cfg_weight=cfg_weight,
119
+ temperature=temperature, seed=seed, takes=takes,
120
+ on_progress=lambda f, d: progress(f, desc=d))
121
+ info = [f"Generated **{r['seconds']:.1f}s** · seed **{r['seed']}** · `{Path(r['path']).name}`"]
122
+ if r["similarity"] is not None:
123
+ info.append(f"\n### Voice similarity: **{r['similarity'] * 100:.1f}%** \n{sim_label(r['similarity'])}")
124
+ if len(r["scores"]) > 1:
125
+ info.append("\nBest of {} takes (scores: {}).".format(
126
+ len(r["scores"]), ", ".join(f"{s * 100:.1f}%" for s in r["scores"])))
127
+ info.append("\n<sub>Independent speaker-verification score. Two real recordings of the same "
128
+ "person typically score 75-90%; different people 45-70%.</sub>")
129
+ return r["path"], r["path"], "\n".join(info), history()
130
+
131
+
132
+ def ui_style(style):
133
+ e, c, t = STYLES[style]
134
+ return e, c, t
135
+
136
+
137
+ def ui_delete(name):
138
+ if name:
139
+ engine.delete_voice(name)
140
+ vs = engine.list_voices()
141
+ return (gr.update(choices=[BUILTIN] + vs, value=BUILTIN), gr.update(choices=vs, value=vs[0] if vs else None),
142
+ library_rows(), f"Deleted **{name}**." if name else "Nothing selected.")
143
+
144
+
145
+ def ui_preview(name):
146
+ p = engine.reference_path(name) if name else None
147
+ return str(p) if p else None
148
+
149
+
150
+ def history():
151
+ files = sorted(engine.output_dir.glob("*.wav"), key=lambda p: p.stat().st_mtime, reverse=True)
152
+ return [str(p) for p in files[:30]]
153
+
154
+
155
+ def build_ui():
156
+ voices = engine.list_voices()
157
+ with gr.Blocks(title="Voice Cloner Studio") as demo:
158
+ gr.Markdown(f"# 🎙️ Voice Cloner Studio <small>v{__version__}</small>\n"
159
+ f"Natural voice cloning in {len(LANGUAGES)} languages (incl. Hindi) · "
160
+ f"engine: Chatterbox Multilingual · running on **{engine.device.upper()}**", elem_id="title")
161
+ with gr.Tabs():
162
+ with gr.Tab("1 · Clone a voice"):
163
+ with gr.Row():
164
+ with gr.Column(scale=3):
165
+ c_name = gr.Textbox(label="Voice name", placeholder="e.g. my_voice")
166
+ c_files = gr.File(label="Reference clip(s) - one clean 10-20 s clip is ideal",
167
+ file_count="multiple", file_types=["audio"])
168
+ c_mic = gr.Audio(label="…or record: read a paragraph naturally for 15 s",
169
+ sources=["microphone"], type="filepath")
170
+ c_denoise = gr.Checkbox(label="Reduce background noise (only if your recording is noisy)",
171
+ value=False)
172
+ c_notes = gr.Textbox(label="Notes (optional)")
173
+ c_consent = gr.Checkbox(label="I own this voice or have the speaker's explicit permission "
174
+ "to clone it, and won't use it to deceive or impersonate.")
175
+ c_btn = gr.Button("🧬 Create voice", variant="primary", size="lg")
176
+ with gr.Column(scale=2):
177
+ c_report = gr.Markdown("Upload or record audio, then press **Create voice**.")
178
+ gr.Markdown(TIPS)
179
+
180
+ with gr.Tab("2 · Speak"):
181
+ with gr.Row():
182
+ with gr.Column(scale=3):
183
+ with gr.Row():
184
+ s_voice = gr.Dropdown([BUILTIN] + voices, value=voices[0] if voices else BUILTIN,
185
+ label="Voice", scale=3)
186
+ s_lang = gr.Dropdown(list(LANGUAGES), value="English", label="Language", scale=2)
187
+ s_text = gr.Textbox(label="Text", lines=7,
188
+ value="Hello! This is my cloned voice. Sounds natural, doesn't it?",
189
+ placeholder="Type anything. Blank line = paragraph pause.")
190
+ s_style = gr.Radio(list(STYLES), value="Natural", label="Style")
191
+ with gr.Accordion("Fine-tune", open=False):
192
+ s_exag = gr.Slider(0.25, 2.0, 0.5, step=0.05, label="Emotion / expressiveness")
193
+ s_cfg = gr.Slider(0.0, 1.0, 0.5, step=0.05,
194
+ label="Pace & accent guidance (lower = slower, less accent carry-over)")
195
+ s_temp = gr.Slider(0.05, 1.5, 0.8, step=0.05, label="Variation (temperature)")
196
+ s_seed = gr.Number(value=-1, precision=0, label="Seed (-1 = random)")
197
+ s_takes = gr.Slider(1, 5, 1, step=1, label="Takes - keep the one most like the voice")
198
+ s_btn = gr.Button("🔊 Generate speech", variant="primary", size="lg")
199
+ with gr.Column(scale=2):
200
+ s_audio = gr.Audio(label="Result", type="filepath", interactive=False)
201
+ s_file = gr.File(label="Download WAV")
202
+ s_info = gr.Markdown()
203
+ s_hist = gr.Files(label="Recent generations", value=history())
204
+
205
+ with gr.Tab("3 · Voice library"):
206
+ l_table = gr.Dataframe(headers=["Voice", "Created", "Clips", "Seconds", "Notes"],
207
+ value=library_rows(), interactive=False)
208
+ with gr.Row():
209
+ l_voice = gr.Dropdown(voices, value=voices[0] if voices else None, label="Voice")
210
+ l_play = gr.Audio(label="Reference used for this voice", interactive=False)
211
+ with gr.Row():
212
+ l_refresh = gr.Button("🔄 Refresh")
213
+ l_del = gr.Button("🗑️ Delete voice", variant="stop")
214
+ l_msg = gr.Markdown()
215
+
216
+ c_btn.click(ui_clone, [c_name, c_files, c_mic, c_denoise, c_notes, c_consent],
217
+ [c_report, s_voice, l_voice, l_table], api_name="clone")
218
+ s_style.change(ui_style, s_style, [s_exag, s_cfg, s_temp], api_name="style")
219
+ s_btn.click(ui_speak, [s_voice, s_text, s_lang, s_style, s_exag, s_cfg, s_temp, s_seed, s_takes],
220
+ [s_audio, s_file, s_info, s_hist], api_name="speak")
221
+ l_voice.change(ui_preview, l_voice, l_play, api_name="preview")
222
+ l_del.click(ui_delete, l_voice, [s_voice, l_voice, l_table, l_msg], api_name="delete")
223
+ l_refresh.click(lambda: (library_rows(), gr.update(choices=voice_choices()),
224
+ gr.update(choices=engine.list_voices())),
225
+ None, [l_table, s_voice, l_voice], api_name="refresh")
226
+ return demo
227
+
228
+
229
+ def launch(data_dir=None, host="127.0.0.1", port=7860, share=False, inbrowser=True, prevent_thread_lock=False,
230
+ cloner=None):
231
+ global engine
232
+ engine = cloner or VoiceCloner(data_dir)
233
+ demo = build_ui().queue()
234
+ # Gradio only serves files from temp/cwd unless told otherwise; voices and outputs live in data_dir.
235
+ demo.launch(server_name=host, server_port=port, share=share, inbrowser=inbrowser, show_error=True,
236
+ prevent_thread_lock=prevent_thread_lock, theme=gr.themes.Soft(primary_hue="violet"), css=CSS,
237
+ allowed_paths=[str(engine.voices_dir), str(engine.output_dir)])
238
+ return demo
239
+
240
+
241
+ def self_check(data_dir=None):
242
+ import importlib
243
+ import platform
244
+ import urllib.request
245
+ ok = True
246
+
247
+ def item(name, fn):
248
+ nonlocal ok
249
+ try:
250
+ detail = fn()
251
+ print(f" [OK] {name}{': ' + str(detail) if detail not in (None, '') else ''}")
252
+ except Exception as e:
253
+ ok = False
254
+ print(f" [FAIL] {name}: {type(e).__name__}: {e}")
255
+
256
+ def writable(d):
257
+ d.mkdir(parents=True, exist_ok=True)
258
+ (d / ".write_test").write_text("ok")
259
+ (d / ".write_test").unlink()
260
+ return d
261
+
262
+ d = Path(data_dir or default_data_dir())
263
+ print(f"Voice Cloner Studio v{__version__} - self check")
264
+ item("Python", lambda: f"{platform.python_version()} ({platform.architecture()[0]})")
265
+ import torch
266
+ item("PyTorch", lambda: f"{torch.__version__}, GPU: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'none (CPU mode, slower)'}")
267
+ for mod in ["chatterbox", "gradio", "librosa", "soundfile", "pyloudnorm"]:
268
+ item(f"package {mod}", lambda m=mod: getattr(importlib.import_module(m), "__version__", ""))
269
+ item("text splitter", lambda: f"{len(split_text('Hello there. नमस्ते।', 'hi'))} chunk(s)")
270
+ item("similarity scorer", lambda: VoiceCloner(d)._embed(np.random.randn(16000).astype(np.float32) * .1, 16000).shape)
271
+ item("data folder writable", lambda: writable(d))
272
+ item("huggingface.co reachable (model download)",
273
+ lambda: urllib.request.urlopen("https://huggingface.co/api/models/ResembleAI/chatterbox", timeout=15).status)
274
+ print("\nAll good - start the app with: voice-cloner-studio" if ok else "\nSome checks failed - see above.")
275
+ return 0 if ok else 1
276
+
277
+
278
+ def main(argv=None):
279
+ ap = argparse.ArgumentParser(prog="voice-cloner-studio", description="Voice Cloner Studio GUI")
280
+ ap.add_argument("--host", default="127.0.0.1")
281
+ ap.add_argument("--port", type=int, default=7860)
282
+ ap.add_argument("--share", action="store_true", help="also create a public gradio.live link")
283
+ ap.add_argument("--data-dir", help=f"where voices/outputs are stored (default: {default_data_dir()})")
284
+ ap.add_argument("--no-browser", action="store_true", help="don't open a browser tab")
285
+ ap.add_argument("--check", action="store_true", help="check this PC's setup and exit")
286
+ args = ap.parse_args(argv)
287
+ if args.check:
288
+ sys.exit(self_check(args.data_dir))
289
+ print(f"[voice-cloner] Voice Cloner Studio v{__version__}")
290
+ print(f"[voice-cloner] Data folder: {args.data_dir or default_data_dir()}")
291
+ print(f"[voice-cloner] Opening http://{args.host}:{args.port} - keep this window open while using the app.")
292
+ launch(args.data_dir, args.host, args.port, args.share, not args.no_browser)
293
+
294
+
295
+ if __name__ == "__main__":
296
+ main()