From 8bdcf4209a32bdf5ea2f4dd994d5d30197d75255 Mon Sep 17 00:00:00 2001 From: svollbehr Date: Sat, 11 Jun 2011 16:46:52 +0000 Subject: [PATCH] Add read support for FLAC metadata blocks git-svn-id: http://php-reader.googlecode.com/svn/trunk@241 51a70ab9-7547-0410-9469-37e369ee0574 --- src/Zend/Media/Flac.php | 245 ++++++++++++++++++ src/Zend/Media/Flac/Exception.php | 40 +++ src/Zend/Media/Flac/MetadataBlock.php | 173 +++++++++++++ .../Media/Flac/MetadataBlock/Application.php | 77 ++++++ .../Media/Flac/MetadataBlock/Cuesheet.php | 189 ++++++++++++++ src/Zend/Media/Flac/MetadataBlock/Padding.php | 53 ++++ src/Zend/Media/Flac/MetadataBlock/Picture.php | 199 ++++++++++++++ .../Media/Flac/MetadataBlock/Seektable.php | 82 ++++++ .../Media/Flac/MetadataBlock/Streaminfo.php | 185 +++++++++++++ .../Flac/MetadataBlock/VorbisComment.php | 155 +++++++++++ src/Zend/Media/Id3/Frame/Apic.php | 2 +- src/Zend/Media/Vorbis/Header/Comment.php | 43 ++- 12 files changed, 1431 insertions(+), 12 deletions(-) create mode 100644 src/Zend/Media/Flac.php create mode 100644 src/Zend/Media/Flac/Exception.php create mode 100644 src/Zend/Media/Flac/MetadataBlock.php create mode 100644 src/Zend/Media/Flac/MetadataBlock/Application.php create mode 100644 src/Zend/Media/Flac/MetadataBlock/Cuesheet.php create mode 100644 src/Zend/Media/Flac/MetadataBlock/Padding.php create mode 100644 src/Zend/Media/Flac/MetadataBlock/Picture.php create mode 100644 src/Zend/Media/Flac/MetadataBlock/Seektable.php create mode 100644 src/Zend/Media/Flac/MetadataBlock/Streaminfo.php create mode 100644 src/Zend/Media/Flac/MetadataBlock/VorbisComment.php diff --git a/src/Zend/Media/Flac.php b/src/Zend/Media/Flac.php new file mode 100644 index 0000000..4ce6e3d --- /dev/null +++ b/src/Zend/Media/Flac.php @@ -0,0 +1,245 @@ + + * @copyright Copyright (c) 2005-2011 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_Flac +{ + /** The streaminfo metadata block */ + const STREAMINFO = 0; + + /** The padding metadata block */ + const PADDING = 1; + + /** The application metadata block */ + const APPLICATION = 2; + + /** The seektable metadata block */ + const SEEKTABLE = 3; + + /** The vorbis comment metadata block */ + const VORBIS_COMMENT = 4; + + /** The cuesheet metadata block */ + const CUESHEET = 5; + + /** The picture metadata block */ + const PICTURE = 6; + + /** @var Zend_Io_Reader */ + private $_reader; + + /** @var Array */ + private $_metadataBlocks = array(); + + /** @var string */ + private $_filename = null; + + /** + * Constructs the class with given filename. + * + * @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. + * @throws Zend_Io_Exception if an error occur in stream handling. + * @throws Zend_Media_Flac_Exception if an error occurs in vorbis bitstream reading. + */ + public function __construct($filename) + { + if ($filename instanceof Zend_Io_Reader) { + $this->_reader = &$filename; + } else { + $this->_filename = $filename; + 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/Flac/Exception.php'; + throw new Zend_Media_Flac_Exception($e->getMessage()); + } + } + + $capturePattern = $this->_reader->read(4); + if ($capturePattern != 'fLaC') { + require_once 'Zend/Media/Flac/Exception.php'; + throw new Zend_Media_Flac_Exception('Not a valid FLAC bitstream'); + } + + while (true) { + $offset = $this->_reader->getOffset(); + $last = ($tmp = $this->_reader->readUInt8()) >> 7 & 0x1; + $type = $tmp & 0x7f; + $size = $this->_reader->readUInt24BE(); + + $this->_reader->setOffset($offset); + switch ($type) { + case self::STREAMINFO: // 0 + require_once 'Zend/Media/Flac/MetadataBlock/Streaminfo.php'; + $this->_metadataBlocks[] = new Zend_Media_Flac_MetadataBlock_Streaminfo($this->_reader); + break; + case self::PADDING: // 1 + require_once 'Zend/Media/Flac/MetadataBlock/Padding.php'; + $this->_metadataBlocks[] = new Zend_Media_Flac_MetadataBlock_Padding($this->_reader); + break; + case self::APPLICATION: // 2 + require_once 'Zend/Media/Flac/MetadataBlock/Application.php'; + $this->_metadataBlocks[] = new Zend_Media_Flac_MetadataBlock_Application($this->_reader); + break; + case self::SEEKTABLE: // 3 + require_once 'Zend/Media/Flac/MetadataBlock/Seektable.php'; + $this->_metadataBlocks[] = new Zend_Media_Flac_MetadataBlock_Seektable($this->_reader); + break; + case self::VORBIS_COMMENT: // 4 + require_once 'Zend/Media/Flac/MetadataBlock/VorbisComment.php'; + $this->_metadataBlocks[] = new Zend_Media_Flac_MetadataBlock_VorbisComment($this->_reader); + break; + case self::CUESHEET: // 5 + require_once 'Zend/Media/Flac/MetadataBlock/Cuesheet.php'; + $this->_metadataBlocks[] = new Zend_Media_Flac_MetadataBlock_Cuesheet($this->_reader); + break; + case self::PICTURE: // 6 + require_once 'Zend/Media/Flac/MetadataBlock/Picture.php'; + $this->_metadataBlocks[] = new Zend_Media_Flac_MetadataBlock_Picture($this->_reader); + break; + default: + // break intentionally omitted + } + $this->_reader->setOffset($offset + 4 /* header */ + $size); + + // Jump off the loop if we reached the end of metadata blocks + if ($last === 1) { + break; + } + } + } + + /** + * Checks whether the given metadata block is there. Returns true if one ore more frames are present, + * false otherwise. + * + * @param string $type The metadata block type. + * @return boolean + */ + public function hasMetadataBlock($type) + { + $metadataBlockCount = count($this->_metadataBlocks); + for ($i = 0; $i < $metadataBlockCount; $i++) { + if ($this->_metadataBlocks[$i]->getType() === $type) { + return true; + } + } + return false; + } + + /** + * Returns all the metadata blocks as an associate array. + * + * @return Array + */ + public function getMetadataBlocks() + { + return $this->_metadataBlocks; + } + + /** + * Returns an array of metadata blocks frames matching the given type or an empty array if no metadata blocks + * matched the type. + * + * Please note that one may also use the shorthand $obj->type to access the first metadata block with the given + * type. + * + * @param string $type The metadata block type. + * @return Array + */ + public function getMetadataBlocksByType($type) + { + $matches = array(); + $metadataBlockCount = count($this->_metadataBlocks); + for ($i = 0; $i < $metadataBlockCount; $i++) { + if ($this->_metadataBlocks[$i]->getType() === $type) { + $matches[] = $this->_metadataBlocks[$i]; + } + } + return $matches; + } + + /** + * 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($name))) { + return call_user_func + (array($this, 'get' . ucfirst($name))); + } + if (defined($constant = 'self::' . strtoupper(preg_replace('/(?<=[a-z])[A-Z]/', '_$0', $name)))) { + $metadataBlocks = $this->getMetadataBlocksByType(constant($constant)); + if (isset($metadataBlocks[0])) { + return $metadataBlocks[0]; + } + } + require_once 'Zend/Media/Flac/Exception.php'; + throw new Zend_Media_Flac_Exception('Unknown metadata block or 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/Media/Flac/Exception.php'); + throw new Zend_Media_Flac_Exception('Unknown field: ' . $name); + } + } +} diff --git a/src/Zend/Media/Flac/Exception.php b/src/Zend/Media/Flac/Exception.php new file mode 100644 index 0000000..5c28c8e --- /dev/null +++ b/src/Zend/Media/Flac/Exception.php @@ -0,0 +1,40 @@ + + * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id$ + */ +class Zend_Media_Flac_Exception extends Zend_Media_Exception +{} diff --git a/src/Zend/Media/Flac/MetadataBlock.php b/src/Zend/Media/Flac/MetadataBlock.php new file mode 100644 index 0000000..079634e --- /dev/null +++ b/src/Zend/Media/Flac/MetadataBlock.php @@ -0,0 +1,173 @@ + + * @copyright Copyright (c) 2005-2011 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_Flac_MetadataBlock +{ + /** + * The reader object. + * + * @var Zend_Io_Reader + */ + protected $_reader; + + /** @var integer */ + private $_last; + + /** @var integer */ + private $_type; + + /** @var integer */ + private $_size; + + /** + * Constructs the class with given parameters and reads object related data + * from the Flac bitstream. + * + * @param Zend_Io_Reader $reader The reader object. + */ + public function __construct($reader) + { + $this->_reader = $reader; + + $this->_last = ($tmp = $this->_reader->readUInt8()) >> 7 & 0x1; + $this->_type = $tmp & 0x7f; + $this->_size = $this->_reader->readUInt24BE(); + } + + /** + * Returns the metadata block type. The type is one of the following. + * + * o 0: STREAMINFO + * o 1: PADDING + * o 2: APPLICATION + * o 3: SEEKTABLE + * o 4: VORBIS_COMMENT + * o 5: CUESHEET + * o 6: PICTURE + * o 7-126: reserved + * o 127: invalid, to avoid confusion with a frame sync code + * + * @return integer + */ + public function getType() + { + return $this->_type; + } + + /** + * Returns the metadata block length without the header, in bytes. + * + * @return integer + */ + public function getSize() + { + return $this->_size; + } + + /** + * 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($name))) { + return call_user_func(array($this, 'get' . ucfirst($name))); + } else { + require_once 'Zend/Media/Flac/Exception.php'; + throw new Zend_Media_Flac_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($name))) { + call_user_func(array($this, 'set' . ucfirst($name)), $value); + } else { + require_once 'Zend/Media/Flac/Exception.php'; + throw new Zend_Media_Flac_Exception('Unknown field: ' . $name); + } + } +} diff --git a/src/Zend/Media/Flac/MetadataBlock/Application.php b/src/Zend/Media/Flac/MetadataBlock/Application.php new file mode 100644 index 0000000..1a68f33 --- /dev/null +++ b/src/Zend/Media/Flac/MetadataBlock/Application.php @@ -0,0 +1,77 @@ + + * @copyright Copyright (c) 2005-2011 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_Flac_MetadataBlock_Application extends Zend_Media_Flac_MetadataBlock +{ + /** + * Constructs the class with given parameters and parses object related data. + * + * @param Zend_Io_Reader $reader The reader object. + */ + public function __construct($reader) + { + parent::__construct($reader); + + $this->_identifier = $this->_reader->readUInt32BE(); + $this->_data = $this->_reader->read($this->getSize() - 4); + } + + /** + * Returns the application identifier. + * + * @return integer + */ + public function getIdentifier() + { + return $this->_identifier; + } + + /** + * Returns the application data. + * + * @return string + */ + public function getData() + { + return $this->_data; + } +} diff --git a/src/Zend/Media/Flac/MetadataBlock/Cuesheet.php b/src/Zend/Media/Flac/MetadataBlock/Cuesheet.php new file mode 100644 index 0000000..f60f2a2 --- /dev/null +++ b/src/Zend/Media/Flac/MetadataBlock/Cuesheet.php @@ -0,0 +1,189 @@ + + * @copyright Copyright (c) 2005-2011 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_Flac_MetadataBlock_Cuesheet extends Zend_Media_Flac_MetadataBlock +{ + /** @var string */ + private $_catalogNumber; + + /** @var integer */ + private $_leadinSamples; + + /** @var boolean */ + private $_compactDisc; + + /** @var Array */ + private $_tracks; + + /** + * Constructs the class with given parameters and parses object related data. + * + * @param Zend_Io_Reader $reader The reader object. + */ + public function __construct($reader) + { + parent::__construct($reader); + + $this->_catalogNumber = rtrim($this->_reader->read(128), "\x00"); + $this->_leadinSamples = $this->_reader->readInt64BE(); + $this->_compactDisc = ($this->_reader->readUInt8() >> 7) & 0x1; + $this->_reader->skip(258); + $tracksLength = $this->_reader->readUInt8(); + for ($i = 0; $i < $tracksLength; $i++) { + $this->_tracks[$i] = array( + 'offset' => $this->_reader->readInt64BE(), + 'number' => $this->_reader->readUInt8(), + 'isrc' => rtrim($this->_reader->read(12), "\x00"), + 'type' => (($tmp = $this->_reader->readUInt8()) >> 7 ) & 0x1, + 'pre-emphasis' => (($tmp) >> 6 ) & 0x1, + 'index' => array()); + $this->_reader->skip(13); + $indexPointsLength = $this->_reader->readUInt8(); + for ($j = 0; $j < $indexPointsLength; $j++) { + $this->_tracks[$i]['index'][$j] = array( + 'offset' => $this->_reader->readInt64BE(), + 'number' => $this->_reader->readUInt8() + ); + $this->_reader->skip(3); + } + } + } + + /** + * Returns the media catalog number, in ASCII printable characters 0x20-0x7e. In general, the media catalog number + * may be 0 to 128 bytes long; any unused characters should be right-padded with NUL characters. For CD-DA, this is + * a thirteen digit number, followed by 115 NUL bytes.minimum block size (in samples) used in the stream. + * + * @return string + */ + public function getCatalogNumber() + { + return $this->_catalogNumber; + } + + /** + * Returns the number of lead-in samples. This field has meaning only for CD-DA cuesheets; for other uses it should + * be 0. For CD-DA, the lead-in is the TRACK 00 area where the table of contents is stored; more precisely, it is + * the number of samples from the first sample of the media to the first sample of the first index point of the + * first track. According to the Red Book, the lead-in must be silence and CD grabbing software does not usually + * store it; additionally, the lead-in must be at least two seconds but may be longer. For these reasons the + * lead-in length is stored here so that the absolute position of the first track can be computed. Note that the + * lead-in stored here is the number of samples up to the first index point of the first track, not necessarily to + * INDEX 01 of the first track; even the first track may have INDEX 00 data. + * + * @return integer + */ + public function getLeadinSamples() + { + return $this->_leadinSamples; + } + + /** + * Returns the minimum frame size (in bytes) used in the stream. May be 0 to imply the value is not known. + * + * @return integer + */ + public function getMinimumFrameSize() + { + return $this->_minimumFrameSize; + } + + /** + * Returns the maximum frame size (in bytes) used in the stream. May be 0 to imply the value is not known. + * + * @return integer + */ + public function getMaximumFrameSize() + { + return $this->_maximumFrameSize; + } + + /** + * Returns sample rate in Hz. The maximum sample rate is limited by the structure of frame headers to 655350Hz. + * Also, a value of 0 is invalid. + * + * @return integer + */ + public function getSampleRate() + { + return $this->_sampleRate; + } + + /** + * Returns whether the CUESHEET corresponds to a Compact Disc or not. + * + * @return boolean + */ + public function getCompactDisk() + { + return $this->_compactDisk == 1; + } + + /** + * Returns an array of values. Each entry is an array containing the following keys. + * o offset -- Track offset in samples, relative to the beginning of the FLAC audio stream. It is the offset to + * the first index point of the track. (Note how this differs from CD-DA, where the track's offset in the TOC + * is that of the track's INDEX 01 even if there is an INDEX 00.) For CD-DA, the offset must be evenly divisible + * by 588 samples (588 samples = 44100 samples/sec * 1/75th of a sec). + * o number -- Track number. A track number of 0 is not allowed to avoid conflicting with the CD-DA spec, which + * reserves this for the lead-in. For CD-DA the number must be 1-99, or 170 for the lead-out; for non-CD-DA, + * the track number must for 255 for the lead-out. It is not required but encouraged to start with track 1 and + * increase sequentially. Track numbers must be unique within a CUESHEET. + * o isrc -- Track ISRC. This is a 12-digit alphanumeric code or an empty string to denote absence of an ISRC. + * o type -- The track type: 0 for audio, 1 for non-audio. This corresponds to the CD-DA Q-channel control bit 3. + * o pre-emphasis -- The pre-emphasis flag: 0 for no pre-emphasis, 1 for pre-emphasis. This corresponds to the + * CD-DA Q-channel control bit 5. + * o index -- An array of track index points. There must be at least one index in every track in a CUESHEET except + * for the lead-out track, which must have zero. For CD-DA, this number may be no more than 100. Each entry is + * an array containing the following keys. + * o offset -- Offset in samples, relative to the track offset, of the index point. For CD-DA, the offset must + * be evenly divisible by 588 samples (588 samples = 44100 samples/sec * 1/75th of a sec). Note that the + * offset is from the beginning of the track, not the beginning of the audio data. + * o number -- The index point number. For CD-DA, an index number of 0 corresponds to the track pre-gap. The + * first index in a track must have a number of 0 or 1, and subsequently, index numbers must increase by 1. + * Index numbers must be unique within a track. + * + * @return Array + */ + public function getTracks() + { + return $this->_tracks; + } +} diff --git a/src/Zend/Media/Flac/MetadataBlock/Padding.php b/src/Zend/Media/Flac/MetadataBlock/Padding.php new file mode 100644 index 0000000..9cfac61 --- /dev/null +++ b/src/Zend/Media/Flac/MetadataBlock/Padding.php @@ -0,0 +1,53 @@ + + * @copyright Copyright (c) 2005-2011 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_Flac_MetadataBlock_Padding extends Zend_Media_Flac_MetadataBlock +{ + /** + * Constructs the class with given parameters and parses object related data. + * + * @param Zend_Io_Reader $reader The reader object. + */ + public function __construct($reader) + { + parent::__construct($reader); + } +} diff --git a/src/Zend/Media/Flac/MetadataBlock/Picture.php b/src/Zend/Media/Flac/MetadataBlock/Picture.php new file mode 100644 index 0000000..de1f5c2 --- /dev/null +++ b/src/Zend/Media/Flac/MetadataBlock/Picture.php @@ -0,0 +1,199 @@ + + * @copyright Copyright (c) 2005-2011 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_Flac_MetadataBlock_Picture extends Zend_Media_Flac_MetadataBlock +{ + /** + * The list of picture types. + * + * @var Array + */ + public static $types = array + ('Other', '32x32 pixels file icon (PNG only)', 'Other file icon', + 'Cover (front)', 'Cover (back)', 'Leaflet page', + 'Media (e.g. label side of CD)', 'Lead artist/lead performer/soloist', + 'Artist/performer', 'Conductor', 'Band/Orchestra', 'Composer', + 'Lyricist/text writer', 'Recording Location', 'During recording', + 'During performance', 'Movie/video screen capture', + 'A bright coloured fish', 'Illustration', 'Band/artist logotype', + 'Publisher/Studio logotype'); + + /** @var integer */ + private $_type; + + /** @var string */ + private $_mimeType; + + /** @var string */ + private $_description; + + /** @var integer */ + private $_width; + + /** @var integer */ + private $_height; + + /** @var integer */ + private $_colorDepth; + + /** @var integer */ + private $_numberOfColors; + + /** @var integer */ + private $_dataSize; + + /** @var string */ + private $_data; + + /** + * Constructs the class with given parameters and parses object related data. + * + * @todo There is the possibility to put only a link to the picture file by + * using the MIME type '-->' and having a complete URL instead of picture + * data. Support for such needs design considerations. + * @param Zend_Io_Reader $reader The reader object. + */ + public function __construct($reader) + { + parent::__construct($reader); + + $this->_type = $this->_reader->readUInt32BE(); + $this->_mimeType = $this->_reader->read($this->_reader->readUInt32BE()); + $this->_description = $this->_reader->read($this->_reader->readUInt32BE()); + $this->_width = $this->_reader->readUInt32BE(); + $this->_height = $this->_reader->readUInt32BE(); + $this->_colorDepth = $this->_reader->readUInt32BE(); + $this->_numberOfColors = $this->_reader->readUInt32BE(); + $this->_data = $this->_reader->read($this->_dataSize = $this->_reader->readUInt32BE()); + } + + /** + * Returns the picture type. + * + * @return integer + */ + public function getPictureType() + { + return $this->_pictureType; + } + + /** + * Returns the MIME type. + * + * @return string + */ + public function getMimeType() + { + return $this->_mimeType; + } + + /** + * Returns the picture description. + * + * @return string + */ + public function getDescription() + { + return $this->_description; + } + + /** + * Returns the picture width. + * + * @return integer + */ + public function getWidth() + { + return $this->_width; + } + + /** + * Returns the picture height. + * + * @return integer + */ + public function getHeight() + { + return $this->_height; + } + + /** + * Returns the color depth of the picture in bits-per-pixel. + * + * @return integer + */ + public function getColorDepth() + { + return $this->_colorDepth; + } + + /** + * Returns the number of colors used for indexed-color pictures, or 0 for non-indexed pictures. + * + * @return integer + */ + public function getNumberOfColors() + { + return $this->_numberOfColors; + } + + /** + * Returns the picture data size. + * + * @return integer + */ + public function getDataSize() + { + return $this->_dataSize; + } + + /** + * Returns the picture data. + * + * @return string + */ + public function getData() + { + return $this->_data; + } +} diff --git a/src/Zend/Media/Flac/MetadataBlock/Seektable.php b/src/Zend/Media/Flac/MetadataBlock/Seektable.php new file mode 100644 index 0000000..b785019 --- /dev/null +++ b/src/Zend/Media/Flac/MetadataBlock/Seektable.php @@ -0,0 +1,82 @@ + + * @copyright Copyright (c) 2005-2011 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_Flac_MetadataBlock_Seektable extends Zend_Media_Flac_MetadataBlock +{ + /** @var Array */ + private $_seekpoints = array(); + + /** + * Constructs the class with given parameters and parses object related data. + * + * @param Zend_Io_Reader $reader The reader object. + */ + public function __construct($reader) + { + parent::__construct($reader); + + $seekpointCount = $this->getSize() / 18; + for ($i = 0; $i < $seekpointCount; $i++) { + $this->_seekpoints[] = array( + 'sampleNumber' => $this->_reader->readInt64BE(), + 'offset' => $this->_reader->readInt64BE(), + 'numberOfSamples' => $this->_reader->readUInt16BE() + ); + } + } + + /** + * Returns the seekpoint table. The array consists of items having three keys. + * + * o sampleNumber -- Sample number of first sample in the target frame, or 0xFFFFFFFFFFFFFFFF for a + * placeholder point. + * o offset -- Offset (in bytes) from the first byte of the first frame header to the first byte of the + * target frame's header. + * o numberOfSamples -- Number of samples in the target frame. + * + * @return Array + */ + public function getSeekpoints() + { + return $this->_seekpoints; + } +} diff --git a/src/Zend/Media/Flac/MetadataBlock/Streaminfo.php b/src/Zend/Media/Flac/MetadataBlock/Streaminfo.php new file mode 100644 index 0000000..6d5b6fc --- /dev/null +++ b/src/Zend/Media/Flac/MetadataBlock/Streaminfo.php @@ -0,0 +1,185 @@ + + * @copyright Copyright (c) 2005-2011 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_Flac_MetadataBlock_Streaminfo extends Zend_Media_Flac_MetadataBlock +{ + /** @var integer */ + private $_minimumBlockSize; + + /** @var integer */ + private $_maximumBlockSize; + + /** @var integer */ + private $_minimumFrameSize; + + /** @var integer */ + private $_maximumFrameSize; + + /** @var integer */ + private $_sampleRate; + + /** @var integer */ + private $_numberOfChannels; + + /** @var integer */ + private $_bitsPerSample; + + /** @var integer */ + private $_numberOfSamples; + + /** @var string */ + private $_md5Signature; + + /** + * Constructs the class with given parameters and parses object related data. + * + * @param Zend_Io_Reader $reader The reader object. + */ + public function __construct($reader) + { + parent::__construct($reader); + + $this->_minimumBlockSize = $this->_reader->readUInt16BE(); + $this->_maximumBlockSize = $this->_reader->readUInt16BE(); + $this->_minimumFrameSize = $this->_reader->readUInt24BE(); + $this->_maximumFrameSize = $this->_reader->readUInt24BE(); + $this->_sampleRate = Zend_Bit_Twiddling::getValue(($tmp = $this->_reader->readUInt32BE()), 12, 31); + $this->_numberOfChannels = Zend_Bit_Twiddling::getValue($tmp, 9, 11) + 1; + $this->_bitsPerSample = Zend_Bit_Twiddling::getValue($tmp, 4, 8) + 1; + $this->_numberOfSamples = (Zend_Bit_Twiddling::getValue($tmp, 0, 3) << 32) | $this->_reader->readUInt32BE(); + $this->_md5Signature = bin2hex($this->_reader->read(16)); + } + + /** + * Returns the minimum block size (in samples) used in the stream. + * + * @return integer + */ + public function getMinimumBlockSize() + { + return $this->_minimumBlockSize; + } + + /** + * Returns the maximum block size (in samples) used in the stream. (Minimum blocksize == maximum blocksize) implies + * a fixed-blocksize stream. + * + * @return integer + */ + public function getMaximumBlockSize() + { + return $this->_maximumBlockSize; + } + + /** + * Returns the minimum frame size (in bytes) used in the stream. May be 0 to imply the value is not known. + * + * @return integer + */ + public function getMinimumFrameSize() + { + return $this->_minimumFrameSize; + } + + /** + * Returns the maximum frame size (in bytes) used in the stream. May be 0 to imply the value is not known. + * + * @return integer + */ + public function getMaximumFrameSize() + { + return $this->_maximumFrameSize; + } + + /** + * Returns sample rate in Hz. The maximum sample rate is limited by the structure of frame headers to 655350Hz. + * Also, a value of 0 is invalid. + * + * @return integer + */ + public function getSampleRate() + { + return $this->_sampleRate; + } + + /** + * Returns number of channels. FLAC supports from 1 to 8 channels. + * + * @return integer + */ + public function getNumberOfChannels() + { + return $this->_numberOfChannels; + } + + /** + * Returns bits per sample. FLAC supports from 4 to 32 bits per sample. Currently the reference encoder and + * decoders only support up to 24 bits per sample. + * + * @return integer + */ + public function getBitsPerSample() + { + return $this->_bitsPerSample; + } + + /** + * Returns total samples in stream. 'Samples' means inter-channel sample, i.e. one second of 44.1Khz audio will + * have 44100 samples regardless of the number of channels. A value of zero here means the number of total samples + * is unknown. + * + * @return integer + */ + public function getNumberOfSamples() + { + return $this->_numberOfSamples; + } + + /** + * Returns MD5 signature of the unencoded audio data. This allows the decoder to determine if an error exists in + * the audio data even when the error does not result in an invalid bitstream. + * + * @return integer + */ + public function getMd5Signature() + { + return $this->_md5Signature; + } +} diff --git a/src/Zend/Media/Flac/MetadataBlock/VorbisComment.php b/src/Zend/Media/Flac/MetadataBlock/VorbisComment.php new file mode 100644 index 0000000..84e8258 --- /dev/null +++ b/src/Zend/Media/Flac/MetadataBlock/VorbisComment.php @@ -0,0 +1,155 @@ + + * @copyright Copyright (c) 2005-2011 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_Flac_MetadataBlock_VorbisComment extends Zend_Media_Flac_MetadataBlock +{ + /** @var Zend_Media_Vorbis_Header_Comment */ + private $_impl; + + /** + * Constructs the class with given parameters and parses object related data using the vorbis comment implementation + * class {@link Zend_Media_Vorbis_Header_Comment}. + * + * @param Zend_Io_Reader $reader The reader object. + */ + public function __construct($reader) + { + parent::__construct($reader); + $this->_impl = new Zend_Media_Vorbis_Header_Comment($this->_reader, array('vorbisContext' => false)); + } + + /** + * Forward all calls to the vorbis comment implementation class {@link Zend_Media_Vorbis_Header_Comment}. + * + * @param string $name The method name. + * @param Array $arguments The method arguments. + * @return mixed + */ + public function __call($name, $arguments) + { + if (method_exists($this, $name)) { + return $this->$name($arguments); + } + try { + return $this->_impl->$name($arguments); + } catch (Zend_Media_Vorbis_Exception $e) { + require_once 'Zend/Media/Flac/Exception.php'; + throw new Zend_Media_Flac_Exception($e->getMessage()); + } + } + + /** + * Forward all calls to the vorbis comment implementation class {@link Zend_Media_Vorbis_Header_Comment}. + * + * @param string $name The field name. + * @return mixed + */ + public function __get($name) + { + if (method_exists($this, 'get' . ucfirst($name))) { + return call_user_func(array($this, 'get' . ucfirst($name))); + } + if (method_exists($this->_impl, 'get' . ucfirst($name))) { + return call_user_func(array($this->_impl, 'get' . ucfirst($name))); + } + try { + return $this->_impl->__get($name); + } catch (Zend_Media_Vorbis_Exception $e) { + require_once 'Zend/Media/Flac/Exception.php'; + throw new Zend_Media_Flac_Exception($e->getMessage()); + } + } + + /** + * Forward all calls to the vorbis comment implementation class {@link Zend_Media_Vorbis_Header_Comment}. + * + * @param string $name The field name. + * @param string $name 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 { + try { + return $this->_impl->__set($name, $value); + } catch (Zend_Media_Vorbis_Exception $e) { + require_once 'Zend/Media/Flac/Exception.php'; + throw new Zend_Media_Flac_Exception($e->getMessage()); + } + } + } + + /** + * Forward all calls to the vorbis comment implementation class {@link Zend_Media_Vorbis_Header_Comment}. + * + * @param string $name The field name. + * @return boolean + */ + public function __isset($name) + { + try { + return $this->_impl->__isset($name); + } catch (Zend_Media_Vorbis_Exception $e) { + require_once 'Zend/Media/Flac/Exception.php'; + throw new Zend_Media_Flac_Exception($e->getMessage()); + } + } + + /** + * Forward all calls to the vorbis comment implementation class {@link Zend_Media_Vorbis_Header_Comment}. + * + * @param string $name The field name. + */ + public function __unset($name) + { + try { + $this->_impl->__unset($name); + } catch (Zend_Media_Vorbis_Exception $e) { + require_once 'Zend/Media/Flac/Exception.php'; + throw new Zend_Media_Flac_Exception($e->getMessage()); + } + } +} diff --git a/src/Zend/Media/Id3/Frame/Apic.php b/src/Zend/Media/Id3/Frame/Apic.php index 1a6a4eb..f4adade 100644 --- a/src/Zend/Media/Id3/Frame/Apic.php +++ b/src/Zend/Media/Id3/Frame/Apic.php @@ -84,7 +84,7 @@ final class Zend_Media_Id3_Frame_Apic extends Zend_Media_Id3_Frame * * @todo There is the possibility to put only a link to the image file by * using the MIME type '-->' and having a complete URL instead of picture - * data. Support for such needs design considerations. + * data. Support for such needs further design considerations. * @param Zend_Io_Reader $reader The reader object. * @param Array $options The options array. */ diff --git a/src/Zend/Media/Vorbis/Header/Comment.php b/src/Zend/Media/Vorbis/Header/Comment.php index ca153a0..967e985 100644 --- a/src/Zend/Media/Vorbis/Header/Comment.php +++ b/src/Zend/Media/Vorbis/Header/Comment.php @@ -62,17 +62,26 @@ final class Zend_Media_Vorbis_Header_Comment extends Zend_Media_Vorbis_Header private $_comments; /** @var integer */ - private $_framingFlag; + private $_framingFlag = 1; /** - * Constructs the class with given parameters. + * Constructs the class with given parameters and reads object related data from the bitstream. + * + * The following options are currently recognized: + * o vorbisContext -- Indicates whether to expect comments to be in the context of a vorbis bitstream or not. This + * option can be used to parse vorbis comments in another formats, eg FLAC, that do not use for example the + * framing flags. Defaults to true. * * @param Zend_Io_Reader $reader The reader object. + * @param Array $options Array of options. */ - public function __construct($reader) + public function __construct($reader, $options = array()) { - parent::__construct($reader); - + if (!isset($options['vorbisContext']) || $options['vorbisContext']) { + parent::__construct($reader); + } else { + $this->_reader = $reader; + } $this->_vendor = $this->_reader->read($this->_reader->readUInt32LE()); $userCommentListLength = $this->_reader->readUInt32LE(); for ($i = 0; $i < $userCommentListLength; $i++) { @@ -82,12 +91,24 @@ final class Zend_Media_Vorbis_Header_Comment extends Zend_Media_Vorbis_Header } $this->_comments[strtoupper($name)][] = $value; } - $this->_framingFlag = $this->_reader->readUInt8() & 0x1; - if ($this->_framingFlag == 0) { - require_once 'Zend/Media/Vorbis/Exception.php'; - throw new Zend_Media_Vorbis_Exception('Undecodable Vorbis stream'); + if (!isset($options['vorbisContext']) || $options['vorbisContext']) { + $this->_framingFlag = $this->_reader->readUInt8() & 0x1; + if ($this->_framingFlag == 0) { + require_once 'Zend/Media/Vorbis/Exception.php'; + throw new Zend_Media_Vorbis_Exception('Undecodable Vorbis stream'); + } + $this->_reader->skip($this->_packetSize - $this->_reader->getOffset() + 30 /* header */); } - $this->_reader->skip($this->_packetSize - $this->_reader->getOffset() + 30 /* header */); + } + + /** + * Returns the vendor string. + * + * @return string + */ + public function getVendor() + { + return $this->_vendor; } /** @@ -124,7 +145,7 @@ final class Zend_Media_Vorbis_Header_Comment extends Zend_Media_Vorbis_Header */ public function __isset($name) { - return empty($this->_comments[strtoupper($name)]); + return count($this->_comments[strtoupper($name)]) > 0; } /**