From 9fca42586ee863c2a5158816cba8814410a03a6d Mon Sep 17 00:00:00 2001 From: svollbehr Date: Thu, 4 Mar 2010 11:09:03 +0000 Subject: [PATCH] Add Zend_Media_Iso14496 class proposal git-svn-id: http://php-reader.googlecode.com/svn/branches/zend@165 51a70ab9-7547-0410-9469-37e369ee0574 --- src/Zend/Media/Iso14496.php | 405 ++++++++++++++++ src/Zend/Media/Iso14496/Box.php | 663 ++++++++++++++++++++++++++ src/Zend/Media/Iso14496/Box/Elst.php | 17 +- src/Zend/Media/Iso14496/Box/Free.php | 8 +- src/Zend/Media/Iso14496/Box/Sbgp.php | 197 ++++++++ src/Zend/Media/Iso14496/Box/Schm.php | 177 +++++++ src/Zend/Media/Iso14496/Box/Sdtp.php | 187 ++++++++ src/Zend/Media/Iso14496/Exception.php | 49 ++ src/Zend/Media/Iso14496/FullBox.php | 150 ++++++ 9 files changed, 1841 insertions(+), 12 deletions(-) create mode 100644 src/Zend/Media/Iso14496.php create mode 100644 src/Zend/Media/Iso14496/Box.php create mode 100644 src/Zend/Media/Iso14496/Box/Sbgp.php create mode 100644 src/Zend/Media/Iso14496/Box/Schm.php create mode 100644 src/Zend/Media/Iso14496/Box/Sdtp.php create mode 100644 src/Zend/Media/Iso14496/Exception.php create mode 100644 src/Zend/Media/Iso14496/FullBox.php diff --git a/src/Zend/Media/Iso14496.php b/src/Zend/Media/Iso14496.php new file mode 100644 index 0000000..141e731 --- /dev/null +++ b/src/Zend/Media/Iso14496.php @@ -0,0 +1,405 @@ + + *
  • ftyp -- {@link Zend_Media_Iso14496_Box_Ftyp File Type Box}; + * file type and compatibility + *
  • pdin -- {@link Zend_Media_Iso14496_Box_Pdin Progressive Download + * Information Box} + *
  • moov -- {@link Zend_Media_Iso14496_Box_Moov Movie Box}; + * container for all the metadata + * + *
  • moof -- {@link Zend_Media_Iso14496_Box_Moof Movie Fragment Box} + * + *
  • mfra -- {@link Zend_Media_Iso14496_Box_Mfra Movie Fragment Random + * Access Box} + * + *
  • mdat -- {@link Zend_Media_Iso14496_Box_Mdat Media Data Box} + *
  • free -- {@link Zend_Media_Iso14496_Box_Free Free Space Box} + *
  • skip -- {@link Zend_Media_Iso14496_Box_Skip Free Space Box} + * + *
  • meta -- {@link Zend_Media_Iso14496_Box_Meta The Meta Box} + * + * + * + * There are two non-standard extensions to the ISO 14496 standard that add the + * ability to include file meta information. Both the boxes reside under + * moov.udta.meta. + * + * + * + * @category Zend + * @package Zend_Media + * @subpackage ISO 14496 + * @author Sven Vollbehr + * @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_Iso14496 extends Zend_Media_Iso14496_Box +{ + /** @var string */ + private $_filename; + + /** + * Constructs the Zend_Media_Iso14496 class with given file and options. + * + * The following options are currently recognized: + * o base -- Indicates that only boxes with the given base path are parsed + * from the ISO base media file. Parsing all boxes can possibly have a + * significant impact on running time. Base path is a list of nested + * boxes separated by a dot. The use of base option implies readonly + * option. + * o readonly -- Indicates that the file is read from a temporary location + * or another source it cannot be written back to. + * + * @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. + */ + public function __construct($filename, $options = array()) + { + if (isset($options['base'])) { + $options['readonly'] = true; + } + 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_Iso14496_Exception($e->getMessage()); + } + if (is_string($filename) && !isset($options['readonly'])) { + $this->_filename = $filename; + } + } + $this->setOptions($options); + $this->setOffset(0); + $this->setSize($this->_reader->getSize()); + $this->setType('file'); + $this->setContainer(true); + $this->constructBoxes(); + } + + /** + * Writes the changes back to the original media file. If the class was + * constructed without a file name, one can be provided here as an argument. + * + * The write operation commits only changes made to the Movie Box. It + * further changes the order of the Movie Box and Media Data Box in a way + * compatible for progressive download from a web page. + * + * All file offsets must be assumed to be invalid after the write operation. + * + * @param string $filename The optional path to the file, use null to save + * to the same file. + */ + public function write($filename) + { + if ($filename === null && ($filename = $this->_filename) === null) { + require_once 'Zend/Media/Iso14496/Exception.php'; + throw new Zend_Media_Iso14496_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/Iso14496/Exception.php'; + throw new Zend_Media_Iso14496_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/Iso14496/Exception.php'; + throw new Zend_Media_Iso14496_Exception + ('Unable to open file for writing: ' . $filename); + } + + /* Calculate file size */ + fseek($fd, 0, SEEK_END); + $oldFileSize = ftell($fd); + $oldMoovSize = $this->moov->getSize(); + $this->moov->udta->meta->free->setSize(8); + $this->moov->udta->meta->hdlr->setHandlerType('mdir'); + $newFileSize = $oldFileSize - $oldMoovSize + $this->moov->getHeapSize(); + + /* Calculate free space size */ + if ($oldFileSize < $newFileSize || + $this->mdat->getOffset() < $this->moov->getOffset()) { + // Add constant 4096 bytes for free space to be used later + $this->moov->udta->meta->free->setSize(8 /* header */ + 4096); + ftruncate($fd, $newFileSize += 4096); + } else { + // Adjust free space to fill up the rest of the space + $this->moov->udta->meta->free->setSize + (8 + $oldFileSize - $newFileSize); + $newFileSize = $oldFileSize; + } + + /* Calculate positions */ + if ($this->mdat->getOffset() < $this->moov->getOffset()) { + $start = $this->mdat->getOffset(); + $until = $this->moov->getOffset(); + $where = $newFileSize; + $delta = $this->moov->getHeapSize(); + } else { + $start = $this->moov->getOffset(); + $until = $oldFileSize; + $where = $newFileSize; + $delta = $newFileSize - $oldFileSize; + } + + /* Move data to the end of the file */ + if ($newFileSize != $oldFileSize) { + for ($i = 1, $cur = $until; $cur > $start; $cur -= 1024, $i++) { + fseek + ($fd, $until - (($i * 1024) + + ($excess = $cur - 1024 > $start ? + 0 : $cur - $start - 1024))); + $buffer = fread($fd, 1024); + fseek($fd, $where - (($i * 1024) + $excess)); + fwrite($fd, $buffer, 1024); + } + } + + + /* Update stco/co64 to correspond the data move */ + foreach ($this->moov->getBoxesByIdentifier('trak') as $trak) { + $chunkOffsetBox = + (isset($trak->mdia->minf->stbl->stco) ? + $trak->mdia->minf->stbl->stco : + $trak->mdia->minf->stbl->co64); + $chunkOffsetTable = $chunkOffsetBox->getChunkOffsetTable(); + $chunkOffsetTableCount = count($chunkOffsetTable); + for ($i = 1; $i <= $chunkOffsetTableCount; $i++) { + $chunkOffsetTable[$i] += $delta; + } + $chunkOffsetBox->setChunkOffsetTable($chunkOffsetTable); + } + + /* Write moov box */ + fseek($fd, $start); + $this->moov->write(new Zend_Io_Writer($fd)); + fclose($fd); + } +} diff --git a/src/Zend/Media/Iso14496/Box.php b/src/Zend/Media/Iso14496/Box.php new file mode 100644 index 0000000..8582465 --- /dev/null +++ b/src/Zend/Media/Iso14496/Box.php @@ -0,0 +1,663 @@ + + * @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_Iso14496_Box +{ + /** + * The reader object. + * + * @var Reader + */ + protected $_reader; + + /** @var Array */ + private $_options; + + /** @var integer */ + private $_offset = -1; + + /** @var integer */ + private $_size = -1; + + /** @var string */ + private $_type; + + /** @var Zend_Media_Iso14496_Box */ + private $_parent = null; + + /** @var boolean */ + private $_container = false; + + /** @var Array */ + private $_boxes = array(); + + /** @var Array */ + private static $_path = array(); + + /** + * Constructs the class with given parameters and options. + * + * @param Zend_Io_Reader $reader The reader object. + * @param Array $options The options array. + */ + public function __construct($reader, &$options = array()) + { + if (($this->_reader = $reader) === null) { + $this->_type = strtolower(substr(get_class($this), -4)); + } else { + $this->_offset = $this->_reader->getOffset(); + $this->_size = $this->_reader->readUInt32BE(); + $this->_type = $this->_reader->read(4); + + if ($this->_size == 1) { + $this->_size = $this->_reader->readInt64BE(); + } + if ($this->_size == 0) { + $this->_size = $this->_reader->getSize() - $this->_offset; + } + if ($this->_type == 'uuid') { + $this->_type = $this->_reader->readGUID(); + } + } + $this->_options = &$options; + } + + public function __destruct() + { + unset($this->_boxes); + unset($this->_parent); + } + + /** + * Returns the options array. + * + * @return Array + */ + public final function &getOptions() + { + return $this->_options; + } + + /** + * Returns the given option value, or the default value if the option is not + * defined. + * + * @param string $option The name of the option. + * @param mixed $defaultValue The default value to be returned. + */ + public final function getOption($option, $defaultValue = null) + { + if (isset($this->_options[$option])) { + return $this->_options[$option]; + } + return $defaultValue; + } + + /** + * Sets the options array. See {@link Zend_Media_Id3v2} class for available + * options. + * + * @param Array $options The options array. + */ + public final function setOptions(&$options) + { + $this->_options = &$options; + } + + /** + * Sets the given option the given value. + * + * @param string $option The name of the option. + * @param mixed $value The value to set for the option. + */ + public final function setOption($option, $value) + { + $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]); + } + + /** + * Returns the file offset to box start, or -1 if the box was created on heap. + * + * @return integer + */ + public final function getOffset() + { + return $this->_offset; + } + + /** + * Sets the file offset where the box starts. + * + * @param integer $offset The file offset to box start. + */ + public final function setOffset($offset) + { + $this->_offset = $offset; + } + + /** + * Returns the box size in bytes read from the file, including the size and + * type header, fields, and all contained boxes, or -1 if the box was + * created on heap. + * + * @return integer + */ + public final function getSize() + { + return $this->_size; + } + + /** + * Sets the box size. The size must include the size and type header, + * fields, and all contained boxes. + * + * The method will propagate size change to box parents. + * + * @param integer $size The box size. + */ + protected final function setSize($size) + { + if ($this->_parent !== null) { + $this->_parent->setSize + (($this->_parent->getSize() > 0 ? + $this->_parent->getSize() : 0) + + $size - ($this->_size > 0 ? $this->_size : 0)); + } + $this->_size = $size; + } + + /** + * Returns the box type. + * + * @return string + */ + public final function getType() + { + return $this->_type; + } + + /** + * Sets the box type. + * + * @param string $type The box type. + */ + public final function setType($type) + { + $this->_type = $type; + } + + /** + * Returns the parent box containing this box. + * + * @return Zend_Media_Iso14496_Box + */ + public final function getParent() + { + return $this->_parent; + } + + /** + * Sets the parent containing box. + * + * @param Zend_Media_Iso14496_Box $parent The parent box. + */ + public function setParent(&$parent) + { + $this->_parent = $parent; + } + + /** + * Returns a boolean value corresponding to whether the box is a container. + * + * @return boolean + */ + public final function isContainer() + { + return $this->_container; + } + + /** + * Returns a boolean value corresponding to whether the box is a container. + * + * @return boolean + */ + public final function getContainer() + { + return $this->_container; + } + + /** + * Sets whether the box is a container. + * + * @param boolean $container Whether the box is a container. + */ + protected final function setContainer($container) + { + $this->_container = $container; + } + + /** + * Reads and constructs the boxes found within this box. + * + * @todo Does not parse iTunes internal ---- boxes. + */ + protected final function constructBoxes + ($defaultclassname = 'Zend_Media_Iso14496_Box') + { + $base = $this->getOption('base', ''); + if ($this->getType() != 'file') { + self::$_path[] = $this->getType(); + } + $path = implode(self::$_path, '.'); + + while (true) { + $offset = $this->_reader->getOffset(); + if ($offset >= $this->_offset + $this->_size) { + break; + } + $size = $this->_reader->readUInt32BE(); + $type = rtrim($this->_reader->read(4), ' '); + if ($size == 1) { + $size = $this->_reader->readInt64BE(); + } + if ($size == 0) { + $size = $this->_reader->getSize() - $offset; + } + + if (preg_match("/^\xa9?[a-z0-9]{3,4}$/i", $type) && + substr($base, 0, min(strlen($base), strlen + ($tmp = $path . ($path ? '.' : '') . $type))) == + substr($tmp, 0, min(strlen($base), strlen($tmp)))) + { + $this->_reader->setOffset($offset); + if (@fopen($filename = 'Zend/Media/Iso14496/Box/' . + ucfirst($type) . '.php', 'r', true) !== false) { + require_once($filename); + } + if (class_exists + ($classname = 'Zend_Media_Iso14496_Box_' . + ucfirst($type))) { + $box = new $classname($this->_reader, $this->_options); + } else { + $box = + new $defaultclassname($this->_reader, $this->_options); + } + $box->setParent($this); + if (!isset($this->_boxes[$box->getType()])) { + $this->_boxes[$box->getType()] = array(); + } + $this->_boxes[$box->getType()][] = $box; + } + $this->_reader->setOffset($offset + $size); + } + + array_pop(self::$_path); + } + + /** + * Checks whether the box given as an argument is present in the file. Returns + * true if one or more boxes are present, false + * otherwise. + * + * @param string $identifier The box identifier. + * @return boolean + * @throws Zend_Media_Iso14496_Exception if called on a non-container box + */ + public final function hasBox($identifier) + { + if (!$this->isContainer()) { + require_once 'Zend/Media/Iso14496/Exception.php'; + throw new Zend_Media_Iso14496_Exception('Box not a container'); + } + return isset($this->_boxes[$identifier]); + } + + /** + * Returns all the boxes the file contains as an associate array. The box + * identifiers work as keys having an array of boxes as associated value. + * + * @return Array + * @throws Zend_Media_Iso14496_Exception if called on a non-container box + */ + public final function getBoxes() + { + if (!$this->isContainer()) { + require_once 'Zend/Media/Iso14496/Exception.php'; + throw new Zend_Media_Iso14496_Exception('Box not a container'); + } + return $this->_boxes; + } + + /** + * Returns an array of boxes matching the given identifier or an empty array + * if no boxes 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 box with the identifier given. Wildcards cannot be used + * with the shorthand and they will not work with user defined uuid types. + * + * @param string $identifier The box identifier. + * @return Array + * @throws Zend_Media_Iso14496_Exception if called on a non-container box + */ + public final function getBoxesByIdentifier($identifier) + { + if (!$this->isContainer()) { + require_once 'Zend/Media/Iso14496/Exception.php'; + throw new Zend_Media_Iso14496_Exception('Box not a container'); + } + $matches = array(); + $searchPattern = "/^" . + str_replace(array("*", "?"), array(".*", "."), $identifier) . "$/i"; + foreach ($this->_boxes as $identifier => $boxes) { + if (preg_match($searchPattern, $identifier)) { + foreach ($boxes as $box) { + $matches[] = $box; + } + } + } + return $matches; + } + + /** + * Removes any boxes matching the given box 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 box identifier. + * @throws Zend_Media_Iso14496_Exception if called on a non-container box + */ + public final function removeBoxesByIdentifier($identifier) + { + if (!$this->isContainer()) { + require_once 'Zend/Media/Iso14496/Exception.php'; + throw new Zend_Media_Iso14496_Exception("Box not a container"); + } + $searchPattern = "/^" . + str_replace(array("*", "?"), array(".*", "."), $identifier) . "$/i"; + foreach ($this->_objects as $identifier => $objects) { + if (preg_match($searchPattern, $identifier)) { + unset($this->_objects[$identifier]); + } + } + } + + /** + * Adds a new box into the current box and returns it. + * + * @param Zend_Media_Iso14496_Box $box The box to add + * @return Zend_Media_Iso14496_Box + * @throws Zend_Media_Iso14496_Exception if called on a non-container box + */ + public final function addBox(&$box) + { + if (!$this->isContainer()) { + require_once 'Zend/Media/Iso14496/Exception.php'; + throw new Zend_Media_Iso14496_Exception('Box not a container'); + } + $box->setParent($this); + $box->setOptions($this->_options); + if (!$this->hasBox($box->getType())) { + $this->_boxes[$box->getType()] = array(); + } + return $this->_boxes[$box->getType()][] = $box; + } + + /** + * Removes the given box. + * + * @param Zend_Media_Iso14496_Box $box The box to remove + * @throws Zend_Media_Iso14496_Exception if called on a non-container box + */ + public final function removeBox($box) + { + if (!$this->isContainer()) { + require_once 'Zend/Media/Iso14496/Exception.php'; + throw new Zend_Media_Iso14496_Exception('Box not a container'); + } + if ($this->hasBox($box->getType())) { + foreach ($this->_boxes[$box->getType()] as $key => $value) { + if ($box === $value) { + unset($this->_boxes[$box->getType()][$key]); + } + } + } + } + + /** + * Returns the number of boxes this box contains. + * + * @return integer + */ + public final function getBoxCount() + { + if (!$this->isContainer()) { + return 0; + } + return count($this->_boxes); + } + + /** + * Magic function so that $obj->value will work. If called on a container box, + * the method will first attempt to return the first contained box that + * matches the identifier, and if not found, invoke a getter method. + * + * If there are no boxes or getter methods with given name, the method + * attempts to create a frame with given identifier. + * + * If none of these work, an exception is thrown. + * + * @param string $name The box or field name. + * @return mixed + */ + public function __get($name) + { + if ($this->isContainer() && + isset($this->_boxes[str_pad($name, 4, ' ')])) { + return $this->_boxes[str_pad($name, 4, ' ')][0]; + } + if (method_exists($this, 'get' . ucfirst($name))) { + return call_user_func(array($this, 'get' . ucfirst($name))); + } + if (@fopen($filename = 'Zend/Media/Iso14496/Box/' . + ucfirst($name) . '.php', 'r', true) !== false) { + require_once($filename); + } + if (class_exists + ($classname = 'Zend_Media_Iso14496_Box_' . ucfirst($name))) { + return $this->addBox(new $classname()); + } + require_once 'Zend/Media/Iso14496/Exception.php'; + throw new Zend_Media_Iso14496_Exception('Unknown box/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($name))) { + call_user_func(array($this, 'set' . ucfirst($name)), $value); + } else { + require_once 'Zend/Media/Iso14496/Exception.php'; + throw new Zend_Media_Iso14496_Exception('Unknown field: ' . $name); + } + } + + /** + * Magic function so that isset($obj->value) will work. This method checks + * whether the box is a container and contains a box that matches the + * identifier. + * + * @param string $name The box name. + * @return boolean + */ + public function __isset($name) + { + return ($this->isContainer() && isset($this->_boxes[$name])); + } + + /** + * Magic function so that unset($obj->value) will work. This method removes + * all the boxes from this container that match the identifier. + * + * @param string $name The box name. + */ + public function __unset($name) + { + if ($this->isContainer()) { + unset($this->_boxes[$name]); + } + } + + /** + * Returns the box heap size in bytes, including the size and + * type header, fields, and all contained boxes. The box size is updated to + * reflect that of the heap size upon write. Subclasses should overwrite + * this method and call the parent method to get the calculated header and + * subbox sizes and then add their own bytes to that. + * + * @return integer + */ + public function getHeapSize() + { + $size = 8; + if ($this->isContainer()) { + foreach ($this->getBoxes() as $name => $boxes) { + foreach ($boxes as $box) { + $size += $box->getHeapSize(); + } + } + } + if ($size > 0xffffffff) { + $size += 8; + } + if (strlen($this->_type) > 4) { + $size += 16; + } + return $size; + } + + /** + * Writes the box header. Subclasses should overwrite this method and call + * the parent method first and then write the box related data. + * + * @param Zend_Io_Writer $writer The writer object. + * @return void + */ + protected function _writeData($writer) + { + if (get_class($this) == "Zend_Media_Iso14496_Box") { + require_once 'Zend/Media/Iso14496/Exception.php'; + throw new Zend_Media_Iso14496_Exception + ('Unknown box \'' . $this->getType() . '\' cannot be written.'); + } + + $this->_size = $this->getHeapSize(); + if ($this->_size > 0xffffffff) { + $writer->writeUInt32BE(1); + } else { + $writer->writeUInt32BE($this->_size); + } + if (strlen($this->_type) > 4) { + $writer->write('uuid'); + } else { + $writer->write($this->_type); + } + if ($this->_size > 0xffffffff) { + $writer->writeInt64BE($this->_size); + } + if (strlen($this->_type) > 4) { + $writer->writeGuid($this->_type); + } + } + + /** + * Writes the frame data with the header. + * + * @param Zend_Io_Writer $writer The writer object. + * @return void + */ + public function write($writer) + { + if (get_class($this) == "Zend_Media_Iso14496_Box") { + require_once 'Zend/Media/Iso14496/Exception.php'; + throw new Zend_Media_Iso14496_Exception + ('Unknown box \'' . $this->getType() . '\' cannot be written.'); + } + + $this->_writeData($writer); + if ($this->isContainer()) { + foreach ($this->getBoxes() as $name => $boxes) { + foreach ($boxes as $box) { + $box->write($writer); + } + } + } + } +} diff --git a/src/Zend/Media/Iso14496/Box/Elst.php b/src/Zend/Media/Iso14496/Box/Elst.php index 2d0aff1..fa4324b 100644 --- a/src/Zend/Media/Iso14496/Box/Elst.php +++ b/src/Zend/Media/Iso14496/Box/Elst.php @@ -67,7 +67,7 @@ final class Zend_Media_Iso14496_Box_Elst extends Zend_Media_Iso14496_FullBox parent::__construct($reader, $options); $entryCount = $this->_reader->readUInt32BE(); - for ($i = 1; $i <= $entryCount; $i++) { + for ($i = 0; $i < $entryCount; $i++) { $entry = array(); if ($this->getVersion() == 1) { $entry['segmentDuration'] = $this->_reader->readInt64BE(); @@ -77,8 +77,8 @@ final class Zend_Media_Iso14496_Box_Elst extends Zend_Media_Iso14496_FullBox $entry['mediaTime'] = $this->_reader->readInt32BE(); } $entry['mediaRate'] = - ((($tmp = $this->_reader->readUInt32BE()) >> 16) & 0xffff) + - (float)("0." . ((string)($tmp & 0xffff))); + (float)($this->_reader->readInt16BE() . "." . + $this->_reader->readInt16BE()); $this->_entries[] = $entry; } } @@ -152,19 +152,18 @@ final class Zend_Media_Iso14496_Box_Elst extends Zend_Media_Iso14496_FullBox { parent::_writeData($writer); $writer->writeUInt32BE($entryCount = count($this->_entries)); - for ($i = 1; $i <= $entryCount; $i++) { + for ($i = 0; $i < $entryCount; $i++) { if ($this->getVersion() == 1) { $writer->writeInt64BE($this->_entries[$i]['segmentDuration']) ->writeInt64BE($this->_entries[$i]['mediaTime']); } else { $writer->writeUInt32BE($this->_entries[$i]['segmentDuration']) - ->writeUInt32BE($this->_entries[$i]['mediaTime']); + ->writeInt32BE($this->_entries[$i]['mediaTime']); } - @list(, $mediaRateDecimals) = explode + @list($mediaRateInteger, $mediaRateFraction) = explode ('.', (float)$this->_entries[$i]['mediaRate']); - $writer->writeUInt32BE - (floor($this->_entries[$i]['mediaRate']) << 16 | - $mediaRateDecimals); + $writer->writeInt16BE($mediaRateInteger) + ->writeInt16BE($mediaRateFraction); } } } diff --git a/src/Zend/Media/Iso14496/Box/Free.php b/src/Zend/Media/Iso14496/Box/Free.php index 83cc148..927f0a9 100644 --- a/src/Zend/Media/Iso14496/Box/Free.php +++ b/src/Zend/Media/Iso14496/Box/Free.php @@ -70,7 +70,7 @@ final class Zend_Media_Iso14496_Box_Free extends Zend_Media_Iso14496_Box */ public function getHeapSize() { - return parent::getHeapSize() + $this->getSize(); + return ($this->getSize() >= 8 ? $this->getSize() : 0); } /** @@ -81,7 +81,9 @@ final class Zend_Media_Iso14496_Box_Free extends Zend_Media_Iso14496_Box */ protected function _writeData($writer) { - parent::_writeData($writer); - $writer->write(str_repeat("\0", $this->getSize())); + if ($this->getSize() >= 8) { + parent::_writeData($writer); + $writer->write(str_repeat("\0", $this->getSize() - 8)); + } } } diff --git a/src/Zend/Media/Iso14496/Box/Sbgp.php b/src/Zend/Media/Iso14496/Box/Sbgp.php new file mode 100644 index 0000000..9cd4efe --- /dev/null +++ b/src/Zend/Media/Iso14496/Box/Sbgp.php @@ -0,0 +1,197 @@ +Sample To Group Box table can be used to find the group that a + * sample belongs to and the associated description of that sample group. The + * table is compactly coded with each entry giving the index of the first sample + * of a run of samples with the same sample group descriptor. The sample group + * description ID is an index that refers to a + * {@link Zend_Media_Iso14496_Box_Sgpd Sample Group Description Box}, which + * contains entries describing the characteristics of each sample group. + * + * There may be multiple instances of this box if there is more than one sample + * grouping for the samples in a track. Each instance of the Sample To Group Box + * has a type code that distinguishes different sample groupings. Within a + * track, there shall be at most one instance of this box with a particular + * grouping type. The associated Sample Group Description shall indicate the + * same value for the grouping type. + * + * @category Zend + * @package Zend_Media + * @subpackage ISO 14496 + * @author Sven Vollbehr + * @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_Iso14496_Box_Sbgp extends Zend_Media_Iso14496_FullBox +{ + /** @var integer */ + private $_groupingType; + + /** @var Array */ + private $_sampleToGroupTable = array(); + + /** + * Constructs the class with given parameters and reads box related data + * from the ISO Base Media file. + * + * @param Zend_Io_Reader $reader The reader object. + * @param Array $options The options array. + */ + public function __construct($reader, &$options = array()) + { + parent::__construct($reader, $options); + + $groupingType = $this->_reader->readUInt32BE(); + $entryCount = $this->_reader->readUInt32BE(); + for ($i = 1; $i <= $entryCount; $i++) { + $this->_sampleToGroupTable[$i] = array + ('sampleCount' => $this->_reader->readUInt32BE(), + 'groupDescriptionIndex' => $this->_reader->readUInt32BE()); + } + } + + /** + * Returns the grouping type that identifies the type (i.e. criterion used + * to form the sample groups) of the sample grouping and links it to its + * sample group description table with the same value for grouping type. At + * most one occurrence of this box with the same value for groupingType + * shall exist for a track. + * + * @return integer + */ + public function getGroupingType() + { + return $this->_groupingType; + } + + /** + * Sets the grouping type that identifies the type (i.e. criterion used + * to form the sample groups) of the sample grouping and links it to its + * sample group description table with the same value for grouping type. At + * most one occurrence of this box with the same value for groupingType + * shall exist for a track. + * + * @param integer $groupingType The grouping type. + */ + public function setGroupingType($groupingType) + { + $this->_groupingType = $groupingType; + } + + /** + * Returns an array of values. Each entry is an array containing the + * following keys. + * o sampleCount -- an integer that gives the number of consecutive + * samples with the same sample group descriptor. If the sum of the + * sample count in this box is less than the total sample count, then + * the reader should effectively extend it with an entry that associates + * the remaining samples with no group. It is an error for the total in + * this box to be greater than the sample_count documented elsewhere, + * and the reader behavior would then be undefined. + * o groupDescriptionIndex -- an integer that gives the index of the + * sample group entry which describes the samples in this group. The + * index ranges from 1 to the number of sample group entries in the + * {@link Zend_Media_Iso14496_Box_Sgpd Sample Group Description Box}, + * or takes the value 0 to indicate that this sample is a member of no + * group of this type. + * + * @return Array + */ + public function getSampleToGroupTable() + { + return $this->_sampleToGroupTable; + } + + /** + * Sets the array of values. Each entry must be an array containing the + * following keys. + * o sampleCount -- an integer that gives the number of consecutive + * samples with the same sample group descriptor. If the sum of the + * sample count in this box is less than the total sample count, then + * the reader should effectively extend it with an entry that associates + * the remaining samples with no group. It is an error for the total in + * this box to be greater than the sample_count documented elsewhere, + * and the reader behavior would then be undefined. + * o groupDescriptionIndex -- an integer that gives the index of the + * sample group entry which describes the samples in this group. The + * index ranges from 1 to the number of sample group entries in the + * {@link Zend_Media_Iso14496_Box_Sgpd Sample Group Description Box}, + * or takes the value 0 to indicate that this sample is a member of no + * group of this type. + * + * @param Array $sampleToGroupTable The array of entries + */ + public function setSampleToGroupTable($sampleToGroupTable) + { + $this->_sampleToGroupTable = $sampleToGroupTable; + } + + /** + * Returns the box heap size in bytes. + * + * @return integer + */ + public function getHeapSize() + { + return parent::getHeapSize() + 8 + + count($this->_sampleToGroupTable) * 8; + } + + /** + * Writes the box data. + * + * @param Zend_Io_Writer $writer The writer object. + * @return void + */ + protected function _writeData($writer) + { + parent::_writeData($writer); + $writer->writeUInt32BE($this->_groupingType); + $writer->writeUInt32BE($entryCount = count($this->_sampleToGroupTable)); + for ($i = 1; $i <= $entryCount; $i++) { + $writer->writeUInt32BE + ($this->_sampleToGroupTable[$i]['sampleCount']) + ->writeUInt32BE + ($this->_sampleToGroupTable[$i] + ['groupDescriptionIndex']); + } + } +} diff --git a/src/Zend/Media/Iso14496/Box/Schm.php b/src/Zend/Media/Iso14496/Box/Schm.php new file mode 100644 index 0000000..dd8c1a4 --- /dev/null +++ b/src/Zend/Media/Iso14496/Box/Schm.php @@ -0,0 +1,177 @@ +Scheme Type Box identifies the protection scheme. + * + * @category Zend + * @package Zend_Media + * @subpackage ISO 14496 + * @author Sven Vollbehr + * @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_Iso14496_Box_Schm extends Zend_Media_Iso14496_FullBox +{ + /** @var string */ + private $_schemeType; + + /** @var integer */ + private $_schemeVersion; + + /** @var string */ + private $_schemeUri; + + /** + * Constructs the class with given parameters and reads box related data + * from the ISO Base Media file. + * + * @param Zend_Io_Reader $reader The reader object. + * @param Array $options The options array. + */ + public function __construct($reader, &$options = array()) + { + parent::__construct($reader, $options); + + $this->_schemeType = $this->_reader->read(4); + $this->_schemeVersion = $this->_reader->readUInt32BE(); + if ($this->hasFlag(1)) { + $this->_schemeUri = preg_split + ("/\\x00/", $this->_reader->read + ($this->getOffset() + $this->getSize() - + $this->_reader->getOffset())); + } + } + + /** + * Returns the code defining the protection scheme. + * + * @return string + */ + public function getSchemeType() + { + return $this->_schemeType; + } + + /** + * Sets the code defining the protection scheme. + * + * @param string $schemeType The scheme type. + */ + public function setSchemeType($schemeType) + { + $this->_schemeType = $schemeType; + } + + /** + * Returns the version of the scheme used to create the content. + * + * @return integer + */ + public function getSchemeVersion() + { + return $this->_schemeVersion; + } + + /** + * Sets the version of the scheme used to create the content. + * + * @param integer $schemeVersion The scheme version. + */ + public function setSchemeVersion($schemeVersion) + { + $this->_schemeVersion = $schemeVersion; + } + + /** + * Returns the optional scheme address to allow for the option of directing + * the user to a web-page if they do not have the scheme installed on their + * system. It is an absolute URI. + * + * @return string + */ + public function getSchemeUri() + { + return $this->_schemeUri; + } + + /** + * Sets the optional scheme address to allow for the option of directing + * the user to a web-page if they do not have the scheme installed on their + * system. It is an absolute URI. + * + * @param string $schemeUri The scheme URI. + */ + public function setSchemeUri($schemeUri) + { + $this->_schemeUri = $schemeUri; + if ($schemeUri === null) { + $this->setFlags(0); + } else { + $this->setFlags(1); + } + } + + /** + * Returns the box heap size in bytes. + * + * @return integer + */ + public function getHeapSize() + { + return parent::getHeapSize() + 8 + + ($this->hasFlag(1) ? strlen($this->_schemeUri) + 1 : 0); + } + + /** + * Writes the box data. + * + * @param Zend_Io_Writer $writer The writer object. + * @return void + */ + protected function _writeData($writer) + { + parent::_writeData($writer); + $writer->write($this->_schemeType); + $writer->writeUInt32BE($this->_schemeVersion); + if ($this->hasFlag(1)) { + $writer->writeString8($this->_schemeUri, 1); + } + } +} diff --git a/src/Zend/Media/Iso14496/Box/Sdtp.php b/src/Zend/Media/Iso14496/Box/Sdtp.php new file mode 100644 index 0000000..307ba19 --- /dev/null +++ b/src/Zend/Media/Iso14496/Box/Sdtp.php @@ -0,0 +1,187 @@ +Independent and Disposable Samples Box optional table answers + * three questions about sample dependency: + * 1) does this sample depend on others (is it an I-picture)? + * 2) do no other samples depend on this one? + * 3) does this sample contain multiple (redundant) encodings of the data at + * this time-instant (possibly with different dependencies)? + * + * In the absence of this table: + * 1) the sync sample table answers the first question; in most video codecs, + * I-pictures are also sync points, + * 2) the dependency of other samples on this one is unknown. + * 3) the existence of redundant coding is unknown. + * + * When performing trick modes, such as fast-forward, it is possible to use the + * first piece of information to locate independently decodable samples. + * Similarly, when performing random access, it may be necessary to locate the + * previous sync point or random access recovery point, and roll-forward from + * the sync point or the pre-roll starting point of the random access recovery + * point to the desired point. While rolling forward, samples on which no others + * depend need not be retrieved or decoded. + * + + * The value of sampleIsDependedOn is independent of the existence of redundant + * codings. However, a redundant coding may have different dependencies from the + * primary coding; if redundant codings are available, the value of + * sampleDependsOn documents only the primary coding. + * + * A sample dependency Box may also occur in the + * {@link Zend_Media_Iso14496_Box_Traf Track Fragment Box}. + * + * @category Zend + * @package Zend_Media + * @subpackage ISO 14496 + * @author Sven Vollbehr + * @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_Iso14496_Box_Sdtp extends Zend_Media_Iso14496_FullBox +{ + /** @var Array */ + private $_sampleDependencyTypeTable = array(); + + /** + * Constructs the class with given parameters and reads box related data + * from the ISO Base Media file. + * + * @param Zend_Io_Reader $reader The reader object. + * @param Array $options The options array. + */ + public function __construct($reader, &$options = array()) + { + parent::__construct($reader, $options); + + $data = $this->_reader->read + ($this->getOffset() + $this->getSize() - + $this->_reader->getOffset()); + $dataSize = strlen($data); + for ($i = 1; $i <= $dataSize; $i++) { + $this->_sampleDependencyTypeTable[$i] = array + ('sampleDependsOn' => (($tmp = ord($data[$i - 1])) >> 4) & 0x3, + 'sampleIsDependedOn' => ($tmp >> 2) & 0x3, + 'sampleHasRedundancy' => $tmp & 0x3); + } + } + + /** + * Returns an array of values. Each entry is an array containing the + * following keys. + * o sampleDependsOn -- takes one of the following four values: + * 0: the dependency of this sample is unknown; + * 1: this sample does depend on others (not an I picture); + * 2: this sample does not depend on others (I picture); + * 3: reserved + * o sampleIsDependedOn -- takes one of the following four values: + * 0: the dependency of other samples on this sample is unknown; + * 1: other samples depend on this one (not disposable); + * 2: no other sample depends on this one (disposable); + * 3: reserved + * o sampleHasRedundancy -- takes one of the following four values: + * 0: it is unknown whether there is redundant coding in this sample; + * 1: there is redundant coding in this sample; + * 2: there is no redundant coding in this sample; + * 3: reserved + * + * @return Array + */ + public function getSampleDependencyTypeTable() + { + return $this->_sampleDependencyTypeTable; + } + + /** + * Sets the array of values. Each entry must be an array containing the + * following keys. + * o sampleDependsOn -- takes one of the following four values: + * 0: the dependency of this sample is unknown; + * 1: this sample does depend on others (not an I picture); + * 2: this sample does not depend on others (I picture); + * 3: reserved + * o sampleIsDependedOn -- takes one of the following four values: + * 0: the dependency of other samples on this sample is unknown; + * 1: other samples depend on this one (not disposable); + * 2: no other sample depends on this one (disposable); + * 3: reserved + * o sampleHasRedundancy -- takes one of the following four values: + * 0: it is unknown whether there is redundant coding in this sample; + * 1: there is redundant coding in this sample; + * 2: there is no redundant coding in this sample; + * 3: reserved + * + * @param Array $sampleDependencyTypeTable The array of values + */ + public function setSampleDependencyTypeTable($sampleDependencyTypeTable) + { + $this->_sampleDependencyTypeTable = $sampleDependencyTypeTable; + } + + /** + * Returns the box heap size in bytes. + * + * @return integer + */ + public function getHeapSize() + { + return parent::getHeapSize() + count($this->_sampleDependencyTypeTable); + } + + /** + * Writes the box data. + * + * @param Zend_Io_Writer $writer The writer object. + * @return void + */ + protected function _writeData($writer) + { + parent::_writeData($writer); + for ($i = 1; $i <= count($this->_sampleDependencyTypeTable); $i++) { + $writer->write(chr( + (($this->_sampleDependencyTypeTable[$i] + ['sampleDependsOn'] & 0x3) << 4) | + (($this->_sampleDependencyTypeTable[$i] + ['sampleIsDependedOn'] & 0x3) << 2) | + (($this->_sampleDependencyTypeTable[$i] + ['sampleHasRedundancy'] & 0x3)))); + } + } +} diff --git a/src/Zend/Media/Iso14496/Exception.php b/src/Zend/Media/Iso14496/Exception.php new file mode 100644 index 0000000..2a0dfa0 --- /dev/null +++ b/src/Zend/Media/Iso14496/Exception.php @@ -0,0 +1,49 @@ + + * @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_Iso14496_Exception extends Exception +{ +} diff --git a/src/Zend/Media/Iso14496/FullBox.php b/src/Zend/Media/Iso14496/FullBox.php new file mode 100644 index 0000000..6c5a3e5 --- /dev/null +++ b/src/Zend/Media/Iso14496/FullBox.php @@ -0,0 +1,150 @@ + + * @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$ + */ +abstract class Zend_Media_Iso14496_FullBox extends Zend_Media_Iso14496_Box +{ + /** @var integer */ + protected $_version = 0; + + /** @var integer */ + protected $_flags = 0; + + /** + * Constructs the class with given parameters and reads box related data + * from the ISO Base Media file. + * + * @param Zend_Io_Reader $reader The reader object. + */ + public function __construct($reader = null, &$options = array()) + { + parent::__construct($reader, $options); + + if ($reader === null) { + return; + } + + $this->_version = + (($field = $this->_reader->readUInt32BE()) >> 24) & 0xff; + $this->_flags = $field & 0xffffff; + } + + /** + * Returns the version of this format of the box. + * + * @return integer + */ + public function getVersion() + { + return $this->_version; + } + + /** + * Sets the version of this format of the box. + * + * @param integer $version The version. + */ + public function setVersion($version) + { + $this->_version = $version; + } + + /** + * Checks whether or not the flag is set. Returns true if the + * flag is set, false otherwise. + * + * @param integer $flag The flag to query. + * @return boolean + */ + public function hasFlag($flag) + { + return ($this->_flags & $flag) == $flag; + } + + /** + * Returns the map of flags. + * + * @return integer + */ + public function getFlags() + { + return $this->_flags; + } + + /** + * Sets the map of flags. + * + * @param string $flags The map of flags. + */ + public function setFlags($flags) + { + $this->_flags = $flags; + } + + /** + * Returns the box heap size in bytes. + * + * @return integer + */ + public function getHeapSize() + { + return parent::getHeapSize() + 4; + } + + /** + * Writes the box data without the header. + * + * @param Zend_Io_Writer $writer The writer object. + * @return void + */ + protected function _writeData($writer) + { + parent::_writeData($writer); + $writer->writeUInt32BE($this->_version << 24 | $this->_flags); + } +}