cvs: pear /File_Ogg/File Ogg.php /File_Ogg/File/Ogg Vorbis.php
| From: | David Grant | Date: | Sat, 19 Nov 2005 09:06:32 +0000 |
| Subject: | cvs: pear /File_Ogg/File Ogg.php /File_Ogg/File/Ogg Vorbis.php | ||
| Groups: | php.pear.cvs | ||
| Request: | Send a blank email to pear-cvs+get-36371@lists.php.net to get a copy of this message | ||
djg Sat Nov 19 04:06:32 2005 EDT
Modified files:
/pear/File_Ogg/File Ogg.php
/pear/File_Ogg/File/Ogg Vorbis.php
Log:
Added ability to save vorbis streams with updated metadata. CRC needs attention.
http://cvs.php.net/diff.php/pear/File_Ogg/File/Ogg.php?r1=1.13&r2=1.14&ty=u Index: pear/File_Ogg/File/Ogg.php diff -u pear/File_Ogg/File/Ogg.php:1.13 pear/File_Ogg/File/Ogg.php:1.14 --- pear/File_Ogg/File/Ogg.php:1.13 Wed Nov 16 15:43:26 2005 +++ pear/File_Ogg/File/Ogg.php Sat Nov 19 04:06:30 2005 @@ -26,11 +26,15 @@ * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL * @link http://pear.php.net/package/File_Ogg * @package File_Ogg - * @version CVS: $Id: Ogg.php,v 1.13 2005/11/16 20:43:26 djg Exp $ + * @version CVS: $Id: Ogg.php,v 1.14 2005/11/19 09:06:30 djg Exp $ */ require_once('PEAR.php'); require_once('File/Ogg/Bitstream.php'); +require_once("File/Ogg/Flac.php"); +require_once("File/Ogg/Speex.php"); +require_once("File/Ogg/Theora.php"); +require_once("File/Ogg/Vorbis.php"); /** * @access public @@ -51,6 +55,7 @@ /** * Capture pattern to determine if a file is an Ogg physical stream. + * * @access private */ define("OGG_CAPTURE_PATTERN", "OggS"); @@ -58,11 +63,13 @@ * Maximum size of an Ogg stream page plus four. This value is specified to allow * efficient parsing of the physical stream. The extra four is a paranoid measure * to make sure a capture pattern is not split into two parts accidentally. + * * @access private */ define("OGG_MAXIMUM_PAGE_SIZE", 65311); /** * Capture pattern for an Ogg Vorbis logical stream. + * * @access private */ define("OGG_STREAM_CAPTURE_VORBIS", "vorbis"); @@ -73,27 +80,32 @@ define("OGG_STREAM_CAPTURE_SPEEX", "Speex "); /** * Capture pattern for an Ogg FLAC logical stream. + * * @access private */ define("OGG_STREAM_CAPTURE_FLAC", "fLaC"); /** * Capture pattern for an Ogg Theora logical stream. + * * @access private */ define("OGG_STREAM_CAPTURE_THEORA", "theora"); /** * Error thrown if the file location passed is nonexistant or unreadable. + * * @access private */ define("OGG_ERROR_INVALID_FILE", 1); /** * Error thrown if the user attempts to extract an unsupported logical stream. + * * @access private */ define("OGG_ERROR_UNSUPPORTED", 2); /** * Error thrown if the user attempts to extract an logical stream with no * corresponding serial number. + * * @access private */ define("OGG_ERROR_BAD_SERIAL", 3); @@ -104,8 +116,8 @@ * This class provides a means to access several types of logical bitstreams (e.g. Vorbis) * within a Ogg physical bitstream. * - * @package File_Ogg * @link http://www.xiph.org/ogg/doc/ + * @package File_Ogg */ class File_Ogg { @@ -131,6 +143,7 @@ * @access private */ var $_streamList = array(); + var $_streams = array(); /** * Returns an interface to an Ogg physical stream. @@ -186,7 +199,7 @@ $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['stream_version'] = $stream_version['data']; $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['header_flag'] = $header_flag['data']; $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['abs_granual_pos'] = $abs_granual_pos['data']; - $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['checksum'] = $checksum['data']; + $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['checksum'] = sprintf("%u", $checksum['data']); $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['segments'] = $segments_total; $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['head_offset'] = $pageOffset; $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['body_offset'] = $pageOffset + 26 + $page_segments['data'] + 1; @@ -241,17 +254,22 @@ foreach ($this->_streamList as $stream_serial => $pages) { fseek($this->_filePointer, $pages['stream_page'][0]['body_offset'], SEEK_SET); $pattern = fread($this->_filePointer, 8); - if (preg_match("/" . OGG_STREAM_CAPTURE_VORBIS . "/", $pattern)) + if (preg_match("/" . OGG_STREAM_CAPTURE_VORBIS . "/", $pattern)) { $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_VORBIS; - elseif (preg_match("/" . OGG_STREAM_CAPTURE_SPEEX . "/", $pattern)) + $this->_streams[$stream_serial] =& new File_Ogg_Vorbis($stream_serial, $this->_streamList[$stream_serial]['stream_page'], $this->_filePointer); + } elseif (preg_match("/" . OGG_STREAM_CAPTURE_SPEEX . "/", $pattern)) { $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_SPEEX; - elseif (preg_match("/" . OGG_STREAM_CAPTURE_FLAC . "/", $pattern)) + $this->_streams[$stream_serial] =& new File_Ogg_Speex($stream_serial, $this->_streamList[$stream_serial]['stream_page'], $this->_filePointer); + } elseif (preg_match("/" . OGG_STREAM_CAPTURE_FLAC . "/", $pattern)) { $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_FLAC; - elseif (preg_match("/" . OGG_STREAM_CAPTURE_THEORA . "/", $pattern)) + $this->_streams[$stream_serial] =& new File_Ogg_Flac($stream_serial, $this->_streamList[$stream_serial]['stream_page'], $this->_filePointer); + } elseif (preg_match("/" . OGG_STREAM_CAPTURE_THEORA . "/", $pattern)) { $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_THEORA; - else + $this->_streams[$stream_serial] =& new File_Ogg_Theora($stream_serial, $this->_streamList[$stream_serial]['stream_page'], $this->_filePointer); + } else $this->_streamList[$stream_serial]['stream_type'] = "unknown"; } + unset($this->_streamList); } /** @@ -265,8 +283,8 @@ function getOverhead() { $header_size = 0; $stream_size = 0; - foreach ($this->_streamList as $stream) { - foreach ($stream['stream_page'] as $offset => $stream_data) { + foreach ($this->_streams as $serial => $stream) { + foreach ($stream->_streamList as $offset => $stream_data) { $header_size += $stream_data['body_offset'] - $stream_data['head_offset']; $stream_size = $stream_data['body_finish']; } @@ -284,28 +302,12 @@ * * @return File_Ogg_Bitstream */ - function getStream($streamSerial) + function &getStream($streamSerial) { - if (! array_key_exists($streamSerial, $this->_streamList)) + if (! array_key_exists($streamSerial, $this->_streams)) PEAR::raiseError("The stream number is invalid.", OGG_ERROR_BAD_SERIAL); - switch ($this->_streamList[$streamSerial]['stream_type']) { - case (OGG_STREAM_VORBIS): - require_once("File/Ogg/Vorbis.php"); - return (new File_Ogg_Vorbis($streamSerial, $this->_streamList[$streamSerial], $this->_filePointer)); - case (OGG_STREAM_SPEEX): - require_once("File/Ogg/Speex.php"); - return (new File_Ogg_Speex($streamSerial, $this->_streamList[$streamSerial], $this->_filePointer)); - case (OGG_STREAM_FLAC): - require_once("File/Ogg/Flac.php"); - return (new File_Ogg_Flac($streamSerial, $this->_streamList[$streamSerial], $this->_filePointer)); - case (OGG_STREAM_THEORA): - require_once("File/Ogg/Theora.php"); - return (new File_Ogg_Theora($streamSerial, $this->_streamList[$streamSerial], $this->_filePointer)); - default: - PEAR::raiseError("This stream could not be identified.", OGG_ERROR_UNSUPPORTED); - } - return false; + return $this->_streams[$streamSerial]; } /** @@ -320,7 +322,7 @@ */ function hasStream($streamType) { - foreach ($this->_streamList as $stream) { + foreach ($this->_stream as $stream) { if ($stream['stream_type'] == $streamType) return (true); } @@ -342,14 +344,32 @@ function listStreams($filter = null) { $streams = array(); - foreach ($this->_streamList as $stream_serial => $stream) { - if (! isset($streams[$stream['stream_type']])) + // Loops through the streams and assign them to an appropriate index, + // ready for filtering the second part of this function. + foreach ($this->_streams as $serial => $stream) { + $stream_type = 0; + switch (get_class($stream)) { + case "file_ogg_flac": + $stream_type = OGG_STREAM_FLAC; + break; + case "file_ogg_speex": + $stream_type = OGG_STREAM_SPEEX; + break; + case "file_ogg_theora": + $stream_type = OGG_STREAM_THEORA; + break; + case "file_ogg_vorbis": + $stream_type = OGG_STREAM_VORBIS; + break; + } + if (! isset($streams[$stream_type])) // Initialise the result list for this stream type. - $streams[$stream['stream_type']] = array(); + $streams[$stream_type] = array(); - $streams[$stream['stream_type']][] = $stream_serial; + $streams[$stream_type][] = $serial; } + // Perform filtering. if (is_null($filter)) return ($streams); elseif (isset($streams[$filter])) @@ -357,5 +377,13 @@ else return array(); } + + function saveChanges() + { + $fp = tmpfile(); + $diff = 0; + foreach ($this->_streams as $stream) + $stream->_saveChanges($fp, $diff); + } } ?> \ No newline at end of file http://cvs.php.net/diff.php/pear/File_Ogg/File/Ogg/Vorbis.php?r1=1.12&r2=1.13&ty=u Index: pear/File_Ogg/File/Ogg/Vorbis.php diff -u pear/File_Ogg/File/Ogg/Vorbis.php:1.12 pear/File_Ogg/File/Ogg/Vorbis.php:1.13 --- pear/File_Ogg/File/Ogg/Vorbis.php:1.12 Wed Nov 16 15:43:27 2005 +++ pear/File_Ogg/File/Ogg/Vorbis.php Sat Nov 19 04:06:32 2005 @@ -53,6 +53,17 @@ */ define("OGG_VORBIS_ERROR_INVALID_COMMENT", 2); +define("OGG_VORBIS_IDENTIFICATION_PAGE_OFFSET", 0); +define("OGG_VORBIS_COMMENTS_PAGE_OFFSET", 1); + +/** + * Error thrown if the user attempts to write a comment containing an illegal + * character + * + * @access private + */ +define("OGG_VORBIS_ERROR_ILLEGAL_COMMENT", 3); + /** * Extract the contents of a Vorbis logical stream. * @@ -68,7 +79,7 @@ * @link http://pear.php.net/package/File_Ogg * @link http://www.xiph.org/vorbis/doc/ * @package File_Ogg - * @version CVS: $Id: Vorbis.php,v 1.12 2005/11/16 20:43:27 djg Exp $ + * @version CVS: $Id: Vorbis.php,v 1.13 2005/11/19 09:06:32 djg Exp $ */ class File_Ogg_Vorbis extends File_Ogg_Bitstream { @@ -151,6 +162,8 @@ * @var int */ var $_streamLength; + + var $_original_comments_length; /** * Constructor for accessing a Vorbis logical stream. @@ -171,15 +184,75 @@ $this->_filePointer = $filePointer; $this->_decodeIdentificationHeader(); $this->_decodeCommentsHeader(); - $this->_streamLength = round($streamData['stream_page'][count($streamData['stream_page']) - 1]['abs_granual_pos'] / $this->_sampleRate); + $this->_streamLength = round($streamData[count($streamData) - 1]['abs_granual_pos'] / $this->_sampleRate); // This gives an accuracy of approximately 99.7% to the streamsize of ogginfo. - for ($i = 0; $i < count($streamData['stream_page']); ++$i) - $this->_streamSize += $streamData['stream_page'][$i]['data_length']; + for ($i = 0; $i < count($streamData); ++$i) + $this->_streamSize += $streamData[$i]['data_length']; $this->_avgBitrate = round(($this->_streamSize * 8) / $this->_streamLength); } /** + * Save changes made to this stream. + * + * @access private + * @param resource $fp + * @param int $diff + */ + function _saveChanges($fp, &$diff) { + // Reset the file point to the original head offset of this stream. + fseek($this->_filePointer, $this->_streamList[0]['head_offset']); + + // Reset the checksum. + $this->_streamList[OGG_VORBIS_IDENTIFICATION_PAGE_OFFSET]['checksum'] = 0; + // Change the offset of the page indices by the number of characters + // carried forward from the previous stream. + $this->_streamList[OGG_VORBIS_IDENTIFICATION_PAGE_OFFSET]['head_offset'] -= $diff; + $this->_streamList[OGG_VORBIS_IDENTIFICATION_PAGE_OFFSET]['body_offset'] -= $diff; + $this->_streamList[OGG_VORBIS_IDENTIFICATION_PAGE_OFFSET]['body_finish'] -= $diff; + + // Change the offset of the page indices by the number of characters + // carried forward from the previous stream. + $this->_streamList[OGG_VORBIS_COMMENTS_PAGE_OFFSET]['head_offset'] -= $diff; + $this->_streamList[OGG_VORBIS_COMMENTS_PAGE_OFFSET]['body_offset'] -= $diff; + + // Adjust the page index offset for all streams in the ogg container. + // The diff variable is passed by reference, so the changes will be + // carried forward for following streams. + $diff += $this->_original_comments_length - strlen($this->_encodeCommentsHeader()); + + // Adjust the offset of the comments page. The head + // start and body start remain the same, since the change + // is made in the page body. + $this->_streamList[OGG_VORBIS_COMMENTS_PAGE_OFFSET]['checksum'] = 0; + $this->_streamList[OGG_VORBIS_COMMENTS_PAGE_OFFSET]['body_finish'] -= $diff; + $this->_streamList[OGG_VORBIS_COMMENTS_PAGE_OFFSET]['data_length'] -= $diff; + + foreach ($this->_streamList as $offset => $stream_parts) { + if ($offset > OGG_VORBIS_COMMENTS_PAGE_OFFSET) { + // Forward the file pointer + fseek($this->_filePointer, $this->_streamList[$offset]['head_offset'], SEEK_SET); + $page = fread($this->_filePointer, 22); + // Jump over the reserved checksum space. + fseek($this->_filePointer, 4, SEEK_CUR); + // Pack in an empty checksum, ready for page CRC operation. + $page .= pack("V", 0); + // Read the remainder of this page. + $page .= fread($this->_filePointer, $this->_streamList[$offset]['data_length'] - 26); + echo $this->_streamList[$offset]['checksum'] . "\n"; + echo sprintf("%u", crc32($page)) . "\n\n"; + + // Reset the checksum ready for CRC recalculation. + $this->_streamList[$offset]['checksum'] = 0; + // Adjust the offset of the various indices in the header. + $this->_streamList[$offset]['head_offset'] -= $diff; + $this->_streamList[$offset]['body_offset'] -= $diff; + $this->_streamList[$offset]['body_finish'] -= $diff; + } + } + } + + /** * Parse the identification header (the first of three headers) in a Vorbis stream. * * This function parses the identification header. The identification header @@ -191,7 +264,7 @@ */ function _decodeIdentificationHeader() { - $this->_decodeCommonHeader(OGG_VORBIS_IDENTIFICATION_HEADER, 0); + $this->_decodeCommonHeader(OGG_VORBIS_IDENTIFICATION_HEADER, OGG_VORBIS_IDENTIFICATION_PAGE_OFFSET); $version = unpack("Vdata", fread($this->_filePointer, 4)); @@ -210,7 +283,7 @@ else $this->_channels = $channels['data']; - // The sample rate is a 32 bit unsigned integer. + // The sample rate is a 32 bit unsigned integer. $sample_rate = unpack("Vdata", fread($this->_filePointer, 4)); // The sample rate MUST be greater than 0. if ($sample_rate['data'] == 0) @@ -260,7 +333,7 @@ */ function _decodeCommonHeader($packetType, $pageOffset) { - fseek($this->_filePointer, $this->_streamList['stream_page'][$pageOffset]['body_offset'], SEEK_SET); + fseek($this->_filePointer, $this->_streamList[$pageOffset]['body_offset'], SEEK_SET); // Check if this is the correct header. $packet = unpack("Cdata", fread($this->_filePointer, 1)); if ($packet['data'] != $packetType) @@ -272,6 +345,20 @@ } /** + * Encode a standard vorbis header. + * + * @access private + * @param int $packetType + * @return string + */ + function _encodeCommonHeader($packetType) { + $header = ""; + $header .= pack("C", $packetType); + $header .= OGG_STREAM_CAPTURE_VORBIS; + return ($header); + } + + /** * Parse the comments header (the second of three headers) of a Vorbis stream. * * This function parses the comments header. The comments header contains a series of @@ -283,14 +370,17 @@ */ function _decodeCommentsHeader() { - $this->_decodeCommonHeader(OGG_VORBIS_COMMENTS_HEADER, 1); + $this->_decodeCommonHeader(OGG_VORBIS_COMMENTS_HEADER, OGG_VORBIS_COMMENTS_PAGE_OFFSET); - // Decode the vendor string length. + // Decode the vendor string length as a 32-bit unsigned integer. $vendor_len = unpack("Vdata", fread($this->_filePointer, 4)); + // Retrieve the vendor string from the stream. $this->_vendor = fread($this->_filePointer, $vendor_len['data']); - // Decode the size of the comments list. + // Decode the size of the comments list as a 32-bit unsigned integer. $comment_list_length = unpack("Vdata", fread($this->_filePointer, 4)); + // Iterate through the comments list. for ($i = 0; $i < $comment_list_length['data']; ++$i) { + // Unpack the length of this comment. $comment_length = unpack("Vdata", fread($this->_filePointer, 4)); // Comments are in the format 'ARTIST=Super Furry Animals', so split it on the equals character. // NOTE: Equals characters are strictly prohibited in either the COMMENT or DATA parts. @@ -316,6 +406,54 @@ $framing_bit = unpack("Cdata", fread($this->_filePointer, 1)); if ($framing_bit['data'] != 1) PEAR::raiseError("Stream Undecodable", OGG_VORBIS_ERROR_UNDECODABLE); + $this->_original_comments_length = ftell($this->_filePointer) - $this->_streamList[OGG_VORBIS_COMMENTS_PAGE_OFFSET]['body_offset']; + } + + function _encodeCommentsHeader() + { + $header = $this->_encodeCommonHeader(OGG_VORBIS_COMMENTS_HEADER); + $header .= pack("V", strlen($this->_vendor)); + $header .= $this->_vendor; + $cmts = 0; + foreach ($this->_comments as $comment) { + if (is_array($comment)) + $cmts += count($comment); + else + ++$cmts; + } + // Pack the length of comments. + $header .= pack("V", $cmts); + // Iterate through the comments, and pack them into the stream. + foreach ($this->_comments as $title => $comment) { + // This comment has two or greater values. + if (is_array($comment)) { + foreach ($comment as $offset => $s_comment) + $header .= $this->_packComment($title, $s_comment); + } else + $header .= $this->_packComment($title, $comment); + } + // Pack the framing bit. + $header .= pack("C", 1); + + return ($header); + } + + /** + * Pack a comment into binary format. + * + * @access private + * @param string $title + * @param string $value + * @return string + */ + function _packComment($title, $value) + { + $header = ""; + $value = utf8_encode($value); + $comment = $title . "=" . $value; + $header .= pack("V", strlen($comment)); + $header .= $comment; + return $header; } /** @@ -327,8 +465,8 @@ * comments (e.g. TITLE, ARTIST, etc.) for use in Vorbis streams, they are not * guaranteed to appear. * - * @return array * @access public + * @return array */ function getCommentList() { @@ -371,6 +509,11 @@ */ function setField($field, $value, $replace = true) { + if (strpos($field, "=") !== false) + return PEAR::raiseError("Comments must not contain equals signs.", OGG_VORBIS_ERROR_ILLEGAL_COMMENT); + if (strpos($value, "=") !== false) + return PEAR::raiseError("Comments must not contain equals signs.", OGG_VORBIS_ERROR_ILLEGAL_COMMENT); + if ($replace || ! isset($this->_comments[$field])) { $this->_comments[$field] = $value; } else {
http://cvs.php.net/diff.php/pear/File_Ogg/File/Ogg.php?r1=1.13&r2=1.14&ty=u Index: pear/File_Ogg/File/Ogg.php diff -u pear/File_Ogg/File/Ogg.php:1.13 pear/File_Ogg/File/Ogg.php:1.14 --- pear/File_Ogg/File/Ogg.php:1.13 Wed Nov 16 15:43:26 2005 +++ pear/File_Ogg/File/Ogg.php Sat Nov 19 04:06:30 2005 @@ -26,11 +26,15 @@ * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL * @link http://pear.php.net/package/File_Ogg * @package File_Ogg - * @version CVS: $Id: Ogg.php,v 1.13 2005/11/16 20:43:26 djg Exp $ + * @version CVS: $Id: Ogg.php,v 1.14 2005/11/19 09:06:30 djg Exp $ */ require_once('PEAR.php'); require_once('File/Ogg/Bitstream.php'); +require_once("File/Ogg/Flac.php"); +require_once("File/Ogg/Speex.php"); +require_once("File/Ogg/Theora.php"); +require_once("File/Ogg/Vorbis.php"); /** * @access public @@ -51,6 +55,7 @@ /** * Capture pattern to determine if a file is an Ogg physical stream. + * * @access private */ define("OGG_CAPTURE_PATTERN", "OggS"); @@ -58,11 +63,13 @@ * Maximum size of an Ogg stream page plus four. This value is specified to allow * efficient parsing of the physical stream. The extra four is a paranoid measure * to make sure a capture pattern is not split into two parts accidentally. + * * @access private */ define("OGG_MAXIMUM_PAGE_SIZE", 65311); /** * Capture pattern for an Ogg Vorbis logical stream. + * * @access private */ define("OGG_STREAM_CAPTURE_VORBIS", "vorbis"); @@ -73,27 +80,32 @@ define("OGG_STREAM_CAPTURE_SPEEX", "Speex "); /** * Capture pattern for an Ogg FLAC logical stream. + * * @access private */ define("OGG_STREAM_CAPTURE_FLAC", "fLaC"); /** * Capture pattern for an Ogg Theora logical stream. + * * @access private */ define("OGG_STREAM_CAPTURE_THEORA", "theora"); /** * Error thrown if the file location passed is nonexistant or unreadable. + * * @access private */ define("OGG_ERROR_INVALID_FILE", 1); /** * Error thrown if the user attempts to extract an unsupported logical stream. + * * @access private */ define("OGG_ERROR_UNSUPPORTED", 2); /** * Error thrown if the user attempts to extract an logical stream with no * corresponding serial number. + * * @access private */ define("OGG_ERROR_BAD_SERIAL", 3); @@ -104,8 +116,8 @@ * This class provides a means to access several types of logical bitstreams (e.g. Vorbis) * within a Ogg physical bitstream. * - * @package File_Ogg * @link http://www.xiph.org/ogg/doc/ + * @package File_Ogg */ class File_Ogg { @@ -131,6 +143,7 @@ * @access private */ var $_streamList = array(); + var $_streams = array(); /** * Returns an interface to an Ogg physical stream. @@ -186,7 +199,7 @@ $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['stream_version'] = $stream_version['data']; $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['header_flag'] = $header_flag['data']; $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['abs_granual_pos'] = $abs_granual_pos['data']; - $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['checksum'] = $checksum['data']; + $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['checksum'] = sprintf("%u", $checksum['data']); $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['segments'] = $segments_total; $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['head_offset'] = $pageOffset; $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['body_offset'] = $pageOffset + 26 + $page_segments['data'] + 1; @@ -241,17 +254,22 @@ foreach ($this->_streamList as $stream_serial => $pages) { fseek($this->_filePointer, $pages['stream_page'][0]['body_offset'], SEEK_SET); $pattern = fread($this->_filePointer, 8); - if (preg_match("/" . OGG_STREAM_CAPTURE_VORBIS . "/", $pattern)) + if (preg_match("/" . OGG_STREAM_CAPTURE_VORBIS . "/", $pattern)) { $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_VORBIS; - elseif (preg_match("/" . OGG_STREAM_CAPTURE_SPEEX . "/", $pattern)) + $this->_streams[$stream_serial] =& new File_Ogg_Vorbis($stream_serial, $this->_streamList[$stream_serial]['stream_page'], $this->_filePointer); + } elseif (preg_match("/" . OGG_STREAM_CAPTURE_SPEEX . "/", $pattern)) { $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_SPEEX; - elseif (preg_match("/" . OGG_STREAM_CAPTURE_FLAC . "/", $pattern)) + $this->_streams[$stream_serial] =& new File_Ogg_Speex($stream_serial, $this->_streamList[$stream_serial]['stream_page'], $this->_filePointer); + } elseif (preg_match("/" . OGG_STREAM_CAPTURE_FLAC . "/", $pattern)) { $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_FLAC; - elseif (preg_match("/" . OGG_STREAM_CAPTURE_THEORA . "/", $pattern)) + $this->_streams[$stream_serial] =& new File_Ogg_Flac($stream_serial, $this->_streamList[$stream_serial]['stream_page'], $this->_filePointer); + } elseif (preg_match("/" . OGG_STREAM_CAPTURE_THEORA . "/", $pattern)) { $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_THEORA; - else + $this->_streams[$stream_serial] =& new File_Ogg_Theora($stream_serial, $this->_streamList[$stream_serial]['stream_page'], $this->_filePointer); + } else $this->_streamList[$stream_serial]['stream_type'] = "unknown"; } + unset($this->_streamList); } /** @@ -265,8 +283,8 @@ function getOverhead() { $header_size = 0; $stream_size = 0; - foreach ($this->_streamList as $stream) { - foreach ($stream['stream_page'] as $offset => $stream_data) { + foreach ($this->_streams as $serial => $stream) { + foreach ($stream->_streamList as $offset => $stream_data) { $header_size += $stream_data['body_offset'] - $stream_data['head_offset']; $stream_size = $stream_data['body_finish']; } @@ -284,28 +302,12 @@ * * @return File_Ogg_Bitstream */ - function getStream($streamSerial) + function &getStream($streamSerial) { - if (! array_key_exists($streamSerial, $this->_streamList)) + if (! array_key_exists($streamSerial, $this->_streams)) PEAR::raiseError("The stream number is invalid.", OGG_ERROR_BAD_SERIAL); - switch ($this->_streamList[$streamSerial]['stream_type']) { - case (OGG_STREAM_VORBIS): - require_once("File/Ogg/Vorbis.php"); - return (new File_Ogg_Vorbis($streamSerial, $this->_streamList[$streamSerial], $this->_filePointer)); - case (OGG_STREAM_SPEEX): - require_once("File/Ogg/Speex.php"); - return (new File_Ogg_Speex($streamSerial, $this->_streamList[$streamSerial], $this->_filePointer)); - case (OGG_STREAM_FLAC): - require_once("File/Ogg/Flac.php"); - return (new File_Ogg_Flac($streamSerial, $this->_streamList[$streamSerial], $this->_filePointer)); - case (OGG_STREAM_THEORA): - require_once("File/Ogg/Theora.php"); - return (new File_Ogg_Theora($streamSerial, $this->_streamList[$streamSerial], $this->_filePointer)); - default: - PEAR::raiseError("This stream could not be identified.", OGG_ERROR_UNSUPPORTED); - } - return false; + return $this->_streams[$streamSerial]; } /** @@ -320,7 +322,7 @@ */ function hasStream($streamType) { - foreach ($this->_streamList as $stream) { + foreach ($this->_stream as $stream) { if ($stream['stream_type'] == $streamType) return (true); } @@ -342,14 +344,32 @@ function listStreams($filter = null) { $streams = array(); - foreach ($this->_streamList as $stream_serial => $stream) { - if (! isset($streams[$stream['stream_type']])) + // Loops through the streams and assign them to an appropriate index, + // ready for filtering the second part of this function. + foreach ($this->_streams as $serial => $stream) { + $stream_type = 0; + switch (get_class($stream)) { + case "file_ogg_flac": + $stream_type = OGG_STREAM_FLAC; + break; + case "file_ogg_speex": + $stream_type = OGG_STREAM_SPEEX; + break; + case "file_ogg_theora": + $stream_type = OGG_STREAM_THEORA; + break; + case "file_ogg_vorbis": + $stream_type = OGG_STREAM_VORBIS; + break; + } + if (! isset($streams[$stream_type])) // Initialise the result list for this stream type. - $streams[$stream['stream_type']] = array(); + $streams[$stream_type] = array(); - $streams[$stream['stream_type']][] = $stream_serial; + $streams[$stream_type][] = $serial; } + // Perform filtering. if (is_null($filter)) return ($streams); elseif (isset($streams[$filter])) @@ -357,5 +377,13 @@ else return array(); } + + function saveChanges() + { + $fp = tmpfile(); + $diff = 0; + foreach ($this->_streams as $stream) + $stream->_saveChanges($fp, $diff); + } } ?> \ No newline at end of file http://cvs.php.net/diff.php/pear/File_Ogg/File/Ogg/Vorbis.php?r1=1.12&r2=1.13&ty=u Index: pear/File_Ogg/File/Ogg/Vorbis.php diff -u pear/File_Ogg/File/Ogg/Vorbis.php:1.12 pear/File_Ogg/File/Ogg/Vorbis.php:1.13 --- pear/File_Ogg/File/Ogg/Vorbis.php:1.12 Wed Nov 16 15:43:27 2005 +++ pear/File_Ogg/File/Ogg/Vorbis.php Sat Nov 19 04:06:32 2005 @@ -53,6 +53,17 @@ */ define("OGG_VORBIS_ERROR_INVALID_COMMENT", 2); +define("OGG_VORBIS_IDENTIFICATION_PAGE_OFFSET", 0); +define("OGG_VORBIS_COMMENTS_PAGE_OFFSET", 1); + +/** + * Error thrown if the user attempts to write a comment containing an illegal + * character + * + * @access private + */ +define("OGG_VORBIS_ERROR_ILLEGAL_COMMENT", 3); + /** * Extract the contents of a Vorbis logical stream. * @@ -68,7 +79,7 @@ * @link http://pear.php.net/package/File_Ogg * @link http://www.xiph.org/vorbis/doc/ * @package File_Ogg - * @version CVS: $Id: Vorbis.php,v 1.12 2005/11/16 20:43:27 djg Exp $ + * @version CVS: $Id: Vorbis.php,v 1.13 2005/11/19 09:06:32 djg Exp $ */ class File_Ogg_Vorbis extends File_Ogg_Bitstream { @@ -151,6 +162,8 @@ * @var int */ var $_streamLength; + + var $_original_comments_length; /** * Constructor for accessing a Vorbis logical stream. @@ -171,15 +184,75 @@ $this->_filePointer = $filePointer; $this->_decodeIdentificationHeader(); $this->_decodeCommentsHeader(); - $this->_streamLength = round($streamData['stream_page'][count($streamData['stream_page']) - 1]['abs_granual_pos'] / $this->_sampleRate); + $this->_streamLength = round($streamData[count($streamData) - 1]['abs_granual_pos'] / $this->_sampleRate); // This gives an accuracy of approximately 99.7% to the streamsize of ogginfo. - for ($i = 0; $i < count($streamData['stream_page']); ++$i) - $this->_streamSize += $streamData['stream_page'][$i]['data_length']; + for ($i = 0; $i < count($streamData); ++$i) + $this->_streamSize += $streamData[$i]['data_length']; $this->_avgBitrate = round(($this->_streamSize * 8) / $this->_streamLength); } /** + * Save changes made to this stream. + * + * @access private + * @param resource $fp + * @param int $diff + */ + function _saveChanges($fp, &$diff) { + // Reset the file point to the original head offset of this stream. + fseek($this->_filePointer, $this->_streamList[0]['head_offset']); + + // Reset the checksum. + $this->_streamList[OGG_VORBIS_IDENTIFICATION_PAGE_OFFSET]['checksum'] = 0; + // Change the offset of the page indices by the number of characters + // carried forward from the previous stream. + $this->_streamList[OGG_VORBIS_IDENTIFICATION_PAGE_OFFSET]['head_offset'] -= $diff; + $this->_streamList[OGG_VORBIS_IDENTIFICATION_PAGE_OFFSET]['body_offset'] -= $diff; + $this->_streamList[OGG_VORBIS_IDENTIFICATION_PAGE_OFFSET]['body_finish'] -= $diff; + + // Change the offset of the page indices by the number of characters + // carried forward from the previous stream. + $this->_streamList[OGG_VORBIS_COMMENTS_PAGE_OFFSET]['head_offset'] -= $diff; + $this->_streamList[OGG_VORBIS_COMMENTS_PAGE_OFFSET]['body_offset'] -= $diff; + + // Adjust the page index offset for all streams in the ogg container. + // The diff variable is passed by reference, so the changes will be + // carried forward for following streams. + $diff += $this->_original_comments_length - strlen($this->_encodeCommentsHeader()); + + // Adjust the offset of the comments page. The head + // start and body start remain the same, since the change + // is made in the page body. + $this->_streamList[OGG_VORBIS_COMMENTS_PAGE_OFFSET]['checksum'] = 0; + $this->_streamList[OGG_VORBIS_COMMENTS_PAGE_OFFSET]['body_finish'] -= $diff; + $this->_streamList[OGG_VORBIS_COMMENTS_PAGE_OFFSET]['data_length'] -= $diff; + + foreach ($this->_streamList as $offset => $stream_parts) { + if ($offset > OGG_VORBIS_COMMENTS_PAGE_OFFSET) { + // Forward the file pointer + fseek($this->_filePointer, $this->_streamList[$offset]['head_offset'], SEEK_SET); + $page = fread($this->_filePointer, 22); + // Jump over the reserved checksum space. + fseek($this->_filePointer, 4, SEEK_CUR); + // Pack in an empty checksum, ready for page CRC operation. + $page .= pack("V", 0); + // Read the remainder of this page. + $page .= fread($this->_filePointer, $this->_streamList[$offset]['data_length'] - 26); + echo $this->_streamList[$offset]['checksum'] . "\n"; + echo sprintf("%u", crc32($page)) . "\n\n"; + + // Reset the checksum ready for CRC recalculation. + $this->_streamList[$offset]['checksum'] = 0; + // Adjust the offset of the various indices in the header. + $this->_streamList[$offset]['head_offset'] -= $diff; + $this->_streamList[$offset]['body_offset'] -= $diff; + $this->_streamList[$offset]['body_finish'] -= $diff; + } + } + } + + /** * Parse the identification header (the first of three headers) in a Vorbis stream. * * This function parses the identification header. The identification header @@ -191,7 +264,7 @@ */ function _decodeIdentificationHeader() { - $this->_decodeCommonHeader(OGG_VORBIS_IDENTIFICATION_HEADER, 0); + $this->_decodeCommonHeader(OGG_VORBIS_IDENTIFICATION_HEADER, OGG_VORBIS_IDENTIFICATION_PAGE_OFFSET); $version = unpack("Vdata", fread($this->_filePointer, 4)); @@ -210,7 +283,7 @@ else $this->_channels = $channels['data']; - // The sample rate is a 32 bit unsigned integer. + // The sample rate is a 32 bit unsigned integer. $sample_rate = unpack("Vdata", fread($this->_filePointer, 4)); // The sample rate MUST be greater than 0. if ($sample_rate['data'] == 0) @@ -260,7 +333,7 @@ */ function _decodeCommonHeader($packetType, $pageOffset) { - fseek($this->_filePointer, $this->_streamList['stream_page'][$pageOffset]['body_offset'], SEEK_SET); + fseek($this->_filePointer, $this->_streamList[$pageOffset]['body_offset'], SEEK_SET); // Check if this is the correct header. $packet = unpack("Cdata", fread($this->_filePointer, 1)); if ($packet['data'] != $packetType) @@ -272,6 +345,20 @@ } /** + * Encode a standard vorbis header. + * + * @access private + * @param int $packetType + * @return string + */ + function _encodeCommonHeader($packetType) { + $header = ""; + $header .= pack("C", $packetType); + $header .= OGG_STREAM_CAPTURE_VORBIS; + return ($header); + } + + /** * Parse the comments header (the second of three headers) of a Vorbis stream. * * This function parses the comments header. The comments header contains a series of @@ -283,14 +370,17 @@ */ function _decodeCommentsHeader() { - $this->_decodeCommonHeader(OGG_VORBIS_COMMENTS_HEADER, 1); + $this->_decodeCommonHeader(OGG_VORBIS_COMMENTS_HEADER, OGG_VORBIS_COMMENTS_PAGE_OFFSET); - // Decode the vendor string length. + // Decode the vendor string length as a 32-bit unsigned integer. $vendor_len = unpack("Vdata", fread($this->_filePointer, 4)); + // Retrieve the vendor string from the stream. $this->_vendor = fread($this->_filePointer, $vendor_len['data']); - // Decode the size of the comments list. + // Decode the size of the comments list as a 32-bit unsigned integer. $comment_list_length = unpack("Vdata", fread($this->_filePointer, 4)); + // Iterate through the comments list. for ($i = 0; $i < $comment_list_length['data']; ++$i) { + // Unpack the length of this comment. $comment_length = unpack("Vdata", fread($this->_filePointer, 4)); // Comments are in the format 'ARTIST=Super Furry Animals', so split it on the equals character. // NOTE: Equals characters are strictly prohibited in either the COMMENT or DATA parts. @@ -316,6 +406,54 @@ $framing_bit = unpack("Cdata", fread($this->_filePointer, 1)); if ($framing_bit['data'] != 1) PEAR::raiseError("Stream Undecodable", OGG_VORBIS_ERROR_UNDECODABLE); + $this->_original_comments_length = ftell($this->_filePointer) - $this->_streamList[OGG_VORBIS_COMMENTS_PAGE_OFFSET]['body_offset']; + } + + function _encodeCommentsHeader() + { + $header = $this->_encodeCommonHeader(OGG_VORBIS_COMMENTS_HEADER); + $header .= pack("V", strlen($this->_vendor)); + $header .= $this->_vendor; + $cmts = 0; + foreach ($this->_comments as $comment) { + if (is_array($comment)) + $cmts += count($comment); + else + ++$cmts; + } + // Pack the length of comments. + $header .= pack("V", $cmts); + // Iterate through the comments, and pack them into the stream. + foreach ($this->_comments as $title => $comment) { + // This comment has two or greater values. + if (is_array($comment)) { + foreach ($comment as $offset => $s_comment) + $header .= $this->_packComment($title, $s_comment); + } else + $header .= $this->_packComment($title, $comment); + } + // Pack the framing bit. + $header .= pack("C", 1); + + return ($header); + } + + /** + * Pack a comment into binary format. + * + * @access private + * @param string $title + * @param string $value + * @return string + */ + function _packComment($title, $value) + { + $header = ""; + $value = utf8_encode($value); + $comment = $title . "=" . $value; + $header .= pack("V", strlen($comment)); + $header .= $comment; + return $header; } /** @@ -327,8 +465,8 @@ * comments (e.g. TITLE, ARTIST, etc.) for use in Vorbis streams, they are not * guaranteed to appear. * - * @return array * @access public + * @return array */ function getCommentList() { @@ -371,6 +509,11 @@ */ function setField($field, $value, $replace = true) { + if (strpos($field, "=") !== false) + return PEAR::raiseError("Comments must not contain equals signs.", OGG_VORBIS_ERROR_ILLEGAL_COMMENT); + if (strpos($value, "=") !== false) + return PEAR::raiseError("Comments must not contain equals signs.", OGG_VORBIS_ERROR_ILLEGAL_COMMENT); + if ($replace || ! isset($this->_comments[$field])) { $this->_comments[$field] = $value; } else {