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