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}||'',
};
}
# }}}
|