PageRenderTime 56ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 1ms

/application/libraries/getid3/module.audio.xiph.php

https://github.com/libis/Flandrica
PHP | 968 lines | 632 code | 272 blank | 64 comment | 94 complexity | 64c023f29957fbeb660d699d3b42a72a MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, Apache-2.0
  1. <?php
  2. // +----------------------------------------------------------------------+
  3. // | PHP version 5 |
  4. // +----------------------------------------------------------------------+
  5. // | Copyright (c) 2002-2009 James Heinrich, Allan Hansen |
  6. // +----------------------------------------------------------------------+
  7. // | This source file is subject to version 2 of the GPL license, |
  8. // | that is bundled with this package in the file license.txt and is |
  9. // | available through the world-wide-web at the following url: |
  10. // | http://www.gnu.org/copyleft/gpl.html |
  11. // +----------------------------------------------------------------------+
  12. // | getID3() - http://getid3.sourceforge.net or http://www.getid3.org |
  13. // +----------------------------------------------------------------------+
  14. // | Authors: James Heinrich <infoŘgetid3*org> |
  15. // | Allan Hansen <ahŘartemis*dk> |
  16. // +----------------------------------------------------------------------+
  17. // | module.audio.xiph.php |
  18. // | Module for analyzing Xiph.org audio file formats: |
  19. // | Ogg Vorbis, FLAC, OggFLAC and Speex - not Ogg Theora |
  20. // | dependencies: module.lib.image_size.php (optional) |
  21. // +----------------------------------------------------------------------+
  22. //
  23. // $Id: module.audio.xiph.php,v 1.5 2006/12/03 21:12:43 ah Exp $
  24. class getid3_xiph extends getid3_handler
  25. {
  26. public function Analyze() {
  27. $getid3 = $this->getid3;
  28. if ($getid3->option_tags_images) {
  29. $getid3->include_module('lib.image_size');
  30. }
  31. fseek($getid3->fp, $getid3->info['avdataoffset'], SEEK_SET);
  32. $magic = fread($getid3->fp, 4);
  33. if ($magic == 'OggS') {
  34. return $this->ParseOgg();
  35. }
  36. if ($magic == 'fLaC') {
  37. return $this->ParseFLAC();
  38. }
  39. }
  40. private function ParseOgg() {
  41. $getid3 = $this->getid3;
  42. fseek($getid3->fp, $getid3->info['avdataoffset'], SEEK_SET);
  43. $getid3->info['audio'] = $getid3->info['ogg'] = array ();
  44. $info_ogg = &$getid3->info['ogg'];
  45. $info_audio = &$getid3->info['audio'];
  46. $getid3->info['fileformat'] = 'ogg';
  47. //// Page 1 - Stream Header
  48. $ogg_page_info = $this->ParseOggPageHeader();
  49. $info_ogg['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;
  50. if (ftell($getid3->fp) >= getid3::FREAD_BUFFER_SIZE) {
  51. throw new getid3_exception('Could not find start of Ogg page in the first '.getid3::FREAD_BUFFER_SIZE.' bytes (this might not be an Ogg file?)');
  52. }
  53. $file_data = fread($getid3->fp, $ogg_page_info['page_length']);
  54. $file_data_offset = 0;
  55. // OggFLAC
  56. if (substr($file_data, 0, 4) == 'fLaC') {
  57. $info_audio['dataformat'] = 'flac';
  58. $info_audio['bitrate_mode'] = 'vbr';
  59. $info_audio['lossless'] = true;
  60. }
  61. // Ogg Vorbis
  62. elseif (substr($file_data, 1, 6) == 'vorbis') {
  63. $info_audio['dataformat'] = 'vorbis';
  64. $info_audio['lossless'] = false;
  65. $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int($file_data[0]);
  66. $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['stream_type'] = substr($file_data, 1, 6); // hard-coded to 'vorbis'
  67. getid3_lib::ReadSequence('LittleEndian2Int', $info_ogg, $file_data, 7,
  68. array (
  69. 'bitstreamversion' => 4,
  70. 'numberofchannels' => 1,
  71. 'samplerate' => 4,
  72. 'bitrate_max' => 4,
  73. 'bitrate_nominal' => 4,
  74. 'bitrate_min' => 4
  75. )
  76. );
  77. $n28 = getid3_lib::LittleEndian2Int($file_data{28});
  78. $info_ogg['blocksize_small'] = pow(2, $n28 & 0x0F);
  79. $info_ogg['blocksize_large'] = pow(2, ($n28 & 0xF0) >> 4);
  80. $info_ogg['stop_bit'] = $n28;
  81. $info_audio['channels'] = $info_ogg['numberofchannels'];
  82. $info_audio['sample_rate'] = $info_ogg['samplerate'];
  83. $info_audio['bitrate_mode'] = 'vbr'; // overridden if actually abr
  84. if ($info_ogg['bitrate_max'] == 0xFFFFFFFF) {
  85. unset($info_ogg['bitrate_max']);
  86. $info_audio['bitrate_mode'] = 'abr';
  87. }
  88. if ($info_ogg['bitrate_nominal'] == 0xFFFFFFFF) {
  89. unset($info_ogg['bitrate_nominal']);
  90. }
  91. if ($info_ogg['bitrate_min'] == 0xFFFFFFFF) {
  92. unset($info_ogg['bitrate_min']);
  93. $info_audio['bitrate_mode'] = 'abr';
  94. }
  95. }
  96. // Speex
  97. elseif (substr($file_data, 0, 8) == 'Speex ') {
  98. // http://www.speex.org/manual/node10.html
  99. $info_audio['dataformat'] = 'speex';
  100. $getid3->info['mime_type'] = 'audio/speex';
  101. $info_audio['bitrate_mode'] = 'abr';
  102. $info_audio['lossless'] = false;
  103. getid3_lib::ReadSequence('LittleEndian2Int', $info_ogg['pageheader'][$ogg_page_info['page_seqno']], $file_data, 0,
  104. array (
  105. 'speex_string' => -8, // hard-coded to 'Speex '
  106. 'speex_version' => -20, // string
  107. 'speex_version_id' => 4,
  108. 'header_size' => 4,
  109. 'rate' => 4,
  110. 'mode' => 4,
  111. 'mode_bitstream_version' => 4,
  112. 'nb_channels' => 4,
  113. 'bitrate' => 4,
  114. 'framesize' => 4,
  115. 'vbr' => 4,
  116. 'frames_per_packet' => 4,
  117. 'extra_headers' => 4,
  118. 'reserved1' => 4,
  119. 'reserved2' => 4
  120. )
  121. );
  122. $getid3->info['speex']['speex_version'] = trim($info_ogg['pageheader'][$ogg_page_info['page_seqno']]['speex_version']);
  123. $getid3->info['speex']['sample_rate'] = $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['rate'];
  124. $getid3->info['speex']['channels'] = $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['nb_channels'];
  125. $getid3->info['speex']['vbr'] = (bool)$info_ogg['pageheader'][$ogg_page_info['page_seqno']]['vbr'];
  126. $getid3->info['speex']['band_type'] = getid3_xiph::SpeexBandModeLookup($info_ogg['pageheader'][$ogg_page_info['page_seqno']]['mode']);
  127. $info_audio['sample_rate'] = $getid3->info['speex']['sample_rate'];
  128. $info_audio['channels'] = $getid3->info['speex']['channels'];
  129. if ($getid3->info['speex']['vbr']) {
  130. $info_audio['bitrate_mode'] = 'vbr';
  131. }
  132. }
  133. // Unsupported Ogg file
  134. else {
  135. throw new getid3_exception('Expecting either "Speex " or "vorbis" identifier strings, found neither');
  136. }
  137. //// Page 2 - Comment Header
  138. $ogg_page_info = $this->ParseOggPageHeader();
  139. $info_ogg['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;
  140. switch ($info_audio['dataformat']) {
  141. case 'vorbis':
  142. $file_data = fread($getid3->fp, $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['page_length']);
  143. $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($file_data, 0, 1));
  144. $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['stream_type'] = substr($file_data, 1, 6); // hard-coded to 'vorbis'
  145. $this->ParseVorbisCommentsFilepointer();
  146. break;
  147. case 'flac':
  148. if (!$this->FLACparseMETAdata()) {
  149. throw new getid3_exception('Failed to parse FLAC headers');
  150. }
  151. break;
  152. case 'speex':
  153. fseek($getid3->fp, $info_ogg['pageheader'][$ogg_page_info['page_seqno']]['page_length'], SEEK_CUR);
  154. $this->ParseVorbisCommentsFilepointer();
  155. break;
  156. }
  157. //// Last Page - Number of Samples
  158. fseek($getid3->fp, max($getid3->info['avdataend'] - getid3::FREAD_BUFFER_SIZE, 0), SEEK_SET);
  159. $last_chunk_of_ogg = strrev(fread($getid3->fp, getid3::FREAD_BUFFER_SIZE));
  160. if ($last_OggS_postion = strpos($last_chunk_of_ogg, 'SggO')) {
  161. fseek($getid3->fp, $getid3->info['avdataend'] - ($last_OggS_postion + strlen('SggO')), SEEK_SET);
  162. $getid3->info['avdataend'] = ftell($getid3->fp);
  163. $info_ogg['pageheader']['eos'] = $this->ParseOggPageHeader();
  164. $info_ogg['samples'] = $info_ogg['pageheader']['eos']['pcm_abs_position'];
  165. $info_ogg['bitrate_average'] = (($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / ($info_ogg['samples'] / $info_audio['sample_rate']);
  166. }
  167. if (!empty($info_ogg['bitrate_average'])) {
  168. $info_audio['bitrate'] = $info_ogg['bitrate_average'];
  169. } elseif (!empty($info_ogg['bitrate_nominal'])) {
  170. $info_audio['bitrate'] = $info_ogg['bitrate_nominal'];
  171. } elseif (!empty($info_ogg['bitrate_min']) && !empty($info_ogg['bitrate_max'])) {
  172. $info_audio['bitrate'] = ($info_ogg['bitrate_min'] + $info_ogg['bitrate_max']) / 2;
  173. }
  174. if (isset($info_audio['bitrate']) && !isset($getid3->info['playtime_seconds'])) {
  175. $getid3->info['playtime_seconds'] = (float)((($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / $info_audio['bitrate']);
  176. }
  177. if (isset($info_ogg['vendor'])) {
  178. $info_audio['encoder'] = preg_replace('/^Encoded with /', '', $info_ogg['vendor']);
  179. // Vorbis only
  180. if ($info_audio['dataformat'] == 'vorbis') {
  181. // Vorbis 1.0 starts with Xiph.Org
  182. if (preg_match('/^Xiph.Org/', $info_audio['encoder'])) {
  183. if ($info_audio['bitrate_mode'] == 'abr') {
  184. // Set -b 128 on abr files
  185. $info_audio['encoder_options'] = '-b '.round($info_ogg['bitrate_nominal'] / 1000);
  186. } elseif (($info_audio['bitrate_mode'] == 'vbr') && ($info_audio['channels'] == 2) && ($info_audio['sample_rate'] >= 44100) && ($info_audio['sample_rate'] <= 48000)) {
  187. // Set -q N on vbr files
  188. $info_audio['encoder_options'] = '-q '.getid3_xiph::GetQualityFromNominalBitrate($info_ogg['bitrate_nominal']);
  189. }
  190. }
  191. if (empty($info_audio['encoder_options']) && !empty($info_ogg['bitrate_nominal'])) {
  192. $info_audio['encoder_options'] = 'Nominal bitrate: '.intval(round($info_ogg['bitrate_nominal'] / 1000)).'kbps';
  193. }
  194. }
  195. }
  196. return true;
  197. }
  198. private function ParseOggPageHeader() {
  199. $getid3 = $this->getid3;
  200. // http://xiph.org/ogg/vorbis/doc/framing.html
  201. $ogg_header['page_start_offset'] = ftell($getid3->fp); // where we started from in the file
  202. $file_data = fread($getid3->fp, getid3::FREAD_BUFFER_SIZE);
  203. $file_data_offset = 0;
  204. while ((substr($file_data, $file_data_offset++, 4) != 'OggS')) {
  205. if ((ftell($getid3->fp) - $ogg_header['page_start_offset']) >= getid3::FREAD_BUFFER_SIZE) {
  206. // should be found before here
  207. return false;
  208. }
  209. if ((($file_data_offset + 28) > strlen($file_data)) || (strlen($file_data) < 28)) {
  210. if (feof($getid3->fp) || (($file_data .= fread($getid3->fp, getid3::FREAD_BUFFER_SIZE)) === false)) {
  211. // get some more data, unless eof, in which case fail
  212. return false;
  213. }
  214. }
  215. }
  216. $file_data_offset += 3; // page, delimited by 'OggS'
  217. getid3_lib::ReadSequence('LittleEndian2Int', $ogg_header, $file_data, $file_data_offset,
  218. array (
  219. 'stream_structver' => 1,
  220. 'flags_raw' => 1,
  221. 'pcm_abs_position' => 8,
  222. 'stream_serialno' => 4,
  223. 'page_seqno' => 4,
  224. 'page_checksum' => 4,
  225. 'page_segments' => 1
  226. )
  227. );
  228. $file_data_offset += 23;
  229. $ogg_header['flags']['fresh'] = (bool)($ogg_header['flags_raw'] & 0x01); // fresh packet
  230. $ogg_header['flags']['bos'] = (bool)($ogg_header['flags_raw'] & 0x02); // first page of logical bitstream (bos)
  231. $ogg_header['flags']['eos'] = (bool)($ogg_header['flags_raw'] & 0x04); // last page of logical bitstream (eos)
  232. $ogg_header['page_length'] = 0;
  233. for ($i = 0; $i < $ogg_header['page_segments']; $i++) {
  234. $ogg_header['segment_table'][$i] = getid3_lib::LittleEndian2Int($file_data{$file_data_offset++});
  235. $ogg_header['page_length'] += $ogg_header['segment_table'][$i];
  236. }
  237. $ogg_header['header_end_offset'] = $ogg_header['page_start_offset'] + $file_data_offset;
  238. $ogg_header['page_end_offset'] = $ogg_header['header_end_offset'] + $ogg_header['page_length'];
  239. fseek($getid3->fp, $ogg_header['header_end_offset'], SEEK_SET);
  240. return $ogg_header;
  241. }
  242. private function ParseVorbisCommentsFilepointer() {
  243. $getid3 = $this->getid3;
  244. $original_offset = ftell($getid3->fp);
  245. $comment_start_offset = $original_offset;
  246. $comment_data_offset = 0;
  247. $vorbis_comment_page = 1;
  248. switch ($getid3->info['audio']['dataformat']) {
  249. case 'vorbis':
  250. $comment_start_offset = $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_start_offset']; // Second Ogg page, after header block
  251. fseek($getid3->fp, $comment_start_offset, SEEK_SET);
  252. $comment_data_offset = 27 + $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_segments'];
  253. $comment_data = fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1) + $comment_data_offset);
  254. $comment_data_offset += (strlen('vorbis') + 1);
  255. break;
  256. case 'flac':
  257. fseek($getid3->fp, $getid3->info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4, SEEK_SET);
  258. $comment_data = fread($getid3->fp, $getid3->info['flac']['VORBIS_COMMENT']['raw']['block_length']);
  259. break;
  260. case 'speex':
  261. $comment_start_offset = $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_start_offset']; // Second Ogg page, after header block
  262. fseek($getid3->fp, $comment_start_offset, SEEK_SET);
  263. $comment_data_offset = 27 + $getid3->info['ogg']['pageheader'][$vorbis_comment_page]['page_segments'];
  264. $comment_data = fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1) + $comment_data_offset);
  265. break;
  266. default:
  267. return false;
  268. }
  269. $vendor_size = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4));
  270. $comment_data_offset += 4;
  271. $getid3->info['ogg']['vendor'] = substr($comment_data, $comment_data_offset, $vendor_size);
  272. $comment_data_offset += $vendor_size;
  273. $comments_count = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4));
  274. $comment_data_offset += 4;
  275. $getid3->info['avdataoffset'] = $comment_start_offset + $comment_data_offset;
  276. for ($i = 0; $i < $comments_count; $i++) {
  277. $getid3->info['ogg']['comments_raw'][$i]['dataoffset'] = $comment_start_offset + $comment_data_offset;
  278. if (ftell($getid3->fp) < ($getid3->info['ogg']['comments_raw'][$i]['dataoffset'] + 4)) {
  279. $vorbis_comment_page++;
  280. $ogg_page_info = $this->ParseOggPageHeader();
  281. $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;
  282. // First, save what we haven't read yet
  283. $as_yet_unused_data = substr($comment_data, $comment_data_offset);
  284. // Then take that data off the end
  285. $comment_data = substr($comment_data, 0, $comment_data_offset);
  286. // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
  287. $comment_data .= str_repeat("\x00", 27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']);
  288. $comment_data_offset += (27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']);
  289. // Finally, stick the unused data back on the end
  290. $comment_data .= $as_yet_unused_data;
  291. $comment_data .= fread($getid3->fp, getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1));
  292. }
  293. $getid3->info['ogg']['comments_raw'][$i]['size'] = getid3_lib::LittleEndian2Int(substr($comment_data, $comment_data_offset, 4));
  294. // replace avdataoffset with position just after the last vorbiscomment
  295. $getid3->info['avdataoffset'] = $getid3->info['ogg']['comments_raw'][$i]['dataoffset'] + $getid3->info['ogg']['comments_raw'][$i]['size'] + 4;
  296. $comment_data_offset += 4;
  297. while ((strlen($comment_data) - $comment_data_offset) < $getid3->info['ogg']['comments_raw'][$i]['size']) {
  298. if (($getid3->info['ogg']['comments_raw'][$i]['size'] > $getid3->info['avdataend']) || ($getid3->info['ogg']['comments_raw'][$i]['size'] < 0)) {
  299. throw new getid3_exception('Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($getid3->info['ogg']['comments_raw'][$i]['size']).' bytes) - aborting reading comments');
  300. }
  301. $vorbis_comment_page++;
  302. $ogg_page_info = $this->ParseOggPageHeader();
  303. $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']] = $ogg_page_info;
  304. // First, save what we haven't read yet
  305. $as_yet_unused_data = substr($comment_data, $comment_data_offset);
  306. // Then take that data off the end
  307. $comment_data = substr($comment_data, 0, $comment_data_offset);
  308. // Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
  309. $comment_data .= str_repeat("\x00", 27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']);
  310. $comment_data_offset += (27 + $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_segments']);
  311. // Finally, stick the unused data back on the end
  312. $comment_data .= $as_yet_unused_data;
  313. //$comment_data .= fread($getid3->fp, $getid3->info['ogg']['pageheader'][$ogg_page_info['page_seqno']]['page_length']);
  314. if (!isset($getid3->info['ogg']['pageheader'][$vorbis_comment_page])) {
  315. $getid3->warning('undefined Vorbis Comment page "'.$vorbis_comment_page.'" at offset '.ftell($getid3->fp));
  316. break;
  317. }
  318. $readlength = getid3_xiph::OggPageSegmentLength($getid3->info['ogg']['pageheader'][$vorbis_comment_page], 1);
  319. if ($readlength <= 0) {
  320. $getid3->warning('invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.ftell($getid3->fp));
  321. break;
  322. }
  323. $comment_data .= fread($getid3->fp, $readlength);
  324. //$filebaseoffset += $ogg_page_info['header_end_offset'] - $ogg_page_info['page_start_offset'];
  325. }
  326. $comment_string = substr($comment_data, $comment_data_offset, $getid3->info['ogg']['comments_raw'][$i]['size']);
  327. $comment_data_offset += $getid3->info['ogg']['comments_raw'][$i]['size'];
  328. if (!$comment_string) {
  329. // no comment?
  330. $getid3->warning('Blank Ogg comment ['.$i.']');
  331. } elseif (strstr($comment_string, '=')) {
  332. $comment_exploded = explode('=', $comment_string, 2);
  333. $getid3->info['ogg']['comments_raw'][$i]['key'] = strtoupper($comment_exploded[0]);
  334. $getid3->info['ogg']['comments_raw'][$i]['value'] = @$comment_exploded[1];
  335. $getid3->info['ogg']['comments_raw'][$i]['data'] = base64_decode($getid3->info['ogg']['comments_raw'][$i]['value']);
  336. $getid3->info['ogg']['comments'][strtolower($getid3->info['ogg']['comments_raw'][$i]['key'])][] = $getid3->info['ogg']['comments_raw'][$i]['value'];
  337. if ($getid3->option_tags_images) {
  338. $image_chunk_check = getid3_lib_image_size::get($getid3->info['ogg']['comments_raw'][$i]['data']);
  339. $getid3->info['ogg']['comments_raw'][$i]['image_mime'] = image_type_to_mime_type($image_chunk_check[2]);
  340. }
  341. if (!@$getid3->info['ogg']['comments_raw'][$i]['image_mime'] || ($getid3->info['ogg']['comments_raw'][$i]['image_mime'] == 'application/octet-stream')) {
  342. unset($getid3->info['ogg']['comments_raw'][$i]['image_mime']);
  343. unset($getid3->info['ogg']['comments_raw'][$i]['data']);
  344. }
  345. } else {
  346. $getid3->warning('[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$comment_string);
  347. }
  348. }
  349. // Replay Gain Adjustment
  350. // http://privatewww.essex.ac.uk/~djmrob/replaygain/
  351. if (isset($getid3->info['ogg']['comments']) && is_array($getid3->info['ogg']['comments'])) {
  352. foreach ($getid3->info['ogg']['comments'] as $index => $commentvalue) {
  353. switch ($index) {
  354. case 'rg_audiophile':
  355. case 'replaygain_album_gain':
  356. $getid3->info['replay_gain']['album']['adjustment'] = (float)$commentvalue[0];
  357. unset($getid3->info['ogg']['comments'][$index]);
  358. break;
  359. case 'rg_radio':
  360. case 'replaygain_track_gain':
  361. $getid3->info['replay_gain']['track']['adjustment'] = (float)$commentvalue[0];
  362. unset($getid3->info['ogg']['comments'][$index]);
  363. break;
  364. case 'replaygain_album_peak':
  365. $getid3->info['replay_gain']['album']['peak'] = (float)$commentvalue[0];
  366. unset($getid3->info['ogg']['comments'][$index]);
  367. break;
  368. case 'rg_peak':
  369. case 'replaygain_track_peak':
  370. $getid3->info['replay_gain']['track']['peak'] = (float)$commentvalue[0];
  371. unset($getid3->info['ogg']['comments'][$index]);
  372. break;
  373. case 'replaygain_reference_loudness':
  374. $getid3->info['replay_gain']['reference_volume'] = (float)$commentvalue[0];
  375. unset($getid3->info['ogg']['comments'][$index]);
  376. break;
  377. }
  378. }
  379. }
  380. fseek($getid3->fp, $original_offset, SEEK_SET);
  381. return true;
  382. }
  383. private function ParseFLAC() {
  384. $getid3 = $this->getid3;
  385. // http://flac.sourceforge.net/format.html
  386. $getid3->info['fileformat'] = 'flac';
  387. $getid3->info['audio']['dataformat'] = 'flac';
  388. $getid3->info['audio']['bitrate_mode'] = 'vbr';
  389. $getid3->info['audio']['lossless'] = true;
  390. return $this->FLACparseMETAdata();
  391. }
  392. private function FLACparseMETAdata() {
  393. $getid3 = $this->getid3;
  394. do {
  395. $meta_data_block_offset = ftell($getid3->fp);
  396. $meta_data_block_header = fread($getid3->fp, 4);
  397. $meta_data_last_block_flag = (bool)(getid3_lib::BigEndian2Int($meta_data_block_header[0]) & 0x80);
  398. $meta_data_block_type = getid3_lib::BigEndian2Int($meta_data_block_header[0]) & 0x7F;
  399. $meta_data_block_length = getid3_lib::BigEndian2Int(substr($meta_data_block_header, 1, 3));
  400. $meta_data_block_type_text = getid3_xiph::FLACmetaBlockTypeLookup($meta_data_block_type);
  401. if ($meta_data_block_length < 0) {
  402. throw new getid3_exception('corrupt or invalid METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$meta_data_block_type.') at offset '.$meta_data_block_offset);
  403. }
  404. $getid3->info['flac'][$meta_data_block_type_text]['raw'] = array (
  405. 'offset' => $meta_data_block_offset,
  406. 'last_meta_block' => $meta_data_last_block_flag,
  407. 'block_type' => $meta_data_block_type,
  408. 'block_type_text' => $meta_data_block_type_text,
  409. 'block_length' => $meta_data_block_length,
  410. 'block_data' => @fread($getid3->fp, $meta_data_block_length)
  411. );
  412. $getid3->info['avdataoffset'] = ftell($getid3->fp);
  413. switch ($meta_data_block_type_text) {
  414. case 'STREAMINFO':
  415. if (!$this->FLACparseSTREAMINFO($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
  416. return false;
  417. }
  418. break;
  419. case 'PADDING':
  420. // ignore
  421. break;
  422. case 'APPLICATION':
  423. if (!$this->FLACparseAPPLICATION($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
  424. return false;
  425. }
  426. break;
  427. case 'SEEKTABLE':
  428. if (!$this->FLACparseSEEKTABLE($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
  429. return false;
  430. }
  431. break;
  432. case 'VORBIS_COMMENT':
  433. $old_offset = ftell($getid3->fp);
  434. fseek($getid3->fp, 0 - $meta_data_block_length, SEEK_CUR);
  435. $this->ParseVorbisCommentsFilepointer($getid3->fp, $getid3->info);
  436. fseek($getid3->fp, $old_offset, SEEK_SET);
  437. break;
  438. case 'CUESHEET':
  439. if (!$this->FLACparseCUESHEET($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
  440. return false;
  441. }
  442. break;
  443. case 'PICTURE':
  444. if (!$this->FLACparsePICTURE($getid3->info['flac'][$meta_data_block_type_text]['raw']['block_data'])) {
  445. return false;
  446. }
  447. break;
  448. default:
  449. $getid3->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$meta_data_block_type.') at offset '.$meta_data_block_offset);
  450. }
  451. } while ($meta_data_last_block_flag === false);
  452. if (isset($getid3->info['flac']['STREAMINFO'])) {
  453. $getid3->info['flac']['compressed_audio_bytes'] = $getid3->info['avdataend'] - $getid3->info['avdataoffset'];
  454. $getid3->info['flac']['uncompressed_audio_bytes'] = $getid3->info['flac']['STREAMINFO']['samples_stream'] * $getid3->info['flac']['STREAMINFO']['channels'] * ($getid3->info['flac']['STREAMINFO']['bits_per_sample'] / 8);
  455. if ($getid3->info['flac']['uncompressed_audio_bytes'] <= 0) {
  456. $getid3->warning('Corrupt FLAC file: uncompressed_audio_bytes == zero');
  457. return false;
  458. }
  459. $getid3->info['flac']['compression_ratio'] = $getid3->info['flac']['compressed_audio_bytes'] / $getid3->info['flac']['uncompressed_audio_bytes'];
  460. }
  461. // set md5_data_source - built into flac 0.5+
  462. if (isset($getid3->info['flac']['STREAMINFO']['audio_signature'])) {
  463. if ($getid3->info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) {
  464. $getid3->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)');
  465. } else {
  466. $getid3->info['md5_data_source'] = '';
  467. $md5 = $getid3->info['flac']['STREAMINFO']['audio_signature'];
  468. for ($i = 0; $i < strlen($md5); $i++) {
  469. $getid3->info['md5_data_source'] .= str_pad(dechex(ord($md5{$i})), 2, '00', STR_PAD_LEFT);
  470. }
  471. if (!preg_match('/^[0-9a-f]{32}$/', $getid3->info['md5_data_source'])) {
  472. unset($getid3->info['md5_data_source']);
  473. }
  474. }
  475. }
  476. $getid3->info['audio']['bits_per_sample'] = $getid3->info['flac']['STREAMINFO']['bits_per_sample'];
  477. if ($getid3->info['audio']['bits_per_sample'] == 8) {
  478. // special case
  479. // must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value
  480. // MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed
  481. $getid3->warning('FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file');
  482. }
  483. if (!empty($getid3->info['ogg']['vendor'])) {
  484. $getid3->info['audio']['encoder'] = $getid3->info['ogg']['vendor'];
  485. }
  486. return true;
  487. }
  488. private function FLACparseSTREAMINFO($meta_data_block_data) {
  489. $getid3 = $this->getid3;
  490. getid3_lib::ReadSequence('BigEndian2Int', $getid3->info['flac']['STREAMINFO'], $meta_data_block_data, 0,
  491. array (
  492. 'min_block_size' => 2,
  493. 'max_block_size' => 2,
  494. 'min_frame_size' => 3,
  495. 'max_frame_size' => 3
  496. )
  497. );
  498. $sample_rate_channels_sample_bits_stream_samples = getid3_lib::BigEndian2Bin(substr($meta_data_block_data, 10, 8));
  499. $getid3->info['flac']['STREAMINFO']['sample_rate'] = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 0, 20));
  500. $getid3->info['flac']['STREAMINFO']['channels'] = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 20, 3)) + 1;
  501. $getid3->info['flac']['STREAMINFO']['bits_per_sample'] = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 23, 5)) + 1;
  502. $getid3->info['flac']['STREAMINFO']['samples_stream'] = bindec(substr($sample_rate_channels_sample_bits_stream_samples, 28, 36)); // bindec() returns float in case of int overrun
  503. $getid3->info['flac']['STREAMINFO']['audio_signature'] = substr($meta_data_block_data, 18, 16);
  504. if (!empty($getid3->info['flac']['STREAMINFO']['sample_rate'])) {
  505. $getid3->info['audio']['bitrate_mode'] = 'vbr';
  506. $getid3->info['audio']['sample_rate'] = $getid3->info['flac']['STREAMINFO']['sample_rate'];
  507. $getid3->info['audio']['channels'] = $getid3->info['flac']['STREAMINFO']['channels'];
  508. $getid3->info['audio']['bits_per_sample'] = $getid3->info['flac']['STREAMINFO']['bits_per_sample'];
  509. $getid3->info['playtime_seconds'] = $getid3->info['flac']['STREAMINFO']['samples_stream'] / $getid3->info['flac']['STREAMINFO']['sample_rate'];
  510. if ($getid3->info['playtime_seconds'] > 0) {
  511. $getid3->info['audio']['bitrate'] = (($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / $getid3->info['playtime_seconds'];
  512. }
  513. } else {
  514. throw new getid3_exception('Corrupt METAdata block: STREAMINFO');
  515. }
  516. unset($getid3->info['flac']['STREAMINFO']['raw']);
  517. return true;
  518. }
  519. private function FLACparseAPPLICATION($meta_data_block_data) {
  520. $getid3 = $this->getid3;
  521. $application_id = getid3_lib::BigEndian2Int(substr($meta_data_block_data, 0, 4));
  522. $getid3->info['flac']['APPLICATION'][$application_id]['name'] = getid3_xiph::FLACapplicationIDLookup($application_id);
  523. $getid3->info['flac']['APPLICATION'][$application_id]['data'] = substr($meta_data_block_data, 4);
  524. unset($getid3->info['flac']['APPLICATION']['raw']);
  525. return true;
  526. }
  527. private function FLACparseSEEKTABLE($meta_data_block_data) {
  528. $getid3 = $this->getid3;
  529. $offset = 0;
  530. $meta_data_block_length = strlen($meta_data_block_data);
  531. while ($offset < $meta_data_block_length) {
  532. $sample_number_string = substr($meta_data_block_data, $offset, 8);
  533. $offset += 8;
  534. if ($sample_number_string == "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF") {
  535. // placeholder point
  536. @$getid3->info['flac']['SEEKTABLE']['placeholders']++;
  537. $offset += 10;
  538. } else {
  539. $sample_number = getid3_lib::BigEndian2Int($sample_number_string);
  540. $getid3->info['flac']['SEEKTABLE'][$sample_number]['offset'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8));
  541. $offset += 8;
  542. $getid3->info['flac']['SEEKTABLE'][$sample_number]['samples'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 2));
  543. $offset += 2;
  544. }
  545. }
  546. unset($getid3->info['flac']['SEEKTABLE']['raw']);
  547. return true;
  548. }
  549. private function FLACparseCUESHEET($meta_data_block_data) {
  550. $getid3 = $this->getid3;
  551. $getid3->info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($meta_data_block_data, 0, 128), "\0");
  552. $getid3->info['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, 128, 8));
  553. $getid3->info['flac']['CUESHEET']['flags']['is_cd'] = (bool)(getid3_lib::BigEndian2Int($meta_data_block_data[136]) & 0x80);
  554. $getid3->info['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int($meta_data_block_data[395]);
  555. $offset = 396;
  556. for ($track = 0; $track < $getid3->info['flac']['CUESHEET']['number_tracks']; $track++) {
  557. $track_sample_offset = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8));
  558. $offset += 8;
  559. $track_number = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});
  560. $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['sample_offset'] = $track_sample_offset;
  561. $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['isrc'] = substr($meta_data_block_data, $offset, 12);
  562. $offset += 12;
  563. $track_flags_raw = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});
  564. $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['flags']['is_audio'] = (bool)($track_flags_raw & 0x80);
  565. $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['flags']['pre_emphasis'] = (bool)($track_flags_raw & 0x40);
  566. $offset += 13; // reserved
  567. $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['index_points'] = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});
  568. for ($index = 0; $index < $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['index_points']; $index++) {
  569. $index_sample_offset = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 8));
  570. $offset += 8;
  571. $index_number = getid3_lib::BigEndian2Int($meta_data_block_data{$offset++});
  572. $getid3->info['flac']['CUESHEET']['tracks'][$track_number]['indexes'][$index_number] = $index_sample_offset;
  573. $offset += 3; // reserved
  574. }
  575. }
  576. unset($getid3->info['flac']['CUESHEET']['raw']);
  577. return true;
  578. }
  579. private function FLACparsePICTURE($meta_data_block_data) {
  580. $getid3 = $this->getid3;
  581. $picture = &$getid3->info['flac']['PICTURE'][sizeof($getid3->info['flac']['PICTURE']) - 1];
  582. $offset = 0;
  583. $picture['typeid'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
  584. $picture['type'] = $this->FLACpictureTypeLookup($picture['typeid']);
  585. $offset += 4;
  586. $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
  587. $offset += 4;
  588. $picture['mime_type'] = substr($meta_data_block_data, $offset, $length);
  589. $offset += $length;
  590. $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
  591. $offset += 4;
  592. $picture['description'] = substr($meta_data_block_data, $offset, $length);
  593. $offset += $length;
  594. $picture['width'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
  595. $offset += 4;
  596. $picture['height'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
  597. $offset += 4;
  598. $picture['color_depth'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
  599. $offset += 4;
  600. $picture['colors_indexed'] = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
  601. $offset += 4;
  602. $length = getid3_lib::BigEndian2Int(substr($meta_data_block_data, $offset, 4));
  603. $offset += 4;
  604. $picture['image_data'] = substr($meta_data_block_data, $offset, $length);
  605. $offset += $length;
  606. unset($getid3->info['flac']['PICTURE']['raw']);
  607. return true;
  608. }
  609. public static function SpeexBandModeLookup($mode) {
  610. static $lookup = array (
  611. 0 => 'narrow',
  612. 1 => 'wide',
  613. 2 => 'ultra-wide'
  614. );
  615. return (isset($lookup[$mode]) ? $lookup[$mode] : null);
  616. }
  617. public static function OggPageSegmentLength($ogg_info_array, $segment_number=1) {
  618. for ($i = 0; $i < $segment_number; $i++) {
  619. $segment_length = 0;
  620. foreach ($ogg_info_array['segment_table'] as $key => $value) {
  621. $segment_length += $value;
  622. if ($value < 255) {
  623. break;
  624. }
  625. }
  626. }
  627. return $segment_length;
  628. }
  629. public static function GetQualityFromNominalBitrate($nominal_bitrate) {
  630. // decrease precision
  631. $nominal_bitrate = $nominal_bitrate / 1000;
  632. if ($nominal_bitrate < 128) {
  633. // q-1 to q4
  634. $qval = ($nominal_bitrate - 64) / 16;
  635. } elseif ($nominal_bitrate < 256) {
  636. // q4 to q8
  637. $qval = $nominal_bitrate / 32;
  638. } elseif ($nominal_bitrate < 320) {
  639. // q8 to q9
  640. $qval = ($nominal_bitrate + 256) / 64;
  641. } else {
  642. // q9 to q10
  643. $qval = ($nominal_bitrate + 1300) / 180;
  644. }
  645. return round($qval, 1); // 5 or 4.9
  646. }
  647. public static function FLACmetaBlockTypeLookup($block_type) {
  648. static $lookup = array (
  649. 0 => 'STREAMINFO',
  650. 1 => 'PADDING',
  651. 2 => 'APPLICATION',
  652. 3 => 'SEEKTABLE',
  653. 4 => 'VORBIS_COMMENT',
  654. 5 => 'CUESHEET',
  655. 6 => 'PICTURE'
  656. );
  657. return (isset($lookup[$block_type]) ? $lookup[$block_type] : 'reserved');
  658. }
  659. public static function FLACapplicationIDLookup($application_id) {
  660. // http://flac.sourceforge.net/id.html
  661. static $lookup = array (
  662. 0x46746F6C => 'flac-tools', // 'Ftol'
  663. 0x46746F6C => 'Sound Font FLAC', // 'SFFL'
  664. 0x7065656D => 'Parseable Embedded Extensible Metadata (specification)', // 'peem'
  665. 0x786D6364 => 'xmcd'
  666. );
  667. return (isset($lookup[$application_id]) ? $lookup[$application_id] : 'reserved');
  668. }
  669. public static function FLACpictureTypeLookup($type_id) {
  670. static $lookup = array (
  671. 0 => 'Other',
  672. 1 => "32x32 pixels 'file icon' (PNG only)",
  673. 2 => 'Other file icon',
  674. 3 => 'Cover (front)',
  675. 4 => 'Cover (back)',
  676. 5 => 'Leaflet page',
  677. 6 => 'Media (e.g. label side of CD)',
  678. 7 => 'Lead artist/lead performer/soloist',
  679. 8 => 'Artist/performer',
  680. 9 => 'Conductor',
  681. 10 => 'Band/Orchestra',
  682. 11 => 'Composer',
  683. 12 => 'Lyricist/text writer',
  684. 13 => 'Recording Location',
  685. 14 => 'During recording',
  686. 15 => 'During performance',
  687. 16 => 'Movie/video screen capture',
  688. 17 => 'A bright coloured fish',
  689. 18 => 'Illustration',
  690. 19 => 'Band/artist logotype',
  691. 20 => 'Publisher/Studio logotype'
  692. );
  693. return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved');
  694. }
  695. }
  696. ?>