Index: trunk/extensions/MetavidWiki/skins/mv_embed/oggServer/OggChopRequestHanlder.php |
— | — | @@ -0,0 +1,45 @@ |
| 2 | +<?php |
| 3 | +/* |
| 4 | + * OggChopRequestHanlder.php is a simple ogg video server for mediawiki |
| 5 | + * it uses the general oggChop class (which we should probably make part of |
| 6 | + * PEAR oggServer or something like that ) |
| 7 | + * |
| 8 | + * it takes arguments http arguments: |
| 9 | + * file=WikiFileTitle.ogg |
| 10 | + * t=start_time_npt/end_time_npt |
| 11 | + * |
| 12 | + * if no filename.ogg.meta file is available it generates it. |
| 13 | + * (this can be slow its recommended you pre-generate the .meta files) |
| 14 | + * |
| 15 | + * * This is just a fallback solution / prototypeing for oggz_chop fastCGI.\ |
| 16 | + * this will just server from the nearest keyframe |
| 17 | +*/ |
| 18 | + |
| 19 | +//for now just hard code the request: |
| 20 | +//$oggPath = '/var/www/house_proceeding_01-04-07.ogg'; |
| 21 | +$oggPath = '/var/www/lucky.ogv'; |
| 22 | +$time = '0:0:10/0:0:20'; |
| 23 | + |
| 24 | +require_once( 'OggChop.php' ); |
| 25 | + |
| 26 | +$ogg = new OggChop( $oggPath ); |
| 27 | +$ogg->play(); |
| 28 | +die(); |
| 29 | + |
| 30 | +/*utility functions*/ |
| 31 | +function npt2seconds( $str_time ) { |
| 32 | + $time_ary = explode( ':', $str_time ); |
| 33 | + $hours = $min = $sec = 0; |
| 34 | + if ( count( $time_ary ) == 3 ) { |
| 35 | + $hours = (int) $time_ary[0]; |
| 36 | + $min = (int) $time_ary[1]; |
| 37 | + $sec = (float) $time_ary[2]; |
| 38 | + } else if ( count( $time_ary ) == 2 ) { |
| 39 | + $min = (int) $time_ary[0]; |
| 40 | + $sec = (float) $time_ary[1]; |
| 41 | + } else if ( count( $time_ary ) == 1 ) { |
| 42 | + $sec = (float) $time_ary[0]; |
| 43 | + } |
| 44 | + return ( $hours * 3600 ) + ( $min * 60 ) + $sec; |
| 45 | +} |
| 46 | +?> |
\ No newline at end of file |
Index: trunk/extensions/MetavidWiki/skins/mv_embed/oggServer/OggChop.php |
— | — | @@ -0,0 +1,260 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +define('OGGCHOP_META_VERSION', 1); |
| 5 | + |
| 6 | +define('OGGCHOP_META_EXT', '.meta'); |
| 7 | + |
| 8 | +$oggDir = dirname(__FILE__); |
| 9 | + |
| 10 | +//require the PEAR php module |
| 11 | +ini_set( 'include_path', |
| 12 | + "$oggDir/PEAR/File_Ogg" . |
| 13 | + PATH_SEPARATOR . |
| 14 | + ini_get( 'include_path' )); |
| 15 | + |
| 16 | +class OggChop { |
| 17 | + //initial variable values: |
| 18 | + var $meta = false; |
| 19 | + var $loadWaitCount = 0; |
| 20 | + |
| 21 | + //header values: |
| 22 | + var $contentLength = 0; |
| 23 | + |
| 24 | + function __construct( $oggPath ){ |
| 25 | + $this->oggPath = $oggPath; |
| 26 | + } |
| 27 | + /* |
| 28 | + * play takes in start_sec and end_sec and sends out packet |
| 29 | + * |
| 30 | + * @param float $start_sec ( start time in float seconds) |
| 31 | + * @param floast $end_sec (optional end time in float) |
| 32 | + */ |
| 33 | + function play($start_sec=false, $end_sec = false){ |
| 34 | + //make sure we have the metadata ready: |
| 35 | + $this->loadMeta(); |
| 36 | + |
| 37 | + //get http byte range headers:: |
| 38 | + $this->getByteRangeRequest(); |
| 39 | + |
| 40 | + //if both start and end are false send the full file: |
| 41 | + if(!$start_sec && !$end_sec){ |
| 42 | + //set from full file context:: |
| 43 | + $this->contentLength = filesize( $this->oggPath ); |
| 44 | + $this->contentRange = array( |
| 45 | + 's' => 0, |
| 46 | + 'e' => $this->contentLength -1, |
| 47 | + 't' => $this->contentLength |
| 48 | + ); |
| 49 | + $this->duration = $this->getMeta('duration'); |
| 50 | + $this->sendHeaders(); |
| 51 | + //output the full file: |
| 52 | + |
| 53 | + //turn off output buffering |
| 54 | + while (ob_get_level() > 0) { |
| 55 | + ob_end_flush(); |
| 56 | + } |
| 57 | + @readfile( $this->oggPath ); |
| 58 | + //exit the application (might be a cleaner way to do this) |
| 59 | + die(); |
| 60 | + }else{ |
| 61 | + $kEnd = false; |
| 62 | + //we have a temporal request |
| 63 | + if(!$start_sec || $start_sec < 0) |
| 64 | + $start_sec = 0; |
| 65 | + |
| 66 | + if(!$end_sec || $end_sec > $this->getMeta('duration')){ |
| 67 | + $end_sec = $this->getMeta('duration'); |
| 68 | + $kEnd = array( |
| 69 | + $this->getMeta('duration'), |
| 70 | + filesize( $this->oggPath ) |
| 71 | + ); |
| 72 | + } |
| 73 | + |
| 74 | + //set the duration: |
| 75 | + $this->duration = $end_sec - $start_sec; |
| 76 | + |
| 77 | + //set the content size for the segment: |
| 78 | + $kStart = $this->getKeyFrameByteFromTime( $start_sec ); |
| 79 | + if( ! $kEnd ) |
| 80 | + $kEnd = $this->getKeyFrameByteFromTime( $end_sec , false); |
| 81 | + |
| 82 | + //debug output: |
| 83 | + /* |
| 84 | + print_r($this->meta['theoraKeyFrameInx']); |
| 85 | + print "Start : ". print_r($kStart, true) ."\n"; |
| 86 | + print "End Byte:" . print_r($kEnd, true) . "\n"; |
| 87 | + die(); |
| 88 | + |
| 89 | + @@todo build the ogg skeleton header |
| 90 | + 1) that gives the offset between |
| 91 | + // $kStart time and the requested time. |
| 92 | + |
| 93 | + //for now just start output at the given byte range |
| 94 | + $this->outputByteRange( $kStart[1], $kEnd[1]); |
| 95 | + } |
| 96 | + |
| 97 | + } |
| 98 | + function getKeyFrameByteFromTime( $reqTime, $prev_key=true ){ |
| 99 | + //::binary array search goes here:: |
| 100 | + |
| 101 | + //linear search (may be faster in some cases (like start of the file seeks) |
| 102 | + $timeDiff = $this->getMeta('duration'); |
| 103 | + reset($this->meta['theoraKeyFrameInx']); |
| 104 | + $pByte = current( $this->meta['theoraKeyFrameInx'] ); |
| 105 | + $pKtime = key( $this->meta['theoraKeyFrameInx'] ); |
| 106 | + foreach($this->meta['theoraKeyFrameInx'] as $kTime => $byte){ |
| 107 | + if($kTime > $reqTime) |
| 108 | + break; |
| 109 | + $pByte = $byte; |
| 110 | + $pKtime = $kTime; |
| 111 | + } |
| 112 | + //return the keyframe array by default the prev key |
| 113 | + if($prev_key){ |
| 114 | + return array($pKtime, $pByte); |
| 115 | + }else{ |
| 116 | + return array($kTime, $byte); |
| 117 | + } |
| 118 | + } |
| 119 | + /* |
| 120 | + * outputByteRange |
| 121 | + */ |
| 122 | + function outputByteRange($startByte, $endByte = null){ |
| 123 | + //media files use large chunk size: |
| 124 | + $chunkSize = 32768; |
| 125 | + $this->fp = fopen( $this->oggPath, 'r'); |
| 126 | + fseek($this->fp, $startByte); |
| 127 | + while (! feof($this->fp)) |
| 128 | + { |
| 129 | + if( $endByte != null ){ |
| 130 | + if( ftell( $this->fp ) + $chunkSize > $endByte ){ |
| 131 | + $read_amount = ( ftell ( $this->fp ) + $chunkSize ) - $endByte; |
| 132 | + echo fread($this->fp, $read_amount); |
| 133 | + break; |
| 134 | + } |
| 135 | + } |
| 136 | + echo fread($this->fp, $chunkSize); |
| 137 | + } |
| 138 | + } |
| 139 | + function getByteRangeRequest(){ |
| 140 | + //set local vars for byte range request handling |
| 141 | + } |
| 142 | + function sendHeaders(){ |
| 143 | + header ("Accept-Ranges: bytes"); |
| 144 | + |
| 145 | + //set range conditional headers: |
| 146 | + if( $this->contentLength ) |
| 147 | + header ( "Content-Length: " . $this->contentLength ); |
| 148 | + |
| 149 | + //set the X-content duration: |
| 150 | + if( $this->duration ) |
| 151 | + header ( "X-Content-Duration: " . $this->duration ); |
| 152 | + |
| 153 | + //set content range see spec: |
| 154 | + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.16 |
| 155 | + if( $this->contentRange ) |
| 156 | + header ( "Content-Range: bytes " . |
| 157 | + $this->contentRange['s'] . "-" . |
| 158 | + $this->contentRange['e'] . "/" . |
| 159 | + $this->contentRange['t'] |
| 160 | + ); |
| 161 | + |
| 162 | + //constant headers (for video) |
| 163 | + if( isset($this->meta['height']) ) |
| 164 | + header( "X-Content-Video-Height: " . $this->meta['height'] ); |
| 165 | + |
| 166 | + if( isset($this->meta['width']) ) |
| 167 | + header( "X-Content-Video-Width: " . $this->meta['width'] ); |
| 168 | + |
| 169 | + //set mime type (only video for now) |
| 170 | + header ("Content-Type: video/ogg"); |
| 171 | + |
| 172 | + } |
| 173 | + function sendByteRange( $startByte, $endByte){ |
| 174 | + |
| 175 | + } |
| 176 | + /* |
| 177 | + * getMeta (returns the value of a metadata key) |
| 178 | + */ |
| 179 | + function getMeta( $key ){ |
| 180 | + if( !$this->meta ){ |
| 181 | + $this->loadMeta(); |
| 182 | + } |
| 183 | + if( isset( $this->meta[$key] ) ) |
| 184 | + return $this->meta[ $key ]; |
| 185 | + return false; |
| 186 | + } |
| 187 | + function loadMeta(){ |
| 188 | + //load from the file: |
| 189 | + if( is_file( $this->oggPath . OGGCHOP_META_EXT ) ){ |
| 190 | + $oggMeta = file_get_contents( $this->oggPath . OGGCHOP_META_EXT); |
| 191 | + //check if a separate request is working on generating the file: |
| 192 | + if( trim( $oggMeta ) == 'loading' ){ |
| 193 | + if( $this->loadWaitCount >= 24 ){ |
| 194 | + //we have waited 2 min with no luck.. |
| 195 | + //@@todo we should flag that ogg file as broken? |
| 196 | + // and just redirect to normal output? (for now just set meta to false) |
| 197 | + $this->meta = false; |
| 198 | + //fail: |
| 199 | + return false; |
| 200 | + }else{ |
| 201 | + //some other request is "loading" metadata sleep for 5 seconds and try again |
| 202 | + sleep(5); |
| 203 | + $this->loadWaitCount++; |
| 204 | + return $this->loadMeta(); |
| 205 | + } |
| 206 | + }else{ |
| 207 | + $this->meta = unserialize ( $oggMeta ); |
| 208 | + if( $this->meta['version'] == 'OGGCHOP_META_VERSION' ){ |
| 209 | + //we have a good version of the metadata return true: |
| 210 | + return true; |
| 211 | + }else{ |
| 212 | + $this->meta = false; |
| 213 | + } |
| 214 | + } |
| 215 | + } |
| 216 | + //if the file does not exist or $this->meta is still false:: |
| 217 | + if( ! is_file( $this->oggPath . OGGCHOP_META_EXT ) || $this->meta === false ){ |
| 218 | + //set the meta file to "loading" (avoids multiple indexing requests) |
| 219 | + file_put_contents( $this->oggPath . OGGCHOP_META_EXT, 'loading'); |
| 220 | + |
| 221 | + //load up the File/Ogg Pear module |
| 222 | + if ( !class_exists( 'File_Ogg' ) ) { |
| 223 | + require( 'File/Ogg.php' ); |
| 224 | + } |
| 225 | + $f = new File_Ogg( $this->oggPath ); |
| 226 | + $streams = array(); |
| 227 | + $this->meta = array( |
| 228 | + 'version' => OGGCHOP_META_VERSION |
| 229 | + ); |
| 230 | + foreach ( $f->listStreams() as $streamType => $streamIDs ) { |
| 231 | + foreach ( $streamIDs as $streamID ) { |
| 232 | + $stream = $f->getStream( $streamID ); |
| 233 | + //for now only support a fist theora stream we find: |
| 234 | + if( strtolower( $stream->getType() ) == 'theora'){ |
| 235 | + $this->meta['theoraKeyFrameInx'] = $stream->getKeyFrameIndex(); |
| 236 | + //set the width and height: |
| 237 | + $head = $stream->getHeader(); |
| 238 | + $this->meta['width'] = $head['PICW']; |
| 239 | + $this->meta['height'] = $head['PICH']; |
| 240 | + break; |
| 241 | + } |
| 242 | + /* more detailed per-stream metadata:: |
| 243 | + * $this->meta['streams'][$streamID] = array( |
| 244 | + 'serial' => $stream->getSerial(), |
| 245 | + 'group' => $stream->getGroup(), |
| 246 | + 'type' => $stream->getType(), |
| 247 | + 'vendor' => $stream->getVendor(), |
| 248 | + 'length' => $stream->getLength(), |
| 249 | + 'size' => $stream->getSize(), |
| 250 | + 'header' => $stream->getHeader(), |
| 251 | + 'comments' => $stream->getComments() |
| 252 | + );*/ |
| 253 | + } |
| 254 | + } |
| 255 | + $this->meta['duration'] = $f->getLength(); |
| 256 | + //cahce the metadata:: |
| 257 | + file_put_contents( $this->oggPath . OGGCHOP_META_EXT, serialize( $this->meta) ); |
| 258 | + return true; |
| 259 | + } |
| 260 | + } |
| 261 | +} |