ssml/
lib.rs

1//! Utilities for writing SSML documents.
2//!
3//! The root document in SSML is [`Speak`]. Use [`speak()`] to quickly create a document.
4//!
5//! ```
6//! let doc = ssml::speak(Some("en-US"), ["Hello, world!"]);
7//! ```
8//!
9//! Use [`Serialize`] to convert SSML elements to their string XML representation, which can then be sent to your speech
10//! synthesis service of chocie.
11//!
12//! ```
13//! use ssml::Serialize;
14//! # fn main() -> ssml::Result<()> {
15//! # let doc = ssml::speak(Some("en-US"), ["Hello, world!"]);
16//! let str = doc.serialize_to_string(&ssml::SerializeOptions::default().pretty())?;
17//! assert_eq!(
18//! 	str,
19//! 	r#"<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
20//! 	Hello, world!
21//! </speak>"#
22//! );
23//! # Ok(())
24//! # }
25//! ```
26
27#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
28#![allow(clippy::tabs_in_doc_comments)]
29
30extern crate alloc;
31extern crate core;
32
33use alloc::{
34	borrow::Cow,
35	string::{String, ToString}
36};
37use core::fmt::{Debug, Write};
38
39mod audio;
40mod r#break;
41mod element;
42mod emphasis;
43mod error;
44mod group;
45mod lang;
46mod mark;
47pub mod mstts;
48mod prosody;
49mod say_as;
50mod speak;
51mod text;
52mod unit;
53pub mod util;
54pub mod visit;
55pub mod visit_mut;
56mod voice;
57mod xml;
58
59pub use self::{
60	audio::{Audio, AudioRepeat, audio},
61	r#break::{Break, BreakStrength, breaks},
62	element::{CustomElement, Element, IntoElement},
63	emphasis::{Emphasis, EmphasisLevel, emphasis},
64	error::{Error, Result},
65	group::{Group, group},
66	lang::{Lang, lang},
67	mark::{Mark, mark},
68	prosody::{Prosody, ProsodyContour, ProsodyControl, ProsodyPitch, ProsodyRate, ProsodyVolume, prosody},
69	say_as::{DateFormat, SayAs, SpeechFormat, say_as},
70	speak::{Speak, speak},
71	text::{Text, text},
72	unit::{Decibels, DecibelsError, TimeDesignation, TimeDesignationError},
73	voice::{Voice, VoiceConfig, VoiceGender, voice},
74	xml::{EscapedDisplay, XmlWriter}
75};
76
77/// Vendor-specific flavor of SSML. Specifying this can be used to enable compatibility checks & add additional
78/// metadata required by certain services.
79#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
80#[non_exhaustive]
81#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
82pub enum Flavor {
83	/// Generic SSML.
84	///
85	/// This skips any compatibility checks and assumes all elements are supported.
86	#[default]
87	Generic,
88	/// Microsoft Azure Cognitive Speech Services (ACSS / MSTTS) flavored SSML.
89	///
90	/// Selecting this flavor will namely add the proper `xmlns` to the XML document, which is required by ACSS.
91	MicrosoftAzureCognitiveSpeechServices,
92	/// Google Cloud Text-to-Speech (GCTTS) flavored SSML.
93	GoogleCloudTextToSpeech,
94	/// Amazon Polly flavored SSML.
95	///
96	/// This will use compatibility checks for Standard voices only. Some SSML elements are not supported by Neural
97	/// voices. See the [Amazon Polly documentation](https://docs.aws.amazon.com/polly/latest/dg/supportedtags.html)
98	/// for more information on what tags Neural voices do not support.
99	AmazonPolly,
100	/// pyke Songbird flavored SSML.
101	PykeSongbird
102}
103
104/// Configuration for elements that support [`Serialize`].
105#[derive(Debug, Clone)]
106#[non_exhaustive]
107pub struct SerializeOptions {
108	/// The flavor of SSML to output; see [`Flavor`]. When `perform_checks` is enabled (which it is by default), this
109	/// can help catch compatibility issues with different speech synthesis providers.
110	pub flavor: Flavor,
111	/// Whether or not to format the outputted SSML in a human-readable format.
112	///
113	/// Generally, this should only be used for debugging. Some providers may charge per SSML character (not just spoken
114	/// character), so enabling this option in production may significantly increase costs.
115	pub pretty: bool
116}
117
118impl Default for SerializeOptions {
119	fn default() -> Self {
120		SerializeOptions {
121			flavor: Flavor::Generic,
122			pretty: false
123		}
124	}
125}
126
127impl SerializeOptions {
128	pub fn min(mut self) -> Self {
129		self.pretty = false;
130		self
131	}
132
133	pub fn pretty(mut self) -> Self {
134		self.pretty = true;
135		self
136	}
137
138	pub fn flavor(mut self, flavor: Flavor) -> Self {
139		self.flavor = flavor;
140		self
141	}
142}
143
144/// Trait to support serializing SSML elements.
145pub trait Serialize {
146	/// Serialize this SSML element into an `std` [`Write`]r.
147	fn serialize<W: Write>(&self, writer: &mut W, options: &SerializeOptions) -> crate::Result<()> {
148		let mut writer = XmlWriter::new(writer, options.pretty);
149		self.serialize_xml(&mut writer, options)?;
150		Ok(())
151	}
152
153	/// Serialize this SSML element into an [`XmlWriter`].
154	fn serialize_xml<W: Write>(&self, writer: &mut XmlWriter<W>, options: &SerializeOptions) -> crate::Result<()>;
155
156	/// Serialize this SSML element into a string.
157	fn serialize_to_string(&self, options: &SerializeOptions) -> crate::Result<String> {
158		let mut out = String::new();
159		self.serialize(&mut out, options)?;
160		Ok(out)
161	}
162}
163
164/// An [`Element`] that outputs a string of XML.
165///
166/// It differs from [`Text`] in that the contents of `Meta` are not escaped, meaning `Meta` can be used to write raw
167/// XML into the document.
168#[derive(Debug, Clone)]
169#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
170pub struct Meta<'s> {
171	raw: Cow<'s, str>,
172	name: Option<Cow<'s, str>>
173}
174
175impl<'s> Meta<'s> {
176	pub fn new(xml: impl Into<Cow<'s, str>>) -> Self {
177		Meta { raw: xml.into(), name: None }
178	}
179
180	pub fn with_name(mut self, name: impl Into<Cow<'s, str>>) -> Self {
181		self.name = Some(name.into());
182		self
183	}
184
185	pub fn to_owned(&self) -> Meta<'static> {
186		self.clone().into_owned()
187	}
188
189	pub fn into_owned(self) -> Meta<'static> {
190		Meta {
191			raw: match self.raw {
192				Cow::Borrowed(b) => Cow::Owned(b.to_string()),
193				Cow::Owned(b) => Cow::Owned(b)
194			},
195			name: match self.name {
196				Some(Cow::Borrowed(b)) => Some(Cow::Owned(b.to_string())),
197				Some(Cow::Owned(b)) => Some(Cow::Owned(b)),
198				None => None
199			}
200		}
201	}
202}
203
204impl<'s> Serialize for Meta<'s> {
205	fn serialize_xml<W: Write>(&self, writer: &mut XmlWriter<W>, _: &SerializeOptions) -> crate::Result<()> {
206		writer.raw(&self.raw)
207	}
208}