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}