ruma_events/room/message/audio.rs
1use std::time::Duration;
2
3use js_int::UInt;
4use ruma_common::OwnedMxcUri;
5use serde::{Deserialize, Serialize};
6
7use super::FormattedBody;
8use crate::room::{
9 message::media_caption::{caption, formatted_caption},
10 EncryptedFile, MediaSource,
11};
12
13/// The payload for an audio message.
14#[derive(Clone, Debug, Deserialize, Serialize)]
15#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
16#[serde(tag = "msgtype", rename = "m.audio")]
17pub struct AudioMessageEventContent {
18 /// The textual representation of this message.
19 ///
20 /// If the `filename` field is not set or has the same value, this is the filename of the
21 /// uploaded file. Otherwise, this should be interpreted as a user-written media caption.
22 pub body: String,
23
24 /// Formatted form of the message `body`.
25 ///
26 /// This should only be set if the body represents a caption.
27 #[serde(flatten)]
28 pub formatted: Option<FormattedBody>,
29
30 /// The original filename of the uploaded file as deserialized from the event.
31 ///
32 /// It is recommended to use the `filename` method to get the filename which automatically
33 /// falls back to the `body` field when the `filename` field is not set.
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub filename: Option<String>,
36
37 /// The source of the audio clip.
38 #[serde(flatten)]
39 pub source: MediaSource,
40
41 /// Metadata for the audio clip referred to in `source`.
42 #[serde(skip_serializing_if = "Option::is_none")]
43 pub info: Option<Box<AudioInfo>>,
44
45 /// Extensible event fallback data for audio messages, from the
46 /// [first version of MSC3245][msc].
47 ///
48 /// [msc]: https://github.com/matrix-org/matrix-spec-proposals/blob/83f6c5b469c1d78f714e335dcaa25354b255ffa5/proposals/3245-voice-messages.md
49 #[cfg(feature = "unstable-msc3245-v1-compat")]
50 #[serde(rename = "org.matrix.msc1767.audio", skip_serializing_if = "Option::is_none")]
51 pub audio: Option<UnstableAudioDetailsContentBlock>,
52
53 /// Extensible event fallback data for voice messages, from the
54 /// [first version of MSC3245][msc].
55 ///
56 /// [msc]: https://github.com/matrix-org/matrix-spec-proposals/blob/83f6c5b469c1d78f714e335dcaa25354b255ffa5/proposals/3245-voice-messages.md
57 #[cfg(feature = "unstable-msc3245-v1-compat")]
58 #[serde(rename = "org.matrix.msc3245.voice", skip_serializing_if = "Option::is_none")]
59 pub voice: Option<UnstableVoiceContentBlock>,
60}
61
62impl AudioMessageEventContent {
63 /// Creates a new `AudioMessageEventContent` with the given body and source.
64 pub fn new(body: String, source: MediaSource) -> Self {
65 Self {
66 body,
67 formatted: None,
68 filename: None,
69 source,
70 info: None,
71 #[cfg(feature = "unstable-msc3245-v1-compat")]
72 audio: None,
73 #[cfg(feature = "unstable-msc3245-v1-compat")]
74 voice: None,
75 }
76 }
77
78 /// Creates a new non-encrypted `AudioMessageEventContent` with the given body and url.
79 pub fn plain(body: String, url: OwnedMxcUri) -> Self {
80 Self::new(body, MediaSource::Plain(url))
81 }
82
83 /// Creates a new encrypted `AudioMessageEventContent` with the given body and encrypted
84 /// file.
85 pub fn encrypted(body: String, file: EncryptedFile) -> Self {
86 Self::new(body, MediaSource::Encrypted(Box::new(file)))
87 }
88
89 /// Creates a new `AudioMessageEventContent` from `self` with the `info` field set to the given
90 /// value.
91 ///
92 /// Since the field is public, you can also assign to it directly. This method merely acts
93 /// as a shorthand for that, because it is very common to set this field.
94 pub fn info(self, info: impl Into<Option<Box<AudioInfo>>>) -> Self {
95 Self { info: info.into(), ..self }
96 }
97
98 /// Computes the filename for the audio file as defined by the [spec](https://spec.matrix.org/latest/client-server-api/#media-captions).
99 ///
100 /// This differs from the `filename` field as this method falls back to the `body` field when
101 /// the `filename` field is not set.
102 pub fn filename(&self) -> &str {
103 self.filename.as_deref().unwrap_or(&self.body)
104 }
105
106 /// Returns the caption for the audio as defined by the [spec](https://spec.matrix.org/latest/client-server-api/#media-captions).
107 ///
108 /// In short, this is the `body` field if the `filename` field exists and has a different value,
109 /// otherwise the media file does not have a caption.
110 pub fn caption(&self) -> Option<&str> {
111 caption(&self.body, self.filename.as_deref())
112 }
113
114 /// Returns the formatted caption for the audio as defined by the [spec](https://spec.matrix.org/latest/client-server-api/#media-captions).
115 ///
116 /// This is the same as `caption`, but returns the formatted body instead of the plain body.
117 pub fn formatted_caption(&self) -> Option<&FormattedBody> {
118 formatted_caption(&self.body, self.formatted.as_ref(), self.filename.as_deref())
119 }
120}
121
122/// Metadata about an audio clip.
123#[derive(Clone, Debug, Default, Deserialize, Serialize)]
124#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
125pub struct AudioInfo {
126 /// The duration of the audio in milliseconds.
127 #[serde(
128 with = "ruma_common::serde::duration::opt_ms",
129 default,
130 skip_serializing_if = "Option::is_none"
131 )]
132 pub duration: Option<Duration>,
133
134 /// The mimetype of the audio, e.g. "audio/aac".
135 #[serde(skip_serializing_if = "Option::is_none")]
136 pub mimetype: Option<String>,
137
138 /// The size of the audio clip in bytes.
139 #[serde(skip_serializing_if = "Option::is_none")]
140 pub size: Option<UInt>,
141}
142
143impl AudioInfo {
144 /// Creates an empty `AudioInfo`.
145 pub fn new() -> Self {
146 Self::default()
147 }
148}
149
150/// Extensible event fallback data for audio messages, from the
151/// [first version of MSC3245][msc].
152///
153/// [msc]: https://github.com/matrix-org/matrix-spec-proposals/blob/83f6c5b469c1d78f714e335dcaa25354b255ffa5/proposals/3245-voice-messages.md
154#[cfg(feature = "unstable-msc3245-v1-compat")]
155#[derive(Clone, Debug, Deserialize, Serialize)]
156#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
157pub struct UnstableAudioDetailsContentBlock {
158 /// The duration of the audio in milliseconds.
159 ///
160 /// Note that the MSC says this should be in seconds but for compatibility with the Element
161 /// clients, this uses milliseconds.
162 #[serde(with = "ruma_common::serde::duration::ms")]
163 pub duration: Duration,
164
165 /// The waveform representation of the audio content, if any.
166 ///
167 /// This is optional and defaults to an empty array.
168 #[serde(default, skip_serializing_if = "Vec::is_empty")]
169 pub waveform: Vec<UnstableAmplitude>,
170}
171
172#[cfg(feature = "unstable-msc3245-v1-compat")]
173impl UnstableAudioDetailsContentBlock {
174 /// Creates a new `UnstableAudioDetailsContentBlock ` with the given duration and waveform.
175 pub fn new(duration: Duration, waveform: Vec<UnstableAmplitude>) -> Self {
176 Self { duration, waveform }
177 }
178}
179
180/// Extensible event fallback data for voice messages, from the
181/// [first version of MSC3245][msc].
182///
183/// [msc]: https://github.com/matrix-org/matrix-spec-proposals/blob/83f6c5b469c1d78f714e335dcaa25354b255ffa5/proposals/3245-voice-messages.md
184#[cfg(feature = "unstable-msc3245-v1-compat")]
185#[derive(Clone, Debug, Default, Deserialize, Serialize)]
186#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
187pub struct UnstableVoiceContentBlock {}
188
189#[cfg(feature = "unstable-msc3245-v1-compat")]
190impl UnstableVoiceContentBlock {
191 /// Creates a new `UnstableVoiceContentBlock`.
192 pub fn new() -> Self {
193 Self::default()
194 }
195}
196
197/// The unstable version of the amplitude of a waveform sample.
198///
199/// Must be an integer between 0 and 1024.
200#[cfg(feature = "unstable-msc3245-v1-compat")]
201#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)]
202pub struct UnstableAmplitude(UInt);
203
204#[cfg(feature = "unstable-msc3245-v1-compat")]
205impl UnstableAmplitude {
206 /// The smallest value that can be represented by this type, 0.
207 pub const MIN: u16 = 0;
208
209 /// The largest value that can be represented by this type, 1024.
210 pub const MAX: u16 = 1024;
211
212 /// Creates a new `UnstableAmplitude` with the given value.
213 ///
214 /// It will saturate if it is bigger than [`UnstableAmplitude::MAX`].
215 pub fn new(value: u16) -> Self {
216 Self(value.min(Self::MAX).into())
217 }
218
219 /// The value of this `UnstableAmplitude`.
220 pub fn get(&self) -> UInt {
221 self.0
222 }
223}
224
225#[cfg(feature = "unstable-msc3245-v1-compat")]
226impl From<u16> for UnstableAmplitude {
227 fn from(value: u16) -> Self {
228 Self::new(value)
229 }
230}
231
232#[cfg(feature = "unstable-msc3245-v1-compat")]
233impl<'de> Deserialize<'de> for UnstableAmplitude {
234 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
235 where
236 D: serde::Deserializer<'de>,
237 {
238 let uint = UInt::deserialize(deserializer)?;
239 Ok(Self(uint.min(Self::MAX.into())))
240 }
241}