26 Commits

Author SHA1 Message Date
e64c30e569 Turn returned HTTP headers lowercase before parsing
NGINX for some reason returns "content-length" instead of
"Content-Length".
2025-06-15 01:36:12 +01:00
7a8dede10b Move ID3v2 flags parsing into method
Signed-off-by: Markus Birth <markus@birth-online.de>
2025-06-14 22:19:49 +01:00
ad8183b41f Add error handling if block can't be downloaded 2024-05-29 01:28:59 +01:00
aaaba0e2f1 Fix Protection bit inverted meaning 2024-05-29 01:28:43 +01:00
3e1288a68c Added support for informational bits in Mpeg frame header
Copyright, Original, Private, CRC protected, Padding
2024-05-28 01:24:46 +01:00
4b2cbfb384 Cosmetics 2024-05-28 01:23:52 +01:00
2a98464bbe Put id3v1 genre_id in brackets as seen with id3v2 2024-05-27 23:53:08 +01:00
c54e75a899 Show error for invalid LAYER/CODEC versions 2024-05-27 23:46:04 +01:00
9b92776607 Differentiate btw id3v1.0 and id3v1.1 2024-05-27 23:45:35 +01:00
0ef2575001 Fix Mpeg frame verification 2024-05-27 23:44:54 +01:00
5b8e15a1e0 Fix Mpeg frame sync detection 2024-05-27 23:44:22 +01:00
c387953040 Optimise parsing of id3v2 flags 2024-05-27 20:56:20 +01:00
3184973867 Optimise searching for Mpeg frame 2024-05-27 20:55:51 +01:00
2a16b836b7 Return payload size for ID3v2 tags 2024-05-27 20:55:22 +01:00
38a962cbdc Style changes 2024-05-27 20:54:44 +01:00
3ff00fde72 Rename Syncsafe to Synchsafe according to spec 2024-05-27 20:53:50 +01:00
c98606734a Set filename and size in Mp3Info object 2024-05-27 20:53:03 +01:00
a3fddb495d Optimise id3v2.4 comment parsing 2024-05-27 20:04:43 +01:00
37bc9d5ec6 Optimise id3v2 header parsing 2024-05-27 20:04:23 +01:00
d474f40403 Helper for Syncsafe size values 2024-05-27 20:03:14 +01:00
c0d64e81aa Minor style optimisations 2024-05-27 20:01:29 +01:00
4de63bd94b Proper TXXX tag handling
TXXX consists of description and value. Descriptions should be unique, but rarely are. This now parses them into TXXX:<description> and turns it into an array if multiple tags are found.

Signed-off-by: Markus Birth <markus@birth-online.de>
2024-05-27 16:20:49 +01:00
d6977ef4dc Fix ID3v2 frame size calculation
Frame size uses 7-out-of-8-Bits notation.

Signed-off-by: Markus Birth <markus@birth-online.de>
2024-05-27 15:28:22 +01:00
48edb42c1e Fix isValidAudio()
Signed-off-by: Markus Birth <markus@birth-online.de>
2024-05-27 13:49:41 +01:00
120617847c Fix cover retrieval
Signed-off-by: Markus Birth <markus@birth-online.de>
2024-05-27 13:05:30 +01:00
3699fd5ecb Add separate classes for local and remote file access
Improves parsing of remote files by using HTTP Range requests so it doesn't have to download the whole file.

Signed-off-by: Markus Birth <markus@birth-online.de>
2024-05-26 14:15:17 +01:00
3 changed files with 659 additions and 348 deletions

83
src/Mp3FileLocal.php Normal file
View File

@ -0,0 +1,83 @@
<?php
namespace wapmorgan\Mp3Info;
class Mp3FileLocal
{
public string $fileName;
protected int $fileSize;
private $_filePtr;
/**
* Creates a new local file object.
*
* @param string $fileName URL to open
*/
public function __construct(string $fileName)
{
$this->fileName = $fileName;
$this->_filePtr = fopen($this->fileName, 'rb');
$this->fileSize = filesize($this->fileName);
}
/**
* Returns the file size
*
* @return int File size
*/
public function getFileSize(): int
{
return $this->fileSize;
}
/**
* Returns the given amount of Bytes from the current file position.
*
* @param int $numBytes Bytes to read
*
* @return string Read Bytes
*/
public function getBytes(int $numBytes): string
{
return fread($this->_filePtr, $numBytes);
}
/**
* Returns the current file position
*
* @return int File position
*/
public function getFilePos(): int
{
return ftell($this->_filePtr);
}
/**
* Sets the file point to the given position.
*
* @param int $posBytes Position to jump to
*
* @return bool TRUE if successful
*/
public function seekTo(int $posBytes): bool
{
$result = fseek($this->_filePtr, $posBytes);
return ($result == 0);
}
/**
* Advances the file pointer the given amount.
*
* @param int $posBytes Bytes to advance
*
* @return bool TRUE if successful
*/
public function seekForward(int $posBytes): bool
{
$newPos = $this->getFilePos() + $posBytes;
return $this->seekTo($newPos);
}
}

