Developer Forums | About Us | Site Map
Search  
HOME > TUTORIALS > SERVER SIDE CODING > PERL TUTORIALS > CULTURED PERL: FUN WITH MP3 AND PERL, PART 1


Sponsors





Useful Lists

Web Host
site hosted by netplex

Online Manuals

Cultured Perl: Fun with MP3 and Perl, Part 1
By Teodor Zlatanov - 2004-01-13 Page:  1 2 3 4 5 6

ID3v2 tag-related functions

The get_tag() function is essential to autotag.pl. Given an MP3 file name, it builds a hash tag from the file. If the tag is only ID3v1, get_tag() will offer to upgrade the ID3 tag for free (what a deal!). If there is no ID3 tag, get_tag() will create one. Furthermore, get_tag() knows to look at the Text and URL sub-elements of the COMM and WXXX tag elements, respectively.

Listing 5. The get_tag() function

# {{{ get_tag: get a ID3 V2 tag, using V1 if necessary
sub get_tag
{
 my $file    = shift @_;
 my $upgrade = shift @_;
 my $mp3 = MP3::Tag->new($file);

 return undef unless defined $mp3;

 $mp3->get_tags();

 my $tag = {};

 if (exists $mp3->{ID3v2})
 {
  my $id3v2 = $mp3->{ID3v2};
  my $frames = $id3v2->supported_frames();
  while (my ($fname, $longname) = each %$frames)
  {
   # only grab the frames we know
   next unless exists $supported_frames{$fname};

   $tag->{$fname} = $id3v2->get_frame($fname);
   delete $tag->{$fname} unless defined $tag->{$fname};
   $tag->{$fname} = $tag->{$fname}->{Text} if $fname eq 'COMM';
   $tag->{$fname} = $tag->{$fname}->{URL} if $fname eq 'WXXX';
   $tag->{$fname} = '' unless defined $tag->{$fname};
  }
 }
 elsif (exists $mp3->{ID3v1})
 {
  warn "No ID3 v2 TAG info in $file, using the v1 tag";
  my $id3v1 = $mp3->{ID3v1};
  $tag->{COMM} = $id3v1->comment();
  $tag->{TIT2} = $id3v1->song();
  $tag->{TPE1} = $id3v1->artist();
  $tag->{TALB} = $id3v1->album();
  $tag->{TYER} = $id3v1->year();
  $tag->{TRCK} = $id3v1->track();
  $tag->{TIT1} = $id3v1->genre();

  if ($upgrade && read_yes_no("Upgrade ID3v1 tag to ID3v2 for $file?", 1))
  {
   set_tag($file, $tag);
  }
 }
 else
 {
  warn "No ID3 TAG info in $file, creating it";
  $tag = {
      TIT2 => '',
      TPE1 => '',
      TALB => '',
      TYER => 9999,
      COMM => '',
      };
 }
 print "Got tag ", Dumper $tag
  if $config->DEBUG();
 return $tag;
}
# }}}

The set_tag() function is the sibling of get_tag(). It writes a ID3v2 tag, observing the COMM and WXXX frames' sub-elements. It takes a hash reference such as get_tag() might produce.

Listing 6. The set_tag() function

# {{{ set_tag: set a ID3 V2 tag on a file
sub set_tag
{
 my $file = shift @_;
 my $tag  = shift @_;
 my $mp3 = MP3::Tag->new($file);
 print Dumper $tag;
 my $tags = $mp3->get_tags();
 my $id3v2;

 if (ref $tags eq 'HASH' && exists $tags->{ID3v2})
 {
  $id3v2 = $tags->{ID3v2};
 }
 else
 {
  $id3v2 = $mp3->new_tag("ID3v2");
 }

 my %old_frames = %{$id3v2->get_frame_ids()};

 foreach my $fname (keys %$tag)
 {
  $id3v2->remove_frame($fname)
   if exists $old_frames{$fname};

  if ($fname eq 'WXXX')
  {
   $id3v2->add_frame('WXXX', 'ENG', 'FreeDB URL', $tag->{WXXX}) ;
  }
  elsif ($fname eq 'COMM')
  {
   $id3v2->add_frame('COMM', 'ENG', 'Comment', $tag->{COMM}) ;
  }
  else
  {
   $id3v2->add_frame($fname, $tag->{$fname});
  }
 }

 $id3v2->write_tag();
 return 0;
}
# }}}

