diff --git a/bin/mp3scan b/bin/mp3scan index 07a88b6..7777e90 100755 --- a/bin/mp3scan +++ b/bin/mp3scan @@ -61,8 +61,8 @@ class Mp3InfoConsoleRunner { echo sprintf($this->songRowTempalte, 'File name', 'dur.', 'bitrate', 'sample', 'song', 'artist', 'track', 'time').PHP_EOL; - foreach ($fileNames as $file) { - $file = realpath($file); + foreach ($fileNames as $originalFile) { + $file = realpath($originalFile); if (is_dir($file)) { echo $file.':'.PHP_EOL; foreach (glob(rtrim($file, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'*.mp3') as $f) { @@ -74,6 +74,8 @@ class Mp3InfoConsoleRunner { } else if (is_file($file)) { $this->analyze($file, true, $verbose); if ($this->compareWithId3) $this->analyzeId3($file); + } else if (strpos($originalFile, '://') !== false) { + $this->analyze($originalFile, true, $verbose); } } @@ -135,11 +137,12 @@ class Mp3InfoConsoleRunner { * @return null|void */ protected function analyze($filename, $id3v2 = false, $verbose = false) { - if (!is_readable($filename)) return; + if (!is_readable($filename) && strpos($filename, '://') === false) return; + try { $audio = new Mp3Info($filename, true); } catch (Exception $e) { - var_dump("Exception when parsing ".$filename.': '.$e->getMessage()); + echo "Exception when parsing ".$filename.': '.$e->getMessage().PHP_EOL; return null; } diff --git a/src/Mp3Info.php b/src/Mp3Info.php index 7e1d870..f19c349 100644 --- a/src/Mp3Info.php +++ b/src/Mp3Info.php @@ -212,11 +212,23 @@ class Mp3Info { if (self::$_sampleRateTable === null) self::$_sampleRateTable = require dirname(__FILE__).'/../data/sampleRateTable.php'; - if (!file_exists($filename)) - throw new \Exception('File '.$filename.' is not present!'); + $this->_fileName = $filename; + + if (strpos($filename, '://') !== false) { + $this->_fileSize = static::getUrlContentLength($filename); + } else { + if (!file_exists($filename)) { + throw new \Exception('File ' . $filename . ' is not present!'); + } + $this->_fileSize = filesize($filename); + } + + if (!static::isValidAudio($filename)) { + throw new \Exception('File ' . $filename . ' is not mpeg/audio!'); + } $mode = $parseTags ? self::META | self::TAGS : self::META; - $this->audioSize = $this->parseAudio($this->_fileName = $filename, $this->_fileSize = filesize($filename), $mode); + $this->audioSize = $this->parseAudio($this->_fileName, $this->_fileSize, $mode); } /** @@ -241,15 +253,23 @@ 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 + * @param string $filename + * @param int $fileSize + * @param int $mode * @return float|int * @throws \Exception */ private function parseAudio($filename, $fileSize, $mode) { $time = microtime(true); - $fp = fopen($filename, 'rb'); + + // create temp storage for media + if (strpos($filename, '://') !== false) { + $fp = fopen('php://memory', 'rwb'); + fwrite($fp, file_get_contents($filename)); + rewind($fp); + } else { + $fp = fopen($filename, 'rb'); + } /** @var int Size of audio data (exclude tags size) */ $audioSize = $fileSize; @@ -937,30 +957,6 @@ class Mp3Info { } } - /** - * Simple function that checks mpeg-audio correctness of given file. - * Actually it checks that first 3 bytes of file is a id3v2 tag mark or - * that first 11 bits of file is a frame header sync mark or that 3 bytes on -128 position of file is id3v1 tag. - * To perform full test create an instance of Mp3Info with given file. - * - * @param string $filename File to be tested. - * @return boolean True if file looks that correct mpeg audio, False otherwise. - * @throws \Exception - */ - 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); - return $raw == self::TAG2_SYNC // id3v2 tag - || (self::FRAME_SYNC == (unpack('n*', $raw)[1] & self::FRAME_SYNC)) // mpeg header tag - || ( - filesize($filename) > 128 - && file_get_contents($filename, false, null, -128, 3) === self::TAG1_SYNC - ) // id3v1 tag - ; - } - /** * @param $frameSize * @param $raw @@ -1028,4 +1024,49 @@ class Mp3Info { : $this->tags1[$tag]; } } + + /** + * Simple function that checks mpeg-audio correctness of given file. + * Actually it checks that first 3 bytes of file is a id3v2 tag mark or + * that first 11 bits of file is a frame header sync mark or that 3 bytes on -128 position of file is id3v1 tag. + * To perform full test create an instance of Mp3Info with given file. + * + * @param string $filename File to be tested. + * @return boolean True if file looks that correct mpeg audio, False otherwise. + * @throws \Exception + */ + public static function isValidAudio($filename) { + if (!file_exists($filename) && strpos($filename, '://')) { + throw new Exception('File ' . $filename . ' is not present!'); + } + + $filesize = file_exists($filename) ? filesize($filename) : static::getUrlContentLength($filename); + + $raw = file_get_contents($filename, false, null, 0, 3); + return $raw === self::TAG2_SYNC // id3v2 tag + || (self::FRAME_SYNC === (unpack('n*', $raw)[1] & self::FRAME_SYNC)) // mpeg header tag + || ( + $filesize > 128 + && file_get_contents($filename, false, null, -128, 3) === self::TAG1_SYNC + ) // id3v1 tag + ; + } + + /** + * @param string $url + * @return int|mixed|string + */ + public static function getUrlContentLength($url) { + $context = stream_context_create(['http' => ['method' => 'HEAD']]); + $head = array_change_key_case(get_headers($url, true, $context)); + // content-length of download (in bytes), read from Content-Length: field + $clen = isset($head['content-length']) ? $head['content-length'] : 0; + + // cannot retrieve file size, return "-1" + if (!$clen) { + return -1; + } + + return $clen; // return size in bytes + } }