190
src/Mp3FileRemote.php Normal file
View File

@ -0,0 +1,190 @@
<?php
namespace wapmorgan\Mp3Info;
use \Exception;
class Mp3FileRemote
{
public string $fileName;
public int $blockSize;
protected $buffer;
protected int $filePos;
protected $fileSize;
/**
* Creates a new remote file object.
*
* @param string $fileName URL to open
* @param int $blockSize Size of the blocks to query from the server (default: 4096)
*/
public function __construct(string $fileName, int $blockSize = 4096)
{
$this->fileName = $fileName;
$this->blockSize = $blockSize;
$this->buffer = [];
$this->filePos = 0;
$this->fileSize = $this->_readFileSize();
}
/**
* Returns the file size
*
* @return int File size
*/
public function getFileSize(): int
{
return $this->fileSize;
}
/**
* Makes a HEAD request to get the file size
*
* @return int Content-Length header
*/
private function _readFileSize(): int
{
// make HTTP HEAD request to get Content-Length
$context = stream_context_create([
'http' => [
'method' => 'HEAD',
],
]);
$result = get_headers($this->fileName, true, $context);
$result = array_change_key_case($result, CASE_LOWER);
return $result['content-length'];
}
/**
* Returns the given amount of Bytes from the current file position.
*
* @param int $numBytes Bytes to read
*
* @return string Read Bytes
*/
public function getBytes(int $numBytes): string
{
$blockId = intdiv($this->filePos, $this->blockSize);
$blockPos = $this->filePos % $this->blockSize;
$output = [];
do {
// make sure we have this block
if (!$this->downloadBlock($blockId)) {
throw new Exception('Error downloading block ' . $blockId . ' (starting at pos. ' . ($blockId * $this->blockSize) . ')!');
}
if ($blockPos + $numBytes >= $this->blockSize) {
// length of request is more than this block has, truncate to block len
$subLen = $this->blockSize - $blockPos;
} else {
// requested length fits inside this block
$subLen = $numBytes;
}
// $subLen = ($blockPos + $numBytes >= $this->blockSize) ? ($this->blockSize - $blockPos) : $numBytes;
$output[] = substr($this->buffer[$blockId], $blockPos, $subLen);
$this->filePos += $subLen;
$numBytes -= $subLen;
// advance to next block
$blockPos = 0;
$blockId++;
} while ($numBytes > 0);
return implode('', $output);
}
/**
* Returns the current file position
*
* @return int File position
*/
public function getFilePos(): int
{
return $this->filePos;
}
/**
* Sets the file pointer to the given position.
*
* @param int $posBytes Position to jump to
*
* @return bool TRUE if successful
*/
public function seekTo(int $posBytes): bool
{
if ($posBytes < 0 || $posBytes > $this->fileSize) {
return false;
}
$this->filePos = $posBytes;
return true;
}
/**
* Advances the file pointer the given amount.
*
* @param int $posBytes Bytes to advance
*
* @return bool TRUE if successful
*/
public function seekForward(int $posBytes): bool
{
$newPos = $this->filePos + $posBytes;
return $this->seekTo($newPos);
}
/**
* Downloads the given block if needed
*
* @param int $blockNo Block to download
*
* @return bool TRUE if successful
*/
protected function downloadBlock(int $blockNo): bool
{
if (array_key_exists($blockNo, $this->buffer)) {
// already downloaded
return true;
}
$bytesFrom = $blockNo * $this->blockSize;
$bytesTo = $bytesFrom + $this->blockSize - 1;
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => [
'Range: bytes=' . $bytesFrom . '-' . $bytesTo,
],
],
]);
$filePtr = fopen($this->fileName, 'rb', false, $context);
if ($filePtr === false) {
return false;
}
$this->buffer[$blockNo] = fread($filePtr, $this->blockSize);
$status = stream_get_meta_data($filePtr);
$httpStatus = explode(' ', $status['wrapper_data'][0])[1];
if ($httpStatus != '206') {
if ($httpStatus != '200') {
echo 'Download error!' . PHP_EOL;
var_dump($status);
return false;
}
echo 'Server doesn\'t support partial content!' . PHP_EOL;
// Content received is whole file from start
if ($blockNo != 0) {
// move block to start if needed
$this->buffer[0] =& $this->buffer[$blockNo];
unset($this->buffer[$blockNo]);
$blockNo = 0;
}
// receive remaining parts while we're at it
while (!feof($filePtr)) {
$blockNo++;
$this->buffer[$blockNo] = fread($filePtr, $this->blockSize);
}
}
fclose($filePtr);
return true;
}
}

File diff suppressed because it is too large Load Diff