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