From 44b19c9e37e2709b03acfc4333545d79a7852504 Mon Sep 17 00:00:00 2001 From: svollbehr Date: Sun, 31 May 2009 06:17:55 +0000 Subject: [PATCH] Add missing Id3v2 class git-svn-id: http://php-reader.googlecode.com/svn/branches/zend@154 51a70ab9-7547-0410-9469-37e369ee0574 --- src/Zend/Io/Writer.php | 1262 ++++++++++++++++----------------- src/Zend/Media/Exception.php | 78 +- src/Zend/Media/Id3/Object.php | 16 +- src/Zend/Media/Id3v2.php | 635 +++++++++++++++++ 4 files changed, 1318 insertions(+), 673 deletions(-) create mode 100644 src/Zend/Media/Id3v2.php diff --git a/src/Zend/Io/Writer.php b/src/Zend/Io/Writer.php index e8bddc4..1de4c75 100644 --- a/src/Zend/Io/Writer.php +++ b/src/Zend/Io/Writer.php @@ -1,631 +1,631 @@ - - * @author Ryan Butterfield - * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com) - * @license http://framework.zend.com/license/new-bsd New BSD License - * @version $Id$ - */ -class Zend_Io_Writer -{ - const MACHINE_ENDIAN_ORDER = 0; - const LITTLE_ENDIAN_ORDER = 1; - const BIG_ENDIAN_ORDER = 2; - - /** - * The endianess of the current machine. - * - * @var integer - */ - private static $_endianess = 0; - - /** - * The resource identifier of the stream. - * - * @var resource - */ - protected $_fd = null; - - /** - * Size of the underlying stream. - * - * @var integer - */ - protected $_size = 0; - - /** - * Constructs the Zend_Io_Writer class with given open file descriptor. - * - * @param resource $fd The file descriptor. - * @throws Zend_Io_Exception if file descriptor is not valid - */ - public function __construct($fd) - { - if (!is_resource($fd) || - !in_array(get_resource_type($fd), array('stream'))) { - require_once('Zend/Io/Exception.php'); - throw new Zend_Io_Exception - ('Invalid resource type (only resources of type stream are supported)'); - } - - $this->_fd = $fd; - - $offset = $this->getOffset(); - fseek($this->_fd, 0, SEEK_END); - $this->_size = ftell($this->_fd); - fseek($this->_fd, $offset); - } - - /** - * Default destructor. - */ - public function __destruct() {} - - /** - * Returns the current point of operation. - * - * @return integer - * @throws Zend_Io_Exception if the stream is closed - */ - public function getOffset() - { - if ($this->_fd === null) { - require_once('Zend/Io/Exception.php'); - throw new Zend_Io_Exception('Cannot operate on a closed stream'); - } - return ftell($this->_fd); - } - - /** - * Sets the point of operation, ie the cursor offset value. The offset may - * also be set to a negative value when it is interpreted as an offset from - * the end of the stream instead of the beginning. - * - * @param integer $offset The new point of operation. - * @return void - * @throws Zend_Io_Exception if the stream is closed - */ - public function setOffset($offset) - { - if ($this->_fd === null) { - require_once('Zend/Io/Exception.php'); - throw new Zend_Io_Exception('Cannot operate on a closed stream'); - } - fseek($this->_fd, $offset < 0 ? $this->getSize() + $offset : $offset); - } - - /** - * Returns the stream size in bytes. - * - * @return integer - * @throws Zend_Io_Exception if the stream is closed - */ - public function getSize() - { - if ($this->_fd === null) { - require_once('Zend/Io/Exception.php'); - throw new Zend_Io_Exception('Cannot operate on a closed stream'); - } - return $this->_size; - } - - /** - * Sets the stream size in bytes, and truncates if required. - * - * @param integer $size The new size - * @return void - * @throws Zend_Io_Exception if the stream is closed - */ - public function setSize($size) - { - if ($this->_fd === null) { - require_once('Zend/Io/Exception.php'); - throw new Zend_Io_Exception('Cannot operate on a closed stream'); - } - ftruncate($this->_fd, $size); - } - - /** - * Returns the underlying stream file descriptor. - * - * @return resource - */ - public function getFileDescriptor() - { - return $this->_fd; - } - - /** - * Writes value up to length bytes to the stream. - * - * @param string $value The value to write to the stream. - * @param integer $length The number of bytes to write. Defaults to the - * length of the given value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public function write($value, $length = null) - { - if ($this->_fd === null) { - require_once('Zend/Io/Exception.php'); - throw new Zend_Io_Exception('Cannot operate on a closed stream'); - } - if ($length === null) { - $length = strlen($value); - } - fwrite($this->_fd, $value, $length); - $this->_size += $length; - return $this; - } - - /** - * Writes an 8-bit integer as binary data to the stream. - * - * @param integer $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeInt8($value) - { - return $this->write(pack('c*', $value)); - } - - /** - * Writes an unsigned 8-bit integer as binary data to the stream. - * - * @param integer $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeUInt8($value) - { - return $this->write(pack('C*', $value)); - } - - /** - * Returns signed 16-bit integer as machine endian ordered binary data. - * - * @param integer $value The input value. - * @return string - */ - private function _toInt16($value) - { - return pack('s*', $value); - } - - /** - * Writes a signed 16-bit integer as little-endian ordered binary data to - * the stream. - * - * @param integer $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeInt16LE($value) - { - if ($this->_isLittleEndian()) { - return $this->write(strrev($this->_toInt16($value))); - } else { - return $this->write($this->_toInt16($value)); - } - } - - /** - * Returns signed 16-bit integer as big-endian ordered binary data to the - * stream. - * - * @param integer $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeInt16BE($value) - { - if ($this->_isBigEndian()) { - return $this->write(strrev($this->_toInt16($value))); - } else { - return $this->write($this->_toInt16($value)); - } - } - - /** - * Writes unsigned 16-bit integer as little-endian ordered binary data - * to the stream. - * - * @param integer $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeUInt16LE($value) - { - return $this->write(pack('v*', $value)); - } - - /** - * Writes unsigned 16-bit integer as big-endian ordered binary data to the - * stream. - * - * @param integer $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeUInt16BE($value) - { - return $this->write(pack('n*', $value)); - } - - /** - * Returns signed 32-bit integer as machine-endian ordered binary data. - * - * @param integer $value The input value. - * @return string - */ - private final function _toInt32($value) - { - return pack('l*', $value); - } - - /** - * Writes signed 32-bit integer as little-endian ordered binary data to the - * stream. - * - * @param integer $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeInt32LE($value) - { - if ($this->_isLittleEndian()) { - return $this->write(strrev($this->_toInt32($value))); - } else { - return $this->write($this->_toInt32($value)); - } - } - - /** - * Writes signed 32-bit integer as big-endian ordered binary data to the - * stream. - * - * @param integer $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeInt32BE($value) - { - if ($this->_isBigEndian()) { - return $this->write(strrev($this->_toInt32($value))); - } else { - return $this->write($this->_toInt32($value)); - } - } - - /** - * Writes unsigned 32-bit integer as little-endian ordered binary data to - * the stream. - * - * @param integer $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeUInt32LE($value) - { - return $this->write(pack('V*', $value)); - } - - /** - * Writes unsigned 32-bit integer as big-endian ordered binary data to the - * stream. - * - * @param integer $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeUInt32BE($value) - { - return $this->write(pack('N*', $value)); - } - - /** - * Writes 64-bit float as little-endian ordered binary data string to the - * stream. - * - * @param integer $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeInt64LE($value) - { - return $this->write - (pack('V*', $value & 0xffffffff, $value / (0xffffffff+1))); - } - - /** - * Writes 64-bit float as big-endian ordered binary data string to the - * stream. - * - * @param integer $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeInt64BE($value) - { - return $this->write - (pack('N*', $value / (0xffffffff+1), $value & 0xffffffff)); - } - - /** - * Returns a floating point number as machine endian ordered binary data. - * - * @param float $value The input value. - * @return string - */ - private function _toFloat($value) - { - return pack('f*', $value); - } - - /** - * Writes a floating point number as little-endian ordered binary data to - * the stream. - * - * @param float $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeFloatLE($value) - { - if ($this->_isLittleEndian()) { - return $this->write(strrev($this->_toFloat($value))); - } else { - return $this->write($this->_toFloat($value)); - } - } - - /** - * Writes a floating point number as big-endian ordered binary data to the - * stream. - * - * @param float $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeFloatBE($value) - { - if ($this->_isBigEndian()) { - return $this->write(strrev($this->_toFloat($value))); - } else { - return $this->write($this->_toFloat($value)); - } - } - - /** - * Writes string as binary data padded to given length with zeros. If - * length is smaller than the length of the string, it is - * considered as the length of the padding. - * - * @param string $value The input value. - * @param integer $length The length to which to pad the value. - * @param string $padding The padding character. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeString8($value, $length = null, $padding = "\0") - { - if ($length === null) { - $length = strlen($value); - } - if ($length < ($tmp = strlen($value))) { - $length = $tmp + $length; - } - return $this->write(str_pad($value, $length, $padding)); - } - - /** - * Writes the multibyte string as binary data with given byte order mark - * (BOM) and padded to given length with zeros. Length is given in unicode - * characters so each character adds two zeros to the string. If length is - * smaller than the length of the string, it is considered as the length of - * the padding. - * - * If byte order mark is null no mark is inserted to the binary - * data. - * - * @param string $value The input value. - * @param integer $order The byte order of the binary data string. - * @param integer $length The length to which to pad the value. - * @param string $padding The padding character. - * @return string - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeString16 - ($value, $order = null, $length = null, $padding = "\0") - { - if ($length === null) { - $length = (int)(strlen($value) / 2); - } - if ($length < ($tmp = strlen($value) / 2)) { - $length = $tmp + $length; - } - if ($order == self::BIG_ENDIAN_ORDER && - !(ord($value[0]) == 0xfe && ord($value[1]) == 0xff)) { - $value = 0xfeff . $value; - $length++; - } - if ($order == self::LITTLE_ENDIAN_ORDER && - !(ord($value[0]) == 0xff && ord($value[1]) == 0xfe)) { - $value = 0xfffe . $value; - $length++; - } - return $this->write(str_pad($value, $length * 2, $padding)); - } - - /** - * Writes hexadecimal string having high nibble first as binary data to the - * stream. - * - * @param string $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if length attribute is negative or - * if the stream is closed - */ - public final function writeHHex($length) - { - return $this->write(pack('H*', $value)); - } - - /** - * Writes hexadecimal string having low nibble first as binary data to the - * stream. - * - * @param string $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if length attribute is negative or - * if the stream is closed - */ - public final function writeLHex($length) - { - return $this->write(pack('h*', $value)); - } - - /** - * Writes big-endian ordered hexadecimal GUID string as little-endian - * ordered binary data string to the stream. - * - * @param string $value The input value. - * @return Zend_Io_Writer - * @throws Zend_Io_Exception if the stream is closed - */ - public final function writeGuid() - { - $string = ''; - $C = preg_split('/-/', $value); - return $this->write - (pack - ('V1v2N2', hexdec($C[0]), hexdec($C[1]), hexdec($C[2]), - hexdec($C[3] . substr($C[4], 0, 4)), hexdec(substr($C[4], 4)))); - } - - /** - * Forces write of all buffered output to the underlying resource. - * - * @return void - * @throws Zend_Io_Exception if the stream is closed - */ - public function flush() - { - if ($this->_fd === null) { - require_once('Zend/Io/Exception.php'); - throw new Zend_Io_Exception('Cannot operate on a closed stream'); - } - fflush($this->_fd); - } - - /** - * Closes the stream. Once a stream has been closed, further calls to write - * methods will throw an exception. Closing a previously-closed stream, - * however, has no effect. - * - * @return void - * @throws Zend_Io_Exception if the stream is closed - */ - public function close() - { - if ($this->_fd !== null) { - @fclose($this->_fd); - $this->_fd = null; - } - } - - /** - * Returns the current machine endian order. - * - * @return integer - */ - private function _getEndianess() - { - if (self::$_endianess === 0) { - self::$_endianess = $this->_toInt32("\x01\x00\x00\x00") == 1 ? - self::LITTLE_ENDIAN_ORDER : self::BIG_ENDIAN_ORDER; - } - return self::$_endianess; - } - - /** - * Returns whether the current machine endian order is little endian. - * - * @return boolean - */ - private function _isLittleEndian() - { - return $this->_getEndianess() == self::LITTLE_ENDIAN_ORDER; - } - - /** - * Returns whether the current machine endian order is big endian. - * - * @return boolean - */ - private function _isBigEndian() - { - return $this->_getEndianess() == self::BIG_ENDIAN_ORDER; - } - - /** - * Magic function so that $obj->value will work. - * - * @param string $name The field name. - * @return mixed - */ - public function __get($name) - { - if (method_exists($this, 'get' . ucfirst(strtolower($name)))) { - return call_user_func - (array($this, 'get' . ucfirst(strtolower($name)))); - } else { - require_once('Zend/Io/Exception.php'); - throw new Zend_Io_Exception('Unknown field: ' . $name); - } - } - - /** - * Magic function so that assignments with $obj->value will work. - * - * @param string $name The field name. - * @param string $value The field value. - * @return mixed - */ - public function __set($name, $value) - { - if (method_exists($this, 'set' . ucfirst(strtolower($name)))) { - call_user_func - (array($this, 'set' . ucfirst(strtolower($name))), $value); - } else { - require_once('Zend/Io/Exception.php'); - throw new Zend_Io_Exception('Unknown field: ' . $name); - } - } -} + + * @author Ryan Butterfield + * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id$ + */ +class Zend_Io_Writer +{ + const MACHINE_ENDIAN_ORDER = 0; + const LITTLE_ENDIAN_ORDER = 1; + const BIG_ENDIAN_ORDER = 2; + + /** + * The endianess of the current machine. + * + * @var integer + */ + private static $_endianess = 0; + + /** + * The resource identifier of the stream. + * + * @var resource + */ + protected $_fd = null; + + /** + * Size of the underlying stream. + * + * @var integer + */ + protected $_size = 0; + + /** + * Constructs the Zend_Io_Writer class with given open file descriptor. + * + * @param resource $fd The file descriptor. + * @throws Zend_Io_Exception if file descriptor is not valid + */ + public function __construct($fd) + { + if (!is_resource($fd) || + !in_array(get_resource_type($fd), array('stream'))) { + require_once('Zend/Io/Exception.php'); + throw new Zend_Io_Exception + ('Invalid resource type (only resources of type stream are supported)'); + } + + $this->_fd = $fd; + + $offset = $this->getOffset(); + fseek($this->_fd, 0, SEEK_END); + $this->_size = ftell($this->_fd); + fseek($this->_fd, $offset); + } + + /** + * Default destructor. + */ + public function __destruct() {} + + /** + * Returns the current point of operation. + * + * @return integer + * @throws Zend_Io_Exception if the stream is closed + */ + public function getOffset() + { + if ($this->_fd === null) { + require_once('Zend/Io/Exception.php'); + throw new Zend_Io_Exception('Cannot operate on a closed stream'); + } + return ftell($this->_fd); + } + + /** + * Sets the point of operation, ie the cursor offset value. The offset may + * also be set to a negative value when it is interpreted as an offset from + * the end of the stream instead of the beginning. + * + * @param integer $offset The new point of operation. + * @return void + * @throws Zend_Io_Exception if the stream is closed + */ + public function setOffset($offset) + { + if ($this->_fd === null) { + require_once('Zend/Io/Exception.php'); + throw new Zend_Io_Exception('Cannot operate on a closed stream'); + } + fseek($this->_fd, $offset < 0 ? $this->getSize() + $offset : $offset); + } + + /** + * Returns the stream size in bytes. + * + * @return integer + * @throws Zend_Io_Exception if the stream is closed + */ + public function getSize() + { + if ($this->_fd === null) { + require_once('Zend/Io/Exception.php'); + throw new Zend_Io_Exception('Cannot operate on a closed stream'); + } + return $this->_size; + } + + /** + * Sets the stream size in bytes, and truncates if required. + * + * @param integer $size The new size + * @return void + * @throws Zend_Io_Exception if the stream is closed + */ + public function setSize($size) + { + if ($this->_fd === null) { + require_once('Zend/Io/Exception.php'); + throw new Zend_Io_Exception('Cannot operate on a closed stream'); + } + ftruncate($this->_fd, $size); + } + + /** + * Returns the underlying stream file descriptor. + * + * @return resource + */ + public function getFileDescriptor() + { + return $this->_fd; + } + + /** + * Writes value up to length bytes to the stream. + * + * @param string $value The value to write to the stream. + * @param integer $length The number of bytes to write. Defaults to the + * length of the given value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public function write($value, $length = null) + { + if ($this->_fd === null) { + require_once('Zend/Io/Exception.php'); + throw new Zend_Io_Exception('Cannot operate on a closed stream'); + } + if ($length === null) { + $length = strlen($value); + } + fwrite($this->_fd, $value, $length); + $this->_size += $length; + return $this; + } + + /** + * Writes an 8-bit integer as binary data to the stream. + * + * @param integer $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeInt8($value) + { + return $this->write(pack('c*', $value)); + } + + /** + * Writes an unsigned 8-bit integer as binary data to the stream. + * + * @param integer $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeUInt8($value) + { + return $this->write(pack('C*', $value)); + } + + /** + * Returns signed 16-bit integer as machine endian ordered binary data. + * + * @param integer $value The input value. + * @return string + */ + private function _toInt16($value) + { + return pack('s*', $value); + } + + /** + * Writes a signed 16-bit integer as little-endian ordered binary data to + * the stream. + * + * @param integer $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeInt16LE($value) + { + if ($this->_isLittleEndian()) { + return $this->write(strrev($this->_toInt16($value))); + } else { + return $this->write($this->_toInt16($value)); + } + } + + /** + * Returns signed 16-bit integer as big-endian ordered binary data to the + * stream. + * + * @param integer $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeInt16BE($value) + { + if ($this->_isBigEndian()) { + return $this->write(strrev($this->_toInt16($value))); + } else { + return $this->write($this->_toInt16($value)); + } + } + + /** + * Writes unsigned 16-bit integer as little-endian ordered binary data + * to the stream. + * + * @param integer $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeUInt16LE($value) + { + return $this->write(pack('v*', $value)); + } + + /** + * Writes unsigned 16-bit integer as big-endian ordered binary data to the + * stream. + * + * @param integer $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeUInt16BE($value) + { + return $this->write(pack('n*', $value)); + } + + /** + * Returns signed 32-bit integer as machine-endian ordered binary data. + * + * @param integer $value The input value. + * @return string + */ + private final function _toInt32($value) + { + return pack('l*', $value); + } + + /** + * Writes signed 32-bit integer as little-endian ordered binary data to the + * stream. + * + * @param integer $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeInt32LE($value) + { + if ($this->_isLittleEndian()) { + return $this->write(strrev($this->_toInt32($value))); + } else { + return $this->write($this->_toInt32($value)); + } + } + + /** + * Writes signed 32-bit integer as big-endian ordered binary data to the + * stream. + * + * @param integer $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeInt32BE($value) + { + if ($this->_isBigEndian()) { + return $this->write(strrev($this->_toInt32($value))); + } else { + return $this->write($this->_toInt32($value)); + } + } + + /** + * Writes unsigned 32-bit integer as little-endian ordered binary data to + * the stream. + * + * @param integer $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeUInt32LE($value) + { + return $this->write(pack('V*', $value)); + } + + /** + * Writes unsigned 32-bit integer as big-endian ordered binary data to the + * stream. + * + * @param integer $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeUInt32BE($value) + { + return $this->write(pack('N*', $value)); + } + + /** + * Writes 64-bit float as little-endian ordered binary data string to the + * stream. + * + * @param integer $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeInt64LE($value) + { + return $this->write + (pack('V*', $value & 0xffffffff, $value / (0xffffffff+1))); + } + + /** + * Writes 64-bit float as big-endian ordered binary data string to the + * stream. + * + * @param integer $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeInt64BE($value) + { + return $this->write + (pack('N*', $value / (0xffffffff+1), $value & 0xffffffff)); + } + + /** + * Returns a floating point number as machine endian ordered binary data. + * + * @param float $value The input value. + * @return string + */ + private function _toFloat($value) + { + return pack('f*', $value); + } + + /** + * Writes a floating point number as little-endian ordered binary data to + * the stream. + * + * @param float $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeFloatLE($value) + { + if ($this->_isLittleEndian()) { + return $this->write(strrev($this->_toFloat($value))); + } else { + return $this->write($this->_toFloat($value)); + } + } + + /** + * Writes a floating point number as big-endian ordered binary data to the + * stream. + * + * @param float $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeFloatBE($value) + { + if ($this->_isBigEndian()) { + return $this->write(strrev($this->_toFloat($value))); + } else { + return $this->write($this->_toFloat($value)); + } + } + + /** + * Writes string as binary data padded to given length with zeros. If + * length is smaller than the length of the string, it is + * considered as the length of the padding. + * + * @param string $value The input value. + * @param integer $length The length to which to pad the value. + * @param string $padding The padding character. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeString8($value, $length = null, $padding = "\0") + { + if ($length === null) { + $length = strlen($value); + } + if ($length < ($tmp = strlen($value))) { + $length = $tmp + $length; + } + return $this->write(str_pad($value, $length, $padding)); + } + + /** + * Writes the multibyte string as binary data with given byte order mark + * (BOM) and padded to given length with zeros. Length is given in unicode + * characters so each character adds two zeros to the string. If length is + * smaller than the length of the string, it is considered as the length of + * the padding. + * + * If byte order mark is null no mark is inserted to the binary + * data. + * + * @param string $value The input value. + * @param integer $order The byte order of the binary data string. + * @param integer $length The length to which to pad the value. + * @param string $padding The padding character. + * @return string + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeString16 + ($value, $order = null, $length = null, $padding = "\0") + { + if ($length === null) { + $length = (int)(strlen($value) / 2); + } + if ($length < ($tmp = strlen($value) / 2)) { + $length = $tmp + $length; + } + if ($order == self::BIG_ENDIAN_ORDER && + !(ord($value[0]) == 0xfe && ord($value[1]) == 0xff)) { + $value = 0xfeff . $value; + $length++; + } + if ($order == self::LITTLE_ENDIAN_ORDER && + !(ord($value[0]) == 0xff && ord($value[1]) == 0xfe)) { + $value = 0xfffe . $value; + $length++; + } + return $this->write(str_pad($value, $length * 2, $padding)); + } + + /** + * Writes hexadecimal string having high nibble first as binary data to the + * stream. + * + * @param string $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if length attribute is negative or + * if the stream is closed + */ + public final function writeHHex($length) + { + return $this->write(pack('H*', $value)); + } + + /** + * Writes hexadecimal string having low nibble first as binary data to the + * stream. + * + * @param string $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if length attribute is negative or + * if the stream is closed + */ + public final function writeLHex($length) + { + return $this->write(pack('h*', $value)); + } + + /** + * Writes big-endian ordered hexadecimal GUID string as little-endian + * ordered binary data string to the stream. + * + * @param string $value The input value. + * @return Zend_Io_Writer + * @throws Zend_Io_Exception if the stream is closed + */ + public final function writeGuid() + { + $string = ''; + $C = preg_split('/-/', $value); + return $this->write + (pack + ('V1v2N2', hexdec($C[0]), hexdec($C[1]), hexdec($C[2]), + hexdec($C[3] . substr($C[4], 0, 4)), hexdec(substr($C[4], 4)))); + } + + /** + * Forces write of all buffered output to the underlying resource. + * + * @return void + * @throws Zend_Io_Exception if the stream is closed + */ + public function flush() + { + if ($this->_fd === null) { + require_once('Zend/Io/Exception.php'); + throw new Zend_Io_Exception('Cannot operate on a closed stream'); + } + fflush($this->_fd); + } + + /** + * Closes the stream. Once a stream has been closed, further calls to write + * methods will throw an exception. Closing a previously-closed stream, + * however, has no effect. + * + * @return void + * @throws Zend_Io_Exception if the stream is closed + */ + public function close() + { + if ($this->_fd !== null) { + @fclose($this->_fd); + $this->_fd = null; + } + } + + /** + * Returns the current machine endian order. + * + * @return integer + */ + private function _getEndianess() + { + if (self::$_endianess === 0) { + self::$_endianess = $this->_toInt32("\x01\x00\x00\x00") == 1 ? + self::LITTLE_ENDIAN_ORDER : self::BIG_ENDIAN_ORDER; + } + return self::$_endianess; + } + + /** + * Returns whether the current machine endian order is little endian. + * + * @return boolean + */ + private function _isLittleEndian() + { + return $this->_getEndianess() == self::LITTLE_ENDIAN_ORDER; + } + + /** + * Returns whether the current machine endian order is big endian. + * + * @return boolean + */ + private function _isBigEndian() + { + return $this->_getEndianess() == self::BIG_ENDIAN_ORDER; + } + + /** + * Magic function so that $obj->value will work. + * + * @param string $name The field name. + * @return mixed + */ + public function __get($name) + { + if (method_exists($this, 'get' . ucfirst(strtolower($name)))) { + return call_user_func + (array($this, 'get' . ucfirst(strtolower($name)))); + } else { + require_once('Zend/Io/Exception.php'); + throw new Zend_Io_Exception('Unknown field: ' . $name); + } + } + + /** + * Magic function so that assignments with $obj->value will work. + * + * @param string $name The field name. + * @param string $value The field value. + * @return mixed + */ + public function __set($name, $value) + { + if (method_exists($this, 'set' . ucfirst(strtolower($name)))) { + call_user_func + (array($this, 'set' . ucfirst(strtolower($name))), $value); + } else { + require_once('Zend/Io/Exception.php'); + throw new Zend_Io_Exception('Unknown field: ' . $name); + } + } +} diff --git a/src/Zend/Media/Exception.php b/src/Zend/Media/Exception.php index 611621d..bb68741 100644 --- a/src/Zend/Media/Exception.php +++ b/src/Zend/Media/Exception.php @@ -1,39 +1,39 @@ - - * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com) - * @license http://framework.zend.com/license/new-bsd New BSD License - * @version $Id$ - */ -class Zend_Media_Exception extends Zend_Exception -{} + + * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id$ + */ +class Zend_Media_Exception extends Zend_Exception +{} diff --git a/src/Zend/Media/Id3/Object.php b/src/Zend/Media/Id3/Object.php index d1f11e7..1cacca2 100644 --- a/src/Zend/Media/Id3/Object.php +++ b/src/Zend/Media/Id3/Object.php @@ -70,9 +70,9 @@ abstract class Zend_Media_Id3_Object * * @return Array */ - public final function getOptions() + public final function &getOptions() { - return $this->_options; + return $this->_options; } /** @@ -98,7 +98,7 @@ abstract class Zend_Media_Id3_Object */ public final function setOptions(&$options) { - $this->_options = &$options; + $this->_options = &$options; } /** @@ -112,6 +112,16 @@ abstract class Zend_Media_Id3_Object $this->_options[$option] = $value; } + /** + * Clears the given option value. + * + * @param string $option The name of the option. + */ + public final function clearOption($option) + { + unset($this->_options[$option]); + } + /** * Magic function so that $obj->value will work. * diff --git a/src/Zend/Media/Id3v2.php b/src/Zend/Media/Id3v2.php new file mode 100644 index 0000000..239e528 --- /dev/null +++ b/src/Zend/Media/Id3v2.php @@ -0,0 +1,635 @@ + + * @author Ryan Butterfield + * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id$ + */ +final class Zend_Media_Id3v2 extends Zend_Media_Id3_Object +{ + /** @var Zend_Media_Id3_Header */ + private $_header; + + /** @var Zend_Media_Id3_ExtendedHeader */ + private $_extendedHeader; + + /** @var Zend_Media_Id3_Header */ + private $_footer; + + /** @var Array */ + private $_frames = array(); + + /** @var string */ + private $_filename = null; + + /** + * Constructs the Zend_Media_Id3v2 class with given file and options. The + * options array may also be given as the only parameter. + * + * The following options are currently recognized: + * o encoding -- Indicates the encoding that all the texts are presented + * with. See the documentation of iconv for supported values. Please + * note that write operations do not convert string and thus encodings + * are limited to those supported by the {@link Zend_Media_Id3_Encoding} + * interface. + * o version -- The ID3v2 tag version to use in write operation. This + * option is automatically set when a tag is read from a file and + * defaults to version 4.0 for tag write. + * o readonly -- Indicates that the tag is read from a temporary file or + * another source it cannot be written back to. The tag can, however, + * still be written to another file. + * + * @todo Only limited subset of flags are processed. + * @todo Utilize the SEEK frame and search for a footer to find the tag + * @todo Utilize the LINK frame to fetch frames from other sources + * @param string|resource|Zend_Io_Reader $filename The path to the file, + * file descriptor of an opened file, or a {@link Zend_Io_Reader} instance. + * @param Array $options The options array. + * @throws Zend_Media_Id3_Exception if given file descriptor is not valid + */ + public function __construct($filename = null, $options = array()) + { + parent::__construct(null, $options); + + if (is_array($filename)) { + $options = $filename; + $filename = null; + } + + if ($filename === null) { + $this->_header = new Zend_Media_Id3_Header(null, $options); + return; + } + + if ($filename instanceof Zend_Io_Reader) { + $this->_reader = &$filename; + } else { + require_once('Zend/Io/FileReader.php'); + try { + $this->_reader = new Zend_Io_FileReader($filename); + } catch (Zend_Io_Exception $e) { + $this->_reader = null; + require_once 'Zend/Media/Id3/Exception.php'; + throw new Zend_Media_Id3_Exception($e->getMessage()); + } + if (is_string($filename) && !isset($options['readonly'])) { + $this->_filename = $filename; + } + } + + $startOffset = $this->_reader->getOffset(); + + if ($this->_reader->read(3) != 'ID3') { + require_once 'Zend/Media/Id3/Exception.php'; + throw new Zend_Media_Id3_Exception + ('File does not contain ID3v2 tag'); + } + + $this->_header = new Zend_Media_Id3_Header($this->_reader, $options); + if ($this->_header->getVersion() < 3 || + $this->_header->getVersion() > 4) { + require_once 'Zend/Media/Id3/Exception.php'; + throw new Zend_Media_Id3_Exception + ('File does not contain ID3v2 tag of supported version'); + } + 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'); + } + $this->clearOption('unsyncronisation'); + if ($this->_header->hasFlag(Zend_Media_Id3_Header::UNSYNCHRONISATION)) { + $this->setOption('unsyncronisation', true); + } + if ($this->_header->hasFlag(Zend_Media_Id3_Header::EXTENDED_HEADER)) { + require_once 'Zend/Media/Id3/ExtendedHeader.php'; + $this->_extendedHeader = + new Zend_Media_Id3_ExtendedHeader($this->_reader, $options); + } + if ($this->_header->hasFlag(Zend_Media_Id3_Header::FOOTER)) { + // skip footer, and rather copy header + $this->_footer = &$this->_header; + } + + while (true) { + $offset = $this->_reader->getOffset(); + + // Jump off the loop if we reached the end of the tag + if ($offset - $startOffset - 10 >= $this->_header->getSize() - + ($this->hasFooter() ? 10 : 0) - 10 /* header */) { + break; + } + + // Jump off the loop if we reached padding + if (ord($identifier = $this->_reader->read(1)) === 0) { + break; + } + + $identifier .= $this->_reader->read(3); + + // Jump off the loop if we reached invalid entities. This fix is + // just to make things work. Utility called MP3ext does not seem + // to know what it is doing as it uses padding to write its + // version information there. + if ($identifier == 'MP3e') { + break; + } + + $this->_reader->setOffset($offset); + if (@fopen($filename = 'Zend/Media/Id3/Frame/' . + strtoupper($identifier) . '.php', 'r', true) !== false) { + require_once($filename); + } + if (class_exists + ($classname = 'Zend_Media_Id3_Frame_' . $identifier)) { + $frame = new $classname($this->_reader, $options); + } else { + require_once 'Zend/Media/Id3/Frame/Unknown.php'; + $frame = + new Zend_Media_Id3_Frame_Unknown($this->_reader, $options); + } + + if (!isset($this->_frames[$frame->getIdentifier()])) { + $this->_frames[$frame->getIdentifier()] = array(); + } + $this->_frames[$frame->getIdentifier()][] = $frame; + } + } + + /** + * Returns the header object. + * + * @return Zend_Media_Id3_Header + */ + public function getHeader() + { + return $this->_header; + } + + /** + * Checks whether there is an extended header present in the tag. Returns + * true if the header is present, false otherwise. + * + * @return boolean + */ + public function hasExtendedHeader() + { + if ($this->_header) { + return $this->_header->hasFlag + (Zend_Media_Id3_Header::EXTENDED_HEADER); + } + return false; + } + + /** + * Returns the extended header object if present, or false + * otherwise. + * + * @return Zend_Media_Id3_ExtendedHeader|false + */ + public function getExtendedHeader() + { + if ($this->hasExtendedHeader()) { + return $this->_extendedHeader; + } + return false; + } + + /** + * Sets the extended header object. + * + * @param Zend_Media_Id3_ExtendedHeader $extendedHeader The header object + */ + public function setExtendedHeader($extendedHeader) + { + if (is_subclass_of($extendedHeader, 'Zend_Media_Id3_ExtendedHeader')) { + $this->_header->flags = + $this->_header->flags | Zend_Media_Id3_Header::EXTENDED_HEADER; + $this->_extendedHeader->setOptions($this->getOptions()); + $this->_extendedHeader = $extendedHeader; + } else { + require_once 'Zend/Media/Id3/Exception.php'; + throw new Zend_Media_Id3_Exception('Invalid argument'); + } + } + + /** + * Checks whether there is a frame given as an argument defined in the tag. + * Returns true if one ore more frames are present, + * false otherwise. + * + * @param string $identifier The frame name. + * @return boolean + */ + public function hasFrame($identifier) + { + return isset($this->_frames[$identifier]); + } + + /** + * Returns all the frames the tag contains as an associate array. The frame + * identifiers work as keys having an array of frames as associated value. + * + * @return Array + */ + public function getFrames() + { + return $this->_frames; + } + + /** + * Returns an array of frames matching the given identifier or an empty + * array if no frames matched the identifier. + * + * The identifier may contain wildcard characters '*' and '?'. The asterisk + * matches against zero or more characters, and the question mark matches + * any single character. + * + * Please note that one may also use the shorthand $obj->identifier to + * access the first frame with the identifier given. Wildcards cannot be + * used with the shorthand method. + * + * @param string $identifier The frame name. + * @return Array + */ + public function getFramesByIdentifier($identifier) + { + $matches = array(); + $searchPattern = '/^' . + str_replace(array('*', '?'), array('.*', '.'), $identifier) . '$/i'; + foreach ($this->_frames as $identifier => $frames) { + if (preg_match($searchPattern, $identifier)) { + foreach ($frames as $frame) { + $matches[] = $frame; + } + } + } + return $matches; + } + + /** + * Removes any frames matching the given object identifier. + * + * The identifier may contain wildcard characters '*' and '?'. The asterisk + * matches against zero or more characters, and the question mark matches + * any single character. + * + * One may also use the shorthand unset($obj->identifier) to achieve the + * same result. Wildcards cannot be used with the shorthand method. + * + * @param string $identifier The frame name. + */ + public final function removeFramesByIdentifier($identifier) + { + $searchPattern = '/^' . + str_replace(array('*', '?'), array('.*', '.'), $identifier) . '$/i'; + foreach ($this->_frames as $identifier => $frames) { + if (preg_match($searchPattern, $identifier)) { + unset($this->_frames[$identifier]); + } + } + } + + /** + * Adds a new frame to the tag and returns it. + * + * @param Zend_Media_Id3_Frame $frame The frame to add. + * @return Zend_Media_Id3_Frame + */ + public function addFrame($frame) + { + $frame->setOptions($this->getOptions()); + if (!$this->hasFrame($frame->getIdentifier())) { + $this->_frames[$frame->getIdentifier()] = array(); + } + return $this->_frames[$frame->getIdentifier()][] = $frame; + } + + /** + * Remove the given frame from the tag. + * + * @param Zend_Media_Id3_Frame $frame The frame to remove. + */ + public function removeFrame($frame) + { + if (!$this->hasFrame($frame->getIdentifier())) { + return; + } + foreach ($this->_frames[$frame->getIdentifier()] as $key => $value) { + if ($frame === $value) { + unset($this->_frames[$frame->getIdentifier()][$key]); + } + } + } + + /** + * Checks whether there is a footer present in the tag. Returns + * true if the footer is present, false otherwise. + * + * @return boolean + */ + public function hasFooter() + { + return $this->_header->hasFlag(Zend_Media_Id3_Header::FOOTER); + } + + /** + * Returns the footer object if present, or false otherwise. + * + * @return Zend_Media_Id3_Header|false + */ + public function getFooter() + { + if ($this->hasFooter()) { + return $this->_footer; + } + return false; + } + + /** + * Sets whether the tag should have a footer defined. + * + * @param boolean $useFooter Whether the tag should have a footer + */ + public function setFooter($useFooter) + { + if ($useFooter) { + $this->_header->setFlags + ($this->_header->getFlags() | Zend_Media_Id3_Header::FOOTER); + $this->_footer = &$this->_header; + } else { + /* Count footer bytes towards the tag size, so it gets removed or + overridden upon re-write */ + if ($this->hasFooter()) { + $this->_header->setSize($this->_header->getSize() + 10); + } + + $this->_header->setFlags + ($this->_header->getFlags() & ~Zend_Media_Id3_Header::FOOTER); + $this->_footer = null; + } + } + + /** + * Writes the possibly altered ID3v2 tag back to the file where it was read. + * If the class was constructed without a file name, one can be provided + * here as an argument. Regardless, the write operation will override + * previous tag information, if found. + * + * If write is called without setting any frames to the tag, the tag is + * removed from the file. + * + * @param string $filename The optional path to the file. + */ + public function write($filename) + { + if ($filename === null && ($filename = $this->_filename) === null) { + require_once 'Zend/Media/Id3/Exception.php'; + throw new Zend_Media_Id3_Exception + ('No file given to write the tag to'); + } else if ($filename !== null && $this->_filename !== null && + realpath($filename) != realpath($this->_filename) && + !copy($this->_filename, $filename)) { + require_once 'Zend/Media/Id3/Exception.php'; + throw new Zend_Media_Id3_Exception + ('Unable to copy source to destination: ' . + realpath($this->_filename) . '->' . realpath($filename)); + } + + if (($fd = fopen + ($filename, file_exists($filename) ? 'r+b' : 'wb')) === false) { + require_once 'Zend/Media/Id3/Exception.php'; + throw new Zend_Media_Id3_Exception + ('Unable to open file for writing: ' . $filename); + } + + if ($this->_reader !== null) { + $oldTagSize = 10 /* header */ + $this->_header->getSize(); + } else { + $reader = new Zend_Io_Reader($fd); + if ($reader->read(3) == 'ID3') { + $header = new Zend_Media_Id3_Header($reader); + $oldTagSize = 10 /* header */ + $header->getSize(); + } else { + $oldTagSize = 0; + } + } + $tag = new Zend_Io_StringWriter(); + $this->_writeTag($tag); + $tagSize = empty($this->_frames) ? 0 : $tag->getSize(); + + if ($tagSize > $oldTagSize || $tagSize == 0) { + fseek($fd, 0, SEEK_END); + $oldFileSize = ftell($fd); + ftruncate + ($fd, $newFileSize = $tagSize - $oldTagSize + $oldFileSize); + for ($i = 1, $cur = $oldFileSize; $cur > 0; $cur -= 1024, $i++) { + if ($cur >= 1024) { + fseek($fd, $off=-(($i * 1024) + + ($newFileSize - $oldFileSize)), SEEK_END); + $buffer = fread($fd, 1024); + fseek($fd, $off=-($i * 1024), SEEK_END); + $bytes = fwrite($fd, $buffer, 1024); + } else { + fseek($fd, 0); + $buffer = fread($fd, $cur); + fseek($fd, $off=$newFileSize % 1024 - $cur); + $bytes = fwrite($fd, $buffer, $cur); + } + } + if (($remaining = $oldFileSize % 1024) != 0) { + + } + fseek($fd, 0, SEEK_END); + } + fseek($fd, 0); + for ($i = 0; $i < $tagSize; $i += 1024) { + fseek($tag->getFileDescriptor(), $i); + $bytes = fwrite($fd, fread($tag->getFileDescriptor(), 1024)); + } + fclose($fd); + + $this->_filename = $filename; + } + + /** + * Writes the tag data. + * + * @param Zend_Io_Writer $writer The writer object. + * @return void + */ + private function _writeTag($writer) + { + $this->clearOption('unsyncronisation'); + + $buffer = new Zend_Io_StringWriter(); + foreach ($this->_frames as $frames) { + foreach ($frames as $frame) { + $frame->write($buffer); + } + } + $data = $buffer->toString(); + $datalen = strlen($data); + $padlen = 0; + + if ($this->getOption('unsyncronisation', false) === true) { + $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 + // the space taken by the previous tag, the padding is a constant + // 4096 bytes. + if ($this->hasFooter() === false) { + if ($this->_reader !== null && + $datalen < $this->_header->getSize()) { + $padlen = $this->_header->getSize() - $datalen; + } else { + $padlen = 4096; + } + } + + /* ID3v2.4.0 CRC calculated w/ padding */ + if ($this->getOption('version', 4) >= 4) { + $data = str_pad($data, $datalen + $padlen, "\0"); + } + + if ($this->hasExtendedHeader()) { + $this->_extendedHeader->setPadding($padlen); + if ($this->_extendedHeader->hasFlag + (Zend_Media_Id3_ExtendedHeader::CRC32)) { + $crc = crc32($data); + if ($crc & 0x80000000) { + $crc = -(($crc ^ 0xffffffff) + 1); + } + $this->_extendedHeader->setCrc($crc); + } + } + + /* ID3v2.3.0 CRC calculated w/o padding */ + if ($this->getOption('version', 4) < 4) { + $data = str_pad($data, $datalen + $padlen, "\0"); + } + + $this->_header->setSize(strlen($data)); + + $writer->write('ID3'); + $this->_header->write($writer); + if ($this->hasExtendedHeader()) { + $this->_extendedHeader->write($writer); + } + + $writer->write($data); + + if ($this->hasFooter()) { + $writer->write('3DI'); + $this->_footer->write($writer); + } + } + + /** + * Magic function so that $obj->value will work. The method will attempt to + * return the first frame that matches the identifier. + * + * If there is no frame or field with given name, the method will attempt to + * create a frame with given identifier. + * + * If none of these work, an exception is thrown. + * + * @param string $name The frame or field name. + * @return mixed + */ + public function __get($name) { + if (isset($this->_frames[strtoupper($name)])) { + return $this->_frames[strtoupper($name)][0]; + } + if (method_exists($this, 'get' . ucfirst($name))) { + return call_user_func(array($this, 'get' . ucfirst($name))); + } + if (@fopen($filename = 'Zend/Media/Id3/Frame/' . strtoupper($name) . + '.php', 'r', true) !== false) { + require_once $filename; + } + if (class_exists + ($classname = 'Zend_Media_Id3_Frame_' . strtoupper($name))) { + return $this->addFrame(new $classname()); + } + require_once 'Zend/Media/Id3/Exception.php'; + throw new Zend_Media_Id3_Exception('Unknown frame/field: ' . $name); + } + + /** + * Magic function so that isset($obj->value) will work. This method checks + * whether the frame matching the identifier exists. + * + * @param string $name The frame identifier. + * @return boolean + */ + public function __isset($name) + { + return isset($this->_frames[strtoupper($name)]); + } + + /** + * Magic function so that unset($obj->value) will work. This method removes + * all the frames matching the identifier. + * + * @param string $name The frame identifier. + */ + public function __unset($name) + { + unset($this->_frames[strtoupper($name)]); + } +}