line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# |
2
|
|
|
|
|
|
|
# Copyright (c) 2004-2010 Jonathan Harris |
3
|
|
|
|
|
|
|
# |
4
|
|
|
|
|
|
|
# This program is free software; you can redistribute it and/or modify it |
5
|
|
|
|
|
|
|
# under the the same terms as Perl itself. |
6
|
|
|
|
|
|
|
# |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
package MP4::Info; |
9
|
|
|
|
|
|
|
|
10
|
1
|
|
|
1
|
|
31271
|
use overload; |
|
1
|
|
|
|
|
1518
|
|
|
1
|
|
|
|
|
7
|
|
11
|
1
|
|
|
1
|
|
54
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
44
|
|
12
|
1
|
|
|
1
|
|
7
|
use Carp; |
|
1
|
|
|
|
|
8
|
|
|
1
|
|
|
|
|
77
|
|
13
|
1
|
|
|
1
|
|
1246
|
use Symbol; |
|
1
|
|
|
|
|
1480
|
|
|
1
|
|
|
|
|
101
|
|
14
|
1
|
|
|
1
|
|
13116
|
use Encode; |
|
1
|
|
|
|
|
29690
|
|
|
1
|
|
|
|
|
115
|
|
15
|
1
|
|
|
1
|
|
6242
|
use Encode::Guess qw(latin1); |
|
1
|
|
|
|
|
30797
|
|
|
1
|
|
|
|
|
9
|
|
16
|
1
|
|
|
1
|
|
5741
|
use IO::String; |
|
1
|
|
|
|
|
6212
|
|
|
1
|
|
|
|
|
65
|
|
17
|
|
|
|
|
|
|
|
18
|
1
|
|
|
|
|
21408
|
use vars qw( |
19
|
|
|
|
|
|
|
$VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $AUTOLOAD |
20
|
|
|
|
|
|
|
%data_atoms %other_atoms %container_atoms @mp4_genres |
21
|
1
|
|
|
1
|
|
13
|
); |
|
1
|
|
|
|
|
3
|
|
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
@ISA = 'Exporter'; |
24
|
|
|
|
|
|
|
@EXPORT = qw(get_mp4tag get_mp4info); |
25
|
|
|
|
|
|
|
@EXPORT_OK = qw(use_mp4_utf8); |
26
|
|
|
|
|
|
|
%EXPORT_TAGS = ( |
27
|
|
|
|
|
|
|
utf8 => [qw(use_mp4_utf8)], |
28
|
|
|
|
|
|
|
all => [@EXPORT, @EXPORT_OK] |
29
|
|
|
|
|
|
|
); |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
$VERSION = '1.13'; |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
my $debug = 0; |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
=head1 NAME |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
MP4::Info - Fetch info from MPEG-4 files (.mp4, .m4a, .m4p, .3gp) |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
=head1 SYNOPSIS |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
#!perl -w |
43
|
|
|
|
|
|
|
use MP4::Info; |
44
|
|
|
|
|
|
|
my $file = 'Pearls_Before_Swine.m4a'; |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
my $tag = get_mp4tag($file) or die "No TAG info"; |
47
|
|
|
|
|
|
|
printf "$file is a %s track\n", $tag->{GENRE}; |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
my $info = get_mp4info($file); |
50
|
|
|
|
|
|
|
printf "$file length is %d:%d\n", $info->{MM}, $info->{SS}; |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
my $mp4 = new MP4::Info $file; |
53
|
|
|
|
|
|
|
printf "$file length is %s, title is %s\n", |
54
|
|
|
|
|
|
|
$mp4->time, $mp4->title; |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
=head1 DESCRIPTION |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
The MP4::Info module can be used to extract tag and meta information from |
59
|
|
|
|
|
|
|
MPEG-4 audio (AAC) and video files. It is designed as a drop-in replacement |
60
|
|
|
|
|
|
|
for L. |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
Note that this module does not allow you to update the information in MPEG-4 |
63
|
|
|
|
|
|
|
files. |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
=over 4 |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
=item $mp4 = MP4::Info-Enew(FILE) |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
OOP interface to the rest of the module. The same keys available via |
70
|
|
|
|
|
|
|
C and C are available via the returned object |
71
|
|
|
|
|
|
|
(using upper case or lower case; but note that all-caps 'VERSION' will |
72
|
|
|
|
|
|
|
return the module version, not the MPEG-4 version). |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
Passing a value to one of the methods will B set the value for that tag |
75
|
|
|
|
|
|
|
in the MPEG-4 file. |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=cut |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
sub new |
80
|
|
|
|
|
|
|
{ |
81
|
5
|
|
|
5
|
1
|
7854
|
my ($class, $file) = @_; |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
# Supported tags |
84
|
5
|
|
|
|
|
125
|
my %tag_names = |
85
|
|
|
|
|
|
|
( |
86
|
|
|
|
|
|
|
ALB => 1, APID => 1, ART => 1, CMT => 1, COVR => 1, CPIL => 1, CPRT => 1, DAY => 1, DISK => 1, GNRE => 1, GRP => 1, NAM => 1, RTNG => 1, TMPO => 1, TOO => 1, TRKN => 1, WRT => 1, |
87
|
|
|
|
|
|
|
TITLE => 1, ARTIST => 1, ALBUM => 1, YEAR => 1, COMMENT => 1, GENRE => 1, TRACKNUM => 1, |
88
|
|
|
|
|
|
|
VERSION => 1, LAYER => 1, |
89
|
|
|
|
|
|
|
BITRATE => 1, FREQUENCY => 1, SIZE => 1, |
90
|
|
|
|
|
|
|
SECS => 1, MM => 1, SS => 1, MS => 1, TIME => 1, |
91
|
|
|
|
|
|
|
COPYRIGHT => 1, ENCODING => 1, ENCRYPTED => 1, |
92
|
|
|
|
|
|
|
); |
93
|
|
|
|
|
|
|
|
94
|
5
|
50
|
|
|
|
20
|
my $tags = get_mp4tag ($file) or return undef; |
95
|
5
|
|
|
|
|
119
|
my $self = { |
96
|
|
|
|
|
|
|
_permitted => \%tag_names, |
97
|
|
|
|
|
|
|
%$tags |
98
|
|
|
|
|
|
|
}; |
99
|
5
|
|
|
|
|
74
|
return bless $self, $class; |
100
|
|
|
|
|
|
|
} |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
# Create accessor functions - see perltoot manpage |
104
|
|
|
|
|
|
|
sub AUTOLOAD |
105
|
|
|
|
|
|
|
{ |
106
|
313
|
|
|
313
|
|
54682
|
my $self = shift; |
107
|
313
|
50
|
|
|
|
960
|
my $type = ref($self) or croak "$self is not an object"; |
108
|
313
|
|
|
|
|
476
|
my $name = $AUTOLOAD; |
109
|
313
|
|
|
|
|
2424
|
$name =~ s/.*://; # strip fully-qualified portion |
110
|
|
|
|
|
|
|
|
111
|
313
|
50
|
|
|
|
1050
|
unless (exists $self->{_permitted}->{uc $name} ) |
112
|
|
|
|
|
|
|
{ |
113
|
0
|
|
|
|
|
0
|
croak "No method '$name' available in class $type"; |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# Ignore any parameter |
117
|
313
|
|
|
|
|
1715
|
return $self->{uc $name}; |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
sub DESTROY |
122
|
0
|
|
|
0
|
|
0
|
{ |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
############################################################################ |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
=item use_mp4_utf8([STATUS]) |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
Tells MP4::Info whether to assume that ambiguously encoded TAG info is UTF-8 |
131
|
|
|
|
|
|
|
or Latin-1. 1 is UTF-8, 0 is Latin-1. Default is UTF-8. |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
Function returns new status (1/0). If no argument is supplied, or an |
134
|
|
|
|
|
|
|
unaccepted argument is supplied, function merely returns existing status. |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
This function is not exported by default, but may be exported |
137
|
|
|
|
|
|
|
with the C<:utf8> or C<:all> export tag. |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=cut |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
my $utf8 = 1; |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
sub use_mp4_utf8 |
144
|
|
|
|
|
|
|
{ |
145
|
1
|
|
|
1
|
1
|
1798
|
my ($val) = @_; |
146
|
1
|
50
|
33
|
|
|
8
|
$utf8 = $val if (($val == 0) || ($val == 1)); |
147
|
1
|
|
|
|
|
3
|
return $utf8; |
148
|
|
|
|
|
|
|
} |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=item get_mp4tag (FILE) |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
Returns hash reference containing the tag information from the MP4 file. |
154
|
|
|
|
|
|
|
The following keys may be defined: |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
ALB Album |
157
|
|
|
|
|
|
|
APID Apple Store ID |
158
|
|
|
|
|
|
|
ART Artist |
159
|
|
|
|
|
|
|
CMT Comment |
160
|
|
|
|
|
|
|
COVR Album art (typically JPEG or PNG data) |
161
|
|
|
|
|
|
|
CPIL Compilation (boolean) |
162
|
|
|
|
|
|
|
CPRT Copyright statement |
163
|
|
|
|
|
|
|
DAY Year |
164
|
|
|
|
|
|
|
DISK Disk number & total (2 integers) |
165
|
|
|
|
|
|
|
GNRE Genre |
166
|
|
|
|
|
|
|
GRP Grouping |
167
|
|
|
|
|
|
|
NAM Title |
168
|
|
|
|
|
|
|
RTNG Rating (integer) |
169
|
|
|
|
|
|
|
TMPO Tempo (integer) |
170
|
|
|
|
|
|
|
TOO Encoder |
171
|
|
|
|
|
|
|
TRKN Track number & total (2 integers) |
172
|
|
|
|
|
|
|
WRT Author or composer |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
For compatibility with L, the MP3 ID3v1-style keys |
175
|
|
|
|
|
|
|
TITLE, ARTIST, ALBUM, YEAR, COMMENT, GENRE and TRACKNUM are defined as |
176
|
|
|
|
|
|
|
synonyms for NAM, ART, ALB, DAY, CMT, GNRE and TRKN[0]. |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
Any and all of these keys may be undefined if the corresponding information |
179
|
|
|
|
|
|
|
is missing from the MPEG-4 file. |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
On error, returns nothing and sets C<$@>. |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=cut |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
sub get_mp4tag |
186
|
|
|
|
|
|
|
{ |
187
|
12
|
|
|
12
|
1
|
540
|
my ($file) = @_; |
188
|
12
|
|
|
|
|
29
|
my (%tags); |
189
|
|
|
|
|
|
|
|
190
|
12
|
50
|
|
|
|
45
|
return parse_file ($file, \%tags) ? undef : {%tags}; |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=item get_mp4info (FILE) |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
Returns hash reference containing file information from the MPEG-4 file. |
197
|
|
|
|
|
|
|
The following keys may be defined: |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
VERSION MPEG version (=4) |
200
|
|
|
|
|
|
|
LAYER MPEG layer description (=1 for compatibility with MP3::Info) |
201
|
|
|
|
|
|
|
BITRATE bitrate in kbps (average for VBR files) |
202
|
|
|
|
|
|
|
FREQUENCY frequency in kHz |
203
|
|
|
|
|
|
|
SIZE bytes in audio stream |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
SECS total seconds, rounded to nearest second |
206
|
|
|
|
|
|
|
MM minutes |
207
|
|
|
|
|
|
|
SS leftover seconds |
208
|
|
|
|
|
|
|
MS leftover milliseconds, rounded to nearest millisecond |
209
|
|
|
|
|
|
|
TIME time in MM:SS, rounded to nearest second |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
COPYRIGHT boolean for audio is copyrighted |
212
|
|
|
|
|
|
|
ENCODING audio codec name. Possible values include: |
213
|
|
|
|
|
|
|
'mp4a' - AAC, aacPlus |
214
|
|
|
|
|
|
|
'alac' - Apple lossless |
215
|
|
|
|
|
|
|
'drms' - Apple encrypted AAC |
216
|
|
|
|
|
|
|
'samr' - 3GPP narrow-band AMR |
217
|
|
|
|
|
|
|
'sawb' - 3GPP wide-band AMR |
218
|
|
|
|
|
|
|
'enca' - Unspecified encrypted audio |
219
|
|
|
|
|
|
|
ENCRYPTED boolean for audio data is encrypted |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
Any and all of these keys may be undefined if the corresponding information |
222
|
|
|
|
|
|
|
is missing from the MPEG-4 file. |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
On error, returns nothing and sets C<$@>. |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
=cut |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
sub get_mp4info |
229
|
|
|
|
|
|
|
{ |
230
|
5
|
|
|
5
|
1
|
15636
|
my ($file) = @_; |
231
|
5
|
|
|
|
|
13
|
my (%tags); |
232
|
|
|
|
|
|
|
|
233
|
5
|
50
|
|
|
|
24
|
return parse_file ($file, \%tags) ? undef : {%tags}; |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
############################################################################ |
238
|
|
|
|
|
|
|
# No user-servicable parts below |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
# Interesting atoms that contain data in standard format. |
242
|
|
|
|
|
|
|
# The items marked ??? contain integers - I don't know what these are for |
243
|
|
|
|
|
|
|
# but return them anyway because the user might know. |
244
|
|
|
|
|
|
|
my %data_atoms = |
245
|
|
|
|
|
|
|
( |
246
|
|
|
|
|
|
|
AART => 1, # Album artist - returned in ART field no ART found |
247
|
|
|
|
|
|
|
ALB => 1, |
248
|
|
|
|
|
|
|
ART => 1, |
249
|
|
|
|
|
|
|
CMT => 1, |
250
|
|
|
|
|
|
|
COVR => 1, # Cover art |
251
|
|
|
|
|
|
|
CPIL => 1, |
252
|
|
|
|
|
|
|
CPRT => 1, |
253
|
|
|
|
|
|
|
DAY => 1, |
254
|
|
|
|
|
|
|
DISK => 1, |
255
|
|
|
|
|
|
|
GEN => 1, # Custom genre - returned in GNRE field no GNRE found |
256
|
|
|
|
|
|
|
GNRE => 1, # Standard ID3/WinAmp genre |
257
|
|
|
|
|
|
|
GRP => 1, |
258
|
|
|
|
|
|
|
NAM => 1, |
259
|
|
|
|
|
|
|
RTNG => 1, |
260
|
|
|
|
|
|
|
TMPO => 1, |
261
|
|
|
|
|
|
|
TOO => 1, |
262
|
|
|
|
|
|
|
TRKN => 1, |
263
|
|
|
|
|
|
|
WRT => 1, |
264
|
|
|
|
|
|
|
# Apple store |
265
|
|
|
|
|
|
|
APID => 1, |
266
|
|
|
|
|
|
|
AKID => 1, # ??? |
267
|
|
|
|
|
|
|
ATID => 1, # ??? |
268
|
|
|
|
|
|
|
CNID => 1, # ??? |
269
|
|
|
|
|
|
|
GEID => 1, # Some kind of watermarking ??? |
270
|
|
|
|
|
|
|
PLID => 1, # ??? |
271
|
|
|
|
|
|
|
# 3GPP |
272
|
|
|
|
|
|
|
TITL => 1, # title - returned in NAM field no NAM found |
273
|
|
|
|
|
|
|
DSCP => 1, # description - returned in CMT field no CMT found |
274
|
|
|
|
|
|
|
#CPRT=> 1, |
275
|
|
|
|
|
|
|
PERF => 1, # performer - returned in ART field no ART found |
276
|
|
|
|
|
|
|
AUTH => 1, # author - returned in WRT field no WRT found |
277
|
|
|
|
|
|
|
#GNRE=> 1, |
278
|
|
|
|
|
|
|
MEAN => 1, |
279
|
|
|
|
|
|
|
NAME => 1, |
280
|
|
|
|
|
|
|
DATA => 1, |
281
|
|
|
|
|
|
|
); |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
# More interesting atoms, but with non-standard data layouts |
284
|
|
|
|
|
|
|
my %other_atoms = |
285
|
|
|
|
|
|
|
( |
286
|
|
|
|
|
|
|
MOOV => \&parse_moov, |
287
|
|
|
|
|
|
|
MDAT => \&parse_mdat, |
288
|
|
|
|
|
|
|
META => \&parse_meta, |
289
|
|
|
|
|
|
|
MVHD => \&parse_mvhd, |
290
|
|
|
|
|
|
|
STSD => \&parse_stsd, |
291
|
|
|
|
|
|
|
UUID => \&parse_uuid, |
292
|
|
|
|
|
|
|
); |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
# Standard container atoms that contain either kind of above atoms |
295
|
|
|
|
|
|
|
my %container_atoms = |
296
|
|
|
|
|
|
|
( |
297
|
|
|
|
|
|
|
ILST => 1, |
298
|
|
|
|
|
|
|
MDIA => 1, |
299
|
|
|
|
|
|
|
MINF => 1, |
300
|
|
|
|
|
|
|
STBL => 1, |
301
|
|
|
|
|
|
|
TRAK => 1, |
302
|
|
|
|
|
|
|
UDTA => 1, |
303
|
|
|
|
|
|
|
'----' => 1, # iTunes and aacgain info |
304
|
|
|
|
|
|
|
); |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
# Standard ID3 plus non-standard WinAmp genres |
308
|
|
|
|
|
|
|
my @mp4_genres = |
309
|
|
|
|
|
|
|
( |
310
|
|
|
|
|
|
|
'N/A', 'Blues', 'Classic Rock', 'Country', 'Dance', 'Disco', |
311
|
|
|
|
|
|
|
'Funk', 'Grunge', 'Hip-Hop', 'Jazz', 'Metal', 'New Age', 'Oldies', |
312
|
|
|
|
|
|
|
'Other', 'Pop', 'R&B', 'Rap', 'Reggae', 'Rock', 'Techno', |
313
|
|
|
|
|
|
|
'Industrial', 'Alternative', 'Ska', 'Death Metal', 'Pranks', |
314
|
|
|
|
|
|
|
'Soundtrack', 'Euro-Techno', 'Ambient', 'Trip-Hop', 'Vocal', |
315
|
|
|
|
|
|
|
'Jazz+Funk', 'Fusion', 'Trance', 'Classical', 'Instrumental', |
316
|
|
|
|
|
|
|
'Acid', 'House', 'Game', 'Sound Clip', 'Gospel', 'Noise', |
317
|
|
|
|
|
|
|
'AlternRock', 'Bass', 'Soul', 'Punk', 'Space', 'Meditative', |
318
|
|
|
|
|
|
|
'Instrumental Pop', 'Instrumental Rock', 'Ethnic', 'Gothic', |
319
|
|
|
|
|
|
|
'Darkwave', 'Techno-Industrial', 'Electronic', 'Pop-Folk', |
320
|
|
|
|
|
|
|
'Eurodance', 'Dream', 'Southern Rock', 'Comedy', 'Cult', 'Gangsta', |
321
|
|
|
|
|
|
|
'Top 40', 'Christian Rap', 'Pop/Funk', 'Jungle', 'Native American', |
322
|
|
|
|
|
|
|
'Cabaret', 'New Wave', 'Psychadelic', 'Rave', 'Showtunes', |
323
|
|
|
|
|
|
|
'Trailer', 'Lo-Fi', 'Tribal', 'Acid Punk', 'Acid Jazz', 'Polka', |
324
|
|
|
|
|
|
|
'Retro', 'Musical', 'Rock & Roll', 'Hard Rock', 'Folk', |
325
|
|
|
|
|
|
|
'Folk/Rock', 'National Folk', 'Swing', 'Fast-Fusion', 'Bebob', |
326
|
|
|
|
|
|
|
'Latin', 'Revival', 'Celtic', 'Bluegrass', 'Avantgarde', |
327
|
|
|
|
|
|
|
'Gothic Rock', 'Progressive Rock', 'Psychedelic Rock', |
328
|
|
|
|
|
|
|
'Symphonic Rock', 'Slow Rock', 'Big Band', 'Chorus', |
329
|
|
|
|
|
|
|
'Easy Listening', 'Acoustic', 'Humour', 'Speech', 'Chanson', |
330
|
|
|
|
|
|
|
'Opera', 'Chamber Music', 'Sonata', 'Symphony', 'Booty Bass', |
331
|
|
|
|
|
|
|
'Primus', 'Porn Groove', 'Satire', 'Slow Jam', 'Club', 'Tango', |
332
|
|
|
|
|
|
|
'Samba', 'Folklore', 'Ballad', 'Power Ballad', 'Rhythmic Soul', |
333
|
|
|
|
|
|
|
'Freestyle', 'Duet', 'Punk Rock', 'Drum Solo', 'A capella', |
334
|
|
|
|
|
|
|
'Euro-House', 'Dance Hall', 'Goa', 'Drum & Bass', 'Club House', |
335
|
|
|
|
|
|
|
'Hardcore', 'Terror', 'Indie', 'BritPop', 'NegerPunk', |
336
|
|
|
|
|
|
|
'Polsk Punk', 'Beat', 'Christian Gangsta', 'Heavy Metal', |
337
|
|
|
|
|
|
|
'Black Metal', 'Crossover', 'Contemporary C', 'Christian Rock', |
338
|
|
|
|
|
|
|
'Merengue', 'Salsa', 'Thrash Metal', 'Anime', 'JPop', 'SynthPop' |
339
|
|
|
|
|
|
|
); |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
sub parse_file |
343
|
|
|
|
|
|
|
{ |
344
|
17
|
|
|
17
|
0
|
41
|
my ($file, $tags) = @_; |
345
|
17
|
|
|
|
|
28
|
my ($fh, $err, $header, $size); |
346
|
|
|
|
|
|
|
|
347
|
17
|
50
|
33
|
|
|
140
|
if (not (defined $file && $file ne '')) |
348
|
|
|
|
|
|
|
{ |
349
|
0
|
|
|
|
|
0
|
$@ = 'No file specified'; |
350
|
0
|
|
|
|
|
0
|
return -1; |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
|
353
|
17
|
50
|
|
|
|
60
|
if (ref $file) # filehandle passed |
354
|
|
|
|
|
|
|
{ |
355
|
0
|
|
|
|
|
0
|
$fh = $file; |
356
|
|
|
|
|
|
|
} |
357
|
|
|
|
|
|
|
else |
358
|
|
|
|
|
|
|
{ |
359
|
17
|
|
|
|
|
86
|
$fh = gensym; |
360
|
17
|
50
|
|
|
|
2008
|
if (not open $fh, "< $file\0") |
361
|
|
|
|
|
|
|
{ |
362
|
0
|
|
|
|
|
0
|
$@ = "Can't open $file: $!"; |
363
|
0
|
|
|
|
|
0
|
return -1; |
364
|
|
|
|
|
|
|
} |
365
|
|
|
|
|
|
|
} |
366
|
|
|
|
|
|
|
|
367
|
17
|
|
|
|
|
65
|
binmode $fh; |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
# Sanity check that this looks vaguely like an MP4 file |
370
|
17
|
50
|
33
|
|
|
16074
|
if ((read ($fh, $header, 8) != 8) || (lc substr ($header, 4) ne 'ftyp')) |
371
|
|
|
|
|
|
|
{ |
372
|
0
|
|
|
|
|
0
|
close ($fh); |
373
|
0
|
|
|
|
|
0
|
$@ = 'Not an MPEG-4 file'; |
374
|
0
|
|
|
|
|
0
|
return -1; |
375
|
|
|
|
|
|
|
} |
376
|
17
|
|
|
|
|
17459
|
seek $fh, 0, 2; |
377
|
17
|
|
|
|
|
46
|
$size = tell $fh; |
378
|
17
|
|
|
|
|
96
|
seek $fh, 0, 0; |
379
|
|
|
|
|
|
|
|
380
|
17
|
|
|
|
|
80
|
$err = parse_container($fh, 0, $size, $tags); |
381
|
17
|
|
|
|
|
414
|
close ($fh); |
382
|
17
|
50
|
|
|
|
59
|
return $err if $err; |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
# remaining get_mp4tag() stuff |
385
|
17
|
100
|
|
|
|
69
|
$tags->{CPIL} = 0 unless defined ($tags->{CPIL}); |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
# MP3::Info compatibility |
388
|
17
|
50
|
|
|
|
99
|
$tags->{TITLE} = $tags->{NAM} if defined ($tags->{NAM}); |
389
|
17
|
50
|
|
|
|
78
|
$tags->{ARTIST} = $tags->{ART} if defined ($tags->{ART}); |
390
|
17
|
100
|
|
|
|
87
|
$tags->{ALBUM} = $tags->{ALB} if defined ($tags->{ALB}); |
391
|
17
|
100
|
|
|
|
78
|
$tags->{YEAR} = $tags->{DAY} if defined ($tags->{DAY}); |
392
|
17
|
100
|
|
|
|
63
|
$tags->{COMMENT} = $tags->{CMT} if defined ($tags->{CMT}); |
393
|
17
|
100
|
|
|
|
62
|
$tags->{GENRE} = $tags->{GNRE} if defined ($tags->{GNRE}); |
394
|
17
|
100
|
|
|
|
77
|
$tags->{TRACKNUM} = $tags->{TRKN}[0] if defined ($tags->{TRKN}); |
395
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
# remaining get_mp4info() stuff |
397
|
17
|
|
|
|
|
88
|
$tags->{VERSION} = 4; |
398
|
17
|
50
|
|
|
|
67
|
$tags->{LAYER} = 1 if defined ($tags->{FREQUENCY}); |
399
|
17
|
50
|
|
|
|
62
|
$tags->{COPYRIGHT}= (defined ($tags->{CPRT}) ? 1 : 0); |
400
|
17
|
50
|
|
|
|
59
|
$tags->{ENCRYPTED}= 0 unless defined ($tags->{ENCRYPTED}); |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
# Returns actual (not requested) bitrate |
403
|
17
|
50
|
33
|
|
|
357
|
if (defined($tags->{SIZE}) && $tags->{SIZE} && defined($tags->{SECS}) && ($tags->{MM}+$tags->{SS}+$tags->{MS})) |
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
404
|
|
|
|
|
|
|
{ |
405
|
17
|
|
|
|
|
137
|
$tags->{BITRATE} = int (0.5 + $tags->{SIZE} * 0.008 / ($tags->{MM}*60+$tags->{SS}+$tags->{MS}/1000)) |
406
|
|
|
|
|
|
|
} |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
# Post process '---' container |
409
|
17
|
100
|
66
|
|
|
2351
|
if ($tags->{MEAN} && ref($tags->{MEAN}) eq 'ARRAY') |
410
|
|
|
|
|
|
|
{ |
411
|
8
|
|
|
|
|
21
|
for (my $i = 0; $i < scalar @{$tags->{MEAN}}; $i++) |
|
16
|
|
|
|
|
53
|
|
412
|
|
|
|
|
|
|
{ |
413
|
8
|
|
|
|
|
14
|
push @{$tags->{META}}, { |
|
8
|
|
|
|
|
91
|
|
414
|
|
|
|
|
|
|
MEAN => $tags->{MEAN}->[$i], |
415
|
|
|
|
|
|
|
NAME => $tags->{NAME}->[$i], |
416
|
|
|
|
|
|
|
DATA => $tags->{DATA}->[$i], |
417
|
|
|
|
|
|
|
}; |
418
|
|
|
|
|
|
|
} |
419
|
|
|
|
|
|
|
|
420
|
8
|
|
|
|
|
32
|
delete $tags->{MEAN}; |
421
|
8
|
|
|
|
|
19
|
delete $tags->{NAME}; |
422
|
8
|
|
|
|
|
27
|
delete $tags->{DATA}; |
423
|
|
|
|
|
|
|
} |
424
|
|
|
|
|
|
|
|
425
|
17
|
|
|
|
|
529
|
return 0; |
426
|
|
|
|
|
|
|
} |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
# Pre: $size=size of container contents |
430
|
|
|
|
|
|
|
# $fh points to start of container contents |
431
|
|
|
|
|
|
|
# Post: $fh points past end of container contents |
432
|
|
|
|
|
|
|
sub parse_container |
433
|
|
|
|
|
|
|
{ |
434
|
185
|
|
|
185
|
0
|
311
|
my ($fh, $level, $size, $tags) = @_; |
435
|
185
|
|
|
|
|
235
|
my ($end, $err); |
436
|
|
|
|
|
|
|
|
437
|
185
|
|
|
|
|
228
|
$level++; |
438
|
185
|
|
|
|
|
537
|
$end = (tell $fh) + $size; |
439
|
185
|
|
|
|
|
2163
|
while (tell $fh < $end) |
440
|
|
|
|
|
|
|
{ |
441
|
703
|
|
|
|
|
12433
|
$err = parse_atom($fh, $level, $end-(tell $fh), $tags); |
442
|
703
|
50
|
|
|
|
4568
|
return $err if $err; |
443
|
|
|
|
|
|
|
} |
444
|
185
|
50
|
|
|
|
1513
|
if (tell $fh != $end) |
445
|
|
|
|
|
|
|
{ |
446
|
0
|
|
|
|
|
0
|
$@ = 'Parse error'; |
447
|
0
|
|
|
|
|
0
|
return -1; |
448
|
|
|
|
|
|
|
} |
449
|
185
|
|
|
|
|
1633
|
return 0; |
450
|
|
|
|
|
|
|
} |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
# Pre: $fh points to start of atom |
454
|
|
|
|
|
|
|
# $parentsize is remaining size of parent container |
455
|
|
|
|
|
|
|
# Post: $fh points past end of atom |
456
|
|
|
|
|
|
|
sub parse_atom |
457
|
|
|
|
|
|
|
{ |
458
|
703
|
|
|
703
|
0
|
6329
|
my ($fh, $level, $parentsize, $tags) = @_; |
459
|
703
|
|
|
|
|
862
|
my ($header, $size, $id, $err, $pos); |
460
|
703
|
50
|
|
|
|
22219
|
if (read ($fh, $header, 8) != 8) |
461
|
|
|
|
|
|
|
{ |
462
|
0
|
|
|
|
|
0
|
$@ = 'Premature eof'; |
463
|
0
|
|
|
|
|
0
|
return -1; |
464
|
|
|
|
|
|
|
} |
465
|
|
|
|
|
|
|
|
466
|
703
|
|
|
|
|
15762
|
($size,$id) = unpack 'Na4', $header; |
467
|
703
|
50
|
|
|
|
2782
|
if ($size==0) |
|
|
50
|
|
|
|
|
|
468
|
|
|
|
|
|
|
{ |
469
|
|
|
|
|
|
|
# Zero-sized atom extends to eof (14496-12:2004 S4.2) |
470
|
0
|
|
|
|
|
0
|
$pos=tell($fh); |
471
|
0
|
|
|
|
|
0
|
seek $fh, 0, 2; |
472
|
0
|
|
|
|
|
0
|
$size = tell($fh) - $pos; # Error if parent size doesn't match |
473
|
0
|
|
|
|
|
0
|
seek $fh, $pos, 0; |
474
|
|
|
|
|
|
|
} |
475
|
|
|
|
|
|
|
elsif ($size == 1) |
476
|
|
|
|
|
|
|
{ |
477
|
|
|
|
|
|
|
# extended size |
478
|
0
|
|
|
|
|
0
|
my ($hi, $lo); |
479
|
0
|
0
|
|
|
|
0
|
if (read ($fh, $header, 8) != 8) |
480
|
|
|
|
|
|
|
{ |
481
|
0
|
|
|
|
|
0
|
$@ = 'Premature eof'; |
482
|
0
|
|
|
|
|
0
|
return -1; |
483
|
|
|
|
|
|
|
} |
484
|
0
|
|
|
|
|
0
|
($hi,$lo) = unpack 'NN', $header; |
485
|
0
|
|
|
|
|
0
|
$size=$hi*(2**32) + $lo; |
486
|
0
|
0
|
|
|
|
0
|
if ($size>$parentsize) |
487
|
|
|
|
|
|
|
{ |
488
|
|
|
|
|
|
|
# atom extends outside of parent container - skip to end of parent |
489
|
0
|
|
|
|
|
0
|
seek $fh, $parentsize-16, 1; |
490
|
0
|
|
|
|
|
0
|
return 0; |
491
|
|
|
|
|
|
|
} |
492
|
0
|
|
|
|
|
0
|
$size -= 16; |
493
|
|
|
|
|
|
|
} |
494
|
|
|
|
|
|
|
else |
495
|
|
|
|
|
|
|
{ |
496
|
703
|
50
|
|
|
|
1347
|
if ($size>$parentsize) |
497
|
|
|
|
|
|
|
{ |
498
|
|
|
|
|
|
|
# atom extends outside of parent container - skip to end of parent |
499
|
0
|
|
|
|
|
0
|
seek $fh, $parentsize-8, 1; |
500
|
0
|
|
|
|
|
0
|
return 0; |
501
|
|
|
|
|
|
|
} |
502
|
703
|
|
|
|
|
1050
|
$size -= 8; |
503
|
|
|
|
|
|
|
} |
504
|
703
|
50
|
|
|
|
1331
|
if ($size<0) |
505
|
|
|
|
|
|
|
{ |
506
|
0
|
|
|
|
|
0
|
$@ = 'Parse error'; |
507
|
0
|
|
|
|
|
0
|
return -1; |
508
|
|
|
|
|
|
|
} |
509
|
703
|
|
|
|
|
1817
|
$id =~ s/[^\w\-]//; |
510
|
703
|
|
|
|
|
10438
|
$id = uc $id; |
511
|
|
|
|
|
|
|
|
512
|
703
|
50
|
|
|
|
6879
|
printf "%s%s: %d bytes\n", ' 'x(2*$level), $id, $size if $debug; |
513
|
|
|
|
|
|
|
|
514
|
703
|
100
|
|
|
|
4352
|
if (defined($data_atoms{$id})) |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
515
|
|
|
|
|
|
|
{ |
516
|
194
|
|
|
|
|
425
|
return parse_data ($fh, $level, $size, $id, $tags); |
517
|
|
|
|
|
|
|
} |
518
|
|
|
|
|
|
|
elsif (defined($other_atoms{$id})) |
519
|
|
|
|
|
|
|
{ |
520
|
94
|
|
|
|
|
224
|
return &{$other_atoms{$id}}($fh, $level, $size, $tags); |
|
94
|
|
|
|
|
29536
|
|
521
|
|
|
|
|
|
|
} |
522
|
|
|
|
|
|
|
elsif ($container_atoms{$id}) |
523
|
|
|
|
|
|
|
{ |
524
|
134
|
|
|
|
|
322
|
return parse_container ($fh, $level, $size, $tags); |
525
|
|
|
|
|
|
|
} |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
# Unkown atom - skip past it |
528
|
281
|
|
|
|
|
1108
|
seek $fh, $size, 1; |
529
|
281
|
|
|
|
|
11999
|
return 0; |
530
|
|
|
|
|
|
|
} |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
# Pre: $size=size of atom contents |
534
|
|
|
|
|
|
|
# $fh points to start of atom contents |
535
|
|
|
|
|
|
|
# Post: $fh points past end of atom contents |
536
|
|
|
|
|
|
|
sub parse_moov |
537
|
|
|
|
|
|
|
{ |
538
|
17
|
|
|
17
|
0
|
54
|
my ($fh, $level, $size, $tags) = @_; |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
# MOOV is a normal container. |
541
|
|
|
|
|
|
|
# Read ahead to improve performance on high-latency filesystems. |
542
|
17
|
|
|
|
|
25
|
my $data; |
543
|
17
|
50
|
|
|
|
119
|
if (read ($fh, $data, $size) != $size) |
544
|
|
|
|
|
|
|
{ |
545
|
0
|
|
|
|
|
0
|
$@ = 'Premature eof'; |
546
|
0
|
|
|
|
|
0
|
return -1; |
547
|
|
|
|
|
|
|
} |
548
|
17
|
|
|
|
|
176
|
my $cache=IO::String->new($data); |
549
|
17
|
|
|
|
|
2235
|
return parse_container ($cache, $level, $size, $tags); |
550
|
|
|
|
|
|
|
} |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
# Pre: $size=size of atom contents |
554
|
|
|
|
|
|
|
# $fh points to start of atom contents |
555
|
|
|
|
|
|
|
# Post: $fh points past end of atom contents |
556
|
|
|
|
|
|
|
sub parse_mdat |
557
|
|
|
|
|
|
|
{ |
558
|
20
|
|
|
20
|
0
|
36
|
my ($fh, $level, $size, $tags) = @_; |
559
|
|
|
|
|
|
|
|
560
|
20
|
100
|
|
|
|
106
|
$tags->{SIZE} = 0 unless defined($tags->{SIZE}); |
561
|
20
|
|
|
|
|
43
|
$tags->{SIZE} += $size; |
562
|
20
|
|
|
|
|
190
|
seek $fh, $size, 1; |
563
|
|
|
|
|
|
|
|
564
|
20
|
|
|
|
|
72
|
return 0; |
565
|
|
|
|
|
|
|
} |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
# Pre: $size=size of atom contents |
569
|
|
|
|
|
|
|
# $fh points to start of atom contents |
570
|
|
|
|
|
|
|
# Post: $fh points past end of atom contents |
571
|
|
|
|
|
|
|
sub parse_meta |
572
|
|
|
|
|
|
|
{ |
573
|
17
|
|
|
17
|
0
|
38
|
my ($fh, $level, $size, $tags) = @_; |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
# META is just a container preceded by a version field |
576
|
17
|
|
|
|
|
61
|
seek $fh, 4, 1; |
577
|
17
|
|
|
|
|
240
|
return parse_container ($fh, $level, $size-4, $tags); |
578
|
|
|
|
|
|
|
} |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
# Pre: $size=size of atom contents |
582
|
|
|
|
|
|
|
# $fh points to start of atom contents |
583
|
|
|
|
|
|
|
# Post: $fh points past end of atom contents |
584
|
|
|
|
|
|
|
sub parse_mvhd |
585
|
|
|
|
|
|
|
{ |
586
|
17
|
|
|
17
|
0
|
44
|
my ($fh, $level, $size, $tags) = @_; |
587
|
17
|
|
|
|
|
53
|
my ($data, $version, $scale, $duration, $secs); |
588
|
|
|
|
|
|
|
|
589
|
17
|
50
|
|
|
|
55
|
if ($size < 32) |
590
|
|
|
|
|
|
|
{ |
591
|
0
|
|
|
|
|
0
|
$@ = 'Parse error'; |
592
|
0
|
|
|
|
|
0
|
return -1; |
593
|
|
|
|
|
|
|
} |
594
|
17
|
50
|
|
|
|
67
|
if (read ($fh, $data, $size) != $size) |
595
|
|
|
|
|
|
|
{ |
596
|
0
|
|
|
|
|
0
|
$@ = 'Premature eof'; |
597
|
0
|
|
|
|
|
0
|
return -1; |
598
|
|
|
|
|
|
|
} |
599
|
|
|
|
|
|
|
|
600
|
17
|
|
|
|
|
303
|
$version = unpack('C', $data) & 255; |
601
|
17
|
50
|
|
|
|
54
|
if ($version==0) |
|
|
0
|
|
|
|
|
|
602
|
|
|
|
|
|
|
{ |
603
|
17
|
|
|
|
|
61
|
($scale,$duration) = unpack 'NN', substr ($data, 12, 8); |
604
|
|
|
|
|
|
|
} |
605
|
|
|
|
|
|
|
elsif ($version==1) |
606
|
|
|
|
|
|
|
{ |
607
|
0
|
|
|
|
|
0
|
my ($hi,$lo); |
608
|
0
|
0
|
|
|
|
0
|
print "Long version\n" if $debug; |
609
|
0
|
|
|
|
|
0
|
($scale,$hi,$lo) = unpack 'NNN', substr ($data, 20, 12); |
610
|
0
|
|
|
|
|
0
|
$duration=$hi*(2**32) + $lo; |
611
|
|
|
|
|
|
|
} |
612
|
|
|
|
|
|
|
else |
613
|
|
|
|
|
|
|
{ |
614
|
0
|
|
|
|
|
0
|
return 0; |
615
|
|
|
|
|
|
|
} |
616
|
|
|
|
|
|
|
|
617
|
17
|
50
|
|
|
|
47
|
printf " %sDur/Scl=$duration/$scale\n", ' 'x(2*$level) if $debug; |
618
|
17
|
|
|
|
|
44
|
$secs=$duration/$scale; |
619
|
17
|
|
|
|
|
74
|
$tags->{SECS} = int (0.5+$secs); |
620
|
17
|
|
|
|
|
39
|
$tags->{MM} = int ($secs/60); |
621
|
17
|
|
|
|
|
64
|
$tags->{SS} = int ($secs - $tags->{MM}*60); |
622
|
17
|
|
|
|
|
51
|
$tags->{MS} = int (0.5 + 1000*($secs - int ($secs))); |
623
|
17
|
|
|
|
|
167
|
$tags->{TIME} = sprintf "%02d:%02d", |
624
|
|
|
|
|
|
|
$tags->{MM}, $tags->{SECS} - $tags->{MM}*60; |
625
|
|
|
|
|
|
|
|
626
|
17
|
|
|
|
|
61
|
return 0; |
627
|
|
|
|
|
|
|
} |
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
# Pre: $size=size of atom contents |
631
|
|
|
|
|
|
|
# $fh points to start of atom contents |
632
|
|
|
|
|
|
|
# Post: $fh points past end of atom contents |
633
|
|
|
|
|
|
|
sub parse_stsd |
634
|
|
|
|
|
|
|
{ |
635
|
23
|
|
|
23
|
0
|
59
|
my ($fh, $level, $size, $tags) = @_; |
636
|
23
|
|
|
|
|
32
|
my ($data, $data_format); |
637
|
|
|
|
|
|
|
|
638
|
23
|
50
|
|
|
|
67
|
if ($size < 44) |
639
|
|
|
|
|
|
|
{ |
640
|
0
|
|
|
|
|
0
|
$@ = 'Parse error'; |
641
|
0
|
|
|
|
|
0
|
return -1; |
642
|
|
|
|
|
|
|
} |
643
|
23
|
50
|
|
|
|
256
|
if (read ($fh, $data, $size) != $size) |
644
|
|
|
|
|
|
|
{ |
645
|
0
|
|
|
|
|
0
|
$@ = 'Premature eof'; |
646
|
0
|
|
|
|
|
0
|
return -1; |
647
|
|
|
|
|
|
|
} |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
# Assumes first entry in table contains the data |
650
|
23
|
50
|
|
|
|
343
|
printf " %sSample=%s\n", ' 'x(2*$level), substr ($data, 12, 4) if $debug; |
651
|
23
|
|
|
|
|
58
|
$data_format = lc substr ($data, 12, 4); |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
# Is this an audio track? (Ought to look for presence of an SMHD uncle |
654
|
|
|
|
|
|
|
# atom instead to allow for other audio data formats). |
655
|
23
|
50
|
100
|
|
|
220
|
if (($data_format eq 'mp4a') || # AAC, aacPlus |
|
|
|
66
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
656
|
|
|
|
|
|
|
($data_format eq 'alac') || # Apple lossless |
657
|
|
|
|
|
|
|
($data_format eq 'drms') || # Apple encrypted AAC |
658
|
|
|
|
|
|
|
($data_format eq 'samr') || # Narrow-band AMR |
659
|
|
|
|
|
|
|
($data_format eq 'sawb') || # AMR wide-band |
660
|
|
|
|
|
|
|
($data_format eq 'sawp') || # AMR wide-band + |
661
|
|
|
|
|
|
|
($data_format eq 'enca')) # Generic encrypted audio |
662
|
|
|
|
|
|
|
{ |
663
|
17
|
|
|
|
|
51
|
$tags->{ENCODING} = $data_format; |
664
|
|
|
|
|
|
|
# $version = unpack "n", substr ($data, 24, 2); |
665
|
|
|
|
|
|
|
# s8.16 is inconsistent. In practice, channels always appears == 2. |
666
|
|
|
|
|
|
|
# $tags->{STEREO} = (unpack ("n", substr ($data, 32, 2)) > 1) ? 1 : 0; |
667
|
|
|
|
|
|
|
# Old Quicktime field. No longer used. |
668
|
|
|
|
|
|
|
# $tags->{VBR} = (unpack ("n", substr ($data, 36, 2)) == -2) ? 1 : 0; |
669
|
17
|
|
|
|
|
77
|
$tags->{FREQUENCY} = unpack ('N', substr ($data, 40, 4)) / 65536000; |
670
|
17
|
50
|
|
|
|
54
|
printf " %sFreq=%s\n", ' 'x(2*$level), $tags->{FREQUENCY} if $debug; |
671
|
|
|
|
|
|
|
} |
672
|
|
|
|
|
|
|
|
673
|
23
|
50
|
33
|
|
|
165
|
$tags->{ENCRYPTED}=1 if (($data_format eq 'drms') || |
674
|
|
|
|
|
|
|
(substr($data_format, 0, 3) eq 'enc')); |
675
|
|
|
|
|
|
|
|
676
|
23
|
|
|
|
|
78
|
return 0; |
677
|
|
|
|
|
|
|
} |
678
|
|
|
|
|
|
|
|
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
# User-defined box. Used by PSP - See ffmpeg libavformat/movenc.c |
681
|
|
|
|
|
|
|
# |
682
|
|
|
|
|
|
|
# Pre: $size=size of atom contents |
683
|
|
|
|
|
|
|
# $fh points to start of atom contents |
684
|
|
|
|
|
|
|
# Post: $fh points past end of atom contents |
685
|
|
|
|
|
|
|
sub parse_uuid |
686
|
|
|
|
|
|
|
{ |
687
|
0
|
|
|
0
|
0
|
0
|
my ($fh, $level, $size, $tags) = @_; |
688
|
0
|
|
|
|
|
0
|
my $data; |
689
|
|
|
|
|
|
|
|
690
|
0
|
0
|
|
|
|
0
|
if (read ($fh, $data, $size) != $size) |
691
|
|
|
|
|
|
|
{ |
692
|
0
|
|
|
|
|
0
|
$@ = 'Premature eof'; |
693
|
0
|
|
|
|
|
0
|
return -1; |
694
|
|
|
|
|
|
|
} |
695
|
0
|
0
|
|
|
|
0
|
($size > 26) || return 0; # 16byte uuid, 10byte psp-specific |
696
|
|
|
|
|
|
|
|
697
|
0
|
|
|
|
|
0
|
my ($u1,$u2,$u3,$u4)=unpack 'a4NNN', $data; |
698
|
0
|
0
|
|
|
|
0
|
if ($u1 eq 'USMT') # PSP also uses a uuid starting with 'PROF' |
699
|
|
|
|
|
|
|
{ |
700
|
0
|
|
|
|
|
0
|
my ($pspsize,$pspid) = unpack 'Na4', substr ($data, 16, 8); |
701
|
0
|
0
|
|
|
|
0
|
printf " %s$pspid: $pspsize bytes\n", ' 'x(2*$level) if $debug; |
702
|
0
|
0
|
|
|
|
0
|
($pspsize==$size-16) || return 0; # sanity check |
703
|
0
|
0
|
|
|
|
0
|
if ($pspid eq 'MTDT') |
704
|
|
|
|
|
|
|
{ |
705
|
0
|
|
|
|
|
0
|
my $nblocks = unpack 'n', substr ($data, 24, 2); |
706
|
0
|
|
|
|
|
0
|
$data = substr($data, 26); |
707
|
0
|
|
|
|
|
0
|
while ($nblocks) |
708
|
|
|
|
|
|
|
{ |
709
|
0
|
|
|
|
|
0
|
my ($bsize, $btype, $flags, $ptype) = unpack 'nNnn', $data; |
710
|
0
|
0
|
|
|
|
0
|
printf " %s0x%x: $bsize bytes, Type=$ptype\n", ' 'x(2*$level), $btype if $debug; |
711
|
0
|
0
|
0
|
|
|
0
|
if ($btype==1 && $bsize>12 && $ptype==1 && !defined($tags->{NAM})) |
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
712
|
|
|
|
|
|
|
{ |
713
|
|
|
|
|
|
|
# Could have titles in different langauges - use first |
714
|
0
|
|
|
|
|
0
|
$tags->{NAM} = decode("UTF-16BE", substr($data, 10, $bsize-12)); |
715
|
|
|
|
|
|
|
} |
716
|
|
|
|
|
|
|
elsif ($btype==4 && $bsize>12 && $ptype==1) |
717
|
|
|
|
|
|
|
{ |
718
|
0
|
|
|
|
|
0
|
$tags->{TOO} = decode("UTF-16BE", substr($data, 10, $bsize-12)); |
719
|
|
|
|
|
|
|
} |
720
|
0
|
|
|
|
|
0
|
$data = substr($data, $bsize); |
721
|
0
|
|
|
|
|
0
|
$nblocks-=1; |
722
|
|
|
|
|
|
|
} |
723
|
|
|
|
|
|
|
} |
724
|
|
|
|
|
|
|
} |
725
|
0
|
|
|
|
|
0
|
return 0; |
726
|
|
|
|
|
|
|
} |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
# Pre: $size=size of atom contents |
730
|
|
|
|
|
|
|
# $fh points to start of atom contents |
731
|
|
|
|
|
|
|
# Post: $fh points past end of atom contents |
732
|
|
|
|
|
|
|
sub parse_data |
733
|
|
|
|
|
|
|
{ |
734
|
194
|
|
|
194
|
0
|
1153
|
my ($fh, $level, $size, $id, $tags) = @_; |
735
|
194
|
|
|
|
|
1943
|
my ($data, $atom, $type); |
736
|
|
|
|
|
|
|
|
737
|
194
|
50
|
|
|
|
532
|
if (read ($fh, $data, $size) != $size) |
738
|
|
|
|
|
|
|
{ |
739
|
0
|
|
|
|
|
0
|
$@ = 'Premature eof'; |
740
|
0
|
|
|
|
|
0
|
return -1; |
741
|
|
|
|
|
|
|
} |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
# 3GPP - different format when child of 'udta'. |
744
|
|
|
|
|
|
|
# Let existing tags (if any) override these. |
745
|
194
|
100
|
33
|
|
|
4794
|
if (($id eq 'TITL') || |
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
66
|
|
|
|
|
746
|
|
|
|
|
|
|
($id eq 'DSCP') || |
747
|
|
|
|
|
|
|
($id eq 'CPRT') || |
748
|
|
|
|
|
|
|
($id eq 'PERF') || |
749
|
|
|
|
|
|
|
($id eq 'AUTH') || |
750
|
|
|
|
|
|
|
($id eq 'GNRE')) |
751
|
|
|
|
|
|
|
{ |
752
|
9
|
|
|
|
|
23
|
my ($ver) = unpack 'N', $data; |
753
|
9
|
50
|
|
|
|
27
|
if ($ver == 0) |
754
|
|
|
|
|
|
|
{ |
755
|
0
|
0
|
|
|
|
0
|
($size > 7) || return 0; |
756
|
0
|
|
|
|
|
0
|
$size -= 7; |
757
|
0
|
|
|
|
|
0
|
$type = 1; |
758
|
0
|
|
|
|
|
0
|
$data = substr ($data, 6, $size); |
759
|
|
|
|
|
|
|
|
760
|
0
|
0
|
|
|
|
0
|
if ($id eq 'TITL') |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
761
|
|
|
|
|
|
|
{ |
762
|
0
|
0
|
|
|
|
0
|
return 0 if defined ($tags->{NAM}); |
763
|
0
|
|
|
|
|
0
|
$id = 'NAM'; |
764
|
|
|
|
|
|
|
} |
765
|
|
|
|
|
|
|
elsif ($id eq 'DSCP') |
766
|
|
|
|
|
|
|
{ |
767
|
0
|
0
|
|
|
|
0
|
return 0 if defined ($tags->{CMT}); |
768
|
0
|
|
|
|
|
0
|
$id = 'CMT'; |
769
|
|
|
|
|
|
|
} |
770
|
|
|
|
|
|
|
elsif ($id eq 'PERF') |
771
|
|
|
|
|
|
|
{ |
772
|
0
|
0
|
|
|
|
0
|
return 0 if defined ($tags->{ART}); |
773
|
0
|
|
|
|
|
0
|
$id = 'ART'; |
774
|
|
|
|
|
|
|
} |
775
|
|
|
|
|
|
|
elsif ($id eq 'AUTH') |
776
|
|
|
|
|
|
|
{ |
777
|
0
|
0
|
|
|
|
0
|
return 0 if defined ($tags->{WRT}); |
778
|
0
|
|
|
|
|
0
|
$id = 'WRT'; |
779
|
|
|
|
|
|
|
} |
780
|
|
|
|
|
|
|
} |
781
|
|
|
|
|
|
|
} |
782
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
# Parse out the tuple that contains aacgain data, etc. |
784
|
194
|
100
|
100
|
|
|
1232
|
if (($id eq 'MEAN') || |
|
|
|
100
|
|
|
|
|
785
|
|
|
|
|
|
|
($id eq 'NAME') || |
786
|
|
|
|
|
|
|
($id eq 'DATA')) |
787
|
|
|
|
|
|
|
{ |
788
|
|
|
|
|
|
|
# The first 4 or 8 bytes are nulls. |
789
|
24
|
100
|
|
|
|
66
|
if ($id eq 'DATA') |
790
|
|
|
|
|
|
|
{ |
791
|
8
|
|
|
|
|
22
|
$data = substr ($data, 8); |
792
|
|
|
|
|
|
|
} |
793
|
|
|
|
|
|
|
else |
794
|
|
|
|
|
|
|
{ |
795
|
16
|
|
|
|
|
35
|
$data = substr ($data, 4); |
796
|
|
|
|
|
|
|
} |
797
|
|
|
|
|
|
|
|
798
|
24
|
|
|
|
|
30
|
push @{$tags->{$id}}, $data; |
|
24
|
|
|
|
|
92
|
|
799
|
24
|
|
|
|
|
83
|
return 0; |
800
|
|
|
|
|
|
|
} |
801
|
|
|
|
|
|
|
|
802
|
170
|
50
|
|
|
|
475
|
if (!defined($type)) |
803
|
|
|
|
|
|
|
{ |
804
|
170
|
50
|
|
|
|
344
|
($size > 16) || return 0; |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
# Assumes first atom is the data atom we're after |
807
|
170
|
|
|
|
|
2253
|
($size,$atom,$type) = unpack 'Na4N', $data; |
808
|
170
|
50
|
|
|
|
584
|
(lc $atom eq 'data') || return 0; |
809
|
170
|
50
|
|
|
|
316
|
($size > 16) || return 0; |
810
|
170
|
|
|
|
|
234
|
$size -= 16; |
811
|
170
|
|
|
|
|
203
|
$type &= 255; |
812
|
170
|
|
|
|
|
1030
|
$data = substr ($data, 16, $size); |
813
|
|
|
|
|
|
|
} |
814
|
170
|
50
|
|
|
|
331
|
printf " %sType=$type, Size=$size\n", ' 'x(2*$level) if $debug; |
815
|
|
|
|
|
|
|
|
816
|
170
|
50
|
|
|
|
525
|
if ($id eq 'COVR') |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
817
|
|
|
|
|
|
|
{ |
818
|
|
|
|
|
|
|
# iTunes appears to use random data types for cover art |
819
|
0
|
|
|
|
|
0
|
$tags->{$id} = $data; |
820
|
|
|
|
|
|
|
} |
821
|
|
|
|
|
|
|
elsif ($type==0) # 16bit int data array |
822
|
|
|
|
|
|
|
{ |
823
|
34
|
|
|
|
|
150
|
my @ints = unpack 'n' x ($size / 2), $data; |
824
|
34
|
100
|
66
|
|
|
158
|
if ($id eq 'GNRE') |
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
825
|
|
|
|
|
|
|
{ |
826
|
9
|
|
|
|
|
39
|
$tags->{$id} = $mp4_genres[$ints[0]]; |
827
|
|
|
|
|
|
|
} |
828
|
|
|
|
|
|
|
elsif ($id eq 'DISK' or $id eq 'TRKN') |
829
|
|
|
|
|
|
|
{ |
830
|
|
|
|
|
|
|
# Real 10.0 sometimes omits the second integer, but we require it |
831
|
25
|
50
|
|
|
|
155
|
$tags->{$id} = [$ints[1], ($size>=6 ? $ints[2] : 0)] if ($size>=4); |
|
|
50
|
|
|
|
|
|
832
|
|
|
|
|
|
|
} |
833
|
|
|
|
|
|
|
elsif ($size>=4) |
834
|
|
|
|
|
|
|
{ |
835
|
0
|
|
|
|
|
0
|
$tags->{$id} = $ints[1]; |
836
|
|
|
|
|
|
|
} |
837
|
|
|
|
|
|
|
} |
838
|
|
|
|
|
|
|
elsif ($type==1) # Char data |
839
|
|
|
|
|
|
|
{ |
840
|
|
|
|
|
|
|
# faac 1.24 and Real 10.0 encode data as unspecified 8 bit, which |
841
|
|
|
|
|
|
|
# goes against s8.28 of ISO/IEC 14496-12:2004. How tedious. |
842
|
|
|
|
|
|
|
# Assume data is utf8 if it could be utf8, otherwise assume latin1. |
843
|
117
|
|
|
|
|
536
|
my $decoder = Encode::Guess->guess ($data); |
844
|
117
|
100
|
|
|
|
37649
|
$data = (ref ($decoder)) ? |
|
|
100
|
|
|
|
|
|
845
|
|
|
|
|
|
|
$decoder->decode($data) : # found one of utf8, utf16, latin1 |
846
|
|
|
|
|
|
|
decode($utf8 ? 'utf8' : 'latin1', $data); # ambiguous so force |
847
|
|
|
|
|
|
|
|
848
|
117
|
100
|
|
|
|
1151
|
if ($id eq 'GEN') |
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
849
|
|
|
|
|
|
|
{ |
850
|
5
|
50
|
|
|
|
24
|
return 0 if defined ($tags->{GNRE}); |
851
|
5
|
|
|
|
|
74
|
$id='GNRE'; |
852
|
|
|
|
|
|
|
} |
853
|
|
|
|
|
|
|
elsif ($id eq 'AART') |
854
|
|
|
|
|
|
|
{ |
855
|
0
|
0
|
|
|
|
0
|
return 0 if defined ($tags->{ART}); |
856
|
0
|
|
|
|
|
0
|
$id = 'ART'; |
857
|
|
|
|
|
|
|
} |
858
|
|
|
|
|
|
|
elsif ($id eq 'DAY') |
859
|
|
|
|
|
|
|
{ |
860
|
14
|
|
|
|
|
67
|
$data = substr ($data, 0, 4); |
861
|
|
|
|
|
|
|
# Real 10.0 supplies DAY=0 instead of deleting the atom if the |
862
|
|
|
|
|
|
|
# year is not known. What's wrong with these people? |
863
|
14
|
50
|
|
|
|
58
|
return 0 if $data==0; |
864
|
|
|
|
|
|
|
} |
865
|
117
|
|
|
|
|
363
|
$tags->{$id} = $data; |
866
|
|
|
|
|
|
|
} |
867
|
|
|
|
|
|
|
elsif ($type==21) # Integer data |
868
|
|
|
|
|
|
|
{ |
869
|
|
|
|
|
|
|
# Convert to an integer if of an appropriate size |
870
|
19
|
100
|
|
|
|
49
|
if ($size==1) |
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
871
|
|
|
|
|
|
|
{ |
872
|
11
|
|
|
|
|
41
|
$tags->{$id} = unpack 'C', $data; |
873
|
|
|
|
|
|
|
} |
874
|
|
|
|
|
|
|
elsif ($size==2) |
875
|
|
|
|
|
|
|
{ |
876
|
8
|
|
|
|
|
28
|
$tags->{$id} = unpack 'n', $data; |
877
|
|
|
|
|
|
|
} |
878
|
|
|
|
|
|
|
elsif ($size==4) |
879
|
|
|
|
|
|
|
{ |
880
|
0
|
|
|
|
|
0
|
$tags->{$id} = unpack 'N', $data; |
881
|
|
|
|
|
|
|
} |
882
|
|
|
|
|
|
|
elsif ($size==8) |
883
|
|
|
|
|
|
|
{ |
884
|
0
|
|
|
|
|
0
|
my ($hi,$lo); |
885
|
0
|
|
|
|
|
0
|
($hi,$lo) = unpack 'NN', $data; |
886
|
0
|
|
|
|
|
0
|
$tags->{$id} = $hi*(2**32) + $lo; |
887
|
|
|
|
|
|
|
} |
888
|
|
|
|
|
|
|
else |
889
|
|
|
|
|
|
|
{ |
890
|
|
|
|
|
|
|
# Non-standard size - just return the raw data |
891
|
0
|
|
|
|
|
0
|
$tags->{$id} = $data; |
892
|
|
|
|
|
|
|
} |
893
|
|
|
|
|
|
|
} |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
# Silently ignore other data types |
896
|
170
|
|
|
|
|
510
|
return 0; |
897
|
|
|
|
|
|
|
} |
898
|
|
|
|
|
|
|
|
899
|
|
|
|
|
|
|
1; |
900
|
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
__END__ |