{"id":"ca3f2a7f8bc9cf21","repo":"gohugoio/hugo","slug":"gif-number-of-frame-durations-does-not-match-numb","errorCode":null,"errorMessage":"gif: number of frame durations does not match number of frames","messagePattern":"gif: number of frame durations does not match number of frames","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"resources/images/codec.go","lineNumber":117,"sourceCode":"\t\treturn jpeg.Encode(w, img, &jpeg.Options{Quality: quality})\n\tcase PNG:\n\t\tencoder := png.Encoder{CompressionLevel: png.DefaultCompression}\n\t\treturn encoder.Encode(w, img)\n\tcase GIF:\n\t\tif anim, ok := img.(himage.AnimatedImage); ok {\n\t\t\tif g, ok := anim.GetRaw().(*gif.GIF); ok {\n\t\t\t\treturn gif.EncodeAll(w, g)\n\t\t\t}\n\n\t\t\t// Animated image, but not a GIF. Convert it.\n\t\t\tframes := anim.GetFrames()\n\t\t\tif len(frames) == 0 {\n\t\t\t\treturn gif.Encode(w, img, &gif.Options{NumColors: 256})\n\t\t\t}\n\n\t\t\tframeDurations := anim.GetFrameDurations()\n\t\t\tif len(frameDurations) != len(frames) {\n\t\t\t\treturn errors.New(\"gif: number of frame durations does not match number of frames\")\n\t\t\t}\n\n\t\t\toutGif := &gif.GIF{\n\t\t\t\tDelay: himage.FrameDurationsToGifDelays(frameDurations),\n\t\t\t}\n\n\t\t\toutGif.LoopCount = anim.GetLoopCount()\n\n\t\t\tfor _, frame := range frames {\n\t\t\t\tbounds := frame.Bounds()\n\t\t\t\tpalettedImage := image.NewPaletted(bounds, palette.Plan9)\n\t\t\t\tdraw.Draw(palettedImage, palettedImage.Rect, frame, bounds.Min, draw.Src)\n\t\t\t\toutGif.Image = append(outGif.Image, palettedImage)\n\t\t\t}\n\n\t\t\treturn gif.EncodeAll(w, outGif)\n\t\t}\n\t\treturn gif.Encode(w, img, &gif.Options{","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/resources/images/codec.go#L99-L135","documentation":"When encoding an animated image (that is not already a raw *gif.GIF) to GIF, Hugo pulls the frames and per-frame durations from the AnimatedImage and requires the two slices to be the same length, since each GIF frame needs a delay entry. A mismatch means the source animation's metadata is inconsistent, so encoding aborts rather than guessing durations.","triggerScenarios":"Converting an animated source (e.g. animated WebP decoded via the WASM codec) to GIF output — `.Resize \"300x gif\"` or setting the target format to gif on an animated image whose decoded frame-duration list doesn't match its frame list.","commonSituations":"Malformed or unusually-authored animated WebP/AVIF files with truncated timing metadata; edge-case files produced by online GIF/WebP converters; codec bugs surfaced after the v0.153.0 image pipeline rework.","solutions":["Re-encode the source animation with a standard tool (e.g. `ffmpeg` or `img2webp`) so every frame carries a duration, then rebuild.","Convert the asset to GIF outside Hugo and use the GIF directly as the source.","If a well-formed file reproduces this, file a Hugo issue with the source image attached."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// For animated gif encoding, ensure durations align with frames\nif len(g.Delay) != len(g.Image) {\n    return fmt.Errorf(\"gif frame/delay mismatch: %d delays vs %d frames\", len(g.Delay), len(g.Image))\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"number of frame durations\") {\n    // malformed animated gif source — re-export the asset or drop animation\n}","preventionTips":["Validate animated GIF sources with a decoder pass before publishing","Re-export problematic GIFs from the original tool rather than patching bytes","When constructing gif.GIF programmatically, append Delay and Image together"],"tags":["hugo","gif","animation","images","encode"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}