From 9049767016f835f57280a13589fcf145fbf6b658 Mon Sep 17 00:00:00 2001 From: wapmorgan Date: Tue, 3 Dec 2019 02:55:12 +0300 Subject: [PATCH] Adjust audio duration in case of VBR with present quality --- bin/mp3scan | 2 +- src/Mp3Info.php | 56 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/bin/mp3scan b/bin/mp3scan index bc09889..96f2c1c 100644 --- a/bin/mp3scan +++ b/bin/mp3scan @@ -131,7 +131,7 @@ class Mp3InfoConsoleRunner { try { $audio = new Mp3Info($filename, true); } catch (Exception $e) { - var_dump($filename.': '.$e->getMessage()); + var_dump("Exception when parsing ".$filename.': '.$e->getMessage()); return null; } diff --git a/src/Mp3Info.php b/src/Mp3Info.php index 4d34695..fd95b22 100644 --- a/src/Mp3Info.php +++ b/src/Mp3Info.php @@ -19,6 +19,7 @@ use Exception; * Used sources: * * {@link http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm mpeg header description} * * {@link http://id3.org/Developer%20Information id3v2 tag specifications}. Specially: {@link http://id3.org/id3v2.3.0 id3v2.3.0}, {@link http://id3.org/id3v2-00 id3v2.2.0}, {@link http://id3.org/id3v2.4.0-changes id3v2.4.0} + * * {@link https://multimedia.cx/mp3extensions.txt Descripion of VBR header "Xing"} * * {@link http://gabriel.mp3-tech.org/mp3infotag.html Xing, Info and Lame tags specifications} */ class Mp3Info { @@ -118,6 +119,12 @@ class Mp3Info { */ public $isVbr = false; + /** + * Contains VBR properties + * @var array + */ + public $vbrProperties = []; + /** * Channel mode (stereo or dual_mono or joint_stereo or mono) * @var string @@ -130,12 +137,6 @@ class Mp3Info { */ public $framesCount = 0; - /** - * Contains extra flags - * @var array - */ - public $extraFlags = []; - /** * Audio tags ver. 1 (aka id3v1) * @var array @@ -188,6 +189,12 @@ class Mp3Info { */ private $__cbrFrameSize; + /** + * Frame size for Variable Bit Rate + * @var int + */ + private $__vbrFrameSize; + /** * $mode is self::META, self::TAGS or their combination. * @@ -259,6 +266,7 @@ class Mp3Info { * @link https://github.com/wapmorgan/Mp3Info/issues/13#issuecomment-447470813 */ $framesCount = $this->readMpegFrame($fp); + $this->framesCount = $framesCount !== null ? $framesCount : ceil($audioSize / $this->__cbrFrameSize); @@ -270,10 +278,13 @@ class Mp3Info { } // The faster way to detect audio duration: + $samples_in_second = $this->layerVersion == 1 ? self::LAYER_1_FRAME_SIZE : self::LAYERS_23_FRAME_SIZE; + // for VBR: adjust samples in second according to VBR quality + if ($this->isVbr && isset($this->vbrProperties['quality'])) { + $samples_in_second = floor($samples_in_second * $this->vbrProperties['quality'] / 100); + } // Calculate total number of audio samples (framesCount * sampleInFrameCount) / samplesInSecondCount - $this->duration = ($this->framesCount - 1) - * ($this->layerVersion == 1 ? self::LAYER_1_FRAME_SIZE : self::LAYERS_23_FRAME_SIZE) - / $this->sampleRate; + $this->duration = ($this->framesCount - 1) * $samples_in_second / $this->sampleRate; } fclose($fp); @@ -284,7 +295,7 @@ class Mp3Info { /** * Read first frame information. * @param resource $fp - * @return int Number of frames (if present if first frame) + * @return int Number of frames (if present if first frame of VBR-file) * @throws \Exception */ private function readMpegFrame($fp) { @@ -332,15 +343,28 @@ class Mp3Info { case '2mono': $offset = 13; break; } + // check for VBR fseek($fp, $pos + $offset); if (fread($fp, 4) == self::VBR_SYNC) { $this->isVbr = true; $flagsBytes = $this->readBytes($fp, 4); - $this->extraFlags['frames'] = (bool)($flagsBytes[3] & 1); - $this->extraFlags['bytes'] = (bool)($flagsBytes[3] & 2); - $this->extraFlags['TOC'] = (bool)($flagsBytes[3] & 4); - $this->extraFlags['VBR'] = (bool)($flagsBytes[3] & 8); - if ($this->extraFlags['frames']) $framesCount = implode(null, unpack('N', fread($fp, 4))); + + // VBR frames count presence + if (($flagsBytes[3] & 2)) { + $this->vbrProperties['frames'] = implode(null, unpack('N', fread($fp, 4))); + } + // VBR stream size presence + if ($flagsBytes[3] & 4) { + $this->vbrProperties['bytes'] = implode(null, unpack('N', fread($fp, 4))); + } + // VBR TOC presence + if ($flagsBytes[3] & 1) { + fseek($fp, 100, SEEK_CUR); + } + // VBR quality + if ($flagsBytes[3] & 8) { + $this->vbrProperties['quality'] = implode(null, unpack('N', fread($fp, 4))); + } } // go to the end of frame @@ -352,7 +376,7 @@ class Mp3Info { fseek($fp, $pos + $this->__cbrFrameSize); - return isset($framesCount) ? $framesCount : null; + return isset($this->vbrProperties['frames']) ? $this->vbrProperties['frames'] : null; } /**