4

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 below using pngcheck allowed me to check if the image was an animation. In addition I check the size of the file, if it's "large" I ignore it as well. Lastly, as of Nov 2017, "identify" does NOT work which was my initial issue mainly. Thanks Mark for your help.

Mike Q
  • 6,716
  • 5
  • 55
  • 62
  • Is that `.apng` or `.png`? – bhansa Nov 03 '17 at 21:15
  • Do these images have correct file extensions? For example, will an animated png always end with `.apng`? And will a generic png end with `.png`? Also, in what context are you checking for the file? Is this in a while loop while reading `ls -l` from `stdin`? – R4F6 Nov 03 '17 at 21:15
  • Try running `file YourImage.png` and `file YourAnimatedImage.png` to see if it can tell the difference. Failing that, try `pngcheck YourImage.png`. Failing that, try **ImageMagick** `identify YourImage.png` where there will be one line per frame - so animated files will have multiple line outputs. – Mark Setchell Nov 03 '17 at 21:19
  • I wish it were that easy .... An animated png files can contain : .apng or .png file ext... ex. http://apng.onevcat.com/assets/elephant.png – Mike Q Nov 03 '17 at 21:21
  • @MarkSetchell Good suggestion, I actually tried that, it doesn't seem to really help. I also tried identify -verbose but I'm not sure what is unique to apng – Mike Q Nov 03 '17 at 21:22
  • https://stackoverflow.com/questions/2227182/how-can-i-find-out-a-files-mime-typecontent-type. You want `file -b --mime-type image.png`. It should return `image/apng`. – R4F6 Nov 03 '17 at 21:24
  • Also, if you cant rely on MIME, I noticed this: `tail -c 50 elephant.png` gives: `EXtSoftwareAPNG Assembler 2.8Q\?IEND?B`?` So perhaps you can use tail to see if it contains EXtSoftwareAPNG? – R4F6 Nov 03 '17 at 21:29
  • @R4F6 – Great ideas, but those don't work reliably. See my answer below. – Adam Katz Nov 03 '17 at 23:02
  • Imagemagick does not know about APNG format, only MNG. – fmw42 Nov 03 '17 at 23:09

3 Answers3

6

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);
}
Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • Thank You! It should be noted that as of Nov 2017 "Identify" does not list this information. You went way beyond! I am using this in AMI instance and sadly the pngcheck command seems to be missing so I'm using an old RPM. – Mike Q Nov 06 '17 at 16:22
1

2022 Update: APNG support has improved.

To do this with ImageMagick (requires v6 at 6.9.11-31+ or else 7.0.10-31+, added 2020-09-20):

$ is_apng() { identify apng:"$1" |grep -Fq "$1[1]"; }

ImageMagick requires denoting that this is an APNG (it otherwise operates solely on the first frame). I'm seeking the second frame in this grep call (it's zero-indexed, so that's frame 1. -F disables regex and just does a plain string match, -q suppresses output). If it has multiple frames, it's animated.

To do this with ffmpeg (alternatively ffprobe, requires 2.7+):

$ is_apng() { ffmpeg -i "$1" 2>&1 |grep -q '^Input #[0-9]*, apng,'; }

Usage:

$ is_apng ball.apng
$ echo $?
0 # true

$ convert ball.apng ball.png
$ is_apng ball.png
$ echo $?
1 # false

I tried this on two test images: elephant.apng and ball.apng, plus their conversions to static PNG as noted above.


Original answer (trimmed; for the full investigation, methodology, mediainfo temporary solution, and why you can't use the watermark from the APNG Assembler, see revision 4 of this answer):

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:

#!/bin/sh

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

This compares the size of the image in bytes (using wc -c to get the character count) to twice 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 the original image is at least twice as big as its pure PNG conversion then we assume it's an animated (and/or steganographic) 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.

Adam Katz
  • 14,455
  • 5
  • 68
  • 83
  • I should have mentioned that this is a good option as well if needed. Thank you. I voted it up. – Mike Q May 11 '22 at 16:17
  • 1
    @MikeQ – I got it working with ImageMagick! I rewrote the answer with ImageMagick and ffmpeg solutions and shrank my original hack for brevity. – Adam Katz May 13 '22 at 22:07
0

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"

Community
  • 1
  • 1
  • 1
    No I don't just need to check if it has the ext , that isn't good enough ... You can accomplish all of this with ext="${file_name##*.}" – Mike Q Nov 03 '17 at 21:33