forked from jmug/stoatchat
feat: compute thumbhash for images (#596)
* feat: compute thumbhash for images Signed-off-by: newt <hi@newty.dev> * Merge branch 'main' into feat/thumbhash Signed-off-by: newt <hi@newty.dev> * style: move comment onto newline Signed-off-by: newt <hi@newty.dev> * feat: make thumbhash optional Signed-off-by: newt <hi@newty.dev> --------- Signed-off-by: newt <hi@newty.dev> Signed-off-by: newt (: <hi@newty.dev> Signed-off-by: Tom <iamtomahawkx@gmail.com> Co-authored-by: Tom <iamtomahawkx@gmail.com>
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -6824,6 +6824,7 @@ dependencies = [
|
|||||||
"simdutf8",
|
"simdutf8",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"thumbhash",
|
||||||
"tokio 1.49.0",
|
"tokio 1.49.0",
|
||||||
"tower-http 0.5.2",
|
"tower-http 0.5.2",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -8835,6 +8836,12 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thumbhash"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b7726e0245a7331bd0c9a1fb4fd99fd695bcd478ca569f0eda2ff2cb14e7a00"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiff"
|
name = "tiff"
|
||||||
version = "0.10.3"
|
version = "0.10.3"
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ auto_derived!(
|
|||||||
Image {
|
Image {
|
||||||
width: isize,
|
width: isize,
|
||||||
height: isize,
|
height: isize,
|
||||||
|
thumbhash: Option<Vec<u8>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
animated: bool,
|
animated: bool,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -415,11 +415,13 @@ impl From<crate::Metadata> for Metadata {
|
|||||||
crate::Metadata::Image {
|
crate::Metadata::Image {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
thumbhash,
|
||||||
animated,
|
animated,
|
||||||
} => Metadata::Image {
|
} => Metadata::Image {
|
||||||
width: width as usize,
|
width: width as usize,
|
||||||
height: height as usize,
|
height: height as usize,
|
||||||
animated: animated as bool,
|
thumbhash,
|
||||||
|
animated,
|
||||||
},
|
},
|
||||||
crate::Metadata::Video { width, height } => Metadata::Video {
|
crate::Metadata::Video { width, height } => Metadata::Video {
|
||||||
width: width as usize,
|
width: width as usize,
|
||||||
@@ -438,10 +440,12 @@ impl From<Metadata> for crate::Metadata {
|
|||||||
Metadata::Image {
|
Metadata::Image {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
thumbhash,
|
||||||
animated,
|
animated,
|
||||||
} => crate::Metadata::Image {
|
} => crate::Metadata::Image {
|
||||||
width: width as isize,
|
width: width as isize,
|
||||||
height: height as isize,
|
height: height as isize,
|
||||||
|
thumbhash,
|
||||||
animated,
|
animated,
|
||||||
},
|
},
|
||||||
Metadata::Video { width, height } => crate::Metadata::Video {
|
Metadata::Video { width, height } => crate::Metadata::Video {
|
||||||
@@ -531,7 +535,9 @@ impl From<crate::SystemMessage> for SystemMessage {
|
|||||||
crate::SystemMessage::UserRemove { id, by } => Self::UserRemove { id, by },
|
crate::SystemMessage::UserRemove { id, by } => Self::UserRemove { id, by },
|
||||||
crate::SystemMessage::MessagePinned { id, by } => Self::MessagePinned { id, by },
|
crate::SystemMessage::MessagePinned { id, by } => Self::MessagePinned { id, by },
|
||||||
crate::SystemMessage::MessageUnpinned { id, by } => Self::MessageUnpinned { id, by },
|
crate::SystemMessage::MessageUnpinned { id, by } => Self::MessageUnpinned { id, by },
|
||||||
crate::SystemMessage::CallStarted { by, finished_at } => Self::CallStarted { by, finished_at }
|
crate::SystemMessage::CallStarted { by, finished_at } => {
|
||||||
|
Self::CallStarted { by, finished_at }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1398,7 +1404,7 @@ impl From<FieldsMessage> for crate::FieldsMessage {
|
|||||||
impl From<VoiceInformation> for crate::VoiceInformation {
|
impl From<VoiceInformation> for crate::VoiceInformation {
|
||||||
fn from(value: VoiceInformation) -> Self {
|
fn from(value: VoiceInformation) -> Self {
|
||||||
crate::VoiceInformation {
|
crate::VoiceInformation {
|
||||||
max_users: value.max_users
|
max_users: value.max_users,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1406,7 +1412,7 @@ impl From<VoiceInformation> for crate::VoiceInformation {
|
|||||||
impl From<crate::VoiceInformation> for VoiceInformation {
|
impl From<crate::VoiceInformation> for VoiceInformation {
|
||||||
fn from(value: crate::VoiceInformation) -> Self {
|
fn from(value: crate::VoiceInformation) -> Self {
|
||||||
VoiceInformation {
|
VoiceInformation {
|
||||||
max_users: value.max_users
|
max_users: value.max_users,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ auto_derived!(
|
|||||||
Image {
|
Image {
|
||||||
width: usize,
|
width: usize,
|
||||||
height: usize,
|
height: usize,
|
||||||
|
thumbhash: Option<Vec<u8>>,
|
||||||
animated: bool,
|
animated: bool,
|
||||||
},
|
},
|
||||||
/// File is a video with specific dimensions
|
/// File is a video with specific dimensions
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ jxl-oxide = "0.8.1"
|
|||||||
kamadak-exif = "0.5.4"
|
kamadak-exif = "0.5.4"
|
||||||
# revolt_little_exif = "0.5.1"
|
# revolt_little_exif = "0.5.1"
|
||||||
image = { version = "0.25.2" } # avif encode requires dav1d system library: features = ["avif-native"]
|
image = { version = "0.25.2" } # avif encode requires dav1d system library: features = ["avif-native"]
|
||||||
|
thumbhash = "0.1.0"
|
||||||
|
|
||||||
# File processing
|
# File processing
|
||||||
revolt_clamav-client = { version = "0.1.5" }
|
revolt_clamav-client = { version = "0.1.5" }
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ pub async fn strip_metadata(
|
|||||||
Metadata::Image {
|
Metadata::Image {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
thumbhash,
|
||||||
animated,
|
animated,
|
||||||
} => match mime {
|
} => match mime {
|
||||||
// // little_exif does not appear to parse JPEGs correctly? had 2/2 files fail
|
// // little_exif does not appear to parse JPEGs correctly? had 2/2 files fail
|
||||||
@@ -102,6 +103,7 @@ pub async fn strip_metadata(
|
|||||||
Metadata::Image {
|
Metadata::Image {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
thumbhash: thumbhash.clone(),
|
||||||
animated: *animated,
|
animated: *animated,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
|
|
||||||
|
use image::{GenericImageView, ImageError, ImageReader};
|
||||||
use revolt_database::Metadata;
|
use revolt_database::Metadata;
|
||||||
use revolt_files::{image_size, is_animated, video_size};
|
use revolt_files::{image_size, is_animated, video_size};
|
||||||
use tempfile::NamedTempFile;
|
use tempfile::NamedTempFile;
|
||||||
@@ -26,6 +27,16 @@ pub fn generate_metadata(f: &NamedTempFile, mime_type: &str) -> Metadata {
|
|||||||
.map(|(width, height)| Metadata::Image {
|
.map(|(width, height)| Metadata::Image {
|
||||||
width: width as isize,
|
width: width as isize,
|
||||||
height: height as isize,
|
height: height as isize,
|
||||||
|
thumbhash: ImageReader::open(f)
|
||||||
|
.and_then(|r| r.with_guessed_format())
|
||||||
|
.map_err(ImageError::from)
|
||||||
|
.and_then(|r| r.decode())
|
||||||
|
.map(|img| img.thumbnail(100, 100))
|
||||||
|
.map(|img| (img.dimensions(), img.to_rgba8().into_raw()))
|
||||||
|
.map(|((width, height), rgba)| {
|
||||||
|
thumbhash::rgba_to_thumb_hash(width as usize, height as usize, &rgba)
|
||||||
|
})
|
||||||
|
.ok(),
|
||||||
animated: is_animated(f, mime_type).unwrap_or(false),
|
animated: is_animated(f, mime_type).unwrap_or(false),
|
||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
|
|||||||
Reference in New Issue
Block a user