1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9pub enum TTSModel {
10 #[serde(rename = "ssfm-v30")]
12 SsfmV30,
13 #[serde(rename = "ssfm-v21")]
15 SsfmV21,
16}
17
18impl Default for TTSModel {
19 fn default() -> Self {
20 TTSModel::SsfmV30
21 }
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(rename_all = "lowercase")]
27pub enum EmotionPreset {
28 Normal,
30 Happy,
32 Sad,
34 Angry,
36 Whisper,
38 #[serde(rename = "toneup")]
40 ToneUp,
41 #[serde(rename = "tonedown")]
43 ToneDown,
44}
45
46impl Default for EmotionPreset {
47 fn default() -> Self {
48 EmotionPreset::Normal
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
54#[serde(rename_all = "lowercase")]
55pub enum AudioFormat {
56 Wav,
58 Mp3,
60}
61
62impl Default for AudioFormat {
63 fn default() -> Self {
64 AudioFormat::Wav
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
70#[serde(rename_all = "lowercase")]
71pub enum Gender {
72 Male,
73 Female,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
78#[serde(rename_all = "snake_case")]
79pub enum Age {
80 Child,
82 Teenager,
84 YoungAdult,
86 MiddleAge,
88 Elder,
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
94pub enum UseCase {
95 Announcer,
96 Anime,
97 Audiobook,
98 Conversational,
99 Documentary,
100 #[serde(rename = "E-learning")]
101 ELearning,
102 Rapper,
103 Game,
104 #[serde(rename = "Tiktok/Reels")]
105 TikTokReels,
106 News,
107 Podcast,
108 Voicemail,
109 Ads,
110}
111
112#[derive(Debug, Clone, Default, Serialize, Deserialize)]
114pub struct Output {
115 #[serde(skip_serializing_if = "Option::is_none")]
118 pub volume: Option<i32>,
119 #[serde(skip_serializing_if = "Option::is_none")]
122 pub target_lufs: Option<f64>,
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub audio_pitch: Option<i32>,
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub audio_tempo: Option<f64>,
129 #[serde(skip_serializing_if = "Option::is_none")]
131 pub audio_format: Option<AudioFormat>,
132}
133
134impl Output {
135 pub fn new() -> Self {
137 Self::default()
138 }
139
140 pub fn volume(mut self, volume: i32) -> Self {
143 self.volume = Some(volume.clamp(0, 200));
144 self
145 }
146
147 pub fn target_lufs(mut self, lufs: f64) -> Self {
149 self.target_lufs = Some(lufs.clamp(-70.0, 0.0));
150 self
151 }
152
153 pub fn audio_pitch(mut self, pitch: i32) -> Self {
155 self.audio_pitch = Some(pitch.clamp(-12, 12));
156 self
157 }
158
159 pub fn audio_tempo(mut self, tempo: f64) -> Self {
161 self.audio_tempo = Some(tempo.clamp(0.5, 2.0));
162 self
163 }
164
165 pub fn audio_format(mut self, format: AudioFormat) -> Self {
167 self.audio_format = Some(format);
168 self
169 }
170}
171
172#[derive(Debug, Clone, Default, Serialize, Deserialize)]
177pub struct OutputStream {
178 #[serde(skip_serializing_if = "Option::is_none")]
180 pub audio_pitch: Option<i32>,
181 #[serde(skip_serializing_if = "Option::is_none")]
183 pub audio_tempo: Option<f64>,
184 #[serde(skip_serializing_if = "Option::is_none")]
186 pub audio_format: Option<AudioFormat>,
187}
188
189impl OutputStream {
190 pub fn new() -> Self {
192 Self::default()
193 }
194
195 pub fn audio_pitch(mut self, pitch: i32) -> Self {
197 self.audio_pitch = Some(pitch.clamp(-12, 12));
198 self
199 }
200
201 pub fn audio_tempo(mut self, tempo: f64) -> Self {
203 self.audio_tempo = Some(tempo.clamp(0.5, 2.0));
204 self
205 }
206
207 pub fn audio_format(mut self, format: AudioFormat) -> Self {
209 self.audio_format = Some(format);
210 self
211 }
212}
213
214#[derive(Debug, Clone, Default, Serialize, Deserialize)]
216pub struct Prompt {
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub emotion_preset: Option<EmotionPreset>,
220 #[serde(skip_serializing_if = "Option::is_none")]
222 pub emotion_intensity: Option<f64>,
223}
224
225impl Prompt {
226 pub fn new() -> Self {
228 Self::default()
229 }
230
231 pub fn emotion_preset(mut self, preset: EmotionPreset) -> Self {
233 self.emotion_preset = Some(preset);
234 self
235 }
236
237 pub fn emotion_intensity(mut self, intensity: f64) -> Self {
239 self.emotion_intensity = Some(intensity.clamp(0.0, 2.0));
240 self
241 }
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct PresetPrompt {
247 pub emotion_type: String,
249 #[serde(skip_serializing_if = "Option::is_none")]
251 pub emotion_preset: Option<EmotionPreset>,
252 #[serde(skip_serializing_if = "Option::is_none")]
254 pub emotion_intensity: Option<f64>,
255}
256
257impl Default for PresetPrompt {
258 fn default() -> Self {
259 Self {
260 emotion_type: "preset".to_string(),
261 emotion_preset: None,
262 emotion_intensity: None,
263 }
264 }
265}
266
267impl PresetPrompt {
268 pub fn new() -> Self {
270 Self::default()
271 }
272
273 pub fn emotion_preset(mut self, preset: EmotionPreset) -> Self {
275 self.emotion_preset = Some(preset);
276 self
277 }
278
279 pub fn emotion_intensity(mut self, intensity: f64) -> Self {
281 self.emotion_intensity = Some(intensity.clamp(0.0, 2.0));
282 self
283 }
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
288pub struct SmartPrompt {
289 pub emotion_type: String,
291 #[serde(skip_serializing_if = "Option::is_none")]
293 pub previous_text: Option<String>,
294 #[serde(skip_serializing_if = "Option::is_none")]
296 pub next_text: Option<String>,
297}
298
299impl Default for SmartPrompt {
300 fn default() -> Self {
301 Self {
302 emotion_type: "smart".to_string(),
303 previous_text: None,
304 next_text: None,
305 }
306 }
307}
308
309impl SmartPrompt {
310 pub fn new() -> Self {
312 Self::default()
313 }
314
315 pub fn previous_text(mut self, text: impl Into<String>) -> Self {
317 self.previous_text = Some(text.into());
318 self
319 }
320
321 pub fn next_text(mut self, text: impl Into<String>) -> Self {
323 self.next_text = Some(text.into());
324 self
325 }
326}
327
328#[derive(Debug, Clone, Serialize, Deserialize)]
330#[serde(untagged)]
331pub enum TTSPrompt {
332 Basic(Prompt),
334 Preset(PresetPrompt),
336 Smart(SmartPrompt),
338}
339
340impl From<Prompt> for TTSPrompt {
341 fn from(prompt: Prompt) -> Self {
342 TTSPrompt::Basic(prompt)
343 }
344}
345
346impl From<PresetPrompt> for TTSPrompt {
347 fn from(prompt: PresetPrompt) -> Self {
348 TTSPrompt::Preset(prompt)
349 }
350}
351
352impl From<SmartPrompt> for TTSPrompt {
353 fn from(prompt: SmartPrompt) -> Self {
354 TTSPrompt::Smart(prompt)
355 }
356}
357
358#[derive(Debug, Clone, Serialize, Deserialize)]
360pub struct TTSRequest {
361 pub voice_id: String,
364 pub text: String,
366 pub model: TTSModel,
368 #[serde(skip_serializing_if = "Option::is_none")]
370 pub language: Option<String>,
371 #[serde(skip_serializing_if = "Option::is_none")]
373 pub prompt: Option<TTSPrompt>,
374 #[serde(skip_serializing_if = "Option::is_none")]
376 pub output: Option<Output>,
377 #[serde(skip_serializing_if = "Option::is_none")]
379 pub seed: Option<i32>,
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize)]
386pub struct GenerateToFileRequest {
387 pub voice_id: String,
390 pub text: String,
392 pub model: TTSModel,
394 #[serde(skip_serializing_if = "Option::is_none")]
396 pub language: Option<String>,
397 #[serde(skip_serializing_if = "Option::is_none")]
399 pub prompt: Option<TTSPrompt>,
400 #[serde(skip_serializing_if = "Option::is_none")]
402 pub output: Option<Output>,
403 #[serde(skip_serializing_if = "Option::is_none")]
405 pub seed: Option<i32>,
406}
407
408impl GenerateToFileRequest {
409 pub fn new(voice_id: impl Into<String>, text: impl Into<String>) -> Self {
411 let voice_id = voice_id.into();
412 let text = text.into();
413 assert!(!voice_id.trim().is_empty(), "voice_id is required");
414 assert!(!text.trim().is_empty(), "text is required");
415 assert!(
416 text.chars().count() <= 2000,
417 "text must not exceed 2000 characters"
418 );
419 Self {
420 voice_id,
421 text,
422 model: TTSModel::SsfmV30,
423 language: None,
424 prompt: None,
425 output: None,
426 seed: None,
427 }
428 }
429
430 pub fn model(mut self, model: TTSModel) -> Self {
431 self.model = model;
432 self
433 }
434
435 pub fn language(mut self, language: impl Into<String>) -> Self {
436 self.language = Some(language.into());
437 self
438 }
439
440 pub fn prompt(mut self, prompt: impl Into<TTSPrompt>) -> Self {
441 self.prompt = Some(prompt.into());
442 self
443 }
444
445 pub fn output(mut self, output: Output) -> Self {
446 self.output = Some(output);
447 self
448 }
449
450 pub fn seed(mut self, seed: i32) -> Self {
451 self.seed = Some(seed);
452 self
453 }
454
455 pub fn into_tts_request(self) -> TTSRequest {
456 TTSRequest {
457 voice_id: self.voice_id,
458 text: self.text,
459 model: self.model,
460 language: self.language,
461 prompt: self.prompt,
462 output: self.output,
463 seed: self.seed,
464 }
465 }
466}
467
468impl TTSRequest {
469 pub fn new(voice_id: impl Into<String>, text: impl Into<String>, model: TTSModel) -> Self {
471 Self {
472 voice_id: voice_id.into(),
473 text: text.into(),
474 model,
475 language: None,
476 prompt: None,
477 output: None,
478 seed: None,
479 }
480 }
481
482 pub fn language(mut self, language: impl Into<String>) -> Self {
484 self.language = Some(language.into());
485 self
486 }
487
488 pub fn prompt(mut self, prompt: impl Into<TTSPrompt>) -> Self {
490 self.prompt = Some(prompt.into());
491 self
492 }
493
494 pub fn output(mut self, output: Output) -> Self {
496 self.output = Some(output);
497 self
498 }
499
500 pub fn seed(mut self, seed: i32) -> Self {
502 self.seed = Some(seed);
503 self
504 }
505}
506
507#[derive(Debug, Clone, Serialize, Deserialize)]
512pub struct TTSRequestStream {
513 pub voice_id: String,
516 pub text: String,
518 pub model: TTSModel,
520 #[serde(skip_serializing_if = "Option::is_none")]
522 pub language: Option<String>,
523 #[serde(skip_serializing_if = "Option::is_none")]
525 pub prompt: Option<TTSPrompt>,
526 #[serde(skip_serializing_if = "Option::is_none")]
528 pub output: Option<OutputStream>,
529 #[serde(skip_serializing_if = "Option::is_none")]
531 pub seed: Option<i32>,
532}
533
534impl TTSRequestStream {
535 pub fn new(voice_id: impl Into<String>, text: impl Into<String>, model: TTSModel) -> Self {
537 Self {
538 voice_id: voice_id.into(),
539 text: text.into(),
540 model,
541 language: None,
542 prompt: None,
543 output: None,
544 seed: None,
545 }
546 }
547
548 pub fn language(mut self, language: impl Into<String>) -> Self {
550 self.language = Some(language.into());
551 self
552 }
553
554 pub fn prompt(mut self, prompt: impl Into<TTSPrompt>) -> Self {
556 self.prompt = Some(prompt.into());
557 self
558 }
559
560 pub fn output(mut self, output: OutputStream) -> Self {
562 self.output = Some(output);
563 self
564 }
565
566 pub fn seed(mut self, seed: i32) -> Self {
568 self.seed = Some(seed);
569 self
570 }
571}
572
573#[derive(Debug, Clone)]
575pub struct TTSResponse {
576 pub audio_data: Vec<u8>,
578 pub duration: f64,
580 pub format: AudioFormat,
582}
583
584#[derive(Debug, Clone, Serialize, Deserialize)]
586pub struct ModelInfo {
587 pub version: TTSModel,
589 pub emotions: Vec<String>,
591}
592
593#[derive(Debug, Clone, Serialize, Deserialize)]
595pub struct VoiceV2 {
596 pub voice_id: String,
598 pub voice_name: String,
600 pub models: Vec<ModelInfo>,
602 #[serde(skip_serializing_if = "Option::is_none")]
604 pub gender: Option<Gender>,
605 #[serde(skip_serializing_if = "Option::is_none")]
607 pub age: Option<Age>,
608 #[serde(skip_serializing_if = "Option::is_none")]
610 pub use_cases: Option<Vec<String>>,
611}
612
613#[derive(Debug, Clone, Default)]
615pub struct VoicesV2Filter {
616 pub model: Option<TTSModel>,
618 pub gender: Option<Gender>,
620 pub age: Option<Age>,
622 pub use_cases: Option<UseCase>,
624}
625
626impl VoicesV2Filter {
627 pub fn new() -> Self {
629 Self::default()
630 }
631
632 pub fn model(mut self, model: TTSModel) -> Self {
634 self.model = Some(model);
635 self
636 }
637
638 pub fn gender(mut self, gender: Gender) -> Self {
640 self.gender = Some(gender);
641 self
642 }
643
644 pub fn age(mut self, age: Age) -> Self {
646 self.age = Some(age);
647 self
648 }
649
650 pub fn use_cases(mut self, use_case: UseCase) -> Self {
652 self.use_cases = Some(use_case);
653 self
654 }
655}
656
657#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
659#[serde(rename_all = "lowercase")]
660pub enum PlanTier {
661 Free,
663 Lite,
665 Plus,
667 Custom,
669}
670
671#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
673pub struct Credits {
674 pub plan_credits: i64,
676 pub used_credits: i64,
678}
679
680#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
682pub struct Limits {
683 pub concurrency_limit: i64,
685}
686
687#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
689pub struct SubscriptionResponse {
690 pub plan: PlanTier,
692 pub credits: Credits,
694 pub limits: Limits,
696}
697
698#[derive(Debug, Clone, Serialize, Deserialize)]
700pub struct ErrorResponse {
701 pub detail: String,
703}
704
705pub const CLONING_MAX_FILE_SIZE: usize = 25 * 1024 * 1024;
711
712pub const NAME_MIN_LENGTH: usize = 1;
714
715pub const NAME_MAX_LENGTH: usize = 30;
717
718#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
720pub struct CustomVoice {
721 pub voice_id: String,
723 pub name: String,
725 pub model: String,
727}