getid3_ogg

The WordPress Core getid3 ogg class.

Defined (1)

The class is defined in the following location(s).

/wp-includes/ID3/module.audio.ogg.php  
  1. class getid3_ogg extends getid3_handler 
  2. // http://xiph.org/vorbis/doc/Vorbis_I_spec.html 
  3. public function Analyze() { 
  4. $info = &$this->getid3->info; 
  5.  
  6. $info['fileformat'] = 'ogg'; 
  7.  
  8. // Warn about illegal tags - only vorbiscomments are allowed 
  9. if (isset($info['id3v2'])) { 
  10. $info['warning'][] = 'Illegal ID3v2 tag present.'; 
  11. if (isset($info['id3v1'])) { 
  12. $info['warning'][] = 'Illegal ID3v1 tag present.'; 
  13. if (isset($info['ape'])) { 
  14. $info['warning'][] = 'Illegal APE tag present.'; 
  15.  
  16.  
  17. // Page 1 - Stream Header 
  18.  
  19. $this->fseek($info['avdataoffset']); 
  20.  
  21. $oggpageinfo = $this->ParseOggPageHeader(); 
  22. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; 
  23.  
  24. if ($this->ftell() >= $this->getid3->fread_buffer_size()) { 
  25. $info['error'][] = 'Could not find start of Ogg page in the first '.$this->getid3->fread_buffer_size().' bytes (this might not be an Ogg-Vorbis file?)'; 
  26. unset($info['fileformat']); 
  27. unset($info['ogg']); 
  28. return false; 
  29.  
  30. $filedata = $this->fread($oggpageinfo['page_length']); 
  31. $filedataoffset = 0; 
  32.  
  33. if (substr($filedata, 0, 4) == 'fLaC') { 
  34.  
  35. $info['audio']['dataformat'] = 'flac'; 
  36. $info['audio']['bitrate_mode'] = 'vbr'; 
  37. $info['audio']['lossless'] = true; 
  38.  
  39. } elseif (substr($filedata, 1, 6) == 'vorbis') { 
  40.  
  41. $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo); 
  42.  
  43. } elseif (substr($filedata, 0, 8) == 'OpusHead') { 
  44.  
  45. if( $this->ParseOpusPageHeader($filedata, $filedataoffset, $oggpageinfo) == false ) { 
  46. return false; 
  47.  
  48. } elseif (substr($filedata, 0, 8) == 'Speex ') { 
  49.  
  50. // http://www.speex.org/manual/node10.html 
  51.  
  52. $info['audio']['dataformat'] = 'speex'; 
  53. $info['mime_type'] = 'audio/speex'; 
  54. $info['audio']['bitrate_mode'] = 'abr'; 
  55. $info['audio']['lossless'] = false; 
  56.  
  57. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex ' 
  58. $filedataoffset += 8; 
  59. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version'] = substr($filedata, $filedataoffset, 20); 
  60. $filedataoffset += 20; 
  61. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  62. $filedataoffset += 4; 
  63. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  64. $filedataoffset += 4; 
  65. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  66. $filedataoffset += 4; 
  67. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  68. $filedataoffset += 4; 
  69. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  70. $filedataoffset += 4; 
  71. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  72. $filedataoffset += 4; 
  73. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  74. $filedataoffset += 4; 
  75. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  76. $filedataoffset += 4; 
  77. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  78. $filedataoffset += 4; 
  79. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  80. $filedataoffset += 4; 
  81. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  82. $filedataoffset += 4; 
  83. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  84. $filedataoffset += 4; 
  85. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  86. $filedataoffset += 4; 
  87.  
  88. $info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']); 
  89. $info['speex']['sample_rate'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate']; 
  90. $info['speex']['channels'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels']; 
  91. $info['speex']['vbr'] = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr']; 
  92. $info['speex']['band_type'] = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']); 
  93.  
  94. $info['audio']['sample_rate'] = $info['speex']['sample_rate']; 
  95. $info['audio']['channels'] = $info['speex']['channels']; 
  96. if ($info['speex']['vbr']) { 
  97. $info['audio']['bitrate_mode'] = 'vbr'; 
  98.  
  99. } elseif (substr($filedata, 0, 7) == "\x80".'theora') { 
  100.  
  101. // http://www.theora.org/doc/Theora.pdf (section 6.2) 
  102.  
  103. $info['ogg']['pageheader']['theora']['theora_magic'] = substr($filedata, $filedataoffset, 7); // hard-coded to "\x80.'theora' 
  104. $filedataoffset += 7; 
  105. $info['ogg']['pageheader']['theora']['version_major'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); 
  106. $filedataoffset += 1; 
  107. $info['ogg']['pageheader']['theora']['version_minor'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); 
  108. $filedataoffset += 1; 
  109. $info['ogg']['pageheader']['theora']['version_revision'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); 
  110. $filedataoffset += 1; 
  111. $info['ogg']['pageheader']['theora']['frame_width_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2)); 
  112. $filedataoffset += 2; 
  113. $info['ogg']['pageheader']['theora']['frame_height_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2)); 
  114. $filedataoffset += 2; 
  115. $info['ogg']['pageheader']['theora']['resolution_x'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); 
  116. $filedataoffset += 3; 
  117. $info['ogg']['pageheader']['theora']['resolution_y'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); 
  118. $filedataoffset += 3; 
  119. $info['ogg']['pageheader']['theora']['picture_offset_x'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); 
  120. $filedataoffset += 1; 
  121. $info['ogg']['pageheader']['theora']['picture_offset_y'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); 
  122. $filedataoffset += 1; 
  123. $info['ogg']['pageheader']['theora']['frame_rate_numerator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 4)); 
  124. $filedataoffset += 4; 
  125. $info['ogg']['pageheader']['theora']['frame_rate_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 4)); 
  126. $filedataoffset += 4; 
  127. $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); 
  128. $filedataoffset += 3; 
  129. $info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); 
  130. $filedataoffset += 3; 
  131. $info['ogg']['pageheader']['theora']['color_space_id'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); 
  132. $filedataoffset += 1; 
  133. $info['ogg']['pageheader']['theora']['nominal_bitrate'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); 
  134. $filedataoffset += 3; 
  135. $info['ogg']['pageheader']['theora']['flags'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2)); 
  136. $filedataoffset += 2; 
  137.  
  138. $info['ogg']['pageheader']['theora']['quality'] = ($info['ogg']['pageheader']['theora']['flags'] & 0xFC00) >> 10; 
  139. $info['ogg']['pageheader']['theora']['kfg_shift'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x03E0) >> 5; 
  140. $info['ogg']['pageheader']['theora']['pixel_format_id'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0018) >> 3; 
  141. $info['ogg']['pageheader']['theora']['reserved'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0007) >> 0; // should be 0 
  142. $info['ogg']['pageheader']['theora']['color_space'] = self::TheoraColorSpace($info['ogg']['pageheader']['theora']['color_space_id']); 
  143. $info['ogg']['pageheader']['theora']['pixel_format'] = self::TheoraPixelFormat($info['ogg']['pageheader']['theora']['pixel_format_id']); 
  144.  
  145. $info['video']['dataformat'] = 'theora'; 
  146. $info['mime_type'] = 'video/ogg'; 
  147. //$info['audio']['bitrate_mode'] = 'abr'; 
  148. //$info['audio']['lossless'] = false; 
  149. $info['video']['resolution_x'] = $info['ogg']['pageheader']['theora']['resolution_x']; 
  150. $info['video']['resolution_y'] = $info['ogg']['pageheader']['theora']['resolution_y']; 
  151. if ($info['ogg']['pageheader']['theora']['frame_rate_denominator'] > 0) { 
  152. $info['video']['frame_rate'] = (float) $info['ogg']['pageheader']['theora']['frame_rate_numerator'] / $info['ogg']['pageheader']['theora']['frame_rate_denominator']; 
  153. if ($info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] > 0) { 
  154. $info['video']['pixel_aspect_ratio'] = (float) $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] / $info['ogg']['pageheader']['theora']['pixel_aspect_denominator']; 
  155. $info['warning'][] = 'Ogg Theora (v3) not fully supported in this version of getID3 ['.$this->getid3->version().'] -- bitrate, playtime and all audio data are currently unavailable'; 
  156.  
  157.  
  158. } elseif (substr($filedata, 0, 8) == "fishead\x00") { 
  159.  
  160. // Ogg Skeleton version 3.0 Format Specification 
  161. // http://xiph.org/ogg/doc/skeleton.html 
  162. $filedataoffset += 8; 
  163. $info['ogg']['skeleton']['fishead']['raw']['version_major'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); 
  164. $filedataoffset += 2; 
  165. $info['ogg']['skeleton']['fishead']['raw']['version_minor'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); 
  166. $filedataoffset += 2; 
  167. $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 
  168. $filedataoffset += 8; 
  169. $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 
  170. $filedataoffset += 8; 
  171. $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 
  172. $filedataoffset += 8; 
  173. $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 
  174. $filedataoffset += 8; 
  175. $info['ogg']['skeleton']['fishead']['raw']['utc'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20)); 
  176. $filedataoffset += 20; 
  177.  
  178. $info['ogg']['skeleton']['fishead']['version'] = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor']; 
  179. $info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator']; 
  180. $info['ogg']['skeleton']['fishead']['basetime'] = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']; 
  181. $info['ogg']['skeleton']['fishead']['utc'] = $info['ogg']['skeleton']['fishead']['raw']['utc']; 
  182.  
  183.  
  184. $counter = 0; 
  185. do { 
  186. $oggpageinfo = $this->ParseOggPageHeader(); 
  187. $info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo; 
  188. $filedata = $this->fread($oggpageinfo['page_length']); 
  189. $this->fseek($oggpageinfo['page_end_offset']); 
  190.  
  191. if (substr($filedata, 0, 8) == "fisbone\x00") { 
  192.  
  193. $filedataoffset = 8; 
  194. $info['ogg']['skeleton']['fisbone']['raw']['message_header_offset'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  195. $filedataoffset += 4; 
  196. $info['ogg']['skeleton']['fisbone']['raw']['serial_number'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  197. $filedataoffset += 4; 
  198. $info['ogg']['skeleton']['fisbone']['raw']['number_header_packets'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  199. $filedataoffset += 4; 
  200. $info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 
  201. $filedataoffset += 8; 
  202. $info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 
  203. $filedataoffset += 8; 
  204. $info['ogg']['skeleton']['fisbone']['raw']['basegranule'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 
  205. $filedataoffset += 8; 
  206. $info['ogg']['skeleton']['fisbone']['raw']['preroll'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  207. $filedataoffset += 4; 
  208. $info['ogg']['skeleton']['fisbone']['raw']['granuleshift'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 
  209. $filedataoffset += 1; 
  210. $info['ogg']['skeleton']['fisbone']['raw']['padding'] = substr($filedata, $filedataoffset, 3); 
  211. $filedataoffset += 3; 
  212.  
  213. } elseif (substr($filedata, 1, 6) == 'theora') { 
  214.  
  215. $info['video']['dataformat'] = 'theora1'; 
  216. $info['error'][] = 'Ogg Theora (v1) not correctly handled in this version of getID3 ['.$this->getid3->version().']'; 
  217. //break; 
  218.  
  219. } elseif (substr($filedata, 1, 6) == 'vorbis') { 
  220.  
  221. $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo); 
  222.  
  223. } else { 
  224. $info['error'][] = 'unexpected'; 
  225. //break; 
  226. //} while ($oggpageinfo['page_seqno'] == 0); 
  227. } while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00")); 
  228.  
  229. $this->fseek($oggpageinfo['page_start_offset']); 
  230.  
  231. $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']'; 
  232. //return false; 
  233.  
  234. } else { 
  235.  
  236. $info['error'][] = 'Expecting either "Speex ", "OpusHead" or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"'; 
  237. unset($info['ogg']); 
  238. unset($info['mime_type']); 
  239. return false; 
  240.  
  241.  
  242. // Page 2 - Comment Header 
  243. $oggpageinfo = $this->ParseOggPageHeader(); 
  244. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; 
  245.  
  246. switch ($info['audio']['dataformat']) { 
  247. case 'vorbis': 
  248. $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); 
  249. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1)); 
  250. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 1, 6); // hard-coded to 'vorbis' 
  251.  
  252. $this->ParseVorbisComments(); 
  253. break; 
  254.  
  255. case 'flac': 
  256. $flac = new getid3_flac($this->getid3); 
  257. if (!$flac->parseMETAdata()) { 
  258. $info['error'][] = 'Failed to parse FLAC headers'; 
  259. return false; 
  260. unset($flac); 
  261. break; 
  262.  
  263. case 'speex': 
  264. $this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR); 
  265. $this->ParseVorbisComments(); 
  266. break; 
  267.  
  268. case 'opus': 
  269. $filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); 
  270. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 0, 8); // hard-coded to 'OpusTags' 
  271. if(substr($filedata, 0, 8) != 'OpusTags') { 
  272. $info['error'][] = 'Expected "OpusTags" as header but got "'.substr($filedata, 0, 8).'"'; 
  273. return false; 
  274.  
  275. $this->ParseVorbisComments(); 
  276. break; 
  277.  
  278.  
  279. // Last Page - Number of Samples 
  280. if (!getid3_lib::intValueSupported($info['avdataend'])) { 
  281.  
  282. $info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)'; 
  283.  
  284. } else { 
  285.  
  286. $this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0)); 
  287. $LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size())); 
  288. if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) { 
  289. $this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO'))); 
  290. $info['avdataend'] = $this->ftell(); 
  291. $info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader(); 
  292. $info['ogg']['samples'] = $info['ogg']['pageheader']['eos']['pcm_abs_position']; 
  293. if ($info['ogg']['samples'] == 0) { 
  294. $info['error'][] = 'Corrupt Ogg file: eos.number of samples == zero'; 
  295. return false; 
  296. if (!empty($info['audio']['sample_rate'])) { 
  297. $info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']); 
  298.  
  299.  
  300. if (!empty($info['ogg']['bitrate_average'])) { 
  301. $info['audio']['bitrate'] = $info['ogg']['bitrate_average']; 
  302. } elseif (!empty($info['ogg']['bitrate_nominal'])) { 
  303. $info['audio']['bitrate'] = $info['ogg']['bitrate_nominal']; 
  304. } elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) { 
  305. $info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2; 
  306. if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) { 
  307. if ($info['audio']['bitrate'] == 0) { 
  308. $info['error'][] = 'Corrupt Ogg file: bitrate_audio == zero'; 
  309. return false; 
  310. $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']); 
  311.  
  312. if (isset($info['ogg']['vendor'])) { 
  313. $info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']); 
  314.  
  315. // Vorbis only 
  316. if ($info['audio']['dataformat'] == 'vorbis') { 
  317.  
  318. // Vorbis 1.0 starts with Xiph.Org 
  319. if (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) { 
  320.  
  321. if ($info['audio']['bitrate_mode'] == 'abr') { 
  322.  
  323. // Set -b 128 on abr files 
  324. $info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000); 
  325.  
  326. } elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) { 
  327. // Set -q N on vbr files 
  328. $info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']); 
  329.  
  330.  
  331. if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) { 
  332. $info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps'; 
  333.  
  334. return true; 
  335.  
  336. public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) { 
  337. $info = &$this->getid3->info; 
  338. $info['audio']['dataformat'] = 'vorbis'; 
  339. $info['audio']['lossless'] = false; 
  340.  
  341. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 
  342. $filedataoffset += 1; 
  343. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis' 
  344. $filedataoffset += 6; 
  345. $info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  346. $filedataoffset += 4; 
  347. $info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 
  348. $filedataoffset += 1; 
  349. $info['audio']['channels'] = $info['ogg']['numberofchannels']; 
  350. $info['ogg']['samplerate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  351. $filedataoffset += 4; 
  352. if ($info['ogg']['samplerate'] == 0) { 
  353. $info['error'][] = 'Corrupt Ogg file: sample rate == zero'; 
  354. return false; 
  355. $info['audio']['sample_rate'] = $info['ogg']['samplerate']; 
  356. $info['ogg']['samples'] = 0; // filled in later 
  357. $info['ogg']['bitrate_average'] = 0; // filled in later 
  358. $info['ogg']['bitrate_max'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  359. $filedataoffset += 4; 
  360. $info['ogg']['bitrate_nominal'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  361. $filedataoffset += 4; 
  362. $info['ogg']['bitrate_min'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  363. $filedataoffset += 4; 
  364. $info['ogg']['blocksize_small'] = pow(2, getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F); 
  365. $info['ogg']['blocksize_large'] = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4); 
  366. $info['ogg']['stop_bit'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet 
  367.  
  368. $info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr 
  369. if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) { 
  370. unset($info['ogg']['bitrate_max']); 
  371. $info['audio']['bitrate_mode'] = 'abr'; 
  372. if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) { 
  373. unset($info['ogg']['bitrate_nominal']); 
  374. if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) { 
  375. unset($info['ogg']['bitrate_min']); 
  376. $info['audio']['bitrate_mode'] = 'abr'; 
  377. return true; 
  378.  
  379. // http://tools.ietf.org/html/draft-ietf-codec-oggopus-03 
  380. public function ParseOpusPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) { 
  381. $info = &$this->getid3->info; 
  382. $info['audio']['dataformat'] = 'opus'; 
  383. $info['mime_type'] = 'audio/ogg; codecs=opus'; 
  384.  
  385. /** @todo find a usable way to detect abr (vbr that is padded to be abr) */ 
  386. $info['audio']['bitrate_mode'] = 'vbr'; 
  387.  
  388. $info['audio']['lossless'] = false; 
  389.  
  390. $info['ogg']['pageheader']['opus']['opus_magic'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'OpusHead' 
  391. $filedataoffset += 8; 
  392. $info['ogg']['pageheader']['opus']['version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 
  393. $filedataoffset += 1; 
  394.  
  395. if ($info['ogg']['pageheader']['opus']['version'] < 1 || $info['ogg']['pageheader']['opus']['version'] > 15) { 
  396. $info['error'][] = 'Unknown opus version number (only accepting 1-15)'; 
  397. return false; 
  398.  
  399. $info['ogg']['pageheader']['opus']['out_channel_count'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 
  400. $filedataoffset += 1; 
  401.  
  402. if ($info['ogg']['pageheader']['opus']['out_channel_count'] == 0) { 
  403. $info['error'][] = 'Invalid channel count in opus header (must not be zero)'; 
  404. return false; 
  405.  
  406. $info['ogg']['pageheader']['opus']['pre_skip'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); 
  407. $filedataoffset += 2; 
  408.  
  409. $info['ogg']['pageheader']['opus']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  410. $filedataoffset += 4; 
  411.  
  412. //$info['ogg']['pageheader']['opus']['output_gain'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); 
  413. //$filedataoffset += 2; 
  414.  
  415. //$info['ogg']['pageheader']['opus']['channel_mapping_family'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 
  416. //$filedataoffset += 1; 
  417.  
  418. $info['opus']['opus_version'] = $info['ogg']['pageheader']['opus']['version']; 
  419. $info['opus']['sample_rate'] = $info['ogg']['pageheader']['opus']['sample_rate']; 
  420. $info['opus']['out_channel_count'] = $info['ogg']['pageheader']['opus']['out_channel_count']; 
  421.  
  422. $info['audio']['channels'] = $info['opus']['out_channel_count']; 
  423. $info['audio']['sample_rate'] = $info['opus']['sample_rate']; 
  424. return true; 
  425.  
  426.  
  427. public function ParseOggPageHeader() { 
  428. // http://xiph.org/ogg/vorbis/doc/framing.html 
  429. $oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file 
  430.  
  431. $filedata = $this->fread($this->getid3->fread_buffer_size()); 
  432. $filedataoffset = 0; 
  433. while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) { 
  434. if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) { 
  435. // should be found before here 
  436. return false; 
  437. if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) { 
  438. if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === false)) { 
  439. // get some more data, unless eof, in which case fail 
  440. return false; 
  441. $filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS' 
  442.  
  443. $oggheader['stream_structver'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 
  444. $filedataoffset += 1; 
  445. $oggheader['flags_raw'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 
  446. $filedataoffset += 1; 
  447. $oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet 
  448. $oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos) 
  449. $oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos) 
  450.  
  451. $oggheader['pcm_abs_position'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); 
  452. $filedataoffset += 8; 
  453. $oggheader['stream_serialno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  454. $filedataoffset += 4; 
  455. $oggheader['page_seqno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  456. $filedataoffset += 4; 
  457. $oggheader['page_checksum'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); 
  458. $filedataoffset += 4; 
  459. $oggheader['page_segments'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 
  460. $filedataoffset += 1; 
  461. $oggheader['page_length'] = 0; 
  462. for ($i = 0; $i < $oggheader['page_segments']; $i++) { 
  463. $oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); 
  464. $filedataoffset += 1; 
  465. $oggheader['page_length'] += $oggheader['segment_table'][$i]; 
  466. $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset; 
  467. $oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length']; 
  468. $this->fseek($oggheader['header_end_offset']); 
  469.  
  470. return $oggheader; 
  471.  
  472. // http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005 
  473. public function ParseVorbisComments() { 
  474. $info = &$this->getid3->info; 
  475.  
  476. $OriginalOffset = $this->ftell(); 
  477. $commentdataoffset = 0; 
  478. $VorbisCommentPage = 1; 
  479.  
  480. switch ($info['audio']['dataformat']) { 
  481. case 'vorbis': 
  482. case 'speex': 
  483. case 'opus': 
  484. $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block 
  485. $this->fseek($CommentStartOffset); 
  486. $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; 
  487. $commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); 
  488.  
  489. if ($info['audio']['dataformat'] == 'vorbis') { 
  490. $commentdataoffset += (strlen('vorbis') + 1); 
  491. else if ($info['audio']['dataformat'] == 'opus') { 
  492. $commentdataoffset += strlen('OpusTags'); 
  493.  
  494. break; 
  495.  
  496. case 'flac': 
  497. $CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4; 
  498. $this->fseek($CommentStartOffset); 
  499. $commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']); 
  500. break; 
  501.  
  502. default: 
  503. return false; 
  504.  
  505. $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); 
  506. $commentdataoffset += 4; 
  507.  
  508. $info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize); 
  509. $commentdataoffset += $VendorSize; 
  510.  
  511. $CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); 
  512. $commentdataoffset += 4; 
  513. $info['avdataoffset'] = $CommentStartOffset + $commentdataoffset; 
  514.  
  515. $basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT'); 
  516. $ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw']; 
  517. for ($i = 0; $i < $CommentsCount; $i++) { 
  518.  
  519. if ($i >= 10000) { 
  520. // https://github.com/owncloud/music/issues/212#issuecomment-43082336 
  521. $info['warning'][] = 'Unexpectedly large number ('.$CommentsCount.') of Ogg comments - breaking after reading '.$i.' comments'; 
  522. break; 
  523.  
  524. $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset; 
  525.  
  526. if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) { 
  527. if ($oggpageinfo = $this->ParseOggPageHeader()) { 
  528. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; 
  529.  
  530. $VorbisCommentPage++; 
  531.  
  532. // First, save what we haven't read yet 
  533. $AsYetUnusedData = substr($commentdata, $commentdataoffset); 
  534.  
  535. // Then take that data off the end 
  536. $commentdata = substr($commentdata, 0, $commentdataoffset); 
  537.  
  538. // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct 
  539. $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); 
  540. $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); 
  541.  
  542. // Finally, stick the unused data back on the end 
  543. $commentdata .= $AsYetUnusedData; 
  544.  
  545. //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); 
  546. $commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1)); 
  547.  
  548. $ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); 
  549.  
  550. // replace avdataoffset with position just after the last vorbiscomment 
  551. $info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4; 
  552.  
  553. $commentdataoffset += 4; 
  554. while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) { 
  555. if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) { 
  556. $info['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments'; 
  557. break 2; 
  558.  
  559. $VorbisCommentPage++; 
  560.  
  561. $oggpageinfo = $this->ParseOggPageHeader(); 
  562. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; 
  563.  
  564. // First, save what we haven't read yet 
  565. $AsYetUnusedData = substr($commentdata, $commentdataoffset); 
  566.  
  567. // Then take that data off the end 
  568. $commentdata = substr($commentdata, 0, $commentdataoffset); 
  569.  
  570. // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct 
  571. $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); 
  572. $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); 
  573.  
  574. // Finally, stick the unused data back on the end 
  575. $commentdata .= $AsYetUnusedData; 
  576.  
  577. //$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); 
  578. if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) { 
  579. $info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell(); 
  580. break; 
  581. $readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1); 
  582. if ($readlength <= 0) { 
  583. $info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell(); 
  584. break; 
  585. $commentdata .= $this->fread($readlength); 
  586.  
  587. //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset']; 
  588. $ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset; 
  589. $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']); 
  590. $commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size']; 
  591.  
  592. if (!$commentstring) { 
  593.  
  594. // no comment? 
  595. $info['warning'][] = 'Blank Ogg comment ['.$i.']'; 
  596.  
  597. } elseif (strstr($commentstring, '=')) { 
  598.  
  599. $commentexploded = explode('=', $commentstring, 2); 
  600. $ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]); 
  601. $ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : ''); 
  602.  
  603. if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') { 
  604.  
  605. // http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE 
  606. // The unencoded format is that of the FLAC picture block. The fields are stored in big endian order as in FLAC, picture data is stored according to the relevant standard. 
  607. // http://flac.sourceforge.net/format.html#metadata_block_picture 
  608. $flac = new getid3_flac($this->getid3); 
  609. $flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value'])); 
  610. $flac->parsePICTURE(); 
  611. $info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0]; 
  612. unset($flac); 
  613.  
  614. } elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') { 
  615.  
  616. $data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']); 
  617. $this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure'); 
  618. /** @todo use 'coverartmime' where available */ 
  619. $imageinfo = getid3_lib::GetDataImageSize($data); 
  620. if ($imageinfo === false || !isset($imageinfo['mime'])) { 
  621. $this->warning('COVERART vorbiscomment tag contains invalid image'); 
  622. continue; 
  623.  
  624. $ogg = new self($this->getid3); 
  625. $ogg->setStringMode($data); 
  626. $info['ogg']['comments']['picture'][] = array( 
  627. 'image_mime' => $imageinfo['mime'],  
  628. 'datalength' => strlen($data),  
  629. 'picturetype' => 'cover art',  
  630. 'image_height' => $imageinfo['height'],  
  631. 'image_width' => $imageinfo['width'],  
  632. 'data' => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']),  
  633. ); 
  634. unset($ogg); 
  635.  
  636. } else { 
  637.  
  638. $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value']; 
  639.  
  640.  
  641. } else { 
  642.  
  643. $info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring; 
  644.  
  645. unset($ThisFileInfo_ogg_comments_raw[$i]); 
  646. unset($ThisFileInfo_ogg_comments_raw); 
  647.  
  648.  
  649. // Replay Gain Adjustment 
  650. // http://privatewww.essex.ac.uk/~djmrob/replaygain/ 
  651. if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) { 
  652. foreach ($info['ogg']['comments'] as $index => $commentvalue) { 
  653. switch ($index) { 
  654. case 'rg_audiophile': 
  655. case 'replaygain_album_gain': 
  656. $info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0]; 
  657. unset($info['ogg']['comments'][$index]); 
  658. break; 
  659.  
  660. case 'rg_radio': 
  661. case 'replaygain_track_gain': 
  662. $info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0]; 
  663. unset($info['ogg']['comments'][$index]); 
  664. break; 
  665.  
  666. case 'replaygain_album_peak': 
  667. $info['replay_gain']['album']['peak'] = (double) $commentvalue[0]; 
  668. unset($info['ogg']['comments'][$index]); 
  669. break; 
  670.  
  671. case 'rg_peak': 
  672. case 'replaygain_track_peak': 
  673. $info['replay_gain']['track']['peak'] = (double) $commentvalue[0]; 
  674. unset($info['ogg']['comments'][$index]); 
  675. break; 
  676.  
  677. case 'replaygain_reference_loudness': 
  678. $info['replay_gain']['reference_volume'] = (double) $commentvalue[0]; 
  679. unset($info['ogg']['comments'][$index]); 
  680. break; 
  681.  
  682. default: 
  683. // do nothing 
  684. break; 
  685.  
  686. $this->fseek($OriginalOffset); 
  687.  
  688. return true; 
  689.  
  690. public static function SpeexBandModeLookup($mode) { 
  691. static $SpeexBandModeLookup = array(); 
  692. if (empty($SpeexBandModeLookup)) { 
  693. $SpeexBandModeLookup[0] = 'narrow'; 
  694. $SpeexBandModeLookup[1] = 'wide'; 
  695. $SpeexBandModeLookup[2] = 'ultra-wide'; 
  696. return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null); 
  697.  
  698.  
  699. public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) { 
  700. for ($i = 0; $i < $SegmentNumber; $i++) { 
  701. $segmentlength = 0; 
  702. foreach ($OggInfoArray['segment_table'] as $key => $value) { 
  703. $segmentlength += $value; 
  704. if ($value < 255) { 
  705. break; 
  706. return $segmentlength; 
  707.  
  708.  
  709. public static function get_quality_from_nominal_bitrate($nominal_bitrate) { 
  710.  
  711. // decrease precision 
  712. $nominal_bitrate = $nominal_bitrate / 1000; 
  713.  
  714. if ($nominal_bitrate < 128) { 
  715. // q-1 to q4 
  716. $qval = ($nominal_bitrate - 64) / 16; 
  717. } elseif ($nominal_bitrate < 256) { 
  718. // q4 to q8 
  719. $qval = $nominal_bitrate / 32; 
  720. } elseif ($nominal_bitrate < 320) { 
  721. // q8 to q9 
  722. $qval = ($nominal_bitrate + 256) / 64; 
  723. } else { 
  724. // q9 to q10 
  725. $qval = ($nominal_bitrate + 1300) / 180; 
  726. //return $qval; // 5.031324 
  727. //return intval($qval); // 5 
  728. return round($qval, 1); // 5 or 4.9 
  729.  
  730. public static function TheoraColorSpace($colorspace_id) { 
  731. // http://www.theora.org/doc/Theora.pdf (table 6.3) 
  732. static $TheoraColorSpaceLookup = array(); 
  733. if (empty($TheoraColorSpaceLookup)) { 
  734. $TheoraColorSpaceLookup[0] = 'Undefined'; 
  735. $TheoraColorSpaceLookup[1] = 'Rec. 470M'; 
  736. $TheoraColorSpaceLookup[2] = 'Rec. 470BG'; 
  737. $TheoraColorSpaceLookup[3] = 'Reserved'; 
  738. return (isset($TheoraColorSpaceLookup[$colorspace_id]) ? $TheoraColorSpaceLookup[$colorspace_id] : null); 
  739.  
  740. public static function TheoraPixelFormat($pixelformat_id) { 
  741. // http://www.theora.org/doc/Theora.pdf (table 6.4) 
  742. static $TheoraPixelFormatLookup = array(); 
  743. if (empty($TheoraPixelFormatLookup)) { 
  744. $TheoraPixelFormatLookup[0] = '4:2:0'; 
  745. $TheoraPixelFormatLookup[1] = 'Reserved'; 
  746. $TheoraPixelFormatLookup[2] = '4:2:2'; 
  747. $TheoraPixelFormatLookup[3] = '4:4:4'; 
  748. return (isset($TheoraPixelFormatLookup[$pixelformat_id]) ? $TheoraPixelFormatLookup[$pixelformat_id] : null); 
  749.