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 = lufs.is_finite().then_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 target_lufs: Option<f64>,
181 #[serde(skip_serializing_if = "Option::is_none")]
183 pub audio_pitch: Option<i32>,
184 #[serde(skip_serializing_if = "Option::is_none")]
186 pub audio_tempo: Option<f64>,
187 #[serde(skip_serializing_if = "Option::is_none")]
189 pub audio_format: Option<AudioFormat>,
190}
191
192impl OutputStream {
193 pub fn new() -> Self {
195 Self::default()
196 }
197
198 pub fn audio_pitch(mut self, pitch: i32) -> Self {
200 self.audio_pitch = Some(pitch.clamp(-12, 12));
201 self
202 }
203
204 pub fn audio_tempo(mut self, tempo: f64) -> Self {
206 self.audio_tempo = Some(tempo.clamp(0.5, 2.0));
207 self
208 }
209
210 pub fn audio_format(mut self, format: AudioFormat) -> Self {
212 self.audio_format = Some(format);
213 self
214 }
215
216 pub fn target_lufs(mut self, lufs: f64) -> Self {
218 self.target_lufs = Some(lufs.clamp(-70.0, 0.0));
219 self
220 }
221}
222
223#[derive(Debug, Clone, Default, Serialize, Deserialize)]
225pub struct Prompt {
226 #[serde(skip_serializing_if = "Option::is_none")]
228 pub emotion_preset: Option<EmotionPreset>,
229 #[serde(skip_serializing_if = "Option::is_none")]
231 pub emotion_intensity: Option<f64>,
232}
233
234impl Prompt {
235 pub fn new() -> Self {
237 Self::default()
238 }
239
240 pub fn emotion_preset(mut self, preset: EmotionPreset) -> Self {
242 self.emotion_preset = Some(preset);
243 self
244 }
245
246 pub fn emotion_intensity(mut self, intensity: f64) -> Self {
248 self.emotion_intensity = Some(intensity.clamp(0.0, 2.0));
249 self
250 }
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct PresetPrompt {
256 pub emotion_type: String,
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub emotion_preset: Option<EmotionPreset>,
261 #[serde(skip_serializing_if = "Option::is_none")]
263 pub emotion_intensity: Option<f64>,
264}
265
266impl Default for PresetPrompt {
267 fn default() -> Self {
268 Self {
269 emotion_type: "preset".to_string(),
270 emotion_preset: None,
271 emotion_intensity: None,
272 }
273 }
274}
275
276impl PresetPrompt {
277 pub fn new() -> Self {
279 Self::default()
280 }
281
282 pub fn emotion_preset(mut self, preset: EmotionPreset) -> Self {
284 self.emotion_preset = Some(preset);
285 self
286 }
287
288 pub fn emotion_intensity(mut self, intensity: f64) -> Self {
290 self.emotion_intensity = Some(intensity.clamp(0.0, 2.0));
291 self
292 }
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct SmartPrompt {
298 pub emotion_type: String,
300 #[serde(skip_serializing_if = "Option::is_none")]
302 pub previous_text: Option<String>,
303 #[serde(skip_serializing_if = "Option::is_none")]
305 pub next_text: Option<String>,
306}
307
308impl Default for SmartPrompt {
309 fn default() -> Self {
310 Self {
311 emotion_type: "smart".to_string(),
312 previous_text: None,
313 next_text: None,
314 }
315 }
316}
317
318impl SmartPrompt {
319 pub fn new() -> Self {
321 Self::default()
322 }
323
324 pub fn previous_text(mut self, text: impl Into<String>) -> Self {
326 self.previous_text = Some(text.into());
327 self
328 }
329
330 pub fn next_text(mut self, text: impl Into<String>) -> Self {
332 self.next_text = Some(text.into());
333 self
334 }
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize)]
339#[serde(untagged)]
340pub enum TTSPrompt {
341 Basic(Prompt),
343 Preset(PresetPrompt),
345 Smart(SmartPrompt),
347}
348
349impl From<Prompt> for TTSPrompt {
350 fn from(prompt: Prompt) -> Self {
351 TTSPrompt::Basic(prompt)
352 }
353}
354
355impl From<PresetPrompt> for TTSPrompt {
356 fn from(prompt: PresetPrompt) -> Self {
357 TTSPrompt::Preset(prompt)
358 }
359}
360
361impl From<SmartPrompt> for TTSPrompt {
362 fn from(prompt: SmartPrompt) -> Self {
363 TTSPrompt::Smart(prompt)
364 }
365}
366
367#[derive(Debug, Clone, Serialize, Deserialize)]
369pub struct TTSRequest {
370 pub voice_id: String,
373 pub text: String,
375 pub model: TTSModel,
377 #[serde(skip_serializing_if = "Option::is_none")]
379 pub language: Option<String>,
380 #[serde(skip_serializing_if = "Option::is_none")]
382 pub prompt: Option<TTSPrompt>,
383 #[serde(skip_serializing_if = "Option::is_none")]
385 pub output: Option<Output>,
386 #[serde(skip_serializing_if = "Option::is_none")]
388 pub seed: Option<i32>,
389}
390
391#[derive(Debug, Clone, Serialize, Deserialize)]
395pub struct GenerateToFileRequest {
396 pub voice_id: String,
399 pub text: String,
401 pub model: TTSModel,
403 #[serde(skip_serializing_if = "Option::is_none")]
405 pub language: Option<String>,
406 #[serde(skip_serializing_if = "Option::is_none")]
408 pub prompt: Option<TTSPrompt>,
409 #[serde(skip_serializing_if = "Option::is_none")]
411 pub output: Option<Output>,
412 #[serde(skip_serializing_if = "Option::is_none")]
414 pub seed: Option<i32>,
415}
416
417impl GenerateToFileRequest {
418 pub fn new(voice_id: impl Into<String>, text: impl Into<String>) -> Self {
420 let voice_id = voice_id.into();
421 let text = text.into();
422 assert!(!voice_id.trim().is_empty(), "voice_id is required");
423 assert!(!text.trim().is_empty(), "text is required");
424 assert!(
425 text.chars().count() <= 2000,
426 "text must not exceed 2000 characters"
427 );
428 Self {
429 voice_id,
430 text,
431 model: TTSModel::SsfmV30,
432 language: None,
433 prompt: None,
434 output: None,
435 seed: None,
436 }
437 }
438
439 pub fn model(mut self, model: TTSModel) -> Self {
440 self.model = model;
441 self
442 }
443
444 pub fn language(mut self, language: impl Into<String>) -> Self {
445 self.language = Some(language.into());
446 self
447 }
448
449 pub fn prompt(mut self, prompt: impl Into<TTSPrompt>) -> Self {
450 self.prompt = Some(prompt.into());
451 self
452 }
453
454 pub fn output(mut self, output: Output) -> Self {
455 self.output = Some(output);
456 self
457 }
458
459 pub fn seed(mut self, seed: i32) -> Self {
460 self.seed = Some(seed);
461 self
462 }
463
464 pub fn into_tts_request(self) -> TTSRequest {
465 TTSRequest {
466 voice_id: self.voice_id,
467 text: self.text,
468 model: self.model,
469 language: self.language,
470 prompt: self.prompt,
471 output: self.output,
472 seed: self.seed,
473 }
474 }
475}
476
477impl TTSRequest {
478 pub fn new(voice_id: impl Into<String>, text: impl Into<String>, model: TTSModel) -> Self {
480 Self {
481 voice_id: voice_id.into(),
482 text: text.into(),
483 model,
484 language: None,
485 prompt: None,
486 output: None,
487 seed: None,
488 }
489 }
490
491 pub fn language(mut self, language: impl Into<String>) -> Self {
493 self.language = Some(language.into());
494 self
495 }
496
497 pub fn prompt(mut self, prompt: impl Into<TTSPrompt>) -> Self {
499 self.prompt = Some(prompt.into());
500 self
501 }
502
503 pub fn output(mut self, output: Output) -> Self {
505 self.output = Some(output);
506 self
507 }
508
509 pub fn seed(mut self, seed: i32) -> Self {
511 self.seed = Some(seed);
512 self
513 }
514}
515
516#[derive(Debug, Clone, Serialize, Deserialize)]
521pub struct TTSRequestStream {
522 pub voice_id: String,
525 pub text: String,
527 pub model: TTSModel,
529 #[serde(skip_serializing_if = "Option::is_none")]
531 pub language: Option<String>,
532 #[serde(skip_serializing_if = "Option::is_none")]
534 pub prompt: Option<TTSPrompt>,
535 #[serde(skip_serializing_if = "Option::is_none")]
537 pub output: Option<OutputStream>,
538 #[serde(skip_serializing_if = "Option::is_none")]
540 pub seed: Option<i32>,
541}
542
543impl TTSRequestStream {
544 pub fn new(voice_id: impl Into<String>, text: impl Into<String>, model: TTSModel) -> Self {
546 Self {
547 voice_id: voice_id.into(),
548 text: text.into(),
549 model,
550 language: None,
551 prompt: None,
552 output: None,
553 seed: None,
554 }
555 }
556
557 pub fn language(mut self, language: impl Into<String>) -> Self {
559 self.language = Some(language.into());
560 self
561 }
562
563 pub fn prompt(mut self, prompt: impl Into<TTSPrompt>) -> Self {
565 self.prompt = Some(prompt.into());
566 self
567 }
568
569 pub fn output(mut self, output: OutputStream) -> Self {
571 self.output = Some(output);
572 self
573 }
574
575 pub fn seed(mut self, seed: i32) -> Self {
577 self.seed = Some(seed);
578 self
579 }
580}
581
582#[derive(Debug, Clone)]
584pub struct TTSResponse {
585 pub audio_data: Vec<u8>,
587 pub duration: f64,
589 pub format: AudioFormat,
591}
592
593#[derive(Debug, Clone, Serialize, Deserialize)]
595pub struct ModelInfo {
596 pub version: TTSModel,
598 pub emotions: Vec<String>,
600}
601
602#[derive(Debug, Clone, Serialize, Deserialize)]
604pub struct VoiceV2 {
605 pub voice_id: String,
607 pub voice_name: String,
609 pub models: Vec<ModelInfo>,
611 #[serde(skip_serializing_if = "Option::is_none")]
613 pub gender: Option<Gender>,
614 #[serde(skip_serializing_if = "Option::is_none")]
616 pub age: Option<Age>,
617 #[serde(skip_serializing_if = "Option::is_none")]
619 pub use_cases: Option<Vec<String>>,
620}
621
622#[derive(Debug, Clone, Serialize, Deserialize)]
628pub struct RecommendedVoice {
629 pub voice_id: String,
631 pub voice_name: String,
633 pub score: f64,
635}
636
637#[derive(Debug, Clone, Default)]
639pub struct VoicesV2Filter {
640 pub model: Option<TTSModel>,
642 pub gender: Option<Gender>,
644 pub age: Option<Age>,
646 pub use_cases: Option<UseCase>,
648}
649
650impl VoicesV2Filter {
651 pub fn new() -> Self {
653 Self::default()
654 }
655
656 pub fn model(mut self, model: TTSModel) -> Self {
658 self.model = Some(model);
659 self
660 }
661
662 pub fn gender(mut self, gender: Gender) -> Self {
664 self.gender = Some(gender);
665 self
666 }
667
668 pub fn age(mut self, age: Age) -> Self {
670 self.age = Some(age);
671 self
672 }
673
674 pub fn use_cases(mut self, use_case: UseCase) -> Self {
676 self.use_cases = Some(use_case);
677 self
678 }
679}
680
681#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
683#[serde(rename_all = "lowercase")]
684pub enum PlanTier {
685 Free,
687 Lite,
689 Plus,
691 Custom,
693}
694
695#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
697pub struct Credits {
698 pub plan_credits: i64,
700 pub used_credits: i64,
702}
703
704#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
706pub struct Limits {
707 pub concurrency_limit: i64,
709}
710
711#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
713pub struct SubscriptionResponse {
714 pub plan: PlanTier,
716 pub credits: Credits,
718 pub limits: Limits,
720}
721
722#[derive(Debug, Clone, Serialize, Deserialize)]
724pub struct ErrorResponse {
725 pub detail: String,
727}
728
729pub const CLONING_MAX_FILE_SIZE: usize = 25 * 1024 * 1024;
735
736pub const NAME_MIN_LENGTH: usize = 1;
738
739pub const NAME_MAX_LENGTH: usize = 30;
741
742#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
744pub struct CustomVoice {
745 pub voice_id: String,
747 pub name: String,
749 pub model: String,
751}