[SPIP] v3.2.1-->v3.2.3
[lhc/web/www.git] / www / plugins-dist / medias / lib / getid3 / module.audio.mp3.php
index ca3ec54..4434380 100644 (file)
@@ -1,11 +1,11 @@
 <?php
+
 /////////////////////////////////////////////////////////////////
 /// getID3() by James Heinrich <info@getid3.org>               //
-//  available at http://getid3.sourceforge.net                 //
-//            or http://www.getid3.org                         //
-//          also https://github.com/JamesHeinrich/getID3       //
-/////////////////////////////////////////////////////////////////
-// See readme.txt for more details                             //
+//  available at https://github.com/JamesHeinrich/getID3       //
+//            or https://www.getid3.org                        //
+//            or http://getid3.sourceforge.net                 //
+//  see readme.txt for more details                            //
 /////////////////////////////////////////////////////////////////
 //                                                             //
 // module.audio.mp3.php                                        //
@@ -24,9 +24,17 @@ define('GETID3_MP3_VALID_CHECK_FRAMES', 35);
 
 class getid3_mp3 extends getid3_handler
 {
-
-       public $allow_bruteforce = false; // forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow, unrecommended, but may provide data from otherwise-unusuable files
-
+       /**
+        * Forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow,
+        * unrecommended, but may provide data from otherwise-unusable files.
+        *
+        * @var bool
+        */
+       public $allow_bruteforce = false;
+
+       /**
+        * @return bool
+        */
        public function Analyze() {
                $info = &$this->getid3->info;
 
@@ -35,7 +43,7 @@ class getid3_mp3 extends getid3_handler
                if (!$this->getOnlyMPEGaudioInfo($info['avdataoffset'])) {
                        if ($this->allow_bruteforce) {
                                $this->error('Rescanning file in BruteForce mode');
-                               $this->getOnlyMPEGaudioInfoBruteForce($this->getid3->fp, $info);
+                               $this->getOnlyMPEGaudioInfoBruteForce();
                        }
                }
 
@@ -152,7 +160,11 @@ class getid3_mp3 extends getid3_handler
 
                // Calculate playtime
                if (!isset($info['playtime_seconds']) && isset($info['audio']['bitrate']) && ($info['audio']['bitrate'] > 0)) {
-                       $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['audio']['bitrate'];
+                       // https://github.com/JamesHeinrich/getID3/issues/161
+                       // VBR header frame contains ~0.026s of silent audio data, but is not actually part of the original encoding and should be ignored
+                       $xingVBRheaderFrameLength = ((isset($info['mpeg']['audio']['VBR_frames']) && isset($info['mpeg']['audio']['framelength'])) ? $info['mpeg']['audio']['framelength'] : 0);
+
+                       $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset'] - $xingVBRheaderFrameLength) * 8 / $info['audio']['bitrate'];
                }
 
                $info['audio']['encoder_options'] = $this->GuessEncoderOptions();
@@ -160,10 +172,14 @@ class getid3_mp3 extends getid3_handler
                return true;
        }
 
-
+       /**
+        * @return string
+        */
        public function GuessEncoderOptions() {
                // shortcuts
                $info = &$this->getid3->info;
+               $thisfile_mpeg_audio = array();
+               $thisfile_mpeg_audio_lame = array();
                if (!empty($info['mpeg']['audio'])) {
                        $thisfile_mpeg_audio = &$info['mpeg']['audio'];
                        if (!empty($thisfile_mpeg_audio['LAME'])) {
@@ -178,7 +194,7 @@ class getid3_mp3 extends getid3_handler
 
                        $encoder_options = 'VBR q'.$thisfile_mpeg_audio['VBR_quality'];
 
-               } elseif (!empty($thisfile_mpeg_audio_lame['preset_used']) && (!in_array($thisfile_mpeg_audio_lame['preset_used_id'], $NamedPresetBitrates))) {
+               } elseif (!empty($thisfile_mpeg_audio_lame['preset_used']) && isset($thisfile_mpeg_audio_lame['preset_used_id']) && (!in_array($thisfile_mpeg_audio_lame['preset_used_id'], $NamedPresetBitrates))) {
 
                        $encoder_options = $thisfile_mpeg_audio_lame['preset_used'];
 
@@ -404,7 +420,15 @@ class getid3_mp3 extends getid3_handler
                return $encoder_options;
        }
 
-
+       /**
+        * @param int   $offset
+        * @param array $info
+        * @param bool  $recursivesearch
+        * @param bool  $ScanAsCBR
+        * @param bool  $FastMPEGheaderScan
+        *
+        * @return bool
+        */
        public function decodeMPEGaudioHeader($offset, &$info, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) {
                static $MPEGaudioVersionLookup;
                static $MPEGaudioLayerLookup;
@@ -437,18 +461,19 @@ class getid3_mp3 extends getid3_handler
                // and $cc... is the audio data
 
                $head4 = substr($headerstring, 0, 4);
+               $head4_key = getid3_lib::PrintHexBytes($head4, true, false, false);
                static $MPEGaudioHeaderDecodeCache = array();
-               if (isset($MPEGaudioHeaderDecodeCache[$head4])) {
-                       $MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4];
+               if (isset($MPEGaudioHeaderDecodeCache[$head4_key])) {
+                       $MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4_key];
                } else {
                        $MPEGheaderRawArray = self::MPEGaudioHeaderDecode($head4);
-                       $MPEGaudioHeaderDecodeCache[$head4] = $MPEGheaderRawArray;
+                       $MPEGaudioHeaderDecodeCache[$head4_key] = $MPEGheaderRawArray;
                }
 
                static $MPEGaudioHeaderValidCache = array();
-               if (!isset($MPEGaudioHeaderValidCache[$head4])) { // Not in cache
-                       //$MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true);  // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1)
-                       $MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false);
+               if (!isset($MPEGaudioHeaderValidCache[$head4_key])) { // Not in cache
+                       //$MPEGaudioHeaderValidCache[$head4_key] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true);  // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1)
+                       $MPEGaudioHeaderValidCache[$head4_key] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false);
                }
 
                // shortcut
@@ -457,8 +482,7 @@ class getid3_mp3 extends getid3_handler
                }
                $thisfile_mpeg_audio = &$info['mpeg']['audio'];
 
-
-               if ($MPEGaudioHeaderValidCache[$head4]) {
+               if ($MPEGaudioHeaderValidCache[$head4_key]) {
                        $thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray;
                } else {
                        $this->error('Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset);
@@ -561,7 +585,7 @@ class getid3_mp3 extends getid3_handler
 
                        $thisfile_mpeg_audio['bitrate_mode'] = 'vbr';
                        $thisfile_mpeg_audio['VBR_method']   = 'Fraunhofer';
-                       $info['audio']['codec']                = 'Fraunhofer';
+                       $info['audio']['codec']              = 'Fraunhofer';
 
                        $SideInfoData = substr($headerstring, 4 + 2, 32);
 
@@ -654,7 +678,7 @@ class getid3_mp3 extends getid3_handler
                                                $used_filesize = $thisfile_mpeg_audio['VBR_bytes'];
                                        } elseif (!empty($info['filesize'])) {
                                                $used_filesize  = $info['filesize'];
-                                               $used_filesize -= intval(@$info['id3v2']['headerlength']);
+                                               $used_filesize -= (isset($info['id3v2']['headerlength']) ? intval($info['id3v2']['headerlength']) : 0);
                                                $used_filesize -= (isset($info['id3v1']) ? 128 : 0);
                                                $used_filesize -= (isset($info['tag_offset_end']) ? $info['tag_offset_end'] - $info['tag_offset_start'] : 0);
                                                $this->warning('MP3.Xing header missing VBR_bytes, assuming MPEG audio portion of file is '.number_format($used_filesize).' bytes');
@@ -1082,6 +1106,13 @@ class getid3_mp3 extends getid3_handler
                return true;
        }
 
+       /**
+        * @param int $offset
+        * @param int $nextframetestoffset
+        * @param bool $ScanAsCBR
+        *
+        * @return bool
+        */
        public function RecursiveFrameScanning(&$offset, &$nextframetestoffset, $ScanAsCBR) {
                $info = &$this->getid3->info;
                $firstframetestarray = array('error' => array(), 'warning'=> array(), 'avdataend' => $info['avdataend'], 'avdataoffset' => $info['avdataoffset']);
@@ -1128,6 +1159,12 @@ class getid3_mp3 extends getid3_handler
                return true;
        }
 
+       /**
+        * @param int  $offset
+        * @param bool $deepscan
+        *
+        * @return int|false
+        */
        public function FreeFormatFrameLength($offset, $deepscan=false) {
                $info = &$this->getid3->info;
 
@@ -1205,6 +1242,9 @@ class getid3_mp3 extends getid3_handler
                return $framelength;
        }
 
+       /**
+        * @return bool
+        */
        public function getOnlyMPEGaudioInfoBruteForce() {
                $MPEGaudioHeaderDecodeCache   = array();
                $MPEGaudioHeaderValidCache    = array();
@@ -1352,7 +1392,12 @@ class getid3_mp3 extends getid3_handler
                return true;
        }
 
-
+       /**
+        * @param int  $avdataoffset
+        * @param bool $BitrateHistogram
+        *
+        * @return bool
+        */
        public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) {
                // looks for synch, decodes MPEG audio header
 
@@ -1416,6 +1461,7 @@ class getid3_mp3 extends getid3_handler
                        }
 
                        if (($header{$SynchSeekOffset} == "\xFF") && ($header{($SynchSeekOffset + 1)} > "\xE0")) { // synch detected
+                               $FirstFrameAVDataOffset = null;
                                if (!isset($FirstFrameThisfileInfo) && !isset($info['mpeg']['audio'])) {
                                        $FirstFrameThisfileInfo = $info;
                                        $FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset;
@@ -1439,7 +1485,7 @@ class getid3_mp3 extends getid3_handler
                                                        $info['audio']['dataformat'] = 'mp3';
                                                        break;
                                        }
-                                       if (isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) {
+                                       if (isset($FirstFrameThisfileInfo) && isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) {
                                                if (!(abs($info['audio']['bitrate'] - $FirstFrameThisfileInfo['audio']['bitrate']) <= 1)) {
                                                        // If there is garbage data between a valid VBR header frame and a sequence
                                                        // of valid MPEG-audio frames the VBR data is no longer discarded.
@@ -1521,7 +1567,7 @@ class getid3_mp3 extends getid3_handler
                                                                }
                                                        }
                                                        $synchstartoffset = $scan_start_offset[$current_segment];
-                                                       while ($this->decodeMPEGaudioHeader($synchstartoffset, $dummy, false, false, $FastMode)) {
+                                                       while (($synchstartoffset < $info['avdataend']) && $this->decodeMPEGaudioHeader($synchstartoffset, $dummy, false, false, $FastMode)) {
                                                                $FastMode = true;
                                                                $thisframebitrate = $MPEGaudioBitrateLookup[$MPEGaudioVersionLookup[$dummy['mpeg']['audio']['raw']['version']]][$MPEGaudioLayerLookup[$dummy['mpeg']['audio']['raw']['layer']]][$dummy['mpeg']['audio']['raw']['bitrate']];
 
@@ -1632,17 +1678,25 @@ class getid3_mp3 extends getid3_handler
                return true;
        }
 
-
+       /**
+        * @return array
+        */
        public static function MPEGaudioVersionArray() {
                static $MPEGaudioVersion = array('2.5', false, '2', '1');
                return $MPEGaudioVersion;
        }
 
+       /**
+        * @return array
+        */
        public static function MPEGaudioLayerArray() {
                static $MPEGaudioLayer = array(false, 3, 2, 1);
                return $MPEGaudioLayer;
        }
 
+       /**
+        * @return array
+        */
        public static function MPEGaudioBitrateArray() {
                static $MPEGaudioBitrate;
                if (empty($MPEGaudioBitrate)) {
@@ -1662,6 +1716,9 @@ class getid3_mp3 extends getid3_handler
                return $MPEGaudioBitrate;
        }
 
+       /**
+        * @return array
+        */
        public static function MPEGaudioFrequencyArray() {
                static $MPEGaudioFrequency;
                if (empty($MPEGaudioFrequency)) {
@@ -1674,11 +1731,17 @@ class getid3_mp3 extends getid3_handler
                return $MPEGaudioFrequency;
        }
 
+       /**
+        * @return array
+        */
        public static function MPEGaudioChannelModeArray() {
                static $MPEGaudioChannelMode = array('stereo', 'joint stereo', 'dual channel', 'mono');
                return $MPEGaudioChannelMode;
        }
 
+       /**
+        * @return array
+        */
        public static function MPEGaudioModeExtensionArray() {
                static $MPEGaudioModeExtension;
                if (empty($MPEGaudioModeExtension)) {
@@ -1691,15 +1754,31 @@ class getid3_mp3 extends getid3_handler
                return $MPEGaudioModeExtension;
        }
 
+       /**
+        * @return array
+        */
        public static function MPEGaudioEmphasisArray() {
                static $MPEGaudioEmphasis = array('none', '50/15ms', false, 'CCIT J.17');
                return $MPEGaudioEmphasis;
        }
 
+       /**
+        * @param string $head4
+        * @param bool   $allowBitrate15
+        *
+        * @return bool
+        */
        public static function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) {
                return self::MPEGaudioHeaderValid(self::MPEGaudioHeaderDecode($head4), false, $allowBitrate15);
        }
 
+       /**
+        * @param array $rawarray
+        * @param bool  $echoerrors
+        * @param bool  $allowBitrate15
+        *
+        * @return bool
+        */
        public static function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) {
                if (($rawarray['synch'] & 0x0FFE) != 0x0FFE) {
                        return false;
@@ -1772,6 +1851,11 @@ class getid3_mp3 extends getid3_handler
                return true;
        }
 
+       /**
+        * @param string $Header4Bytes
+        *
+        * @return array|false
+        */
        public static function MPEGaudioHeaderDecode($Header4Bytes) {
                // AAAA AAAA  AAAB BCCD  EEEE FFGH  IIJJ KLMM
                // A - Frame sync (all bits set)
@@ -1809,6 +1893,15 @@ class getid3_mp3 extends getid3_handler
                return $MPEGrawHeader;
        }
 
+       /**
+        * @param int|string $bitrate
+        * @param string     $version
+        * @param string     $layer
+        * @param bool       $padding
+        * @param int        $samplerate
+        *
+        * @return int|false
+        */
        public static function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) {
                static $AudioFrameLengthCache = array();
 
@@ -1870,6 +1963,11 @@ class getid3_mp3 extends getid3_handler
                return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate];
        }
 
+       /**
+        * @param float|int $bit_rate
+        *
+        * @return int|float|string
+        */
        public static function ClosestStandardMP3Bitrate($bit_rate) {
                static $standard_bit_rates = array (320000, 256000, 224000, 192000, 160000, 128000, 112000, 96000, 80000, 64000, 56000, 48000, 40000, 32000, 24000, 16000, 8000);
                static $bit_rate_table = array (0=>'-');
@@ -1890,10 +1988,16 @@ class getid3_mp3 extends getid3_handler
                return $bit_rate_table[$round_bit_rate];
        }
 
+       /**
+        * @param string $version
+        * @param string $channelmode
+        *
+        * @return int
+        */
        public static function XingVBRidOffset($version, $channelmode) {
                static $XingVBRidOffsetCache = array();
-               if (empty($XingVBRidOffset)) {
-                       $XingVBRidOffset = array (
+               if (empty($XingVBRidOffsetCache)) {
+            $XingVBRidOffsetCache = array (
                                '1'   => array ('mono'          => 0x15, // 4 + 17 = 21
                                                                'stereo'        => 0x24, // 4 + 32 = 36
                                                                'joint stereo'  => 0x24,
@@ -1913,9 +2017,14 @@ class getid3_mp3 extends getid3_handler
                                                           )
                        );
                }
-               return $XingVBRidOffset[$version][$channelmode];
+               return $XingVBRidOffsetCache[$version][$channelmode];
        }
 
+       /**
+        * @param int $VBRmethodID
+        *
+        * @return string
+        */
        public static function LAMEvbrMethodLookup($VBRmethodID) {
                static $LAMEvbrMethodLookup = array(
                        0x00 => 'unknown',
@@ -1932,6 +2041,11 @@ class getid3_mp3 extends getid3_handler
                return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : '');
        }
 
+       /**
+        * @param int $StereoModeID
+        *
+        * @return string
+        */
        public static function LAMEmiscStereoModeLookup($StereoModeID) {
                static $LAMEmiscStereoModeLookup = array(
                        0 => 'mono',
@@ -1946,6 +2060,11 @@ class getid3_mp3 extends getid3_handler
                return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : '');
        }
 
+       /**
+        * @param int $SourceSampleFrequencyID
+        *
+        * @return string
+        */
        public static function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) {
                static $LAMEmiscSourceSampleFrequencyLookup = array(
                        0 => '<= 32 kHz',
@@ -1956,6 +2075,11 @@ class getid3_mp3 extends getid3_handler
                return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : '');
        }
 
+       /**
+        * @param int $SurroundInfoID
+        *
+        * @return string
+        */
        public static function LAMEsurroundInfoLookup($SurroundInfoID) {
                static $LAMEsurroundInfoLookup = array(
                        0 => 'no surround info',
@@ -1966,6 +2090,11 @@ class getid3_mp3 extends getid3_handler
                return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved');
        }
 
+       /**
+        * @param array $LAMEtag
+        *
+        * @return string
+        */
        public static function LAMEpresetUsedLookup($LAMEtag) {
 
                if ($LAMEtag['preset_used_id'] == 0) {