File Coverage

lib/MP3/PodcastFetch/TagManager.pm
Criterion Covered Total %
statement 8 111 7.2
branch 1 76 1.3
condition 3 14 21.4
subroutine 3 17 17.6
pod 13 16 81.2
total 28 234 11.9


line stmt bran cond sub pod time code
1             package MP3::PodcastFetch::TagManager;
2             # $Id: TagManager.pm,v 1.4 2007/01/02 00:56:55 lstein Exp $
3              
4             # Handle various differences between ID3 tag libraries
5              
6             =head1 NAME
7              
8             MP3::PodcastFetch::TagManager -- Handle differences among ID3 tag libraries
9              
10             =head1 SYNOPSIS
11              
12             use MP3::PodcastFetch::TagManager;
13             my $manager = MP3::PodcastFetch::TagManager->new();
14             $manager->fix_tags('/tmp/podcasts/mypodcast.mp3',
15             { genre => 'Podcast',
16             album => 'My album',
17             artist => 'Lincoln Stein',
18             title => 'Podcast #18'
19             },
20             'auto');
21             my $duration = $manager->get_duration('/tmp/podcasts/mypodcast.mp3');
22              
23             =head1 DESCRIPTION
24              
25             This is a utility class written for MP3::PodcastFetch. It papers over
26             the differences between three Perl ID3 tagging modules, MP3::Info,
27             MP3::Tag and Audio::TagLib. No other tagging libraries are currently
28             supported.
29              
30             =head2 Main Methods
31              
32             The following methods are intended for public consumption.
33              
34             =over 4
35              
36             =cut
37              
38             my $MANAGER; # singleton
39              
40 1     1   6 use strict;
  1         2  
  1         1628  
