【问题标题】:How do I programmatically check whether a GIF image is animated?如何以编程方式检查 GIF 图像是否为动画?
【发布时间】:2010-11-27 14:31:46
【问题描述】:

这是another question 的链接,我询问了我正在从事的同一个项目。我认为一点背景知识会有所帮助。

对于那些懒得打开新标签来回答这个问题的人,我将在这里总结一下我正在尝试做的事情:我从 4scrape 下载了大约 250,000 张图片,我想浏览 GIF 并找到哪些是动画的。我需要以编程方式执行此操作,因为我真的不觉得我的灵魂(或我与女友的关系)可以使用查看来自 4chan 的几千个 GIF 来查看它们是否是动画的。如果您了解 4chan 的性质,那么您就知道图像的性质(即“山雀或 GTFO”)。

我了解 PHP 和 Python,但愿意探索其他解决方案。在 Windows 上运行的独立软件也可以运行。

非常感谢!

【问题讨论】:

标签: php python image


【解决方案1】:

阅读 GIF89A 规范并提取信息。 http://www.w3.org/Graphics/GIF/spec-gif89a.txt

或者简单而懒惰并准备好使用intergif程序,它可以从动画gif中提取单个图像。解压到一个临时目录,看看你得到了多少文件。 http://utter.chaos.org.uk/~pdh/software/intergif/download.htm

