gohugoio/hugo · error
gif: number of frame durations does not match number of…
Error message
gif: number of frame durations does not match number of frames
What it means
When Hugo converts an animated (non-GIF) image to GIF, it pulls frames and their durations from the source animated image and rebuilds a `gif.GIF`. At codec.go:116 it asserts that the number of frame durations equals the number of frames; a mismatch would produce a corrupt animated GIF, so encoding is aborted.
Solutions
- Re-export or fix the source animated image so frames and durations align, then retry the transform.
- Drop the `gif` target format from the action and keep the original format (or use PNG/JPEG for a single frame).
- If the source is legitimately valid, file a Hugo bug with the source file — this indicates a bug in the AnimatedImage frame extraction, not in your config.
Example fix
{{/* before */}}
{{ $img.Resize "200x200 gif" }}
{{/* after: keep lossless single-frame output */}}
{{ $img.Resize "200x200 png" }} Defensive patterns
Strategy: validation
Prevention
- Pre-convert animated sources to GIF externally if you need animated GIF output.
- Avoid targeting gif for animated non-GIF sources.
- Report reproducible cases to Hugo — it indicates a frame-extraction bug.
When it happens
Trigger: Calling an image transform that targets GIF output (e.g. `{{ $img.Resize "100x100 gif" }}` or `Fit`/`Crop` with `gif`) on an animated source image whose underlying AnimatedImage implementation returns inconsistent frame/duration counts.
Common situations: Animated WebP/AVIF source re-encoded to GIF via an image processing action; a malformed or truncated animated source; edge cases in the WASM codec frame extraction that yield zero durations for non-zero frames.
Related errors
- format not supported
- failed to decode gif
- format not supported
- invalid image dimensions
- must provide Width and Height
AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09).
Data as JSON: /api/errors/ca3f2a7f8bc9cf21.
Report an issue: GitHub.
Appendix: source
Thrown at resources/images/codec.go:117
return jpeg.Encode(w, img, &jpeg.Options{Quality: quality})
case PNG:
encoder := png.Encoder{CompressionLevel: png.DefaultCompression}
return encoder.Encode(w, img)
case GIF:
if anim, ok := img.(himage.AnimatedImage); ok {
if g, ok := anim.GetRaw().(*gif.GIF); ok {
return gif.EncodeAll(w, g)
}
// Animated image, but not a GIF. Convert it.
frames := anim.GetFrames()
if len(frames) == 0 {
return gif.Encode(w, img, &gif.Options{NumColors: 256})
}
frameDurations := anim.GetFrameDurations()
if len(frameDurations) != len(frames) {
return errors.New("gif: number of frame durations does not match number of frames")
}
outGif := &gif.GIF{
Delay: himage.FrameDurationsToGifDelays(frameDurations),
}
outGif.LoopCount = anim.GetLoopCount()
for _, frame := range frames {
bounds := frame.Bounds()
palettedImage := image.NewPaletted(bounds, palette.Plan9)
draw.Draw(palettedImage, palettedImage.Rect, frame, bounds.Min, draw.Src)
outGif.Image = append(outGif.Image, palettedImage)
}
return gif.EncodeAll(w, outGif)
}
return gif.Encode(w, img, &gif.Options{View on GitHub (pinned to 52c9bd7908)