-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcli.py
More file actions
126 lines (109 loc) · 5.8 KB
/
Copy pathcli.py
File metadata and controls
126 lines (109 loc) · 5.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import argparse
import sys
import os
from voice_clone import VoiceCloner
def main():
parser = argparse.ArgumentParser(description="Voice Clone CLI")
subparsers = parser.add_subparsers(dest="command", help="Available commands")
# Clone command
clone_parser = subparsers.add_parser("clone", help="Clone voice from reference audio")
clone_parser.add_argument("--ref-audio", required=True, help="Path to reference audio file")
clone_parser.add_argument("--ref-text", required=True, help="Transcript of reference audio")
clone_parser.add_argument("--text", required=True, help="Target text to synthesize")
clone_parser.add_argument("--model", default="1.7B", choices=["1.7B", "0.6B"], help="Model size (default: 1.7B)")
clone_parser.add_argument("--output", default="output.wav", help="Output file path (default: output.wav)")
# Save Profile command
save_parser = subparsers.add_parser("save-profile", help="Save reference as a profile")
save_parser.add_argument("--name", required=True, help="Profile name")
save_parser.add_argument("--ref-audio", required=True, help="Path to reference audio file")
save_parser.add_argument("--ref-text", required=True, help="Transcript of reference audio")
# List Profiles command
subparsers.add_parser("list-profiles", help="List available profiles")
# Use Profile command
use_parser = subparsers.add_parser("use-profile", help="Generate audio using a saved profile")
use_parser.add_argument("--name", required=True, help="Profile name")
use_parser.add_argument("--text", required=True, help="Target text to synthesize")
use_parser.add_argument("--model", default="1.7B", choices=["1.7B", "0.6B"], help="Model size (default: 1.7B)")
use_parser.add_argument("--output", default="output.wav", help="Output file path (default: output.wav)")
# Delete Profile command
delete_parser = subparsers.add_parser("delete-profile", help="Delete a saved profile")
delete_parser.add_argument("--name", required=True, help="Profile name to delete")
args = parser.parse_args()
if not args.command:
parser.print_help()
return
# Helper to load model
def get_cloner(model_choice):
model_path = "mlx-community/Qwen3-TTS-12Hz-1.7B-Base-bf16"
if model_choice == "0.6B":
model_path = "mlx-community/Qwen3-TTS-12Hz-0.6B-Base-bf16"
try:
return VoiceCloner(model_path=model_path)
except Exception as e:
print(f"Error initializing model: {e}")
sys.exit(1)
if args.command == "clone":
cloner = get_cloner(args.model)
try:
output = cloner.clone_and_generate(args.ref_audio, args.ref_text, args.text)
if args.output != "output.wav":
os.rename(output, args.output)
output = args.output
print(f"Successfully generated audio at: {output}")
except Exception as e:
print(f"Error cloning voice: {e}")
elif args.command == "save-profile":
# No need to load model just to save profile, but VoiceCloner handles paths.
# We can instantiate with default but not load if we refactor, but current init loads.
# Let's instantiate and unload quickly or just use it.
# Ideally VoiceCloner shouldn't load model in init if we only want utility,
# but for now we follow existing pattern.
cloner = get_cloner("1.7B") # Model irrelevant for saving
try:
msg = cloner.save_profile(args.name, args.ref_audio, args.ref_text)
print(msg)
except Exception as e:
print(f"Error saving profile: {e}")
elif args.command == "list-profiles":
# Hack: instantiating just to list might be slow due to load.
# Refactoring VoiceCloner to lazy load would be better, but avoiding big refactors for now.
# Actually initializing VoiceCloner loads the model.
# Let's just manually check the directory for speed.
if os.path.exists("profiles"):
profiles = [d for d in os.listdir("profiles") if os.path.isdir(os.path.join("profiles", d))]
if profiles:
print("Available Profiles:")
for p in profiles:
print(f" - {p}")
else:
print("No profiles found.")
else:
print("No profiles directory found.")
elif args.command == "use-profile":
cloner = get_cloner(args.model)
try:
audio_path, ref_text = cloner.load_profile(args.name)
output = cloner.clone_and_generate(audio_path, ref_text, args.text)
if args.output != "output.wav":
os.rename(output, args.output)
output = args.output
print(f"Successfully generated audio at: {output}")
except Exception as e:
print(f"Error using profile: {e}")
elif args.command == "delete-profile":
# Check manually first to avoid loading model if possible,
# or just use cloner. VoiceCloner init loads model which is slow.
# But for 'delete', we don't need the model.
# Ideally we refactor VoiceCloner, but for now we can just use shutil directly here or instantiate cloner.
# Instantiating is slow (loads model). Let's do a direct removal for CLI speed,
# OR fix VoiceCloner lazy loading.
# Let's fix VoiceCloner lazy loading in a separate step if needed.
# For now, to suffice the user request without major refactor:
if os.path.exists(os.path.join("profiles", args.name)):
import shutil
shutil.rmtree(os.path.join("profiles", args.name))
print(f"Profile '{args.name}' deleted successfully.")
else:
print(f"Profile '{args.name}' not found.")
if __name__ == "__main__":
main()