Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3699fd5
Add separate classes for local and remote file access
mbirth May 26, 2024
1206178
Fix cover retrieval
mbirth May 27, 2024
48edb42
Fix isValidAudio()
mbirth May 27, 2024
d6977ef
Fix ID3v2 frame size calculation
mbirth May 27, 2024
4de63bd
Proper TXXX tag handling
mbirth May 27, 2024
c0d64e8
Minor style optimisations
mbirth May 27, 2024
d474f40
Helper for Syncsafe size values
mbirth May 27, 2024
37bc9d5
Optimise id3v2 header parsing
mbirth May 27, 2024
a3fddb4
Optimise id3v2.4 comment parsing
mbirth May 27, 2024
c986067
Set filename and size in Mp3Info object
mbirth May 27, 2024
3ff00fd
Rename Syncsafe to Synchsafe according to spec
mbirth May 27, 2024
38a962c
Style changes
mbirth May 27, 2024
2a16b83
Return payload size for ID3v2 tags
mbirth May 27, 2024
3184973
Optimise searching for Mpeg frame
mbirth May 27, 2024
c387953
Optimise parsing of id3v2 flags
mbirth May 27, 2024
5b8e15a
Fix Mpeg frame sync detection
mbirth May 27, 2024
0ef2575
Fix Mpeg frame verification
mbirth May 27, 2024
9b92776
Differentiate btw id3v1.0 and id3v1.1
mbirth May 27, 2024
c54e75a
Show error for invalid LAYER/CODEC versions
mbirth May 27, 2024
2a98464
Put id3v1 genre_id in brackets as seen with id3v2
mbirth May 27, 2024
4b2cbfb
Cosmetics
mbirth May 28, 2024
3e1288a
Added support for informational bits in Mpeg frame header
mbirth May 28, 2024
aaaba0e
Fix Protection bit inverted meaning
mbirth May 29, 2024
ad8183b
Add error handling if block can't be downloaded
mbirth May 29, 2024
7a8dede
Move ID3v2 flags parsing into method
mbirth Jun 14, 2025
e64c30e
Turn returned HTTP headers lowercase before parsing
mbirth Jun 15, 2025
734bcae
Remove require() and let autoloader do its thing
mbirth Aug 16, 2025
2154601
Replace echos with proper Exception and trigger_error()
mbirth Aug 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions src/Mp3FileLocal.php
Original file line number Diff line number Diff line change
@@ -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);
}

}
186 changes: 186 additions & 0 deletions src/Mp3FileRemote.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<?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
$this->downloadBlock($blockId);
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, Exception on errors
*/
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') {
throw new Exception('Error downloading block ' . $blockNo . ' (starting at pos. ' . $bytesFrom . ')!\n' . print_r($status, true));
}
trigger_error('Server doesn\'t support partial content! Downloading whole file.', E_USER_NOTICE);
// 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;
}
}
Loading