diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..65034a1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +vendor +*.mp3 diff --git a/README.md b/README.md index 8a33a26..354986b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Mp3Info The fastest PHP library to get mp3 tags&meta. -[![Composer package](http://composer.network/badge/wapmorgan/mp3info)](https://packagist.org/packages/wapmorgan/mp3info) [![Latest Stable Version](https://poser.pugx.org/wapmorgan/mp3info/v/stable)](https://packagist.org/packages/wapmorgan/mp3info) [![Total Downloads](https://poser.pugx.org/wapmorgan/mp3info/downloads)](https://packagist.org/packages/wapmorgan/mp3info) [![Latest Unstable Version](https://poser.pugx.org/wapmorgan/mp3info/v/unstable)](https://packagist.org/packages/wapmorgan/mp3info) @@ -9,15 +8,15 @@ The fastest PHP library to get mp3 tags&meta. This class extracts information from mpeg/mp3 audio: -| Audio | id3v1 Tags | id3v2 Tags | -|--------------|------------|------------| -| duration | song | TIT2 | -| bitRate | artist | TPE1 | -| sampleRate | album | TALB | -| channel | year | TYER | -| framesCount | comment | COMM | -| codecVersion | track | TRCK | -| layerVersion | genre | TCON | +| Audio | id3v1 & id3v2 Tags | +|--------------|--------------------| +| duration | song (TIT2) | +| bitRate | artist (TPE1) | +| sampleRate | album (TALB) | +| channel | year (TYER) | +| framesCount | comment (COMM) | +| codecVersion | track (TRCK) | +| layerVersion | genre (TCON) | 1. Usage 2. Performance @@ -31,13 +30,14 @@ This class extracts information from mpeg/mp3 audio: # Usage After creating an instance of `Mp3Info` with passing filename as the first argument to the constructor, you can retrieve data from object properties (listed below). -If you need parse tags, you should set 2nd argument this way: ```php use wapmorgan\Mp3Info\Mp3Info; -$audio = new Mp3Info($fileName, true); -// or omit 2nd argument to increase parsing speed -$audio = new Mp3Info($fileName); +// To get basic audio information +$audio = new Mp3Info('./audio.mp3'); + +// If you need parse tags, you should set 2nd argument this way: +$audio = new Mp3Info('./audio.mp3', true); ``` And after that access object properties to get audio information: @@ -60,7 +60,6 @@ echo 'Song '.$audio->tags1['song'].' from '.$audio->tags1['artist'].PHP_EOL; * List of 112 files with constant & variable bitRate with total duration 5:22:28 are parsed in 1.76 sec. *getId3* library against exactly the same mp3 list works for 8x-10x slower - 9.9 sec. * If you want, there's a very easy way to compare. Just install `nass600/get-id3` package and run console scanner against any folder with audios. It will print time that Mp3Info spent and that getId3. - # Console scanner To test Mp3Info you can use built-in script that scans dirs and analyzes all mp3-files inside them. To launch script against current folder: diff --git a/composer.json b/composer.json index e0ca406..781b7e4 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ } }, "require": { + "php": ">=5.4.0", "ext-mbstring": "*" }, "require-dev": { diff --git a/data/bitRateTable.php b/data/bitRateTable.php index 9673c62..8101334 100644 --- a/data/bitRateTable.php +++ b/data/bitRateTable.php @@ -1,13 +1,18 @@ array( - 1 => array(null, 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000, false), // MPEG 1 layer 1 - 2 => array(null, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000, false), // MPEG 1 layer 2 - 3 => array(null, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, false), // MPEG 1 layer 3 - ), - 2 => array( - 1 => array(null, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000, false), // MPEG 2 layer 1 - 2 => array(null, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, false), // MPEG 2 layer 2 - 3 => array(null, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, false), // MPEG 2 layer 3 - ), -); +use wapmorgan\Mp3Info\Mp3Info; + +$data = [ + Mp3Info::MPEG_1 => [ + 1 => [null, 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000, false], // MPEG 1 layer 1 + 2 => [null, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000, false], // MPEG 1 layer 2 + 3 => [null, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, false], // MPEG 1 layer 3 + ], + Mp3Info::MPEG_2 => [ + 1 => [null, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000, false], // MPEG 2 layer 1 + 2 => [null, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, false], // MPEG 2 layer 2 + 3 => [null, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, false], // MPEG 2 layer 3 + ], +]; + +$data[Mp3Info::MPEG_25] = $data[Mp3Info::MPEG_2]; +return $data; diff --git a/data/sampleRateTable.php b/data/sampleRateTable.php index 30d67e2..6bccee3 100644 --- a/data/sampleRateTable.php +++ b/data/sampleRateTable.php @@ -1,5 +1,8 @@ array(44100, 48000, 32000, false), // MPEG 1 - 2 => array(22050, 24000, 16000, false), // MPEG 2 -); +use wapmorgan\Mp3Info\Mp3Info; + +return [ + Mp3Info::MPEG_1 => [44100, 48000, 32000, false], // MPEG 1 + Mp3Info::MPEG_2 => [22050, 24000, 16000, false], // MPEG 2 + Mp3Info::MPEG_25 => [11025, 12000, 8000, false], // MPEG 2 +]; diff --git a/src/Mp3Info.php b/src/Mp3Info.php index 64f6710..e0e2ece 100644 --- a/src/Mp3Info.php +++ b/src/Mp3Info.php @@ -35,25 +35,22 @@ class Mp3Info { const LAYERS_23_FRAME_SIZE = 1152; const META = 1; - const TAGS = 2; - const MPEG_1 = 1; + const MPEG_1 = 1; const MPEG_2 = 2; + const MPEG_25 = 3; + const CODEC_UNDEFINED = 4; + const LAYER_1 = 1; const LAYER_2 = 2; - const LAYER_3 = 3; + const STEREO = 'stereo'; const JOINT_STEREO = 'joint_stereo'; const DUAL_MONO = 'dual_mono'; - const MONO = 'mono'; - /** - * Boolean trigger to enable / disable trace output - */ - static public $traceOutput = false; /** * @var array */ @@ -63,6 +60,7 @@ class Mp3Info { * @var array */ static private $_sampleRateTable; + /** * MPEG codec version (1 or 2) * @var int @@ -79,60 +77,72 @@ class Mp3Info { * @var int */ public $audioSize; + /** * Contains audio file name * @var string */ public $_fileName; + /** * Contains file size * @var int */ public $_fileSize; + /** * Audio duration in seconds.microseconds (e.g. 3603.0171428571) * @var float */ public $duration; + /** * Audio bit rate in bps (e.g. 128000) */ public $bitRate; + /** * Audio sample rate in Hz (e.g. 44100) * @var int */ public $sampleRate; + /** * Contains true if audio has variable bit rate * @var boolean */ public $isVbr = false; + /** * Channel mode (stereo or dual_mono or joint_stereo or mono) * @var string */ public $channel; + /** * Number of audio frames in file * @var int */ public $framesCount = 0; + /** * Contains extra flags * @var array */ - public $extraFlags = array(); + public $extraFlags = []; + /** * Audio tags ver. 1 (aka id3v1) * @var array */ - public $tags1 = array(); + public $tags1 = []; + /** * Audio tags ver. 2 (aka id3v2) * @var array */ - public $tags2 = array(); + public $tags2 = []; + /** * Major version of id3v2 tag (if id3v2 present) (2 or 3 or 4) * @var int @@ -147,12 +157,12 @@ class Mp3Info { * List of id3v2 header flags (if id3v2 present) * @var array */ - public $id3v2Flags = array(); + public $id3v2Flags = []; /** * List of id3v2 tags flags (if id3v2 present) * @var array */ - public $id3v2TagsFlags = array(); + public $id3v2TagsFlags = []; /** * Contains time spent to read&extract audio information. @@ -234,7 +244,7 @@ class Mp3Info { * First frame can lie. Need to fix in future. * @link https://github.com/wapmorgan/Mp3Info/issues/13#issuecomment-447470813 */ - $framesCount = $this->readFirstFrame($fp); + $framesCount = $this->readMpegFrame($fp); $this->framesCount = $framesCount !== null ? $framesCount @@ -264,7 +274,7 @@ class Mp3Info { * @return int Number of frames (if present if first frame) * @throws \Exception */ - private function readFirstFrame($fp) { + private function readMpegFrame($fp) { $pos = ftell($fp); $headerBytes = $this->readBytes($fp, 4); @@ -282,9 +292,11 @@ class Mp3Info { } while (ftell($fp) < $limit_pos); } - if ($headerBytes[0] !== 0xFF || (($headerBytes[1] >> 5) & 0b111) != 0b111) throw new \Exception("At 0x".$pos."(".dechex($pos).") should be the first frame header!"); + if ($headerBytes[0] !== 0xFF || (($headerBytes[1] >> 5) & 0b111) != 0b111) throw new \Exception("At 0x".$pos."(".dechex($pos).") should be a frame header!"); switch ($headerBytes[1] >> 3 & 0b11) { + case 0b00: $this->codecVersion = self::MPEG_25; break; + case 0b01: $this->codecVersion = self::CODEC_UNDEFINED; break; case 0b10: $this->codecVersion = self::MPEG_2; break; case 0b11: $this->codecVersion = self::MPEG_1; break; } @@ -310,6 +322,7 @@ class Mp3Info { case '2stereo': $offset = 21; break; case '2mono': $offset = 13; break; } + fseek($fp, $pos + $offset); if (fread($fp, 4) == self::VBR_SYNC) { $this->isVbr = true; @@ -320,12 +333,14 @@ class Mp3Info { $this->extraFlags['VBR'] = (bool)($flagsBytes[3] & 8); if ($this->extraFlags['frames']) $framesCount = implode(null, unpack('N', fread($fp, 4))); } + // go to the end of frame if ($this->layerVersion == 1) { $this->__cbrFrameSize = floor((12 * $this->bitRate / $this->sampleRate + ($headerBytes[2] >> 1 & 0b1)) * 4); } else { $this->__cbrFrameSize = floor(144 * $this->bitRate / $this->sampleRate + ($headerBytes[2] >> 1 & 0b1)); } + fseek($fp, $pos + $this->__cbrFrameSize); return isset($framesCount) ? $framesCount : null; @@ -653,7 +668,7 @@ class Mp3Info { * @return boolean True if file is looks correct, False otherwise. * @throws \Exception */ - static public function isValidAudio($filename) { + public static function isValidAudio($filename) { if (!file_exists($filename)) throw new Exception('File '.$filename.' is not present!'); $raw = file_get_contents($filename, false, null, 0, 3); @@ -664,7 +679,7 @@ class Mp3Info { * @param $frameSize * @param $raw * - * @return array + * @return string */ private function handleTextFrame($frameSize, $raw) {