diff --git a/crates/crates_io_og_image/examples/test_generator.rs b/crates/crates_io_og_image/examples/test_generator.rs index 40bffcc876f..729ecce87a9 100644 --- a/crates/crates_io_og_image/examples/test_generator.rs +++ b/crates/crates_io_og_image/examples/test_generator.rs @@ -32,6 +32,10 @@ async fn main() -> Result<(), Box> { "Turbo87", "https://avatars.githubusercontent.com/u/141300", ), + OgImageAuthorData::with_url( + "carols10cents", + "https://avatars.githubusercontent.com/u/193874", + ), ], lines_of_code: Some(2000), crate_size: 75, diff --git a/crates/crates_io_og_image/src/lib.rs b/crates/crates_io_og_image/src/lib.rs index 8bb91fa3a6c..0f85fa9c7b2 100644 --- a/crates/crates_io_og_image/src/lib.rs +++ b/crates/crates_io_og_image/src/lib.rs @@ -98,6 +98,26 @@ impl OgImageGenerator { Self::default() } + /// Detects the image format from the first few bytes using magic numbers. + /// + /// Returns the appropriate file extension for supported formats: + /// - PNG: returns "png" + /// - JPEG: returns "jpg" + /// - Unsupported formats: returns None + fn detect_image_format(bytes: &[u8]) -> Option<&'static str> { + // PNG magic number: 89 50 4E 47 0D 0A 1A 0A + if bytes.starts_with(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) { + return Some("png"); + } + + // JPEG magic number: FF D8 FF + if bytes.starts_with(&[0xFF, 0xD8, 0xFF]) { + return Some("jpg"); + } + + None + } + /// Creates a new `OgImageGenerator` using the `TYPST_PATH` environment variable. /// /// If the `TYPST_PATH` environment variable is set, uses that path. @@ -218,13 +238,9 @@ impl OgImageGenerator { let client = reqwest::Client::new(); for (index, author) in data.authors.iter().enumerate() { if let Some(avatar) = &author.avatar { - let filename = format!("avatar_{index}.png"); - let avatar_path = assets_dir.join(&filename); - debug!( author_name = %author.name, avatar_url = %avatar, - avatar_path = %avatar_path.display(), "Processing avatar for author {}", author.name ); @@ -271,6 +287,32 @@ impl OgImageGenerator { bytes }; + // Detect the image format and determine the appropriate file extension + let Some(extension) = Self::detect_image_format(&bytes) else { + // Format not supported, log warning with first 20 bytes for debugging + let debug_bytes = &bytes[..bytes.len().min(20)]; + let hex_bytes = debug_bytes + .iter() + .map(|b| format!("{b:02x}")) + .collect::>() + .join(" "); + + warn!("Unsupported avatar format at {avatar}, first 20 bytes: {hex_bytes}"); + + // Skip this avatar and continue with the next one + continue; + }; + + let filename = format!("avatar_{index}.{extension}"); + let avatar_path = assets_dir.join(&filename); + + debug!( + author_name = %author.name, + avatar_url = %avatar, + avatar_path = %avatar_path.display(), + "Writing avatar file with detected format" + ); + // Write the bytes to the avatar file fs::write(&avatar_path, &bytes).await.map_err(|err| { OgImageError::AvatarWriteError {