PageRenderTime 71ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/components/com_jce/editor/tiny_mce/plugins/mediamanager/classes/getid3/module.audio.ogg.php

https://bitbucket.org/organicdevelopment/joomla-2.5
PHP | 709 lines | 550 code | 109 blank | 50 comment | 83 complexity | bafa7ef7d84480bacfef96259d662f81 MD5 | raw file
Possible License(s): LGPL-3.0, GPL-2.0, MIT, BSD-3-Clause, LGPL-2.1
  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. /////////////////////////////////////////////////////////////////
  7. // See readme.txt for more details //
  8. /////////////////////////////////////////////////////////////////
  9. // //
  10. // module.audio.ogg.php //
  11. // module for analyzing Ogg Vorbis, OggFLAC and Speex files //
  12. // dependencies: module.audio.flac.php //
  13. // ///
  14. /////////////////////////////////////////////////////////////////
  15. getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true);
  16. class getid3_ogg extends getid3_handler
  17. {
  18. var $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory
  19. function Analyze() {
  20. $info = &$this->getid3->info;
  21. $info['fileformat'] = 'ogg';
  22. // Warn about illegal tags - only vorbiscomments are allowed
  23. if (isset($info['id3v2'])) {
  24. $info['warning'][] = 'Illegal ID3v2 tag present.';
  25. }
  26. if (isset($info['id3v1'])) {
  27. $info['warning'][] = 'Illegal ID3v1 tag present.';
  28. }
  29. if (isset($info['ape'])) {
  30. $info['warning'][] = 'Illegal APE tag present.';
  31. }
  32. // Page 1 - Stream Header
  33. fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
  34. $oggpageinfo = $this->ParseOggPageHeader();
  35. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
  36. if (ftell($this->getid3->fp) >= $this->getid3->fread_buffer_size()) {
  37. $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?)';
  38. unset($info['fileformat']);
  39. unset($info['ogg']);
  40. return false;
  41. }
  42. $filedata = fread($this->getid3->fp, $oggpageinfo['page_length']);
  43. $filedataoffset = 0;
  44. if (substr($filedata, 0, 4) == 'fLaC') {
  45. $info['audio']['dataformat'] = 'flac';
  46. $info['audio']['bitrate_mode'] = 'vbr';
  47. $info['audio']['lossless'] = true;
  48. } elseif (substr($filedata, 1, 6) == 'vorbis') {
  49. $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
  50. } elseif (substr($filedata, 0, 8) == 'Speex ') {
  51. // http://www.speex.org/manual/node10.html
  52. $info['audio']['dataformat'] = 'speex';
  53. $info['mime_type'] = 'audio/speex';
  54. $info['audio']['bitrate_mode'] = 'abr';
  55. $info['audio']['lossless'] = false;
  56. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex '
  57. $filedataoffset += 8;
  58. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version'] = substr($filedata, $filedataoffset, 20);
  59. $filedataoffset += 20;
  60. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  61. $filedataoffset += 4;
  62. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  63. $filedataoffset += 4;
  64. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  65. $filedataoffset += 4;
  66. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  67. $filedataoffset += 4;
  68. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  69. $filedataoffset += 4;
  70. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  71. $filedataoffset += 4;
  72. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  73. $filedataoffset += 4;
  74. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  75. $filedataoffset += 4;
  76. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  77. $filedataoffset += 4;
  78. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  79. $filedataoffset += 4;
  80. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  81. $filedataoffset += 4;
  82. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  83. $filedataoffset += 4;
  84. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  85. $filedataoffset += 4;
  86. $info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']);
  87. $info['speex']['sample_rate'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'];
  88. $info['speex']['channels'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'];
  89. $info['speex']['vbr'] = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'];
  90. $info['speex']['band_type'] = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']);
  91. $info['audio']['sample_rate'] = $info['speex']['sample_rate'];
  92. $info['audio']['channels'] = $info['speex']['channels'];
  93. if ($info['speex']['vbr']) {
  94. $info['audio']['bitrate_mode'] = 'vbr';
  95. }
  96. } elseif (substr($filedata, 0, 8) == "fishead\x00") {
  97. // Ogg Skeleton version 3.0 Format Specification
  98. // http://xiph.org/ogg/doc/skeleton.html
  99. $filedataoffset += 8;
  100. $info['ogg']['skeleton']['fishead']['raw']['version_major'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
  101. $filedataoffset += 2;
  102. $info['ogg']['skeleton']['fishead']['raw']['version_minor'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
  103. $filedataoffset += 2;
  104. $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
  105. $filedataoffset += 8;
  106. $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
  107. $filedataoffset += 8;
  108. $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
  109. $filedataoffset += 8;
  110. $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
  111. $filedataoffset += 8;
  112. $info['ogg']['skeleton']['fishead']['raw']['utc'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20));
  113. $filedataoffset += 20;
  114. $info['ogg']['skeleton']['fishead']['version'] = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor'];
  115. $info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'];
  116. $info['ogg']['skeleton']['fishead']['basetime'] = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'];
  117. $info['ogg']['skeleton']['fishead']['utc'] = $info['ogg']['skeleton']['fishead']['raw']['utc'];
  118. $counter = 0;
  119. do {
  120. $oggpageinfo = $this->ParseOggPageHeader();
  121. $info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo;
  122. $filedata = fread($this->getid3->fp, $oggpageinfo['page_length']);
  123. fseek($this->getid3->fp, $oggpageinfo['page_end_offset'], SEEK_SET);
  124. //echo substr($filedata, 0, 8).'<br>';
  125. if (substr($filedata, 0, 8) == "fisbone\x00") {
  126. $filedataoffset = 8;
  127. $info['ogg']['skeleton']['fisbone']['raw']['message_header_offset'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  128. $filedataoffset += 4;
  129. $info['ogg']['skeleton']['fisbone']['raw']['serial_number'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  130. $filedataoffset += 4;
  131. $info['ogg']['skeleton']['fisbone']['raw']['number_header_packets'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  132. $filedataoffset += 4;
  133. $info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
  134. $filedataoffset += 8;
  135. $info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
  136. $filedataoffset += 8;
  137. $info['ogg']['skeleton']['fisbone']['raw']['basegranule'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
  138. $filedataoffset += 8;
  139. $info['ogg']['skeleton']['fisbone']['raw']['preroll'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  140. $filedataoffset += 4;
  141. $info['ogg']['skeleton']['fisbone']['raw']['granuleshift'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
  142. $filedataoffset += 1;
  143. $info['ogg']['skeleton']['fisbone']['raw']['padding'] = substr($filedata, $filedataoffset, 3);
  144. $filedataoffset += 3;
  145. } elseif (substr($filedata, 1, 6) == 'theora') {
  146. $info['video']['dataformat'] = 'theora';
  147. $info['error'][] = 'Ogg Theora not correctly handled in this version of getID3 ['.$this->getid3->version().']';
  148. //break;
  149. } elseif (substr($filedata, 1, 6) == 'vorbis') {
  150. $this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
  151. } else {
  152. $info['error'][] = 'unexpected';
  153. //break;
  154. }
  155. //} while ($oggpageinfo['page_seqno'] == 0);
  156. } while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00"));
  157. fseek($this->getid3->fp, $oggpageinfo['page_start_offset'], SEEK_SET);
  158. $info['error'][] = 'Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']';
  159. //return false;
  160. } else {
  161. $info['error'][] = 'Expecting either "Speex " or "vorbis" identifier strings, found "'.substr($filedata, 0, 8).'"';
  162. unset($info['ogg']);
  163. unset($info['mime_type']);
  164. return false;
  165. }
  166. // Page 2 - Comment Header
  167. $oggpageinfo = $this->ParseOggPageHeader();
  168. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
  169. switch ($info['audio']['dataformat']) {
  170. case 'vorbis':
  171. $filedata = fread($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
  172. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1));
  173. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 1, 6); // hard-coded to 'vorbis'
  174. $this->ParseVorbisCommentsFilepointer();
  175. break;
  176. case 'flac':
  177. $getid3_flac = new getid3_flac($this->getid3);
  178. if (!$getid3_flac->FLACparseMETAdata()) {
  179. $info['error'][] = 'Failed to parse FLAC headers';
  180. return false;
  181. }
  182. unset($getid3_flac);
  183. break;
  184. case 'speex':
  185. fseek($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR);
  186. $this->ParseVorbisCommentsFilepointer();
  187. break;
  188. }
  189. // Last Page - Number of Samples
  190. if (!getid3_lib::intValueSupported($info['avdataend'])) {
  191. $info['warning'][] = 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)';
  192. } else {
  193. fseek($this->getid3->fp, max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0), SEEK_SET);
  194. $LastChunkOfOgg = strrev(fread($this->getid3->fp, $this->getid3->fread_buffer_size()));
  195. if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) {
  196. fseek($this->getid3->fp, $info['avdataend'] - ($LastOggSpostion + strlen('SggO')), SEEK_SET);
  197. $info['avdataend'] = ftell($this->getid3->fp);
  198. $info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader();
  199. $info['ogg']['samples'] = $info['ogg']['pageheader']['eos']['pcm_abs_position'];
  200. if ($info['ogg']['samples'] == 0) {
  201. $info['error'][] = 'Corrupt Ogg file: eos.number of samples == zero';
  202. return false;
  203. }
  204. if (!empty($info['audio']['sample_rate'])) {
  205. $info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']);
  206. }
  207. }
  208. }
  209. if (!empty($info['ogg']['bitrate_average'])) {
  210. $info['audio']['bitrate'] = $info['ogg']['bitrate_average'];
  211. } elseif (!empty($info['ogg']['bitrate_nominal'])) {
  212. $info['audio']['bitrate'] = $info['ogg']['bitrate_nominal'];
  213. } elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) {
  214. $info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2;
  215. }
  216. if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) {
  217. if ($info['audio']['bitrate'] == 0) {
  218. $info['error'][] = 'Corrupt Ogg file: bitrate_audio == zero';
  219. return false;
  220. }
  221. $info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']);
  222. }
  223. if (isset($info['ogg']['vendor'])) {
  224. $info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']);
  225. // Vorbis only
  226. if ($info['audio']['dataformat'] == 'vorbis') {
  227. // Vorbis 1.0 starts with Xiph.Org
  228. if (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) {
  229. if ($info['audio']['bitrate_mode'] == 'abr') {
  230. // Set -b 128 on abr files
  231. $info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000);
  232. } elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) {
  233. // Set -q N on vbr files
  234. $info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']);
  235. }
  236. }
  237. if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) {
  238. $info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps';
  239. }
  240. }
  241. }
  242. return true;
  243. }
  244. function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) {
  245. $info = &$this->getid3->info;
  246. $info['audio']['dataformat'] = 'vorbis';
  247. $info['audio']['lossless'] = false;
  248. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
  249. $filedataoffset += 1;
  250. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis'
  251. $filedataoffset += 6;
  252. $info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  253. $filedataoffset += 4;
  254. $info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
  255. $filedataoffset += 1;
  256. $info['audio']['channels'] = $info['ogg']['numberofchannels'];
  257. $info['ogg']['samplerate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  258. $filedataoffset += 4;
  259. if ($info['ogg']['samplerate'] == 0) {
  260. $info['error'][] = 'Corrupt Ogg file: sample rate == zero';
  261. return false;
  262. }
  263. $info['audio']['sample_rate'] = $info['ogg']['samplerate'];
  264. $info['ogg']['samples'] = 0; // filled in later
  265. $info['ogg']['bitrate_average'] = 0; // filled in later
  266. $info['ogg']['bitrate_max'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  267. $filedataoffset += 4;
  268. $info['ogg']['bitrate_nominal'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  269. $filedataoffset += 4;
  270. $info['ogg']['bitrate_min'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  271. $filedataoffset += 4;
  272. $info['ogg']['blocksize_small'] = pow(2, getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F);
  273. $info['ogg']['blocksize_large'] = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4);
  274. $info['ogg']['stop_bit'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet
  275. $info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr
  276. if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) {
  277. unset($info['ogg']['bitrate_max']);
  278. $info['audio']['bitrate_mode'] = 'abr';
  279. }
  280. if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) {
  281. unset($info['ogg']['bitrate_nominal']);
  282. }
  283. if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) {
  284. unset($info['ogg']['bitrate_min']);
  285. $info['audio']['bitrate_mode'] = 'abr';
  286. }
  287. return true;
  288. }
  289. function ParseOggPageHeader() {
  290. // http://xiph.org/ogg/vorbis/doc/framing.html
  291. $oggheader['page_start_offset'] = ftell($this->getid3->fp); // where we started from in the file
  292. //echo $oggheader['page_start_offset'].'<br>';
  293. $filedata = fread($this->getid3->fp, $this->getid3->fread_buffer_size());
  294. $filedataoffset = 0;
  295. while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) {
  296. if ((ftell($this->getid3->fp) - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
  297. // should be found before here
  298. return false;
  299. }
  300. if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) {
  301. if (feof($this->getid3->fp) || (($filedata .= fread($this->getid3->fp, $this->getid3->fread_buffer_size())) === false)) {
  302. // get some more data, unless eof, in which case fail
  303. echo __LINE__.'<br>';
  304. return false;
  305. }
  306. }
  307. }
  308. $filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS'
  309. $oggheader['stream_structver'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
  310. $filedataoffset += 1;
  311. $oggheader['flags_raw'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
  312. $filedataoffset += 1;
  313. $oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet
  314. $oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos)
  315. $oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos)
  316. $oggheader['pcm_abs_position'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8));
  317. $filedataoffset += 8;
  318. $oggheader['stream_serialno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  319. $filedataoffset += 4;
  320. $oggheader['page_seqno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  321. $filedataoffset += 4;
  322. $oggheader['page_checksum'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
  323. $filedataoffset += 4;
  324. $oggheader['page_segments'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
  325. $filedataoffset += 1;
  326. $oggheader['page_length'] = 0;
  327. for ($i = 0; $i < $oggheader['page_segments']; $i++) {
  328. $oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
  329. $filedataoffset += 1;
  330. $oggheader['page_length'] += $oggheader['segment_table'][$i];
  331. }
  332. $oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset;
  333. $oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length'];
  334. fseek($this->getid3->fp, $oggheader['header_end_offset'], SEEK_SET);
  335. return $oggheader;
  336. }
  337. function ParseVorbisCommentsFilepointer() {
  338. $info = &$this->getid3->info;
  339. $OriginalOffset = ftell($this->getid3->fp);
  340. $commentdataoffset = 0;
  341. $VorbisCommentPage = 1;
  342. switch ($info['audio']['dataformat']) {
  343. case 'vorbis':
  344. $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block
  345. fseek($this->getid3->fp, $CommentStartOffset, SEEK_SET);
  346. $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
  347. $commentdata = fread($this->getid3->fp, getid3_ogg::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
  348. $commentdataoffset += (strlen('vorbis') + 1);
  349. break;
  350. case 'flac':
  351. $CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4;
  352. fseek($this->getid3->fp, $CommentStartOffset, SEEK_SET);
  353. $commentdata = fread($this->getid3->fp, $info['flac']['VORBIS_COMMENT']['raw']['block_length']);
  354. break;
  355. case 'speex':
  356. $CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block
  357. fseek($this->getid3->fp, $CommentStartOffset, SEEK_SET);
  358. $commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments'];
  359. $commentdata = fread($this->getid3->fp, getid3_ogg::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset);
  360. break;
  361. default:
  362. return false;
  363. break;
  364. }
  365. $VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
  366. $commentdataoffset += 4;
  367. $info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize);
  368. $commentdataoffset += $VendorSize;
  369. $CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
  370. $commentdataoffset += 4;
  371. $info['avdataoffset'] = $CommentStartOffset + $commentdataoffset;
  372. $basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT');
  373. $ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw'];
  374. for ($i = 0; $i < $CommentsCount; $i++) {
  375. //echo $i.' :: '.$CommentStartOffset.' + '.$commentdataoffset.' = '.($CommentStartOffset + $commentdataoffset).'<br>';
  376. $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset;
  377. if (ftell($this->getid3->fp) < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) {
  378. if ($oggpageinfo = $this->ParseOggPageHeader()) {
  379. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
  380. $VorbisCommentPage++;
  381. // First, save what we haven't read yet
  382. $AsYetUnusedData = substr($commentdata, $commentdataoffset);
  383. // Then take that data off the end
  384. $commentdata = substr($commentdata, 0, $commentdataoffset);
  385. // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
  386. $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
  387. $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
  388. // Finally, stick the unused data back on the end
  389. $commentdata .= $AsYetUnusedData;
  390. //$commentdata .= fread($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
  391. $commentdata .= fread($this->getid3->fp, $this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1));
  392. }
  393. }
  394. $ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4));
  395. // replace avdataoffset with position just after the last vorbiscomment
  396. $info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4;
  397. $commentdataoffset += 4;
  398. while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) {
  399. if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) {
  400. $info['warning'][] = 'Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments';
  401. break 2;
  402. }
  403. $VorbisCommentPage++;
  404. $oggpageinfo = $this->ParseOggPageHeader();
  405. $info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
  406. // First, save what we haven't read yet
  407. $AsYetUnusedData = substr($commentdata, $commentdataoffset);
  408. // Then take that data off the end
  409. $commentdata = substr($commentdata, 0, $commentdataoffset);
  410. // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
  411. $commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
  412. $commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']);
  413. // Finally, stick the unused data back on the end
  414. $commentdata .= $AsYetUnusedData;
  415. //$commentdata .= fread($this->getid3->fp, $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
  416. if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) {
  417. $info['warning'][] = 'undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.ftell($this->getid3->fp);
  418. break;
  419. }
  420. $readlength = getid3_ogg::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1);
  421. if ($readlength <= 0) {
  422. $info['warning'][] = 'invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.ftell($this->getid3->fp);
  423. break;
  424. }
  425. $commentdata .= fread($this->getid3->fp, $readlength);
  426. //$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
  427. }
  428. $ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset;
  429. $commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']);
  430. $commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size'];
  431. if (!$commentstring) {
  432. // no comment?
  433. $info['warning'][] = 'Blank Ogg comment ['.$i.']';
  434. } elseif (strstr($commentstring, '=')) {
  435. $commentexploded = explode('=', $commentstring, 2);
  436. $ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]);
  437. $ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : '');
  438. $ThisFileInfo_ogg_comments_raw[$i]['data'] = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']);
  439. $ThisFileInfo_ogg_comments_raw[$i]['data_length'] = strlen($ThisFileInfo_ogg_comments_raw[$i]['data']);
  440. if (preg_match('#^(BM|GIF|\xFF\xD8\xFF|\x89\x50\x4E\x47\x0D\x0A\x1A\x0A|II\x2A\x00|MM\x00\x2A)#s', $ThisFileInfo_ogg_comments_raw[$i]['data'])) {
  441. $imageinfo = array();
  442. $imagechunkcheck = getid3_lib::GetDataImageSize($ThisFileInfo_ogg_comments_raw[$i]['data'], $imageinfo);
  443. unset($imageinfo);
  444. if (!empty($imagechunkcheck)) {
  445. $ThisFileInfo_ogg_comments_raw[$i]['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
  446. if ($ThisFileInfo_ogg_comments_raw[$i]['image_mime'] && ($ThisFileInfo_ogg_comments_raw[$i]['image_mime'] != 'application/octet-stream')) {
  447. unset($ThisFileInfo_ogg_comments_raw[$i]['value']);
  448. }
  449. }
  450. }
  451. if (isset($ThisFileInfo_ogg_comments_raw[$i]['value'])) {
  452. unset($ThisFileInfo_ogg_comments_raw[$i]['data']);
  453. $info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];
  454. } else {
  455. do {
  456. if ($this->inline_attachments === false) {
  457. // skip entirely
  458. unset($ThisFileInfo_ogg_comments_raw[$i]['data']);
  459. break;
  460. }
  461. if ($this->inline_attachments === true) {
  462. // great
  463. } elseif (is_int($this->inline_attachments)) {
  464. if ($this->inline_attachments < $ThisFileInfo_ogg_comments_raw[$i]['data_length']) {
  465. // too big, skip
  466. $info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' is too large to process inline ('.number_format($ThisFileInfo_ogg_comments_raw[$i]['data_length']).' bytes)';
  467. unset($ThisFileInfo_ogg_comments_raw[$i]['data']);
  468. break;
  469. }
  470. } elseif (is_string($this->inline_attachments)) {
  471. $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
  472. if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) {
  473. // cannot write, skip
  474. $info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)';
  475. unset($ThisFileInfo_ogg_comments_raw[$i]['data']);
  476. break;
  477. }
  478. }
  479. // if we get this far, must be OK
  480. if (is_string($this->inline_attachments)) {
  481. $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$ThisFileInfo_ogg_comments_raw[$i]['offset'];
  482. if (!file_exists($destination_filename) || is_writable($destination_filename)) {
  483. file_put_contents($destination_filename, $ThisFileInfo_ogg_comments_raw[$i]['data']);
  484. } else {
  485. $info['warning'][] = 'attachment at '.$ThisFileInfo_ogg_comments_raw[$i]['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)';
  486. }
  487. $ThisFileInfo_ogg_comments_raw[$i]['data_filename'] = $destination_filename;
  488. unset($ThisFileInfo_ogg_comments_raw[$i]['data']);
  489. } else {
  490. $info['ogg']['comments']['picture'][] = array('data'=>$ThisFileInfo_ogg_comments_raw[$i]['data'], 'image_mime'=>$ThisFileInfo_ogg_comments_raw[$i]['image_mime']);
  491. }
  492. } while (false);
  493. }
  494. } else {
  495. $info['warning'][] = '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring;
  496. }
  497. }
  498. // Replay Gain Adjustment
  499. // http://privatewww.essex.ac.uk/~djmrob/replaygain/
  500. if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) {
  501. foreach ($info['ogg']['comments'] as $index => $commentvalue) {
  502. switch ($index) {
  503. case 'rg_audiophile':
  504. case 'replaygain_album_gain':
  505. $info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0];
  506. unset($info['ogg']['comments'][$index]);
  507. break;
  508. case 'rg_radio':
  509. case 'replaygain_track_gain':
  510. $info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0];
  511. unset($info['ogg']['comments'][$index]);
  512. break;
  513. case 'replaygain_album_peak':
  514. $info['replay_gain']['album']['peak'] = (double) $commentvalue[0];
  515. unset($info['ogg']['comments'][$index]);
  516. break;
  517. case 'rg_peak':
  518. case 'replaygain_track_peak':
  519. $info['replay_gain']['track']['peak'] = (double) $commentvalue[0];
  520. unset($info['ogg']['comments'][$index]);
  521. break;
  522. case 'replaygain_reference_loudness':
  523. $info['replay_gain']['reference_volume'] = (double) $commentvalue[0];
  524. unset($info['ogg']['comments'][$index]);
  525. break;
  526. default:
  527. // do nothing
  528. break;
  529. }
  530. }
  531. }
  532. fseek($this->getid3->fp, $OriginalOffset, SEEK_SET);
  533. return true;
  534. }
  535. static function SpeexBandModeLookup($mode) {
  536. static $SpeexBandModeLookup = array();
  537. if (empty($SpeexBandModeLookup)) {
  538. $SpeexBandModeLookup[0] = 'narrow';
  539. $SpeexBandModeLookup[1] = 'wide';
  540. $SpeexBandModeLookup[2] = 'ultra-wide';
  541. }
  542. return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null);
  543. }
  544. static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) {
  545. for ($i = 0; $i < $SegmentNumber; $i++) {
  546. $segmentlength = 0;
  547. foreach ($OggInfoArray['segment_table'] as $key => $value) {
  548. $segmentlength += $value;
  549. if ($value < 255) {
  550. break;
  551. }
  552. }
  553. }
  554. return $segmentlength;
  555. }
  556. static function get_quality_from_nominal_bitrate($nominal_bitrate) {
  557. // decrease precision
  558. $nominal_bitrate = $nominal_bitrate / 1000;
  559. if ($nominal_bitrate < 128) {
  560. // q-1 to q4
  561. $qval = ($nominal_bitrate - 64) / 16;
  562. } elseif ($nominal_bitrate < 256) {
  563. // q4 to q8
  564. $qval = $nominal_bitrate / 32;
  565. } elseif ($nominal_bitrate < 320) {
  566. // q8 to q9
  567. $qval = ($nominal_bitrate + 256) / 64;
  568. } else {
  569. // q9 to q10
  570. $qval = ($nominal_bitrate + 1300) / 180;
  571. }
  572. //return $qval; // 5.031324
  573. //return intval($qval); // 5
  574. return round($qval, 1); // 5 or 4.9
  575. }
  576. }
  577. ?>