#[cfg(feature = "image-proc")]
use std::io::{BufRead, Cursor, Seek};
use std::time::Duration;
#[cfg(feature = "image-proc")]
use image::GenericImageView;
use ruma::{
assign,
events::room::{
message::{AudioInfo, FileInfo, VideoInfo},
ImageInfo, ThumbnailInfo,
},
TransactionId, UInt,
};
#[cfg(feature = "image-proc")]
use crate::ImageError;
#[derive(Debug, Clone)]
pub struct BaseImageInfo {
pub height: Option<UInt>,
pub width: Option<UInt>,
pub size: Option<UInt>,
pub blurhash: Option<String>,
}
#[derive(Debug, Clone)]
pub struct BaseVideoInfo {
pub duration: Option<Duration>,
pub height: Option<UInt>,
pub width: Option<UInt>,
pub size: Option<UInt>,
pub blurhash: Option<String>,
}
#[derive(Debug, Clone)]
pub struct BaseAudioInfo {
pub duration: Option<Duration>,
pub size: Option<UInt>,
}
#[derive(Debug, Clone)]
pub struct BaseFileInfo {
pub size: Option<UInt>,
}
#[derive(Debug)]
pub enum AttachmentInfo {
Image(BaseImageInfo),
Video(BaseVideoInfo),
Audio(BaseAudioInfo),
File(BaseFileInfo),
}
impl From<AttachmentInfo> for ImageInfo {
fn from(info: AttachmentInfo) -> Self {
match info {
AttachmentInfo::Image(info) => assign!(ImageInfo::new(), {
height: info.height,
width: info.width,
size: info.size,
blurhash: info.blurhash,
}),
_ => ImageInfo::new(),
}
}
}
impl From<AttachmentInfo> for VideoInfo {
fn from(info: AttachmentInfo) -> Self {
match info {
AttachmentInfo::Video(info) => assign!(VideoInfo::new(), {
duration: info.duration,
height: info.height,
width: info.width,
size: info.size,
blurhash: info.blurhash,
}),
_ => VideoInfo::new(),
}
}
}
impl From<AttachmentInfo> for AudioInfo {
fn from(info: AttachmentInfo) -> Self {
match info {
AttachmentInfo::Audio(info) => assign!(AudioInfo::new(), {
duration: info.duration,
size: info.size,
}),
_ => AudioInfo::new(),
}
}
}
impl From<AttachmentInfo> for FileInfo {
fn from(info: AttachmentInfo) -> Self {
match info {
AttachmentInfo::File(info) => assign!(FileInfo::new(), {
size: info.size,
}),
_ => FileInfo::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct BaseThumbnailInfo {
pub height: Option<UInt>,
pub width: Option<UInt>,
pub size: Option<UInt>,
}
impl From<BaseThumbnailInfo> for ThumbnailInfo {
fn from(info: BaseThumbnailInfo) -> Self {
assign!(ThumbnailInfo::new(), {
height: info.height,
width: info.width,
size: info.size,
})
}
}
#[derive(Debug)]
pub struct Thumbnail<'a> {
pub data: &'a [u8],
pub content_type: &'a mime::Mime,
pub info: Option<BaseThumbnailInfo>,
}
#[derive(Debug)]
pub struct AttachmentConfig<'a> {
pub(crate) txn_id: Option<&'a TransactionId>,
pub(crate) info: Option<AttachmentInfo>,
pub(crate) thumbnail: Option<Thumbnail<'a>>,
#[cfg(feature = "image-proc")]
pub(crate) generate_thumbnail: bool,
#[cfg(feature = "image-proc")]
pub(crate) thumbnail_size: Option<(u32, u32)>,
}
impl AttachmentConfig<'static> {
pub fn new() -> Self {
Self {
txn_id: Default::default(),
info: Default::default(),
thumbnail: None,
#[cfg(feature = "image-proc")]
generate_thumbnail: Default::default(),
#[cfg(feature = "image-proc")]
thumbnail_size: Default::default(),
}
}
#[cfg(feature = "image-proc")]
#[must_use]
pub fn generate_thumbnail(mut self, size: Option<(u32, u32)>) -> Self {
self.generate_thumbnail = true;
self.thumbnail_size = size;
self
}
}
impl Default for AttachmentConfig<'static> {
fn default() -> Self {
Self::new()
}
}
impl<'a> AttachmentConfig<'a> {
pub fn with_thumbnail(thumbnail: Thumbnail<'a>) -> Self {
Self {
txn_id: Default::default(),
info: Default::default(),
thumbnail: Some(thumbnail),
#[cfg(feature = "image-proc")]
generate_thumbnail: Default::default(),
#[cfg(feature = "image-proc")]
thumbnail_size: Default::default(),
}
}
#[must_use]
pub fn txn_id(mut self, txn_id: &'a TransactionId) -> Self {
self.txn_id = Some(txn_id);
self
}
#[must_use]
pub fn info(mut self, info: AttachmentInfo) -> Self {
self.info = Some(info);
self
}
}
#[cfg(feature = "image-proc")]
pub fn generate_image_thumbnail<R: BufRead + Seek>(
content_type: &mime::Mime,
reader: R,
size: Option<(u32, u32)>,
) -> Result<(Vec<u8>, BaseThumbnailInfo), ImageError> {
let image_format = image::ImageFormat::from_mime_type(content_type);
if image_format.is_none() {
return Err(ImageError::FormatNotSupported);
}
let image_format = image_format.unwrap();
let image = image::load(reader, image_format)?;
let (original_width, original_height) = image.dimensions();
let (width, height) = size.unwrap_or((800, 600));
if height >= original_height && width >= original_width {
return Err(ImageError::ThumbnailBiggerThanOriginal);
}
let thumbnail = image.thumbnail(width, height);
let (thumbnail_width, thumbnail_height) = thumbnail.dimensions();
let mut data: Vec<u8> = vec![];
thumbnail.write_to(&mut Cursor::new(&mut data), image_format)?;
let data_size = data.len() as u32;
Ok((
data,
BaseThumbnailInfo {
width: Some(thumbnail_width.into()),
height: Some(thumbnail_height.into()),
size: Some(data_size.into()),
},
))
}