http://www.perlmonks.org?node_id=55341
Category: Audio Related Programs
Author/Contact Info sacked
Description: This script reads in a list of mp3s (assumed to be in one directory) and organizes them into directories by artist/album. It first attempts to use the id3 tag, but if one is not found, it falls back to parsing the filename for the artist name. I use this script every so often because I download all my mp3s to one directory, and it gets cluttered quickly. The script doesn't have the desired results, however, if mp3s without id3 tags have a hyphen in the filename, but don't have the artist first (e.g., "ice ice baby - vanilla ice.mp3").

This is my first post, please feel free to offer comments/criticism. Thanks!

Update: I removed the system calls to /bin/mv and replaced them with calls to rename, after a tip from salvadors.
#!/usr/bin/perl -w
#
# attempts to organize mp3s into dirs by:
#   1. checking ID3 tag for Artist name
#   2. scanning the filename
#
# it will further organize the mp3s into subdirectories under the
# artist name by album
#

use strict;
use MP3::Info;

use vars qw( $mp3dir @files %dirs );

# change as necessary
#
$mp3dir = '/opt/mp3/';

chdir $mp3dir or die "can't chdir to $mp3dir: $!\n\n";

# get file list
#
opendir(DIR, $mp3dir) or die "can't open $mp3dir for read: $!\n\n";
@files = grep { /\.[Mm][Pp]3$/ } readdir(DIR);
closedir(DIR) or warn "error closing $mp3dir: $!\n\n";

&make_dir_list;
&move_files;
exit;


# create dir listing
# %dirs = ( 'dirname' => { 'subdir1' => [ file1, file2, ... ], ... }, 
+...  );
#
sub make_dir_list {
  foreach ( @files ) {
    my ($artist, $album, $tag);

    # attempt to find artist name through mp3 tag
    #
    $tag = get_mp3tag( $_ );

    # fall back to scanning filename. we're assuming artist name
    # is everything up to the first hyphen
    #
    unless ( $tag && $tag->{ARTIST} !~ /^\s*$/ && $tag->{ARTIST} ne 'a
+rtist' ) {
      ($artist) = /^([^-]+?)-.+$/;
      $artist ||= 'unsorted';
    }
    else {
      $artist = $tag->{ARTIST};
    }

    $album  = $tag->{ALBUM} || "";
    if ( $album =~ /^\s*$/ || $album eq 'title' ) { $album = 'misc' }

    $artist = clean_name( $artist ) unless $artist eq 'unsorted';
    $album  = clean_name( $album ) unless $album eq 'misc';
    push @{ $dirs{$artist}{$album} }, $_;
  }
}


# sanitize artist name (or filename fragment) for use as a dir name
#
sub clean_name {
  my $dir = shift;
  return 'unsorted' unless $dir;

  $dir =  lc($dir);
  $dir =~ s/\bthe\b//;
  $dir =~ s/_/ /g;
  $dir =~ s/^ +//;
  $dir =~ s/ +$//;
  $dir =~ s/ +/ /g;
  $dir =~ s/[,(){}\[\]]//g;

  return $dir;
}


# create dirs, put sorted files into them
#
sub move_files {
  foreach my $artist ( keys %dirs ) {
    # XXX debug
    #
    #print "  $artist\n";

    unless ( -d qq{$artist} ) {
      mkdir( qq{$artist}, 0755 ) or die "error mkdir($artist): $!\n\n"
+;
    }

    foreach my $album ( keys %{ $dirs{$artist} } ) {
      # XXX debug
      #
      #print "    $album\n", map { "\t$_\n" } @{ $dirs{$artist}{$album
+} }, "\n";

      unless ( -d qq{$artist/$album} ) {
        mkdir( qq{$artist/$album}, 0755 ) or die "error mkdir($artist/
+$album): $
!\n\n";
      }

      #system('/bin/mv', @{ $dirs{$artist}{$album} }, qq{$artist/$albu
+m/}) == 0
      #  or die "system('/bin/mv'): $?\n\n";

      foreach my $mp3 ( @{ $dirs{$artist}{$album} } ) {
        rename $mp3, qq{$artist/$album/$mp3} or die "can't rename $mp3
+: $!\n\n";
      }
    }
  }
}