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