PageRenderTime 117ms CodeModel.GetById 28ms RepoModel.GetById 2ms app.codeStats 0ms

/common/libraries/plugin/getid3/write.id3v2.php

https://bitbucket.org/renaatdemuynck/chamilo
PHP | 2637 lines | 2159 code | 166 blank | 312 comment | 339 complexity | 2e6f9ddef7a2f0f3585b9fcbb032e6ab MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1, LGPL-3.0, GPL-3.0, MIT, GPL-2.0

Large files files are truncated, but you can click here to view the full file

  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. // write.id3v2.php //
  11. // module for writing ID3v2 tags //
  12. // dependencies: module.tag.id3v2.php //
  13. // ///
  14. /////////////////////////////////////////////////////////////////
  15. getid3_lib :: IncludeDependency(GETID3_INCLUDEPATH . 'module.tag.id3v2.php', __FILE__, true);
  16. class getid3_write_id3v2
  17. {
  18. var $filename;
  19. var $tag_data;
  20. var $paddedlength = 4096; // minimum length of ID3v2 tag in bytes
  21. var $majorversion = 3; // ID3v2 major version (2, 3 (recommended), 4)
  22. var $minorversion = 0; // ID3v2 minor version - always 0
  23. var $merge_existing_data = false; // if true, merge new data with existing tags; if false, delete old tag data and only write new tags
  24. var $id3v2_default_encodingid = 0; // default text encoding (ISO-8859-1) if not explicitly passed
  25. var $id3v2_use_unsynchronisation = false; // the specs say it should be TRUE, but most other ID3v2-aware programs are broken if unsynchronization is used, so by default don't use it.
  26. var $warnings = array(); // any non-critical errors will be stored here
  27. var $errors = array(); // any critical errors will be stored here
  28. function __construct()
  29. {
  30. return true;
  31. }
  32. function WriteID3v2()
  33. {
  34. // File MUST be writeable - CHMOD(646) at least. It's best if the
  35. // directory is also writeable, because that method is both faster and less susceptible to errors.
  36. if (is_writeable($this->filename) || (! file_exists($this->filename) && is_writeable(dirname($this->filename))))
  37. {
  38. // Initialize getID3 engine
  39. $getID3 = new getID3();
  40. $OldThisFileInfo = $getID3->analyze($this->filename);
  41. if ($OldThisFileInfo['filesize'] >= pow(2, 31))
  42. {
  43. $this->errors[] = 'Unable to write ID3v2 because file is larger than 2GB';
  44. fclose($fp_source);
  45. return false;
  46. }
  47. if ($this->merge_existing_data)
  48. {
  49. // merge with existing data
  50. if (! empty($OldThisFileInfo['id3v2']))
  51. {
  52. $this->tag_data = $this->array_join_merge($OldThisFileInfo['id3v2'], $this->tag_data);
  53. }
  54. }
  55. $this->paddedlength = max(@$OldThisFileInfo['id3v2']['headerlength'], $this->paddedlength);
  56. if ($NewID3v2Tag = $this->GenerateID3v2Tag())
  57. {
  58. if (file_exists($this->filename) && is_writeable($this->filename) && isset($OldThisFileInfo['id3v2']['headerlength']) && ($OldThisFileInfo['id3v2']['headerlength'] == strlen($NewID3v2Tag)))
  59. {
  60. // best and fastest method - insert-overwrite existing tag (padded to length of old tag if neccesary)
  61. if (file_exists($this->filename))
  62. {
  63. ob_start();
  64. if ($fp = fopen($this->filename, 'r+b'))
  65. {
  66. rewind($fp);
  67. fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag));
  68. fclose($fp);
  69. }
  70. else
  71. {
  72. $this->errors[] = 'Could not open ' . $this->filename . ' mode "r+b" - ' . strip_tags(ob_get_contents());
  73. }
  74. ob_end_clean();
  75. }
  76. else
  77. {
  78. ob_start();
  79. if ($fp = fopen($this->filename, 'wb'))
  80. {
  81. rewind($fp);
  82. fwrite($fp, $NewID3v2Tag, strlen($NewID3v2Tag));
  83. fclose($fp);
  84. }
  85. else
  86. {
  87. $this->errors[] = 'Could not open ' . $this->filename . ' mode "wb" - ' . strip_tags(ob_get_contents());
  88. }
  89. ob_end_clean();
  90. }
  91. }
  92. else
  93. {
  94. if ($tempfilename = tempnam('*', 'getID3'))
  95. {
  96. ob_start();
  97. if ($fp_source = fopen($this->filename, 'rb'))
  98. {
  99. if ($fp_temp = fopen($tempfilename, 'wb'))
  100. {
  101. fwrite($fp_temp, $NewID3v2Tag, strlen($NewID3v2Tag));
  102. rewind($fp_source);
  103. if (! empty($OldThisFileInfo['avdataoffset']))
  104. {
  105. fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET);
  106. }
  107. while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE))
  108. {
  109. fwrite($fp_temp, $buffer, strlen($buffer));
  110. }
  111. fclose($fp_temp);
  112. fclose($fp_source);
  113. copy($tempfilename, $this->filename);
  114. unlink($tempfilename);
  115. ob_end_clean();
  116. return true;
  117. }
  118. else
  119. {
  120. $this->errors[] = 'Could not open ' . $tempfilename . ' mode "wb" - ' . strip_tags(ob_get_contents());
  121. }
  122. fclose($fp_source);
  123. }
  124. else
  125. {
  126. $this->errors[] = 'Could not open ' . $this->filename . ' mode "rb" - ' . strip_tags(ob_get_contents());
  127. }
  128. ob_end_clean();
  129. }
  130. return false;
  131. }
  132. }
  133. else
  134. {
  135. $this->errors[] = '$this->GenerateID3v2Tag() failed';
  136. }
  137. if (! empty($this->errors))
  138. {
  139. return false;
  140. }
  141. return true;
  142. }
  143. else
  144. {
  145. $this->errors[] = '!is_writeable(' . $this->filename . ')';
  146. }
  147. return false;
  148. }
  149. function RemoveID3v2()
  150. {
  151. // File MUST be writeable - CHMOD(646) at least. It's best if the
  152. // directory is also writeable, because that method is both faster and less susceptible to errors.
  153. if (is_writeable(dirname($this->filename)))
  154. {
  155. // preferred method - only one copying operation, minimal chance of corrupting
  156. // original file if script is interrupted, but required directory to be writeable
  157. if ($fp_source = @fopen($this->filename, 'rb'))
  158. {
  159. // Initialize getID3 engine
  160. $getID3 = new getID3();
  161. $OldThisFileInfo = $getID3->analyze($this->filename);
  162. if ($OldThisFileInfo['filesize'] >= pow(2, 31))
  163. {
  164. $this->errors[] = 'Unable to remove ID3v2 because file is larger than 2GB';
  165. fclose($fp_source);
  166. return false;
  167. }
  168. rewind($fp_source);
  169. if ($OldThisFileInfo['avdataoffset'] !== false)
  170. {
  171. fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET);
  172. }
  173. if ($fp_temp = @fopen($this->filename . 'getid3tmp', 'w+b'))
  174. {
  175. while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE))
  176. {
  177. fwrite($fp_temp, $buffer, strlen($buffer));
  178. }
  179. fclose($fp_temp);
  180. }
  181. else
  182. {
  183. $this->errors[] = 'Could not open ' . $this->filename . 'getid3tmp mode "w+b"';
  184. }
  185. fclose($fp_source);
  186. }
  187. else
  188. {
  189. $this->errors[] = 'Could not open ' . $this->filename . ' mode "rb"';
  190. }
  191. if (file_exists($this->filename))
  192. {
  193. unlink($this->filename);
  194. }
  195. rename($this->filename . 'getid3tmp', $this->filename);
  196. }
  197. elseif (is_writable($this->filename))
  198. {
  199. // less desirable alternate method - double-copies the file, overwrites original file
  200. // and could corrupt source file if the script is interrupted or an error occurs.
  201. if ($fp_source = @fopen($this->filename, 'rb'))
  202. {
  203. // Initialize getID3 engine
  204. $getID3 = new getID3();
  205. $OldThisFileInfo = $getID3->analyze($this->filename);
  206. if ($OldThisFileInfo['filesize'] >= pow(2, 31))
  207. {
  208. $this->errors[] = 'Unable to remove ID3v2 because file is larger than 2GB';
  209. fclose($fp_source);
  210. return false;
  211. }
  212. rewind($fp_source);
  213. if ($OldThisFileInfo['avdataoffset'] !== false)
  214. {
  215. fseek($fp_source, $OldThisFileInfo['avdataoffset'], SEEK_SET);
  216. }
  217. if ($fp_temp = tmpfile())
  218. {
  219. while ($buffer = fread($fp_source, GETID3_FREAD_BUFFER_SIZE))
  220. {
  221. fwrite($fp_temp, $buffer, strlen($buffer));
  222. }
  223. fclose($fp_source);
  224. if ($fp_source = @fopen($this->filename, 'wb'))
  225. {
  226. rewind($fp_temp);
  227. while ($buffer = fread($fp_temp, GETID3_FREAD_BUFFER_SIZE))
  228. {
  229. fwrite($fp_source, $buffer, strlen($buffer));
  230. }
  231. fseek($fp_temp, - 128, SEEK_END);
  232. fclose($fp_source);
  233. }
  234. else
  235. {
  236. $this->errors[] = 'Could not open ' . $this->filename . ' mode "wb"';
  237. }
  238. fclose($fp_temp);
  239. }
  240. else
  241. {
  242. $this->errors[] = 'Could not create tmpfile()';
  243. }
  244. }
  245. else
  246. {
  247. $this->errors[] = 'Could not open ' . $this->filename . ' mode "rb"';
  248. }
  249. }
  250. else
  251. {
  252. $this->errors[] = 'Directory and file both not writeable';
  253. }
  254. if (! empty($this->errors))
  255. {
  256. return false;
  257. }
  258. return true;
  259. }
  260. function GenerateID3v2TagFlags($flags)
  261. {
  262. switch ($this->majorversion)
  263. {
  264. case 4 :
  265. // %abcd0000
  266. $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation
  267. $flag .= (@$flags['extendedheader'] ? '1' : '0'); // b - Extended header
  268. $flag .= (@$flags['experimental'] ? '1' : '0'); // c - Experimental indicator
  269. $flag .= (@$flags['footer'] ? '1' : '0'); // d - Footer present
  270. $flag .= '0000';
  271. break;
  272. case 3 :
  273. // %abc00000
  274. $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation
  275. $flag .= (@$flags['extendedheader'] ? '1' : '0'); // b - Extended header
  276. $flag .= (@$flags['experimental'] ? '1' : '0'); // c - Experimental indicator
  277. $flag .= '00000';
  278. break;
  279. case 2 :
  280. // %ab000000
  281. $flag = (@$flags['unsynchronisation'] ? '1' : '0'); // a - Unsynchronisation
  282. $flag .= (@$flags['compression'] ? '1' : '0'); // b - Compression
  283. $flag .= '000000';
  284. break;
  285. default :
  286. return false;
  287. break;
  288. }
  289. return chr(bindec($flag));
  290. }
  291. function GenerateID3v2FrameFlags($TagAlter = false, $FileAlter = false, $ReadOnly = false, $Compression = false, $Encryption = false, $GroupingIdentity = false, $Unsynchronisation = false, $DataLengthIndicator = false)
  292. {
  293. switch ($this->majorversion)
  294. {
  295. case 4 :
  296. // %0abc0000 %0h00kmnp
  297. $flag1 = '0';
  298. $flag1 .= $TagAlter ? '1' : '0'; // a - Tag alter preservation (true == discard)
  299. $flag1 .= $FileAlter ? '1' : '0'; // b - File alter preservation (true == discard)
  300. $flag1 .= $ReadOnly ? '1' : '0'; // c - Read only (true == read only)
  301. $flag1 .= '0000';
  302. $flag2 = '0';
  303. $flag2 .= $GroupingIdentity ? '1' : '0'; // h - Grouping identity (true == contains group information)
  304. $flag2 .= '00';
  305. $flag2 .= $Compression ? '1' : '0'; // k - Compression (true == compressed)
  306. $flag2 .= $Encryption ? '1' : '0'; // m - Encryption (true == encrypted)
  307. $flag2 .= $Unsynchronisation ? '1' : '0'; // n - Unsynchronisation (true == unsynchronised)
  308. $flag2 .= $DataLengthIndicator ? '1' : '0'; // p - Data length indicator (true == data length indicator added)
  309. break;
  310. case 3 :
  311. // %abc00000 %ijk00000
  312. $flag1 = $TagAlter ? '1' : '0'; // a - Tag alter preservation (true == discard)
  313. $flag1 .= $FileAlter ? '1' : '0'; // b - File alter preservation (true == discard)
  314. $flag1 .= $ReadOnly ? '1' : '0'; // c - Read only (true == read only)
  315. $flag1 .= '00000';
  316. $flag2 = $Compression ? '1' : '0'; // i - Compression (true == compressed)
  317. $flag2 .= $Encryption ? '1' : '0'; // j - Encryption (true == encrypted)
  318. $flag2 .= $GroupingIdentity ? '1' : '0'; // k - Grouping identity (true == contains group information)
  319. $flag2 .= '00000';
  320. break;
  321. default :
  322. return false;
  323. break;
  324. }
  325. return chr(bindec($flag1)) . chr(bindec($flag2));
  326. }
  327. function GenerateID3v2FrameData($frame_name, $source_data_array)
  328. {
  329. if (! getid3_id3v2 :: IsValidID3v2FrameName($frame_name, $this->majorversion))
  330. {
  331. return false;
  332. }
  333. $framedata = '';
  334. if (($this->majorversion < 3) || ($this->majorversion > 4))
  335. {
  336. $this->errors[] = 'Only ID3v2.3 and ID3v2.4 are supported in GenerateID3v2FrameData()';
  337. }
  338. else
  339. { // $this->majorversion 3 or 4
  340. switch ($frame_name)
  341. {
  342. case 'UFID' :
  343. // 4.1 UFID Unique file identifier
  344. // Owner identifier <text string> $00
  345. // Identifier <up to 64 bytes binary data>
  346. if (strlen($source_data_array['data']) > 64)
  347. {
  348. $this->errors[] = 'Identifier not allowed to be longer than 64 bytes in ' . $frame_name . ' (supplied data was ' . strlen($source_data_array['data']) . ' bytes long)';
  349. }
  350. else
  351. {
  352. $framedata .= str_replace("\x00", '', $source_data_array['ownerid']) . "\x00";
  353. $framedata .= substr($source_data_array['data'], 0, 64); // max 64 bytes - truncate anything longer
  354. }
  355. break;
  356. case 'TXXX' :
  357. // 4.2.2 TXXX User defined text information frame
  358. // Text encoding $xx
  359. // Description <text string according to encoding> $00 (00)
  360. // Value <text string according to encoding>
  361. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  362. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion))
  363. {
  364. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  365. }
  366. else
  367. {
  368. $framedata .= chr($source_data_array['encodingid']);
  369. $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  370. $framedata .= $source_data_array['data'];
  371. }
  372. break;
  373. case 'WXXX' :
  374. // 4.3.2 WXXX User defined URL link frame
  375. // Text encoding $xx
  376. // Description <text string according to encoding> $00 (00)
  377. // URL <text string>
  378. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  379. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion))
  380. {
  381. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  382. }
  383. elseif (! isset($source_data_array['data']) || ! $this->IsValidURL($source_data_array['data'], false, false))
  384. {
  385. //$this->errors[] = 'Invalid URL in '.$frame_name.' ('.$source_data_array['data'].')';
  386. // probably should be an error, need to rewrite IsValidURL() to handle other encodings
  387. $this->warnings[] = 'Invalid URL in ' . $frame_name . ' (' . $source_data_array['data'] . ')';
  388. }
  389. else
  390. {
  391. $framedata .= chr($source_data_array['encodingid']);
  392. $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  393. $framedata .= $source_data_array['data'];
  394. }
  395. break;
  396. case 'IPLS' :
  397. // 4.4 IPLS Involved people list (ID3v2.3 only)
  398. // Text encoding $xx
  399. // People list strings <textstrings>
  400. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  401. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid'], $this->majorversion))
  402. {
  403. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  404. }
  405. else
  406. {
  407. $framedata .= chr($source_data_array['encodingid']);
  408. $framedata .= $source_data_array['data'];
  409. }
  410. break;
  411. case 'MCDI' :
  412. // 4.4 MCDI Music CD identifier
  413. // CD TOC <binary data>
  414. $framedata .= $source_data_array['data'];
  415. break;
  416. case 'ETCO' :
  417. // 4.5 ETCO Event timing codes
  418. // Time stamp format $xx
  419. // Where time stamp format is:
  420. // $01 (32-bit value) MPEG frames from beginning of file
  421. // $02 (32-bit value) milliseconds from beginning of file
  422. // Followed by a list of key events in the following format:
  423. // Type of event $xx
  424. // Time stamp $xx (xx ...)
  425. // The 'Time stamp' is set to zero if directly at the beginning of the sound
  426. // or after the previous event. All events MUST be sorted in chronological order.
  427. if (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1))
  428. {
  429. $this->errors[] = 'Invalid Time Stamp Format byte in ' . $frame_name . ' (' . $source_data_array['timestampformat'] . ')';
  430. }
  431. else
  432. {
  433. $framedata .= chr($source_data_array['timestampformat']);
  434. foreach ($source_data_array as $key => $val)
  435. {
  436. if (! $this->ID3v2IsValidETCOevent($val['typeid']))
  437. {
  438. $this->errors[] = 'Invalid Event Type byte in ' . $frame_name . ' (' . $val['typeid'] . ')';
  439. }
  440. elseif (($key != 'timestampformat') && ($key != 'flags'))
  441. {
  442. if (($val['timestamp'] > 0) && ($previousETCOtimestamp >= $val['timestamp']))
  443. {
  444. // The 'Time stamp' is set to zero if directly at the beginning of the sound
  445. // or after the previous event. All events MUST be sorted in chronological order.
  446. $this->errors[] = 'Out-of-order timestamp in ' . $frame_name . ' (' . $val['timestamp'] . ') for Event Type (' . $val['typeid'] . ')';
  447. }
  448. else
  449. {
  450. $framedata .= chr($val['typeid']);
  451. $framedata .= getid3_lib :: BigEndian2String($val['timestamp'], 4, false);
  452. }
  453. }
  454. }
  455. }
  456. break;
  457. case 'MLLT' :
  458. // 4.6 MLLT MPEG location lookup table
  459. // MPEG frames between reference $xx xx
  460. // Bytes between reference $xx xx xx
  461. // Milliseconds between reference $xx xx xx
  462. // Bits for bytes deviation $xx
  463. // Bits for milliseconds dev. $xx
  464. // Then for every reference the following data is included;
  465. // Deviation in bytes %xxx....
  466. // Deviation in milliseconds %xxx....
  467. if (($source_data_array['framesbetweenreferences'] > 0) && ($source_data_array['framesbetweenreferences'] <= 65535))
  468. {
  469. $framedata .= getid3_lib :: BigEndian2String($source_data_array['framesbetweenreferences'], 2, false);
  470. }
  471. else
  472. {
  473. $this->errors[] = 'Invalid MPEG Frames Between References in ' . $frame_name . ' (' . $source_data_array['framesbetweenreferences'] . ')';
  474. }
  475. if (($source_data_array['bytesbetweenreferences'] > 0) && ($source_data_array['bytesbetweenreferences'] <= 16777215))
  476. {
  477. $framedata .= getid3_lib :: BigEndian2String($source_data_array['bytesbetweenreferences'], 3, false);
  478. }
  479. else
  480. {
  481. $this->errors[] = 'Invalid bytes Between References in ' . $frame_name . ' (' . $source_data_array['bytesbetweenreferences'] . ')';
  482. }
  483. if (($source_data_array['msbetweenreferences'] > 0) && ($source_data_array['msbetweenreferences'] <= 16777215))
  484. {
  485. $framedata .= getid3_lib :: BigEndian2String($source_data_array['msbetweenreferences'], 3, false);
  486. }
  487. else
  488. {
  489. $this->errors[] = 'Invalid Milliseconds Between References in ' . $frame_name . ' (' . $source_data_array['msbetweenreferences'] . ')';
  490. }
  491. if (! $this->IsWithinBitRange($source_data_array['bitsforbytesdeviation'], 8, false))
  492. {
  493. if (($source_data_array['bitsforbytesdeviation'] % 4) == 0)
  494. {
  495. $framedata .= chr($source_data_array['bitsforbytesdeviation']);
  496. }
  497. else
  498. {
  499. $this->errors[] = 'Bits For Bytes Deviation in ' . $frame_name . ' (' . $source_data_array['bitsforbytesdeviation'] . ') must be a multiple of 4.';
  500. }
  501. }
  502. else
  503. {
  504. $this->errors[] = 'Invalid Bits For Bytes Deviation in ' . $frame_name . ' (' . $source_data_array['bitsforbytesdeviation'] . ')';
  505. }
  506. if (! $this->IsWithinBitRange($source_data_array['bitsformsdeviation'], 8, false))
  507. {
  508. if (($source_data_array['bitsformsdeviation'] % 4) == 0)
  509. {
  510. $framedata .= chr($source_data_array['bitsformsdeviation']);
  511. }
  512. else
  513. {
  514. $this->errors[] = 'Bits For Milliseconds Deviation in ' . $frame_name . ' (' . $source_data_array['bitsforbytesdeviation'] . ') must be a multiple of 4.';
  515. }
  516. }
  517. else
  518. {
  519. $this->errors[] = 'Invalid Bits For Milliseconds Deviation in ' . $frame_name . ' (' . $source_data_array['bitsformsdeviation'] . ')';
  520. }
  521. foreach ($source_data_array as $key => $val)
  522. {
  523. if (($key != 'framesbetweenreferences') && ($key != 'bytesbetweenreferences') && ($key != 'msbetweenreferences') && ($key != 'bitsforbytesdeviation') && ($key != 'bitsformsdeviation') && ($key != 'flags'))
  524. {
  525. $unwrittenbitstream .= str_pad(getid3_lib :: Dec2Bin($val['bytedeviation']), $source_data_array['bitsforbytesdeviation'], '0', STR_PAD_LEFT);
  526. $unwrittenbitstream .= str_pad(getid3_lib :: Dec2Bin($val['msdeviation']), $source_data_array['bitsformsdeviation'], '0', STR_PAD_LEFT);
  527. }
  528. }
  529. for($i = 0; $i < strlen($unwrittenbitstream); $i += 8)
  530. {
  531. $highnibble = bindec(substr($unwrittenbitstream, $i, 4)) << 4;
  532. $lownibble = bindec(substr($unwrittenbitstream, $i + 4, 4));
  533. $framedata .= chr($highnibble & $lownibble);
  534. }
  535. break;
  536. case 'SYTC' :
  537. // 4.7 SYTC Synchronised tempo codes
  538. // Time stamp format $xx
  539. // Tempo data <binary data>
  540. // Where time stamp format is:
  541. // $01 (32-bit value) MPEG frames from beginning of file
  542. // $02 (32-bit value) milliseconds from beginning of file
  543. if (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1))
  544. {
  545. $this->errors[] = 'Invalid Time Stamp Format byte in ' . $frame_name . ' (' . $source_data_array['timestampformat'] . ')';
  546. }
  547. else
  548. {
  549. $framedata .= chr($source_data_array['timestampformat']);
  550. foreach ($source_data_array as $key => $val)
  551. {
  552. if (! $this->ID3v2IsValidETCOevent($val['typeid']))
  553. {
  554. $this->errors[] = 'Invalid Event Type byte in ' . $frame_name . ' (' . $val['typeid'] . ')';
  555. }
  556. elseif (($key != 'timestampformat') && ($key != 'flags'))
  557. {
  558. if (($val['tempo'] < 0) || ($val['tempo'] > 510))
  559. {
  560. $this->errors[] = 'Invalid Tempo (max = 510) in ' . $frame_name . ' (' . $val['tempo'] . ') at timestamp (' . $val['timestamp'] . ')';
  561. }
  562. else
  563. {
  564. if ($val['tempo'] > 255)
  565. {
  566. $framedata .= chr(255);
  567. $val['tempo'] -= 255;
  568. }
  569. $framedata .= chr($val['tempo']);
  570. $framedata .= getid3_lib :: BigEndian2String($val['timestamp'], 4, false);
  571. }
  572. }
  573. }
  574. }
  575. break;
  576. case 'USLT' :
  577. // 4.8 USLT Unsynchronised lyric/text transcription
  578. // Text encoding $xx
  579. // Language $xx xx xx
  580. // Content descriptor <text string according to encoding> $00 (00)
  581. // Lyrics/text <full text string according to encoding>
  582. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  583. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  584. {
  585. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  586. }
  587. elseif (getid3_id3v2 :: LanguageLookup($source_data_array['language'], true) == '')
  588. {
  589. $this->errors[] = 'Invalid Language in ' . $frame_name . ' (' . $source_data_array['language'] . ')';
  590. }
  591. else
  592. {
  593. $framedata .= chr($source_data_array['encodingid']);
  594. $framedata .= strtolower($source_data_array['language']);
  595. $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  596. $framedata .= $source_data_array['data'];
  597. }
  598. break;
  599. case 'SYLT' :
  600. // 4.9 SYLT Synchronised lyric/text
  601. // Text encoding $xx
  602. // Language $xx xx xx
  603. // Time stamp format $xx
  604. // $01 (32-bit value) MPEG frames from beginning of file
  605. // $02 (32-bit value) milliseconds from beginning of file
  606. // Content type $xx
  607. // Content descriptor <text string according to encoding> $00 (00)
  608. // Terminated text to be synced (typically a syllable)
  609. // Sync identifier (terminator to above string) $00 (00)
  610. // Time stamp $xx (xx ...)
  611. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  612. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  613. {
  614. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  615. }
  616. elseif (getid3_id3v2 :: LanguageLookup($source_data_array['language'], true) == '')
  617. {
  618. $this->errors[] = 'Invalid Language in ' . $frame_name . ' (' . $source_data_array['language'] . ')';
  619. }
  620. elseif (($source_data_array['timestampformat'] > 2) || ($source_data_array['timestampformat'] < 1))
  621. {
  622. $this->errors[] = 'Invalid Time Stamp Format byte in ' . $frame_name . ' (' . $source_data_array['timestampformat'] . ')';
  623. }
  624. elseif (! $this->ID3v2IsValidSYLTtype($source_data_array['contenttypeid']))
  625. {
  626. $this->errors[] = 'Invalid Content Type byte in ' . $frame_name . ' (' . $source_data_array['contenttypeid'] . ')';
  627. }
  628. elseif (! is_array($source_data_array['data']))
  629. {
  630. $this->errors[] = 'Invalid Lyric/Timestamp data in ' . $frame_name . ' (must be an array)';
  631. }
  632. else
  633. {
  634. $framedata .= chr($source_data_array['encodingid']);
  635. $framedata .= strtolower($source_data_array['language']);
  636. $framedata .= chr($source_data_array['timestampformat']);
  637. $framedata .= chr($source_data_array['contenttypeid']);
  638. $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  639. ksort($source_data_array['data']);
  640. foreach ($source_data_array['data'] as $key => $val)
  641. {
  642. $framedata .= $val['data'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  643. $framedata .= getid3_lib :: BigEndian2String($val['timestamp'], 4, false);
  644. }
  645. }
  646. break;
  647. case 'COMM' :
  648. // 4.10 COMM Comments
  649. // Text encoding $xx
  650. // Language $xx xx xx
  651. // Short content descrip. <text string according to encoding> $00 (00)
  652. // The actual text <full text string according to encoding>
  653. $source_data_array['encodingid'] = (isset($source_data_array['encodingid']) ? $source_data_array['encodingid'] : $this->id3v2_default_encodingid);
  654. if (! $this->ID3v2IsValidTextEncoding($source_data_array['encodingid']))
  655. {
  656. $this->errors[] = 'Invalid Text Encoding in ' . $frame_name . ' (' . $source_data_array['encodingid'] . ') for ID3v2.' . $this->majorversion;
  657. }
  658. elseif (getid3_id3v2 :: LanguageLookup($source_data_array['language'], true) == '')
  659. {
  660. $this->errors[] = 'Invalid Language in ' . $frame_name . ' (' . $source_data_array['language'] . ')';
  661. }
  662. else
  663. {
  664. $framedata .= chr($source_data_array['encodingid']);
  665. $framedata .= strtolower($source_data_array['language']);
  666. $framedata .= $source_data_array['description'] . getid3_id3v2 :: TextEncodingTerminatorLookup($source_data_array['encodingid']);
  667. $framedata .= $source_data_array['data'];
  668. }
  669. break;
  670. case 'RVA2' :
  671. // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
  672. // Identification <text string> $00
  673. // The 'identification' string is used to identify the situation and/or
  674. // device where this adjustment should apply. The following is then
  675. // repeated for every channel:
  676. // Type of channel $xx
  677. // Volume adjustment $xx xx
  678. // Bits representing peak $xx
  679. // Peak volume $xx (xx ...)
  680. $framedata .= str_replace("\x00", '', $source_data_array['description']) . "\x00";
  681. foreach ($source_data_array as $key => $val)
  682. {
  683. if ($key != 'description')
  684. {
  685. $framedata .= chr($val['channeltypeid']);
  686. $framedata .= getid3_lib :: BigEndian2String($val['volumeadjust'], 2, false, true); // signed 16-bit
  687. if (! $this->IsWithinBitRange($source_data_array['bitspeakvolume'], 8, false))
  688. {
  689. $framedata .= chr($val['bitspeakvolume']);
  690. if ($val['bitspeakvolume'] > 0)
  691. {
  692. $framedata .= getid3_lib :: BigEndian2String($val['peakvolume'], ceil($val['bitspeakvolume'] / 8), false, false);
  693. }
  694. }
  695. else
  696. {
  697. $this->errors[] = 'Invalid Bits Representing Peak Volume in ' . $frame_name . ' (' . $val['bitspeakvolume'] . ') (range = 0 to 255)';
  698. }
  699. }
  700. }
  701. break;
  702. case 'RVAD' :
  703. // 4.12 RVAD Relative volume adjustment (ID3v2.3 only)
  704. // Increment/decrement %00fedcba
  705. // Bits used for volume descr. $xx
  706. // Relative volume change, right $xx xx (xx ...) // a
  707. // Relative volume change, left $xx xx (xx ...) // b
  708. // Peak volume right $xx xx (xx ...)
  709. // Peak volume left $xx xx (xx ...)
  710. // Relative volume change, right back $xx xx (xx ...) // c
  711. // Relative volume change, left back $xx xx (xx ...) // d
  712. // Peak volume right back $xx xx (xx ...)
  713. // Peak volume left back $xx xx (xx ...)
  714. // Relative volume change, center $xx xx (xx ...) // e
  715. // Peak volume center $xx xx (xx ...)
  716. // Relative volume change, bass $xx xx (xx ...) // f
  717. // Peak volume bass $xx xx (xx ...)
  718. if (! $this->IsWithinBitRange($source_data_array['bitsvolume'], 8, false))
  719. {
  720. $this->errors[] = 'Invalid Bits For Volume Description byte in ' . $frame_name . ' (' . $source_data_array['bitsvolume'] . ') (range = 1 to 255)';
  721. }
  722. else
  723. {
  724. $incdecflag .= '00';
  725. $incdecflag .= $source_data_array['incdec']['right'] ? '1' : '0'; // a - Relative volume change, right
  726. $incdecflag .= $source_data_array['incdec']['left'] ? '1' : '0'; // b - Relative volume change, left
  727. $incdecflag .= $source_data_array['incdec']['rightrear'] ? '1' : '0'; // c - Relative volume change, right back
  728. $incdecflag .= $source_data_array['incdec']['leftrear'] ? '1' : '0'; // d - Relative volume change, left back
  729. $incdecflag .= $source_data_array['incdec']['center'] ? '1' : '0'; // e - Relative volume change, center
  730. $incdecflag .= $source_data_array['incdec']['bass'] ? '1' : '0'; // f - Relative volume change, bass
  731. $framedata .= chr(bindec($incdecflag));
  732. $framedata .= chr($source_data_array['bitsvolume']);
  733. $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['right'], ceil($source_data_array['bitsvolume'] / 8), false);
  734. $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['left'], ceil($source_data_array['bitsvolume'] / 8), false);
  735. $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['right'], ceil($source_data_array['bitsvolume'] / 8), false);
  736. $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['left'], ceil($source_data_array['bitsvolume'] / 8), false);
  737. if ($source_data_array['volumechange']['rightrear'] || $source_data_array['volumechange']['leftrear'] || $source_data_array['peakvolume']['rightrear'] || $source_data_array['peakvolume']['leftrear'] || $source_data_array['volumechange']['center'] || $source_data_array['peakvolume']['center'] || $source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass'])
  738. {
  739. $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['rightrear'], ceil($source_data_array['bitsvolume'] / 8), false);
  740. $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['leftrear'], ceil($source_data_array['bitsvolume'] / 8), false);
  741. $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['rightrear'], ceil($source_data_array['bitsvolume'] / 8), false);
  742. $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['leftrear'], ceil($source_data_array['bitsvolume'] / 8), false);
  743. }
  744. if ($source_data_array['volumechange']['center'] || $source_data_array['peakvolume']['center'] || $source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass'])
  745. {
  746. $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['center'], ceil($source_data_array['bitsvolume'] / 8), false);
  747. $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['center'], ceil($source_data_array['bitsvolume'] / 8), false);
  748. }
  749. if ($source_data_array['volumechange']['bass'] || $source_data_array['peakvolume']['bass'])
  750. {
  751. $framedata .= getid3_lib :: BigEndian2String($source_data_array['volumechange']['bass'], ceil($source_data_array['bitsvolume'] / 8), false);
  752. $framedata .= getid3_lib :: BigEndian2String($source_data_array['peakvolume']['bass'], ceil($source_data_array['bitsvolume'] / 8), false);
  753. }
  754. }
  755. break;
  756. case 'EQU2' :
  757. // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only)
  758. // Interpolation method $xx
  759. // $00 Band
  760. // $01 Linear
  761. // Identification <text string> $00
  762. // The following is then repeated for every adjustment point
  763. // Frequency $xx xx
  764. // Volume adjustment $xx xx
  765. if (($source_data_array['interpolationmethod'] < 0) || ($source_data_array['interpolationmethod'] > 1))
  766. {
  767. $this->errors[] = 'Invalid Interpolation Method byte in ' . $frame_name . ' (' . $source_data_array['interpolationmethod'] . ') (valid = 0 or 1)';
  768. }
  769. else
  770. {
  771. $framedata .= chr($source_data_array['interpolationmethod']);
  772. $framedata .= str_replace("\x00", '', $source_data_array['description']) . "\x00";
  773. foreach ($source_data_array['data'] as $key => $val)
  774. {
  775. $framedata .= getid3_lib :: BigEndian2String(intval(round($key * 2)), 2, false);
  776. $framedata .= getid3_lib :: BigEndian2String($val, 2, false, true); // signed 16-bit
  777. }
  778. }
  779. break;
  780. case 'EQUA' :
  781. // 4.12 EQUA Equalisation (ID3v2.3 only)
  782. // Adjustment bits $xx
  783. // This is followed by 2 bytes + ('adjustment bits' rounded up to the
  784. // nearest byte) for every equalisation band in the following format,
  785. // giving a frequency range of 0 - 32767Hz:
  786. // Increment/decrement %x (MSB of the Frequency)
  787. // Frequency (lower 15 bits)
  788. // Adjustment $xx (xx ...)
  789. if (! $this->IsWithinBitRange($source_data_array['bitsvolume'], 8, false))
  790. {
  791. $this->errors[] = 'Invalid Adjustment Bits byte in ' . $frame_name . ' (' . $source_data_array['bitsvolume'] . ') (range = 1 to 255)';
  792. }
  793. else
  794. {
  795. $framedata .= chr($source_data_array['adjustmentbits']);
  796. foreach ($source_data_array as $key => $val)
  797. {
  798. if ($key != 'bitsvolume')
  799. {
  800. if (($key > 32767) || ($key < 0))
  801. {
  802. $this->errors[] = 'Invalid Frequency in ' . $frame_name . ' (' . $key . ') (range = 0 to 32767)';
  803. }
  804. else
  805. {
  806. if ($val >= 0)
  807. {
  808. // put MSB of frequency to 1 if increment, 0 if decrement
  809. $key |= 0x8000;
  810. }
  811. $framedata .= getid3_lib :: BigEndian2String($key, 2, false);
  812. $framedata .= getid3_lib :: BigEndian2String($val, ceil($source_data_array['adjustmentbits'] / 8), false);
  813. }
  814. }
  815. }
  816. }
  817. break;
  818. case 'RVRB' :
  819. // 4.13 RVRB Reverb
  820. // Reverb left (ms) $xx xx
  821. // Reverb right (ms) $xx xx
  822. // Reverb bounces, lef…

Large files files are truncated, but you can click here to view the full file