Fall back to ffmpeg when Go's JPEG decoder rejects a source
Go's stdlib image/jpeg refuses some valid-but-rare features such as 4:1:1 luma/chroma subsampling (ANN's CDN serves these). When the in-process decode fails, route the bytes through ffmpeg the same way we already do for video sources.
This commit is contained in:
@@ -178,7 +178,7 @@ func buildAvifThumb(src, outPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if urlIsVideo || isVideoContentType(resp.Header.Get("Content-Type")) {
|
if urlIsVideo || isVideoContentType(resp.Header.Get("Content-Type")) {
|
||||||
return buildAvifFromVideoFrame(body, outPath)
|
return buildAvifFromFFmpeg(body, outPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isAvif(body) {
|
if isAvif(body) {
|
||||||
@@ -195,6 +195,13 @@ func buildAvifThumb(src, outPath string) error {
|
|||||||
}
|
}
|
||||||
src2, _, err := image.Decode(bytes.NewReader(body))
|
src2, _, err := image.Decode(bytes.NewReader(body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// Go's stdlib JPEG decoder rejects some valid-but-rare features
|
||||||
|
// (e.g. 4:1:1 luma/chroma subsampling served by ANN's CDN). Hand
|
||||||
|
// the bytes to ffmpeg as a permissive fallback — it produces the
|
||||||
|
// same shape of output as the video path.
|
||||||
|
if ffErr := buildAvifFromFFmpeg(body, outPath); ffErr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return fmt.Errorf("decode: %w", err)
|
return fmt.Errorf("decode: %w", err)
|
||||||
}
|
}
|
||||||
b := src2.Bounds()
|
b := src2.Bounds()
|
||||||
@@ -265,12 +272,13 @@ func isVideoContentType(ct string) bool {
|
|||||||
return strings.HasPrefix(strings.ToLower(strings.TrimSpace(ct)), "video/")
|
return strings.HasPrefix(strings.ToLower(strings.TrimSpace(ct)), "video/")
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildAvifFromVideoFrame writes the downloaded video to a temp file, asks
|
// buildAvifFromFFmpeg writes the downloaded bytes to a temp file, asks
|
||||||
// ffmpeg to extract a single resized frame as PNG, then hands it to avifenc.
|
// ffmpeg to extract a single resized frame as PNG, then hands it to avifenc.
|
||||||
// ffmpeg is optional — if it's missing we return an error and the caller
|
// Used both for video sources and as a permissive fallback for images whose
|
||||||
// falls back to redirecting to the source URL, just like any other build
|
// JPEG features Go's stdlib decoder rejects. ffmpeg is optional — if it's
|
||||||
// failure.
|
// missing we return an error and the caller falls back to redirecting to
|
||||||
func buildAvifFromVideoFrame(body []byte, outPath string) error {
|
// the source URL, just like any other build failure.
|
||||||
|
func buildAvifFromFFmpeg(body []byte, outPath string) error {
|
||||||
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
||||||
return fmt.Errorf("ffmpeg not available: %w", err)
|
return fmt.Errorf("ffmpeg not available: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user