diff options
| author | Paul Buetow <paul@buetow.org> | 2026-07-08 01:07:36 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-07-08 01:07:36 +0300 |
| commit | 6bd28f285708e7bf3567bd82783665fd3ff53a3d (patch) | |
| tree | 39e07494e9a0dddf3d0f9c4db7be7aa177688d48 | |
| parent | 394647f756d089fbe2ed5e58ba8da2583b699256 (diff) | |
jo
| -rw-r--r-- | internal/processor/image.go | 70 |
1 files changed, 48 insertions, 22 deletions
diff --git a/internal/processor/image.go b/internal/processor/image.go index f68f533..fe5615f 100644 --- a/internal/processor/image.go +++ b/internal/processor/image.go @@ -71,6 +71,7 @@ func writeImageAsset(img image.Image, postDir string) error { } // decodeImage decodes a JPEG, PNG, or GIF (first frame) from srcPath. +// It sniffs the actual file type from magic bytes instead of trusting the extension. func decodeImage(srcPath string) (image.Image, error) { f, err := os.Open(srcPath) if err != nil { @@ -78,33 +79,58 @@ func decodeImage(srcPath string) (image.Image, error) { } defer f.Close() - ext := filepath.Ext(srcPath) - switch ext { - case ".jpg", ".jpeg": - img, err := jpeg.Decode(f) - if err != nil { - return nil, fmt.Errorf("decode JPEG %s: %w", srcPath, err) - } - return img, nil + // Read the first 512 bytes to detect the actual file format. + const sniffLen = 512 + head := make([]byte, sniffLen) + n, err := f.Read(head) + if err != nil { + return nil, fmt.Errorf("read image %s: %w", srcPath, err) + } + if n == 0 { + return nil, fmt.Errorf("empty image file: %s", srcPath) + } - case ".png": - img, err := png.Decode(f) - if err != nil { - return nil, fmt.Errorf("decode PNG %s: %w", srcPath, err) - } - return img, nil + // Determine the actual image format from magic bytes. + sniff := head[:n] + var format string + switch { + case len(sniff) >= 2 && sniff[0] == 0xFF && sniff[1] == 0xD8: + format = "jpeg" + case len(sniff) >= 8 && string(sniff[:8]) == "\x89PNG\r\n\x1a\n": + format = "png" + case len(sniff) >= 6 && string(sniff[:6]) == "GIF87a" || string(sniff[:6]) == "GIF89a": + format = "gif" + default: + return nil, fmt.Errorf("unsupported image format for %s (extension: %s sniff: %q)", srcPath, filepath.Ext(srcPath), string(sniff)) + } - case ".gif": + // Rewind to the beginning so the decoder sees the full stream. + if _, err := f.Seek(0, 0); err != nil { + return nil, fmt.Errorf("rewind image %s: %w", srcPath, err) + } + + var img image.Image + var decodeErr error + switch format { + case "jpeg": + img, decodeErr = jpeg.Decode(f) + if decodeErr != nil { + return nil, fmt.Errorf("decode JPEG %s: %w", srcPath, decodeErr) + } + case "png": + img, decodeErr = png.Decode(f) + if decodeErr != nil { + return nil, fmt.Errorf("decode PNG %s: %w", srcPath, decodeErr) + } + case "gif": // Use only the first frame of animated GIFs. - g, err := gif.Decode(f) - if err != nil { - return nil, fmt.Errorf("decode GIF %s: %w", srcPath, err) + img, decodeErr = gif.Decode(f) + if decodeErr != nil { + return nil, fmt.Errorf("decode GIF %s: %w", srcPath, decodeErr) } - return g, nil - - default: - return nil, fmt.Errorf("unsupported image format: %s", ext) } + + return img, nil } // resizeIfNeeded returns a resized copy of img if its width exceeds maxImageWidth, |
