#!/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   => 20 * 1024 * 1024;
use constant LEAK_THRESHOLD => 5;

my $check_leaks = 0;

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 ) {
            print "$file ($length) no offset, using SV mode... ";

            return scan_audio_embedded($file);
        }
    }

    # 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;
}

# Let Audio::Scan extract the artwork, needed for some APIC tags
sub scan_audio_embedded {
    my $file = shift;

    my $image_data = '';
    my $length     = 0;

    local $ENV{AUDIO_SCAN_NO_ARTWORK} = 0;

    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
            $image_data = ( sort { $a->[1] <=> $b->[1] } @{$pic} )[0]->[3];
        }
        else {
            $image_data = $pic->[3];
        }

        $length = length $image_data;
    }

    if ( !$length ) {
        warn "  !!! No image data found, possible Audio::Scan bug?\n";
        sleep 5;
        return;
    }

    # resize
    my $im = Image::Scale->new( \$image_data );
    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?";
}
