1 | <?php |
---|
2 | ///////////////////////////////////////////////////////////////// |
---|
3 | /// getID3() by James Heinrich <info@getid3.org> // |
---|
4 | // available at http://getid3.sourceforge.net // |
---|
5 | // or http://www.getid3.org // |
---|
6 | ///////////////////////////////////////////////////////////////// |
---|
7 | // See readme.txt for more details // |
---|
8 | ///////////////////////////////////////////////////////////////// |
---|
9 | // // |
---|
10 | // module.audio.la.php // |
---|
11 | // module for analyzing LA audio files // |
---|
12 | // dependencies: module.audio.riff.php // |
---|
13 | // /// |
---|
14 | ///////////////////////////////////////////////////////////////// |
---|
15 | |
---|
16 | getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true); |
---|
17 | |
---|
18 | class getid3_la |
---|
19 | { |
---|
20 | |
---|
21 | function getid3_la(&$fd, &$ThisFileInfo) { |
---|
22 | $offset = 0; |
---|
23 | fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET); |
---|
24 | $rawdata = fread($fd, GETID3_FREAD_BUFFER_SIZE); |
---|
25 | |
---|
26 | switch (substr($rawdata, $offset, 4)) { |
---|
27 | case 'LA02': |
---|
28 | case 'LA03': |
---|
29 | case 'LA04': |
---|
30 | $ThisFileInfo['fileformat'] = 'la'; |
---|
31 | $ThisFileInfo['audio']['dataformat'] = 'la'; |
---|
32 | $ThisFileInfo['audio']['lossless'] = true; |
---|
33 | |
---|
34 | $ThisFileInfo['la']['version_major'] = (int) substr($rawdata, $offset + 2, 1); |
---|
35 | $ThisFileInfo['la']['version_minor'] = (int) substr($rawdata, $offset + 3, 1); |
---|
36 | $ThisFileInfo['la']['version'] = (float) $ThisFileInfo['la']['version_major'] + ($ThisFileInfo['la']['version_minor'] / 10); |
---|
37 | $offset += 4; |
---|
38 | |
---|
39 | $ThisFileInfo['la']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); |
---|
40 | $offset += 4; |
---|
41 | if ($ThisFileInfo['la']['uncompressed_size'] == 0) { |
---|
42 | $ThisFileInfo['error'][] = 'Corrupt LA file: uncompressed_size == zero'; |
---|
43 | return false; |
---|
44 | } |
---|
45 | |
---|
46 | $WAVEchunk = substr($rawdata, $offset, 4); |
---|
47 | if ($WAVEchunk !== 'WAVE') { |
---|
48 | $ThisFileInfo['error'][] = 'Expected "WAVE" ('.getid3_lib::PrintHexBytes('WAVE').') at offset '.$offset.', found "'.$WAVEchunk.'" ('.getid3_lib::PrintHexBytes($WAVEchunk).') instead.'; |
---|
49 | return false; |
---|
50 | } |
---|
51 | $offset += 4; |
---|
52 | |
---|
53 | $ThisFileInfo['la']['fmt_size'] = 24; |
---|
54 | if ($ThisFileInfo['la']['version'] >= 0.3) { |
---|
55 | |
---|
56 | $ThisFileInfo['la']['fmt_size'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); |
---|
57 | $ThisFileInfo['la']['header_size'] = 49 + $ThisFileInfo['la']['fmt_size'] - 24; |
---|
58 | $offset += 4; |
---|
59 | |
---|
60 | } else { |
---|
61 | |
---|
62 | // version 0.2 didn't support additional data blocks |
---|
63 | $ThisFileInfo['la']['header_size'] = 41; |
---|
64 | |
---|
65 | } |
---|
66 | |
---|
67 | $fmt_chunk = substr($rawdata, $offset, 4); |
---|
68 | if ($fmt_chunk !== 'fmt ') { |
---|
69 | $ThisFileInfo['error'][] = 'Expected "fmt " ('.getid3_lib::PrintHexBytes('fmt ').') at offset '.$offset.', found "'.$fmt_chunk.'" ('.getid3_lib::PrintHexBytes($fmt_chunk).') instead.'; |
---|
70 | return false; |
---|
71 | } |
---|
72 | $offset += 4; |
---|
73 | $fmt_size = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); |
---|
74 | $offset += 4; |
---|
75 | |
---|
76 | $ThisFileInfo['la']['raw']['format'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2)); |
---|
77 | $offset += 2; |
---|
78 | |
---|
79 | $ThisFileInfo['la']['channels'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2)); |
---|
80 | $offset += 2; |
---|
81 | if ($ThisFileInfo['la']['channels'] == 0) { |
---|
82 | $ThisFileInfo['error'][] = 'Corrupt LA file: channels == zero'; |
---|
83 | return false; |
---|
84 | } |
---|
85 | |
---|
86 | $ThisFileInfo['la']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); |
---|
87 | $offset += 4; |
---|
88 | if ($ThisFileInfo['la']['sample_rate'] == 0) { |
---|
89 | $ThisFileInfo['error'][] = 'Corrupt LA file: sample_rate == zero'; |
---|
90 | return false; |
---|
91 | } |
---|
92 | |
---|
93 | $ThisFileInfo['la']['bytes_per_second'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); |
---|
94 | $offset += 4; |
---|
95 | $ThisFileInfo['la']['bytes_per_sample'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2)); |
---|
96 | $offset += 2; |
---|
97 | $ThisFileInfo['la']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2)); |
---|
98 | $offset += 2; |
---|
99 | |
---|
100 | $ThisFileInfo['la']['samples'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); |
---|
101 | $offset += 4; |
---|
102 | |
---|
103 | $ThisFileInfo['la']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 1)); |
---|
104 | $offset += 1; |
---|
105 | $ThisFileInfo['la']['flags']['seekable'] = (bool) ($ThisFileInfo['la']['raw']['flags'] & 0x01); |
---|
106 | if ($ThisFileInfo['la']['version'] >= 0.4) { |
---|
107 | $ThisFileInfo['la']['flags']['high_compression'] = (bool) ($ThisFileInfo['la']['raw']['flags'] & 0x02); |
---|
108 | } |
---|
109 | |
---|
110 | $ThisFileInfo['la']['original_crc'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); |
---|
111 | $offset += 4; |
---|
112 | |
---|
113 | // mikeØbevin*de |
---|
114 | // Basically, the blocksize/seekevery are 61440/19 in La0.4 and 73728/16 |
---|
115 | // in earlier versions. A seekpoint is added every blocksize * seekevery |
---|
116 | // samples, so 4 * int(totalSamples / (blockSize * seekEvery)) should |
---|
117 | // give the number of bytes used for the seekpoints. Of course, if seeking |
---|
118 | // is disabled, there are no seekpoints stored. |
---|
119 | if ($ThisFileInfo['la']['version'] >= 0.4) { |
---|
120 | $ThisFileInfo['la']['blocksize'] = 61440; |
---|
121 | $ThisFileInfo['la']['seekevery'] = 19; |
---|
122 | } else { |
---|
123 | $ThisFileInfo['la']['blocksize'] = 73728; |
---|
124 | $ThisFileInfo['la']['seekevery'] = 16; |
---|
125 | } |
---|
126 | |
---|
127 | $ThisFileInfo['la']['seekpoint_count'] = 0; |
---|
128 | if ($ThisFileInfo['la']['flags']['seekable']) { |
---|
129 | $ThisFileInfo['la']['seekpoint_count'] = floor($ThisFileInfo['la']['samples'] / ($ThisFileInfo['la']['blocksize'] * $ThisFileInfo['la']['seekevery'])); |
---|
130 | |
---|
131 | for ($i = 0; $i < $ThisFileInfo['la']['seekpoint_count']; $i++) { |
---|
132 | $ThisFileInfo['la']['seekpoints'][] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); |
---|
133 | $offset += 4; |
---|
134 | } |
---|
135 | } |
---|
136 | |
---|
137 | if ($ThisFileInfo['la']['version'] >= 0.3) { |
---|
138 | |
---|
139 | // Following the main header information, the program outputs all of the |
---|
140 | // seekpoints. Following these is what I called the 'footer start', |
---|
141 | // i.e. the position immediately after the La audio data is finished. |
---|
142 | $ThisFileInfo['la']['footerstart'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4)); |
---|
143 | $offset += 4; |
---|
144 | |
---|
145 | if ($ThisFileInfo['la']['footerstart'] > $ThisFileInfo['filesize']) { |
---|
146 | $ThisFileInfo['warning'][] = 'FooterStart value points to offset '.$ThisFileInfo['la']['footerstart'].' which is beyond end-of-file ('.$ThisFileInfo['filesize'].')'; |
---|
147 | $ThisFileInfo['la']['footerstart'] = $ThisFileInfo['filesize']; |
---|
148 | } |
---|
149 | |
---|
150 | } else { |
---|
151 | |
---|
152 | // La v0.2 didn't have FooterStart value |
---|
153 | $ThisFileInfo['la']['footerstart'] = $ThisFileInfo['avdataend']; |
---|
154 | |
---|
155 | } |
---|
156 | |
---|
157 | if ($ThisFileInfo['la']['footerstart'] < $ThisFileInfo['avdataend']) { |
---|
158 | if ($RIFFtempfilename = tempnam('*', 'id3')) { |
---|
159 | if ($RIFF_fp = fopen($RIFFtempfilename, 'w+b')) { |
---|
160 | $RIFFdata = 'WAVE'; |
---|
161 | if ($ThisFileInfo['la']['version'] == 0.2) { |
---|
162 | $RIFFdata .= substr($rawdata, 12, 24); |
---|
163 | } else { |
---|
164 | $RIFFdata .= substr($rawdata, 16, 24); |
---|
165 | } |
---|
166 | if ($ThisFileInfo['la']['footerstart'] < $ThisFileInfo['avdataend']) { |
---|
167 | fseek($fd, $ThisFileInfo['la']['footerstart'], SEEK_SET); |
---|
168 | $RIFFdata .= fread($fd, $ThisFileInfo['avdataend'] - $ThisFileInfo['la']['footerstart']); |
---|
169 | } |
---|
170 | $RIFFdata = 'RIFF'.getid3_lib::LittleEndian2String(strlen($RIFFdata), 4, false).$RIFFdata; |
---|
171 | fwrite($RIFF_fp, $RIFFdata, strlen($RIFFdata)); |
---|
172 | $dummy = $ThisFileInfo; |
---|
173 | $dummy['filesize'] = strlen($RIFFdata); |
---|
174 | $dummy['avdataoffset'] = 0; |
---|
175 | $dummy['avdataend'] = $dummy['filesize']; |
---|
176 | |
---|
177 | $riff = new getid3_riff($RIFF_fp, $dummy); |
---|
178 | if (empty($dummy['error'])) { |
---|
179 | $ThisFileInfo['riff'] = $dummy['riff']; |
---|
180 | } else { |
---|
181 | $ThisFileInfo['warning'][] = 'Error parsing RIFF portion of La file: '.implode($dummy['error']); |
---|
182 | } |
---|
183 | unset($riff); |
---|
184 | unset($dummy); |
---|
185 | fclose($RIFF_fp); |
---|
186 | } |
---|
187 | unlink($RIFFtempfilename); |
---|
188 | } |
---|
189 | } |
---|
190 | |
---|
191 | // $ThisFileInfo['avdataoffset'] should be zero to begin with, but just in case it's not, include the addition anyway |
---|
192 | $ThisFileInfo['avdataend'] = $ThisFileInfo['avdataoffset'] + $ThisFileInfo['la']['footerstart']; |
---|
193 | $ThisFileInfo['avdataoffset'] = $ThisFileInfo['avdataoffset'] + $offset; |
---|
194 | |
---|
195 | //$ThisFileInfo['la']['codec'] = RIFFwFormatTagLookup($ThisFileInfo['la']['raw']['format']); |
---|
196 | $ThisFileInfo['la']['compression_ratio'] = (float) (($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) / $ThisFileInfo['la']['uncompressed_size']); |
---|
197 | $ThisFileInfo['playtime_seconds'] = (float) ($ThisFileInfo['la']['samples'] / $ThisFileInfo['la']['sample_rate']) / $ThisFileInfo['la']['channels']; |
---|
198 | if ($ThisFileInfo['playtime_seconds'] == 0) { |
---|
199 | $ThisFileInfo['error'][] = 'Corrupt LA file: playtime_seconds == zero'; |
---|
200 | return false; |
---|
201 | } |
---|
202 | |
---|
203 | $ThisFileInfo['audio']['bitrate'] = ($ThisFileInfo['avdataend'] - $ThisFileInfo['avdataoffset']) * 8 / $ThisFileInfo['playtime_seconds']; |
---|
204 | //$ThisFileInfo['audio']['codec'] = $ThisFileInfo['la']['codec']; |
---|
205 | $ThisFileInfo['audio']['bits_per_sample'] = $ThisFileInfo['la']['bits_per_sample']; |
---|
206 | break; |
---|
207 | |
---|
208 | default: |
---|
209 | if (substr($rawdata, $offset, 2) == 'LA') { |
---|
210 | $ThisFileInfo['error'][] = 'This version of getID3() (v'.GETID3_VERSION.') doesn\'t support LA version '.substr($rawdata, $offset + 2, 1).'.'.substr($rawdata, $offset + 3, 1).' which this appears to be - check http://getid3.sourceforge.net for updates.'; |
---|
211 | } else { |
---|
212 | $ThisFileInfo['error'][] = 'Not a LA (Lossless-Audio) file'; |
---|
213 | } |
---|
214 | return false; |
---|
215 | break; |
---|
216 | } |
---|
217 | |
---|
218 | $ThisFileInfo['audio']['channels'] = $ThisFileInfo['la']['channels']; |
---|
219 | $ThisFileInfo['audio']['sample_rate'] = (int) $ThisFileInfo['la']['sample_rate']; |
---|
220 | $ThisFileInfo['audio']['encoder'] = 'LA v'.$ThisFileInfo['la']['version']; |
---|
221 | |
---|
222 | return true; |
---|
223 | } |
---|
224 | |
---|
225 | } |
---|
226 | |
---|
227 | |
---|
228 | ?> |
---|