-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathexpressive.py
More file actions
146 lines (129 loc) · 5.41 KB
/
Copy pathexpressive.py
File metadata and controls
146 lines (129 loc) · 5.41 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#!/usr/bin/env python3
"""
Expressive TTS — Generate speech with emotion, pitch, volume, prosody, and accent.
Uses Lightning v3.2 on waves-api.smallest.ai.
Usage:
python expressive.py # Generate all demo samples
python expressive.py --emotion angry --text "This is unacceptable!"
python expressive.py --emotion calm --volume whispering --accent british --text "Shh..."
"""
import argparse
import os
import sys
import requests
from dotenv import load_dotenv
load_dotenv()
API_URL = "https://waves-api.smallest.ai/api/v1/lightning-v3.2/get_speech"
SAMPLE_RATE = 44100 # v3.2 uses 44100, NOT 24000
def synthesize(text, voice_id="natalie", emotion="neutral", pitch="mid-range",
volume="normal", prosody="normal", accent="general american",
api_key=None):
"""Generate expressive speech. Returns audio bytes."""
response = requests.post(
API_URL,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
json={
"text": text,
"voice_id": voice_id,
"emotion": emotion,
"pitch": pitch,
"volume": volume,
"prosody": prosody,
"accent": accent,
"sample_rate": SAMPLE_RATE,
"output_format": "wav",
},
)
if response.status_code != 200:
raise Exception(f"API error ({response.status_code}): {response.text}")
return response.content
# Demo samples — showcases the range of expressive controls
DEMOS = [
{
"name": "happy_excited",
"text": "Oh my goodness, I just got the best news ever! This is absolutely incredible!",
"emotion": "excited", "pitch": "high-pitched", "volume": "normal", "prosody": "fast",
},
{
"name": "sad_slow",
"text": "Everything we built together... it's all just gone now. I don't know what to do.",
"emotion": "sad", "pitch": "low-pitched", "volume": "soft", "prosody": "slow",
},
{
"name": "angry_british",
"text": "This is absolutely unacceptable! How could you possibly let this happen?",
"emotion": "angry", "pitch": "mid-range", "volume": "loud", "prosody": "normal",
"accent": "british",
},
{
"name": "sarcastic",
"text": "Oh sure, what could possibly go wrong? Everything always works out perfectly.",
"emotion": "sarcastic", "pitch": "mid-range", "volume": "normal", "prosody": "measured",
},
{
"name": "anxious_whisper",
"text": "Shh... did you hear that? I think there's someone outside the window.",
"emotion": "anxious", "pitch": "high-pitched", "volume": "whispering", "prosody": "hesitant",
},
{
"name": "confident_presenter",
"text": "Welcome everyone to our product launch! Today, we're going to change everything.",
"emotion": "confident", "pitch": "mid-range", "volume": "normal", "prosody": "melodic",
},
]
def main():
api_key = os.environ.get("SMALLEST_API_KEY")
if not api_key:
print("Error: set SMALLEST_API_KEY environment variable")
sys.exit(1)
parser = argparse.ArgumentParser(description="Expressive TTS (Lightning v3.2)")
parser.add_argument("--text", help="Text to synthesize (runs all demos if omitted)")
parser.add_argument("--emotion", default="neutral")
parser.add_argument("--pitch", default="mid-range")
parser.add_argument("--volume", default="normal")
parser.add_argument("--prosody", default="normal")
parser.add_argument("--accent", default="general american")
parser.add_argument("--voice", default="natalie")
parser.add_argument("--output", "-o", default=None)
args = parser.parse_args()
os.makedirs("output", exist_ok=True)
if args.text:
# Single generation
output_file = args.output or f"output/{args.emotion}_{args.volume}_{args.prosody}.wav"
print(f"Generating: {args.emotion}, {args.volume}, {args.prosody}, {args.accent}")
audio = synthesize(
args.text, voice_id=args.voice, emotion=args.emotion,
pitch=args.pitch, volume=args.volume, prosody=args.prosody,
accent=args.accent, api_key=api_key,
)
with open(output_file, "wb") as f:
f.write(audio)
print(f" Saved {output_file} ({len(audio):,} bytes)")
else:
# Run all demos
print(f"Generating {len(DEMOS)} expressive demos...\n")
for demo in DEMOS:
name = demo["name"]
output_file = f"output/{name}.wav"
print(f" {name}: {demo['emotion']}, {demo.get('volume', 'normal')}, {demo.get('prosody', 'normal')}")
try:
audio = synthesize(
demo["text"],
emotion=demo.get("emotion", "neutral"),
pitch=demo.get("pitch", "mid-range"),
volume=demo.get("volume", "normal"),
prosody=demo.get("prosody", "normal"),
accent=demo.get("accent", "general american"),
api_key=api_key,
)
with open(output_file, "wb") as f:
f.write(audio)
print(f" -> {output_file} ({len(audio):,} bytes)")
except Exception as e:
print(f" -> ERROR: {e}")
print(f"\nDone! Check the output/ folder.")
if __name__ == "__main__":
main()