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

  1. Re-export or fix the source animated image so frames and durations align, then retry the transform.
  2. Drop the `gif` target format from the action and keep the original format (or use PNG/JPEG for a single frame).
  3. 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

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


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)