imagedatadupes (29628B)
1 #!/usr/bin/env perl 2 3 # License: See the bottom of this file. 4 5 # NOTE: There might be various inaccuracies in the image decompression, but that's probably not an 6 # issue for any realistic scenario that this program is used for: 7 # https://stackoverflow.com/questions/25119285/weird-results-using-php-imagick-getimagesignature-method 8 9 use strict; 10 use warnings; 11 use Image::ExifTool qw(ImageInfo); 12 use Image::Magick; 13 use File::MimeInfo::Magic; 14 use Data::Compare; 15 use File::Spec::Functions qw(catfile); 16 use Pod::Usage; 17 use Getopt::Long; 18 use Cwd qw(realpath); 19 20 # ignored in comparison 21 # NOTE: If more common tags are added after a 1.0 release is tagged, they 22 # should go in a different array that can be enabled with a separate option 23 # to avoid breaking old commands that might have depended on the old list 24 # of ignored tags. 25 my @exif_ignore = ( 26 "FileInodeChangeDate", 27 "Directory", 28 "FileModifyDate", 29 "FileAccessDate", 30 "FileName", 31 "FileSize", 32 "FilePermissions", 33 ); 34 my $verbose = 0; 35 my $skip_mime_check = 0; 36 37 sub load_exif_data { 38 my $filename = shift; 39 my $info = ImageInfo($filename); 40 # Not too important since ImageMagick already should weed out unsupported filetypes 41 # ExifTool seems to print at least *something* (e.g. permissions, etc.) even for files 42 # that don't have any embedded tags (including directories), so there probably aren't 43 # any files that ImageMagick supports but ExifTool doesn't. 44 if (exists $info->{"Error"}) { 45 warn "WARNING: Ignoring $filename as ExifTool returned an error: $info->{Error}\n"; 46 return undef; 47 } elsif (exists $info->{"Warning"}) { 48 warn "WARNING: ExifTool returned a warning for $filename: $info->{Warning}\n"; 49 } 50 51 for my $ignore (@exif_ignore) { 52 delete $info->{$ignore}; 53 } 54 return $info; 55 } 56 57 # -1: error 58 # 0: doesn't matter which image is chosen 59 # 1: image1 should be chosen (includes more metadata than image2) 60 # 2: image2 should be chosen (includes more metadata than image1) 61 sub compare_exif_data { 62 # filenames are just for debugging 63 my ($info1, $info2, $filename1, $filename2) = @_; 64 # Make a new hash containing just the keys so we can delete keys even if 65 # the original infos should not be destroyed (makes the logic a bit simpler). 66 sub copy_hash_keys { 67 my %new_hash; 68 my $hash = shift; 69 for my $key (keys %$hash) { 70 $new_hash{$key} = 1; 71 } 72 return \%new_hash; 73 } 74 my $info1_keys = copy_hash_keys($info1); 75 my $info2_keys = copy_hash_keys($info2); 76 77 my $info1_superset = 0; 78 for my $key (keys %$info1_keys) { 79 # Need Data::Compare because there may be references, etc. 80 # (e.g. ThumbnailImage is a reference) 81 if (exists $info2_keys->{$key} && Compare($info1->{$key}, $info2->{$key}) == 1) { 82 # NOP 83 } elsif (!exists $info2_keys->{$key} || !defined $info2->{$key} || $info2->{$key} eq "") { 84 if ($key eq "Warning") { 85 warn "WARNING: ExifTool gave a warning on $filename1 but not on $filename2\n"; 86 warn "Treating these files as different to be on the safe side.\n"; 87 return -1; 88 } 89 # $info1 contains a key that is empty or nonexistent in $info2 90 $info1_superset = 1; 91 warn "$filename1 includes $key which is empty/nonexistent in $filename2\n" if ($verbose); 92 } else { 93 warn "$key is different for $filename1 and $filename2\n" if ($verbose); 94 return -1; 95 } 96 delete $info1_keys->{$key}; 97 delete $info2_keys->{$key}; 98 } 99 100 if ($info1_superset && %$info2_keys) { 101 if ($verbose) { 102 for my $key (keys %$info2_keys) { 103 warn "$filename2 includes $key which is nonexistent in $filename1\n"; 104 } 105 warn "$filename1 and $filename2 both contain EXIF data not included in the other file\n"; 106 } 107 return -1; 108 } elsif ($info1_superset) { 109 return 1; 110 } elsif (!%$info2_keys) { 111 return 0; 112 } else { 113 if (exists $info2_keys->{"Warning"}) { 114 warn "WARNING: ExifTool gave a warning on $filename2 but not on $filename1\n"; 115 warn "Treating these files as different to be on the safe side.\n"; 116 return -1; 117 } 118 if ($verbose) { 119 for my $key (keys %$info2_keys) { 120 warn "$filename2 includes $key which is nonexistent in $filename1\n"; 121 } 122 } 123 return 2; 124 } 125 } 126 127 sub build_signature_list { 128 my @queue = @_; 129 my @signatures; 130 while (@queue) { 131 my $filename = pop @queue; 132 # Just ignore all symlinks. This is the easiest way to 133 # prevent issues, and there shouldn't be any symlinks in 134 # the directories I use this on anyways. 135 if (-l $filename) { 136 warn "WARNING: Ignoring symlink $filename\n"; 137 next; 138 } 139 if (-d $filename) { 140 my $dh; 141 if (!opendir $dh, $filename) { 142 warn "WARNING: Unable to open directory $filename\n"; 143 next; 144 } 145 # FIXME: Maybe test for errors on readdir: 146 # https://github.com/Perl/perl5/issues/17907 147 my @new_files = map catfile($filename, $_), grep {$_ ne "." && $_ ne ".."} readdir $dh; 148 closedir $dh; 149 push @queue, @new_files; 150 } elsif (-f $filename) { 151 if (!-r $filename) { 152 warn "WARNING: File $filename is not readable\n"; 153 next; 154 } 155 if (!$skip_mime_check) { 156 my $mime = mimetype($filename); 157 if (!defined($mime)) { 158 warn "Unable to determine MIME type for $filename\n" if ($verbose); 159 next; 160 } elsif ($mime !~ /^image/) { 161 warn "Unsupported MIME type for $filename: $mime\n" if ($verbose); 162 next; 163 } 164 } 165 my $image = Image::Magick->new; 166 my $err = $image->Read($filename); 167 if ($err) { 168 $err =~ /(\d+)/; 169 # 420 is "no decode delegate for this image format", we don't want 170 # to spam that for every file that isn't an image format 171 if ($1 != 420 || $verbose) { 172 warn "WARNING: Unable to open file $filename with ImageMagick: $err\n"; 173 } 174 next; 175 } 176 my $sig = $image->Get("signature"); 177 if (!defined($sig)) { 178 warn "WARNING: Unable to obtain signature for $filename\n"; 179 next; 180 } 181 # This is needed so that it's possible to avoid the same file ending up 182 # in the same deletion group (if the same file/directory was added 183 # to the command-line arguments twice - not very realistic, but let's 184 # just be on the safe side). 185 my $fullpath = realpath($filename); 186 if (!defined($fullpath)) { 187 warn "WARNING: Unable to get absolute path for $filename\n"; 188 next; 189 } 190 push @signatures, [$fullpath, $sig]; 191 } else { 192 warn "WARNING: Ignoring non-existent or non-regular file $filename\n"; 193 } 194 } 195 return @signatures; 196 } 197 198 sub handle_group_exif { 199 # At this point, there really shouldn't be any errors reading the EXIF data since 200 # ImageMagick already successfully read the images (unless there's a file format 201 # for which ImageMagick has a loader but ExifTool doesn't). 202 my @exif_arr = grep defined($_->[3]), map [@$_, load_exif_data($_->[1])], @_; 203 my @all_groups; 204 while (@exif_arr) { 205 my $first = $exif_arr[0]; 206 # Strip signature and exif data for elements in final array since we don't need them anymore 207 push @all_groups, [[$first->[0], $first->[1]]]; 208 my @tmp_arr; 209 for my $i (1..$#exif_arr) { 210 my $ret = compare_exif_data($first->[3], $exif_arr[$i]->[3], $first->[1], $exif_arr[$i]->[1]); 211 if ($ret == 0 || $ret == 1) { 212 push @{$all_groups[-1]}, [$exif_arr[$i]->[0], $exif_arr[$i]->[1]]; 213 } elsif ($ret == 2) { 214 unshift @{$all_groups[-1]}, [$exif_arr[$i]->[0], $exif_arr[$i]->[1]]; 215 $first = $exif_arr[$i]; 216 } else { 217 push @tmp_arr, $exif_arr[$i]; 218 } 219 } 220 @exif_arr = @tmp_arr; 221 } 222 # Filter out single-element groups 223 return grep {$#$_ > 0} @all_groups; 224 } 225 226 sub handle_group_image_data { 227 my @cur_imgs = @_; 228 my @all_groups; 229 while (@cur_imgs) { 230 my $first = shift @cur_imgs; 231 my $image1 = Image::Magick->new; 232 my $err = $image1->Read($first->[1]); 233 # There really shouldn't be any errors here since all images have been 234 # previously read by ImageMagick to calculate the signatures, but let's 235 # just be on the safe side. 236 # Error 420 isn't ignored here because it doesn't matter if it's shown 237 # in the extremely rare case that it occurs here even though all the 238 # files were already opened previously. 239 if ($err) { 240 warn "WARNING: ImageMagick error while reading $first->[1]: $err\n"; 241 next; 242 } 243 push @all_groups, [$first]; 244 my @tmp_arr; 245 for my $i (0..$#cur_imgs) { 246 my $image2 = Image::Magick->new; 247 $err = $image2->Read($cur_imgs[$i]->[1]); 248 if ($err) { 249 warn "WARNING: ImageMagick error while reading $cur_imgs[$i]->[1]: $err\n"; 250 next; 251 } 252 my $diff = $image1->Compare(image=>$image2, metric=>"ae"); 253 # NOTE: If the two images have different sizes, ImageMagick doesn't seem to throw 254 # an error, but the returned error should be non-zero. 255 my $img_error = $diff->Get("error"); 256 if (!defined($img_error)) { 257 warn "WARNING: Unknown error during comparison of $first->[1] and $cur_imgs[$i]->[1]\n"; 258 $img_error = 1; 259 } 260 if ($img_error == 0) { 261 push @{$all_groups[-1]}, $cur_imgs[$i]; 262 } else { 263 push @tmp_arr, $cur_imgs[$i]; 264 } 265 } 266 @cur_imgs = @tmp_arr; 267 } 268 # Filter out single-element groups 269 return grep {$#$_ > 0} @all_groups; 270 } 271 272 my @extra_exif_ignore; 273 my $no_default_exif_ignore = 0; 274 my $show_help = 0; 275 my $isolate = 0; # only find duplicates between different commandline arguments (i.e. not within one directory that is given) 276 my $print_ignored = 0; 277 my $skip_exact_equality = 0; 278 my $delete_files = 0; 279 my $no_prompt = 0; 280 my $number_output = 0; 281 my $default_choice = ""; 282 283 Getopt::Long::Configure("bundling"); 284 GetOptions ( 285 "exif-ignore=s" => \@extra_exif_ignore, 286 "no-default-exif-ignore" => \$no_default_exif_ignore, 287 "h|help" => \$show_help, 288 "v|verbose" => \$verbose, 289 "I|isolate" => \$isolate, 290 "print-exif-ignore" => \$print_ignored, 291 "Q|quick" => \$skip_exact_equality, 292 "d|delete" => \$delete_files, 293 "N|no-prompt" => \$no_prompt, 294 "n|number" => \$number_output, 295 "default-choice=s" => \$default_choice, 296 "skip-mime-check" => \$skip_mime_check, 297 ) || pod2usage(-exitval => 1, -verbose => 1); 298 @extra_exif_ignore = split(/,/, join(',', @extra_exif_ignore)); 299 if ($no_default_exif_ignore) { 300 @exif_ignore = @extra_exif_ignore; 301 } else { 302 push @exif_ignore, @extra_exif_ignore; 303 } 304 305 if ($print_ignored) { 306 print join(",", @exif_ignore) . "\n"; 307 exit 0; 308 } 309 310 pod2usage(-exitval => 0, -verbose => 2) if $show_help; 311 pod2usage(-exitval => 1, -verbose => 1) if @ARGV < 1; 312 if ($default_choice ne "" && $default_choice !~ /^[yYnNp]$/) { 313 warn "ERROR: --default-choice must be y, Y, n, N, or p!\n"; 314 pod2usage(-exitval => 1, -verbose => 1); 315 } 316 317 my @siglist; 318 for my $i (0..$#ARGV) { 319 # Add index of command-line argument for precedence when outputting which 320 # files should be deleted 321 # Format is now [original cmd argument index, full path, signature] 322 push @siglist, map [$i, $_->[0], $_->[1]], build_signature_list($ARGV[$i]); 323 } 324 325 # Sort first by signature, then by original command-line argument index, then by file path 326 # Last sort by file path is so that if, for instance, there are two directories within 327 # the same cmd arg (and isolate isn't enabled), duplicates are always removed from the 328 # same directory. 329 @siglist = sort { 330 $a->[2] cmp $b->[2] || $a->[0] <=> $b->[0] || $a->[1] cmp $b->[1]; 331 } @siglist; 332 333 my @group; 334 my @final_groups; 335 my $prev_sig = ""; 336 for my $file (@siglist) { 337 if ($file->[2] ne $prev_sig) { 338 if (scalar(@group) > 1) { 339 if ($skip_exact_equality) { 340 # Need to make a copy of @group when storing it in @final_groups 341 push @final_groups, [@group]; 342 } else { 343 push @final_groups, handle_group_image_data(@group); 344 } 345 } 346 @group = (); 347 $prev_sig = $file->[2]; 348 } 349 push @group, $file; 350 } 351 if (scalar(@group) > 1) { 352 if ($skip_exact_equality) { 353 push @final_groups, [@group]; 354 } else { 355 push @final_groups, handle_group_image_data(@group); 356 } 357 } 358 359 @final_groups = map {handle_group_exif(@$_)} @final_groups; 360 # Sort by length of group, then by filename of first file in group 361 @final_groups = sort {$#$a <=> $#$b || $a->[0]->[1] cmp $b->[0]->[1]} @final_groups; 362 363 # 0: Do not delete files 364 # 1: Delete files 365 # 2: Go to previous duplicate group 366 sub prompt_delete { 367 if ($no_prompt) { 368 return $delete_files; 369 } 370 my $choice = ""; 371 my $default_printstr = $default_choice eq "" ? "" : "[$default_choice]"; 372 while ($choice !~ /^[yYnNp]$/) { 373 print STDERR "Delete files? (y/Y/n/N/p)$default_printstr "; 374 $choice = <STDIN>; 375 chomp $choice; 376 if ($choice eq "") { 377 $choice = $default_choice; 378 } 379 } 380 if ($choice eq "y") { 381 return 1; 382 } elsif ($choice eq "n") { 383 return 0; 384 } elsif ($choice eq "Y") { 385 $delete_files = 1; # should already be 1 anyways 386 $no_prompt = 1; 387 return 1; 388 } elsif ($choice eq "N") { 389 $delete_files = 0; 390 $no_prompt = 1; 391 return 0; 392 } else { 393 return 2; 394 } 395 } 396 397 # It doesn't matter that the same reference will be repeated here since the 398 # array elements are replaced later anyways 399 my @final_delete_files = ([]) x scalar(@final_groups); 400 sub output_groups { 401 my $cur_group_id = shift; 402 while ($cur_group_id <= $#final_groups) { 403 # Reset so it is set correctly when going back to correct mistakes 404 $final_delete_files[$cur_group_id] = []; 405 my $group = $final_groups[$cur_group_id]; 406 print "\n" if ($cur_group_id > 0); 407 if ($number_output) { 408 print "[" . ($cur_group_id + 1) . "/" . ($#final_groups + 1) . "]\n"; 409 } 410 my $first = $group->[0]; 411 print "[+] $first->[1]\n"; 412 # Note: This may be a bit inefficient, but these hashes make it simple to avoid mistakes 413 # and give somewhat decent diagnostic messages. 414 my %keep_list; 415 my %delete_list; 416 $keep_list{$first->[1]} = 1; 417 for my $file (@$group[1..$#$group]) { 418 if ($isolate && $file->[0] == $first->[0]) { 419 print "[+] $file->[1]\n"; 420 $keep_list{$file->[1]} = 1; 421 } else { 422 if (exists $keep_list{$file->[1]}) { 423 warn "WARNING: Ignoring $file->[1] as it was queued for deletion but refers " . 424 "to the same file as a file in the list of files to keep\n"; 425 } elsif (exists $delete_list{$file->[1]}) { 426 warn "WARNING: $file->[1] already queued for deletion\n"; 427 } else { 428 print "[-] $file->[1]\n"; 429 $delete_list{$file->[1]} = 1; 430 } 431 } 432 } 433 if ($delete_files && (my $choice = prompt_delete())) { 434 if ($choice == 1) { 435 $final_delete_files[$cur_group_id] = [keys %delete_list]; 436 } else { 437 $cur_group_id--; 438 if ($cur_group_id <= 0) { 439 $cur_group_id = 0; 440 print "\n"; 441 } 442 next; 443 } 444 } 445 $cur_group_id++; 446 } 447 } 448 449 output_groups(0); 450 451 # Loop here so the user can still correct mistakes for the previous prompts when 452 # we have already reached the final prompt. 453 while (1) { 454 my $any_to_delete = 0; 455 for my $delete_group (@final_delete_files) { 456 if (@$delete_group) { 457 $any_to_delete = 1; 458 last; 459 } 460 } 461 if ($any_to_delete) { 462 print "\n" if !$no_prompt; 463 my $choice = $no_prompt ? "y" : ""; 464 while ($choice !~ /^[ynp]$/) { 465 print "Delete all chosen files? (y/n/p) "; 466 $choice = <STDIN>; 467 chomp $choice; 468 } 469 if ($choice eq "y") { 470 for my $delete_group (@final_delete_files) { 471 for my $filename (@$delete_group) { 472 warn "Deleting $filename\n" if ($verbose); 473 if (!unlink($filename)) { 474 warn "WARNING: Unable to delete $filename\n"; 475 } 476 } 477 } 478 } elsif ($choice eq "p") { 479 print "\n" if $#final_groups == 0; 480 # Go back to group output, starting at last group 481 output_groups($#final_groups); 482 next; 483 } 484 } 485 last; 486 } 487 488 __END__ 489 490 =head1 NAME 491 492 imagedatadupes - find duplicate images 493 494 =head1 SYNOPSIS 495 496 B<imagedatadupes> [option ...] file/directory ... 497 498 Options: 499 -I, --isolate 500 -d, --delete 501 -N, --no-prompt 502 -Q, --quick 503 -n, --number 504 -v, --verbose 505 -h, --help 506 --default-choice=CHOICE 507 --exif-ignore=TAG,... 508 --no-default-exif-ignore 509 --print-exif-ignore 510 --skip-mime-check 511 512 =head1 DESCRIPTION 513 514 B<imagedatadupes> compares image files to check for duplicates and optionally deletes them. 515 Duplicates are found even if the files are not identical, as long as the image data is the same, 516 and the metadata is similar enough based on certain criteria. 517 518 The images are first compared using signatures of the image data calculated with ImageMagick(1). 519 Then, images with matching signatures are compared for exact equality of the image data. Lastly, 520 the metadata returned by ExifTool(1) is compared (this includes various metadata tags, not just 521 EXIF data). If the metadata in one file is a superset of the metadata in another file, the 522 former is preferred over the latter when deciding which files to keep. 523 524 The specific use-case for this is files where the EXIF data was modified to add a comment (e.g. where 525 a photo was taken). If an old copy of the same image still exists, it won't be found as a duplicate 526 by programs such as fdupes(1) and jdupes(1) since the EXIF data is different, but the new version 527 only contains more data than the old one, so there's no point in keeping the old copy. In order for this 528 to work in practice, some metadata tags that ExifTool returns, such as "FileModifyDate", need to be 529 ignored (see B<--exif-ignore>, B<--no-default-exif-ignore>, and B<--print-exif-ignore>). 530 531 The output format consists of groups of files separated by empty lines, with each file being preceded 532 by [+] or [-], depending on whether it was automatically selected for keeping or deletion, respectively. 533 If B<--delete> is not used, duplicates are only printed and no actual deletion is performed. 534 535 If multiple files are identical (both image data and metadata), files that were included in earlier 536 command-line parameters are preferred for keeping. If multiple files included in the same command-line 537 parameter are identical and B<--isolate> is not used, the file whose absolute path comes first 538 lexicographically is preferred for keeping. 539 540 Before being output, the groups are sorted first based on the number of files in each group, then 541 based on the lexicographical order of the absolute path of the first file in each group. 542 This is mainly so that it is easier to spot outliers, such as a group where the file chosen 543 for keeping (i.e. the first file in the group) is not in the expected directory. 544 545 See B<CAVEATS> for some issues that can arise. 546 547 B<WARNING:> Always make backups of your files before using this program to delete duplicates. There is 548 a nonzero chance that bugs still exist which could cause the wrong files to be deleted. If you have 549 directories containing files that should not be modified in any way, and you only want to check if 550 there are duplicates of those files in other directories, you may want to change the permissions 551 of those files so they are read-only and a malfunction of this program cannot cause any issues. 552 At least that's what a paranoid person such as the author of this program would do. 553 554 =head1 OPTIONS 555 556 =over 8 557 558 =item B<-I>, B<--isolate> 559 560 Isolate the command-line parameters from each other. In other words, for each group of 561 duplicates, all files belonging to the same command-line parameter as the file selected 562 for keeping are also kept. 563 564 =item B<-d>, B<--delete> 565 566 Prompt user for deletion of duplicates. Note that the actual deletion is not performed until all 567 duplicate groups have been processed and the user has been prompted once more to confirm that all 568 the chosen files should really be deleted. 569 The options are as follows: 570 571 =over 4 572 573 =item B<y> 574 575 Delete the files that were automatically selected for deletion in the 576 current duplicate group. 577 578 =item B<Y> 579 580 Delete the files that were automatically selected for deletion in the 581 current duplicate group and in all subsequent duplicate groups. 582 583 =item B<n> 584 585 Do not delete the files that were automatically selected for deletion 586 in the current duplicate group. 587 588 =item B<N> 589 590 Do not delete the files that were automatically selected for deletion 591 in the current duplicate group and in all subsequent duplicate groups. 592 593 =item B<p> 594 595 Go back to the previous duplicate group. This is particularly useful 596 together with B<--default-choice> so that choices can be made very 597 quickly, but mistakes can still be corrected by going back to previous 598 duplicate groups. Previously chosen options are not saved, so when 599 B<p> is used to go back multiple groups, all choices have to be made 600 again. 601 602 =back 603 604 Note that B<Y> and B<N> also cause the final prompt asking if all chosen 605 files should be deleted to be skipped. 606 607 =item B<-N>, B<--no-prompt> 608 609 When used in conjunction with B<--delete>, automatically delete duplicates without prompting the user. 610 This option should be used with care as it can lead to data loss. Make sure to always have backups 611 of your data in case something goes wrong. 612 613 =item B<-Q>, B<--quick> 614 615 Only determine duplicates based on the data signature and metadata (skip the check for exact 616 equality of the image data). 617 618 =item B<-n>, B<--number> 619 620 Print the number of the current group and the total number of groups for each duplicate group 621 that is output. This is useful in interactive mode so there is an indication of how many 622 groups are still left. 623 624 =item B<-v>, B<--verbose> 625 626 Print informational messages to stdout. 627 This currently includes some file reading errors, as well as information on the metadata 628 comparison (e.g. which tags were present in one file but not in the other one). 629 Additionally, all filenames are printed once more right before they are actually deleted. 630 631 =item B<-h>, B<--help> 632 633 Print usage information, then exit. 634 635 =item B<--default-choice>=CHOICE 636 637 Set a default choice to use in interactive mode. This must be one of the characters that can be 638 entered at a prompt during interactive mode. When this is set, it is possible to simply press 639 enter at each prompt in order to use the default choice. Note that this is only used during 640 interactive mode, it does not have any effect when B<--no-prompt> is in effect. 641 642 =item B<--exif-ignore>=TAG,... 643 644 Add additional metadata tags to the list of tags that should be ignored when comparing two files. 645 This option can be given multiple times. Alternatively, multiple tags can be given at once 646 as a comma-separated list. The B<--verbose> option might help to figure out why two images 647 aren't being considered as duplicates. Additionally, ExifTool can be used to find the list 648 of all tag names an image contains using the command "exiftool -s image.jpg". It is generally 649 a good idea to start with the default ignore list and only add more tags later if needed. 650 Adding too many tags to the ignore list can easily lead to data loss when files are considered 651 to be duplicates even though the values for some ignored metadata tags were different. 652 653 =item B<--no-default-exif-ignore> 654 655 Do not use the default list of ignored metadata tags. 656 657 WARNING: As this program has not reached version 1.0 yet, the default list of ignored metadata tags 658 may still change without warning. Always use B<--exif-ignore> together with B<--no-default-exif-ignore> 659 if you want to be certain that nothing unexpected happens. 660 661 =item B<--print-exif-ignore> 662 663 Print all metadata tags that will be ignored, including the default ones if they haven't been turned off 664 using B<--no-default-exif-ignore>. 665 666 =item B<--skip-mime-check> 667 668 Do not check the MIME type of files. Instead, simply process anything that is supported by ImageMagick 669 and ExifTool. By default, only MIME types starting with "image" are processed in order to avoid issues 670 with ImageMagick trying to open large video files or similar. 671 672 =back 673 674 =head1 CAVEATS 675 676 The ordering of the files within a duplicate group in the output can be a bit weird. 677 In particular, if a file is chosen for keeping because it contains more metadata, it will be displayed 678 at the beginning of the file list, regardless of the regular sorting. This can seem strange when 679 there are multiple duplicates each in multiple command-line parameters since it can happen that the 680 duplicates from one command-line parameter are not written right after each another if one of them was 681 moved to the beginning of the output due to its metadata. 682 683 Before printing the output, a final check is performed to make sure that none of the files in 684 a duplicate group have the same path (this should only happen if the same directory/file was given 685 multiple times). This can cause only one file to be displayed for a duplicate group. 686 In a similar vein, when B<--isolate> is used, it is possible for a duplicate group to be printed 687 even though none of the files are considered for deletion. 688 689 If there are any errors while reading a file, the file is ignored during further processing. 690 Errors that are only due to a filetype not being supported are normally not printed (except 691 in rare circumstances) in order to avoid spamming the output with error messages for all 692 non-image files. These are only printed when B<--verbose> is used. 693 694 Originally, any warnings given by ExifTool also caused the file to be ignored. However, this 695 caused issues because it meant that identical files which both included the same warning 696 were not considered as duplicates. Thus, warnings are now treated almost the same as 697 other metadata. The only difference is that warnings cannot be used to decide that one file 698 contains a superset of another file's metadata. This is a safety feature as it is theoretically 699 possible for two files to be identical, except that one is damaged in some way, leading to a 700 warning. If warnings were treated exactly the same as other metadata, the file without the 701 warning would be deleted as it contains less metadata, but this would probably not be the 702 correct choice. In that case, a warning is printed and the files are not considered as 703 duplicates so they can be examined manually later. If warnings should be disregarded 704 entirely when comparing files, the "Warning" tag can be ignored using B<--exif-ignore>. 705 706 If there is an unknown error during the exact comparison of the image data, this might be due to 707 memory restrictions in the ImageMagick configuration. Try opening the ImageMagick configuration 708 file "policy.xml" and changing the maximum memory. The configuration file is probably located 709 at /etc/ImageMagick-6/policy.xml or /etc/ImageMagick-7/policy.xml, depending on the version of 710 ImageMagick. There should be a line similar to 711 '<policy domain="resource" name="memory" value="256MiB"/>' that can be changed to increase the 712 maximum memory. There is also a similar line with "disk" instead of "memory" that can be used 713 to control how much temporary disk space ImageMagick is allowed to use when there is not enough 714 memory. The "map" and "area" options may also be of interest. See the ImageMagick documentation 715 for a description of the other options in the policy.xml file. Note that, depending on how much 716 memory is available, it might be a good idea to set the disk limit to 0 in order to fail more 717 quickly and avoid a lot of disk writes when large files are opened. 718 719 ImageMagick seems to sometimes open files without any error even though they don't contain any 720 image data. For instance, it managed to open some JPEG files that had been corrupted and only 721 contained zeroes, calculating the same signature for all of them. This should not be an issue 722 normally since a MIME type check is now performed before trying to read any files with ImageMagick. 723 Another interesting case found in the wild dealt with .exi files, which appeared to contain just 724 the EXIF data from JPEG files. These passed the MIME type check, and ImageMagick calculated the 725 same signature for all of them, but returned an error later on during the exact image data 726 comparison. Such cases should generally not be a problem since the image data or metadata 727 comparison should still fail, causing the files to be treated as different, but it's probably 728 still a good idea to keep these edge cases in mind. 729 730 The image data used for the comparisons is whatever ImageMagick decompresses the image 731 files to, which might include various inaccuracies. However, that probably isn't an issue 732 in any realistic scenario. 733 734 For simplicity, all comparisons are performed even when B<--isolate> is used and it technically 735 isn't necessary to compare files from the same command-line parameter. 736 737 This program completely ignores all symbolic links in order to avoid all the issues that 738 can be caused by them. 739 740 =head1 EXAMPLES 741 742 =over 8 743 744 =item B<imagedatadupes -dnI --default-choice=y directory1 directory2 ...> 745 746 Look for duplicates in the given directories, making sure that duplicate images belonging to the 747 same command-line paramater are either all kept or all deleted. The user is prompted to confirm 748 the deletion of each duplicate group, with the default choice being set to B<y> so that it is 749 easy to always press enter (if a mistake is made, B<p> can be entered at the prompt to go back 750 to the previous duplicate group). In order to give an indication of progress during the 751 prompting for deletion, the output groups are numbered. 752 753 =item B<imagedatadupes -dnI --default-choice=y --exif-ignore=ThumbnailOffset directory1 directory2 ...> 754 755 Same as above, but additionally ignore the "ThumbnailOffset" tag when comparing image metadata. 756 757 =back 758 759 =head1 EXIT STATUS 760 761 Always 0, unless the arguments given were invalid. 762 763 =head1 SEE ALSO 764 765 ExifTool(1), fdupes(1), findimagedupes(1p), ImageMagick(1), jdupes(1) 766 767 =head1 LICENSE 768 769 Copyright (c) 2026 lumidify <nobody@lumidify.org> 770 771 Permission to use, copy, modify, and/or distribute this software for any 772 purpose with or without fee is hereby granted, provided that the above 773 copyright notice and this permission notice appear in all copies. 774 775 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 776 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 777 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 778 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 779 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 780 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 781 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.