tts_urls/
voicerss.rs

1use std::borrow::Cow;
2
3macro_rules! define_enum {
4    ($name:ident with $($str:literal $($alias:literal)* $variant:ident)* ) => {
5        #[allow(missing_docs)]
6        #[derive(Debug, PartialEq, Eq, Copy, Clone)]
7        pub enum $name {
8            $($variant),*
9        }
10        impl std::fmt::Display for $name {
11            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
12                let s = match self {
13                    $( $name::$variant => $str, )*
14                };
15                f.write_str(s)
16            }
17        }
18
19        impl std::str::FromStr for $name {
20            type Err = ();
21            fn from_str(s: &str) -> Result<Self, Self::Err> {
22                let val = match s.to_lowercase().as_str() {
23                    $($str $(| $alias)* => $name::$variant,)*
24                    _ => return Err(()),
25                };
26                Ok(val)
27            }
28        }
29    };
30}
31
32define_enum!(Codec with
33    "mp3" MP3
34    "wav" WAV
35    "aac" AAC
36    "ogg" OGG
37    "caf" CAF
38);
39impl Default for Codec {
40    fn default() -> Self {
41        Codec::MP3
42    }
43}
44
45define_enum!(Language with
46    "ar-eg" ArabicEgypt
47    "ar-sa" ArabicSaudiArabia
48    "bg-bg" "bg" Bulgarian
49    "ca-es" Catalan
50    "zh-cn" Chinese
51    "zh-hk" ChineseHongKong
52    "zh-tw" ChineseTaiwan
53    "hr-hr" Croatian
54    "cs-cz" Czech
55    "da-dk" Danish
56    "nl-be" DutchBelgium
57    "nl-nl" DutchNetherlands
58    "en-au" EnglishAustralia
59    "en-ca" EnglishCanada
60    "en-gb" EnglishGreatBritain
61    "en-in" EnglishIndia
62    "en-ie" EnglishIreland
63    "en-us" EnglishUnitedStates
64    "fi-fi" "fi" Finnish
65    "fr-ca" FrenchCanada
66    "fr-fr" "fr" French
67    "fr-ch" FrenchSwitzerland
68    "de-de" "de" German
69    "de-at" GermanAustria
70    "de-ch" GermanSwitzerland
71    "el-gr" Greek
72    "he-il" Hebrew
73    "hi-in" Hindi
74    "hu-hu" "hu" Hungarian
75    "id-id" "id" Indonesian
76    "it-it" "it" Italian
77    "ja-jp" Japanese
78    "ko-kr" Korean
79    "ms-my" Malay
80    "nb-no" "no" Norwegian
81    "pl-pl" "pl" Polish
82    "pt-pt" Portuguese
83    "pt-br" PortugueseBrazil
84    "ro-ro" "ro" Romanian
85    "ru-ru" "ru" Russian
86    "sk-sk" "sk" Slovak
87    "sl-si" Slovenian
88    "es-es" "es" Spanish
89    "es-mx" SpanishMexico
90    "sv-se" Swedish
91    "ta-in" Tamil
92    "th-th" "th" Thai
93    "tr-tr" "tr" Turkish
94    "vi-vn" Vietnamese
95);
96impl Default for Language {
97    fn default() -> Self {
98        Language::EnglishUnitedStates
99    }
100}
101
102/// The audio formats supported by VoiceRSS
103///
104/// <http://www.voicerss.org/api/>
105pub const AUDIO_FORMATS: &[&str] = &[
106    "8khz_8bit_mono",
107    "8khz_8bit_stereo",
108    "8khz_16bit_mono",
109    "8khz_16bit_stereo",
110    "11khz_8bit_mono",
111    "11khz_8bit_stereo",
112    "11khz_16bit_mono",
113    "11khz_16bit_stereo",
114    "12khz_8bit_mono",
115    "12khz_8bit_stereo",
116    "12khz_16bit_mono",
117    "12khz_16bit_stereo",
118    "16khz_8bit_mono",
119    "16khz_8bit_stereo",
120    "16khz_16bit_mono",
121    "16khz_16bit_stereo",
122    "22khz_8bit_mono",
123    "22khz_8bit_stereo",
124    "22khz_16bit_mono",
125    "22khz_16bit_stereo",
126    "24khz_8bit_mono",
127    "24khz_8bit_stereo",
128    "24khz_16bit_mono",
129    "24khz_16bit_stereo",
130    "32khz_8bit_mono",
131    "32khz_8bit_stereo",
132    "32khz_16bit_mono",
133    "32khz_16bit_stereo",
134    "44khz_8bit_mono",
135    "44khz_8bit_stereo",
136    "44khz_16bit_mono",
137    "44khz_16bit_stereo",
138    "48khz_8bit_mono",
139    "48khz_8bit_stereo",
140    "48khz_16bit_mono",
141    "48khz_16bit_stereo",
142    "alaw_8khz_mono",
143    "alaw_8khz_stereo",
144    "alaw_11khz_mono",
145    "alaw_11khz_stereo",
146    "alaw_22khz_mono",
147    "alaw_22khz_stereo",
148    "alaw_44khz_mono",
149    "alaw_44khz_stereo",
150    "ulaw_8khz_mono",
151    "ulaw_8khz_stereo",
152    "ulaw_11khz_mono",
153    "ulaw_11khz_stereo",
154    "ulaw_22khz_mono",
155    "ulaw_22khz_stereo",
156    "ulaw_44khz_mono",
157    "ulaw_44khz_stereo",
158];
159
160///
161/// # Example usage:
162/// ```rust
163/// use tts_urls::voicerss::{VoiceRSSOptions, Language, Codec};
164/// let key = "key";
165///
166/// let url = VoiceRSSOptions::new()
167///     .language(Language::German)
168///     .audio_format("32khz_16bit_stereo")
169///     .codec(Codec::MP3)
170///     .url(key, "Hallo Welt!");
171/// assert_eq!(url, "http://api.voicerss.org/?key=key&hl=de-de&c=MP3&f=32khz_16bit_stereo&src=Hallo%20Welt%21");
172/// ```
173#[derive(Default)]
174pub struct VoiceRSSOptions {
175    language: Option<Language>,
176    voice: Option<Cow<'static, str>>,
177    speed: Option<i8>,
178    codec: Option<Codec>,
179    audio_format: Option<Cow<'static, str>>,
180    ssml: Option<bool>,
181    base64: Option<bool>,
182}
183
184impl VoiceRSSOptions {
185    #[allow(missing_docs)]
186    pub fn new() -> Self {
187        Self::default()
188    }
189
190    /// see [VoiceRSS documentation](http://www.voicerss.org/api/) for possible values
191    pub fn language(&mut self, language: Language) -> &mut Self {
192        self.language = Some(language);
193        self
194    }
195
196    /// see [VoiceRSS documentation](http://www.voicerss.org/api/) for possible values
197    pub fn voice(&mut self, voice: impl Into<Cow<'static, str>>) -> &mut Self {
198        self.voice = Some(voice.into());
199        self
200    }
201
202    /// The speech rate. Allows values from -10 to 10.
203    pub fn speed(&mut self, speed: i8) -> &mut Self {
204        assert!(
205            speed >= -10 && speed <= 10,
206            "speed should be between -10 and 10"
207        );
208        self.speed = Some(speed);
209        self
210    }
211
212    /// see [VoiceRSS documentation](http://www.voicerss.org/api/) for possible values
213    pub fn codec(&mut self, codec: Codec) -> &mut Self {
214        self.codec = Some(codec);
215        self
216    }
217
218    /// see [www.voicerss.org/api/documentation.aspx](VoiceRSS documentation for possible values)
219    pub fn audio_format(&mut self, audio_format: impl Into<Cow<'static, str>>) -> &mut Self {
220        let format = audio_format.into();
221
222        assert!(AUDIO_FORMATS.iter().any(|&f| f == format));
223
224        self.audio_format = Some(format);
225        self
226    }
227
228    /// Enable the SSML textual content format
229    pub fn ssml(&mut self, ssml: bool) -> &mut Self {
230        self.ssml = Some(ssml);
231        self
232    }
233
234    /// makes the VoiceRSS api return the inline base64 `src` for an HTML <audio> element
235    pub fn base64(&mut self, base64: bool) -> &mut Self {
236        self.base64 = Some(base64);
237        self
238    }
239
240    /// Returns the URL to the TTS audio for the given term and api key.
241    pub fn url(&self, key: &str, text: &str) -> String {
242        assert!(
243            key.chars().all(char::is_alphanumeric),
244            "key should be alphanumeric"
245        );
246
247        let language = self.language.unwrap_or_default();
248        let text = percent_encoding::utf8_percent_encode(text, crate::ENCODE_SET);
249
250        let mut url = format!("http://api.voicerss.org/?key={}&hl={}", key, language);
251
252        if let Some(voice) = &self.voice {
253            url.push_str(&format!("&v={}", voice));
254        }
255        if let Some(speed) = self.speed {
256            url.push_str(&format!("&r={}", speed));
257        }
258        if let Some(codec) = &self.codec {
259            url.push_str(&format!("&c={}", codec));
260        }
261        if let Some(audio_format) = &self.audio_format {
262            url.push_str(&format!("&f={}", audio_format));
263        }
264        if let Some(ssml) = &self.ssml {
265            url.push_str(&format!("&sml={}", ssml));
266        }
267
268        url.push_str(&format!("&src={}", text));
269
270        url
271    }
272}
273
274/// Short version of [`VoiceRSSOptions::url`](struct.VoiceRSSOptions.html#method.url) with default options.
275pub fn url(key: &str, text: &str) -> String {
276    VoiceRSSOptions::default().url(key, text)
277}
278
279#[test]
280#[should_panic]
281fn invalid_speed() {
282    VoiceRSSOptions::new().speed(11);
283}
284
285#[test]
286#[should_panic]
287fn invalid_key() {
288    VoiceRSSOptions::new().url("/?;", "");
289}
290
291#[test]
292fn unicode() {
293    let url = VoiceRSSOptions::new()
294        .language(Language::Russian)
295        .url("key", "Добрый день!");
296
297    assert_eq!(
298        url,
299        "http://api.voicerss.org/?key=key&hl=ru-ru&src=%D0%94%D0%BE%D0%B1%D1%80%D1%8B%D0%B9%20%D0%B4%D0%B5%D0%BD%D1%8C%21"
300    );
301}
302#[test]
303fn language_parse() {
304    assert_eq!(Language::ArabicEgypt, "ar-eg".parse().unwrap());
305}
306
307#[test]
308fn language_alias() {
309    assert_eq!(Language::German, "de".parse().unwrap());
310}