atagen

macltok

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