How to identify whether webp image is static or animated?

Viewed 4033

I am working on a project where a user can upload webp images. I know how to convert webp images into jpg/png, but am stuck on how to identify whether the webp image is static (non-animated) or animated.

I want to identify them because I use different commands for conversion:

Command for non-animated webp to jpg:

dwebp nonanimated.webp -o jpg.jpg

Command for animated webp to non-animated webp (takes 2nd frame):

webpmux -get frame 2 animated.webp -o nonanimated.webp

But I cannot find a single command that handle both cases.

I am using PHP on the server side, and HTML and Javascript for frontend.

5 Answers

There are flags in Webp header, ANIMATION among others. Small function to check it:

function isWebpAnimated($fn){
  $result = false;
  $fh = fopen($fn, "rb"); 
  fseek($fh, 12);
  if(fread($fh, 4) === 'VP8X'){
    fseek($fh, 16);
    $myByte = fread($fh, 1);
    $result = ((ord($myByte) >> 1) & 1)?true:false;
  }
  fclose($fh);
  return $result;
}

ANIM and ANMF are from next chunk headers.

RIFF container specification

According Sven Liivak's isWebpAnimated()... there is a small bug.

fseek($fh, 16);

should be:

fseek($fh, 20);

Because postion 16 is the chunk_size position in VP8X. But we need the flag position which is at 20.

Fixed function:

function isWebpAnimated($fn){
  $result = false;
  $fh = fopen($fn, "rb"); 
  fseek($fh, 12);
  if(fread($fh, 4) === 'VP8X'){
    fseek($fh, 20);
    $myByte = fread($fh, 1);
    $result = ((ord($myByte) >> 1) & 1)?true:false;
  }
  fclose($fh);
  return $result;
}

This is my java code, it works for me.

static boolean isWebpAnimated(InputStream in) {
        boolean result = false;
        try {
            in.skip(12);
            byte[] buf = new byte[4];
            int i = in.read(buf);
            if ("VP8X".equals(new String(buf, 0, i))) {
                in.skip(12);
                result = (in.read(buf) == 4 && (buf[3] & 0x00000002) != 0);
            }
        } catch (Exception e) {
        } finally {
            try {
                in.close();
            } catch (Exception e) {
            }
        }
        return result;
    }

Fixing @ccomangee's solution. Some static webp images would be detected as animated and can cause issue in the application.

I had extracted webp frames and saved as webp images and tried to identify by checking VP8X signature and it exists in given position although It is static image. So if there is VP8X it does not mean that the image would be animated having more than one frames.

I tried few images with my solution and the result is below:

                             riff   webp  vp8*   anim
OK(anim-trans).webp:      [ RIFF | WEBP | VP8X | ANIM ]
Cuppy(static-trans).webp: [ RIFF | WEBP | VP8L | NA?  ]
glass(anim-solid).webp:   [ RIFF | WEBP | VP8X | ANIM ]
sunset(anim-trans).webp:  [ RIFF | WEBP | VP8X | ANIM ]
atom(anim_solid).webp:    [ RIFF | WEBP | VP8X | ANIM ]
spread(anim-trans).webp:  [ RIFF | WEBP | VP8X | ANIM ]
heart(static-trans).webp: [ RIFF | WEBP | VP8X | NA?  ] 
ludo(static-trans).webp:  [ RIFF | WEBP | VP8X | NA?  ]
scene(static_solid).webp: [ RIFF | WEBP | VP8  | NA?  ]

Here all images are named according to its type. anim-trans: animated image contains transparency (alpha channel support) anim-solid: animated image having no transparency static-trans and static-solid are static images.

VP8L is loseless webp and VP8X contains extended features. VP8 is surely static image.

If VP8X exists it could be static or animated most images would be animated.

The solution is

Read 4 bytes -> 'RIFF' Skip 4 bytes Read 4 bytes -> 'WEBP' Read 4 bytes -> 'VP8X' / 'VP8L' / 'VP8' skip 14 bytes Read 4 bytes -> 'ANIM'

Java Code:

 public static boolean check(File file) {
            boolean riff = false;
            boolean webp = false;
            boolean vp8x = false;
            boolean anim = false;
        try (InputStream in = new FileInputStream(file)) {

            byte[] buf = new byte[4];
            int i = in.read(buf); // 4
            
            if(buf[0] == 0x52 && buf[1] == 0x49 && buf[2]==0x46 && buf[3] == buf[2] )
                riff = true;

            in.skip(4); // ???? (8+)
            i = in.read(buf); // (12+)
            if(buf[0] == 0x57 && buf[1] == 0x45 && buf[2]==0x42 && buf[3] == 0x50 )
                    webp = true   ;             

            i = in.read(buf); // (16+)
            if(buf[0] == 0x41 && buf[1] == 0x4e && buf[2]==0x49 && buf[3] == 0x4d );
                vp8x = true;
                
            in.skip(14); // then next 4 should contain ANIM - 41 4e 49 4d
            i = in.read(buf);
            if(buf[0] == 0x41 && buf[1] == 0x4e && buf[2]==0x49 && buf[3] == 0x4d )
                anim = true;

        } catch (Exception e) {
            System.out.println("errrrrr "+e.getMessage());
        }
        return riff && webp && anim;

    }

you can directly read WEBP by skipping 8 bytes then count and skipp all chunks before ANIM and read that position if ANIM exist then its animated webp image else static.

File layout of webp images https://developers.google.com/speed/webp/docs/riff_container#example_file_layouts

Ref: Google WEBP specification https://developers.google.com/speed/webp/docs/riff_container

Related