1use strict;
2
3use File::Spec::Functions;
4use FindBin ();
5use Test::More tests => 120;
6
7use Audio::Scan;
8
9# TODO: DLNA profile tests:
10#  AAC_ISO
11#  AAC_MULT5_ISO
12#  AAC_LTP_ISO
13#  AAC_LTP_MULT5_ISO
14#  AAC_LTP_MULT7_ISO
15#  HEAAC_L2_ISO_128
16#  HEAAC_L2_ISO_320
17#  HEAAC_L2_ISO
18#  HEAAC_MULT5_ISO
19#  HEAAC_MULT7
20#  HEAACv2_L2_128
21#  HEAACv2_L2_320
22#  HEAACv2_L3
23#  HEAACv2_L4
24#  HEAACv2_MULT5
25#  HEAACv2_MULT7
26
27# Failing profiles:
28#   O-HEAAC_ISO_128-stereo-16kHz-12.mp4 (channels 1, should be 2)
29
30# AAC file from iTunes 8.1.1
31{
32    my $s = Audio::Scan->scan( _f('itunes811.m4a'), { md5_size => 4096 } );
33
34    my $info  = $s->{info};
35    my $tags  = $s->{tags};
36    my $track = $info->{tracks}->[0];
37
38    is( $info->{audio_offset}, 6169, 'Audio offset ok' );
39    is( $info->{audio_size}, 320, 'Audio size ok' );
40    is( $info->{audio_md5}, '9bf0388a5bfd81c857fdce52dac9ce7f', 'Audio MD5 ok' );
41    is( $info->{compatible_brands}->[0], 'M4A ', 'Compatible brand 1 ok' );
42    is( $info->{compatible_brands}->[1], 'mp42', 'Compatible brand 2 ok' );
43    is( $info->{compatible_brands}->[2], 'isom', 'Compatible brand 3 ok' );
44    is( $info->{leading_mdat}, undef, 'Leading MDAT flag is blank' );
45    is( $info->{file_size}, 6489, 'File size ok' );
46    is( $info->{major_brand}, 'M4A ', 'Major brand ok' );
47    is( $info->{minor_version}, 0, 'Minor version ok' );
48    is( $info->{song_length_ms}, 69, 'Song length ok' );
49    is( $info->{samplerate}, 44100, 'Sample rate ok' );
50    is( $info->{avg_bitrate}, 96000, 'Avg bitrate ok' );
51    is( $info->{dlna_profile}, 'AAC_ISO_192', 'DLNA profile AAC_ISO_192 ok' );
52
53    is( $track->{audio_object_type}, 2, 'Audio object type ok' );
54    is( $track->{audio_type}, 64, 'Audio type ok' );
55    is( $track->{bits_per_sample}, 16, 'Bits per sample ok' );
56    is( $track->{channels}, 2, 'Channels ok' );
57    is( $track->{duration}, 69, 'Duration ok' );
58    is( $track->{encoding}, 'mp4a', 'Encoding ok' );
59    is( $track->{handler_name}, '', 'Handler name ok' );
60    is( $track->{handler_type}, 'soun', 'Handler type ok' );
61    is( $track->{id}, 1, 'Track ID ok' );
62    is( $track->{max_bitrate}, 0, 'Max bitrate ok' );
63
64    is( $tags->{AART}, 'Album Artist', 'AART ok' );
65    is( $tags->{ALB}, 'Album', 'ALB ok' );
66    is( $tags->{ART}, 'Artist', 'ART ok' );
67    is( $tags->{CMT}, 'Comments', 'CMT ok' );
68    is( length($tags->{COVR}), 2103, 'COVR ok' );
69    is( $tags->{CPIL}, 1, 'CPIL ok' );
70    is( $tags->{DAY}, 2009, 'DAY ok' );
71    is( $tags->{DESC}, 'Video Description', 'DESC ok' );
72    is( $tags->{DISK}, '1/2', 'DISK ok' );
73    is( $tags->{GNRE}, 'Jazz', 'GNRE ok' );
74    is( $tags->{GRP}, 'Grouping', 'GRP ok' );
75    is( $tags->{ITUNNORM}, ' 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000', 'ITUNNORM ok' );
76    is( $tags->{ITUNSMPB}, ' 00000000 00000840 000001E4 00000000000001DC 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000', 'ITUNSMPB ok' );
77    is( $tags->{LYR}, 'Lyrics', 'LYR ok' );
78    is( $tags->{NAM}, 'Name', 'NAM ok' );
79    is( $tags->{PGAP}, 1, 'PGAP ok' );
80    is( $tags->{SOAA}, 'Sort Album Artist', 'SOAA ok' );
81    is( $tags->{SOAL}, 'Sort Album', 'SOAL ok' );
82    is( $tags->{SOAR}, 'Sort Artist', 'SOAR ok' );
83    is( $tags->{SOCO}, 'Sort Composer', 'SOCO ok' );
84    is( $tags->{SONM}, 'Sort Name', 'SONM ok' );
85    is( $tags->{SOSN}, 'Sort Show', 'SOSN ok' );
86    is( $tags->{TMPO}, 120, 'TMPO ok' );
87    is( $tags->{TOO}, 'iTunes 8.1.1, QuickTime 7.6', 'TOO ok' );
88    is( $tags->{TRKN}, '1/10', 'TRKN ok' );
89    is( $tags->{TVEN}, 'Episode ID', 'TVEN ok' );
90    is( $tags->{TVES}, 12, 'TVES ok' );
91    is( $tags->{TVSH}, 'Show', 'TVSH ok' );
92    is( $tags->{TVSN}, 12, 'TVSN ok' );
93    is( $tags->{WRT}, 'Composer', 'WRT ok' );
94}
95
96# ALAC file from iTunes 8.1.1
97{
98    my $s = Audio::Scan->scan( _f('alac.m4a') );
99
100    my $info  = $s->{info};
101    my $tags  = $s->{tags};
102    my $track = $info->{tracks}->[0];
103
104    is( $info->{audio_offset}, 3850, 'ALAC audio offset ok' );
105    is( $info->{song_length_ms}, 10, 'ALAC song length ok' );
106    is( $info->{samplerate}, 44100, 'ALAC samplerate ok' );
107    is( $info->{avg_bitrate}, 981600, 'ALAC avg bitrate ok' );
108    ok( !exists $info->{dlna_profile}, 'ALAC no DLNA profile ok' );
109
110    is( $track->{duration}, 10, 'ALAC duration ok' );
111    is( $track->{encoding}, 'alac', 'ALAC encoding ok' );
112    is( $track->{bits_per_sample}, 16, 'ALAC bits_per_sample ok' );
113    is( $track->{channels}, 2, 'ALAC channels ok' );
114
115    is( $tags->{CPIL}, 0, 'ALAC CPIL ok' );
116    is( $tags->{DISK}, '1/2', 'ALAC DISK ok' );
117    is( $tags->{TOO}, 'iTunes 8.1.1', 'ALAC TOO ok' );
118}
119
120# File with mdat before the rest of the boxes
121{
122    my $s = Audio::Scan->scan( _f('leading-mdat.m4a') );
123
124    my $info  = $s->{info};
125    my $tags  = $s->{tags};
126
127    is( $info->{audio_offset}, 20, 'Leading MDAT offset ok' );
128    is( $info->{leading_mdat}, 1, 'Leading MDAT flag ok' );
129    is( $info->{song_length_ms}, 69845, 'Leading MDAT length ok' );
130    is( $info->{samplerate}, 44100, 'Leading MDAT samplerate ok' );
131    is( $info->{avg_bitrate}, 128000, 'Leading MDAT bitrate ok' );
132    ok( !exists $info->{dlna_profile}, 'Leading MDAT no DLNA profile ok' );
133
134    is( $tags->{DAY}, '-001', 'Leading MDAT DAY ok' );
135    is( $tags->{TOO}, 'avc2.0.11.1110', 'Leading MDAT TOO ok' );
136}
137
138# File with array keys, bug 13486
139{
140    my $s = Audio::Scan->scan( _f('array-keys.m4a') );
141
142    my $tags = $s->{tags};
143
144    is( $tags->{AART}, 'Sonic Youth', 'Array key single key ok' );
145    is( ref $tags->{PRODUCER}, 'ARRAY', 'Array key array element ok' );
146    is( $tags->{PRODUCER}->[0], 'Ron Saint Germain', 'Array key element 0 ok' );
147    is( $tags->{PRODUCER}->[1], 'Nick Sansano', 'Array key element 1 ok' );
148    is( $tags->{PRODUCER}->[2], 'Sonic Youth', 'Array key element 2 ok' );
149    is( $tags->{PRODUCER}->[3], 'J Mascis', 'Array key element 3 ok' );
150    is( $tags->{PRODUCER}->[4], 'Don Fleming', 'Array key element 4 ok' );
151}
152
153# 88.2 kHz sample rate, bug 8563
154{
155    my $s = Audio::Scan->scan( _f('882-sample-rate.m4a') );
156
157    my $info = $s->{info};
158
159    is( $info->{samplerate}, 88200, '88.2 sample rate ok' );
160    is( $info->{song_length_ms}, 179006, '88.2 song length ok' );
161    ok( !exists $info->{dlna_profile}, '88.2 no DLNA profile ok' );
162}
163
164# Multiple covers, bug 14476
165{
166	my $s = Audio::Scan->scan( _f('multiple-covers.m4a') );
167
168	my $tags = $s->{tags};
169
170	is( length( $tags->{COVR} ), 2103, 'Multiple cover art reads first cover ok' );
171}
172
173# Test ignoring artwork
174{
175    local $ENV{AUDIO_SCAN_NO_ARTWORK} = 1;
176
177    my $s = Audio::Scan->scan( _f('multiple-covers.m4a') );
178
179	my $tags = $s->{tags};
180
181	is( $tags->{COVR}, 2103, 'COVR with AUDIO_SCAN_NO_ARTWORK ok' );
182	is( $tags->{COVR_offset}, 1926, 'COVR with AUDIO_SCAN_NO_ARTWORK offset ok' );
183}
184
185# File with array keys that are integers, bug 14462
186{
187    my $s = Audio::Scan->scan( _f('array-keys-int.m4a') );
188
189    my $tags = $s->{tags};
190
191    is( $tags->{AART}, 'Stevie Wonder', 'Array key int single key ok' );
192    is( ref $tags->{FREE}, 'ARRAY', 'Array key int array element ok' );
193    is( $tags->{FREE}->[0], 1969970, 'Array key int element 0 ok' );
194    is( $tags->{FREE}->[1], 'xxxxxx@xxxxxx.com', 'Array key int element 1 ok' );
195    is( $tags->{FREE}->[2], 46726, 'Array key int element 2 ok' );
196    is( $tags->{FREE}->[3], 1969972, 'Array key int element 3 ok' );
197    is( $tags->{FREE}->[4], 15, 'Array key int element 4 ok' );
198    is( $tags->{FREE}->[5], 0, 'Array key int element 5 ok' );
199}
200
201# File with short trkn field
202{
203    my $s = Audio::Scan->scan( _f('short-trkn.m4a') );
204
205    my $tags = $s->{tags};
206
207    is( $tags->{TRKN}, 10, 'Short trkn ok' );
208}
209
210# HD-AAC file
211# Contains 48khz LC track and 96khz SLS track
212{
213    my $s = Audio::Scan->scan( _f('hd-aac.m4a') );
214
215    my $info = $s->{info};
216
217    is( $info->{samplerate}, 96000, 'HD-AAC samplerate ok' );
218    is( $info->{song_length_ms}, 409130, 'HD-AAC song length ok' );
219    is( $info->{avg_bitrate}, 4, 'HD-AAC avg bitrate ok' );
220    ok( !exists $info->{dlna_profile}, 'HD-AAC no DLNA profile ok' );
221
222    my $track1 = $info->{tracks}->[0];
223    my $track2 = $info->{tracks}->[1];
224
225    is( $track1->{audio_object_type}, 2, 'HD-AAC LC track ok' );
226    is( $track1->{samplerate}, 48000, 'HD-AAC LC track samplerate ok' );
227    is( $track1->{bits_per_sample}, 16, 'HD-AAC LC track bps ok' );
228
229    is( $track2->{audio_object_type}, 37, 'HD-AAC SLS track ok' );
230    is( $track2->{samplerate}, 96000, 'HD-AAC SLS track samplerate ok' );
231    is( $track2->{bits_per_sample}, 24, 'HD-AAC SLS track bps ok' );
232}
233
234# Bug 15262, secondary hint track with 0 duration, caused bad song_length_ms value
235{
236    my $s = Audio::Scan->scan( _f('hint-track.m4a') );
237
238    my $info = $s->{info};
239
240    is( $info->{song_length_ms}, 263433, 'MP4 hint track song_length_ms ok' );
241    is( $info->{dlna_profile}, 'AAC_ISO_320', 'MP4 hint track DLNA profile AAC_ISO_320 ok' );
242    is( $info->{tracks}->[0]->{duration}, 263433, 'MP4 hint track track 1 duration ok' );
243    is( $info->{tracks}->[1]->{duration}, 0, 'MP4 hint track track 2 duration ok' );
244}
245
246# HE-AAC file, tests that we got the right samplerate from esds
247{
248    my $s = Audio::Scan->scan( _f('heaac.mp4') );
249
250    my $info = $s->{info};
251
252    is( $info->{samplerate}, 16000, 'HE-AAC main samplerate 16000 ok' );
253    is( $info->{tracks}->[0]->{samplerate}, 16000, 'HE-AAC track 1 samplerate 16000 ok' );
254
255    # XXX this should be 2
256    #is( $info->{tracks}->[0]->{channels}, 2, 'HE-AAC track 1 channels 2 ok' );
257}
258
259# Find frame
260{
261    my $offset = Audio::Scan->find_frame( _f('itunes811.m4a'), 30 );
262
263    is( $offset, 6183, 'Find frame ok' );
264}
265
266# Find frame with info
267{
268    my $info = Audio::Scan->find_frame_return_info( _f('itunes811.m4a'), 30 );
269
270    is( $info->{seek_offset}, 6183, 'Find frame return info offset ok' );
271    is( length( $info->{seek_header} ), 6173, 'Find frame return info header rewrite ok' );
272}
273
274# Find frame in ALAC file with unusual stts values
275{
276    my $info = Audio::Scan->find_frame_return_info( _f('alac-multiple-stts.m4a'), 30000 );
277
278    is( $info->{seek_offset}, 2123193, 'Find frame in ALAC multiple stts ok' );
279    is( length( $info->{seek_header} ), 34274, 'Find frame in ALAC multiple stts header ok' );
280}
281
282# Find frame in HD-AAC file (2 tracks) (not yet supported)
283{
284    my $info = Audio::Scan->find_frame_return_info( _f('hd-aac.m4a'), 10 );
285
286    is( $info->{seek_offset}, -1, 'Find frame in HD-AAC ok' );
287}
288
289# Find frame with info from filehandle
290{
291    open my $fh, '<', _f('itunes811.m4a');
292
293    my $info = Audio::Scan->find_frame_fh_return_info( mp4 => $fh, 30 );
294
295    is( $info->{seek_offset}, 6183, 'Find frame return info via filehandle ok' );
296    is( length( $info->{seek_header} ), 6173, 'Find frame return info via filehandle rewrite ok' );
297
298    close $fh;
299}
300
301sub _f {
302    return catfile( $FindBin::Bin, 'mp4', shift );
303}
304