Index: trunk/extensions/OggHandler/PEAR/File_Ogg/File/Ogg.php |
— | — | @@ -156,6 +156,16 @@ |
157 | 157 | var $_streams = array(); |
158 | 158 | |
159 | 159 | /** |
| 160 | + * Length in seconds of each stream group |
| 161 | + */ |
| 162 | + var $_groupLengths = array(); |
| 163 | + |
| 164 | + /** |
| 165 | + * Total length in seconds of the entire file |
| 166 | + */ |
| 167 | + var $_totalLength; |
| 168 | + |
| 169 | + /** |
160 | 170 | * Returns an interface to an Ogg physical stream. |
161 | 171 | * |
162 | 172 | * This method takes the path to a local file and examines it for a physical |
— | — | @@ -335,17 +345,17 @@ |
336 | 346 | /** |
337 | 347 | * @access private |
338 | 348 | */ |
339 | | - function _decodePageHeader($pageData, $pageOffset, $pageFinish) |
| 349 | + function _decodePageHeader($pageData, $pageOffset, $groupId) |
340 | 350 | { |
341 | 351 | // Extract the various bits and pieces found in each packet header. |
342 | 352 | if (substr($pageData, 0, 4) != OGG_CAPTURE_PATTERN) |
343 | 353 | return (false); |
344 | 354 | |
345 | | - $stream_version = unpack("c1data", substr($pageData, 4, 1)); |
| 355 | + $stream_version = unpack("C1data", substr($pageData, 4, 1)); |
346 | 356 | if ($stream_version['data'] != 0x00) |
347 | | - return (falses); |
| 357 | + return (false); |
348 | 358 | |
349 | | - $header_flag = unpack("cdata", substr($pageData, 5, 1)); |
| 359 | + $header_flag = unpack("Cdata", substr($pageData, 5, 1)); |
350 | 360 | |
351 | 361 | // Exact granule position |
352 | 362 | $abs_granule_pos = self::_littleEndianBin2Hex( substr($pageData, 6, 8)); |
— | — | @@ -357,24 +367,29 @@ |
358 | 368 | $stream_serial = unpack("Vdata", substr($pageData, 14, 4)); |
359 | 369 | $page_sequence = unpack("Vdata", substr($pageData, 18, 4)); |
360 | 370 | $checksum = unpack("Vdata", substr($pageData, 22, 4)); |
361 | | - $page_segments = unpack("cdata", substr($pageData, 26, 1)); |
| 371 | + $page_segments = unpack("Cdata", substr($pageData, 26, 1)); |
362 | 372 | $segments_total = 0; |
363 | 373 | for ($i = 0; $i < $page_segments['data']; ++$i) { |
364 | | - $segments = unpack("Cdata", substr($pageData, 26 + ($i + 1), 1)); |
365 | | - $segments_total += $segments['data']; |
| 374 | + $segment_length = unpack("Cdata", substr($pageData, 26 + ($i + 1), 1)); |
| 375 | + $segments_total += $segment_length['data']; |
366 | 376 | } |
367 | | - $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['stream_version'] = $stream_version['data']; |
368 | | - $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['header_flag'] = $header_flag['data']; |
369 | | - $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['abs_granule_pos'] = $abs_granule_pos; |
370 | | - $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['approx_granule_pos'] = $approx_granule_pos; |
371 | | - $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['checksum'] = sprintf("%u", $checksum['data']); |
372 | | - $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['segments'] = $segments_total; |
373 | | - $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['head_offset'] = $pageOffset; |
374 | | - $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['body_offset'] = $pageOffset + 26 + $page_segments['data'] + 1; |
375 | | - $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['body_finish'] = $pageFinish; |
376 | | - $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']]['data_length'] = $pageFinish - $pageOffset; |
377 | | - |
378 | | - return (true); |
| 377 | + $pageFinish = $pageOffset + 27 + $page_segments['data'] + $segments_total; |
| 378 | + $page = array( |
| 379 | + 'stream_version' => $stream_version['data'], |
| 380 | + 'header_flag' => $header_flag['data'], |
| 381 | + 'abs_granule_pos' => $abs_granule_pos, |
| 382 | + 'approx_granule_pos' => $approx_granule_pos, |
| 383 | + 'checksum' => sprintf("%u", $checksum['data']), |
| 384 | + 'segments' => $page_segments['data'], |
| 385 | + 'head_offset' => $pageOffset, |
| 386 | + 'body_offset' => $pageOffset + 27 + $page_segments['data'], |
| 387 | + 'body_finish' => $pageFinish, |
| 388 | + 'data_length' => $pageFinish - $pageOffset, |
| 389 | + 'group' => $groupId, |
| 390 | + ); |
| 391 | + |
| 392 | + $this->_streamList[$stream_serial['data']]['stream_page'][$page_sequence['data']] = $page; |
| 393 | + return $page; |
379 | 394 | } |
380 | 395 | |
381 | 396 | /** |
— | — | @@ -383,64 +398,70 @@ |
384 | 399 | function _splitStreams() |
385 | 400 | { |
386 | 401 | // Loop through the physical stream until there are no more pages to read. |
387 | | - while (true) { |
388 | | - $this_page_offset = ftell($this->_filePointer); |
389 | | - $next_page_offset = $this_page_offset; |
390 | | - |
391 | | - // Read in 65311 bytes from the physical stream. Ogg documentation |
392 | | - // states that a page has a maximum size of 65307 bytes. An extra |
393 | | - // 4 bytes are added to ensure that the capture pattern of the next |
394 | | - // pages comes through. |
395 | | - if (! ($stream_data = fread($this->_filePointer, OGG_MAXIMUM_PAGE_SIZE))) |
| 402 | + $groupId = 0; |
| 403 | + $openStreams = 0; |
| 404 | + $this_page_offset = 0; |
| 405 | + while (!feof($this->_filePointer)) { |
| 406 | + $pageData = fread($this->_filePointer, 282); |
| 407 | + if (strval($pageData) === '') { |
396 | 408 | break; |
| 409 | + } |
| 410 | + $page = $this->_decodePageHeader($pageData, $this_page_offset, $groupId); |
| 411 | + if ($page === false) { |
| 412 | + throw new PEAR_Exception("Cannot decode Ogg file: Invalid page at offset $this_page_offset", OGG_ERROR_UNDECODABLE); |
| 413 | + } |
397 | 414 | |
398 | | - // Split the data into various pages. |
399 | | - $stream_pages = explode(OGG_CAPTURE_PATTERN, $stream_data); |
400 | | - // If the maximum data has been read, it is likely that this is an |
401 | | - // intermediate page. Since the split adds an empty element at the |
402 | | - // start of the array, we must account for that by substracting one |
403 | | - // iteration from the loop. This argument also follows if the data |
404 | | - // includes an incomplete page at the end, in which case we substract |
405 | | - // two iterations from the loop. |
406 | | - $number_pages = (strlen($stream_data) == OGG_MAXIMUM_PAGE_SIZE) ? count($stream_pages) - 2 : count($stream_pages) - 1; |
407 | | - if (! count($stream_pages)) |
408 | | - break; |
409 | | - if ($number_pages <= 0) { |
410 | | - // Don't go into an infinite loop |
411 | | - throw new PEAR_Exception('No pages found', OGG_ERROR_UNDECODABLE); |
| 415 | + // Keep track of multiplexed groups |
| 416 | + if ($page['header_flag'] & 2/*bos*/) { |
| 417 | + $openStreams++; |
| 418 | + } elseif ($page['header_flag'] & 4/*eos*/) { |
| 419 | + $openStreams--; |
| 420 | + if (!$openStreams) { |
| 421 | + // End of group |
| 422 | + $groupId++; |
| 423 | + } |
412 | 424 | } |
| 425 | + if ($openStreams < 0) { |
| 426 | + throw new PEAR_Exception("Unexpected end of stream", OGG_ERROR_UNDECODABLE); |
| 427 | + } |
413 | 428 | |
414 | | - for ($i = 1; $i <= $number_pages; ++$i) { |
415 | | - $stream_pages[$i] = OGG_CAPTURE_PATTERN . $stream_pages[$i]; |
416 | | - // Set the current page offset to the next page offset of the |
417 | | - // previous loop iteration. |
418 | | - $this_page_offset = $next_page_offset; |
419 | | - // Set the next page offset to the current page offset plus the |
420 | | - // length of the current page. |
421 | | - $next_page_offset += strlen($stream_pages[$i]); |
422 | | - $this->_decodePageHeader($stream_pages[$i], $this_page_offset, $next_page_offset - 1); |
423 | | - } |
424 | | - fseek($this->_filePointer, $next_page_offset, SEEK_SET); |
| 429 | + $this_page_offset = $page['body_finish']; |
| 430 | + fseek($this->_filePointer, $this_page_offset, SEEK_SET); |
425 | 431 | } |
426 | 432 | // Loop through the streams, and find out what type of stream is available. |
| 433 | + $groupLengths = array(); |
427 | 434 | foreach ($this->_streamList as $stream_serial => $pages) { |
428 | 435 | fseek($this->_filePointer, $pages['stream_page'][0]['body_offset'], SEEK_SET); |
429 | 436 | $pattern = fread($this->_filePointer, 8); |
430 | 437 | if (preg_match("/" . OGG_STREAM_CAPTURE_VORBIS . "/", $pattern)) { |
431 | 438 | $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_VORBIS; |
432 | | - $this->_streams[$stream_serial] =& new File_Ogg_Vorbis($stream_serial, $this->_streamList[$stream_serial]['stream_page'], $this->_filePointer); |
| 439 | + $stream = new File_Ogg_Vorbis($stream_serial, $pages['stream_page'], $this->_filePointer); |
433 | 440 | } elseif (preg_match("/" . OGG_STREAM_CAPTURE_SPEEX . "/", $pattern)) { |
434 | 441 | $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_SPEEX; |
435 | | - $this->_streams[$stream_serial] =& new File_Ogg_Speex($stream_serial, $this->_streamList[$stream_serial]['stream_page'], $this->_filePointer); |
| 442 | + $stream = new File_Ogg_Speex($stream_serial, $pages['stream_page'], $this->_filePointer); |
436 | 443 | } elseif (preg_match("/" . OGG_STREAM_CAPTURE_FLAC . "/", $pattern)) { |
437 | 444 | $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_FLAC; |
438 | | - $this->_streams[$stream_serial] =& new File_Ogg_Flac($stream_serial, $this->_streamList[$stream_serial]['stream_page'], $this->_filePointer); |
| 445 | + $stream = new File_Ogg_Flac($stream_serial, $pages['stream_page'], $this->_filePointer); |
439 | 446 | } elseif (preg_match("/" . OGG_STREAM_CAPTURE_THEORA . "/", $pattern)) { |
440 | 447 | $this->_streamList[$stream_serial]['stream_type'] = OGG_STREAM_THEORA; |
441 | | - $this->_streams[$stream_serial] =& new File_Ogg_Theora($stream_serial, $this->_streamList[$stream_serial]['stream_page'], $this->_filePointer); |
442 | | - } else |
443 | | - $this->_streamList[$stream_serial]['stream_type'] = "unknown"; |
| 448 | + $stream = new File_Ogg_Theora($stream_serial, $pages['stream_page'], $this->_filePointer); |
| 449 | + } else { |
| 450 | + $pages['stream_type'] = "unknown"; |
| 451 | + $stream = false; |
| 452 | + } |
| 453 | + |
| 454 | + if ($stream) { |
| 455 | + $this->_streams[$stream_serial] = $stream; |
| 456 | + $group = $pages['stream_page'][0]['group']; |
| 457 | + if (isset($groupLengths[$group])) { |
| 458 | + $groupLengths[$group] = max($groupLengths[$group], $stream->getLength()); |
| 459 | + } else { |
| 460 | + $groupLengths[$group] = $stream->getLength(); |
| 461 | + } |
| 462 | + } |
444 | 463 | } |
| 464 | + $this->_groupLengths = $groupLengths; |
| 465 | + $this->_totalLength = array_sum( $groupLengths ); |
445 | 466 | unset($this->_streamList); |
446 | 467 | } |
447 | 468 | |
— | — | @@ -549,5 +570,12 @@ |
550 | 571 | else |
551 | 572 | return array(); |
552 | 573 | } |
| 574 | + |
| 575 | + /** |
| 576 | + * Get the total length of the group of streams |
| 577 | + */ |
| 578 | + function getLength() { |
| 579 | + return $this->_totalLength; |
| 580 | + } |
553 | 581 | } |
554 | 582 | ?> |
Index: trunk/extensions/OggHandler/PEAR/File_Ogg/File/Ogg/Bitstream.php |
— | — | @@ -81,8 +81,12 @@ |
82 | 82 | // This gives an accuracy of approximately 99.7% to the streamsize of ogginfo. |
83 | 83 | foreach ( $streamData as $packet ) { |
84 | 84 | $this->_streamSize += $packet['data_length']; |
85 | | - $this->_lastGranulePos = max($this->_lastGranulePos, $packet['abs_granule_pos']); |
| 85 | + # Reject -1 as a granule pos, that means no segment finished in the packet |
| 86 | + if ( $packet['abs_granule_pos'] != 'ffffffffffffffff' ) { |
| 87 | + $this->_lastGranulePos = max($this->_lastGranulePos, $packet['abs_granule_pos']); |
| 88 | + } |
86 | 89 | } |
| 90 | + $this->_group = $streamData[0]['group']; |
87 | 91 | } |
88 | 92 | |
89 | 93 | /** |
— | — | @@ -114,6 +118,14 @@ |
115 | 119 | return ($this->_streamSize); |
116 | 120 | } |
117 | 121 | |
| 122 | + /** |
| 123 | + * Get the multiplexed group ID |
| 124 | + */ |
| 125 | + function getGroup() |
| 126 | + { |
| 127 | + return $this->_group; |
| 128 | + } |
| 129 | + |
118 | 130 | } |
119 | 131 | |
120 | 132 | ?> |
Index: trunk/extensions/OggHandler/PEAR/File_Ogg/File/Ogg/Media.php |
— | — | @@ -104,8 +104,12 @@ |
105 | 105 | { |
106 | 106 | // Decode the vendor string length as a 32-bit unsigned integer. |
107 | 107 | $vendor_len = unpack("Vdata", fread($this->_filePointer, 4)); |
108 | | - // Retrieve the vendor string from the stream. |
109 | | - $this->_vendor = fread($this->_filePointer, $vendor_len['data']); |
| 108 | + if ( $vendor_len['data'] > 0 ) { |
| 109 | + // Retrieve the vendor string from the stream. |
| 110 | + $this->_vendor = fread($this->_filePointer, $vendor_len['data']); |
| 111 | + } else { |
| 112 | + $this->_vendor = ''; |
| 113 | + } |
110 | 114 | // Decode the size of the comments list as a 32-bit unsigned integer. |
111 | 115 | $comment_list_length = unpack("Vdata", fread($this->_filePointer, 4)); |
112 | 116 | // Iterate through the comments list. |
— | — | @@ -177,31 +181,6 @@ |
178 | 182 | } |
179 | 183 | |
180 | 184 | /** |
181 | | - * Set a field for this stream's meta description. |
182 | | - * |
183 | | - * @access public |
184 | | - * @param string $field |
185 | | - * @param mixed $value |
186 | | - * @param boolean $replace |
187 | | - */ |
188 | | - function setField($field, $value, $replace = true) |
189 | | - { |
190 | | - if (strpos($field, "=") !== false) |
191 | | - return PEAR::raiseError("Comments must not contain equals signs.", OGG_VORBIS_ERROR_ILLEGAL_COMMENT); |
192 | | - if (strpos($value, "=") !== false) |
193 | | - return PEAR::raiseError("Comments must not contain equals signs.", OGG_VORBIS_ERROR_ILLEGAL_COMMENT); |
194 | | - |
195 | | - if ($replace || ! isset($this->_comments[$field])) { |
196 | | - $this->_comments[$field] = $value; |
197 | | - } else { |
198 | | - if (is_array($this->_comments[$field])) |
199 | | - $this->_comments[$field][] = $value; |
200 | | - else |
201 | | - $this->_comments[$field] = array($this->_comments[$field], $value); |
202 | | - } |
203 | | - } |
204 | | - |
205 | | - /** |
206 | 185 | * Get the entire comments array. |
207 | 186 | * May return an empty array if the bitstream does not support comments. |
208 | 187 | * |