ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
La.php
Go to the documentation of this file.
1 <?php
2 
3 namespace 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 
31 class 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 }
GetId3() by James Heinrich info@getid3.org //.
Definition: La.php:31
static PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8')
Definition: Helper.php:36
GetId3() by James Heinrich info@getid3.org //.
Definition: BaseHandler.php:25
fseek($bytes, $whence=SEEK_SET)
$info
Definition: example_052.php:80
GetId3() by James Heinrich info@getid3.org //.
Definition: GetId3Core.php:25
static LittleEndian2Int($byteword, $signed=false)
Definition: Helper.php:413
GetId3() by James Heinrich info@getid3.org //.
Definition: Riff.php:42