From 0ed1ec7caa3d3543a2456c4f1b75e2ac24b7ba47 Mon Sep 17 00:00:00 2001 From: wapmorgan Date: Mon, 19 Mar 2018 22:47:12 +0300 Subject: [PATCH] Fix scanning mp3-files with padding before audio data --- bin/scan | 3 ++- src/Mp3Info.php | 38 +++++++++++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/bin/scan b/bin/scan index 60f10c6..b25e2bc 100755 --- a/bin/scan +++ b/bin/scan @@ -23,8 +23,9 @@ function substrIfLonger($string, $maxLength) { function analyze($filename, &$total_duration, &$total_parse_time, $id3v2 = false) { if (!is_readable($filename)) return; try { - $audio = new Mp3Info($filename, true); + $audio = new Mp3Info($filename, true); } catch (Exception $e) { + var_dump($filename.': '.$e->getMessage()); return null; } echo sprintf('%15s | %4s | %7s | %0.1fkHz | %-11s | %-10s | %5d | %.5f', substrIfLonger(basename($filename), 15), formatTime($audio->duration), $audio->isVbr ? 'vbr' : ($audio->bitRate / 1000).'kbps', ($audio->sampleRate / 1000), isset($audio->tags1['song']) ? substrIfLonger($audio->tags1['song'], 11) : null, isset($audio->tags1['artist']) ? substrIfLonger($audio->tags1['artist'], 10) : null, isset($audio->tags1['track']) ? substrIfLonger($audio->tags1['track'], 5) : null, $audio->_parsingTime).PHP_EOL; diff --git a/src/Mp3Info.php b/src/Mp3Info.php index 6d4861f..fb909a6 100644 --- a/src/Mp3Info.php +++ b/src/Mp3Info.php @@ -66,7 +66,7 @@ class Mp3Info { /** * Audio size in bytes. Note that this value is NOT equals file size. - * @var int|long + * @var int */ public $audioSize; /** @@ -76,7 +76,7 @@ class Mp3Info { public $_fileName; /** * Contains file size - * @var long + * @var int */ public $_fileSize; /** @@ -157,6 +157,7 @@ class Mp3Info { /** * $mode is self::META, self::TAGS or their combination. + * @throws \Exception */ public function __construct($filename, $parseTags = false) { if (is_null(self::$_bitRateTable)) self::$_bitRateTable = require dirname(__FILE__).'/../data/bitRateTable.php'; @@ -174,14 +175,19 @@ class Mp3Info { * ID3V2 TAG - provides a lot of meta data. [optional] * MPEG AUDIO FRAMES - contains audio data. A frame consists of a frame header and a frame data. The first frame may contain extra information about mp3 (marked with "Xing" or "Info" string). Rest of frames can contain only audio data. * ID3V1 TAG - provides a few of meta data. [optional] + * @param $filename + * @param $fileSize + * @param $mode + * @return float|int + * @throws \Exception */ - private function parseAudio($filename, $filesize, $mode) { + private function parseAudio($filename, $fileSize, $mode) { $time = microtime(true); $fp = fopen($filename, "rb"); /** Size of audio data (exclude tags size) * @var int */ - $audioSize = $filesize; + $audioSize = $fileSize; // parse tags if (fread($fp, 3) == self::TAG2_SYNC) { @@ -197,7 +203,7 @@ class Mp3Info { $audioSize -= ($id3v2Size = $size); } } - fseek($fp, $filesize - 128); + fseek($fp, $fileSize - 128); if (fread($fp, 3) == self::TAG1_SYNC) { if ($mode & self::TAGS) $audioSize -= $this->readId3v1Body($fp); else $audioSize -= 128; @@ -227,11 +233,28 @@ class Mp3Info { /** * Read first frame information. + * @param resource $fp * @return int Number of frames (if present if first frame) + * @throws \Exception */ private function readFirstFrame($fp) { $pos = ftell($fp); $headerBytes = $this->readBytes($fp, 4); + + // if bytes are null, search for something else 1024 bytes forward + if (array_unique($headerBytes) === [0]) { + $limit_pos = $pos + 2048; + do { + $pos = ftell($fp); + $bytes = $this->readBytes($fp, 1); + if ($bytes[0] !== 0) { + fseek($fp, $pos); + $headerBytes = $this->readBytes($fp, 4); + break; + } + } while (ftell($fp) < $limit_pos); + } + if (($headerBytes[0] & 0xFF) != 0xFF || (($headerBytes[1] >> 5) & 0b111) != 0b111) throw new \Exception("At ".$pos."(".dechex($pos).") should be the first frame header!"); switch ($headerBytes[1] >> 3 & 0b11) { @@ -349,7 +372,9 @@ class Mp3Info { * b - Extended header * c - Experimental indicator * d - Footer present + * @param resource $fp * @return int Returns length of id3v2 tag. + * @throws \Exception */ private function readId3v2Body($fp) { // read the rest of the id3v2 header @@ -377,11 +402,14 @@ class Mp3Info { {} } $size = substr($data, 8, 32); + // some fucking shit + // getting only 7 of 8 bits of size bytes $sizes = str_split($size, 8); array_walk($sizes, function (&$value) { $value = substr($value, 1);}); $size = implode("", $sizes); $size = bindec($size); + if ($this->id3v2MajorVersion == 2) // parse id3v2.2.0 body /*throw new \Exception('NEED TO PARSE id3v2.2.0 flags!');*/ {}