diff -rbc FuzzyOcr-2.3g/CHANGES FuzzyOcr-2.3h/CHANGES *** FuzzyOcr-2.3g/CHANGES Thu Sep 14 14:56:06 2006 --- FuzzyOcr-2.3h/CHANGES Mon Sep 18 15:57:59 2006 *************** *** 1,3 **** --- 1,41 ---- + version 2.3h: + Require: + New Perl Module + Image::Magick; + Added: + Option: 'focr_anim_delay' Default: 100 + This option is used with animated GIF files, and keeps all images + that are displayed for at least 1 sec. + + Option: 'focr_anim_max_frames' Default: 2 + This option is used with animated GIF files, and keeps top N + largest frames. + + Fixed: + Option: 'focr_digest_hash' + Fixed internal parameter to reflect option from original plugin (Thanks Bill). + + Option: 'focr_db_hash' + Updated FuzzyOcr.cf to reflect plugin option. + + Option: 'focr_db_safe' + Updated FuzzyOcr.cf to reflect plugin option. + + Removed: + Option: 'focr_bin_identify' + Option: 'focr_bin_convert' + These options are no longer valid, since the external programs are no longer called + in favor of using PERL module. Makes things 'simpler'. + + Option: 'focr_bin_gifasm' + Option: 'focr_bin_tifftopnm' + external program not used anymore. + + Changed: + The plugin now uses Image::Magick module to access ImageMagick functions from PERL instead + of accessing external programs. This makes for fewer system calls to run external programs. + (Idea from Eric Yiu) + version 2.3g: Added: Option: focr_keep_bad_images diff -rbc FuzzyOcr-2.3g/FuzzyOcr.cf FuzzyOcr-2.3h/FuzzyOcr.cf *** FuzzyOcr-2.3g/FuzzyOcr.cf Thu Sep 14 10:35:24 2006 --- FuzzyOcr-2.3h/FuzzyOcr.cf Mon Sep 18 16:11:02 2006 *************** *** 36,64 **** ##### Location of helper applications (path + binary) (Default values: /usr/bin/) ##### #focr_bin_giffix /usr/bin/giffix #focr_bin_giftext /usr/bin/giftext - #focr_bin_gifasm /usr/bin/gifasm #focr_bin_gifinter /usr/bin/gifinter #focr_bin_giftopnm /usr/bin/giftopnm #focr_bin_jpegtopnm /usr/bin/jpegtopnm #focr_bin_pngtopnm /usr/bin/pngtopnm #focr_bin_bmptopnm /usr/bin/bmptopnm - #focr_bin_tifftopnm /usr/bin/tifftopnm #focr_bin_ppmhist /usr/bin/ppmhist - #focr_bin_convert /usr/bin/convert - #focr_bin_identify /usr/bin/identify #focr_bin_gocr /usr/bin/gocr # - # Use this option to search for all of the above utilitites using the following search order (path like): - # This reduces typing errors, and allows to have more than one version installed in the system, and - # give priority to selected dirs. #focr_path_bin /usr/local/netpbm/bin:/usr/local/bin:/usr/bin # ############################################################################################ ! ##### Scansets, comma seperated (Default value: $gocr -i $pfile, $gocr -l 180 -d 2 -i $pfile) ##### # Each scanset consists of one or more commands which make text out of pnm input. # Each scanset is run seperately on the PNM data, results are combined in scoring. ! #focr_scansets $gocr -i $pfile, $gocr -l 180 -d 2 -i $pfile, $gocr -l 140 -d 2 -i $pfile # # To use only one scan with default values, uncomment the next line instead #focr_scansets $gocr -i - --- 36,57 ---- ##### Location of helper applications (path + binary) (Default values: /usr/bin/) ##### #focr_bin_giffix /usr/bin/giffix #focr_bin_giftext /usr/bin/giftext #focr_bin_gifinter /usr/bin/gifinter #focr_bin_giftopnm /usr/bin/giftopnm #focr_bin_jpegtopnm /usr/bin/jpegtopnm #focr_bin_pngtopnm /usr/bin/pngtopnm #focr_bin_bmptopnm /usr/bin/bmptopnm #focr_bin_ppmhist /usr/bin/ppmhist #focr_bin_gocr /usr/bin/gocr # #focr_path_bin /usr/local/netpbm/bin:/usr/local/bin:/usr/bin # ############################################################################################ ! ##### Scansets, comma seperated (Default value: $gocr -i -, $gocr -l 180 -d 2 -i -) ##### # Each scanset consists of one or more commands which make text out of pnm input. # Each scanset is run seperately on the PNM data, results are combined in scoring. ! focr_scansets $gocr -i $pfile, $gocr -l 180 -d 2 -i $pfile, $gocr -l 140 -d 2 -i $pfile # # To use only one scan with default values, uncomment the next line instead #focr_scansets $gocr -i - *************** *** 77,83 **** # Default detection treshold (see manual) (Default value: 0.3) (Can be changed on a per word basis in the wordlist). #focr_threshold 0.3 # ! # This is the score for a hit after focr_counts_required matches (Default value: 5) #focr_base_score 5 # # This is the additional score for every additional match after focr_counts_required matches (Default value: 1) --- 70,76 ---- # Default detection treshold (see manual) (Default value: 0.3) (Can be changed on a per word basis in the wordlist). #focr_threshold 0.3 # ! # This is the score for a hit after focr_counts_required matches #focr_base_score 5 # # This is the additional score for every additional match after focr_counts_required matches (Default value: 1) *************** *** 93,102 **** #focr_corrupt_unfixable_score 5 # # This is used to disable the OCR engine if the message has already more points than this value (Default value: 10) ! #focr_autodisable_score 25 # # Number of minimum matches before the rule scores (Default value: 2) ! #focr_counts_required 2 # # Specifies, how many frames an animated gif must contain, so the second (less resource consuming) animated gif test is used. (Default value: 5) #focr_gif_max_frames 5 --- 86,95 ---- #focr_corrupt_unfixable_score 5 # # This is used to disable the OCR engine if the message has already more points than this value (Default value: 10) ! #focr_autodisable_score 20 # # Number of minimum matches before the rule scores (Default value: 2) ! #focr_counts_required 3 # # Specifies, how many frames an animated gif must contain, so the second (less resource consuming) animated gif test is used. (Default value: 5) #focr_gif_max_frames 5 diff -rbc FuzzyOcr-2.3g/FuzzyOcr.pm FuzzyOcr-2.3h/FuzzyOcr.pm *** FuzzyOcr-2.3g/FuzzyOcr.pm Thu Sep 14 14:53:02 2006 --- FuzzyOcr-2.3h/FuzzyOcr.pm Mon Sep 18 16:08:47 2006 *************** *** 52,79 **** use Mail::SpamAssassin::Plugin; use String::Approx 'adistr'; ! use MLDBM qw(DB_File Storable); use FileHandle; use Fcntl ':flock'; our @ISA = qw (Mail::SpamAssassin::Plugin); - our @err_msges = ( - "Failed to open pipe to external programs with pipe command \"%s\". - Please check that all helper programs are installed and in the correct path. - (Pipe Command \"%s\", Pipe exit code %d (\"%s\"), Temporary file: \"%s\")", - "Unexpected error in pipe to external programs. - Please check that all helper programs are installed and in the correct path. - (Pipe Command \"%s\", Pipe exit code %d (\"%s\"), Temporary file: \"%s\")", - "Cannot open \"%s\" to read previously produced data! - (Previously used pipe: \"%s\", error code %d (\"%s\"), Temporary file: \"%s\")", - "Unexpected error while trying executing gocr with arguments \"%s\". - Make sure the gocr location is specified correctly and the arguments are correct.", - "Failed to open global wordlist \"%s\" for reading. - Please check that path and permissions are correct." - ); - our %App = (); our %Option = (); our %Score = (); --- 52,64 ---- use Mail::SpamAssassin::Plugin; use String::Approx 'adistr'; ! use Image::Magick; use MLDBM qw(DB_File Storable); use FileHandle; use Fcntl ':flock'; our @ISA = qw (Mail::SpamAssassin::Plugin); our %App = (); our %Option = (); our %Score = (); *************** *** 83,90 **** our $pms; our @scansets; ! our @bin_utils = qw/giffix giftext gifasm gifinter giftopnm gif2anim ! jpegtopnm pngtopnm bmptopnm tifftopnm ppmhist convert identify gocr/; our @pgm_scores = qw/base add corrupt corrupt_unfixable wrongctype autodisable/; --- 68,75 ---- our $pms; our @scansets; ! our @bin_utils = qw/giffix giftext gifinter giftopnm ! jpegtopnm pngtopnm bmptopnm ppmhist gocr/; our @pgm_scores = qw/base add corrupt corrupt_unfixable wrongctype autodisable/; *************** *** 92,104 **** our @pgm_opts = qw/personal_wordlist global_wordlist logfile threshold counts_required verbose timeout gif_max_frames db_hash db_safe db_max_days path_bin scansets keep_bad_images enable_image_hashing digest_db hashing_learn_scanned/; our @paths = qw(/usr/local/netpbm/bin /usr/local/bin /usr/bin); # Default values $Option{threshold} = 0.3; ! $Option{counts_required} = 5; $Option{verbose} = 1; $Option{timeout} = 10; $Option{gif_max_frames} = 5; --- 77,90 ---- our @pgm_opts = qw/personal_wordlist global_wordlist logfile threshold counts_required verbose timeout gif_max_frames db_hash db_safe db_max_days path_bin scansets keep_bad_images + anim_delay anim_max_frames enable_image_hashing digest_db hashing_learn_scanned/; our @paths = qw(/usr/local/netpbm/bin /usr/local/bin /usr/bin); # Default values $Option{threshold} = 0.3; ! $Option{counts_required} = 2; $Option{verbose} = 1; $Option{timeout} = 10; $Option{gif_max_frames} = 5; *************** *** 112,117 **** --- 98,105 ---- $Option{db_safe} = "/etc/mail/spamassassin/FuzzyOcr.safe.db"; $Option{db_max_days} = 35; $Option{keep_bad_images} = 0; + $Option{anim_delay} = 100; + $Option{anim_max_frames} = 2; # Default scores $Score{base} = 4; *************** *** 302,308 **** sub load_global_words { unless ( -r $_[0] ) { ! handle_error( $err_msges[3], ( $_[0] ) ); return; } my $cnt = 0; --- 290,296 ---- sub load_global_words { unless ( -r $_[0] ) { ! debuglog("Cannot read Global wordlist: \"$_[0]\"\n Please check file path and permissions are correct."); return; } my $cnt = 0; *************** *** 329,336 **** return; } unless ( -r $_[0] ) { ! debuglog( ! "Unable to read from wordlist \"$_[0]\", please make sure that permissions are correct." ); return; } --- 317,323 ---- return; } unless ( -r $_[0] ) { ! debuglog("Cannot read from wordlist \"$_[0]\"\n Please make sure that permissions are correct." ); return; } *************** *** 360,370 **** else { return $_[0] } } - sub handle_error { - my ( $err_msg, @var_vals ) = @_; - debuglog(sprintf( $err_msg, @var_vals )); - } - sub within_threshold { my $digest = shift; my $record = shift; --- 347,352 ---- *************** *** 554,591 **** sub calc_image_hash { my $pfile = $_[0]; my ($rcode, $hash); - my $s = -s $pfile; ! foreach my $a (qw/identify ppmhist/) { unless (defined $App{$a}) { info("FuzzyOcr: calc_image_hash cannot exec $a"); return (1, ''); } } - my $t = Mail::SpamAssassin::Timeout->new({ secs => $Option{timeout} }); - my @stdout_data; ! $rcode = $t->run_and_catch(sub { ! @stdout_data = qx($App{identify} $pfile 2>/dev/null); ! }); ! if ($rcode) { ! chomp $rcode; ! debuglog("$App{identify}: Timed out [$rcode], skipping..."); ! return (1, ''); ! } ! my ($h,$w) = (0,0); ! foreach (@stdout_data) { ! if ($_ =~ /(\d+)x(\d+)/) { ! $h = $1; ! $w = $2; ! last; ! } ! } ! if ($h == 0 or $w == 0) { ! debuglog("Unable to determine size of image, skipping..."); ! return(1,''); ! } $rcode = $t->run_and_catch(sub { @stdout_data = qx($App{ppmhist} -noheader $pfile 2>/dev/null); }); --- 536,554 ---- sub calc_image_hash { my $pfile = $_[0]; my ($rcode, $hash); ! foreach my $a (qw/ppmhist/) { unless (defined $App{$a}) { info("FuzzyOcr: calc_image_hash cannot exec $a"); return (1, ''); } } ! my $img = new Image::Magick; ! my ($w,$h,$s,$t) = $img->ping($pfile); ! $t = Mail::SpamAssassin::Timeout->new({ secs => $Option{timeout} }); ! my @stdout_data; $rcode = $t->run_and_catch(sub { @stdout_data = qx($App{ppmhist} -noheader $pfile 2>/dev/null); }); *************** *** 782,788 **** if ($cnt == 0) { #debuglog("Skipping OCR, no image files found..."); ! removedir($imgdir) if defined $imgdir; return 0; } debuglog("Found: $cnt images"); $cnt = 0; --- 745,751 ---- if ($cnt == 0) { #debuglog("Skipping OCR, no image files found..."); ! removedir($imgdir) if (defined($imgdir) and ($Option{keep_bad_images}<2)); return 0; } debuglog("Found: $cnt images"); $cnt = 0; *************** *** 813,819 **** my $interlaced_gif = 0; my $image_count = 0; ! foreach my $a (qw/giftext giffix gifasm gifinter giftopnm convert/) { unless (defined $App{$a}) { debuglog("Cannot exec $a, skipping image"); next IMAGE; --- 776,782 ---- my $interlaced_gif = 0; my $image_count = 0; ! foreach my $a (qw/giftext giffix gifinter giftopnm/) { unless (defined $App{$a}) { debuglog("Cannot exec $a, skipping image"); next IMAGE; *************** *** 828,834 **** if ($retcode) { chomp $retcode; debuglog("$App{giftext} Timed out [$retcode], skipping..."); ! ++$imgerr if $Option{keep_bad_images}; next; } foreach (@stdout_data) { unless ($interlaced_gif) { --- 791,797 ---- if ($retcode) { chomp $retcode; debuglog("$App{giftext} Timed out [$retcode], skipping..."); ! ++$imgerr if $Option{keep_bad_images}>0; next; } foreach (@stdout_data) { unless ($interlaced_gif) { *************** *** 845,851 **** } else { debuglog("Image is single non-interlaced..."); ! $tfile .= ".fixed"; printf RAWERR "## $App{giffix} $file >$tfile 2>>$efile\n" if ($haserr>0); $retcode = $t->run_and_catch(sub { qx($App{giffix} $file >$tfile 2>>$efile); --- 808,814 ---- } else { debuglog("Image is single non-interlaced..."); ! $tfile .= "-fixed.gif"; printf RAWERR "## $App{giffix} $file >$tfile 2>>$efile\n" if ($haserr>0); $retcode = $t->run_and_catch(sub { qx($App{giffix} $file >$tfile 2>>$efile); *************** *** 854,860 **** chomp $retcode; debuglog("$App{giffix}: Timed out [$retcode], skipping..."); printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0); ! ++$imgerr if $Option{keep_bad_images}; next; } if (open ERR, $efile) { @stderr_data = ; --- 817,823 ---- chomp $retcode; debuglog("$App{giffix}: Timed out [$retcode], skipping..."); printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0); ! ++$imgerr if $Option{keep_bad_images}>0; next; } if (open ERR, $efile) { @stderr_data = ; *************** *** 885,949 **** if ($image_count gt 1) { debuglog("File contains more than one image..."); ! if ($image_count lt $Option{gif_max_frames}) { ! debuglog("Assembling images..."); ! my $cfile = $tfile; $tfile .= ".gif"; ! printf RAWERR qq(## $App{convert} $cfile -append >$tfile 2>>$efile\n) if ($haserr>0); ! $retcode = $t->run_and_catch(sub { ! qx($App{convert} $cfile -append >$tfile 2>>$efile); ! }); ! if ($retcode) { ! chomp $retcode; ! debuglog("$App{convert}: Timed out [$retcode], skipping..."); ! printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0); ! ++$imgerr if $Option{keep_bad_images}; next; ! } ! } else { debuglog("Image count exceeds limit, skipping some..."); ! my $app = $App{gifasm}; ! if (-x $App{gif2anim}) { ! $app = $App{gif2anim}; $tfile .= ".gif"; ! printf RAWERR qq(## $app $tfile\n) if ($haserr>0); ! $retcode = $t->run_and_catch(sub{ ! qx($app $tfile); ! }); ! } else { ! printf RAWERR qq(## $app -d $imgdir/out $tfile 2>>$efile\n) if ($haserr>0); ! $retcode = $t->run_and_catch(sub{ ! qx($app -d $imgdir/out $tfile 2>>$efile); ! }); ! } ! if ($retcode) { ! chomp $retcode; ! debuglog("$app: Timed out [$retcode], skipping..."); ! printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0); ! ++$imgerr if $Option{keep_bad_images}; next; ! }; ! my $fs = 0; ! if ($app eq $App{gifasm}) { ! foreach my $n (0 .. $image_count) { ! my $f = Mail::SpamAssassin::Util::untaint_file_path( ! sprintf("%s/out%02d",$imgdir,$n) ! ); ! my $s = -s $f || 0; ! $tfile = $f if ($fs < $s); ! } ! } else { ! my $base = $file; $base =~ s/\.\S+//; ! opendir TMP, $imgdir; ! my @files = grep {m/^${base}_\d{4}.gif$/i} readdir TMP; ! closedir TMP; ! foreach my $f (@files) { ! my $uf = Mail::SpamAssassin::Util::untaint_file_path($f); ! my $s = -s $uf || 0; ! $tfile = $f if ($fs < $s); ! } } } } if ($interlaced_gif) { ! my $cfile = $tfile; $tfile .= ".non"; printf RAWERR qq(## $App{gifinter} $cfile >$tfile 2>>$efile\n) if ($haserr>0); $retcode = $t->run_and_catch(sub{ qx($App{gifinter} $cfile >$tfile 2>>$efile); --- 848,887 ---- if ($image_count gt 1) { debuglog("File contains more than one image..."); ! my $img = new Image::Magick; ! $img->Read($tfile); ! if ($image_count gt $Option{gif_max_frames}) { debuglog("Image count exceeds limit, skipping some..."); ! my $num = $#{$img}; ! my %size = (); my %delay = (); ! my %imgs = (); ! foreach my $n (0 .. $num) { ! ($delay{$n},$size{$n}) = $img->[$n]->Get('delay','filesize'); ! $imgs{$n} = 0; ! } ! foreach my $k (keys %delay) { ! $imgs{$k}++ if ($delay{$k} ge $Option{anim_delay}); ! } ! my $cnt = 1; ! foreach my $k (sort {$size{$b} <=> $size{$a}} keys %size) { ! $imgs{$k}++; last if ++$cnt>$Option{anim_max_frames}; ! } ! foreach my $n (0 .. $num) { ! next if $imgs{$n} > 0; ! undef $img->[$n]; # Remove unwanted frames; } } + debuglog("Assembling images..."); + my $img2 = $img->Append(); + $tfile =~ s/gif$/-multi.gif/i; + open IMAGE, ">$tfile"; + $img2->Write(file=>\*IMAGE, filename=>$tfile); + close IMAGE; } if ($interlaced_gif) { ! debuglog("Processing interlaced_gif $tfile..."); ! my $cfile = $tfile; $tfile =~ s/gif$/-fixed.gif/i; printf RAWERR qq(## $App{gifinter} $cfile >$tfile 2>>$efile\n) if ($haserr>0); $retcode = $t->run_and_catch(sub{ qx($App{gifinter} $cfile >$tfile 2>>$efile); *************** *** 952,958 **** chomp $retcode; debuglog("$App{gifinter}: Timed out [$retcode], skipping..."); printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0); ! ++$imgerr if $Option{keep_bad_images}; next; } } --- 890,896 ---- chomp $retcode; debuglog("$App{gifinter}: Timed out [$retcode], skipping..."); printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0); ! ++$imgerr if $Option{keep_bad_images}>0; next; } } *************** *** 964,970 **** chomp $retcode; debuglog("$App{giftopnm}: Timed out [$retcode], skipping..."); printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0); ! ++$imgerr if $Option{keep_bad_images}; next; } } elsif ( substr($$pic{header},0,2) eq "\xff\xd8" ) { --- 902,908 ---- chomp $retcode; debuglog("$App{giftopnm}: Timed out [$retcode], skipping..."); printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0); ! ++$imgerr if $Option{keep_bad_images}>0; next; } } elsif ( substr($$pic{header},0,2) eq "\xff\xd8" ) { *************** *** 986,992 **** if ($retcode) { chomp $retcode; debuglog("$App{jpegtopnm}: Timed out [$retcode], skipping..."); ! ++$imgerr if $Option{keep_bad_images}; next; } } elsif ( substr($$pic{header},0,4) eq "\x89\x50\x4e\x47" ) { --- 924,930 ---- if ($retcode) { chomp $retcode; debuglog("$App{jpegtopnm}: Timed out [$retcode], skipping..."); ! ++$imgerr if $Option{keep_bad_images}>0; next; } } elsif ( substr($$pic{header},0,4) eq "\x89\x50\x4e\x47" ) { *************** *** 1009,1015 **** chomp $retcode; debuglog("$App{pngtopnm}: Timed out [$retcode], skipping..."); printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0); ! ++$imgerr if $Option{keep_bad_images}; next; } } elsif ( substr($$pic{header},0,2) eq "BM" ) { --- 947,953 ---- chomp $retcode; debuglog("$App{pngtopnm}: Timed out [$retcode], skipping..."); printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0); ! ++$imgerr if $Option{keep_bad_images}>0; next; } } elsif ( substr($$pic{header},0,2) eq "BM" ) { *************** *** 1032,1038 **** chomp $retcode; debuglog("$App{bmptopnm}: Timed out [$retcode], skipping..."); printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0); ! ++$imgerr if $Option{keep_bad_images}; next; } } elsif ( --- 970,976 ---- chomp $retcode; debuglog("$App{bmptopnm}: Timed out [$retcode], skipping..."); printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0); ! ++$imgerr if $Option{keep_bad_images}>0; next; } } elsif ( *************** *** 1044,1065 **** if ( $$pic{ctype} !~ /tiff/i ) { wrong_ctype( "TIFF", $$pic{ctype} ); } ! foreach my $a (qw/convert/) { ! unless (defined $App{$a}) { ! debuglog("Cannot exec $a, skipping image"); ! next IMAGE; ! } ! } ! printf RAWERR qq(## $App{convert} tiff:$file pnm:$pfile 2>>$efile\n) if ($haserr>0); ! $retcode = $t->run_and_catch(sub { ! qx($App{convert} tiff:$file pnm:$pfile 2>>$efile); ! }); ! if ($retcode) { ! chomp $retcode; ! debuglog("$App{convert}: Timed out [$retcode], skipping..."); ! printf RAWERR "?? Timed out > $retcode\n" if ($haserr>0); ! ++$imgerr if $Option{keep_bad_images}; next; } } else { debuglog("Image type not recognized, unknown format. Skipping this image..."); --- 982,995 ---- if ( $$pic{ctype} !~ /tiff/i ) { wrong_ctype( "TIFF", $$pic{ctype} ); } ! my $img = new Image::Magick; ! $img->Read($file); ! unless (open PNM, ">$pfile") { ! debuglog("Cannot create $pfile"); ! ++$imgerr if $Option{keep_bad_images}>0; next; } + $img->Write(file=>\*PNM, filename=>$pfile); + close PNM; } else { debuglog("Image type not recognized, unknown format. Skipping this image..."); *************** *** 1189,1195 **** } } } else { ! debuglog("Ignoring ".scalar(@hashes)." hashes, ".$cnt." words found!"); } if ($imgerr == 0 and $Option{keep_bad_images}<2) { removedir($imgdir); --- 1119,1125 ---- } } } else { ! debuglog("Ignoring ".scalar(@hashes)." hashes, only ".$cnt."/".$Option{counts_required}." words found!"); } if ($imgerr == 0 and $Option{keep_bad_images}<2) { removedir($imgdir);