%PDF- %PDF-
| Direktori : /data/www_bck/varak.net_bck/gnufm.varak.net/utils/oggclass/ |
| Current File : //data/www_bck/varak.net_bck/gnufm.varak.net/utils/oggclass/ogg.class.php |
<?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));
}
}
?>