From 752c03858a6da7f3f04680eb680ef1b6a1d0dc03 Mon Sep 17 00:00:00 2001 From: svollbehr Date: Sun, 31 May 2009 08:07:58 +0000 Subject: [PATCH] Add support for ID3v2.3 unsynchronisation git-svn-id: http://php-reader.googlecode.com/svn/branches/zend@155 51a70ab9-7547-0410-9469-37e369ee0574 --- src/Zend/Media/Id3/Frame.php | 4 +++ src/Zend/Media/Id3v2.php | 69 ++++++++++++++++++++++++------------ 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/Zend/Media/Id3/Frame.php b/src/Zend/Media/Id3/Frame.php index 72c65ff..a491339 100644 --- a/src/Zend/Media/Id3/Frame.php +++ b/src/Zend/Media/Id3/Frame.php @@ -290,6 +290,10 @@ abstract class Zend_Media_Id3_Frame extends Zend_Media_Id3_Object $data = $buffer->toString(); $size = $this->_size = strlen($data); + // ID3v2.4.0 supports frame level unsynchronisation. The corresponding + // option is set to true when any of the frames use the + // unsynchronisation scheme. The usage is denoted by + // Zend_Media_Id3_Header flag that is set accordingly upon file write. if ($this->getOption('version', 4) >= 4) { $data = $this->_encodeUnsynchronisation($data); if (($dataLength = strlen($data)) != $size) { diff --git a/src/Zend/Media/Id3v2.php b/src/Zend/Media/Id3v2.php index 239e528..414194a 100644 --- a/src/Zend/Media/Id3v2.php +++ b/src/Zend/Media/Id3v2.php @@ -43,7 +43,6 @@ require_once 'Zend/Media/Id3/Header.php'; * unique and predefined identifier which allows software to skip unknown * frames. * - * @todo Unsynchronisation not supported for ID3v2.3 tag * @category Zend * @package Zend_Media * @subpackage ID3 @@ -134,6 +133,9 @@ final class Zend_Media_Id3v2 extends Zend_Media_Id3_Object } $this->_header = new Zend_Media_Id3_Header($this->_reader, $options); + + $tagSize = $this->_header->getSize(); + if ($this->_header->getVersion() < 3 || $this->_header->getVersion() > 4) { require_once 'Zend/Media/Id3/Exception.php'; @@ -142,9 +144,10 @@ final class Zend_Media_Id3v2 extends Zend_Media_Id3_Object } if ($this->_header->getVersion() < 4 && $this->_header->hasFlag(Zend_Media_Id3_Header::UNSYNCHRONISATION)) { - require_once 'Zend/Media/Id3/Exception.php'; - throw new Zend_Media_Id3_Exception - ('Unsynchronisation not yet supported for this version of ID3v2 tag'); + $data = $this->_reader->read($this->_header->getSize()); + $this->_reader = new Zend_Io_StringReader + ($this->_decodeUnsynchronisation($data)); + $tagSize = $this->_reader->getSize(); } $this->clearOption('unsyncronisation'); if ($this->_header->hasFlag(Zend_Media_Id3_Header::UNSYNCHRONISATION)) { @@ -164,7 +167,7 @@ final class Zend_Media_Id3v2 extends Zend_Media_Id3_Object $offset = $this->_reader->getOffset(); // Jump off the loop if we reached the end of the tag - if ($offset - $startOffset - 10 >= $this->_header->getSize() - + if ($offset - $startOffset - 10 >= $tagSize - ($this->hasFooter() ? 10 : 0) - 10 /* header */) { break; } @@ -517,16 +520,34 @@ final class Zend_Media_Id3v2 extends Zend_Media_Id3_Object $frame->write($buffer); } } - $data = $buffer->toString(); - $datalen = strlen($data); - $padlen = 0; + $frameData = $buffer->toString(); + $frameDataLength = strlen($frameData); + $paddingLength = 0; + // ID3v2.4.0 supports frame level unsynchronisation. The corresponding + // option is set to true when any of the frames use the + // unsynchronisation scheme. if ($this->getOption('unsyncronisation', false) === true) { $this->_header->setFlags ($this->_header->getFlags() | Zend_Media_Id3_Header::UNSYNCHRONISATION); } + // ID3v2.3.0 supports only tag level unsynchronisation + if ($this->getOption('version', 4) < 4) { + $frameData = $this->_encodeUnsynchronisation($frameData); + if (($len = strlen($frameData)) != $frameDataLength) { + $frameDataLength = $len; + $this->_header->setFlags + ($this->_header->getFlags() | + Zend_Media_Id3_Header::UNSYNCHRONISATION); + } else { + $this->_header->setFlags + ($this->_header->getFlags() & + ~Zend_Media_Id3_Header::UNSYNCHRONISATION); + } + } + // The tag padding is calculated as follows. If the tag can be written // in the space of the previous tag, the remaining space is used for // padding. If there is no previous tag or the new tag is bigger than @@ -534,45 +555,49 @@ final class Zend_Media_Id3v2 extends Zend_Media_Id3_Object // 4096 bytes. if ($this->hasFooter() === false) { if ($this->_reader !== null && - $datalen < $this->_header->getSize()) { - $padlen = $this->_header->getSize() - $datalen; + $frameDataLength < $this->_header->getSize()) { + $paddingLength = $this->_header->getSize() - $frameDataLength; } else { - $padlen = 4096; + $paddingLength = 4096; } } /* ID3v2.4.0 CRC calculated w/ padding */ if ($this->getOption('version', 4) >= 4) { - $data = str_pad($data, $datalen + $padlen, "\0"); + $frameData = + str_pad($frameData, $frameDataLength += $paddingLength, "\0"); } + $extendedHeaderData = ''; + $extendedHeaderDataLength = 0; if ($this->hasExtendedHeader()) { - $this->_extendedHeader->setPadding($padlen); + $this->_extendedHeader->setPadding($paddingLength); if ($this->_extendedHeader->hasFlag (Zend_Media_Id3_ExtendedHeader::CRC32)) { - $crc = crc32($data); + $crc = crc32($frameData); if ($crc & 0x80000000) { $crc = -(($crc ^ 0xffffffff) + 1); } $this->_extendedHeader->setCrc($crc); } + $buffer = new Zend_Io_StringWriter(); + $this->_extendedHeader->write($buffer); + $extendedHeaderData = $buffer->toString(); + $extendedHeaderDataLength = strlen($extendedHeaderData); } /* ID3v2.3.0 CRC calculated w/o padding */ if ($this->getOption('version', 4) < 4) { - $data = str_pad($data, $datalen + $padlen, "\0"); + $frameData = + str_pad($frameData, $frameDataLength += $paddingLength, "\0"); } - $this->_header->setSize(strlen($data)); + $this->_header->setSize($extendedHeaderDataLength + $frameDataLength); $writer->write('ID3'); $this->_header->write($writer); - if ($this->hasExtendedHeader()) { - $this->_extendedHeader->write($writer); - } - - $writer->write($data); - + $writer->write($extendedHeaderData); + $writer->write($frameData); if ($this->hasFooter()) { $writer->write('3DI'); $this->_footer->write($writer);