| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package MP3::Album; |
|
2
|
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
43172
|
use strict; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
43
|
|
|
4
|
1
|
|
|
1
|
|
434
|
use MP3::Info; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
use MP3::Album::Track; |
|
6
|
|
|
|
|
|
|
use MP3::Album::Layout::Fetcher; |
|
7
|
|
|
|
|
|
|
use MP3::Album::Layout; |
|
8
|
|
|
|
|
|
|
use File::Basename; |
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
our $VERSION = '0.14'; |
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
sub new { |
|
13
|
|
|
|
|
|
|
my $c = shift; |
|
14
|
|
|
|
|
|
|
my %a = @_; |
|
15
|
|
|
|
|
|
|
my $s = { }; |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
$s = bless $s, $c; |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
$s->{tracks} = []; |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
if ($a{files}) { |
|
22
|
|
|
|
|
|
|
$a{files} = [ $a{files} ] if ( ref($a{files}) ne 'ARRAY' ); |
|
23
|
|
|
|
|
|
|
foreach my $f (@{$a{files}}) { |
|
24
|
|
|
|
|
|
|
my $tr = MP3::Album::Track->new(filename => $f); |
|
25
|
|
|
|
|
|
|
unless ($tr) { return undef; } |
|
26
|
|
|
|
|
|
|
push @{$s->{tracks}}, $tr; |
|
27
|
|
|
|
|
|
|
} |
|
28
|
|
|
|
|
|
|
} |
|
29
|
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
$s->{current_layout} = undef; |
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
return $s; |
|
33
|
|
|
|
|
|
|
} |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
sub rename_files { |
|
36
|
|
|
|
|
|
|
my $s = shift; |
|
37
|
|
|
|
|
|
|
my %a = @_; |
|
38
|
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
# %t track title; |
|
40
|
|
|
|
|
|
|
# %a album name |
|
41
|
|
|
|
|
|
|
# %n track number |
|
42
|
|
|
|
|
|
|
# %p artist name |
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
$a{format} = '%p - %a - %n - %t.mp3' unless $a{format}; |
|
45
|
|
|
|
|
|
|
$a{keep_copy} |= 0; |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
my $layout = $s->layout(); |
|
48
|
|
|
|
|
|
|
my $li = $layout->info(); |
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
for (my $i = 0; $i < scalar(@{$s->{tracks}}); $i++) { |
|
51
|
|
|
|
|
|
|
next unless $s->{tracks}->[$i]; |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
my $track_number = sprintf("%02d",$i+1); |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
my $fn = $a{format}; |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
$fn =~ s/\%p/$li->{tracks}->[$i]->{artist}/g; |
|
58
|
|
|
|
|
|
|
$fn =~ s/\%a/$li->{title}/g; |
|
59
|
|
|
|
|
|
|
$fn =~ s/\%n/$track_number/g; |
|
60
|
|
|
|
|
|
|
$fn =~ s/\%t/$li->{tracks}->[$i]->{title}/g; |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
$fn =~ s/\//_/g; |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
my $dirname = $a{out_dir} ? $a{out_dir} : dirname($s->{tracks}->[$i]->filename()); |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
my $r = $s->{tracks}->[$i]->rename(filename=>"$dirname/$fn", keep_copy => $a{keep_copy}); |
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
return undef unless $r; |
|
69
|
|
|
|
|
|
|
}; |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
return 1; |
|
72
|
|
|
|
|
|
|
} |
|
73
|
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
sub generate_tags { |
|
75
|
|
|
|
|
|
|
my $s = shift; |
|
76
|
|
|
|
|
|
|
$s->_init unless $s->{_init_done}; |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
my $layout = $s->layout(); |
|
79
|
|
|
|
|
|
|
my $li = $layout->info(); |
|
80
|
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
for (my $i = 0; $i < scalar(@{$s->{tracks}}); $i++) { |
|
82
|
|
|
|
|
|
|
next unless $s->{tracks}->[$i]; |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
my $track_number = sprintf("%02d",$i+1); |
|
85
|
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
my $r = $s->{tracks}->[$i]->set_tag( |
|
87
|
|
|
|
|
|
|
title => $li->{tracks}->[$i]->{title}, |
|
88
|
|
|
|
|
|
|
artist => $li->{tracks}->[$i]->{artist}, |
|
89
|
|
|
|
|
|
|
album => $li->{title}, |
|
90
|
|
|
|
|
|
|
year => $li->{year}, |
|
91
|
|
|
|
|
|
|
genre => $li->{genre}, |
|
92
|
|
|
|
|
|
|
comment => $li->{comment}, |
|
93
|
|
|
|
|
|
|
track_number => $track_number |
|
94
|
|
|
|
|
|
|
); |
|
95
|
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
return undef unless $r; |
|
97
|
|
|
|
|
|
|
} |
|
98
|
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
return 1; |
|
100
|
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
} |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
sub info { |
|
104
|
|
|
|
|
|
|
my $s = shift; |
|
105
|
|
|
|
|
|
|
$s->_init unless $s->{_init_done}; |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
my %info = ( |
|
108
|
|
|
|
|
|
|
number_oF_tracks => scalar(@{$s->{tracks}}), |
|
109
|
|
|
|
|
|
|
bitrates => [ $s->_album_bitrate() ], |
|
110
|
|
|
|
|
|
|
uniform_bitrate => scalar($s->_album_bitrate()) == 1 ? 1 : 0, |
|
111
|
|
|
|
|
|
|
frequencies => [ $s->_album_frequencies() ], |
|
112
|
|
|
|
|
|
|
uniform_frequency => scalar($s->_album_frequency()) == 1 ? 1 : 0, |
|
113
|
|
|
|
|
|
|
cddb_disc_id => $s->{cddb_query}->{discid} |
|
114
|
|
|
|
|
|
|
); |
|
115
|
|
|
|
|
|
|
return wantarray ? %info : \%info; |
|
116
|
|
|
|
|
|
|
} |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
sub frequency_check { |
|
119
|
|
|
|
|
|
|
my $s = shift; |
|
120
|
|
|
|
|
|
|
$s->_init unless $s->{_init_done}; |
|
121
|
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
return scalar($s->_album_frequencies()) == 1 ? 1 : 0; |
|
123
|
|
|
|
|
|
|
} |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
sub bitrate_check{ |
|
126
|
|
|
|
|
|
|
my $s = shift; |
|
127
|
|
|
|
|
|
|
$s->_init unless $s->{_init_done}; |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
return scalar($s->_album_bitrate()) == 1 ? 1 : 0; |
|
130
|
|
|
|
|
|
|
} |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
sub tracks { |
|
133
|
|
|
|
|
|
|
my $s = shift; |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
return @{$s->{tracks}}; |
|
136
|
|
|
|
|
|
|
} |
|
137
|
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
sub add { |
|
139
|
|
|
|
|
|
|
my $s = shift; |
|
140
|
|
|
|
|
|
|
my %a = @_; |
|
141
|
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
$s->_init unless $s->{_init_done}; |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
unless ( $a{track} ) { |
|
145
|
|
|
|
|
|
|
$@ = "missing param track"; |
|
146
|
|
|
|
|
|
|
return undef; |
|
147
|
|
|
|
|
|
|
} |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
my $tr = MP3::Album::Track->new(filename => $a{track}); |
|
150
|
|
|
|
|
|
|
unless ($tr) { return undef } |
|
151
|
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
$a{position} = scalar(@{$s->{tracks}})+1 unless $a{position}; |
|
153
|
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
if ( $a{position} > scalar(@{$s->{tracks}})+1 ) { |
|
155
|
|
|
|
|
|
|
$@ = "position must be between start and end of the album"; |
|
156
|
|
|
|
|
|
|
return undef; |
|
157
|
|
|
|
|
|
|
} |
|
158
|
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
my @tracks = @{$s->{tracks}}; |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
my @f = splice(@tracks, 0, ($a{position}-1)); |
|
162
|
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
$s->{tracks} = [@f, $tr, @tracks]; |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
delete $s->{_init_done}; |
|
166
|
|
|
|
|
|
|
return 1; |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
}; |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
sub layout { |
|
171
|
|
|
|
|
|
|
my $s = shift; |
|
172
|
|
|
|
|
|
|
$s->_init unless $s->{_init_done}; |
|
173
|
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
if (@_) { |
|
175
|
|
|
|
|
|
|
unless ( $_[0] && (ref $_[0] eq 'MP3::Album::Layout') ) { |
|
176
|
|
|
|
|
|
|
$@ = "I need a MP3::Album::Layout to set up the album layout"; |
|
177
|
|
|
|
|
|
|
return undef; |
|
178
|
|
|
|
|
|
|
} |
|
179
|
|
|
|
|
|
|
$s->{current_layout} = $_[0]; |
|
180
|
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
} elsif (! $s->{current_layout} ) { |
|
182
|
|
|
|
|
|
|
my $l = $s->fetch_layout(); |
|
183
|
|
|
|
|
|
|
return undef if (!$l); |
|
184
|
|
|
|
|
|
|
$s->{current_layout} = $l->[0]; |
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
return $s->{current_layout}; |
|
188
|
|
|
|
|
|
|
} |
|
189
|
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
sub fetch_layout { |
|
191
|
|
|
|
|
|
|
my $s = shift; |
|
192
|
|
|
|
|
|
|
my %a = @_; |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
$s->_init unless $s->{_init_done}; |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
$a{method} = 'Tag' unless $a{method}; |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
my $layouts = MP3::Album::Layout::Fetcher->fetch(album=>$s, method=>$a{method}); |
|
199
|
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
return undef unless $layouts; |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
return wantarray ? @$layouts : $layouts; |
|
203
|
|
|
|
|
|
|
}; |
|
204
|
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
sub available_fetchers { |
|
206
|
|
|
|
|
|
|
return MP3::Album::Layout::Fetcher->available_fetchers(); |
|
207
|
|
|
|
|
|
|
} |
|
208
|
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
sub _album_frequencies { |
|
210
|
|
|
|
|
|
|
my $s = shift; |
|
211
|
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
my %freqs; |
|
213
|
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
foreach my $t (@{$s->{tracks}}) { |
|
215
|
|
|
|
|
|
|
$freqs{$t->bitrate()}=1 if $t->bitrate(); |
|
216
|
|
|
|
|
|
|
} |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
return keys(%freqs); |
|
219
|
|
|
|
|
|
|
} |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
sub _album_bitrate { |
|
222
|
|
|
|
|
|
|
my $s = shift; |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
my %bitrates; |
|
225
|
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
foreach my $t (@{$s->{tracks}}) { |
|
227
|
|
|
|
|
|
|
$bitrates{$t->bitrate()}=1 if $t->bitrate(); |
|
228
|
|
|
|
|
|
|
} |
|
229
|
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
return keys(%bitrates); |
|
231
|
|
|
|
|
|
|
} |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
sub _generate_toc { |
|
234
|
|
|
|
|
|
|
my $s = shift; |
|
235
|
|
|
|
|
|
|
my @toc; |
|
236
|
|
|
|
|
|
|
foreach my $f (@{$s->{tracks}}) { |
|
237
|
|
|
|
|
|
|
push @toc, $f->{info}; |
|
238
|
|
|
|
|
|
|
} |
|
239
|
|
|
|
|
|
|
return @toc; |
|
240
|
|
|
|
|
|
|
} |
|
241
|
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
sub _cddb_query_builder { |
|
243
|
|
|
|
|
|
|
my $s = shift; |
|
244
|
|
|
|
|
|
|
my %a = @_; |
|
245
|
|
|
|
|
|
|
die "need a toc to build a query\n" unless $a{toc}; |
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
my $discid = $s->_disc_id(toc => $a{toc}); |
|
248
|
|
|
|
|
|
|
my $disctime = $s->_disc_time(toc => $a{toc}); |
|
249
|
|
|
|
|
|
|
my $total_tracks = @{$a{toc}}; |
|
250
|
|
|
|
|
|
|
my $frames = $s->_get_frames(toc=> $a{toc}); |
|
251
|
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
return { discid=> $discid, disctime=>$disctime, disctracks=>$total_tracks, frames=>$frames }; |
|
253
|
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
} |
|
255
|
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
sub _get_frames { |
|
257
|
|
|
|
|
|
|
my $s = shift; |
|
258
|
|
|
|
|
|
|
my %a = @_; |
|
259
|
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
my @frames; |
|
261
|
|
|
|
|
|
|
my $t = 0; |
|
262
|
|
|
|
|
|
|
foreach my $track ( @{$a{toc}} ) { |
|
263
|
|
|
|
|
|
|
push @frames, $t * 75; |
|
264
|
|
|
|
|
|
|
$t += ( $track->{MM} * 60 + $track->{SS} ); |
|
265
|
|
|
|
|
|
|
} |
|
266
|
|
|
|
|
|
|
return \@frames; |
|
267
|
|
|
|
|
|
|
} |
|
268
|
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
sub _disc_time { |
|
270
|
|
|
|
|
|
|
my $s = shift; |
|
271
|
|
|
|
|
|
|
my %a = @_; |
|
272
|
|
|
|
|
|
|
my $total_time = 0; |
|
273
|
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
foreach my $track (@{$a{toc}}) { |
|
275
|
|
|
|
|
|
|
my $track_time = $track->{MM} * 60 + $track->{SS}; |
|
276
|
|
|
|
|
|
|
$total_time += $track_time; |
|
277
|
|
|
|
|
|
|
} |
|
278
|
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
return $total_time; |
|
280
|
|
|
|
|
|
|
} |
|
281
|
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
sub _disc_id { |
|
283
|
|
|
|
|
|
|
my $s = shift; |
|
284
|
|
|
|
|
|
|
my %a = @_; |
|
285
|
|
|
|
|
|
|
my $n = 0; |
|
286
|
|
|
|
|
|
|
my $total_time = 0; |
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
foreach my $track ( @{$a{toc}} ) { |
|
289
|
|
|
|
|
|
|
my $track_time = $track->{MM} * 60 + $track->{SS}; |
|
290
|
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
$n += $s->_cddb_sum($total_time); |
|
292
|
|
|
|
|
|
|
$total_time += $track_time; |
|
293
|
|
|
|
|
|
|
} |
|
294
|
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
return sprintf("%08x", ($n % 0xFF) << 24 | $total_time << 8 | @{$a{toc}}); |
|
296
|
|
|
|
|
|
|
} |
|
297
|
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
sub _cddb_sum { |
|
299
|
|
|
|
|
|
|
my $s = shift; |
|
300
|
|
|
|
|
|
|
my ($n, $ret) = (shift, 0); |
|
301
|
|
|
|
|
|
|
for (split //, $n) { $ret += $_ } |
|
302
|
|
|
|
|
|
|
return $ret; |
|
303
|
|
|
|
|
|
|
} |
|
304
|
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
sub _init { |
|
306
|
|
|
|
|
|
|
my $s = shift; |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
@{$s->{toc}} = $s->_generate_toc; |
|
309
|
|
|
|
|
|
|
$s->{cddb_query} = $s->_cddb_query_builder(toc=>$s->{toc}); |
|
310
|
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
$s->{_init_done} = 1; |
|
312
|
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
return 1; |
|
314
|
|
|
|
|
|
|
} |
|
315
|
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
1; |