group_audio_files.pl 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. #!/usr/bin/perl
  2. # Daniel "Trizen" Șuteu
  3. # License: GPLv3
  4. # Date: 11 August 2019
  5. # https://github.com/trizen
  6. # Group MP3 files in directories based on their artist name.
  7. # Example:
  8. # Foo - abc.mp3
  9. # Foo - xyz.mp3
  10. # Both files will be moved in a new directory named "Foo".
  11. # The directory "Foo" is created in the current working directory from which the script is executed.
  12. use 5.016;
  13. use strict;
  14. use warnings;
  15. binmode(STDOUT, ':utf8');
  16. use Encode qw(decode_utf8);
  17. use Text::Unidecode qw(unidecode);
  18. use File::Find qw(find);
  19. use File::Copy qw(move);
  20. use File::Basename qw(basename);
  21. use File::Spec::Functions qw(catdir catfile curdir);
  22. use List::Util qw(sum);
  23. use List::UtilsBy qw(max_by);
  24. my $file_formats = qr{\.(?:mp3|mp4|webm|mkv|opus|ogg|oga)\z}i; # file formats
  25. my (@files) = grep { -e $_ } @ARGV;
  26. if (not @files) {
  27. die "usage: $0 [dir]\n";
  28. }
  29. my @audio_files;
  30. find(\&wanted_files, @files);
  31. sub wanted_files {
  32. my $file = $File::Find::name;
  33. push @audio_files, $file if ($file =~ $file_formats);
  34. }
  35. if (@audio_files) {
  36. say ":: Found ", scalar(@audio_files), " audio files...";
  37. }
  38. else {
  39. say ":: No file found...";
  40. }
  41. my %groups;
  42. foreach my $filename (@audio_files) {
  43. my $basename = decode_utf8(basename($filename));
  44. my $artist;
  45. if ($basename =~ /^[\d\s.\-–]*(.+?) -/) {
  46. $artist = $1;
  47. }
  48. elsif ($basename =~ /^[\d\s.\-–]*(.+?)-/) {
  49. $artist = $1;
  50. }
  51. else {
  52. next;
  53. }
  54. # Remove extra whitespace
  55. $artist = join(' ', split(' ', $artist));
  56. # Unidecode key and remove whitespace
  57. my $key = join('', split(' ', unidecode(CORE::fc($artist))));
  58. $key =~ s/[[:punct:]]+//g; # remove any punctuation characters
  59. $key =~ s/\d+//g; # remove any digits
  60. if ($key eq '' or $artist eq '') {
  61. next;
  62. }
  63. push @{$groups{$key}{files}},
  64. {
  65. filepath => $filename,
  66. basename => $basename,
  67. };
  68. ++$groups{$key}{artists}{$artist};
  69. }
  70. while (my ($key, $group) = each %groups) {
  71. my $files = $group->{files};
  72. my $artists = $group->{artists};
  73. sum(values %$artists) > 1 or next; # ignore single files
  74. my $common_name = max_by { $artists->{$_} } sort { $a cmp $b } keys %$artists;
  75. foreach my $file (@{$files}) {
  76. my $group_dir = catdir(curdir(), $common_name);
  77. if (not -e $group_dir) {
  78. mkdir($group_dir) || do {
  79. warn "[!] Can't create directory `$group_dir`: $!\n";
  80. next;
  81. };
  82. }
  83. if (not -d $group_dir) {
  84. warn "[!] Not a directory: $group_dir\n";
  85. next;
  86. }
  87. my $target = catfile($group_dir, $file->{basename});
  88. if (not -e $target) {
  89. say "[*] Moving file `$file->{basename}` into `$common_name` directory...";
  90. move($file->{filepath}, $target) || warn "[!] Failed to move: $!\n";
  91. }
  92. }
  93. }