ssml/
unit.rs

1use core::{
2	fmt::{self, Debug, Display},
3	num::ParseFloatError,
4	str::FromStr
5};
6
7use crate::xml::TrustedNoEscape;
8
9#[derive(Debug, PartialEq)]
10pub enum TimeDesignationError {
11	BadUnit,
12	BadLength,
13	Negative,
14	ParseFloat(ParseFloatError)
15}
16
17impl Display for TimeDesignationError {
18	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19		match self {
20			TimeDesignationError::BadUnit => f.write_str("time designation has invalid unit (allowed are ms, s)"),
21			TimeDesignationError::BadLength => f.write_str("string is too short to be a valid time designation"),
22			TimeDesignationError::Negative => f.write_str("time designations cannot be negative"),
23			TimeDesignationError::ParseFloat(e) => f.write_fmt(format_args!("couldn't parse float: {e}"))
24		}
25	}
26}
27
28#[cfg(feature = "std")]
29impl std::error::Error for TimeDesignationError {}
30
31/// A time designation is a representation of a non-negative offset of time.
32///
33/// Time designations can be provided in either seconds (`s`) or milliseconds (`ms`):
34/// ```
35/// # use ssml::TimeDesignation;
36/// # fn main() -> ssml::Result<()> {
37/// assert_eq!("15s".parse::<TimeDesignation>()?, TimeDesignation::from_millis(15_000.));
38/// assert_eq!("750ms".parse::<TimeDesignation>()?, TimeDesignation::from_millis(750.));
39/// assert_eq!("+0.75s".parse::<TimeDesignation>()?, TimeDesignation::from_millis(750.));
40///
41/// // Fails
42/// assert!("-5s".parse::<TimeDesignation>().is_err());
43/// assert!("5 s".parse::<TimeDesignation>().is_err());
44/// assert!("15sec".parse::<TimeDesignation>().is_err());
45/// assert!("5m".parse::<TimeDesignation>().is_err());
46/// # Ok(())
47/// # }
48/// ```
49#[derive(Default, Clone, PartialEq, PartialOrd)]
50#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51pub struct TimeDesignation {
52	millis: f32
53}
54
55impl TimeDesignation {
56	/// Create a [`TimeDesignation`] from a set number of milliseconds.
57	pub fn from_millis(millis: f32) -> Self {
58		Self { millis }
59	}
60
61	/// Convert this time designation to milliseconds.
62	pub fn to_millis(&self) -> f32 {
63		self.millis
64	}
65}
66
67impl FromStr for TimeDesignation {
68	type Err = TimeDesignationError;
69
70	fn from_str(s: &str) -> Result<Self, Self::Err> {
71		let len = s.len();
72		if len < 2 {
73			return Err(TimeDesignationError::BadLength);
74		}
75
76		let (unit, skip) = if s.ends_with("ms") {
77			(1., 2)
78		} else if s.ends_with('s') && matches!(s.chars().nth(len - 2), Some('0'..='9') | Some('.')) {
79			(1000., 1)
80		} else {
81			return Err(TimeDesignationError::BadUnit);
82		};
83
84		let f = s[..len - skip].parse::<f32>().map_err(TimeDesignationError::ParseFloat)?;
85		if f.is_sign_negative() {
86			return Err(TimeDesignationError::Negative);
87		}
88
89		Ok(Self::from_millis(f * unit))
90	}
91}
92
93impl From<&str> for TimeDesignation {
94	fn from(value: &str) -> Self {
95		value.parse().unwrap_or_default()
96	}
97}
98
99impl Display for TimeDesignation {
100	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101		f.write_fmt(format_args!("{:+}ms", self.to_millis()))
102	}
103}
104impl TrustedNoEscape for TimeDesignation {}
105
106impl Debug for TimeDesignation {
107	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108		Display::fmt(self, f)
109	}
110}
111
112#[derive(Debug, PartialEq)]
113pub enum DecibelsError {
114	BadUnit,
115	BadLength,
116	ParseFloat(ParseFloatError)
117}
118
119impl Display for DecibelsError {
120	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121		match self {
122			DecibelsError::BadUnit => f.write_str("decibels has invalid unit (allowed are dB)"),
123			DecibelsError::BadLength => f.write_str("string is too short to be a valid decibel value"),
124			DecibelsError::ParseFloat(e) => f.write_fmt(format_args!("couldn't parse float: {e}"))
125		}
126	}
127}
128
129#[cfg(feature = "std")]
130impl std::error::Error for DecibelsError {}
131
132/// A string representation of a signed amplitude offset in decibels (`dB`).
133///
134/// ```
135/// # use ssml::Decibels;
136/// # fn main() -> ssml::Result<()> {
137/// assert_eq!("+0.0dB".parse::<Decibels>()?, Decibels::new(0.));
138/// assert_eq!("-6dB".parse::<Decibels>()?, Decibels::new(-6.));
139/// assert_eq!("2dB".parse::<Decibels>()?, Decibels::new(2.));
140///
141/// // Fails
142/// assert!("-3DB".parse::<Decibels>().is_err());
143/// assert!("0 dB".parse::<Decibels>().is_err());
144/// assert!("6".parse::<Decibels>().is_err());
145/// # Ok(())
146/// # }
147/// ```
148#[derive(Default, Clone, PartialEq, PartialOrd)]
149#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
150pub struct Decibels(f32);
151
152impl Decibels {
153	pub fn new(value: f32) -> Self {
154		Self(value)
155	}
156
157	pub fn value(&self) -> f32 {
158		self.0
159	}
160}
161
162impl FromStr for Decibels {
163	type Err = DecibelsError;
164
165	fn from_str(s: &str) -> Result<Self, Self::Err> {
166		let len = s.len();
167		if len < 2 {
168			return Err(DecibelsError::BadLength);
169		}
170
171		if !s.ends_with("dB") {
172			return Err(DecibelsError::BadUnit);
173		}
174
175		let f = s[..len - 2].parse::<f32>().map_err(DecibelsError::ParseFloat)?;
176		Ok(Self(f))
177	}
178}
179
180impl From<f32> for Decibels {
181	fn from(value: f32) -> Self {
182		Decibels(value)
183	}
184}
185
186impl From<&str> for Decibels {
187	fn from(value: &str) -> Self {
188		value.parse().unwrap_or_default()
189	}
190}
191
192impl Display for Decibels {
193	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194		f.write_fmt(format_args!("{:+}dB", self.0))
195	}
196}
197impl TrustedNoEscape for Decibels {}
198
199impl Debug for Decibels {
200	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201		Display::fmt(self, f)
202	}
203}
204
205pub(crate) struct SpeedFormatter(pub(crate) f32);
206impl Display for SpeedFormatter {
207	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208		f.write_fmt(format_args!("{}%", self.0 * 100.))
209	}
210}
211impl TrustedNoEscape for SpeedFormatter {}
212
213#[cfg(test)]
214mod tests {
215	use super::{Decibels, TimeDesignation};
216
217	#[test]
218	fn parse_time_designation() {
219		assert_eq!("+7s".parse::<TimeDesignation>(), Ok(TimeDesignation::from_millis(7000.0)));
220		assert_eq!("700ms".parse::<TimeDesignation>(), Ok(TimeDesignation::from_millis(700.0)));
221		assert!("-.7s".parse::<TimeDesignation>().is_err());
222	}
223
224	#[test]
225	fn parse_decibels() {
226		assert_eq!("+6dB".parse::<Decibels>(), Ok(Decibels(6.0)));
227		assert_eq!("-.6dB".parse::<Decibels>(), Ok(Decibels(-0.6)));
228		assert!("6".parse::<Decibels>().is_err());
229		assert!("6db".parse::<Decibels>().is_err());
230	}
231}