Can I detect animated gifs using php and gd?

I'm currently running into some issues resizing images using GD.

Everything works fine until i want to resize an animated gif, which delivers the first frame on a black background.

I've tried using getimagesize but that only gives me dimensions and nothing to distinguish between just any gif and an animated one.

Actual resizing is not required for animated gifs, just being able to skip them would be enough for our purposes.

Any clues?

PS. I don't have access to imagemagick.

Kind regards,

Kris


Solution 1:

While searching for a solution to the same problem I noticed that the php.net site has a follow-up to the code Davide and Kris are referring to, but, according to the author, less memory-intensive, and possibly less disk-intensive.

I'll replicate it here, because it may be of interest.

source: http://www.php.net/manual/en/function.imagecreatefromgif.php#88005

function is_ani($filename) {
    if(!($fh = @fopen($filename, 'rb')))
        return false;
    $count = 0;
    //an animated gif contains multiple "frames", with each frame having a
    //header made up of:
    // * a static 4-byte sequence (\x00\x21\xF9\x04)
    // * 4 variable bytes
    // * a static 2-byte sequence (\x00\x2C)

    // We read through the file til we reach the end of the file, or we've found
    // at least 2 frame headers
    while(!feof($fh) && $count < 2) {
        $chunk = fread($fh, 1024 * 100); //read 100kb at a time
        $count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00[\x2C\x21]#s', $chunk, $matches);
    }

    fclose($fh);
    return $count > 1;
}

Solution 2:

There is a brief snippet of code in the PHP manual page of the imagecreatefromgif() function that should be what you need:

imagecreatefromgif comment #59787 by ZeBadger

Solution 3:

Here's the working function:

/**
 * Thanks to ZeBadger for original example, and Davide Gualano for pointing me to it
 * Original at http://it.php.net/manual/en/function.imagecreatefromgif.php#59787
 **/
function is_animated_gif( $filename )
{
    $raw = file_get_contents( $filename );

    $offset = 0;
    $frames = 0;
    while ($frames < 2)
    {
        $where1 = strpos($raw, "\x00\x21\xF9\x04", $offset);
        if ( $where1 === false )
        {
            break;
        }
        else
        {
            $offset = $where1 + 1;
            $where2 = strpos( $raw, "\x00\x2C", $offset );
            if ( $where2 === false )
            {
                break;
            }
            else
            {
                if ( $where1 + 8 == $where2 )
                {
                    $frames ++;
                }
                $offset = $where2 + 1;
            }
        }
    }

    return $frames > 1;
}

Solution 4:

This is an improvement of the current top voted answer but I don't have enough reputation to comment yet. The problem with that answer is that it reads the file in 100Kb chunks and the end of frame marker might be split in between 2 chunks. A fix for that is to add the last 20b of the previous frame to the next one:

<?php
function is_ani($filename) {
  if(!($fh = @fopen($filename, 'rb')))
    return false;
  $count = 0;
  //an animated gif contains multiple "frames", with each frame having a
  //header made up of:
  // * a static 4-byte sequence (\x00\x21\xF9\x04)
  // * 4 variable bytes
  // * a static 2-byte sequence (\x00\x2C) (some variants may use \x00\x21 ?)

  // We read through the file til we reach the end of the file, or we've found
  // at least 2 frame headers
  $chunk = false;
  while(!feof($fh) && $count < 2) {
    //add the last 20 characters from the previous string, to make sure the searched pattern is not split.
    $chunk = ($chunk ? substr($chunk, -20) : "") . fread($fh, 1024 * 100); //read 100kb at a time
    $count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches);
  }

  fclose($fh);
  return $count > 1;
}