123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579 |
- <?php
- /* Ogg (vorbis/theora) php manipulation library v1.3g
- by Nicolas Ricquemaque <f1iqf@hotmail.com>
- GNU GPL >= 2.1 http://www.gnu.org/copyleft/lesser.html GNU LGPL
- Primary for use with itheora http://menguy.aymeric.free.fr/theora/ */
-
- define("OGGCLASSVERSION", " [Ogg.class.php v1.3g]");
- define("CACHING",0);
- define("ANSI",0);
- define("NOCACHING",1);
- define("UTF8",2);
- define("NOVENDORTAG",4);
- define("UPDATECACHE",8);
-
- class Ogg
- {
- // Public variables
- var $LastError=false; // false if no error occured, else error text
- var $Streams=array(); // All the file data stored here
- // Private variables
- var $Data;
- var $cache,$cachedir; // shall we use & write cache and where ?
- var $tagvendor; // change it to false if you don't want the oggclass to add its name to your vendor field when writting new comments
- var $StreamsUTF8; // if true, the vendor and comment information will be in utf8 inside the Streams array. Else, will be ansi.
- var $InputFile,$OutputFile,$MaxTime=0,$refresh;
- // constructor; $options contains a conbination of (CACHING or NOCACHING) + (ANSI or UTF8) + NOVENDORTAG + UPDATECACHE
- // by default $options enables CACHING, ANSI strings and Vendor Tag tagging
- // UPDATECACHE is forcing the analysing of the file and the update of the cache, if caching enabled.
- // $cachedir is optional cache directory
- function Ogg ($OggFile, $options=0, $cachedir="/cache.ogg")
- {
- // Check php version
- if (intval(PHP_VERSION)<4) return($this->creturn("PHP version must me >= 4 but we show PHP v".PHP_VERSION));
-
- // Options
- $this->StreamsUTF8=($options&UTF8)>0;
- $this->cache=($options&NOCACHING)==0;
- $this->tagvendor=($options&NOVENDORTAG)==0;
-
- clearstatcache();
- $this->Streams['oggfile']=$OggFile;
- if ((strpos($OggFile,"://")===false)&&!file_exists($this->realpath($OggFile))) return ($this->creturn("Inexisting OGG file ".$OggFile));
- // handle caching
- if ($this->cache)
- {
- $this->cachedir=rtrim($cachedir,"/");
- if (!is_dir($this->realpath($this->cachedir))) @mkdir ($this->realpath($this->cachedir));
- $cache=$this->cachedir."/".strtr($this->Streams['oggfile'],"/:?&#","-----").".cache";
- $this->Streams['cachefile']=$cache;
- if ((($options&UPDATECACHE)==0)&&is_resource($cachefile = @fopen($this->realpath($this->Streams['cachefile']), "r")))
- {
- if ($s=fread($cachefile,filesize($this->realpath($this->Streams['cachefile']))))
- {
- $this->Streams=unserialize($s);
- $this->Streams['picturable']=$this->Picturable(); // Can the server extract a picture ? better check again.
- // Adapt string encoding to what's required by user
- if (isset($this->Streams['summary'])) $this->Streams['summary']=$this->Decode($this->Streams['summary']);
- if (isset($this->Streams['theora']['vendor'])) $this->Streams['theora']['vendor']=$this->Decode($this->Streams['theora']['vendor']);
- if (isset($this->Streams['vorbis']['vendor'])) $this->Streams['vorbis']['vendor']=$this->Decode($this->Streams['vorbis']['vendor']);
- if (isset($this->Streams['theora']['comments'])) foreach ($this->Streams['theora']['comments'] as $key => $comment) $this->Streams['theora']['comments'][$key]=$this->Decode($comment);
- if (isset($this->Streams['vorbis']['comments'])) foreach ($this->Streams['vorbis']['comments'] as $key => $comment) $this->Streams['vorbis']['comments'][$key]=$this->Decode($comment);
- $this->Streams['encoding']=$this->StreamsUTF8?"utf-8":"ansi";
- // double-check if the file we found in cache is really the one we should have, by comparing their sizes, for local files only
- if ((strpos($OggFile,"://")===false)&&($this->Streams['size']!=filesize($this->realpath($this->Streams['oggfile'])))) unset($this->Streams['source']);
- $this->creturn(true);
- }
- fclose($cachefile);
- $this->Streams['cachefile']=$cache;
- //echo "<!--\n".print_r($this->Streams,true)."-->\n";
- }
- }
-
- //if file could not be read from cache (or sizes did not match), we parse it from file
- if (!isset($this->Streams['source'])) $this->analyze();
-
- // Now check if a write has been left in progress (for local files only)
- if ((strpos($OggFile,"://")===false)&&($tmp=glob($this->realpath($this->Streams['oggfile']."*.tmp"))))
- {
- if (count($tmp)==1) $this->Streams['tmpfile']=$tmp[0];
- elseif (count($tmp)>1) // if there is several possible files, use the last one, delete others
- {
- $this->Streams['tmpfile']=$tmp[0];
- foreach ($tmp as $file)
- if (filemtime($file)>filemtime($this->Streams['tmpfile']))
- {
- @unlink($this->Streams['tmpfile']); // supress old uncompleted tmp file
- $this->Streams['tmpfile']=$file;
- }
- }
- if (isset($this->Streams['tmpfile']))
- {
- $this->Streams['tmpfile']=substr($this->Streams['tmpfile'],strpos($this->Streams['tmpfile'],$this->Streams['oggfile']));
- list($this->refresh,$this->Streams['tmpfileptr']) = sscanf($this->Streams['tmpfile'],$this->Streams['oggfile'].".r%d.p%d.tmp");
- }
- }
- }
- // Public methods
- function GetPicture ($framepos=1,$where=false) //return $path of picture or false; $frame=frame number; $where=path to file to create, if false will be in cache;
- {
- if (!$this->Streams['picturable'])
- {
- if (!$this->Picturable()) return(false);
- else $this->Streams['picturable']=true;
- }
- if (isset($this->Streams['theora']['pictures']))
- {
- if (isset($this->Streams['theora']['pictures'][$framepos]))
- {
- if (file_exists($this->realpath($this->Streams['theora']['pictures'][$framepos]))) return ($this->Streams['theora']['pictures'][$framepos]);
- }
- }
- if (!$this->cache && !$where) return($this->creturn("If the CACHING is disabled, you got to provide a path to your picture..."));
- $file=$where?$where:substr($this->Streams['cachefile'],0,-6).".f$framepos.jpg";
- if (file_exists($this->realpath($file))) // picture seem to have previously alread been computed, but is not in cache: either cache was relaculated and information was lost, or caching is disabled
- { // we use the picture we found, avoiding wasting server time to recalculate
- if (!isset($this->Streams['theora']['pictures'])) $this->Streams['theora']['pictures']=array();
- $this->Streams['theora']['pictures'][$framepos]=$file;
- if ($this->cache) $this->CacheUpdate();
- $this->creturn(true);
- return($file);
- }
- $errorreport=error_reporting(0);
- $movie = new ffmpeg_movie($this->Streams['oggfile'],false);
- if (!$movie) { error_reporting($errorreport); return($this->creturn("Unable to extract a picture from ".$this->Streams['oggfile'])); }
- $frame=$movie->getFrame($framepos);
- if (!$frame) { error_reporting($errorreport); return($this->creturn("Unable to extract the frame #".$framepos." from ".$this->Streams['oggfile'])); }
- $image=$frame->toGDImage();
- if (!$image) { error_reporting($errorreport); return($this->creturn("Unable to extract an image in frame #".$framepos." from ".$this->Streams['oggfile'])); }
- if (!imagejpeg($image,$this->realpath($file))) { error_reporting($errorreport); return($this->creturn("Unable to save a jpeg image from frame #".$framepos." from ".$this->Streams['oggfile'])); }
- if (!isset($this->Streams['theora']['pictures'])) $this->Streams['theora']['pictures']=array();
- $this->Streams['theora']['pictures'][$framepos]=$file;
- if ($this->cache) $this->CacheUpdate();
- error_reporting($errorreport);
- $this->creturn(true);
- return($file);
- }
- function WriteNewComments ($refresh=5)
- {
- if ((strpos($this->Streams['oggfile'],"://")!==false)) return ($this->creturn("It is not possible to change the comment tags on a remote server ! "));
-
- $this->InputFile= fopen($this->realpath($this->Streams['oggfile']), "rb");
- if (!is_resource($this->InputFile)) return ($this->creturn("Could not open OGG file ".$this->Streams['oggfile']));
- if ($this->Streams['source']=='cache')
- if (! ($this->Data = fread($this->InputFile, 0xFFFF)) )
- return ($this->creturn("Could not read in OGG file ".$this->Streams['oggfile']));
-
- if (isset($this->Streams['vorbis']['commentpos']))
- {
- $vorbispacket=$this->PackComments($this->Streams['vorbis']);
- if (!$vorbispacket) return (false);
- }
- else $vorbispacket=false;
-
- if (isset($this->Streams['theora']['commentpos']))
- {
- $theorapacket=$this->PackComments($this->Streams['theora']);
- if (!$theorapacket) return (false);
- }
- else $theorapacket=false;
-
- if (!$vorbispacket && !$theorapacket) return ($this->creturn("Could not find a stream to comment !"));
-
- // Make sure the refresh time will not exceed the script time limit
- if ((get_cfg_var("max_execution_time")>0)&&($refresh>=get_cfg_var("max_execution_time"))) $this->refresh=get_cfg_var("max_execution_time")-2;
- else $this->refresh=$refresh;
-
- $this->MaxTime=time()+$this->refresh;
- $this->Streams['tmpfile']=$this->Streams['oggfile'].".r".$this->refresh;
- @unlink($this->realpath($this->Streams['tmpfile']));
- $this->OutputFile= fopen($this->realpath($this->Streams['tmpfile']), "wb");
- if (!is_resource($this->OutputFile)) return ($this->creturn("Could not create temp file ".$this->Streams['tmpfile']));
-
- if ($vorbispacket&&$theorapacket)
- {
- if ($this->Streams['vorbis']['commentpos']<$this->Streams['theora']['commentpos'])
- {
- if ($this->Streams['vorbis']['commentpos']>0) fwrite($this->OutputFile,substr($this->Data,0,$this->Streams['vorbis']['commentpos']));
- fwrite($this->OutputFile,$vorbispacket);
- if ($this->Streams['vorbis']['commentnext']<$this->Streams['theora']['commentpos'])
- fwrite($this->OutputFile,substr($this->Data,$this->Streams['vorbis']['commentnext'],$this->Streams['theora']['commentpos']-$this->Streams['vorbis']['commentnext']));
- fwrite($this->OutputFile,$theorapacket);
- if ($this->Streams['theora']['commentnext']<strlen($this->Data)) fwrite($this->OutputFile,substr($this->Data,$this->Streams['theora']['commentnext']));
- }
- else
- {
- if ($this->Streams['theora']['commentpos']>0) fwrite($this->OutputFile,substr($this->Data,0,$this->Streams['theora']['commentpos']));
- fwrite($this->OutputFile,$theorapacket);
- if ($this->Streams['theora']['commentnext']<$this->Streams['vorbis']['commentpos'])
- fwrite($this->OutputFile,substr($this->Data,$this->Streams['theora']['commentnext'],$this->Streams['vorbis']['commentpos']-$this->Streams['theora']['commentnext']));
- fwrite($this->OutputFile,$vorbispacket);
- if ($this->Streams['vorbis']['commentnext']<strlen($this->Data)) fwrite($this->OutputFile,substr($this->Data,$this->Streams['vorbis']['commentnext']));
- }
- }
- elseif ($vorbispacket)
- {
- if ($this->Streams['vorbis']['commentpos']>0) fwrite($this->OutputFile,substr($this->Data,0,$this->Streams['vorbis']['commentpos']));
- fwrite($this->OutputFile,$vorbispacket);
- if ($this->Streams['vorbis']['commentnext']<strlen($this->Data)) fwrite($this->OutputFile,substr($this->Data,$this->Streams['vorbis']['commentnext']));
- }
- else {
- if ($this->Streams['theora']['commentpos']>0) fwrite($this->OutputFile,substr($this->Data,0,$this->Streams['theora']['commentpos']));
- fwrite($this->OutputFile,$theorapacket);
- if ($this->Streams['theora']['commentnext']<strlen($this->Data)) fwrite($this->OutputFile,substr($this->Data,$this->Streams['theora']['commentnext']));
- }
- return($this->ContinueWrite());
- }
-
- function ContinueWrite()
- {
- if ($this->MaxTime==0) // not called from WriteNewComments()
- {
- $this->InputFile=fopen($this->realpath($this->Streams['oggfile']),"rb");
- if (!is_resource($this->InputFile)) return ($this->creturn("Could not open OGG file ".$this->Streams['oggfile']));
- $this->OutputFile=fopen($this->realpath($this->Streams['tmpfile']),"ab");
- if (!is_resource($this->OutputFile)) { fclose($this->InputFile); return ($this->creturn("Could not open tmp file ".$this->Streams['tmpfile'])); }
- $this->MaxTime=time()+$this->refresh;
- fseek($this->InputFile,$this->Streams['tmpfileptr']);
- fseek($this->OutputFile,0,SEEK_END);
- }
-
- $interupted=false;
- while (!feof($this->InputFile))
- {
- fwrite($this->OutputFile,fread($this->InputFile,0xFFFF));
- if (time()>=$this->MaxTime) { $interupted=ftell($this->InputFile); break; }
- }
- fclose($this->InputFile);
- fclose($this->OutputFile);
- $this->MaxTime=0; //timer reset
- if ($interupted)
- {
- @rename($this->realpath($this->Streams['tmpfile']),$this->realpath($this->Streams['oggfile'].".r".$this->refresh.".p".$interupted.".tmp"));
- $this->Streams['tmpfile']=$this->Streams['oggfile'].".r".$this->refresh.".p".$interupted.".tmp";
- return($this->Streams['tmpfileptr']=$interupted);
- }
- unlink($this->realpath($this->Streams['oggfile']));
- rename($this->realpath($this->Streams['tmpfile']),$this->realpath($this->Streams['oggfile']));
- $this->analyze(); // update cache
- return($this->Streams['size']);
- }
-
- function CacheUpdate() // update cache file
- {
- if (!$this->cache) return($this->creturn("CACHING disabled, therefore updating cache is not possible..."));
- if (is_resource($cachefile = fopen($this->realpath($this->Streams['cachefile']), "w")))
- {
- $src=$this->Streams['source'];
- $cache=$this->Streams['cachefile'];
- $this->Streams['source']="cache";
- unset ($this->Streams['cachefile']);
- fputs($cachefile, serialize($this->Streams));
- $this->Streams['source']=$src;
- $this->Streams['cachefile']=$cache;
- fclose($cachefile);
- return($this->creturn(true));
- }
- else return($this->creturn("Writting cache file unsuccessfull"));
- }
-
- // Private methods
- function creturn($error=false) { if (is_string($error)) { $this->LastError="OGG Error: $error"; return(false); } else { $this->LastError=false; return($error); } }
- function Decode($string)
- {
- if ($this->StreamsUTF8) return((utf8_encode(utf8_decode($string)) == $string)?$string:utf8_encode($string)); // if string utf8, return it, or encode it in utf8
- else return((utf8_encode(utf8_decode($string)) == $string)?utf8_decode($string):$string); // return ansi
- }
- function EncodeUTF8($string) { return((utf8_encode(utf8_decode($string)) == $string)?$string:utf8_encode($string)); }
- function Read32LE(&$buffer,$pos) { return(ord($buffer[$pos+0])+(ord($buffer[$pos+1])<<8)+(ord($buffer[$pos+2])<<16)+(ord($buffer[$pos+3])<<24)); } // Read 32 bits little endian from buffer from index $pos
- function readBits ($nb,&$buffer,&$bitptr) //read and decodes an integer up to 32 bits from the buffer
- {
- if ($nb>32) $nb=32;
- if ($nb==0) return(0);
- for ($bit=$nb,$r=0;$bit>0;$bit--,$bitptr++) $r+=(ord(substr($buffer,$bitptr>>3,1))&pow(2,7-$bitptr%8))>0?pow(2,$bit-1):0;
- return ($r);
- }
- function realpath($path) // if a path to a file is absolute, add document_root
- {
- if ((strpos($path,$_SERVER['DOCUMENT_ROOT'])!==false)||(strpos($path,"://")!==false)) return($path); // path already absolute or remote
- elseif ($path[0]=='/') return str_replace("//","/",$_SERVER['DOCUMENT_ROOT'].$path);
- else return($path); // relative path
- }
- function Picturable() //boolean, says if this server/library is able to extract a picture from movie
- {
- if (!isset($this->Streams['theora'])) return($this->creturn("Only for video streams..."));
- if ((strpos($this->Streams['oggfile'],"://")!==false)) return ($this->creturn("Picture extraction not possible on a remote server ! "));
- $extensions=get_loaded_extensions();
- if (!in_array("ffmpeg",$extensions)) return($this->creturn("Module ffmpeg not found on this PHP server !"));
- if ($gd=in_array("gd",$extensions))
- {
- $GDArray=gd_info();
- if (intval(ereg_replace('[[:alpha:][:space:]()]+', '', $GDArray['GD Version'])) < 2) $gd=false;
- }
- if (!gd) return($this->creturn("Module gd2 not found on this PHP server !"));
- return($this->creturn(true));
- }
-
- function crcOgg (&$str) // add a CRC to an ogg page $str (including headers, crc set to 0)
- {
- $crc=0;
- $polynom=0x04C11DB7;
- for ($i=0; $i<strlen($str); $i++)
- {
- $c = ord($str[$i]);
- for ($j=0; $j<8; $j++)
- {
- $bit=0;
- if ($crc&0x80000000) $bit=1;
- if ($c&0x80) $bit^=1;
- $c<<=1; $crc<<=1;
- if ($bit) $crc^=$polynom;
- }
- }
- $str[22]=chr($crc&0xFF); $str[23]=chr(($crc>>8)&0xFF); $str[24]=chr(($crc>>16)&0xFF); $str[25]=chr(($crc>>24)&0xFF);
- }
-
- function PackComments ($stream)
- {
- if ($this->tagvendor && (strpos($stream['vendor'],OGGCLASSVERSION)===false)) $stream['vendor'].=OGGCLASSVERSION;
- // check that no empty comments or array
- if (isset($stream['comments'])) { foreach ($stream['comments'] as $key=>$comment) if (!strlen($comment)) unset($stream['comments'][$key]); }
- else $stream['comments']=array();
- $data=(isset($stream['channels'])?chr(0x03)."vorbis":chr(0x81)."theora").pack("V",strlen($this->EncodeUTF8($stream['vendor']))).$this->EncodeUTF8($stream['vendor']).pack("V",count($stream['comments']));
- foreach ($stream['comments'] as $comment)
- {
- if (!preg_match("`\A[A-Za-z]*=.+`",$comment)) return($this->creturn("Invalid comment format : ".$comment));
- $data.=pack("V",strlen($this->EncodeUTF8(($comment)))).$this->EncodeUTF8($comment);
- }
- if (isset($stream['channels'])) $data.=chr(0x01); // vorbis stream adds this
- $segments=1+(strlen($data)>>8);
- $packet="OggS".pack("CCVVVVVC",0,0,0,0,$stream['serial'],1,0,$segments+strlen($stream['commentleftSegments']));
- for ($i=0;$i<($segments-1);$i++) $packet.=chr(0xFF);
- $packet.=chr(strlen($data)%0xFF);
- if (strlen($stream['commentleftSegments'])>0) $packet.=$stream['commentleftSegments'];
- $packet.=$data;
- if (($stream['commentpos']+$stream['commentlen'])<$stream['commentnext']) $packet.=substr($this->Data,$stream['commentpos']+ord($this->Data[$stream['commentpos']+26])+27+$stream['commentlen'],$stream['commentnext']-$stream['commentpos']-$stream['commentlen']-27-ord($this->Data[$stream['commentpos']+26]));
- $this->crcOgg($packet);
- return($packet);
- }
- function analyze() // Parse headers to retrieve identification and comments infos
- {
- clearstatcache();
-
- unset($this->Streams['vorbis']); unset($this->Streams['theora']);
- unset($this->Streams['tmpfile']); unset($this->Streams['tmpfileptr']);
- unset($this->Streams['size']); unset($this->Streams['duration']);
- $this->Streams['source']="file";
- $this->Streams['encoding']=$this->StreamsUTF8?"utf-8":"ansi";
- $this->Data="";
-
- if (strpos($this->Streams['oggfile'],"http://")!==false) // If remote file
- {
- $url = parse_url($this->Streams['oggfile']);
- $port = isset($url['port']) ? $url['port'] : 80;
- $inputfile = @fsockopen($url['host'], $port,$errno,$errstr,5);
- if ($inputfile)
- {
- stream_set_timeout($inputfile, 2);
- $query=isset($url['query'])?"?".$url['query']:"";
- @fwrite($inputfile, "GET ".$url['path'].$query." HTTP/1.1\r\nHost: ".$url['host']."\r\nUser-Agent:".OGGCLASSVERSION."\r\nConnection: close\r\n\r\n");
- for ($i=0;$i<66;$i++) if (!feof($inputfile)) $this->Data.=@fread($inputfile, 1024); else break;
- preg_match('/Content-Length: ([0-9]+)/', $this->Data, $length);
- if (isset($length[1])) $this->Streams['size']=$length[1];
- fclose($inputfile);
- }
- }
-
- if (strpos($this->Data,"OggS")===false) // not a remote file or could cold read inside it
- {
- $inputfile = @fopen($this->realpath($this->Streams['oggfile']), "rb");
- if ($s=@filesize($this->realpath($this->Streams['oggfile']))) $this->Streams['size']=$s;
- if (!is_resource($inputfile)) return ($this->creturn("Could not open OGG file ".$this->Streams['oggfile']));
- // First read the first 65536 bytes of the file to parse identification and comments headers
- if (! ($this->Data = fread($inputfile, 0xFFFF))) return ($this->creturn("Could not read in OGG file ".$this->Streams['oggfile']));
- }
-
- $this->Streams['summary']=basename($this->Streams['oggfile'])." (".floor($this->Streams['size']/1024)." kB)\n\n";
- for ($pos=0;($pos=strpos($this->Data,"OggS",$pos))!==false;$pos++) // parse OGG pages to retrieve interresting data
- {
- if (ord($this->Data[$pos+4])!=0) continue; // unknown stream structure version. We continue parsing, but don't analyse this one which might be a sync problem
- $packet=$pos+27+ord($this->Data[$pos+26]); // offset of the packet after header
- if (isset($this->Streams['cmml']) && $this->Read32LE($this->Data,$pos+14)==$this->Streams['cmml']['serial']) // read CMML data
- {
- $nextogg=strpos($this->Data,"OggS",$pos+1);
- if (isset($this->Streams['cmml']['text'])) $this->Streams['cmml']['text'].="\n";
- $this->Streams['cmml']['text'].=substr($this->Data,$packet,$nextogg-$packet);
- }
- if ($this->Read32LE($this->Data,$pos+18)==0) // Page Count = 0 : we can find the stream type and read identification headers
- {
- if ((substr($this->Data,$packet+1,6)=="vorbis") && !isset($this->Streams['vorbis'])) // decode vorbis identification header
- {
- $this->Streams['vorbis']=array();
- $vorbis=&$this->Streams['vorbis'];
- $vorbis['serial']=$this->Read32LE($this->Data,$pos+14);
- if (ord($this->Data[$packet]) != 0x01) return($this->creturn(sprintf("Incorrect Vorbis Identification Header(0x%x)",ord($this->Data[$packet]))));
- if ($this->Read32LE($this->Data,$packet+7)!=0) return($this->creturn("Incorrect Vorbis stream version = ".$this->Read32LE($this->Data,$packet+7)));
- if (($vorbis['channels'] = ord($this->Data[$packet+11])) == 0) return($this->creturn("Incorrect Vorbis channels number = ".ord($this->Data[$packet+11])));
- if (($vorbis['samplerate']= $this->Read32LE($this->Data,$packet+12)) == 0) return($this->creturn("Incorrect Vorbis sample rate = ".$this->Read32LE($this->Data,$packet+12)));
- $vorbis['maxbitrate'] = $this->Read32LE($this->Data,$packet+16);
- $vorbis['nombitrate'] = $this->Read32LE($this->Data,$packet+20);
- $vorbis['minbitrate'] = $this->Read32LE($this->Data,$packet+24);
- $vorbis['bitrate']=($vorbis['nombitrate'] != 0)?$vorbis['nombitrate']:($vorbis['minbitrate'] + $vorbis['maxbitrate']) / 2;
- }
- elseif ((substr($this->Data,$packet+1,6)=="theora") && !isset($this->Streams['theora'])) // decode theora identification header
- {
- $this->Streams['theora']=array();
- $theora=&$this->Streams['theora'];
- $theora['serial']=$this->Read32LE($this->Data,$pos+14);
- if (ord($this->Data[$packet]) != 0x80) return($this->creturn(sprintf("Incorrect Theora Identification Header(0x%x)",ord($this->Data[$packet]))));
- if ((($theora['vmaj']=ord($this->Data[$packet+7]))!=3) || (($theora['vmin']=ord($this->Data[$packet+8]))!=2)) return($this->creturn("Incorrect Theora stream version"));
- $bitptr=($packet+9)*8;
- $theora['vrev']=$this->readBits(8,$this->Data,$bitptr);
- $theora['fmbw']=$this->readBits(16,$this->Data,$bitptr); $theora['fmbh']=$this->readBits(16,$this->Data,$bitptr);
- $theora['picw']=$this->readBits(24,$this->Data,$bitptr); $theora['pich']=$this->readBits(24,$this->Data,$bitptr);
- $theora['picx']=$this->readBits(8,$this->Data,$bitptr); $theora['picy']=$this->readBits(8,$this->Data,$bitptr);
- $theora['width']=$theora['picw']; $theora['height']=$theora['pich'];
- $theora['frn']=$this->readBits(32,$this->Data,$bitptr); $theora['frd']=$this->readBits(32,$this->Data,$bitptr); $theora['frate']=round($theora['frn']/$theora['frd'],2);
- $theora['parn']=$this->readBits(24,$this->Data,$bitptr); $theora['pard']=$this->readBits(24,$this->Data,$bitptr); if ($theora['parn']*$theora['pard']!=0) $theora['pixelaspectratio']=$theora['parn'].":".$theora['pard']; else $theora['pixelaspectratio']="1:1";
- $theora['cs']=$this->readBits(8,$this->Data,$bitptr); if ($theora['cs']==1) $theora['colorspace']="Rec. 470M"; elseif ($theora['cs']==2) $theora['colorspace']="Rec. 470BG";
- $theora['nombr']=$this->readBits(24,$this->Data,$bitptr);
- $theora['qual']=$this->readBits(6,$this->Data,$bitptr);
- $theora['kfgshift']=$this->readBits(5,$this->Data,$bitptr);
- $theora['pf']=$this->readBits(2,$this->Data,$bitptr); if ($theora['pf']==0) $theora['pixelformat']="4:2:0"; elseif ($theora['pf']==2) $theora['pixelformat']="4:2:2"; elseif ($theora['pf']==3) $theora['pixelformat']="4:4:4";
- }
- elseif ((substr($this->Data,$packet,8)=="fishead\0")&& !isset($this->Streams['skeleton'])) //decode ogg skeleton primary header
- {
- $this->Streams['skeleton']=array();
- $this->Streams['skeleton']['version']=(ord($this->Data[$packet+8])+(ord($this->Data[$packet+9])<<8)).".".(ord($this->Data[$packet+10])+(ord($this->Data[$packet+11])<<8));
- if (ord($this->Data[$packet+44])>0) $this->Streams['skeleton']['utc']=substr($this->Data,$packet+44,20);
- }
- elseif ((substr($this->Data,$packet,8)=="CMML\0\0\0\0")&& !isset($this->Streams['cmml'])) //decode ogg CMML primary header
- {
- $this->Streams['cmml']=array();
- $this->Streams['cmml']['serial']=$this->Read32LE($this->Data,$pos+14);
- $this->Streams['cmml']['version']=(ord($this->Data[$packet+8])+(ord($this->Data[$packet+9])<<8)).".".(ord($this->Data[$packet+10])+(ord($this->Data[$packet+11])<<8));
- }
- }
- elseif ($this->Read32LE($this->Data,$pos+18)==1) // Page Count = 1 : we can read comments headers
- {
- $type=substr($this->Data,$packet+1,6);
- if ($type=="vorbis")
- {
- if (ord($this->Data[$packet])!=0x03) return($this->creturn(sprintf("Incorrect Vorbis Comment Header(0x%x)",ord($this->Data[$packet]))));
- $table=&$this->Streams['vorbis'];
- }
- elseif ($type=="theora")
- {
- if (ord($this->Data[$packet])!=0x81) return($this->creturn(sprintf("Incorrect Theora Comment Header(0x%x)",ord($this->Data[$packet]))));
- $table=&$this->Streams['theora'];
- }
- else continue; // unknown page 1, neither theora nor vorbis comments
- $offset=$packet;
- $lenv=$this->Read32LE($this->Data,$offset+7);
- $table['vendor']=$this->Decode(substr($this->Data,$offset+11,$lenv));
- $offset+=11+$lenv;
- $ncomments=$this->Read32LE($this->Data,$offset);
- $offset+=4;
- $table['comments']=array();
- for ($i=0; $i<$ncomments; $i++)
- {
- $lcomment=$this->Read32LE($this->Data,$offset);
- $table['comments'][$i]=$this->Decode(substr($this->Data,$offset+4,$lcomment));
- $offset+=4+$lcomment;
- }
- if ($type=="vorbis") $offset++; //vorbis format adds a "0x01" at the end, which theora doesn't
- $table['commentlen']=$offset-$packet;
- // This last part only to get necessary information to change the comments tags
- $table['commentpos']=$pos; //page position in file
- $table['commentnext']=strpos($this->Data,"OggS",$pos+1); //next page position in file
- $lseg=ord($this->Data[$pos+26])-($table['commentlen']>>8)-1;
- $table['commentleftSegments']=substr($this->Data,$pos+27+($table['commentlen']>>8)+1,$lseg);
- }
- if (isset($this->Streams['vorbis']['vendor'])&&isset($this->Streams['theora']['vendor'])&&!isset($this->Streams['cmml'])) break; // we already have what we need, we can stop parsing
- }
-
- if (isset($this->Streams['skeleton'])) // read ogg skeleton secondary headers
- {
- for ($pos=0;($pos=strpos($this->Data,"fisbone\0",$pos))!==false;$pos++) // parse fisbone pages to retrieve interresting data
- {
- $serial=$this->Read32LE($this->Data,$pos+12);
- $nextfis=strpos($this->Data,"fisbone\0",$pos+1);
- $nextogg=strpos($this->Data,"OggS",$pos+1);
- $endfis=($nextfis>0 && $nextfis<$nextogg)?$nextfis:$nextogg;
- $posmessage=$pos+8+$this->Read32LE($this->Data,$pos+8);
- $lenmessage=$endfis-$posmessage;
- $message=trim(substr($this->Data,$posmessage,$lenmessage));
- if (strlen($message)>0)
- {
- if (isset($this->Streams['theora']) && $this->Streams['theora']['serial']==$serial) $this->Streams['theora']['skeleton']=$message;
- if (isset($this->Streams['vorbis']) && $this->Streams['vorbis']['serial']==$serial) $this->Streams['vorbis']['skeleton']=$message;
- }
- }
- }
-
- // Then read the last 65536 bytes of the file to get last granular pos to calculate streams duration
- $endbuffer="";
- if (isset($this->Streams['size'])&&($this->Streams['size']>0xFFFF))
- {
- if (is_resource($inputfile)) // still open: so this is local file
- {
- @fseek($inputfile,-1*0xFFFF,SEEK_END);
- if (! ($endbuffer = fread($inputfile, 0xFFFF))) return ($this->creturn("Could not read in OGG file ".$this->Streams['oggfile']));
- fclose($inputfile);
- }
- else {
- $url = parse_url($this->Streams['oggfile']);
- $port = isset($url['port']) ? $url['port'] : 80;
- $inputfile = @fsockopen($url['host'], $port,$errno,$errstr,5);
- if ($inputfile)
- {
- stream_set_timeout($inputfile, 2);
- $query=isset($url['query'])?"?".$url['query']:"";
- @fwrite($inputfile, "GET ".$url['path'].$query." HTTP/1.1\r\nHost: ".$url['host']."\r\nUser-Agent:".OGGCLASSVERSION."\r\nAccept-Ranges: bytes\r\nRange: bytes=".($this->Streams['size']-0xFFFF)."-\r\nConnection: close\r\n\r\n");
- for ($i=0;$i<70;$i++) if (!feof($inputfile)) $endbuffer.=@fread($inputfile, 1024); else break;
- fclose($inputfile);
- }
- }
- }
- else $endbuffer=&$this->Data;
- for ($pos=0;($pos=strpos($endbuffer,"OggS",$pos))!==false;$pos++) // parse OGG pages to retrieve interesting data
- {
- if (isset($this->Streams['vorbis'])&&($this->Read32LE($endbuffer,$pos+6)!=-1)&&($this->Read32LE($endbuffer,$pos+14)==$this->Streams['vorbis']['serial'])&&(ord($endbuffer[$pos+5])&0x4))
- $this->Streams['vorbis']['duration']=round($this->Read32LE($endbuffer,$pos+6) / $this->Streams['vorbis']['samplerate']);
- elseif (isset($this->Streams['theora'])&&($this->Read32LE($endbuffer,$pos+6)!=-1)&&($this->Read32LE($endbuffer,$pos+14)==$this->Streams['theora']['serial'])&&(ord($endbuffer[$pos+5])&0x4))
- {
- $this->Streams['theora']['framecount']=($this->Read32LE($endbuffer,$pos+6)>>$this->Streams['theora']['kfgshift']) + ($this->Read32LE($endbuffer,$pos+6)&(pow(2,$this->Streams['theora']['kfgshift'])-1));
- $this->Streams['theora']['duration']=round( $this->Streams['theora']['framecount'] * $this->Streams['theora']['frd'] / $this->Streams['theora']['frn']);
- }
- if (isset($this->Streams['vorbis']['duration'])&&isset($this->Streams['theora']['duration'])) break; // we alread have what we need, we can stop parsing
- }
-
- // update summary
- if (isset($this->Streams['theora']))
- {
- $this->Streams['summary'].="Video (theora): ";
- if (isset($this->Streams['theora']['duration'])) $this->Streams['summary'].=$this->Streams['theora']['duration']."s ";
- $this->Streams['summary'].=$this->Streams['theora']['width']."x".$this->Streams['theora']['height'];
- $this->Streams['summary'].=" ".$this->Streams['theora']['frate']."fps";
- if ($q=$this->Streams['theora']['qual']) $this->Streams['summary'].=" Q=$q";
- $this->Streams['summary'].="\n";
- if (isset($this->Streams['theora']['comments'])) foreach ($this->Streams['theora']['comments'] as $value) $this->Streams['summary'].="$value\n";
- $this->Streams['summary'].="\n";
- }
- if (isset($this->Streams['vorbis']))
- {
- $this->Streams['summary'].="Audio (Vorbis";
- $this->Streams['summary'].=" ".floor($this->Streams['vorbis']['bitrate']/1000)."kb/s";
- $this->Streams['summary'].="): ";
- if (isset($this->Streams['vorbis']['duration'])) $this->Streams['summary'].=$this->Streams['vorbis']['duration']."s ";
- $this->Streams['summary'].=($this->Streams['vorbis']['channels']>1)?"stereo":"mono";
- $this->Streams['summary'].=" ".floor($this->Streams['vorbis']['samplerate']/1000)."kB/s\n";
- if (isset($this->Streams['vorbis']['comments'])) foreach ($this->Streams['vorbis']['comments'] as $value) $this->Streams['summary'].="$value\n";
- }
-
- //global duration is the biggest of each
- if (isset($this->Streams['vorbis']['duration'])&&isset($this->Streams['theora']['duration'])) $this->Streams['duration']=$this->Streams['vorbis']['duration']>$this->Streams['theora']['duration']?$this->Streams['vorbis']['duration']:$this->Streams['theora']['duration'];
- elseif (isset($this->Streams['vorbis']['duration'])) $this->Streams['duration']=$this->Streams['vorbis']['duration'];
- elseif (isset($this->Streams['theora']['duration'])) $this->Streams['duration']=$this->Streams['theora']['duration'];
- $this->Streams['picturable']=$this->Picturable(); // Can the server extract a picture ?
- if ($this->cache) $this->CacheUpdate();
- return($this->creturn(true));
- }
- }
- ?>
|