【讨论】:

    【解决方案2】:

    PHP docs 页面上给出了imagecreatefromgif 函数的一些解决方案。

    从我读过的解决方案来看,这似乎是最好的,因为它的内存要求更严格。

    <?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)
    
        // 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#s', $chunk, $matches);
        }
        fclose($fh);
        return $count > 1;
    }
    ?>
    

    【讨论】:

    • 警告!这段 sn-p 代码有一个重大错误。 while 语句应包含括号,否则此函数将无法捕获所有动画 gif。尽管它会捕获一些(当然,这比失败 100% 更糟糕)。在这个 stackoverflow url stackoverflow.com/questions/280658/… 上查看完整和固定的代码
    • 事实上,这个脚本有一个更新版本,它修复了一个可能由 photoshop cs5 动画 gif 造成的错误。看到它here
    【解决方案3】:

    我从来没有见过能告诉你这个的程序。但是 GIF 是一种块结构格式,您可以检查指示动画 GIF 的块是否存在于您的文件中。

    来自下面提到的维基百科文章:在偏移量 0x30D 处,GIF 文件中的应用程序扩展(即:3 字节幻数 21 FF 0B)块,后跟幻数 4E 45 54 53 43 41 50 45 32 9 在偏移量 0x310 表示文件的其余部分包含多张图片,它们应该是动画的。

    真的,Wikipedia 文章解释得更好,下面提到的格式文档扩展了 Wiki 文章。

    因此,您可以使用 Python 编写的程序解析 GIF(我多年前使用 C 解析 GIF,主要是移动文件指针和读取字节的练习)。确定 AE 是否存在正确的 3 字节 ID,后跟 9 字节幻数。

    http://en.wikipedia.org/wiki/Graphics_Interchange_Format#Animated_.gif

    另见http://www.martinreddy.net/gfx/2d/GIF87a.txt

    另见http://www.martinreddy.net/gfx/2d/GIF89a.txt

    对不起,我能为你做最好的。

    【讨论】:

      【解决方案4】:

      我不是 GIF 文件格式专家,但这对我来说是一个有趣的问题,所以我稍微研究了一下。仅当动画 gif 在位置 0x310(编辑)处具有值 NETSCAPE2.0 并且静态 gif 没有(/edit)始终是正确的情况下才有效,这在我的测试文件中就是这种情况。这是 C#,如果你愿意,我可以将它编译成一个以目录为参数的控制台应用程序,你可以对你非常大的 gif 集合运行一些测试,看看它是否会产生可靠的结果。

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Xml.Linq;
      using System.IO;
      
      namespace ConsoleApplication2
      {
          class Program
          {
              static void Main(string[] args)
              {
                  string ani = @"C:\path\to\ani.gif";
                  string sta = @"C:\path\to\static.gif";
      
                  Console.WriteLine(isAnimated(ani));
                  Console.WriteLine(isAnimated(sta));
              }
      
              static bool isAnimated(string path)
              {
                  byte[] bytes = File.ReadAllBytes(path);
                  byte[] netscape = bytes.Skip(0x310).Take(11).ToArray();
      
                  StringBuilder sb = new StringBuilder();
      
                  foreach (var item in netscape)
                  {
                      sb.Append((char)item);
                  }
      
                  return sb.ToString() == "NETSCAPE2.0";
              }
          }
      }
      

      【讨论】:

      • 不是完整的检查。虽然有用并不适用于所有 gif。
      【解决方案5】:

      如果您使用的是 Linux(或任何带有 ImageMagick 的系统),您可以使用单行 shell 脚本和 identify 程序:

      identify *.gif | fgrep '.gif[1] '
      

      我知道你说你更喜欢 PHP 和 Python,但你也说你愿意探索其他解决方案。 :)

      【讨论】:

        【解决方案6】:

        使用 Python 和 PIL:

        from PIL import Image
        gif = Image.open('path.gif')
        try:
            gif.seek(1)
        except EOFError:
            isanimated = False
        else:
            isanimated = True
        

        【讨论】:

        • 感谢您的提示。 Tiny nit:如果我没记错的话,将图像返回 0 可能很重要,因为即使在 except 块中,gif.tell() 也会返回 1。
        • 使用getIteratorIndexphp.net/manual/en/imagick.getiteratorindex.php这个函数其实很简单
        • 错了。如果图像文件的格式是MPO。上面的例子也可以。
        • 这些天(枕头==4.3.0)你可以简单地做from PIL import Image; Image.open('animated.gif').is_animated
        【解决方案7】:

        试试这个

        import Image
        
        def checkAnimate():
            im = Image.open('image.gif')
            if not im.info['version'].__contains__('GIF'):
                print "It's not a GIF file"
            else:
                if im.info.has_key('duration'):
                    if im.info['duration'] > 0:
                        return True
                    else:
                        return False
                else:
                    return False
        

        【讨论】:

          【解决方案8】:

          查看 GIF 文件中是否存在多个 LocalDescriptor。

          【讨论】:

          • 请扩展您的答案。这更像是一条评论。
          【解决方案9】:
          from PIL import Image
          fp = open('1.gif', 'rb')
          im = Image.open(fp)
          is_gif = bool(im.format and im.format.upper() == 'GIF')
          

          【讨论】:

          • 虽然此代码可能会回答问题,但提供有关 why 和/或 如何 回答问题的额外上下文将显着改善其长期价值。请edit你的答案添加一些解释。
          【解决方案10】:

          Pillow 2.9.0 added is_animated:

          这将添加属性is_animated,以检查图像是否具有多个图层或框架。

          示例用法:

          from PIL import Image
          print(Image.open("test.gif").is_animated)
          

          【讨论】:

          • 这仅适用于 GIF。如果我用另一个图像(如 JPEG)检查它,我会得到一个异常。
          • @Uri 可能是一个应该向开发人员报告的错误。
          【解决方案11】:

          ImageMagick 函数 getNumberImages 将为您完成这项工作。因为它返回对象中的图像数量。 Imagick::getNumberImages

          <?php
          
          $image = new Imagick( YOUR_FILE );
          
          if ( $image->getNumberImages() ) {
              echo "It's animated";
          }
          

          【讨论】:

            【解决方案12】:

            为了那些不想依赖像 Pillow 这样的第三方模块的人的利益,这里是一个完全 python 2 和 3 原生的解决方案:

            import sys
            is_py2 = sys.version_info[0] == 2
            
            
            def is_animated_gif(image_path):
                """Return true if image is an animated gif
            
                primarily used this great deep dive into the structure of an animated gif
                to figure out how to parse it:
            
                    http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
            
                Other links that also helped:
            
                    https://en.wikipedia.org/wiki/GIF#Animated_GIF
                    https://www.w3.org/Graphics/GIF/spec-gif89a.txt
                    https://stackoverflow.com/a/1412644/5006
            
                :param image_path: string, assumed to be a path to a gif file
                :returns: boolean, True if the image is an animated gif
                """
                ret = False
                image_count = 0
            
                def skip_color_table(fp, packed_byte):
                    """this will fp.seek() completely passed the color table"""
                    if is_py2:
                        packed_byte = int(packed_byte.encode("hex"), 16)
            
                    has_gct = (packed_byte & 0b10000000) >> 7
                    gct_size = packed_byte & 0b00000111
            
                    if has_gct:
                        global_color_table = fp.read(3 * pow(2, gct_size + 1))
            
                def skip_image_data(fp):
                    """skips the image data, which is basically just a series of sub blocks
                    with the addition of the lzw minimum code to decompress the file data"""
                    lzw_minimum_code_size = fp.read(1)
                    skip_sub_blocks(fp)
            
                def skip_sub_blocks(fp):
                    """skips over the sub blocks
            
                    the first byte of the sub block tells you how big that sub block is, then
                    you read those, then read the next byte, which will tell you how big
                    the next sub block is, you keep doing this until you get a sub block
                    size of zero"""
                    num_sub_blocks = ord(fp.read(1))
                    while num_sub_blocks != 0x00:
                        fp.read(num_sub_blocks)
                        num_sub_blocks = ord(fp.read(1))
            
                with open(image_path, "rb") as fp:
                    header = fp.read(6)
                    if header == b"GIF89a": # GIF87a doesn't support animation
                        logical_screen_descriptor = fp.read(7)
                        skip_color_table(fp, logical_screen_descriptor[4])
            
                        b = ord(fp.read(1))
                        while b != 0x3B: # 3B is always the last byte in the gif
                            if b == 0x21: # 21 is the extension block byte
                                b = ord(fp.read(1))
                                if b == 0xF9: # graphic control extension
                                    block_size = ord(fp.read(1))
                                    fp.read(block_size)
                                    b = ord(fp.read(1))
                                    if b != 0x00:
                                        raise ValueError("GCT should end with 0x00")
            
                                elif b == 0xFF: # application extension
                                    block_size = ord(fp.read(1))
                                    fp.read(block_size)
                                    skip_sub_blocks(fp)
            
                                elif b == 0x01: # plain text extension
                                    block_size = ord(fp.read(1))
                                    fp.read(block_size)
                                    skip_sub_blocks(fp)
            
                                elif b == 0xFE: # comment extension
                                    skip_sub_blocks(fp)
            
                            elif b == 0x2C: # Image descriptor
                                # if we've seen more than one image it's animated
                                image_count += 1
                                if image_count > 1:
                                    ret = True
                                    break
            
                                # total size is 10 bytes, we already have the first byte so
                                # let's grab the other 9 bytes
                                image_descriptor = fp.read(9)
                                skip_color_table(fp, image_descriptor[-1])
                                skip_image_data(fp)
            
                            b = ord(fp.read(1))
            
                return ret
            

            is_animated_gif() 函数通过跳过所有扩展名和颜色信息并计算文件中的实际图像来工作,当它找到第二张图像时,它可以安全地假定 gif 动画并且它的工作已经完成。

            它不依赖于任何快捷方式,例如检查应用程序扩展块是否存在,因为看起来动画 gif 不需要这些快捷方式,我不想假设任何事情。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-04-10
              • 2021-01-13
              • 2015-02-08
              • 2014-10-25
              • 1970-01-01
              • 1970-01-01
              • 2011-12-10
              相关资源
              最近更新 更多