41              
42             =item $manager = MP3::PodcastFetch::TagManager->new();
43              
44             Create a new manager object. At any time there can only be one such
45             object. Attempts to create new objects will retrieve the original
46             object.
47              
48             =cut
49              
50             sub new {
51 6     6 1 11 my $class = shift;
52 6   33     155 return $MANAGER ||= bless {},ref $class || $class;
      100        
53             }
54              
55             =item $manager->fix_tags($filename,$tag_hash,$upgrade_type)
56              
57             Attempt to write the tags from the keys and values contained in
58             $tag_hash. $filename is a path to a valid MP3 file. $tag_hash is a
59             hash reference containing one or more of the keys:
60              
61             genre
62             title
63             album
64             artist
65             comment
66             year
67              
68             $upgrade_type indicates what type of tag to write, and must be one of:
69              
70             id3v1
71             id3v2.3
72             id3v2.4
73             auto
74              
75             These will attempt to write ID3 tags at the indicated level. "auto"
76             attempts to write tags at the highest possible leve. Whether the
77             manager will be able to comply depends on which tagging modules are
78             present. For example, MP3::Tag can write ID3v2.3 and ID3v1 tags, but
79             not ID3v2.4.
80              
81             You should place this method in an eval {}, as errors are indicated by
82             raising a die() exception.
83              
84             =cut
85              
86             sub fix_tags {
87 6     6 1 11 my $self = shift;
88 6         10 my ($filename,$tags,$upgrade_type) = @_;
89 6 50       24 return unless $upgrade_type;
90 0 0 0       $self->{$upgrade_type} ||= $self->load_tag_fixer_code($upgrade_type)
91             or die "Couldn't load appropriate tagging library: $@";
92 0           $self->{$upgrade_type}->($filename,$tags);
93             }
94              
95             =item $duration = $manager->get_duration($filename)
96              
97             Get the duration of the indicated MP3 file using whatever library is
98             available. Returns undef if no tag library is available.
99              
100             =back
101              
102             =cut
103              
104             sub get_duration {
105 0     0 1   my $self = shift;
106 0           my $filename = shift;
107             # try various ways of getting the duration
108              
109 0 0         unless ($self->{duration_getter}) {
110              
111 0 0         if (eval {require Audio::TagLib; 1}) {
  0 0          
  0 0          
  0            
112 0           $self->{duration_getter} = \&get_duration_from_audiotaglib;
113             }
114 0           elsif (eval {require MP3::Info; 1}) {
  0            
115 0           $self->{duration_getter} = \&get_duration_from_mp3info;
116             }
117 0           elsif (eval {require MP3::Tag; 1}) {
118 0           $self->{duration_getter} = \&get_duration_from_mp3tag;
119             }
120             else {
121 0           return;
122             }
123             }
124 0           return $self->{duration_getter}->($filename);
125             }
126              
127             =head2 Internal Methods & Functions.
128              
129             The following methods are used internally, and may be overridden for
130             further functionality.
131              
132             =over 4
133              
134             =item $seconds = MP3::PodcastFetch::TagManager::get_duration_from_mp3info($filename)
135              
136             Get the duration using MP3::Info. Note that this is a function, not a method.
137              
138             =cut
139              
140             sub get_duration_from_mp3info {
141 0     0 1   my $filename = shift;
142 0 0         my $info = MP3::Info::get_mp3info($filename) or return 0;
143 0           return $info->{SS}
144             }
145              
146             =over 4
147              
148             =item $seconds = MP3::PodcastFetch::TagManager::get_duration_from_audiotaglib($filename)
149              
150             Get the duration using Audio::Taglib. Note that this is a function, not a method.
151              
152             =cut
153              
154             sub get_duration_from_audiotaglib {
155 0     0 1   my $filename = shift;
156 0           my $file = Audio::TagLib::MPEG::File->new($filename);
157 0 0         defined $file or return 0;
158 0           my $props = $file->audioProperties;
159 0           return $props->length;
160             }
161              
162             =over 4
163              
164             =item $seconds = MP3::PodcastFetch::TagManager::get_duration_from_mp3tag($filename)
165              
166             Get the duration using MP3::Tag. Note that this is a function, not a method.
167              
168             =cut
169              
170             sub get_duration_from_mp3tag {
171 0     0 1   my $filename = shift;
172 0 0         open OLDOUT, ">&", \*STDOUT or die "Can't dup STDOUT: $!";
173 0 0         open OLDERR, ">&", \*STDERR or die "Can't dup STDERR: $!";
174 0           open STDOUT, ">","/dev/null";
175 0           open STDERR, ">","/dev/null";
176 0 0         my $file = MP3::Tag->new($filename) or return 0;
177 0           open STDOUT, ">&",\*OLDOUT;
178 0           open STDERR, ">&",\*OLDERR;
179 0           return $file->total_secs_int;
180             }
181              
182             =item $coderef = $manager->load_tag_fixer_code($upgrade_type)
183              
184             Return a coderef to the appropriate function for updating the tag.
185              
186             =cut
187              
188             sub load_tag_fixer_code {
189 0     0 1   my $self = shift;
190 0           my $upgrade_type = shift;
191 0           $self->upgrade_tags($upgrade_type);
192 0 0 0       return $self->load_mp3_tag_lib if lc $upgrade_type eq 'id3v1' or lc $upgrade_type eq 'id3v2.3';
193 0 0         return $self->load_audio_tag_lib if lc $upgrade_type eq 'id3v2.4';
194 0 0 0       return $self->load_audio_tag_lib || $self->load_mp3_tag_lib
195             || $self->load_mp3_info_lib if lc $upgrade_type eq 'auto';
196 0           return;
197             }
198              
199             =item $result = $manager->load_mp3_tag_lib
200             =item $result = $manager->load_audio_tag_lib
201             =item $result = $manager->load_mp3_info_lib;
202              
203             These methods attempt to load the corresponding tagging libraries,
204             returning true if successful.
205              
206             =cut
207              
208             sub load_mp3_tag_lib {
209 0     0 1   my $self = shift;
210 0           my $loaded = eval {require MP3::Tag; 1; };
  0            
  0            
211 0 0         return unless $loaded;
212 0 0         return lc $self->upgrade_tags eq 'id3v1' ? \&upgrade_to_ID3v1 : \&upgrade_to_ID3v23;
213             }
214              
215             sub load_audio_tag_lib {
216 0     0 1   my $self = shift;
217 0           my $loaded = eval {require Audio::TagLib; 1; };
  0            
  0            
218 0 0         return unless $loaded;
219 0           return \&upgrade_to_ID3v24;
220             }
221              
222             sub load_mp3_info_lib {
223 0     0 1   my $self = shift;
224 0           my $loaded = eval {require MP3::Info; 1; };
  0            
  0            
225 0 0         return unless $loaded;
226 0           return \&upgrade_to_ID3v1_with_info;
227             }
228              
229             =item MP3::PodcastFetch::TagManager::upgrade_to_ID3v24($filename,$tags)
230             =item MP3::PodcastFetch::TagManager::upgrade_to_ID3v23($filename,$tags)
231             =item MP3::PodcastFetch::TagManager::upgrade_to_ID3v1($filename,$tags)
232              
233             These functions (not methods) update the tags of $filename to the
234             requested level.
235              
236             =back
237              
238             =cut
239              
240             sub upgrade_tags {
241 0     0 0   my $self = shift;
242 0           my $d = $self->{upgrade_type};
243 0 0         $self->{upgrade_type} = shift if @_;
244 0           $d;
245             }
246              
247             sub upgrade_to_ID3v24 {
248 0     0 1   my ($filename,$tags) = @_;
249 0           my $mp3 = Audio::TagLib::FileRef->new($filename);
250 0 0         defined $mp3 or die "Audio::TabLib::FileRef->new: $!";
251 0           $mp3->save; # this seems to upgrade the tag to v2.4
252 0           undef $mp3;
253 0           $mp3 = Audio::TagLib::FileRef->new($filename);
254 0           my $tag = $mp3->tag;
255 0 0         $tag->setGenre(Audio::TagLib::String->new($tags->{genre})) if defined $tags->{genre};
256 0 0         $tag->setTitle(Audio::TagLib::String->new($tags->{title})) if defined $tags->{title};
257 0 0         $tag->setAlbum(Audio::TagLib::String->new($tags->{album})) if defined $tags->{album};
258 0 0         $tag->setArtist(Audio::TagLib::String->new($tags->{artist})) if defined $tags->{artist};
259 0 0         $tag->setComment(Audio::TagLib::String->new($tags->{comment})) if defined $tags->{comment};
260 0 0         $tag->setYear($tags->{year}) if defined $tags->{year};
261 0           $mp3->save;
262             }
263              
264             sub upgrade_to_ID3v1 {
265 0     0 1   my ($filename,$tags,) = @_;
266 0           upgrade_to_ID3v1_or_23($filename,$tags,0);
267             }
268              
269             sub upgrade_to_ID3v23 {
270 0     0 1   my ($filename,$tags,) = @_;
271 0           upgrade_to_ID3v1_or_23($filename,$tags,1);
272             }
273              
274             sub upgrade_to_ID3v1_or_23 {
275 0     0 0   my ($filename,$tags,$v2) = @_;
276             # quench warnings from MP3::Tag
277 0 0         open OLDOUT, ">&", \*STDOUT or die "Can't dup STDOUT: $!";
278 0 0         open OLDERR, ">&", \*STDERR or die "Can't dup STDERR: $!";
279 0           open STDOUT, ">","/dev/null";
280 0           open STDERR, ">","/dev/null";
281 0 0         MP3::Tag->config(autoinfo=> $v2 ? ('ID3v1','ID3v1') : ('ID3v2','ID3v1'));
282 0 0         my $mp3 = MP3::Tag->new($filename) or die "MP3::Tag->new($filename): $!";
283 0           my $data = $mp3->autoinfo;
284 0 0         do { $data->{$_} = $tags->{$_} if defined $tags->{$_} } foreach qw(genre title album artist comment year);
  0            
285 0           $mp3->update_tags($data,$v2);
286 0           $mp3->close;
287 0           open STDOUT, ">&",\*OLDOUT;
288 0           open STDERR, ">&",\*OLDERR;
289             }
290              
291             sub upgrade_to_ID3v1_with_info {
292 0     0 0   my ($filename,$tags) = @_;
293 0 0         my $mp3 = MP3::Info->new($filename) or die;
294 0 0         $mp3->title($tags->{title}) if defined $tags->{title};
295 0 0         $mp3->genre($tags->{genre}) if defined $tags->{genre};
296 0 0         $mp3->album($tags->{album}) if defined $tags->{album};
297 0 0         $mp3->artist($tags->{artist}) if defined $tags->{artist};
298 0 0         $mp3->comment($tags->{comment}) if defined $tags->{comment};
299 0 0         $mp3->year($tags->{year}) if defined $tags->{year};
300             }
301              
302             1;
303              
304             __END__