package image import ( "bufio" "errors" "io" ) var ErrNotAGif = errors.New("not a gif") const ( gifSignaturePrefix = "GIF" gifHeaderLength = 6 gifScreenDescriptorSize = 7 gifImageDescriptorSize = 9 gifExtensionIntroducer = 0x21 gifImageSeparator = 0x2C gifTrailer = 0x3B gifColorTableFlag = 0x80 gifColorTableSizeMask = 0x07 gifColorTableEntryBytes = 3 gifFramesBeforeAnimation = 2 ) func IsAnimatedGIF(source io.Reader) (bool, error) { reader := bufio.NewReader(source) if err := skipGifPreamble(reader); err != nil { return false, err } frames := 0 for { marker, err := reader.ReadByte() if err != nil { return false, err } switch marker { case gifTrailer: return false, nil case gifExtensionIntroducer: err = skipGifExtension(reader) case gifImageSeparator: frames++ if frames >= gifFramesBeforeAnimation { return true, nil } err = skipGifImage(reader) default: return false, ErrNotAGif } if err != nil { return false, err } } } func skipGifPreamble(reader *bufio.Reader) error { header := make([]byte, gifHeaderLength) if _, err := io.ReadFull(reader, header); err != nil { return err } if string(header[:len(gifSignaturePrefix)]) != gifSignaturePrefix { return ErrNotAGif } screen := make([]byte, gifScreenDescriptorSize) if _, err := io.ReadFull(reader, screen); err != nil { return err } return skipGifColorTable(reader, screen[4]) } func skipGifColorTable(reader *bufio.Reader, packed byte) error { if packed&gifColorTableFlag == 0 { return nil } entries := 1 << (int(packed&gifColorTableSizeMask) + 1) _, err := reader.Discard(entries * gifColorTableEntryBytes) return err } func skipGifExtension(reader *bufio.Reader) error { if _, err := reader.ReadByte(); err != nil { return err } return skipGifSubBlocks(reader) } func skipGifImage(reader *bufio.Reader) error { descriptor := make([]byte, gifImageDescriptorSize) if _, err := io.ReadFull(reader, descriptor); err != nil { return err } if err := skipGifColorTable(reader, descriptor[8]); err != nil { return err } if _, err := reader.ReadByte(); err != nil { return err } return skipGifSubBlocks(reader) } func skipGifSubBlocks(reader *bufio.Reader) error { for { size, err := reader.ReadByte() if err != nil { return err } if size == 0 { return nil } if _, err := reader.Discard(int(size)); err != nil { return err } } }