source: spip-zone/_core_/branches/spip-3.1/plugins/medias/lib/getid3/module.audio.flac.php @ 113163

Last change on this file since 113163 was 113163, checked in by spip.franck@…, 5 months ago

Mise à jour de la lib getid en version 1.9.16, nous étions en 1.9.12
https://github.com/JamesHeinrich/getID3/blob/master/changelog.txt

File size: 19.1 KB
Line 
1<?php
2
3/////////////////////////////////////////////////////////////////
4/// getID3() by James Heinrich <info@getid3.org>               //
5//  available at https://github.com/JamesHeinrich/getID3       //
6//            or https://www.getid3.org                        //
7//            or http://getid3.sourceforge.net                 //
8//  see readme.txt for more details                            //
9/////////////////////////////////////////////////////////////////
10//                                                             //
11// module.audio.flac.php                                       //
12// module for analyzing FLAC and OggFLAC audio files           //
13// dependencies: module.audio.ogg.php                          //
14//                                                            ///
15/////////////////////////////////////////////////////////////////
16
17
18getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true);
19
20/**
21* @tutorial http://flac.sourceforge.net/format.html
22*/
23class getid3_flac extends getid3_handler
24{
25        const syncword = 'fLaC';
26
27        /**
28         * @return bool
29         */
30        public function Analyze() {
31                $info = &$this->getid3->info;
32
33                $this->fseek($info['avdataoffset']);
34                $StreamMarker = $this->fread(4);
35                if ($StreamMarker != self::syncword) {
36                        return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"');
37                }
38                $info['fileformat']            = 'flac';
39                $info['audio']['dataformat']   = 'flac';
40                $info['audio']['bitrate_mode'] = 'vbr';
41                $info['audio']['lossless']     = true;
42
43                // parse flac container
44                return $this->parseMETAdata();
45        }
46
47        /**
48         * @return bool
49         */
50        public function parseMETAdata() {
51                $info = &$this->getid3->info;
52                do {
53                        $BlockOffset   = $this->ftell();
54                        $BlockHeader   = $this->fread(4);
55                        $LBFBT         = getid3_lib::BigEndian2Int(substr($BlockHeader, 0, 1));  // LBFBT = LastBlockFlag + BlockType
56                        $LastBlockFlag = (bool) ($LBFBT & 0x80);
57                        $BlockType     =        ($LBFBT & 0x7F);
58                        $BlockLength   = getid3_lib::BigEndian2Int(substr($BlockHeader, 1, 3));
59                        $BlockTypeText = self::metaBlockTypeLookup($BlockType);
60
61                        if (($BlockOffset + 4 + $BlockLength) > $info['avdataend']) {
62                                $this->warning('METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockTypeText.') at offset '.$BlockOffset.' extends beyond end of file');
63                                break;
64                        }
65                        if ($BlockLength < 1) {
66                                if ($BlockTypeText != 'reserved') {
67                                        // probably supposed to be zero-length
68                                        $this->warning('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockTypeText.') at offset '.$BlockOffset.' is zero bytes');
69                                        continue;
70                                }
71                                $this->error('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockLength.') at offset '.$BlockOffset.' is invalid');
72                                break;
73                        }
74
75                        $info['flac'][$BlockTypeText]['raw'] = array();
76                        $BlockTypeText_raw = &$info['flac'][$BlockTypeText]['raw'];
77
78                        $BlockTypeText_raw['offset']          = $BlockOffset;
79                        $BlockTypeText_raw['last_meta_block'] = $LastBlockFlag;
80                        $BlockTypeText_raw['block_type']      = $BlockType;
81                        $BlockTypeText_raw['block_type_text'] = $BlockTypeText;
82                        $BlockTypeText_raw['block_length']    = $BlockLength;
83                        if ($BlockTypeText_raw['block_type'] != 0x06) { // do not read attachment data automatically
84                                $BlockTypeText_raw['block_data']  = $this->fread($BlockLength);
85                        }
86
87                        switch ($BlockTypeText) {
88                                case 'STREAMINFO':     // 0x00
89                                        if (!$this->parseSTREAMINFO($BlockTypeText_raw['block_data'])) {
90                                                return false;
91                                        }
92                                        break;
93
94                                case 'PADDING':        // 0x01
95                                        unset($info['flac']['PADDING']); // ignore
96                                        break;
97
98                                case 'APPLICATION':    // 0x02
99                                        if (!$this->parseAPPLICATION($BlockTypeText_raw['block_data'])) {
100                                                return false;
101                                        }
102                                        break;
103
104                                case 'SEEKTABLE':      // 0x03
105                                        if (!$this->parseSEEKTABLE($BlockTypeText_raw['block_data'])) {
106                                                return false;
107                                        }
108                                        break;
109
110                                case 'VORBIS_COMMENT': // 0x04
111                                        if (!$this->parseVORBIS_COMMENT($BlockTypeText_raw['block_data'])) {
112                                                return false;
113                                        }
114                                        break;
115
116                                case 'CUESHEET':       // 0x05
117                                        if (!$this->parseCUESHEET($BlockTypeText_raw['block_data'])) {
118                                                return false;
119                                        }
120                                        break;
121
122                                case 'PICTURE':        // 0x06
123                                        if (!$this->parsePICTURE()) {
124                                                return false;
125                                        }
126                                        break;
127
128                                default:
129                                        $this->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockType.') at offset '.$BlockOffset);
130                        }
131
132                        unset($info['flac'][$BlockTypeText]['raw']);
133                        $info['avdataoffset'] = $this->ftell();
134                }
135                while ($LastBlockFlag === false);
136
137                // handle tags
138                if (!empty($info['flac']['VORBIS_COMMENT']['comments'])) {
139                        $info['flac']['comments'] = $info['flac']['VORBIS_COMMENT']['comments'];
140                }
141                if (!empty($info['flac']['VORBIS_COMMENT']['vendor'])) {
142                        $info['audio']['encoder'] = str_replace('reference ', '', $info['flac']['VORBIS_COMMENT']['vendor']);
143                }
144
145                // copy attachments to 'comments' array if nesesary
146                if (isset($info['flac']['PICTURE']) && ($this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE)) {
147                        foreach ($info['flac']['PICTURE'] as $entry) {
148                                if (!empty($entry['data'])) {
149                                        if (!isset($info['flac']['comments']['picture'])) {
150                                                $info['flac']['comments']['picture'] = array();
151                                        }
152                                        $comments_picture_data = array();
153                                        foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
154                                                if (isset($entry[$picture_key])) {
155                                                        $comments_picture_data[$picture_key] = $entry[$picture_key];
156                                                }
157                                        }
158                                        $info['flac']['comments']['picture'][] = $comments_picture_data;
159                                        unset($comments_picture_data);
160                                }
161                        }
162                }
163
164                if (isset($info['flac']['STREAMINFO'])) {
165                        if (!$this->isDependencyFor('matroska')) {
166                                $info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset'];
167                        }
168                        $info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8);
169                        if ($info['flac']['uncompressed_audio_bytes'] == 0) {
170                                return $this->error('Corrupt FLAC file: uncompressed_audio_bytes == zero');
171                        }
172                        if (!empty($info['flac']['compressed_audio_bytes'])) {
173                                $info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes'];
174                        }
175                }
176
177                // set md5_data_source - built into flac 0.5+
178                if (isset($info['flac']['STREAMINFO']['audio_signature'])) {
179
180                        if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) {
181                $this->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)');
182                        }
183                        else {
184                                $info['md5_data_source'] = '';
185                                $md5 = $info['flac']['STREAMINFO']['audio_signature'];
186                                for ($i = 0; $i < strlen($md5); $i++) {
187                                        $info['md5_data_source'] .= str_pad(dechex(ord($md5[$i])), 2, '00', STR_PAD_LEFT);
188                                }
189                                if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) {
190                                        unset($info['md5_data_source']);
191                                }
192                        }
193                }
194
195                if (isset($info['flac']['STREAMINFO']['bits_per_sample'])) {
196                        $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
197                        if ($info['audio']['bits_per_sample'] == 8) {
198                                // special case
199                                // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value
200                                // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed
201                                $this->warning('FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file');
202                        }
203                }
204
205                return true;
206        }
207
208
209        /**
210         * @param string $BlockData
211         *
212         * @return array
213         */
214        public static function parseSTREAMINFOdata($BlockData) {
215                $streaminfo = array();
216                $streaminfo['min_block_size']  = getid3_lib::BigEndian2Int(substr($BlockData, 0, 2));
217                $streaminfo['max_block_size']  = getid3_lib::BigEndian2Int(substr($BlockData, 2, 2));
218                $streaminfo['min_frame_size']  = getid3_lib::BigEndian2Int(substr($BlockData, 4, 3));
219                $streaminfo['max_frame_size']  = getid3_lib::BigEndian2Int(substr($BlockData, 7, 3));
220
221                $SRCSBSS                       = getid3_lib::BigEndian2Bin(substr($BlockData, 10, 8));
222                $streaminfo['sample_rate']     = getid3_lib::Bin2Dec(substr($SRCSBSS,  0, 20));
223                $streaminfo['channels']        = getid3_lib::Bin2Dec(substr($SRCSBSS, 20,  3)) + 1;
224                $streaminfo['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 23,  5)) + 1;
225                $streaminfo['samples_stream']  = getid3_lib::Bin2Dec(substr($SRCSBSS, 28, 36));
226
227                $streaminfo['audio_signature'] =                           substr($BlockData, 18, 16);
228
229                return $streaminfo;
230        }
231
232        /**
233         * @param string $BlockData
234         *
235         * @return bool
236         */
237        private function parseSTREAMINFO($BlockData) {
238                $info = &$this->getid3->info;
239
240                $info['flac']['STREAMINFO'] = self::parseSTREAMINFOdata($BlockData);
241
242                if (!empty($info['flac']['STREAMINFO']['sample_rate'])) {
243
244                        $info['audio']['bitrate_mode']    = 'vbr';
245                        $info['audio']['sample_rate']     = $info['flac']['STREAMINFO']['sample_rate'];
246                        $info['audio']['channels']        = $info['flac']['STREAMINFO']['channels'];
247                        $info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
248                        $info['playtime_seconds']         = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate'];
249                        if ($info['playtime_seconds'] > 0) {
250                                if (!$this->isDependencyFor('matroska')) {
251                                        $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
252                                }
253                                else {
254                                        $this->warning('Cannot determine audio bitrate because total stream size is unknown');
255                                }
256                        }
257
258                } else {
259                        return $this->error('Corrupt METAdata block: STREAMINFO');
260                }
261
262                return true;
263        }
264
265        /**
266         * @param string $BlockData
267         *
268         * @return bool
269         */
270        private function parseAPPLICATION($BlockData) {
271                $info = &$this->getid3->info;
272
273                $ApplicationID = getid3_lib::BigEndian2Int(substr($BlockData, 0, 4));
274                $info['flac']['APPLICATION'][$ApplicationID]['name'] = self::applicationIDLookup($ApplicationID);
275                $info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($BlockData, 4);
276
277                return true;
278        }
279
280        /**
281         * @param string $BlockData
282         *
283         * @return bool
284         */
285        private function parseSEEKTABLE($BlockData) {
286                $info = &$this->getid3->info;
287
288                $offset = 0;
289                $BlockLength = strlen($BlockData);
290                $placeholderpattern = str_repeat("\xFF", 8);
291                while ($offset < $BlockLength) {
292                        $SampleNumberString = substr($BlockData, $offset, 8);
293                        $offset += 8;
294                        if ($SampleNumberString == $placeholderpattern) {
295
296                                // placeholder point
297                                getid3_lib::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1);
298                                $offset += 10;
299
300                        } else {
301
302                                $SampleNumber                                        = getid3_lib::BigEndian2Int($SampleNumberString);
303                                $info['flac']['SEEKTABLE'][$SampleNumber]['offset']  = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
304                                $offset += 8;
305                                $info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 2));
306                                $offset += 2;
307
308                        }
309                }
310
311                return true;
312        }
313
314        /**
315         * @param string $BlockData
316         *
317         * @return bool
318         */
319        private function parseVORBIS_COMMENT($BlockData) {
320                $info = &$this->getid3->info;
321
322                $getid3_ogg = new getid3_ogg($this->getid3);
323                if ($this->isDependencyFor('matroska')) {
324                        $getid3_ogg->setStringMode($this->data_string);
325                }
326                $getid3_ogg->ParseVorbisComments();
327                if (isset($info['ogg'])) {
328                        unset($info['ogg']['comments_raw']);
329                        $info['flac']['VORBIS_COMMENT'] = $info['ogg'];
330                        unset($info['ogg']);
331                }
332
333                unset($getid3_ogg);
334
335                return true;
336        }
337
338        /**
339         * @param string $BlockData
340         *
341         * @return bool
342         */
343        private function parseCUESHEET($BlockData) {
344                $info = &$this->getid3->info;
345                $offset = 0;
346                $info['flac']['CUESHEET']['media_catalog_number'] =                              trim(substr($BlockData, $offset, 128), "\0");
347                $offset += 128;
348                $info['flac']['CUESHEET']['lead_in_samples']      =         getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
349                $offset += 8;
350                $info['flac']['CUESHEET']['flags']['is_cd']       = (bool) (getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)) & 0x80);
351                $offset += 1;
352
353                $offset += 258; // reserved
354
355                $info['flac']['CUESHEET']['number_tracks']        =         getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
356                $offset += 1;
357
358                for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) {
359                        $TrackSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
360                        $offset += 8;
361                        $TrackNumber       = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
362                        $offset += 1;
363
364                        $info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset']         = $TrackSampleOffset;
365
366                        $info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc']                  =                           substr($BlockData, $offset, 12);
367                        $offset += 12;
368
369                        $TrackFlagsRaw                                                             = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
370                        $offset += 1;
371                        $info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio']     = (bool) ($TrackFlagsRaw & 0x80);
372                        $info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40);
373
374                        $offset += 13; // reserved
375
376                        $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']          = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
377                        $offset += 1;
378
379                        for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) {
380                                $IndexSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
381                                $offset += 8;
382                                $IndexNumber       = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
383                                $offset += 1;
384
385                                $offset += 3; // reserved
386
387                                $info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset;
388                        }
389                }
390
391                return true;
392        }
393
394        /**
395         * Parse METADATA_BLOCK_PICTURE flac structure and extract attachment
396         * External usage: audio.ogg
397         *
398         * @return bool
399         */
400        public function parsePICTURE() {
401                $info = &$this->getid3->info;
402
403                $picture['typeid']         = getid3_lib::BigEndian2Int($this->fread(4));
404                $picture['picturetype']    = self::pictureTypeLookup($picture['typeid']);
405                $picture['image_mime']     = $this->fread(getid3_lib::BigEndian2Int($this->fread(4)));
406                $descr_length              = getid3_lib::BigEndian2Int($this->fread(4));
407                if ($descr_length) {
408                        $picture['description'] = $this->fread($descr_length);
409                }
410                $picture['image_width']    = getid3_lib::BigEndian2Int($this->fread(4));
411                $picture['image_height']   = getid3_lib::BigEndian2Int($this->fread(4));
412                $picture['color_depth']    = getid3_lib::BigEndian2Int($this->fread(4));
413                $picture['colors_indexed'] = getid3_lib::BigEndian2Int($this->fread(4));
414                $picture['datalength']     = getid3_lib::BigEndian2Int($this->fread(4));
415
416                if ($picture['image_mime'] == '-->') {
417                        $picture['data'] = $this->fread($picture['datalength']);
418                } else {
419                        $picture['data'] = $this->saveAttachment(
420                                str_replace('/', '_', $picture['picturetype']).'_'.$this->ftell(),
421                                $this->ftell(),
422                                $picture['datalength'],
423                                $picture['image_mime']);
424                }
425
426                $info['flac']['PICTURE'][] = $picture;
427
428                return true;
429        }
430
431        /**
432         * @param int $blocktype
433         *
434         * @return string
435         */
436        public static function metaBlockTypeLookup($blocktype) {
437                static $lookup = array(
438                        0 => 'STREAMINFO',
439                        1 => 'PADDING',
440                        2 => 'APPLICATION',
441                        3 => 'SEEKTABLE',
442                        4 => 'VORBIS_COMMENT',
443                        5 => 'CUESHEET',
444                        6 => 'PICTURE',
445                );
446                return (isset($lookup[$blocktype]) ? $lookup[$blocktype] : 'reserved');
447        }
448
449        /**
450         * @param int $applicationid
451         *
452         * @return string
453         */
454        public static function applicationIDLookup($applicationid) {
455                // http://flac.sourceforge.net/id.html
456                static $lookup = array(
457                        0x41544348 => 'FlacFile',                                                                           // "ATCH"
458                        0x42534F4C => 'beSolo',                                                                             // "BSOL"
459                        0x42554753 => 'Bugs Player',                                                                        // "BUGS"
460                        0x43756573 => 'GoldWave cue points (specification)',                                                // "Cues"
461                        0x46696361 => 'CUE Splitter',                                                                       // "Fica"
462                        0x46746F6C => 'flac-tools',                                                                         // "Ftol"
463                        0x4D4F5442 => 'MOTB MetaCzar',                                                                      // "MOTB"
464                        0x4D505345 => 'MP3 Stream Editor',                                                                  // "MPSE"
465                        0x4D754D4C => 'MusicML: Music Metadata Language',                                                   // "MuML"
466                        0x52494646 => 'Sound Devices RIFF chunk storage',                                                   // "RIFF"
467                        0x5346464C => 'Sound Font FLAC',                                                                    // "SFFL"
468                        0x534F4E59 => 'Sony Creative Software',                                                             // "SONY"
469                        0x5351455A => 'flacsqueeze',                                                                        // "SQEZ"
470                        0x54745776 => 'TwistedWave',                                                                        // "TtWv"
471                        0x55495453 => 'UITS Embedding tools',                                                               // "UITS"
472                        0x61696666 => 'FLAC AIFF chunk storage',                                                            // "aiff"
473                        0x696D6167 => 'flac-image application for storing arbitrary files in APPLICATION metadata blocks',  // "imag"
474                        0x7065656D => 'Parseable Embedded Extensible Metadata (specification)',                             // "peem"
475                        0x71667374 => 'QFLAC Studio',                                                                       // "qfst"
476                        0x72696666 => 'FLAC RIFF chunk storage',                                                            // "riff"
477                        0x74756E65 => 'TagTuner',                                                                           // "tune"
478                        0x78626174 => 'XBAT',                                                                               // "xbat"
479                        0x786D6364 => 'xmcd',                                                                               // "xmcd"
480                );
481                return (isset($lookup[$applicationid]) ? $lookup[$applicationid] : 'reserved');
482        }
483
484        /**
485         * @param int $type_id
486         *
487         * @return string
488         */
489        public static function pictureTypeLookup($type_id) {
490                static $lookup = array (
491                         0 => 'Other',
492                         1 => '32x32 pixels \'file icon\' (PNG only)',
493                         2 => 'Other file icon',
494                         3 => 'Cover (front)',
495                         4 => 'Cover (back)',
496                         5 => 'Leaflet page',
497                         6 => 'Media (e.g. label side of CD)',
498                         7 => 'Lead artist/lead performer/soloist',
499                         8 => 'Artist/performer',
500                         9 => 'Conductor',
501                        10 => 'Band/Orchestra',
502                        11 => 'Composer',
503                        12 => 'Lyricist/text writer',
504                        13 => 'Recording Location',
505                        14 => 'During recording',
506                        15 => 'During performance',
507                        16 => 'Movie/video screen capture',
508                        17 => 'A bright coloured fish',
509                        18 => 'Illustration',
510                        19 => 'Band/artist logotype',
511                        20 => 'Publisher/Studio logotype',
512                );
513                return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved');
514        }
515
516}
Note: See TracBrowser for help on using the repository browser.