atagen

macltok

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