Skip to content

Commit fba06d2

Browse files
authored
og_image: Add support for JPEG avatars (#11516)
1 parent 42c0502 commit fba06d2

File tree

2 files changed

+50
-4
lines changed

2 files changed

+50
-4
lines changed

crates/crates_io_og_image/examples/test_generator.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
3232
"Turbo87",
3333
"https://avatars.githubusercontent.com/u/141300",
3434
),
35+
OgImageAuthorData::with_url(
36+
"carols10cents",
37+
"https://avatars.githubusercontent.com/u/193874",
38+
),
3539
],
3640
lines_of_code: Some(2000),
3741
crate_size: 75,

crates/crates_io_og_image/src/lib.rs

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,26 @@ impl OgImageGenerator {
9898
Self::default()
9999
}
100100

101+
/// Detects the image format from the first few bytes using magic numbers.
102+
///
103+
/// Returns the appropriate file extension for supported formats:
104+
/// - PNG: returns "png"
105+
/// - JPEG: returns "jpg"
106+
/// - Unsupported formats: returns None
107+
fn detect_image_format(bytes: &[u8]) -> Option<&'static str> {
108+
// PNG magic number: 89 50 4E 47 0D 0A 1A 0A
109+
if bytes.starts_with(&[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]) {
110+
return Some("png");
111+
}
112+
113+
// JPEG magic number: FF D8 FF
114+
if bytes.starts_with(&[0xFF, 0xD8, 0xFF]) {
115+
return Some("jpg");
116+
}
117+
118+
None
119+
}
120+
101121
/// Creates a new `OgImageGenerator` using the `TYPST_PATH` environment variable.
102122
///
103123
/// If the `TYPST_PATH` environment variable is set, uses that path.
@@ -218,13 +238,9 @@ impl OgImageGenerator {
218238
let client = reqwest::Client::new();
219239
for (index, author) in data.authors.iter().enumerate() {
220240
if let Some(avatar) = &author.avatar {
221-
let filename = format!("avatar_{index}.png");
222-
let avatar_path = assets_dir.join(&filename);
223-
224241
debug!(
225242
author_name = %author.name,
226243
avatar_url = %avatar,
227-
avatar_path = %avatar_path.display(),
228244
"Processing avatar for author {}", author.name
229245
);
230246

@@ -271,6 +287,32 @@ impl OgImageGenerator {
271287
bytes
272288
};
273289

290+
// Detect the image format and determine the appropriate file extension
291+
let Some(extension) = Self::detect_image_format(&bytes) else {
292+
// Format not supported, log warning with first 20 bytes for debugging
293+
let debug_bytes = &bytes[..bytes.len().min(20)];
294+
let hex_bytes = debug_bytes
295+
.iter()
296+
.map(|b| format!("{b:02x}"))
297+
.collect::<Vec<_>>()
298+
.join(" ");
299+
300+
warn!("Unsupported avatar format at {avatar}, first 20 bytes: {hex_bytes}");
301+
302+
// Skip this avatar and continue with the next one
303+
continue;
304+
};
305+
306+
let filename = format!("avatar_{index}.{extension}");
307+
let avatar_path = assets_dir.join(&filename);
308+
309+
debug!(
310+
author_name = %author.name,
311+
avatar_url = %avatar,
312+
avatar_path = %avatar_path.display(),
313+
"Writing avatar file with detected format"
314+
);
315+
274316
// Write the bytes to the avatar file
275317
fs::write(&avatar_path, &bytes).await.map_err(|err| {
276318
OgImageError::AvatarWriteError {

0 commit comments

Comments
 (0)