From 20d79970a268c661f8bada3fee0f4e0e69630ad5 Mon Sep 17 00:00:00 2001 From: iequidoo Date: Thu, 25 Apr 2024 04:02:40 -0300 Subject: [PATCH] fix: Correct message viewtype before recoding image blob (#5496) Otherwise, e.g. if a message is a large GIF, but its viewtype is set to `Image` by the app, this GIF will be recoded to JPEG to reduce its size. GIFs and other special viewtypes must be always detected and sent as is. --- src/blob.rs | 36 +++++++++++++++++++++++++ src/chat.rs | 48 +++++++++++++++------------------ test-data/image/screenshot.gif | Bin 0 -> 207052 bytes 3 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 test-data/image/screenshot.gif diff --git a/src/blob.rs b/src/blob.rs index dcc157ada6..886f295ae2 100644 --- a/src/blob.rs +++ b/src/blob.rs @@ -1330,6 +1330,42 @@ mod tests { Ok(img) } + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_send_big_gif_as_image() -> Result<()> { + let bytes = include_bytes!("../test-data/image/screenshot.gif"); + let (width, height) = (1920u32, 1080u32); + let alice = TestContext::new_alice().await; + let bob = TestContext::new_bob().await; + alice + .set_config( + Config::MediaQuality, + Some(&(MediaQuality::Worse as i32).to_string()), + ) + .await?; + let file = alice.get_blobdir().join("file").with_extension("gif"); + fs::write(&file, &bytes) + .await + .context("failed to write file")?; + let mut msg = Message::new(Viewtype::Image); + msg.set_file(file.to_str().unwrap(), None); + let chat = alice.create_chat(&bob).await; + let sent = alice.send_msg(chat.id, &mut msg).await; + let bob_msg = bob.recv_msg(&sent).await; + // DC must detect the image as GIF and send it w/o reencoding. + assert_eq!(bob_msg.get_viewtype(), Viewtype::Gif); + assert_eq!(bob_msg.get_width() as u32, width); + assert_eq!(bob_msg.get_height() as u32, height); + let file_saved = bob + .get_blobdir() + .join("saved-".to_string() + &bob_msg.get_filename().unwrap()); + bob_msg.save_file(&bob, &file_saved).await?; + let blob = BlobObject::new_from_path(&bob, &file_saved).await?; + let (file_size, _) = blob.metadata()?; + assert_eq!(file_size, bytes.len() as u64); + check_image_size(file_saved, width, height); + Ok(()) + } + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_increation_in_blobdir() -> Result<()> { let t = TestContext::new_alice().await; diff --git a/src/chat.rs b/src/chat.rs index 3af6f94c03..f7ef21be6e 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2505,27 +2505,6 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> { .await? .with_context(|| format!("attachment missing for message of type #{}", msg.viewtype))?; - let mut maybe_sticker = msg.viewtype == Viewtype::Sticker; - if msg.viewtype == Viewtype::Image - || maybe_sticker && !msg.param.exists(Param::ForceSticker) - { - blob.recode_to_image_size(context, &mut maybe_sticker) - .await?; - - if !maybe_sticker { - msg.viewtype = Viewtype::Image; - } - } - msg.param.set(Param::File, blob.as_name()); - if let (Some(filename), Some(blob_ext)) = (msg.param.get(Param::Filename), blob.suffix()) { - let stem = match filename.rsplit_once('.') { - Some((stem, _)) => stem, - None => filename, - }; - msg.param - .set(Param::Filename, stem.to_string() + "." + blob_ext); - } - if msg.viewtype == Viewtype::File || msg.viewtype == Viewtype::Image { // Correct the type, take care not to correct already very special // formats as GIF or VOICE. @@ -2533,8 +2512,7 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> { // Typical conversions: // - from FILE to AUDIO/VIDEO/IMAGE // - from FILE/IMAGE to GIF */ - if let Some((better_type, better_mime)) = - message::guess_msgtype_from_suffix(&blob.to_abs_path()) + if let Some((better_type, _)) = message::guess_msgtype_from_suffix(&blob.to_abs_path()) { if better_type != Viewtype::Webxdc || context @@ -2543,9 +2521,6 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> { .is_ok() { msg.viewtype = better_type; - if !msg.param.exists(Param::MimeType) { - msg.param.set(Param::MimeType, better_mime); - } } } } else if msg.viewtype == Viewtype::Webxdc { @@ -2554,6 +2529,27 @@ async fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<()> { .await?; } + let mut maybe_sticker = msg.viewtype == Viewtype::Sticker; + if msg.viewtype == Viewtype::Image + || maybe_sticker && !msg.param.exists(Param::ForceSticker) + { + blob.recode_to_image_size(context, &mut maybe_sticker) + .await?; + + if !maybe_sticker { + msg.viewtype = Viewtype::Image; + } + } + msg.param.set(Param::File, blob.as_name()); + if let (Some(filename), Some(blob_ext)) = (msg.param.get(Param::Filename), blob.suffix()) { + let stem = match filename.rsplit_once('.') { + Some((stem, _)) => stem, + None => filename, + }; + msg.param + .set(Param::Filename, stem.to_string() + "." + blob_ext); + } + if !msg.param.exists(Param::MimeType) { if let Some((_, mime)) = message::guess_msgtype_from_suffix(&blob.to_abs_path()) { msg.param.set(Param::MimeType, mime); diff --git a/test-data/image/screenshot.gif b/test-data/image/screenshot.gif new file mode 100644 index 0000000000000000000000000000000000000000..57221835a04ef0e63c41997d61cef9aea4c7f3de GIT binary patch literal 207052 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%0Dby*F(?cN??QmOIP&_y7Gx+?~KaCfSgTJ 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