ogg.class.php 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. <?php
  2. /* Ogg (vorbis/theora) php manipulation library v1.3g
  3. by Nicolas Ricquemaque <f1iqf@hotmail.com>
  4. GNU GPL >= 2.1 http://www.gnu.org/copyleft/lesser.html GNU LGPL
  5. Primary for use with itheora http://menguy.aymeric.free.fr/theora/ */
  6. define("OGGCLASSVERSION", " [Ogg.class.php v1.3g]");
  7. define("CACHING",0);
  8. define("ANSI",0);
  9. define("NOCACHING",1);
  10. define("UTF8",2);
  11. define("NOVENDORTAG",4);
  12. define("UPDATECACHE",8);
  13. class Ogg
  14. {
  15. // Public variables
  16. var $LastError=false; // false if no error occured, else error text
  17. var $Streams=array(); // All the file data stored here
  18. // Private variables
  19. var $Data;
  20. var $cache,$cachedir; // shall we use & write cache and where ?
  21. 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
  22. var $StreamsUTF8; // if true, the vendor and comment information will be in utf8 inside the Streams array. Else, will be ansi.
  23. var $InputFile,$OutputFile,$MaxTime=0,$refresh;
  24. // constructor; $options contains a conbination of (CACHING or NOCACHING) + (ANSI or UTF8) + NOVENDORTAG + UPDATECACHE
  25. // by default $options enables CACHING, ANSI strings and Vendor Tag tagging
  26. // UPDATECACHE is forcing the analysing of the file and the update of the cache, if caching enabled.
  27. // $cachedir is optional cache directory
  28. function Ogg ($OggFile, $options=0, $cachedir="/cache.ogg")
  29. {
  30. // Check php version
  31. if (intval(PHP_VERSION)<4) return($this->creturn("PHP version must me >= 4 but we show PHP v".PHP_VERSION));
  32. // Options
  33. $this->StreamsUTF8=($options&UTF8)>0;
  34. $this->cache=($options&NOCACHING)==0;
  35. $this->tagvendor=($options&NOVENDORTAG)==0;
  36. clearstatcache();
  37. $this->Streams['oggfile']=$OggFile;
  38. if ((strpos($OggFile,"://")===false)&&!file_exists($this->realpath($OggFile))) return ($this->creturn("Inexisting OGG file ".$OggFile));
  39. // handle caching
  40. if ($this->cache)
  41. {
  42. $this->cachedir=rtrim($cachedir,"/");
  43. if (!is_dir($this->realpath($this->cachedir))) @mkdir ($this->realpath($this->cachedir));
  44. $cache=$this->cachedir."/".strtr($this->Streams['oggfile'],"/:?&#","-----").".cache";
  45. $this->Streams['cachefile']=$cache;
  46. if ((($options&UPDATECACHE)==0)&&is_resource($cachefile = @fopen($this->realpath($this->Streams['cachefile']), "r")))
  47. {
  48. if ($s=fread($cachefile,filesize($this->realpath($this->Streams['cachefile']))))
  49. {
  50. $this->Streams=unserialize($s);
  51. $this->Streams['picturable']=$this->Picturable(); // Can the server extract a picture ? better check again.
  52. // Adapt string encoding to what's required by user
  53. if (isset($this->Streams['summary'])) $this->Streams['summary']=$this->Decode($this->Streams['summary']);
  54. if (isset($this->Streams['theora']['vendor'])) $this->Streams['theora']['vendor']=$this->Decode($this->Streams['theora']['vendor']);
  55. if (isset($this->Streams['vorbis']['vendor'])) $this->Streams['vorbis']['vendor']=$this->Decode($this->Streams['vorbis']['vendor']);
  56. if (isset($this->Streams['theora']['comments'])) foreach ($this->Streams['theora']['comments'] as $key => $comment) $this->Streams['theora']['comments'][$key]=$this->Decode($comment);
  57. if (isset($this->Streams['vorbis']['comments'])) foreach ($this->Streams['vorbis']['comments'] as $key => $comment) $this->Streams['vorbis']['comments'][$key]=$this->Decode($comment);
  58. $this->Streams['encoding']=$this->StreamsUTF8?"utf-8":"ansi";
  59. // double-check if the file we found in cache is really the one we should have, by comparing their sizes, for local files only
  60. if ((strpos($OggFile,"://")===false)&&($this->Streams['size']!=filesize($this->realpath($this->Streams['oggfile'])))) unset($this->Streams['source']);
  61. $this->creturn(true);
  62. }
  63. fclose($cachefile);
  64. $this->Streams['cachefile']=$cache;
  65. //echo "<!--\n".print_r($this->Streams,true)."-->\n";
  66. }
  67. }
  68. //if file could not be read from cache (or sizes did not match), we parse it from file
  69. if (!isset($this->Streams['source'])) $this->analyze();
  70. // Now check if a write has been left in progress (for local files only)
  71. if ((strpos($OggFile,"://")===false)&&($tmp=glob($this->realpath($this->Streams['oggfile']."*.tmp"))))
  72. {
  73. if (count($tmp)==1) $this->Streams['tmpfile']=$tmp[0];
  74. elseif (count($tmp)>1) // if there is several possible files, use the last one, delete others
  75. {
  76. $this->Streams['tmpfile']=$tmp[0];
  77. foreach ($tmp as $file)
  78. if (filemtime($file)>filemtime($this->Streams['tmpfile']))
  79. {
  80. @unlink($this->Streams['tmpfile']); // supress old uncompleted tmp file
  81. $this->Streams['tmpfile']=$file;
  82. }
  83. }
  84. if (isset($this->Streams['tmpfile']))
  85. {
  86. $this->Streams['tmpfile']=substr($this->Streams['tmpfile'],strpos($this->Streams['tmpfile'],$this->Streams['oggfile']));
  87. list($this->refresh,$this->Streams['tmpfileptr']) = sscanf($this->Streams['tmpfile'],$this->Streams['oggfile'].".r%d.p%d.tmp");
  88. }
  89. }
  90. }
  91. // Public methods
  92. 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;
  93. {
  94. if (!$this->Streams['picturable'])
  95. {
  96. if (!$this->Picturable()) return(false);
  97. else $this->Streams['picturable']=true;
  98. }
  99. if (isset($this->Streams['theora']['pictures']))
  100. {
  101. if (isset($this->Streams['theora']['pictures'][$framepos]))
  102. {
  103. if (file_exists($this->realpath($this->Streams['theora']['pictures'][$framepos]))) return ($this->Streams['theora']['pictures'][$framepos]);
  104. }
  105. }
  106. if (!$this->cache && !$where) return($this->creturn("If the CACHING is disabled, you got to provide a path to your picture..."));
  107. $file=$where?$where:substr($this->Streams['cachefile'],0,-6).".f$framepos.jpg";
  108. 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
  109. { // we use the picture we found, avoiding wasting server time to recalculate
  110. if (!isset($this->Streams['theora']['pictures'])) $this->Streams['theora']['pictures']=array();
  111. $this->Streams['theora']['pictures'][$framepos]=$file;
  112. if ($this->cache) $this->CacheUpdate();
  113. $this->creturn(true);
  114. return($file);
  115. }
  116. $errorreport=error_reporting(0);
  117. $movie = new ffmpeg_movie($this->Streams['oggfile'],false);
  118. if (!$movie) { error_reporting($errorreport); return($this->creturn("Unable to extract a picture from ".$this->Streams['oggfile'])); }
  119. $frame=$movie->getFrame($framepos);
  120. if (!$frame) { error_reporting($errorreport); return($this->creturn("Unable to extract the frame #".$framepos." from ".$this->Streams['oggfile'])); }
  121. $image=$frame->toGDImage();
  122. if (!$image) { error_reporting($errorreport); return($this->creturn("Unable to extract an image in frame #".$framepos." from ".$this->Streams['oggfile'])); }
  123. 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'])); }
  124. if (!isset($this->Streams['theora']['pictures'])) $this->Streams['theora']['pictures']=array();
  125. $this->Streams['theora']['pictures'][$framepos]=$file;
  126. if ($this->cache) $this->CacheUpdate();
  127. error_reporting($errorreport);
  128. $this->creturn(true);
  129. return($file);
  130. }
  131. function WriteNewComments ($refresh=5)
  132. {
  133. if ((strpos($this->Streams['oggfile'],"://")!==false)) return ($this->creturn("It is not possible to change the comment tags on a remote server ! "));
  134. $this->InputFile= fopen($this->realpath($this->Streams['oggfile']), "rb");
  135. if (!is_resource($this->InputFile)) return ($this->creturn("Could not open OGG file ".$this->Streams['oggfile']));
  136. if ($this->Streams['source']=='cache')
  137. if (! ($this->Data = fread($this->InputFile, 0xFFFF)) )
  138. return ($this->creturn("Could not read in OGG file ".$this->Streams['oggfile']));
  139. if (isset($this->Streams['vorbis']['commentpos']))
  140. {
  141. $vorbispacket=$this->PackComments($this->Streams['vorbis']);
  142. if (!$vorbispacket) return (false);
  143. }
  144. else $vorbispacket=false;
  145. if (isset($this->Streams['theora']['commentpos']))
  146. {
  147. $theorapacket=$this->PackComments($this->Streams['theora']);
  148. if (!$theorapacket) return (false);
  149. }
  150. else $theorapacket=false;
  151. if (!$vorbispacket && !$theorapacket) return ($this->creturn("Could not find a stream to comment !"));
  152. // Make sure the refresh time will not exceed the script time limit
  153. if ((get_cfg_var("max_execution_time")>0)&&($refresh>=get_cfg_var("max_execution_time"))) $this->refresh=get_cfg_var("max_execution_time")-2;
  154. else $this->refresh=$refresh;
  155. $this->MaxTime=time()+$this->refresh;
  156. $this->Streams['tmpfile']=$this->Streams['oggfile'].".r".$this->refresh;
  157. @unlink($this->realpath($this->Streams['tmpfile']));
  158. $this->OutputFile= fopen($this->realpath($this->Streams['tmpfile']), "wb");
  159. if (!is_resource($this->OutputFile)) return ($this->creturn("Could not create temp file ".$this->Streams['tmpfile']));
  160. if ($vorbispacket&&$theorapacket)
  161. {
  162. if ($this->Streams['vorbis']['commentpos']<$this->Streams['theora']['commentpos'])
  163. {
  164. if ($this->Streams['vorbis']['commentpos']>0) fwrite($this->OutputFile,substr($this->Data,0,$this->Streams['vorbis']['commentpos']));
  165. fwrite($this->OutputFile,$vorbispacket);
  166. if ($this->Streams['vorbis']['commentnext']<$this->Streams['theora']['commentpos'])
  167. fwrite($this->OutputFile,substr($this->Data,$this->Streams['vorbis']['commentnext'],$this->Streams['theora']['commentpos']-$this->Streams['vorbis']['commentnext']));
  168. fwrite($this->OutputFile,$theorapacket);
  169. if ($this->Streams['theora']['commentnext']<strlen($this->Data)) fwrite($this->OutputFile,substr($this->Data,$this->Streams['theora']['commentnext']));
  170. }
  171. else
  172. {
  173. if ($this->Streams['theora']['commentpos']>0) fwrite($this->OutputFile,substr($this->Data,0,$this->Streams['theora']['commentpos']));
  174. fwrite($this->OutputFile,$theorapacket);
  175. if ($this->Streams['theora']['commentnext']<$this->Streams['vorbis']['commentpos'])
  176. fwrite($this->OutputFile,substr($this->Data,$this->Streams['theora']['commentnext'],$this->Streams['vorbis']['commentpos']-$this->Streams['theora']['commentnext']));
  177. fwrite($this->OutputFile,$vorbispacket);
  178. if ($this->Streams['vorbis']['commentnext']<strlen($this->Data)) fwrite($this->OutputFile,substr($this->Data,$this->Streams['vorbis']['commentnext']));
  179. }
  180. }
  181. elseif ($vorbispacket)
  182. {
  183. if ($this->Streams['vorbis']['commentpos']>0) fwrite($this->OutputFile,substr($this->Data,0,$this->Streams['vorbis']['commentpos']));
  184. fwrite($this->OutputFile,$vorbispacket);
  185. if ($this->Streams['vorbis']['commentnext']<strlen($this->Data)) fwrite($this->OutputFile,substr($this->Data,$this->Streams['vorbis']['commentnext']));
  186. }
  187. else {
  188. if ($this->Streams['theora']['commentpos']>0) fwrite($this->OutputFile,substr($this->Data,0,$this->Streams['theora']['commentpos']));
  189. fwrite($this->OutputFile,$theorapacket);
  190. if ($this->Streams['theora']['commentnext']<strlen($this->Data)) fwrite($this->OutputFile,substr($this->Data,$this->Streams['theora']['commentnext']));
  191. }
  192. return($this->ContinueWrite());
  193. }
  194. function ContinueWrite()
  195. {
  196. if ($this->MaxTime==0) // not called from WriteNewComments()
  197. {
  198. $this->InputFile=fopen($this->realpath($this->Streams['oggfile']),"rb");
  199. if (!is_resource($this->InputFile)) return ($this->creturn("Could not open OGG file ".$this->Streams['oggfile']));
  200. $this->OutputFile=fopen($this->realpath($this->Streams['tmpfile']),"ab");
  201. if (!is_resource($this->OutputFile)) { fclose($this->InputFile); return ($this->creturn("Could not open tmp file ".$this->Streams['tmpfile'])); }
  202. $this->MaxTime=time()+$this->refresh;
  203. fseek($this->InputFile,$this->Streams['tmpfileptr']);
  204. fseek($this->OutputFile,0,SEEK_END);
  205. }
  206. $interupted=false;
  207. while (!feof($this->InputFile))
  208. {
  209. fwrite($this->OutputFile,fread($this->InputFile,0xFFFF));
  210. if (time()>=$this->MaxTime) { $interupted=ftell($this->InputFile); break; }
  211. }
  212. fclose($this->InputFile);
  213. fclose($this->OutputFile);
  214. $this->MaxTime=0; //timer reset
  215. if ($interupted)
  216. {
  217. @rename($this->realpath($this->Streams['tmpfile']),$this->realpath($this->Streams['oggfile'].".r".$this->refresh.".p".$interupted.".tmp"));
  218. $this->Streams['tmpfile']=$this->Streams['oggfile'].".r".$this->refresh.".p".$interupted.".tmp";
  219. return($this->Streams['tmpfileptr']=$interupted);
  220. }
  221. unlink($this->realpath($this->Streams['oggfile']));
  222. rename($this->realpath($this->Streams['tmpfile']),$this->realpath($this->Streams['oggfile']));
  223. $this->analyze(); // update cache
  224. return($this->Streams['size']);
  225. }
  226. function CacheUpdate() // update cache file
  227. {
  228. if (!$this->cache) return($this->creturn("CACHING disabled, therefore updating cache is not possible..."));
  229. if (is_resource($cachefile = fopen($this->realpath($this->Streams['cachefile']), "w")))
  230. {
  231. $src=$this->Streams['source'];
  232. $cache=$this->Streams['cachefile'];
  233. $this->Streams['source']="cache";
  234. unset ($this->Streams['cachefile']);
  235. fputs($cachefile, serialize($this->Streams));
  236. $this->Streams['source']=$src;
  237. $this->Streams['cachefile']=$cache;
  238. fclose($cachefile);
  239. return($this->creturn(true));
  240. }
  241. else return($this->creturn("Writting cache file unsuccessfull"));
  242. }
  243. // Private methods
  244. function creturn($error=false) { if (is_string($error)) { $this->LastError="OGG Error: $error"; return(false); } else { $this->LastError=false; return($error); } }
  245. function Decode($string)
  246. {
  247. if ($this->StreamsUTF8) return((utf8_encode(utf8_decode($string)) == $string)?$string:utf8_encode($string)); // if string utf8, return it, or encode it in utf8
  248. else return((utf8_encode(utf8_decode($string)) == $string)?utf8_decode($string):$string); // return ansi
  249. }
  250. function EncodeUTF8($string) { return((utf8_encode(utf8_decode($string)) == $string)?$string:utf8_encode($string)); }
  251. 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
  252. function readBits ($nb,&$buffer,&$bitptr) //read and decodes an integer up to 32 bits from the buffer
  253. {
  254. if ($nb>32) $nb=32;
  255. if ($nb==0) return(0);
  256. 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;
  257. return ($r);
  258. }
  259. function realpath($path) // if a path to a file is absolute, add document_root
  260. {
  261. if ((strpos($path,$_SERVER['DOCUMENT_ROOT'])!==false)||(strpos($path,"://")!==false)) return($path); // path already absolute or remote
  262. elseif ($path[0]=='/') return str_replace("//","/",$_SERVER['DOCUMENT_ROOT'].$path);
  263. else return($path); // relative path
  264. }
  265. function Picturable() //boolean, says if this server/library is able to extract a picture from movie
  266. {
  267. if (!isset($this->Streams['theora'])) return($this->creturn("Only for video streams..."));
  268. if ((strpos($this->Streams['oggfile'],"://")!==false)) return ($this->creturn("Picture extraction not possible on a remote server ! "));
  269. $extensions=get_loaded_extensions();
  270. if (!in_array("ffmpeg",$extensions)) return($this->creturn("Module ffmpeg not found on this PHP server !"));
  271. if ($gd=in_array("gd",$extensions))
  272. {
  273. $GDArray=gd_info();
  274. if (intval(ereg_replace('[[:alpha:][:space:]()]+', '', $GDArray['GD Version'])) < 2) $gd=false;
  275. }
  276. if (!gd) return($this->creturn("Module gd2 not found on this PHP server !"));
  277. return($this->creturn(true));
  278. }
  279. function crcOgg (&$str) // add a CRC to an ogg page $str (including headers, crc set to 0)
  280. {
  281. $crc=0;
  282. $polynom=0x04C11DB7;
  283. for ($i=0; $i<strlen($str); $i++)
  284. {
  285. $c = ord($str[$i]);
  286. for ($j=0; $j<8; $j++)
  287. {
  288. $bit=0;
  289. if ($crc&0x80000000) $bit=1;
  290. if ($c&0x80) $bit^=1;
  291. $c<<=1; $crc<<=1;
  292. if ($bit) $crc^=$polynom;
  293. }
  294. }
  295. $str[22]=chr($crc&0xFF); $str[23]=chr(($crc>>8)&0xFF); $str[24]=chr(($crc>>16)&0xFF); $str[25]=chr(($crc>>24)&0xFF);
  296. }
  297. function PackComments ($stream)
  298. {
  299. if ($this->tagvendor && (strpos($stream['vendor'],OGGCLASSVERSION)===false)) $stream['vendor'].=OGGCLASSVERSION;
  300. // check that no empty comments or array
  301. if (isset($stream['comments'])) { foreach ($stream['comments'] as $key=>$comment) if (!strlen($comment)) unset($stream['comments'][$key]); }
  302. else $stream['comments']=array();
  303. $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']));
  304. foreach ($stream['comments'] as $comment)
  305. {
  306. if (!preg_match("`\A[A-Za-z]*=.+`",$comment)) return($this->creturn("Invalid comment format : ".$comment));
  307. $data.=pack("V",strlen($this->EncodeUTF8(($comment)))).$this->EncodeUTF8($comment);
  308. }
  309. if (isset($stream['channels'])) $data.=chr(0x01); // vorbis stream adds this
  310. $segments=1+(strlen($data)>>8);
  311. $packet="OggS".pack("CCVVVVVC",0,0,0,0,$stream['serial'],1,0,$segments+strlen($stream['commentleftSegments']));
  312. for ($i=0;$i<($segments-1);$i++) $packet.=chr(0xFF);
  313. $packet.=chr(strlen($data)%0xFF);
  314. if (strlen($stream['commentleftSegments'])>0) $packet.=$stream['commentleftSegments'];
  315. $packet.=$data;
  316. 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]));
  317. $this->crcOgg($packet);
  318. return($packet);
  319. }
  320. function analyze() // Parse headers to retrieve identification and comments infos
  321. {
  322. clearstatcache();
  323. unset($this->Streams['vorbis']); unset($this->Streams['theora']);
  324. unset($this->Streams['tmpfile']); unset($this->Streams['tmpfileptr']);
  325. unset($this->Streams['size']); unset($this->Streams['duration']);
  326. $this->Streams['source']="file";
  327. $this->Streams['encoding']=$this->StreamsUTF8?"utf-8":"ansi";
  328. $this->Data="";
  329. if (strpos($this->Streams['oggfile'],"http://")!==false) // If remote file
  330. {
  331. $url = parse_url($this->Streams['oggfile']);
  332. $port = isset($url['port']) ? $url['port'] : 80;
  333. $inputfile = @fsockopen($url['host'], $port,$errno,$errstr,5);
  334. if ($inputfile)
  335. {
  336. stream_set_timeout($inputfile, 2);
  337. $query=isset($url['query'])?"?".$url['query']:"";
  338. @fwrite($inputfile, "GET ".$url['path'].$query." HTTP/1.1\r\nHost: ".$url['host']."\r\nUser-Agent:".OGGCLASSVERSION."\r\nConnection: close\r\n\r\n");
  339. for ($i=0;$i<66;$i++) if (!feof($inputfile)) $this->Data.=@fread($inputfile, 1024); else break;
  340. preg_match('/Content-Length: ([0-9]+)/', $this->Data, $length);
  341. if (isset($length[1])) $this->Streams['size']=$length[1];
  342. fclose($inputfile);
  343. }
  344. }
  345. if (strpos($this->Data,"OggS")===false) // not a remote file or could cold read inside it
  346. {
  347. $inputfile = @fopen($this->realpath($this->Streams['oggfile']), "rb");
  348. if ($s=@filesize($this->realpath($this->Streams['oggfile']))) $this->Streams['size']=$s;
  349. if (!is_resource($inputfile)) return ($this->creturn("Could not open OGG file ".$this->Streams['oggfile']));
  350. // First read the first 65536 bytes of the file to parse identification and comments headers
  351. if (! ($this->Data = fread($inputfile, 0xFFFF))) return ($this->creturn("Could not read in OGG file ".$this->Streams['oggfile']));
  352. }
  353. $this->Streams['summary']=basename($this->Streams['oggfile'])." (".floor($this->Streams['size']/1024)." kB)\n\n";
  354. for ($pos=0;($pos=strpos($this->Data,"OggS",$pos))!==false;$pos++) // parse OGG pages to retrieve interresting data
  355. {
  356. 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
  357. $packet=$pos+27+ord($this->Data[$pos+26]); // offset of the packet after header
  358. if (isset($this->Streams['cmml']) && $this->Read32LE($this->Data,$pos+14)==$this->Streams['cmml']['serial']) // read CMML data
  359. {
  360. $nextogg=strpos($this->Data,"OggS",$pos+1);
  361. if (isset($this->Streams['cmml']['text'])) $this->Streams['cmml']['text'].="\n";
  362. $this->Streams['cmml']['text'].=substr($this->Data,$packet,$nextogg-$packet);
  363. }
  364. if ($this->Read32LE($this->Data,$pos+18)==0) // Page Count = 0 : we can find the stream type and read identification headers
  365. {
  366. if ((substr($this->Data,$packet+1,6)=="vorbis") && !isset($this->Streams['vorbis'])) // decode vorbis identification header
  367. {
  368. $this->Streams['vorbis']=array();
  369. $vorbis=&$this->Streams['vorbis'];
  370. $vorbis['serial']=$this->Read32LE($this->Data,$pos+14);
  371. if (ord($this->Data[$packet]) != 0x01) return($this->creturn(sprintf("Incorrect Vorbis Identification Header(0x%x)",ord($this->Data[$packet]))));
  372. if ($this->Read32LE($this->Data,$packet+7)!=0) return($this->creturn("Incorrect Vorbis stream version = ".$this->Read32LE($this->Data,$packet+7)));
  373. if (($vorbis['channels'] = ord($this->Data[$packet+11])) == 0) return($this->creturn("Incorrect Vorbis channels number = ".ord($this->Data[$packet+11])));
  374. if (($vorbis['samplerate']= $this->Read32LE($this->Data,$packet+12)) == 0) return($this->creturn("Incorrect Vorbis sample rate = ".$this->Read32LE($this->Data,$packet+12)));
  375. $vorbis['maxbitrate'] = $this->Read32LE($this->Data,$packet+16);
  376. $vorbis['nombitrate'] = $this->Read32LE($this->Data,$packet+20);
  377. $vorbis['minbitrate'] = $this->Read32LE($this->Data,$packet+24);
  378. $vorbis['bitrate']=($vorbis['nombitrate'] != 0)?$vorbis['nombitrate']:($vorbis['minbitrate'] + $vorbis['maxbitrate']) / 2;
  379. }
  380. elseif ((substr($this->Data,$packet+1,6)=="theora") && !isset($this->Streams['theora'])) // decode theora identification header
  381. {
  382. $this->Streams['theora']=array();
  383. $theora=&$this->Streams['theora'];
  384. $theora['serial']=$this->Read32LE($this->Data,$pos+14);
  385. if (ord($this->Data[$packet]) != 0x80) return($this->creturn(sprintf("Incorrect Theora Identification Header(0x%x)",ord($this->Data[$packet]))));
  386. if ((($theora['vmaj']=ord($this->Data[$packet+7]))!=3) || (($theora['vmin']=ord($this->Data[$packet+8]))!=2)) return($this->creturn("Incorrect Theora stream version"));
  387. $bitptr=($packet+9)*8;
  388. $theora['vrev']=$this->readBits(8,$this->Data,$bitptr);
  389. $theora['fmbw']=$this->readBits(16,$this->Data,$bitptr); $theora['fmbh']=$this->readBits(16,$this->Data,$bitptr);
  390. $theora['picw']=$this->readBits(24,$this->Data,$bitptr); $theora['pich']=$this->readBits(24,$this->Data,$bitptr);
  391. $theora['picx']=$this->readBits(8,$this->Data,$bitptr); $theora['picy']=$this->readBits(8,$this->Data,$bitptr);
  392. $theora['width']=$theora['picw']; $theora['height']=$theora['pich'];
  393. $theora['frn']=$this->readBits(32,$this->Data,$bitptr); $theora['frd']=$this->readBits(32,$this->Data,$bitptr); $theora['frate']=round($theora['frn']/$theora['frd'],2);
  394. $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";
  395. $theora['cs']=$this->readBits(8,$this->Data,$bitptr); if ($theora['cs']==1) $theora['colorspace']="Rec. 470M"; elseif ($theora['cs']==2) $theora['colorspace']="Rec. 470BG";
  396. $theora['nombr']=$this->readBits(24,$this->Data,$bitptr);
  397. $theora['qual']=$this->readBits(6,$this->Data,$bitptr);
  398. $theora['kfgshift']=$this->readBits(5,$this->Data,$bitptr);
  399. $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";
  400. }
  401. elseif ((substr($this->Data,$packet,8)=="fishead\0")&& !isset($this->Streams['skeleton'])) //decode ogg skeleton primary header
  402. {
  403. $this->Streams['skeleton']=array();
  404. $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));
  405. if (ord($this->Data[$packet+44])>0) $this->Streams['skeleton']['utc']=substr($this->Data,$packet+44,20);
  406. }
  407. elseif ((substr($this->Data,$packet,8)=="CMML\0\0\0\0")&& !isset($this->Streams['cmml'])) //decode ogg CMML primary header
  408. {
  409. $this->Streams['cmml']=array();
  410. $this->Streams['cmml']['serial']=$this->Read32LE($this->Data,$pos+14);
  411. $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));
  412. }
  413. }
  414. elseif ($this->Read32LE($this->Data,$pos+18)==1) // Page Count = 1 : we can read comments headers
  415. {
  416. $type=substr($this->Data,$packet+1,6);
  417. if ($type=="vorbis")
  418. {
  419. if (ord($this->Data[$packet])!=0x03) return($this->creturn(sprintf("Incorrect Vorbis Comment Header(0x%x)",ord($this->Data[$packet]))));
  420. $table=&$this->Streams['vorbis'];
  421. }
  422. elseif ($type=="theora")
  423. {
  424. if (ord($this->Data[$packet])!=0x81) return($this->creturn(sprintf("Incorrect Theora Comment Header(0x%x)",ord($this->Data[$packet]))));
  425. $table=&$this->Streams['theora'];
  426. }
  427. else continue; // unknown page 1, neither theora nor vorbis comments
  428. $offset=$packet;
  429. $lenv=$this->Read32LE($this->Data,$offset+7);
  430. $table['vendor']=$this->Decode(substr($this->Data,$offset+11,$lenv));
  431. $offset+=11+$lenv;
  432. $ncomments=$this->Read32LE($this->Data,$offset);
  433. $offset+=4;
  434. $table['comments']=array();
  435. for ($i=0; $i<$ncomments; $i++)
  436. {
  437. $lcomment=$this->Read32LE($this->Data,$offset);
  438. $table['comments'][$i]=$this->Decode(substr($this->Data,$offset+4,$lcomment));
  439. $offset+=4+$lcomment;
  440. }
  441. if ($type=="vorbis") $offset++; //vorbis format adds a "0x01" at the end, which theora doesn't
  442. $table['commentlen']=$offset-$packet;
  443. // This last part only to get necessary information to change the comments tags
  444. $table['commentpos']=$pos; //page position in file
  445. $table['commentnext']=strpos($this->Data,"OggS",$pos+1); //next page position in file
  446. $lseg=ord($this->Data[$pos+26])-($table['commentlen']>>8)-1;
  447. $table['commentleftSegments']=substr($this->Data,$pos+27+($table['commentlen']>>8)+1,$lseg);
  448. }
  449. 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
  450. }
  451. if (isset($this->Streams['skeleton'])) // read ogg skeleton secondary headers
  452. {
  453. for ($pos=0;($pos=strpos($this->Data,"fisbone\0",$pos))!==false;$pos++) // parse fisbone pages to retrieve interresting data
  454. {
  455. $serial=$this->Read32LE($this->Data,$pos+12);
  456. $nextfis=strpos($this->Data,"fisbone\0",$pos+1);
  457. $nextogg=strpos($this->Data,"OggS",$pos+1);
  458. $endfis=($nextfis>0 && $nextfis<$nextogg)?$nextfis:$nextogg;
  459. $posmessage=$pos+8+$this->Read32LE($this->Data,$pos+8);
  460. $lenmessage=$endfis-$posmessage;
  461. $message=trim(substr($this->Data,$posmessage,$lenmessage));
  462. if (strlen($message)>0)
  463. {
  464. if (isset($this->Streams['theora']) && $this->Streams['theora']['serial']==$serial) $this->Streams['theora']['skeleton']=$message;
  465. if (isset($this->Streams['vorbis']) && $this->Streams['vorbis']['serial']==$serial) $this->Streams['vorbis']['skeleton']=$message;
  466. }
  467. }
  468. }
  469. // Then read the last 65536 bytes of the file to get last granular pos to calculate streams duration
  470. $endbuffer="";
  471. if (isset($this->Streams['size'])&&($this->Streams['size']>0xFFFF))
  472. {
  473. if (is_resource($inputfile)) // still open: so this is local file
  474. {
  475. @fseek($inputfile,-1*0xFFFF,SEEK_END);
  476. if (! ($endbuffer = fread($inputfile, 0xFFFF))) return ($this->creturn("Could not read in OGG file ".$this->Streams['oggfile']));
  477. fclose($inputfile);
  478. }
  479. else {
  480. $url = parse_url($this->Streams['oggfile']);
  481. $port = isset($url['port']) ? $url['port'] : 80;
  482. $inputfile = @fsockopen($url['host'], $port,$errno,$errstr,5);
  483. if ($inputfile)
  484. {
  485. stream_set_timeout($inputfile, 2);
  486. $query=isset($url['query'])?"?".$url['query']:"";
  487. @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");
  488. for ($i=0;$i<70;$i++) if (!feof($inputfile)) $endbuffer.=@fread($inputfile, 1024); else break;
  489. fclose($inputfile);
  490. }
  491. }
  492. }
  493. else $endbuffer=&$this->Data;
  494. for ($pos=0;($pos=strpos($endbuffer,"OggS",$pos))!==false;$pos++) // parse OGG pages to retrieve interesting data
  495. {
  496. 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))
  497. $this->Streams['vorbis']['duration']=round($this->Read32LE($endbuffer,$pos+6) / $this->Streams['vorbis']['samplerate']);
  498. 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))
  499. {
  500. $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));
  501. $this->Streams['theora']['duration']=round( $this->Streams['theora']['framecount'] * $this->Streams['theora']['frd'] / $this->Streams['theora']['frn']);
  502. }
  503. if (isset($this->Streams['vorbis']['duration'])&&isset($this->Streams['theora']['duration'])) break; // we alread have what we need, we can stop parsing
  504. }
  505. // update summary
  506. if (isset($this->Streams['theora']))
  507. {
  508. $this->Streams['summary'].="Video (theora): ";
  509. if (isset($this->Streams['theora']['duration'])) $this->Streams['summary'].=$this->Streams['theora']['duration']."s ";
  510. $this->Streams['summary'].=$this->Streams['theora']['width']."x".$this->Streams['theora']['height'];
  511. $this->Streams['summary'].=" ".$this->Streams['theora']['frate']."fps";
  512. if ($q=$this->Streams['theora']['qual']) $this->Streams['summary'].=" Q=$q";
  513. $this->Streams['summary'].="\n";
  514. if (isset($this->Streams['theora']['comments'])) foreach ($this->Streams['theora']['comments'] as $value) $this->Streams['summary'].="$value\n";
  515. $this->Streams['summary'].="\n";
  516. }
  517. if (isset($this->Streams['vorbis']))
  518. {
  519. $this->Streams['summary'].="Audio (Vorbis";
  520. $this->Streams['summary'].=" ".floor($this->Streams['vorbis']['bitrate']/1000)."kb/s";
  521. $this->Streams['summary'].="): ";
  522. if (isset($this->Streams['vorbis']['duration'])) $this->Streams['summary'].=$this->Streams['vorbis']['duration']."s ";
  523. $this->Streams['summary'].=($this->Streams['vorbis']['channels']>1)?"stereo":"mono";
  524. $this->Streams['summary'].=" ".floor($this->Streams['vorbis']['samplerate']/1000)."kB/s\n";
  525. if (isset($this->Streams['vorbis']['comments'])) foreach ($this->Streams['vorbis']['comments'] as $value) $this->Streams['summary'].="$value\n";
  526. }
  527. //global duration is the biggest of each
  528. 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'];
  529. elseif (isset($this->Streams['vorbis']['duration'])) $this->Streams['duration']=$this->Streams['vorbis']['duration'];
  530. elseif (isset($this->Streams['theora']['duration'])) $this->Streams['duration']=$this->Streams['theora']['duration'];
  531. $this->Streams['picturable']=$this->Picturable(); // Can the server extract a picture ?
  532. if ($this->cache) $this->CacheUpdate();
  533. return($this->creturn(true));
  534. }
  535. }
  536. ?>