Move ID3v2 flags parsing into method

Signed-off-by: Markus Birth <markus@birth-online.de>
This commit is contained in:
2025-06-14 22:19:49 +01:00
parent ad8183b41f
commit 7a8dede10b
+79 -82
View File
@@ -78,6 +78,15 @@ class Mp3Info
self::MPEG_2 => [13, 21],
self::MPEG_25 => [13, 21],
];
/**
* @var array
*/
private static $_id3v2HeaderFlags = [
2 => ['unsynchronisation', 'compression'],
3 => ['unsynchronisation', 'extended_header', 'experimental_indicator'],
4 => ['unsynchronisation', 'extended_header', 'experimental_indicator', 'footer_present'],
];
/**
* @var int Limit in bytes for seeking a mpeg header in file
@@ -190,19 +199,20 @@ class Mp3Info
/**
* @var int Major version of id3v2 tag (if id3v2 present) (2 or 3 or 4)
*/
public $id3v2MajorVersion;
public $id3v2MajorVersion; // @deprecated
public $id3v2Version;
/**
* @var int Minor version of id3v2 tag (if id3v2 present)
*/
public $id3v2MinorVersion;
public $id3v2MinorVersion; // @deprecated
public $id3v2Revision;
/**
* @var array List of id3v2 header flags (if id3v2 present)
*/
public $id3v2Flags = [];
public $id3v2HeaderFlags = [];
/**
* @var array List of id3v2 tags flags (if id3v2 present)
@@ -307,18 +317,21 @@ class Mp3Info
/** @var int Size of audio data (exclude tags size) */
$audioSize = $this->fileObj->getFileSize();
// parse tags
// try ID3v2 parsing
$audioSize -= ($this->_id3Size = $this->_parseId3v2Header(!($mode & self::TAGS)));
/*/ parse tags
if ($this->fileObj->getBytes(3) == self::TAG2_SYNC) {
if ($mode & self::TAGS) {
$audioSize -= ($this->_id3Size = $this->readId3v2Body());
} else {
$this->fileObj->seekForward(2); // 2 bytes of tag version
$this->fileObj->seekForward(2); // 2 bytes of tag version+revision
$this->fileObj->seekForward(1); // 1 byte of tag flags
$size = $this->getSynchsafeSize($this->fileObj->getBytes(4));
$size += 10; // add header size
$audioSize -= ($this->_id3Size = $size);
}
}
}*/
$this->fileObj->seekTo($this->fileObj->getFileSize() - 128);
if ($this->fileObj->getBytes(3) == self::TAG1_SYNC) {
@@ -486,60 +499,60 @@ class Mp3Info
$this->tags1['genre'] = '(' . ord($this->fileObj->getBytes(1)) . ')';
return 128;
}
private function _parseId3v2Flags(int $version, int $flags): array
{
$result = array();
if ($version >= 2) {
// parse id3v2.2.0 header flags
$result['unsynchronisation'] = ($flags & 128 == 128);
$result['compression'] = ($flags & 64 == 64);
}
if ($version >= 3) {
// id3v2.3 changes second bit from compression to extended_header
$result['extended_header'] = &$result['compression'];
unset($result['compression']);
// parse additional id3v2.3.0 header flags
$result['experimental_indicator'] = ($flags & 32 == 32);
}
if ($version >= 4) {
// parse additional id3v2.4.0 header flags
$result['footer_present'] = ($flags & 16 == 16);
}
return $result;
}
/**
* Reads id3v2 tag.
* -----------------------------------
* Overall tag header structure (10 bytes)
* ID3v2/file identifier "ID3" (3 bytes)
* ID3v2 version (2 bytes)
* ID3v2 flags (1 byte)
* ID3v2 size 4 * %0xxxxxxx (4 bytes)
* -----------------------------------
* id3v2.2.0 tag header (10 bytes)
* ID3/file identifier "ID3" (3 bytes)
* ID3 version $02 00 (2 bytes)
* ID3 flags %xx000000 (1 byte)
* ID3 size 4 * %0xxxxxxx (4 bytes)
* Flags:
* x (bit 7) - unsynchronisation
* x (bit 6) - compression
* -----------------------------------
* id3v2.3.0 tag header (10 bytes)
* ID3v2/file identifier "ID3" (3 bytes)
* ID3v2 version $03 00 (2 bytes)
* ID3v2 flags %abc00000 (1 byte)
* ID3v2 size 4 * %0xxxxxxx (4 bytes)
* Flags:
* a - Unsynchronisation
* b - Extended header
* c - Experimental indicator
* Extended header structure (10 bytes)
* Extended header size $xx xx xx xx
* Extended Flags $xx xx
* Size of padding $xx xx xx xx
* Extended flags:
* %x0000000 00000000
* x - CRC data present
* -----------------------------------
* id3v2.4.0 tag header (10 bytes)
* ID3v2/file identifier "ID3" (3 bytes)
* ID3v2 version $04 00 (2 bytes)
* ID3v2 flags %abcd0000 (1 byte)
* ID3v2 size 4 * %0xxxxxxx (4 bytes)
* Flags:
* a - Unsynchronisation
* b - Extended header
* c - Experimental indicator
* d - Footer present
* @param resource $fp
* @return int Returns length of id3v2 tag.
* @throws \Exception
* Reads ID3v2 header and returns the ID3v2 tag size. 0 if no header was found.
*
* @param bool $sizeOnly Only parse ID3v2 size
*
* @return int Size of ID3v2 struct
*/
private function readId3v2Body()
private function _parseId3v2Header(bool $sizeOnly = true): int
{
// check for "ID3" marker
if ($this->fileObj->getBytes(3) != self::TAG2_SYNC) {
// No ID3V2 tag found
return 0;
}
$headerSize = 10;
$footerSize = 0;
// read the rest of the id3v2 header
$raw = $this->fileObj->getBytes(3);
$size = $this->getSynchsafeSize($this->fileObj->getBytes(4));
if ($sizeOnly) {
// just return the size
// NOTE: $footerSize unclear at this point, might be inaccurate
return $headerSize + $footerSize + $size;
}
// parse version and flags
$data = unpack('Cversion/Crevision/Cflags', $raw);
$this->id3v2Version = $data['version'];
$this->id3v2Revision = $data['revision'];
@@ -547,39 +560,21 @@ class Mp3Info
$this->id3v2MajorVersion = $data['version'];
$this->id3v2MinorVersion = $data['revision'];
$this->id3v2Flags = array();
$flagsList = self::$_id3v2HeaderFlags[$this->id3v2Version];
// TODO: CONTINUE HERE - PARSE DIFFERENTLY
$this->id3v2Flags = $this->_parseId3v2Flags($this->id3v2Version, $data['flags']);
if ($this->id3v2Version >= 2) {
// parse id3v2.2.0 header flags
$this->id3v2Flags['unsynchronisation'] = ($data['flags'] & 128 == 128);
$this->id3v2Flags['compression'] = ($data['flags'] & 64 == 64);
}
if ($this->id3v2Version >= 3) {
// id3v2.3 changes second bit from compression to extended_header
$this->id3v2Flags['extended_header'] = &$this->id3v2Flags['compression'];
unset($this->id3v2Flags['compression']);
// parse additional id3v2.3.0 header flags
$this->id3v2Flags['experimental_indicator'] = ($data['flags'] & 32 == 32);
if ($this->id3v2Flags['extended_header']) {
throw new Exception('NEED TO PARSE EXTENDED HEADER!');
}
if ($this->id3v2Flags['extended_header']) {
throw new Exception('NEED TO PARSE EXTENDED HEADER!');
}
if ($this->id3v2Version >= 4) {
// parse additional id3v2.4.0 header flags
$this->id3v2Flags['footer_present'] = ($data['flags'] & 16 == 16);
if ($this->id3v2Flags['footer_present']) {
// footer is a copy of header - so can be ignored
// (10 Bytes not included in $size)
//throw new Exception('NEED TO PARSE id3v2.4 FOOTER!');
}
if ($this->id3v2Flags['footer_present']) {
// footer is a copy of header - so can be ignored
// (10 Bytes not included in $size)
$footerSize = 10;
}
$size = $this->getSynchsafeSize($this->fileObj->getBytes(4));
// Now parse the frames
if ($this->id3v2Version == 2) {
// parse id3v2.2.0 body
/*throw new \Exception('NEED TO PARSE id3v2.2.0 flags!');*/
@@ -591,7 +586,7 @@ class Mp3Info
$this->parseId3v24Body(10 + $size);
}
return 10 + $size; // 10 bytes - header, rest - body
return $headerSize + $footerSize + $size;
}
/**
@@ -600,6 +595,7 @@ class Mp3Info
*/
protected function parseId3v23Body($lastByte)
{
// TODO: Change $lastByte into $payloadLength so it works independent of starting location
while ($this->fileObj->getFilePos() < $lastByte) {
$raw = $this->fileObj->getBytes(10);
$frame_id = substr($raw, 0, 4);
@@ -798,6 +794,7 @@ class Mp3Info
*/
protected function parseId3v24Body($lastByte)
{
// TODO: Change $lastByte into $payloadLength so it works independent of starting location
while ($this->fileObj->getFilePos() < $lastByte) {
$frame_id = $this->fileObj->getBytes(4);