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__ |