Add default-enabled feature-gates for url_preview and media_thumbnail
Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
@@ -1,7 +1,13 @@
|
||||
use std::{cmp, io::Cursor, num::Saturating as Sat};
|
||||
//! Media Thumbnails
|
||||
//!
|
||||
//! This functionality is gated by 'media_thumbnail', but not at the unit level
|
||||
//! for historical and simplicity reasons. Instead the feature gates the
|
||||
//! inclusion of dependencies and nulls out results using the existing interface
|
||||
//! when not featured.
|
||||
|
||||
use conduwuit::{checked, err, Result};
|
||||
use image::{imageops::FilterType, DynamicImage};
|
||||
use std::{cmp, num::Saturating as Sat};
|
||||
|
||||
use conduwuit::{checked, err, implement, Result};
|
||||
use ruma::{http_headers::ContentDisposition, media::Method, Mxc, UInt, UserId};
|
||||
use tokio::{
|
||||
fs,
|
||||
@@ -67,65 +73,89 @@ impl super::Service {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Using saved thumbnail
|
||||
#[tracing::instrument(skip(self), name = "saved", level = "debug")]
|
||||
async fn get_thumbnail_saved(&self, data: Metadata) -> Result<Option<FileMeta>> {
|
||||
let mut content = Vec::new();
|
||||
let path = self.get_media_file(&data.key);
|
||||
fs::File::open(path)
|
||||
.await?
|
||||
.read_to_end(&mut content)
|
||||
.await?;
|
||||
|
||||
Ok(Some(into_filemeta(data, content)))
|
||||
}
|
||||
|
||||
/// Generate a thumbnail
|
||||
#[tracing::instrument(skip(self), name = "generate", level = "debug")]
|
||||
async fn get_thumbnail_generate(
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
dim: &Dim,
|
||||
data: Metadata,
|
||||
) -> Result<Option<FileMeta>> {
|
||||
let mut content = Vec::new();
|
||||
let path = self.get_media_file(&data.key);
|
||||
fs::File::open(path)
|
||||
.await?
|
||||
.read_to_end(&mut content)
|
||||
.await?;
|
||||
|
||||
let Ok(image) = image::load_from_memory(&content) else {
|
||||
// Couldn't parse file to generate thumbnail, send original
|
||||
return Ok(Some(into_filemeta(data, content)));
|
||||
};
|
||||
|
||||
if dim.width > image.width() || dim.height > image.height() {
|
||||
return Ok(Some(into_filemeta(data, content)));
|
||||
}
|
||||
|
||||
let mut thumbnail_bytes = Vec::new();
|
||||
let thumbnail = thumbnail_generate(&image, dim)?;
|
||||
thumbnail.write_to(&mut Cursor::new(&mut thumbnail_bytes), image::ImageFormat::Png)?;
|
||||
|
||||
// Save thumbnail in database so we don't have to generate it again next time
|
||||
let thumbnail_key = self.db.create_file_metadata(
|
||||
mxc,
|
||||
None,
|
||||
dim,
|
||||
data.content_disposition.as_ref(),
|
||||
data.content_type.as_deref(),
|
||||
)?;
|
||||
|
||||
let mut f = self.create_media_file(&thumbnail_key).await?;
|
||||
f.write_all(&thumbnail_bytes).await?;
|
||||
|
||||
Ok(Some(into_filemeta(data, thumbnail_bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
fn thumbnail_generate(image: &DynamicImage, requested: &Dim) -> Result<DynamicImage> {
|
||||
/// Using saved thumbnail
|
||||
#[implement(super::Service)]
|
||||
#[tracing::instrument(name = "saved", level = "debug", skip(self, data))]
|
||||
async fn get_thumbnail_saved(&self, data: Metadata) -> Result<Option<FileMeta>> {
|
||||
let mut content = Vec::new();
|
||||
let path = self.get_media_file(&data.key);
|
||||
fs::File::open(path)
|
||||
.await?
|
||||
.read_to_end(&mut content)
|
||||
.await?;
|
||||
|
||||
Ok(Some(into_filemeta(data, content)))
|
||||
}
|
||||
|
||||
/// Generate a thumbnail
|
||||
#[cfg(feature = "media_thumbnail")]
|
||||
#[implement(super::Service)]
|
||||
#[tracing::instrument(name = "generate", level = "debug", skip(self, data))]
|
||||
async fn get_thumbnail_generate(
|
||||
&self,
|
||||
mxc: &Mxc<'_>,
|
||||
dim: &Dim,
|
||||
data: Metadata,
|
||||
) -> Result<Option<FileMeta>> {
|
||||
let mut content = Vec::new();
|
||||
let path = self.get_media_file(&data.key);
|
||||
fs::File::open(path)
|
||||
.await?
|
||||
.read_to_end(&mut content)
|
||||
.await?;
|
||||
|
||||
let Ok(image) = image::load_from_memory(&content) else {
|
||||
// Couldn't parse file to generate thumbnail, send original
|
||||
return Ok(Some(into_filemeta(data, content)));
|
||||
};
|
||||
|
||||
if dim.width > image.width() || dim.height > image.height() {
|
||||
return Ok(Some(into_filemeta(data, content)));
|
||||
}
|
||||
|
||||
let mut thumbnail_bytes = Vec::new();
|
||||
let thumbnail = thumbnail_generate(&image, dim)?;
|
||||
let mut cursor = std::io::Cursor::new(&mut thumbnail_bytes);
|
||||
thumbnail
|
||||
.write_to(&mut cursor, image::ImageFormat::Png)
|
||||
.map_err(|error| err!(error!(?error, "Error writing PNG thumbnail.")))?;
|
||||
|
||||
// Save thumbnail in database so we don't have to generate it again next time
|
||||
let thumbnail_key = self.db.create_file_metadata(
|
||||
mxc,
|
||||
None,
|
||||
dim,
|
||||
data.content_disposition.as_ref(),
|
||||
data.content_type.as_deref(),
|
||||
)?;
|
||||
|
||||
let mut f = self.create_media_file(&thumbnail_key).await?;
|
||||
f.write_all(&thumbnail_bytes).await?;
|
||||
|
||||
Ok(Some(into_filemeta(data, thumbnail_bytes)))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "media_thumbnail"))]
|
||||
#[implement(super::Service)]
|
||||
#[tracing::instrument(name = "fallback", level = "debug", skip_all)]
|
||||
async fn get_thumbnail_generate(
|
||||
&self,
|
||||
_mxc: &Mxc<'_>,
|
||||
_dim: &Dim,
|
||||
data: Metadata,
|
||||
) -> Result<Option<FileMeta>> {
|
||||
self.get_thumbnail_saved(data).await
|
||||
}
|
||||
|
||||
#[cfg(feature = "media_thumbnail")]
|
||||
fn thumbnail_generate(
|
||||
image: &image::DynamicImage,
|
||||
requested: &Dim,
|
||||
) -> Result<image::DynamicImage> {
|
||||
use image::imageops::FilterType;
|
||||
|
||||
let thumbnail = if !requested.crop() {
|
||||
let Dim { width, height, .. } = requested.scaled(&Dim {
|
||||
width: image.width(),
|
||||
|
||||
Reference in New Issue
Block a user