The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

# Test resizing on all image/audio files found within a directory tree
# Also checks for large memory use

use strict;
use lib qw(blib/lib blib/arch);

$|++;

use Audio::Scan 0.84;
use Image::Scale;
use File::Find;
use Proc::ProcessTable;

use constant MEMORY_LIMIT   => 1024 * 1024;
use constant LEAK_THRESHOLD => 5;

my $check_leaks = 1;

my $dir = shift || die "Usage: $0 directory\n";

my $t = Proc::ProcessTable->new( cache_ttys => 1 );
my $before;
$check_leaks && ($before = size_of());

find( {
    wanted => sub {
        # Skip ._* files OSX leaves around
        return if /\._/;
        
        my $ok;
        
        if ( Audio::Scan->is_supported($File::Find::name) ) {
            $ok = scan_audio($File::Find::name);
        }
        elsif ( /\.(jpg|gif|png|bmp)$/i ) {
            # Image file
            $ok = scan_image($File::Find::name);
        }
        else {
            return;
        }
        
        if ($check_leaks && $ok) {
            my $after = size_of();
            if ( ($after - $before) > LEAK_THRESHOLD ) {
                print " *** grew " . ($after - $before) . " KB\n";
            }
            $before = $after;
        }        
    },
    no_chdir => 1,
}, $dir );

sub scan_audio {
    my $file = shift;
    
    my $offset;
    my $length;
    
    local $ENV{AUDIO_SCAN_NO_ARTWORK} = 1;
    
    my $s = Audio::Scan->scan_tags($file);
    my $tags = $s->{tags};
    
    # MP3, other files with ID3v2
	if ( my $pic = $tags->{APIC} ) {
		if ( ref $pic->[0] eq 'ARRAY' ) {
			# multiple images, return image with lowest image_type value
			$length = ( sort { $a->[1] <=> $b->[1] } @{$pic} )[0]->[3];
			$offset = ( sort { $a->[1] <=> $b->[1] } @{$pic} )[0]->[4];
		}
		else {
		    $length = $pic->[3];
		    $offset = $pic->[4];
		}
		
		if ( $length && !$offset ) {
		    warn "No offset for image length $length: $file\n";
		    return;
	    }
	}
	
	# FLAC/Ogg picture block
	elsif ( $tags->{ALLPICTURES} ) {
		$length = ( sort { $a->{picture_type} <=> $b->{picture_type} } @{ $tags->{ALLPICTURES} } )[0]->{image_data};
		$offset = ( sort { $a->{picture_type} <=> $b->{picture_type} } @{ $tags->{ALLPICTURES} } )[0]->{offset};
	}
	
	# ALAC/M4A
	elsif ( $tags->{COVR} ) {
		$length = $tags->{COVR};
		$offset = $tags->{COVR_offset};
	}
	
	# WMA
    elsif ( my $pic = $tags->{'WM/Picture'} ) {
		if ( ref $pic eq 'ARRAY' ) {
			# return image with lowest image_type value
			$length = ( sort { $a->{image_type} <=> $b->{image_type} } @{$pic} )[0]->{image};
			$offset = ( sort { $a->{image_type} <=> $b->{image_type} } @{$pic} )[0]->{offset};
		}
		else {
		    $length = $pic->{image};
		    $offset = $pic->{offset};		}
	}
	
	# APE
	elsif ( $tags->{'COVER ART (FRONT)'} ) {
		$length = $tags->{'COVER ART (FRONT)'};
		$offset = $tags->{'COVER ART (FRONT)_offset'};
	}
	
	return if !$length && !$offset;
	
	print "$file ($length @ $offset)... ";
	
	# resize
	my $im = Image::Scale->new( $file, { offset => $offset, length => $length } );
	if ($im) {
	    my $ok;
	    print $im->width . 'x' . $im->height . ' ';
	    $ok = eval { $im->resize_gd_fixed_point( { width => 100, memory_limit => MEMORY_LIMIT } ) };
	    if ($@) {
	        print "resize failed ($@)";
	        sleep 5;
        }
        elsif (!$ok) {
            print "resize failed";
        }
        else {
            print "ok";
        }
    }
    else {
        print "read failed";
        sleep 5;
    }
	
	print "\n";
	
	return 1;
}

sub scan_image {
    my $file = shift;
    
    print "$file... ";
    
    my $im = Image::Scale->new($file);
	if ($im) {
	    my $ok;
	    print $im->width . 'x' . $im->height . ' ';
	    $ok = eval { $im->resize_gd_fixed_point( { width => 100, memory_limit => MEMORY_LIMIT } ); };
	    if ($@) {
	        print "resize failed ($@)";
	        sleep 5;
        }
        elsif (!$ok) {
            print "resize failed";
        }
        else {
            print "ok";
        }
    }
    else {
        print "read failed";
        sleep 5;
    }
    
    print "\n";
    
    return 1;
}

sub size_of {
    my $pid = shift || $$;
    
    foreach my $p ( @{ $t->table } ) {
        if ( $p->pid == $pid ) {
            return $p->rss;
        }
    }
    
    die "Pid $pid not found?";
}