matrix_sdk_base/
media.rs

1//! Common types for [media content](https://matrix.org/docs/spec/client_server/r0.6.1#id66).
2
3use ruma::{
4    api::client::media::get_content_thumbnail::v3::Method,
5    events::{
6        room::{
7            message::{
8                AudioMessageEventContent, FileMessageEventContent, ImageMessageEventContent,
9                LocationMessageEventContent, VideoMessageEventContent,
10            },
11            MediaSource,
12        },
13        sticker::StickerEventContent,
14    },
15    MxcUri, UInt,
16};
17use serde::{Deserialize, Serialize};
18
19const UNIQUE_SEPARATOR: &str = "_";
20
21/// A trait to uniquely identify values of the same type.
22pub trait UniqueKey {
23    /// A string that uniquely identifies `Self` compared to other values of
24    /// the same type.
25    fn unique_key(&self) -> String;
26}
27
28/// The requested format of a media file.
29#[derive(Clone, Debug, Serialize, Deserialize)]
30pub enum MediaFormat {
31    /// The file that was uploaded.
32    File,
33
34    /// A thumbnail of the file that was uploaded.
35    Thumbnail(MediaThumbnailSettings),
36}
37
38impl UniqueKey for MediaFormat {
39    fn unique_key(&self) -> String {
40        match self {
41            Self::File => "file".into(),
42            Self::Thumbnail(settings) => settings.unique_key(),
43        }
44    }
45}
46
47/// The desired settings of a media thumbnail.
48#[derive(Clone, Debug, Serialize, Deserialize)]
49pub struct MediaThumbnailSettings {
50    /// The desired resizing method.
51    pub method: Method,
52
53    /// The desired width of the thumbnail. The actual thumbnail may not match
54    /// the size specified.
55    pub width: UInt,
56
57    /// The desired height of the thumbnail. The actual thumbnail may not match
58    /// the size specified.
59    pub height: UInt,
60
61    /// If we want to request an animated thumbnail from the homeserver.
62    ///
63    /// If it is `true`, the server should return an animated thumbnail if
64    /// the media supports it.
65    ///
66    /// Defaults to `false`.
67    pub animated: bool,
68}
69
70impl MediaThumbnailSettings {
71    /// Constructs a new `MediaThumbnailSettings` with the given method, width
72    /// and height.
73    ///
74    /// Requests a non-animated thumbnail by default.
75    pub fn with_method(method: Method, width: UInt, height: UInt) -> Self {
76        Self { method, width, height, animated: false }
77    }
78
79    /// Constructs a new `MediaThumbnailSettings` with the given width and
80    /// height.
81    ///
82    /// Requests scaling, and a non-animated thumbnail.
83    pub fn new(width: UInt, height: UInt) -> Self {
84        Self { method: Method::Scale, width, height, animated: false }
85    }
86}
87
88impl UniqueKey for MediaThumbnailSettings {
89    fn unique_key(&self) -> String {
90        let mut key = format!("{}{UNIQUE_SEPARATOR}{}x{}", self.method, self.width, self.height);
91
92        if self.animated {
93            key.push_str(UNIQUE_SEPARATOR);
94            key.push_str("animated");
95        }
96
97        key
98    }
99}
100
101impl UniqueKey for MediaSource {
102    fn unique_key(&self) -> String {
103        match self {
104            Self::Plain(uri) => uri.to_string(),
105            Self::Encrypted(file) => file.url.to_string(),
106        }
107    }
108}
109
110/// Parameters for a request for retrieve media data.
111///
112/// This is used as a key in the media cache too.
113#[derive(Clone, Debug, Serialize, Deserialize)]
114pub struct MediaRequestParameters {
115    /// The source of the media file.
116    pub source: MediaSource,
117
118    /// The requested format of the media data.
119    pub format: MediaFormat,
120}
121
122impl MediaRequestParameters {
123    /// Get the [`MxcUri`] from `Self`.
124    pub fn uri(&self) -> &MxcUri {
125        match &self.source {
126            MediaSource::Plain(url) => url.as_ref(),
127            MediaSource::Encrypted(file) => file.url.as_ref(),
128        }
129    }
130}
131
132impl UniqueKey for MediaRequestParameters {
133    fn unique_key(&self) -> String {
134        format!("{}{UNIQUE_SEPARATOR}{}", self.source.unique_key(), self.format.unique_key())
135    }
136}
137
138/// Trait for media event content.
139pub trait MediaEventContent {
140    /// Get the source of the file for `Self`.
141    ///
142    /// Returns `None` if `Self` has no file.
143    fn source(&self) -> Option<MediaSource>;
144
145    /// Get the source of the thumbnail for `Self`.
146    ///
147    /// Returns `None` if `Self` has no thumbnail.
148    fn thumbnail_source(&self) -> Option<MediaSource>;
149}
150
151impl MediaEventContent for StickerEventContent {
152    fn source(&self) -> Option<MediaSource> {
153        Some(MediaSource::from(self.source.clone()))
154    }
155
156    fn thumbnail_source(&self) -> Option<MediaSource> {
157        None
158    }
159}
160
161impl MediaEventContent for AudioMessageEventContent {
162    fn source(&self) -> Option<MediaSource> {
163        Some(self.source.clone())
164    }
165
166    fn thumbnail_source(&self) -> Option<MediaSource> {
167        None
168    }
169}
170
171impl MediaEventContent for FileMessageEventContent {
172    fn source(&self) -> Option<MediaSource> {
173        Some(self.source.clone())
174    }
175
176    fn thumbnail_source(&self) -> Option<MediaSource> {
177        self.info.as_ref()?.thumbnail_source.clone()
178    }
179}
180
181impl MediaEventContent for ImageMessageEventContent {
182    fn source(&self) -> Option<MediaSource> {
183        Some(self.source.clone())
184    }
185
186    fn thumbnail_source(&self) -> Option<MediaSource> {
187        self.info
188            .as_ref()
189            .and_then(|info| info.thumbnail_source.clone())
190            .or_else(|| Some(self.source.clone()))
191    }
192}
193
194impl MediaEventContent for VideoMessageEventContent {
195    fn source(&self) -> Option<MediaSource> {
196        Some(self.source.clone())
197    }
198
199    fn thumbnail_source(&self) -> Option<MediaSource> {
200        self.info
201            .as_ref()
202            .and_then(|info| info.thumbnail_source.clone())
203            .or_else(|| Some(self.source.clone()))
204    }
205}
206
207impl MediaEventContent for LocationMessageEventContent {
208    fn source(&self) -> Option<MediaSource> {
209        None
210    }
211
212    fn thumbnail_source(&self) -> Option<MediaSource> {
213        self.info.as_ref()?.thumbnail_source.clone()
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use ruma::mxc_uri;
220    use serde_json::json;
221
222    use super::*;
223
224    #[test]
225    fn test_media_request_url() {
226        let mxc_uri = mxc_uri!("mxc://homeserver/media");
227
228        let plain = MediaRequestParameters {
229            source: MediaSource::Plain(mxc_uri.to_owned()),
230            format: MediaFormat::File,
231        };
232
233        assert_eq!(plain.uri(), mxc_uri);
234
235        let file = MediaRequestParameters {
236            source: MediaSource::Encrypted(Box::new(
237                serde_json::from_value(json!({
238                    "url": mxc_uri,
239                    "key": {
240                        "kty": "oct",
241                        "key_ops": ["encrypt", "decrypt"],
242                        "alg": "A256CTR",
243                        "k": "b50ACIv6LMn9AfMCFD1POJI_UAFWIclxAN1kWrEO2X8",
244                        "ext": true,
245                    },
246                    "iv": "AK1wyzigZtQAAAABAAAAKK",
247                    "hashes": {
248                        "sha256": "foobar",
249                    },
250                    "v": "v2",
251                }))
252                .unwrap(),
253            )),
254            format: MediaFormat::File,
255        };
256
257        assert_eq!(file.uri(), mxc_uri);
258    }
259}