atagen

macltok

public
Star0
Watch0
cbb715e119632f60c9b3ec1bc8803f1f33e39dce
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: src/normalise/media.rs

96fdf90aacd6atagen2026-09-08 08:24 UTC1
use std::sync::atomic::{AtomicU64, Ordering};
96fdf90aacd6atagen2026-09-08 08:24 UTC2
96fdf90aacd6atagen2026-09-08 08:24 UTC3
use matrix_sdk::{media::MediaEventContent, ruma::events::room::MediaSource};
96fdf90aacd6atagen2026-09-08 08:24 UTC4
use matrix_sdk_ui::timeline::{MsgLikeKind, TimelineItem as SdkTimelineItem, TimelineItemContent};
9bd34e25bb86atagen2026-10-24 07:23 UTC5
use ruma::{
9bd34e25bb86atagen2026-10-24 07:23 UTC6
    OwnedMxcUri,
9bd34e25bb86atagen2026-10-24 07:23 UTC7
    events::{
9bd34e25bb86atagen2026-10-24 07:23 UTC8
        room::{
9bd34e25bb86atagen2026-10-24 07:23 UTC9
            ImageInfo, ThumbnailInfo,
9bd34e25bb86atagen2026-10-24 07:23 UTC10
            message::{FileInfo, MessageType, VideoInfo},
9bd34e25bb86atagen2026-10-24 07:23 UTC11
        },
9bd34e25bb86atagen2026-10-24 07:23 UTC12
        sticker::{StickerEventContent, StickerMediaSource},
96fdf90aacd6atagen2026-09-08 08:24 UTC13
    },
96fdf90aacd6atagen2026-09-08 08:24 UTC14
};
96fdf90aacd6atagen2026-09-08 08:24 UTC15
0291516d40b5atagen2026-10-22 08:31 UTC16
use super::link_preview::sender_previews;
96fdf90aacd6atagen2026-09-08 08:24 UTC17
use crate::{MediaDescriptor, MediaId, MediaKind};
96fdf90aacd6atagen2026-09-08 08:24 UTC18
96fdf90aacd6atagen2026-09-08 08:24 UTC19
static NEXT_MEDIA_SCOPE: AtomicU64 = AtomicU64::new(1);
96fdf90aacd6atagen2026-09-08 08:24 UTC20
96fdf90aacd6atagen2026-09-08 08:24 UTC21
/// Per-timeline-session nonce namespacing recycled SDK unique IDs.
96fdf90aacd6atagen2026-09-08 08:24 UTC22
///
96fdf90aacd6atagen2026-09-08 08:24 UTC23
/// SDK 0.18 recycles [`SdkTimelineItem::unique_id`] across the local-to-remote
96fdf90aacd6atagen2026-09-08 08:24 UTC24
/// echo replacement, while transaction IDs disappear. Scoping the recycled ID
96fdf90aacd6atagen2026-09-08 08:24 UTC25
/// by a process-wide nonce keeps media IDs stable across replacement without
96fdf90aacd6atagen2026-09-08 08:24 UTC26
/// colliding across rooms, threads, or reopened sessions.
96fdf90aacd6atagen2026-09-08 08:24 UTC27
#[derive(Clone, Debug)]
96fdf90aacd6atagen2026-09-08 08:24 UTC28
pub(crate) struct MediaScope {
96fdf90aacd6atagen2026-09-08 08:24 UTC29
    nonce: u64,
96fdf90aacd6atagen2026-09-08 08:24 UTC30
}
96fdf90aacd6atagen2026-09-08 08:24 UTC31
96fdf90aacd6atagen2026-09-08 08:24 UTC32
impl MediaScope {
96fdf90aacd6atagen2026-09-08 08:24 UTC33
    pub(crate) fn new() -> Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC34
        Self {
96fdf90aacd6atagen2026-09-08 08:24 UTC35
            nonce: NEXT_MEDIA_SCOPE.fetch_add(1, Ordering::Relaxed),
96fdf90aacd6atagen2026-09-08 08:24 UTC36
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC37
    }
fa9114fcf75fatagen2026-09-24 12:51 UTC38
fa9114fcf75fatagen2026-09-24 12:51 UTC39
    pub(crate) fn owns(&self, id: &MediaId) -> bool {
fa9114fcf75fatagen2026-09-24 12:51 UTC40
        id.as_str()
fa9114fcf75fatagen2026-09-24 12:51 UTC41
            .split_once('#')
fa9114fcf75fatagen2026-09-24 12:51 UTC42
            .is_some_and(|(nonce, _)| nonce.parse() == Ok(self.nonce))
fa9114fcf75fatagen2026-09-24 12:51 UTC43
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC44
}
96fdf90aacd6atagen2026-09-08 08:24 UTC45
96fdf90aacd6atagen2026-09-08 08:24 UTC46
#[derive(Clone)]
cbb715e11963atagen2026-10-24 23:24 UTC47
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
96fdf90aacd6atagen2026-09-08 08:24 UTC48
pub(crate) struct MediaRegistration {
96fdf90aacd6atagen2026-09-08 08:24 UTC49
    pub(crate) id: MediaId,
96fdf90aacd6atagen2026-09-08 08:24 UTC50
    pub(crate) kind: MediaKind,
96fdf90aacd6atagen2026-09-08 08:24 UTC51
    pub(crate) full: MediaSourceInfo,
96fdf90aacd6atagen2026-09-08 08:24 UTC52
    pub(crate) thumbnail: Option<MediaSourceInfo>,
96fdf90aacd6atagen2026-09-08 08:24 UTC53
}
96fdf90aacd6atagen2026-09-08 08:24 UTC54
96fdf90aacd6atagen2026-09-08 08:24 UTC55
#[derive(Clone)]
cbb715e11963atagen2026-10-24 23:24 UTC56
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
96fdf90aacd6atagen2026-09-08 08:24 UTC57
pub(crate) struct MediaSourceInfo {
96fdf90aacd6atagen2026-09-08 08:24 UTC58
    pub(crate) source: MediaSource,
96fdf90aacd6atagen2026-09-08 08:24 UTC59
    pub(crate) mimetype: Option<String>,
96fdf90aacd6atagen2026-09-08 08:24 UTC60
    pub(crate) declared_size: Option<u64>,
96fdf90aacd6atagen2026-09-08 08:24 UTC61
}
96fdf90aacd6atagen2026-09-08 08:24 UTC62
0291516d40b5atagen2026-10-22 08:31 UTC63
pub(super) fn media_registrations_for_item(
96fdf90aacd6atagen2026-09-08 08:24 UTC64
    item: &SdkTimelineItem,
96fdf90aacd6atagen2026-09-08 08:24 UTC65
    scope: &MediaScope,
0291516d40b5atagen2026-10-22 08:31 UTC66
) -> Vec<MediaRegistration> {
0291516d40b5atagen2026-10-22 08:31 UTC67
    let Some(event) = item.as_event() else {
0291516d40b5atagen2026-10-22 08:31 UTC68
        return Vec::new();
0291516d40b5atagen2026-10-22 08:31 UTC69
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC70
    let identity = media_identity(item, scope);
0291516d40b5atagen2026-10-22 08:31 UTC71
    let TimelineItemContent::MsgLike(content) = event.content() else {
0291516d40b5atagen2026-10-22 08:31 UTC72
        return Vec::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC73
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC74
    match &content.kind {
0291516d40b5atagen2026-10-22 08:31 UTC75
        MsgLikeKind::Message(message) => media_registration(&identity, message.msgtype())
0291516d40b5atagen2026-10-22 08:31 UTC76
            .into_iter()
0291516d40b5atagen2026-10-22 08:31 UTC77
            .chain(
0291516d40b5atagen2026-10-22 08:31 UTC78
                sender_previews(Some(&identity), message.msgtype())
0291516d40b5atagen2026-10-22 08:31 UTC79
                    .into_iter()
0291516d40b5atagen2026-10-22 08:31 UTC80
                    .filter_map(|(_, registration)| registration),
0291516d40b5atagen2026-10-22 08:31 UTC81
            )
0291516d40b5atagen2026-10-22 08:31 UTC82
            .collect(),
0291516d40b5atagen2026-10-22 08:31 UTC83
        MsgLikeKind::Sticker(sticker) => media_sticker_registration(&identity, sticker.content())
0291516d40b5atagen2026-10-22 08:31 UTC84
            .into_iter()
0291516d40b5atagen2026-10-22 08:31 UTC85
            .collect(),
0291516d40b5atagen2026-10-22 08:31 UTC86
        _ => Vec::new(),
96fdf90aacd6atagen2026-09-08 08:24 UTC87
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC88
}
96fdf90aacd6atagen2026-09-08 08:24 UTC89
96fdf90aacd6atagen2026-09-08 08:24 UTC90
pub(super) fn media_identity(item: &SdkTimelineItem, scope: &MediaScope) -> String {
96fdf90aacd6atagen2026-09-08 08:24 UTC91
    format!("{}#{}", scope.nonce, item.unique_id().0)
96fdf90aacd6atagen2026-09-08 08:24 UTC92
}
96fdf90aacd6atagen2026-09-08 08:24 UTC93
96fdf90aacd6atagen2026-09-08 08:24 UTC94
pub(super) fn media_from_message(identity: &str, message: &MessageType) -> Option<MediaDescriptor> {
9bd34e25bb86atagen2026-10-24 07:23 UTC95
    let descriptor =
9bd34e25bb86atagen2026-10-24 07:23 UTC96
        |kind, source: &MediaSource, filename: &str, caption: Option<&str>| MediaDescriptor {
9bd34e25bb86atagen2026-10-24 07:23 UTC97
            id: media_id(identity, "media"),
9bd34e25bb86atagen2026-10-24 07:23 UTC98
            kind,
9bd34e25bb86atagen2026-10-24 07:23 UTC99
            mxc: plain_mxc(source),
9bd34e25bb86atagen2026-10-24 07:23 UTC100
            filename: filename.to_owned(),
9bd34e25bb86atagen2026-10-24 07:23 UTC101
            caption: caption.map(str::to_owned),
9bd34e25bb86atagen2026-10-24 07:23 UTC102
            mimetype: None,
9bd34e25bb86atagen2026-10-24 07:23 UTC103
            size_bytes: None,
9bd34e25bb86atagen2026-10-24 07:23 UTC104
            width: None,
9bd34e25bb86atagen2026-10-24 07:23 UTC105
            height: None,
9bd34e25bb86atagen2026-10-24 07:23 UTC106
            duration_ms: None,
9bd34e25bb86atagen2026-10-24 07:23 UTC107
            blurhash: None,
9bd34e25bb86atagen2026-10-24 07:23 UTC108
            has_thumbnail: false,
9bd34e25bb86atagen2026-10-24 07:23 UTC109
        };
5a52012c9b1datagen2026-09-24 23:45 UTC110
    let millis = |duration: std::time::Duration| u64::try_from(duration.as_millis()).ok();
5a52012c9b1datagen2026-09-24 23:45 UTC111
    Some(match message {
5a52012c9b1datagen2026-09-24 23:45 UTC112
        MessageType::Image(content) => {
5a52012c9b1datagen2026-09-24 23:45 UTC113
            let info = content.info.as_deref();
5a52012c9b1datagen2026-09-24 23:45 UTC114
            MediaDescriptor {
5a52012c9b1datagen2026-09-24 23:45 UTC115
                mimetype: info.and_then(|info| info.mimetype.clone()),
5a52012c9b1datagen2026-09-24 23:45 UTC116
                size_bytes: info.and_then(|info| info.size).map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC117
                width: info.and_then(|info| info.width).map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC118
                height: info.and_then(|info| info.height).map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC119
                blurhash: info.and_then(|info| info.blurhash.clone()),
5a52012c9b1datagen2026-09-24 23:45 UTC120
                has_thumbnail: info.is_some_and(|info| info.thumbnail_source.is_some()),
9bd34e25bb86atagen2026-10-24 07:23 UTC121
                ..descriptor(
9bd34e25bb86atagen2026-10-24 07:23 UTC122
                    MediaKind::Image,
9bd34e25bb86atagen2026-10-24 07:23 UTC123
                    &content.source,
9bd34e25bb86atagen2026-10-24 07:23 UTC124
                    content.filename(),
9bd34e25bb86atagen2026-10-24 07:23 UTC125
                    content.caption(),
9bd34e25bb86atagen2026-10-24 07:23 UTC126
                )
5a52012c9b1datagen2026-09-24 23:45 UTC127
            }
5a52012c9b1datagen2026-09-24 23:45 UTC128
        }
5a52012c9b1datagen2026-09-24 23:45 UTC129
        MessageType::Video(content) => {
5a52012c9b1datagen2026-09-24 23:45 UTC130
            let info = content.info.as_deref();
5a52012c9b1datagen2026-09-24 23:45 UTC131
            MediaDescriptor {
5a52012c9b1datagen2026-09-24 23:45 UTC132
                mimetype: info.and_then(|info| info.mimetype.clone()),
5a52012c9b1datagen2026-09-24 23:45 UTC133
                size_bytes: info.and_then(|info| info.size).map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC134
                width: info.and_then(|info| info.width).map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC135
                height: info.and_then(|info| info.height).map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC136
                duration_ms: info.and_then(|info| info.duration).and_then(millis),
5a52012c9b1datagen2026-09-24 23:45 UTC137
                blurhash: info.and_then(|info| info.blurhash.clone()),
5a52012c9b1datagen2026-09-24 23:45 UTC138
                has_thumbnail: info.is_some_and(|info| info.thumbnail_source.is_some()),
9bd34e25bb86atagen2026-10-24 07:23 UTC139
                ..descriptor(
9bd34e25bb86atagen2026-10-24 07:23 UTC140
                    MediaKind::Video,
9bd34e25bb86atagen2026-10-24 07:23 UTC141
                    &content.source,
9bd34e25bb86atagen2026-10-24 07:23 UTC142
                    content.filename(),
9bd34e25bb86atagen2026-10-24 07:23 UTC143
                    content.caption(),
9bd34e25bb86atagen2026-10-24 07:23 UTC144
                )
5a52012c9b1datagen2026-09-24 23:45 UTC145
            }
5a52012c9b1datagen2026-09-24 23:45 UTC146
        }
5a52012c9b1datagen2026-09-24 23:45 UTC147
        MessageType::Audio(content) => {
5a52012c9b1datagen2026-09-24 23:45 UTC148
            let info = content.info.as_deref();
5a52012c9b1datagen2026-09-24 23:45 UTC149
            MediaDescriptor {
5a52012c9b1datagen2026-09-24 23:45 UTC150
                mimetype: info.and_then(|info| info.mimetype.clone()),
5a52012c9b1datagen2026-09-24 23:45 UTC151
                size_bytes: info.and_then(|info| info.size).map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC152
                duration_ms: info.and_then(|info| info.duration).and_then(millis),
9bd34e25bb86atagen2026-10-24 07:23 UTC153
                ..descriptor(
9bd34e25bb86atagen2026-10-24 07:23 UTC154
                    MediaKind::Audio,
9bd34e25bb86atagen2026-10-24 07:23 UTC155
                    &content.source,
9bd34e25bb86atagen2026-10-24 07:23 UTC156
                    content.filename(),
9bd34e25bb86atagen2026-10-24 07:23 UTC157
                    content.caption(),
9bd34e25bb86atagen2026-10-24 07:23 UTC158
                )
5a52012c9b1datagen2026-09-24 23:45 UTC159
            }
5a52012c9b1datagen2026-09-24 23:45 UTC160
        }
5a52012c9b1datagen2026-09-24 23:45 UTC161
        MessageType::File(content) => {
5a52012c9b1datagen2026-09-24 23:45 UTC162
            let info = content.info.as_deref();
5a52012c9b1datagen2026-09-24 23:45 UTC163
            MediaDescriptor {
5a52012c9b1datagen2026-09-24 23:45 UTC164
                mimetype: info.and_then(|info| info.mimetype.clone()),
5a52012c9b1datagen2026-09-24 23:45 UTC165
                size_bytes: info.and_then(|info| info.size).map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC166
                has_thumbnail: info.is_some_and(|info| info.thumbnail_source.is_some()),
9bd34e25bb86atagen2026-10-24 07:23 UTC167
                ..descriptor(
9bd34e25bb86atagen2026-10-24 07:23 UTC168
                    MediaKind::File,
9bd34e25bb86atagen2026-10-24 07:23 UTC169
                    &content.source,
9bd34e25bb86atagen2026-10-24 07:23 UTC170
                    content.filename(),
9bd34e25bb86atagen2026-10-24 07:23 UTC171
                    content.caption(),
9bd34e25bb86atagen2026-10-24 07:23 UTC172
                )
5a52012c9b1datagen2026-09-24 23:45 UTC173
            }
5a52012c9b1datagen2026-09-24 23:45 UTC174
        }
5a52012c9b1datagen2026-09-24 23:45 UTC175
        _ => return None,
96fdf90aacd6atagen2026-09-08 08:24 UTC176
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC177
}
96fdf90aacd6atagen2026-09-08 08:24 UTC178
96fdf90aacd6atagen2026-09-08 08:24 UTC179
pub(super) fn media_descriptor_for_sticker(
96fdf90aacd6atagen2026-09-08 08:24 UTC180
    identity: &str,
96fdf90aacd6atagen2026-09-08 08:24 UTC181
    content: &StickerEventContent,
96fdf90aacd6atagen2026-09-08 08:24 UTC182
) -> MediaDescriptor {
96fdf90aacd6atagen2026-09-08 08:24 UTC183
    MediaDescriptor {
96fdf90aacd6atagen2026-09-08 08:24 UTC184
        id: media_id(identity, "sticker"),
96fdf90aacd6atagen2026-09-08 08:24 UTC185
        kind: MediaKind::Sticker,
9bd34e25bb86atagen2026-10-24 07:23 UTC186
        mxc: match &content.source {
9bd34e25bb86atagen2026-10-24 07:23 UTC187
            StickerMediaSource::Plain(uri) => Some(uri.clone()),
9bd34e25bb86atagen2026-10-24 07:23 UTC188
            _ => None,
9bd34e25bb86atagen2026-10-24 07:23 UTC189
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC190
        filename: content.body.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC191
        caption: None,
96fdf90aacd6atagen2026-09-08 08:24 UTC192
        mimetype: content.info.mimetype.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC193
        size_bytes: content.info.size.map(u64::from),
96fdf90aacd6atagen2026-09-08 08:24 UTC194
        width: content.info.width.map(u64::from),
96fdf90aacd6atagen2026-09-08 08:24 UTC195
        height: content.info.height.map(u64::from),
5a52012c9b1datagen2026-09-24 23:45 UTC196
        duration_ms: None,
5a52012c9b1datagen2026-09-24 23:45 UTC197
        blurhash: content.info.blurhash.clone(),
5a52012c9b1datagen2026-09-24 23:45 UTC198
        has_thumbnail: content.info.thumbnail_source.is_some(),
96fdf90aacd6atagen2026-09-08 08:24 UTC199
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC200
}
96fdf90aacd6atagen2026-09-08 08:24 UTC201
9bd34e25bb86atagen2026-10-24 07:23 UTC202
fn plain_mxc(source: &MediaSource) -> Option<OwnedMxcUri> {
9bd34e25bb86atagen2026-10-24 07:23 UTC203
    match source {
9bd34e25bb86atagen2026-10-24 07:23 UTC204
        MediaSource::Plain(uri) => Some(uri.clone()),
9bd34e25bb86atagen2026-10-24 07:23 UTC205
        MediaSource::Encrypted(_) => None,
9bd34e25bb86atagen2026-10-24 07:23 UTC206
    }
9bd34e25bb86atagen2026-10-24 07:23 UTC207
}
9bd34e25bb86atagen2026-10-24 07:23 UTC208
96fdf90aacd6atagen2026-09-08 08:24 UTC209
pub(super) fn media_id(identity: &str, suffix: &str) -> MediaId {
96fdf90aacd6atagen2026-09-08 08:24 UTC210
    MediaId::new(format!("{identity}#{suffix}"))
96fdf90aacd6atagen2026-09-08 08:24 UTC211
}
96fdf90aacd6atagen2026-09-08 08:24 UTC212
cbb715e11963atagen2026-10-24 23:24 UTC213
pub(super) fn media_sticker_registration(
96fdf90aacd6atagen2026-09-08 08:24 UTC214
    identity: &str,
96fdf90aacd6atagen2026-09-08 08:24 UTC215
    content: &StickerEventContent,
96fdf90aacd6atagen2026-09-08 08:24 UTC216
) -> Option<MediaRegistration> {
96fdf90aacd6atagen2026-09-08 08:24 UTC217
    Some(MediaRegistration {
96fdf90aacd6atagen2026-09-08 08:24 UTC218
        id: media_id(identity, "sticker"),
96fdf90aacd6atagen2026-09-08 08:24 UTC219
        kind: MediaKind::Sticker,
96fdf90aacd6atagen2026-09-08 08:24 UTC220
        full: MediaSourceInfo {
96fdf90aacd6atagen2026-09-08 08:24 UTC221
            source: content.source()?,
96fdf90aacd6atagen2026-09-08 08:24 UTC222
            mimetype: content.info.mimetype.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC223
            declared_size: content.info.size.map(u64::from),
96fdf90aacd6atagen2026-09-08 08:24 UTC224
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC225
        thumbnail: thumbnail_media_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC226
            content.info.thumbnail_source.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC227
            content.info.thumbnail_info.as_deref(),
96fdf90aacd6atagen2026-09-08 08:24 UTC228
        ),
96fdf90aacd6atagen2026-09-08 08:24 UTC229
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC230
}
96fdf90aacd6atagen2026-09-08 08:24 UTC231
cbb715e11963atagen2026-10-24 23:24 UTC232
pub(super) fn media_registration(
cbb715e11963atagen2026-10-24 23:24 UTC233
    identity: &str,
cbb715e11963atagen2026-10-24 23:24 UTC234
    message: &MessageType,
cbb715e11963atagen2026-10-24 23:24 UTC235
) -> Option<MediaRegistration> {
96fdf90aacd6atagen2026-09-08 08:24 UTC236
    match message {
96fdf90aacd6atagen2026-09-08 08:24 UTC237
        MessageType::Image(content) => media_registration_with_info(
96fdf90aacd6atagen2026-09-08 08:24 UTC238
            identity,
96fdf90aacd6atagen2026-09-08 08:24 UTC239
            MediaKind::Image,
96fdf90aacd6atagen2026-09-08 08:24 UTC240
            content.source(),
96fdf90aacd6atagen2026-09-08 08:24 UTC241
            content.info.as_deref(),
96fdf90aacd6atagen2026-09-08 08:24 UTC242
        ),
96fdf90aacd6atagen2026-09-08 08:24 UTC243
        MessageType::Video(content) => media_registration_with_info(
96fdf90aacd6atagen2026-09-08 08:24 UTC244
            identity,
96fdf90aacd6atagen2026-09-08 08:24 UTC245
            MediaKind::Video,
96fdf90aacd6atagen2026-09-08 08:24 UTC246
            content.source(),
96fdf90aacd6atagen2026-09-08 08:24 UTC247
            content.info.as_deref(),
96fdf90aacd6atagen2026-09-08 08:24 UTC248
        ),
96fdf90aacd6atagen2026-09-08 08:24 UTC249
        MessageType::File(content) => media_registration_with_info(
96fdf90aacd6atagen2026-09-08 08:24 UTC250
            identity,
96fdf90aacd6atagen2026-09-08 08:24 UTC251
            MediaKind::File,
96fdf90aacd6atagen2026-09-08 08:24 UTC252
            content.source(),
96fdf90aacd6atagen2026-09-08 08:24 UTC253
            content.info.as_deref(),
96fdf90aacd6atagen2026-09-08 08:24 UTC254
        ),
96fdf90aacd6atagen2026-09-08 08:24 UTC255
        MessageType::Audio(content) => media_registration_with_parts(
96fdf90aacd6atagen2026-09-08 08:24 UTC256
            identity,
96fdf90aacd6atagen2026-09-08 08:24 UTC257
            MediaKind::Audio,
96fdf90aacd6atagen2026-09-08 08:24 UTC258
            content.source(),
96fdf90aacd6atagen2026-09-08 08:24 UTC259
            content.info.as_ref().and_then(|info| info.mimetype.clone()),
96fdf90aacd6atagen2026-09-08 08:24 UTC260
            content
96fdf90aacd6atagen2026-09-08 08:24 UTC261
                .info
96fdf90aacd6atagen2026-09-08 08:24 UTC262
                .as_ref()
96fdf90aacd6atagen2026-09-08 08:24 UTC263
                .and_then(|info| info.size)
96fdf90aacd6atagen2026-09-08 08:24 UTC264
                .map(u64::from),
96fdf90aacd6atagen2026-09-08 08:24 UTC265
            None,
96fdf90aacd6atagen2026-09-08 08:24 UTC266
        ),
96fdf90aacd6atagen2026-09-08 08:24 UTC267
        _ => None,
96fdf90aacd6atagen2026-09-08 08:24 UTC268
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC269
}
96fdf90aacd6atagen2026-09-08 08:24 UTC270
96fdf90aacd6atagen2026-09-08 08:24 UTC271
fn media_registration_with_info<T: MediaInfo>(
96fdf90aacd6atagen2026-09-08 08:24 UTC272
    identity: &str,
96fdf90aacd6atagen2026-09-08 08:24 UTC273
    kind: MediaKind,
96fdf90aacd6atagen2026-09-08 08:24 UTC274
    source: Option<MediaSource>,
96fdf90aacd6atagen2026-09-08 08:24 UTC275
    info: Option<&T>,
96fdf90aacd6atagen2026-09-08 08:24 UTC276
) -> Option<MediaRegistration> {
96fdf90aacd6atagen2026-09-08 08:24 UTC277
    media_registration_with_parts(
96fdf90aacd6atagen2026-09-08 08:24 UTC278
        identity,
96fdf90aacd6atagen2026-09-08 08:24 UTC279
        kind,
96fdf90aacd6atagen2026-09-08 08:24 UTC280
        source,
96fdf90aacd6atagen2026-09-08 08:24 UTC281
        info.and_then(MediaInfo::mimetype),
96fdf90aacd6atagen2026-09-08 08:24 UTC282
        info.and_then(MediaInfo::declared_size),
96fdf90aacd6atagen2026-09-08 08:24 UTC283
        info.and_then(MediaInfo::thumbnail),
96fdf90aacd6atagen2026-09-08 08:24 UTC284
    )
96fdf90aacd6atagen2026-09-08 08:24 UTC285
}
96fdf90aacd6atagen2026-09-08 08:24 UTC286
96fdf90aacd6atagen2026-09-08 08:24 UTC287
fn media_registration_with_parts(
96fdf90aacd6atagen2026-09-08 08:24 UTC288
    identity: &str,
96fdf90aacd6atagen2026-09-08 08:24 UTC289
    kind: MediaKind,
96fdf90aacd6atagen2026-09-08 08:24 UTC290
    source: Option<MediaSource>,
96fdf90aacd6atagen2026-09-08 08:24 UTC291
    mimetype: Option<String>,
96fdf90aacd6atagen2026-09-08 08:24 UTC292
    declared_size: Option<u64>,
96fdf90aacd6atagen2026-09-08 08:24 UTC293
    thumbnail: Option<MediaSourceInfo>,
96fdf90aacd6atagen2026-09-08 08:24 UTC294
) -> Option<MediaRegistration> {
96fdf90aacd6atagen2026-09-08 08:24 UTC295
    Some(MediaRegistration {
96fdf90aacd6atagen2026-09-08 08:24 UTC296
        id: media_id(identity, "media"),
96fdf90aacd6atagen2026-09-08 08:24 UTC297
        kind,
96fdf90aacd6atagen2026-09-08 08:24 UTC298
        full: MediaSourceInfo {
96fdf90aacd6atagen2026-09-08 08:24 UTC299
            source: source?,
96fdf90aacd6atagen2026-09-08 08:24 UTC300
            mimetype,
96fdf90aacd6atagen2026-09-08 08:24 UTC301
            declared_size,
96fdf90aacd6atagen2026-09-08 08:24 UTC302
        },
96fdf90aacd6atagen2026-09-08 08:24 UTC303
        thumbnail,
96fdf90aacd6atagen2026-09-08 08:24 UTC304
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC305
}
96fdf90aacd6atagen2026-09-08 08:24 UTC306
96fdf90aacd6atagen2026-09-08 08:24 UTC307
trait MediaInfo {
96fdf90aacd6atagen2026-09-08 08:24 UTC308
    fn mimetype(&self) -> Option<String>;
96fdf90aacd6atagen2026-09-08 08:24 UTC309
    fn declared_size(&self) -> Option<u64>;
96fdf90aacd6atagen2026-09-08 08:24 UTC310
    fn thumbnail(&self) -> Option<MediaSourceInfo>;
96fdf90aacd6atagen2026-09-08 08:24 UTC311
}
96fdf90aacd6atagen2026-09-08 08:24 UTC312
96fdf90aacd6atagen2026-09-08 08:24 UTC313
macro_rules! impl_media_info {
96fdf90aacd6atagen2026-09-08 08:24 UTC314
    ($type:ty) => {
96fdf90aacd6atagen2026-09-08 08:24 UTC315
        impl MediaInfo for $type {
96fdf90aacd6atagen2026-09-08 08:24 UTC316
            fn mimetype(&self) -> Option<String> {
96fdf90aacd6atagen2026-09-08 08:24 UTC317
                self.mimetype.clone()
96fdf90aacd6atagen2026-09-08 08:24 UTC318
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC319
96fdf90aacd6atagen2026-09-08 08:24 UTC320
            fn declared_size(&self) -> Option<u64> {
96fdf90aacd6atagen2026-09-08 08:24 UTC321
                self.size.map(u64::from)
96fdf90aacd6atagen2026-09-08 08:24 UTC322
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC323
96fdf90aacd6atagen2026-09-08 08:24 UTC324
            fn thumbnail(&self) -> Option<MediaSourceInfo> {
96fdf90aacd6atagen2026-09-08 08:24 UTC325
                thumbnail_media_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC326
                    self.thumbnail_source.clone(),
96fdf90aacd6atagen2026-09-08 08:24 UTC327
                    self.thumbnail_info.as_deref(),
96fdf90aacd6atagen2026-09-08 08:24 UTC328
                )
96fdf90aacd6atagen2026-09-08 08:24 UTC329
            }
96fdf90aacd6atagen2026-09-08 08:24 UTC330
        }
96fdf90aacd6atagen2026-09-08 08:24 UTC331
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC332
}
96fdf90aacd6atagen2026-09-08 08:24 UTC333
96fdf90aacd6atagen2026-09-08 08:24 UTC334
impl_media_info!(ImageInfo);
96fdf90aacd6atagen2026-09-08 08:24 UTC335
impl_media_info!(VideoInfo);
96fdf90aacd6atagen2026-09-08 08:24 UTC336
impl_media_info!(FileInfo);
96fdf90aacd6atagen2026-09-08 08:24 UTC337
96fdf90aacd6atagen2026-09-08 08:24 UTC338
fn thumbnail_media_source(
96fdf90aacd6atagen2026-09-08 08:24 UTC339
    source: Option<MediaSource>,
96fdf90aacd6atagen2026-09-08 08:24 UTC340
    info: Option<&ThumbnailInfo>,
96fdf90aacd6atagen2026-09-08 08:24 UTC341
) -> Option<MediaSourceInfo> {
96fdf90aacd6atagen2026-09-08 08:24 UTC342
    source.map(|source| MediaSourceInfo {
96fdf90aacd6atagen2026-09-08 08:24 UTC343
        source,
96fdf90aacd6atagen2026-09-08 08:24 UTC344
        mimetype: info.and_then(|info| info.mimetype.clone()),
96fdf90aacd6atagen2026-09-08 08:24 UTC345
        declared_size: info.and_then(|info| info.size).map(u64::from),
96fdf90aacd6atagen2026-09-08 08:24 UTC346
    })
96fdf90aacd6atagen2026-09-08 08:24 UTC347
}
96fdf90aacd6atagen2026-09-08 08:24 UTC348
96fdf90aacd6atagen2026-09-08 08:24 UTC349
#[cfg(test)]
96fdf90aacd6atagen2026-09-08 08:24 UTC350
mod tests {
96fdf90aacd6atagen2026-09-08 08:24 UTC351
    use matrix_sdk::ruma::{
96fdf90aacd6atagen2026-09-08 08:24 UTC352
        events::room::{ImageInfo, message::ImageMessageEventContent},
96fdf90aacd6atagen2026-09-08 08:24 UTC353
        mxc_uri,
96fdf90aacd6atagen2026-09-08 08:24 UTC354
    };
96fdf90aacd6atagen2026-09-08 08:24 UTC355
96fdf90aacd6atagen2026-09-08 08:24 UTC356
    use super::*;
96fdf90aacd6atagen2026-09-08 08:24 UTC357
96fdf90aacd6atagen2026-09-08 08:24 UTC358
    #[test]
96fdf90aacd6atagen2026-09-08 08:24 UTC359
    fn image_messages_keep_media_metadata() {
96fdf90aacd6atagen2026-09-08 08:24 UTC360
        let mut info = ImageInfo::new();
96fdf90aacd6atagen2026-09-08 08:24 UTC361
        info.mimetype = Some("image/png".to_owned());
96fdf90aacd6atagen2026-09-08 08:24 UTC362
        info.size = Some(65_536_u32.into());
96fdf90aacd6atagen2026-09-08 08:24 UTC363
        info.width = Some(640_u32.into());
96fdf90aacd6atagen2026-09-08 08:24 UTC364
        info.height = Some(480_u32.into());
96fdf90aacd6atagen2026-09-08 08:24 UTC365
        let content = ImageMessageEventContent::plain(
96fdf90aacd6atagen2026-09-08 08:24 UTC366
            "screen.png".to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC367
            mxc_uri!("mxc://example.org/screen").to_owned(),
96fdf90aacd6atagen2026-09-08 08:24 UTC368
        )
96fdf90aacd6atagen2026-09-08 08:24 UTC369
        .info(Box::new(info));
96fdf90aacd6atagen2026-09-08 08:24 UTC370
96fdf90aacd6atagen2026-09-08 08:24 UTC371
        let media = media_from_message("$event:example.org", &MessageType::Image(content)).unwrap();
96fdf90aacd6atagen2026-09-08 08:24 UTC372
        assert_eq!(media.id.as_str(), "$event:example.org#media");
96fdf90aacd6atagen2026-09-08 08:24 UTC373
        assert_eq!(media.kind, MediaKind::Image);
96fdf90aacd6atagen2026-09-08 08:24 UTC374
        assert_eq!(media.filename, "screen.png");
96fdf90aacd6atagen2026-09-08 08:24 UTC375
        assert_eq!(media.caption, None);
96fdf90aacd6atagen2026-09-08 08:24 UTC376
        assert_eq!(media.mimetype.as_deref(), Some("image/png"));
96fdf90aacd6atagen2026-09-08 08:24 UTC377
        assert_eq!(media.size_bytes, Some(65_536));
96fdf90aacd6atagen2026-09-08 08:24 UTC378
        assert_eq!((media.width, media.height), (Some(640), Some(480)));
96fdf90aacd6atagen2026-09-08 08:24 UTC379
    }
96fdf90aacd6atagen2026-09-08 08:24 UTC380
}