Ability to scan remote files (downloads the whole file; #19) and checking file before scanning

This commit is contained in:
wapmorgan 2021-08-12 03:38:40 +03:00
parent 9e9e29fc3f
commit b53a5e9368
2 changed files with 79 additions and 35 deletions

View File

@ -61,8 +61,8 @@ class Mp3InfoConsoleRunner {
echo sprintf($this->songRowTempalte, 'File name', 'dur.', 'bitrate', 'sample', 'song', 'artist', 'track', echo sprintf($this->songRowTempalte, 'File name', 'dur.', 'bitrate', 'sample', 'song', 'artist', 'track',
'time').PHP_EOL; 'time').PHP_EOL;
foreach ($fileNames as $file) { foreach ($fileNames as $originalFile) {
$file = realpath($file); $file = realpath($originalFile);
if (is_dir($file)) { if (is_dir($file)) {
echo $file.':'.PHP_EOL; echo $file.':'.PHP_EOL;
foreach (glob(rtrim($file, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'*.mp3') as $f) { foreach (glob(rtrim($file, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'*.mp3') as $f) {
@ -74,6 +74,8 @@ class Mp3InfoConsoleRunner {
} else if (is_file($file)) { } else if (is_file($file)) {
$this->analyze($file, true, $verbose); $this->analyze($file, true, $verbose);
if ($this->compareWithId3) $this->analyzeId3($file); 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 * @return null|void
*/ */
protected function analyze($filename, $id3v2 = false, $verbose = false) { protected function analyze($filename, $id3v2 = false, $verbose = false) {
if (!is_readable($filename)) return; if (!is_readable($filename) && strpos($filename, '://') === false) return;
try { try {
$audio = new Mp3Info($filename, true); $audio = new Mp3Info($filename, true);
} catch (Exception $e) { } catch (Exception $e) {
var_dump("Exception when parsing ".$filename.': '.$e->getMessage()); echo "Exception when parsing ".$filename.': '.$e->getMessage().PHP_EOL;
return null; return null;
} }

View File

@ -212,11 +212,23 @@ class Mp3Info {
if (self::$_sampleRateTable === null) if (self::$_sampleRateTable === null)
self::$_sampleRateTable = require dirname(__FILE__).'/../data/sampleRateTable.php'; self::$_sampleRateTable = require dirname(__FILE__).'/../data/sampleRateTable.php';
if (!file_exists($filename)) $this->_fileName = $filename;
throw new \Exception('File '.$filename.' is not present!');
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; $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] * 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. * 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] * ID3V1 TAG - provides a few of meta data. [optional]
* @param $filename * @param string $filename
* @param $fileSize * @param int $fileSize
* @param $mode * @param int $mode
* @return float|int * @return float|int
* @throws \Exception * @throws \Exception
*/ */
private function parseAudio($filename, $fileSize, $mode) { private function parseAudio($filename, $fileSize, $mode) {
$time = microtime(true); $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) */ /** @var int Size of audio data (exclude tags size) */
$audioSize = $fileSize; $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 $frameSize
* @param $raw * @param $raw
@ -1028,4 +1024,49 @@ class Mp3Info {
: $this->tags1[$tag]; : $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
}
} }