1package MP3::Tag::ID3v1; 2 3use strict; 4use vars qw /@mp3_genres @winamp_genres $AUTOLOAD %ok_length $VERSION/; 5 6$VERSION="0.60"; 7 8# allowed fields in ID3v1.1 and max length of this fields (expect for track and genre which are coded later) 9%ok_length = (song => 30, artist => 30, album => 30, comment => 28, track => 3, genre => 30, year=>4, genreID=>1); 10 11=pod 12 13=head1 NAME 14 15MP3::Tag::ID3v1 - Module for reading / writing ID3v1 tags of MP3 audio files 16 17=head1 SYNOPSIS 18 19MP3::Tag::ID3v1 is designed to be called from the MP3::Tag module. 20 21 use MP3::Tag; 22 $mp3 = MP3::Tag->new($filename); 23 24 # read an existing tag 25 $mp3->get_tags(); 26 $id3v1 = $mp3->{ID3v1} if exists $mp3->{ID3v1}; 27 28 # or create a new tag 29 $id3v1 = $mp3->new_tag("ID3v1"); 30 31See L<MP3::Tag|according documentation> for information on the above used functions. 32 33* Reading the tag 34 35 print " Song: " .$id3v1->song . "\n"; 36 print " Artist: " .$id3v1->artist . "\n"; 37 print " Album: " .$id3v1->album . "\n"; 38 print "Comment: " .$id3v1->comment . "\n"; 39 print " Year: " .$id3v1->year . "\n"; 40 print " Genre: " .$id3v1->genre . "\n"; 41 print " Track: " .$id3v1->track . "\n"; 42 43 # or at once 44 @tagdata = $mp3->all(); 45 foreach $tag (@tagdata) { 46 print $tag; 47 } 48 49* Changing / Writing the tag 50 51 $id3v1->comment("This is only a Test Tag"); 52 $id3v1->song("testing"); 53 $id3v1->artist("Artest"); 54 $id3v1->album("Test it"); 55 $id3v1->year("1965"); 56 $id3v1->track("5"); 57 $id3v1->genre("Blues"); 58 # or at once 59 $id3v1->all("song title","artist","album","1900","comment",10,"Ska"); 60 $id3v1->write_tag(); 61 62* Removing the tag from the file 63 64 $id3v1->remove_tag(); 65 66=head1 AUTHOR 67 68Thomas Geffert, thg@users.sourceforge.net 69 70=head1 DESCRIPTION 71 72=pod 73 74=item song(), artist(), album(), year(), comment(), track(), genre() 75 76 $artist = $id3v1->artist; 77 $artist = $id3v1->artist($artist); 78 $album = $id3v1->album; 79 $album = $id3v1->album($album); 80 $year = $id3v1->year; 81 $year = $id3v1->year($year); 82 $comment = $id3v1->comment; 83 $comment = $id3v1->comment($comment); 84 $track = $id3v1->track; 85 $track = $id3v1->track($track); 86 $genre = $id3v1->genre; 87 $genre = $id3v1->genre($genre); 88 89Use these functions to retrieve the date of these fields, 90or to set the data. 91 92$genre can be a string with the name of the genre, or a number 93describing the genre. 94 95=cut 96 97sub AUTOLOAD { 98 my $self = shift; 99 my $attr = $AUTOLOAD; 100 101 # is it an allowed field 102 $attr =~ s/.*:://; 103 return unless $attr =~ /[^A-Z]/; 104 warn "invalid field: ->$attr()" unless $ok_length{$attr}; 105 106 if (my $new = shift) { 107 $new =~ s/ *$//; 108 $new = substr $new, 0, $ok_length{$attr}; 109 if ($attr eq "genre") { 110 if ($new =~ /^\d+$/) { 111 $self->{genreID} = $new; 112 } else { 113 $self->{genreID} = genre2id($new); 114 } 115 $new = id2genre($self->{genreID}); 116 } 117 $self->{$attr}=$new; 118 $self->{changed} = 1; 119 } 120 $self->{$attr} =~ s/ +$//; 121 return $self->{$attr}; 122} 123 124=pod 125 126=item all() 127 128 @tagdata = $id3v1->all; 129 @tagdata = $id3v1->all($song, $artist, $album, $year, $comment, $track, $genre); 130 131Returns all information of the tag in a list. 132You can use this sub also to set the data of the complete tag. 133 134The order of the data is always song, artist, album, year, comment, track, and genre. 135genre has to be a string with the name of the genre, or a number identifying the genre. 136 137=cut 138 139sub all { 140 my $self=shift; 141 if ($#_ == 6) { 142 my $new; 143 for (qw/song artist album year comment track genre/) { 144 $new = shift; 145 $new =~ s/ +$//; 146 $new = substr $new, 0, $ok_length{$_}; 147 $self->{$_}=$new; 148 } 149 if ($self->{genre} =~ /^\d+$/) { 150 $self->{genreID} = $self->{genre}; 151 } else { 152 $self->{genreID} = genre2id($self->{genre}); 153 } 154 $self->{genre} = id2genre($self->{genreID}); 155 $self->{changed} = 1; 156 } 157 for (qw/song artist album year comment track genre/) { 158 $self->{$_} =~ s/ +$//; 159 } 160 if (wantarray) { 161 return ($self->{song},$self->{artist},$self->{album}, 162 $self->{year},$self->{comment}, $self->{track}, $self->{genre}); 163 } 164 return $self->{song}; 165} 166=pod 167 168=item write_tag() 169 170 $id3v1->write_tag(); 171 172 [old name: writeTag() . The old name is still available, but you should use the new name] 173 174Writes the ID3v1 tag to the file. 175 176=cut 177 178sub write_tag { 179 my $self = shift; 180 return undef unless exists $self->{song} && exists $self->{changed}; 181 $self->{track}=0 unless $self->{track} =~ /^\d+$/; 182 $self->{genreID}=255 unless $self->{genreID} =~ /^\d+$/; 183 my $data = pack("a30a30a30a4a28xCC",$self->{song},$self->{artist},$self->{album}, 184 $self->{year}, $self->{comment}, $self->{track}, $self->{genreID}); 185 my $mp3obj = $self->{mp3}; 186 my $mp3tag; 187 $mp3obj->close; 188 if ($mp3obj->open("write")) { 189 $mp3obj->seek(-128,2); 190 $mp3obj->read(\$mp3tag, 3); 191 if ($mp3tag eq "TAG") { 192 $mp3obj->seek(-125,2); # neccessary for windows 193 $mp3obj->write($data); 194 } else { 195 $mp3obj->seek(0,2); 196 $mp3obj->write("TAG$data"); 197 } 198 } else { 199 warn "Couldn't open file to write tag"; 200 return 0; 201 } 202 return 1; 203} 204 205*writeTag = \&write_tag; 206 207=pod 208 209=item remove_tag() 210 211 $id3v1->remove_tag(); 212 213 [old name: removeTag() . The old name is still available, but you should use the new name] 214 215Removes the ID3v1 tag from the file. 216 217=cut 218 219sub remove_tag { 220 my $self = shift; 221 my $mp3obj = $self->{mp3}; 222 my $mp3tag; 223 $mp3obj->seek(-128,2); 224 $mp3obj->read(\$mp3tag, 3); 225 if ($mp3tag eq "TAG") { 226 $mp3obj->close; 227 if ($mp3obj->open("write")) { 228 $mp3obj->truncate(-128); 229 $self->all("","","","","",0,255); 230 $mp3obj->close; 231 $self->{changed} = 1; 232 return 1; 233 } 234 return -1; 235 } 236 return 0; 237} 238 239*removeTag = \&remove_tag; 240 241=pod 242 243=item genres() 244 245 @allgenres = $id3v1->genres; 246 $genreName = $id3v1->genres($genreID); 247 $genreID = $id3v1->genres($genreName); 248 249Returns a list of all genres, or the according name or id to 250a given id or name. 251 252=cut 253 254sub genres { 255 # return an array with all genres, of if a parameter is given, the according genre 256 my ($self, $genre) = @_; 257 if ( (defined $self) and (not defined $genre) and ($self !~ /MP3::Tag/)) { 258 ## genres may be called directly via MP3::Tag::ID3v1::genres() 259 ## and $self is then not used for an id3v1 object 260 $genre = $self; 261 } 262 return \@winamp_genres unless defined $genre; 263 return $winamp_genres[$genre] if $genre =~ /^\d+$/; 264 my $r; 265 foreach (@winamp_genres) { 266 if ($_ eq $genre) { 267 $r=$_; 268 last; 269 } 270 } 271 return $r; 272} 273 274=item new() 275 276 $id3v1 = MP3::Tag::ID3v1->new($mp3fileobj[, $create]); 277 278Generally called from MP3::Tag, because a $mp3fileobj is needed. 279If $create is true, a new tag is created. Otherwise undef is 280returned, if now ID3v1 tag is found in the $mp3obj. 281 282Please use 283 284 $mp3 = MP3::Tag->new($filename); 285 $id3v1 = $mp3->new_tag($filename); 286 287instead of using this function directly 288 289=cut 290 291# create a ID3v1 object 292sub new { 293 my ($class, $mp3obj, $create) = @_; 294 my $self={mp3=>$mp3obj}; 295 my $buffer; 296 297 if (defined $create && $create) { 298 $self->{new} = 1; 299 } else { 300 $mp3obj->seek(-128,2); 301 $mp3obj->read(\$buffer, 128); 302 $mp3obj->close; 303 } 304 305 if (exists $self->{new} || substr ($buffer,0,3) eq "TAG") { 306 bless $self, $class; 307 $self->read_tag($buffer); 308 309 return $self; 310 } else { 311 return undef; 312 } 313} 314 315################# 316## 317## internal subs 318 319# actually read the tag data 320sub read_tag { 321 my ($self, $buffer) = @_; 322 my $mp3obj = $self->{mp3}; 323 my $id3v1; 324 325 if ($self->{new}) { 326 ($self->{song}, $self->{artist}, $self->{album}, $self->{year}, 327 $self->{comment}, $self->{track}, $self->{genre}, $self->{genreID}) = ("","","","","",0,"",255); 328 $self->{changed} = 1; 329 } else { 330 (undef, $self->{song}, $self->{artist}, $self->{album}, $self->{year}, 331 $self->{comment}, $id3v1, $self->{track}, $self->{genreID}) = 332 unpack ("a3Z30Z30Z30Z4Z28CCC", $buffer); 333 334 if ($id3v1!=0) { # ID3v1 tag found: track is not valid, comment two chars longer 335 $self->{comment} .= chr($id3v1); 336 $self->{comment} .= chr($self->{track}) if $self->{track}!=32; 337 $self->{track} = 0; 338 }; 339 $self->{genre} = id2genre($self->{genreID}); 340 } 341} 342 343# convert one byte id to genre name 344sub id2genre { 345 my $id=shift; 346 return "" unless defined $id && $id<$#winamp_genres; 347 return $winamp_genres[$id]; 348} 349 350# convert genre name to one byte id 351sub genre2id { 352 my $genre = shift; 353 my $i=0; 354 foreach (@winamp_genres) { 355 if (uc $genre eq uc $_) { 356 return $i; 357 } 358 $i++, 359 } 360 return 255; 361} 362 363# nothing to do for destroy 364sub DESTROY { 365} 366 3671; 368 369######## define all the genres 370 371BEGIN { @mp3_genres = ( 'Blues', 'Classic Rock', 'Country', 'Dance', 372 'Disco', 'Funk', 'Grunge', 'Hip-Hop', 'Jazz', 'Metal', 'New Age', 373 'Oldies', 'Other', 'Pop', 'R&B', 'Rap', 'Reggae', 'Rock', 'Techno', 374 'Industrial', 'Alternative', 'Ska', 'Death Metal', 'Pranks', 375 'Soundtrack', 'Euro-Techno', 'Ambient', 'Trip-Hop', 'Vocal', 376 'Jazz+Funk', 'Fusion', 'Trance', 'Classical', 'Instrumental', 'Acid', 377 'House', 'Game', 'Sound Clip', 'Gospel', 'Noise', 'AlternRock', 378 'Bass', 'Soul', 'Punk', 'Space', 'Meditative', 'Instrumental Pop', 379 'Instrumental Rock', 'Ethnic', 'Gothic', 'Darkwave', 380 'Techno-Industrial', 'Electronic', 'Pop-Folk', 'Eurodance', 'Dream', 381 'Southern Rock', 'Comedy', 'Cult', 'Gangsta', 'Top 40', 382 'Christian Rap', 'Pop/Funk', 'Jungle', 'Native American', 'Cabaret', 'New Wave', 383 'Psychadelic', 'Rave', 'Showtunes', 'Trailer', 'Lo-Fi', 'Tribal', 384 'Acid Punk', 'Acid Jazz', 'Polka', 'Retro', 'Musical', 'Rock & Roll', 385 'Hard Rock', ); 386 387 @winamp_genres = ( @mp3_genres, 'Folk', 'Folk-Rock', 388 'National Folk', 'Swing', 'Fast Fusion', 'Bebob', 'Latin', 'Revival', 389 'Celtic', 'Bluegrass', 'Avantgarde', 'Gothic Rock', 390 'Progressive Rock', 'Psychedelic Rock', 'Symphonic Rock', 391 'Slow Rock', 'Big Band', 'Chorus', 'Easy Listening', 392 'Acoustic', 'Humour', 'Speech', 'Chanson', 'Opera', 393 'Chamber Music', 'Sonata', 'Symphony', 'Booty Bass', 'Primus', 394 'Porn Groove', 'Satire', 'Slow Jam', 'Club', 'Tango', 'Samba', 395 'Folklore', 'Ballad', 'Power Ballad', 'Rhythmic Soul', 396 'Freestyle', 'Duet', 'Punk Rock', 'Drum Solo', 'Acapella', 397 'Euro-House', 'Dance Hall', ); 398} 399 400=pod 401 402=head1 SEE ALSO 403 404L<MP3::Tag>, L<MP3::Tag::ID3v2> 405 406ID3v1 standard - http://www.id3.org 407 408=cut 409