Mercurial > judyates
comparison e2gallerypro/e2upload/Backend/Assets/getid3/module.audio.xiph.php @ 3:3f6b44aa6b35 judyates
[svn r4] added ability to buy stuff, from a Prints page, but it doesn't work well with the css, and it also has not been fitted into the perl make system.
author | rlm |
---|---|
date | Mon, 22 Feb 2010 08:02:39 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
2:670229c4eb4b | 3:3f6b44aa6b35 |
---|---|
1 <?php | |
2 // +----------------------------------------------------------------------+ | |
3 // | PHP version 5 | | |
4 // +----------------------------------------------------------------------+ | |
5 // | Copyright (c) 2002-2006 James Heinrich, Allan Hansen | | |
6 // +----------------------------------------------------------------------+ | |
7 // | This source file is subject to version 2 of the GPL license, | | |
8 // | that is bundled with this package in the file license.txt and is | | |
9 // | available through the world-wide-web at the following url: | | |
10 // | http://www.gnu.org/copyleft/gpl.html | | |
11 // +----------------------------------------------------------------------+ | |
12 // | getID3() - http://getid3.sourceforge.net or http://www.getid3.org | | |
13 // +----------------------------------------------------------------------+ | |
14 // | Authors: James Heinrich <infoØgetid3*org> | | |
15 // | Allan Hansen <ahØartemis*dk> | | |
16 // +----------------------------------------------------------------------+ | |
17 // | module.audio.xiph.php | | |
18 // | Module for analyzing Xiph.org audio file formats: | | |
19 // | Ogg Vorbis, FLAC, OggFLAC and Speex - not Ogg Theora | | |
20 // | dependencies: module.lib.image_size.php (optional) | | |
21 // +----------------------------------------------------------------------+ | |
22 // | |
23 // $Id: module.audio.xiph.php,v 1.5 2006/12/03 21:12:43 ah Exp $ | |
24 | |
25 | |
26 | |
27 class getid3_xiph extends getid3_handler | |
28 { | |
29 | |
30 public function Analyze() { | |
31 | |
32 $getid3 = $this->getid3; | |
33 | |
34 if ($getid3->option_tags_images) { | |
35 $getid3->include_module('lib.image_size'); | |
36 } | |
37 | |
38 fseek($getid3->fp, $getid3->info['avdataoffset'], SEEK_SET); | |
39 | |
40 $magic = fread($getid3->fp, 4); | |
41 | |
42 if ($magic == 'OggS') { | |
43 return $this->ParseOgg(); | |
44 } | |
45 | |
46 if ($magic == 'fLaC') { | |
47 return $this->ParseFLAC(); | |
48 } | |
49 | |
50 } | |
51 | |
52 | |
53 | |
54 private function ParseOgg() { | |
55 | |
56 $getid3 = $this->getid3; | |
57 | |
58 fseek($getid3->fp, $getid3->info['avdataoffset'], SEEK_SET); | |
59 | |
60 $getid3->info['audio'] = $getid3->info['ogg'] = array (); | |
61 $info_ogg = &$getid3->info['ogg']; | |
62 $info_audio = &$getid3->info['audio']; | |
63 | |
64 $getid3->info['fileformat'] = 'ogg'; | |
65 | |
66 | |
67 //// Page 1 - Stream Header | |
68 | |
69 $ogg_page_info = $this->ParseOggPageHeader(); | |
70 $info_ogg['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info; | |
71 | |
72 if (ftell($getid3->fp) >= getid3::FREAD_BUFFER_SIZE) { | |
73 throw new getid3_exception('Could not find start of Ogg page in the first '.getid3::FREAD_BUFFER_SIZE.' bytes (this might not be an Ogg file?)'); | |
74 } | |
75 | |
76 $file_data = fread($getid3->fp, $ogg_page_info['page_length']); | |
77 $file_data_offset = 0; | |
78 | |
79 | |
80 // OggFLAC | |
81 if (substr($file_data, 0, 4) == 'fLaC') { | |
82 | |
83 $info_audio['dataformat'] = 'flac'; | |
84 $info_audio['bitrate_mode'] = 'vbr'; | |
85 $info_audio['lossless'] = true; | |
86 | |
87 } | |
88 | |
89 | |
90 // Ogg Vorbis | |
91 elseif (substr($file_data, 1, 6) == 'vorbis') { | |
92 | |
93 $info_audio['dataformat'] = 'vorbis'; | |
94 $info_audio['lossless'] = false; | |
95 | |
96 $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int($file_data[0]); | |
97 $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['stream_type'] = substr($file_data, 1, 6); // hard-coded to 'vorbis' | |
98 | |
99 getid3_lib::ReadSequence('LittleEndian2Int', $info_ogg, $file_data, 7, | |
100 array ( | |
101 'bitstreamversion' => 4, | |
102 'numberofchannels' => 1, | |
103 'samplerate' => 4, | |
104 'bitrate_max' => 4, | |
105 'bitrate_nominal' => 4, | |
106 'bitrate_min' => 4 | |
107 ) | |
108 ); | |
109 | |
110 $n28 = getid3_lib::LittleEndian2Int($file_data{28}); | |
111 $info_ogg['blocksize_small'] = pow(2, $n28 & 0x0F); | |
112 $info_ogg['blocksize_large'] = pow(2, ($n28 & 0xF0) >> 4); | |
113 $info_ogg['stop_bit'] = $n28; | |
114 | |
115 $info_audio['channels'] = $info_ogg['numberofchannels']; | |
116 $info_audio['sample_rate'] = $info_ogg['samplerate']; | |
117 | |
118 $info_audio['bitrate_mode'] = 'vbr'; // overridden if actually abr | |
119 | |
120 if ($info_ogg['bitrate_max'] == 0xFFFFFFFF) { | |
121 unset($info_ogg['bitrate_max']); | |
122 $info_audio['bitrate_mode'] = 'abr'; | |
123 } | |
124 | |
125 if ($info_ogg['bitrate_nominal'] == 0xFFFFFFFF) { | |
126 unset($info_ogg['bitrate_nominal']); | |
127 } | |
128 | |
129 if ($info_ogg['bitrate_min'] == 0xFFFFFFFF) { | |
130 unset($info_ogg['bitrate_min']); | |
131 $info_audio['bitrate_mode'] = 'abr'; | |
132 } | |
133 } | |
134 | |
135 | |
136 // Speex | |
137 elseif (substr($file_data, 0, 8) == 'Speex ') { | |
138 | |
139 // http://www.speex.org/manual/node10.html | |
140 | |
141 $info_audio['dataformat'] = 'speex'; | |
142 $getid3->info['mime_type'] = 'audio/speex'; | |
143 $info_audio['bitrate_mode'] = 'abr'; | |
144 $info_audio['lossless'] = false; | |
145 | |
146 getid3_lib::ReadSequence('LittleEndian2Int', $info_ogg['pageheader'][$ogg_page_info['page_seqno']], $file_data, 0, | |
147 array ( | |
148 'speex_string' => -8, // hard-coded to 'Speex ' | |
149 'speex_version' => -20, // string | |
150 'speex_version_id' => 4, | |
151 'header_size' => 4, | |
152 'rate' => 4, | |
153 'mode' => 4, | |
154 'mode_bitstream_version' => 4, | |
155 'nb_channels' => 4, | |
156 'bitrate' => 4, | |
157 'framesize' => 4, | |
158 'vbr' => 4, | |
159 'frames_per_packet' => 4, | |
160 'extra_headers' => 4, | |
161 'reserved1' => 4, | |
162 'reserved2' => 4 | |
163 ) | |
164 ); | |
165 | |
166 $getid3->info['speex']['speex_version'] = trim($info_ogg['pageheader'][$ogg_page_info['page_seqno']]['speex_version']); | |
167 $getid3->info['speex']['sample_rate'] = $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['rate']; | |
168 $getid3->info['speex']['channels'] = $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['nb_channels']; | |
169 $getid3->info['speex']['vbr'] = (bool)$info_ogg['pageheader'][$ogg_page_info['page_seqno']]['vbr']; | |
170 $getid3->info['speex']['band_type'] = getid3_xiph::SpeexBandModeLookup($info_ogg['pageheader'][$ogg_page_info['page_seqno']]['mode']); | |
171 | |
172 $info_audio['sample_rate'] = $getid3->info['speex']['sample_rate']; | |
173 $info_audio['channels'] = $getid3->info['speex']['channels']; | |
174 | |
175 if ($getid3->info['speex']['vbr']) { | |
176 $info_audio['bitrate_mode'] = 'vbr'; | |
177 } | |
178 } | |
179 | |
180 // Unsupported Ogg file | |
181 else { | |
182 | |
183 throw new getid3_exception('Expecting either "Speex " or "vorbis" identifier strings, found neither'); | |
184 } | |
185 | |
186 | |
187 //// Page 2 - Comment Header | |
188 | |
189 $ogg_page_info = $this->ParseOggPageHeader(); | |
190 $info_ogg['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info; | |
191 | |
192 switch ($info_audio['dataformat']) { | |
193 | |
194 case 'vorbis': | |
195 $file_data = fread($getid3->fp, $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['page_length']); | |
196 $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($file_data, 0, 1)); | |
197 $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['stream_type'] = substr($file_data, 1, 6); // hard-coded to 'vorbis' | |
198 $this->ParseVorbisCommentsFilepointer(); | |
199 break; | |
200 | |
201 case 'flac': | |
202 if (!$this->FLACparseMETAdata()) { | |
203 throw new getid3_exception('Failed to parse FLAC headers'); | |
204 } | |
205 break; | |
206 | |
207 case 'speex': | |
208 fseek($getid3->fp, $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['page_length'], SEEK_CUR); | |
209 $this->ParseVorbisCommentsFilepointer(); | |
210 break; | |
211 } | |
212 | |
213 | |
214 //// Last Page - Number of Samples | |
215 | |
216 fseek($getid3->fp, max($getid3->info['avdataend'] - getid3::FREAD_BUFFER_SIZE, 0), SEEK_SET); | |
217 $last_chunk_of_ogg = strrev(fread($getid3->fp, getid3::FREAD_BUFFER_SIZE)); | |
218 | |
219 if ($last_OggS_postion = strpos($last_chunk_of_ogg, 'SggO')) { | |
220 fseek($getid3->fp, $getid3->info['avdataend'] - ($last_OggS_postion + strlen('SggO')), SEEK_SET); | |
221 $getid3->info['avdataend'] = ftell($getid3->fp); | |
222 $info_ogg['pageheader']['eos'] = $this->ParseOggPageHeader(); | |
223 $info_ogg['samples'] = $info_ogg['pageheader']['eos']['pcm_abs_position']; | |
224 $info_ogg['bitrate_average'] = (($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / ($info_ogg['samples'] / $info_audio['sample_rate']); | |
225 } | |
226 | |
227 if (!empty($info_ogg['bitrate_average'])) { | |
228 $info_audio['bitrate'] = $info_ogg['bitrate_average']; | |
229 } elseif (!empty($info_ogg['bitrate_nominal'])) { | |
230 $info_audio['bitrate'] = $info_ogg['bitrate_nominal']; | |
231 } elseif (!empty($info_ogg['bitrate_min']) && !empty($info_ogg['bitrate_max'])) { | |
232 $info_audio['bitrate'] = ($info_ogg['bitrate_min'] + $info_ogg['bitrate_max']) / 2; | |
233 } | |
234 if (isset($info_audio['bitrate']) && !isset($getid3->info['playtime_seconds'])) { | |
235 $getid3->info['playtime_seconds'] = (float)((($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / $info_audio['bitrate']); | |
236 } | |
237 | |
238 if (isset($info_ogg['vendor'])) { | |
239 $info_audio['encoder'] = preg_replace('/^Encoded with /', '', $info_ogg['vendor']); | |
240 | |
241 // Vorbis only | |
242 if ($info_audio['dataformat'] == 'vorbis') { | |
243 | |
244 // Vorbis 1.0 starts with Xiph.Org | |
245 if (preg_match('/^Xiph.Org/', $info_audio['encoder'])) { | |
246 | |
247 if ($info_audio['bitrate_mode'] == 'abr') { | |
248 | |
249 // Set -b 128 on abr files | |
250 $info_audio['encoder_options'] = '-b '.round($info_ogg['bitrate_nominal'] / 1000); | |
251 | |
252 } elseif (($info_audio['bitrate_mode'] == 'vbr') && ($info_audio['channels'] == 2) && ($info_audio['sample_rate'] >= 44100) && ($info_audio['sample_rate'] <= 48000)) { | |
253 // Set -q N on vbr files | |
254 $info_audio['encoder_options'] = '-q '.getid3_xiph::GetQualityFromNominalBitrate($info_ogg['bitrate_nominal']); | |
255 } | |
256 } | |
257 | |
258 if (empty($info_audio['encoder_options']) && !empty($info_ogg['bitrate_nominal'])) { | |
259 $info_audio['encoder_options'] = 'Nominal bitrate: '.intval(round($info_ogg['bitrate_nominal'] / 1000)).'kbps'; | |
260 } | |
261 } | |
262 } | |
263 | |
264 return true; | |
265 } | |
266 | |
267 | |
268 | |
269 private function ParseOggPageHeader() { | |
270 | |
271 $getid3 = $this->getid3; | |
272 | |
273 // http://xiph.org/ogg/vorbis/doc/framing.html | |
274 $ogg_header['page_start_offset'] = ftell($getid3->fp); // where we started from in the file | |
275 | |
276 $file_data = fread($getid3->fp, getid3::FREAD_BUFFER_SIZE); | |
277 $file_data_offset = 0; | |
278 | |
279 while ((substr($file_data, $file_data_offset++, 4) != 'OggS')) { | |
280 if ((ftell($getid3->fp) - $ogg_header['page_start_offset']) >= getid3::FREAD_BUFFER_SIZE) { | |
281 // should be found before here | |
282 return false; | |
283 } | |
284 if ((($file_data_offset + 28) > strlen($file_data)) || (strlen($file_data) < 28)) { | |
285 if (feof($getid3->fp) || (($file_data .= fread($getid3->fp, getid3::FREAD_BUFFER_SIZE)) === false)) { | |
286 // get some more data, unless eof, in which case fail | |
287 return false; | |
288 } | |
289 } | |
290 } | |
291 | |
292 $file_data_offset += 3; // page, delimited by 'OggS' | |
293 | |
294 getid3_lib::ReadSequence('LittleEndian2Int', $ogg_header, $file_data, $file_data_offset, | |
295 array ( | |
296 'stream_structver' => 1, | |
297 'flags_raw' => 1, | |
298 'pcm_abs_position' => 8, | |
299 'stream_serialno' => 4, | |
300 'page_seqno' => 4, | |
301 'page_checksum' => 4, | |
302 'page_segments' => 1 | |
303 ) | |
304 ); | |
305 | |
306 $file_data_offset += 23; | |
307 | |
308 $ogg_header['flags']['fresh'] = (bool)($ogg_header['flags_raw'] & 0x01); // fresh packet | |
309 $ogg_header['flags']['bos'] = (bool)($ogg_header['flags_raw'] & 0x02); // first page of logical bitstream (bos) | |
310 $ogg_header['flags']['eos'] = (bool)($ogg_header['flags_raw'] & 0x04); // last page of logical bitstream (eos) | |
311 | |
312 $ogg_header['page_length'] = 0; | |
313 for ($i = 0; $i < $ogg_header['page_segments']; $i++) { | |
314 $ogg_header['segment_table'][$i] = getid3_lib::LittleEndian2Int($file_data{$file_data_offset++}); | |
315 $ogg_header['page_length'] += $ogg_header['segment_table'][$i]; | |
316 } | |
317 $ogg_header['header_end_offset'] = $ogg_header['page_start_offset'] + $file_data_offset; | |
318 $ogg_header['page_end_offset'] = $ogg_header['header_end_offset'] + $ogg_header['page_length']; | |
319 fseek($getid3->fp, $ogg_header['header_end_offset'], SEEK_SET); | |
320 | |
321 return $ogg_header; | |
322 } | |
323 | |
324 | |
325 | |
326 private function ParseVorbisCommentsFilepointer() { | |
327 | |
328 $getid3 = $this->getid3; | |
329 | |
330 $original_offset = ftell($getid3->fp); | |
331 $comment_start_offset = $original_offset; | |
332 $comment_data_offset = 0; | |
333 $vorbis_comment_page = 1; | |
334 | |
335 switch ($getid3->info['audio']['dataformat']) { | |
336 | |
337 case 'vorbis': | |
338 $comment_start_offset = $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_start_offset']; // Second Ogg page, after header block | |
339 fseek($getid3->fp, $comment_start_offset, SEEK_SET); | |
340 $comment_data_offset = 27 + $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_segments']; | |
341 $comment_data = fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1) + $comment_data_offset); | |
342 $comment_data_offset += (strlen('vorbis') + 1); | |
343 break; | |
344 | |
345 | |
346 case 'flac': | |
347 fseek($getid3->fp, $getid3->info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4, SEEK_SET); | |
348 $comment_data = fread($getid3->fp, $getid3->info['flac']['VORBIS_COMMENT']['raw']['block_length']); | |
349 break; | |
350 | |
351 | |
352 case 'speex': | |
353 $comment_start_offset = $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_start_offset']; // Second Ogg page, after header block | |
354 fseek($getid3->fp, $comment_start_offset, SEEK_SET); | |
355 $comment_data_offset = 27 + $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_segments']; | |
356 $comment_data = fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1) + $comment_data_offset); | |
357 break; | |
358 | |
359 | |
360 default: | |
361 return false; | |
362 } | |
363 | |
364 $vendor_size = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4)); | |
365 $comment_data_offset += 4; | |
366 | |
367 $getid3->info['ogg']['vendor'] = substr($comment_data, $comment_data_offset, $vendor_size); | |
368 $comment_data_offset += $vendor_size; | |
369 | |
370 $comments_count = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4)); | |
371 $comment_data_offset += 4; | |
372 | |
373 $getid3->info['avdataoffset'] = $comment_start_offset + $comment_data_offset; | |
374 | |
375 for ($i = 0; $i < $comments_count; $i++) { | |
376 | |
377 $getid3->info['ogg']['comments_raw'][$i]['dataoffset'] = $comment_start_offset + $comment_data_offset; | |
378 | |
379 if (ftell($getid3->fp) < ($getid3->info['ogg']['comments_raw'][$i]['dataoffset'] + 4)) { | |
380 $vorbis_comment_page++; | |
381 | |
382 $ogg_page_info = $this->ParseOggPageHeader(); | |
383 $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info; | |
384 | |
385 // First, save what we haven't read yet | |
386 $as_yet_unused_data = substr($comment_data, $comment_data_offset); | |
387 | |
388 // Then take that data off the end | |
389 $comment_data = substr($comment_data, 0, $comment_data_offset); | |
390 | |
391 // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct | |
392 $comment_data .= str_repeat("\x00", 27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']); | |
393 $comment_data_offset += (27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']); | |
394 | |
395 // Finally, stick the unused data back on the end | |
396 $comment_data .= $as_yet_unused_data; | |
397 | |
398 $comment_data .= fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1)); | |
399 } | |
400 $getid3->info['ogg']['comments_raw'][$i]['size'] = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4)); | |
401 | |
402 // replace avdataoffset with position just after the last vorbiscomment | |
403 $getid3->info['avdataoffset'] = $getid3->info['ogg']['comments_raw'][$i]['dataoffset'] + $getid3->info['ogg']['comments_raw'][$i]['size'] + 4; | |
404 | |
405 $comment_data_offset += 4; | |
406 while ((strlen($comment_data) - $comment_data_offset) < $getid3->info['ogg']['comments_raw'][$i]['size']) { | |
407 | |
408 if (($getid3->info['ogg']['comments_raw'][$i]['size'] > $getid3->info['avdataend']) || ($getid3->info['ogg']['comments_raw'][$i]['size'] < 0)) { | |
409 throw new getid3_exception('Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($getid3->info['ogg']['comments_raw'][$i]['size']).' bytes) - aborting reading comments'); | |
410 } | |
411 | |
412 $vorbis_comment_page++; | |
413 | |
414 $ogg_page_info = $this->ParseOggPageHeader(); | |
415 $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info; | |
416 | |
417 // First, save what we haven't read yet | |
418 $as_yet_unused_data = substr($comment_data, $comment_data_offset); | |
419 | |
420 // Then take that data off the end | |
421 $comment_data = substr($comment_data, 0, $comment_data_offset); | |
422 | |
423 // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct | |
424 $comment_data .= str_repeat("\x00", 27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']); | |
425 $comment_data_offset += (27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']); | |
426 | |
427 // Finally, stick the unused data back on the end | |
428 $comment_data .= $as_yet_unused_data; | |
429 | |
430 //$comment_data .= fread($getid3->fp, $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_length']); | |
431 $comment_data .= fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1)); | |
432 | |
433 //$filebaseoffset += $ogg_page_info['header_end_offset'] - $ogg_page_info['page_start_offset']; | |
434 } | |
435 $comment_string = substr($comment_data, $comment_data_offset, $getid3->info['ogg']['comments_raw'][$i]['size']); | |
436 $comment_data_offset += $getid3->info['ogg']['comments_raw'][$i]['size']; | |
437 | |
438 if (!$comment_string) { | |
439 | |
440 // no comment? | |
441 $getid3->warning('Blank Ogg comment ['.$i.']'); | |
442 | |
443 } elseif (strstr($comment_string, '=')) { | |
444 | |
445 $comment_exploded = explode('=', $comment_string, 2); | |
446 $getid3->info['ogg']['comments_raw'][$i]['key'] = strtoupper($comment_exploded[0]); | |
447 $getid3->info['ogg']['comments_raw'][$i]['value'] = @$comment_exploded[1]; | |
448 $getid3->info['ogg']['comments_raw'][$i]['data'] = base64_decode($getid3->info['ogg']['comments_raw'][$i]['value']); | |
449 | |
450 $getid3->info['ogg']['comments'][strtolower($getid3->info['ogg']['comments_raw'][$i]['key'])][] = $getid3->info['ogg']['comments_raw'][$i]['value']; | |
451 | |
452 if ($getid3->option_tags_images) { | |
453 $image_chunk_check = getid3_lib_image_size::get($getid3->info['ogg']['comments_raw'][$i]['data']); | |
454 $getid3->info['ogg']['comments_raw'][$i]['image_mime'] = image_type_to_mime_type($image_chunk_check[2]); | |
455 } | |
456 | |
457 if (!@$getid3->info['ogg']['comments_raw'][$i]['image_mime'] || ($getid3->info['ogg']['comments_raw'][$i]['image_mime'] == 'application/octet-stream')) { | |
458 unset($getid3->info['ogg']['comments_raw'][$i]['image_mime']); | |
459 unset($getid3->info['ogg']['comments_raw'][$i]['data']); | |
460 } | |
461 | |
462 | |
463 } else { | |
464 | |
465 $getid3->warning('[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$comment_string); | |
466 } | |
467 } | |
468 | |
469 | |
470 // Replay Gain Adjustment | |
471 // http://privatewww.essex.ac.uk/~djmrob/replaygain/ | |
472 if (isset($getid3->info['ogg']['comments']) && is_array($getid3->info['ogg']['comments'])) { | |
473 foreach ($getid3->info['ogg']['comments'] as $index => $commentvalue) { | |
474 switch ($index) { | |
475 case 'rg_audiophile': | |
476 case 'replaygain_album_gain': | |
477 $getid3->info['replay_gain']['album']['adjustment'] = (float)$commentvalue[0]; | |
478 unset($getid3->info['ogg']['comments'][$index]); | |
479 break; | |
480 | |
481 case 'rg_radio': | |
482 case 'replaygain_track_gain': | |
483 $getid3->info['replay_gain']['track']['adjustment'] = (float)$commentvalue[0]; | |
484 unset($getid3->info['ogg']['comments'][$index]); | |
485 break; | |
486 | |
487 case 'replaygain_album_peak': | |
488 $getid3->info['replay_gain']['album']['peak'] = (float)$commentvalue[0]; | |
489 unset($getid3->info['ogg']['comments'][$index]); | |
490 break; | |
491 | |
492 case 'rg_peak': | |
493 case 'replaygain_track_peak': | |
494 $getid3->info['replay_gain']['track']['peak'] = (float)$commentvalue[0]; | |
495 unset($getid3->info['ogg']['comments'][$index]); | |
496 break; | |
497 | |
498 case 'replaygain_reference_loudness': | |
499 $getid3->info['replay_gain']['reference_volume'] = (float)$commentvalue[0]; | |
500 unset($getid3->info['ogg']['comments'][$index]); | |
501 break; | |
502 } | |
503 } | |
504 } | |
505 | |
506 fseek($getid3->fp, $original_offset, SEEK_SET); | |
507 | |
508 return true; | |
509 } | |
510 | |
511 | |
512 | |
513 private function ParseFLAC() { | |
514 | |
515 $getid3 = $this->getid3; | |
516 | |
517 // http://flac.sourceforge.net/format.html | |
518 | |
519 $getid3->info['fileformat'] = 'flac'; | |
520 $getid3->info['audio']['dataformat'] = 'flac'; | |
521 $getid3->info['audio']['bitrate_mode'] = 'vbr'; | |
522 $getid3->info['audio']['lossless'] = true; | |
523 | |
524 return $this->FLACparseMETAdata(); | |
525 } | |
526 | |
527 | |
528 | |
529 private function FLACparseMETAdata() { | |
530 | |
531 $getid3 = $this->getid3; | |
532 | |
533 do { | |
534 | |
535 $meta_data_block_offset = ftell($getid3->fp); | |
536 $meta_data_block_header = fread($getid3->fp, 4); | |
537 $meta_data_last_block_flag = (bool)(getid3_lib::BigEndian2Int($meta_data_block_header[0]) & 0x80); | |
538 $meta_data_block_type = getid3_lib::BigEndian2Int($meta_data_block_header[0]) & 0x7F; | |
539 $meta_data_block_length = getid3_lib::BigEndian2Int(substr($meta_data_block_header, 1, 3)); | |
540 $meta_data_block_type_text = getid3_xiph::FLACmetaBlockTypeLookup($meta_data_block_type); | |
541 | |
542 if ($meta_data_block_length < 0) { | |
543 throw new getid3_exception('corrupt or invalid METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$meta_data_block_type.') at offset '.$meta_data_block_offset); | |
544 } | |
545 | |
546 $getid3->info['flac'][$meta_data_block_type_text]['raw'] = array ( | |
547 'offset' => $meta_data_block_offset, | |
548 'last_meta_block' => $meta_data_last_block_flag, | |
549 'block_type' => $meta_data_block_type, | |
550 'block_type_text' => $meta_data_block_type_text, | |
551 'block_length' => $meta_data_block_length, | |
552 'block_data' => @fread($getid3->fp, $meta_data_block_length) | |
553 ); | |
554 $getid3->info['avdataoffset'] = ftell($getid3->fp); | |
555 | |
556 switch ($meta_data_block_type_text) { | |
557 | |
558 case 'STREAMINFO': | |
559 if (!$this->FLACparseSTREAMINFO($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) { | |
560 return false; | |
561 } | |
562 break; | |
563 | |
564 case 'PADDING': | |
565 // ignore | |
566 break; | |
567 | |
568 case 'APPLICATION': | |
569 if (!$this->FLACparseAPPLICATION($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) { | |
570 return false; | |
571 } | |
572 break; | |
573 | |
574 case 'SEEKTABLE': | |
575 if (!$this->FLACparseSEEKTABLE($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) { | |
576 return false; | |
577 } | |
578 break; | |
579 | |
580 case 'VORBIS_COMMENT': | |
581 $old_offset = ftell($getid3->fp); | |
582 fseek($getid3->fp, 0 - $meta_data_block_length, SEEK_CUR); | |
583 $this->ParseVorbisCommentsFilepointer($getid3->fp, $getid3->info); | |
584 fseek($getid3->fp, $old_offset, SEEK_SET); | |
585 break; | |
586 | |
587 case 'CUESHEET': | |
588 if (!$this->FLACparseCUESHEET($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) { | |
589 return false; | |
590 } | |
591 break; | |
592 | |
593 case 'PICTURE': | |
594 if (!$this->FLACparsePICTURE($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) { | |
595 return false; | |
596 } | |
597 break; | |
598 | |
599 default: | |
600 $getid3->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$meta_data_block_type.') at offset '.$meta_data_block_offset); | |
601 } | |
602 | |
603 } while ($meta_data_last_block_flag === false); | |
604 | |
605 | |
606 if (isset($getid3->info['flac']['STREAMINFO'])) { | |
607 $getid3->info['flac']['compressed_audio_bytes'] = $getid3->info['avdataend'] - $getid3->info['avdataoffset']; | |
608 $getid3->info['flac']['uncompressed_audio_bytes'] = $getid3->info['flac']['STREAMINFO']['samples_stream'] * $getid3->info['flac']['STREAMINFO']['channels'] * ($getid3->info['flac']['STREAMINFO']['bits_per_sample'] / 8); | |
609 $getid3->info['flac']['compression_ratio'] = $getid3->info['flac']['compressed_audio_bytes'] / $getid3->info['flac']['uncompressed_audio_bytes']; | |
610 } | |
611 | |
612 // set md5_data_source - built into flac 0.5+ | |
613 if (isset($getid3->info['flac']['STREAMINFO']['audio_signature'])) { | |
614 | |
615 if ($getid3->info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) { | |
616 $getid3->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)'); | |
617 | |
618 } else { | |
619 | |
620 $getid3->info['md5_data_source'] = ''; | |
621 $md5 = $getid3->info['flac']['STREAMINFO']['audio_signature']; | |
622 for ($i = 0; $i < strlen($md5); $i++) { | |
623 $getid3->info['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT); | |
624 } | |
625 if (!preg_match('/^[0-9a-f]{32}$/', $getid3->info['md5_data_source'])) { | |
626 unset($getid3->info['md5_data_source']); | |
627 } | |
628 | |
629 } | |
630 | |
631 } | |
632 | |
633 $getid3->info['audio']['bits_per_sample'] = $getid3->info['flac']['STREAMINFO']['bits_per_sample']; | |
634 if ($getid3->info['audio']['bits_per_sample'] == 8) { | |
635 // special case | |
636 // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value | |
637 // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed | |
638 $getid3->warning('FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file'); | |
639 } | |
640 if (!empty($getid3->info['ogg']['vendor'])) { | |
641 $getid3->info['audio']['encoder'] = $getid3->info['ogg']['vendor']; | |
642 } | |
643 | |
644 return true; | |
645 } | |
646 | |
647 | |
648 | |
649 private function FLACparseSTREAMINFO($meta_data_block_data) { | |
650 | |
651 $getid3 = $this->getid3; | |
652 | |
653 getid3_lib::ReadSequence('BigEndian2Int', $getid3->info['flac']['STREAMINFO'], $meta_data_block_data, 0, | |
654 array ( | |
655 'min_block_size' => 2, | |
656 'max_block_size' => 2, | |
657 'min_frame_size' => 3, | |
658 'max_frame_size' => 3 | |
659 ) | |
660 ); | |
661 | |
662 $sample_rate_channels_sample_bits_stream_samples = getid3_lib::BigEndian2Bin(substr($meta_data_block_data, 10, 8)); | |
663 | |
664 $getid3->info['flac']['STREAMINFO']['sample_rate'] = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 0, 20)); | |
665 $getid3->info['flac']['STREAMINFO']['channels'] = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 20, 3)) + 1; | |
666 $getid3->info['flac']['STREAMINFO']['bits_per_sample'] = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 23, 5)) + 1; | |
667 $getid3->info['flac']['STREAMINFO']['samples_stream'] = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 28, 36)); // bindec() returns float in case of int overrun | |
668 $getid3->info['flac']['STREAMINFO']['audio_signature'] = substr($meta_data_block_data, 18, 16); | |
669 | |
670 if (!empty($getid3->info['flac']['STREAMINFO']['sample_rate'])) { | |
671 | |
672 $getid3->info['audio']['bitrate_mode'] = 'vbr'; | |
673 $getid3->info['audio']['sample_rate'] = $getid3->info['flac']['STREAMINFO']['sample_rate']; | |
674 $getid3->info['audio']['channels'] = $getid3->info['flac']['STREAMINFO']['channels']; | |
675 $getid3->info['audio']['bits_per_sample'] = $getid3->info['flac']['STREAMINFO']['bits_per_sample']; | |
676 $getid3->info['playtime_seconds'] = $getid3->info['flac']['STREAMINFO']['samples_stream'] / $getid3->info['flac']['STREAMINFO']['sample_rate']; | |
677 $getid3->info['audio']['bitrate'] = (($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / $getid3->info['playtime_seconds']; | |
678 | |
679 } else { | |
680 | |
681 throw new getid3_exception('Corrupt METAdata block: STREAMINFO'); | |
682 } | |
683 | |
684 unset($getid3->info['flac']['STREAMINFO']['raw']); | |
685 | |
686 return true; | |
687 } | |
688 | |
689 | |
690 | |
691 private function FLACparseAPPLICATION($meta_data_block_data) { | |
692 | |
693 $getid3 = $this->getid3; | |
694 | |
695 $application_id = getid3_lib::BigEndian2Int(substr($meta_data_block_data, 0, 4)); | |
696 | |
697 $getid3->info['flac']['APPLICATION'][$application_id]['name'] = getid3_xiph::FLACapplicationIDLookup($application_id); | |
698 $getid3->info['flac']['APPLICATION'][$application_id]['data'] = substr($meta_data_block_data, 4); | |
699 | |
700 unset($getid3->info['flac']['APPLICATION']['raw']); | |
701 | |
702 return true; | |
703 } | |
704 | |
705 | |
706 | |
707 private function FLACparseSEEKTABLE($meta_data_block_data) { | |
708 | |
709 $getid3 = $this->getid3; | |
710 | |
711 $offset = 0; | |
712 $meta_data_block_length = strlen($meta_data_block_data); | |
713 while ($offset < $meta_data_block_length) { | |
714 $sample_number_string = substr($meta_data_block_data, $offset, 8); | |
715 $offset += 8; | |
716 if ($sample_number_string == "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF") { | |
717 | |
718 // placeholder point | |
719 @$getid3->info['flac']['SEEKTABLE']['placeholders']++; | |
720 $offset += 10; | |
721 | |
722 } else { | |
723 | |
724 $sample_number = getid3_lib::BigEndian2Int($sample_number_string); | |
725 | |
726 $getid3->info['flac']['SEEKTABLE'][$sample_number]['offset'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8)); | |
727 $offset += 8; | |
728 | |
729 $getid3->info['flac']['SEEKTABLE'][$sample_number]['samples'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 2)); | |
730 $offset += 2; | |
731 | |
732 } | |
733 } | |
734 | |
735 unset($getid3->info['flac']['SEEKTABLE']['raw']); | |
736 | |
737 return true; | |
738 } | |
739 | |
740 | |
741 | |
742 private function FLACparseCUESHEET($meta_data_block_data) { | |
743 | |
744 $getid3 = $this->getid3; | |
745 | |
746 $getid3->info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($meta_data_block_data, 0, 128), "\0"); | |
747 $getid3->info['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, 128, 8)); | |
748 $getid3->info['flac']['CUESHEET']['flags']['is_cd'] = (bool)(getid3_lib::BigEndian2Int($meta_data_block_data[136]) & 0x80); | |
749 $getid3->info['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int($meta_data_block_data[395]); | |
750 | |
751 $offset = 396; | |
752 | |
753 for ($track = 0; $track < $getid3->info['flac']['CUESHEET']['number_tracks']; $track++) { | |
754 | |
755 $track_sample_offset = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8)); | |
756 $offset += 8; | |
757 | |
758 $track_number = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++}); | |
759 | |
760 $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['sample_offset'] = $track_sample_offset; | |
761 $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['isrc'] = substr($meta_data_block_data, $offset, 12); | |
762 $offset += 12; | |
763 | |
764 $track_flags_raw = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++}); | |
765 $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['flags']['is_audio'] = (bool)($track_flags_raw & 0x80); | |
766 $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['flags']['pre_emphasis'] = (bool)($track_flags_raw & 0x40); | |
767 | |
768 $offset += 13; // reserved | |
769 | |
770 $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['index_points'] = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++}); | |
771 | |
772 for ($index = 0; $index < $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['index_points']; $index++) { | |
773 | |
774 $index_sample_offset = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8)); | |
775 $offset += 8; | |
776 | |
777 $index_number = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++}); | |
778 $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['indexes'][$index_number] = $index_sample_offset; | |
779 | |
780 $offset += 3; // reserved | |
781 } | |
782 } | |
783 | |
784 unset($getid3->info['flac']['CUESHEET']['raw']); | |
785 | |
786 return true; | |
787 } | |
788 | |
789 | |
790 | |
791 private function FLACparsePICTURE($meta_data_block_data) { | |
792 | |
793 $getid3 = $this->getid3; | |
794 | |
795 $picture = &$getid3->info['flac']['PICTURE'][sizeof($getid3->info['flac']['PICTURE']) - 1]; | |
796 | |
797 $offset = 0; | |
798 | |
799 $picture['type'] = $this->FLACpictureTypeLookup(getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4))); | |
800 $offset += 4; | |
801 | |
802 $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); | |
803 $offset += 4; | |
804 | |
805 $picture['mime_type'] = substr($meta_data_block_data, $offset, $length); | |
806 $offset += $length; | |
807 | |
808 $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); | |
809 $offset += 4; | |
810 | |
811 $picture['description'] = substr($meta_data_block_data, $offset, $length); | |
812 $offset += $length; | |
813 | |
814 $picture['width'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); | |
815 $offset += 4; | |
816 | |
817 $picture['height'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); | |
818 $offset += 4; | |
819 | |
820 $picture['color_depth'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); | |
821 $offset += 4; | |
822 | |
823 $picture['colors_indexed'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); | |
824 $offset += 4; | |
825 | |
826 $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4)); | |
827 $offset += 4; | |
828 | |
829 $picture['image_data'] = substr($meta_data_block_data, $offset, $length); | |
830 $offset += $length; | |
831 | |
832 unset($getid3->info['flac']['PICTURE']['raw']); | |
833 | |
834 return true; | |
835 } | |
836 | |
837 | |
838 | |
839 public static function SpeexBandModeLookup($mode) { | |
840 | |
841 static $lookup = array ( | |
842 0 => 'narrow', | |
843 1 => 'wide', | |
844 2 => 'ultra-wide' | |
845 ); | |
846 return (isset($lookup[$mode]) ? $lookup[$mode] : null); | |
847 } | |
848 | |
849 | |
850 | |
851 public static function OggPageSegmentLength($ogg_info_array, $segment_number=1) { | |
852 | |
853 for ($i = 0; $i < $segment_number; $i++) { | |
854 $segment_length = 0; | |
855 foreach ($ogg_info_array['segment_table'] as $key => $value) { | |
856 $segment_length += $value; | |
857 if ($value < 255) { | |
858 break; | |
859 } | |
860 } | |
861 } | |
862 return $segment_length; | |
863 } | |
864 | |
865 | |
866 | |
867 public static function GetQualityFromNominalBitrate($nominal_bitrate) { | |
868 | |
869 // decrease precision | |
870 $nominal_bitrate = $nominal_bitrate / 1000; | |
871 | |
872 if ($nominal_bitrate < 128) { | |
873 // q-1 to q4 | |
874 $qval = ($nominal_bitrate - 64) / 16; | |
875 } elseif ($nominal_bitrate < 256) { | |
876 // q4 to q8 | |
877 $qval = $nominal_bitrate / 32; | |
878 } elseif ($nominal_bitrate < 320) { | |
879 // q8 to q9 | |
880 $qval = ($nominal_bitrate + 256) / 64; | |
881 } else { | |
882 // q9 to q10 | |
883 $qval = ($nominal_bitrate + 1300) / 180; | |
884 } | |
885 return round($qval, 1); // 5 or 4.9 | |
886 } | |
887 | |
888 | |
889 | |
890 public static function FLACmetaBlockTypeLookup($block_type) { | |
891 | |
892 static $lookup = array ( | |
893 0 => 'STREAMINFO', | |
894 1 => 'PADDING', | |
895 2 => 'APPLICATION', | |
896 3 => 'SEEKTABLE', | |
897 4 => 'VORBIS_COMMENT', | |
898 5 => 'CUESHEET', | |
899 6 => 'PICTURE' | |
900 ); | |
901 return (isset($lookup[$block_type]) ? $lookup[$block_type] : 'reserved'); | |
902 } | |
903 | |
904 | |
905 | |
906 public static function FLACapplicationIDLookup($application_id) { | |
907 | |
908 // http://flac.sourceforge.net/id.html | |
909 | |
910 static $lookup = array ( | |
911 0x46746F6C => 'flac-tools', // 'Ftol' | |
912 0x46746F6C => 'Sound Font FLAC', // 'SFFL' | |
913 0x7065656D => 'Parseable Embedded Extensible Metadata (specification)', // 'peem' | |
914 0x786D6364 => 'xmcd' | |
915 | |
916 ); | |
917 return (isset($lookup[$application_id]) ? $lookup[$application_id] : 'reserved'); | |
918 } | |
919 | |
920 | |
921 public static function FLACpictureTypeLookup($type_id) { | |
922 | |
923 static $lookup = array ( | |
924 | |
925 0 => 'Other', | |
926 1 => "32x32 pixels 'file icon' (PNG only)", | |
927 2 => 'Other file icon', | |
928 3 => 'Cover (front)', | |
929 4 => 'Cover (back)', | |
930 5 => 'Leaflet page', | |
931 6 => 'Media (e.g. label side of CD)', | |
932 7 => 'Lead artist/lead performer/soloist', | |
933 8 => 'Artist/performer', | |
934 9 => 'Conductor', | |
935 10 => 'Band/Orchestra', | |
936 11 => 'Composer', | |
937 12 => 'Lyricist/text writer', | |
938 13 => 'Recording Location', | |
939 14 => 'During recording', | |
940 15 => 'During performance', | |
941 16 => 'Movie/video screen capture', | |
942 17 => 'A bright coloured fish', | |
943 18 => 'Illustration', | |
944 19 => 'Band/artist logotype', | |
945 20 => 'Publisher/Studio logotype' | |
946 ); | |
947 return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved'); | |
948 } | |
949 | |
950 } | |
951 | |
952 ?> |