fix(images): pick the decoder from the bytes, not the extension
`image::open` chooses the decoder by extension, so a PNG saved as .jpg (Android screenshots, chat exports) failed thumbnails, previews, perceptual hashes, insights and face crops with "Illegal start bytes: 8950". New `file_types::open_image` sniffs the header and falls back to the extension when it's unknown; every decode site uses it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bc27aea81a
commit
b95adb4f77
5 files changed
+29
-6
No files matched your search
@@ -115,6 +115,16 @@ pub fn direntry_is_media(entry: &DirEntry) -> bool {
|
||||
is_media_file(entry.path())
|
||||
}
|
||||
|
||||
/// Decode an image by its bytes, not its extension. `image::open` picks
|
||||
/// the decoder from the extension, so a PNG saved as `.jpg` (Android
|
||||
/// screenshots, chat exports) fails with "Illegal start bytes: 8950".
|
||||
/// Sniffing falls back to the extension when the header is unknown.
|
||||
pub fn open_image(path: &Path) -> image::ImageResult<image::DynamicImage> {
|
||||
image::ImageReader::open(path)?
|
||||
.with_guessed_format()?
|
||||
.decode()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -190,4 +200,16 @@ mod tests {
|
||||
assert!(is_image_file(Path::new(".cover.jpg")));
|
||||
assert!(!is_filesystem_metadata(Path::new(".cover.jpg")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_image_sniffs_format_over_extension() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("Screenshot_2014-11-17-21-30-31.jpg");
|
||||
image::RgbImage::new(3, 2)
|
||||
.save_with_format(&path, image::ImageFormat::Png)
|
||||
.unwrap();
|
||||
assert!(image::open(&path).is_err(), "extension-based decode fails");
|
||||
let img = open_image(&path).unwrap();
|
||||
assert_eq!((img.width(), img.height()), (3, 2));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user