module.audio.ogg.php 39 KB

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