Bash script check if image is animated png (apng)

前端 未结 3 925
我寻月下人不归
我寻月下人不归 2021-01-23 04:20

Trying to figure out a way in my bash script to check if a file is an animated PNG (apng) file. In my case I want to ignore it if it is . Any ideas ?

UPDATE: The answer

相关标签:
3条回答
  • 2021-01-23 05:05

    An animated PNG is characterised by the presence of an acTL (Animation Control Chunk), and fcTL (Frame Control Chunks) - see Wikipedia article.

    So, I think a suitable test would be to run pngcheck with the verbose option, and look for at least the acTL chunk:

    pngcheck -v animated.png | grep -E "acTL|fcTL"
    

    Sample Output

      chunk acTL at offset 0x00025, length 8
      chunk fcTL at offset 0x00039, length 26
      chunk fcTL at offset 0x02f27, length 26
      chunk fcTL at offset 0x05901, length 26
      chunk fcTL at offset 0x083a2, length 26
      chunk fcTL at offset 0x0aea8, length 26
      chunk fcTL at offset 0x0d98c, length 26
      chunk fcTL at offset 0x10406, length 26
      chunk fcTL at offset 0x12e19, length 26
      chunk fcTL at offset 0x15985, length 26
      chunk fcTL at offset 0x185e2, length 26
      chunk fcTL at offset 0x1b2b0, length 26
      chunk fcTL at offset 0x1dfe1, length 26
      chunk fcTL at offset 0x20d24, length 26
      chunk fcTL at offset 0x23a03, length 26
      chunk fcTL at offset 0x26663, length 26
      chunk fcTL at offset 0x29218, length 26
      chunk fcTL at offset 0x2bcdf, length 26
      chunk fcTL at offset 0x2e7e0, length 26
      chunk fcTL at offset 0x312b0, length 26
      chunk fcTL at offset 0x33c51, length 26
      chunk fcTL at offset 0x36598, length 26
      chunk fcTL at offset 0x38f49, length 26
      chunk fcTL at offset 0x3b9bd, length 26
      chunk fcTL at offset 0x3e45e, length 26
      chunk fcTL at offset 0x40ed9, length 26
      chunk fcTL at offset 0x4393c, length 26
      chunk fcTL at offset 0x46521, length 26
      chunk fcTL at offset 0x4919b, length 26
      chunk fcTL at offset 0x4bde2, length 26
      chunk fcTL at offset 0x4eabd, length 26
      chunk fcTL at offset 0x51827, length 26
      chunk fcTL at offset 0x5453a, length 26
      chunk fcTL at offset 0x571c7, length 26
      chunk fcTL at offset 0x59d94, length 26
    

    So, that would suggest this test in a script:

    # Test an animated image, `grep` exit status is zero meaning `acTL` was found
    pngcheck -v animated.png | grep -q "acTL"
    echo $?
    0
    
    # Test a still image, `grep` exit status is 1 meaning `acTL` was not found
    pngcheck -v still.png | grep -q "acTL"
    echo $?
    1
    

    If you don't have, or don't want to ship pngcheck with your project, I made a little Perl script that just de-chunks a PNG file and tells you the chunks and offsets and it should run anywhere since it is Perl. You are welcome to use it.

    Sample Run

    ./pngchunks ball.png
    
    13 IHDR
    8 acTL                   <--- This one means it is animated
    26 fcTL
    4634 IDAT
    26 fcTL
    4344 fdAT
    26 fcTL
    4042 fdAT
    26 fcTL
    3828 fdAT
    26 fcTL
    3521 fdAT
    26 fcTL
    3168 fdAT
    26 fcTL
    2777 fdAT
    26 fcTL
    2588 fdAT
    26 fcTL
    2720 fdAT
    26 fcTL
    2792 fdAT
    26 fcTL
    2665 fdAT
    26 fcTL
    2581 fdAT
    26 fcTL
    2652 fdAT
    26 fcTL
    2774 fdAT
    26 fcTL
    2844 fdAT
    26 fcTL
    2886 fdAT
    26 fcTL
    2966 fdAT
    26 fcTL
    3197 fdAT
    26 fcTL
    3518 fdAT
    26 fcTL
    3995 fdAT
    0 IEND
    

    #!/usr/bin/perl -w
    ################################################################################
    # pngchunks
    # Mark Setchell
    #
    # Simple Perl tool to read the chunks in a PNG image file
    # See https://en.wikipedia.org/wiki/Portable_Network_Graphics
    #
    # Usage: pngchunks image.png
    ################################################################################
    use strict;
    use Fcntl qw( SEEK_CUR );
    
    my $f=shift or die "Usage: pngchunks image.png\n";
    
    my ($handle,$offset,$buffer,$type,$length);
    
    # Open file
    open($handle,'<',$f) || die("Error opening file\n");
    
    # Check 8 byte PNG signature
    read($handle,$buffer,8);
    if(substr($buffer,0,8) ne "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"){
       die("ERROR: Invalid PNG signature\n");
    }
    
    # Loop till IEND chunk
    for(;;){
    
       # Read 4 bytes of length, Network (big-endian)
       read($handle,$buffer,4);
       $length=unpack("N",$buffer);
    
       # Read 4 bytes of chunk type
       read($handle,$buffer,4);
       $type=substr($buffer,0,4);
    
       printf("%d %s\n",$length,$type);
    
       # Break out of loop if IEND chunk
       last if $type eq "IEND";
    
       # Seek past this chunk and its 4 byte CRC
       $offset=4+$length;
       seek($handle,$offset,SEEK_CUR);
    }
    
    0 讨论(0)
  • 2021-01-23 05:05

    First extract the extension of your file using expr.

    Check http://tldp.org/LDP/abs/html/string-manipulation.html:

    expr match "$string" '.*\($substring\)'

    Extracts $substring at end of $string, where $substring is a regular expression.

    Then compare extracted extension with ".png"

    0 讨论(0)
  • 2021-01-23 05:21

    I don't see a "good" way to do this, so I'm going to cheat. First, my attempts:

    Two test images: An elephant and a ball. They're actually quite distracting, so I'll let you open them separately rather than including them here (I just burned a whole minute staring blankly at them).

    Let's rename them elephant.apng and ball.apng for simplicity.

    $ file elephant.apng ball.apng
    elephant.apng: PNG image data, 480 x 400, 8-bit/color RGBA, non-interlaced
    ball.apng:     PNG image data, 100 x 100, 8-bit/color RGBA, non-interlaced
    
    $ file -b --mime-type elephant.apng ball.apng
    image/png
    image/png
    
    $ identify elephant.apng ball.apng
    elephant.apng PNG 480x400 480x400+0+0 8-bit sRGB 379KB 0.000u 0:00.000
    ball.apng PNG 100x100 100x100+0+0 8-bit sRGB 65.6KB 0.000u 0:00.000
    
    $ strings elephant.apng |grep -i png
    APNG Assembler 2.8Q\
    
    $ strings ball.apng |grep -i png
    
    $
    

    So libmagic (File 5.32, either without options or by requesting a MIME type) and identify (ImageMagick 6.9.7-4) and strings (GNU Binutils 2.29.1) with grep (GNU grep 3.1) all fail to find anything aside from a watermark left by APNG Assembler, which can't be guaranteed.


    Now let's cheat. I'm going to assume that lots of entropy means it's an animated PNG (rather than something else, like a steganographic message) and merely run this through ImageMagic convert to turn it into a standard PNG:

    $ convert elephant.apng elephant.png
    $ convert ball.apng ball.png
    $ wc -c *
     65557 ball.apng
      5239 ball.png
    379013 elephant.apng
     21068 elephant.png
    470877 total
    

    Okay, we can work with that:

    #!/bin/sh
    
    for image in "$@"; do
      size1="$(wc -c "$image")"
      size2="$(convert "$image" -format PNG - |wc -c)"
      if [ "$size1" -ge "$((size2*2))" ]
        then echo "$image: Animated or steganographic PNG"
        else echo "$image: Basic PNG"
      fi
    done
    

    This sets size1 to the size of the image in bytes (using wc -c to get the character count), then sets size2 to the size of the image after "converting" it to a PNG (- tells convert to push the final image to standard output and wc reads that in). If size1 is at least twice as big as size2 then we assume it's an animated PNG.

    That's in a loop, so you can call it on any number of image files (even non-PNGs, though that doesn't make much sense and you'll always be told it's a PNG of some sort). Note, you can't pipe into it because it'd absorb standard input on the first wc call and have nothing left over for the conversion.


    If you have MediaInfo installed (e.g. apt install mediainfo), you can (temporarily!) detect something amiss with this command:

    $ mediainfo elephant.apng |grep ^FileExtension_Invalid
    FileExtension_Invalid                    : png pns
    
    $ mediainfo ball.apng |grep ^FileExtension_Invalid
    FileExtension_Invalid                    : png pns
    

    I'm not exactly sure what that means, but it doesn't show up on my converted PNGs, so it might be safe … at least until MediaInfoLib actually support APNG file formats (this is why I said "temporarily"). This works in v0.7.99 at least.

    Invoke as grep -q to make that silent for your if tests.

    0 讨论(0)
提交回复
热议问题