File Coverage

blib/lib/MP3/ID3v1Tag.pm
Criterion Covered Total %
statement 12 129 9.3
branch 0 38 0.0
condition 0 19 0.0
subroutine 4 31 12.9
pod 0 26 0.0
total 16 243 6.5


line stmt bran cond sub pod time code
1             package MP3::ID3v1Tag;
2             require 5.004;
3              
4             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5             # $Id: ID3v1Tag.pm,v 2.11 2000/09/01 00:26:18 sander Exp $
6             #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7              
8 1     1   841 use strict;
  1         2  
  1         35  
9 1     1   5 use vars qw(@ISA @EXPORT @EXPORT_OK);
  1         2  
  1         56  
10 1     1   6 use Carp;
  1         5  
  1         77  
11 1     1   927 use IO::File;
  1         13229  
  1         2658  
12              
13             require Exporter;
14             @ISA = ('Exporter');
15             @EXPORT = qw();
16             @EXPORT_OK = qw();
17              
18             $MP3::ID3v1Tag::VERSION = do { my @r = (q$Revision: 2.11 $ =~ /\d+/g); $r[0]--;sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker
19              
20             ## Revision and debuging
21             $MP3::ID3v1Tag::revision = '$Id: ID3v1Tag.pm,v 2.11 2000/09/01 00:26:18 sander Exp $ ';
22             my $DEBUG = 0;
23              
24             ## SOME USEFULL CONSTANTS.
25             ## see http://www.dv.co.yu/mpgscript/mpeghdr.htm
26             ## by Predrag Supurovic
27             ## http://www.id3.org/
28             my $DefaultClass = 'MP3::ID3v1Tag';
29              
30             @MP3::ID3v1Tag::id3_genres_array = (
31             'Blues', 'Classic Rock', 'Country', 'Dance',
32             'Disco', 'Funk', 'Grunge', 'Hip-Hop', 'Jazz',
33             'Metal', 'New Age', 'Oldies', 'Other', 'Pop', 'R&B',
34             'Rap', 'Reggae', 'Rock', 'Techno', 'Industrial',
35             'Alternative', 'Ska', 'Death Metal', 'Pranks',
36             'Soundtrack', 'Euro-Techno', 'Ambient', 'Trip-Hop',
37             'Vocal', 'Jazz+Funk', 'Fusion', 'Trance',
38             'Classical', 'Instrumental', 'Acid', 'House',
39             'Game', 'Sound Clip', 'Gospel', 'Noise',
40             'AlternRock', 'Bass', 'Soul', 'Punk', 'Space',
41             'Meditative', 'Instrumental Pop',
42             'Instrumental Rock', 'Ethnic', 'Gothic', 'Darkwave',
43             'Techno-Industrial', 'Electronic', 'Pop-Folk',
44             'Eurodance', 'Dream', 'Southern Rock', 'Comedy',
45             'Cult', 'Gangsta', 'Top 40', 'Christian Rap',
46             'Pop/Funk', 'Jungle', 'Native American', 'Cabaret',
47             'New Wave', 'Psychadelic', 'Rave', 'Showtunes',
48             'Trailer', 'Lo-Fi', 'Tribal', 'Acid Punk',
49             'Acid Jazz', 'Polka', 'Retro', 'Musical',
50             'Rock & Roll',
51             'Hard Rock', 'Folk', 'Folk/Rock', 'National Folk',
52             'Swing', 'Fast Fusion', 'Bebob', 'Latin', 'Revival',
53             'Celtic', 'Bluegrass', 'Avantgarde', 'Gothic Rock',
54             'Progressive Rock', 'Psychedelic Rock',
55             'Symphonic Rock', 'Slow Rock', 'Big Band',
56             'Chorus', 'Easy Listening', 'Acoustic', 'Humour',
57             'Speech',
58             'Chanson', 'Opera', 'Chamber Music', 'Sonata',
59             'Symphony', 'Booty Bass', 'Primus', 'Porn Groove',
60             'Satire', 'Slow Jam', 'Club', 'Tango', 'Samba',
61             'Folklore', 'Ballad', 'Power Ballad',
62             'Rhythmic Soul', 'Freestyle', 'Duet',
63             'Punk Rock', 'Drum Solo', 'Acapella',
64             'Euro-house', 'Dance Hall'
65             );
66              
67             my $c = 0;
68             %MP3::ID3v1Tag::id3_genres = map {$_ => $c++ } @MP3::ID3v1Tag::id3_genres_array;
69              
70             ## A silly print routine useful for debugging.
71             sub debug {
72 0     0 0   my($self,$message) = @_;
73 0 0         print STDERR "$message\n" if $DEBUG;
74             }
75              
76             ## Constructor for Object of Module
77             sub new {
78 0     0 0   my($class,$mp3_file,$readonly) = @_;
79 0           my $self = {};
80 0 0         $readonly = 0 unless defined($readonly);
81 0           $self->{FileHandle} = new IO::File;
82 0 0 0       if( -w $mp3_file || !$readonly) {
83 0 0 0       $self->{FileHandle}->open("+<${mp3_file}") or (warn("Can't open ${mp3_file}: $!") and return undef);
84 0           $self->{readonly} = 0;
85             } else {
86 0 0 0       $self->{FileHandle}->open("<${mp3_file}") or (warn("Can't open ${mp3_file}: $!") and return undef);
87 0           $self->{readonly} = 1;
88             }
89 0           $self->{filename} = $mp3_file;
90 0           $self->{tag} = ();
91 0   0       bless($self, ref $class || $class || $DefaultClass);
92 0           my $initialized = $self->init();
93 0           return $self;
94             }
95              
96             # We provide a DESTROY method so that the autoloader
97             # doesn't bother trying to find it.
98             sub DESTROY {
99 0     0     my($self) = @_;
100 0           $self->{FileHandle}->close();
101             }
102              
103             ## Generic routine to see if this MP3 has an ID3v1Tag
104             sub got_tag {
105 0     0 0   my($self) = @_;
106 0 0         return ($self->find_tag_id3v1())?1:0;
107             }
108              
109             ## Some generic initialization
110             ## Find the headers and be ready for questions.
111             sub init {
112 0     0 0   my($self) = @_;
113 0           my $bytestring ="";
114 0           $bytestring = $self->find_tag_id3v1();
115 0 0         if(!defined($bytestring)) {
116 0           return 0;
117             } else {
118 0           $self->decode_tag_id3v1($bytestring);
119             }
120 0           return 1;
121             }
122              
123             ## Print a Genre Chart for easy reference.
124             sub print_genre_chart {
125 0     0 0   my($self,$columns) = @_;
126 0 0         $columns = 3 if ($columns <=0);
127 0           my $i = 0;
128 0           for(my $i = 0;$i < $#MP3::ID3v1Tag::id3_genres_array+1 ; $i += $columns) {
129 0   0       for(my $j = 0;($j < $columns) && ($i + $j < $#MP3::ID3v1Tag::id3_genres_array+1); $j++) {
130 0           printf("%2s. %-20s",$i + $j, $MP3::ID3v1Tag::id3_genres_array[$i + $j]);
131             }
132 0           print "\n";
133             }
134             }
135              
136             ## ID3v1 TAG at the END of the File.
137             ## Talks about ID3v2 being at the beginning of the file.
138             sub find_tag_id3v1 {
139 0     0 0   my($self) = @_;
140 0           my($bytes,$line);
141             ## MusicMatch has their data here aswell, but they are planning
142             ## on supporting ID3 to prevent issues.
143 0           $self->{FileHandle}->seek(-128,SEEK_END); # Find the last 128 bytes
144 0           while($line = $self->{FileHandle}->getline()) { $bytes .= $line; }
  0            
145 0 0         return undef if $bytes !~ /^TAG/; # Must have Tag Ident to be valid.
146 0           return $bytes;
147             }
148              
149              
150             ## Decode the ID3v1 Tag into useful tidbits.
151             sub decode_tag_id3v1 {
152 0     0 0   my($self,$buffer) = @_;
153             ## Unpack the Audio ID3v1
154 0           (undef, @{$self->{tag}}{qw/title artist album year comment genre_num/}) =
  0            
155             unpack('a3a30a30a30a4a30C1', $buffer);
156            
157             ## Clean em up a bit
158 0           foreach (sort keys %{$self->{tag}}) {
  0            
159 0 0         if(defined($self->{tag}{$_})) {
160 0           $self->{tag}{$_} =~ s/\s+$//;
161 0           $self->{tag}{$_} =~ s/\0.*$//;
162 0           $self->debug(sprintf("ID3v1: %s = ", $_ ) . $self->{tag}{$_});
163             }
164             }
165 0           $self->{tag}{'genre'} = $MP3::ID3v1Tag::id3_genres_array[$self->{tag}{'genre_num'}];
166 0           $self->debug(sprintf("ID3v1: %s = ", 'genre' ) . $self->{tag}{'genre'});
167             }
168              
169             sub encode_tag_id3v1 {
170 0     0 0   my($self) = @_;
171             ## Visit the beginning of the id3 tag if it exists
172 0 0         return 0 if($self->{readonly});
173 0 0         if(!defined($self->find_tag_id3v1())) {
174 0           $self->debug("Going to Append Tag");
175             # No Tag
176 0           $self->{FileHandle}->seek(0,SEEK_END); # Find EOF
177             } else {
178 0           $self->debug("Going to Re-write Tag.");
179 0           $self->{FileHandle}->seek(-128,SEEK_END); # Find the last 128 bytes
180             }
181 0 0         $self->{tag}{'genre_num'} = 255 if(!defined($self->{tag}{'genre_num'}));
182 0           $self->{FileHandle}->print(pack("a3a30a30a30a4a30C1",
183             'TAG',
184             $self->{tag}{'title'},
185             $self->{tag}{'artist'},
186             $self->{tag}{'album'},
187             $self->{tag}{'year'},
188             $self->{tag}{'comment'},
189             $self->{tag}{'genre_num'}));
190 0           $self->{FileHandle}->flush();
191 0           return 1;
192             }
193              
194             sub remove_tag {
195 0     0 0   my($self) = @_;
196 0 0         return 0 if($self->{readonly});
197 0 0         return 1 if(!defined($self->find_tag_id3v1()));
198 0           my $filesize = (stat($self->{FileHandle}))[7];
199 0           $self->debug("Removing Tag: File size = $filesize");
200 0           my $success = truncate($self->{FileHandle},($filesize - 128));
201 0           $filesize = (stat($self->{FileHandle}))[7];
202 0           $self->debug("Removed Tag : File size = $filesize");
203             }
204              
205             sub save {
206 0     0 0   my($self) = @_;
207 0           return $self->encode_tag_id3v1();
208             }
209             ## Print the Tag to default File Handler (usually STDOUT)
210             sub print_tag {
211 0     0 0   my($self) = @_;
212              
213 0 0         if(defined($self->{tag})) {
214 0           foreach (sort keys %{$self->{tag}}) {
  0            
215 0           print(sprintf("%-10s = ",$_ ) . $self->{tag}{$_} . "\n");
216             }
217             } else {
218 0           print "No ID3v1 Tag Found\n";
219             }
220             }
221              
222              
223             ##
224             sub get_title {
225 0     0 0   my($self) = @_;
226 0           return $self->{tag}{'title'};
227             }
228             sub set_title {
229 0     0 0   my ($self,$title) = @_;
230 0           $self->{tag}{'title'} = $title;
231             }
232              
233             ##
234             sub get_artist {
235 0     0 0   my($self) = @_;
236 0           return $self->{tag}{'artist'};
237             }
238             sub set_artist {
239 0     0 0   my($self,$artist) = @_;
240 0           $self->{tag}{'artist'} = $artist;
241             }
242              
243             ##
244             sub get_album {
245 0     0 0   my($self) = @_;
246 0           return $self->{tag}{'album'};
247             }
248             sub set_album {
249 0     0 0   my($self,$album) = @_;
250 0           $self->{tag}{'album'} = $album;
251             }
252              
253             ##
254             sub get_year {
255 0     0 0   my($self) = @_;
256 0           return $self->{tag}{'year'};
257             }
258             sub set_year {
259 0     0 0   my($self,$year) = @_;
260 0           $self->{tag}{'year'} = $year;
261             }
262              
263             sub get_comment {
264 0     0 0   my($self) = @_;
265 0           return $self->{tag}{'comment'};
266             }
267             sub set_comment {
268 0     0 0   my($self,$comment) = @_;
269 0           $self->{tag}{'comment'} = $comment;
270             }
271              
272             sub set_genre {
273 0     0 0   my($self,$genre) = @_;
274 0           my $genre_num = $MP3::ID3v1Tag::id3_genres{$genre};
275 0 0 0       if($genre_num >= 0 && $genre_num <= $#MP3::ID3v1Tag::id3_genres_array) {
276 0           $self->{tag}{'genre'} = $genre;
277 0           $self->{tag}{'genre_num'} = $genre_num;
278             }
279             }
280              
281             sub get_genre {
282 0     0 0   my($self) = @_;
283 0           return $self->{tag}{'genre'};
284             }
285              
286             sub set_genre_num {
287 0     0 0   my($self,$genre_num) = @_;
288 0 0 0       if( $genre_num >= 0 && $genre_num <= $#MP3::ID3v1Tag::id3_genres_array) {
289 0           $self->{tag}{'genre_num'} = $genre_num;
290 0           $self->{tag}{'genre'} = $MP3::ID3v1Tag::id3_genres_array[$genre_num];
291 0           return 1;
292             }
293 0           return 0;
294             }
295            
296             sub get_genre_num {
297 0     0 0   my($self) = @_;
298 0           return $self->{tag}{'genre_num'};
299             }
300            
301             ## Gives direct access to the %tag hash
302             sub tag {
303 0     0 0   my($self,$key) = @_;
304 0 0         return wantarray ? keys %{$self->{tag}}: $self->{tag}{$key};
  0            
305             }
306              
307            
308             1;
309              
310             =pod
311              
312             =head1 NAME
313              
314             MP3::ID3v1Tag - Edit ID3v1 Tags from an Audio MPEG Layer 3.
315              
316             =head1 SYNOPSIS
317              
318             use MP3::ID3v1Tag;
319              
320             $mp3_file = new MP3::ID3v1Tag("filename.mp3");
321             $mp3_file->print_tag();
322              
323             if($mp3_file->got_tag()) {
324             $mp3_file->set_title($title);
325             $save_status = $mp3_file->save();
326             }
327              
328              
329             =head1 DESCRIPTION
330              
331             The ID3v1Tag routines are useful for setting and reading ID3 MP3 Audio Tags.
332             Just create an MP3::ID3v1Tag Object with the path to the file of interest,
333             and query any of the methods below.
334              
335             =head2 Print Full ID3 Tag
336              
337             To get a print out of all the header information (Default FileHandler), simply
338             state the following
339              
340             $mp3_file->print_tag();
341              
342             =head2 Print Genre Chart
343              
344             With an optional number of columns argument (default is 3) this will
345             return a list of genre numbers with their appropriate genre.
346              
347             $mp3_file->print_genre_chart($COLUMNS);
348              
349             =head2 Checking for the Existance of ID3 Tags
350              
351             There is a handy method named got_tag() that can be easily used to determine
352             if a particular MP3 file contains an ID3 Tag.
353              
354             if $mp3_file->got_tag() {
355             $mp3_file->print_tag();
356             }
357              
358             =head2 Viewing Tag Compontents individually
359              
360             There exist several methods that will return you the individual components
361             of the ID3 Tag.
362              
363             $title = $mp3_file->get_title();
364             $artist = $mp3_file->get_artist();
365             $album = $mp3_file->get_album();
366             $year = $mp3_file->get_year();
367             $genre = $mp3_file->get_genre();
368             $genre_num = $mp3_file->get_genre_num();
369             $comment = $mp3_file->get_comment();
370              
371             =head2 Editing and Removing Tags
372              
373             Similar methods exist to allow you to change the components of the Tag,
374             but none of the changes will actually be changed in the file until you
375             call the save() routine.
376              
377             $mp3_file->set_title("New Title");
378             $mp3_file->set_artist("New Artist");
379             $mp3_file->set_album("New Album");
380             $mp3_file->set_year(1999);
381             $mp3_file->set_genre("Blues");
382             # Or use the genre numbers ->
383             $mp3_file->set_genre_num(0);
384              
385             To remove an tag in its entirely just calling the remove_tag() method
386             should work for you.
387              
388             $mp3_file->remove_tag() if $mp3_file->got_tag();
389              
390             You could access all the components directly for a read only loop such
391             as the following
392              
393             foreach (sort $mp3_file->tag) {
394             print "$_: " . $mp3_file->tag($_) . "\n";
395             }
396              
397              
398             =head1 AUTHOR
399              
400             Sander van Zoest Esvanzoest@cpan.orgE
401              
402             =head1 THANKS
403              
404             Matt Plummer Ematt@mp3.comE, Mike Oliphant Eoliphant@gtk.orgE,
405             Matt DiMeo Emattd@mp3.comE, Olaf Maetzner, Jason Bodnar and Peter
406             Johansson
407              
408             =head1 COPYRIGHT
409              
410             Copyright 2000, Alexander van Zoest. All rights reserved.
411             Copyright 1999-2000, Alexander van Zoest, MP3.com, Inc. All rights reserved.
412              
413             This program is free software; you can redistribute it and/or modify it
414             under the same terms as Perl itself.
415              
416             =head1 REFERENCES
417              
418             For general overview of MPEG 1.0, Layer 3 (MP3) Audio visit
419             or get the book,
420             "MP3: The Definitive Guide" by O'Reilly and Associates
421             .
422              
423             For technical details about MP3 Audio read the
424             ISO/IEC 11172 and ISO/IEC 13818 specifications, obtained via
425             in the US or
426             elsewhere in the world. For more information also check
427             out compiled by Gabriel Bouvigne.
428              
429             For more specific references to the MP3 Audio ID3 Tags visit
430            
431              
432             For information about ID3v2 and a perl implementation see MPEG::ID3v2Tag
433             written by Matt DiMeo Emattd@mp3.comE.
434              
435             =cut