The print_tag_info() function simply prints out a summary of the tag. Unlike Data::Dumper, which I've used elsewhere in autotag.pl (sometimes needlessly, I must say), print_tag_info() provides a nice, user-oriented printout of the hash tag elements. Note that this function takes a hash reference, not an actual file name.

The guess_track_number() and guess_artist_and_track() functions do the best they can, given a file name and possibly some ID3 tag information. Note that guess_track_number() understands that track numbers are very rarely higher than 30.

Listing 7. The print_tag_info(), guess_track_number(), and guess_artist_and_track() functions


# {{{ print_tag_info: print the tag info

sub print_tag_info
{
 my $filename = shift @_;
 my $tag      = shift @_;
 my $extra    = shift @_ || 'Track info';

 # argument checking
 return unless ref $tag eq 'HASH';

 print "$extra for '$filename':\n";

 foreach (keys %$tag)
 {
  printf "%10s : %s\n", $_, $tag->{$_};
 }
}

# }}}

# {{{ guess_track_number: guess track number from ID3 tag and file name
sub guess_track_number
{
 my $filename = shift @_;
 my $tag      = shift @_ || return undef;

 $filename = basename($filename);   # directories can contain confusing data

 # first try to guess the track number from the old tag
 if (exists $tag->{TRCK} && contains_word_char($tag->{TRCK}))
 {
  my $n = $tag->{TRCK} + 0;    # fix tracks like 1/10
  return $n;
 }
 elsif ($filename =~ m/([012]?\d).*\.[^.]+$/)
                     # now look for numbers in the filename (0 through 29)
 {
  print "Guessed track number $1 from filename '$filename'\n"
   if $config->DEBUG();
  return $1;
 }

 return undef; # if all else fails, return undef
}
# }}}

# {{{ guess_artist_and_track: guess artist and track from file name
sub guess_artist_and_track
{
 my $filename = shift @_;
 my $artist;
 my $track;

 $filename = basename($filename);   # directories can contain confusing data

 if ($filename =~ m/([^-_]{3,})\s*-\s*(.{3,})\s*\.[^.]+$/)
 {
  print "Guessed artist $1 from filename '$filename'\n"
   if $config->DEBUG();
  $artist = $1;
  $track = $2;
 }

 return ($artist, $track);
}
# }}}

I use the data returned from the FreeDB search to make an anonymous hash with the appropriate elements. The mapping between WebService::FreeDB fields and ID3v2 tag elements is tentative, but it has worked very well for me.

Listing 8. The make_tag_from_freedb() function

# {{{ make_tag_from_freedb: make the ID3 tag info from a FreeDB entry
sub make_tag_from_freedb
{
 my $disc  = shift @_;
 my $track = shift @_;

 # argument checking
 return undef unless $track =~ m/^\d+$/;

 # note that the user inputs track "1" but WebService::FreeDB gives us that
 # track at position 0, so we decrement $track
 $track--;

 return undef unless exists $disc->{trackinfo};

 return undef unless exists $disc->{trackinfo}->[$track];

 my $track_data = $disc->{trackinfo}->[$track];

 return {
      TIT1 => $disc->{genre},
      TIT2 => $track_data->[0],
      TRCK => $track+1,
      TPE1 => $disc->{artist},
      TALB => $disc->{cdname},
      TYER => $disc->{year},
      WXXX => $disc->{url},
      COMM => $disc->{rest}||'',
   };

}
# }}}



View Cultured Perl: Fun with MP3 and Perl, Part 1 Discussion

Page:  1 2 3 4 5 6 Next Page: Mass tagging, mass renaming

First published by IBM developerWorks


Copyright 2004-2017 GrindingGears.com. All rights reserved.
Article copyright and all rights retained by the author.